From 2dbe14346cb671fa07b1df6d325ac8346cf960e9 Mon Sep 17 00:00:00 2001 From: actions Date: Fri, 3 Feb 2023 16:52:17 +0000 Subject: [PATCH 01/16] Add deno release files --- .github/workflows/advisory-cron.yaml | 17 - .github/workflows/ci.yaml | 177 --- .github/workflows/docs.yaml | 52 - .github/workflows/release.yaml | 214 ---- deno_wasm_dist/LICENSE | 10 + deno_wasm_dist/README.md | 469 +++++++ deno_wasm_dist/automerge_wasm.js | 1663 +++++++++++++++++++++++++ deno_wasm_dist/automerge_wasm_bg.wasm | Bin 0 -> 1305595 bytes deno_wasm_dist/index.d.ts | 238 ++++ 9 files changed, 2380 insertions(+), 460 deletions(-) delete mode 100644 .github/workflows/advisory-cron.yaml delete mode 100644 .github/workflows/ci.yaml delete mode 100644 .github/workflows/docs.yaml delete mode 100644 .github/workflows/release.yaml create mode 100644 deno_wasm_dist/LICENSE create mode 100644 deno_wasm_dist/README.md create mode 100644 deno_wasm_dist/automerge_wasm.js create mode 100644 deno_wasm_dist/automerge_wasm_bg.wasm create mode 100644 deno_wasm_dist/index.d.ts diff --git a/.github/workflows/advisory-cron.yaml b/.github/workflows/advisory-cron.yaml deleted file mode 100644 index 31bac5a3..00000000 --- a/.github/workflows/advisory-cron.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: Advisories -on: - schedule: - - cron: '0 18 * * *' -jobs: - cargo-deny: - runs-on: ubuntu-latest - strategy: - matrix: - checks: - - advisories - - bans licenses sources - steps: - - uses: actions/checkout@v2 - - uses: EmbarkStudios/cargo-deny-action@v1 - with: - command: check ${{ matrix.checks }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml deleted file mode 100644 index c2d469d5..00000000 --- a/.github/workflows/ci.yaml +++ /dev/null @@ -1,177 +0,0 @@ -name: CI -on: - push: - branches: - - main - pull_request: - branches: - - main -jobs: - fmt: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: 1.66.0 - default: true - components: rustfmt - - uses: Swatinem/rust-cache@v1 - - run: ./scripts/ci/fmt - shell: bash - - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: 1.66.0 - default: true - components: clippy - - uses: Swatinem/rust-cache@v1 - - run: ./scripts/ci/lint - shell: bash - - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: 1.66.0 - default: true - - uses: Swatinem/rust-cache@v1 - - name: Build rust docs - run: ./scripts/ci/rust-docs - shell: bash - - name: Install doxygen - run: sudo apt-get install -y doxygen - shell: bash - - cargo-deny: - runs-on: ubuntu-latest - strategy: - matrix: - checks: - - advisories - - bans licenses sources - continue-on-error: ${{ matrix.checks == 'advisories' }} - steps: - - uses: actions/checkout@v2 - - uses: EmbarkStudios/cargo-deny-action@v1 - with: - arguments: '--manifest-path ./rust/Cargo.toml' - command: check ${{ matrix.checks }} - - wasm_tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install wasm-bindgen-cli - run: cargo install wasm-bindgen-cli wasm-opt - - name: Install wasm32 target - run: rustup target add wasm32-unknown-unknown - - name: run tests - run: ./scripts/ci/wasm_tests - deno_tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x - - name: Install wasm-bindgen-cli - run: cargo install wasm-bindgen-cli wasm-opt - - name: Install wasm32 target - run: rustup target add wasm32-unknown-unknown - - name: run tests - run: ./scripts/ci/deno_tests - - js_fmt: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: install - run: yarn global add prettier - - name: format - run: prettier -c javascript/.prettierrc javascript - - js_tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install wasm-bindgen-cli - run: cargo install wasm-bindgen-cli wasm-opt - - name: Install wasm32 target - run: rustup target add wasm32-unknown-unknown - - name: run tests - run: ./scripts/ci/js_tests - - cmake_build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: 1.66.0 - default: true - - uses: Swatinem/rust-cache@v1 - - name: Install CMocka - run: sudo apt-get install -y libcmocka-dev - - name: Install/update CMake - uses: jwlawson/actions-setup-cmake@v1.12 - with: - cmake-version: latest - - name: Build and test C bindings - run: ./scripts/ci/cmake-build Release Static - shell: bash - - linux: - runs-on: ubuntu-latest - strategy: - matrix: - toolchain: - - 1.66.0 - - nightly - continue-on-error: ${{ matrix.toolchain == 'nightly' }} - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.toolchain }} - default: true - - uses: Swatinem/rust-cache@v1 - - run: ./scripts/ci/build-test - shell: bash - - macos: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: 1.66.0 - default: true - - uses: Swatinem/rust-cache@v1 - - run: ./scripts/ci/build-test - shell: bash - - windows: - runs-on: windows-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: 1.66.0 - default: true - - uses: Swatinem/rust-cache@v1 - - run: ./scripts/ci/build-test - shell: bash diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml deleted file mode 100644 index b501d526..00000000 --- a/.github/workflows/docs.yaml +++ /dev/null @@ -1,52 +0,0 @@ -on: - push: - branches: - - main - -name: Documentation - -jobs: - deploy-docs: - concurrency: deploy-docs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - - name: Cache - uses: Swatinem/rust-cache@v1 - - - name: Clean docs dir - run: rm -rf docs - shell: bash - - - name: Clean Rust docs dir - uses: actions-rs/cargo@v1 - with: - command: clean - args: --manifest-path ./rust/Cargo.toml --doc - - - name: Build Rust docs - uses: actions-rs/cargo@v1 - with: - command: doc - args: --manifest-path ./rust/Cargo.toml --workspace --all-features --no-deps - - - name: Move Rust docs - run: mkdir -p docs && mv rust/target/doc/* docs/. - shell: bash - - - name: Configure root page - run: echo '' > docs/index.html - - - name: Deploy docs - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index 762671ff..00000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,214 +0,0 @@ -name: Release -on: - push: - branches: - - main - -jobs: - check_if_wasm_version_upgraded: - name: Check if WASM version has been upgraded - runs-on: ubuntu-latest - outputs: - wasm_version: ${{ steps.version-updated.outputs.current-package-version }} - wasm_has_updated: ${{ steps.version-updated.outputs.has-updated }} - steps: - - uses: JiPaix/package-json-updated-action@v1.0.5 - id: version-updated - with: - path: rust/automerge-wasm/package.json - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - publish-wasm: - name: Publish WASM package - runs-on: ubuntu-latest - needs: - - check_if_wasm_version_upgraded - # We create release only if the version in the package.json has been upgraded - if: needs.check_if_wasm_version_upgraded.outputs.wasm_has_updated == 'true' - steps: - - uses: actions/setup-node@v3 - with: - node-version: '16.x' - registry-url: 'https://registry.npmjs.org' - - uses: denoland/setup-deno@v1 - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ github.ref }} - - name: Get rid of local github workflows - run: rm -r .github/workflows - - name: Remove tmp_branch if it exists - run: git push origin :tmp_branch || true - - run: git checkout -b tmp_branch - - name: Install wasm-bindgen-cli - run: cargo install wasm-bindgen-cli wasm-opt - - name: Install wasm32 target - run: rustup target add wasm32-unknown-unknown - - name: run wasm js tests - id: wasm_js_tests - run: ./scripts/ci/wasm_tests - - name: run wasm deno tests - id: wasm_deno_tests - run: ./scripts/ci/deno_tests - - name: build release - id: build_release - run: | - npm --prefix $GITHUB_WORKSPACE/rust/automerge-wasm run release - - name: Collate deno release files - if: steps.wasm_js_tests.outcome == 'success' && steps.wasm_deno_tests.outcome == 'success' - run: | - mkdir $GITHUB_WORKSPACE/deno_wasm_dist - cp $GITHUB_WORKSPACE/rust/automerge-wasm/deno/* $GITHUB_WORKSPACE/deno_wasm_dist - cp $GITHUB_WORKSPACE/rust/automerge-wasm/index.d.ts $GITHUB_WORKSPACE/deno_wasm_dist - cp $GITHUB_WORKSPACE/rust/automerge-wasm/README.md $GITHUB_WORKSPACE/deno_wasm_dist - cp $GITHUB_WORKSPACE/rust/automerge-wasm/LICENSE $GITHUB_WORKSPACE/deno_wasm_dist - sed -i '1i /// ' $GITHUB_WORKSPACE/deno_wasm_dist/automerge_wasm.js - - name: Create npm release - if: steps.wasm_js_tests.outcome == 'success' && steps.wasm_deno_tests.outcome == 'success' - run: | - if [ "$(npm --prefix $GITHUB_WORKSPACE/rust/automerge-wasm show . version)" = "$VERSION" ]; then - echo "This version is already published" - exit 0 - fi - EXTRA_ARGS="--access public" - if [[ $VERSION == *"alpha."* ]] || [[ $VERSION == *"beta."* ]] || [[ $VERSION == *"rc."* ]]; then - echo "Is pre-release version" - EXTRA_ARGS="$EXTRA_ARGS --tag next" - fi - if [ "$NODE_AUTH_TOKEN" = "" ]; then - echo "Can't publish on NPM, You need a NPM_TOKEN secret." - false - fi - npm publish $GITHUB_WORKSPACE/rust/automerge-wasm $EXTRA_ARGS - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - VERSION: ${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }} - - name: Commit wasm deno release files - run: | - git config --global user.name "actions" - git config --global user.email actions@github.com - git add $GITHUB_WORKSPACE/deno_wasm_dist - git commit -am "Add deno release files" - git push origin tmp_branch - - name: Tag wasm release - if: steps.wasm_js_tests.outcome == 'success' && steps.wasm_deno_tests.outcome == 'success' - uses: softprops/action-gh-release@v1 - with: - name: Automerge Wasm v${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }} - tag_name: js/automerge-wasm-${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }} - target_commitish: tmp_branch - generate_release_notes: false - draft: false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Remove tmp_branch - run: git push origin :tmp_branch - check_if_js_version_upgraded: - name: Check if JS version has been upgraded - runs-on: ubuntu-latest - outputs: - js_version: ${{ steps.version-updated.outputs.current-package-version }} - js_has_updated: ${{ steps.version-updated.outputs.has-updated }} - steps: - - uses: JiPaix/package-json-updated-action@v1.0.5 - id: version-updated - with: - path: javascript/package.json - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - publish-js: - name: Publish JS package - runs-on: ubuntu-latest - needs: - - check_if_js_version_upgraded - - check_if_wasm_version_upgraded - - publish-wasm - # We create release only if the version in the package.json has been upgraded and after the WASM release - if: | - (always() && ! cancelled()) && - (needs.publish-wasm.result == 'success' || needs.publish-wasm.result == 'skipped') && - needs.check_if_js_version_upgraded.outputs.js_has_updated == 'true' - steps: - - uses: actions/setup-node@v3 - with: - node-version: '16.x' - registry-url: 'https://registry.npmjs.org' - - uses: denoland/setup-deno@v1 - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ github.ref }} - - name: Get rid of local github workflows - run: rm -r .github/workflows - - name: Remove js_tmp_branch if it exists - run: git push origin :js_tmp_branch || true - - run: git checkout -b js_tmp_branch - - name: check js formatting - run: | - yarn global add prettier - prettier -c javascript/.prettierrc javascript - - name: run js tests - id: js_tests - run: | - cargo install wasm-bindgen-cli wasm-opt - rustup target add wasm32-unknown-unknown - ./scripts/ci/js_tests - - name: build js release - id: build_release - run: | - npm --prefix $GITHUB_WORKSPACE/javascript run build - - name: build js deno release - id: build_deno_release - run: | - VERSION=$WASM_VERSION npm --prefix $GITHUB_WORKSPACE/javascript run deno:build - env: - WASM_VERSION: ${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }} - - name: run deno tests - id: deno_tests - run: | - npm --prefix $GITHUB_WORKSPACE/javascript run deno:test - - name: Collate deno release files - if: steps.js_tests.outcome == 'success' && steps.deno_tests.outcome == 'success' - run: | - mkdir $GITHUB_WORKSPACE/deno_js_dist - cp $GITHUB_WORKSPACE/javascript/deno_dist/* $GITHUB_WORKSPACE/deno_js_dist - - name: Create npm release - if: steps.js_tests.outcome == 'success' && steps.deno_tests.outcome == 'success' - run: | - if [ "$(npm --prefix $GITHUB_WORKSPACE/javascript show . version)" = "$VERSION" ]; then - echo "This version is already published" - exit 0 - fi - EXTRA_ARGS="--access public" - if [[ $VERSION == *"alpha."* ]] || [[ $VERSION == *"beta."* ]] || [[ $VERSION == *"rc."* ]]; then - echo "Is pre-release version" - EXTRA_ARGS="$EXTRA_ARGS --tag next" - fi - if [ "$NODE_AUTH_TOKEN" = "" ]; then - echo "Can't publish on NPM, You need a NPM_TOKEN secret." - false - fi - npm publish $GITHUB_WORKSPACE/javascript $EXTRA_ARGS - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - VERSION: ${{ needs.check_if_js_version_upgraded.outputs.js_version }} - - name: Commit js deno release files - run: | - git config --global user.name "actions" - git config --global user.email actions@github.com - git add $GITHUB_WORKSPACE/deno_js_dist - git commit -am "Add deno js release files" - git push origin js_tmp_branch - - name: Tag JS release - if: steps.js_tests.outcome == 'success' && steps.deno_tests.outcome == 'success' - uses: softprops/action-gh-release@v1 - with: - name: Automerge v${{ needs.check_if_js_version_upgraded.outputs.js_version }} - tag_name: js/automerge-${{ needs.check_if_js_version_upgraded.outputs.js_version }} - target_commitish: js_tmp_branch - generate_release_notes: false - draft: false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Remove js_tmp_branch - run: git push origin :js_tmp_branch diff --git a/deno_wasm_dist/LICENSE b/deno_wasm_dist/LICENSE new file mode 100644 index 00000000..63b21502 --- /dev/null +++ b/deno_wasm_dist/LICENSE @@ -0,0 +1,10 @@ +MIT License + +Copyright 2022, Ink & Switch LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/deno_wasm_dist/README.md b/deno_wasm_dist/README.md new file mode 100644 index 00000000..20256313 --- /dev/null +++ b/deno_wasm_dist/README.md @@ -0,0 +1,469 @@ +## Automerge WASM Low Level Interface + +This package is a low level interface to the [automerge rust](https://github.com/automerge/automerge-rs/tree/experiment) CRDT. The api is intended to be as "close to the metal" as possible with only a few ease of use accommodations. This library is used as the underpinnings for the [Automerge JS wrapper](https://github.com/automerge/automerge-rs/tree/experiment/automerge-js) and can be used as is or as a basis for another higher level expression of a CRDT. + +All example code can be found in `test/readme.ts` + +### Why CRDT? + +CRDT stands for Conflict Free Replicated Data Type. It is a data structure that offers eventual consistency where multiple actors can write to the document independently and then these edits can be automatically merged together into a coherent document that, as much as possible, preserves the intent of the different writers. This allows for novel masterless application design where different components need not have a central coordinating server when altering application state. + +### Terminology + +The term Actor, Object Id and Heads are used through this documentation. Detailed explanations are in the glossary at the end of this readme. But the most basic definition would be... + +An Actor is a unique id that distinguishes a single writer to a document. It can be any hex string. + +An Object id uniquely identifies a Map, List or Text object within a document. It can be treated as an opaque string and can be used across documents. This id comes as a string in the form of `{number}@{actor}` - so `"10@aabbcc"` for example. The string `"_root"` or `"/"` can also be used to refer to the document root. These strings are durable and can be used on any descendant or copy of the document that generated them. + +Heads refers to a set of hashes that uniquely identifies a point in time in a document's history. Heads are useful for comparing documents state or retrieving past states from the document. + +### Automerge Scalar Types + +Automerge has many scalar types. Methods like `put()` and `insert()` take an optional data type parameter. Normally the type can be inferred but in some cases, such as telling the difference between int, uint and a counter, it cannot. + +These are puts without a data type + +```javascript + import { create } from "@automerge/automerge-wasm" + + let doc = create() + doc.put("/", "prop1", 100) // int + doc.put("/", "prop2", 3.14) // f64 + doc.put("/", "prop3", "hello world") + doc.put("/", "prop4", new Date()) + doc.put("/", "prop5", new Uint8Array([1,2,3])) + doc.put("/", "prop6", true) + doc.put("/", "prop7", null) +``` + +Put's with a data type and examples of all the supported data types. + +While int vs uint vs f64 matters little in javascript, Automerge is a cross platform library where these distinctions matter. + +```javascript + import { create } from "@automerge/automerge-wasm" + + let doc = create() + doc.put("/", "prop1", 100, "int") + doc.put("/", "prop2", 100, "uint") + doc.put("/", "prop3", 100.5, "f64") + doc.put("/", "prop4", 100, "counter") + doc.put("/", "prop5", 1647531707301, "timestamp") + doc.put("/", "prop6", new Date(), "timestamp") + doc.put("/", "prop7", "hello world", "str") + doc.put("/", "prop8", new Uint8Array([1,2,3]), "bytes") + doc.put("/", "prop9", true, "boolean") + doc.put("/", "prop10", null, "null") +``` + +### Automerge Object Types + +Automerge WASM supports 3 object types. Maps, lists, and text. Maps are key value stores where the values can be any scalar type or any object type. Lists are numerically indexed sets of data that can hold any scalar or any object type. + +```javascript + import { create } from "@automerge/automerge-wasm" + + let doc = create() + + // you can create an object by passing in the inital state - if blank pass in `{}` + // the return value is the Object Id + // these functions all return an object id + + let config = doc.putObject("/", "config", { align: "left", archived: false, cycles: [10, 19, 21] }) + let token = doc.putObject("/", "tokens", {}) + + // lists can be made with javascript arrays + + let birds = doc.putObject("/", "birds", ["bluejay", "penguin", "puffin"]) + let bots = doc.putObject("/", "bots", []) + + // text is initialized with a string + + let notes = doc.putObject("/", "notes", "Hello world!") +``` + +You can access objects by passing the object id as the first parameter for a call. + +```javascript + import { create } from "@automerge/automerge-wasm" + + let doc = create() + + let config = doc.putObject("/", "config", { align: "left", archived: false, cycles: [10, 19, 21] }) + + doc.put(config, "align", "right") + + // Anywhere Object Ids are being used a path can also be used. + // The following two statements are equivalent: + + // get the id then use it + + // get returns a single simple javascript value or undefined + // getWithType returns an Array of the datatype plus basic type or null + + let id = doc.getWithType("/", "config") + if (id && id[0] === 'map') { + doc.put(id[1], "align", "right") + } + + // use a path instead + + doc.put("/config", "align", "right") +``` + +Using the id directly is always faster (as it prevents the path to id conversion internally) so it is preferred for performance critical code. + +### Maps + +Maps are key/value stores. The root object is always a map. The keys are always strings. The values can be any scalar type or any object. + +```javascript + let doc = create() + let mymap = doc.putObject("_root", "mymap", { foo: "bar"}) + // make a new map with the foo key + + doc.put(mymap, "bytes", new Uint8Array([1,2,3])) + // assign a byte array to key `bytes` of the mymap object + + let submap = doc.putObject(mymap, "sub", {}) + // make a new empty object and assign it to the key `sub` of mymap + + doc.keys(mymap) // returns ["bytes","foo","sub"] + doc.materialize("_root") // returns { mymap: { bytes: new Uint8Array([1,2,3]), foo: "bar", sub: {}}} +``` + +### Lists + +Lists are index addressable sets of values. These values can be any scalar or object type. You can manipulate lists with `insert()`, `put()`, `insertObject()`, `putObject()`, `push()`, `pushObject()`, `splice()`, and `delete()`. + +```javascript + let doc = create() + let items = doc.putObject("_root", "items", [10,"box"]) + // init a new list with two elements + doc.push(items, true) // push `true` to the end of the list + doc.putObject(items, 0, { hello: "world" }) // overwrite the value 10 with an object with a key and value + doc.delete(items, 1) // delete "box" + doc.splice(items, 2, 0, ["bag", "brick"]) // splice in "bag" and "brick" at position 2 + doc.insert(items, 0, "bat") // insert "bat" to the beginning of the list + doc.insertObject(items, 1, [1,2]) // insert a list with 2 values at pos 1 + + doc.materialize(items) // returns [ "bat", [1,2], { hello : "world" }, true, "bag", "brick"] + doc.length(items) // returns 6 +``` + +### Text + +Text is a specialized list type intended for modifying a text document. The primary way to interact with a text document is via the `splice()` method. Spliced strings will be indexable by character (important to note for platforms that index by graphmeme cluster). + +```javascript + let doc = create("aaaaaa") + let notes = doc.putObject("_root", "notes", "Hello world") + doc.splice(notes, 6, 5, "everyone") + + doc.text(notes) // returns "Hello everyone" +``` + +### Tables + +Automerge's Table type is currently not implemented. + +### Querying Data + +When querying maps use the `get()` method with the object in question and the property to query. This method returns a tuple with the data type and the data. The `keys()` method will return all the keys on the object. If you are interested in conflicted values from a merge use `getAll()` instead which returns an array of values instead of just the winner. + +```javascript + let doc1 = create("aabbcc") + doc1.put("_root", "key1", "val1") + let key2 = doc1.putObject("_root", "key2", []) + + doc1.get("_root", "key1") // returns "val1" + doc1.getWithType("_root", "key2") // returns ["list", "2@aabbcc"] + doc1.keys("_root") // returns ["key1", "key2"] + + let doc2 = doc1.fork("ffaaff") + + // put a value concurrently + doc1.put("_root","key3","doc1val") + doc2.put("_root","key3","doc2val") + + doc1.merge(doc2) + + doc1.get("_root","key3") // returns "doc2val" + doc1.getAll("_root","key3") // returns [[ "str", "doc1val"], ["str", "doc2val"]] +``` + +### Counters + +Counters are 64 bit ints that support the increment operation. Frequently different actors will want to increment or decrement a number and have all these coalesse into a merged value. + +```javascript + let doc1 = create("aaaaaa") + doc1.put("_root", "number", 0) + doc1.put("_root", "total", 0, "counter") + + let doc2 = doc1.fork("bbbbbb") + doc2.put("_root", "number", 10) + doc2.increment("_root", "total", 11) + + doc1.put("_root", "number", 20) + doc1.increment("_root", "total", 22) + + doc1.merge(doc2) + + doc1.materialize("_root") // returns { number: 10, total: 33 } +``` + +### Transactions + +Generally speaking you don't need to think about transactions when using Automerge. Normal edits queue up into an in-progress transaction. You can query the number of ops in the current transaction with `pendingOps()`. The transaction will commit automatically on certains calls such as `save()`, `saveIncremental()`, `fork()`, `merge()`, `getHeads()`, `applyChanges()`, `generateSyncMessage()`, and `receiveSyncMessage()`. When the transaction commits the heads of the document change. If you want to roll back all the in progress ops you can call `doc.rollback()`. If you want to manually commit a transaction in progress you can call `doc.commit()` with an optional commit message and timestamp. + +```javascript + let doc = create() + + doc.put("_root", "key", "val1") + + doc.get("_root", "key") // returns "val1" + doc.pendingOps() // returns 1 + + doc.rollback() + + doc.get("_root", "key") // returns null + doc.pendingOps() // returns 0 + + doc.put("_root", "key", "val2") + + doc.pendingOps() // returns 1 + + doc.commit("test commit 1") + + doc.get("_root", "key") // returns "val2" + doc.pendingOps() // returns 0 +``` + +### Viewing Old Versions of the Document + +All query functions can take an optional argument of `heads` which allow you to query a prior document state. Heads are a set of change hashes that uniquely identify a point in the document history. The `getHeads()` method can retrieve these at any point. + +```javascript + let doc = create() + + doc.put("_root", "key", "val1") + let heads1 = doc.getHeads() + + doc.put("_root", "key", "val2") + let heads2 = doc.getHeads() + + doc.put("_root", "key", "val3") + + doc.get("_root","key") // returns "val3" + doc.get("_root","key",heads2) // returns "val2" + doc.get("_root","key",heads1) // returns "val1" + doc.get("_root","key",[]) // returns undefined +``` + +This works for `get()`, `getAll()`, `keys()`, `length()`, `text()`, and `materialize()` + +Queries of old document states are not indexed internally and will be slower than normal access. If you need a fast indexed version of a document at a previous point in time you can create one with `doc.forkAt(heads, actor?)` + +### Forking and Merging + +You can `fork()` a document which makes an exact copy of it. This assigns a new actor so changes made to the fork can be merged back in with the original. The `forkAt()` takes a Heads, allowing you to fork off a document from a previous point in its history. These documents allocate new memory in WASM and need to be freed. + +The `merge()` command applies all changes in the argument doc into the calling doc. Therefore if doc a has 1000 changes that doc b lacks and doc b has only 10 changes that doc a lacks, `a.merge(b)` will be much faster than `b.merge(a)`. + +```javascript + let doc1 = create() + doc1.put("_root", "key1", "val1") + + let doc2 = doc1.fork() + + doc1.put("_root", "key2", "val2") + doc2.put("_root", "key3", "val3") + + doc1.merge(doc2) + + doc1.materialize("_root") // returns { key1: "val1", key2: "val2", key3: "val3" } + doc2.materialize("_root") // returns { key1: "val1", key3: "val3" } +``` + +Note that calling `a.merge(a)` will produce an unrecoverable error from the wasm-bindgen layer which (as of this writing) there is no workaround for. + +### Saving and Loading + +Calling `save()` converts the document to a compressed `Uint8Array()` that can be saved to durable storage. This format uses a columnar storage format that compresses away most of the Automerge metadata needed to manage the CRDT state, but does include all of the change history. + +If you wish to incrementally update a saved Automerge doc you can call `saveIncremental()` to get a `Uint8Array()` of bytes that can be appended to the file with all the new changes(). Note that the `saveIncremental()` bytes are not as compressed as the whole document save as each chunk has metadata information needed to parse it. It may make sense to periodically perform a new `save()` to get the smallest possible file footprint. + +The `load()` function takes a `Uint8Array()` of bytes produced in this way and constitutes a new document. The `loadIncremental()` method is available if you wish to consume the result of a `saveIncremental()` with an already instanciated document. + +```javascript + import { create, load } from "@automerge/automerge-wasm" + + let doc1 = create() + + doc1.put("_root", "key1", "value1") + + let save1 = doc1.save() + + let doc2 = load(save1) + + doc2.materialize("_root") // returns { key1: "value1" } + + doc1.put("_root", "key2", "value2") + + let saveIncremental = doc1.saveIncremental() + + let save2 = doc1.save() + + let save3 = new Uint8Array([... save1, ... saveIncremental]) + + // save2 has fewer bytes than save3 but contains the same ops + + doc2.loadIncremental(saveIncremental) + + let doc3 = load(save2) + + let doc4 = load(save3) + + doc1.materialize("_root") // returns { key1: "value1", key2: "value2" } + doc2.materialize("_root") // returns { key1: "value1", key2: "value2" } + doc3.materialize("_root") // returns { key1: "value1", key2: "value2" } + doc4.materialize("_root") // returns { key1: "value1", key2: "value2" } +``` + +One interesting feature of automerge binary saves is that they can be concatenated together in any order and can still be loaded into a coherent merged document. + +```javascript +import { load } from "@automerge/automerge-wasm" +import * as fs from "fs" + +let file1 = fs.readFileSync("automerge_save_1"); +let file2 = fs.readFileSync("automerge_save_2"); + +let docA = load(file1).merge(load(file2)) +let docB = load(Buffer.concat([ file1, file2 ])) + +assert.deepEqual(docA.materialize("/"), docB.materialize("/")) +assert.equal(docA.save(), docB.save()) +``` + +### Syncing + +When syncing a document the `generateSyncMessage()` and `receiveSyncMessage()` methods will produce and consume sync messages. A sync state object will need to be managed for the duration of the connection (created by the function `initSyncState()` and can be serialized to a Uint8Array() to preserve sync state with the `encodeSyncState()` and `decodeSyncState()` functions. + +A very simple sync implementation might look like this. + +```javascript + import { encodeSyncState, decodeSyncState, initSyncState } from "@automerge/automerge-wasm" + + let states = {} + + function receiveMessageFromPeer(doc, peer_id, message) { + let syncState = states[peer_id] + doc.receiveMessage(syncState, message) + let reply = doc.generateSyncMessage(syncState) + if (reply) { + sendMessage(peer_id, reply) + } + } + + function notifyPeerAboutUpdates(doc, peer_id) { + let syncState = states[peer_id] + let message = doc.generateSyncMessage(syncState) + if (message) { + sendMessage(peer_id, message) + } + } + + function onDisconnect(peer_id) { + let state = states[peer_id] + if (state) { + saveSyncToStorage(peer_id, encodeSyncState(state)) + } + delete states[peer_id] + } + + function onConnect(peer_id) { + let state = loadSyncFromStorage(peer_id) + if (state) { + states[peer_id] = decodeSyncState(state) + } else { + states[peer_id] = initSyncState() + } + } +``` + +### Glossary: Actors + +Some basic concepts you will need to know to better understand the api are Actors and Object Ids. + +Actors are ids that need to be unique to each process writing to a document. This is normally one actor per device. Or for a web app one actor per tab per browser would be needed. It can be a uuid, or a public key, or a certificate, as your application demands. All that matters is that its bytes are unique. Actors are always expressed in this api as a hex string. + +Methods that create new documents will generate random actors automatically - if you wish to supply your own it is always taken as an optional argument. This is true for the following functions. + +```javascript + import { create, load } from "@automerge/automerge-wasm" + + let doc1 = create() // random actorid + let doc2 = create("aabbccdd") + let doc3 = doc1.fork() // random actorid + let doc4 = doc2.fork("ccdd0011") + let doc5 = load(doc3.save()) // random actorid + let doc6 = load(doc4.save(), "00aabb11") + + let actor = doc1.getActor() +``` + +### Glossary: Object Id's + +Object Ids uniquely identify an object within a document. They are represented as strings in the format of `{counter}@{actor}`. The root object is a special case and can be referred to as `_root`. The counter is an ever increasing integer, starting at 1, that is always one higher than the highest counter seen in the document thus far. Object Id's do not change when the object is modified but they do if it is overwritten with a new object. + +```javascript + let doc = create("aabbcc") + let o1 = doc.putObject("_root", "o1", {}) + let o2 = doc.putObject("_root", "o2", {}) + doc.put(o1, "hello", "world") + + assert.deepEqual(doc.materialize("_root"), { "o1": { hello: "world" }, "o2": {} }) + assert.equal(o1, "1@aabbcc") + assert.equal(o2, "2@aabbcc") + + let o1v2 = doc.putObject("_root", "o1", {}) + + doc.put(o1, "a", "b") // modifying an overwritten object - does nothing + doc.put(o1v2, "x", "y") // modifying the new "o1" object + + assert.deepEqual(doc.materialize("_root"), { "o1": { x: "y" }, "o2": {} }) +``` + +### Appendix: Building + + The following steps should allow you to build the package + + ``` + $ rustup target add wasm32-unknown-unknown + $ cargo install wasm-bindgen-cli + $ cargo install wasm-opt + $ yarn + $ yarn release + $ yarn pack + ``` + +### Appendix: WASM and Memory Allocation + +Allocated memory in rust will be freed automatically on platforms that support `FinalizationRegistry`. + +This is currently supported in [all major browsers and nodejs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry). + +On unsupported platforms you can free memory explicitly. + +```javascript + import { create, initSyncState } from "@automerge/automerge-wasm" + + let doc = create() + let sync = initSyncState() + + doc.free() + sync.free() +``` diff --git a/deno_wasm_dist/automerge_wasm.js b/deno_wasm_dist/automerge_wasm.js new file mode 100644 index 00000000..58fe8ad2 --- /dev/null +++ b/deno_wasm_dist/automerge_wasm.js @@ -0,0 +1,1663 @@ +/// + + +const heap = new Array(128).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let heap_next = heap.length; + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachedUint8Memory0 = null; + +function getUint8Memory0() { + if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +let WASM_VECTOR_LEN = 0; + +const cachedTextEncoder = new TextEncoder('utf-8'); + +const encodeString = function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +}; + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length); + getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len); + + const mem = getUint8Memory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3); + const view = getUint8Memory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function isLikeNone(x) { + return x === undefined || x === null; +} + +let cachedInt32Memory0 = null; + +function getInt32Memory0() { + if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { + cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachedInt32Memory0; +} + +let cachedFloat64Memory0 = null; + +function getFloat64Memory0() { + if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) { + cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer); + } + return cachedFloat64Memory0; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +function _assertClass(instance, klass) { + if (!(instance instanceof klass)) { + throw new Error(`expected instance of ${klass.name}`); + } + return instance.ptr; +} +/** +* @param {boolean} text_v2 +* @param {string | undefined} actor +* @returns {Automerge} +*/ +export function create(text_v2, actor) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + var ptr0 = isLikeNone(actor) ? 0 : passStringToWasm0(actor, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + wasm.create(retptr, text_v2, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return Automerge.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @param {Uint8Array} data +* @param {boolean} text_v2 +* @param {string | undefined} actor +* @returns {Automerge} +*/ +export function load(data, text_v2, actor) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + var ptr0 = isLikeNone(actor) ? 0 : passStringToWasm0(actor, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + wasm.load(retptr, addHeapObject(data), text_v2, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return Automerge.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @param {any} change +* @returns {Uint8Array} +*/ +export function encodeChange(change) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.encodeChange(retptr, addHeapObject(change)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @param {Uint8Array} change +* @returns {any} +*/ +export function decodeChange(change) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.decodeChange(retptr, addHeapObject(change)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @returns {SyncState} +*/ +export function initSyncState() { + const ret = wasm.initSyncState(); + return SyncState.__wrap(ret); +} + +/** +* @param {any} state +* @returns {SyncState} +*/ +export function importSyncState(state) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.importSyncState(retptr, addHeapObject(state)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return SyncState.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @param {SyncState} state +* @returns {any} +*/ +export function exportSyncState(state) { + _assertClass(state, SyncState); + const ret = wasm.exportSyncState(state.ptr); + return takeObject(ret); +} + +/** +* @param {any} message +* @returns {Uint8Array} +*/ +export function encodeSyncMessage(message) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.encodeSyncMessage(retptr, addHeapObject(message)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @param {Uint8Array} msg +* @returns {any} +*/ +export function decodeSyncMessage(msg) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.decodeSyncMessage(retptr, addHeapObject(msg)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @param {SyncState} state +* @returns {Uint8Array} +*/ +export function encodeSyncState(state) { + _assertClass(state, SyncState); + const ret = wasm.encodeSyncState(state.ptr); + return takeObject(ret); +} + +/** +* @param {Uint8Array} data +* @returns {SyncState} +*/ +export function decodeSyncState(data) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.decodeSyncState(retptr, addHeapObject(data)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return SyncState.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } +} + +function getArrayU8FromWasm0(ptr, len) { + return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); +} +/** +* How text is represented in materialized objects on the JS side +*/ +export const TextRepresentation = Object.freeze({ +/** +* As an array of characters and objects +*/ +Array:0,"0":"Array", +/** +* As a single JS string +*/ +String:1,"1":"String", }); + +const AutomergeFinalization = new FinalizationRegistry(ptr => wasm.__wbg_automerge_free(ptr)); +/** +*/ +export class Automerge { + + static __wrap(ptr) { + const obj = Object.create(Automerge.prototype); + obj.ptr = ptr; + AutomergeFinalization.register(obj, obj.ptr, obj); + return obj; + } + + __destroy_into_raw() { + const ptr = this.ptr; + this.ptr = 0; + AutomergeFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_automerge_free(ptr); + } + /** + * @param {string | undefined} actor + * @param {number} text_rep + * @returns {Automerge} + */ + static new(actor, text_rep) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + var ptr0 = isLikeNone(actor) ? 0 : passStringToWasm0(actor, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + wasm.automerge_new(retptr, ptr0, len0, text_rep); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return Automerge.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {string | undefined} actor + * @returns {Automerge} + */ + clone(actor) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + var ptr0 = isLikeNone(actor) ? 0 : passStringToWasm0(actor, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + wasm.automerge_clone(retptr, this.ptr, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return Automerge.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {string | undefined} actor + * @param {any} heads + * @returns {Automerge} + */ + fork(actor, heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + var ptr0 = isLikeNone(actor) ? 0 : passStringToWasm0(actor, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + wasm.automerge_fork(retptr, this.ptr, ptr0, len0, addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return Automerge.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {any} + */ + pendingOps() { + const ret = wasm.automerge_pendingOps(this.ptr); + return takeObject(ret); + } + /** + * @param {string | undefined} message + * @param {number | undefined} time + * @returns {any} + */ + commit(message, time) { + var ptr0 = isLikeNone(message) ? 0 : passStringToWasm0(message, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + const ret = wasm.automerge_commit(this.ptr, ptr0, len0, !isLikeNone(time), isLikeNone(time) ? 0 : time); + return takeObject(ret); + } + /** + * @param {Automerge} other + * @returns {Array} + */ + merge(other) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + _assertClass(other, Automerge); + wasm.automerge_merge(retptr, this.ptr, other.ptr); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {number} + */ + rollback() { + const ret = wasm.automerge_rollback(this.ptr); + return ret; + } + /** + * @param {any} obj + * @param {Array | undefined} heads + * @returns {Array} + */ + keys(obj, heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_keys(retptr, this.ptr, addHeapObject(obj), isLikeNone(heads) ? 0 : addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {Array | undefined} heads + * @returns {string} + */ + text(obj, heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_text(retptr, this.ptr, addHeapObject(obj), isLikeNone(heads) ? 0 : addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + var ptr0 = r0; + var len0 = r1; + if (r3) { + ptr0 = 0; len0 = 0; + throw takeObject(r2); + } + return getStringFromWasm0(ptr0, len0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_free(ptr0, len0); + } + } + /** + * @param {any} obj + * @param {number} start + * @param {number} delete_count + * @param {any} text + */ + splice(obj, start, delete_count, text) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_splice(retptr, this.ptr, addHeapObject(obj), start, delete_count, addHeapObject(text)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} value + * @param {any} datatype + */ + push(obj, value, datatype) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_push(retptr, this.ptr, addHeapObject(obj), addHeapObject(value), addHeapObject(datatype)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} value + * @returns {string | undefined} + */ + pushObject(obj, value) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_pushObject(retptr, this.ptr, addHeapObject(obj), addHeapObject(value)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + if (r3) { + throw takeObject(r2); + } + let v0; + if (r0 !== 0) { + v0 = getStringFromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1); + } + return v0; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {number} index + * @param {any} value + * @param {any} datatype + */ + insert(obj, index, value, datatype) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_insert(retptr, this.ptr, addHeapObject(obj), index, addHeapObject(value), addHeapObject(datatype)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {number} index + * @param {any} value + * @returns {string | undefined} + */ + insertObject(obj, index, value) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_insertObject(retptr, this.ptr, addHeapObject(obj), index, addHeapObject(value)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + if (r3) { + throw takeObject(r2); + } + let v0; + if (r0 !== 0) { + v0 = getStringFromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1); + } + return v0; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} prop + * @param {any} value + * @param {any} datatype + */ + put(obj, prop, value, datatype) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_put(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop), addHeapObject(value), addHeapObject(datatype)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} prop + * @param {any} value + * @returns {any} + */ + putObject(obj, prop, value) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_putObject(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop), addHeapObject(value)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} prop + * @param {any} value + */ + increment(obj, prop, value) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_increment(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop), addHeapObject(value)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} prop + * @param {Array | undefined} heads + * @returns {any} + */ + get(obj, prop, heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_get(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop), isLikeNone(heads) ? 0 : addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} prop + * @param {Array | undefined} heads + * @returns {any} + */ + getWithType(obj, prop, heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_getWithType(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop), isLikeNone(heads) ? 0 : addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} arg + * @param {Array | undefined} heads + * @returns {Array} + */ + getAll(obj, arg, heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_getAll(retptr, this.ptr, addHeapObject(obj), addHeapObject(arg), isLikeNone(heads) ? 0 : addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} enable + * @returns {any} + */ + enableFreeze(enable) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_enableFreeze(retptr, this.ptr, addHeapObject(enable)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} enable + * @returns {any} + */ + enablePatches(enable) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_enablePatches(retptr, this.ptr, addHeapObject(enable)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} datatype + * @param {any} _function + */ + registerDatatype(datatype, _function) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_registerDatatype(retptr, this.ptr, addHeapObject(datatype), addHeapObject(_function)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} object + * @param {any} meta + * @param {any} callback + * @returns {any} + */ + applyPatches(object, meta, callback) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_applyPatches(retptr, this.ptr, addHeapObject(object), addHeapObject(meta), addHeapObject(callback)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {Array} + */ + popPatches() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_popPatches(retptr, this.ptr); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {Array | undefined} heads + * @returns {number} + */ + length(obj, heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_length(retptr, this.ptr, addHeapObject(obj), isLikeNone(heads) ? 0 : addHeapObject(heads)); + var r0 = getFloat64Memory0()[retptr / 8 + 0]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + if (r3) { + throw takeObject(r2); + } + return r0; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} prop + */ + delete(obj, prop) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_delete(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {Uint8Array} + */ + save() { + const ret = wasm.automerge_save(this.ptr); + return takeObject(ret); + } + /** + * @returns {Uint8Array} + */ + saveIncremental() { + const ret = wasm.automerge_saveIncremental(this.ptr); + return takeObject(ret); + } + /** + * @param {Uint8Array} data + * @returns {number} + */ + loadIncremental(data) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_loadIncremental(retptr, this.ptr, addHeapObject(data)); + var r0 = getFloat64Memory0()[retptr / 8 + 0]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + if (r3) { + throw takeObject(r2); + } + return r0; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} changes + */ + applyChanges(changes) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_applyChanges(retptr, this.ptr, addHeapObject(changes)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} have_deps + * @returns {Array} + */ + getChanges(have_deps) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_getChanges(retptr, this.ptr, addHeapObject(have_deps)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} hash + * @returns {any} + */ + getChangeByHash(hash) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_getChangeByHash(retptr, this.ptr, addHeapObject(hash)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {Automerge} other + * @returns {Array} + */ + getChangesAdded(other) { + _assertClass(other, Automerge); + const ret = wasm.automerge_getChangesAdded(this.ptr, other.ptr); + return takeObject(ret); + } + /** + * @returns {Array} + */ + getHeads() { + const ret = wasm.automerge_getHeads(this.ptr); + return takeObject(ret); + } + /** + * @returns {string} + */ + getActorId() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_getActorId(retptr, this.ptr); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_free(r0, r1); + } + } + /** + * @returns {any} + */ + getLastLocalChange() { + const ret = wasm.automerge_getLastLocalChange(this.ptr); + return takeObject(ret); + } + /** + */ + dump() { + wasm.automerge_dump(this.ptr); + } + /** + * @param {Array | undefined} heads + * @returns {Array} + */ + getMissingDeps(heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_getMissingDeps(retptr, this.ptr, isLikeNone(heads) ? 0 : addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {SyncState} state + * @param {Uint8Array} message + */ + receiveSyncMessage(state, message) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + _assertClass(state, SyncState); + wasm.automerge_receiveSyncMessage(retptr, this.ptr, state.ptr, addHeapObject(message)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {SyncState} state + * @returns {any} + */ + generateSyncMessage(state) { + _assertClass(state, SyncState); + const ret = wasm.automerge_generateSyncMessage(this.ptr, state.ptr); + return takeObject(ret); + } + /** + * @param {any} meta + * @returns {any} + */ + toJS(meta) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_toJS(retptr, this.ptr, addHeapObject(meta)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {Array | undefined} heads + * @param {any} meta + * @returns {any} + */ + materialize(obj, heads, meta) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_materialize(retptr, this.ptr, addHeapObject(obj), isLikeNone(heads) ? 0 : addHeapObject(heads), addHeapObject(meta)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {string | undefined} message + * @param {number | undefined} time + * @returns {any} + */ + emptyChange(message, time) { + var ptr0 = isLikeNone(message) ? 0 : passStringToWasm0(message, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + const ret = wasm.automerge_emptyChange(this.ptr, ptr0, len0, !isLikeNone(time), isLikeNone(time) ? 0 : time); + return takeObject(ret); + } +} + +const SyncStateFinalization = new FinalizationRegistry(ptr => wasm.__wbg_syncstate_free(ptr)); +/** +*/ +export class SyncState { + + static __wrap(ptr) { + const obj = Object.create(SyncState.prototype); + obj.ptr = ptr; + SyncStateFinalization.register(obj, obj.ptr, obj); + return obj; + } + + __destroy_into_raw() { + const ptr = this.ptr; + this.ptr = 0; + SyncStateFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_syncstate_free(ptr); + } + /** + * @returns {any} + */ + get sharedHeads() { + const ret = wasm.syncstate_sharedHeads(this.ptr); + return takeObject(ret); + } + /** + * @returns {any} + */ + get lastSentHeads() { + const ret = wasm.syncstate_lastSentHeads(this.ptr); + return takeObject(ret); + } + /** + * @param {any} heads + */ + set lastSentHeads(heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.syncstate_set_lastSentHeads(retptr, this.ptr, addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} hashes + */ + set sentHashes(hashes) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.syncstate_set_sentHashes(retptr, this.ptr, addHeapObject(hashes)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {SyncState} + */ + clone() { + const ret = wasm.syncstate_clone(this.ptr); + return SyncState.__wrap(ret); + } +} + +const imports = { + __wbindgen_placeholder__: { + __wbindgen_object_clone_ref: function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }, + __wbindgen_object_drop_ref: function(arg0) { + takeObject(arg0); + }, + __wbindgen_string_new: function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }, + __wbindgen_is_undefined: function(arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }, + __wbindgen_string_get: function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }, + __wbindgen_number_new: function(arg0) { + const ret = arg0; + return addHeapObject(ret); + }, + __wbindgen_is_string: function(arg0) { + const ret = typeof(getObject(arg0)) === 'string'; + return ret; + }, + __wbindgen_is_function: function(arg0) { + const ret = typeof(getObject(arg0)) === 'function'; + return ret; + }, + __wbindgen_number_get: function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'number' ? obj : undefined; + getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret; + getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret); + }, + __wbindgen_is_null: function(arg0) { + const ret = getObject(arg0) === null; + return ret; + }, + __wbindgen_boolean_get: function(arg0) { + const v = getObject(arg0); + const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; + return ret; + }, + __wbindgen_json_serialize: function(arg0, arg1) { + const obj = getObject(arg1); + const ret = JSON.stringify(obj === undefined ? null : obj); + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }, + __wbindgen_error_new: function(arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, + __wbg_new_abda76e883ba8a5f: function() { + const ret = new Error(); + return addHeapObject(ret); + }, + __wbg_stack_658279fe44541cf6: function(arg0, arg1) { + const ret = getObject(arg1).stack; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }, + __wbg_error_f851667af71bcfc6: function(arg0, arg1) { + try { + console.error(getStringFromWasm0(arg0, arg1)); + } finally { + wasm.__wbindgen_free(arg0, arg1); + } + }, + __wbindgen_bigint_from_i64: function(arg0) { + const ret = arg0; + return addHeapObject(ret); + }, + __wbindgen_bigint_from_u64: function(arg0) { + const ret = BigInt.asUintN(64, arg0); + return addHeapObject(ret); + }, + __wbindgen_is_object: function(arg0) { + const val = getObject(arg0); + const ret = typeof(val) === 'object' && val !== null; + return ret; + }, + __wbindgen_jsval_loose_eq: function(arg0, arg1) { + const ret = getObject(arg0) == getObject(arg1); + return ret; + }, + __wbg_String_91fba7ded13ba54c: function(arg0, arg1) { + const ret = String(getObject(arg1)); + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }, + __wbg_set_20cbc34131e76824: function(arg0, arg1, arg2) { + getObject(arg0)[takeObject(arg1)] = takeObject(arg2); + }, + __wbg_randomFillSync_6894564c2c334c42: function() { return handleError(function (arg0, arg1, arg2) { + getObject(arg0).randomFillSync(getArrayU8FromWasm0(arg1, arg2)); + }, arguments) }, + __wbg_getRandomValues_805f1c3d65988a5a: function() { return handleError(function (arg0, arg1) { + getObject(arg0).getRandomValues(getObject(arg1)); + }, arguments) }, + __wbg_crypto_e1d53a1d73fb10b8: function(arg0) { + const ret = getObject(arg0).crypto; + return addHeapObject(ret); + }, + __wbg_process_038c26bf42b093f8: function(arg0) { + const ret = getObject(arg0).process; + return addHeapObject(ret); + }, + __wbg_versions_ab37218d2f0b24a8: function(arg0) { + const ret = getObject(arg0).versions; + return addHeapObject(ret); + }, + __wbg_node_080f4b19d15bc1fe: function(arg0) { + const ret = getObject(arg0).node; + return addHeapObject(ret); + }, + __wbg_msCrypto_6e7d3e1f92610cbb: function(arg0) { + const ret = getObject(arg0).msCrypto; + return addHeapObject(ret); + }, + __wbg_require_78a3dcfbdba9cbce: function() { return handleError(function () { + const ret = module.require; + return addHeapObject(ret); + }, arguments) }, + __wbg_log_7bb108d119bafbc1: function(arg0) { + console.log(getObject(arg0)); + }, + __wbg_log_d047cf0648d2678e: function(arg0, arg1) { + console.log(getObject(arg0), getObject(arg1)); + }, + __wbg_get_27fe3dac1c4d0224: function(arg0, arg1) { + const ret = getObject(arg0)[arg1 >>> 0]; + return addHeapObject(ret); + }, + __wbg_length_e498fbc24f9c1d4f: function(arg0) { + const ret = getObject(arg0).length; + return ret; + }, + __wbg_new_b525de17f44a8943: function() { + const ret = new Array(); + return addHeapObject(ret); + }, + __wbg_newnoargs_2b8b6bd7753c76ba: function(arg0, arg1) { + const ret = new Function(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, + __wbg_next_b7d530c04fd8b217: function(arg0) { + const ret = getObject(arg0).next; + return addHeapObject(ret); + }, + __wbg_next_88560ec06a094dea: function() { return handleError(function (arg0) { + const ret = getObject(arg0).next(); + return addHeapObject(ret); + }, arguments) }, + __wbg_done_1ebec03bbd919843: function(arg0) { + const ret = getObject(arg0).done; + return ret; + }, + __wbg_value_6ac8da5cc5b3efda: function(arg0) { + const ret = getObject(arg0).value; + return addHeapObject(ret); + }, + __wbg_iterator_55f114446221aa5a: function() { + const ret = Symbol.iterator; + return addHeapObject(ret); + }, + __wbg_get_baf4855f9a986186: function() { return handleError(function (arg0, arg1) { + const ret = Reflect.get(getObject(arg0), getObject(arg1)); + return addHeapObject(ret); + }, arguments) }, + __wbg_call_95d1ea488d03e4e8: function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }, + __wbg_new_f9876326328f45ed: function() { + const ret = new Object(); + return addHeapObject(ret); + }, + __wbg_length_ea0846e494e3b16e: function(arg0) { + const ret = getObject(arg0).length; + return ret; + }, + __wbg_self_e7c1f827057f6584: function() { return handleError(function () { + const ret = self.self; + return addHeapObject(ret); + }, arguments) }, + __wbg_window_a09ec664e14b1b81: function() { return handleError(function () { + const ret = window.window; + return addHeapObject(ret); + }, arguments) }, + __wbg_globalThis_87cbb8506fecf3a9: function() { return handleError(function () { + const ret = globalThis.globalThis; + return addHeapObject(ret); + }, arguments) }, + __wbg_global_c85a9259e621f3db: function() { return handleError(function () { + const ret = global.global; + return addHeapObject(ret); + }, arguments) }, + __wbg_set_17224bc548dd1d7b: function(arg0, arg1, arg2) { + getObject(arg0)[arg1 >>> 0] = takeObject(arg2); + }, + __wbg_from_67ca20fa722467e6: function(arg0) { + const ret = Array.from(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_isArray_39d28997bf6b96b4: function(arg0) { + const ret = Array.isArray(getObject(arg0)); + return ret; + }, + __wbg_push_49c286f04dd3bf59: function(arg0, arg1) { + const ret = getObject(arg0).push(getObject(arg1)); + return ret; + }, + __wbg_unshift_06a94bcbcb492eb3: function(arg0, arg1) { + const ret = getObject(arg0).unshift(getObject(arg1)); + return ret; + }, + __wbg_instanceof_ArrayBuffer_a69f02ee4c4f5065: function(arg0) { + let result; + try { + result = getObject(arg0) instanceof ArrayBuffer; + } catch { + result = false; + } + const ret = result; + return ret; + }, + __wbg_new_15d3966e9981a196: function(arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, + __wbg_call_9495de66fdbe016b: function() { return handleError(function (arg0, arg1, arg2) { + const ret = getObject(arg0).call(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }, arguments) }, + __wbg_call_99043a1e2a9e5916: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + const ret = getObject(arg0).call(getObject(arg1), getObject(arg2), getObject(arg3), getObject(arg4)); + return addHeapObject(ret); + }, arguments) }, + __wbg_instanceof_Date_e353425d719aa266: function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Date; + } catch { + result = false; + } + const ret = result; + return ret; + }, + __wbg_getTime_7c59072d1651a3cf: function(arg0) { + const ret = getObject(arg0).getTime(); + return ret; + }, + __wbg_new_f127e324c1313064: function(arg0) { + const ret = new Date(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_instanceof_Object_f5a826c4da0d4a94: function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Object; + } catch { + result = false; + } + const ret = result; + return ret; + }, + __wbg_assign_b0b6530984f36574: function(arg0, arg1) { + const ret = Object.assign(getObject(arg0), getObject(arg1)); + return addHeapObject(ret); + }, + __wbg_defineProperty_4926f24c724d5310: function(arg0, arg1, arg2) { + const ret = Object.defineProperty(getObject(arg0), getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }, + __wbg_entries_4e1315b774245952: function(arg0) { + const ret = Object.entries(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_freeze_4dcdbf0b5d9b50f4: function(arg0) { + const ret = Object.freeze(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_keys_60443f4f867207f9: function(arg0) { + const ret = Object.keys(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_values_7444c4c2ccefdc9b: function(arg0) { + const ret = Object.values(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_concat_040af6c9ba38dd98: function(arg0, arg1) { + const ret = getObject(arg0).concat(getObject(arg1)); + return addHeapObject(ret); + }, + __wbg_slice_47202b1d012cdc55: function(arg0, arg1, arg2) { + const ret = getObject(arg0).slice(arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); + }, + __wbg_for_9a885d0d6d415e40: function(arg0, arg1) { + const ret = Symbol.for(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, + __wbg_toString_7a3e0cd68ea2a337: function(arg0) { + const ret = getObject(arg0).toString(); + return addHeapObject(ret); + }, + __wbg_buffer_cf65c07de34b9a08: function(arg0) { + const ret = getObject(arg0).buffer; + return addHeapObject(ret); + }, + __wbg_newwithbyteoffsetandlength_9fb2f11355ecadf5: function(arg0, arg1, arg2) { + const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); + }, + __wbg_new_537b7341ce90bb31: function(arg0) { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_set_17499e8aa4003ebd: function(arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0); + }, + __wbg_length_27a2afe8ab42b09f: function(arg0) { + const ret = getObject(arg0).length; + return ret; + }, + __wbg_instanceof_Uint8Array_01cebe79ca606cca: function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Uint8Array; + } catch { + result = false; + } + const ret = result; + return ret; + }, + __wbg_newwithlength_b56c882b57805732: function(arg0) { + const ret = new Uint8Array(arg0 >>> 0); + return addHeapObject(ret); + }, + __wbg_subarray_7526649b91a252a6: function(arg0, arg1, arg2) { + const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); + }, + __wbg_apply_5435e78b95a524a6: function() { return handleError(function (arg0, arg1, arg2) { + const ret = Reflect.apply(getObject(arg0), getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }, arguments) }, + __wbg_deleteProperty_31090878b92a7c0e: function() { return handleError(function (arg0, arg1) { + const ret = Reflect.deleteProperty(getObject(arg0), getObject(arg1)); + return ret; + }, arguments) }, + __wbg_ownKeys_9efe69be404540aa: function() { return handleError(function (arg0) { + const ret = Reflect.ownKeys(getObject(arg0)); + return addHeapObject(ret); + }, arguments) }, + __wbg_set_6aa458a4ebdb65cb: function() { return handleError(function (arg0, arg1, arg2) { + const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2)); + return ret; + }, arguments) }, + __wbindgen_debug_string: function(arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }, + __wbindgen_throw: function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }, + __wbindgen_memory: function() { + const ret = wasm.memory; + return addHeapObject(ret); + }, + }, + +}; + +const wasm_url = new URL('automerge_wasm_bg.wasm', import.meta.url); +let wasmCode = ''; +switch (wasm_url.protocol) { + case 'file:': + wasmCode = await Deno.readFile(wasm_url); + break + case 'https:': + case 'http:': + wasmCode = await (await fetch(wasm_url)).arrayBuffer(); + break + default: + throw new Error(`Unsupported protocol: ${wasm_url.protocol}`); +} + +const wasmInstance = (await WebAssembly.instantiate(wasmCode, imports)).instance; +const wasm = wasmInstance.exports; + diff --git a/deno_wasm_dist/automerge_wasm_bg.wasm b/deno_wasm_dist/automerge_wasm_bg.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8c4f8b7d010363a34d22b8c7748ad9f1f3d23027 GIT binary patch literal 1305595 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¨w79qBmi-Eu%;9vH@00@>0q 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 Y%3qf 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�q(4puMhpgYj`k;w? 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 z

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

&7nqmFM(w<_~@Ztc!y53GVv(=%b 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�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 F`5(xM1egE- literal 0 HcmV?d00001 diff --git a/deno_wasm_dist/index.d.ts b/deno_wasm_dist/index.d.ts new file mode 100644 index 00000000..be12e4c1 --- /dev/null +++ b/deno_wasm_dist/index.d.ts @@ -0,0 +1,238 @@ +export type Actor = string; +export type ObjID = string; +export type Change = Uint8Array; +export type SyncMessage = Uint8Array; +export type Prop = string | number; +export type Hash = string; +export type Heads = Hash[]; +export type Value = string | number | boolean | null | Date | Uint8Array +export type MaterializeValue = { [key:string]: MaterializeValue } | Array | Value +export type ObjType = string | Array | { [key: string]: ObjType | Value } +export type FullValue = + ["str", string] | + ["int", number] | + ["uint", number] | + ["f64", number] | + ["boolean", boolean] | + ["timestamp", Date] | + ["counter", number] | + ["bytes", Uint8Array] | + ["null", null] | + ["map", ObjID] | + ["list", ObjID] | + ["text", ObjID] | + ["table", ObjID] + +export type FullValueWithId = + ["str", string, ObjID ] | + ["int", number, ObjID ] | + ["uint", number, ObjID ] | + ["f64", number, ObjID ] | + ["boolean", boolean, ObjID ] | + ["timestamp", Date, ObjID ] | + ["counter", number, ObjID ] | + ["bytes", Uint8Array, ObjID ] | + ["null", null, ObjID ] | + ["map", ObjID ] | + ["list", ObjID] | + ["text", ObjID] | + ["table", ObjID] + +export enum ObjTypeName { + list = "list", + map = "map", + table = "table", + text = "text", +} + +export type Datatype = + "boolean" | + "str" | + "int" | + "uint" | + "f64" | + "null" | + "timestamp" | + "counter" | + "bytes" | + "map" | + "text" | + "list"; + +export type SyncHave = { + lastSync: Heads, + bloom: Uint8Array, +} + +export type DecodedSyncMessage = { + heads: Heads, + need: Heads, + have: SyncHave[] + changes: Change[] +} + +export type DecodedChange = { + actor: Actor, + seq: number + startOp: number, + time: number, + message: string | null, + deps: Heads, + hash: Hash, + ops: Op[] +} + +type PartialBy = Omit & Partial> +export type ChangeToEncode = PartialBy + +export type Op = { + action: string, + obj: ObjID, + key: string, + value?: string | number | boolean, + datatype?: string, + pred: string[], +} + +export type Patch = PutPatch | DelPatch | SpliceTextPatch | IncPatch | InsertPatch; + +export type PutPatch = { + action: 'put' + path: Prop[], + value: Value + conflict: boolean +} + +export type IncPatch = { + action: 'inc' + path: Prop[], + value: number +} + +export type DelPatch = { + action: 'del' + path: Prop[], + length?: number, +} + +export type SpliceTextPatch = { + action: 'splice' + path: Prop[], + value: string, +} + +export type InsertPatch = { + action: 'insert' + path: Prop[], + values: Value[], +} + +export function encodeChange(change: ChangeToEncode): Change; +export function create(text_v2: boolean, actor?: Actor): Automerge; +export function load(data: Uint8Array, text_v2: boolean, actor?: Actor): Automerge; +export function decodeChange(change: Change): DecodedChange; +export function initSyncState(): SyncState; +export function encodeSyncMessage(message: DecodedSyncMessage): SyncMessage; +export function decodeSyncMessage(msg: SyncMessage): DecodedSyncMessage; +export function encodeSyncState(state: SyncState): Uint8Array; +export function decodeSyncState(data: Uint8Array): SyncState; +export function exportSyncState(state: SyncState): JsSyncState; +export function importSyncState(state: JsSyncState): SyncState; + +export interface API { + create(text_v2: boolean, actor?: Actor): Automerge; + load(data: Uint8Array, text_v2: boolean, actor?: Actor): Automerge; + encodeChange(change: ChangeToEncode): Change; + decodeChange(change: Change): DecodedChange; + initSyncState(): SyncState; + encodeSyncMessage(message: DecodedSyncMessage): SyncMessage; + decodeSyncMessage(msg: SyncMessage): DecodedSyncMessage; + encodeSyncState(state: SyncState): Uint8Array; + decodeSyncState(data: Uint8Array): SyncState; + exportSyncState(state: SyncState): JsSyncState; + importSyncState(state: JsSyncState): SyncState; +} + +export class Automerge { + // change state + put(obj: ObjID, prop: Prop, value: Value, datatype?: Datatype): void; + putObject(obj: ObjID, prop: Prop, value: ObjType): ObjID; + insert(obj: ObjID, index: number, value: Value, datatype?: Datatype): void; + insertObject(obj: ObjID, index: number, value: ObjType): ObjID; + push(obj: ObjID, value: Value, datatype?: Datatype): void; + pushObject(obj: ObjID, value: ObjType): ObjID; + splice(obj: ObjID, start: number, delete_count: number, text?: string | Array): ObjID[] | undefined; + increment(obj: ObjID, prop: Prop, value: number): void; + delete(obj: ObjID, prop: Prop): void; + + // returns a single value - if there is a conflict return the winner + get(obj: ObjID, prop: Prop, heads?: Heads): Value | undefined; + getWithType(obj: ObjID, prop: Prop, heads?: Heads): FullValue | null; + // return all values in case of a conflict + getAll(obj: ObjID, arg: Prop, heads?: Heads): FullValueWithId[]; + keys(obj: ObjID, heads?: Heads): string[]; + text(obj: ObjID, heads?: Heads): string; + length(obj: ObjID, heads?: Heads): number; + materialize(obj?: ObjID, heads?: Heads, metadata?: unknown): MaterializeValue; + toJS(): MaterializeValue; + + // transactions + commit(message?: string, time?: number): Hash | null; + emptyChange(message?: string, time?: number): Hash; + merge(other: Automerge): Heads; + getActorId(): Actor; + pendingOps(): number; + rollback(): number; + + // patches + enablePatches(enable: boolean): boolean; + enableFreeze(enable: boolean): boolean; + registerDatatype(datatype: string, callback: Function): void; + popPatches(): Patch[]; + + // save and load to local store + save(): Uint8Array; + saveIncremental(): Uint8Array; + loadIncremental(data: Uint8Array): number; + + // sync over network + receiveSyncMessage(state: SyncState, message: SyncMessage): void; + generateSyncMessage(state: SyncState): SyncMessage | null; + + // low level change functions + applyChanges(changes: Change[]): void; + getChanges(have_deps: Heads): Change[]; + getChangeByHash(hash: Hash): Change | null; + getChangesAdded(other: Automerge): Change[]; + getHeads(): Heads; + getLastLocalChange(): Change | null; + getMissingDeps(heads?: Heads): Heads; + + // memory management + free(): void; // only needed if weak-refs are unsupported + clone(actor?: string): Automerge; // TODO - remove, this is dangerous + fork(actor?: string, heads?: Heads): Automerge; + + // dump internal state to console.log - for debugging + dump(): void; + + // experimental api can go here + applyPatches(obj: Doc, meta?: unknown, callback?: (patch: Array, before: Doc, after: Doc) => void): Doc; +} + +export interface JsSyncState { + sharedHeads: Heads; + lastSentHeads: Heads; + theirHeads: Heads | undefined; + theirHeed: Heads | undefined; + theirHave: SyncHave[] | undefined; + sentHashes: Heads; +} + +export class SyncState { + free(): void; + clone(): SyncState; + lastSentHeads: Heads; + sentHashes: Heads; + readonly sharedHeads: Heads; +} From a24d536d16f2adeea7bbdf094402665a80f400ab Mon Sep 17 00:00:00 2001 From: Alex Good Date: Sat, 4 Feb 2023 14:05:10 +0000 Subject: [PATCH 02/16] Move automerge::SequenceTree to automerge_wasm::SequenceTree The `SequenceTree` is only ever used in `automerge_wasm` so move it there. --- rust/automerge-wasm/Cargo.toml | 1 + rust/automerge-wasm/src/lib.rs | 1 + rust/automerge-wasm/src/observer.rs | 4 +- .../src/sequence_tree.rs | 81 +++---------------- rust/automerge/src/lib.rs | 3 - 5 files changed, 14 insertions(+), 76 deletions(-) rename rust/{automerge => automerge-wasm}/src/sequence_tree.rs (87%) diff --git a/rust/automerge-wasm/Cargo.toml b/rust/automerge-wasm/Cargo.toml index 3d2fafe4..b6055a7d 100644 --- a/rust/automerge-wasm/Cargo.toml +++ b/rust/automerge-wasm/Cargo.toml @@ -57,5 +57,6 @@ features = ["console"] [dev-dependencies] futures = "^0.1" +proptest = { version = "^1.0.0", default-features = false, features = ["std"] } wasm-bindgen-futures = "^0.4" wasm-bindgen-test = "^0.3" diff --git a/rust/automerge-wasm/src/lib.rs b/rust/automerge-wasm/src/lib.rs index b53bf3b9..09072ca7 100644 --- a/rust/automerge-wasm/src/lib.rs +++ b/rust/automerge-wasm/src/lib.rs @@ -41,6 +41,7 @@ use wasm_bindgen::JsCast; mod interop; mod observer; +mod sequence_tree; mod sync; mod value; diff --git a/rust/automerge-wasm/src/observer.rs b/rust/automerge-wasm/src/observer.rs index c0b462a6..2351c762 100644 --- a/rust/automerge-wasm/src/observer.rs +++ b/rust/automerge-wasm/src/observer.rs @@ -6,10 +6,12 @@ use crate::{ interop::{self, alloc, js_set}, TextRepresentation, }; -use automerge::{ObjId, OpObserver, Prop, ReadDoc, ScalarValue, SequenceTree, Value}; +use automerge::{ObjId, OpObserver, Prop, ReadDoc, ScalarValue, Value}; use js_sys::{Array, Object}; use wasm_bindgen::prelude::*; +use crate::sequence_tree::SequenceTree; + #[derive(Debug, Clone, Default)] pub(crate) struct Observer { enabled: bool, diff --git a/rust/automerge/src/sequence_tree.rs b/rust/automerge-wasm/src/sequence_tree.rs similarity index 87% rename from rust/automerge/src/sequence_tree.rs rename to rust/automerge-wasm/src/sequence_tree.rs index f95ceab3..91b183a2 100644 --- a/rust/automerge/src/sequence_tree.rs +++ b/rust/automerge-wasm/src/sequence_tree.rs @@ -5,10 +5,10 @@ use std::{ }; pub(crate) const B: usize = 16; -pub type SequenceTree = SequenceTreeInternal; +pub(crate) type SequenceTree = SequenceTreeInternal; #[derive(Clone, Debug)] -pub struct SequenceTreeInternal { +pub(crate) struct SequenceTreeInternal { root_node: Option>, } @@ -24,22 +24,17 @@ where T: Clone + Debug, { /// Construct a new, empty, sequence. - pub fn new() -> Self { + pub(crate) fn new() -> Self { Self { root_node: None } } /// Get the length of the sequence. - pub fn len(&self) -> usize { + pub(crate) fn len(&self) -> usize { self.root_node.as_ref().map_or(0, |n| n.len()) } - /// Check if the sequence is empty. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - /// Create an iterator through the sequence. - pub fn iter(&self) -> Iter<'_, T> { + pub(crate) fn iter(&self) -> Iter<'_, T> { Iter { inner: self, index: 0, @@ -51,7 +46,7 @@ where /// # Panics /// /// Panics if `index > len`. - pub fn insert(&mut self, index: usize, element: T) { + pub(crate) fn insert(&mut self, index: usize, element: T) { let old_len = self.len(); if let Some(root) = self.root_node.as_mut() { #[cfg(debug_assertions)] @@ -94,27 +89,22 @@ where } /// Push the `element` onto the back of the sequence. - pub fn push(&mut self, element: T) { + pub(crate) fn push(&mut self, element: T) { let l = self.len(); self.insert(l, element) } /// Get the `element` at `index` in the sequence. - pub fn get(&self, index: usize) -> Option<&T> { + pub(crate) fn get(&self, index: usize) -> Option<&T> { self.root_node.as_ref().and_then(|n| n.get(index)) } - /// Get the `element` at `index` in the sequence. - pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { - self.root_node.as_mut().and_then(|n| n.get_mut(index)) - } - /// Removes the element at `index` from the sequence. /// /// # Panics /// /// Panics if `index` is out of bounds. - pub fn remove(&mut self, index: usize) -> T { + pub(crate) fn remove(&mut self, index: usize) -> T { if let Some(root) = self.root_node.as_mut() { #[cfg(debug_assertions)] let len = root.check(); @@ -135,15 +125,6 @@ where panic!("remove from empty tree") } } - - /// Update the `element` at `index` in the sequence, returning the old value. - /// - /// # Panics - /// - /// Panics if `index > len` - pub fn set(&mut self, index: usize, element: T) -> T { - self.root_node.as_mut().unwrap().set(index, element) - } } impl SequenceTreeNode @@ -432,30 +413,6 @@ where assert!(self.is_full()); } - pub(crate) fn set(&mut self, index: usize, element: T) -> T { - if self.is_leaf() { - let old_element = self.elements.get_mut(index).unwrap(); - mem::replace(old_element, element) - } else { - let mut cumulative_len = 0; - for (child_index, child) in self.children.iter_mut().enumerate() { - match (cumulative_len + child.len()).cmp(&index) { - Ordering::Less => { - cumulative_len += child.len() + 1; - } - Ordering::Equal => { - let old_element = self.elements.get_mut(child_index).unwrap(); - return mem::replace(old_element, element); - } - Ordering::Greater => { - return child.set(index - cumulative_len, element); - } - } - } - panic!("Invalid index to set: {} but len was {}", index, self.len()) - } - } - pub(crate) fn get(&self, index: usize) -> Option<&T> { if self.is_leaf() { return self.elements.get(index); @@ -475,26 +432,6 @@ where } None } - - pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut T> { - if self.is_leaf() { - return self.elements.get_mut(index); - } else { - let mut cumulative_len = 0; - for (child_index, child) in self.children.iter_mut().enumerate() { - match (cumulative_len + child.len()).cmp(&index) { - Ordering::Less => { - cumulative_len += child.len() + 1; - } - Ordering::Equal => return self.elements.get_mut(child_index), - Ordering::Greater => { - return child.get_mut(index - cumulative_len); - } - } - } - } - None - } } impl Default for SequenceTreeInternal diff --git a/rust/automerge/src/lib.rs b/rust/automerge/src/lib.rs index fb8a3793..cbb535af 100644 --- a/rust/automerge/src/lib.rs +++ b/rust/automerge/src/lib.rs @@ -264,7 +264,6 @@ mod op_tree; mod parents; mod query; mod read; -mod sequence_tree; mod storage; pub mod sync; pub mod transaction; @@ -294,8 +293,6 @@ pub use op_observer::Patch; pub use op_observer::VecOpObserver; pub use parents::{Parent, Parents}; pub use read::ReadDoc; -#[doc(hidden)] -pub use sequence_tree::SequenceTree; pub use types::{ActorId, ChangeHash, ObjType, OpType, ParseChangeHashError, Prop, TextEncoding}; pub use value::{ScalarValue, Value}; pub use values::Values; From 11f063cbfe71bb81d849baca89f5eba8d441d594 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Thu, 9 Feb 2023 11:06:08 +0000 Subject: [PATCH 03/16] Remove nightly from CI --- .github/workflows/ci.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c2d469d5..bfa31bd5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -137,8 +137,6 @@ jobs: matrix: toolchain: - 1.66.0 - - nightly - continue-on-error: ${{ matrix.toolchain == 'nightly' }} steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 From 2cd7427f35e3b9b4a6b4d22d21dd083872015b57 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 17 Jan 2023 14:51:02 -0700 Subject: [PATCH 04/16] Use our leb128 parser for values This ensures that values in automerge documents are encoded correctly, and that no extra data is smuggled in any LEB fields. --- .../src/columnar/column_range/value.rs | 62 +++++++++--------- rust/automerge/src/columnar/encoding.rs | 2 + ...counter_value_has_incorrect_meta.automerge | Bin 0 -> 63 bytes .../fixtures/counter_value_is_ok.automerge | Bin 0 -> 63 bytes .../counter_value_is_overlong.automerge | Bin 0 -> 63 bytes rust/automerge/tests/test.rs | 14 ++++ 6 files changed, 48 insertions(+), 30 deletions(-) create mode 100644 rust/automerge/tests/fixtures/counter_value_has_incorrect_meta.automerge create mode 100644 rust/automerge/tests/fixtures/counter_value_is_ok.automerge create mode 100644 rust/automerge/tests/fixtures/counter_value_is_overlong.automerge diff --git a/rust/automerge/src/columnar/column_range/value.rs b/rust/automerge/src/columnar/column_range/value.rs index 43f63437..03a5aa60 100644 --- a/rust/automerge/src/columnar/column_range/value.rs +++ b/rust/automerge/src/columnar/column_range/value.rs @@ -4,10 +4,15 @@ use crate::{ columnar::{ encoding::{ leb128::{lebsize, ulebsize}, - raw, DecodeColumnError, RawBytes, RawDecoder, RawEncoder, RleDecoder, RleEncoder, Sink, + raw, DecodeColumnError, DecodeError, RawBytes, RawDecoder, RawEncoder, RleDecoder, + RleEncoder, Sink, }, SpliceError, }, + storage::parse::{ + leb128::{leb128_i64, leb128_u64}, + Input, ParseResult, + }, ScalarValue, }; @@ -217,18 +222,8 @@ impl<'a> Iterator for ValueIter<'a> { ValueType::Null => Some(Ok(ScalarValue::Null)), ValueType::True => Some(Ok(ScalarValue::Boolean(true))), ValueType::False => Some(Ok(ScalarValue::Boolean(false))), - ValueType::Uleb => self.parse_raw(val_meta, |mut bytes| { - let val = leb128::read::unsigned(&mut bytes).map_err(|e| { - DecodeColumnError::invalid_value("value", e.to_string()) - })?; - Ok(ScalarValue::Uint(val)) - }), - ValueType::Leb => self.parse_raw(val_meta, |mut bytes| { - let val = leb128::read::signed(&mut bytes).map_err(|e| { - DecodeColumnError::invalid_value("value", e.to_string()) - })?; - Ok(ScalarValue::Int(val)) - }), + ValueType::Uleb => self.parse_input(val_meta, leb128_u64), + ValueType::Leb => self.parse_input(val_meta, leb128_i64), ValueType::String => self.parse_raw(val_meta, |bytes| { let val = std::str::from_utf8(bytes) .map_err(|e| DecodeColumnError::invalid_value("value", e.to_string()))? @@ -250,17 +245,11 @@ impl<'a> Iterator for ValueIter<'a> { let val = f64::from_le_bytes(raw); Ok(ScalarValue::F64(val)) }), - ValueType::Counter => self.parse_raw(val_meta, |mut bytes| { - let val = leb128::read::signed(&mut bytes).map_err(|e| { - DecodeColumnError::invalid_value("value", e.to_string()) - })?; - Ok(ScalarValue::Counter(val.into())) + ValueType::Counter => self.parse_input(val_meta, |input| { + leb128_i64(input).map(|(i, n)| (i, ScalarValue::Counter(n.into()))) }), - ValueType::Timestamp => self.parse_raw(val_meta, |mut bytes| { - let val = leb128::read::signed(&mut bytes).map_err(|e| { - DecodeColumnError::invalid_value("value", e.to_string()) - })?; - Ok(ScalarValue::Timestamp(val)) + ValueType::Timestamp => self.parse_input(val_meta, |input| { + leb128_i64(input).map(|(i, n)| (i, ScalarValue::Timestamp(n))) }), ValueType::Unknown(code) => self.parse_raw(val_meta, |bytes| { Ok(ScalarValue::Unknown { @@ -284,8 +273,8 @@ impl<'a> Iterator for ValueIter<'a> { } impl<'a> ValueIter<'a> { - fn parse_raw Result>( - &mut self, + fn parse_raw<'b, R, F: Fn(&'b [u8]) -> Result>( + &'b mut self, meta: ValueMeta, f: F, ) -> Option> { @@ -298,11 +287,24 @@ impl<'a> ValueIter<'a> { } Ok(bytes) => bytes, }; - let val = match f(raw) { - Ok(v) => v, - Err(e) => return Some(Err(e)), - }; - Some(Ok(val)) + Some(f(raw)) + } + + fn parse_input<'b, R, F: Fn(Input<'b>) -> ParseResult<'b, R, DecodeError>>( + &'b mut self, + meta: ValueMeta, + f: F, + ) -> Option> + where + R: Into, + { + self.parse_raw(meta, |raw| match f(Input::new(raw)) { + Err(e) => Err(DecodeColumnError::invalid_value("value", e.to_string())), + Ok((i, _)) if !i.is_empty() => { + Err(DecodeColumnError::invalid_value("value", "extra bytes")) + } + Ok((_, v)) => Ok(v.into()), + }) } pub(crate) fn done(&self) -> bool { diff --git a/rust/automerge/src/columnar/encoding.rs b/rust/automerge/src/columnar/encoding.rs index bbdb34a8..c9435448 100644 --- a/rust/automerge/src/columnar/encoding.rs +++ b/rust/automerge/src/columnar/encoding.rs @@ -46,6 +46,8 @@ pub(crate) enum DecodeError { FromInt(#[from] std::num::TryFromIntError), #[error("bad leb128")] BadLeb(#[from] ::leb128::read::Error), + #[error(transparent)] + BadLeb128(#[from] crate::storage::parse::leb128::Error), #[error("attempted to allocate {attempted} which is larger than the maximum of {maximum}")] OverlargeAllocation { attempted: usize, maximum: usize }, #[error("invalid string encoding")] diff --git a/rust/automerge/tests/fixtures/counter_value_has_incorrect_meta.automerge b/rust/automerge/tests/fixtures/counter_value_has_incorrect_meta.automerge new file mode 100644 index 0000000000000000000000000000000000000000..2290b446ca661f302f6591c522a6653ba0be54a6 GIT binary patch literal 63 zcmZq8_iDCFPJPB`${^6qmb+L*-z{NbN`A*m!H-iI8Mkb^bm5T!0|T2Vvk9XUQy5b? TQvp*wVH2@I&u}A*O5KaD{l&S)MXnSh`0lxRq(Bd!v00tEUGyy^a VRsvT7Z~}h;VF7;ue<;uoe*j$F7aafq literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/fixtures/counter_value_is_overlong.automerge b/rust/automerge/tests/fixtures/counter_value_is_overlong.automerge new file mode 100644 index 0000000000000000000000000000000000000000..831346f7f4109e2f292e502e13b326ca2485b351 GIT binary patch literal 63 zcmZq8_iD~Rd#9GsltG}IEqAeszFWe=l>CmBf*+?aGH%&+>B1ue1_m}!W)nsyrZA>( TrUIsV#ze+?#(Iql_4Nz@=B*VY literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/test.rs b/rust/automerge/tests/test.rs index ca6c64c0..191ce2f9 100644 --- a/rust/automerge/tests/test.rs +++ b/rust/automerge/tests/test.rs @@ -1412,6 +1412,20 @@ fn fuzz_crashers() { } } +fn fixture(name: &str) -> Vec { + fs::read("./tests/fixtures/".to_owned() + name).unwrap() +} + +#[test] +fn overlong_leb() { + // the value metadata says "2", but the LEB is only 1-byte long and there's an extra 0 + assert!(Automerge::load(&fixture("counter_value_has_incorrect_meta.automerge")).is_err()); + // the LEB is overlong (using 2 bytes where one would have sufficed) + assert!(Automerge::load(&fixture("counter_value_is_overlong.automerge")).is_err()); + // the LEB is correct + assert!(Automerge::load(&fixture("counter_value_is_ok.automerge")).is_ok()); +} + #[test] fn negative_64() { let mut doc = Automerge::new(); From 5e82dbc3c83c2336ca675ba8f167db5dba9b17cb Mon Sep 17 00:00:00 2001 From: Orion Henry Date: Mon, 13 Feb 2023 21:17:27 -0600 Subject: [PATCH 05/16] rework how skip works to push the logic into node --- javascript/test/basic_test.ts | 16 +++++ rust/automerge/src/op_tree/node.rs | 68 +++++++++++-------- rust/automerge/src/query/prop.rs | 47 ++----------- rust/automerge/src/query/seek_op.rs | 39 ++--------- .../automerge/src/query/seek_op_with_patch.rs | 38 +---------- 5 files changed, 67 insertions(+), 141 deletions(-) diff --git a/javascript/test/basic_test.ts b/javascript/test/basic_test.ts index 5aa1ac34..0e30dc7c 100644 --- a/javascript/test/basic_test.ts +++ b/javascript/test/basic_test.ts @@ -58,6 +58,22 @@ describe("Automerge", () => { }) }) + it("should be able to insert and delete a large number of properties", () => { + let doc = Automerge.init() + + doc = Automerge.change(doc, doc => { + doc['k1'] = true; + }); + + for (let idx = 1; idx <= 200; idx++) { + doc = Automerge.change(doc, doc => { + delete doc['k' + idx]; + doc['k' + (idx + 1)] = true; + assert(Object.keys(doc).length == 1) + }); + } + }) + it("can detect an automerge doc with isAutomerge()", () => { const doc1 = Automerge.from({ sub: { object: true } }) assert(Automerge.isAutomerge(doc1)) diff --git a/rust/automerge/src/op_tree/node.rs b/rust/automerge/src/op_tree/node.rs index ea7fbf48..8f2de662 100644 --- a/rust/automerge/src/op_tree/node.rs +++ b/rust/automerge/src/op_tree/node.rs @@ -27,50 +27,67 @@ impl OpTreeNode { } } + fn search_element<'a, 'b: 'a, Q>( + &'b self, + query: &mut Q, + m: &OpSetMetadata, + ops: &'a [Op], + index: usize, + ) -> bool + where + Q: TreeQuery<'a>, + { + if let Some(e) = self.elements.get(index) { + if query.query_element_with_metadata(&ops[*e], m) == QueryResult::Finish { + return true; + } + } + false + } + pub(crate) fn search<'a, 'b: 'a, Q>( &'b self, query: &mut Q, m: &OpSetMetadata, ops: &'a [Op], - skip: Option, + mut skip: Option, ) -> bool where Q: TreeQuery<'a>, { if self.is_leaf() { - let skip = skip.unwrap_or(0); - for e in self.elements.iter().skip(skip) { + for e in self.elements.iter().skip(skip.unwrap_or(0)) { if query.query_element_with_metadata(&ops[*e], m) == QueryResult::Finish { return true; } } false } else { - let mut skip = skip.unwrap_or(0); for (child_index, child) in self.children.iter().enumerate() { - match skip.cmp(&child.len()) { - Ordering::Greater => { - // not in this child at all - // take off the number of elements in the child as well as the next element - skip -= child.len() + 1; + match skip { + Some(n) if n > child.len() => { + skip = Some(n - child.len() - 1); } - Ordering::Equal => { - // just try the element - skip -= child.len(); - if let Some(e) = self.elements.get(child_index) { - if query.query_element_with_metadata(&ops[*e], m) == QueryResult::Finish - { - return true; - } + Some(n) if n == child.len() => { + skip = None; + if self.search_element(query, m, ops, child_index) { + return true; } } - Ordering::Less => { + Some(n) => { + if child.search(query, m, ops, Some(n)) { + return true; + } + skip = Some(0); // important to not be None so we never call query_node again + if self.search_element(query, m, ops, child_index) { + return true; + } + } + None => { // descend and try find it match query.query_node_with_metadata(child, m, ops) { QueryResult::Descend => { - // search in the child node, passing in the number of items left to - // skip - if child.search(query, m, ops, Some(skip)) { + if child.search(query, m, ops, None) { return true; } } @@ -78,14 +95,9 @@ impl OpTreeNode { QueryResult::Next => (), QueryResult::Skip(_) => panic!("had skip from non-root node"), } - if let Some(e) = self.elements.get(child_index) { - if query.query_element_with_metadata(&ops[*e], m) == QueryResult::Finish - { - return true; - } + if self.search_element(query, m, ops, child_index) { + return true; } - // reset the skip to zero so we continue iterating normally - skip = 0; } } } diff --git a/rust/automerge/src/query/prop.rs b/rust/automerge/src/query/prop.rs index f6062ec6..d2a11361 100644 --- a/rust/automerge/src/query/prop.rs +++ b/rust/automerge/src/query/prop.rs @@ -1,6 +1,6 @@ use crate::op_tree::{OpSetMetadata, OpTreeNode}; use crate::query::{binary_search_by, QueryResult, TreeQuery}; -use crate::types::{Key, ListEncoding, Op}; +use crate::types::{Key, Op}; use std::fmt::Debug; #[derive(Debug, Clone, PartialEq)] @@ -9,15 +9,6 @@ pub(crate) struct Prop<'a> { pub(crate) ops: Vec<&'a Op>, pub(crate) ops_pos: Vec, pub(crate) pos: usize, - start: Option, -} - -#[derive(Debug, Clone, PartialEq)] -struct Start { - /// The index to start searching for in the optree - idx: usize, - /// The total length of the optree - optree_len: usize, } impl<'a> Prop<'a> { @@ -27,7 +18,6 @@ impl<'a> Prop<'a> { ops: vec![], ops_pos: vec![], pos: 0, - start: None, } } } @@ -39,38 +29,9 @@ impl<'a> TreeQuery<'a> for Prop<'a> { m: &OpSetMetadata, ops: &[Op], ) -> QueryResult { - if let Some(Start { - idx: start, - optree_len, - }) = self.start - { - if self.pos + child.len() >= start { - // skip empty nodes - if child.index.visible_len(ListEncoding::default()) == 0 { - if self.pos + child.len() >= optree_len { - self.pos = optree_len; - QueryResult::Finish - } else { - self.pos += child.len(); - QueryResult::Next - } - } else { - QueryResult::Descend - } - } else { - self.pos += child.len(); - QueryResult::Next - } - } else { - // in the root node find the first op position for the key - let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.key)); - self.start = Some(Start { - idx: start, - optree_len: child.len(), - }); - self.pos = start; - QueryResult::Skip(start) - } + let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.key)); + self.pos = start; + QueryResult::Skip(start) } fn query_element(&mut self, op: &'a Op) -> QueryResult { diff --git a/rust/automerge/src/query/seek_op.rs b/rust/automerge/src/query/seek_op.rs index 22d1f58d..2ed875d2 100644 --- a/rust/automerge/src/query/seek_op.rs +++ b/rust/automerge/src/query/seek_op.rs @@ -1,6 +1,6 @@ use crate::op_tree::{OpSetMetadata, OpTreeNode}; use crate::query::{binary_search_by, QueryResult, TreeQuery}; -use crate::types::{Key, ListEncoding, Op, HEAD}; +use crate::types::{Key, Op, HEAD}; use std::cmp::Ordering; use std::fmt::Debug; @@ -14,8 +14,6 @@ pub(crate) struct SeekOp<'a> { pub(crate) succ: Vec, /// whether a position has been found found: bool, - /// The found start position of the key if there is one yet (for map objects). - start: Option, } impl<'a> SeekOp<'a> { @@ -25,7 +23,6 @@ impl<'a> SeekOp<'a> { succ: vec![], pos: 0, found: false, - start: None, } } @@ -72,37 +69,9 @@ impl<'a> TreeQuery<'a> for SeekOp<'a> { } } Key::Map(_) => { - if let Some(start) = self.start { - if self.pos + child.len() >= start { - // skip empty nodes - if child.index.visible_len(ListEncoding::List) == 0 { - let child_contains_key = - child.elements.iter().any(|e| ops[*e].key == self.op.key); - if !child_contains_key { - // If we are in a node which has no visible ops, but none of the - // elements of the node match the key of the op, then we must have - // finished processing and so we can just return. - // See https://github.com/automerge/automerge-rs/pull/480 - QueryResult::Finish - } else { - // Otherwise, we need to proceed to the next node - self.pos += child.len(); - QueryResult::Next - } - } else { - QueryResult::Descend - } - } else { - self.pos += child.len(); - QueryResult::Next - } - } else { - // in the root node find the first op position for the key - let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.op.key)); - self.start = Some(start); - self.pos = start; - QueryResult::Skip(start) - } + let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.op.key)); + self.pos = start; + QueryResult::Skip(start) } } } diff --git a/rust/automerge/src/query/seek_op_with_patch.rs b/rust/automerge/src/query/seek_op_with_patch.rs index 7cacb032..cd30f5bb 100644 --- a/rust/automerge/src/query/seek_op_with_patch.rs +++ b/rust/automerge/src/query/seek_op_with_patch.rs @@ -16,8 +16,6 @@ pub(crate) struct SeekOpWithPatch<'a> { last_seen: Option, pub(crate) values: Vec<&'a Op>, pub(crate) had_value_before: bool, - /// The found start position of the key if there is one yet (for map objects). - start: Option, } impl<'a> SeekOpWithPatch<'a> { @@ -33,7 +31,6 @@ impl<'a> SeekOpWithPatch<'a> { last_seen: None, values: vec![], had_value_before: false, - start: None, } } @@ -132,38 +129,9 @@ impl<'a> TreeQuery<'a> for SeekOpWithPatch<'a> { // Updating a map: operations appear in sorted order by key Key::Map(_) => { - if let Some(start) = self.start { - if self.pos + child.len() >= start { - // skip empty nodes - if child.index.visible_len(self.encoding) == 0 { - let child_contains_key = - child.elements.iter().any(|e| ops[*e].key == self.op.key); - if !child_contains_key { - // If we are in a node which has no visible ops, but none of the - // elements of the node match the key of the op, then we must have - // finished processing and so we can just return. - // See https://github.com/automerge/automerge-rs/pull/480 - QueryResult::Finish - } else { - self.pos += child.len(); - QueryResult::Next - } - } else { - QueryResult::Descend - } - } else { - self.pos += child.len(); - QueryResult::Next - } - } else { - // in the root node find the first op position for the key - // Search for the place where we need to insert the new operation. First find the - // first op with a key >= the key we're updating - let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.op.key)); - self.start = Some(start); - self.pos = start; - QueryResult::Skip(start) - } + let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.op.key)); + self.pos = start; + QueryResult::Skip(start) } } } From 9271b20cf5442369f21dec43ebeed097e8092da8 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Tue, 14 Feb 2023 16:24:25 +0000 Subject: [PATCH 06/16] Correct logic when skip = B and fix formatting A few tests were failing which exposed the fact that if skip is `B` (the out factor of the OpTree) then we set `skip = None` and this causes us to attempt to return `Skip` in a non root node. I ported the failing test from JS to Rust and fixed the problem. I also fixed the formatting issues. --- javascript/test/basic_test.ts | 10 +++---- rust/automerge-wasm/test/test.ts | 2 +- rust/automerge/src/op_tree/node.rs | 4 +-- rust/automerge/src/sync.rs | 45 ++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/javascript/test/basic_test.ts b/javascript/test/basic_test.ts index 0e30dc7c..e34484c4 100644 --- a/javascript/test/basic_test.ts +++ b/javascript/test/basic_test.ts @@ -62,15 +62,15 @@ describe("Automerge", () => { let doc = Automerge.init() doc = Automerge.change(doc, doc => { - doc['k1'] = true; - }); + doc["k1"] = true + }) for (let idx = 1; idx <= 200; idx++) { doc = Automerge.change(doc, doc => { - delete doc['k' + idx]; - doc['k' + (idx + 1)] = true; + delete doc["k" + idx] + doc["k" + (idx + 1)] = true assert(Object.keys(doc).length == 1) - }); + }) } }) diff --git a/rust/automerge-wasm/test/test.ts b/rust/automerge-wasm/test/test.ts index 56aaae74..bb4f71e3 100644 --- a/rust/automerge-wasm/test/test.ts +++ b/rust/automerge-wasm/test/test.ts @@ -1447,7 +1447,7 @@ describe('Automerge', () => { sync(n1, n2, s1, s2) // Having n3's last change concurrent to the last sync heads forces us into the slower code path - const change3 = n2.getLastLocalChange() + const change3 = n3.getLastLocalChange() if (change3 === null) throw new RangeError("no local change") n2.applyChanges([change3]) n1.put("_root", "n1", "final"); n1.commit("", 0) diff --git a/rust/automerge/src/op_tree/node.rs b/rust/automerge/src/op_tree/node.rs index 8f2de662..ed1b7646 100644 --- a/rust/automerge/src/op_tree/node.rs +++ b/rust/automerge/src/op_tree/node.rs @@ -69,7 +69,7 @@ impl OpTreeNode { skip = Some(n - child.len() - 1); } Some(n) if n == child.len() => { - skip = None; + skip = Some(0); // important to not be None so we never call query_node again if self.search_element(query, m, ops, child_index) { return true; } @@ -78,7 +78,7 @@ impl OpTreeNode { if child.search(query, m, ops, Some(n)) { return true; } - skip = Some(0); // important to not be None so we never call query_node again + skip = Some(0); // important to not be None so we never call query_node again if self.search_element(query, m, ops, child_index) { return true; } diff --git a/rust/automerge/src/sync.rs b/rust/automerge/src/sync.rs index d3b6b3fa..d6dc2580 100644 --- a/rust/automerge/src/sync.rs +++ b/rust/automerge/src/sync.rs @@ -887,6 +887,51 @@ mod tests { assert_eq!(doc2.get_heads(), all_heads); } + #[test] + fn should_handle_lots_of_branching_and_merging() { + let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("01234567").unwrap()); + let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("89abcdef").unwrap()); + let mut doc3 = crate::AutoCommit::new().with_actor(ActorId::try_from("fedcba98").unwrap()); + let mut s1 = State::new(); + let mut s2 = State::new(); + + doc1.put(crate::ROOT, "x", 0).unwrap(); + let change1 = doc1.get_last_local_change().unwrap().clone(); + + doc2.apply_changes([change1.clone()]).unwrap(); + doc3.apply_changes([change1]).unwrap(); + + doc3.put(crate::ROOT, "x", 1).unwrap(); + + //// - n1c1 <------ n1c2 <------ n1c3 <-- etc. <-- n1c20 <------ n1c21 + //// / \/ \/ \/ + //// / /\ /\ /\ + //// c0 <---- n2c1 <------ n2c2 <------ n2c3 <-- etc. <-- n2c20 <------ n2c21 + //// \ / + //// ---------------------------------------------- n3c1 <----- + for i in 1..20 { + doc1.put(crate::ROOT, "n1", i).unwrap(); + doc2.put(crate::ROOT, "n2", i).unwrap(); + let change1 = doc1.get_last_local_change().unwrap().clone(); + let change2 = doc2.get_last_local_change().unwrap().clone(); + doc1.apply_changes([change2.clone()]).unwrap(); + doc2.apply_changes([change1]).unwrap(); + } + + sync(&mut doc1, &mut doc2, &mut s1, &mut s2); + + //// Having n3's last change concurrent to the last sync heads forces us into the slower code path + let change3 = doc3.get_last_local_change().unwrap().clone(); + doc2.apply_changes([change3]).unwrap(); + + doc1.put(crate::ROOT, "n1", "final").unwrap(); + doc2.put(crate::ROOT, "n1", "final").unwrap(); + + sync(&mut doc1, &mut doc2, &mut s1, &mut s2); + + assert_eq!(doc1.get_heads(), doc2.get_heads()); + } + fn sync( a: &mut crate::AutoCommit, b: &mut crate::AutoCommit, From c92d042c87eb724e4878a4df0f8d31177c410c01 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Tue, 14 Feb 2023 17:25:25 +0000 Subject: [PATCH 07/16] @automerge/automerge-wasm@0.1.24 and @automerge/automerge@2.0.2-alpha.2 --- javascript/package.json | 4 ++-- rust/automerge-wasm/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/javascript/package.json b/javascript/package.json index 8712920c..e39f398a 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -4,7 +4,7 @@ "Orion Henry ", "Martin Kleppmann" ], - "version": "2.0.2-alpha.1", + "version": "2.0.2-alpha.2", "description": "Javascript implementation of automerge, backed by @automerge/automerge-wasm", "homepage": "https://github.com/automerge/automerge-rs/tree/main/wrappers/javascript", "repository": "github:automerge/automerge-rs", @@ -47,7 +47,7 @@ "typescript": "^4.9.4" }, "dependencies": { - "@automerge/automerge-wasm": "0.1.24", + "@automerge/automerge-wasm": "0.1.25", "uuid": "^9.0.0" } } diff --git a/rust/automerge-wasm/package.json b/rust/automerge-wasm/package.json index 57354ce1..80b39fd4 100644 --- a/rust/automerge-wasm/package.json +++ b/rust/automerge-wasm/package.json @@ -8,7 +8,7 @@ "description": "wasm-bindgen bindings to the automerge rust implementation", "homepage": "https://github.com/automerge/automerge-rs/tree/main/automerge-wasm", "repository": "github:automerge/automerge-rs", - "version": "0.1.24", + "version": "0.1.25", "license": "MIT", "files": [ "README.md", From 1425af43cdcd61295e0e65bf47fbce0076353682 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Tue, 14 Feb 2023 19:47:53 +0000 Subject: [PATCH 08/16] @automerge/automerge@2.0.2 --- javascript/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/package.json b/javascript/package.json index e39f398a..79309907 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -4,7 +4,7 @@ "Orion Henry ", "Martin Kleppmann" ], - "version": "2.0.2-alpha.2", + "version": "2.0.2", "description": "Javascript implementation of automerge, backed by @automerge/automerge-wasm", "homepage": "https://github.com/automerge/automerge-rs/tree/main/wrappers/javascript", "repository": "github:automerge/automerge-rs", From 407faefa6e838abe0bd8526716c98eab592aa123 Mon Sep 17 00:00:00 2001 From: Philip Schatz <253202+philschatz@users.noreply.github.com> Date: Wed, 15 Feb 2023 03:23:02 -0600 Subject: [PATCH 09/16] A few setup fixes (#529) * include deno in dependencies * install javascript dependencies * remove redundant operation --- README.md | 3 +++ flake.nix | 1 + rust/automerge/src/automerge.rs | 1 - 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 94e1bbb8..76d48ddd 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,9 @@ brew install cmake node cmocka # install yarn npm install --global yarn +# install javascript dependencies +yarn --cwd ./javascript + # install rust dependencies cargo install wasm-bindgen-cli wasm-opt cargo-deny diff --git a/flake.nix b/flake.nix index 4f9ba1fe..37835738 100644 --- a/flake.nix +++ b/flake.nix @@ -54,6 +54,7 @@ nodejs yarn + deno # c deps cmake diff --git a/rust/automerge/src/automerge.rs b/rust/automerge/src/automerge.rs index 128d4418..09c3cc9d 100644 --- a/rust/automerge/src/automerge.rs +++ b/rust/automerge/src/automerge.rs @@ -897,7 +897,6 @@ impl Automerge { .add_change(&change, actor_index) .expect("Change's deps should already be in the document"); - self.history_index.insert(change.hash(), history_index); self.history.push(change); history_index From 8de2fa9bd49e1bf04f2a864b3a57f911419a86ba Mon Sep 17 00:00:00 2001 From: Jason Kankiewicz Date: Sat, 25 Feb 2023 10:47:00 -0800 Subject: [PATCH 10/16] C API 2 (#530) The AMvalue union, AMlistItem struct, AMmapItem struct, and AMobjItem struct are gone, replaced by the AMitem struct. The AMchangeHashes, AMchanges, AMlistItems, AMmapItems, AMobjItems, AMstrs, and AMsyncHaves iterators are gone, replaced by the AMitems iterator. The AMitem struct is opaque, getting and setting values is now achieved exclusively through function calls. The AMitemsNext(), AMitemsPrev(), and AMresultItem() functions return a pointer to an AMitem struct so you ultimately get the same thing whether you're iterating over a sequence or calling AMmapGet() or AMlistGet(). Calling AMitemResult() on an AMitem struct will produce a new AMresult struct referencing its storage so now the AMresult struct for an iterator can be subsequently freed without affecting the AMitem structs that were filtered out of it. The storage for a set of AMitem structs can be recombined into a single AMresult struct by passing pointers to their corresponding AMresult structs to AMresultCat(). For C/C++ programmers, I've added AMstrCmp(), AMstrdup(), AM{idxType,objType,status,valType}ToString() and AM{idxType,objType,status,valType}FromString(). It's also now possible to pass arbitrary parameters through AMstack{Item,Items,Result}() to a callback function. --- rust/automerge-c/.clang-format | 250 +++ rust/automerge-c/.gitignore | 8 +- rust/automerge-c/CMakeLists.txt | 344 ++- rust/automerge-c/Cargo.toml | 4 +- rust/automerge-c/README.md | 197 +- rust/automerge-c/cbindgen.toml | 20 +- rust/automerge-c/cmake/Cargo.toml.in | 22 + rust/automerge-c/cmake/cbindgen.toml.in | 48 + rust/automerge-c/cmake/config.h.in | 31 +- .../cmake/enum-string-functions-gen.cmake | 183 ++ ...replace.cmake => file-regex-replace.cmake} | 4 +- .../{file_touch.cmake => file-touch.cmake} | 4 +- rust/automerge-c/docs/CMakeLists.txt | 35 + rust/automerge-c/{ => docs}/img/brandmark.png | Bin rust/automerge-c/examples/CMakeLists.txt | 20 +- rust/automerge-c/examples/README.md | 2 +- rust/automerge-c/examples/quickstart.c | 195 +- .../include/automerge-c/utils/result.h | 30 + .../include/automerge-c/utils/stack.h | 130 ++ .../automerge-c/utils/stack_callback_data.h | 53 + .../include/automerge-c/utils/string.h | 29 + rust/automerge-c/src/CMakeLists.txt | 250 --- rust/automerge-c/src/actor_id.rs | 84 +- rust/automerge-c/src/byte_span.rs | 146 +- rust/automerge-c/src/change.rs | 148 +- rust/automerge-c/src/change_hashes.rs | 400 ---- rust/automerge-c/src/changes.rs | 399 ---- rust/automerge-c/src/doc.rs | 607 +++-- rust/automerge-c/src/doc/list.rs | 555 ++--- rust/automerge-c/src/doc/list/item.rs | 97 - rust/automerge-c/src/doc/list/items.rs | 348 --- rust/automerge-c/src/doc/map.rs | 324 +-- rust/automerge-c/src/doc/map/item.rs | 98 - rust/automerge-c/src/doc/map/items.rs | 340 --- rust/automerge-c/src/doc/utils.rs | 27 +- rust/automerge-c/src/index.rs | 84 + rust/automerge-c/src/item.rs | 1963 ++++++++++++++++ rust/automerge-c/src/items.rs | 401 ++++ rust/automerge-c/src/lib.rs | 9 +- rust/automerge-c/src/obj.rs | 86 +- rust/automerge-c/src/obj/item.rs | 73 - rust/automerge-c/src/obj/items.rs | 341 --- rust/automerge-c/src/result.rs | 1039 ++++----- rust/automerge-c/src/result_stack.rs | 156 -- rust/automerge-c/src/strs.rs | 359 --- rust/automerge-c/src/sync.rs | 2 +- rust/automerge-c/src/sync/have.rs | 25 +- rust/automerge-c/src/sync/haves.rs | 378 ---- rust/automerge-c/src/sync/message.rs | 114 +- rust/automerge-c/src/sync/state.rs | 149 +- rust/automerge-c/src/utils/result.c | 33 + rust/automerge-c/src/utils/stack.c | 106 + .../src/utils/stack_callback_data.c | 9 + rust/automerge-c/src/utils/string.c | 46 + rust/automerge-c/test/CMakeLists.txt | 44 +- rust/automerge-c/test/actor_id_tests.c | 145 +- rust/automerge-c/test/base_state.c | 17 + rust/automerge-c/test/base_state.h | 39 + rust/automerge-c/test/byte_span_tests.c | 118 + rust/automerge-c/test/cmocka_utils.c | 88 + rust/automerge-c/test/cmocka_utils.h | 42 +- rust/automerge-c/test/doc_state.c | 27 + rust/automerge-c/test/doc_state.h | 17 + rust/automerge-c/test/doc_tests.c | 351 ++- rust/automerge-c/test/enum_string_tests.c | 148 ++ rust/automerge-c/test/group_state.c | 27 - rust/automerge-c/test/group_state.h | 16 - rust/automerge-c/test/item_tests.c | 94 + rust/automerge-c/test/list_tests.c | 720 +++--- rust/automerge-c/test/macro_utils.c | 47 +- rust/automerge-c/test/macro_utils.h | 29 +- rust/automerge-c/test/main.c | 17 +- rust/automerge-c/test/map_tests.c | 1754 ++++++++------- .../test/ported_wasm/basic_tests.c | 1986 ++++++++--------- rust/automerge-c/test/ported_wasm/suite.c | 7 +- .../automerge-c/test/ported_wasm/sync_tests.c | 1276 +++++------ rust/automerge-c/test/stack_utils.c | 31 - rust/automerge-c/test/stack_utils.h | 38 - rust/automerge-c/test/str_utils.c | 2 +- rust/automerge-c/test/str_utils.h | 19 +- rust/automerge/src/error.rs | 5 + scripts/ci/cmake-build | 2 +- 82 files changed, 9304 insertions(+), 8607 deletions(-) create mode 100644 rust/automerge-c/.clang-format create mode 100644 rust/automerge-c/cmake/Cargo.toml.in create mode 100644 rust/automerge-c/cmake/cbindgen.toml.in create mode 100644 rust/automerge-c/cmake/enum-string-functions-gen.cmake rename rust/automerge-c/cmake/{file_regex_replace.cmake => file-regex-replace.cmake} (87%) rename rust/automerge-c/cmake/{file_touch.cmake => file-touch.cmake} (82%) create mode 100644 rust/automerge-c/docs/CMakeLists.txt rename rust/automerge-c/{ => docs}/img/brandmark.png (100%) create mode 100644 rust/automerge-c/include/automerge-c/utils/result.h create mode 100644 rust/automerge-c/include/automerge-c/utils/stack.h create mode 100644 rust/automerge-c/include/automerge-c/utils/stack_callback_data.h create mode 100644 rust/automerge-c/include/automerge-c/utils/string.h delete mode 100644 rust/automerge-c/src/CMakeLists.txt delete mode 100644 rust/automerge-c/src/change_hashes.rs delete mode 100644 rust/automerge-c/src/changes.rs delete mode 100644 rust/automerge-c/src/doc/list/item.rs delete mode 100644 rust/automerge-c/src/doc/list/items.rs delete mode 100644 rust/automerge-c/src/doc/map/item.rs delete mode 100644 rust/automerge-c/src/doc/map/items.rs create mode 100644 rust/automerge-c/src/index.rs create mode 100644 rust/automerge-c/src/item.rs create mode 100644 rust/automerge-c/src/items.rs delete mode 100644 rust/automerge-c/src/obj/item.rs delete mode 100644 rust/automerge-c/src/obj/items.rs delete mode 100644 rust/automerge-c/src/result_stack.rs delete mode 100644 rust/automerge-c/src/strs.rs delete mode 100644 rust/automerge-c/src/sync/haves.rs create mode 100644 rust/automerge-c/src/utils/result.c create mode 100644 rust/automerge-c/src/utils/stack.c create mode 100644 rust/automerge-c/src/utils/stack_callback_data.c create mode 100644 rust/automerge-c/src/utils/string.c create mode 100644 rust/automerge-c/test/base_state.c create mode 100644 rust/automerge-c/test/base_state.h create mode 100644 rust/automerge-c/test/byte_span_tests.c create mode 100644 rust/automerge-c/test/cmocka_utils.c create mode 100644 rust/automerge-c/test/doc_state.c create mode 100644 rust/automerge-c/test/doc_state.h create mode 100644 rust/automerge-c/test/enum_string_tests.c delete mode 100644 rust/automerge-c/test/group_state.c delete mode 100644 rust/automerge-c/test/group_state.h create mode 100644 rust/automerge-c/test/item_tests.c delete mode 100644 rust/automerge-c/test/stack_utils.c delete mode 100644 rust/automerge-c/test/stack_utils.h diff --git a/rust/automerge-c/.clang-format b/rust/automerge-c/.clang-format new file mode 100644 index 00000000..dbf16c21 --- /dev/null +++ b/rust/automerge-c/.clang-format @@ -0,0 +1,250 @@ +--- +Language: Cpp +# BasedOnStyle: Chromium +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +QualifierAlignment: Leave +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: NextLine +BasedOnStyle: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 3 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: true +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequiresClause: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Left +PPIndentWidth: -1 +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + - ParseTestProto + - ParsePartialTestProto + CanonicalDelimiter: pb + BasedOnStyle: google +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RequiresClausePosition: OwnLine +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/rust/automerge-c/.gitignore b/rust/automerge-c/.gitignore index f04de582..14d74973 100644 --- a/rust/automerge-c/.gitignore +++ b/rust/automerge-c/.gitignore @@ -1,10 +1,10 @@ automerge automerge.h automerge.o -*.cmake +build/ +CMakeCache.txt CMakeFiles +CMakePresets.json Makefile DartConfiguration.tcl -config.h -CMakeCache.txt -Cargo +out/ diff --git a/rust/automerge-c/CMakeLists.txt b/rust/automerge-c/CMakeLists.txt index 1b68669a..056d111b 100644 --- a/rust/automerge-c/CMakeLists.txt +++ b/rust/automerge-c/CMakeLists.txt @@ -1,97 +1,279 @@ -cmake_minimum_required(VERSION 3.18 FATAL_ERROR) +cmake_minimum_required(VERSION 3.23 FATAL_ERROR) -set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") +project(automerge-c VERSION 0.1.0 + LANGUAGES C + DESCRIPTION "C bindings for the Automerge Rust library.") -# Parse the library name, project name and project version out of Cargo's TOML file. -set(CARGO_LIB_SECTION OFF) +set(LIBRARY_NAME "automerge") -set(LIBRARY_NAME "") - -set(CARGO_PKG_SECTION OFF) - -set(CARGO_PKG_NAME "") - -set(CARGO_PKG_VERSION "") - -file(READ Cargo.toml TOML_STRING) - -string(REPLACE ";" "\\\\;" TOML_STRING "${TOML_STRING}") - -string(REPLACE "\n" ";" TOML_LINES "${TOML_STRING}") - -foreach(TOML_LINE IN ITEMS ${TOML_LINES}) - string(REGEX MATCH "^\\[(lib|package)\\]$" _ ${TOML_LINE}) - - if(CMAKE_MATCH_1 STREQUAL "lib") - set(CARGO_LIB_SECTION ON) - - set(CARGO_PKG_SECTION OFF) - elseif(CMAKE_MATCH_1 STREQUAL "package") - set(CARGO_LIB_SECTION OFF) - - set(CARGO_PKG_SECTION ON) - endif() - - string(REGEX MATCH "^name += +\"([^\"]+)\"$" _ ${TOML_LINE}) - - if(CMAKE_MATCH_1 AND (CARGO_LIB_SECTION AND NOT CARGO_PKG_SECTION)) - set(LIBRARY_NAME "${CMAKE_MATCH_1}") - elseif(CMAKE_MATCH_1 AND (NOT CARGO_LIB_SECTION AND CARGO_PKG_SECTION)) - set(CARGO_PKG_NAME "${CMAKE_MATCH_1}") - endif() - - string(REGEX MATCH "^version += +\"([^\"]+)\"$" _ ${TOML_LINE}) - - if(CMAKE_MATCH_1 AND CARGO_PKG_SECTION) - set(CARGO_PKG_VERSION "${CMAKE_MATCH_1}") - endif() - - if(LIBRARY_NAME AND (CARGO_PKG_NAME AND CARGO_PKG_VERSION)) - break() - endif() -endforeach() - -project(${CARGO_PKG_NAME} VERSION 0.0.1 LANGUAGES C DESCRIPTION "C bindings for the Automerge Rust backend.") - -include(CTest) +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) option(BUILD_SHARED_LIBS "Enable the choice of a shared or static library.") +include(CTest) + include(CMakePackageConfigHelpers) include(GNUInstallDirs) +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") + string(MAKE_C_IDENTIFIER ${PROJECT_NAME} SYMBOL_PREFIX) string(TOUPPER ${SYMBOL_PREFIX} SYMBOL_PREFIX) -set(CARGO_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/Cargo/target") +set(CARGO_TARGET_DIR "${CMAKE_BINARY_DIR}/Cargo/target") -set(CBINDGEN_INCLUDEDIR "${CARGO_TARGET_DIR}/${CMAKE_INSTALL_INCLUDEDIR}") +set(CBINDGEN_INCLUDEDIR "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_INCLUDEDIR}") set(CBINDGEN_TARGET_DIR "${CBINDGEN_INCLUDEDIR}/${PROJECT_NAME}") -add_subdirectory(src) +find_program ( + CARGO_CMD + "cargo" + PATHS "$ENV{CARGO_HOME}/bin" + DOC "The Cargo command" +) -# Generate and install the configuration header. +if(NOT CARGO_CMD) + message(FATAL_ERROR "Cargo (Rust package manager) not found! " + "Please install it and/or set the CARGO_HOME " + "environment variable to its path.") +endif() + +string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_LOWER) + +if(BUILD_TYPE_LOWER STREQUAL debug) + set(CARGO_BUILD_TYPE "debug") + + set(CARGO_FLAG "") +else() + set(CARGO_BUILD_TYPE "release") + + set(CARGO_FLAG "--release") +endif() + +set(CARGO_FEATURES "") + +set(CARGO_BINARY_DIR "${CARGO_TARGET_DIR}/${CARGO_BUILD_TYPE}") + +set(BINDINGS_NAME "${LIBRARY_NAME}_core") + +configure_file( + ${CMAKE_MODULE_PATH}/Cargo.toml.in + ${CMAKE_SOURCE_DIR}/Cargo.toml + @ONLY + NEWLINE_STYLE LF +) + +set(INCLUDE_GUARD_PREFIX "${SYMBOL_PREFIX}") + +configure_file( + ${CMAKE_MODULE_PATH}/cbindgen.toml.in + ${CMAKE_SOURCE_DIR}/cbindgen.toml + @ONLY + NEWLINE_STYLE LF +) + +set(CARGO_OUTPUT + ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h + ${CARGO_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${BINDINGS_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX} +) + +# \note cbindgen's naming behavior isn't fully configurable and it ignores +# `const fn` calls (https://github.com/eqrion/cbindgen/issues/252). +add_custom_command( + OUTPUT + ${CARGO_OUTPUT} + COMMAND + # \note cbindgen won't regenerate its output header file after it's been removed but it will after its + # configuration file has been updated. + ${CMAKE_COMMAND} -DCONDITION=NOT_EXISTS -P ${CMAKE_SOURCE_DIR}/cmake/file-touch.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CMAKE_SOURCE_DIR}/cbindgen.toml + COMMAND + ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} CBINDGEN_TARGET_DIR=${CBINDGEN_TARGET_DIR} ${CARGO_CMD} build ${CARGO_FLAG} ${CARGO_FEATURES} + COMMAND + # Compensate for cbindgen's translation of consecutive uppercase letters to "ScreamingSnakeCase". + ${CMAKE_COMMAND} -DMATCH_REGEX=A_M\([^_]+\)_ -DREPLACE_EXPR=AM_\\1_ -P ${CMAKE_SOURCE_DIR}/cmake/file-regex-replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h + COMMAND + # Compensate for cbindgen ignoring `std:mem::size_of()` calls. + ${CMAKE_COMMAND} -DMATCH_REGEX=USIZE_ -DREPLACE_EXPR=\+${CMAKE_SIZEOF_VOID_P} -P ${CMAKE_SOURCE_DIR}/cmake/file-regex-replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h + MAIN_DEPENDENCY + src/lib.rs + DEPENDS + src/actor_id.rs + src/byte_span.rs + src/change.rs + src/doc.rs + src/doc/list.rs + src/doc/map.rs + src/doc/utils.rs + src/index.rs + src/item.rs + src/items.rs + src/obj.rs + src/result.rs + src/sync.rs + src/sync/have.rs + src/sync/message.rs + src/sync/state.rs + ${CMAKE_SOURCE_DIR}/build.rs + ${CMAKE_MODULE_PATH}/Cargo.toml.in + ${CMAKE_MODULE_PATH}/cbindgen.toml.in + WORKING_DIRECTORY + ${CMAKE_SOURCE_DIR} + COMMENT + "Producing the bindings' artifacts with Cargo..." + VERBATIM +) + +add_custom_target(${BINDINGS_NAME}_artifacts ALL + DEPENDS ${CARGO_OUTPUT} +) + +add_library(${BINDINGS_NAME} STATIC IMPORTED GLOBAL) + +target_include_directories(${BINDINGS_NAME} INTERFACE "${CBINDGEN_INCLUDEDIR}") + +set_target_properties( + ${BINDINGS_NAME} + PROPERTIES + # \note Cargo writes a debug build into a nested directory instead of + # decorating its name. + DEBUG_POSTFIX "" + DEFINE_SYMBOL "" + IMPORTED_IMPLIB "" + IMPORTED_LOCATION "${CARGO_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${BINDINGS_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}" + IMPORTED_NO_SONAME "TRUE" + IMPORTED_SONAME "" + LINKER_LANGUAGE C + PUBLIC_HEADER "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h" + SOVERSION "${PROJECT_VERSION_MAJOR}" + VERSION "${PROJECT_VERSION}" + # \note Cargo exports all of the symbols automatically. + WINDOWS_EXPORT_ALL_SYMBOLS "TRUE" +) + +target_compile_definitions(${BINDINGS_NAME} INTERFACE $) + +set(UTILS_SUBDIR "utils") + +add_custom_command( + OUTPUT + ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h + ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c + COMMAND + ${CMAKE_COMMAND} -DPROJECT_NAME=${PROJECT_NAME} -DLIBRARY_NAME=${LIBRARY_NAME} -DSUBDIR=${UTILS_SUBDIR} -P ${CMAKE_SOURCE_DIR}/cmake/enum-string-functions-gen.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c + MAIN_DEPENDENCY + ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h + DEPENDS + ${CMAKE_SOURCE_DIR}/cmake/enum-string-functions-gen.cmake + WORKING_DIRECTORY + ${CMAKE_SOURCE_DIR} + COMMENT + "Generating the enum string functions with CMake..." + VERBATIM +) + +add_custom_target(${LIBRARY_NAME}_utilities + DEPENDS ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h + ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c +) + +add_library(${LIBRARY_NAME}) + +target_compile_features(${LIBRARY_NAME} PRIVATE c_std_99) + +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) + +set(THREADS_PREFER_PTHREAD_FLAG TRUE) + +find_package(Threads REQUIRED) + +set(LIBRARY_DEPENDENCIES Threads::Threads ${CMAKE_DL_LIBS}) + +if(WIN32) + list(APPEND LIBRARY_DEPENDENCIES Bcrypt userenv ws2_32) +else() + list(APPEND LIBRARY_DEPENDENCIES m) +endif() + +target_link_libraries(${LIBRARY_NAME} + PUBLIC ${BINDINGS_NAME} + ${LIBRARY_DEPENDENCIES} +) + +# \note An imported library's INTERFACE_INCLUDE_DIRECTORIES property can't +# contain a non-existent path so its build-time include directory +# must be specified for all of its dependent targets instead. +target_include_directories(${LIBRARY_NAME} + PUBLIC "$" + "$" +) + +add_dependencies(${LIBRARY_NAME} ${BINDINGS_NAME}_artifacts) + +# Generate the configuration header. math(EXPR INTEGER_PROJECT_VERSION_MAJOR "${PROJECT_VERSION_MAJOR} * 100000") math(EXPR INTEGER_PROJECT_VERSION_MINOR "${PROJECT_VERSION_MINOR} * 100") math(EXPR INTEGER_PROJECT_VERSION_PATCH "${PROJECT_VERSION_PATCH}") -math(EXPR INTEGER_PROJECT_VERSION "${INTEGER_PROJECT_VERSION_MAJOR} + ${INTEGER_PROJECT_VERSION_MINOR} + ${INTEGER_PROJECT_VERSION_PATCH}") +math(EXPR INTEGER_PROJECT_VERSION "${INTEGER_PROJECT_VERSION_MAJOR} + \ + ${INTEGER_PROJECT_VERSION_MINOR} + \ + ${INTEGER_PROJECT_VERSION_PATCH}") configure_file( ${CMAKE_MODULE_PATH}/config.h.in - config.h + ${CBINDGEN_TARGET_DIR}/config.h @ONLY NEWLINE_STYLE LF ) +target_sources(${LIBRARY_NAME} + PRIVATE + src/${UTILS_SUBDIR}/result.c + src/${UTILS_SUBDIR}/stack_callback_data.c + src/${UTILS_SUBDIR}/stack.c + src/${UTILS_SUBDIR}/string.c + ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c + PUBLIC + FILE_SET api TYPE HEADERS + BASE_DIRS + ${CBINDGEN_INCLUDEDIR} + ${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR} + FILES + ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h + ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h + ${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/result.h + ${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack_callback_data.h + ${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack.h + ${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/string.h + INTERFACE + FILE_SET config TYPE HEADERS + BASE_DIRS + ${CBINDGEN_INCLUDEDIR} + FILES + ${CBINDGEN_TARGET_DIR}/config.h +) + install( - FILES ${CMAKE_BINARY_DIR}/config.h - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} + TARGETS ${LIBRARY_NAME} + EXPORT ${PROJECT_NAME}-config + FILE_SET api + FILE_SET config +) + +# \note Install the Cargo-built core bindings to enable direct linkage. +install( + FILES $ + DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +install(EXPORT ${PROJECT_NAME}-config + FILE ${PROJECT_NAME}-config.cmake + NAMESPACE "${PROJECT_NAME}::" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${LIB} ) if(BUILD_TESTING) @@ -100,42 +282,6 @@ if(BUILD_TESTING) enable_testing() endif() +add_subdirectory(docs) + add_subdirectory(examples EXCLUDE_FROM_ALL) - -# Generate and install .cmake files -set(PROJECT_CONFIG_NAME "${PROJECT_NAME}-config") - -set(PROJECT_CONFIG_VERSION_NAME "${PROJECT_CONFIG_NAME}-version") - -write_basic_package_version_file( - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_VERSION_NAME}.cmake - VERSION ${PROJECT_VERSION} - COMPATIBILITY ExactVersion -) - -# The namespace label starts with the title-cased library name. -string(SUBSTRING ${LIBRARY_NAME} 0 1 NS_FIRST) - -string(SUBSTRING ${LIBRARY_NAME} 1 -1 NS_REST) - -string(TOUPPER ${NS_FIRST} NS_FIRST) - -string(TOLOWER ${NS_REST} NS_REST) - -string(CONCAT NAMESPACE ${NS_FIRST} ${NS_REST} "::") - -# \note CMake doesn't automate the exporting of an imported library's targets -# so the package configuration script must do it. -configure_package_config_file( - ${CMAKE_MODULE_PATH}/${PROJECT_CONFIG_NAME}.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_NAME}.cmake - INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} -) - -install( - FILES - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_NAME}.cmake - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_VERSION_NAME}.cmake - DESTINATION - ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} -) diff --git a/rust/automerge-c/Cargo.toml b/rust/automerge-c/Cargo.toml index d039e460..95a3a29c 100644 --- a/rust/automerge-c/Cargo.toml +++ b/rust/automerge-c/Cargo.toml @@ -7,8 +7,8 @@ license = "MIT" rust-version = "1.57.0" [lib] -name = "automerge" -crate-type = ["cdylib", "staticlib"] +name = "automerge_core" +crate-type = ["staticlib"] bench = false doc = false diff --git a/rust/automerge-c/README.md b/rust/automerge-c/README.md index a9f097e2..1fbca3df 100644 --- a/rust/automerge-c/README.md +++ b/rust/automerge-c/README.md @@ -1,22 +1,29 @@ -automerge-c exposes an API to C that can either be used directly or as a basis -for other language bindings that have good support for calling into C functions. +# Overview -# Building +automerge-c exposes a C API that can either be used directly or as the basis +for other language bindings that have good support for calling C functions. -See the main README for instructions on getting your environment set up, then -you can use `./scripts/ci/cmake-build Release static` to build automerge-c. +# Installing -It will output two files: +See the main README for instructions on getting your environment set up and then +you can build the automerge-c library and install its constituent files within +a root directory of your choosing (e.g. "/usr/local") like so: +```shell +cmake -E make_directory automerge-c/build +cmake -S automerge-c -B automerge-c/build +cmake --build automerge-c/build +cmake --install automerge-c/build --prefix "/usr/local" +``` +Installation is important because the name, location and structure of CMake's +out-of-source build subdirectory is subject to change based on the platform and +the release version; generated headers like `automerge-c/config.h` and +`automerge-c/utils/enum_string.h` are only sure to be found within their +installed locations. -- ./build/Cargo/target/include/automerge-c/automerge.h -- ./build/Cargo/target/release/libautomerge.a - -To use these in your application you must arrange for your C compiler to find -these files, either by moving them to the right location on your computer, or -by configuring the compiler to reference these directories. - -- `export LDFLAGS=-L./build/Cargo/target/release -lautomerge` -- `export CFLAGS=-I./build/Cargo/target/include` +It's not obvious because they are versioned but the `Cargo.toml` and +`cbindgen.toml` configuration files are also generated in order to ensure that +the project name, project version and library name that they contain match those +specified within the top-level `CMakeLists.txt` file. If you'd like to cross compile the library for different platforms you can do so using [cross](https://github.com/cross-rs/cross). For example: @@ -25,134 +32,176 @@ using [cross](https://github.com/cross-rs/cross). For example: This will output a shared library in the directory `rust/target/aarch64-unknown-linux-gnu/release/`. -You can replace `aarch64-unknown-linux-gnu` with any [cross supported targets](https://github.com/cross-rs/cross#supported-targets). The targets below are known to work, though other targets are expected to work too: +You can replace `aarch64-unknown-linux-gnu` with any +[cross supported targets](https://github.com/cross-rs/cross#supported-targets). +The targets below are known to work, though other targets are expected to work +too: - `x86_64-apple-darwin` - `aarch64-apple-darwin` - `x86_64-unknown-linux-gnu` - `aarch64-unknown-linux-gnu` -As a caveat, the header file is currently 32/64-bit dependant. You can re-use it -for all 64-bit architectures, but you must generate a specific header for 32-bit -targets. +As a caveat, CMake generates the `automerge.h` header file in terms of the +processor architecture of the computer on which it was built so, for example, +don't use a header generated for a 64-bit processor if your target is a 32-bit +processor. # Usage -For full reference, read through `automerge.h`, or to get started quickly look -at the +You can build and view the C API's HTML reference documentation like so: +```shell +cmake -E make_directory automerge-c/build +cmake -S automerge-c -B automerge-c/build +cmake --build automerge-c/build --target automerge_docs +firefox automerge-c/build/src/html/index.html +``` + +To get started quickly, look at the [examples](https://github.com/automerge/automerge-rs/tree/main/rust/automerge-c/examples). -Almost all operations in automerge-c act on an AMdoc struct which you can get -from `AMcreate()` or `AMload()`. Operations on a given doc are not thread safe -so you must use a mutex or similar to avoid calling more than one function with -the same AMdoc pointer concurrently. +Almost all operations in automerge-c act on an Automerge document +(`AMdoc` struct) which is structurally similar to a JSON document. -As with all functions that either allocate memory, or could fail if given -invalid input, `AMcreate()` returns an `AMresult`. The `AMresult` contains the -returned doc (or error message), and must be freed with `AMfree()` after you are -done to avoid leaking memory. +You can get a document by calling either `AMcreate()` or `AMload()`. Operations +on a given document are not thread-safe so you must use a mutex or similar to +avoid calling more than one function on the same one concurrently. +A C API function that could succeed or fail returns a result (`AMresult` struct) +containing a status code (`AMstatus` enum) and either a sequence of at least one +item (`AMitem` struct) or a read-only view onto a UTF-8 error message string +(`AMbyteSpan` struct). +An item contains up to three components: an index within its parent object +(`AMbyteSpan` struct or `size_t`), a unique identifier (`AMobjId` struct) and a +value. +The result of a successful function call that doesn't produce any values will +contain a single item that is void (`AM_VAL_TYPE_VOID`). +A returned result **must** be passed to `AMresultFree()` once the item(s) or +error message it contains is no longer needed in order to avoid a memory leak. ``` -#include #include +#include +#include +#include int main(int argc, char** argv) { AMresult *docResult = AMcreate(NULL); if (AMresultStatus(docResult) != AM_STATUS_OK) { - printf("failed to create doc: %s", AMerrorMessage(docResult).src); + char* const err_msg = AMstrdup(AMresultError(docResult), NULL); + printf("failed to create doc: %s", err_msg); + free(err_msg); goto cleanup; } - AMdoc *doc = AMresultValue(docResult).doc; + AMdoc *doc; + AMitemToDoc(AMresultItem(docResult), &doc); // useful code goes here! cleanup: - AMfree(docResult); + AMresultFree(docResult); } ``` -If you are writing code in C directly, you can use the `AMpush()` helper -function to reduce the boilerplate of error handling and freeing for you (see -examples/quickstart.c). +If you are writing an application in C, the `AMstackItem()`, `AMstackItems()` +and `AMstackResult()` functions enable the lifetimes of anonymous results to be +centrally managed and allow the same validation logic to be reused without +relying upon the `goto` statement (see examples/quickstart.c). If you are wrapping automerge-c in another language, particularly one that has a -garbage collector, you can call `AMfree` within a finalizer to ensure that memory -is reclaimed when it is no longer needed. +garbage collector, you can call the `AMresultFree()` function within a finalizer +to ensure that memory is reclaimed when it is no longer needed. -An AMdoc wraps an automerge document which are very similar to JSON documents. -Automerge documents consist of a mutable root, which is always a map from string -keys to values. Values can have the following types: +Automerge documents consist of a mutable root which is always a map from string +keys to values. A value can be one of the following types: - A number of type double / int64_t / uint64_t -- An explicit true / false / nul -- An immutable utf-8 string (AMbyteSpan) -- An immutable array of arbitrary bytes (AMbyteSpan) -- A mutable map from string keys to values (AMmap) -- A mutable list of values (AMlist) -- A mutable string (AMtext) +- An explicit true / false / null +- An immutable UTF-8 string (`AMbyteSpan`). +- An immutable array of arbitrary bytes (`AMbyteSpan`). +- A mutable map from string keys to values. +- A mutable list of values. +- A mutable UTF-8 string. -If you read from a location in the document with no value a value with -`.tag == AM_VALUE_VOID` will be returned, but you cannot write such a value explicitly. +If you read from a location in the document with no value, an item with type +`AM_VAL_TYPE_VOID` will be returned, but you cannot write such a value +explicitly. -Under the hood, automerge references mutable objects by the internal object id, -and `AM_ROOT` is always the object id of the root value. +Under the hood, automerge references a mutable object by its object identifier +where `AM_ROOT` signifies a document's root map object. -There is a function to put each type of value into either a map or a list, and a -function to read the current value from a list. As (in general) collaborators +There are functions to put each type of value into either a map or a list, and +functions to read the current or a historical value from a map or a list. As (in general) collaborators may edit the document at any time, you cannot guarantee that the type of the -value at a given part of the document will stay the same. As a result reading -from the document will return an `AMvalue` union that you can inspect to -determine its type. +value at a given part of the document will stay the same. As a result, reading +from the document will return an `AMitem` struct that you can inspect to +determine the type of value that it contains. Strings in automerge-c are represented using an `AMbyteSpan` which contains a -pointer and a length. Strings must be valid utf-8 and may contain null bytes. -As a convenience you can use `AMstr()` to get the representation of a -null-terminated C string as an `AMbyteSpan`. +pointer and a length. Strings must be valid UTF-8 and may contain NUL (`0`) +characters. +For your convenience, you can call `AMstr()` to get the `AMbyteSpan` struct +equivalent of a null-terminated byte string or `AMstrdup()` to get the +representation of an `AMbyteSpan` struct as a null-terminated byte string +wherein its NUL characters have been removed/replaced as you choose. Putting all of that together, to read and write from the root of the document you can do this: ``` -#include #include +#include +#include +#include int main(int argc, char** argv) { // ...previous example... - AMdoc *doc = AMresultValue(docResult).doc; + AMdoc *doc; + AMitemToDoc(AMresultItem(docResult), &doc); AMresult *putResult = AMmapPutStr(doc, AM_ROOT, AMstr("key"), AMstr("value")); if (AMresultStatus(putResult) != AM_STATUS_OK) { - printf("failed to put: %s", AMerrorMessage(putResult).src); + char* const err_msg = AMstrdup(AMresultError(putResult), NULL); + printf("failed to put: %s", err_msg); + free(err_msg); goto cleanup; } AMresult *getResult = AMmapGet(doc, AM_ROOT, AMstr("key"), NULL); if (AMresultStatus(getResult) != AM_STATUS_OK) { - printf("failed to get: %s", AMerrorMessage(getResult).src); + char* const err_msg = AMstrdup(AMresultError(putResult), NULL); + printf("failed to get: %s", err_msg); + free(err_msg); goto cleanup; } - AMvalue got = AMresultValue(getResult); - if (got.tag != AM_VALUE_STR) { + AMbyteSpan got; + if (AMitemToStr(AMresultItem(getResult), &got)) { + char* const c_str = AMstrdup(got, NULL); + printf("Got %zu-character string \"%s\"", got.count, c_str); + free(c_str); + } else { printf("expected to read a string!"); goto cleanup; } - printf("Got %zu-character string `%s`", got.str.count, got.str.src); cleanup: - AMfree(getResult); - AMfree(putResult); - AMfree(docResult); + AMresultFree(getResult); + AMresultFree(putResult); + AMresultFree(docResult); } ``` -Functions that do not return an `AMresult` (for example `AMmapItemValue()`) do -not allocate memory, but continue to reference memory that was previously -allocated. It's thus important to keep the original `AMresult` alive (in this -case the one returned by `AMmapRange()`) until after you are done with the return -values of these functions. +Functions that do not return an `AMresult` (for example `AMitemKey()`) do +not allocate memory but rather reference memory that was previously +allocated. It's therefore important to keep the original `AMresult` alive (in +this case the one returned by `AMmapRange()`) until after you are finished with +the items that it contains. However, the memory for an individual `AMitem` can +be shared with a new `AMresult` by calling `AMitemResult()` on it. In other +words, a select group of items can be filtered out of a collection and only each +one's corresponding `AMresult` must be kept alive from that point forward; the +originating collection's `AMresult` can be safely freed. Beyond that, good luck! diff --git a/rust/automerge-c/cbindgen.toml b/rust/automerge-c/cbindgen.toml index ada7f48d..21eaaadd 100644 --- a/rust/automerge-c/cbindgen.toml +++ b/rust/automerge-c/cbindgen.toml @@ -1,7 +1,7 @@ after_includes = """\n /** * \\defgroup enumerations Public Enumerations - Symbolic names for integer constants. + * Symbolic names for integer constants. */ /** @@ -12,21 +12,23 @@ after_includes = """\n #define AM_ROOT NULL /** - * \\memberof AMchangeHash + * \\memberof AMdoc * \\def AM_CHANGE_HASH_SIZE * \\brief The count of bytes in a change hash. */ #define AM_CHANGE_HASH_SIZE 32 """ -autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +autogen_warning = """ +/** + * \\file + * \\brief All constants, functions and types in the core Automerge C API. + * + * \\warning This file is auto-generated by cbindgen. + */ +""" documentation = true documentation_style = "doxy" -header = """ -/** \\file - * All constants, functions and types in the Automerge library's C API. - */ - """ -include_guard = "AUTOMERGE_H" +include_guard = "AUTOMERGE_C_H" includes = [] language = "C" line_length = 140 diff --git a/rust/automerge-c/cmake/Cargo.toml.in b/rust/automerge-c/cmake/Cargo.toml.in new file mode 100644 index 00000000..781e2fef --- /dev/null +++ b/rust/automerge-c/cmake/Cargo.toml.in @@ -0,0 +1,22 @@ +[package] +name = "@PROJECT_NAME@" +version = "@PROJECT_VERSION@" +authors = ["Orion Henry ", "Jason Kankiewicz "] +edition = "2021" +license = "MIT" +rust-version = "1.57.0" + +[lib] +name = "@BINDINGS_NAME@" +crate-type = ["staticlib"] +bench = false +doc = false + +[dependencies] +@LIBRARY_NAME@ = { path = "../@LIBRARY_NAME@" } +hex = "^0.4.3" +libc = "^0.2" +smol_str = "^0.1.21" + +[build-dependencies] +cbindgen = "^0.24" diff --git a/rust/automerge-c/cmake/cbindgen.toml.in b/rust/automerge-c/cmake/cbindgen.toml.in new file mode 100644 index 00000000..5122b75c --- /dev/null +++ b/rust/automerge-c/cmake/cbindgen.toml.in @@ -0,0 +1,48 @@ +after_includes = """\n +/** + * \\defgroup enumerations Public Enumerations + * Symbolic names for integer constants. + */ + +/** + * \\memberof AMdoc + * \\def AM_ROOT + * \\brief The root object of a document. + */ +#define AM_ROOT NULL + +/** + * \\memberof AMdoc + * \\def AM_CHANGE_HASH_SIZE + * \\brief The count of bytes in a change hash. + */ +#define AM_CHANGE_HASH_SIZE 32 +""" +autogen_warning = """ +/** + * \\file + * \\brief All constants, functions and types in the core Automerge C API. + * + * \\warning This file is auto-generated by cbindgen. + */ +""" +documentation = true +documentation_style = "doxy" +include_guard = "@INCLUDE_GUARD_PREFIX@_H" +includes = [] +language = "C" +line_length = 140 +no_includes = true +style = "both" +sys_includes = ["stdbool.h", "stddef.h", "stdint.h", "time.h"] +usize_is_size_t = true + +[enum] +derive_const_casts = true +enum_class = true +must_use = "MUST_USE_ENUM" +prefix_with_name = true +rename_variants = "ScreamingSnakeCase" + +[export] +item_types = ["constants", "enums", "functions", "opaque", "structs", "typedefs"] diff --git a/rust/automerge-c/cmake/config.h.in b/rust/automerge-c/cmake/config.h.in index 44ba5213..40482cb9 100644 --- a/rust/automerge-c/cmake/config.h.in +++ b/rust/automerge-c/cmake/config.h.in @@ -1,14 +1,35 @@ -#ifndef @SYMBOL_PREFIX@_CONFIG_H -#define @SYMBOL_PREFIX@_CONFIG_H - -/* This header is auto-generated by CMake. */ +#ifndef @INCLUDE_GUARD_PREFIX@_CONFIG_H +#define @INCLUDE_GUARD_PREFIX@_CONFIG_H +/** + * \file + * \brief Configuration pararameters defined by the build system. + * + * \warning This file is auto-generated by CMake. + */ +/** + * \def @SYMBOL_PREFIX@_VERSION + * \brief Denotes a semantic version of the form {MAJOR}{MINOR}{PATCH} as three, + * two-digit decimal numbers without leading zeros (e.g. 100 is 0.1.0). + */ #define @SYMBOL_PREFIX@_VERSION @INTEGER_PROJECT_VERSION@ +/** + * \def @SYMBOL_PREFIX@_MAJOR_VERSION + * \brief Denotes a semantic major version as a decimal number. + */ #define @SYMBOL_PREFIX@_MAJOR_VERSION (@SYMBOL_PREFIX@_VERSION / 100000) +/** + * \def @SYMBOL_PREFIX@_MINOR_VERSION + * \brief Denotes a semantic minor version as a decimal number. + */ #define @SYMBOL_PREFIX@_MINOR_VERSION ((@SYMBOL_PREFIX@_VERSION / 100) % 1000) +/** + * \def @SYMBOL_PREFIX@_PATCH_VERSION + * \brief Denotes a semantic patch version as a decimal number. + */ #define @SYMBOL_PREFIX@_PATCH_VERSION (@SYMBOL_PREFIX@_VERSION % 100) -#endif /* @SYMBOL_PREFIX@_CONFIG_H */ +#endif /* @INCLUDE_GUARD_PREFIX@_CONFIG_H */ diff --git a/rust/automerge-c/cmake/enum-string-functions-gen.cmake b/rust/automerge-c/cmake/enum-string-functions-gen.cmake new file mode 100644 index 00000000..77080e8d --- /dev/null +++ b/rust/automerge-c/cmake/enum-string-functions-gen.cmake @@ -0,0 +1,183 @@ +# This CMake script is used to generate a header and a source file for utility +# functions that convert the tags of generated enum types into strings and +# strings into the tags of generated enum types. +cmake_minimum_required(VERSION 3.23 FATAL_ERROR) + +# Seeks the starting line of the source enum's declaration. +macro(seek_enum_mode) + if (line MATCHES "^(typedef[ \t]+)?enum ") + string(REGEX REPLACE "^enum ([0-9a-zA-Z_]+).*$" "\\1" enum_name "${line}") + set(mode "read_tags") + endif() +endmacro() + +# Scans the input for the current enum's tags. +macro(read_tags_mode) + if(line MATCHES "^}") + set(mode "generate") + elseif(line MATCHES "^[A-Z0-9_]+.*$") + string(REGEX REPLACE "^([A-Za-z0-9_]+).*$" "\\1" tmp "${line}") + list(APPEND enum_tags "${tmp}") + endif() +endmacro() + +macro(write_header_file) + # Generate a to-string function declaration. + list(APPEND header_body + "/**\n" + " * \\ingroup enumerations\n" + " * \\brief Gets the string representation of an `${enum_name}` enum tag.\n" + " *\n" + " * \\param[in] tag An `${enum_name}` enum tag.\n" + " * \\return A null-terminated byte string.\n" + " */\n" + "char const* ${enum_name}ToString(${enum_name} const tag)\;\n" + "\n") + # Generate a from-string function declaration. + list(APPEND header_body + "/**\n" + " * \\ingroup enumerations\n" + " * \\brief Gets an `${enum_name}` enum tag from its string representation.\n" + " *\n" + " * \\param[out] dest An `${enum_name}` enum tag pointer.\n" + " * \\param[in] src A null-terminated byte string.\n" + " * \\return `true` if \\p src matches the string representation of an\n" + " * `${enum_name}` enum tag, `false` otherwise.\n" + " */\n" + "bool ${enum_name}FromString(${enum_name}* dest, char const* const src)\;\n" + "\n") +endmacro() + +macro(write_source_file) + # Generate a to-string function implementation. + list(APPEND source_body + "char const* ${enum_name}ToString(${enum_name} const tag) {\n" + " switch (tag) {\n" + " default:\n" + " return \"???\"\;\n") + foreach(label IN LISTS enum_tags) + list(APPEND source_body + " case ${label}:\n" + " return \"${label}\"\;\n") + endforeach() + list(APPEND source_body + " }\n" + "}\n" + "\n") + # Generate a from-string function implementation. + list(APPEND source_body + "bool ${enum_name}FromString(${enum_name}* dest, char const* const src) {\n") + foreach(label IN LISTS enum_tags) + list(APPEND source_body + " if (!strcmp(src, \"${label}\")) {\n" + " *dest = ${label}\;\n" + " return true\;\n" + " }\n") + endforeach() + list(APPEND source_body + " return false\;\n" + "}\n" + "\n") +endmacro() + +function(main) + set(header_body "") + # File header and includes. + list(APPEND header_body + "#ifndef ${include_guard}\n" + "#define ${include_guard}\n" + "/**\n" + " * \\file\n" + " * \\brief Utility functions for converting enum tags into null-terminated\n" + " * byte strings and vice versa.\n" + " *\n" + " * \\warning This file is auto-generated by CMake.\n" + " */\n" + "\n" + "#include \n" + "\n" + "#include <${library_include}>\n" + "\n") + set(source_body "") + # File includes. + list(APPEND source_body + "/** \\warning This file is auto-generated by CMake. */\n" + "\n" + "#include \"stdio.h\"\n" + "#include \"string.h\"\n" + "\n" + "#include <${header_include}>\n" + "\n") + set(enum_name "") + set(enum_tags "") + set(mode "seek_enum") + file(STRINGS "${input_path}" lines) + foreach(line IN LISTS lines) + string(REGEX REPLACE "^(.+)(//.*)?" "\\1" line "${line}") + string(STRIP "${line}" line) + if(mode STREQUAL "seek_enum") + seek_enum_mode() + elseif(mode STREQUAL "read_tags") + read_tags_mode() + else() + # The end of the enum declaration was reached. + if(NOT enum_name) + # The end of the file was reached. + return() + endif() + if(NOT enum_tags) + message(FATAL_ERROR "No tags found for `${enum_name}`.") + endif() + string(TOLOWER "${enum_name}" output_stem_prefix) + string(CONCAT output_stem "${output_stem_prefix}" "_string") + cmake_path(REPLACE_EXTENSION output_stem "h" OUTPUT_VARIABLE output_header_basename) + write_header_file() + write_source_file() + set(enum_name "") + set(enum_tags "") + set(mode "seek_enum") + endif() + endforeach() + # File footer. + list(APPEND header_body + "#endif /* ${include_guard} */\n") + message(STATUS "Generating header file \"${output_header_path}\"...") + file(WRITE "${output_header_path}" ${header_body}) + message(STATUS "Generating source file \"${output_source_path}\"...") + file(WRITE "${output_source_path}" ${source_body}) +endfunction() + +if(NOT DEFINED PROJECT_NAME) + message(FATAL_ERROR "Variable PROJECT_NAME is not defined.") +elseif(NOT DEFINED LIBRARY_NAME) + message(FATAL_ERROR "Variable LIBRARY_NAME is not defined.") +elseif(NOT DEFINED SUBDIR) + message(FATAL_ERROR "Variable SUBDIR is not defined.") +elseif(${CMAKE_ARGC} LESS 9) + message(FATAL_ERROR "Too few arguments.") +elseif(${CMAKE_ARGC} GREATER 10) + message(FATAL_ERROR "Too many arguments.") +elseif(NOT EXISTS ${CMAKE_ARGV5}) + message(FATAL_ERROR "Input header \"${CMAKE_ARGV7}\" not found.") +endif() +cmake_path(CONVERT "${CMAKE_ARGV7}" TO_CMAKE_PATH_LIST input_path NORMALIZE) +cmake_path(CONVERT "${CMAKE_ARGV8}" TO_CMAKE_PATH_LIST output_header_path NORMALIZE) +cmake_path(CONVERT "${CMAKE_ARGV9}" TO_CMAKE_PATH_LIST output_source_path NORMALIZE) +string(TOLOWER "${PROJECT_NAME}" project_root) +cmake_path(CONVERT "${SUBDIR}" TO_CMAKE_PATH_LIST project_subdir NORMALIZE) +string(TOLOWER "${project_subdir}" project_subdir) +string(TOLOWER "${LIBRARY_NAME}" library_stem) +cmake_path(REPLACE_EXTENSION library_stem "h" OUTPUT_VARIABLE library_basename) +string(JOIN "/" library_include "${project_root}" "${library_basename}") +string(TOUPPER "${PROJECT_NAME}" project_name_upper) +string(TOUPPER "${project_subdir}" include_guard_infix) +string(REGEX REPLACE "/" "_" include_guard_infix "${include_guard_infix}") +string(REGEX REPLACE "-" "_" include_guard_prefix "${project_name_upper}") +string(JOIN "_" include_guard_prefix "${include_guard_prefix}" "${include_guard_infix}") +string(JOIN "/" output_header_prefix "${project_root}" "${project_subdir}") +cmake_path(GET output_header_path STEM output_header_stem) +string(TOUPPER "${output_header_stem}" include_guard_stem) +string(JOIN "_" include_guard "${include_guard_prefix}" "${include_guard_stem}" "H") +cmake_path(GET output_header_path FILENAME output_header_basename) +string(JOIN "/" header_include "${output_header_prefix}" "${output_header_basename}") +main() diff --git a/rust/automerge-c/cmake/file_regex_replace.cmake b/rust/automerge-c/cmake/file-regex-replace.cmake similarity index 87% rename from rust/automerge-c/cmake/file_regex_replace.cmake rename to rust/automerge-c/cmake/file-regex-replace.cmake index 27306458..09005bc2 100644 --- a/rust/automerge-c/cmake/file_regex_replace.cmake +++ b/rust/automerge-c/cmake/file-regex-replace.cmake @@ -1,4 +1,6 @@ -cmake_minimum_required(VERSION 3.18 FATAL_ERROR) +# This CMake script is used to perform string substitutions within a generated +# file. +cmake_minimum_required(VERSION 3.23 FATAL_ERROR) if(NOT DEFINED MATCH_REGEX) message(FATAL_ERROR "Variable \"MATCH_REGEX\" is not defined.") diff --git a/rust/automerge-c/cmake/file_touch.cmake b/rust/automerge-c/cmake/file-touch.cmake similarity index 82% rename from rust/automerge-c/cmake/file_touch.cmake rename to rust/automerge-c/cmake/file-touch.cmake index 087d59b6..2c196755 100644 --- a/rust/automerge-c/cmake/file_touch.cmake +++ b/rust/automerge-c/cmake/file-touch.cmake @@ -1,4 +1,6 @@ -cmake_minimum_required(VERSION 3.18 FATAL_ERROR) +# This CMake script is used to force Cargo to regenerate the header file for the +# core bindings after the out-of-source build directory has been cleaned. +cmake_minimum_required(VERSION 3.23 FATAL_ERROR) if(NOT DEFINED CONDITION) message(FATAL_ERROR "Variable \"CONDITION\" is not defined.") diff --git a/rust/automerge-c/docs/CMakeLists.txt b/rust/automerge-c/docs/CMakeLists.txt new file mode 100644 index 00000000..1d94c872 --- /dev/null +++ b/rust/automerge-c/docs/CMakeLists.txt @@ -0,0 +1,35 @@ +find_package(Doxygen OPTIONAL_COMPONENTS dot) + +if(DOXYGEN_FOUND) + set(DOXYGEN_ALIASES "installed_headerfile=\\headerfile ${LIBRARY_NAME}.h <${PROJECT_NAME}/${LIBRARY_NAME}.h>") + + set(DOXYGEN_GENERATE_LATEX YES) + + set(DOXYGEN_PDF_HYPERLINKS YES) + + set(DOXYGEN_PROJECT_LOGO "${CMAKE_CURRENT_SOURCE_DIR}/img/brandmark.png") + + set(DOXYGEN_SORT_BRIEF_DOCS YES) + + set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md") + + doxygen_add_docs( + ${LIBRARY_NAME}_docs + "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h" + "${CBINDGEN_TARGET_DIR}/config.h" + "${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h" + "${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/result.h" + "${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack_callback_data.h" + "${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack.h" + "${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/string.h" + "${CMAKE_SOURCE_DIR}/README.md" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Producing documentation with Doxygen..." + ) + + # \note A Doxygen input file isn't a file-level dependency so the Doxygen + # command must instead depend upon a target that either outputs the + # file or depends upon it also or it will just output an error message + # when it can't be found. + add_dependencies(${LIBRARY_NAME}_docs ${BINDINGS_NAME}_artifacts ${LIBRARY_NAME}_utilities) +endif() diff --git a/rust/automerge-c/img/brandmark.png b/rust/automerge-c/docs/img/brandmark.png similarity index 100% rename from rust/automerge-c/img/brandmark.png rename to rust/automerge-c/docs/img/brandmark.png diff --git a/rust/automerge-c/examples/CMakeLists.txt b/rust/automerge-c/examples/CMakeLists.txt index 3395124c..f080237b 100644 --- a/rust/automerge-c/examples/CMakeLists.txt +++ b/rust/automerge-c/examples/CMakeLists.txt @@ -1,41 +1,39 @@ -cmake_minimum_required(VERSION 3.18 FATAL_ERROR) - add_executable( - example_quickstart + ${LIBRARY_NAME}_quickstart quickstart.c ) -set_target_properties(example_quickstart PROPERTIES LINKER_LANGUAGE C) +set_target_properties(${LIBRARY_NAME}_quickstart PROPERTIES LINKER_LANGUAGE C) # \note An imported library's INTERFACE_INCLUDE_DIRECTORIES property can't # contain a non-existent path so its build-time include directory # must be specified for all of its dependent targets instead. target_include_directories( - example_quickstart + ${LIBRARY_NAME}_quickstart PRIVATE "$" ) -target_link_libraries(example_quickstart PRIVATE ${LIBRARY_NAME}) +target_link_libraries(${LIBRARY_NAME}_quickstart PRIVATE ${LIBRARY_NAME}) -add_dependencies(example_quickstart ${LIBRARY_NAME}_artifacts) +add_dependencies(${LIBRARY_NAME}_quickstart ${BINDINGS_NAME}_artifacts) if(BUILD_SHARED_LIBS AND WIN32) add_custom_command( - TARGET example_quickstart + TARGET ${LIBRARY_NAME}_quickstart POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX} - ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} COMMENT "Copying the DLL built by Cargo into the examples directory..." VERBATIM ) endif() add_custom_command( - TARGET example_quickstart + TARGET ${LIBRARY_NAME}_quickstart POST_BUILD COMMAND - example_quickstart + ${LIBRARY_NAME}_quickstart COMMENT "Running the example quickstart..." VERBATIM diff --git a/rust/automerge-c/examples/README.md b/rust/automerge-c/examples/README.md index 17aa2227..17e69412 100644 --- a/rust/automerge-c/examples/README.md +++ b/rust/automerge-c/examples/README.md @@ -5,5 +5,5 @@ ```shell cmake -E make_directory automerge-c/build cmake -S automerge-c -B automerge-c/build -cmake --build automerge-c/build --target example_quickstart +cmake --build automerge-c/build --target automerge_quickstart ``` diff --git a/rust/automerge-c/examples/quickstart.c b/rust/automerge-c/examples/quickstart.c index bc418511..ab6769ef 100644 --- a/rust/automerge-c/examples/quickstart.c +++ b/rust/automerge-c/examples/quickstart.c @@ -3,152 +3,127 @@ #include #include +#include +#include +#include +#include -static void abort_cb(AMresultStack**, uint8_t); +static bool abort_cb(AMstack**, void*); /** * \brief Based on https://automerge.github.io/docs/quickstart */ int main(int argc, char** argv) { - AMresultStack* stack = NULL; - AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, abort_cb).doc; - AMobjId const* const cards = AMpush(&stack, - AMmapPutObject(doc1, AM_ROOT, AMstr("cards"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - abort_cb).obj_id; - AMobjId const* const card1 = AMpush(&stack, - AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - abort_cb).obj_id; - AMfree(AMmapPutStr(doc1, card1, AMstr("title"), AMstr("Rewrite everything in Clojure"))); - AMfree(AMmapPutBool(doc1, card1, AMstr("done"), false)); - AMobjId const* const card2 = AMpush(&stack, - AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - abort_cb).obj_id; - AMfree(AMmapPutStr(doc1, card2, AMstr("title"), AMstr("Rewrite everything in Haskell"))); - AMfree(AMmapPutBool(doc1, card2, AMstr("done"), false)); - AMfree(AMcommit(doc1, AMstr("Add card"), NULL)); + AMstack* stack = NULL; + AMdoc* doc1; + AMitemToDoc(AMstackItem(&stack, AMcreate(NULL), abort_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1); + AMobjId const* const cards = + AMitemObjId(AMstackItem(&stack, AMmapPutObject(doc1, AM_ROOT, AMstr("cards"), AM_OBJ_TYPE_LIST), abort_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const card1 = + AMitemObjId(AMstackItem(&stack, AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), abort_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMmapPutStr(doc1, card1, AMstr("title"), AMstr("Rewrite everything in Clojure")), abort_cb, + AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutBool(doc1, card1, AMstr("done"), false), abort_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMobjId const* const card2 = + AMitemObjId(AMstackItem(&stack, AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), abort_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMmapPutStr(doc1, card2, AMstr("title"), AMstr("Rewrite everything in Haskell")), abort_cb, + AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutBool(doc1, card2, AMstr("done"), false), abort_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc1, AMstr("Add card"), NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMdoc* doc2 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, abort_cb).doc; - AMfree(AMmerge(doc2, doc1)); + AMdoc* doc2; + AMitemToDoc(AMstackItem(&stack, AMcreate(NULL), abort_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2); + AMstackItem(NULL, AMmerge(doc2, doc1), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMbyteSpan const binary = AMpush(&stack, AMsave(doc1), AM_VALUE_BYTES, abort_cb).bytes; - doc2 = AMpush(&stack, AMload(binary.src, binary.count), AM_VALUE_DOC, abort_cb).doc; + AMbyteSpan binary; + AMitemToBytes(AMstackItem(&stack, AMsave(doc1), abort_cb, AMexpect(AM_VAL_TYPE_BYTES)), &binary); + AMitemToDoc(AMstackItem(&stack, AMload(binary.src, binary.count), abort_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2); - AMfree(AMmapPutBool(doc1, card1, AMstr("done"), true)); - AMfree(AMcommit(doc1, AMstr("Mark card as done"), NULL)); + AMstackItem(NULL, AMmapPutBool(doc1, card1, AMstr("done"), true), abort_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc1, AMstr("Mark card as done"), NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMfree(AMlistDelete(doc2, cards, 0)); - AMfree(AMcommit(doc2, AMstr("Delete card"), NULL)); + AMstackItem(NULL, AMlistDelete(doc2, cards, 0), abort_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc2, AMstr("Delete card"), NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMfree(AMmerge(doc1, doc2)); + AMstackItem(NULL, AMmerge(doc1, doc2), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMchanges changes = AMpush(&stack, AMgetChanges(doc1, NULL), AM_VALUE_CHANGES, abort_cb).changes; - AMchange const* change = NULL; - while ((change = AMchangesNext(&changes, 1)) != NULL) { - AMbyteSpan const change_hash = AMchangeHash(change); - AMchangeHashes const heads = AMpush(&stack, - AMchangeHashesInit(&change_hash, 1), - AM_VALUE_CHANGE_HASHES, - abort_cb).change_hashes; - AMbyteSpan const msg = AMchangeMessage(change); - char* const c_msg = calloc(1, msg.count + 1); - strncpy(c_msg, msg.src, msg.count); - printf("%s %ld\n", c_msg, AMobjSize(doc1, cards, &heads)); + AMitems changes = AMstackItems(&stack, AMgetChanges(doc1, NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMitem* item = NULL; + while ((item = AMitemsNext(&changes, 1)) != NULL) { + AMchange const* change; + AMitemToChange(item, &change); + AMitems const heads = AMstackItems(&stack, AMitemFromChangeHash(AMchangeHash(change)), abort_cb, + AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + char* const c_msg = AMstrdup(AMchangeMessage(change), NULL); + printf("%s %zu\n", c_msg, AMobjSize(doc1, cards, &heads)); free(c_msg); } - AMfreeStack(&stack); + AMstackFree(&stack); } -static char const* discriminant_suffix(AMvalueVariant const); - /** - * \brief Prints an error message to `stderr`, deallocates all results in the - * given stack and exits. + * \brief Examines the result at the top of the given stack and, if it's + * invalid, prints an error message to `stderr`, deallocates all results + * in the stack and exits. * - * \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct. - * \param[in] discriminant An `AMvalueVariant` enum tag. - * \pre \p stack` != NULL`. - * \post `*stack == NULL`. + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \param[in] data A pointer to an owned `AMstackCallbackData` struct or `NULL`. + * \return `true` if the top `AMresult` in \p stack is valid, `false` otherwise. + * \pre \p stack `!= NULL`. */ -static void abort_cb(AMresultStack** stack, uint8_t discriminant) { +static bool abort_cb(AMstack** stack, void* data) { static char buffer[512] = {0}; char const* suffix = NULL; if (!stack) { suffix = "Stack*"; - } - else if (!*stack) { + } else if (!*stack) { suffix = "Stack"; - } - else if (!(*stack)->result) { + } else if (!(*stack)->result) { suffix = ""; } if (suffix) { - fprintf(stderr, "Null `AMresult%s*`.", suffix); - AMfreeStack(stack); + fprintf(stderr, "Null `AMresult%s*`.\n", suffix); + AMstackFree(stack); exit(EXIT_FAILURE); - return; + return false; } AMstatus const status = AMresultStatus((*stack)->result); switch (status) { - case AM_STATUS_ERROR: strcpy(buffer, "Error"); break; - case AM_STATUS_INVALID_RESULT: strcpy(buffer, "Invalid result"); break; - case AM_STATUS_OK: break; - default: sprintf(buffer, "Unknown `AMstatus` tag %d", status); + case AM_STATUS_ERROR: + strcpy(buffer, "Error"); + break; + case AM_STATUS_INVALID_RESULT: + strcpy(buffer, "Invalid result"); + break; + case AM_STATUS_OK: + break; + default: + sprintf(buffer, "Unknown `AMstatus` tag %d", status); } if (buffer[0]) { - AMbyteSpan const msg = AMerrorMessage((*stack)->result); - char* const c_msg = calloc(1, msg.count + 1); - strncpy(c_msg, msg.src, msg.count); - fprintf(stderr, "%s; %s.", buffer, c_msg); + char* const c_msg = AMstrdup(AMresultError((*stack)->result), NULL); + fprintf(stderr, "%s; %s.\n", buffer, c_msg); free(c_msg); - AMfreeStack(stack); + AMstackFree(stack); exit(EXIT_FAILURE); - return; + return false; } - AMvalue const value = AMresultValue((*stack)->result); - fprintf(stderr, "Unexpected tag `AM_VALUE_%s` (%d); expected `AM_VALUE_%s`.", - discriminant_suffix(value.tag), - value.tag, - discriminant_suffix(discriminant)); - AMfreeStack(stack); - exit(EXIT_FAILURE); -} - -/** - * \brief Gets the suffix for a discriminant's corresponding string - * representation. - * - * \param[in] discriminant An `AMvalueVariant` enum tag. - * \return A UTF-8 string. - */ -static char const* discriminant_suffix(AMvalueVariant const discriminant) { - char const* suffix = NULL; - switch (discriminant) { - case AM_VALUE_ACTOR_ID: suffix = "ACTOR_ID"; break; - case AM_VALUE_BOOLEAN: suffix = "BOOLEAN"; break; - case AM_VALUE_BYTES: suffix = "BYTES"; break; - case AM_VALUE_CHANGE_HASHES: suffix = "CHANGE_HASHES"; break; - case AM_VALUE_CHANGES: suffix = "CHANGES"; break; - case AM_VALUE_COUNTER: suffix = "COUNTER"; break; - case AM_VALUE_DOC: suffix = "DOC"; break; - case AM_VALUE_F64: suffix = "F64"; break; - case AM_VALUE_INT: suffix = "INT"; break; - case AM_VALUE_LIST_ITEMS: suffix = "LIST_ITEMS"; break; - case AM_VALUE_MAP_ITEMS: suffix = "MAP_ITEMS"; break; - case AM_VALUE_NULL: suffix = "NULL"; break; - case AM_VALUE_OBJ_ID: suffix = "OBJ_ID"; break; - case AM_VALUE_OBJ_ITEMS: suffix = "OBJ_ITEMS"; break; - case AM_VALUE_STR: suffix = "STR"; break; - case AM_VALUE_STRS: suffix = "STRINGS"; break; - case AM_VALUE_SYNC_MESSAGE: suffix = "SYNC_MESSAGE"; break; - case AM_VALUE_SYNC_STATE: suffix = "SYNC_STATE"; break; - case AM_VALUE_TIMESTAMP: suffix = "TIMESTAMP"; break; - case AM_VALUE_UINT: suffix = "UINT"; break; - case AM_VALUE_VOID: suffix = "VOID"; break; - default: suffix = "..."; + if (data) { + AMstackCallbackData* sc_data = (AMstackCallbackData*)data; + AMvalType const tag = AMitemValType(AMresultItem((*stack)->result)); + if (tag != sc_data->bitmask) { + fprintf(stderr, "Unexpected tag `%s` (%d) instead of `%s` at %s:%d.\n", AMvalTypeToString(tag), tag, + AMvalTypeToString(sc_data->bitmask), sc_data->file, sc_data->line); + free(sc_data); + AMstackFree(stack); + exit(EXIT_FAILURE); + return false; + } } - return suffix; + free(data); + return true; } diff --git a/rust/automerge-c/include/automerge-c/utils/result.h b/rust/automerge-c/include/automerge-c/utils/result.h new file mode 100644 index 00000000..ab8a2f93 --- /dev/null +++ b/rust/automerge-c/include/automerge-c/utils/result.h @@ -0,0 +1,30 @@ +#ifndef AUTOMERGE_C_UTILS_RESULT_H +#define AUTOMERGE_C_UTILS_RESULT_H +/** + * \file + * \brief Utility functions for use with `AMresult` structs. + */ + +#include + +#include + +/** + * \brief Transfers the items within an arbitrary list of results into a + * new result in their order of specification. + * \param[in] count The count of subsequent arguments. + * \param[in] ... A \p count list of arguments, each of which is a pointer to + * an `AMresult` struct whose items will be transferred out of it + * and which is subsequently freed. + * \return A pointer to an `AMresult` struct or `NULL`. + * \pre `∀𝑥 ∈` \p ... `, AMresultStatus(𝑥) == AM_STATUS_OK` + * \post `(∃𝑥 ∈` \p ... `, AMresultStatus(𝑥) != AM_STATUS_OK) -> NULL` + * \attention All `AMresult` struct pointer arguments are passed to + * `AMresultFree()` regardless of success; use `AMresultCat()` + * instead if you wish to pass them to `AMresultFree()` yourself. + * \warning The returned `AMresult` struct pointer must be passed to + * `AMresultFree()` in order to avoid a memory leak. + */ +AMresult* AMresultFrom(int count, ...); + +#endif /* AUTOMERGE_C_UTILS_RESULT_H */ diff --git a/rust/automerge-c/include/automerge-c/utils/stack.h b/rust/automerge-c/include/automerge-c/utils/stack.h new file mode 100644 index 00000000..a8e9fd08 --- /dev/null +++ b/rust/automerge-c/include/automerge-c/utils/stack.h @@ -0,0 +1,130 @@ +#ifndef AUTOMERGE_C_UTILS_STACK_H +#define AUTOMERGE_C_UTILS_STACK_H +/** + * \file + * \brief Utility data structures and functions for hiding `AMresult` structs, + * managing their lifetimes, and automatically applying custom + * validation logic to the `AMitem` structs that they contain. + * + * \note The `AMstack` struct and its related functions drastically reduce the + * need for boilerplate code and/or `goto` statement usage within a C + * application but a higher-level programming language offers even better + * ways to do the same things. + */ + +#include + +/** + * \struct AMstack + * \brief A node in a singly-linked list of result pointers. + */ +typedef struct AMstack { + /** A result to be deallocated. */ + AMresult* result; + /** The previous node in the singly-linked list or `NULL`. */ + struct AMstack* prev; +} AMstack; + +/** + * \memberof AMstack + * \brief The prototype of a function that examines the result at the top of + * the given stack in terms of some arbitrary data. + * + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \param[in] data A pointer to arbitrary data or `NULL`. + * \return `true` if the top `AMresult` struct in \p stack is valid, `false` + * otherwise. + * \pre \p stack `!= NULL`. + */ +typedef bool (*AMstackCallback)(AMstack** stack, void* data); + +/** + * \memberof AMstack + * \brief Deallocates the storage for a stack of results. + * + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \pre \p stack `!= NULL` + * \post `*stack == NULL` + */ +void AMstackFree(AMstack** stack); + +/** + * \memberof AMstack + * \brief Gets a result from the stack after removing it. + * + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \param[in] result A pointer to the `AMresult` to be popped or `NULL` to + * select the top result in \p stack. + * \return A pointer to an `AMresult` struct or `NULL`. + * \pre \p stack `!= NULL` + * \warning The returned `AMresult` struct pointer must be passed to + * `AMresultFree()` in order to avoid a memory leak. + */ +AMresult* AMstackPop(AMstack** stack, AMresult const* result); + +/** + * \memberof AMstack + * \brief Pushes the given result onto the given stack, calls the given + * callback with the given data to validate it and then either gets the + * result if it's valid or gets `NULL` instead. + * + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \param[in] result A pointer to an `AMresult` struct. + * \param[in] callback A pointer to a function with the same signature as + * `AMstackCallback()` or `NULL`. + * \param[in] data A pointer to arbitrary data or `NULL` which is passed to + * \p callback. + * \return \p result or `NULL`. + * \warning If \p stack `== NULL` then \p result is deallocated in order to + * avoid a memory leak. + */ +AMresult* AMstackResult(AMstack** stack, AMresult* result, AMstackCallback callback, void* data); + +/** + * \memberof AMstack + * \brief Pushes the given result onto the given stack, calls the given + * callback with the given data to validate it and then either gets the + * first item in the sequence of items within that result if it's valid + * or gets `NULL` instead. + * + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \param[in] result A pointer to an `AMresult` struct. + * \param[in] callback A pointer to a function with the same signature as + * `AMstackCallback()` or `NULL`. + * \param[in] data A pointer to arbitrary data or `NULL` which is passed to + * \p callback. + * \return A pointer to an `AMitem` struct or `NULL`. + * \warning If \p stack `== NULL` then \p result is deallocated in order to + * avoid a memory leak. + */ +AMitem* AMstackItem(AMstack** stack, AMresult* result, AMstackCallback callback, void* data); + +/** + * \memberof AMstack + * \brief Pushes the given result onto the given stack, calls the given + * callback with the given data to validate it and then either gets an + * `AMitems` struct over the sequence of items within that result if it's + * valid or gets an empty `AMitems` instead. + * + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \param[in] result A pointer to an `AMresult` struct. + * \param[in] callback A pointer to a function with the same signature as + * `AMstackCallback()` or `NULL`. + * \param[in] data A pointer to arbitrary data or `NULL` which is passed to + * \p callback. + * \return An `AMitems` struct. + * \warning If \p stack `== NULL` then \p result is deallocated immediately + * in order to avoid a memory leak. + */ +AMitems AMstackItems(AMstack** stack, AMresult* result, AMstackCallback callback, void* data); + +/** + * \memberof AMstack + * \brief Gets the count of results that have been pushed onto the stack. + * + * \param[in,out] stack A pointer to an `AMstack` struct. + * \return A 64-bit unsigned integer. + */ +size_t AMstackSize(AMstack const* const stack); + +#endif /* AUTOMERGE_C_UTILS_STACK_H */ diff --git a/rust/automerge-c/include/automerge-c/utils/stack_callback_data.h b/rust/automerge-c/include/automerge-c/utils/stack_callback_data.h new file mode 100644 index 00000000..6f9f1edb --- /dev/null +++ b/rust/automerge-c/include/automerge-c/utils/stack_callback_data.h @@ -0,0 +1,53 @@ +#ifndef AUTOMERGE_C_UTILS_PUSH_CALLBACK_DATA_H +#define AUTOMERGE_C_UTILS_PUSH_CALLBACK_DATA_H +/** + * \file + * \brief Utility data structures, functions and macros for supplying + * parameters to the custom validation logic applied to `AMitem` + * structs. + */ + +#include + +/** + * \struct AMstackCallbackData + * \brief A data structure for passing the parameters of an item value test + * to an implementation of the `AMstackCallback` function prototype. + */ +typedef struct { + /** A bitmask of `AMvalType` tags. */ + AMvalType bitmask; + /** A null-terminated file path string. */ + char const* file; + /** The ordinal number of a line within a file. */ + int line; +} AMstackCallbackData; + +/** + * \memberof AMstackCallbackData + * \brief Allocates a new `AMstackCallbackData` struct and initializes its + * members from their corresponding arguments. + * + * \param[in] bitmask A bitmask of `AMvalType` tags. + * \param[in] file A null-terminated file path string. + * \param[in] line The ordinal number of a line within a file. + * \return A pointer to a disowned `AMstackCallbackData` struct. + * \warning The returned pointer must be passed to `free()` to avoid a memory + * leak. + */ +AMstackCallbackData* AMstackCallbackDataInit(AMvalType const bitmask, char const* const file, int const line); + +/** + * \memberof AMstackCallbackData + * \def AMexpect + * \brief Allocates a new `AMstackCallbackData` struct and initializes it from + * an `AMvalueType` bitmask. + * + * \param[in] bitmask A bitmask of `AMvalType` tags. + * \return A pointer to a disowned `AMstackCallbackData` struct. + * \warning The returned pointer must be passed to `free()` to avoid a memory + * leak. + */ +#define AMexpect(bitmask) AMstackCallbackDataInit(bitmask, __FILE__, __LINE__) + +#endif /* AUTOMERGE_C_UTILS_PUSH_CALLBACK_DATA_H */ diff --git a/rust/automerge-c/include/automerge-c/utils/string.h b/rust/automerge-c/include/automerge-c/utils/string.h new file mode 100644 index 00000000..4d61c2e9 --- /dev/null +++ b/rust/automerge-c/include/automerge-c/utils/string.h @@ -0,0 +1,29 @@ +#ifndef AUTOMERGE_C_UTILS_STRING_H +#define AUTOMERGE_C_UTILS_STRING_H +/** + * \file + * \brief Utility functions for use with `AMbyteSpan` structs that provide + * UTF-8 string views. + */ + +#include + +/** + * \memberof AMbyteSpan + * \brief Returns a pointer to a null-terminated byte string which is a + * duplicate of the given UTF-8 string view except for the substitution + * of its NUL (0) characters with the specified null-terminated byte + * string. + * + * \param[in] str A UTF-8 string view as an `AMbyteSpan` struct. + * \param[in] nul A null-terminated byte string to substitute for NUL characters + * or `NULL` to substitute `"\\0"` for NUL characters. + * \return A disowned null-terminated byte string. + * \pre \p str.src `!= NULL` + * \pre \p str.count `<= sizeof(`\p str.src `)` + * \warning The returned pointer must be passed to `free()` to avoid a memory + * leak. + */ +char* AMstrdup(AMbyteSpan const str, char const* nul); + +#endif /* AUTOMERGE_C_UTILS_STRING_H */ diff --git a/rust/automerge-c/src/CMakeLists.txt b/rust/automerge-c/src/CMakeLists.txt deleted file mode 100644 index e02c0a96..00000000 --- a/rust/automerge-c/src/CMakeLists.txt +++ /dev/null @@ -1,250 +0,0 @@ -cmake_minimum_required(VERSION 3.18 FATAL_ERROR) - -find_program ( - CARGO_CMD - "cargo" - PATHS "$ENV{CARGO_HOME}/bin" - DOC "The Cargo command" -) - -if(NOT CARGO_CMD) - message(FATAL_ERROR "Cargo (Rust package manager) not found! Install it and/or set the CARGO_HOME environment variable.") -endif() - -string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_LOWER) - -if(BUILD_TYPE_LOWER STREQUAL debug) - set(CARGO_BUILD_TYPE "debug") - - set(CARGO_FLAG "") -else() - set(CARGO_BUILD_TYPE "release") - - set(CARGO_FLAG "--release") -endif() - -set(CARGO_FEATURES "") - -set(CARGO_CURRENT_BINARY_DIR "${CARGO_TARGET_DIR}/${CARGO_BUILD_TYPE}") - -set( - CARGO_OUTPUT - ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h - ${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX} - ${CARGO_CURRENT_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX} -) - -if(WIN32) - # \note The basename of an import library output by Cargo is the filename - # of its corresponding shared library. - list(APPEND CARGO_OUTPUT ${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}) -endif() - -add_custom_command( - OUTPUT - ${CARGO_OUTPUT} - COMMAND - # \note cbindgen won't regenerate its output header file after it's - # been removed but it will after its configuration file has been - # updated. - ${CMAKE_COMMAND} -DCONDITION=NOT_EXISTS -P ${CMAKE_SOURCE_DIR}/cmake/file_touch.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CMAKE_SOURCE_DIR}/cbindgen.toml - COMMAND - ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} CBINDGEN_TARGET_DIR=${CBINDGEN_TARGET_DIR} ${CARGO_CMD} build ${CARGO_FLAG} ${CARGO_FEATURES} - MAIN_DEPENDENCY - lib.rs - DEPENDS - actor_id.rs - byte_span.rs - change_hashes.rs - change.rs - changes.rs - doc.rs - doc/list.rs - doc/list/item.rs - doc/list/items.rs - doc/map.rs - doc/map/item.rs - doc/map/items.rs - doc/utils.rs - obj.rs - obj/item.rs - obj/items.rs - result.rs - result_stack.rs - strs.rs - sync.rs - sync/have.rs - sync/haves.rs - sync/message.rs - sync/state.rs - ${CMAKE_SOURCE_DIR}/build.rs - ${CMAKE_SOURCE_DIR}/Cargo.toml - ${CMAKE_SOURCE_DIR}/cbindgen.toml - WORKING_DIRECTORY - ${CMAKE_SOURCE_DIR} - COMMENT - "Producing the library artifacts with Cargo..." - VERBATIM -) - -add_custom_target( - ${LIBRARY_NAME}_artifacts ALL - DEPENDS ${CARGO_OUTPUT} -) - -# \note cbindgen's naming behavior isn't fully configurable and it ignores -# `const fn` calls (https://github.com/eqrion/cbindgen/issues/252). -add_custom_command( - TARGET ${LIBRARY_NAME}_artifacts - POST_BUILD - COMMAND - # Compensate for cbindgen's variant struct naming. - ${CMAKE_COMMAND} -DMATCH_REGEX=AM\([^_]+_[^_]+\)_Body -DREPLACE_EXPR=AM\\1 -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h - COMMAND - # Compensate for cbindgen's union tag enum type naming. - ${CMAKE_COMMAND} -DMATCH_REGEX=AM\([^_]+\)_Tag -DREPLACE_EXPR=AM\\1Variant -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h - COMMAND - # Compensate for cbindgen's translation of consecutive uppercase letters to "ScreamingSnakeCase". - ${CMAKE_COMMAND} -DMATCH_REGEX=A_M\([^_]+\)_ -DREPLACE_EXPR=AM_\\1_ -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h - COMMAND - # Compensate for cbindgen ignoring `std:mem::size_of()` calls. - ${CMAKE_COMMAND} -DMATCH_REGEX=USIZE_ -DREPLACE_EXPR=\+${CMAKE_SIZEOF_VOID_P} -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h - WORKING_DIRECTORY - ${CMAKE_SOURCE_DIR} - COMMENT - "Compensating for cbindgen deficits..." - VERBATIM -) - -if(BUILD_SHARED_LIBS) - if(WIN32) - set(LIBRARY_DESTINATION "${CMAKE_INSTALL_BINDIR}") - else() - set(LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}") - endif() - - set(LIBRARY_DEFINE_SYMBOL "${SYMBOL_PREFIX}_EXPORTS") - - # \note The basename of an import library output by Cargo is the filename - # of its corresponding shared library. - set(LIBRARY_IMPLIB "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}") - - set(LIBRARY_LOCATION "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}") - - set(LIBRARY_NO_SONAME "${WIN32}") - - set(LIBRARY_SONAME "${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}") - - set(LIBRARY_TYPE "SHARED") -else() - set(LIBRARY_DEFINE_SYMBOL "") - - set(LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}") - - set(LIBRARY_IMPLIB "") - - set(LIBRARY_LOCATION "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}") - - set(LIBRARY_NO_SONAME "TRUE") - - set(LIBRARY_SONAME "") - - set(LIBRARY_TYPE "STATIC") -endif() - -add_library(${LIBRARY_NAME} ${LIBRARY_TYPE} IMPORTED GLOBAL) - -set_target_properties( - ${LIBRARY_NAME} - PROPERTIES - # \note Cargo writes a debug build into a nested directory instead of - # decorating its name. - DEBUG_POSTFIX "" - DEFINE_SYMBOL "${LIBRARY_DEFINE_SYMBOL}" - IMPORTED_IMPLIB "${LIBRARY_IMPLIB}" - IMPORTED_LOCATION "${LIBRARY_LOCATION}" - IMPORTED_NO_SONAME "${LIBRARY_NO_SONAME}" - IMPORTED_SONAME "${LIBRARY_SONAME}" - LINKER_LANGUAGE C - PUBLIC_HEADER "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h" - SOVERSION "${PROJECT_VERSION_MAJOR}" - VERSION "${PROJECT_VERSION}" - # \note Cargo exports all of the symbols automatically. - WINDOWS_EXPORT_ALL_SYMBOLS "TRUE" -) - -target_compile_definitions(${LIBRARY_NAME} INTERFACE $) - -target_include_directories( - ${LIBRARY_NAME} - INTERFACE - "$" -) - -set(CMAKE_THREAD_PREFER_PTHREAD TRUE) - -set(THREADS_PREFER_PTHREAD_FLAG TRUE) - -find_package(Threads REQUIRED) - -set(LIBRARY_DEPENDENCIES Threads::Threads ${CMAKE_DL_LIBS}) - -if(WIN32) - list(APPEND LIBRARY_DEPENDENCIES Bcrypt userenv ws2_32) -else() - list(APPEND LIBRARY_DEPENDENCIES m) -endif() - -target_link_libraries(${LIBRARY_NAME} INTERFACE ${LIBRARY_DEPENDENCIES}) - -install( - FILES $ - TYPE LIB - # \note The basename of an import library output by Cargo is the filename - # of its corresponding shared library. - RENAME "${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}" - OPTIONAL -) - -set(LIBRARY_FILE_NAME "${CMAKE_${LIBRARY_TYPE}_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_${LIBRARY_TYPE}_LIBRARY_SUFFIX}") - -install( - FILES $ - RENAME "${LIBRARY_FILE_NAME}" - DESTINATION ${LIBRARY_DESTINATION} -) - -install( - FILES $ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} -) - -find_package(Doxygen OPTIONAL_COMPONENTS dot) - -if(DOXYGEN_FOUND) - set(DOXYGEN_ALIASES "installed_headerfile=\\headerfile ${LIBRARY_NAME}.h <${PROJECT_NAME}/${LIBRARY_NAME}.h>") - - set(DOXYGEN_GENERATE_LATEX YES) - - set(DOXYGEN_PDF_HYPERLINKS YES) - - set(DOXYGEN_PROJECT_LOGO "${CMAKE_SOURCE_DIR}/img/brandmark.png") - - set(DOXYGEN_SORT_BRIEF_DOCS YES) - - set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md") - - doxygen_add_docs( - ${LIBRARY_NAME}_docs - "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h" - "${CMAKE_SOURCE_DIR}/README.md" - USE_STAMP_FILE - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - COMMENT "Producing documentation with Doxygen..." - ) - - # \note A Doxygen input file isn't a file-level dependency so the Doxygen - # command must instead depend upon a target that outputs the file or - # it will just output an error message when it can't be found. - add_dependencies(${LIBRARY_NAME}_docs ${LIBRARY_NAME}_artifacts) -endif() diff --git a/rust/automerge-c/src/actor_id.rs b/rust/automerge-c/src/actor_id.rs index bc86d5ef..5a28959e 100644 --- a/rust/automerge-c/src/actor_id.rs +++ b/rust/automerge-c/src/actor_id.rs @@ -1,4 +1,5 @@ use automerge as am; +use libc::c_int; use std::cell::RefCell; use std::cmp::Ordering; use std::str::FromStr; @@ -11,7 +12,7 @@ macro_rules! to_actor_id { let handle = $handle.as_ref(); match handle { Some(b) => b, - None => return AMresult::err("Invalid AMactorId pointer").into(), + None => return AMresult::error("Invalid `AMactorId*`").into(), } }}; } @@ -57,11 +58,11 @@ impl AsRef for AMactorId { } /// \memberof AMactorId -/// \brief Gets the value of an actor identifier as a sequence of bytes. +/// \brief Gets the value of an actor identifier as an array of bytes. /// /// \param[in] actor_id A pointer to an `AMactorId` struct. -/// \pre \p actor_id `!= NULL`. -/// \return An `AMbyteSpan` struct. +/// \return An `AMbyteSpan` struct for an array of bytes. +/// \pre \p actor_id `!= NULL` /// \internal /// /// # Safety @@ -82,8 +83,8 @@ pub unsafe extern "C" fn AMactorIdBytes(actor_id: *const AMactorId) -> AMbyteSpa /// \return `-1` if \p actor_id1 `<` \p actor_id2, `0` if /// \p actor_id1 `==` \p actor_id2 and `1` if /// \p actor_id1 `>` \p actor_id2. -/// \pre \p actor_id1 `!= NULL`. -/// \pre \p actor_id2 `!= NULL`. +/// \pre \p actor_id1 `!= NULL` +/// \pre \p actor_id2 `!= NULL` /// \internal /// /// #Safety @@ -93,7 +94,7 @@ pub unsafe extern "C" fn AMactorIdBytes(actor_id: *const AMactorId) -> AMbyteSpa pub unsafe extern "C" fn AMactorIdCmp( actor_id1: *const AMactorId, actor_id2: *const AMactorId, -) -> isize { +) -> c_int { match (actor_id1.as_ref(), actor_id2.as_ref()) { (Some(actor_id1), Some(actor_id2)) => match actor_id1.as_ref().cmp(actor_id2.as_ref()) { Ordering::Less => -1, @@ -101,65 +102,69 @@ pub unsafe extern "C" fn AMactorIdCmp( Ordering::Greater => 1, }, (None, Some(_)) => -1, - (Some(_), None) => 1, (None, None) => 0, + (Some(_), None) => 1, } } /// \memberof AMactorId -/// \brief Allocates a new actor identifier and initializes it with a random -/// UUID. +/// \brief Allocates a new actor identifier and initializes it from a random +/// UUID value. /// -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMactorId` struct. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. #[no_mangle] pub unsafe extern "C" fn AMactorIdInit() -> *mut AMresult { to_result(Ok::(am::ActorId::random())) } /// \memberof AMactorId -/// \brief Allocates a new actor identifier and initializes it from a sequence -/// of bytes. +/// \brief Allocates a new actor identifier and initializes it from an array of +/// bytes value. /// -/// \param[in] src A pointer to a contiguous sequence of bytes. -/// \param[in] count The number of bytes to copy from \p src. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMactorId` struct. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] src A pointer to an array of bytes. +/// \param[in] count The count of bytes to copy from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item. +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety -/// src must be a byte array of size `>= count` +/// src must be a byte array of length `>= count` #[no_mangle] -pub unsafe extern "C" fn AMactorIdInitBytes(src: *const u8, count: usize) -> *mut AMresult { - let slice = std::slice::from_raw_parts(src, count); - to_result(Ok::(am::ActorId::from( - slice, - ))) +pub unsafe extern "C" fn AMactorIdFromBytes(src: *const u8, count: usize) -> *mut AMresult { + if !src.is_null() { + let value = std::slice::from_raw_parts(src, count); + to_result(Ok::(am::ActorId::from( + value, + ))) + } else { + AMresult::error("Invalid uint8_t*").into() + } } /// \memberof AMactorId /// \brief Allocates a new actor identifier and initializes it from a -/// hexadecimal string. +/// hexadecimal UTF-8 string view value. /// -/// \param[in] hex_str A UTF-8 string view as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMactorId` struct. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] value A UTF-8 string view as an `AMbyteSpan` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// hex_str must be a valid pointer to an AMbyteSpan #[no_mangle] -pub unsafe extern "C" fn AMactorIdInitStr(hex_str: AMbyteSpan) -> *mut AMresult { +pub unsafe extern "C" fn AMactorIdFromStr(value: AMbyteSpan) -> *mut AMresult { use am::AutomergeError::InvalidActorId; - to_result(match (&hex_str).try_into() { + to_result(match (&value).try_into() { Ok(s) => match am::ActorId::from_str(s) { Ok(actor_id) => Ok(actor_id), Err(_) => Err(InvalidActorId(String::from(s))), @@ -169,11 +174,12 @@ pub unsafe extern "C" fn AMactorIdInitStr(hex_str: AMbyteSpan) -> *mut AMresult } /// \memberof AMactorId -/// \brief Gets the value of an actor identifier as a hexadecimal string. +/// \brief Gets the value of an actor identifier as a UTF-8 hexadecimal string +/// view. /// /// \param[in] actor_id A pointer to an `AMactorId` struct. -/// \pre \p actor_id `!= NULL`. /// \return A UTF-8 string view as an `AMbyteSpan` struct. +/// \pre \p actor_id `!= NULL` /// \internal /// /// # Safety diff --git a/rust/automerge-c/src/byte_span.rs b/rust/automerge-c/src/byte_span.rs index fd4c3ca0..5855cfc7 100644 --- a/rust/automerge-c/src/byte_span.rs +++ b/rust/automerge-c/src/byte_span.rs @@ -1,14 +1,17 @@ use automerge as am; -use libc::strlen; +use std::cmp::Ordering; use std::convert::TryFrom; use std::os::raw::c_char; +use libc::{c_int, strlen}; +use smol_str::SmolStr; + macro_rules! to_str { - ($span:expr) => {{ - let result: Result<&str, am::AutomergeError> = (&$span).try_into(); + ($byte_span:expr) => {{ + let result: Result<&str, am::AutomergeError> = (&$byte_span).try_into(); match result { Ok(s) => s, - Err(e) => return AMresult::err(&e.to_string()).into(), + Err(e) => return AMresult::error(&e.to_string()).into(), } }}; } @@ -17,16 +20,17 @@ pub(crate) use to_str; /// \struct AMbyteSpan /// \installed_headerfile -/// \brief A view onto a contiguous sequence of bytes. +/// \brief A view onto an array of bytes. #[repr(C)] pub struct AMbyteSpan { - /// A pointer to an array of bytes. - /// \attention NEVER CALL `free()` ON \p src! - /// \warning \p src is only valid until the `AMfree()` function is called - /// on the `AMresult` struct that stores the array of bytes to - /// which it points. + /// A pointer to the first byte of an array of bytes. + /// \warning \p src is only valid until the array of bytes to which it + /// points is freed. + /// \note If the `AMbyteSpan` came from within an `AMitem` struct then + /// \p src will be freed when the pointer to the `AMresult` struct + /// containing the `AMitem` struct is passed to `AMresultFree()`. pub src: *const u8, - /// The number of bytes in the array. + /// The count of bytes in the array. pub count: usize, } @@ -52,9 +56,7 @@ impl PartialEq for AMbyteSpan { } else if self.src == other.src { return true; } - let slice = unsafe { std::slice::from_raw_parts(self.src, self.count) }; - let other_slice = unsafe { std::slice::from_raw_parts(other.src, other.count) }; - slice == other_slice + <&[u8]>::from(self) == <&[u8]>::from(other) } } @@ -72,10 +74,15 @@ impl From<&am::ActorId> for AMbyteSpan { impl From<&mut am::ActorId> for AMbyteSpan { fn from(actor: &mut am::ActorId) -> Self { - let slice = actor.to_bytes(); + actor.as_ref().into() + } +} + +impl From<&am::ChangeHash> for AMbyteSpan { + fn from(change_hash: &am::ChangeHash) -> Self { Self { - src: slice.as_ptr(), - count: slice.len(), + src: change_hash.0.as_ptr(), + count: change_hash.0.len(), } } } @@ -93,12 +100,9 @@ impl From<*const c_char> for AMbyteSpan { } } -impl From<&am::ChangeHash> for AMbyteSpan { - fn from(change_hash: &am::ChangeHash) -> Self { - Self { - src: change_hash.0.as_ptr(), - count: change_hash.0.len(), - } +impl From<&SmolStr> for AMbyteSpan { + fn from(smol_str: &SmolStr) -> Self { + smol_str.as_bytes().into() } } @@ -111,13 +115,39 @@ impl From<&[u8]> for AMbyteSpan { } } +impl From<&AMbyteSpan> for &[u8] { + fn from(byte_span: &AMbyteSpan) -> Self { + unsafe { std::slice::from_raw_parts(byte_span.src, byte_span.count) } + } +} + +impl From<&AMbyteSpan> for Vec { + fn from(byte_span: &AMbyteSpan) -> Self { + <&[u8]>::from(byte_span).to_vec() + } +} + +impl TryFrom<&AMbyteSpan> for am::ChangeHash { + type Error = am::AutomergeError; + + fn try_from(byte_span: &AMbyteSpan) -> Result { + use am::AutomergeError::InvalidChangeHashBytes; + + let slice: &[u8] = byte_span.into(); + match slice.try_into() { + Ok(change_hash) => Ok(change_hash), + Err(e) => Err(InvalidChangeHashBytes(e)), + } + } +} + impl TryFrom<&AMbyteSpan> for &str { type Error = am::AutomergeError; - fn try_from(span: &AMbyteSpan) -> Result { + fn try_from(byte_span: &AMbyteSpan) -> Result { use am::AutomergeError::InvalidCharacter; - let slice = unsafe { std::slice::from_raw_parts(span.src, span.count) }; + let slice = byte_span.into(); match std::str::from_utf8(slice) { Ok(str_) => Ok(str_), Err(e) => Err(InvalidCharacter(e.valid_up_to())), @@ -125,17 +155,69 @@ impl TryFrom<&AMbyteSpan> for &str { } } -/// \brief Creates an AMbyteSpan from a pointer + length +/// \memberof AMbyteSpan +/// \brief Creates a view onto an array of bytes. /// -/// \param[in] src A pointer to a span of bytes -/// \param[in] count The number of bytes in the span -/// \return An `AMbyteSpan` struct +/// \param[in] src A pointer to an array of bytes or `NULL`. +/// \param[in] count The count of bytes to view from the array pointed to by +/// \p src. +/// \return An `AMbyteSpan` struct. +/// \pre \p count `<= sizeof(`\p src `)` +/// \post `(`\p src `== NULL) -> (AMbyteSpan){NULL, 0}` /// \internal /// /// #Safety -/// AMbytes does not retain the underlying storage, so you must discard the -/// return value before freeing the bytes. +/// src must be a byte array of length `>= count` or `std::ptr::null()` #[no_mangle] pub unsafe extern "C" fn AMbytes(src: *const u8, count: usize) -> AMbyteSpan { - AMbyteSpan { src, count } + AMbyteSpan { + src, + count: if src.is_null() { 0 } else { count }, + } +} + +/// \memberof AMbyteSpan +/// \brief Creates a view onto a C string. +/// +/// \param[in] c_str A null-terminated byte string or `NULL`. +/// \return An `AMbyteSpan` struct. +/// \pre Each byte in \p c_str encodes one UTF-8 character. +/// \internal +/// +/// #Safety +/// c_str must be a null-terminated array of `std::os::raw::c_char` or `std::ptr::null()`. +#[no_mangle] +pub unsafe extern "C" fn AMstr(c_str: *const c_char) -> AMbyteSpan { + c_str.into() +} + +/// \memberof AMbyteSpan +/// \brief Compares two UTF-8 string views lexicographically. +/// +/// \param[in] lhs A UTF-8 string view as an `AMbyteSpan` struct. +/// \param[in] rhs A UTF-8 string view as an `AMbyteSpan` struct. +/// \return Negative value if \p lhs appears before \p rhs in lexicographical order. +/// Zero if \p lhs and \p rhs compare equal. +/// Positive value if \p lhs appears after \p rhs in lexicographical order. +/// \pre \p lhs.src `!= NULL` +/// \pre \p lhs.count `<= sizeof(`\p lhs.src `)` +/// \pre \p rhs.src `!= NULL` +/// \pre \p rhs.count `<= sizeof(`\p rhs.src `)` +/// \internal +/// +/// #Safety +/// lhs.src must be a byte array of length >= lhs.count +/// rhs.src must be a a byte array of length >= rhs.count +#[no_mangle] +pub unsafe extern "C" fn AMstrCmp(lhs: AMbyteSpan, rhs: AMbyteSpan) -> c_int { + match (<&str>::try_from(&lhs), <&str>::try_from(&rhs)) { + (Ok(lhs), Ok(rhs)) => match lhs.cmp(rhs) { + Ordering::Less => -1, + Ordering::Equal => 0, + Ordering::Greater => 1, + }, + (Err(_), Ok(_)) => -1, + (Err(_), Err(_)) => 0, + (Ok(_), Err(_)) => 1, + } } diff --git a/rust/automerge-c/src/change.rs b/rust/automerge-c/src/change.rs index d64a2635..8529ed94 100644 --- a/rust/automerge-c/src/change.rs +++ b/rust/automerge-c/src/change.rs @@ -2,7 +2,6 @@ use automerge as am; use std::cell::RefCell; use crate::byte_span::AMbyteSpan; -use crate::change_hashes::AMchangeHashes; use crate::result::{to_result, AMresult}; macro_rules! to_change { @@ -10,7 +9,7 @@ macro_rules! to_change { let handle = $handle.as_ref(); match handle { Some(b) => b, - None => return AMresult::err("Invalid AMchange pointer").into(), + None => return AMresult::error("Invalid `AMchange*`").into(), } }}; } @@ -21,14 +20,14 @@ macro_rules! to_change { #[derive(Eq, PartialEq)] pub struct AMchange { body: *mut am::Change, - changehash: RefCell>, + change_hash: RefCell>, } impl AMchange { pub fn new(change: &mut am::Change) -> Self { Self { body: change, - changehash: Default::default(), + change_hash: Default::default(), } } @@ -40,12 +39,12 @@ impl AMchange { } pub fn hash(&self) -> AMbyteSpan { - let mut changehash = self.changehash.borrow_mut(); - if let Some(changehash) = changehash.as_ref() { - changehash.into() + let mut change_hash = self.change_hash.borrow_mut(); + if let Some(change_hash) = change_hash.as_ref() { + change_hash.into() } else { let hash = unsafe { (*self.body).hash() }; - let ptr = changehash.insert(hash); + let ptr = change_hash.insert(hash); AMbyteSpan { src: ptr.0.as_ptr(), count: hash.as_ref().len(), @@ -70,11 +69,10 @@ impl AsRef for AMchange { /// \brief Gets the first referenced actor identifier in a change. /// /// \param[in] change A pointer to an `AMchange` struct. -/// \pre \p change `!= NULL`. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMactorId` struct. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item. +/// \pre \p change `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -90,8 +88,8 @@ pub unsafe extern "C" fn AMchangeActorId(change: *const AMchange) -> *mut AMresu /// \memberof AMchange /// \brief Compresses the raw bytes of a change. /// -/// \param[in,out] change A pointer to an `AMchange` struct. -/// \pre \p change `!= NULL`. +/// \param[in] change A pointer to an `AMchange` struct. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -107,18 +105,20 @@ pub unsafe extern "C" fn AMchangeCompress(change: *mut AMchange) { /// \brief Gets the dependencies of a change. /// /// \param[in] change A pointer to an `AMchange` struct. -/// \return A pointer to an `AMchangeHashes` struct or `NULL`. -/// \pre \p change `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p change `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// change must be a valid pointer to an AMchange #[no_mangle] -pub unsafe extern "C" fn AMchangeDeps(change: *const AMchange) -> AMchangeHashes { - match change.as_ref() { - Some(change) => AMchangeHashes::new(change.as_ref().deps()), +pub unsafe extern "C" fn AMchangeDeps(change: *const AMchange) -> *mut AMresult { + to_result(match change.as_ref() { + Some(change) => change.as_ref().deps(), None => Default::default(), - } + }) } /// \memberof AMchange @@ -126,7 +126,7 @@ pub unsafe extern "C" fn AMchangeDeps(change: *const AMchange) -> AMchangeHashes /// /// \param[in] change A pointer to an `AMchange` struct. /// \return An `AMbyteSpan` struct. -/// \pre \p change `!= NULL`. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -141,32 +141,33 @@ pub unsafe extern "C" fn AMchangeExtraBytes(change: *const AMchange) -> AMbyteSp } /// \memberof AMchange -/// \brief Loads a sequence of bytes into a change. +/// \brief Allocates a new change and initializes it from an array of bytes value. /// /// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes in \p src to load. -/// \return A pointer to an `AMresult` struct containing an `AMchange` struct. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] count The count of bytes to load from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_CHANGE` item. +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety -/// src must be a byte array of size `>= count` +/// src must be a byte array of length `>= count` #[no_mangle] pub unsafe extern "C" fn AMchangeFromBytes(src: *const u8, count: usize) -> *mut AMresult { - let mut data = Vec::new(); - data.extend_from_slice(std::slice::from_raw_parts(src, count)); - to_result(am::Change::from_bytes(data)) + let data = std::slice::from_raw_parts(src, count); + to_result(am::Change::from_bytes(data.to_vec())) } /// \memberof AMchange /// \brief Gets the hash of a change. /// /// \param[in] change A pointer to an `AMchange` struct. -/// \return A change hash as an `AMbyteSpan` struct. -/// \pre \p change `!= NULL`. +/// \return An `AMbyteSpan` struct for a change hash. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -183,8 +184,8 @@ pub unsafe extern "C" fn AMchangeHash(change: *const AMchange) -> AMbyteSpan { /// \brief Tests the emptiness of a change. /// /// \param[in] change A pointer to an `AMchange` struct. -/// \return A boolean. -/// \pre \p change `!= NULL`. +/// \return `true` if \p change is empty, `false` otherwise. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -198,12 +199,37 @@ pub unsafe extern "C" fn AMchangeIsEmpty(change: *const AMchange) -> bool { } } +/// \memberof AMchange +/// \brief Loads a document into a sequence of changes. +/// +/// \param[in] src A pointer to an array of bytes. +/// \param[in] count The count of bytes to load from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE` items. +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +/// \internal +/// +/// # Safety +/// src must be a byte array of length `>= count` +#[no_mangle] +pub unsafe extern "C" fn AMchangeLoadDocument(src: *const u8, count: usize) -> *mut AMresult { + let data = std::slice::from_raw_parts(src, count); + to_result::, _>>( + am::Automerge::load(data) + .and_then(|d| d.get_changes(&[]).map(|c| c.into_iter().cloned().collect())), + ) +} + /// \memberof AMchange /// \brief Gets the maximum operation index of a change. /// /// \param[in] change A pointer to an `AMchange` struct. /// \return A 64-bit unsigned integer. -/// \pre \p change `!= NULL`. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -221,8 +247,8 @@ pub unsafe extern "C" fn AMchangeMaxOp(change: *const AMchange) -> u64 { /// \brief Gets the message of a change. /// /// \param[in] change A pointer to an `AMchange` struct. -/// \return A UTF-8 string view as an `AMbyteSpan` struct. -/// \pre \p change `!= NULL`. +/// \return An `AMbyteSpan` struct for a UTF-8 string. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -240,7 +266,7 @@ pub unsafe extern "C" fn AMchangeMessage(change: *const AMchange) -> AMbyteSpan /// /// \param[in] change A pointer to an `AMchange` struct. /// \return A 64-bit unsigned integer. -/// \pre \p change `!= NULL`. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -259,7 +285,7 @@ pub unsafe extern "C" fn AMchangeSeq(change: *const AMchange) -> u64 { /// /// \param[in] change A pointer to an `AMchange` struct. /// \return A 64-bit unsigned integer. -/// \pre \p change `!= NULL`. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -267,10 +293,9 @@ pub unsafe extern "C" fn AMchangeSeq(change: *const AMchange) -> u64 { #[no_mangle] pub unsafe extern "C" fn AMchangeSize(change: *const AMchange) -> usize { if let Some(change) = change.as_ref() { - change.as_ref().len() - } else { - 0 + return change.as_ref().len(); } + 0 } /// \memberof AMchange @@ -278,7 +303,7 @@ pub unsafe extern "C" fn AMchangeSize(change: *const AMchange) -> usize { /// /// \param[in] change A pointer to an `AMchange` struct. /// \return A 64-bit unsigned integer. -/// \pre \p change `!= NULL`. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -297,7 +322,7 @@ pub unsafe extern "C" fn AMchangeStartOp(change: *const AMchange) -> u64 { /// /// \param[in] change A pointer to an `AMchange` struct. /// \return A 64-bit signed integer. -/// \pre \p change `!= NULL`. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -315,8 +340,8 @@ pub unsafe extern "C" fn AMchangeTime(change: *const AMchange) -> i64 { /// \brief Gets the raw bytes of a change. /// /// \param[in] change A pointer to an `AMchange` struct. -/// \return An `AMbyteSpan` struct. -/// \pre \p change `!= NULL`. +/// \return An `AMbyteSpan` struct for an array of bytes. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -329,28 +354,3 @@ pub unsafe extern "C" fn AMchangeRawBytes(change: *const AMchange) -> AMbyteSpan Default::default() } } - -/// \memberof AMchange -/// \brief Loads a document into a sequence of changes. -/// -/// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes in \p src to load. -/// \return A pointer to an `AMresult` struct containing a sequence of -/// `AMchange` structs. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. -/// \internal -/// -/// # Safety -/// src must be a byte array of size `>= count` -#[no_mangle] -pub unsafe extern "C" fn AMchangeLoadDocument(src: *const u8, count: usize) -> *mut AMresult { - let mut data = Vec::new(); - data.extend_from_slice(std::slice::from_raw_parts(src, count)); - to_result::, _>>( - am::Automerge::load(&data) - .and_then(|d| d.get_changes(&[]).map(|c| c.into_iter().cloned().collect())), - ) -} diff --git a/rust/automerge-c/src/change_hashes.rs b/rust/automerge-c/src/change_hashes.rs deleted file mode 100644 index 029612e9..00000000 --- a/rust/automerge-c/src/change_hashes.rs +++ /dev/null @@ -1,400 +0,0 @@ -use automerge as am; -use std::cmp::Ordering; -use std::ffi::c_void; -use std::mem::size_of; - -use crate::byte_span::AMbyteSpan; -use crate::result::{to_result, AMresult}; - -#[repr(C)] -struct Detail { - len: usize, - offset: isize, - ptr: *const c_void, -} - -/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call -/// (https://github.com/eqrion/cbindgen/issues/252) but it will -/// propagate the name of a constant initialized from it so if the -/// constant's name is a symbolic representation of the value it can be -/// converted into a number by post-processing the header it generated. -pub const USIZE_USIZE_USIZE_: usize = size_of::(); - -impl Detail { - fn new(change_hashes: &[am::ChangeHash], offset: isize) -> Self { - Self { - len: change_hashes.len(), - offset, - ptr: change_hashes.as_ptr() as *const c_void, - } - } - - pub fn advance(&mut self, n: isize) { - if n == 0 { - return; - } - let len = self.len as isize; - self.offset = if self.offset < 0 { - // It's reversed. - let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); - if unclipped >= 0 { - // Clip it to the forward stop. - len - } else { - std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) - } - } else { - let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); - if unclipped < 0 { - // Clip it to the reverse stop. - -(len + 1) - } else { - std::cmp::max(0, std::cmp::min(unclipped, len)) - } - } - } - - pub fn get_index(&self) -> usize { - (self.offset - + if self.offset < 0 { - self.len as isize - } else { - 0 - }) as usize - } - - pub fn next(&mut self, n: isize) -> Option<&am::ChangeHash> { - if self.is_stopped() { - return None; - } - let slice: &[am::ChangeHash] = - unsafe { std::slice::from_raw_parts(self.ptr as *const am::ChangeHash, self.len) }; - let value = &slice[self.get_index()]; - self.advance(n); - Some(value) - } - - pub fn is_stopped(&self) -> bool { - let len = self.len as isize; - self.offset < -len || self.offset == len - } - - pub fn prev(&mut self, n: isize) -> Option<&am::ChangeHash> { - self.advance(-n); - if self.is_stopped() { - return None; - } - let slice: &[am::ChangeHash] = - unsafe { std::slice::from_raw_parts(self.ptr as *const am::ChangeHash, self.len) }; - Some(&slice[self.get_index()]) - } - - pub fn reversed(&self) -> Self { - Self { - len: self.len, - offset: -(self.offset + 1), - ptr: self.ptr, - } - } - - pub fn rewound(&self) -> Self { - Self { - len: self.len, - offset: if self.offset < 0 { -1 } else { 0 }, - ptr: self.ptr, - } - } -} - -impl From for [u8; USIZE_USIZE_USIZE_] { - fn from(detail: Detail) -> Self { - unsafe { - std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) - .try_into() - .unwrap() - } - } -} - -/// \struct AMchangeHashes -/// \installed_headerfile -/// \brief A random-access iterator over a sequence of change hashes. -#[repr(C)] -#[derive(Eq, PartialEq)] -pub struct AMchangeHashes { - /// An implementation detail that is intentionally opaque. - /// \warning Modifying \p detail will cause undefined behavior. - /// \note The actual size of \p detail will vary by platform, this is just - /// the one for the platform this documentation was built on. - detail: [u8; USIZE_USIZE_USIZE_], -} - -impl AMchangeHashes { - pub fn new(change_hashes: &[am::ChangeHash]) -> Self { - Self { - detail: Detail::new(change_hashes, 0).into(), - } - } - - pub fn advance(&mut self, n: isize) { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.advance(n); - } - - pub fn len(&self) -> usize { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - detail.len - } - - pub fn next(&mut self, n: isize) -> Option<&am::ChangeHash> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.next(n) - } - - pub fn prev(&mut self, n: isize) -> Option<&am::ChangeHash> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.prev(n) - } - - pub fn reversed(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.reversed().into(), - } - } - - pub fn rewound(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.rewound().into(), - } - } -} - -impl AsRef<[am::ChangeHash]> for AMchangeHashes { - fn as_ref(&self) -> &[am::ChangeHash] { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - unsafe { std::slice::from_raw_parts(detail.ptr as *const am::ChangeHash, detail.len) } - } -} - -impl Default for AMchangeHashes { - fn default() -> Self { - Self { - detail: [0; USIZE_USIZE_USIZE_], - } - } -} - -/// \memberof AMchangeHashes -/// \brief Advances an iterator over a sequence of change hashes by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] change_hashes A pointer to an `AMchangeHashes` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \pre \p change_hashes `!= NULL`. -/// \internal -/// -/// #Safety -/// change_hashes must be a valid pointer to an AMchangeHashes -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesAdvance(change_hashes: *mut AMchangeHashes, n: isize) { - if let Some(change_hashes) = change_hashes.as_mut() { - change_hashes.advance(n); - }; -} - -/// \memberof AMchangeHashes -/// \brief Compares the sequences of change hashes underlying a pair of -/// iterators. -/// -/// \param[in] change_hashes1 A pointer to an `AMchangeHashes` struct. -/// \param[in] change_hashes2 A pointer to an `AMchangeHashes` struct. -/// \return `-1` if \p change_hashes1 `<` \p change_hashes2, `0` if -/// \p change_hashes1 `==` \p change_hashes2 and `1` if -/// \p change_hashes1 `>` \p change_hashes2. -/// \pre \p change_hashes1 `!= NULL`. -/// \pre \p change_hashes2 `!= NULL`. -/// \internal -/// -/// #Safety -/// change_hashes1 must be a valid pointer to an AMchangeHashes -/// change_hashes2 must be a valid pointer to an AMchangeHashes -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesCmp( - change_hashes1: *const AMchangeHashes, - change_hashes2: *const AMchangeHashes, -) -> isize { - match (change_hashes1.as_ref(), change_hashes2.as_ref()) { - (Some(change_hashes1), Some(change_hashes2)) => { - match change_hashes1.as_ref().cmp(change_hashes2.as_ref()) { - Ordering::Less => -1, - Ordering::Equal => 0, - Ordering::Greater => 1, - } - } - (None, Some(_)) => -1, - (Some(_), None) => 1, - (None, None) => 0, - } -} - -/// \memberof AMchangeHashes -/// \brief Allocates an iterator over a sequence of change hashes and -/// initializes it from a sequence of byte spans. -/// -/// \param[in] src A pointer to an array of `AMbyteSpan` structs. -/// \param[in] count The number of `AMbyteSpan` structs to copy from \p src. -/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` -/// struct. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`) / sizeof(AMbyteSpan)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. -/// \internal -/// -/// # Safety -/// src must be an AMbyteSpan array of size `>= count` -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesInit(src: *const AMbyteSpan, count: usize) -> *mut AMresult { - let mut change_hashes = Vec::::new(); - for n in 0..count { - let byte_span = &*src.add(n); - let slice = std::slice::from_raw_parts(byte_span.src, byte_span.count); - match slice.try_into() { - Ok(change_hash) => { - change_hashes.push(change_hash); - } - Err(e) => { - return to_result(Err(e)); - } - } - } - to_result(Ok::, am::InvalidChangeHashSlice>( - change_hashes, - )) -} - -/// \memberof AMchangeHashes -/// \brief Gets the change hash at the current position of an iterator over a -/// sequence of change hashes and then advances it by at most \p |n| -/// positions where the sign of \p n is relative to the iterator's -/// direction. -/// -/// \param[in,out] change_hashes A pointer to an `AMchangeHashes` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return An `AMbyteSpan` struct with `.src == NULL` when \p change_hashes -/// was previously advanced past its forward/reverse limit. -/// \pre \p change_hashes `!= NULL`. -/// \internal -/// -/// #Safety -/// change_hashes must be a valid pointer to an AMchangeHashes -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesNext( - change_hashes: *mut AMchangeHashes, - n: isize, -) -> AMbyteSpan { - if let Some(change_hashes) = change_hashes.as_mut() { - if let Some(change_hash) = change_hashes.next(n) { - return change_hash.into(); - } - } - Default::default() -} - -/// \memberof AMchangeHashes -/// \brief Advances an iterator over a sequence of change hashes by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction and then gets the change hash at its new -/// position. -/// -/// \param[in,out] change_hashes A pointer to an `AMchangeHashes` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return An `AMbyteSpan` struct with `.src == NULL` when \p change_hashes is -/// presently advanced past its forward/reverse limit. -/// \pre \p change_hashes `!= NULL`. -/// \internal -/// -/// #Safety -/// change_hashes must be a valid pointer to an AMchangeHashes -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesPrev( - change_hashes: *mut AMchangeHashes, - n: isize, -) -> AMbyteSpan { - if let Some(change_hashes) = change_hashes.as_mut() { - if let Some(change_hash) = change_hashes.prev(n) { - return change_hash.into(); - } - } - Default::default() -} - -/// \memberof AMchangeHashes -/// \brief Gets the size of the sequence of change hashes underlying an -/// iterator. -/// -/// \param[in] change_hashes A pointer to an `AMchangeHashes` struct. -/// \return The count of values in \p change_hashes. -/// \pre \p change_hashes `!= NULL`. -/// \internal -/// -/// #Safety -/// change_hashes must be a valid pointer to an AMchangeHashes -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesSize(change_hashes: *const AMchangeHashes) -> usize { - if let Some(change_hashes) = change_hashes.as_ref() { - change_hashes.len() - } else { - 0 - } -} - -/// \memberof AMchangeHashes -/// \brief Creates an iterator over the same sequence of change hashes as the -/// given one but with the opposite position and direction. -/// -/// \param[in] change_hashes A pointer to an `AMchangeHashes` struct. -/// \return An `AMchangeHashes` struct -/// \pre \p change_hashes `!= NULL`. -/// \internal -/// -/// #Safety -/// change_hashes must be a valid pointer to an AMchangeHashes -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesReversed( - change_hashes: *const AMchangeHashes, -) -> AMchangeHashes { - if let Some(change_hashes) = change_hashes.as_ref() { - change_hashes.reversed() - } else { - Default::default() - } -} - -/// \memberof AMchangeHashes -/// \brief Creates an iterator at the starting position over the same sequence -/// of change hashes as the given one. -/// -/// \param[in] change_hashes A pointer to an `AMchangeHashes` struct. -/// \return An `AMchangeHashes` struct -/// \pre \p change_hashes `!= NULL`. -/// \internal -/// -/// #Safety -/// change_hashes must be a valid pointer to an AMchangeHashes -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesRewound( - change_hashes: *const AMchangeHashes, -) -> AMchangeHashes { - if let Some(change_hashes) = change_hashes.as_ref() { - change_hashes.rewound() - } else { - Default::default() - } -} diff --git a/rust/automerge-c/src/changes.rs b/rust/automerge-c/src/changes.rs deleted file mode 100644 index 1bff35c8..00000000 --- a/rust/automerge-c/src/changes.rs +++ /dev/null @@ -1,399 +0,0 @@ -use automerge as am; -use std::collections::BTreeMap; -use std::ffi::c_void; -use std::mem::size_of; - -use crate::byte_span::AMbyteSpan; -use crate::change::AMchange; -use crate::result::{to_result, AMresult}; - -#[repr(C)] -struct Detail { - len: usize, - offset: isize, - ptr: *const c_void, - storage: *mut c_void, -} - -/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call -/// (https://github.com/eqrion/cbindgen/issues/252) but it will -/// propagate the name of a constant initialized from it so if the -/// constant's name is a symbolic representation of the value it can be -/// converted into a number by post-processing the header it generated. -pub const USIZE_USIZE_USIZE_USIZE_: usize = size_of::(); - -impl Detail { - fn new(changes: &[am::Change], offset: isize, storage: &mut BTreeMap) -> Self { - let storage: *mut BTreeMap = storage; - Self { - len: changes.len(), - offset, - ptr: changes.as_ptr() as *const c_void, - storage: storage as *mut c_void, - } - } - - pub fn advance(&mut self, n: isize) { - if n == 0 { - return; - } - let len = self.len as isize; - self.offset = if self.offset < 0 { - // It's reversed. - let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); - if unclipped >= 0 { - // Clip it to the forward stop. - len - } else { - std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) - } - } else { - let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); - if unclipped < 0 { - // Clip it to the reverse stop. - -(len + 1) - } else { - std::cmp::max(0, std::cmp::min(unclipped, len)) - } - } - } - - pub fn get_index(&self) -> usize { - (self.offset - + if self.offset < 0 { - self.len as isize - } else { - 0 - }) as usize - } - - pub fn next(&mut self, n: isize) -> Option<*const AMchange> { - if self.is_stopped() { - return None; - } - let slice: &mut [am::Change] = - unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut am::Change, self.len) }; - let storage = unsafe { &mut *(self.storage as *mut BTreeMap) }; - let index = self.get_index(); - let value = match storage.get_mut(&index) { - Some(value) => value, - None => { - storage.insert(index, AMchange::new(&mut slice[index])); - storage.get_mut(&index).unwrap() - } - }; - self.advance(n); - Some(value) - } - - pub fn is_stopped(&self) -> bool { - let len = self.len as isize; - self.offset < -len || self.offset == len - } - - pub fn prev(&mut self, n: isize) -> Option<*const AMchange> { - self.advance(-n); - if self.is_stopped() { - return None; - } - let slice: &mut [am::Change] = - unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut am::Change, self.len) }; - let storage = unsafe { &mut *(self.storage as *mut BTreeMap) }; - let index = self.get_index(); - Some(match storage.get_mut(&index) { - Some(value) => value, - None => { - storage.insert(index, AMchange::new(&mut slice[index])); - storage.get_mut(&index).unwrap() - } - }) - } - - pub fn reversed(&self) -> Self { - Self { - len: self.len, - offset: -(self.offset + 1), - ptr: self.ptr, - storage: self.storage, - } - } - - pub fn rewound(&self) -> Self { - Self { - len: self.len, - offset: if self.offset < 0 { -1 } else { 0 }, - ptr: self.ptr, - storage: self.storage, - } - } -} - -impl From for [u8; USIZE_USIZE_USIZE_USIZE_] { - fn from(detail: Detail) -> Self { - unsafe { - std::slice::from_raw_parts( - (&detail as *const Detail) as *const u8, - USIZE_USIZE_USIZE_USIZE_, - ) - .try_into() - .unwrap() - } - } -} - -/// \struct AMchanges -/// \installed_headerfile -/// \brief A random-access iterator over a sequence of changes. -#[repr(C)] -#[derive(Eq, PartialEq)] -pub struct AMchanges { - /// An implementation detail that is intentionally opaque. - /// \warning Modifying \p detail will cause undefined behavior. - /// \note The actual size of \p detail will vary by platform, this is just - /// the one for the platform this documentation was built on. - detail: [u8; USIZE_USIZE_USIZE_USIZE_], -} - -impl AMchanges { - pub fn new(changes: &[am::Change], storage: &mut BTreeMap) -> Self { - Self { - detail: Detail::new(changes, 0, &mut *storage).into(), - } - } - - pub fn advance(&mut self, n: isize) { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.advance(n); - } - - pub fn len(&self) -> usize { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - detail.len - } - - pub fn next(&mut self, n: isize) -> Option<*const AMchange> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.next(n) - } - - pub fn prev(&mut self, n: isize) -> Option<*const AMchange> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.prev(n) - } - - pub fn reversed(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.reversed().into(), - } - } - - pub fn rewound(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.rewound().into(), - } - } -} - -impl AsRef<[am::Change]> for AMchanges { - fn as_ref(&self) -> &[am::Change] { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - unsafe { std::slice::from_raw_parts(detail.ptr as *const am::Change, detail.len) } - } -} - -impl Default for AMchanges { - fn default() -> Self { - Self { - detail: [0; USIZE_USIZE_USIZE_USIZE_], - } - } -} - -/// \memberof AMchanges -/// \brief Advances an iterator over a sequence of changes by at most \p |n| -/// positions where the sign of \p n is relative to the iterator's -/// direction. -/// -/// \param[in,out] changes A pointer to an `AMchanges` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \pre \p changes `!= NULL`. -/// \internal -/// -/// #Safety -/// changes must be a valid pointer to an AMchanges -#[no_mangle] -pub unsafe extern "C" fn AMchangesAdvance(changes: *mut AMchanges, n: isize) { - if let Some(changes) = changes.as_mut() { - changes.advance(n); - }; -} - -/// \memberof AMchanges -/// \brief Tests the equality of two sequences of changes underlying a pair of -/// iterators. -/// -/// \param[in] changes1 A pointer to an `AMchanges` struct. -/// \param[in] changes2 A pointer to an `AMchanges` struct. -/// \return `true` if \p changes1 `==` \p changes2 and `false` otherwise. -/// \pre \p changes1 `!= NULL`. -/// \pre \p changes2 `!= NULL`. -/// \internal -/// -/// #Safety -/// changes1 must be a valid pointer to an AMchanges -/// changes2 must be a valid pointer to an AMchanges -#[no_mangle] -pub unsafe extern "C" fn AMchangesEqual( - changes1: *const AMchanges, - changes2: *const AMchanges, -) -> bool { - match (changes1.as_ref(), changes2.as_ref()) { - (Some(changes1), Some(changes2)) => changes1.as_ref() == changes2.as_ref(), - (None, Some(_)) | (Some(_), None) | (None, None) => false, - } -} - -/// \memberof AMchanges -/// \brief Allocates an iterator over a sequence of changes and initializes it -/// from a sequence of byte spans. -/// -/// \param[in] src A pointer to an array of `AMbyteSpan` structs. -/// \param[in] count The number of `AMbyteSpan` structs to copy from \p src. -/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`) / sizeof(AMbyteSpan)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. -/// \internal -/// -/// # Safety -/// src must be an AMbyteSpan array of size `>= count` -#[no_mangle] -pub unsafe extern "C" fn AMchangesInit(src: *const AMbyteSpan, count: usize) -> *mut AMresult { - let mut changes = Vec::::new(); - for n in 0..count { - let byte_span = &*src.add(n); - let slice = std::slice::from_raw_parts(byte_span.src, byte_span.count); - match slice.try_into() { - Ok(change) => { - changes.push(change); - } - Err(e) => { - return to_result(Err::, am::LoadChangeError>(e)); - } - } - } - to_result(Ok::, am::LoadChangeError>(changes)) -} - -/// \memberof AMchanges -/// \brief Gets the change at the current position of an iterator over a -/// sequence of changes and then advances it by at most \p |n| positions -/// where the sign of \p n is relative to the iterator's direction. -/// -/// \param[in,out] changes A pointer to an `AMchanges` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMchange` struct that's `NULL` when \p changes was -/// previously advanced past its forward/reverse limit. -/// \pre \p changes `!= NULL`. -/// \internal -/// -/// #Safety -/// changes must be a valid pointer to an AMchanges -#[no_mangle] -pub unsafe extern "C" fn AMchangesNext(changes: *mut AMchanges, n: isize) -> *const AMchange { - if let Some(changes) = changes.as_mut() { - if let Some(change) = changes.next(n) { - return change; - } - } - std::ptr::null() -} - -/// \memberof AMchanges -/// \brief Advances an iterator over a sequence of changes by at most \p |n| -/// positions where the sign of \p n is relative to the iterator's -/// direction and then gets the change at its new position. -/// -/// \param[in,out] changes A pointer to an `AMchanges` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMchange` struct that's `NULL` when \p changes is -/// presently advanced past its forward/reverse limit. -/// \pre \p changes `!= NULL`. -/// \internal -/// -/// #Safety -/// changes must be a valid pointer to an AMchanges -#[no_mangle] -pub unsafe extern "C" fn AMchangesPrev(changes: *mut AMchanges, n: isize) -> *const AMchange { - if let Some(changes) = changes.as_mut() { - if let Some(change) = changes.prev(n) { - return change; - } - } - std::ptr::null() -} - -/// \memberof AMchanges -/// \brief Gets the size of the sequence of changes underlying an iterator. -/// -/// \param[in] changes A pointer to an `AMchanges` struct. -/// \return The count of values in \p changes. -/// \pre \p changes `!= NULL`. -/// \internal -/// -/// #Safety -/// changes must be a valid pointer to an AMchanges -#[no_mangle] -pub unsafe extern "C" fn AMchangesSize(changes: *const AMchanges) -> usize { - if let Some(changes) = changes.as_ref() { - changes.len() - } else { - 0 - } -} - -/// \memberof AMchanges -/// \brief Creates an iterator over the same sequence of changes as the given -/// one but with the opposite position and direction. -/// -/// \param[in] changes A pointer to an `AMchanges` struct. -/// \return An `AMchanges` struct. -/// \pre \p changes `!= NULL`. -/// \internal -/// -/// #Safety -/// changes must be a valid pointer to an AMchanges -#[no_mangle] -pub unsafe extern "C" fn AMchangesReversed(changes: *const AMchanges) -> AMchanges { - if let Some(changes) = changes.as_ref() { - changes.reversed() - } else { - Default::default() - } -} - -/// \memberof AMchanges -/// \brief Creates an iterator at the starting position over the same sequence -/// of changes as the given one. -/// -/// \param[in] changes A pointer to an `AMchanges` struct. -/// \return An `AMchanges` struct -/// \pre \p changes `!= NULL`. -/// \internal -/// -/// #Safety -/// changes must be a valid pointer to an AMchanges -#[no_mangle] -pub unsafe extern "C" fn AMchangesRewound(changes: *const AMchanges) -> AMchanges { - if let Some(changes) = changes.as_ref() { - changes.rewound() - } else { - Default::default() - } -} diff --git a/rust/automerge-c/src/doc.rs b/rust/automerge-c/src/doc.rs index f02c01bf..82f52bf7 100644 --- a/rust/automerge-c/src/doc.rs +++ b/rust/automerge-c/src/doc.rs @@ -6,43 +6,23 @@ use std::ops::{Deref, DerefMut}; use crate::actor_id::{to_actor_id, AMactorId}; use crate::byte_span::{to_str, AMbyteSpan}; -use crate::change_hashes::AMchangeHashes; +use crate::items::AMitems; use crate::obj::{to_obj_id, AMobjId, AMobjType}; -use crate::result::{to_result, AMresult, AMvalue}; +use crate::result::{to_result, AMresult}; use crate::sync::{to_sync_message, AMsyncMessage, AMsyncState}; pub mod list; pub mod map; pub mod utils; -use crate::changes::AMchanges; -use crate::doc::utils::{to_doc, to_doc_mut}; - -macro_rules! to_changes { - ($handle:expr) => {{ - let handle = $handle.as_ref(); - match handle { - Some(b) => b, - None => return AMresult::err("Invalid AMchanges pointer").into(), - } - }}; -} - -macro_rules! to_index { - ($index:expr, $len:expr, $param_name:expr) => {{ - if $index > $len && $index != usize::MAX { - return AMresult::err(&format!("Invalid {} {}", $param_name, $index)).into(); - } - std::cmp::min($index, $len) - }}; -} +use crate::doc::utils::{clamp, to_doc, to_doc_mut, to_items}; macro_rules! to_sync_state_mut { ($handle:expr) => {{ let handle = $handle.as_mut(); match handle { Some(b) => b, - None => return AMresult::err("Invalid AMsyncState pointer").into(), + None => return AMresult::error("Invalid `AMsyncState*`").into(), } }}; } @@ -57,6 +37,10 @@ impl AMdoc { pub fn new(auto_commit: am::AutoCommit) -> Self { Self(auto_commit) } + + pub fn is_equal_to(&mut self, other: &mut Self) -> bool { + self.document().get_heads() == other.document().get_heads() + } } impl AsRef for AMdoc { @@ -82,38 +66,38 @@ impl DerefMut for AMdoc { /// \memberof AMdoc /// \brief Applies a sequence of changes to a document. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \param[in] changes A pointer to an `AMchanges` struct. -/// \pre \p doc `!= NULL`. -/// \pre \p changes `!= NULL`. -/// \return A pointer to an `AMresult` struct containing a void. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in] items A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE` +/// items. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p items `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc -/// changes must be a valid pointer to an AMchanges. +/// items must be a valid pointer to an AMitems. #[no_mangle] -pub unsafe extern "C" fn AMapplyChanges( - doc: *mut AMdoc, - changes: *const AMchanges, -) -> *mut AMresult { +pub unsafe extern "C" fn AMapplyChanges(doc: *mut AMdoc, items: *const AMitems) -> *mut AMresult { let doc = to_doc_mut!(doc); - let changes = to_changes!(changes); - to_result(doc.apply_changes(changes.as_ref().to_vec())) + let items = to_items!(items); + match Vec::::try_from(items) { + Ok(changes) => to_result(doc.apply_changes(changes)), + Err(e) => AMresult::error(&e.to_string()).into(), + } } /// \memberof AMdoc /// \brief Allocates storage for a document and initializes it by duplicating /// the given document. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMdoc` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_DOC` item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -129,10 +113,9 @@ pub unsafe extern "C" fn AMclone(doc: *const AMdoc) -> *mut AMresult { /// /// \param[in] actor_id A pointer to an `AMactorId` struct or `NULL` for a /// random one. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMdoc` struct. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_DOC` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -149,15 +132,15 @@ pub unsafe extern "C" fn AMcreate(actor_id: *const AMactorId) -> *mut AMresult { /// \brief Commits the current operations on a document with an optional /// message and/or *nix timestamp (milliseconds). /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] message A UTF-8 string view as an `AMbyteSpan` struct. /// \param[in] timestamp A pointer to a 64-bit integer or `NULL`. -/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` -/// with one element if there were operations to commit, or void if -/// there were no operations to commit. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with one `AM_VAL_TYPE_CHANGE_HASH` +/// item if there were operations to commit or an `AM_VAL_TYPE_VOID` item +/// if there were no operations to commit. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -183,24 +166,24 @@ pub unsafe extern "C" fn AMcommit( /// \brief Creates an empty change with an optional message and/or *nix /// timestamp (milliseconds). /// -/// This is useful if you wish to create a "merge commit" which has as its -/// dependents the current heads of the document but you don't have any -/// operations to add to the document. +/// \details This is useful if you wish to create a "merge commit" which has as +/// its dependents the current heads of the document but you don't have +/// any operations to add to the document. /// /// \note If there are outstanding uncommitted changes to the document -/// then two changes will be created: one for creating the outstanding changes -/// and one for the empty change. The empty change will always be the -/// latest change in the document after this call and the returned hash will be -/// the hash of that empty change. +/// then two changes will be created: one for creating the outstanding +/// changes and one for the empty change. The empty change will always be +/// the latest change in the document after this call and the returned +/// hash will be the hash of that empty change. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] message A UTF-8 string view as an `AMbyteSpan` struct. /// \param[in] timestamp A pointer to a 64-bit integer or `NULL`. -/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` -/// with one element. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with one `AM_VAL_TYPE_CHANGE_HASH` +/// item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -226,11 +209,11 @@ pub unsafe extern "C" fn AMemptyChange( /// \brief Tests the equality of two documents after closing their respective /// transactions. /// -/// \param[in,out] doc1 An `AMdoc` struct. -/// \param[in,out] doc2 An `AMdoc` struct. +/// \param[in] doc1 A pointer to an `AMdoc` struct. +/// \param[in] doc2 A pointer to an `AMdoc` struct. /// \return `true` if \p doc1 `==` \p doc2 and `false` otherwise. -/// \pre \p doc1 `!= NULL`. -/// \pre \p doc2 `!= NULL`. +/// \pre \p doc1 `!= NULL` +/// \pre \p doc2 `!= NULL` /// \internal /// /// #Safety @@ -239,33 +222,36 @@ pub unsafe extern "C" fn AMemptyChange( #[no_mangle] pub unsafe extern "C" fn AMequal(doc1: *mut AMdoc, doc2: *mut AMdoc) -> bool { match (doc1.as_mut(), doc2.as_mut()) { - (Some(doc1), Some(doc2)) => doc1.document().get_heads() == doc2.document().get_heads(), - (None, Some(_)) | (Some(_), None) | (None, None) => false, + (Some(doc1), Some(doc2)) => doc1.is_equal_to(doc2), + (None, None) | (None, Some(_)) | (Some(_), None) => false, } } /// \memberof AMdoc -/// \brief Forks this document at the current or a historical point for use by +/// \brief Forks this document at its current or a historical point for use by /// a different actor. -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical -/// point or `NULL` for the current point. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMdoc` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select a historical point or `NULL` to select its +/// current point. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] -pub unsafe extern "C" fn AMfork(doc: *mut AMdoc, heads: *const AMchangeHashes) -> *mut AMresult { +pub unsafe extern "C" fn AMfork(doc: *mut AMdoc, heads: *const AMitems) -> *mut AMresult { let doc = to_doc_mut!(doc); match heads.as_ref() { None => to_result(doc.fork()), - Some(heads) => to_result(doc.fork_at(heads.as_ref())), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.fork_at(&heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } @@ -273,14 +259,14 @@ pub unsafe extern "C" fn AMfork(doc: *mut AMdoc, heads: *const AMchangeHashes) - /// \brief Generates a synchronization message for a peer based upon the given /// synchronization state. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \param[in,out] sync_state A pointer to an `AMsyncState` struct. -/// \return A pointer to an `AMresult` struct containing either a pointer to an -/// `AMsyncMessage` struct or a void. -/// \pre \p doc `!= NULL`. -/// \pre \p sync_state `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in] sync_state A pointer to an `AMsyncState` struct. +/// \return A pointer to an `AMresult` struct with either an +/// `AM_VAL_TYPE_SYNC_MESSAGE` or `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p sync_state `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -300,11 +286,10 @@ pub unsafe extern "C" fn AMgenerateSyncMessage( /// \brief Gets a document's actor identifier. /// /// \param[in] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMactorId` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -320,20 +305,22 @@ pub unsafe extern "C" fn AMgetActorId(doc: *const AMdoc) -> *mut AMresult { /// \memberof AMdoc /// \brief Gets the change added to a document by its respective hash. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes in \p src. -/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct. -/// \pre \p doc `!= NULL`. -/// \pre \p src `!= NULL`. -/// \pre \p count `>= AM_CHANGE_HASH_SIZE`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] count The count of bytes to copy from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_CHANGE` item. +/// \pre \p doc `!= NULL` +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src') >= AM_CHANGE_HASH_SIZE` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc -/// src must be a byte array of size `>= automerge::types::HASH_SIZE` +/// src must be a byte array of length `>= automerge::types::HASH_SIZE` #[no_mangle] pub unsafe extern "C" fn AMgetChangeByHash( doc: *mut AMdoc, @@ -344,48 +331,48 @@ pub unsafe extern "C" fn AMgetChangeByHash( let slice = std::slice::from_raw_parts(src, count); match slice.try_into() { Ok(change_hash) => to_result(doc.get_change_by_hash(&change_hash)), - Err(e) => AMresult::err(&e.to_string()).into(), + Err(e) => AMresult::error(&e.to_string()).into(), } } /// \memberof AMdoc /// \brief Gets the changes added to a document by their respective hashes. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \param[in] have_deps A pointer to an `AMchangeHashes` struct or `NULL`. -/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in] have_deps A pointer to an `AMitems` struct with +/// `AM_VAL_TYPE_CHANGE_HASH` items or `NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE` items. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc #[no_mangle] -pub unsafe extern "C" fn AMgetChanges( - doc: *mut AMdoc, - have_deps: *const AMchangeHashes, -) -> *mut AMresult { +pub unsafe extern "C" fn AMgetChanges(doc: *mut AMdoc, have_deps: *const AMitems) -> *mut AMresult { let doc = to_doc_mut!(doc); - let empty_deps = Vec::::new(); let have_deps = match have_deps.as_ref() { - Some(have_deps) => have_deps.as_ref(), - None => &empty_deps, + Some(have_deps) => match Vec::::try_from(have_deps) { + Ok(change_hashes) => change_hashes, + Err(e) => return AMresult::error(&e.to_string()).into(), + }, + None => Vec::::new(), }; - to_result(doc.get_changes(have_deps)) + to_result(doc.get_changes(&have_deps)) } /// \memberof AMdoc /// \brief Gets the changes added to a second document that weren't added to /// a first document. /// -/// \param[in,out] doc1 An `AMdoc` struct. -/// \param[in,out] doc2 An `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct. -/// \pre \p doc1 `!= NULL`. -/// \pre \p doc2 `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc1 A pointer to an `AMdoc` struct. +/// \param[in] doc2 A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE` items. +/// \pre \p doc1 `!= NULL` +/// \pre \p doc2 `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -401,12 +388,11 @@ pub unsafe extern "C" fn AMgetChangesAdded(doc1: *mut AMdoc, doc2: *mut AMdoc) - /// \memberof AMdoc /// \brief Gets the current heads of a document. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` -/// struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -423,41 +409,42 @@ pub unsafe extern "C" fn AMgetHeads(doc: *mut AMdoc) -> *mut AMresult { /// \brief Gets the hashes of the changes in a document that aren't transitive /// dependencies of the given hashes of changes. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \param[in] heads A pointer to an `AMchangeHashes` struct or `NULL`. -/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` -/// struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items or `NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] -pub unsafe extern "C" fn AMgetMissingDeps( - doc: *mut AMdoc, - heads: *const AMchangeHashes, -) -> *mut AMresult { +pub unsafe extern "C" fn AMgetMissingDeps(doc: *mut AMdoc, heads: *const AMitems) -> *mut AMresult { let doc = to_doc_mut!(doc); - let empty_heads = Vec::::new(); let heads = match heads.as_ref() { - Some(heads) => heads.as_ref(), - None => &empty_heads, + None => Vec::::new(), + Some(heads) => match >::try_from(heads) { + Ok(heads) => heads, + Err(e) => { + return AMresult::error(&e.to_string()).into(); + } + }, }; - to_result(doc.get_missing_deps(heads)) + to_result(doc.get_missing_deps(heads.as_slice())) } /// \memberof AMdoc /// \brief Gets the last change made to a document. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing either an `AMchange` -/// struct or a void. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct containing either an +/// `AM_VAL_TYPE_CHANGE` or `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -473,29 +460,33 @@ pub unsafe extern "C" fn AMgetLastLocalChange(doc: *mut AMdoc) -> *mut AMresult /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical -/// keys or `NULL` for current keys. -/// \return A pointer to an `AMresult` struct containing an `AMstrs` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select historical keys or `NULL` to select current +/// keys. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_STR` items. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMkeys( doc: *const AMdoc, obj_id: *const AMobjId, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); match heads.as_ref() { None => to_result(doc.keys(obj_id)), - Some(heads) => to_result(doc.keys_at(obj_id, heads.as_ref())), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.keys_at(obj_id, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } @@ -504,42 +495,43 @@ pub unsafe extern "C" fn AMkeys( /// form of an incremental save. /// /// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes in \p src to load. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMdoc` struct. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] count The count of bytes to load from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_DOC` item. +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety -/// src must be a byte array of size `>= count` +/// src must be a byte array of length `>= count` #[no_mangle] pub unsafe extern "C" fn AMload(src: *const u8, count: usize) -> *mut AMresult { - let mut data = Vec::new(); - data.extend_from_slice(std::slice::from_raw_parts(src, count)); - to_result(am::AutoCommit::load(&data)) + let data = std::slice::from_raw_parts(src, count); + to_result(am::AutoCommit::load(data)) } /// \memberof AMdoc /// \brief Loads the compact form of an incremental save into a document. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes in \p src to load. -/// \return A pointer to an `AMresult` struct containing the number of -/// operations loaded from \p src. -/// \pre \p doc `!= NULL`. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] count The count of bytes to load from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_UINT` item. +/// \pre \p doc `!= NULL` +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc -/// src must be a byte array of size `>= count` +/// src must be a byte array of length `>= count` #[no_mangle] pub unsafe extern "C" fn AMloadIncremental( doc: *mut AMdoc, @@ -547,23 +539,21 @@ pub unsafe extern "C" fn AMloadIncremental( count: usize, ) -> *mut AMresult { let doc = to_doc_mut!(doc); - let mut data = Vec::new(); - data.extend_from_slice(std::slice::from_raw_parts(src, count)); - to_result(doc.load_incremental(&data)) + let data = std::slice::from_raw_parts(src, count); + to_result(doc.load_incremental(data)) } /// \memberof AMdoc /// \brief Applies all of the changes in \p src which are not in \p dest to /// \p dest. /// -/// \param[in,out] dest A pointer to an `AMdoc` struct. -/// \param[in,out] src A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` -/// struct. -/// \pre \p dest `!= NULL`. -/// \pre \p src `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] dest A pointer to an `AMdoc` struct. +/// \param[in] src A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p dest `!= NULL` +/// \pre \p src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -580,31 +570,37 @@ pub unsafe extern "C" fn AMmerge(dest: *mut AMdoc, src: *mut AMdoc) -> *mut AMre /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical -/// size or `NULL` for current size. -/// \return A 64-bit unsigned integer. -/// \pre \p doc `!= NULL`. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select a historical size or `NULL` to select its +/// current size. +/// \return The count of items in the object identified by \p obj_id. +/// \pre \p doc `!= NULL` /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMobjSize( doc: *const AMdoc, obj_id: *const AMobjId, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> usize { if let Some(doc) = doc.as_ref() { let obj_id = to_obj_id!(obj_id); match heads.as_ref() { - None => doc.length(obj_id), - Some(heads) => doc.length_at(obj_id, heads.as_ref()), + None => { + return doc.length(obj_id); + } + Some(heads) => { + if let Ok(heads) = >::try_from(heads) { + return doc.length_at(obj_id, &heads); + } + } } - } else { - 0 } + 0 } /// \memberof AMdoc @@ -612,8 +608,9 @@ pub unsafe extern "C" fn AMobjSize( /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \return An `AMobjType`. -/// \pre \p doc `!= NULL`. +/// \return An `AMobjType` tag or `0`. +/// \pre \p doc `!= NULL` +/// \pre \p obj_id `!= NULL` /// \internal /// /// # Safety @@ -623,44 +620,45 @@ pub unsafe extern "C" fn AMobjSize( pub unsafe extern "C" fn AMobjObjType(doc: *const AMdoc, obj_id: *const AMobjId) -> AMobjType { if let Some(doc) = doc.as_ref() { let obj_id = to_obj_id!(obj_id); - match doc.object_type(obj_id) { - Err(_) => AMobjType::Void, - Ok(obj_type) => obj_type.into(), + if let Ok(obj_type) = doc.object_type(obj_id) { + return (&obj_type).into(); } - } else { - AMobjType::Void } + Default::default() } /// \memberof AMdoc -/// \brief Gets the current or historical values of an object within its entire -/// range. +/// \brief Gets the current or historical items of an entire object. /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical -/// items or `NULL` for current items. -/// \return A pointer to an `AMresult` struct containing an `AMobjItems` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select its historical items or `NULL` to select +/// its current items. +/// \return A pointer to an `AMresult` struct with an `AMitems` struct. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] -pub unsafe extern "C" fn AMobjValues( +pub unsafe extern "C" fn AMobjItems( doc: *const AMdoc, obj_id: *const AMobjId, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); match heads.as_ref() { None => to_result(doc.values(obj_id)), - Some(heads) => to_result(doc.values_at(obj_id, heads.as_ref())), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.values_at(obj_id, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } @@ -670,7 +668,7 @@ pub unsafe extern "C" fn AMobjValues( /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \return The count of pending operations for \p doc. -/// \pre \p doc `!= NULL`. +/// \pre \p doc `!= NULL` /// \internal /// /// # Safety @@ -678,23 +676,22 @@ pub unsafe extern "C" fn AMobjValues( #[no_mangle] pub unsafe extern "C" fn AMpendingOps(doc: *const AMdoc) -> usize { if let Some(doc) = doc.as_ref() { - doc.pending_ops() - } else { - 0 + return doc.pending_ops(); } + 0 } /// \memberof AMdoc /// \brief Receives a synchronization message from a peer based upon a given /// synchronization state. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \param[in,out] sync_state A pointer to an `AMsyncState` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in] sync_state A pointer to an `AMsyncState` struct. /// \param[in] sync_message A pointer to an `AMsyncMessage` struct. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p sync_state `!= NULL`. -/// \pre \p sync_message `!= NULL`. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p sync_state `!= NULL` +/// \pre \p sync_message `!= NULL` /// \internal /// /// # Safety @@ -720,9 +717,9 @@ pub unsafe extern "C" fn AMreceiveSyncMessage( /// \brief Cancels the pending operations added during a document's current /// transaction and gets the number of cancellations. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \return The count of pending operations for \p doc that were cancelled. -/// \pre \p doc `!= NULL`. +/// \pre \p doc `!= NULL` /// \internal /// /// # Safety @@ -730,21 +727,19 @@ pub unsafe extern "C" fn AMreceiveSyncMessage( #[no_mangle] pub unsafe extern "C" fn AMrollback(doc: *mut AMdoc) -> usize { if let Some(doc) = doc.as_mut() { - doc.rollback() - } else { - 0 + return doc.rollback(); } + 0 } /// \memberof AMdoc /// \brief Saves the entirety of a document into a compact form. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing an array of bytes as -/// an `AMbyteSpan` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTES` item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -759,12 +754,11 @@ pub unsafe extern "C" fn AMsave(doc: *mut AMdoc) -> *mut AMresult { /// \brief Saves the changes to a document since its last save into a compact /// form. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing an array of bytes as -/// an `AMbyteSpan` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTES` item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -778,13 +772,13 @@ pub unsafe extern "C" fn AMsaveIncremental(doc: *mut AMdoc) -> *mut AMresult { /// \memberof AMdoc /// \brief Puts the actor identifier of a document. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] actor_id A pointer to an `AMactorId` struct. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p actor_id `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p actor_id `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -805,76 +799,65 @@ pub unsafe extern "C" fn AMsetActorId( /// \brief Splices values into and/or removes values from the identified object /// at a given position within it. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] pos A position in the object identified by \p obj_id or /// `SIZE_MAX` to indicate one past its end. -/// \param[in] del The number of characters to delete or `SIZE_MAX` to indicate +/// \param[in] del The number of values to delete or `SIZE_MAX` to indicate /// all of them. -/// \param[in] src A pointer to an array of `AMvalue` structs. -/// \param[in] count The number of `AMvalue` structs in \p src to load. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id`)` or \p pos `== SIZE_MAX`. -/// \pre `0 <=` \p del `<= AMobjSize(`\p obj_id`)` or \p del `== SIZE_MAX`. -/// \pre `(`\p src `!= NULL and 1 <=` \p count `<= sizeof(`\p src`)/ -/// sizeof(AMvalue)) or `\p src `== NULL or `\p count `== 0`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] values A copy of an `AMitems` struct from which values will be +/// spliced starting at its current position; call +/// `AMitemsRewound()` on a used `AMitems` first to ensure +/// that all of its values are spliced in. Pass `(AMitems){0}` +/// when zero values should be spliced in. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \pre `0 <=` \p del `<= AMobjSize(`\p obj_id `)` or \p del `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// src must be an AMvalue array of size `>= count` or std::ptr::null() +/// values must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMsplice( doc: *mut AMdoc, obj_id: *const AMobjId, pos: usize, del: usize, - src: *const AMvalue, - count: usize, + values: AMitems, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); let len = doc.length(obj_id); - let pos = to_index!(pos, len, "pos"); - let del = to_index!(del, len, "del"); - let mut vals: Vec = vec![]; - if !(src.is_null() || count == 0) { - let c_vals = std::slice::from_raw_parts(src, count); - for c_val in c_vals { - match c_val.try_into() { - Ok(s) => { - vals.push(s); - } - Err(e) => { - return AMresult::err(&e.to_string()).into(); - } - } - } + let pos = clamp!(pos, len, "pos"); + let del = clamp!(del, len, "del"); + match Vec::::try_from(&values) { + Ok(vals) => to_result(doc.splice(obj_id, pos, del, vals)), + Err(e) => AMresult::error(&e.to_string()).into(), } - to_result(doc.splice(obj_id, pos, del, vals)) } /// \memberof AMdoc /// \brief Splices characters into and/or removes characters from the /// identified object at a given position within it. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] pos A position in the text object identified by \p obj_id or /// `SIZE_MAX` to indicate one past its end. /// \param[in] del The number of characters to delete or `SIZE_MAX` to indicate /// all of them. /// \param[in] text A UTF-8 string view as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id`)` or \p pos `== SIZE_MAX`. -/// \pre `0 <=` \p del `<= AMobjSize(`\p obj_id`)` or \p del `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \pre `0 <=` \p del `<= AMobjSize(`\p obj_id `)` or \p del `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -891,8 +874,8 @@ pub unsafe extern "C" fn AMspliceText( let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); let len = doc.length(obj_id); - let pos = to_index!(pos, len, "pos"); - let del = to_index!(del, len, "del"); + let pos = clamp!(pos, len, "pos"); + let del = clamp!(del, len, "del"); to_result(doc.splice_text(obj_id, pos, del, to_str!(text))) } @@ -901,28 +884,32 @@ pub unsafe extern "C" fn AMspliceText( /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical -/// keys or `NULL` for current keys. -/// \return A pointer to an `AMresult` struct containing a UTF-8 string. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] heads A pointer to an `AMitems` struct containing +/// `AM_VAL_TYPE_CHANGE_HASH` items to select a historical string +/// or `NULL` to select the current string. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_STR` item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMtext( doc: *const AMdoc, obj_id: *const AMobjId, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); match heads.as_ref() { None => to_result(doc.text(obj_id)), - Some(heads) => to_result(doc.text_at(obj_id, heads.as_ref())), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.text_at(obj_id, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } diff --git a/rust/automerge-c/src/doc/list.rs b/rust/automerge-c/src/doc/list.rs index 6bcdeabf..c4503322 100644 --- a/rust/automerge-c/src/doc/list.rs +++ b/rust/automerge-c/src/doc/list.rs @@ -3,47 +3,44 @@ use automerge::transaction::Transactable; use automerge::ReadDoc; use crate::byte_span::{to_str, AMbyteSpan}; -use crate::change_hashes::AMchangeHashes; -use crate::doc::{to_doc, to_doc_mut, to_obj_id, AMdoc}; -use crate::obj::{to_obj_type, AMobjId, AMobjType}; +use crate::doc::{to_doc, to_doc_mut, AMdoc}; +use crate::items::AMitems; +use crate::obj::{to_obj_id, to_obj_type, AMobjId, AMobjType}; use crate::result::{to_result, AMresult}; -pub mod item; -pub mod items; - macro_rules! adjust { - ($index:expr, $insert:expr, $len:expr) => {{ + ($pos:expr, $insert:expr, $len:expr) => {{ // An empty object can only be inserted into. let insert = $insert || $len == 0; let end = if insert { $len } else { $len - 1 }; - if $index > end && $index != usize::MAX { - return AMresult::err(&format!("Invalid index {}", $index)).into(); + if $pos > end && $pos != usize::MAX { + return AMresult::error(&format!("Invalid pos {}", $pos)).into(); } - (std::cmp::min($index, end), insert) + (std::cmp::min($pos, end), insert) }}; } macro_rules! to_range { ($begin:expr, $end:expr) => {{ if $begin > $end { - return AMresult::err(&format!("Invalid range [{}-{})", $begin, $end)).into(); + return AMresult::error(&format!("Invalid range [{}-{})", $begin, $end)).into(); }; ($begin..$end) }}; } /// \memberof AMdoc -/// \brief Deletes an index in a list object. +/// \brief Deletes an item from a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -53,101 +50,109 @@ macro_rules! to_range { pub unsafe extern "C" fn AMlistDelete( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, _) = adjust!(index, false, doc.length(obj_id)); - to_result(doc.delete(obj_id, index)) + let (pos, _) = adjust!(pos, false, doc.length(obj_id)); + to_result(doc.delete(obj_id, pos)) } /// \memberof AMdoc -/// \brief Gets the current or historical value at an index in a list object. +/// \brief Gets a current or historical item within a list object. /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical -/// value or `NULL` for the current value. -/// \return A pointer to an `AMresult` struct that doesn't contain a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select a historical item at \p pos or `NULL` +/// to select the current item at \p pos. +/// \return A pointer to an `AMresult` struct with an `AMitem` struct. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMlistGet( doc: *const AMdoc, obj_id: *const AMobjId, - index: usize, - heads: *const AMchangeHashes, + pos: usize, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); - let (index, _) = adjust!(index, false, doc.length(obj_id)); - to_result(match heads.as_ref() { - None => doc.get(obj_id, index), - Some(heads) => doc.get_at(obj_id, index, heads.as_ref()), - }) + let (pos, _) = adjust!(pos, false, doc.length(obj_id)); + match heads.as_ref() { + None => to_result(doc.get(obj_id, pos)), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.get_at(obj_id, pos, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, + } } /// \memberof AMdoc -/// \brief Gets all of the historical values at an index in a list object until -/// its current one or a specific one. +/// \brief Gets all of the historical items at a position within a list object +/// until its current one or a specific one. /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical -/// last value or `NULL` for the current last value. -/// \return A pointer to an `AMresult` struct containing an `AMobjItems` struct. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select a historical last item or `NULL` to select +/// the current last item. +/// \return A pointer to an `AMresult` struct with an `AMitems` struct. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMlistGetAll( doc: *const AMdoc, obj_id: *const AMobjId, - index: usize, - heads: *const AMchangeHashes, + pos: usize, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); - let (index, _) = adjust!(index, false, doc.length(obj_id)); + let (pos, _) = adjust!(pos, false, doc.length(obj_id)); match heads.as_ref() { - None => to_result(doc.get_all(obj_id, index)), - Some(heads) => to_result(doc.get_all_at(obj_id, index, heads.as_ref())), + None => to_result(doc.get_all(obj_id, pos)), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.get_all_at(obj_id, pos, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } /// \memberof AMdoc -/// \brief Increments a counter at an index in a list object by the given -/// value. +/// \brief Increments a counter value in an item within a list object by the +/// given value. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -157,32 +162,33 @@ pub unsafe extern "C" fn AMlistGetAll( pub unsafe extern "C" fn AMlistIncrement( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, value: i64, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, _) = adjust!(index, false, doc.length(obj_id)); - to_result(doc.increment(obj_id, index, value)) + let (pos, _) = adjust!(pos, false, doc.length(obj_id)); + to_result(doc.increment(obj_id, pos, value)) } /// \memberof AMdoc -/// \brief Puts a boolean as the value at an index in a list object. +/// \brief Puts a boolean value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] value A boolean. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -192,84 +198,85 @@ pub unsafe extern "C" fn AMlistIncrement( pub unsafe extern "C" fn AMlistPutBool( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, value: bool, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); let value = am::ScalarValue::Boolean(value); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Puts a sequence of bytes as the value at an index in a list object. +/// \brief Puts an array of bytes value at a position within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p src before \p index instead of -/// writing \p src over \p index. -/// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes to copy from \p src. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. +/// \param[in] value A view onto the array of bytes to copy from as an +/// `AMbyteSpan` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \pre \p value.src `!= NULL` +/// \pre `0 <` \p value.count `<= sizeof(`\p value.src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// src must be a byte array of size `>= count` +/// value.src must be a byte array of length >= value.count #[no_mangle] pub unsafe extern "C" fn AMlistPutBytes( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, - val: AMbyteSpan, + value: AMbyteSpan, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); - let mut value = Vec::new(); - value.extend_from_slice(std::slice::from_raw_parts(val.src, val.count)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); + let value: Vec = (&value).into(); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Puts a CRDT counter as the value at an index in a list object. +/// \brief Puts a CRDT counter value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -279,38 +286,39 @@ pub unsafe extern "C" fn AMlistPutBytes( pub unsafe extern "C" fn AMlistPutCounter( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, value: i64, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); let value = am::ScalarValue::Counter(value.into()); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Puts a float as the value at an index in a list object. +/// \brief Puts a float value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] value A 64-bit float. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -320,37 +328,38 @@ pub unsafe extern "C" fn AMlistPutCounter( pub unsafe extern "C" fn AMlistPutF64( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, value: f64, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Puts a signed integer as the value at an index in a list object. +/// \brief Puts a signed integer value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -360,36 +369,37 @@ pub unsafe extern "C" fn AMlistPutF64( pub unsafe extern "C" fn AMlistPutInt( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, value: i64, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Puts null as the value at an index in a list object. +/// \brief Puts a null value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -399,38 +409,37 @@ pub unsafe extern "C" fn AMlistPutInt( pub unsafe extern "C" fn AMlistPutNull( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); to_result(if insert { - doc.insert(obj_id, index, ()) + doc.insert(obj_id, pos, ()) } else { - doc.put(obj_id, index, ()) + doc.put(obj_id, pos, ()) }) } /// \memberof AMdoc -/// \brief Puts an empty object as the value at an index in a list object. +/// \brief Puts an empty object value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] obj_type An `AMobjIdType` enum tag. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMobjId` struct. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \pre \p obj_type != `AM_OBJ_TYPE_VOID`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_OBJ_TYPE` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -440,82 +449,85 @@ pub unsafe extern "C" fn AMlistPutNull( pub unsafe extern "C" fn AMlistPutObject( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, obj_type: AMobjType, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); - let object = to_obj_type!(obj_type); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); + let obj_type = to_obj_type!(obj_type); to_result(if insert { - doc.insert_object(obj_id, index, object) + (doc.insert_object(obj_id, pos, obj_type), obj_type) } else { - doc.put_object(obj_id, index, object) + (doc.put_object(obj_id, pos, obj_type), obj_type) }) } /// \memberof AMdoc -/// \brief Puts a UTF-8 string as the value at an index in a list object. +/// \brief Puts a UTF-8 string value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] value A UTF-8 string view as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \pre \p value `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \pre \p value.src `!= NULL` +/// \pre `0 <` \p value.count `<= sizeof(`\p value.src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// value must be a null-terminated array of `c_char` +/// value.src must be a byte array of length >= value.count #[no_mangle] pub unsafe extern "C" fn AMlistPutStr( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, value: AMbyteSpan, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); let value = to_str!(value); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Puts a *nix timestamp (milliseconds) as the value at an index in a +/// \brief Puts a *nix timestamp (milliseconds) value into an item within a /// list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -525,38 +537,39 @@ pub unsafe extern "C" fn AMlistPutStr( pub unsafe extern "C" fn AMlistPutTimestamp( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, value: i64, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); let value = am::ScalarValue::Timestamp(value); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Puts an unsigned integer as the value at an index in a list object. +/// \brief Puts an unsigned integer value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] value A 64-bit unsigned integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -566,56 +579,58 @@ pub unsafe extern "C" fn AMlistPutTimestamp( pub unsafe extern "C" fn AMlistPutUint( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, value: u64, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Gets the current or historical indices and values of the list object -/// within the given range. +/// \brief Gets the current or historical items in the list object within the +/// given range. /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] begin The first index in a range of indices. -/// \param[in] end At least one past the last index in a range of indices. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical -/// indices and values or `NULL` for current indices and -/// values. -/// \return A pointer to an `AMresult` struct containing an `AMlistItems` -/// struct. -/// \pre \p doc `!= NULL`. -/// \pre \p begin `<=` \p end `<= SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] begin The first pos in a range of indices. +/// \param[in] end At least one past the last pos in a range of indices. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select historical items or `NULL` to select +/// current items. +/// \return A pointer to an `AMresult` struct with an `AMitems` struct. +/// \pre \p doc `!= NULL` +/// \pre \p begin `<=` \p end `<= SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMlistRange( doc: *const AMdoc, obj_id: *const AMobjId, begin: usize, end: usize, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); let range = to_range!(begin, end); match heads.as_ref() { None => to_result(doc.list_range(obj_id, range)), - Some(heads) => to_result(doc.list_range_at(obj_id, range, heads.as_ref())), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.list_range_at(obj_id, range, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } diff --git a/rust/automerge-c/src/doc/list/item.rs b/rust/automerge-c/src/doc/list/item.rs deleted file mode 100644 index 7a3869f3..00000000 --- a/rust/automerge-c/src/doc/list/item.rs +++ /dev/null @@ -1,97 +0,0 @@ -use automerge as am; - -use crate::obj::AMobjId; -use crate::result::AMvalue; - -/// \struct AMlistItem -/// \installed_headerfile -/// \brief An item in a list object. -pub struct AMlistItem { - /// The index of an item in a list object. - index: usize, - /// The object identifier of an item in a list object. - obj_id: AMobjId, - /// The value of an item in a list object. - value: am::Value<'static>, -} - -impl AMlistItem { - pub fn new(index: usize, value: am::Value<'static>, obj_id: am::ObjId) -> Self { - Self { - index, - obj_id: AMobjId::new(obj_id), - value, - } - } -} - -impl PartialEq for AMlistItem { - fn eq(&self, other: &Self) -> bool { - self.index == other.index && self.obj_id == other.obj_id && self.value == other.value - } -} - -/* -impl From<&AMlistItem> for (usize, am::Value<'static>, am::ObjId) { - fn from(list_item: &AMlistItem) -> Self { - (list_item.index, list_item.value.0.clone(), list_item.obj_id.as_ref().clone()) - } -} -*/ - -/// \memberof AMlistItem -/// \brief Gets the index of an item in a list object. -/// -/// \param[in] list_item A pointer to an `AMlistItem` struct. -/// \return A 64-bit unsigned integer. -/// \pre \p list_item `!= NULL`. -/// \internal -/// -/// # Safety -/// list_item must be a valid pointer to an AMlistItem -#[no_mangle] -pub unsafe extern "C" fn AMlistItemIndex(list_item: *const AMlistItem) -> usize { - if let Some(list_item) = list_item.as_ref() { - list_item.index - } else { - usize::MAX - } -} - -/// \memberof AMlistItem -/// \brief Gets the object identifier of an item in a list object. -/// -/// \param[in] list_item A pointer to an `AMlistItem` struct. -/// \return A pointer to an `AMobjId` struct. -/// \pre \p list_item `!= NULL`. -/// \internal -/// -/// # Safety -/// list_item must be a valid pointer to an AMlistItem -#[no_mangle] -pub unsafe extern "C" fn AMlistItemObjId(list_item: *const AMlistItem) -> *const AMobjId { - if let Some(list_item) = list_item.as_ref() { - &list_item.obj_id - } else { - std::ptr::null() - } -} - -/// \memberof AMlistItem -/// \brief Gets the value of an item in a list object. -/// -/// \param[in] list_item A pointer to an `AMlistItem` struct. -/// \return An `AMvalue` struct. -/// \pre \p list_item `!= NULL`. -/// \internal -/// -/// # Safety -/// list_item must be a valid pointer to an AMlistItem -#[no_mangle] -pub unsafe extern "C" fn AMlistItemValue<'a>(list_item: *const AMlistItem) -> AMvalue<'a> { - if let Some(list_item) = list_item.as_ref() { - (&list_item.value).into() - } else { - AMvalue::Void - } -} diff --git a/rust/automerge-c/src/doc/list/items.rs b/rust/automerge-c/src/doc/list/items.rs deleted file mode 100644 index 5b4a11fd..00000000 --- a/rust/automerge-c/src/doc/list/items.rs +++ /dev/null @@ -1,348 +0,0 @@ -use std::ffi::c_void; -use std::mem::size_of; - -use crate::doc::list::item::AMlistItem; - -#[repr(C)] -struct Detail { - len: usize, - offset: isize, - ptr: *const c_void, -} - -/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call -/// (https://github.com/eqrion/cbindgen/issues/252) but it will -/// propagate the name of a constant initialized from it so if the -/// constant's name is a symbolic representation of the value it can be -/// converted into a number by post-processing the header it generated. -pub const USIZE_USIZE_USIZE_: usize = size_of::(); - -impl Detail { - fn new(list_items: &[AMlistItem], offset: isize) -> Self { - Self { - len: list_items.len(), - offset, - ptr: list_items.as_ptr() as *const c_void, - } - } - - pub fn advance(&mut self, n: isize) { - if n == 0 { - return; - } - let len = self.len as isize; - self.offset = if self.offset < 0 { - // It's reversed. - let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); - if unclipped >= 0 { - // Clip it to the forward stop. - len - } else { - std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) - } - } else { - let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); - if unclipped < 0 { - // Clip it to the reverse stop. - -(len + 1) - } else { - std::cmp::max(0, std::cmp::min(unclipped, len)) - } - } - } - - pub fn get_index(&self) -> usize { - (self.offset - + if self.offset < 0 { - self.len as isize - } else { - 0 - }) as usize - } - - pub fn next(&mut self, n: isize) -> Option<&AMlistItem> { - if self.is_stopped() { - return None; - } - let slice: &[AMlistItem] = - unsafe { std::slice::from_raw_parts(self.ptr as *const AMlistItem, self.len) }; - let value = &slice[self.get_index()]; - self.advance(n); - Some(value) - } - - pub fn is_stopped(&self) -> bool { - let len = self.len as isize; - self.offset < -len || self.offset == len - } - - pub fn prev(&mut self, n: isize) -> Option<&AMlistItem> { - self.advance(-n); - if self.is_stopped() { - return None; - } - let slice: &[AMlistItem] = - unsafe { std::slice::from_raw_parts(self.ptr as *const AMlistItem, self.len) }; - Some(&slice[self.get_index()]) - } - - pub fn reversed(&self) -> Self { - Self { - len: self.len, - offset: -(self.offset + 1), - ptr: self.ptr, - } - } - - pub fn rewound(&self) -> Self { - Self { - len: self.len, - offset: if self.offset < 0 { -1 } else { 0 }, - ptr: self.ptr, - } - } -} - -impl From for [u8; USIZE_USIZE_USIZE_] { - fn from(detail: Detail) -> Self { - unsafe { - std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) - .try_into() - .unwrap() - } - } -} - -/// \struct AMlistItems -/// \installed_headerfile -/// \brief A random-access iterator over a sequence of list object items. -#[repr(C)] -#[derive(Eq, PartialEq)] -pub struct AMlistItems { - /// An implementation detail that is intentionally opaque. - /// \warning Modifying \p detail will cause undefined behavior. - /// \note The actual size of \p detail will vary by platform, this is just - /// the one for the platform this documentation was built on. - detail: [u8; USIZE_USIZE_USIZE_], -} - -impl AMlistItems { - pub fn new(list_items: &[AMlistItem]) -> Self { - Self { - detail: Detail::new(list_items, 0).into(), - } - } - - pub fn advance(&mut self, n: isize) { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.advance(n); - } - - pub fn len(&self) -> usize { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - detail.len - } - - pub fn next(&mut self, n: isize) -> Option<&AMlistItem> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.next(n) - } - - pub fn prev(&mut self, n: isize) -> Option<&AMlistItem> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.prev(n) - } - - pub fn reversed(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.reversed().into(), - } - } - - pub fn rewound(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.rewound().into(), - } - } -} - -impl AsRef<[AMlistItem]> for AMlistItems { - fn as_ref(&self) -> &[AMlistItem] { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - unsafe { std::slice::from_raw_parts(detail.ptr as *const AMlistItem, detail.len) } - } -} - -impl Default for AMlistItems { - fn default() -> Self { - Self { - detail: [0; USIZE_USIZE_USIZE_], - } - } -} - -/// \memberof AMlistItems -/// \brief Advances an iterator over a sequence of list object items by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] list_items A pointer to an `AMlistItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \pre \p list_items `!= NULL`. -/// \internal -/// -/// #Safety -/// list_items must be a valid pointer to an AMlistItems -#[no_mangle] -pub unsafe extern "C" fn AMlistItemsAdvance(list_items: *mut AMlistItems, n: isize) { - if let Some(list_items) = list_items.as_mut() { - list_items.advance(n); - }; -} - -/// \memberof AMlistItems -/// \brief Tests the equality of two sequences of list object items underlying -/// a pair of iterators. -/// -/// \param[in] list_items1 A pointer to an `AMlistItems` struct. -/// \param[in] list_items2 A pointer to an `AMlistItems` struct. -/// \return `true` if \p list_items1 `==` \p list_items2 and `false` otherwise. -/// \pre \p list_items1 `!= NULL`. -/// \pre \p list_items2 `!= NULL`. -/// \internal -/// -/// #Safety -/// list_items1 must be a valid pointer to an AMlistItems -/// list_items2 must be a valid pointer to an AMlistItems -#[no_mangle] -pub unsafe extern "C" fn AMlistItemsEqual( - list_items1: *const AMlistItems, - list_items2: *const AMlistItems, -) -> bool { - match (list_items1.as_ref(), list_items2.as_ref()) { - (Some(list_items1), Some(list_items2)) => list_items1.as_ref() == list_items2.as_ref(), - (None, Some(_)) | (Some(_), None) | (None, None) => false, - } -} - -/// \memberof AMlistItems -/// \brief Gets the list object item at the current position of an iterator -/// over a sequence of list object items and then advances it by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] list_items A pointer to an `AMlistItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMlistItem` struct that's `NULL` when -/// \p list_items was previously advanced past its forward/reverse -/// limit. -/// \pre \p list_items `!= NULL`. -/// \internal -/// -/// #Safety -/// list_items must be a valid pointer to an AMlistItems -#[no_mangle] -pub unsafe extern "C" fn AMlistItemsNext( - list_items: *mut AMlistItems, - n: isize, -) -> *const AMlistItem { - if let Some(list_items) = list_items.as_mut() { - if let Some(list_item) = list_items.next(n) { - return list_item; - } - } - std::ptr::null() -} - -/// \memberof AMlistItems -/// \brief Advances an iterator over a sequence of list object items by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction and then gets the list object item at its new -/// position. -/// -/// \param[in,out] list_items A pointer to an `AMlistItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMlistItem` struct that's `NULL` when -/// \p list_items is presently advanced past its forward/reverse limit. -/// \pre \p list_items `!= NULL`. -/// \internal -/// -/// #Safety -/// list_items must be a valid pointer to an AMlistItems -#[no_mangle] -pub unsafe extern "C" fn AMlistItemsPrev( - list_items: *mut AMlistItems, - n: isize, -) -> *const AMlistItem { - if let Some(list_items) = list_items.as_mut() { - if let Some(list_item) = list_items.prev(n) { - return list_item; - } - } - std::ptr::null() -} - -/// \memberof AMlistItems -/// \brief Gets the size of the sequence of list object items underlying an -/// iterator. -/// -/// \param[in] list_items A pointer to an `AMlistItems` struct. -/// \return The count of values in \p list_items. -/// \pre \p list_items `!= NULL`. -/// \internal -/// -/// #Safety -/// list_items must be a valid pointer to an AMlistItems -#[no_mangle] -pub unsafe extern "C" fn AMlistItemsSize(list_items: *const AMlistItems) -> usize { - if let Some(list_items) = list_items.as_ref() { - list_items.len() - } else { - 0 - } -} - -/// \memberof AMlistItems -/// \brief Creates an iterator over the same sequence of list object items as -/// the given one but with the opposite position and direction. -/// -/// \param[in] list_items A pointer to an `AMlistItems` struct. -/// \return An `AMlistItems` struct -/// \pre \p list_items `!= NULL`. -/// \internal -/// -/// #Safety -/// list_items must be a valid pointer to an AMlistItems -#[no_mangle] -pub unsafe extern "C" fn AMlistItemsReversed(list_items: *const AMlistItems) -> AMlistItems { - if let Some(list_items) = list_items.as_ref() { - list_items.reversed() - } else { - Default::default() - } -} - -/// \memberof AMlistItems -/// \brief Creates an iterator at the starting position over the same sequence -/// of list object items as the given one. -/// -/// \param[in] list_items A pointer to an `AMlistItems` struct. -/// \return An `AMlistItems` struct -/// \pre \p list_items `!= NULL`. -/// \internal -/// -/// #Safety -/// list_items must be a valid pointer to an AMlistItems -#[no_mangle] -pub unsafe extern "C" fn AMlistItemsRewound(list_items: *const AMlistItems) -> AMlistItems { - if let Some(list_items) = list_items.as_ref() { - list_items.rewound() - } else { - Default::default() - } -} diff --git a/rust/automerge-c/src/doc/map.rs b/rust/automerge-c/src/doc/map.rs index 86c6b4a2..b2f7db02 100644 --- a/rust/automerge-c/src/doc/map.rs +++ b/rust/automerge-c/src/doc/map.rs @@ -3,31 +3,29 @@ use automerge::transaction::Transactable; use automerge::ReadDoc; use crate::byte_span::{to_str, AMbyteSpan}; -use crate::change_hashes::AMchangeHashes; -use crate::doc::{to_doc, to_doc_mut, to_obj_id, AMdoc}; -use crate::obj::{to_obj_type, AMobjId, AMobjType}; +use crate::doc::{to_doc, to_doc_mut, AMdoc}; +use crate::items::AMitems; +use crate::obj::{to_obj_id, to_obj_type, AMobjId, AMobjType}; use crate::result::{to_result, AMresult}; -pub mod item; -pub mod items; - /// \memberof AMdoc -/// \brief Deletes a key in a map object. +/// \brief Deletes an item from a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] key A UTF-8 string view key for the map object identified by -/// \p obj_id as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] key The UTF-8 string view key of an item within the map object +/// identified by \p obj_id as an `AMbyteSpan` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapDelete( doc: *mut AMdoc, @@ -40,96 +38,107 @@ pub unsafe extern "C" fn AMmapDelete( } /// \memberof AMdoc -/// \brief Gets the current or historical value for a key in a map object. +/// \brief Gets a current or historical item within a map object. /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] key A UTF-8 string view key for the map object identified by -/// \p obj_id as an `AMbyteSpan` struct. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical -/// value or `NULL` for the current value. -/// \return A pointer to an `AMresult` struct that doesn't contain a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] key The UTF-8 string view key of an item within the map object +/// identified by \p obj_id as an `AMbyteSpan` struct. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select a historical item at \p key or `NULL` +/// to select the current item at \p key. +/// \return A pointer to an `AMresult` struct with an `AMitem` struct. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// key.src must be a byte array of length >= key.count +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMmapGet( doc: *const AMdoc, obj_id: *const AMobjId, key: AMbyteSpan, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); let key = to_str!(key); match heads.as_ref() { None => to_result(doc.get(obj_id, key)), - Some(heads) => to_result(doc.get_at(obj_id, key, heads.as_ref())), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.get_at(obj_id, key, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } /// \memberof AMdoc -/// \brief Gets all of the historical values for a key in a map object until +/// \brief Gets all of the historical items at a key within a map object until /// its current one or a specific one. /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] key A UTF-8 string view key for the map object identified by -/// \p obj_id as an `AMbyteSpan` struct. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical -/// last value or `NULL` for the current last value. -/// \return A pointer to an `AMresult` struct containing an `AMobjItems` struct. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] key The UTF-8 string view key of an item within the map object +/// identified by \p obj_id as an `AMbyteSpan` struct. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select a historical last item or `NULL` to +/// select the current last item. +/// \return A pointer to an `AMresult` struct with an `AMItems` struct. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// key.src must be a byte array of length >= key.count +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMmapGetAll( doc: *const AMdoc, obj_id: *const AMobjId, key: AMbyteSpan, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); let key = to_str!(key); match heads.as_ref() { None => to_result(doc.get_all(obj_id, key)), - Some(heads) => to_result(doc.get_all_at(obj_id, key, heads.as_ref())), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.get_all_at(obj_id, key, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } /// \memberof AMdoc -/// \brief Increments a counter for a key in a map object by the given value. +/// \brief Increments a counter at a key in a map object by the given value. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] key A UTF-8 string view key for the map object identified by -/// \p obj_id as an `AMbyteSpan` struct. +/// \param[in] key The UTF-8 string view key of an item within the map object +/// identified by \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapIncrement( doc: *mut AMdoc, @@ -145,21 +154,22 @@ pub unsafe extern "C" fn AMmapIncrement( /// \memberof AMdoc /// \brief Puts a boolean as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] key A UTF-8 string view key for the map object identified by -/// \p obj_id as an `AMbyteSpan` struct. +/// \param[in] key The UTF-8 string view key of an item within the map object +/// identified by \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A boolean. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutBool( doc: *mut AMdoc, @@ -173,59 +183,58 @@ pub unsafe extern "C" fn AMmapPutBool( } /// \memberof AMdoc -/// \brief Puts a sequence of bytes as the value of a key in a map object. +/// \brief Puts an array of bytes value at a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] key A UTF-8 string view key for the map object identified by -/// \p obj_id as an `AMbyteSpan` struct. -/// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes to copy from \p src. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] key The UTF-8 string view key of an item within the map object +/// identified by \p obj_id as an `AMbyteSpan` struct. +/// \param[in] value A view onto an array of bytes as an `AMbyteSpan` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \pre \p value.src `!= NULL` +/// \pre `0 <` \p value.count `<= sizeof(`\p value.src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// src must be a byte array of size `>= count` +/// key.src must be a byte array of length >= key.count +/// value.src must be a byte array of length >= value.count #[no_mangle] pub unsafe extern "C" fn AMmapPutBytes( doc: *mut AMdoc, obj_id: *const AMobjId, key: AMbyteSpan, - val: AMbyteSpan, + value: AMbyteSpan, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let key = to_str!(key); - let mut vec = Vec::new(); - vec.extend_from_slice(std::slice::from_raw_parts(val.src, val.count)); - to_result(doc.put(to_obj_id!(obj_id), key, vec)) + to_result(doc.put(to_obj_id!(obj_id), key, Vec::::from(&value))) } /// \memberof AMdoc /// \brief Puts a CRDT counter as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutCounter( doc: *mut AMdoc, @@ -245,20 +254,21 @@ pub unsafe extern "C" fn AMmapPutCounter( /// \memberof AMdoc /// \brief Puts null as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutNull( doc: *mut AMdoc, @@ -273,23 +283,22 @@ pub unsafe extern "C" fn AMmapPutNull( /// \memberof AMdoc /// \brief Puts an empty object as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. /// \param[in] obj_type An `AMobjIdType` enum tag. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMobjId` struct. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \pre \p obj_type != `AM_OBJ_TYPE_VOID`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_OBJ_TYPE` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutObject( doc: *mut AMdoc, @@ -299,27 +308,29 @@ pub unsafe extern "C" fn AMmapPutObject( ) -> *mut AMresult { let doc = to_doc_mut!(doc); let key = to_str!(key); - to_result(doc.put_object(to_obj_id!(obj_id), key, to_obj_type!(obj_type))) + let obj_type = to_obj_type!(obj_type); + to_result((doc.put_object(to_obj_id!(obj_id), key, obj_type), obj_type)) } /// \memberof AMdoc /// \brief Puts a float as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A 64-bit float. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutF64( doc: *mut AMdoc, @@ -335,21 +346,22 @@ pub unsafe extern "C" fn AMmapPutF64( /// \memberof AMdoc /// \brief Puts a signed integer as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutInt( doc: *mut AMdoc, @@ -365,21 +377,22 @@ pub unsafe extern "C" fn AMmapPutInt( /// \memberof AMdoc /// \brief Puts a UTF-8 string as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A UTF-8 string view as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutStr( doc: *mut AMdoc, @@ -395,21 +408,22 @@ pub unsafe extern "C" fn AMmapPutStr( /// \brief Puts a *nix timestamp (milliseconds) as the value of a key in a map /// object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutTimestamp( doc: *mut AMdoc, @@ -425,21 +439,22 @@ pub unsafe extern "C" fn AMmapPutTimestamp( /// \memberof AMdoc /// \brief Puts an unsigned integer as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A 64-bit unsigned integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutUint( doc: *mut AMdoc, @@ -453,71 +468,82 @@ pub unsafe extern "C" fn AMmapPutUint( } /// \memberof AMdoc -/// \brief Gets the current or historical keys and values of the map object -/// within the given range. +/// \brief Gets the current or historical items of the map object within the +/// given range. /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] begin The first key in a subrange or `AMstr(NULL)` to indicate the /// absolute first key. -/// \param[in] end The key one past the last key in a subrange or `AMstr(NULL)` to -/// indicate one past the absolute last key. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical -/// keys and values or `NULL` for current keys and values. -/// \return A pointer to an `AMresult` struct containing an `AMmapItems` -/// struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] end The key one past the last key in a subrange or `AMstr(NULL)` +/// to indicate one past the absolute last key. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select historical items or `NULL` to select +/// current items. +/// \return A pointer to an `AMresult` struct with an `AMitems` struct. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// begin.src must be a byte array of length >= begin.count or std::ptr::null() +/// end.src must be a byte array of length >= end.count or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMmapRange( doc: *const AMdoc, obj_id: *const AMobjId, begin: AMbyteSpan, end: AMbyteSpan, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); + let heads = match heads.as_ref() { + None => None, + Some(heads) => match >::try_from(heads) { + Ok(heads) => Some(heads), + Err(e) => { + return AMresult::error(&e.to_string()).into(); + } + }, + }; match (begin.is_null(), end.is_null()) { (false, false) => { let (begin, end) = (to_str!(begin).to_string(), to_str!(end).to_string()); if begin > end { - return AMresult::err(&format!("Invalid range [{}-{})", begin, end)).into(); + return AMresult::error(&format!("Invalid range [{}-{})", begin, end)).into(); }; let bounds = begin..end; - if let Some(heads) = heads.as_ref() { - to_result(doc.map_range_at(obj_id, bounds, heads.as_ref())) + if let Some(heads) = heads { + to_result(doc.map_range_at(obj_id, bounds, &heads)) } else { to_result(doc.map_range(obj_id, bounds)) } } (false, true) => { let bounds = to_str!(begin).to_string()..; - if let Some(heads) = heads.as_ref() { - to_result(doc.map_range_at(obj_id, bounds, heads.as_ref())) + if let Some(heads) = heads { + to_result(doc.map_range_at(obj_id, bounds, &heads)) } else { to_result(doc.map_range(obj_id, bounds)) } } (true, false) => { let bounds = ..to_str!(end).to_string(); - if let Some(heads) = heads.as_ref() { - to_result(doc.map_range_at(obj_id, bounds, heads.as_ref())) + if let Some(heads) = heads { + to_result(doc.map_range_at(obj_id, bounds, &heads)) } else { to_result(doc.map_range(obj_id, bounds)) } } (true, true) => { let bounds = ..; - if let Some(heads) = heads.as_ref() { - to_result(doc.map_range_at(obj_id, bounds, heads.as_ref())) + if let Some(heads) = heads { + to_result(doc.map_range_at(obj_id, bounds, &heads)) } else { to_result(doc.map_range(obj_id, bounds)) } diff --git a/rust/automerge-c/src/doc/map/item.rs b/rust/automerge-c/src/doc/map/item.rs deleted file mode 100644 index 7914fdc4..00000000 --- a/rust/automerge-c/src/doc/map/item.rs +++ /dev/null @@ -1,98 +0,0 @@ -use automerge as am; - -use crate::byte_span::AMbyteSpan; -use crate::obj::AMobjId; -use crate::result::AMvalue; - -/// \struct AMmapItem -/// \installed_headerfile -/// \brief An item in a map object. -pub struct AMmapItem { - /// The key of an item in a map object. - key: String, - /// The object identifier of an item in a map object. - obj_id: AMobjId, - /// The value of an item in a map object. - value: am::Value<'static>, -} - -impl AMmapItem { - pub fn new(key: &'static str, value: am::Value<'static>, obj_id: am::ObjId) -> Self { - Self { - key: key.to_string(), - obj_id: AMobjId::new(obj_id), - value, - } - } -} - -impl PartialEq for AMmapItem { - fn eq(&self, other: &Self) -> bool { - self.key == other.key && self.obj_id == other.obj_id && self.value == other.value - } -} - -/* -impl From<&AMmapItem> for (String, am::Value<'static>, am::ObjId) { - fn from(map_item: &AMmapItem) -> Self { - (map_item.key.into_string().unwrap(), map_item.value.0.clone(), map_item.obj_id.as_ref().clone()) - } -} -*/ - -/// \memberof AMmapItem -/// \brief Gets the key of an item in a map object. -/// -/// \param[in] map_item A pointer to an `AMmapItem` struct. -/// \return An `AMbyteSpan` view of a UTF-8 string. -/// \pre \p map_item `!= NULL`. -/// \internal -/// -/// # Safety -/// map_item must be a valid pointer to an AMmapItem -#[no_mangle] -pub unsafe extern "C" fn AMmapItemKey(map_item: *const AMmapItem) -> AMbyteSpan { - if let Some(map_item) = map_item.as_ref() { - map_item.key.as_bytes().into() - } else { - Default::default() - } -} - -/// \memberof AMmapItem -/// \brief Gets the object identifier of an item in a map object. -/// -/// \param[in] map_item A pointer to an `AMmapItem` struct. -/// \return A pointer to an `AMobjId` struct. -/// \pre \p map_item `!= NULL`. -/// \internal -/// -/// # Safety -/// map_item must be a valid pointer to an AMmapItem -#[no_mangle] -pub unsafe extern "C" fn AMmapItemObjId(map_item: *const AMmapItem) -> *const AMobjId { - if let Some(map_item) = map_item.as_ref() { - &map_item.obj_id - } else { - std::ptr::null() - } -} - -/// \memberof AMmapItem -/// \brief Gets the value of an item in a map object. -/// -/// \param[in] map_item A pointer to an `AMmapItem` struct. -/// \return An `AMvalue` struct. -/// \pre \p map_item `!= NULL`. -/// \internal -/// -/// # Safety -/// map_item must be a valid pointer to an AMmapItem -#[no_mangle] -pub unsafe extern "C" fn AMmapItemValue<'a>(map_item: *const AMmapItem) -> AMvalue<'a> { - if let Some(map_item) = map_item.as_ref() { - (&map_item.value).into() - } else { - AMvalue::Void - } -} diff --git a/rust/automerge-c/src/doc/map/items.rs b/rust/automerge-c/src/doc/map/items.rs deleted file mode 100644 index cd305971..00000000 --- a/rust/automerge-c/src/doc/map/items.rs +++ /dev/null @@ -1,340 +0,0 @@ -use std::ffi::c_void; -use std::mem::size_of; - -use crate::doc::map::item::AMmapItem; - -#[repr(C)] -struct Detail { - len: usize, - offset: isize, - ptr: *const c_void, -} - -/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call -/// (https://github.com/eqrion/cbindgen/issues/252) but it will -/// propagate the name of a constant initialized from it so if the -/// constant's name is a symbolic representation of the value it can be -/// converted into a number by post-processing the header it generated. -pub const USIZE_USIZE_USIZE_: usize = size_of::(); - -impl Detail { - fn new(map_items: &[AMmapItem], offset: isize) -> Self { - Self { - len: map_items.len(), - offset, - ptr: map_items.as_ptr() as *const c_void, - } - } - - pub fn advance(&mut self, n: isize) { - if n == 0 { - return; - } - let len = self.len as isize; - self.offset = if self.offset < 0 { - // It's reversed. - let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); - if unclipped >= 0 { - // Clip it to the forward stop. - len - } else { - std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) - } - } else { - let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); - if unclipped < 0 { - // Clip it to the reverse stop. - -(len + 1) - } else { - std::cmp::max(0, std::cmp::min(unclipped, len)) - } - } - } - - pub fn get_index(&self) -> usize { - (self.offset - + if self.offset < 0 { - self.len as isize - } else { - 0 - }) as usize - } - - pub fn next(&mut self, n: isize) -> Option<&AMmapItem> { - if self.is_stopped() { - return None; - } - let slice: &[AMmapItem] = - unsafe { std::slice::from_raw_parts(self.ptr as *const AMmapItem, self.len) }; - let value = &slice[self.get_index()]; - self.advance(n); - Some(value) - } - - pub fn is_stopped(&self) -> bool { - let len = self.len as isize; - self.offset < -len || self.offset == len - } - - pub fn prev(&mut self, n: isize) -> Option<&AMmapItem> { - self.advance(-n); - if self.is_stopped() { - return None; - } - let slice: &[AMmapItem] = - unsafe { std::slice::from_raw_parts(self.ptr as *const AMmapItem, self.len) }; - Some(&slice[self.get_index()]) - } - - pub fn reversed(&self) -> Self { - Self { - len: self.len, - offset: -(self.offset + 1), - ptr: self.ptr, - } - } - - pub fn rewound(&self) -> Self { - Self { - len: self.len, - offset: if self.offset < 0 { -1 } else { 0 }, - ptr: self.ptr, - } - } -} - -impl From for [u8; USIZE_USIZE_USIZE_] { - fn from(detail: Detail) -> Self { - unsafe { - std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) - .try_into() - .unwrap() - } - } -} - -/// \struct AMmapItems -/// \installed_headerfile -/// \brief A random-access iterator over a sequence of map object items. -#[repr(C)] -#[derive(Eq, PartialEq)] -pub struct AMmapItems { - /// An implementation detail that is intentionally opaque. - /// \warning Modifying \p detail will cause undefined behavior. - /// \note The actual size of \p detail will vary by platform, this is just - /// the one for the platform this documentation was built on. - detail: [u8; USIZE_USIZE_USIZE_], -} - -impl AMmapItems { - pub fn new(map_items: &[AMmapItem]) -> Self { - Self { - detail: Detail::new(map_items, 0).into(), - } - } - - pub fn advance(&mut self, n: isize) { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.advance(n); - } - - pub fn len(&self) -> usize { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - detail.len - } - - pub fn next(&mut self, n: isize) -> Option<&AMmapItem> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.next(n) - } - - pub fn prev(&mut self, n: isize) -> Option<&AMmapItem> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.prev(n) - } - - pub fn reversed(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.reversed().into(), - } - } - - pub fn rewound(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.rewound().into(), - } - } -} - -impl AsRef<[AMmapItem]> for AMmapItems { - fn as_ref(&self) -> &[AMmapItem] { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - unsafe { std::slice::from_raw_parts(detail.ptr as *const AMmapItem, detail.len) } - } -} - -impl Default for AMmapItems { - fn default() -> Self { - Self { - detail: [0; USIZE_USIZE_USIZE_], - } - } -} - -/// \memberof AMmapItems -/// \brief Advances an iterator over a sequence of map object items by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] map_items A pointer to an `AMmapItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \pre \p map_items `!= NULL`. -/// \internal -/// -/// #Safety -/// map_items must be a valid pointer to an AMmapItems -#[no_mangle] -pub unsafe extern "C" fn AMmapItemsAdvance(map_items: *mut AMmapItems, n: isize) { - if let Some(map_items) = map_items.as_mut() { - map_items.advance(n); - }; -} - -/// \memberof AMmapItems -/// \brief Tests the equality of two sequences of map object items underlying -/// a pair of iterators. -/// -/// \param[in] map_items1 A pointer to an `AMmapItems` struct. -/// \param[in] map_items2 A pointer to an `AMmapItems` struct. -/// \return `true` if \p map_items1 `==` \p map_items2 and `false` otherwise. -/// \pre \p map_items1 `!= NULL`. -/// \pre \p map_items2 `!= NULL`. -/// \internal -/// -/// #Safety -/// map_items1 must be a valid pointer to an AMmapItems -/// map_items2 must be a valid pointer to an AMmapItems -#[no_mangle] -pub unsafe extern "C" fn AMmapItemsEqual( - map_items1: *const AMmapItems, - map_items2: *const AMmapItems, -) -> bool { - match (map_items1.as_ref(), map_items2.as_ref()) { - (Some(map_items1), Some(map_items2)) => map_items1.as_ref() == map_items2.as_ref(), - (None, Some(_)) | (Some(_), None) | (None, None) => false, - } -} - -/// \memberof AMmapItems -/// \brief Gets the map object item at the current position of an iterator -/// over a sequence of map object items and then advances it by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] map_items A pointer to an `AMmapItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMmapItem` struct that's `NULL` when \p map_items -/// was previously advanced past its forward/reverse limit. -/// \pre \p map_items `!= NULL`. -/// \internal -/// -/// #Safety -/// map_items must be a valid pointer to an AMmapItems -#[no_mangle] -pub unsafe extern "C" fn AMmapItemsNext(map_items: *mut AMmapItems, n: isize) -> *const AMmapItem { - if let Some(map_items) = map_items.as_mut() { - if let Some(map_item) = map_items.next(n) { - return map_item; - } - } - std::ptr::null() -} - -/// \memberof AMmapItems -/// \brief Advances an iterator over a sequence of map object items by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction and then gets the map object item at its new -/// position. -/// -/// \param[in,out] map_items A pointer to an `AMmapItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMmapItem` struct that's `NULL` when \p map_items -/// is presently advanced past its forward/reverse limit. -/// \pre \p map_items `!= NULL`. -/// \internal -/// -/// #Safety -/// map_items must be a valid pointer to an AMmapItems -#[no_mangle] -pub unsafe extern "C" fn AMmapItemsPrev(map_items: *mut AMmapItems, n: isize) -> *const AMmapItem { - if let Some(map_items) = map_items.as_mut() { - if let Some(map_item) = map_items.prev(n) { - return map_item; - } - } - std::ptr::null() -} - -/// \memberof AMmapItems -/// \brief Gets the size of the sequence of map object items underlying an -/// iterator. -/// -/// \param[in] map_items A pointer to an `AMmapItems` struct. -/// \return The count of values in \p map_items. -/// \pre \p map_items `!= NULL`. -/// \internal -/// -/// #Safety -/// map_items must be a valid pointer to an AMmapItems -#[no_mangle] -pub unsafe extern "C" fn AMmapItemsSize(map_items: *const AMmapItems) -> usize { - if let Some(map_items) = map_items.as_ref() { - map_items.len() - } else { - 0 - } -} - -/// \memberof AMmapItems -/// \brief Creates an iterator over the same sequence of map object items as -/// the given one but with the opposite position and direction. -/// -/// \param[in] map_items A pointer to an `AMmapItems` struct. -/// \return An `AMmapItems` struct -/// \pre \p map_items `!= NULL`. -/// \internal -/// -/// #Safety -/// map_items must be a valid pointer to an AMmapItems -#[no_mangle] -pub unsafe extern "C" fn AMmapItemsReversed(map_items: *const AMmapItems) -> AMmapItems { - if let Some(map_items) = map_items.as_ref() { - map_items.reversed() - } else { - Default::default() - } -} - -/// \memberof AMmapItems -/// \brief Creates an iterator at the starting position over the same sequence of map object items as the given one. -/// -/// \param[in] map_items A pointer to an `AMmapItems` struct. -/// \return An `AMmapItems` struct -/// \pre \p map_items `!= NULL`. -/// \internal -/// -/// #Safety -/// map_items must be a valid pointer to an AMmapItems -#[no_mangle] -pub unsafe extern "C" fn AMmapItemsRewound(map_items: *const AMmapItems) -> AMmapItems { - if let Some(map_items) = map_items.as_ref() { - map_items.rewound() - } else { - Default::default() - } -} diff --git a/rust/automerge-c/src/doc/utils.rs b/rust/automerge-c/src/doc/utils.rs index d98a9a8b..ce465b84 100644 --- a/rust/automerge-c/src/doc/utils.rs +++ b/rust/automerge-c/src/doc/utils.rs @@ -1,9 +1,20 @@ +macro_rules! clamp { + ($index:expr, $len:expr, $param_name:expr) => {{ + if $index > $len && $index != usize::MAX { + return AMresult::error(&format!("Invalid {} {}", $param_name, $index)).into(); + } + std::cmp::min($index, $len) + }}; +} + +pub(crate) use clamp; + macro_rules! to_doc { ($handle:expr) => {{ let handle = $handle.as_ref(); match handle { Some(b) => b, - None => return AMresult::err("Invalid AMdoc pointer").into(), + None => return AMresult::error("Invalid `AMdoc*`").into(), } }}; } @@ -15,9 +26,21 @@ macro_rules! to_doc_mut { let handle = $handle.as_mut(); match handle { Some(b) => b, - None => return AMresult::err("Invalid AMdoc pointer").into(), + None => return AMresult::error("Invalid `AMdoc*`").into(), } }}; } pub(crate) use to_doc_mut; + +macro_rules! to_items { + ($handle:expr) => {{ + let handle = $handle.as_ref(); + match handle { + Some(b) => b, + None => return AMresult::error("Invalid `AMitems*`").into(), + } + }}; +} + +pub(crate) use to_items; diff --git a/rust/automerge-c/src/index.rs b/rust/automerge-c/src/index.rs new file mode 100644 index 00000000..f1ea153b --- /dev/null +++ b/rust/automerge-c/src/index.rs @@ -0,0 +1,84 @@ +use automerge as am; + +use std::any::type_name; + +use smol_str::SmolStr; + +use crate::byte_span::AMbyteSpan; + +/// \struct AMindex +/// \installed_headerfile +/// \brief An item index. +#[derive(PartialEq)] +pub enum AMindex { + /// A UTF-8 string key variant. + Key(SmolStr), + /// A 64-bit unsigned integer position variant. + Pos(usize), +} + +impl TryFrom<&AMindex> for AMbyteSpan { + type Error = am::AutomergeError; + + fn try_from(item: &AMindex) -> Result { + use am::AutomergeError::InvalidValueType; + use AMindex::*; + + if let Key(key) = item { + return Ok(key.into()); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +impl TryFrom<&AMindex> for usize { + type Error = am::AutomergeError; + + fn try_from(item: &AMindex) -> Result { + use am::AutomergeError::InvalidValueType; + use AMindex::*; + + if let Pos(pos) = item { + return Ok(*pos); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +/// \ingroup enumerations +/// \enum AMidxType +/// \installed_headerfile +/// \brief The type of an item's index. +#[derive(PartialEq, Eq)] +#[repr(u8)] +pub enum AMidxType { + /// The default tag, not a type signifier. + Default = 0, + /// A UTF-8 string view key. + Key, + /// A 64-bit unsigned integer position. + Pos, +} + +impl Default for AMidxType { + fn default() -> Self { + Self::Default + } +} + +impl From<&AMindex> for AMidxType { + fn from(index: &AMindex) -> Self { + use AMindex::*; + + match index { + Key(_) => Self::Key, + Pos(_) => Self::Pos, + } + } +} diff --git a/rust/automerge-c/src/item.rs b/rust/automerge-c/src/item.rs new file mode 100644 index 00000000..94735464 --- /dev/null +++ b/rust/automerge-c/src/item.rs @@ -0,0 +1,1963 @@ +use automerge as am; + +use std::any::type_name; +use std::borrow::Cow; +use std::cell::{RefCell, UnsafeCell}; +use std::rc::Rc; + +use crate::actor_id::AMactorId; +use crate::byte_span::{to_str, AMbyteSpan}; +use crate::change::AMchange; +use crate::doc::AMdoc; +use crate::index::{AMidxType, AMindex}; +use crate::obj::AMobjId; +use crate::result::{to_result, AMresult}; +use crate::sync::{AMsyncHave, AMsyncMessage, AMsyncState}; + +/// \struct AMunknownValue +/// \installed_headerfile +/// \brief A value (typically for a `set` operation) whose type is unknown. +#[derive(Default, Eq, PartialEq)] +#[repr(C)] +pub struct AMunknownValue { + /// The value's raw bytes. + bytes: AMbyteSpan, + /// The value's encoded type identifier. + type_code: u8, +} + +pub enum Value { + ActorId(am::ActorId, UnsafeCell>), + Change(Box, UnsafeCell>), + ChangeHash(am::ChangeHash), + Doc(RefCell), + SyncHave(AMsyncHave), + SyncMessage(AMsyncMessage), + SyncState(RefCell), + Value(am::Value<'static>), +} + +impl Value { + pub fn try_into_bytes(&self) -> Result { + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Self::Value(Scalar(scalar)) = &self { + if let Bytes(vector) = scalar.as_ref() { + return Ok(vector.as_slice().into()); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } + + pub fn try_into_change_hash(&self) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Self::ChangeHash(change_hash) = &self { + return Ok(change_hash.into()); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } + + pub fn try_into_counter(&self) -> Result { + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Self::Value(Scalar(scalar)) = &self { + if let Counter(counter) = scalar.as_ref() { + return Ok(counter.into()); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } + + pub fn try_into_int(&self) -> Result { + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Self::Value(Scalar(scalar)) = &self { + if let Int(int) = scalar.as_ref() { + return Ok(*int); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } + + pub fn try_into_str(&self) -> Result { + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Self::Value(Scalar(scalar)) = &self { + if let Str(smol_str) = scalar.as_ref() { + return Ok(smol_str.into()); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } + + pub fn try_into_timestamp(&self) -> Result { + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Self::Value(Scalar(scalar)) = &self { + if let Timestamp(timestamp) = scalar.as_ref() { + return Ok(*timestamp); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +impl From for Value { + fn from(actor_id: am::ActorId) -> Self { + Self::ActorId(actor_id, Default::default()) + } +} + +impl From for Value { + fn from(auto_commit: am::AutoCommit) -> Self { + Self::Doc(RefCell::new(AMdoc::new(auto_commit))) + } +} + +impl From for Value { + fn from(change: am::Change) -> Self { + Self::Change(Box::new(change), Default::default()) + } +} + +impl From for Value { + fn from(change_hash: am::ChangeHash) -> Self { + Self::ChangeHash(change_hash) + } +} + +impl From for Value { + fn from(have: am::sync::Have) -> Self { + Self::SyncHave(AMsyncHave::new(have)) + } +} + +impl From for Value { + fn from(message: am::sync::Message) -> Self { + Self::SyncMessage(AMsyncMessage::new(message)) + } +} + +impl From for Value { + fn from(state: am::sync::State) -> Self { + Self::SyncState(RefCell::new(AMsyncState::new(state))) + } +} + +impl From> for Value { + fn from(value: am::Value<'static>) -> Self { + Self::Value(value) + } +} + +impl From for Value { + fn from(string: String) -> Self { + Self::Value(am::Value::Scalar(Cow::Owned(am::ScalarValue::Str( + string.into(), + )))) + } +} + +impl<'a> TryFrom<&'a Value> for &'a am::Change { + type Error = am::AutomergeError; + + fn try_from(value: &'a Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + Change(change, _) => Ok(change), + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl<'a> TryFrom<&'a Value> for &'a am::ChangeHash { + type Error = am::AutomergeError; + + fn try_from(value: &'a Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + ChangeHash(change_hash) => Ok(change_hash), + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl<'a> TryFrom<&'a Value> for &'a am::ScalarValue { + type Error = am::AutomergeError; + + fn try_from(value: &'a Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + use am::Value::*; + + if let Value(Scalar(scalar)) = value { + return Ok(scalar.as_ref()); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +impl<'a> TryFrom<&'a Value> for &'a AMactorId { + type Error = am::AutomergeError; + + fn try_from(value: &'a Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + ActorId(actor_id, c_actor_id) => unsafe { + Ok((*c_actor_id.get()).get_or_insert(AMactorId::new(actor_id))) + }, + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl<'a> TryFrom<&'a mut Value> for &'a mut AMchange { + type Error = am::AutomergeError; + + fn try_from(value: &'a mut Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + Change(change, c_change) => unsafe { + Ok((*c_change.get()).get_or_insert(AMchange::new(change))) + }, + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl<'a> TryFrom<&'a mut Value> for &'a mut AMdoc { + type Error = am::AutomergeError; + + fn try_from(value: &'a mut Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + Doc(doc) => Ok(doc.get_mut()), + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl<'a> TryFrom<&'a Value> for &'a AMsyncHave { + type Error = am::AutomergeError; + + fn try_from(value: &'a Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + SyncHave(sync_have) => Ok(sync_have), + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl<'a> TryFrom<&'a Value> for &'a AMsyncMessage { + type Error = am::AutomergeError; + + fn try_from(value: &'a Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + SyncMessage(sync_message) => Ok(sync_message), + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl<'a> TryFrom<&'a mut Value> for &'a mut AMsyncState { + type Error = am::AutomergeError; + + fn try_from(value: &'a mut Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + SyncState(sync_state) => Ok(sync_state.get_mut()), + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl TryFrom<&Value> for bool { + type Error = am::AutomergeError; + + fn try_from(value: &Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Value(Scalar(scalar)) = value { + if let Boolean(boolean) = scalar.as_ref() { + return Ok(*boolean); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +impl TryFrom<&Value> for f64 { + type Error = am::AutomergeError; + + fn try_from(value: &Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Value(Scalar(scalar)) = value { + if let F64(float) = scalar.as_ref() { + return Ok(*float); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +impl TryFrom<&Value> for u64 { + type Error = am::AutomergeError; + + fn try_from(value: &Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Value(Scalar(scalar)) = value { + if let Uint(uint) = scalar.as_ref() { + return Ok(*uint); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +impl TryFrom<&Value> for AMunknownValue { + type Error = am::AutomergeError; + + fn try_from(value: &Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Value(Scalar(scalar)) = value { + if let Unknown { bytes, type_code } = scalar.as_ref() { + return Ok(Self { + bytes: bytes.as_slice().into(), + type_code: *type_code, + }); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + use self::Value::*; + + match (self, other) { + (ActorId(lhs, _), ActorId(rhs, _)) => *lhs == *rhs, + (Change(lhs, _), Change(rhs, _)) => lhs == rhs, + (ChangeHash(lhs), ChangeHash(rhs)) => lhs == rhs, + (Doc(lhs), Doc(rhs)) => lhs.as_ptr() == rhs.as_ptr(), + (SyncMessage(lhs), SyncMessage(rhs)) => *lhs == *rhs, + (SyncState(lhs), SyncState(rhs)) => *lhs == *rhs, + (Value(lhs), Value(rhs)) => lhs == rhs, + _ => false, + } + } +} + +#[derive(Default)] +pub struct Item { + /// The item's index. + index: Option, + /// The item's identifier. + obj_id: Option, + /// The item's value. + value: Option, +} + +impl Item { + pub fn try_into_bytes(&self) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &self.value { + return value.try_into_bytes(); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + + pub fn try_into_change_hash(&self) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &self.value { + return value.try_into_change_hash(); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + + pub fn try_into_counter(&self) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &self.value { + return value.try_into_counter(); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + + pub fn try_into_int(&self) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &self.value { + return value.try_into_int(); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + + pub fn try_into_str(&self) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &self.value { + return value.try_into_str(); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + + pub fn try_into_timestamp(&self) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &self.value { + return value.try_into_timestamp(); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } +} + +impl From for Item { + fn from(actor_id: am::ActorId) -> Self { + Value::from(actor_id).into() + } +} + +impl From for Item { + fn from(auto_commit: am::AutoCommit) -> Self { + Value::from(auto_commit).into() + } +} + +impl From for Item { + fn from(change: am::Change) -> Self { + Value::from(change).into() + } +} + +impl From for Item { + fn from(change_hash: am::ChangeHash) -> Self { + Value::from(change_hash).into() + } +} + +impl From<(am::ObjId, am::ObjType)> for Item { + fn from((obj_id, obj_type): (am::ObjId, am::ObjType)) -> Self { + Self { + index: None, + obj_id: Some(AMobjId::new(obj_id)), + value: Some(am::Value::Object(obj_type).into()), + } + } +} + +impl From for Item { + fn from(have: am::sync::Have) -> Self { + Value::from(have).into() + } +} + +impl From for Item { + fn from(message: am::sync::Message) -> Self { + Value::from(message).into() + } +} + +impl From for Item { + fn from(state: am::sync::State) -> Self { + Value::from(state).into() + } +} + +impl From> for Item { + fn from(value: am::Value<'static>) -> Self { + Value::from(value).into() + } +} + +impl From for Item { + fn from(string: String) -> Self { + Value::from(string).into() + } +} + +impl From for Item { + fn from(value: Value) -> Self { + Self { + index: None, + obj_id: None, + value: Some(value), + } + } +} + +impl PartialEq for Item { + fn eq(&self, other: &Self) -> bool { + self.index == other.index && self.obj_id == other.obj_id && self.value == other.value + } +} + +impl<'a> TryFrom<&'a Item> for &'a am::Change { + type Error = am::AutomergeError; + + fn try_from(item: &'a Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl<'a> TryFrom<&'a Item> for &'a am::ChangeHash { + type Error = am::AutomergeError; + + fn try_from(item: &'a Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl<'a> TryFrom<&'a Item> for &'a am::ScalarValue { + type Error = am::AutomergeError; + + fn try_from(item: &'a Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl<'a> TryFrom<&'a Item> for &'a AMactorId { + type Error = am::AutomergeError; + + fn try_from(item: &'a Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl<'a> TryFrom<&'a mut Item> for &'a mut AMchange { + type Error = am::AutomergeError; + + fn try_from(item: &'a mut Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &mut item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl<'a> TryFrom<&'a mut Item> for &'a mut AMdoc { + type Error = am::AutomergeError; + + fn try_from(item: &'a mut Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &mut item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl From<&Item> for AMidxType { + fn from(item: &Item) -> Self { + if let Some(index) = &item.index { + return index.into(); + } + Default::default() + } +} + +impl<'a> TryFrom<&'a Item> for &'a AMsyncHave { + type Error = am::AutomergeError; + + fn try_from(item: &'a Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl<'a> TryFrom<&'a Item> for &'a AMsyncMessage { + type Error = am::AutomergeError; + + fn try_from(item: &'a Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl<'a> TryFrom<&'a mut Item> for &'a mut AMsyncState { + type Error = am::AutomergeError; + + fn try_from(item: &'a mut Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &mut item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl TryFrom<&Item> for bool { + type Error = am::AutomergeError; + + fn try_from(item: &Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl TryFrom<&Item> for f64 { + type Error = am::AutomergeError; + + fn try_from(item: &Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl TryFrom<&Item> for u64 { + type Error = am::AutomergeError; + + fn try_from(item: &Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl TryFrom<&Item> for AMunknownValue { + type Error = am::AutomergeError; + + fn try_from(item: &Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl TryFrom<&Item> for (am::Value<'static>, am::ObjId) { + type Error = am::AutomergeError; + + fn try_from(item: &Item) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidObjId; + use am::AutomergeError::InvalidValueType; + + let expected = type_name::().to_string(); + match (&item.obj_id, &item.value) { + (None, None) | (None, Some(_)) => Err(InvalidObjId("".to_string())), + (Some(_), None) => Err(InvalidValueType { + expected, + unexpected: type_name::>().to_string(), + }), + (Some(obj_id), Some(value)) => match value { + ActorId(_, _) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + ChangeHash(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + Change(_, _) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + Doc(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + SyncHave(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + SyncMessage(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + SyncState(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + Value(v) => Ok((v.clone(), obj_id.as_ref().clone())), + }, + } + } +} + +/// \struct AMitem +/// \installed_headerfile +/// \brief An item within a result. +#[derive(Clone)] +pub struct AMitem(Rc); + +impl AMitem { + pub fn exact(obj_id: am::ObjId, value: Value) -> Self { + Self(Rc::new(Item { + index: None, + obj_id: Some(AMobjId::new(obj_id)), + value: Some(value), + })) + } + + pub fn indexed(index: AMindex, obj_id: am::ObjId, value: Value) -> Self { + Self(Rc::new(Item { + index: Some(index), + obj_id: Some(AMobjId::new(obj_id)), + value: Some(value), + })) + } +} + +impl AsRef for AMitem { + fn as_ref(&self) -> &Item { + self.0.as_ref() + } +} + +impl Default for AMitem { + fn default() -> Self { + Self(Rc::new(Item { + index: None, + obj_id: None, + value: None, + })) + } +} + +impl From for AMitem { + fn from(actor_id: am::ActorId) -> Self { + Value::from(actor_id).into() + } +} + +impl From for AMitem { + fn from(auto_commit: am::AutoCommit) -> Self { + Value::from(auto_commit).into() + } +} + +impl From for AMitem { + fn from(change: am::Change) -> Self { + Value::from(change).into() + } +} + +impl From for AMitem { + fn from(change_hash: am::ChangeHash) -> Self { + Value::from(change_hash).into() + } +} + +impl From<(am::ObjId, am::ObjType)> for AMitem { + fn from((obj_id, obj_type): (am::ObjId, am::ObjType)) -> Self { + Self(Rc::new(Item::from((obj_id, obj_type)))) + } +} + +impl From for AMitem { + fn from(have: am::sync::Have) -> Self { + Value::from(have).into() + } +} + +impl From for AMitem { + fn from(message: am::sync::Message) -> Self { + Value::from(message).into() + } +} + +impl From for AMitem { + fn from(state: am::sync::State) -> Self { + Value::from(state).into() + } +} + +impl From> for AMitem { + fn from(value: am::Value<'static>) -> Self { + Value::from(value).into() + } +} + +impl From for AMitem { + fn from(string: String) -> Self { + Value::from(string).into() + } +} + +impl From for AMitem { + fn from(value: Value) -> Self { + Self(Rc::new(Item::from(value))) + } +} + +impl PartialEq for AMitem { + fn eq(&self, other: &Self) -> bool { + self.as_ref() == other.as_ref() + } +} + +impl<'a> TryFrom<&'a AMitem> for &'a am::Change { + type Error = am::AutomergeError; + + fn try_from(item: &'a AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl<'a> TryFrom<&'a AMitem> for &'a am::ChangeHash { + type Error = am::AutomergeError; + + fn try_from(item: &'a AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl<'a> TryFrom<&'a AMitem> for &'a am::ScalarValue { + type Error = am::AutomergeError; + + fn try_from(item: &'a AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl<'a> TryFrom<&'a AMitem> for &'a AMactorId { + type Error = am::AutomergeError; + + fn try_from(item: &'a AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl<'a> TryFrom<&'a mut AMitem> for &'a mut AMchange { + type Error = am::AutomergeError; + + fn try_from(item: &'a mut AMitem) -> Result { + if let Some(item) = Rc::get_mut(&mut item.0) { + item.try_into() + } else { + Err(Self::Error::Fail) + } + } +} + +impl<'a> TryFrom<&'a mut AMitem> for &'a mut AMdoc { + type Error = am::AutomergeError; + + fn try_from(item: &'a mut AMitem) -> Result { + if let Some(item) = Rc::get_mut(&mut item.0) { + item.try_into() + } else { + Err(Self::Error::Fail) + } + } +} + +impl<'a> TryFrom<&'a AMitem> for &'a AMsyncHave { + type Error = am::AutomergeError; + + fn try_from(item: &'a AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl<'a> TryFrom<&'a AMitem> for &'a AMsyncMessage { + type Error = am::AutomergeError; + + fn try_from(item: &'a AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl<'a> TryFrom<&'a mut AMitem> for &'a mut AMsyncState { + type Error = am::AutomergeError; + + fn try_from(item: &'a mut AMitem) -> Result { + if let Some(item) = Rc::get_mut(&mut item.0) { + item.try_into() + } else { + Err(Self::Error::Fail) + } + } +} + +impl TryFrom<&AMitem> for bool { + type Error = am::AutomergeError; + + fn try_from(item: &AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl TryFrom<&AMitem> for f64 { + type Error = am::AutomergeError; + + fn try_from(item: &AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl TryFrom<&AMitem> for u64 { + type Error = am::AutomergeError; + + fn try_from(item: &AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl TryFrom<&AMitem> for AMunknownValue { + type Error = am::AutomergeError; + + fn try_from(item: &AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl TryFrom<&AMitem> for (am::Value<'static>, am::ObjId) { + type Error = am::AutomergeError; + + fn try_from(item: &AMitem) -> Result { + item.as_ref().try_into() + } +} + +/// \ingroup enumerations +/// \enum AMvalType +/// \installed_headerfile +/// \brief The type of an item's value. +#[derive(PartialEq, Eq)] +#[repr(u32)] +pub enum AMvalType { + /// An actor identifier value. + ActorId = 1 << 1, + /// A boolean value. + Bool = 1 << 2, + /// A view onto an array of bytes value. + Bytes = 1 << 3, + /// A change value. + Change = 1 << 4, + /// A change hash value. + ChangeHash = 1 << 5, + /// A CRDT counter value. + Counter = 1 << 6, + /// The default tag, not a type signifier. + Default = 0, + /// A document value. + Doc = 1 << 7, + /// A 64-bit float value. + F64 = 1 << 8, + /// A 64-bit signed integer value. + Int = 1 << 9, + /// A null value. + Null = 1 << 10, + /// An object type value. + ObjType = 1 << 11, + /// A UTF-8 string view value. + Str = 1 << 12, + /// A synchronization have value. + SyncHave = 1 << 13, + /// A synchronization message value. + SyncMessage = 1 << 14, + /// A synchronization state value. + SyncState = 1 << 15, + /// A *nix timestamp (milliseconds) value. + Timestamp = 1 << 16, + /// A 64-bit unsigned integer value. + Uint = 1 << 17, + /// An unknown type of value. + Unknown = 1 << 18, + /// A void. + Void = 1 << 0, +} + +impl Default for AMvalType { + fn default() -> Self { + Self::Default + } +} + +impl From<&am::Value<'static>> for AMvalType { + fn from(value: &am::Value<'static>) -> Self { + use am::ScalarValue::*; + use am::Value::*; + + match value { + Object(_) => Self::ObjType, + Scalar(scalar) => match scalar.as_ref() { + Boolean(_) => Self::Bool, + Bytes(_) => Self::Bytes, + Counter(_) => Self::Counter, + F64(_) => Self::F64, + Int(_) => Self::Int, + Null => Self::Null, + Str(_) => Self::Str, + Timestamp(_) => Self::Timestamp, + Uint(_) => Self::Uint, + Unknown { .. } => Self::Unknown, + }, + } + } +} + +impl From<&Value> for AMvalType { + fn from(value: &Value) -> Self { + use self::Value::*; + + match value { + ActorId(_, _) => Self::ActorId, + Change(_, _) => Self::Change, + ChangeHash(_) => Self::ChangeHash, + Doc(_) => Self::Doc, + SyncHave(_) => Self::SyncHave, + SyncMessage(_) => Self::SyncMessage, + SyncState(_) => Self::SyncState, + Value(v) => v.into(), + } + } +} + +impl From<&Item> for AMvalType { + fn from(item: &Item) -> Self { + if let Some(value) = &item.value { + return value.into(); + } + Self::Void + } +} + +/// \memberof AMitem +/// \brief Tests the equality of two items. +/// +/// \param[in] item1 A pointer to an `AMitem` struct. +/// \param[in] item2 A pointer to an `AMitem` struct. +/// \return `true` if \p item1 `==` \p item2 and `false` otherwise. +/// \pre \p item1 `!= NULL` +/// \pre \p item2 `!= NULL` +/// \post `!(`\p item1 `&&` \p item2 `) -> false` +/// \internal +/// +/// #Safety +/// item1 must be a valid AMitem pointer +/// item2 must be a valid AMitem pointer +#[no_mangle] +pub unsafe extern "C" fn AMitemEqual(item1: *const AMitem, item2: *const AMitem) -> bool { + match (item1.as_ref(), item2.as_ref()) { + (Some(item1), Some(item2)) => *item1 == *item2, + (None, None) | (None, Some(_)) | (Some(_), None) => false, + } +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a boolean value. +/// +/// \param[in] value A boolean. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BOOL` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +#[no_mangle] +pub unsafe extern "C" fn AMitemFromBool(value: bool) -> *mut AMresult { + AMresult::item(am::Value::from(value).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from an array of bytes value. +/// +/// \param[in] src A pointer to an array of bytes. +/// \param[in] count The count of bytes to copy from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTES` item. +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +/// \internal +/// +/// # Safety +/// value.src must be a byte array of length >= value.count +#[no_mangle] +pub unsafe extern "C" fn AMitemFromBytes(src: *const u8, count: usize) -> *mut AMresult { + let value = std::slice::from_raw_parts(src, count); + AMresult::item(am::Value::bytes(value.to_vec()).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a change hash value. +/// +/// \param[in] value A change hash as an `AMbyteSpan` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_CHANGE_HASH` item. +/// \pre \p value.src `!= NULL` +/// \pre `0 <` \p value.count `<= sizeof(`\p value.src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +/// \internal +/// +/// # Safety +/// value.src must be a byte array of length >= value.count +#[no_mangle] +pub unsafe extern "C" fn AMitemFromChangeHash(value: AMbyteSpan) -> *mut AMresult { + to_result(am::ChangeHash::try_from(&value)) +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a CRDT counter value. +/// +/// \param[in] value A 64-bit signed integer. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_COUNTER` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +#[no_mangle] +pub unsafe extern "C" fn AMitemFromCounter(value: i64) -> *mut AMresult { + AMresult::item(am::Value::counter(value).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a float value. +/// +/// \param[in] value A 64-bit float. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_F64` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +#[no_mangle] +pub unsafe extern "C" fn AMitemFromF64(value: f64) -> *mut AMresult { + AMresult::item(am::Value::f64(value).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a signed integer value. +/// +/// \param[in] value A 64-bit signed integer. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_INT` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +#[no_mangle] +pub unsafe extern "C" fn AMitemFromInt(value: i64) -> *mut AMresult { + AMresult::item(am::Value::int(value).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a null value. +/// +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_NULL` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +#[no_mangle] +pub unsafe extern "C" fn AMitemFromNull() -> *mut AMresult { + AMresult::item(am::Value::from(()).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a UTF-8 string value. +/// +/// \param[in] value A UTF-8 string view as an `AMbyteSpan` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_STR` item. +/// \pre \p value.src `!= NULL` +/// \pre `0 <` \p value.count `<= sizeof(`\p value.src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +/// \internal +/// +/// # Safety +/// value.src must be a byte array of length >= value.count +#[no_mangle] +pub unsafe extern "C" fn AMitemFromStr(value: AMbyteSpan) -> *mut AMresult { + AMresult::item(am::Value::str(to_str!(value)).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a *nix timestamp +/// (milliseconds) value. +/// +/// \param[in] value A 64-bit signed integer. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_TIMESTAMP` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +#[no_mangle] +pub unsafe extern "C" fn AMitemFromTimestamp(value: i64) -> *mut AMresult { + AMresult::item(am::Value::timestamp(value).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from an unsigned integer value. +/// +/// \param[in] value A 64-bit unsigned integer. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_UINT` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +#[no_mangle] +pub unsafe extern "C" fn AMitemFromUint(value: u64) -> *mut AMresult { + AMresult::item(am::Value::uint(value).into()).into() +} + +/// \memberof AMitem +/// \brief Gets the type of an item's index. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \return An `AMidxType` enum tag. +/// \pre \p item `!= NULL` +/// \post `(`\p item `== NULL) -> 0` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemIdxType(item: *const AMitem) -> AMidxType { + if let Some(item) = item.as_ref() { + return item.0.as_ref().into(); + } + Default::default() +} + +/// \memberof AMitem +/// \brief Gets the object identifier of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \return A pointer to an `AMobjId` struct. +/// \pre \p item `!= NULL` +/// \post `(`\p item `== NULL) -> NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemObjId(item: *const AMitem) -> *const AMobjId { + if let Some(item) = item.as_ref() { + if let Some(obj_id) = &item.as_ref().obj_id { + return obj_id; + } + } + std::ptr::null() +} + +/// \memberof AMitem +/// \brief Gets the UTF-8 string view key index of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a UTF-8 string view as an `AMbyteSpan` struct. +/// \return `true` if `AMitemIdxType(`\p item `) == AM_IDX_TYPE_KEY` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemKey(item: *const AMitem, value: *mut AMbyteSpan) -> bool { + if let Some(item) = item.as_ref() { + if let Some(index) = &item.as_ref().index { + if let Ok(key) = index.try_into() { + if !value.is_null() { + *value = key; + return true; + } + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the unsigned integer position index of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a `size_t`. +/// \return `true` if `AMitemIdxType(`\p item `) == AM_IDX_TYPE_POS` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemPos(item: *const AMitem, value: *mut usize) -> bool { + if let Some(item) = item.as_ref() { + if let Some(index) = &item.as_ref().index { + if let Ok(pos) = index.try_into() { + if !value.is_null() { + *value = pos; + return true; + } + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the reference count of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \return A 64-bit unsigned integer. +/// \pre \p item `!= NULL` +/// \post `(`\p item `== NULL) -> 0` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemRefCount(item: *const AMitem) -> usize { + if let Some(item) = item.as_ref() { + return Rc::strong_count(&item.0); + } + 0 +} + +/// \memberof AMitem +/// \brief Gets a new result for an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \return A pointer to an `AMresult` struct. +/// \pre \p item `!= NULL` +/// \post `(`\p item `== NULL) -> NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemResult(item: *const AMitem) -> *mut AMresult { + if let Some(item) = item.as_ref() { + return AMresult::item(item.clone()).into(); + } + std::ptr::null_mut() +} + +/// \memberof AMitem +/// \brief Gets the actor identifier value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMactorId` struct pointer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_ACTOR_ID` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToActorId( + item: *const AMitem, + value: *mut *const AMactorId, +) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(actor_id) = <&AMactorId>::try_from(item) { + if !value.is_null() { + *value = actor_id; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the boolean value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a boolean. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_BOOL` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToBool(item: *const AMitem, value: *mut bool) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(boolean) = item.try_into() { + if !value.is_null() { + *value = boolean; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the array of bytes value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMbyteSpan` struct. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_BYTES` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToBytes(item: *const AMitem, value: *mut AMbyteSpan) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(bytes) = item.as_ref().try_into_bytes() { + if !value.is_null() { + *value = bytes; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the change value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMchange` struct pointer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_CHANGE` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToChange(item: *mut AMitem, value: *mut *mut AMchange) -> bool { + if let Some(item) = item.as_mut() { + if let Ok(change) = <&mut AMchange>::try_from(item) { + if !value.is_null() { + *value = change; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the change hash value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMbyteSpan` struct. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_CHANGE_HASH` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToChangeHash(item: *const AMitem, value: *mut AMbyteSpan) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(change_hash) = item.as_ref().try_into_change_hash() { + if !value.is_null() { + *value = change_hash; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the CRDT counter value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a signed 64-bit integer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_COUNTER` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToCounter(item: *const AMitem, value: *mut i64) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(counter) = item.as_ref().try_into_counter() { + if !value.is_null() { + *value = counter; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the document value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMdoc` struct pointer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_DOC` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToDoc(item: *mut AMitem, value: *mut *const AMdoc) -> bool { + if let Some(item) = item.as_mut() { + if let Ok(doc) = <&mut AMdoc>::try_from(item) { + if !value.is_null() { + *value = doc; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the float value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a 64-bit float. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_F64` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToF64(item: *const AMitem, value: *mut f64) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(float) = item.try_into() { + if !value.is_null() { + *value = float; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the integer value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a signed 64-bit integer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_INT` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToInt(item: *const AMitem, value: *mut i64) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(int) = item.as_ref().try_into_int() { + if !value.is_null() { + *value = int; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the UTF-8 string view value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a UTF-8 string view as an `AMbyteSpan` struct. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_STR` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToStr(item: *const AMitem, value: *mut AMbyteSpan) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(str) = item.as_ref().try_into_str() { + if !value.is_null() { + *value = str; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the synchronization have value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMsyncHave` struct pointer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_SYNC_HAVE` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToSyncHave( + item: *const AMitem, + value: *mut *const AMsyncHave, +) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(sync_have) = <&AMsyncHave>::try_from(item) { + if !value.is_null() { + *value = sync_have; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the synchronization message value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMsyncMessage` struct pointer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_SYNC_MESSAGE` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToSyncMessage( + item: *const AMitem, + value: *mut *const AMsyncMessage, +) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(sync_message) = <&AMsyncMessage>::try_from(item) { + if !value.is_null() { + *value = sync_message; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the synchronization state value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMsyncState` struct pointer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_SYNC_STATE` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToSyncState( + item: *mut AMitem, + value: *mut *mut AMsyncState, +) -> bool { + if let Some(item) = item.as_mut() { + if let Ok(sync_state) = <&mut AMsyncState>::try_from(item) { + if !value.is_null() { + *value = sync_state; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the *nix timestamp (milliseconds) value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a signed 64-bit integer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_TIMESTAMP` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToTimestamp(item: *const AMitem, value: *mut i64) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(timestamp) = item.as_ref().try_into_timestamp() { + if !value.is_null() { + *value = timestamp; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the unsigned integer value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a unsigned 64-bit integer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_UINT` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToUint(item: *const AMitem, value: *mut u64) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(uint) = item.try_into() { + if !value.is_null() { + *value = uint; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the unknown type of value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMunknownValue` struct. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_UNKNOWN` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToUnknown(item: *const AMitem, value: *mut AMunknownValue) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(unknown) = item.try_into() { + if !value.is_null() { + *value = unknown; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the type of an item's value. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \return An `AMvalType` enum tag. +/// \pre \p item `!= NULL` +/// \post `(`\p item `== NULL) -> 0` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemValType(item: *const AMitem) -> AMvalType { + if let Some(item) = item.as_ref() { + return item.0.as_ref().into(); + } + Default::default() +} diff --git a/rust/automerge-c/src/items.rs b/rust/automerge-c/src/items.rs new file mode 100644 index 00000000..361078b3 --- /dev/null +++ b/rust/automerge-c/src/items.rs @@ -0,0 +1,401 @@ +use automerge as am; + +use std::ffi::c_void; +use std::marker::PhantomData; +use std::mem::size_of; + +use crate::item::AMitem; +use crate::result::AMresult; + +#[repr(C)] +struct Detail { + len: usize, + offset: isize, + ptr: *const c_void, +} + +/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call +/// (https://github.com/eqrion/cbindgen/issues/252) but it will +/// propagate the name of a constant initialized from it so if the +/// constant's name is a symbolic representation of the value it can be +/// converted into a number by post-processing the header it generated. +pub const USIZE_USIZE_USIZE_: usize = size_of::(); + +impl Detail { + fn new(items: &[AMitem], offset: isize) -> Self { + Self { + len: items.len(), + offset, + ptr: items.as_ptr() as *mut c_void, + } + } + + pub fn advance(&mut self, n: isize) { + if n == 0 { + return; + } + let len = self.len as isize; + self.offset = if self.offset < 0 { + // It's reversed. + let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); + if unclipped >= 0 { + // Clip it to the forward stop. + len + } else { + std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) + } + } else { + let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); + if unclipped < 0 { + // Clip it to the reverse stop. + -(len + 1) + } else { + std::cmp::max(0, std::cmp::min(unclipped, len)) + } + } + } + + pub fn get_index(&self) -> usize { + (self.offset + + if self.offset < 0 { + self.len as isize + } else { + 0 + }) as usize + } + + pub fn next(&mut self, n: isize) -> Option<&mut AMitem> { + if self.is_stopped() { + return None; + } + let slice: &mut [AMitem] = + unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut AMitem, self.len) }; + let value = &mut slice[self.get_index()]; + self.advance(n); + Some(value) + } + + pub fn is_stopped(&self) -> bool { + let len = self.len as isize; + self.offset < -len || self.offset == len + } + + pub fn prev(&mut self, n: isize) -> Option<&mut AMitem> { + self.advance(-n); + if self.is_stopped() { + return None; + } + let slice: &mut [AMitem] = + unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut AMitem, self.len) }; + Some(&mut slice[self.get_index()]) + } + + pub fn reversed(&self) -> Self { + Self { + len: self.len, + offset: -(self.offset + 1), + ptr: self.ptr, + } + } + + pub fn rewound(&self) -> Self { + Self { + len: self.len, + offset: if self.offset < 0 { -1 } else { 0 }, + ptr: self.ptr, + } + } +} + +impl From for [u8; USIZE_USIZE_USIZE_] { + fn from(detail: Detail) -> Self { + unsafe { + std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) + .try_into() + .unwrap() + } + } +} + +/// \struct AMitems +/// \installed_headerfile +/// \brief A random-access iterator over a sequence of `AMitem` structs. +#[repr(C)] +#[derive(Eq, PartialEq)] +pub struct AMitems<'a> { + /// An implementation detail that is intentionally opaque. + /// \warning Modifying \p detail will cause undefined behavior. + /// \note The actual size of \p detail will vary by platform, this is just + /// the one for the platform this documentation was built on. + detail: [u8; USIZE_USIZE_USIZE_], + phantom: PhantomData<&'a mut AMresult>, +} + +impl<'a> AMitems<'a> { + pub fn new(items: &[AMitem]) -> Self { + Self { + detail: Detail::new(items, 0).into(), + phantom: PhantomData, + } + } + + pub fn advance(&mut self, n: isize) { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.advance(n); + } + + pub fn len(&self) -> usize { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + detail.len + } + + pub fn next(&mut self, n: isize) -> Option<&mut AMitem> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.next(n) + } + + pub fn prev(&mut self, n: isize) -> Option<&mut AMitem> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.prev(n) + } + + pub fn reversed(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.reversed().into(), + phantom: PhantomData, + } + } + + pub fn rewound(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.rewound().into(), + phantom: PhantomData, + } + } +} + +impl<'a> AsRef<[AMitem]> for AMitems<'a> { + fn as_ref(&self) -> &[AMitem] { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + unsafe { std::slice::from_raw_parts(detail.ptr as *const AMitem, detail.len) } + } +} + +impl<'a> Default for AMitems<'a> { + fn default() -> Self { + Self { + detail: [0; USIZE_USIZE_USIZE_], + phantom: PhantomData, + } + } +} + +impl TryFrom<&AMitems<'_>> for Vec { + type Error = am::AutomergeError; + + fn try_from(items: &AMitems<'_>) -> Result { + let mut changes = Vec::::with_capacity(items.len()); + for item in items.as_ref().iter() { + match <&am::Change>::try_from(item.as_ref()) { + Ok(change) => { + changes.push(change.clone()); + } + Err(e) => { + return Err(e); + } + } + } + Ok(changes) + } +} + +impl TryFrom<&AMitems<'_>> for Vec { + type Error = am::AutomergeError; + + fn try_from(items: &AMitems<'_>) -> Result { + let mut change_hashes = Vec::::with_capacity(items.len()); + for item in items.as_ref().iter() { + match <&am::ChangeHash>::try_from(item.as_ref()) { + Ok(change_hash) => { + change_hashes.push(*change_hash); + } + Err(e) => { + return Err(e); + } + } + } + Ok(change_hashes) + } +} + +impl TryFrom<&AMitems<'_>> for Vec { + type Error = am::AutomergeError; + + fn try_from(items: &AMitems<'_>) -> Result { + let mut scalars = Vec::::with_capacity(items.len()); + for item in items.as_ref().iter() { + match <&am::ScalarValue>::try_from(item.as_ref()) { + Ok(scalar) => { + scalars.push(scalar.clone()); + } + Err(e) => { + return Err(e); + } + } + } + Ok(scalars) + } +} + +/// \memberof AMitems +/// \brief Advances an iterator over a sequence of object items by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction. +/// +/// \param[in] items A pointer to an `AMitems` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \pre \p items `!= NULL` +/// \internal +/// +/// #Safety +/// items must be a valid pointer to an AMitems +#[no_mangle] +pub unsafe extern "C" fn AMitemsAdvance(items: *mut AMitems, n: isize) { + if let Some(items) = items.as_mut() { + items.advance(n); + }; +} + +/// \memberof AMitems +/// \brief Tests the equality of two sequences of object items underlying a +/// pair of iterators. +/// +/// \param[in] items1 A pointer to an `AMitems` struct. +/// \param[in] items2 A pointer to an `AMitems` struct. +/// \return `true` if \p items1 `==` \p items2 and `false` otherwise. +/// \pre \p items1 `!= NULL` +/// \pre \p items1 `!= NULL` +/// \post `!(`\p items1 `&&` \p items2 `) -> false` +/// \internal +/// +/// #Safety +/// items1 must be a valid pointer to an AMitems +/// items2 must be a valid pointer to an AMitems +#[no_mangle] +pub unsafe extern "C" fn AMitemsEqual(items1: *const AMitems, items2: *const AMitems) -> bool { + match (items1.as_ref(), items2.as_ref()) { + (Some(items1), Some(items2)) => items1.as_ref() == items2.as_ref(), + (None, None) | (None, Some(_)) | (Some(_), None) => false, + } +} + +/// \memberof AMitems +/// \brief Gets the object item at the current position of an iterator over a +/// sequence of object items and then advances it by at most \p |n| +/// positions where the sign of \p n is relative to the iterator's +/// direction. +/// +/// \param[in] items A pointer to an `AMitems` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A pointer to an `AMitem` struct that's `NULL` when \p items +/// was previously advanced past its forward/reverse limit. +/// \pre \p items `!= NULL` +/// \internal +/// +/// #Safety +/// items must be a valid pointer to an AMitems +#[no_mangle] +pub unsafe extern "C" fn AMitemsNext(items: *mut AMitems, n: isize) -> *mut AMitem { + if let Some(items) = items.as_mut() { + if let Some(item) = items.next(n) { + return item; + } + } + std::ptr::null_mut() +} + +/// \memberof AMitems +/// \brief Advances an iterator over a sequence of object items by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction and then gets the object item at its new +/// position. +/// +/// \param[in] items A pointer to an `AMitems` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A pointer to an `AMitem` struct that's `NULL` when \p items +/// is presently advanced past its forward/reverse limit. +/// \pre \p items `!= NULL` +/// \internal +/// +/// #Safety +/// items must be a valid pointer to an AMitems +#[no_mangle] +pub unsafe extern "C" fn AMitemsPrev(items: *mut AMitems, n: isize) -> *mut AMitem { + if let Some(items) = items.as_mut() { + if let Some(obj_item) = items.prev(n) { + return obj_item; + } + } + std::ptr::null_mut() +} + +/// \memberof AMitems +/// \brief Gets the size of the sequence underlying an iterator. +/// +/// \param[in] items A pointer to an `AMitems` struct. +/// \return The count of items in \p items. +/// \pre \p items `!= NULL` +/// \internal +/// +/// #Safety +/// items must be a valid pointer to an AMitems +#[no_mangle] +pub unsafe extern "C" fn AMitemsSize(items: *const AMitems) -> usize { + if let Some(items) = items.as_ref() { + return items.len(); + } + 0 +} + +/// \memberof AMitems +/// \brief Creates an iterator over the same sequence of items as the +/// given one but with the opposite position and direction. +/// +/// \param[in] items A pointer to an `AMitems` struct. +/// \return An `AMitems` struct +/// \pre \p items `!= NULL` +/// \internal +/// +/// #Safety +/// items must be a valid pointer to an AMitems +#[no_mangle] +pub unsafe extern "C" fn AMitemsReversed(items: *const AMitems) -> AMitems { + if let Some(items) = items.as_ref() { + return items.reversed(); + } + Default::default() +} + +/// \memberof AMitems +/// \brief Creates an iterator at the starting position over the same sequence +/// of items as the given one. +/// +/// \param[in] items A pointer to an `AMitems` struct. +/// \return An `AMitems` struct +/// \pre \p items `!= NULL` +/// \internal +/// +/// #Safety +/// items must be a valid pointer to an AMitems +#[no_mangle] +pub unsafe extern "C" fn AMitemsRewound(items: *const AMitems) -> AMitems { + if let Some(items) = items.as_ref() { + return items.rewound(); + } + Default::default() +} diff --git a/rust/automerge-c/src/lib.rs b/rust/automerge-c/src/lib.rs index 6418bd33..1ee1a85d 100644 --- a/rust/automerge-c/src/lib.rs +++ b/rust/automerge-c/src/lib.rs @@ -1,11 +1,12 @@ mod actor_id; mod byte_span; mod change; -mod change_hashes; -mod changes; mod doc; +mod index; +mod item; +mod items; mod obj; mod result; -mod result_stack; -mod strs; mod sync; + +// include!(concat!(env!("OUT_DIR"), "/enum_string_functions.rs")); diff --git a/rust/automerge-c/src/obj.rs b/rust/automerge-c/src/obj.rs index 46ff617b..3d52286c 100644 --- a/rust/automerge-c/src/obj.rs +++ b/rust/automerge-c/src/obj.rs @@ -1,12 +1,10 @@ use automerge as am; +use std::any::type_name; use std::cell::RefCell; use std::ops::Deref; use crate::actor_id::AMactorId; -pub mod item; -pub mod items; - macro_rules! to_obj_id { ($handle:expr) => {{ match $handle.as_ref() { @@ -19,12 +17,11 @@ macro_rules! to_obj_id { pub(crate) use to_obj_id; macro_rules! to_obj_type { - ($am_obj_type:expr) => {{ - match $am_obj_type { - AMobjType::Map => am::ObjType::Map, - AMobjType::List => am::ObjType::List, - AMobjType::Text => am::ObjType::Text, - AMobjType::Void => return AMresult::err("Invalid AMobjType value").into(), + ($c_obj_type:expr) => {{ + let result: Result = (&$c_obj_type).try_into(); + match result { + Ok(obj_type) => obj_type, + Err(e) => return AMresult::error(&e.to_string()).into(), } }}; } @@ -79,11 +76,11 @@ impl Deref for AMobjId { } /// \memberof AMobjId -/// \brief Gets the actor identifier of an object identifier. +/// \brief Gets the actor identifier component of an object identifier. /// /// \param[in] obj_id A pointer to an `AMobjId` struct. /// \return A pointer to an `AMactorId` struct or `NULL`. -/// \pre \p obj_id `!= NULL`. +/// \pre \p obj_id `!= NULL` /// \internal /// /// # Safety @@ -97,11 +94,11 @@ pub unsafe extern "C" fn AMobjIdActorId(obj_id: *const AMobjId) -> *const AMacto } /// \memberof AMobjId -/// \brief Gets the counter of an object identifier. +/// \brief Gets the counter component of an object identifier. /// /// \param[in] obj_id A pointer to an `AMobjId` struct. /// \return A 64-bit unsigned integer. -/// \pre \p obj_id `!= NULL`. +/// \pre \p obj_id `!= NULL` /// \internal /// /// # Safety @@ -124,8 +121,9 @@ pub unsafe extern "C" fn AMobjIdCounter(obj_id: *const AMobjId) -> u64 { /// \param[in] obj_id1 A pointer to an `AMobjId` struct. /// \param[in] obj_id2 A pointer to an `AMobjId` struct. /// \return `true` if \p obj_id1 `==` \p obj_id2 and `false` otherwise. -/// \pre \p obj_id1 `!= NULL`. -/// \pre \p obj_id2 `!= NULL`. +/// \pre \p obj_id1 `!= NULL` +/// \pre \p obj_id1 `!= NULL` +/// \post `!(`\p obj_id1 `&&` \p obj_id2 `) -> false` /// \internal /// /// #Safety @@ -135,26 +133,28 @@ pub unsafe extern "C" fn AMobjIdCounter(obj_id: *const AMobjId) -> u64 { pub unsafe extern "C" fn AMobjIdEqual(obj_id1: *const AMobjId, obj_id2: *const AMobjId) -> bool { match (obj_id1.as_ref(), obj_id2.as_ref()) { (Some(obj_id1), Some(obj_id2)) => obj_id1 == obj_id2, - (None, Some(_)) | (Some(_), None) | (None, None) => false, + (None, None) | (None, Some(_)) | (Some(_), None) => false, } } /// \memberof AMobjId -/// \brief Gets the index of an object identifier. +/// \brief Gets the index component of an object identifier. /// /// \param[in] obj_id A pointer to an `AMobjId` struct. /// \return A 64-bit unsigned integer. -/// \pre \p obj_id `!= NULL`. +/// \pre \p obj_id `!= NULL` /// \internal /// /// # Safety /// obj_id must be a valid pointer to an AMobjId #[no_mangle] pub unsafe extern "C" fn AMobjIdIndex(obj_id: *const AMobjId) -> usize { + use am::ObjId::*; + if let Some(obj_id) = obj_id.as_ref() { match obj_id.as_ref() { - am::ObjId::Id(_, _, index) => *index, - am::ObjId::Root => 0, + Id(_, _, index) => *index, + Root => 0, } } else { usize::MAX @@ -163,26 +163,54 @@ pub unsafe extern "C" fn AMobjIdIndex(obj_id: *const AMobjId) -> usize { /// \ingroup enumerations /// \enum AMobjType +/// \installed_headerfile /// \brief The type of an object value. +#[derive(PartialEq, Eq)] #[repr(u8)] pub enum AMobjType { - /// A void. - /// \note This tag is unalphabetized to evaluate as false. - Void = 0, + /// The default tag, not a type signifier. + Default = 0, /// A list. - List, + List = 1, /// A key-value map. Map, /// A list of Unicode graphemes. Text, } -impl From for AMobjType { - fn from(o: am::ObjType) -> Self { +impl Default for AMobjType { + fn default() -> Self { + Self::Default + } +} + +impl From<&am::ObjType> for AMobjType { + fn from(o: &am::ObjType) -> Self { + use am::ObjType::*; + match o { - am::ObjType::Map | am::ObjType::Table => AMobjType::Map, - am::ObjType::List => AMobjType::List, - am::ObjType::Text => AMobjType::Text, + List => Self::List, + Map | Table => Self::Map, + Text => Self::Text, + } + } +} + +impl TryFrom<&AMobjType> for am::ObjType { + type Error = am::AutomergeError; + + fn try_from(c_obj_type: &AMobjType) -> Result { + use am::AutomergeError::InvalidValueType; + use AMobjType::*; + + match c_obj_type { + List => Ok(Self::List), + Map => Ok(Self::Map), + Text => Ok(Self::Text), + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), } } } diff --git a/rust/automerge-c/src/obj/item.rs b/rust/automerge-c/src/obj/item.rs deleted file mode 100644 index a2e99d06..00000000 --- a/rust/automerge-c/src/obj/item.rs +++ /dev/null @@ -1,73 +0,0 @@ -use automerge as am; - -use crate::obj::AMobjId; -use crate::result::AMvalue; - -/// \struct AMobjItem -/// \installed_headerfile -/// \brief An item in an object. -pub struct AMobjItem { - /// The object identifier of an item in an object. - obj_id: AMobjId, - /// The value of an item in an object. - value: am::Value<'static>, -} - -impl AMobjItem { - pub fn new(value: am::Value<'static>, obj_id: am::ObjId) -> Self { - Self { - obj_id: AMobjId::new(obj_id), - value, - } - } -} - -impl PartialEq for AMobjItem { - fn eq(&self, other: &Self) -> bool { - self.obj_id == other.obj_id && self.value == other.value - } -} - -impl From<&AMobjItem> for (am::Value<'static>, am::ObjId) { - fn from(obj_item: &AMobjItem) -> Self { - (obj_item.value.clone(), obj_item.obj_id.as_ref().clone()) - } -} - -/// \memberof AMobjItem -/// \brief Gets the object identifier of an item in an object. -/// -/// \param[in] obj_item A pointer to an `AMobjItem` struct. -/// \return A pointer to an `AMobjId` struct. -/// \pre \p obj_item `!= NULL`. -/// \internal -/// -/// # Safety -/// obj_item must be a valid pointer to an AMobjItem -#[no_mangle] -pub unsafe extern "C" fn AMobjItemObjId(obj_item: *const AMobjItem) -> *const AMobjId { - if let Some(obj_item) = obj_item.as_ref() { - &obj_item.obj_id - } else { - std::ptr::null() - } -} - -/// \memberof AMobjItem -/// \brief Gets the value of an item in an object. -/// -/// \param[in] obj_item A pointer to an `AMobjItem` struct. -/// \return An `AMvalue` struct. -/// \pre \p obj_item `!= NULL`. -/// \internal -/// -/// # Safety -/// obj_item must be a valid pointer to an AMobjItem -#[no_mangle] -pub unsafe extern "C" fn AMobjItemValue<'a>(obj_item: *const AMobjItem) -> AMvalue<'a> { - if let Some(obj_item) = obj_item.as_ref() { - (&obj_item.value).into() - } else { - AMvalue::Void - } -} diff --git a/rust/automerge-c/src/obj/items.rs b/rust/automerge-c/src/obj/items.rs deleted file mode 100644 index d6b847cf..00000000 --- a/rust/automerge-c/src/obj/items.rs +++ /dev/null @@ -1,341 +0,0 @@ -use std::ffi::c_void; -use std::mem::size_of; - -use crate::obj::item::AMobjItem; - -#[repr(C)] -struct Detail { - len: usize, - offset: isize, - ptr: *const c_void, -} - -/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call -/// (https://github.com/eqrion/cbindgen/issues/252) but it will -/// propagate the name of a constant initialized from it so if the -/// constant's name is a symbolic representation of the value it can be -/// converted into a number by post-processing the header it generated. -pub const USIZE_USIZE_USIZE_: usize = size_of::(); - -impl Detail { - fn new(obj_items: &[AMobjItem], offset: isize) -> Self { - Self { - len: obj_items.len(), - offset, - ptr: obj_items.as_ptr() as *const c_void, - } - } - - pub fn advance(&mut self, n: isize) { - if n == 0 { - return; - } - let len = self.len as isize; - self.offset = if self.offset < 0 { - // It's reversed. - let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); - if unclipped >= 0 { - // Clip it to the forward stop. - len - } else { - std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) - } - } else { - let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); - if unclipped < 0 { - // Clip it to the reverse stop. - -(len + 1) - } else { - std::cmp::max(0, std::cmp::min(unclipped, len)) - } - } - } - - pub fn get_index(&self) -> usize { - (self.offset - + if self.offset < 0 { - self.len as isize - } else { - 0 - }) as usize - } - - pub fn next(&mut self, n: isize) -> Option<&AMobjItem> { - if self.is_stopped() { - return None; - } - let slice: &[AMobjItem] = - unsafe { std::slice::from_raw_parts(self.ptr as *const AMobjItem, self.len) }; - let value = &slice[self.get_index()]; - self.advance(n); - Some(value) - } - - pub fn is_stopped(&self) -> bool { - let len = self.len as isize; - self.offset < -len || self.offset == len - } - - pub fn prev(&mut self, n: isize) -> Option<&AMobjItem> { - self.advance(-n); - if self.is_stopped() { - return None; - } - let slice: &[AMobjItem] = - unsafe { std::slice::from_raw_parts(self.ptr as *const AMobjItem, self.len) }; - Some(&slice[self.get_index()]) - } - - pub fn reversed(&self) -> Self { - Self { - len: self.len, - offset: -(self.offset + 1), - ptr: self.ptr, - } - } - - pub fn rewound(&self) -> Self { - Self { - len: self.len, - offset: if self.offset < 0 { -1 } else { 0 }, - ptr: self.ptr, - } - } -} - -impl From for [u8; USIZE_USIZE_USIZE_] { - fn from(detail: Detail) -> Self { - unsafe { - std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) - .try_into() - .unwrap() - } - } -} - -/// \struct AMobjItems -/// \installed_headerfile -/// \brief A random-access iterator over a sequence of object items. -#[repr(C)] -#[derive(Eq, PartialEq)] -pub struct AMobjItems { - /// An implementation detail that is intentionally opaque. - /// \warning Modifying \p detail will cause undefined behavior. - /// \note The actual size of \p detail will vary by platform, this is just - /// the one for the platform this documentation was built on. - detail: [u8; USIZE_USIZE_USIZE_], -} - -impl AMobjItems { - pub fn new(obj_items: &[AMobjItem]) -> Self { - Self { - detail: Detail::new(obj_items, 0).into(), - } - } - - pub fn advance(&mut self, n: isize) { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.advance(n); - } - - pub fn len(&self) -> usize { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - detail.len - } - - pub fn next(&mut self, n: isize) -> Option<&AMobjItem> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.next(n) - } - - pub fn prev(&mut self, n: isize) -> Option<&AMobjItem> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.prev(n) - } - - pub fn reversed(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.reversed().into(), - } - } - - pub fn rewound(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.rewound().into(), - } - } -} - -impl AsRef<[AMobjItem]> for AMobjItems { - fn as_ref(&self) -> &[AMobjItem] { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - unsafe { std::slice::from_raw_parts(detail.ptr as *const AMobjItem, detail.len) } - } -} - -impl Default for AMobjItems { - fn default() -> Self { - Self { - detail: [0; USIZE_USIZE_USIZE_], - } - } -} - -/// \memberof AMobjItems -/// \brief Advances an iterator over a sequence of object items by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] obj_items A pointer to an `AMobjItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \pre \p obj_items `!= NULL`. -/// \internal -/// -/// #Safety -/// obj_items must be a valid pointer to an AMobjItems -#[no_mangle] -pub unsafe extern "C" fn AMobjItemsAdvance(obj_items: *mut AMobjItems, n: isize) { - if let Some(obj_items) = obj_items.as_mut() { - obj_items.advance(n); - }; -} - -/// \memberof AMobjItems -/// \brief Tests the equality of two sequences of object items underlying a -/// pair of iterators. -/// -/// \param[in] obj_items1 A pointer to an `AMobjItems` struct. -/// \param[in] obj_items2 A pointer to an `AMobjItems` struct. -/// \return `true` if \p obj_items1 `==` \p obj_items2 and `false` otherwise. -/// \pre \p obj_items1 `!= NULL`. -/// \pre \p obj_items2 `!= NULL`. -/// \internal -/// -/// #Safety -/// obj_items1 must be a valid pointer to an AMobjItems -/// obj_items2 must be a valid pointer to an AMobjItems -#[no_mangle] -pub unsafe extern "C" fn AMobjItemsEqual( - obj_items1: *const AMobjItems, - obj_items2: *const AMobjItems, -) -> bool { - match (obj_items1.as_ref(), obj_items2.as_ref()) { - (Some(obj_items1), Some(obj_items2)) => obj_items1.as_ref() == obj_items2.as_ref(), - (None, Some(_)) | (Some(_), None) | (None, None) => false, - } -} - -/// \memberof AMobjItems -/// \brief Gets the object item at the current position of an iterator over a -/// sequence of object items and then advances it by at most \p |n| -/// positions where the sign of \p n is relative to the iterator's -/// direction. -/// -/// \param[in,out] obj_items A pointer to an `AMobjItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMobjItem` struct that's `NULL` when \p obj_items -/// was previously advanced past its forward/reverse limit. -/// \pre \p obj_items `!= NULL`. -/// \internal -/// -/// #Safety -/// obj_items must be a valid pointer to an AMobjItems -#[no_mangle] -pub unsafe extern "C" fn AMobjItemsNext(obj_items: *mut AMobjItems, n: isize) -> *const AMobjItem { - if let Some(obj_items) = obj_items.as_mut() { - if let Some(obj_item) = obj_items.next(n) { - return obj_item; - } - } - std::ptr::null() -} - -/// \memberof AMobjItems -/// \brief Advances an iterator over a sequence of object items by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction and then gets the object item at its new -/// position. -/// -/// \param[in,out] obj_items A pointer to an `AMobjItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMobjItem` struct that's `NULL` when \p obj_items -/// is presently advanced past its forward/reverse limit. -/// \pre \p obj_items `!= NULL`. -/// \internal -/// -/// #Safety -/// obj_items must be a valid pointer to an AMobjItems -#[no_mangle] -pub unsafe extern "C" fn AMobjItemsPrev(obj_items: *mut AMobjItems, n: isize) -> *const AMobjItem { - if let Some(obj_items) = obj_items.as_mut() { - if let Some(obj_item) = obj_items.prev(n) { - return obj_item; - } - } - std::ptr::null() -} - -/// \memberof AMobjItems -/// \brief Gets the size of the sequence of object items underlying an -/// iterator. -/// -/// \param[in] obj_items A pointer to an `AMobjItems` struct. -/// \return The count of values in \p obj_items. -/// \pre \p obj_items `!= NULL`. -/// \internal -/// -/// #Safety -/// obj_items must be a valid pointer to an AMobjItems -#[no_mangle] -pub unsafe extern "C" fn AMobjItemsSize(obj_items: *const AMobjItems) -> usize { - if let Some(obj_items) = obj_items.as_ref() { - obj_items.len() - } else { - 0 - } -} - -/// \memberof AMobjItems -/// \brief Creates an iterator over the same sequence of object items as the -/// given one but with the opposite position and direction. -/// -/// \param[in] obj_items A pointer to an `AMobjItems` struct. -/// \return An `AMobjItems` struct -/// \pre \p obj_items `!= NULL`. -/// \internal -/// -/// #Safety -/// obj_items must be a valid pointer to an AMobjItems -#[no_mangle] -pub unsafe extern "C" fn AMobjItemsReversed(obj_items: *const AMobjItems) -> AMobjItems { - if let Some(obj_items) = obj_items.as_ref() { - obj_items.reversed() - } else { - Default::default() - } -} - -/// \memberof AMobjItems -/// \brief Creates an iterator at the starting position over the same sequence -/// of object items as the given one. -/// -/// \param[in] obj_items A pointer to an `AMobjItems` struct. -/// \return An `AMobjItems` struct -/// \pre \p obj_items `!= NULL`. -/// \internal -/// -/// #Safety -/// obj_items must be a valid pointer to an AMobjItems -#[no_mangle] -pub unsafe extern "C" fn AMobjItemsRewound(obj_items: *const AMobjItems) -> AMobjItems { - if let Some(obj_items) = obj_items.as_ref() { - obj_items.rewound() - } else { - Default::default() - } -} diff --git a/rust/automerge-c/src/result.rs b/rust/automerge-c/src/result.rs index 599ada96..2975f38b 100644 --- a/rust/automerge-c/src/result.rs +++ b/rust/automerge-c/src/result.rs @@ -1,513 +1,85 @@ use automerge as am; -use smol_str::SmolStr; -use std::any::type_name; -use std::collections::BTreeMap; use std::ops::{Range, RangeFrom, RangeFull, RangeTo}; -use crate::actor_id::AMactorId; use crate::byte_span::AMbyteSpan; -use crate::change::AMchange; -use crate::change_hashes::AMchangeHashes; -use crate::changes::AMchanges; -use crate::doc::list::{item::AMlistItem, items::AMlistItems}; -use crate::doc::map::{item::AMmapItem, items::AMmapItems}; -use crate::doc::AMdoc; -use crate::obj::item::AMobjItem; -use crate::obj::items::AMobjItems; -use crate::obj::AMobjId; -use crate::strs::AMstrs; -use crate::sync::{AMsyncMessage, AMsyncState}; - -/// \struct AMvalue -/// \installed_headerfile -/// \brief A discriminated union of value type variants for a result. -/// -/// \enum AMvalueVariant -/// \brief A value type discriminant. -/// -/// \var AMvalue::actor_id -/// An actor identifier as a pointer to an `AMactorId` struct. -/// -/// \var AMvalue::boolean -/// A boolean. -/// -/// \var AMvalue::bytes -/// A sequence of bytes as an `AMbyteSpan` struct. -/// -/// \var AMvalue::change_hashes -/// A sequence of change hashes as an `AMchangeHashes` struct. -/// -/// \var AMvalue::changes -/// A sequence of changes as an `AMchanges` struct. -/// -/// \var AMvalue::counter -/// A CRDT counter. -/// -/// \var AMvalue::doc -/// A document as a pointer to an `AMdoc` struct. -/// -/// \var AMvalue::f64 -/// A 64-bit float. -/// -/// \var AMvalue::int_ -/// A 64-bit signed integer. -/// -/// \var AMvalue::list_items -/// A sequence of list object items as an `AMlistItems` struct. -/// -/// \var AMvalue::map_items -/// A sequence of map object items as an `AMmapItems` struct. -/// -/// \var AMvalue::obj_id -/// An object identifier as a pointer to an `AMobjId` struct. -/// -/// \var AMvalue::obj_items -/// A sequence of object items as an `AMobjItems` struct. -/// -/// \var AMvalue::str -/// A UTF-8 string view as an `AMbyteSpan` struct. -/// -/// \var AMvalue::strs -/// A sequence of UTF-8 strings as an `AMstrs` struct. -/// -/// \var AMvalue::sync_message -/// A synchronization message as a pointer to an `AMsyncMessage` struct. -/// -/// \var AMvalue::sync_state -/// A synchronization state as a pointer to an `AMsyncState` struct. -/// -/// \var AMvalue::tag -/// The variant discriminator. -/// -/// \var AMvalue::timestamp -/// A *nix timestamp (milliseconds). -/// -/// \var AMvalue::uint -/// A 64-bit unsigned integer. -/// -/// \var AMvalue::unknown -/// A value of unknown type as an `AMunknownValue` struct. -#[repr(u8)] -pub enum AMvalue<'a> { - /// A void variant. - /// \note This tag is unalphabetized so that a zeroed struct will have it. - Void, - /// An actor identifier variant. - ActorId(&'a AMactorId), - /// A boolean variant. - Boolean(bool), - /// A byte array variant. - Bytes(AMbyteSpan), - /// A change hashes variant. - ChangeHashes(AMchangeHashes), - /// A changes variant. - Changes(AMchanges), - /// A CRDT counter variant. - Counter(i64), - /// A document variant. - Doc(*mut AMdoc), - /// A 64-bit float variant. - F64(f64), - /// A 64-bit signed integer variant. - Int(i64), - /// A list items variant. - ListItems(AMlistItems), - /// A map items variant. - MapItems(AMmapItems), - /// A null variant. - Null, - /// An object identifier variant. - ObjId(&'a AMobjId), - /// An object items variant. - ObjItems(AMobjItems), - /// A UTF-8 string view variant. - Str(AMbyteSpan), - /// A UTF-8 string views variant. - Strs(AMstrs), - /// A synchronization message variant. - SyncMessage(&'a AMsyncMessage), - /// A synchronization state variant. - SyncState(&'a mut AMsyncState), - /// A *nix timestamp (milliseconds) variant. - Timestamp(i64), - /// A 64-bit unsigned integer variant. - Uint(u64), - /// An unknown type of scalar value variant. - Unknown(AMunknownValue), -} - -impl<'a> PartialEq for AMvalue<'a> { - fn eq(&self, other: &Self) -> bool { - use AMvalue::*; - - match (self, other) { - (ActorId(lhs), ActorId(rhs)) => *lhs == *rhs, - (Boolean(lhs), Boolean(rhs)) => lhs == rhs, - (Bytes(lhs), Bytes(rhs)) => lhs == rhs, - (ChangeHashes(lhs), ChangeHashes(rhs)) => lhs == rhs, - (Changes(lhs), Changes(rhs)) => lhs == rhs, - (Counter(lhs), Counter(rhs)) => lhs == rhs, - (Doc(lhs), Doc(rhs)) => *lhs == *rhs, - (F64(lhs), F64(rhs)) => lhs == rhs, - (Int(lhs), Int(rhs)) => lhs == rhs, - (ListItems(lhs), ListItems(rhs)) => lhs == rhs, - (MapItems(lhs), MapItems(rhs)) => lhs == rhs, - (ObjId(lhs), ObjId(rhs)) => *lhs == *rhs, - (ObjItems(lhs), ObjItems(rhs)) => lhs == rhs, - (Str(lhs), Str(rhs)) => lhs == rhs, - (Strs(lhs), Strs(rhs)) => lhs == rhs, - (SyncMessage(lhs), SyncMessage(rhs)) => *lhs == *rhs, - (SyncState(lhs), SyncState(rhs)) => *lhs == *rhs, - (Timestamp(lhs), Timestamp(rhs)) => lhs == rhs, - (Uint(lhs), Uint(rhs)) => lhs == rhs, - (Unknown(lhs), Unknown(rhs)) => lhs == rhs, - (Null, Null) | (Void, Void) => true, - _ => false, - } - } -} - -impl From<&am::Value<'_>> for AMvalue<'_> { - fn from(value: &am::Value<'_>) -> Self { - match value { - am::Value::Scalar(scalar) => match scalar.as_ref() { - am::ScalarValue::Boolean(flag) => AMvalue::Boolean(*flag), - am::ScalarValue::Bytes(bytes) => AMvalue::Bytes(bytes.as_slice().into()), - am::ScalarValue::Counter(counter) => AMvalue::Counter(counter.into()), - am::ScalarValue::F64(float) => AMvalue::F64(*float), - am::ScalarValue::Int(int) => AMvalue::Int(*int), - am::ScalarValue::Null => AMvalue::Null, - am::ScalarValue::Str(smol_str) => AMvalue::Str(smol_str.as_bytes().into()), - am::ScalarValue::Timestamp(timestamp) => AMvalue::Timestamp(*timestamp), - am::ScalarValue::Uint(uint) => AMvalue::Uint(*uint), - am::ScalarValue::Unknown { bytes, type_code } => AMvalue::Unknown(AMunknownValue { - bytes: bytes.as_slice().into(), - type_code: *type_code, - }), - }, - // \todo Confirm that an object variant should be ignored - // when there's no object ID variant. - am::Value::Object(_) => AMvalue::Void, - } - } -} - -impl From<&AMvalue<'_>> for u8 { - fn from(value: &AMvalue) -> Self { - use AMvalue::*; - - // \warning These numbers must correspond to the order in which the - // variants of an AMvalue are declared within it. - match value { - ActorId(_) => 1, - Boolean(_) => 2, - Bytes(_) => 3, - ChangeHashes(_) => 4, - Changes(_) => 5, - Counter(_) => 6, - Doc(_) => 7, - F64(_) => 8, - Int(_) => 9, - ListItems(_) => 10, - MapItems(_) => 11, - Null => 12, - ObjId(_) => 13, - ObjItems(_) => 14, - Str(_) => 15, - Strs(_) => 16, - SyncMessage(_) => 17, - SyncState(_) => 18, - Timestamp(_) => 19, - Uint(_) => 20, - Unknown(..) => 21, - Void => 0, - } - } -} - -impl TryFrom<&AMvalue<'_>> for am::ScalarValue { - type Error = am::AutomergeError; - - fn try_from(c_value: &AMvalue) -> Result { - use am::AutomergeError::InvalidValueType; - use AMvalue::*; - - let expected = type_name::().to_string(); - match c_value { - Boolean(b) => Ok(am::ScalarValue::Boolean(*b)), - Bytes(span) => { - let slice = unsafe { std::slice::from_raw_parts(span.src, span.count) }; - Ok(am::ScalarValue::Bytes(slice.to_vec())) - } - Counter(c) => Ok(am::ScalarValue::Counter(c.into())), - F64(f) => Ok(am::ScalarValue::F64(*f)), - Int(i) => Ok(am::ScalarValue::Int(*i)), - Str(span) => { - let result: Result<&str, am::AutomergeError> = span.try_into(); - match result { - Ok(str_) => Ok(am::ScalarValue::Str(SmolStr::new(str_))), - Err(e) => Err(e), - } - } - Timestamp(t) => Ok(am::ScalarValue::Timestamp(*t)), - Uint(u) => Ok(am::ScalarValue::Uint(*u)), - Null => Ok(am::ScalarValue::Null), - Unknown(AMunknownValue { bytes, type_code }) => { - let slice = unsafe { std::slice::from_raw_parts(bytes.src, bytes.count) }; - Ok(am::ScalarValue::Unknown { - bytes: slice.to_vec(), - type_code: *type_code, - }) - } - ActorId(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - ChangeHashes(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - Changes(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - Doc(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - ListItems(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - MapItems(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - ObjId(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - ObjItems(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - Strs(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - SyncMessage(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - SyncState(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - Void => Err(InvalidValueType { - expected, - unexpected: type_name::<()>().to_string(), - }), - } - } -} - -/// \memberof AMvalue -/// \brief Tests the equality of two values. -/// -/// \param[in] value1 A pointer to an `AMvalue` struct. -/// \param[in] value2 A pointer to an `AMvalue` struct. -/// \return `true` if \p value1 `==` \p value2 and `false` otherwise. -/// \pre \p value1 `!= NULL`. -/// \pre \p value2 `!= NULL`. -/// \internal -/// -/// #Safety -/// value1 must be a valid AMvalue pointer -/// value2 must be a valid AMvalue pointer -#[no_mangle] -pub unsafe extern "C" fn AMvalueEqual(value1: *const AMvalue, value2: *const AMvalue) -> bool { - match (value1.as_ref(), value2.as_ref()) { - (Some(value1), Some(value2)) => *value1 == *value2, - (None, Some(_)) | (Some(_), None) | (None, None) => false, - } -} +use crate::index::AMindex; +use crate::item::AMitem; +use crate::items::AMitems; /// \struct AMresult /// \installed_headerfile /// \brief A discriminated union of result variants. pub enum AMresult { - ActorId(am::ActorId, Option), - ChangeHashes(Vec), - Changes(Vec, Option>), - Doc(Box), + Items(Vec), Error(String), - ListItems(Vec), - MapItems(Vec), - ObjId(AMobjId), - ObjItems(Vec), - String(String), - Strings(Vec), - SyncMessage(AMsyncMessage), - SyncState(Box), - Value(am::Value<'static>), - Void, } impl AMresult { - pub(crate) fn err(s: &str) -> Self { - AMresult::Error(s.to_string()) + pub(crate) fn error(s: &str) -> Self { + Self::Error(s.to_string()) + } + + pub(crate) fn item(item: AMitem) -> Self { + Self::Items(vec![item]) + } + + pub(crate) fn items(items: Vec) -> Self { + Self::Items(items) + } +} + +impl Default for AMresult { + fn default() -> Self { + Self::Items(vec![]) } } impl From for AMresult { fn from(auto_commit: am::AutoCommit) -> Self { - AMresult::Doc(Box::new(AMdoc::new(auto_commit))) + Self::item(AMitem::exact(am::ROOT, auto_commit.into())) + } +} + +impl From for AMresult { + fn from(change: am::Change) -> Self { + Self::item(change.into()) } } impl From for AMresult { fn from(change_hash: am::ChangeHash) -> Self { - AMresult::ChangeHashes(vec![change_hash]) + Self::item(change_hash.into()) } } impl From> for AMresult { - fn from(c: Option) -> Self { - match c { - Some(c) => c.into(), - None => AMresult::Void, + fn from(maybe: Option) -> Self { + match maybe { + Some(change_hash) => change_hash.into(), + None => Self::item(Default::default()), } } } -impl From> for AMresult { - fn from(keys: am::Keys<'_, '_>) -> Self { - AMresult::Strings(keys.collect()) - } -} - -impl From> for AMresult { - fn from(keys: am::KeysAt<'_, '_>) -> Self { - AMresult::Strings(keys.collect()) - } -} - -impl From>> for AMresult { - fn from(list_range: am::ListRange<'static, Range>) -> Self { - AMresult::ListItems( - list_range - .map(|(i, v, o)| AMlistItem::new(i, v.clone(), o)) - .collect(), - ) - } -} - -impl From>> for AMresult { - fn from(list_range: am::ListRangeAt<'static, Range>) -> Self { - AMresult::ListItems( - list_range - .map(|(i, v, o)| AMlistItem::new(i, v.clone(), o)) - .collect(), - ) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRange<'static, Range>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRangeAt<'static, Range>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRange<'static, RangeFrom>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRangeAt<'static, RangeFrom>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) - } -} - -impl From> for AMresult { - fn from(map_range: am::MapRange<'static, RangeFull>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) - } -} - -impl From> for AMresult { - fn from(map_range: am::MapRangeAt<'static, RangeFull>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRange<'static, RangeTo>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRangeAt<'static, RangeTo>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) +impl From> for AMresult { + fn from(maybe: Result) -> Self { + match maybe { + Ok(change_hash) => change_hash.into(), + Err(e) => Self::error(&e.to_string()), + } } } impl From for AMresult { fn from(state: am::sync::State) -> Self { - AMresult::SyncState(Box::new(AMsyncState::new(state))) + Self::item(state.into()) } } impl From> for AMresult { fn from(pairs: am::Values<'static>) -> Self { - AMresult::ObjItems(pairs.map(|(v, o)| AMobjItem::new(v.clone(), o)).collect()) - } -} - -impl From, am::ObjId)>, am::AutomergeError>> for AMresult { - fn from(maybe: Result, am::ObjId)>, am::AutomergeError>) -> Self { - match maybe { - Ok(pairs) => AMresult::ObjItems( - pairs - .into_iter() - .map(|(v, o)| AMobjItem::new(v, o)) - .collect(), - ), - Err(e) => AMresult::err(&e.to_string()), - } + Self::items(pairs.map(|(v, o)| AMitem::exact(o, v.into())).collect()) } } @@ -517,37 +89,150 @@ impl From for *mut AMresult { } } +impl From> for AMresult { + fn from(keys: am::Keys<'_, '_>) -> Self { + Self::items(keys.map(|s| s.into()).collect()) + } +} + +impl From> for AMresult { + fn from(keys: am::KeysAt<'_, '_>) -> Self { + Self::items(keys.map(|s| s.into()).collect()) + } +} + +impl From>> for AMresult { + fn from(list_range: am::ListRange<'static, Range>) -> Self { + Self::items( + list_range + .map(|(i, v, o)| AMitem::indexed(AMindex::Pos(i), o, v.into())) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(list_range: am::ListRangeAt<'static, Range>) -> Self { + Self::items( + list_range + .map(|(i, v, o)| AMitem::indexed(AMindex::Pos(i), o, v.into())) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRange<'static, Range>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRangeAt<'static, Range>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRange<'static, RangeFrom>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRangeAt<'static, RangeFrom>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + +impl From> for AMresult { + fn from(map_range: am::MapRange<'static, RangeFull>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + +impl From> for AMresult { + fn from(map_range: am::MapRangeAt<'static, RangeFull>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRange<'static, RangeTo>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRangeAt<'static, RangeTo>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + impl From> for AMresult { fn from(maybe: Option<&am::Change>) -> Self { - match maybe { - Some(change) => AMresult::Changes(vec![change.clone()], None), - None => AMresult::Void, - } + Self::item(match maybe { + Some(change) => change.clone().into(), + None => Default::default(), + }) } } impl From> for AMresult { fn from(maybe: Option) -> Self { - match maybe { - Some(message) => AMresult::SyncMessage(AMsyncMessage::new(message)), - None => AMresult::Void, - } + Self::item(match maybe { + Some(message) => message.into(), + None => Default::default(), + }) } } impl From> for AMresult { fn from(maybe: Result<(), am::AutomergeError>) -> Self { match maybe { - Ok(()) => AMresult::Void, - Err(e) => AMresult::err(&e.to_string()), + Ok(()) => Self::item(Default::default()), + Err(e) => Self::error(&e.to_string()), } } } + impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(actor_id) => AMresult::ActorId(actor_id, None), - Err(e) => AMresult::err(&e.to_string()), + Ok(actor_id) => Self::item(actor_id.into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -555,8 +240,8 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(actor_id) => AMresult::ActorId(actor_id, None), - Err(e) => AMresult::err(&e.to_string()), + Ok(actor_id) => Self::item(actor_id.into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -564,8 +249,8 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(auto_commit) => AMresult::Doc(Box::new(AMdoc::new(auto_commit))), - Err(e) => AMresult::err(&e.to_string()), + Ok(auto_commit) => Self::item(auto_commit.into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -573,17 +258,17 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(change) => AMresult::Changes(vec![change], None), - Err(e) => AMresult::err(&e.to_string()), + Ok(change) => Self::item(change.into()), + Err(e) => Self::error(&e.to_string()), } } } -impl From> for AMresult { - fn from(maybe: Result) -> Self { - match maybe { - Ok(obj_id) => AMresult::ObjId(AMobjId::new(obj_id)), - Err(e) => AMresult::err(&e.to_string()), +impl From<(Result, am::ObjType)> for AMresult { + fn from(tuple: (Result, am::ObjType)) -> Self { + match tuple { + (Ok(obj_id), obj_type) => Self::item((obj_id, obj_type).into()), + (Err(e), _) => Self::error(&e.to_string()), } } } @@ -591,8 +276,8 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(message) => AMresult::SyncMessage(AMsyncMessage::new(message)), - Err(e) => AMresult::err(&e.to_string()), + Ok(message) => Self::item(message.into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -600,8 +285,8 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(state) => AMresult::SyncState(Box::new(AMsyncState::new(state))), - Err(e) => AMresult::err(&e.to_string()), + Ok(state) => Self::item(state.into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -609,8 +294,8 @@ impl From> for AMresult { impl From, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::AutomergeError>) -> Self { match maybe { - Ok(value) => AMresult::Value(value), - Err(e) => AMresult::err(&e.to_string()), + Ok(value) => Self::item(value.into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -618,12 +303,9 @@ impl From, am::AutomergeError>> for AMresult { impl From, am::ObjId)>, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::ObjId)>, am::AutomergeError>) -> Self { match maybe { - Ok(Some((value, obj_id))) => match value { - am::Value::Object(_) => AMresult::ObjId(AMobjId::new(obj_id)), - _ => AMresult::Value(value), - }, - Ok(None) => AMresult::Void, - Err(e) => AMresult::err(&e.to_string()), + Ok(Some((value, obj_id))) => Self::item(AMitem::exact(obj_id, value.into())), + Ok(None) => Self::item(Default::default()), + Err(e) => Self::error(&e.to_string()), } } } @@ -631,8 +313,8 @@ impl From, am::ObjId)>, am::AutomergeError>> f impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(string) => AMresult::String(string), - Err(e) => AMresult::err(&e.to_string()), + Ok(string) => Self::item(string.into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -640,8 +322,8 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(size) => AMresult::Value(am::Value::uint(size as u64)), - Err(e) => AMresult::err(&e.to_string()), + Ok(size) => Self::item(am::Value::uint(size as u64).into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -649,17 +331,8 @@ impl From> for AMresult { impl From, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::AutomergeError>) -> Self { match maybe { - Ok(changes) => AMresult::Changes(changes, None), - Err(e) => AMresult::err(&e.to_string()), - } - } -} - -impl From, am::LoadChangeError>> for AMresult { - fn from(maybe: Result, am::LoadChangeError>) -> Self { - match maybe { - Ok(changes) => AMresult::Changes(changes, None), - Err(e) => AMresult::err(&e.to_string()), + Ok(changes) => Self::items(changes.into_iter().map(|change| change.into()).collect()), + Err(e) => Self::error(&e.to_string()), } } } @@ -667,12 +340,22 @@ impl From, am::LoadChangeError>> for AMresult { impl From, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::AutomergeError>) -> Self { match maybe { - Ok(changes) => { - let changes: Vec = - changes.iter().map(|&change| change.clone()).collect(); - AMresult::Changes(changes, None) - } - Err(e) => AMresult::err(&e.to_string()), + Ok(changes) => Self::items( + changes + .into_iter() + .map(|change| change.clone().into()) + .collect(), + ), + Err(e) => Self::error(&e.to_string()), + } + } +} + +impl From, am::LoadChangeError>> for AMresult { + fn from(maybe: Result, am::LoadChangeError>) -> Self { + match maybe { + Ok(changes) => Self::items(changes.into_iter().map(|change| change.into()).collect()), + Err(e) => Self::error(&e.to_string()), } } } @@ -680,8 +363,13 @@ impl From, am::AutomergeError>> for AMresult { impl From, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::AutomergeError>) -> Self { match maybe { - Ok(change_hashes) => AMresult::ChangeHashes(change_hashes), - Err(e) => AMresult::err(&e.to_string()), + Ok(change_hashes) => Self::items( + change_hashes + .into_iter() + .map(|change_hash| change_hash.into()) + .collect(), + ), + Err(e) => Self::error(&e.to_string()), } } } @@ -689,8 +377,27 @@ impl From, am::AutomergeError>> for AMresult { impl From, am::InvalidChangeHashSlice>> for AMresult { fn from(maybe: Result, am::InvalidChangeHashSlice>) -> Self { match maybe { - Ok(change_hashes) => AMresult::ChangeHashes(change_hashes), - Err(e) => AMresult::err(&e.to_string()), + Ok(change_hashes) => Self::items( + change_hashes + .into_iter() + .map(|change_hash| change_hash.into()) + .collect(), + ), + Err(e) => Self::error(&e.to_string()), + } + } +} + +impl From, am::ObjId)>, am::AutomergeError>> for AMresult { + fn from(maybe: Result, am::ObjId)>, am::AutomergeError>) -> Self { + match maybe { + Ok(pairs) => Self::items( + pairs + .into_iter() + .map(|(v, o)| AMitem::exact(o, v.into())) + .collect(), + ), + Err(e) => Self::error(&e.to_string()), } } } @@ -698,28 +405,66 @@ impl From, am::InvalidChangeHashSlice>> for AMresult impl From, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::AutomergeError>) -> Self { match maybe { - Ok(bytes) => AMresult::Value(am::Value::bytes(bytes)), - Err(e) => AMresult::err(&e.to_string()), + Ok(bytes) => Self::item(am::Value::bytes(bytes).into()), + Err(e) => Self::error(&e.to_string()), } } } +impl From<&[am::Change]> for AMresult { + fn from(changes: &[am::Change]) -> Self { + Self::items(changes.iter().map(|change| change.clone().into()).collect()) + } +} + impl From> for AMresult { fn from(changes: Vec<&am::Change>) -> Self { - let changes: Vec = changes.iter().map(|&change| change.clone()).collect(); - AMresult::Changes(changes, None) + Self::items( + changes + .into_iter() + .map(|change| change.clone().into()) + .collect(), + ) + } +} + +impl From<&[am::ChangeHash]> for AMresult { + fn from(change_hashes: &[am::ChangeHash]) -> Self { + Self::items( + change_hashes + .iter() + .map(|change_hash| (*change_hash).into()) + .collect(), + ) + } +} + +impl From<&[am::sync::Have]> for AMresult { + fn from(haves: &[am::sync::Have]) -> Self { + Self::items(haves.iter().map(|have| have.clone().into()).collect()) } } impl From> for AMresult { fn from(change_hashes: Vec) -> Self { - AMresult::ChangeHashes(change_hashes) + Self::items( + change_hashes + .into_iter() + .map(|change_hash| change_hash.into()) + .collect(), + ) + } +} + +impl From> for AMresult { + fn from(haves: Vec) -> Self { + Self::items(haves.into_iter().map(|have| have.into()).collect()) } } impl From> for AMresult { fn from(bytes: Vec) -> Self { - AMresult::Value(am::Value::bytes(bytes)) + Self::item(am::Value::bytes(bytes).into()) } } @@ -729,8 +474,9 @@ pub fn to_result>(r: R) -> *mut AMresult { /// \ingroup enumerations /// \enum AMstatus +/// \installed_headerfile /// \brief The status of an API call. -#[derive(Debug)] +#[derive(PartialEq, Eq)] #[repr(u8)] pub enum AMstatus { /// Success. @@ -742,35 +488,80 @@ pub enum AMstatus { InvalidResult, } +/// \memberof AMresult +/// \brief Concatenates the items from two results. +/// +/// \param[in] dest A pointer to an `AMresult` struct. +/// \param[in] src A pointer to an `AMresult` struct. +/// \return A pointer to an `AMresult` struct with the items from \p dest in +/// their original order followed by the items from \p src in their +/// original order. +/// \pre \p dest `!= NULL` +/// \pre \p src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +/// \internal +/// +/// # Safety +/// dest must be a valid pointer to an AMresult +/// src must be a valid pointer to an AMresult +#[no_mangle] +pub unsafe extern "C" fn AMresultCat(dest: *const AMresult, src: *const AMresult) -> *mut AMresult { + use AMresult::*; + + match (dest.as_ref(), src.as_ref()) { + (Some(dest), Some(src)) => match (dest, src) { + (Items(dest_items), Items(src_items)) => { + return AMresult::items( + dest_items + .iter() + .cloned() + .chain(src_items.iter().cloned()) + .collect(), + ) + .into(); + } + (Error(_), Error(_)) | (Error(_), Items(_)) | (Items(_), Error(_)) => { + AMresult::error("Invalid `AMresult`").into() + } + }, + (None, None) | (None, Some(_)) | (Some(_), None) => { + AMresult::error("Invalid `AMresult*`").into() + } + } +} + /// \memberof AMresult /// \brief Gets a result's error message string. /// /// \param[in] result A pointer to an `AMresult` struct. /// \return A UTF-8 string view as an `AMbyteSpan` struct. -/// \pre \p result `!= NULL`. +/// \pre \p result `!= NULL` /// \internal /// /// # Safety /// result must be a valid pointer to an AMresult #[no_mangle] -pub unsafe extern "C" fn AMerrorMessage(result: *const AMresult) -> AMbyteSpan { - match result.as_ref() { - Some(AMresult::Error(s)) => s.as_bytes().into(), - _ => Default::default(), +pub unsafe extern "C" fn AMresultError(result: *const AMresult) -> AMbyteSpan { + use AMresult::*; + + if let Some(Error(message)) = result.as_ref() { + return message.as_bytes().into(); } + Default::default() } /// \memberof AMresult /// \brief Deallocates the storage for a result. /// -/// \param[in,out] result A pointer to an `AMresult` struct. -/// \pre \p result `!= NULL`. +/// \param[in] result A pointer to an `AMresult` struct. +/// \pre \p result `!= NULL` /// \internal /// /// # Safety /// result must be a valid pointer to an AMresult #[no_mangle] -pub unsafe extern "C" fn AMfree(result: *mut AMresult) { +pub unsafe extern "C" fn AMresultFree(result: *mut AMresult) { if !result.is_null() { let result: AMresult = *Box::from_raw(result); drop(result) @@ -778,39 +569,67 @@ pub unsafe extern "C" fn AMfree(result: *mut AMresult) { } /// \memberof AMresult -/// \brief Gets the size of a result's value. +/// \brief Gets a result's first item. /// /// \param[in] result A pointer to an `AMresult` struct. -/// \return The count of values in \p result. -/// \pre \p result `!= NULL`. +/// \return A pointer to an `AMitem` struct. +/// \pre \p result `!= NULL` +/// \internal +/// +/// # Safety +/// result must be a valid pointer to an AMresult +#[no_mangle] +pub unsafe extern "C" fn AMresultItem(result: *mut AMresult) -> *mut AMitem { + use AMresult::*; + + if let Some(Items(items)) = result.as_mut() { + if !items.is_empty() { + return &mut items[0]; + } + } + std::ptr::null_mut() +} + +/// \memberof AMresult +/// \brief Gets a result's items. +/// +/// \param[in] result A pointer to an `AMresult` struct. +/// \return An `AMitems` struct. +/// \pre \p result `!= NULL` +/// \internal +/// +/// # Safety +/// result must be a valid pointer to an AMresult +#[no_mangle] +pub unsafe extern "C" fn AMresultItems<'a>(result: *mut AMresult) -> AMitems<'a> { + use AMresult::*; + + if let Some(Items(items)) = result.as_mut() { + if !items.is_empty() { + return AMitems::new(items); + } + } + Default::default() +} + +/// \memberof AMresult +/// \brief Gets the size of a result. +/// +/// \param[in] result A pointer to an `AMresult` struct. +/// \return The count of items within \p result. +/// \pre \p result `!= NULL` /// \internal /// /// # Safety /// result must be a valid pointer to an AMresult #[no_mangle] pub unsafe extern "C" fn AMresultSize(result: *const AMresult) -> usize { - if let Some(result) = result.as_ref() { - use AMresult::*; + use self::AMresult::*; - match result { - Error(_) | Void => 0, - ActorId(_, _) - | Doc(_) - | ObjId(_) - | String(_) - | SyncMessage(_) - | SyncState(_) - | Value(_) => 1, - ChangeHashes(change_hashes) => change_hashes.len(), - Changes(changes, _) => changes.len(), - ListItems(list_items) => list_items.len(), - MapItems(map_items) => map_items.len(), - ObjItems(obj_items) => obj_items.len(), - Strings(cstrings) => cstrings.len(), - } - } else { - 0 + if let Some(Items(items)) = result.as_ref() { + return items.len(); } + 0 } /// \memberof AMresult @@ -818,94 +637,24 @@ pub unsafe extern "C" fn AMresultSize(result: *const AMresult) -> usize { /// /// \param[in] result A pointer to an `AMresult` struct. /// \return An `AMstatus` enum tag. -/// \pre \p result `!= NULL`. +/// \pre \p result `!= NULL` /// \internal /// /// # Safety /// result must be a valid pointer to an AMresult #[no_mangle] pub unsafe extern "C" fn AMresultStatus(result: *const AMresult) -> AMstatus { - match result.as_ref() { - Some(AMresult::Error(_)) => AMstatus::Error, - None => AMstatus::InvalidResult, - _ => AMstatus::Ok, - } -} + use AMresult::*; -/// \memberof AMresult -/// \brief Gets a result's value. -/// -/// \param[in] result A pointer to an `AMresult` struct. -/// \return An `AMvalue` struct. -/// \pre \p result `!= NULL`. -/// \internal -/// -/// # Safety -/// result must be a valid pointer to an AMresult -#[no_mangle] -pub unsafe extern "C" fn AMresultValue<'a>(result: *mut AMresult) -> AMvalue<'a> { - let mut content = AMvalue::Void; - if let Some(result) = result.as_mut() { + if let Some(result) = result.as_ref() { match result { - AMresult::ActorId(actor_id, c_actor_id) => match c_actor_id { - None => { - content = AMvalue::ActorId(&*c_actor_id.insert(AMactorId::new(&*actor_id))); - } - Some(c_actor_id) => { - content = AMvalue::ActorId(&*c_actor_id); - } - }, - AMresult::ChangeHashes(change_hashes) => { - content = AMvalue::ChangeHashes(AMchangeHashes::new(change_hashes)); + Error(_) => { + return AMstatus::Error; } - AMresult::Changes(changes, storage) => { - content = AMvalue::Changes(AMchanges::new( - changes, - storage.get_or_insert(BTreeMap::new()), - )); + _ => { + return AMstatus::Ok; } - AMresult::Doc(doc) => content = AMvalue::Doc(&mut **doc), - AMresult::Error(_) => {} - AMresult::ListItems(list_items) => { - content = AMvalue::ListItems(AMlistItems::new(list_items)); - } - AMresult::MapItems(map_items) => { - content = AMvalue::MapItems(AMmapItems::new(map_items)); - } - AMresult::ObjId(obj_id) => { - content = AMvalue::ObjId(obj_id); - } - AMresult::ObjItems(obj_items) => { - content = AMvalue::ObjItems(AMobjItems::new(obj_items)); - } - AMresult::String(string) => content = AMvalue::Str(string.as_bytes().into()), - AMresult::Strings(strings) => { - content = AMvalue::Strs(AMstrs::new(strings)); - } - AMresult::SyncMessage(sync_message) => { - content = AMvalue::SyncMessage(sync_message); - } - AMresult::SyncState(sync_state) => { - content = AMvalue::SyncState(&mut *sync_state); - } - AMresult::Value(value) => { - content = (&*value).into(); - } - AMresult::Void => {} } - }; - content -} - -/// \struct AMunknownValue -/// \installed_headerfile -/// \brief A value (typically for a `set` operation) whose type is unknown. -/// -#[derive(Eq, PartialEq)] -#[repr(C)] -pub struct AMunknownValue { - /// The value's raw bytes. - bytes: AMbyteSpan, - /// The value's encoded type identifier. - type_code: u8, + } + AMstatus::InvalidResult } diff --git a/rust/automerge-c/src/result_stack.rs b/rust/automerge-c/src/result_stack.rs deleted file mode 100644 index cfb9c7d2..00000000 --- a/rust/automerge-c/src/result_stack.rs +++ /dev/null @@ -1,156 +0,0 @@ -use crate::result::{AMfree, AMresult, AMresultStatus, AMresultValue, AMstatus, AMvalue}; - -/// \struct AMresultStack -/// \installed_headerfile -/// \brief A node in a singly-linked list of result pointers. -/// -/// \note Using this data structure is purely optional because its only purpose -/// is to make memory management tolerable for direct usage of this API -/// in C, C++ and Objective-C. -#[repr(C)] -pub struct AMresultStack { - /// A result to be deallocated. - pub result: *mut AMresult, - /// The next node in the singly-linked list or `NULL`. - pub next: *mut AMresultStack, -} - -impl AMresultStack { - pub fn new(result: *mut AMresult, next: *mut AMresultStack) -> Self { - Self { result, next } - } -} - -/// \memberof AMresultStack -/// \brief Deallocates the storage for a stack of results. -/// -/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct. -/// \return The number of `AMresult` structs freed. -/// \pre \p stack `!= NULL`. -/// \post `*stack == NULL`. -/// \note Calling this function is purely optional because its only purpose is -/// to make memory management tolerable for direct usage of this API in -/// C, C++ and Objective-C. -/// \internal -/// -/// # Safety -/// stack must be a valid AMresultStack pointer pointer -#[no_mangle] -pub unsafe extern "C" fn AMfreeStack(stack: *mut *mut AMresultStack) -> usize { - if stack.is_null() { - return 0; - } - let mut count: usize = 0; - while !(*stack).is_null() { - AMfree(AMpop(stack)); - count += 1; - } - count -} - -/// \memberof AMresultStack -/// \brief Gets the topmost result from the stack after removing it. -/// -/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct. -/// \return A pointer to an `AMresult` struct or `NULL`. -/// \pre \p stack `!= NULL`. -/// \post `*stack == NULL`. -/// \note Calling this function is purely optional because its only purpose is -/// to make memory management tolerable for direct usage of this API in -/// C, C++ and Objective-C. -/// \internal -/// -/// # Safety -/// stack must be a valid AMresultStack pointer pointer -#[no_mangle] -pub unsafe extern "C" fn AMpop(stack: *mut *mut AMresultStack) -> *mut AMresult { - if stack.is_null() || (*stack).is_null() { - return std::ptr::null_mut(); - } - let top = Box::from_raw(*stack); - *stack = top.next; - let result = top.result; - drop(top); - result -} - -/// \memberof AMresultStack -/// \brief The prototype of a function to be called when a value matching the -/// given discriminant cannot be extracted from the result at the top of -/// the given stack. -/// -/// \note Implementing this function is purely optional because its only purpose -/// is to make memory management tolerable for direct usage of this API -/// in C, C++ and Objective-C. -pub type AMpushCallback = - Option ()>; - -/// \memberof AMresultStack -/// \brief Pushes the given result onto the given stack and then either extracts -/// a value matching the given discriminant from that result or, -/// failing that, calls the given function and gets a void value instead. -/// -/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct. -/// \param[in] result A pointer to an `AMresult` struct. -/// \param[in] discriminant An `AMvalue` variant's corresponding enum tag. -/// \param[in] callback A pointer to a function with the same signature as -/// `AMpushCallback()` or `NULL`. -/// \return An `AMvalue` struct. -/// \pre \p stack `!= NULL`. -/// \pre \p result `!= NULL`. -/// \warning If \p stack `== NULL` then \p result is deallocated in order to -/// prevent a memory leak. -/// \note Calling this function is purely optional because its only purpose is -/// to make memory management tolerable for direct usage of this API in -/// C, C++ and Objective-C. -/// \internal -/// -/// # Safety -/// stack must be a valid AMresultStack pointer pointer -/// result must be a valid AMresult pointer -#[no_mangle] -pub unsafe extern "C" fn AMpush<'a>( - stack: *mut *mut AMresultStack, - result: *mut AMresult, - discriminant: u8, - callback: AMpushCallback, -) -> AMvalue<'a> { - if stack.is_null() { - // There's no stack to push the result onto so it has to be freed in - // order to prevent a memory leak. - AMfree(result); - if let Some(callback) = callback { - callback(stack, discriminant); - } - return AMvalue::Void; - } else if result.is_null() { - if let Some(callback) = callback { - callback(stack, discriminant); - } - return AMvalue::Void; - } - // Always push the result onto the stack, even if it's wrong, so that the - // given callback can retrieve it. - let node = Box::new(AMresultStack::new(result, *stack)); - let top = Box::into_raw(node); - *stack = top; - // Test that the result contains a value. - match AMresultStatus(result) { - AMstatus::Ok => {} - _ => { - if let Some(callback) = callback { - callback(stack, discriminant); - } - return AMvalue::Void; - } - } - // Test that the result's value matches the given discriminant. - let value = AMresultValue(result); - if discriminant != u8::from(&value) { - if let Some(callback) = callback { - callback(stack, discriminant); - } - return AMvalue::Void; - } - value -} diff --git a/rust/automerge-c/src/strs.rs b/rust/automerge-c/src/strs.rs deleted file mode 100644 index a36861b7..00000000 --- a/rust/automerge-c/src/strs.rs +++ /dev/null @@ -1,359 +0,0 @@ -use std::cmp::Ordering; -use std::ffi::c_void; -use std::mem::size_of; -use std::os::raw::c_char; - -use crate::byte_span::AMbyteSpan; - -/// \brief Creates a string view from a C string. -/// -/// \param[in] c_str A UTF-8 C string. -/// \return A UTF-8 string view as an `AMbyteSpan` struct. -/// \internal -/// -/// #Safety -/// c_str must be a null-terminated array of `c_char` -#[no_mangle] -pub unsafe extern "C" fn AMstr(c_str: *const c_char) -> AMbyteSpan { - c_str.into() -} - -#[repr(C)] -struct Detail { - len: usize, - offset: isize, - ptr: *const c_void, -} - -/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call -/// (https://github.com/eqrion/cbindgen/issues/252) but it will -/// propagate the name of a constant initialized from it so if the -/// constant's name is a symbolic representation of the value it can be -/// converted into a number by post-processing the header it generated. -pub const USIZE_USIZE_USIZE_: usize = size_of::(); - -impl Detail { - fn new(strings: &[String], offset: isize) -> Self { - Self { - len: strings.len(), - offset, - ptr: strings.as_ptr() as *const c_void, - } - } - - pub fn advance(&mut self, n: isize) { - if n == 0 { - return; - } - let len = self.len as isize; - self.offset = if self.offset < 0 { - // It's reversed. - let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); - if unclipped >= 0 { - // Clip it to the forward stop. - len - } else { - std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) - } - } else { - let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); - if unclipped < 0 { - // Clip it to the reverse stop. - -(len + 1) - } else { - std::cmp::max(0, std::cmp::min(unclipped, len)) - } - } - } - - pub fn get_index(&self) -> usize { - (self.offset - + if self.offset < 0 { - self.len as isize - } else { - 0 - }) as usize - } - - pub fn next(&mut self, n: isize) -> Option { - if self.is_stopped() { - return None; - } - let slice: &[String] = - unsafe { std::slice::from_raw_parts(self.ptr as *const String, self.len) }; - let value = slice[self.get_index()].as_bytes().into(); - self.advance(n); - Some(value) - } - - pub fn is_stopped(&self) -> bool { - let len = self.len as isize; - self.offset < -len || self.offset == len - } - - pub fn prev(&mut self, n: isize) -> Option { - self.advance(-n); - if self.is_stopped() { - return None; - } - let slice: &[String] = - unsafe { std::slice::from_raw_parts(self.ptr as *const String, self.len) }; - Some(slice[self.get_index()].as_bytes().into()) - } - - pub fn reversed(&self) -> Self { - Self { - len: self.len, - offset: -(self.offset + 1), - ptr: self.ptr, - } - } - - pub fn rewound(&self) -> Self { - Self { - len: self.len, - offset: if self.offset < 0 { -1 } else { 0 }, - ptr: self.ptr, - } - } -} - -impl From for [u8; USIZE_USIZE_USIZE_] { - fn from(detail: Detail) -> Self { - unsafe { - std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) - .try_into() - .unwrap() - } - } -} - -/// \struct AMstrs -/// \installed_headerfile -/// \brief A random-access iterator over a sequence of UTF-8 strings. -#[repr(C)] -#[derive(Eq, PartialEq)] -pub struct AMstrs { - /// An implementation detail that is intentionally opaque. - /// \warning Modifying \p detail will cause undefined behavior. - /// \note The actual size of \p detail will vary by platform, this is just - /// the one for the platform this documentation was built on. - detail: [u8; USIZE_USIZE_USIZE_], -} - -impl AMstrs { - pub fn new(strings: &[String]) -> Self { - Self { - detail: Detail::new(strings, 0).into(), - } - } - - pub fn advance(&mut self, n: isize) { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.advance(n); - } - - pub fn len(&self) -> usize { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - detail.len - } - - pub fn next(&mut self, n: isize) -> Option { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.next(n) - } - - pub fn prev(&mut self, n: isize) -> Option { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.prev(n) - } - - pub fn reversed(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.reversed().into(), - } - } - - pub fn rewound(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.rewound().into(), - } - } -} - -impl AsRef<[String]> for AMstrs { - fn as_ref(&self) -> &[String] { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - unsafe { std::slice::from_raw_parts(detail.ptr as *const String, detail.len) } - } -} - -impl Default for AMstrs { - fn default() -> Self { - Self { - detail: [0; USIZE_USIZE_USIZE_], - } - } -} - -/// \memberof AMstrs -/// \brief Advances an iterator over a sequence of UTF-8 strings by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] strs A pointer to an `AMstrs` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \pre \p strs `!= NULL`. -/// \internal -/// -/// #Safety -/// strs must be a valid pointer to an AMstrs -#[no_mangle] -pub unsafe extern "C" fn AMstrsAdvance(strs: *mut AMstrs, n: isize) { - if let Some(strs) = strs.as_mut() { - strs.advance(n); - }; -} - -/// \memberof AMstrs -/// \brief Compares the sequences of UTF-8 strings underlying a pair of -/// iterators. -/// -/// \param[in] strs1 A pointer to an `AMstrs` struct. -/// \param[in] strs2 A pointer to an `AMstrs` struct. -/// \return `-1` if \p strs1 `<` \p strs2, `0` if -/// \p strs1 `==` \p strs2 and `1` if -/// \p strs1 `>` \p strs2. -/// \pre \p strs1 `!= NULL`. -/// \pre \p strs2 `!= NULL`. -/// \internal -/// -/// #Safety -/// strs1 must be a valid pointer to an AMstrs -/// strs2 must be a valid pointer to an AMstrs -#[no_mangle] -pub unsafe extern "C" fn AMstrsCmp(strs1: *const AMstrs, strs2: *const AMstrs) -> isize { - match (strs1.as_ref(), strs2.as_ref()) { - (Some(strs1), Some(strs2)) => match strs1.as_ref().cmp(strs2.as_ref()) { - Ordering::Less => -1, - Ordering::Equal => 0, - Ordering::Greater => 1, - }, - (None, Some(_)) => -1, - (Some(_), None) => 1, - (None, None) => 0, - } -} - -/// \memberof AMstrs -/// \brief Gets the key at the current position of an iterator over a sequence -/// of UTF-8 strings and then advances it by at most \p |n| positions -/// where the sign of \p n is relative to the iterator's direction. -/// -/// \param[in,out] strs A pointer to an `AMstrs` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A UTF-8 string view as an `AMbyteSpan` struct that's `AMstr(NULL)` -/// when \p strs was previously advanced past its forward/reverse limit. -/// \pre \p strs `!= NULL`. -/// \internal -/// -/// #Safety -/// strs must be a valid pointer to an AMstrs -#[no_mangle] -pub unsafe extern "C" fn AMstrsNext(strs: *mut AMstrs, n: isize) -> AMbyteSpan { - if let Some(strs) = strs.as_mut() { - if let Some(key) = strs.next(n) { - return key; - } - } - Default::default() -} - -/// \memberof AMstrs -/// \brief Advances an iterator over a sequence of UTF-8 strings by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction and then gets the key at its new position. -/// -/// \param[in,out] strs A pointer to an `AMstrs` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A UTF-8 string view as an `AMbyteSpan` struct that's `AMstr(NULL)` -/// when \p strs is presently advanced past its forward/reverse limit. -/// \pre \p strs `!= NULL`. -/// \internal -/// -/// #Safety -/// strs must be a valid pointer to an AMstrs -#[no_mangle] -pub unsafe extern "C" fn AMstrsPrev(strs: *mut AMstrs, n: isize) -> AMbyteSpan { - if let Some(strs) = strs.as_mut() { - if let Some(key) = strs.prev(n) { - return key; - } - } - Default::default() -} - -/// \memberof AMstrs -/// \brief Gets the size of the sequence of UTF-8 strings underlying an -/// iterator. -/// -/// \param[in] strs A pointer to an `AMstrs` struct. -/// \return The count of values in \p strs. -/// \pre \p strs `!= NULL`. -/// \internal -/// -/// #Safety -/// strs must be a valid pointer to an AMstrs -#[no_mangle] -pub unsafe extern "C" fn AMstrsSize(strs: *const AMstrs) -> usize { - if let Some(strs) = strs.as_ref() { - strs.len() - } else { - 0 - } -} - -/// \memberof AMstrs -/// \brief Creates an iterator over the same sequence of UTF-8 strings as the -/// given one but with the opposite position and direction. -/// -/// \param[in] strs A pointer to an `AMstrs` struct. -/// \return An `AMstrs` struct. -/// \pre \p strs `!= NULL`. -/// \internal -/// -/// #Safety -/// strs must be a valid pointer to an AMstrs -#[no_mangle] -pub unsafe extern "C" fn AMstrsReversed(strs: *const AMstrs) -> AMstrs { - if let Some(strs) = strs.as_ref() { - strs.reversed() - } else { - AMstrs::default() - } -} - -/// \memberof AMstrs -/// \brief Creates an iterator at the starting position over the same sequence -/// of UTF-8 strings as the given one. -/// -/// \param[in] strs A pointer to an `AMstrs` struct. -/// \return An `AMstrs` struct -/// \pre \p strs `!= NULL`. -/// \internal -/// -/// #Safety -/// strs must be a valid pointer to an AMstrs -#[no_mangle] -pub unsafe extern "C" fn AMstrsRewound(strs: *const AMstrs) -> AMstrs { - if let Some(strs) = strs.as_ref() { - strs.rewound() - } else { - Default::default() - } -} diff --git a/rust/automerge-c/src/sync.rs b/rust/automerge-c/src/sync.rs index cfed1af5..fe0332a1 100644 --- a/rust/automerge-c/src/sync.rs +++ b/rust/automerge-c/src/sync.rs @@ -1,7 +1,7 @@ mod have; -mod haves; mod message; mod state; +pub(crate) use have::AMsyncHave; pub(crate) use message::{to_sync_message, AMsyncMessage}; pub(crate) use state::AMsyncState; diff --git a/rust/automerge-c/src/sync/have.rs b/rust/automerge-c/src/sync/have.rs index 312151e7..37d2031f 100644 --- a/rust/automerge-c/src/sync/have.rs +++ b/rust/automerge-c/src/sync/have.rs @@ -1,23 +1,23 @@ use automerge as am; -use crate::change_hashes::AMchangeHashes; +use crate::result::{to_result, AMresult}; /// \struct AMsyncHave /// \installed_headerfile /// \brief A summary of the changes that the sender of a synchronization /// message already has. #[derive(Clone, Eq, PartialEq)] -pub struct AMsyncHave(*const am::sync::Have); +pub struct AMsyncHave(am::sync::Have); impl AMsyncHave { - pub fn new(have: &am::sync::Have) -> Self { + pub fn new(have: am::sync::Have) -> Self { Self(have) } } impl AsRef for AMsyncHave { fn as_ref(&self) -> &am::sync::Have { - unsafe { &*self.0 } + &self.0 } } @@ -25,17 +25,18 @@ impl AsRef for AMsyncHave { /// \brief Gets the heads of the sender. /// /// \param[in] sync_have A pointer to an `AMsyncHave` struct. -/// \return An `AMchangeHashes` struct. -/// \pre \p sync_have `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p sync_have `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_have must be a valid pointer to an AMsyncHave #[no_mangle] -pub unsafe extern "C" fn AMsyncHaveLastSync(sync_have: *const AMsyncHave) -> AMchangeHashes { - if let Some(sync_have) = sync_have.as_ref() { - AMchangeHashes::new(&sync_have.as_ref().last_sync) - } else { - Default::default() - } +pub unsafe extern "C" fn AMsyncHaveLastSync(sync_have: *const AMsyncHave) -> *mut AMresult { + to_result(match sync_have.as_ref() { + Some(sync_have) => sync_have.as_ref().last_sync.as_slice(), + None => Default::default(), + }) } diff --git a/rust/automerge-c/src/sync/haves.rs b/rust/automerge-c/src/sync/haves.rs deleted file mode 100644 index c74b8e96..00000000 --- a/rust/automerge-c/src/sync/haves.rs +++ /dev/null @@ -1,378 +0,0 @@ -use automerge as am; -use std::collections::BTreeMap; -use std::ffi::c_void; -use std::mem::size_of; - -use crate::sync::have::AMsyncHave; - -#[repr(C)] -struct Detail { - len: usize, - offset: isize, - ptr: *const c_void, - storage: *mut c_void, -} - -/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call -/// (https://github.com/eqrion/cbindgen/issues/252) but it will -/// propagate the name of a constant initialized from it so if the -/// constant's name is a symbolic representation of the value it can be -/// converted into a number by post-processing the header it generated. -pub const USIZE_USIZE_USIZE_USIZE_: usize = size_of::(); - -impl Detail { - fn new( - haves: &[am::sync::Have], - offset: isize, - storage: &mut BTreeMap, - ) -> Self { - let storage: *mut BTreeMap = storage; - Self { - len: haves.len(), - offset, - ptr: haves.as_ptr() as *const c_void, - storage: storage as *mut c_void, - } - } - - pub fn advance(&mut self, n: isize) { - if n == 0 { - return; - } - let len = self.len as isize; - self.offset = if self.offset < 0 { - // It's reversed. - let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); - if unclipped >= 0 { - // Clip it to the forward stop. - len - } else { - std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) - } - } else { - let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); - if unclipped < 0 { - // Clip it to the reverse stop. - -(len + 1) - } else { - std::cmp::max(0, std::cmp::min(unclipped, len)) - } - } - } - - pub fn get_index(&self) -> usize { - (self.offset - + if self.offset < 0 { - self.len as isize - } else { - 0 - }) as usize - } - - pub fn next(&mut self, n: isize) -> Option<*const AMsyncHave> { - if self.is_stopped() { - return None; - } - let slice: &[am::sync::Have] = - unsafe { std::slice::from_raw_parts(self.ptr as *const am::sync::Have, self.len) }; - let storage = unsafe { &mut *(self.storage as *mut BTreeMap) }; - let index = self.get_index(); - let value = match storage.get_mut(&index) { - Some(value) => value, - None => { - storage.insert(index, AMsyncHave::new(&slice[index])); - storage.get_mut(&index).unwrap() - } - }; - self.advance(n); - Some(value) - } - - pub fn is_stopped(&self) -> bool { - let len = self.len as isize; - self.offset < -len || self.offset == len - } - - pub fn prev(&mut self, n: isize) -> Option<*const AMsyncHave> { - self.advance(-n); - if self.is_stopped() { - return None; - } - let slice: &[am::sync::Have] = - unsafe { std::slice::from_raw_parts(self.ptr as *const am::sync::Have, self.len) }; - let storage = unsafe { &mut *(self.storage as *mut BTreeMap) }; - let index = self.get_index(); - Some(match storage.get_mut(&index) { - Some(value) => value, - None => { - storage.insert(index, AMsyncHave::new(&slice[index])); - storage.get_mut(&index).unwrap() - } - }) - } - - pub fn reversed(&self) -> Self { - Self { - len: self.len, - offset: -(self.offset + 1), - ptr: self.ptr, - storage: self.storage, - } - } - - pub fn rewound(&self) -> Self { - Self { - len: self.len, - offset: if self.offset < 0 { -1 } else { 0 }, - ptr: self.ptr, - storage: self.storage, - } - } -} - -impl From for [u8; USIZE_USIZE_USIZE_USIZE_] { - fn from(detail: Detail) -> Self { - unsafe { - std::slice::from_raw_parts( - (&detail as *const Detail) as *const u8, - USIZE_USIZE_USIZE_USIZE_, - ) - .try_into() - .unwrap() - } - } -} - -/// \struct AMsyncHaves -/// \installed_headerfile -/// \brief A random-access iterator over a sequence of synchronization haves. -#[repr(C)] -#[derive(Eq, PartialEq)] -pub struct AMsyncHaves { - /// An implementation detail that is intentionally opaque. - /// \warning Modifying \p detail will cause undefined behavior. - /// \note The actual size of \p detail will vary by platform, this is just - /// the one for the platform this documentation was built on. - detail: [u8; USIZE_USIZE_USIZE_USIZE_], -} - -impl AMsyncHaves { - pub fn new(haves: &[am::sync::Have], storage: &mut BTreeMap) -> Self { - Self { - detail: Detail::new(haves, 0, storage).into(), - } - } - - pub fn advance(&mut self, n: isize) { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.advance(n); - } - - pub fn len(&self) -> usize { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - detail.len - } - - pub fn next(&mut self, n: isize) -> Option<*const AMsyncHave> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.next(n) - } - - pub fn prev(&mut self, n: isize) -> Option<*const AMsyncHave> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.prev(n) - } - - pub fn reversed(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.reversed().into(), - } - } - - pub fn rewound(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.rewound().into(), - } - } -} - -impl AsRef<[am::sync::Have]> for AMsyncHaves { - fn as_ref(&self) -> &[am::sync::Have] { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - unsafe { std::slice::from_raw_parts(detail.ptr as *const am::sync::Have, detail.len) } - } -} - -impl Default for AMsyncHaves { - fn default() -> Self { - Self { - detail: [0; USIZE_USIZE_USIZE_USIZE_], - } - } -} - -/// \memberof AMsyncHaves -/// \brief Advances an iterator over a sequence of synchronization haves by at -/// most \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] sync_haves A pointer to an `AMsyncHaves` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \pre \p sync_haves `!= NULL`. -/// \internal -/// -/// #Safety -/// sync_haves must be a valid pointer to an AMsyncHaves -#[no_mangle] -pub unsafe extern "C" fn AMsyncHavesAdvance(sync_haves: *mut AMsyncHaves, n: isize) { - if let Some(sync_haves) = sync_haves.as_mut() { - sync_haves.advance(n); - }; -} - -/// \memberof AMsyncHaves -/// \brief Tests the equality of two sequences of synchronization haves -/// underlying a pair of iterators. -/// -/// \param[in] sync_haves1 A pointer to an `AMsyncHaves` struct. -/// \param[in] sync_haves2 A pointer to an `AMsyncHaves` struct. -/// \return `true` if \p sync_haves1 `==` \p sync_haves2 and `false` otherwise. -/// \pre \p sync_haves1 `!= NULL`. -/// \pre \p sync_haves2 `!= NULL`. -/// \internal -/// -/// #Safety -/// sync_haves1 must be a valid pointer to an AMsyncHaves -/// sync_haves2 must be a valid pointer to an AMsyncHaves -#[no_mangle] -pub unsafe extern "C" fn AMsyncHavesEqual( - sync_haves1: *const AMsyncHaves, - sync_haves2: *const AMsyncHaves, -) -> bool { - match (sync_haves1.as_ref(), sync_haves2.as_ref()) { - (Some(sync_haves1), Some(sync_haves2)) => sync_haves1.as_ref() == sync_haves2.as_ref(), - (None, Some(_)) | (Some(_), None) | (None, None) => false, - } -} - -/// \memberof AMsyncHaves -/// \brief Gets the synchronization have at the current position of an iterator -/// over a sequence of synchronization haves and then advances it by at -/// most \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] sync_haves A pointer to an `AMsyncHaves` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMsyncHave` struct that's `NULL` when -/// \p sync_haves was previously advanced past its forward/reverse -/// limit. -/// \pre \p sync_haves `!= NULL`. -/// \internal -/// -/// #Safety -/// sync_haves must be a valid pointer to an AMsyncHaves -#[no_mangle] -pub unsafe extern "C" fn AMsyncHavesNext( - sync_haves: *mut AMsyncHaves, - n: isize, -) -> *const AMsyncHave { - if let Some(sync_haves) = sync_haves.as_mut() { - if let Some(sync_have) = sync_haves.next(n) { - return sync_have; - } - } - std::ptr::null() -} - -/// \memberof AMsyncHaves -/// \brief Advances an iterator over a sequence of synchronization haves by at -/// most \p |n| positions where the sign of \p n is relative to the -/// iterator's direction and then gets the synchronization have at its -/// new position. -/// -/// \param[in,out] sync_haves A pointer to an `AMsyncHaves` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMsyncHave` struct that's `NULL` when -/// \p sync_haves is presently advanced past its forward/reverse limit. -/// \pre \p sync_haves `!= NULL`. -/// \internal -/// -/// #Safety -/// sync_haves must be a valid pointer to an AMsyncHaves -#[no_mangle] -pub unsafe extern "C" fn AMsyncHavesPrev( - sync_haves: *mut AMsyncHaves, - n: isize, -) -> *const AMsyncHave { - if let Some(sync_haves) = sync_haves.as_mut() { - if let Some(sync_have) = sync_haves.prev(n) { - return sync_have; - } - } - std::ptr::null() -} - -/// \memberof AMsyncHaves -/// \brief Gets the size of the sequence of synchronization haves underlying an -/// iterator. -/// -/// \param[in] sync_haves A pointer to an `AMsyncHaves` struct. -/// \return The count of values in \p sync_haves. -/// \pre \p sync_haves `!= NULL`. -/// \internal -/// -/// #Safety -/// sync_haves must be a valid pointer to an AMsyncHaves -#[no_mangle] -pub unsafe extern "C" fn AMsyncHavesSize(sync_haves: *const AMsyncHaves) -> usize { - if let Some(sync_haves) = sync_haves.as_ref() { - sync_haves.len() - } else { - 0 - } -} - -/// \memberof AMsyncHaves -/// \brief Creates an iterator over the same sequence of synchronization haves -/// as the given one but with the opposite position and direction. -/// -/// \param[in] sync_haves A pointer to an `AMsyncHaves` struct. -/// \return An `AMsyncHaves` struct -/// \pre \p sync_haves `!= NULL`. -/// \internal -/// -/// #Safety -/// sync_haves must be a valid pointer to an AMsyncHaves -#[no_mangle] -pub unsafe extern "C" fn AMsyncHavesReversed(sync_haves: *const AMsyncHaves) -> AMsyncHaves { - if let Some(sync_haves) = sync_haves.as_ref() { - sync_haves.reversed() - } else { - Default::default() - } -} - -/// \memberof AMsyncHaves -/// \brief Creates an iterator at the starting position over the same sequence -/// of synchronization haves as the given one. -/// -/// \param[in] sync_haves A pointer to an `AMsyncHaves` struct. -/// \return An `AMsyncHaves` struct -/// \pre \p sync_haves `!= NULL`. -/// \internal -/// -/// #Safety -/// sync_haves must be a valid pointer to an AMsyncHaves -#[no_mangle] -pub unsafe extern "C" fn AMsyncHavesRewound(sync_haves: *const AMsyncHaves) -> AMsyncHaves { - if let Some(sync_haves) = sync_haves.as_ref() { - sync_haves.rewound() - } else { - Default::default() - } -} diff --git a/rust/automerge-c/src/sync/message.rs b/rust/automerge-c/src/sync/message.rs index 46a6d29a..bdb1db34 100644 --- a/rust/automerge-c/src/sync/message.rs +++ b/rust/automerge-c/src/sync/message.rs @@ -3,18 +3,15 @@ use std::cell::RefCell; use std::collections::BTreeMap; use crate::change::AMchange; -use crate::change_hashes::AMchangeHashes; -use crate::changes::AMchanges; use crate::result::{to_result, AMresult}; use crate::sync::have::AMsyncHave; -use crate::sync::haves::AMsyncHaves; macro_rules! to_sync_message { ($handle:expr) => {{ let handle = $handle.as_ref(); match handle { Some(b) => b, - None => return AMresult::err("Invalid AMsyncMessage pointer").into(), + None => return AMresult::error("Invalid `AMsyncMessage*`").into(), } }}; } @@ -51,55 +48,52 @@ impl AsRef for AMsyncMessage { /// \brief Gets the changes for the recipient to apply. /// /// \param[in] sync_message A pointer to an `AMsyncMessage` struct. -/// \return An `AMchanges` struct. -/// \pre \p sync_message `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE` items. +/// \pre \p sync_message `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_message must be a valid pointer to an AMsyncMessage #[no_mangle] -pub unsafe extern "C" fn AMsyncMessageChanges(sync_message: *const AMsyncMessage) -> AMchanges { - if let Some(sync_message) = sync_message.as_ref() { - AMchanges::new( - &sync_message.body.changes, - &mut sync_message.changes_storage.borrow_mut(), - ) - } else { - Default::default() - } +pub unsafe extern "C" fn AMsyncMessageChanges(sync_message: *const AMsyncMessage) -> *mut AMresult { + to_result(match sync_message.as_ref() { + Some(sync_message) => sync_message.body.changes.as_slice(), + None => Default::default(), + }) } /// \memberof AMsyncMessage -/// \brief Decodes a sequence of bytes into a synchronization message. +/// \brief Decodes an array of bytes into a synchronization message. /// /// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes in \p src to decode. -/// \return A pointer to an `AMresult` struct containing an `AMsyncMessage` -/// struct. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] count The count of bytes to decode from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_SYNC_MESSAGE` item. +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety -/// src must be a byte array of size `>= count` +/// src must be a byte array of length `>= count` #[no_mangle] pub unsafe extern "C" fn AMsyncMessageDecode(src: *const u8, count: usize) -> *mut AMresult { - let mut data = Vec::new(); - data.extend_from_slice(std::slice::from_raw_parts(src, count)); - to_result(am::sync::Message::decode(&data)) + let data = std::slice::from_raw_parts(src, count); + to_result(am::sync::Message::decode(data)) } /// \memberof AMsyncMessage -/// \brief Encodes a synchronization message as a sequence of bytes. +/// \brief Encodes a synchronization message as an array of bytes. /// /// \param[in] sync_message A pointer to an `AMsyncMessage` struct. -/// \return A pointer to an `AMresult` struct containing an array of bytes as -/// an `AMbyteSpan` struct. -/// \pre \p sync_message `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTES` item. +/// \pre \p sync_message `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -114,41 +108,40 @@ pub unsafe extern "C" fn AMsyncMessageEncode(sync_message: *const AMsyncMessage) /// \brief Gets a summary of the changes that the sender already has. /// /// \param[in] sync_message A pointer to an `AMsyncMessage` struct. -/// \return An `AMhaves` struct. -/// \pre \p sync_message `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_SYNC_HAVE` items. +/// \pre \p sync_message `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_message must be a valid pointer to an AMsyncMessage #[no_mangle] -pub unsafe extern "C" fn AMsyncMessageHaves(sync_message: *const AMsyncMessage) -> AMsyncHaves { - if let Some(sync_message) = sync_message.as_ref() { - AMsyncHaves::new( - &sync_message.as_ref().have, - &mut sync_message.haves_storage.borrow_mut(), - ) - } else { - Default::default() - } +pub unsafe extern "C" fn AMsyncMessageHaves(sync_message: *const AMsyncMessage) -> *mut AMresult { + to_result(match sync_message.as_ref() { + Some(sync_message) => sync_message.as_ref().have.as_slice(), + None => Default::default(), + }) } /// \memberof AMsyncMessage /// \brief Gets the heads of the sender. /// /// \param[in] sync_message A pointer to an `AMsyncMessage` struct. -/// \return An `AMchangeHashes` struct. -/// \pre \p sync_message `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p sync_message `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_message must be a valid pointer to an AMsyncMessage #[no_mangle] -pub unsafe extern "C" fn AMsyncMessageHeads(sync_message: *const AMsyncMessage) -> AMchangeHashes { - if let Some(sync_message) = sync_message.as_ref() { - AMchangeHashes::new(&sync_message.as_ref().heads) - } else { - Default::default() - } +pub unsafe extern "C" fn AMsyncMessageHeads(sync_message: *const AMsyncMessage) -> *mut AMresult { + to_result(match sync_message.as_ref() { + Some(sync_message) => sync_message.as_ref().heads.as_slice(), + None => Default::default(), + }) } /// \memberof AMsyncMessage @@ -156,17 +149,18 @@ pub unsafe extern "C" fn AMsyncMessageHeads(sync_message: *const AMsyncMessage) /// by the recipient. /// /// \param[in] sync_message A pointer to an `AMsyncMessage` struct. -/// \return An `AMchangeHashes` struct. -/// \pre \p sync_message `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p sync_message `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_message must be a valid pointer to an AMsyncMessage #[no_mangle] -pub unsafe extern "C" fn AMsyncMessageNeeds(sync_message: *const AMsyncMessage) -> AMchangeHashes { - if let Some(sync_message) = sync_message.as_ref() { - AMchangeHashes::new(&sync_message.as_ref().need) - } else { - Default::default() - } +pub unsafe extern "C" fn AMsyncMessageNeeds(sync_message: *const AMsyncMessage) -> *mut AMresult { + to_result(match sync_message.as_ref() { + Some(sync_message) => sync_message.as_ref().need.as_slice(), + None => Default::default(), + }) } diff --git a/rust/automerge-c/src/sync/state.rs b/rust/automerge-c/src/sync/state.rs index 1c1d316f..1d85ed98 100644 --- a/rust/automerge-c/src/sync/state.rs +++ b/rust/automerge-c/src/sync/state.rs @@ -2,17 +2,15 @@ use automerge as am; use std::cell::RefCell; use std::collections::BTreeMap; -use crate::change_hashes::AMchangeHashes; use crate::result::{to_result, AMresult}; use crate::sync::have::AMsyncHave; -use crate::sync::haves::AMsyncHaves; macro_rules! to_sync_state { ($handle:expr) => {{ let handle = $handle.as_ref(); match handle { Some(b) => b, - None => return AMresult::err("Invalid AMsyncState pointer").into(), + None => return AMresult::error("Invalid `AMsyncState*`").into(), } }}; } @@ -56,36 +54,35 @@ impl From for *mut AMsyncState { } /// \memberof AMsyncState -/// \brief Decodes a sequence of bytes into a synchronization state. +/// \brief Decodes an array of bytes into a synchronization state. /// /// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes in \p src to decode. -/// \return A pointer to an `AMresult` struct containing an `AMsyncState` -/// struct. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] count The count of bytes to decode from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_SYNC_STATE` item. +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety -/// src must be a byte array of size `>= count` +/// src must be a byte array of length `>= count` #[no_mangle] pub unsafe extern "C" fn AMsyncStateDecode(src: *const u8, count: usize) -> *mut AMresult { - let mut data = Vec::new(); - data.extend_from_slice(std::slice::from_raw_parts(src, count)); - to_result(am::sync::State::decode(&data)) + let data = std::slice::from_raw_parts(src, count); + to_result(am::sync::State::decode(data)) } /// \memberof AMsyncState -/// \brief Encodes a synchronizaton state as a sequence of bytes. +/// \brief Encodes a synchronization state as an array of bytes. /// /// \param[in] sync_state A pointer to an `AMsyncState` struct. -/// \return A pointer to an `AMresult` struct containing an array of bytes as -/// an `AMbyteSpan` struct. -/// \pre \p sync_state `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTE_SPAN` item. +/// \pre \p sync_state `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -102,8 +99,9 @@ pub unsafe extern "C" fn AMsyncStateEncode(sync_state: *const AMsyncState) -> *m /// \param[in] sync_state1 A pointer to an `AMsyncState` struct. /// \param[in] sync_state2 A pointer to an `AMsyncState` struct. /// \return `true` if \p sync_state1 `==` \p sync_state2 and `false` otherwise. -/// \pre \p sync_state1 `!= NULL`. -/// \pre \p sync_state2 `!= NULL`. +/// \pre \p sync_state1 `!= NULL` +/// \pre \p sync_state2 `!= NULL` +/// \post `!(`\p sync_state1 `&&` \p sync_state2 `) -> false` /// \internal /// /// #Safety @@ -116,18 +114,17 @@ pub unsafe extern "C" fn AMsyncStateEqual( ) -> bool { match (sync_state1.as_ref(), sync_state2.as_ref()) { (Some(sync_state1), Some(sync_state2)) => sync_state1.as_ref() == sync_state2.as_ref(), - (None, Some(_)) | (Some(_), None) | (None, None) => false, + (None, None) | (None, Some(_)) | (Some(_), None) => false, } } /// \memberof AMsyncState -/// \brief Allocates a new synchronization state and initializes it with -/// defaults. +/// \brief Allocates a new synchronization state and initializes it from +/// default values. /// -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMsyncState` struct. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_SYNC_STATE` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. #[no_mangle] pub extern "C" fn AMsyncStateInit() -> *mut AMresult { to_result(am::sync::State::new()) @@ -137,40 +134,36 @@ pub extern "C" fn AMsyncStateInit() -> *mut AMresult { /// \brief Gets the heads that are shared by both peers. /// /// \param[in] sync_state A pointer to an `AMsyncState` struct. -/// \return An `AMchangeHashes` struct. -/// \pre \p sync_state `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p sync_state `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_state must be a valid pointer to an AMsyncState #[no_mangle] -pub unsafe extern "C" fn AMsyncStateSharedHeads(sync_state: *const AMsyncState) -> AMchangeHashes { - if let Some(sync_state) = sync_state.as_ref() { - AMchangeHashes::new(&sync_state.as_ref().shared_heads) - } else { - Default::default() - } +pub unsafe extern "C" fn AMsyncStateSharedHeads(sync_state: *const AMsyncState) -> *mut AMresult { + let sync_state = to_sync_state!(sync_state); + to_result(sync_state.as_ref().shared_heads.as_slice()) } /// \memberof AMsyncState /// \brief Gets the heads that were last sent by this peer. /// /// \param[in] sync_state A pointer to an `AMsyncState` struct. -/// \return An `AMchangeHashes` struct. -/// \pre \p sync_state `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p sync_state `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_state must be a valid pointer to an AMsyncState #[no_mangle] -pub unsafe extern "C" fn AMsyncStateLastSentHeads( - sync_state: *const AMsyncState, -) -> AMchangeHashes { - if let Some(sync_state) = sync_state.as_ref() { - AMchangeHashes::new(&sync_state.as_ref().last_sent_heads) - } else { - Default::default() - } +pub unsafe extern "C" fn AMsyncStateLastSentHeads(sync_state: *const AMsyncState) -> *mut AMresult { + let sync_state = to_sync_state!(sync_state); + to_result(sync_state.as_ref().last_sent_heads.as_slice()) } /// \memberof AMsyncState @@ -178,11 +171,13 @@ pub unsafe extern "C" fn AMsyncStateLastSentHeads( /// /// \param[in] sync_state A pointer to an `AMsyncState` struct. /// \param[out] has_value A pointer to a boolean flag that is set to `true` if -/// the returned `AMhaves` struct is relevant, `false` otherwise. -/// \return An `AMhaves` struct. -/// \pre \p sync_state `!= NULL`. -/// \pre \p has_value `!= NULL`. -/// \internal +/// the returned `AMitems` struct is relevant, `false` otherwise. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_SYNC_HAVE` items. +/// \pre \p sync_state `!= NULL` +/// \pre \p has_value `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +//// \internal /// /// # Safety /// sync_state must be a valid pointer to an AMsyncState @@ -191,15 +186,15 @@ pub unsafe extern "C" fn AMsyncStateLastSentHeads( pub unsafe extern "C" fn AMsyncStateTheirHaves( sync_state: *const AMsyncState, has_value: *mut bool, -) -> AMsyncHaves { +) -> *mut AMresult { if let Some(sync_state) = sync_state.as_ref() { if let Some(haves) = &sync_state.as_ref().their_have { *has_value = true; - return AMsyncHaves::new(haves, &mut sync_state.their_haves_storage.borrow_mut()); - }; + return to_result(haves.as_slice()); + } }; *has_value = false; - Default::default() + to_result(Vec::::new()) } /// \memberof AMsyncState @@ -207,29 +202,31 @@ pub unsafe extern "C" fn AMsyncStateTheirHaves( /// /// \param[in] sync_state A pointer to an `AMsyncState` struct. /// \param[out] has_value A pointer to a boolean flag that is set to `true` if -/// the returned `AMchangeHashes` struct is relevant, `false` -/// otherwise. -/// \return An `AMchangeHashes` struct. -/// \pre \p sync_state `!= NULL`. -/// \pre \p has_value `!= NULL`. +/// the returned `AMitems` struct is relevant, `false` +/// otherwise. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p sync_state `!= NULL` +/// \pre \p has_value `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_state must be a valid pointer to an AMsyncState -/// has_value must be a valid pointer to a bool. +/// has_value must be a valid pointer to a bool #[no_mangle] pub unsafe extern "C" fn AMsyncStateTheirHeads( sync_state: *const AMsyncState, has_value: *mut bool, -) -> AMchangeHashes { +) -> *mut AMresult { if let Some(sync_state) = sync_state.as_ref() { if let Some(change_hashes) = &sync_state.as_ref().their_heads { *has_value = true; - return AMchangeHashes::new(change_hashes); + return to_result(change_hashes.as_slice()); } }; *has_value = false; - Default::default() + to_result(Vec::::new()) } /// \memberof AMsyncState @@ -237,27 +234,29 @@ pub unsafe extern "C" fn AMsyncStateTheirHeads( /// /// \param[in] sync_state A pointer to an `AMsyncState` struct. /// \param[out] has_value A pointer to a boolean flag that is set to `true` if -/// the returned `AMchangeHashes` struct is relevant, `false` -/// otherwise. -/// \return An `AMchangeHashes` struct. -/// \pre \p sync_state `!= NULL`. -/// \pre \p has_value `!= NULL`. +/// the returned `AMitems` struct is relevant, `false` +/// otherwise. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p sync_state `!= NULL` +/// \pre \p has_value `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_state must be a valid pointer to an AMsyncState -/// has_value must be a valid pointer to a bool. +/// has_value must be a valid pointer to a bool #[no_mangle] pub unsafe extern "C" fn AMsyncStateTheirNeeds( sync_state: *const AMsyncState, has_value: *mut bool, -) -> AMchangeHashes { +) -> *mut AMresult { if let Some(sync_state) = sync_state.as_ref() { if let Some(change_hashes) = &sync_state.as_ref().their_need { *has_value = true; - return AMchangeHashes::new(change_hashes); + return to_result(change_hashes.as_slice()); } }; *has_value = false; - Default::default() + to_result(Vec::::new()) } diff --git a/rust/automerge-c/src/utils/result.c b/rust/automerge-c/src/utils/result.c new file mode 100644 index 00000000..f922ca31 --- /dev/null +++ b/rust/automerge-c/src/utils/result.c @@ -0,0 +1,33 @@ +#include + +#include + +AMresult* AMresultFrom(int count, ...) { + AMresult* result = NULL; + bool is_ok = true; + va_list args; + va_start(args, count); + for (int i = 0; i != count; ++i) { + AMresult* src = va_arg(args, AMresult*); + AMresult* dest = result; + is_ok = (AMresultStatus(src) == AM_STATUS_OK); + if (is_ok) { + if (dest) { + result = AMresultCat(dest, src); + is_ok = (AMresultStatus(result) == AM_STATUS_OK); + AMresultFree(dest); + AMresultFree(src); + } else { + result = src; + } + } else { + AMresultFree(src); + } + } + va_end(args); + if (!is_ok) { + AMresultFree(result); + result = NULL; + } + return result; +} diff --git a/rust/automerge-c/src/utils/stack.c b/rust/automerge-c/src/utils/stack.c new file mode 100644 index 00000000..2cad7c5c --- /dev/null +++ b/rust/automerge-c/src/utils/stack.c @@ -0,0 +1,106 @@ +#include +#include + +#include +#include + +void AMstackFree(AMstack** stack) { + if (stack) { + while (*stack) { + AMresultFree(AMstackPop(stack, NULL)); + } + } +} + +AMresult* AMstackPop(AMstack** stack, const AMresult* result) { + if (!stack) { + return NULL; + } + AMstack** prev = stack; + if (result) { + while (*prev && ((*prev)->result != result)) { + *prev = (*prev)->prev; + } + } + if (!*prev) { + return NULL; + } + AMstack* target = *prev; + *prev = target->prev; + AMresult* popped = target->result; + free(target); + return popped; +} + +AMresult* AMstackResult(AMstack** stack, AMresult* result, AMstackCallback callback, void* data) { + if (!stack) { + if (callback) { + /* Create a local stack so that the callback can still examine the + * result. */ + AMstack node = {.result = result, .prev = NULL}; + AMstack* stack = &node; + callback(&stack, data); + } else { + /* \note There is no reason to call this function when both the + * stack and the callback are null. */ + fprintf(stderr, "ERROR: NULL AMstackCallback!\n"); + } + /* \note Nothing can be returned without a stack regardless of + * whether or not the callback validated the result. */ + AMresultFree(result); + return NULL; + } + /* Always push the result onto the stack, even if it's null, so that the + * callback can examine it. */ + AMstack* next = calloc(1, sizeof(AMstack)); + *next = (AMstack){.result = result, .prev = *stack}; + AMstack* top = next; + *stack = top; + if (callback) { + if (!callback(stack, data)) { + /* The result didn't pass the callback's examination. */ + return NULL; + } + } else { + /* Report an obvious error. */ + if (result) { + AMbyteSpan const err_msg = AMresultError(result); + if (err_msg.src && err_msg.count) { + /* \note The callback may be null because the result is supposed + * to be examined externally so return it despite an + * error. */ + char* const cstr = AMstrdup(err_msg, NULL); + fprintf(stderr, "WARNING: %s.\n", cstr); + free(cstr); + } + } else { + /* \note There's no reason to call this function when both the + * result and the callback are null. */ + fprintf(stderr, "ERROR: NULL AMresult*!\n"); + return NULL; + } + } + return result; +} + +AMitem* AMstackItem(AMstack** stack, AMresult* result, AMstackCallback callback, void* data) { + AMitems items = AMstackItems(stack, result, callback, data); + return AMitemsNext(&items, 1); +} + +AMitems AMstackItems(AMstack** stack, AMresult* result, AMstackCallback callback, void* data) { + return (AMstackResult(stack, result, callback, data)) ? AMresultItems(result) : (AMitems){0}; +} + +size_t AMstackSize(AMstack const* const stack) { + if (!stack) { + return 0; + } + size_t count = 0; + AMstack const* prev = stack; + while (prev) { + ++count; + prev = prev->prev; + } + return count; +} \ No newline at end of file diff --git a/rust/automerge-c/src/utils/stack_callback_data.c b/rust/automerge-c/src/utils/stack_callback_data.c new file mode 100644 index 00000000..f1e988d8 --- /dev/null +++ b/rust/automerge-c/src/utils/stack_callback_data.c @@ -0,0 +1,9 @@ +#include + +#include + +AMstackCallbackData* AMstackCallbackDataInit(AMvalType const bitmask, char const* const file, int const line) { + AMstackCallbackData* data = malloc(sizeof(AMstackCallbackData)); + *data = (AMstackCallbackData){.bitmask = bitmask, .file = file, .line = line}; + return data; +} diff --git a/rust/automerge-c/src/utils/string.c b/rust/automerge-c/src/utils/string.c new file mode 100644 index 00000000..a0d1ebe3 --- /dev/null +++ b/rust/automerge-c/src/utils/string.c @@ -0,0 +1,46 @@ +#include +#include + +#include + +char* AMstrdup(AMbyteSpan const str, char const* nul) { + if (!str.src) { + return NULL; + } else if (!str.count) { + return strdup(""); + } + nul = (nul) ? nul : "\\0"; + size_t const nul_len = strlen(nul); + char* dup = NULL; + size_t dup_len = 0; + char const* begin = str.src; + char const* end = begin; + for (size_t i = 0; i != str.count; ++i, ++end) { + if (!*end) { + size_t const len = end - begin; + size_t const alloc_len = dup_len + len + nul_len; + if (dup) { + dup = realloc(dup, alloc_len + 1); + } else { + dup = malloc(alloc_len + 1); + } + memcpy(dup + dup_len, begin, len); + memcpy(dup + dup_len + len, nul, nul_len); + dup[alloc_len] = '\0'; + begin = end + 1; + dup_len = alloc_len; + } + } + if (begin != end) { + size_t const len = end - begin; + size_t const alloc_len = dup_len + len; + if (dup) { + dup = realloc(dup, alloc_len + 1); + } else { + dup = malloc(alloc_len + 1); + } + memcpy(dup + dup_len, begin, len); + dup[alloc_len] = '\0'; + } + return dup; +} diff --git a/rust/automerge-c/test/CMakeLists.txt b/rust/automerge-c/test/CMakeLists.txt index 704a27da..1759f140 100644 --- a/rust/automerge-c/test/CMakeLists.txt +++ b/rust/automerge-c/test/CMakeLists.txt @@ -1,53 +1,51 @@ -cmake_minimum_required(VERSION 3.18 FATAL_ERROR) - -find_package(cmocka REQUIRED) +find_package(cmocka CONFIG REQUIRED) add_executable( - test_${LIBRARY_NAME} + ${LIBRARY_NAME}_test actor_id_tests.c + base_state.c + byte_span_tests.c + cmocka_utils.c + enum_string_tests.c + doc_state.c doc_tests.c - group_state.c + item_tests.c list_tests.c macro_utils.c main.c map_tests.c - stack_utils.c str_utils.c ported_wasm/basic_tests.c ported_wasm/suite.c ported_wasm/sync_tests.c ) -set_target_properties(test_${LIBRARY_NAME} PROPERTIES LINKER_LANGUAGE C) +set_target_properties(${LIBRARY_NAME}_test PROPERTIES LINKER_LANGUAGE C) -# \note An imported library's INTERFACE_INCLUDE_DIRECTORIES property can't -# contain a non-existent path so its build-time include directory -# must be specified for all of its dependent targets instead. -target_include_directories( - test_${LIBRARY_NAME} - PRIVATE "$" -) +if(WIN32) + set(CMOCKA "cmocka::cmocka") +else() + set(CMOCKA "cmocka") +endif() -target_link_libraries(test_${LIBRARY_NAME} PRIVATE cmocka ${LIBRARY_NAME}) +target_link_libraries(${LIBRARY_NAME}_test PRIVATE ${CMOCKA} ${LIBRARY_NAME}) -add_dependencies(test_${LIBRARY_NAME} ${LIBRARY_NAME}_artifacts) +add_dependencies(${LIBRARY_NAME}_test ${BINDINGS_NAME}_artifacts) if(BUILD_SHARED_LIBS AND WIN32) add_custom_command( - TARGET test_${LIBRARY_NAME} + TARGET ${LIBRARY_NAME}_test POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX} - ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Copying the DLL built by Cargo into the test directory..." + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMENT "Copying the DLL into the tests directory..." VERBATIM ) endif() -add_test(NAME test_${LIBRARY_NAME} COMMAND test_${LIBRARY_NAME}) +add_test(NAME ${LIBRARY_NAME}_test COMMAND ${LIBRARY_NAME}_test) add_custom_command( - TARGET test_${LIBRARY_NAME} + TARGET ${LIBRARY_NAME}_test POST_BUILD COMMAND ${CMAKE_CTEST_COMMAND} --config $ --output-on-failure diff --git a/rust/automerge-c/test/actor_id_tests.c b/rust/automerge-c/test/actor_id_tests.c index c98f2554..918d6213 100644 --- a/rust/automerge-c/test/actor_id_tests.c +++ b/rust/automerge-c/test/actor_id_tests.c @@ -14,99 +14,126 @@ #include "cmocka_utils.h" #include "str_utils.h" +/** + * \brief State for a group of cmocka test cases. + */ typedef struct { + /** An actor ID as an array of bytes. */ uint8_t* src; - AMbyteSpan str; + /** The count of bytes in \p src. */ size_t count; -} GroupState; + /** A stack of results. */ + AMstack* stack; + /** An actor ID as a hexadecimal string. */ + AMbyteSpan str; +} DocState; static int group_setup(void** state) { - GroupState* group_state = test_calloc(1, sizeof(GroupState)); - group_state->str.src = "000102030405060708090a0b0c0d0e0f"; - group_state->str.count = strlen(group_state->str.src); - group_state->count = group_state->str.count / 2; - group_state->src = test_malloc(group_state->count); - hex_to_bytes(group_state->str.src, group_state->src, group_state->count); - *state = group_state; + DocState* doc_state = test_calloc(1, sizeof(DocState)); + doc_state->str = AMstr("000102030405060708090a0b0c0d0e0f"); + doc_state->count = doc_state->str.count / 2; + doc_state->src = test_calloc(doc_state->count, sizeof(uint8_t)); + hex_to_bytes(doc_state->str.src, doc_state->src, doc_state->count); + *state = doc_state; return 0; } static int group_teardown(void** state) { - GroupState* group_state = *state; - test_free(group_state->src); - test_free(group_state); + DocState* doc_state = *state; + test_free(doc_state->src); + AMstackFree(&doc_state->stack); + test_free(doc_state); return 0; } -static void test_AMactorIdInit() { +static void test_AMactorIdFromBytes(void** state) { + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->stack; + /* Non-empty string. */ + AMresult* result = AMstackResult(stack_ptr, AMactorIdFromBytes(doc_state->src, doc_state->count), NULL, NULL); + if (AMresultStatus(result) != AM_STATUS_OK) { + fail_msg_view("%s", AMresultError(result)); + } + assert_int_equal(AMresultSize(result), 1); + AMitem* const item = AMresultItem(result); + assert_int_equal(AMitemValType(item), AM_VAL_TYPE_ACTOR_ID); + AMactorId const* actor_id; + assert_true(AMitemToActorId(item, &actor_id)); + AMbyteSpan const bytes = AMactorIdBytes(actor_id); + assert_int_equal(bytes.count, doc_state->count); + assert_memory_equal(bytes.src, doc_state->src, bytes.count); + /* Empty array. */ + /** \todo Find out if this is intentionally allowed. */ + result = AMstackResult(stack_ptr, AMactorIdFromBytes(doc_state->src, 0), NULL, NULL); + if (AMresultStatus(result) != AM_STATUS_OK) { + fail_msg_view("%s", AMresultError(result)); + } + /* NULL array. */ + result = AMstackResult(stack_ptr, AMactorIdFromBytes(NULL, doc_state->count), NULL, NULL); + if (AMresultStatus(result) == AM_STATUS_OK) { + fail_msg("AMactorId from NULL."); + } +} + +static void test_AMactorIdFromStr(void** state) { + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->stack; + AMresult* result = AMstackResult(stack_ptr, AMactorIdFromStr(doc_state->str), NULL, NULL); + if (AMresultStatus(result) != AM_STATUS_OK) { + fail_msg_view("%s", AMresultError(result)); + } + assert_int_equal(AMresultSize(result), 1); + AMitem* const item = AMresultItem(result); + assert_int_equal(AMitemValType(item), AM_VAL_TYPE_ACTOR_ID); + /* The hexadecimal string should've been decoded as identical bytes. */ + AMactorId const* actor_id; + assert_true(AMitemToActorId(item, &actor_id)); + AMbyteSpan const bytes = AMactorIdBytes(actor_id); + assert_int_equal(bytes.count, doc_state->count); + assert_memory_equal(bytes.src, doc_state->src, bytes.count); + /* The bytes should've been encoded as an identical hexadecimal string. */ + assert_true(AMitemToActorId(item, &actor_id)); + AMbyteSpan const str = AMactorIdStr(actor_id); + assert_int_equal(str.count, doc_state->str.count); + assert_memory_equal(str.src, doc_state->str.src, str.count); +} + +static void test_AMactorIdInit(void** state) { + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->stack; AMresult* prior_result = NULL; AMbyteSpan prior_bytes = {NULL, 0}; AMbyteSpan prior_str = {NULL, 0}; - AMresult* result = NULL; for (size_t i = 0; i != 11; ++i) { - result = AMactorIdInit(); + AMresult* result = AMstackResult(stack_ptr, AMactorIdInit(), NULL, NULL); if (AMresultStatus(result) != AM_STATUS_OK) { - fail_msg_view("%s", AMerrorMessage(result)); + fail_msg_view("%s", AMresultError(result)); } assert_int_equal(AMresultSize(result), 1); - AMvalue const value = AMresultValue(result); - assert_int_equal(value.tag, AM_VALUE_ACTOR_ID); - AMbyteSpan const bytes = AMactorIdBytes(value.actor_id); - AMbyteSpan const str = AMactorIdStr(value.actor_id); + AMitem* const item = AMresultItem(result); + assert_int_equal(AMitemValType(item), AM_VAL_TYPE_ACTOR_ID); + AMactorId const* actor_id; + assert_true(AMitemToActorId(item, &actor_id)); + AMbyteSpan const bytes = AMactorIdBytes(actor_id); + assert_true(AMitemToActorId(item, &actor_id)); + AMbyteSpan const str = AMactorIdStr(actor_id); if (prior_result) { size_t const max_byte_count = fmax(bytes.count, prior_bytes.count); assert_memory_not_equal(bytes.src, prior_bytes.src, max_byte_count); size_t const max_char_count = fmax(str.count, prior_str.count); assert_memory_not_equal(str.src, prior_str.src, max_char_count); - AMfree(prior_result); } prior_result = result; prior_bytes = bytes; prior_str = str; } - AMfree(result); -} - -static void test_AMactorIdInitBytes(void **state) { - GroupState* group_state = *state; - AMresult* const result = AMactorIdInitBytes(group_state->src, group_state->count); - if (AMresultStatus(result) != AM_STATUS_OK) { - fail_msg_view("%s", AMerrorMessage(result)); - } - assert_int_equal(AMresultSize(result), 1); - AMvalue const value = AMresultValue(result); - assert_int_equal(value.tag, AM_VALUE_ACTOR_ID); - AMbyteSpan const bytes = AMactorIdBytes(value.actor_id); - assert_int_equal(bytes.count, group_state->count); - assert_memory_equal(bytes.src, group_state->src, bytes.count); - AMfree(result); -} - -static void test_AMactorIdInitStr(void **state) { - GroupState* group_state = *state; - AMresult* const result = AMactorIdInitStr(group_state->str); - if (AMresultStatus(result) != AM_STATUS_OK) { - fail_msg_view("%s", AMerrorMessage(result)); - } - assert_int_equal(AMresultSize(result), 1); - AMvalue const value = AMresultValue(result); - assert_int_equal(value.tag, AM_VALUE_ACTOR_ID); - /* The hexadecimal string should've been decoded as identical bytes. */ - AMbyteSpan const bytes = AMactorIdBytes(value.actor_id); - assert_int_equal(bytes.count, group_state->count); - assert_memory_equal(bytes.src, group_state->src, bytes.count); - /* The bytes should've been encoded as an identical hexadecimal string. */ - AMbyteSpan const str = AMactorIdStr(value.actor_id); - assert_int_equal(str.count, group_state->str.count); - assert_memory_equal(str.src, group_state->str.src, str.count); - AMfree(result); } int run_actor_id_tests(void) { const struct CMUnitTest tests[] = { + cmocka_unit_test(test_AMactorIdFromBytes), + cmocka_unit_test(test_AMactorIdFromStr), cmocka_unit_test(test_AMactorIdInit), - cmocka_unit_test(test_AMactorIdInitBytes), - cmocka_unit_test(test_AMactorIdInitStr), }; return cmocka_run_group_tests(tests, group_setup, group_teardown); diff --git a/rust/automerge-c/test/base_state.c b/rust/automerge-c/test/base_state.c new file mode 100644 index 00000000..53325a99 --- /dev/null +++ b/rust/automerge-c/test/base_state.c @@ -0,0 +1,17 @@ +#include + +/* local */ +#include "base_state.h" + +int setup_base(void** state) { + BaseState* base_state = calloc(1, sizeof(BaseState)); + *state = base_state; + return 0; +} + +int teardown_base(void** state) { + BaseState* base_state = *state; + AMstackFree(&base_state->stack); + free(base_state); + return 0; +} diff --git a/rust/automerge-c/test/base_state.h b/rust/automerge-c/test/base_state.h new file mode 100644 index 00000000..3c4ff01b --- /dev/null +++ b/rust/automerge-c/test/base_state.h @@ -0,0 +1,39 @@ +#ifndef TESTS_BASE_STATE_H +#define TESTS_BASE_STATE_H + +#include + +/* local */ +#include +#include + +/** + * \struct BaseState + * \brief The shared state for one or more cmocka test cases. + */ +typedef struct { + /** A stack of results. */ + AMstack* stack; +} BaseState; + +/** + * \memberof BaseState + * \brief Sets up the shared state for one or more cmocka test cases. + * + * \param[in,out] state A pointer to a pointer to a `BaseState` struct. + * \pre \p state `!= NULL`. + * \warning The `BaseState` struct returned through \p state must be + * passed to `teardown_base()` in order to avoid a memory leak. + */ +int setup_base(void** state); + +/** + * \memberof BaseState + * \brief Tears down the shared state for one or more cmocka test cases. + * + * \param[in] state A pointer to a pointer to a `BaseState` struct. + * \pre \p state `!= NULL`. + */ +int teardown_base(void** state); + +#endif /* TESTS_BASE_STATE_H */ diff --git a/rust/automerge-c/test/byte_span_tests.c b/rust/automerge-c/test/byte_span_tests.c new file mode 100644 index 00000000..43856f3b --- /dev/null +++ b/rust/automerge-c/test/byte_span_tests.c @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include + +/* third-party */ +#include + +/* local */ +#include +#include + +static void test_AMbytes(void** state) { + static char const DATA[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}; + + AMbyteSpan bytes = AMbytes(DATA, sizeof(DATA)); + assert_int_equal(bytes.count, sizeof(DATA)); + assert_memory_equal(bytes.src, DATA, bytes.count); + assert_ptr_equal(bytes.src, DATA); + /* Empty view */ + bytes = AMbytes(DATA, 0); + assert_int_equal(bytes.count, 0); + assert_ptr_equal(bytes.src, DATA); + /* Invalid array */ + bytes = AMbytes(NULL, SIZE_MAX); + assert_int_not_equal(bytes.count, SIZE_MAX); + assert_int_equal(bytes.count, 0); + assert_ptr_equal(bytes.src, NULL); +} + +static void test_AMstr(void** state) { + AMbyteSpan str = AMstr("abcdefghijkl"); + assert_int_equal(str.count, strlen("abcdefghijkl")); + assert_memory_equal(str.src, "abcdefghijkl", str.count); + /* Empty string */ + static char const* const EMPTY = ""; + + str = AMstr(EMPTY); + assert_int_equal(str.count, 0); + assert_ptr_equal(str.src, EMPTY); + /* Invalid string */ + str = AMstr(NULL); + assert_int_equal(str.count, 0); + assert_ptr_equal(str.src, NULL); +} + +static void test_AMstrCmp(void** state) { + /* Length ordering */ + assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("abcdefghijkl")), -1); + assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("abcdefghijkl")), 0); + assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("abcdef")), 1); + /* Lexicographical ordering */ + assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("ghijkl")), -1); + assert_int_equal(AMstrCmp(AMstr("ghijkl"), AMstr("abcdef")), 1); + /* Case ordering */ + assert_int_equal(AMstrCmp(AMstr("ABCDEFGHIJKL"), AMstr("abcdefghijkl")), -1); + assert_int_equal(AMstrCmp(AMstr("ABCDEFGHIJKL"), AMstr("ABCDEFGHIJKL")), 0); + assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("ABCDEFGHIJKL")), 1); + assert_int_equal(AMstrCmp(AMstr("ABCDEFGHIJKL"), AMstr("abcdef")), -1); + assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("ABCDEFGHIJKL")), 1); + assert_int_equal(AMstrCmp(AMstr("GHIJKL"), AMstr("abcdef")), -1); + assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("GHIJKL")), 1); + /* NUL character inclusion */ + static char const SRC[] = {'a', 'b', 'c', 'd', 'e', 'f', '\0', 'g', 'h', 'i', 'j', 'k', 'l'}; + static AMbyteSpan const NUL_STR = {.src = SRC, .count = 13}; + + assert_int_equal(AMstrCmp(AMstr("abcdef"), NUL_STR), -1); + assert_int_equal(AMstrCmp(NUL_STR, NUL_STR), 0); + assert_int_equal(AMstrCmp(NUL_STR, AMstr("abcdef")), 1); + /* Empty string */ + assert_int_equal(AMstrCmp(AMstr(""), AMstr("abcdefghijkl")), -1); + assert_int_equal(AMstrCmp(AMstr(""), AMstr("")), 0); + assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("")), 1); + /* Invalid string */ + assert_int_equal(AMstrCmp(AMstr(NULL), AMstr("abcdefghijkl")), -1); + assert_int_equal(AMstrCmp(AMstr(NULL), AMstr(NULL)), 0); + assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr(NULL)), 1); +} + +static void test_AMstrdup(void** state) { + static char const SRC[] = {'a', 'b', 'c', '\0', 'd', 'e', 'f', '\0', 'g', 'h', 'i', '\0', 'j', 'k', 'l'}; + static AMbyteSpan const NUL_STR = {.src = SRC, .count = 15}; + + /* Default substitution ("\\0") for NUL */ + char* dup = AMstrdup(NUL_STR, NULL); + assert_int_equal(strlen(dup), 18); + assert_string_equal(dup, "abc\\0def\\0ghi\\0jkl"); + free(dup); + /* Arbitrary substitution for NUL */ + dup = AMstrdup(NUL_STR, ":-O"); + assert_int_equal(strlen(dup), 21); + assert_string_equal(dup, "abc:-Odef:-Oghi:-Ojkl"); + free(dup); + /* Empty substitution for NUL */ + dup = AMstrdup(NUL_STR, ""); + assert_int_equal(strlen(dup), 12); + assert_string_equal(dup, "abcdefghijkl"); + free(dup); + /* Empty string */ + dup = AMstrdup(AMstr(""), NULL); + assert_int_equal(strlen(dup), 0); + assert_string_equal(dup, ""); + free(dup); + /* Invalid string */ + assert_null(AMstrdup(AMstr(NULL), NULL)); +} + +int run_byte_span_tests(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_AMbytes), + cmocka_unit_test(test_AMstr), + cmocka_unit_test(test_AMstrCmp), + cmocka_unit_test(test_AMstrdup), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/rust/automerge-c/test/cmocka_utils.c b/rust/automerge-c/test/cmocka_utils.c new file mode 100644 index 00000000..37c57fb1 --- /dev/null +++ b/rust/automerge-c/test/cmocka_utils.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include + +/* third-party */ +#include +#include +#include +#include + +/* local */ +#include "cmocka_utils.h" + +/** + * \brief Assert that the given expression is true and report failure in terms + * of a line number within a file. + * + * \param[in] c An expression. + * \param[in] file A file's full path string. + * \param[in] line A line number. + */ +#define assert_true_where(c, file, line) _assert_true(cast_ptr_to_largest_integral_type(c), #c, file, line) + +/** + * \brief Assert that the given pointer is non-NULL and report failure in terms + * of a line number within a file. + * + * \param[in] c An expression. + * \param[in] file A file's full path string. + * \param[in] line A line number. + */ +#define assert_non_null_where(c, file, line) assert_true_where(c, file, line) + +/** + * \brief Forces the test to fail immediately and quit, printing the reason in + * terms of a line number within a file. + * + * \param[in] msg A message string into which \p str is interpolated. + * \param[in] str An owned string. + * \param[in] file A file's full path string. + * \param[in] line A line number. + */ +#define fail_msg_where(msg, str, file, line) \ + do { \ + print_error("ERROR: " msg "\n", str); \ + _fail(file, line); \ + } while (0) + +/** + * \brief Forces the test to fail immediately and quit, printing the reason in + * terms of a line number within a file. + * + * \param[in] msg A message string into which \p view.src is interpolated. + * \param[in] view A UTF-8 string view as an `AMbyteSpan` struct. + * \param[in] file A file's full path string. + * \param[in] line A line number. + */ +#define fail_msg_view_where(msg, view, file, line) \ + do { \ + char* const str = AMstrdup(view, NULL); \ + print_error("ERROR: " msg "\n", str); \ + free(str); \ + _fail(file, line); \ + } while (0) + +bool cmocka_cb(AMstack** stack, void* data) { + assert_non_null(data); + AMstackCallbackData* const sc_data = (AMstackCallbackData*)data; + assert_non_null_where(stack, sc_data->file, sc_data->line); + assert_non_null_where(*stack, sc_data->file, sc_data->line); + assert_non_null_where((*stack)->result, sc_data->file, sc_data->line); + if (AMresultStatus((*stack)->result) != AM_STATUS_OK) { + fail_msg_view_where("%s", AMresultError((*stack)->result), sc_data->file, sc_data->line); + return false; + } + /* Test that the types of all item values are members of the mask. */ + AMitems items = AMresultItems((*stack)->result); + AMitem* item = NULL; + while ((item = AMitemsNext(&items, 1)) != NULL) { + AMvalType const tag = AMitemValType(item); + if (!(tag & sc_data->bitmask)) { + fail_msg_where("Unexpected value type `%s`.", AMvalTypeToString(tag), sc_data->file, sc_data->line); + return false; + } + } + return true; +} diff --git a/rust/automerge-c/test/cmocka_utils.h b/rust/automerge-c/test/cmocka_utils.h index 1b488362..b6611bcc 100644 --- a/rust/automerge-c/test/cmocka_utils.h +++ b/rust/automerge-c/test/cmocka_utils.h @@ -1,22 +1,42 @@ -#ifndef CMOCKA_UTILS_H -#define CMOCKA_UTILS_H +#ifndef TESTS_CMOCKA_UTILS_H +#define TESTS_CMOCKA_UTILS_H +#include #include /* third-party */ +#include #include +/* local */ +#include "base_state.h" + /** * \brief Forces the test to fail immediately and quit, printing the reason. * - * \param[in] view A string view as an `AMbyteSpan` struct. + * \param[in] msg A message string into which \p view.src is interpolated. + * \param[in] view A UTF-8 string view as an `AMbyteSpan` struct. */ -#define fail_msg_view(msg, view) do { \ - char* const c_str = test_calloc(1, view.count + 1); \ - strncpy(c_str, view.src, view.count); \ - print_error(msg, c_str); \ - test_free(c_str); \ - fail(); \ -} while (0) +#define fail_msg_view(msg, view) \ + do { \ + char* const c_str = AMstrdup(view, NULL); \ + print_error("ERROR: " msg "\n", c_str); \ + free(c_str); \ + fail(); \ + } while (0) -#endif /* CMOCKA_UTILS_H */ +/** + * \brief Validates the top result in a stack based upon the parameters + * specified within the given data structure and reports violations + * using cmocka assertions. + * + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \param[in] data A pointer to an owned `AMpushData` struct. + * \return `true` if the top `AMresult` struct in \p stack is valid, `false` + * otherwise. + * \pre \p stack `!= NULL`. + * \pre \p data `!= NULL`. + */ +bool cmocka_cb(AMstack** stack, void* data); + +#endif /* TESTS_CMOCKA_UTILS_H */ diff --git a/rust/automerge-c/test/doc_state.c b/rust/automerge-c/test/doc_state.c new file mode 100644 index 00000000..3cbece50 --- /dev/null +++ b/rust/automerge-c/test/doc_state.c @@ -0,0 +1,27 @@ +#include +#include +#include + +/* third-party */ +#include + +/* local */ +#include +#include "cmocka_utils.h" +#include "doc_state.h" + +int setup_doc(void** state) { + DocState* doc_state = test_calloc(1, sizeof(DocState)); + setup_base((void**)&doc_state->base_state); + AMitemToDoc(AMstackItem(&doc_state->base_state->stack, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), + &doc_state->doc); + *state = doc_state; + return 0; +} + +int teardown_doc(void** state) { + DocState* doc_state = *state; + teardown_base((void**)&doc_state->base_state); + test_free(doc_state); + return 0; +} diff --git a/rust/automerge-c/test/doc_state.h b/rust/automerge-c/test/doc_state.h new file mode 100644 index 00000000..525a49fa --- /dev/null +++ b/rust/automerge-c/test/doc_state.h @@ -0,0 +1,17 @@ +#ifndef TESTS_DOC_STATE_H +#define TESTS_DOC_STATE_H + +/* local */ +#include +#include "base_state.h" + +typedef struct { + BaseState* base_state; + AMdoc* doc; +} DocState; + +int setup_doc(void** state); + +int teardown_doc(void** state); + +#endif /* TESTS_DOC_STATE_H */ diff --git a/rust/automerge-c/test/doc_tests.c b/rust/automerge-c/test/doc_tests.c index 217a4862..c1d21928 100644 --- a/rust/automerge-c/test/doc_tests.c +++ b/rust/automerge-c/test/doc_tests.c @@ -9,12 +9,14 @@ /* local */ #include -#include "group_state.h" -#include "stack_utils.h" +#include +#include "base_state.h" +#include "cmocka_utils.h" +#include "doc_state.h" #include "str_utils.h" typedef struct { - GroupState* group_state; + DocState* doc_state; AMbyteSpan actor_id_str; uint8_t* actor_id_bytes; size_t actor_id_size; @@ -22,7 +24,7 @@ typedef struct { static int setup(void** state) { TestState* test_state = test_calloc(1, sizeof(TestState)); - group_setup((void**)&test_state->group_state); + setup_doc((void**)&test_state->doc_state); test_state->actor_id_str.src = "000102030405060708090a0b0c0d0e0f"; test_state->actor_id_str.count = strlen(test_state->actor_id_str.src); test_state->actor_id_size = test_state->actor_id_str.count / 2; @@ -34,204 +36,195 @@ static int setup(void** state) { static int teardown(void** state) { TestState* test_state = *state; - group_teardown((void**)&test_state->group_state); + teardown_doc((void**)&test_state->doc_state); test_free(test_state->actor_id_bytes); test_free(test_state); return 0; } -static void test_AMkeys_empty() { - AMresultStack* stack = NULL; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMstrs forward = AMpush(&stack, - AMkeys(doc, AM_ROOT, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - assert_int_equal(AMstrsSize(&forward), 0); - AMstrs reverse = AMstrsReversed(&forward); - assert_int_equal(AMstrsSize(&reverse), 0); - assert_null(AMstrsNext(&forward, 1).src); - assert_null(AMstrsPrev(&forward, 1).src); - assert_null(AMstrsNext(&reverse, 1).src); - assert_null(AMstrsPrev(&reverse, 1).src); - AMfreeStack(&stack); -} - -static void test_AMkeys_list() { - AMresultStack* stack = NULL; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMobjId const* const list = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; - AMfree(AMlistPutInt(doc, list, 0, true, 0)); - AMfree(AMlistPutInt(doc, list, 1, true, 0)); - AMfree(AMlistPutInt(doc, list, 2, true, 0)); - AMstrs forward = AMpush(&stack, - AMkeys(doc, list, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - assert_int_equal(AMstrsSize(&forward), 3); - AMstrs reverse = AMstrsReversed(&forward); - assert_int_equal(AMstrsSize(&reverse), 3); - /* Forward iterator forward. */ - AMbyteSpan str = AMstrsNext(&forward, 1); - assert_ptr_equal(strstr(str.src, "2@"), str.src); - str = AMstrsNext(&forward, 1); - assert_ptr_equal(strstr(str.src, "3@"), str.src); - str = AMstrsNext(&forward, 1); - assert_ptr_equal(strstr(str.src, "4@"), str.src); - assert_null(AMstrsNext(&forward, 1).src); - // /* Forward iterator reverse. */ - str = AMstrsPrev(&forward, 1); - assert_ptr_equal(strstr(str.src, "4@"), str.src); - str = AMstrsPrev(&forward, 1); - assert_ptr_equal(strstr(str.src, "3@"), str.src); - str = AMstrsPrev(&forward, 1); - assert_ptr_equal(strstr(str.src, "2@"), str.src); - assert_null(AMstrsPrev(&forward, 1).src); - /* Reverse iterator forward. */ - str = AMstrsNext(&reverse, 1); - assert_ptr_equal(strstr(str.src, "4@"), str.src); - str = AMstrsNext(&reverse, 1); - assert_ptr_equal(strstr(str.src, "3@"), str.src); - str = AMstrsNext(&reverse, 1); - assert_ptr_equal(strstr(str.src, "2@"), str.src); - assert_null(AMstrsNext(&reverse, 1).src); - /* Reverse iterator reverse. */ - str = AMstrsPrev(&reverse, 1); - assert_ptr_equal(strstr(str.src, "2@"), str.src); - str = AMstrsPrev(&reverse, 1); - assert_ptr_equal(strstr(str.src, "3@"), str.src); - str = AMstrsPrev(&reverse, 1); - assert_ptr_equal(strstr(str.src, "4@"), str.src); - assert_null(AMstrsPrev(&reverse, 1).src); - AMfreeStack(&stack); -} - -static void test_AMkeys_map() { - AMresultStack* stack = NULL; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("one"), 1)); - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("two"), 2)); - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("three"), 3)); - AMstrs forward = AMpush(&stack, - AMkeys(doc, AM_ROOT, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - assert_int_equal(AMstrsSize(&forward), 3); - AMstrs reverse = AMstrsReversed(&forward); - assert_int_equal(AMstrsSize(&reverse), 3); - /* Forward iterator forward. */ - AMbyteSpan str = AMstrsNext(&forward, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "one", str.count); - str = AMstrsNext(&forward, 1); - assert_int_equal(str.count, 5); - assert_memory_equal(str.src, "three", str.count); - str = AMstrsNext(&forward, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "two", str.count); - assert_null(AMstrsNext(&forward, 1).src); - /* Forward iterator reverse. */ - str = AMstrsPrev(&forward, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "two", str.count); - str = AMstrsPrev(&forward, 1); - assert_int_equal(str.count, 5); - assert_memory_equal(str.src, "three", str.count); - str = AMstrsPrev(&forward, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "one", str.count); - assert_null(AMstrsPrev(&forward, 1).src); - /* Reverse iterator forward. */ - str = AMstrsNext(&reverse, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "two", str.count); - str = AMstrsNext(&reverse, 1); - assert_int_equal(str.count, 5); - assert_memory_equal(str.src, "three", str.count); - str = AMstrsNext(&reverse, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "one", str.count); - assert_null(AMstrsNext(&reverse, 1).src); - /* Reverse iterator reverse. */ - str = AMstrsPrev(&reverse, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "one", str.count); - str = AMstrsPrev(&reverse, 1); - assert_int_equal(str.count, 5); - assert_memory_equal(str.src, "three", str.count); - str = AMstrsPrev(&reverse, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "two", str.count); - assert_null(AMstrsPrev(&reverse, 1).src); - AMfreeStack(&stack); -} - -static void test_AMputActor_bytes(void **state) { +static void test_AMkeys_empty(void** state) { TestState* test_state = *state; - AMactorId const* actor_id = AMpush(&test_state->group_state->stack, - AMactorIdInitBytes( - test_state->actor_id_bytes, - test_state->actor_id_size), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; - AMfree(AMsetActorId(test_state->group_state->doc, actor_id)); - actor_id = AMpush(&test_state->group_state->stack, - AMgetActorId(test_state->group_state->doc), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; + AMstack** stack_ptr = &test_state->doc_state->base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMitems forward = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + assert_int_equal(AMitemsSize(&forward), 0); + AMitems reverse = AMitemsReversed(&forward); + assert_int_equal(AMitemsSize(&reverse), 0); + assert_null(AMitemsNext(&forward, 1)); + assert_null(AMitemsPrev(&forward, 1)); + assert_null(AMitemsNext(&reverse, 1)); + assert_null(AMitemsPrev(&reverse, 1)); +} + +static void test_AMkeys_list(void** state) { + TestState* test_state = *state; + AMstack** stack_ptr = &test_state->doc_state->base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMlistPutInt(doc, list, 0, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutInt(doc, list, 1, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutInt(doc, list, 2, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMitems forward = AMstackItems(stack_ptr, AMkeys(doc, list, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&forward), 3); + AMitems reverse = AMitemsReversed(&forward); + assert_int_equal(AMitemsSize(&reverse), 3); + /* Forward iterator forward. */ + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str)); + assert_ptr_equal(strstr(str.src, "2@"), str.src); + assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str)); + assert_ptr_equal(strstr(str.src, "3@"), str.src); + assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str)); + assert_ptr_equal(strstr(str.src, "4@"), str.src); + assert_null(AMitemsNext(&forward, 1)); + // /* Forward iterator reverse. */ + assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str)); + assert_ptr_equal(strstr(str.src, "4@"), str.src); + assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str)); + assert_ptr_equal(strstr(str.src, "3@"), str.src); + assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str)); + assert_ptr_equal(strstr(str.src, "2@"), str.src); + assert_null(AMitemsPrev(&forward, 1)); + /* Reverse iterator forward. */ + assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str)); + assert_ptr_equal(strstr(str.src, "4@"), str.src); + assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str)); + assert_ptr_equal(strstr(str.src, "3@"), str.src); + assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str)); + assert_ptr_equal(strstr(str.src, "2@"), str.src); + assert_null(AMitemsNext(&reverse, 1)); + /* Reverse iterator reverse. */ + assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str)); + assert_ptr_equal(strstr(str.src, "2@"), str.src); + assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str)); + assert_ptr_equal(strstr(str.src, "3@"), str.src); + assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str)); + assert_ptr_equal(strstr(str.src, "4@"), str.src); + assert_null(AMitemsPrev(&reverse, 1)); +} + +static void test_AMkeys_map(void** state) { + TestState* test_state = *state; + AMstack** stack_ptr = &test_state->doc_state->base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("one"), 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("two"), 2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("three"), 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMitems forward = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&forward), 3); + AMitems reverse = AMitemsReversed(&forward); + assert_int_equal(AMitemsSize(&reverse), 3); + /* Forward iterator forward. */ + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "one", str.count); + assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str)); + assert_int_equal(str.count, 5); + assert_memory_equal(str.src, "three", str.count); + assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "two", str.count); + assert_null(AMitemsNext(&forward, 1)); + /* Forward iterator reverse. */ + assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "two", str.count); + assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str)); + assert_int_equal(str.count, 5); + assert_memory_equal(str.src, "three", str.count); + assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "one", str.count); + assert_null(AMitemsPrev(&forward, 1)); + /* Reverse iterator forward. */ + assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "two", str.count); + assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str)); + assert_int_equal(str.count, 5); + assert_memory_equal(str.src, "three", str.count); + assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "one", str.count); + assert_null(AMitemsNext(&reverse, 1)); + /* Reverse iterator reverse. */ + assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "one", str.count); + assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str)); + assert_int_equal(str.count, 5); + assert_memory_equal(str.src, "three", str.count); + assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "two", str.count); + assert_null(AMitemsPrev(&reverse, 1)); +} + +static void test_AMputActor_bytes(void** state) { + TestState* test_state = *state; + AMstack** stack_ptr = &test_state->doc_state->base_state->stack; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromBytes(test_state->actor_id_bytes, test_state->actor_id_size), cmocka_cb, + AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMstackItem(NULL, AMsetActorId(test_state->doc_state->doc, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMgetActorId(test_state->doc_state->doc), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); AMbyteSpan const bytes = AMactorIdBytes(actor_id); assert_int_equal(bytes.count, test_state->actor_id_size); assert_memory_equal(bytes.src, test_state->actor_id_bytes, bytes.count); } -static void test_AMputActor_str(void **state) { +static void test_AMputActor_str(void** state) { TestState* test_state = *state; - AMactorId const* actor_id = AMpush(&test_state->group_state->stack, - AMactorIdInitStr(test_state->actor_id_str), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; - AMfree(AMsetActorId(test_state->group_state->doc, actor_id)); - actor_id = AMpush(&test_state->group_state->stack, - AMgetActorId(test_state->group_state->doc), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; + AMstack** stack_ptr = &test_state->doc_state->base_state->stack; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(test_state->actor_id_str), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMstackItem(NULL, AMsetActorId(test_state->doc_state->doc, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMgetActorId(test_state->doc_state->doc), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); AMbyteSpan const str = AMactorIdStr(actor_id); assert_int_equal(str.count, test_state->actor_id_str.count); assert_memory_equal(str.src, test_state->actor_id_str.src, str.count); } -static void test_AMspliceText() { - AMresultStack* stack = NULL; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMobjId const* const text = AMpush(&stack, - AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; - AMfree(AMspliceText(doc, text, 0, 0, AMstr("one + "))); - AMfree(AMspliceText(doc, text, 4, 2, AMstr("two = "))); - AMfree(AMspliceText(doc, text, 8, 2, AMstr("three"))); - AMbyteSpan const str = AMpush(&stack, - AMtext(doc, text, NULL), - AM_VALUE_STR, - cmocka_cb).str; - static char const* const STR_VALUE = "one two three"; - assert_int_equal(str.count, strlen(STR_VALUE)); - assert_memory_equal(str.src, STR_VALUE, str.count); - AMfreeStack(&stack); +static void test_AMspliceText(void** state) { + TestState* test_state = *state; + AMstack** stack_ptr = &test_state->doc_state->base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMobjId const* const text = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMspliceText(doc, text, 0, 0, AMstr("one + ")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMspliceText(doc, text, 4, 2, AMstr("two = ")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMspliceText(doc, text, 8, 2, AMstr("three")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMbyteSpan str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, AMtext(doc, text, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + assert_int_equal(str.count, strlen("one two three")); + assert_memory_equal(str.src, "one two three", str.count); } int run_doc_tests(void) { const struct CMUnitTest tests[] = { - cmocka_unit_test(test_AMkeys_empty), - cmocka_unit_test(test_AMkeys_list), - cmocka_unit_test(test_AMkeys_map), + cmocka_unit_test_setup_teardown(test_AMkeys_empty, setup, teardown), + cmocka_unit_test_setup_teardown(test_AMkeys_list, setup, teardown), + cmocka_unit_test_setup_teardown(test_AMkeys_map, setup, teardown), cmocka_unit_test_setup_teardown(test_AMputActor_bytes, setup, teardown), cmocka_unit_test_setup_teardown(test_AMputActor_str, setup, teardown), - cmocka_unit_test(test_AMspliceText), + cmocka_unit_test_setup_teardown(test_AMspliceText, setup, teardown), }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/rust/automerge-c/test/enum_string_tests.c b/rust/automerge-c/test/enum_string_tests.c new file mode 100644 index 00000000..11131e43 --- /dev/null +++ b/rust/automerge-c/test/enum_string_tests.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include + +/* third-party */ +#include + +/* local */ +#include +#include + +#define assert_to_string(function, tag) assert_string_equal(function(tag), #tag) + +#define assert_from_string(function, type, tag) \ + do { \ + type out; \ + assert_true(function(&out, #tag)); \ + assert_int_equal(out, tag); \ + } while (0) + +static void test_AMidxTypeToString(void** state) { + assert_to_string(AMidxTypeToString, AM_IDX_TYPE_DEFAULT); + assert_to_string(AMidxTypeToString, AM_IDX_TYPE_KEY); + assert_to_string(AMidxTypeToString, AM_IDX_TYPE_POS); + /* Zero tag */ + assert_string_equal(AMidxTypeToString(0), "AM_IDX_TYPE_DEFAULT"); + /* Invalid tag */ + assert_string_equal(AMidxTypeToString(-1), "???"); +} + +static void test_AMidxTypeFromString(void** state) { + assert_from_string(AMidxTypeFromString, AMidxType, AM_IDX_TYPE_DEFAULT); + assert_from_string(AMidxTypeFromString, AMidxType, AM_IDX_TYPE_KEY); + assert_from_string(AMidxTypeFromString, AMidxType, AM_IDX_TYPE_POS); + /* Invalid tag */ + AMidxType out = -1; + assert_false(AMidxTypeFromString(&out, "???")); + assert_int_equal(out, (AMidxType)-1); +} + +static void test_AMobjTypeToString(void** state) { + assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_DEFAULT); + assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_LIST); + assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_MAP); + assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_TEXT); + /* Zero tag */ + assert_string_equal(AMobjTypeToString(0), "AM_OBJ_TYPE_DEFAULT"); + /* Invalid tag */ + assert_string_equal(AMobjTypeToString(-1), "???"); +} + +static void test_AMobjTypeFromString(void** state) { + assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_DEFAULT); + assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_LIST); + assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_MAP); + assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_TEXT); + /* Invalid tag */ + AMobjType out = -1; + assert_false(AMobjTypeFromString(&out, "???")); + assert_int_equal(out, (AMobjType)-1); +} + +static void test_AMstatusToString(void** state) { + assert_to_string(AMstatusToString, AM_STATUS_ERROR); + assert_to_string(AMstatusToString, AM_STATUS_INVALID_RESULT); + assert_to_string(AMstatusToString, AM_STATUS_OK); + /* Zero tag */ + assert_string_equal(AMstatusToString(0), "AM_STATUS_OK"); + /* Invalid tag */ + assert_string_equal(AMstatusToString(-1), "???"); +} + +static void test_AMstatusFromString(void** state) { + assert_from_string(AMstatusFromString, AMstatus, AM_STATUS_ERROR); + assert_from_string(AMstatusFromString, AMstatus, AM_STATUS_INVALID_RESULT); + assert_from_string(AMstatusFromString, AMstatus, AM_STATUS_OK); + /* Invalid tag */ + AMstatus out = -1; + assert_false(AMstatusFromString(&out, "???")); + assert_int_equal(out, (AMstatus)-1); +} + +static void test_AMvalTypeToString(void** state) { + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_ACTOR_ID); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_BOOL); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_BYTES); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_CHANGE); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_CHANGE_HASH); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_COUNTER); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_DEFAULT); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_DOC); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_F64); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_INT); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_NULL); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_OBJ_TYPE); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_STR); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_SYNC_HAVE); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_SYNC_MESSAGE); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_SYNC_STATE); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_TIMESTAMP); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_UINT); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_UNKNOWN); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_VOID); + /* Zero tag */ + assert_string_equal(AMvalTypeToString(0), "AM_VAL_TYPE_DEFAULT"); + /* Invalid tag */ + assert_string_equal(AMvalTypeToString(-1), "???"); +} + +static void test_AMvalTypeFromString(void** state) { + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_ACTOR_ID); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_BOOL); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_BYTES); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_CHANGE); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_CHANGE_HASH); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_COUNTER); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_DEFAULT); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_DOC); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_F64); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_INT); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_NULL); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_OBJ_TYPE); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_STR); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_SYNC_HAVE); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_SYNC_MESSAGE); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_SYNC_STATE); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_TIMESTAMP); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_UINT); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_UNKNOWN); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_VOID); + /* Invalid tag */ + AMvalType out = -1; + assert_false(AMvalTypeFromString(&out, "???")); + assert_int_equal(out, (AMvalType)-1); +} + +int run_enum_string_tests(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_AMidxTypeToString), cmocka_unit_test(test_AMidxTypeFromString), + cmocka_unit_test(test_AMobjTypeToString), cmocka_unit_test(test_AMobjTypeFromString), + cmocka_unit_test(test_AMstatusToString), cmocka_unit_test(test_AMstatusFromString), + cmocka_unit_test(test_AMvalTypeToString), cmocka_unit_test(test_AMvalTypeFromString), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/rust/automerge-c/test/group_state.c b/rust/automerge-c/test/group_state.c deleted file mode 100644 index 0ee14317..00000000 --- a/rust/automerge-c/test/group_state.c +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include -#include - -/* third-party */ -#include - -/* local */ -#include "group_state.h" -#include "stack_utils.h" - -int group_setup(void** state) { - GroupState* group_state = test_calloc(1, sizeof(GroupState)); - group_state->doc = AMpush(&group_state->stack, - AMcreate(NULL), - AM_VALUE_DOC, - cmocka_cb).doc; - *state = group_state; - return 0; -} - -int group_teardown(void** state) { - GroupState* group_state = *state; - AMfreeStack(&group_state->stack); - test_free(group_state); - return 0; -} diff --git a/rust/automerge-c/test/group_state.h b/rust/automerge-c/test/group_state.h deleted file mode 100644 index a71d9dc9..00000000 --- a/rust/automerge-c/test/group_state.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef GROUP_STATE_H -#define GROUP_STATE_H - -/* local */ -#include - -typedef struct { - AMresultStack* stack; - AMdoc* doc; -} GroupState; - -int group_setup(void** state); - -int group_teardown(void** state); - -#endif /* GROUP_STATE_H */ diff --git a/rust/automerge-c/test/item_tests.c b/rust/automerge-c/test/item_tests.c new file mode 100644 index 00000000..a30b0556 --- /dev/null +++ b/rust/automerge-c/test/item_tests.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include + +/* third-party */ +#include + +/* local */ +#include +#include +#include "cmocka_utils.h" +#include "doc_state.h" + +static void test_AMitemResult(void** state) { + enum { ITEM_COUNT = 1000 }; + + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->base_state->stack; + /* Append the strings to a list so that they'll be in numerical order. */ + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + for (size_t pos = 0; pos != ITEM_COUNT; ++pos) { + size_t const count = snprintf(NULL, 0, "%zu", pos); + char* const src = test_calloc(count + 1, sizeof(char)); + assert_int_equal(sprintf(src, "%zu", pos), count); + AMstackItem(NULL, AMlistPutStr(doc_state->doc, list, pos, true, AMbytes(src, count)), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + test_free(src); + } + /* Get an item iterator. */ + AMitems items = AMstackItems(stack_ptr, AMlistRange(doc_state->doc, list, 0, SIZE_MAX, NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + /* Get the item iterator's result so that it can be freed later. */ + AMresult const* const items_result = (*stack_ptr)->result; + /* Iterate over all of the items and copy their pointers into an array. */ + AMitem* item_ptrs[ITEM_COUNT] = {NULL}; + AMitem* item = NULL; + for (size_t pos = 0; (item = AMitemsNext(&items, 1)) != NULL; ++pos) { + /* The item's reference count should be 1. */ + assert_int_equal(AMitemRefCount(item), 1); + if (pos & 1) { + /* Create a redundant result for an odd item. */ + AMitem* const new_item = AMstackItem(stack_ptr, AMitemResult(item), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + /* The item's old and new pointers will never match. */ + assert_ptr_not_equal(new_item, item); + /* The item's reference count will have been incremented. */ + assert_int_equal(AMitemRefCount(item), 2); + assert_int_equal(AMitemRefCount(new_item), 2); + /* The item's old and new indices should match. */ + assert_int_equal(AMitemIdxType(item), AMitemIdxType(new_item)); + assert_int_equal(AMitemIdxType(item), AM_IDX_TYPE_POS); + size_t pos, new_pos; + assert_true(AMitemPos(item, &pos)); + assert_true(AMitemPos(new_item, &new_pos)); + assert_int_equal(pos, new_pos); + /* The item's old and new object IDs should match. */ + AMobjId const* const obj_id = AMitemObjId(item); + AMobjId const* const new_obj_id = AMitemObjId(new_item); + assert_true(AMobjIdEqual(obj_id, new_obj_id)); + /* The item's old and new value types should match. */ + assert_int_equal(AMitemValType(item), AMitemValType(new_item)); + /* The item's old and new string values should match. */ + AMbyteSpan str; + assert_true(AMitemToStr(item, &str)); + AMbyteSpan new_str; + assert_true(AMitemToStr(new_item, &new_str)); + assert_int_equal(str.count, new_str.count); + assert_memory_equal(str.src, new_str.src, new_str.count); + /* The item's old and new object IDs are one and the same. */ + assert_ptr_equal(obj_id, new_obj_id); + /* The item's old and new string values are one and the same. */ + assert_ptr_equal(str.src, new_str.src); + /* Save the item's new pointer. */ + item_ptrs[pos] = new_item; + } + } + /* Free the item iterator's result. */ + AMresultFree(AMstackPop(stack_ptr, items_result)); + /* An odd item's reference count should be 1 again. */ + for (size_t pos = 1; pos < ITEM_COUNT; pos += 2) { + assert_int_equal(AMitemRefCount(item_ptrs[pos]), 1); + } +} + +int run_item_tests(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_AMitemResult), + }; + + return cmocka_run_group_tests(tests, setup_doc, teardown_doc); +} diff --git a/rust/automerge-c/test/list_tests.c b/rust/automerge-c/test/list_tests.c index f9bbb340..723dd038 100644 --- a/rust/automerge-c/test/list_tests.c +++ b/rust/automerge-c/test/list_tests.c @@ -11,367 +11,417 @@ /* local */ #include +#include +#include "base_state.h" #include "cmocka_utils.h" -#include "group_state.h" +#include "doc_state.h" #include "macro_utils.h" -#include "stack_utils.h" static void test_AMlistIncrement(void** state) { - GroupState* group_state = *state; - AMobjId const* const list = AMpush( - &group_state->stack, - AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; - AMfree(AMlistPutCounter(group_state->doc, list, 0, true, 0)); - assert_int_equal(AMpush(&group_state->stack, - AMlistGet(group_state->doc, list, 0, NULL), - AM_VALUE_COUNTER, - cmocka_cb).counter, 0); - AMfree(AMpop(&group_state->stack)); - AMfree(AMlistIncrement(group_state->doc, list, 0, 3)); - assert_int_equal(AMpush(&group_state->stack, - AMlistGet(group_state->doc, list, 0, NULL), - AM_VALUE_COUNTER, - cmocka_cb).counter, 3); - AMfree(AMpop(&group_state->stack)); + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->base_state->stack; + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMlistPutCounter(doc_state->doc, list, 0, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + int64_t counter; + assert_true(AMitemToCounter( + AMstackItem(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_COUNTER)), + &counter)); + assert_int_equal(counter, 0); + AMresultFree(AMstackPop(stack_ptr, NULL)); + AMstackItem(NULL, AMlistIncrement(doc_state->doc, list, 0, 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + assert_true(AMitemToCounter( + AMstackItem(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_COUNTER)), + &counter)); + assert_int_equal(counter, 3); + AMresultFree(AMstackPop(stack_ptr, NULL)); } -#define test_AMlistPut(suffix, mode) test_AMlistPut ## suffix ## _ ## mode +#define test_AMlistPut(suffix, mode) test_AMlistPut##suffix##_##mode -#define static_void_test_AMlistPut(suffix, mode, member, scalar_value) \ -static void test_AMlistPut ## suffix ## _ ## mode(void **state) { \ - GroupState* group_state = *state; \ - AMobjId const* const list = AMpush( \ - &group_state->stack, \ - AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),\ - AM_VALUE_OBJ_ID, \ - cmocka_cb).obj_id; \ - AMfree(AMlistPut ## suffix(group_state->doc, \ - list, \ - 0, \ - !strcmp(#mode, "insert"), \ - scalar_value)); \ - assert_true(AMpush( \ - &group_state->stack, \ - AMlistGet(group_state->doc, list, 0, NULL), \ - AMvalue_discriminant(#suffix), \ - cmocka_cb).member == scalar_value); \ - AMfree(AMpop(&group_state->stack)); \ -} +#define static_void_test_AMlistPut(suffix, mode, type, scalar_value) \ + static void test_AMlistPut##suffix##_##mode(void** state) { \ + DocState* doc_state = *state; \ + AMstack** stack_ptr = &doc_state->base_state->stack; \ + AMobjId const* const list = AMitemObjId( \ + AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ + AMstackItem(NULL, AMlistPut##suffix(doc_state->doc, list, 0, !strcmp(#mode, "insert"), scalar_value), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); \ + type value; \ + assert_true(AMitemTo##suffix(AMstackItem(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), cmocka_cb, \ + AMexpect(suffix_to_val_type(#suffix))), \ + &value)); \ + assert_true(value == scalar_value); \ + AMresultFree(AMstackPop(stack_ptr, NULL)); \ + } -#define test_AMlistPutBytes(mode) test_AMlistPutBytes ## _ ## mode +#define test_AMlistPutBytes(mode) test_AMlistPutBytes##_##mode -#define static_void_test_AMlistPutBytes(mode, bytes_value) \ -static void test_AMlistPutBytes_ ## mode(void **state) { \ - static size_t const BYTES_SIZE = sizeof(bytes_value) / sizeof(uint8_t); \ - \ - GroupState* group_state = *state; \ - AMobjId const* const list = AMpush( \ - &group_state->stack, \ - AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),\ - AM_VALUE_OBJ_ID, \ - cmocka_cb).obj_id; \ - AMfree(AMlistPutBytes(group_state->doc, \ - list, \ - 0, \ - !strcmp(#mode, "insert"), \ - AMbytes(bytes_value, BYTES_SIZE))); \ - AMbyteSpan const bytes = AMpush( \ - &group_state->stack, \ - AMlistGet(group_state->doc, list, 0, NULL), \ - AM_VALUE_BYTES, \ - cmocka_cb).bytes; \ - assert_int_equal(bytes.count, BYTES_SIZE); \ - assert_memory_equal(bytes.src, bytes_value, BYTES_SIZE); \ - AMfree(AMpop(&group_state->stack)); \ -} +#define static_void_test_AMlistPutBytes(mode, bytes_value) \ + static void test_AMlistPutBytes_##mode(void** state) { \ + static size_t const BYTES_SIZE = sizeof(bytes_value) / sizeof(uint8_t); \ + \ + DocState* doc_state = *state; \ + AMstack** stack_ptr = &doc_state->base_state->stack; \ + AMobjId const* const list = AMitemObjId( \ + AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ + AMstackItem( \ + NULL, AMlistPutBytes(doc_state->doc, list, 0, !strcmp(#mode, "insert"), AMbytes(bytes_value, BYTES_SIZE)), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); \ + AMbyteSpan bytes; \ + assert_true(AMitemToBytes( \ + AMstackItem(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), \ + &bytes)); \ + assert_int_equal(bytes.count, BYTES_SIZE); \ + assert_memory_equal(bytes.src, bytes_value, BYTES_SIZE); \ + AMresultFree(AMstackPop(stack_ptr, NULL)); \ + } -#define test_AMlistPutNull(mode) test_AMlistPutNull_ ## mode +#define test_AMlistPutNull(mode) test_AMlistPutNull_##mode -#define static_void_test_AMlistPutNull(mode) \ -static void test_AMlistPutNull_ ## mode(void **state) { \ - GroupState* group_state = *state; \ - AMobjId const* const list = AMpush( \ - &group_state->stack, \ - AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),\ - AM_VALUE_OBJ_ID, \ - cmocka_cb).obj_id; \ - AMfree(AMlistPutNull(group_state->doc, \ - list, \ - 0, \ - !strcmp(#mode, "insert"))); \ - AMresult* const result = AMlistGet(group_state->doc, list, 0, NULL); \ - if (AMresultStatus(result) != AM_STATUS_OK) { \ - fail_msg_view("%s", AMerrorMessage(result)); \ - } \ - assert_int_equal(AMresultSize(result), 1); \ - assert_int_equal(AMresultValue(result).tag, AM_VALUE_NULL); \ - AMfree(result); \ -} +#define static_void_test_AMlistPutNull(mode) \ + static void test_AMlistPutNull_##mode(void** state) { \ + DocState* doc_state = *state; \ + AMstack** stack_ptr = &doc_state->base_state->stack; \ + AMobjId const* const list = AMitemObjId( \ + AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ + AMstackItem(NULL, AMlistPutNull(doc_state->doc, list, 0, !strcmp(#mode, "insert")), cmocka_cb, \ + AMexpect(AM_VAL_TYPE_VOID)); \ + AMresult* result = AMstackResult(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), NULL, NULL); \ + if (AMresultStatus(result) != AM_STATUS_OK) { \ + fail_msg_view("%s", AMresultError(result)); \ + } \ + assert_int_equal(AMresultSize(result), 1); \ + assert_int_equal(AMitemValType(AMresultItem(result)), AM_VAL_TYPE_NULL); \ + AMresultFree(AMstackPop(stack_ptr, NULL)); \ + } -#define test_AMlistPutObject(label, mode) test_AMlistPutObject_ ## label ## _ ## mode +#define test_AMlistPutObject(label, mode) test_AMlistPutObject_##label##_##mode -#define static_void_test_AMlistPutObject(label, mode) \ -static void test_AMlistPutObject_ ## label ## _ ## mode(void **state) { \ - GroupState* group_state = *state; \ - AMobjId const* const list = AMpush( \ - &group_state->stack, \ - AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),\ - AM_VALUE_OBJ_ID, \ - cmocka_cb).obj_id; \ - AMobjType const obj_type = AMobjType_tag(#label); \ - if (obj_type != AM_OBJ_TYPE_VOID) { \ - AMobjId const* const obj_id = AMpush( \ - &group_state->stack, \ - AMlistPutObject(group_state->doc, \ - list, \ - 0, \ - !strcmp(#mode, "insert"), \ - obj_type), \ - AM_VALUE_OBJ_ID, \ - cmocka_cb).obj_id; \ - assert_non_null(obj_id); \ - assert_int_equal(AMobjObjType(group_state->doc, obj_id), obj_type); \ - assert_int_equal(AMobjSize(group_state->doc, obj_id, NULL), 0); \ - } \ - else { \ - AMpush(&group_state->stack, \ - AMlistPutObject(group_state->doc, \ - list, \ - 0, \ - !strcmp(#mode, "insert"), \ - obj_type), \ - AM_VALUE_VOID, \ - NULL); \ - assert_int_not_equal(AMresultStatus(group_state->stack->result), \ - AM_STATUS_OK); \ - } \ - AMfree(AMpop(&group_state->stack)); \ -} +#define static_void_test_AMlistPutObject(label, mode) \ + static void test_AMlistPutObject_##label##_##mode(void** state) { \ + DocState* doc_state = *state; \ + AMstack** stack_ptr = &doc_state->base_state->stack; \ + AMobjId const* const list = AMitemObjId( \ + AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ + AMobjType const obj_type = suffix_to_obj_type(#label); \ + AMobjId const* const obj_id = AMitemObjId( \ + AMstackItem(stack_ptr, AMlistPutObject(doc_state->doc, list, 0, !strcmp(#mode, "insert"), obj_type), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ + assert_non_null(obj_id); \ + assert_int_equal(AMobjObjType(doc_state->doc, obj_id), obj_type); \ + assert_int_equal(AMobjSize(doc_state->doc, obj_id, NULL), 0); \ + AMresultFree(AMstackPop(stack_ptr, NULL)); \ + } -#define test_AMlistPutStr(mode) test_AMlistPutStr ## _ ## mode +#define test_AMlistPutStr(mode) test_AMlistPutStr##_##mode -#define static_void_test_AMlistPutStr(mode, str_value) \ -static void test_AMlistPutStr_ ## mode(void **state) { \ - GroupState* group_state = *state; \ - AMobjId const* const list = AMpush( \ - &group_state->stack, \ - AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),\ - AM_VALUE_OBJ_ID, \ - cmocka_cb).obj_id; \ - AMfree(AMlistPutStr(group_state->doc, \ - list, \ - 0, \ - !strcmp(#mode, "insert"), \ - AMstr(str_value))); \ - AMbyteSpan const str = AMpush( \ - &group_state->stack, \ - AMlistGet(group_state->doc, list, 0, NULL), \ - AM_VALUE_STR, \ - cmocka_cb).str; \ - assert_int_equal(str.count, strlen(str_value)); \ - assert_memory_equal(str.src, str_value, str.count); \ - AMfree(AMpop(&group_state->stack)); \ -} +#define static_void_test_AMlistPutStr(mode, str_value) \ + static void test_AMlistPutStr_##mode(void** state) { \ + DocState* doc_state = *state; \ + AMstack** stack_ptr = &doc_state->base_state->stack; \ + AMobjId const* const list = AMitemObjId( \ + AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ + AMstackItem(NULL, AMlistPutStr(doc_state->doc, list, 0, !strcmp(#mode, "insert"), AMstr(str_value)), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); \ + AMbyteSpan str; \ + assert_true(AMitemToStr( \ + AMstackItem(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), \ + &str)); \ + assert_int_equal(str.count, strlen(str_value)); \ + assert_memory_equal(str.src, str_value, str.count); \ + AMresultFree(AMstackPop(stack_ptr, NULL)); \ + } -static_void_test_AMlistPut(Bool, insert, boolean, true) +static_void_test_AMlistPut(Bool, insert, bool, true); -static_void_test_AMlistPut(Bool, update, boolean, true) +static_void_test_AMlistPut(Bool, update, bool, true); static uint8_t const BYTES_VALUE[] = {INT8_MIN, INT8_MAX / 2, INT8_MAX}; -static_void_test_AMlistPutBytes(insert, BYTES_VALUE) +static_void_test_AMlistPutBytes(insert, BYTES_VALUE); -static_void_test_AMlistPutBytes(update, BYTES_VALUE) +static_void_test_AMlistPutBytes(update, BYTES_VALUE); -static_void_test_AMlistPut(Counter, insert, counter, INT64_MAX) +static_void_test_AMlistPut(Counter, insert, int64_t, INT64_MAX); -static_void_test_AMlistPut(Counter, update, counter, INT64_MAX) +static_void_test_AMlistPut(Counter, update, int64_t, INT64_MAX); -static_void_test_AMlistPut(F64, insert, f64, DBL_MAX) +static_void_test_AMlistPut(F64, insert, double, DBL_MAX); -static_void_test_AMlistPut(F64, update, f64, DBL_MAX) +static_void_test_AMlistPut(F64, update, double, DBL_MAX); -static_void_test_AMlistPut(Int, insert, int_, INT64_MAX) +static_void_test_AMlistPut(Int, insert, int64_t, INT64_MAX); -static_void_test_AMlistPut(Int, update, int_, INT64_MAX) +static_void_test_AMlistPut(Int, update, int64_t, INT64_MAX); -static_void_test_AMlistPutNull(insert) +static_void_test_AMlistPutNull(insert); -static_void_test_AMlistPutNull(update) +static_void_test_AMlistPutNull(update); -static_void_test_AMlistPutObject(List, insert) +static_void_test_AMlistPutObject(List, insert); -static_void_test_AMlistPutObject(List, update) +static_void_test_AMlistPutObject(List, update); -static_void_test_AMlistPutObject(Map, insert) +static_void_test_AMlistPutObject(Map, insert); -static_void_test_AMlistPutObject(Map, update) +static_void_test_AMlistPutObject(Map, update); -static_void_test_AMlistPutObject(Text, insert) +static_void_test_AMlistPutObject(Text, insert); -static_void_test_AMlistPutObject(Text, update) +static_void_test_AMlistPutObject(Text, update); -static_void_test_AMlistPutObject(Void, insert) +static_void_test_AMlistPutStr(insert, + "Hello, " + "world!"); -static_void_test_AMlistPutObject(Void, update) +static_void_test_AMlistPutStr(update, + "Hello," + " world" + "!"); -static_void_test_AMlistPutStr(insert, "Hello, world!") +static_void_test_AMlistPut(Timestamp, insert, int64_t, INT64_MAX); -static_void_test_AMlistPutStr(update, "Hello, world!") +static_void_test_AMlistPut(Timestamp, update, int64_t, INT64_MAX); -static_void_test_AMlistPut(Timestamp, insert, timestamp, INT64_MAX) +static_void_test_AMlistPut(Uint, insert, uint64_t, UINT64_MAX); -static_void_test_AMlistPut(Timestamp, update, timestamp, INT64_MAX) +static_void_test_AMlistPut(Uint, update, uint64_t, UINT64_MAX); -static_void_test_AMlistPut(Uint, insert, uint, UINT64_MAX) - -static_void_test_AMlistPut(Uint, update, uint, UINT64_MAX) - -static void test_get_list_values(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMobjId const* const list = AMpush( - &stack, - AMmapPutObject(doc1, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; +static void test_get_range_values(void** state) { + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc1, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* Insert elements. */ - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("First"))); - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Second"))); - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Third"))); - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Fourth"))); - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Fifth"))); - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Sixth"))); - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Seventh"))); - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Eighth"))); - AMfree(AMcommit(doc1, AMstr(NULL), NULL)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("First")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Second")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Third")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Fourth")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Fifth")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Sixth")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Seventh")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Eighth")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc1, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMchangeHashes const v1 = AMpush(&stack, - AMgetHeads(doc1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMdoc* const doc2 = AMpush(&stack, - AMfork(doc1, NULL), - AM_VALUE_DOC, - cmocka_cb).doc; + AMitems const v1 = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMdoc* doc2; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMfork(doc1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); - AMfree(AMlistPutStr(doc1, list, 2, false, AMstr("Third V2"))); - AMfree(AMcommit(doc1, AMstr(NULL), NULL)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 2, false, AMstr("Third V2")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc1, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMfree(AMlistPutStr(doc2, list, 2, false, AMstr("Third V3"))); - AMfree(AMcommit(doc2, AMstr(NULL), NULL)); + AMstackItem(NULL, AMlistPutStr(doc2, list, 2, false, AMstr("Third V3")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc2, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMfree(AMmerge(doc1, doc2)); + AMstackItem(NULL, AMmerge(doc1, doc2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMlistItems range = AMpush(&stack, - AMlistRange(doc1, list, 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - assert_int_equal(AMlistItemsSize(&range), 8); + /* Forward vs. reverse: complete current list range. */ + AMitems range = + AMstackItems(stack_ptr, AMlistRange(doc1, list, 0, SIZE_MAX, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + size_t size = AMitemsSize(&range); + assert_int_equal(size, 8); + AMitems range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + size_t pos; + assert_true(AMitemPos(AMitemsNext(&range, 1), &pos)); + assert_int_equal(pos, 0); + assert_true(AMitemPos(AMitemsNext(&range_back, 1), &pos)); + assert_int_equal(pos, 7); - AMlistItem const* list_item = NULL; - while ((list_item = AMlistItemsNext(&range, 1)) != NULL) { - AMvalue const val1 = AMlistItemValue(list_item); - AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), NULL); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMlistItemObjId(list_item)); - AMfree(result); + AMitem *item1, *item_back1; + size_t count, middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + size_t pos1, pos_back1; + assert_true(AMitemPos(item1, &pos1)); + assert_true(AMitemPos(item_back1, &pos_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(pos1, pos_back1); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(pos1, pos_back1); + } + AMitem* item2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos1, NULL), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos_back1, NULL), NULL, NULL); + /** \note An item returned from an `AM...Get()` call doesn't include the + index used to retrieve it. */ + assert_false(AMitemIdxType(item2)); + assert_false(AMitemIdxType(item_back2)); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item_back1, item_back2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - range = AMpush(&stack, - AMlistRange(doc1, list, 3, 6, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMlistItems range_back = AMlistItemsReversed(&range); - assert_int_equal(AMlistItemsSize(&range), 3); - assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range, 1)), 3); - assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range_back, 1)), 5); + /* Forward vs. reverse: partial current list range. */ + range = AMstackItems(stack_ptr, AMlistRange(doc1, list, 1, 6, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + size = AMitemsSize(&range); + assert_int_equal(size, 5); + range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + assert_true(AMitemPos(AMitemsNext(&range, 1), &pos)); + assert_int_equal(pos, 1); + assert_true(AMitemPos(AMitemsNext(&range_back, 1), &pos)); + assert_int_equal(pos, 5); - range = AMlistItemsRewound(&range); - while ((list_item = AMlistItemsNext(&range, 1)) != NULL) { - AMvalue const val1 = AMlistItemValue(list_item); - AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), NULL); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMlistItemObjId(list_item)); - AMfree(result); + middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + size_t pos1, pos_back1; + assert_true(AMitemPos(item1, &pos1)); + assert_true(AMitemPos(item_back1, &pos_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(pos1, pos_back1); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(pos1, pos_back1); + } + AMitem* item2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos1, NULL), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos_back1, NULL), NULL, NULL); + /** \note An item returned from an `AMlistGet()` call doesn't include + the index used to retrieve it. */ + assert_int_equal(AMitemIdxType(item2), 0); + assert_int_equal(AMitemIdxType(item_back2), 0); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item_back1, item_back2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - range = AMpush(&stack, - AMlistRange(doc1, list, 0, SIZE_MAX, &v1), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - assert_int_equal(AMlistItemsSize(&range), 8); - while ((list_item = AMlistItemsNext(&range, 1)) != NULL) { - AMvalue const val1 = AMlistItemValue(list_item); - AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), &v1); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMlistItemObjId(list_item)); - AMfree(result); + /* Forward vs. reverse: complete historical map range. */ + range = AMstackItems(stack_ptr, AMlistRange(doc1, list, 0, SIZE_MAX, &v1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + size = AMitemsSize(&range); + assert_int_equal(size, 8); + range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + assert_true(AMitemPos(AMitemsNext(&range, 1), &pos)); + assert_int_equal(pos, 0); + assert_true(AMitemPos(AMitemsNext(&range_back, 1), &pos)); + assert_int_equal(pos, 7); + + middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + size_t pos1, pos_back1; + assert_true(AMitemPos(item1, &pos1)); + assert_true(AMitemPos(item_back1, &pos_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(pos1, pos_back1); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(pos1, pos_back1); + } + AMitem* item2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos1, &v1), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos_back1, &v1), NULL, NULL); + /** \note An item returned from an `AM...Get()` call doesn't include the + index used to retrieve it. */ + assert_false(AMitemIdxType(item2)); + assert_false(AMitemIdxType(item_back2)); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item_back1, item_back2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - range = AMpush(&stack, - AMlistRange(doc1, list, 3, 6, &v1), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - range_back = AMlistItemsReversed(&range); - assert_int_equal(AMlistItemsSize(&range), 3); - assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range, 1)), 3); - assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range_back, 1)), 5); + /* Forward vs. reverse: partial historical map range. */ + range = AMstackItems(stack_ptr, AMlistRange(doc1, list, 2, 7, &v1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + size = AMitemsSize(&range); + assert_int_equal(size, 5); + range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + assert_true(AMitemPos(AMitemsNext(&range, 1), &pos)); + assert_int_equal(pos, 2); + assert_true(AMitemPos(AMitemsNext(&range_back, 1), &pos)); + assert_int_equal(pos, 6); - range = AMlistItemsRewound(&range); - while ((list_item = AMlistItemsNext(&range, 1)) != NULL) { - AMvalue const val1 = AMlistItemValue(list_item); - AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), &v1); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMlistItemObjId(list_item)); - AMfree(result); + middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + size_t pos1, pos_back1; + assert_true(AMitemPos(item1, &pos1)); + assert_true(AMitemPos(item_back1, &pos_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(pos1, pos_back1); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(pos1, pos_back1); + } + AMitem* item2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos1, &v1), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos_back1, &v1), NULL, NULL); + /** \note An item returned from an `AM...Get()` call doesn't include the + index used to retrieve it. */ + assert_false(AMitemIdxType(item2)); + assert_false(AMitemIdxType(item_back2)); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item_back1, item_back2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - range = AMpush(&stack, - AMlistRange(doc1, list, 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMobjItems values = AMpush(&stack, - AMobjValues(doc1, list, NULL), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; - assert_int_equal(AMlistItemsSize(&range), AMobjItemsSize(&values)); - AMobjItem const* value = NULL; - while ((list_item = AMlistItemsNext(&range, 1)) != NULL && - (value = AMobjItemsNext(&values, 1)) != NULL) { - AMvalue const val1 = AMlistItemValue(list_item); - AMvalue const val2 = AMobjItemValue(value); - assert_true(AMvalueEqual(&val1, &val2)); - assert_true(AMobjIdEqual(AMlistItemObjId(list_item), AMobjItemObjId(value))); + /* List range vs. object range: complete current. */ + range = AMstackItems(stack_ptr, AMlistRange(doc1, list, 0, SIZE_MAX, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMitems obj_items = AMstackItems(stack_ptr, AMobjItems(doc1, list, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&range), AMitemsSize(&obj_items)); + + AMitem *item, *obj_item; + for (item = NULL, obj_item = NULL; item && obj_item; + item = AMitemsNext(&range, 1), obj_item = AMitemsNext(&obj_items, 1)) { + /** \note Object iteration doesn't yield any item indices. */ + assert_true(AMitemIdxType(item)); + assert_false(AMitemIdxType(obj_item)); + assert_true(AMitemEqual(item, obj_item)); + assert_true(AMobjIdEqual(AMitemObjId(item), AMitemObjId(obj_item))); } - range = AMpush(&stack, - AMlistRange(doc1, list, 0, SIZE_MAX, &v1), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - values = AMpush(&stack, - AMobjValues(doc1, list, &v1), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; - assert_int_equal(AMlistItemsSize(&range), AMobjItemsSize(&values)); - while ((list_item = AMlistItemsNext(&range, 1)) != NULL && - (value = AMobjItemsNext(&values, 1)) != NULL) { - AMvalue const val1 = AMlistItemValue(list_item); - AMvalue const val2 = AMobjItemValue(value); - assert_true(AMvalueEqual(&val1, &val2)); - assert_true(AMobjIdEqual(AMlistItemObjId(list_item), AMobjItemObjId(value))); + /* List range vs. object range: complete historical. */ + range = AMstackItems(stack_ptr, AMlistRange(doc1, list, 0, SIZE_MAX, &v1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + obj_items = AMstackItems(stack_ptr, AMobjItems(doc1, list, &v1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&range), AMitemsSize(&obj_items)); + + for (item = NULL, obj_item = NULL; item && obj_item; + item = AMitemsNext(&range, 1), obj_item = AMitemsNext(&obj_items, 1)) { + /** \note Object iteration doesn't yield any item indices. */ + assert_true(AMitemIdxType(item)); + assert_false(AMitemIdxType(obj_item)); + assert_true(AMitemEqual(item, obj_item)); + assert_true(AMobjIdEqual(AMitemObjId(item), AMitemObjId(obj_item))); } } -/** \brief A JavaScript application can introduce NUL (`\0`) characters into a - * list object's string value which will truncate it in a C application. +/** + * \brief A JavaScript application can introduce NUL (`\0`) characters into a + * list object's string value which will truncate it in a C application. */ static void test_get_NUL_string_value(void** state) { /* @@ -381,60 +431,52 @@ static void test_get_NUL_string_value(void** state) { doc[0] = 'o\0ps'; }); const bytes = Automerge.save(doc); - console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], bytes).join(", ") + "};"); + console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], + bytes).join(", ") + "};"); */ static uint8_t const OOPS_VALUE[] = {'o', '\0', 'p', 's'}; static size_t const OOPS_SIZE = sizeof(OOPS_VALUE) / sizeof(uint8_t); static uint8_t const SAVED_DOC[] = { - 133, 111, 74, 131, 224, 28, 197, 17, 0, 113, 1, 16, 246, 137, 63, 193, - 255, 181, 76, 79, 129, 213, 133, 29, 214, 158, 164, 15, 1, 207, 184, - 14, 57, 1, 194, 79, 247, 82, 160, 134, 227, 144, 5, 241, 136, 205, - 238, 250, 251, 54, 34, 250, 210, 96, 204, 132, 153, 203, 110, 109, 6, - 6, 1, 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 3, 33, 2, 35, 2, 52, - 1, 66, 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, - 127, 0, 127, 7, 127, 1, 48, 127, 0, 127, 1, 1, 127, 1, 127, 70, 111, - 0, 112, 115, 127, 0, 0}; + 133, 111, 74, 131, 224, 28, 197, 17, 0, 113, 1, 16, 246, 137, 63, 193, 255, 181, 76, 79, 129, + 213, 133, 29, 214, 158, 164, 15, 1, 207, 184, 14, 57, 1, 194, 79, 247, 82, 160, 134, 227, 144, + 5, 241, 136, 205, 238, 250, 251, 54, 34, 250, 210, 96, 204, 132, 153, 203, 110, 109, 6, 6, 1, + 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 3, 33, 2, 35, 2, 52, 1, 66, + 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, 0, 127, 7, 127, + 1, 48, 127, 0, 127, 1, 1, 127, 1, 127, 70, 111, 0, 112, 115, 127, 0, 0}; static size_t const SAVED_DOC_SIZE = sizeof(SAVED_DOC) / sizeof(uint8_t); - AMresultStack* stack = *state; - AMdoc* const doc = AMpush(&stack, - AMload(SAVED_DOC, SAVED_DOC_SIZE), - AM_VALUE_DOC, - cmocka_cb).doc; - AMbyteSpan const str = AMpush(&stack, - AMlistGet(doc, AM_ROOT, 0, NULL), - AM_VALUE_STR, - cmocka_cb).str; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(SAVED_DOC, SAVED_DOC_SIZE), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMbyteSpan str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, AM_ROOT, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_not_equal(str.count, strlen(OOPS_VALUE)); assert_int_equal(str.count, OOPS_SIZE); assert_memory_equal(str.src, OOPS_VALUE, str.count); } static void test_insert_at_index(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - - AMobjId const* const list = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* Insert both at the same index. */ - AMfree(AMlistPutUint(doc, list, 0, true, 0)); - AMfree(AMlistPutUint(doc, list, 0, true, 1)); + AMstackItem(NULL, AMlistPutUint(doc, list, 0, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutUint(doc, list, 0, true, 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); assert_int_equal(AMobjSize(doc, list, NULL), 2); - AMstrs const keys = AMpush(&stack, - AMkeys(doc, list, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - assert_int_equal(AMstrsSize(&keys), 2); - AMlistItems const range = AMpush(&stack, - AMlistRange(doc, list, 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - assert_int_equal(AMlistItemsSize(&range), 2); + AMitems const keys = AMstackItems(stack_ptr, AMkeys(doc, list, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&keys), 2); + AMitems const range = + AMstackItems(stack_ptr, AMlistRange(doc, list, 0, SIZE_MAX, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT)); + assert_int_equal(AMitemsSize(&range), 2); } int run_list_tests(void) { @@ -458,18 +500,16 @@ int run_list_tests(void) { cmocka_unit_test(test_AMlistPutObject(Map, update)), cmocka_unit_test(test_AMlistPutObject(Text, insert)), cmocka_unit_test(test_AMlistPutObject(Text, update)), - cmocka_unit_test(test_AMlistPutObject(Void, insert)), - cmocka_unit_test(test_AMlistPutObject(Void, update)), cmocka_unit_test(test_AMlistPutStr(insert)), cmocka_unit_test(test_AMlistPutStr(update)), cmocka_unit_test(test_AMlistPut(Timestamp, insert)), cmocka_unit_test(test_AMlistPut(Timestamp, update)), cmocka_unit_test(test_AMlistPut(Uint, insert)), cmocka_unit_test(test_AMlistPut(Uint, update)), - cmocka_unit_test_setup_teardown(test_get_list_values, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_get_NUL_string_value, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_insert_at_index, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_get_range_values, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_get_NUL_string_value, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_insert_at_index, setup_base, teardown_base), }; - return cmocka_run_group_tests(tests, group_setup, group_teardown); + return cmocka_run_group_tests(tests, setup_doc, teardown_doc); } diff --git a/rust/automerge-c/test/macro_utils.c b/rust/automerge-c/test/macro_utils.c index 6d7578b6..3a546eb5 100644 --- a/rust/automerge-c/test/macro_utils.c +++ b/rust/automerge-c/test/macro_utils.c @@ -3,23 +3,36 @@ /* local */ #include "macro_utils.h" -AMvalueVariant AMvalue_discriminant(char const* suffix) { - if (!strcmp(suffix, "Bool")) return AM_VALUE_BOOLEAN; - else if (!strcmp(suffix, "Bytes")) return AM_VALUE_BYTES; - else if (!strcmp(suffix, "Counter")) return AM_VALUE_COUNTER; - else if (!strcmp(suffix, "F64")) return AM_VALUE_F64; - else if (!strcmp(suffix, "Int")) return AM_VALUE_INT; - else if (!strcmp(suffix, "Null")) return AM_VALUE_NULL; - else if (!strcmp(suffix, "Str")) return AM_VALUE_STR; - else if (!strcmp(suffix, "Timestamp")) return AM_VALUE_TIMESTAMP; - else if (!strcmp(suffix, "Uint")) return AM_VALUE_UINT; - else return AM_VALUE_VOID; +AMobjType suffix_to_obj_type(char const* obj_type_label) { + if (!strcmp(obj_type_label, "List")) + return AM_OBJ_TYPE_LIST; + else if (!strcmp(obj_type_label, "Map")) + return AM_OBJ_TYPE_MAP; + else if (!strcmp(obj_type_label, "Text")) + return AM_OBJ_TYPE_TEXT; + else + return AM_OBJ_TYPE_DEFAULT; } -AMobjType AMobjType_tag(char const* obj_type_label) { - if (!strcmp(obj_type_label, "List")) return AM_OBJ_TYPE_LIST; - else if (!strcmp(obj_type_label, "Map")) return AM_OBJ_TYPE_MAP; - else if (!strcmp(obj_type_label, "Text")) return AM_OBJ_TYPE_TEXT; - else if (!strcmp(obj_type_label, "Void")) return AM_OBJ_TYPE_VOID; - else return 0; +AMvalType suffix_to_val_type(char const* suffix) { + if (!strcmp(suffix, "Bool")) + return AM_VAL_TYPE_BOOL; + else if (!strcmp(suffix, "Bytes")) + return AM_VAL_TYPE_BYTES; + else if (!strcmp(suffix, "Counter")) + return AM_VAL_TYPE_COUNTER; + else if (!strcmp(suffix, "F64")) + return AM_VAL_TYPE_F64; + else if (!strcmp(suffix, "Int")) + return AM_VAL_TYPE_INT; + else if (!strcmp(suffix, "Null")) + return AM_VAL_TYPE_NULL; + else if (!strcmp(suffix, "Str")) + return AM_VAL_TYPE_STR; + else if (!strcmp(suffix, "Timestamp")) + return AM_VAL_TYPE_TIMESTAMP; + else if (!strcmp(suffix, "Uint")) + return AM_VAL_TYPE_UINT; + else + return AM_VAL_TYPE_DEFAULT; } diff --git a/rust/automerge-c/test/macro_utils.h b/rust/automerge-c/test/macro_utils.h index 62e262ce..e4c2c5b9 100644 --- a/rust/automerge-c/test/macro_utils.h +++ b/rust/automerge-c/test/macro_utils.h @@ -1,24 +1,23 @@ -#ifndef MACRO_UTILS_H -#define MACRO_UTILS_H +#ifndef TESTS_MACRO_UTILS_H +#define TESTS_MACRO_UTILS_H /* local */ #include /** - * \brief Gets the result value discriminant corresponding to a function name - * suffix. + * \brief Gets the object type tag corresponding to an object type suffix. * - * \param[in] suffix A string. - * \return An `AMvalue` struct discriminant. - */ -AMvalueVariant AMvalue_discriminant(char const* suffix); - -/** - * \brief Gets the object type tag corresponding to an object type label. - * - * \param[in] obj_type_label A string. + * \param[in] suffix An object type suffix string. * \return An `AMobjType` enum tag. */ -AMobjType AMobjType_tag(char const* obj_type_label); +AMobjType suffix_to_obj_type(char const* suffix); -#endif /* MACRO_UTILS_H */ +/** + * \brief Gets the value type tag corresponding to a value type suffix. + * + * \param[in] suffix A value type suffix string. + * \return An `AMvalType` enum tag. + */ +AMvalType suffix_to_val_type(char const* suffix); + +#endif /* TESTS_MACRO_UTILS_H */ diff --git a/rust/automerge-c/test/main.c b/rust/automerge-c/test/main.c index 09b71bd5..2996c9b3 100644 --- a/rust/automerge-c/test/main.c +++ b/rust/automerge-c/test/main.c @@ -1,6 +1,6 @@ +#include #include #include -#include #include /* third-party */ @@ -8,8 +8,14 @@ extern int run_actor_id_tests(void); +extern int run_byte_span_tests(void); + extern int run_doc_tests(void); +extern int run_enum_string_tests(void); + +extern int run_item_tests(void); + extern int run_list_tests(void); extern int run_map_tests(void); @@ -17,11 +23,6 @@ extern int run_map_tests(void); extern int run_ported_wasm_suite(void); int main(void) { - return ( - run_actor_id_tests() + - run_doc_tests() + - run_list_tests() + - run_map_tests() + - run_ported_wasm_suite() - ); + return (run_actor_id_tests() + run_byte_span_tests() + run_doc_tests() + run_enum_string_tests() + + run_item_tests() + run_list_tests() + run_map_tests() + run_ported_wasm_suite()); } diff --git a/rust/automerge-c/test/map_tests.c b/rust/automerge-c/test/map_tests.c index 194da2e8..2ee2e69a 100644 --- a/rust/automerge-c/test/map_tests.c +++ b/rust/automerge-c/test/map_tests.c @@ -11,144 +11,133 @@ /* local */ #include +#include +#include +#include "base_state.h" #include "cmocka_utils.h" -#include "group_state.h" +#include "doc_state.h" #include "macro_utils.h" -#include "stack_utils.h" static void test_AMmapIncrement(void** state) { - GroupState* group_state = *state; - AMfree(AMmapPutCounter(group_state->doc, AM_ROOT, AMstr("Counter"), 0)); - assert_int_equal(AMpush(&group_state->stack, - AMmapGet(group_state->doc, AM_ROOT, AMstr("Counter"), NULL), - AM_VALUE_COUNTER, - cmocka_cb).counter, 0); - AMfree(AMpop(&group_state->stack)); - AMfree(AMmapIncrement(group_state->doc, AM_ROOT, AMstr("Counter"), 3)); - assert_int_equal(AMpush(&group_state->stack, - AMmapGet(group_state->doc, AM_ROOT, AMstr("Counter"), NULL), - AM_VALUE_COUNTER, - cmocka_cb).counter, 3); - AMfree(AMpop(&group_state->stack)); + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->base_state->stack; + AMstackItem(NULL, AMmapPutCounter(doc_state->doc, AM_ROOT, AMstr("Counter"), 0), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + int64_t counter; + assert_true(AMitemToCounter(AMstackItem(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, AMstr("Counter"), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_COUNTER)), + &counter)); + assert_int_equal(counter, 0); + AMresultFree(AMstackPop(stack_ptr, NULL)); + AMstackItem(NULL, AMmapIncrement(doc_state->doc, AM_ROOT, AMstr("Counter"), 3), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + assert_true(AMitemToCounter(AMstackItem(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, AMstr("Counter"), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_COUNTER)), + &counter)); + assert_int_equal(counter, 3); + AMresultFree(AMstackPop(stack_ptr, NULL)); } -#define test_AMmapPut(suffix) test_AMmapPut ## suffix +#define test_AMmapPut(suffix) test_AMmapPut##suffix -#define static_void_test_AMmapPut(suffix, member, scalar_value) \ -static void test_AMmapPut ## suffix(void **state) { \ - GroupState* group_state = *state; \ - AMfree(AMmapPut ## suffix(group_state->doc, \ - AM_ROOT, \ - AMstr(#suffix), \ - scalar_value)); \ - assert_true(AMpush( \ - &group_state->stack, \ - AMmapGet(group_state->doc, AM_ROOT, AMstr(#suffix), NULL), \ - AMvalue_discriminant(#suffix), \ - cmocka_cb).member == scalar_value); \ - AMfree(AMpop(&group_state->stack)); \ -} +#define static_void_test_AMmapPut(suffix, type, scalar_value) \ + static void test_AMmapPut##suffix(void** state) { \ + DocState* doc_state = *state; \ + AMstack** stack_ptr = &doc_state->base_state->stack; \ + AMstackItem(NULL, AMmapPut##suffix(doc_state->doc, AM_ROOT, AMstr(#suffix), scalar_value), cmocka_cb, \ + AMexpect(AM_VAL_TYPE_VOID)); \ + type value; \ + assert_true(AMitemTo##suffix(AMstackItem(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, AMstr(#suffix), NULL), \ + cmocka_cb, AMexpect(suffix_to_val_type(#suffix))), \ + &value)); \ + assert_true(value == scalar_value); \ + AMresultFree(AMstackPop(stack_ptr, NULL)); \ + } -static void test_AMmapPutBytes(void **state) { +static void test_AMmapPutBytes(void** state) { static AMbyteSpan const KEY = {"Bytes", 5}; static uint8_t const BYTES_VALUE[] = {INT8_MIN, INT8_MAX / 2, INT8_MAX}; static size_t const BYTES_SIZE = sizeof(BYTES_VALUE) / sizeof(uint8_t); - GroupState* group_state = *state; - AMfree(AMmapPutBytes(group_state->doc, - AM_ROOT, - KEY, - AMbytes(BYTES_VALUE, BYTES_SIZE))); - AMbyteSpan const bytes = AMpush(&group_state->stack, - AMmapGet(group_state->doc, AM_ROOT, KEY, NULL), - AM_VALUE_BYTES, - cmocka_cb).bytes; + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->base_state->stack; + AMstackItem(NULL, AMmapPutBytes(doc_state->doc, AM_ROOT, KEY, AMbytes(BYTES_VALUE, BYTES_SIZE)), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + AMbyteSpan bytes; + assert_true(AMitemToBytes( + AMstackItem(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, KEY, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), + &bytes)); assert_int_equal(bytes.count, BYTES_SIZE); assert_memory_equal(bytes.src, BYTES_VALUE, BYTES_SIZE); - AMfree(AMpop(&group_state->stack)); + AMresultFree(AMstackPop(stack_ptr, NULL)); } -static void test_AMmapPutNull(void **state) { +static void test_AMmapPutNull(void** state) { static AMbyteSpan const KEY = {"Null", 4}; - GroupState* group_state = *state; - AMfree(AMmapPutNull(group_state->doc, AM_ROOT, KEY)); - AMresult* const result = AMmapGet(group_state->doc, AM_ROOT, KEY, NULL); + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->base_state->stack; + AMstackItem(NULL, AMmapPutNull(doc_state->doc, AM_ROOT, KEY), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMresult* result = AMstackResult(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, KEY, NULL), NULL, NULL); if (AMresultStatus(result) != AM_STATUS_OK) { - fail_msg_view("%s", AMerrorMessage(result)); + fail_msg_view("%s", AMresultError(result)); } assert_int_equal(AMresultSize(result), 1); - assert_int_equal(AMresultValue(result).tag, AM_VALUE_NULL); - AMfree(result); + AMitem* item = AMresultItem(result); + assert_int_equal(AMitemValType(item), AM_VAL_TYPE_NULL); } -#define test_AMmapPutObject(label) test_AMmapPutObject_ ## label +#define test_AMmapPutObject(label) test_AMmapPutObject_##label -#define static_void_test_AMmapPutObject(label) \ -static void test_AMmapPutObject_ ## label(void **state) { \ - GroupState* group_state = *state; \ - AMobjType const obj_type = AMobjType_tag(#label); \ - if (obj_type != AM_OBJ_TYPE_VOID) { \ - AMobjId const* const obj_id = AMpush( \ - &group_state->stack, \ - AMmapPutObject(group_state->doc, \ - AM_ROOT, \ - AMstr(#label), \ - obj_type), \ - AM_VALUE_OBJ_ID, \ - cmocka_cb).obj_id; \ - assert_non_null(obj_id); \ - assert_int_equal(AMobjObjType(group_state->doc, obj_id), obj_type); \ - assert_int_equal(AMobjSize(group_state->doc, obj_id, NULL), 0); \ - } \ - else { \ - AMpush(&group_state->stack, \ - AMmapPutObject(group_state->doc, \ - AM_ROOT, \ - AMstr(#label), \ - obj_type), \ - AM_VALUE_VOID, \ - NULL); \ - assert_int_not_equal(AMresultStatus(group_state->stack->result), \ - AM_STATUS_OK); \ - } \ - AMfree(AMpop(&group_state->stack)); \ -} +#define static_void_test_AMmapPutObject(label) \ + static void test_AMmapPutObject_##label(void** state) { \ + DocState* doc_state = *state; \ + AMstack** stack_ptr = &doc_state->base_state->stack; \ + AMobjType const obj_type = suffix_to_obj_type(#label); \ + AMobjId const* const obj_id = \ + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr(#label), obj_type), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ + assert_non_null(obj_id); \ + assert_int_equal(AMobjObjType(doc_state->doc, obj_id), obj_type); \ + assert_int_equal(AMobjSize(doc_state->doc, obj_id, NULL), 0); \ + AMresultFree(AMstackPop(stack_ptr, NULL)); \ + } -static void test_AMmapPutStr(void **state) { - GroupState* group_state = *state; - AMfree(AMmapPutStr(group_state->doc, AM_ROOT, AMstr("Str"), AMstr("Hello, world!"))); - AMbyteSpan const str = AMpush(&group_state->stack, - AMmapGet(group_state->doc, AM_ROOT, AMstr("Str"), NULL), - AM_VALUE_STR, - cmocka_cb).str; +static void test_AMmapPutStr(void** state) { + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->base_state->stack; + AMstackItem(NULL, AMmapPutStr(doc_state->doc, AM_ROOT, AMstr("Str"), AMstr("Hello, world!")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + AMbyteSpan str; + assert_true(AMitemToStr(AMstackItem(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, AMstr("Str"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)), + &str)); assert_int_equal(str.count, strlen("Hello, world!")); assert_memory_equal(str.src, "Hello, world!", str.count); - AMfree(AMpop(&group_state->stack)); + AMresultFree(AMstackPop(stack_ptr, NULL)); } -static_void_test_AMmapPut(Bool, boolean, true) +static_void_test_AMmapPut(Bool, bool, true); -static_void_test_AMmapPut(Counter, counter, INT64_MAX) +static_void_test_AMmapPut(Counter, int64_t, INT64_MAX); -static_void_test_AMmapPut(F64, f64, DBL_MAX) +static_void_test_AMmapPut(F64, double, DBL_MAX); -static_void_test_AMmapPut(Int, int_, INT64_MAX) +static_void_test_AMmapPut(Int, int64_t, INT64_MAX); -static_void_test_AMmapPutObject(List) +static_void_test_AMmapPutObject(List); -static_void_test_AMmapPutObject(Map) +static_void_test_AMmapPutObject(Map); -static_void_test_AMmapPutObject(Text) +static_void_test_AMmapPutObject(Text); -static_void_test_AMmapPutObject(Void) +static_void_test_AMmapPut(Timestamp, int64_t, INT64_MAX); -static_void_test_AMmapPut(Timestamp, timestamp, INT64_MAX) +static_void_test_AMmapPut(Uint, int64_t, UINT64_MAX); -static_void_test_AMmapPut(Uint, uint, UINT64_MAX) - -/** \brief A JavaScript application can introduce NUL (`\0`) characters into a - * map object's key which will truncate it in a C application. +/** + * \brief A JavaScript application can introduce NUL (`\0`) characters into + * a map object's key which will truncate it in a C application. */ static void test_get_NUL_key(void** state) { /* @@ -158,39 +147,37 @@ static void test_get_NUL_key(void** state) { doc['o\0ps'] = 'oops'; }); const bytes = Automerge.save(doc); - console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], bytes).join(", ") + "};"); + console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], + bytes).join(", ") + "};"); */ static uint8_t const OOPS_SRC[] = {'o', '\0', 'p', 's'}; static AMbyteSpan const OOPS_KEY = {.src = OOPS_SRC, .count = sizeof(OOPS_SRC) / sizeof(uint8_t)}; static uint8_t const SAVED_DOC[] = { - 133, 111, 74, 131, 233, 150, 60, 244, 0, 116, 1, 16, 223, 253, 146, - 193, 58, 122, 66, 134, 151, 225, 210, 51, 58, 86, 247, 8, 1, 49, 118, - 234, 228, 42, 116, 171, 13, 164, 99, 244, 27, 19, 150, 44, 201, 136, - 222, 219, 90, 246, 226, 123, 77, 120, 157, 155, 55, 182, 2, 178, 64, 6, - 1, 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 6, 33, 2, 35, 2, 52, 1, - 66, 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, 0, - 127, 7, 127, 4, 111, 0, 112, 115, 127, 0, 127, 1, 1, 127, 1, 127, 70, - 111, 111, 112, 115, 127, 0, 0 - }; + 133, 111, 74, 131, 233, 150, 60, 244, 0, 116, 1, 16, 223, 253, 146, 193, 58, 122, 66, 134, 151, + 225, 210, 51, 58, 86, 247, 8, 1, 49, 118, 234, 228, 42, 116, 171, 13, 164, 99, 244, 27, 19, + 150, 44, 201, 136, 222, 219, 90, 246, 226, 123, 77, 120, 157, 155, 55, 182, 2, 178, 64, 6, 1, + 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 6, 33, 2, 35, 2, 52, 1, 66, + 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, 0, 127, 7, 127, + 4, 111, 0, 112, 115, 127, 0, 127, 1, 1, 127, 1, 127, 70, 111, 111, 112, 115, 127, 0, 0}; static size_t const SAVED_DOC_SIZE = sizeof(SAVED_DOC) / sizeof(uint8_t); - AMresultStack* stack = *state; - AMdoc* const doc = AMpush(&stack, - AMload(SAVED_DOC, SAVED_DOC_SIZE), - AM_VALUE_DOC, - cmocka_cb).doc; - AMbyteSpan const str = AMpush(&stack, - AMmapGet(doc, AM_ROOT, OOPS_KEY, NULL), - AM_VALUE_STR, - cmocka_cb).str; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(SAVED_DOC, SAVED_DOC_SIZE), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMbyteSpan str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, OOPS_KEY, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_not_equal(OOPS_KEY.count, strlen(OOPS_KEY.src)); assert_int_equal(str.count, strlen("oops")); assert_memory_equal(str.src, "oops", str.count); } -/** \brief A JavaScript application can introduce NUL (`\0`) characters into a - * map object's string value which will truncate it in a C application. +/** + * \brief A JavaScript application can introduce NUL (`\0`) characters into a + * map object's string value which will truncate it in a C application. */ static void test_get_NUL_string_value(void** state) { /* @@ -200,1209 +187,1369 @@ static void test_get_NUL_string_value(void** state) { doc.oops = 'o\0ps'; }); const bytes = Automerge.save(doc); - console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], bytes).join(", ") + "};"); + console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], + bytes).join(", ") + "};"); */ static uint8_t const OOPS_VALUE[] = {'o', '\0', 'p', 's'}; static size_t const OOPS_SIZE = sizeof(OOPS_VALUE) / sizeof(uint8_t); static uint8_t const SAVED_DOC[] = { - 133, 111, 74, 131, 63, 94, 151, 29, 0, 116, 1, 16, 156, 159, 189, 12, - 125, 55, 71, 154, 136, 104, 237, 186, 45, 224, 32, 22, 1, 36, 163, - 164, 222, 81, 42, 1, 247, 231, 156, 54, 222, 76, 6, 109, 18, 172, 75, - 36, 118, 120, 68, 73, 87, 186, 230, 127, 68, 19, 81, 149, 185, 6, 1, - 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 6, 33, 2, 35, 2, 52, 1, - 66, 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, - 0, 127, 7, 127, 4, 111, 111, 112, 115, 127, 0, 127, 1, 1, 127, 1, 127, - 70, 111, 0, 112, 115, 127, 0, 0 - }; + 133, 111, 74, 131, 63, 94, 151, 29, 0, 116, 1, 16, 156, 159, 189, 12, 125, 55, 71, 154, 136, + 104, 237, 186, 45, 224, 32, 22, 1, 36, 163, 164, 222, 81, 42, 1, 247, 231, 156, 54, 222, 76, + 6, 109, 18, 172, 75, 36, 118, 120, 68, 73, 87, 186, 230, 127, 68, 19, 81, 149, 185, 6, 1, + 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 6, 33, 2, 35, 2, 52, 1, 66, + 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, 0, 127, 7, 127, + 4, 111, 111, 112, 115, 127, 0, 127, 1, 1, 127, 1, 127, 70, 111, 0, 112, 115, 127, 0, 0}; static size_t const SAVED_DOC_SIZE = sizeof(SAVED_DOC) / sizeof(uint8_t); - AMresultStack* stack = *state; - AMdoc* const doc = AMpush(&stack, - AMload(SAVED_DOC, SAVED_DOC_SIZE), - AM_VALUE_DOC, - cmocka_cb).doc; - AMbyteSpan const str = AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("oops"), NULL), - AM_VALUE_STR, - cmocka_cb).str; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(SAVED_DOC, SAVED_DOC_SIZE), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMbyteSpan str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("oops"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), + &str)); assert_int_not_equal(str.count, strlen(OOPS_VALUE)); assert_int_equal(str.count, OOPS_SIZE); assert_memory_equal(str.src, OOPS_VALUE, str.count); } static void test_range_iter_map(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("a"), 3)); - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("b"), 4)); - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("c"), 5)); - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("d"), 6)); - AMfree(AMcommit(doc, AMstr(NULL), NULL)); - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("a"), 7)); - AMfree(AMcommit(doc, AMstr(NULL), NULL)); - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("a"), 8)); - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("d"), 9)); - AMfree(AMcommit(doc, AMstr(NULL), NULL)); - AMactorId const* const actor_id = AMpush(&stack, - AMgetActorId(doc), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; - AMmapItems map_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - assert_int_equal(AMmapItemsSize(&map_items), 4); + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("a"), 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("b"), 4), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("c"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("d"), 6), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("a"), 7), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("a"), 8), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("d"), 9), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMactorId const* actor_id; + assert_true(AMitemToActorId(AMstackItem(stack_ptr, AMgetActorId(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMitems map_items = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)); + assert_int_equal(AMitemsSize(&map_items), 4); /* ["b"-"d") */ - AMmapItems range = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr("b"), AMstr("d"), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + AMitems range = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr("b"), AMstr("d"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)); /* First */ - AMmapItem const* next = AMmapItemsNext(&range, 1); + AMitem* next = AMitemsNext(&range, 1); assert_non_null(next); - AMbyteSpan key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "b", key.count); - AMvalue next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 4); - AMobjId const* next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + uint64_t uint; + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 4); + AMobjId const* next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "c", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 5); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 5); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - assert_null(AMmapItemsNext(&range, 1)); + assert_null(AMitemsNext(&range, 1)); /* ["b"-) */ - range = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr("b"), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + range = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr("b"), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)); /* First */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "b", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 4); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 4); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "c", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 5); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 5); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "d", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 9); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 9); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 7); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Fourth */ - assert_null(AMmapItemsNext(&range, 1)); + assert_null(AMitemsNext(&range, 1)); /* [-"d") */ - range = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr("d"), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + range = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr("d"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)); /* First */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "a", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 8); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 8); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 6); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "b", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 4); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 4); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "c", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 5); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 5); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Fourth */ - assert_null(AMmapItemsNext(&range, 1)); + assert_null(AMitemsNext(&range, 1)); /* ["a"-) */ - range = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr("a"), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + range = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr("a"), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)); /* First */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "a", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 8); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 8); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 6); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "b", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 4); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 4); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "c", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 5); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 5); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Fourth */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "d", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 9); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 9); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 7); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Fifth */ - assert_null(AMmapItemsNext(&range, 1)); + assert_null(AMitemsNext(&range, 1)); } static void test_map_range_back_and_forth_single(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMactorId const* const actor_id = AMpush(&stack, - AMgetActorId(doc), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMactorId const* actor_id; + assert_true(AMitemToActorId(AMstackItem(stack_ptr, AMgetActorId(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("1"), AMstr("a"))); - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("2"), AMstr("b"))); - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("3"), AMstr("c"))); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("1"), AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("2"), AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("3"), AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* Forward, back, back. */ - AMmapItems range_all = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + AMitems range_all = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); /* First */ - AMmapItem const* next = AMmapItemsNext(&range_all, 1); + AMitem* next = AMitemsNext(&range_all, 1); assert_non_null(next); - AMbyteSpan key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - AMvalue next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "a", next_value.str.count); - AMobjId const* next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + AMbyteSpan str; + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "a", str.count); + AMobjId const* next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - AMmapItems range_back_all = AMmapItemsReversed(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); - AMmapItem const* next_back = AMmapItemsNext(&range_back_all, 1); + AMitems range_back_all = AMitemsReversed(&range_all); + range_back_all = AMitemsRewound(&range_back_all); + AMitem* next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - AMvalue next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); - AMobjId const* next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + AMbyteSpan str_back; + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "c", str_back.count); + AMobjId const* next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "b", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "b", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Forward, back, forward. */ - range_all = AMmapItemsRewound(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); + range_all = AMitemsRewound(&range_all); + range_back_all = AMitemsRewound(&range_back_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "a", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "a", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "c", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "b", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "b", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Forward, forward, forward. */ - range_all = AMmapItemsRewound(&range_all); + range_all = AMitemsRewound(&range_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "a", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "a", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "b", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "b", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "c", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "c", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Forward stop */ - assert_null(AMmapItemsNext(&range_all, 1)); + assert_null(AMitemsNext(&range_all, 1)); /* Back, back, back. */ - range_back_all = AMmapItemsRewound(&range_back_all); + range_back_all = AMitemsRewound(&range_back_all); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "c", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "b", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "b", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* First */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "a", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "a", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Back stop */ - assert_null(AMmapItemsNext(&range_back_all, 1)); + assert_null(AMitemsNext(&range_back_all, 1)); } static void test_map_range_back_and_forth_double(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMactorId const* const actor_id1= AMpush(&stack, - AMactorIdInitBytes("\0", 1), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; - AMfree(AMsetActorId(doc1, actor_id1)); + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); + AMactorId const* actor_id1; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromBytes("\0", 1), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id1)); + AMstackItem(NULL, AMsetActorId(doc1, actor_id1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("1"), AMstr("a"))); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("2"), AMstr("b"))); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("3"), AMstr("c"))); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("1"), AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("2"), AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("3"), AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* The second actor should win all conflicts here. */ - AMdoc* const doc2 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMactorId const* const actor_id2 = AMpush(&stack, - AMactorIdInitBytes("\1", 1), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; - AMfree(AMsetActorId(doc2, actor_id2)); - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("1"), AMstr("aa"))); - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("2"), AMstr("bb"))); - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("3"), AMstr("cc"))); + AMdoc* doc2; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); + AMactorId const* actor_id2; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromBytes("\1", 1), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id2)); + AMstackItem(NULL, AMsetActorId(doc2, actor_id2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("1"), AMstr("aa")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("2"), AMstr("bb")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("3"), AMstr("cc")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMfree(AMmerge(doc1, doc2)); + AMstackItem(NULL, AMmerge(doc1, doc2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* Forward, back, back. */ - AMmapItems range_all = AMpush(&stack, - AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + AMitems range_all = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); /* First */ - AMmapItem const* next = AMmapItemsNext(&range_all, 1); + AMitem* next = AMitemsNext(&range_all, 1); assert_non_null(next); - AMbyteSpan key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - AMvalue next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "aa", next_value.str.count); - AMobjId const* next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + AMbyteSpan str; + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "aa", str.count); + AMobjId const* next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - AMmapItems range_back_all = AMmapItemsReversed(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); - AMmapItem const* next_back = AMmapItemsNext(&range_back_all, 1); + AMitems range_back_all = AMitemsReversed(&range_all); + range_back_all = AMitemsRewound(&range_back_all); + AMitem* next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - AMvalue next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); - AMobjId const* next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + AMbyteSpan str_back; + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "cc", str_back.count); + AMobjId const* next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "bb", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "bb", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Forward, back, forward. */ - range_all = AMmapItemsRewound(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); + range_all = AMitemsRewound(&range_all); + range_back_all = AMitemsRewound(&range_back_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "aa", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "aa", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "cc", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "bb", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "bb", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Forward, forward, forward. */ - range_all = AMmapItemsRewound(&range_all); + range_all = AMitemsRewound(&range_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "aa", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "aa", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "bb", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "bb", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "cc", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "cc", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Forward stop */ - assert_null(AMmapItemsNext(&range_all, 1)); + assert_null(AMitemsNext(&range_all, 1)); /* Back, back, back. */ - range_back_all = AMmapItemsRewound(&range_back_all); + range_back_all = AMitemsRewound(&range_back_all); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "cc", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "bb", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "bb", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* First */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "aa", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "aa", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Back stop */ - assert_null(AMmapItemsNext(&range_back_all, 1)); + assert_null(AMitemsNext(&range_back_all, 1)); } static void test_map_range_at_back_and_forth_single(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMactorId const* const actor_id = AMpush(&stack, - AMgetActorId(doc), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMactorId const* actor_id; + assert_true(AMitemToActorId(AMstackItem(stack_ptr, AMgetActorId(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("1"), AMstr("a"))); - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("2"), AMstr("b"))); - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("3"), AMstr("c"))); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("1"), AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("2"), AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("3"), AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMchangeHashes const heads = AMpush(&stack, - AMgetHeads(doc), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const heads = AMstackItems(stack_ptr, AMgetHeads(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* Forward, back, back. */ - AMmapItems range_all = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + AMitems range_all = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); /* First */ - AMmapItem const* next = AMmapItemsNext(&range_all, 1); + AMitem* next = AMitemsNext(&range_all, 1); assert_non_null(next); - AMbyteSpan key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - AMvalue next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "a", next_value.str.count); - AMobjId const* next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + AMbyteSpan str; + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "a", str.count); + AMobjId const* next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - AMmapItems range_back_all = AMmapItemsReversed(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); - AMmapItem const* next_back = AMmapItemsNext(&range_back_all, 1); + AMitems range_back_all = AMitemsReversed(&range_all); + range_back_all = AMitemsRewound(&range_back_all); + AMitem* next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - AMvalue next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); - AMobjId const* next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + AMbyteSpan str_back; + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "c", str_back.count); + AMobjId const* next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "b", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "b", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Forward, back, forward. */ - range_all = AMmapItemsRewound(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); + range_all = AMitemsRewound(&range_all); + range_back_all = AMitemsRewound(&range_back_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "a", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "a", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "c", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "b", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "b", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Forward, forward, forward. */ - range_all = AMmapItemsRewound(&range_all); + range_all = AMitemsRewound(&range_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "a", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "a", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "b", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "b", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "c", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "c", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Forward stop */ - assert_null(AMmapItemsNext(&range_all, 1)); + assert_null(AMitemsNext(&range_all, 1)); /* Back, back, back. */ - range_back_all = AMmapItemsRewound(&range_back_all); + range_back_all = AMitemsRewound(&range_back_all); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "c", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "b", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "b", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* First */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "a", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "a", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Back stop */ - assert_null(AMmapItemsNext(&range_back_all, 1)); + assert_null(AMitemsNext(&range_back_all, 1)); } static void test_map_range_at_back_and_forth_double(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMactorId const* const actor_id1= AMpush(&stack, - AMactorIdInitBytes("\0", 1), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; - AMfree(AMsetActorId(doc1, actor_id1)); + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); + AMactorId const* actor_id1; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromBytes("\0", 1), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id1)); + AMstackItem(NULL, AMsetActorId(doc1, actor_id1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("1"), AMstr("a"))); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("2"), AMstr("b"))); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("3"), AMstr("c"))); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("1"), AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("2"), AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("3"), AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* The second actor should win all conflicts here. */ - AMdoc* const doc2 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMactorId const* const actor_id2= AMpush(&stack, - AMactorIdInitBytes("\1", 1), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; - AMfree(AMsetActorId(doc2, actor_id2)); - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("1"), AMstr("aa"))); - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("2"), AMstr("bb"))); - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("3"), AMstr("cc"))); + AMdoc* doc2; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); + AMactorId const* actor_id2; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromBytes("\1", 1), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id2)); + AMstackItem(NULL, AMsetActorId(doc2, actor_id2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("1"), AMstr("aa")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("2"), AMstr("bb")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("3"), AMstr("cc")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMfree(AMmerge(doc1, doc2)); - AMchangeHashes const heads = AMpush(&stack, - AMgetHeads(doc1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMstackItem(NULL, AMmerge(doc1, doc2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems const heads = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* Forward, back, back. */ - AMmapItems range_all = AMpush(&stack, - AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + AMitems range_all = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); /* First */ - AMmapItem const* next = AMmapItemsNext(&range_all, 1); + AMitem* next = AMitemsNext(&range_all, 1); assert_non_null(next); - AMbyteSpan key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - AMvalue next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "aa", next_value.str.count); - AMobjId const* next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + AMbyteSpan str; + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "aa", str.count); + AMobjId const* next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - AMmapItems range_back_all = AMmapItemsReversed(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); - AMmapItem const* next_back = AMmapItemsNext(&range_back_all, 1); + AMitems range_back_all = AMitemsReversed(&range_all); + range_back_all = AMitemsRewound(&range_back_all); + AMitem* next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - AMvalue next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); - AMobjId const* next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + AMbyteSpan str_back; + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "cc", str_back.count); + AMobjId const* next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "bb", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "bb", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Forward, back, forward. */ - range_all = AMmapItemsRewound(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); + range_all = AMitemsRewound(&range_all); + range_back_all = AMitemsRewound(&range_back_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "aa", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "aa", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "cc", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "bb", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "bb", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Forward, forward, forward. */ - range_all = AMmapItemsRewound(&range_all); + range_all = AMitemsRewound(&range_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "aa", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "aa", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "bb", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "bb", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "cc", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "cc", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Forward stop */ - assert_null(AMmapItemsNext(&range_all, 1)); + assert_null(AMitemsNext(&range_all, 1)); /* Back, back, back. */ - range_back_all = AMmapItemsRewound(&range_back_all); + range_back_all = AMitemsRewound(&range_back_all); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "cc", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "bb", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "bb", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* First */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "aa", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "aa", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Back stop */ - assert_null(AMmapItemsNext(&range_back_all, 1)); + assert_null(AMitemsNext(&range_back_all, 1)); } static void test_get_range_values(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("aa"), AMstr("aaa"))); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("bb"), AMstr("bbb"))); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("cc"), AMstr("ccc"))); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("dd"), AMstr("ddd"))); - AMfree(AMcommit(doc1, AMstr(NULL), NULL)); + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("aa"), AMstr("aaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("bb"), AMstr("bbb")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("cc"), AMstr("ccc")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("dd"), AMstr("ddd")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc1, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMchangeHashes const v1 = AMpush(&stack, - AMgetHeads(doc1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMdoc* const doc2 = AMpush(&stack, AMfork(doc1, NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMitems const v1 = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMdoc* doc2; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMfork(doc1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("cc"), AMstr("ccc V2"))); - AMfree(AMcommit(doc1, AMstr(NULL), NULL)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("cc"), AMstr("ccc V2")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc1, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("cc"), AMstr("ccc V3"))); - AMfree(AMcommit(doc2, AMstr(NULL), NULL)); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("cc"), AMstr("ccc V3")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc2, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMfree(AMmerge(doc1, doc2)); + AMstackItem(NULL, AMmerge(doc1, doc2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMmapItems range = AMpush(&stack, - AMmapRange(doc1, AM_ROOT, AMstr("b"), AMstr("d"), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItems range_back = AMmapItemsReversed(&range); - assert_int_equal(AMmapItemsSize(&range), 2); + /* Forward vs. reverse: complete current map range. */ + AMitems range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + size_t size = AMitemsSize(&range); + assert_int_equal(size, 4); + AMitems range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + AMbyteSpan key; + assert_true(AMitemKey(AMitemsNext(&range, 1), &key)); + assert_memory_equal(key.src, "aa", key.count); + assert_true(AMitemKey(AMitemsNext(&range_back, 1), &key)); + assert_memory_equal(key.src, "dd", key.count); - AMmapItem const* map_item = NULL; - while ((map_item = AMmapItemsNext(&range, 1)) != NULL) { - AMvalue const val1 = AMmapItemValue(map_item); - AMresult* result = AMmapGet(doc1, AM_ROOT, AMmapItemKey(map_item), NULL); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMmapItemObjId(map_item)); - AMfree(result); + AMitem *item1, *item_back1; + size_t count, middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + AMbyteSpan key1, key_back1; + assert_true(AMitemKey(item1, &key1)); + assert_true(AMitemKey(item_back1, &key_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(AMstrCmp(key1, key_back1), 0); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(AMstrCmp(key1, key_back1), 0); + } + AMitem* item2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key1, NULL), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key_back1, NULL), NULL, NULL); + /** \note An item returned from an `AM...Get()` call doesn't include the + index used to retrieve it. */ + assert_false(AMitemIdxType(item2)); + assert_false(AMitemIdxType(item_back2)); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - assert_int_equal(AMmapItemsSize(&range_back), 2); + /* Forward vs. reverse: partial current map range. */ + range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr("aa"), AMstr("dd"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + size = AMitemsSize(&range); + assert_int_equal(size, 3); + range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + assert_true(AMitemKey(AMitemsNext(&range, 1), &key)); + assert_memory_equal(key.src, "aa", key.count); + assert_true(AMitemKey(AMitemsNext(&range_back, 1), &key)); + assert_memory_equal(key.src, "cc", key.count); - while ((map_item = AMmapItemsNext(&range_back, 1)) != NULL) { - AMvalue const val1 = AMmapItemValue(map_item); - AMresult* result = AMmapGet(doc1, AM_ROOT, AMmapItemKey(map_item), NULL); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMmapItemObjId(map_item)); - AMfree(result); + middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + AMbyteSpan key1, key_back1; + assert_true(AMitemKey(item1, &key1)); + assert_true(AMitemKey(item_back1, &key_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(AMstrCmp(key1, key_back1), 0); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(AMstrCmp(key1, key_back1), 0); + } + AMitem* item2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key1, NULL), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key_back1, NULL), NULL, NULL); + /** \note An item returned from an `AM...Get()` call doesn't include the + index used to retrieve it. */ + assert_false(AMitemIdxType(item2)); + assert_false(AMitemIdxType(item_back2)); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item_back1, item_back2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - range = AMpush(&stack, - AMmapRange(doc1, AM_ROOT, AMstr("b"), AMstr("d"), &v1), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - range_back = AMmapItemsReversed(&range); - assert_int_equal(AMmapItemsSize(&range), 2); + /* Forward vs. reverse: complete historical map range. */ + range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), &v1), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + size = AMitemsSize(&range); + assert_int_equal(size, 4); + range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + assert_true(AMitemKey(AMitemsNext(&range, 1), &key)); + assert_memory_equal(key.src, "aa", key.count); + assert_true(AMitemKey(AMitemsNext(&range_back, 1), &key)); + assert_memory_equal(key.src, "dd", key.count); - while ((map_item = AMmapItemsNext(&range, 1)) != NULL) { - AMvalue const val1 = AMmapItemValue(map_item); - AMresult* result = AMmapGet(doc1, AM_ROOT, AMmapItemKey(map_item), &v1); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMmapItemObjId(map_item)); - AMfree(result); + middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + AMbyteSpan key1, key_back1; + assert_true(AMitemKey(item1, &key1)); + assert_true(AMitemKey(item_back1, &key_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(AMstrCmp(key1, key_back1), 0); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(AMstrCmp(key1, key_back1), 0); + } + AMitem* item2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key1, &v1), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key_back1, &v1), NULL, NULL); + /** \note An item returned from an `AM...Get()` call doesn't include the + index used to retrieve it. */ + assert_false(AMitemIdxType(item2)); + assert_false(AMitemIdxType(item_back2)); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item_back1, item_back2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - assert_int_equal(AMmapItemsSize(&range_back), 2); + /* Forward vs. reverse: partial historical map range. */ + range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr("bb"), AMstr(NULL), &v1), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + size = AMitemsSize(&range); + assert_int_equal(size, 3); + range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + assert_true(AMitemKey(AMitemsNext(&range, 1), &key)); + assert_memory_equal(key.src, "bb", key.count); + assert_true(AMitemKey(AMitemsNext(&range_back, 1), &key)); + assert_memory_equal(key.src, "dd", key.count); - while ((map_item = AMmapItemsNext(&range_back, 1)) != NULL) { - AMvalue const val1 = AMmapItemValue(map_item); - AMresult* result = AMmapGet(doc1, AM_ROOT, AMmapItemKey(map_item), &v1); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMmapItemObjId(map_item)); - AMfree(result); + middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + AMbyteSpan key1, key_back1; + assert_true(AMitemKey(item1, &key1)); + assert_true(AMitemKey(item_back1, &key_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(AMstrCmp(key1, key_back1), 0); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(AMstrCmp(key1, key_back1), 0); + } + AMitem* item2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key1, &v1), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key_back1, &v1), NULL, NULL); + /** \note An item returned from an `AM...Get()` call doesn't include the + index used to retrieve it. */ + assert_false(AMitemIdxType(item2)); + assert_false(AMitemIdxType(item_back2)); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item_back1, item_back2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - range = AMpush(&stack, - AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMobjItems values = AMpush(&stack, - AMobjValues(doc1, AM_ROOT, NULL), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; - assert_int_equal(AMmapItemsSize(&range), AMobjItemsSize(&values)); - AMobjItem const* value = NULL; - while ((map_item = AMmapItemsNext(&range, 1)) != NULL && - (value = AMobjItemsNext(&values, 1)) != NULL) { - AMvalue const val1 = AMmapItemValue(map_item); - AMvalue const val2 = AMobjItemValue(value); - assert_true(AMvalueEqual(&val1, &val2)); - assert_true(AMobjIdEqual(AMmapItemObjId(map_item), AMobjItemObjId(value))); + /* Map range vs. object range: complete current. */ + range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + AMitems obj_items = AMstackItems(stack_ptr, AMobjItems(doc1, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&range), AMitemsSize(&obj_items)); + + AMitem *item, *obj_item; + for (item = NULL, obj_item = NULL; item && obj_item; + item = AMitemsNext(&range, 1), obj_item = AMitemsNext(&obj_items, 1)) { + /** \note Object iteration doesn't yield any item indices. */ + assert_true(AMitemIdxType(item)); + assert_false(AMitemIdxType(obj_item)); + assert_true(AMitemEqual(item, obj_item)); + assert_true(AMobjIdEqual(AMitemObjId(item), AMitemObjId(obj_item))); } - range = AMpush(&stack, - AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), &v1), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - values = AMpush(&stack, - AMobjValues(doc1, AM_ROOT, &v1), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; - assert_int_equal(AMmapItemsSize(&range), AMobjItemsSize(&values)); - while ((map_item = AMmapItemsNext(&range, 1)) != NULL && - (value = AMobjItemsNext(&values, 1)) != NULL) { - AMvalue const val1 = AMmapItemValue(map_item); - AMvalue const val2 = AMobjItemValue(value); - assert_true(AMvalueEqual(&val1, &val2)); - assert_true(AMobjIdEqual(AMmapItemObjId(map_item), AMobjItemObjId(value))); + /* Map range vs. object range: complete historical. */ + range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), &v1), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + obj_items = AMstackItems(stack_ptr, AMobjItems(doc1, AM_ROOT, &v1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&range), AMitemsSize(&obj_items)); + + for (item = NULL, obj_item = NULL; item && obj_item; + item = AMitemsNext(&range, 1), obj_item = AMitemsNext(&obj_items, 1)) { + /** \note Object iteration doesn't yield any item indices. */ + assert_true(AMitemIdxType(item)); + assert_false(AMitemIdxType(obj_item)); + assert_true(AMitemEqual(item, obj_item)); + assert_true(AMobjIdEqual(AMitemObjId(item), AMitemObjId(obj_item))); } } @@ -1418,19 +1565,18 @@ int run_map_tests(void) { cmocka_unit_test(test_AMmapPutObject(List)), cmocka_unit_test(test_AMmapPutObject(Map)), cmocka_unit_test(test_AMmapPutObject(Text)), - cmocka_unit_test(test_AMmapPutObject(Void)), cmocka_unit_test(test_AMmapPutStr), cmocka_unit_test(test_AMmapPut(Timestamp)), cmocka_unit_test(test_AMmapPut(Uint)), - cmocka_unit_test_setup_teardown(test_get_NUL_key, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_get_NUL_string_value, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_range_iter_map, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_map_range_back_and_forth_single, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_map_range_back_and_forth_double, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_map_range_at_back_and_forth_single, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_map_range_at_back_and_forth_double, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_get_range_values, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_get_NUL_key, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_get_NUL_string_value, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_range_iter_map, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_map_range_back_and_forth_single, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_map_range_back_and_forth_double, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_map_range_at_back_and_forth_single, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_map_range_at_back_and_forth_double, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_get_range_values, setup_base, teardown_base), }; - return cmocka_run_group_tests(tests, group_setup, group_teardown); + return cmocka_run_group_tests(tests, setup_doc, teardown_doc); } diff --git a/rust/automerge-c/test/ported_wasm/basic_tests.c b/rust/automerge-c/test/ported_wasm/basic_tests.c index e2659d62..b83ff132 100644 --- a/rust/automerge-c/test/ported_wasm/basic_tests.c +++ b/rust/automerge-c/test/ported_wasm/basic_tests.c @@ -11,7 +11,10 @@ /* local */ #include -#include "../stack_utils.h" +#include +#include +#include "../base_state.h" +#include "../cmocka_utils.h" /** * \brief default import init() should return a promise @@ -22,163 +25,171 @@ static void test_default_import_init_should_return_a_promise(void** state); * \brief should create, clone and free */ static void test_create_clone_and_free(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc1 = create() */ - AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); /* const doc2 = doc1.clone() */ - AMdoc* const doc2 = AMpush(&stack, AMclone(doc1), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc2; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMclone(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); } /** * \brief should be able to start and commit */ static void test_start_and_commit(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* doc.commit() */ - AMpush(&stack, AMemptyChange(doc, AMstr(NULL), NULL), AM_VALUE_CHANGE_HASHES, cmocka_cb); + AMstackItems(stack_ptr, AMemptyChange(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); } /** * \brief getting a nonexistent prop does not throw an error */ static void test_getting_a_nonexistent_prop_does_not_throw_an_error(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root" */ /* const result = doc.getWithType(root, "hello") */ /* assert.deepEqual(result, undefined) */ - AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("hello"), NULL), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("hello"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); } /** * \brief should be able to set and get a simple value */ static void test_should_be_able_to_set_and_get_a_simple_value(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc: Automerge = create("aabbcc") */ - AMdoc* const doc = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aabbcc")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aabbcc")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root" */ /* let result */ /* */ /* doc.put(root, "hello", "world") */ - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("hello"), AMstr("world"))); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("hello"), AMstr("world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "number1", 5, "uint") */ - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("number1"), 5)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("number1"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "number2", 5) */ - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("number2"), 5)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("number2"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "number3", 5.5) */ - AMfree(AMmapPutF64(doc, AM_ROOT, AMstr("number3"), 5.5)); + AMstackItem(NULL, AMmapPutF64(doc, AM_ROOT, AMstr("number3"), 5.5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "number4", 5.5, "f64") */ - AMfree(AMmapPutF64(doc, AM_ROOT, AMstr("number4"), 5.5)); + AMstackItem(NULL, AMmapPutF64(doc, AM_ROOT, AMstr("number4"), 5.5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "number5", 5.5, "int") */ - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("number5"), 5.5)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("number5"), 5.5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "bool", true) */ - AMfree(AMmapPutBool(doc, AM_ROOT, AMstr("bool"), true)); + AMstackItem(NULL, AMmapPutBool(doc, AM_ROOT, AMstr("bool"), true), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "time1", 1000, "timestamp") */ - AMfree(AMmapPutTimestamp(doc, AM_ROOT, AMstr("time1"), 1000)); + AMstackItem(NULL, AMmapPutTimestamp(doc, AM_ROOT, AMstr("time1"), 1000), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "time2", new Date(1001)) */ - AMfree(AMmapPutTimestamp(doc, AM_ROOT, AMstr("time2"), 1001)); + AMstackItem(NULL, AMmapPutTimestamp(doc, AM_ROOT, AMstr("time2"), 1001), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.putObject(root, "list", []); */ - AMfree(AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST)); + AMstackItem(NULL, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); /* doc.put(root, "null", null) */ - AMfree(AMmapPutNull(doc, AM_ROOT, AMstr("null"))); + AMstackItem(NULL, AMmapPutNull(doc, AM_ROOT, AMstr("null")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* result = doc.getWithType(root, "hello") */ /* assert.deepEqual(result, ["str", "world"]) */ /* assert.deepEqual(doc.get("/", "hello"), "world") */ - AMbyteSpan str = AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("hello"), NULL), - AM_VALUE_STR, - cmocka_cb).str; + AMbyteSpan str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("hello"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), + &str)); assert_int_equal(str.count, strlen("world")); assert_memory_equal(str.src, "world", str.count); /* assert.deepEqual(doc.get("/", "hello"), "world") */ /* */ /* result = doc.getWithType(root, "number1") */ /* assert.deepEqual(result, ["uint", 5]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("number1"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, 5); + uint64_t uint; + assert_true(AMitemToUint( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("number1"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT)), + &uint)); + assert_int_equal(uint, 5); /* assert.deepEqual(doc.get("/", "number1"), 5) */ /* */ /* result = doc.getWithType(root, "number2") */ /* assert.deepEqual(result, ["int", 5]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("number2"), NULL), - AM_VALUE_INT, - cmocka_cb).int_, 5); + int64_t int_; + assert_true(AMitemToInt( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("number2"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_INT)), + &int_)); + assert_int_equal(int_, 5); /* */ /* result = doc.getWithType(root, "number3") */ /* assert.deepEqual(result, ["f64", 5.5]) */ - assert_float_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("number3"), NULL), - AM_VALUE_F64, - cmocka_cb).f64, 5.5, DBL_EPSILON); + double f64; + assert_true(AMitemToF64( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("number3"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_F64)), + &f64)); + assert_float_equal(f64, 5.5, DBL_EPSILON); /* */ /* result = doc.getWithType(root, "number4") */ /* assert.deepEqual(result, ["f64", 5.5]) */ - assert_float_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("number4"), NULL), - AM_VALUE_F64, - cmocka_cb).f64, 5.5, DBL_EPSILON); + assert_true(AMitemToF64( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("number4"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_F64)), + &f64)); + assert_float_equal(f64, 5.5, DBL_EPSILON); /* */ /* result = doc.getWithType(root, "number5") */ /* assert.deepEqual(result, ["int", 5]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("number5"), NULL), - AM_VALUE_INT, - cmocka_cb).int_, 5); + assert_true(AMitemToInt( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("number5"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_INT)), + &int_)); + assert_int_equal(int_, 5); /* */ /* result = doc.getWithType(root, "bool") */ /* assert.deepEqual(result, ["boolean", true]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("bool"), NULL), - AM_VALUE_BOOLEAN, - cmocka_cb).boolean, true); + bool boolean; + assert_true(AMitemToBool( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("bool"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BOOL)), + &boolean)); + assert_true(boolean); /* */ /* doc.put(root, "bool", false, "boolean") */ - AMfree(AMmapPutBool(doc, AM_ROOT, AMstr("bool"), false)); + AMstackItem(NULL, AMmapPutBool(doc, AM_ROOT, AMstr("bool"), false), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* result = doc.getWithType(root, "bool") */ /* assert.deepEqual(result, ["boolean", false]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("bool"), NULL), - AM_VALUE_BOOLEAN, - cmocka_cb).boolean, false); + assert_true(AMitemToBool( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("bool"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BOOL)), + &boolean)); + assert_false(boolean); /* */ /* result = doc.getWithType(root, "time1") */ /* assert.deepEqual(result, ["timestamp", new Date(1000)]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("time1"), NULL), - AM_VALUE_TIMESTAMP, - cmocka_cb).timestamp, 1000); + int64_t timestamp; + assert_true(AMitemToTimestamp(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("time1"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_TIMESTAMP)), + ×tamp)); + assert_int_equal(timestamp, 1000); /* */ /* result = doc.getWithType(root, "time2") */ /* assert.deepEqual(result, ["timestamp", new Date(1001)]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("time2"), NULL), - AM_VALUE_TIMESTAMP, - cmocka_cb).timestamp, 1001); + assert_true(AMitemToTimestamp(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("time2"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_TIMESTAMP)), + ×tamp)); + assert_int_equal(timestamp, 1001); /* */ /* result = doc.getWithType(root, "list") */ /* assert.deepEqual(result, ["list", "10@aabbcc"]); */ - AMobjId const* const list = AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("list"), NULL), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const list = AMitemObjId( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("list"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); assert_int_equal(AMobjIdCounter(list), 10); str = AMactorIdStr(AMobjIdActorId(list)); assert_int_equal(str.count, strlen("aabbcc")); @@ -186,38 +197,39 @@ static void test_should_be_able_to_set_and_get_a_simple_value(void** state) { /* */ /* result = doc.getWithType(root, "null") */ /* assert.deepEqual(result, ["null", null]); */ - AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("null"), NULL), - AM_VALUE_NULL, - cmocka_cb); + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("null"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_NULL)); } /** * \brief should be able to use bytes */ static void test_should_be_able_to_use_bytes(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* doc.put("_root", "data1", new Uint8Array([10, 11, 12])); */ static uint8_t const DATA1[] = {10, 11, 12}; - AMfree(AMmapPutBytes(doc, AM_ROOT, AMstr("data1"), AMbytes(DATA1, sizeof(DATA1)))); + AMstackItem(NULL, AMmapPutBytes(doc, AM_ROOT, AMstr("data1"), AMbytes(DATA1, sizeof(DATA1))), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* doc.put("_root", "data2", new Uint8Array([13, 14, 15]), "bytes"); */ static uint8_t const DATA2[] = {13, 14, 15}; - AMfree(AMmapPutBytes(doc, AM_ROOT, AMstr("data2"), AMbytes(DATA2, sizeof(DATA2)))); + AMstackItem(NULL, AMmapPutBytes(doc, AM_ROOT, AMstr("data2"), AMbytes(DATA2, sizeof(DATA2))), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* const value1 = doc.getWithType("_root", "data1") */ - AMbyteSpan const value1 = AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("data1"), NULL), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan value1; + assert_true(AMitemToBytes( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("data1"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), + &value1)); /* assert.deepEqual(value1, ["bytes", new Uint8Array([10, 11, 12])]); */ assert_int_equal(value1.count, sizeof(DATA1)); assert_memory_equal(value1.src, DATA1, sizeof(DATA1)); /* const value2 = doc.getWithType("_root", "data2") */ - AMbyteSpan const value2 = AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("data2"), NULL), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan value2; + assert_true(AMitemToBytes( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("data2"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), + &value2)); /* assert.deepEqual(value2, ["bytes", new Uint8Array([13, 14, 15])]); */ assert_int_equal(value2.count, sizeof(DATA2)); assert_memory_equal(value2.src, DATA2, sizeof(DATA2)); @@ -227,103 +239,92 @@ static void test_should_be_able_to_use_bytes(void** state) { * \brief should be able to make subobjects */ static void test_should_be_able_to_make_subobjects(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root" */ /* let result */ /* */ /* const submap = doc.putObject(root, "submap", {}) */ - AMobjId const* const submap = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("submap"), AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const submap = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("submap"), AM_OBJ_TYPE_MAP), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* doc.put(submap, "number", 6, "uint") */ - AMfree(AMmapPutUint(doc, submap, AMstr("number"), 6)); + AMstackItem(NULL, AMmapPutUint(doc, submap, AMstr("number"), 6), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.strictEqual(doc.pendingOps(), 2) */ assert_int_equal(AMpendingOps(doc), 2); /* */ /* result = doc.getWithType(root, "submap") */ /* assert.deepEqual(result, ["map", submap]) */ - assert_true(AMobjIdEqual(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("submap"), NULL), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id, + assert_true(AMobjIdEqual(AMitemObjId(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("submap"), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))), submap)); /* */ /* result = doc.getWithType(submap, "number") */ /* assert.deepEqual(result, ["uint", 6]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, submap, AMstr("number"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, - 6); + uint64_t uint; + assert_true(AMitemToUint( + AMstackItem(stack_ptr, AMmapGet(doc, submap, AMstr("number"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT)), + &uint)); + assert_int_equal(uint, 6); } /** * \brief should be able to make lists */ static void test_should_be_able_to_make_lists(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root" */ /* */ /* const sublist = doc.putObject(root, "numbers", []) */ - AMobjId const* const sublist = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("numbers"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const sublist = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("numbers"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* doc.insert(sublist, 0, "a"); */ - AMfree(AMlistPutStr(doc, sublist, 0, true, AMstr("a"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 0, true, AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.insert(sublist, 1, "b"); */ - AMfree(AMlistPutStr(doc, sublist, 1, true, AMstr("b"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 1, true, AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.insert(sublist, 2, "c"); */ - AMfree(AMlistPutStr(doc, sublist, 2, true, AMstr("c"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 2, true, AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.insert(sublist, 0, "z"); */ - AMfree(AMlistPutStr(doc, sublist, 0, true, AMstr("z"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 0, true, AMstr("z")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* assert.deepEqual(doc.getWithType(sublist, 0), ["str", "z"]) */ - AMbyteSpan str = AMpush(&stack, - AMlistGet(doc, sublist, 0, NULL), - AM_VALUE_STR, - cmocka_cb).str; + AMbyteSpan str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, sublist, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "z", str.count); /* assert.deepEqual(doc.getWithType(sublist, 1), ["str", "a"]) */ - str = AMpush(&stack, - AMlistGet(doc, sublist, 1, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, sublist, 1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); /* assert.deepEqual(doc.getWithType(sublist, 2), ["str", "b"]) */ - str = AMpush(&stack, - AMlistGet(doc, sublist, 2, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, sublist, 2, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); /* assert.deepEqual(doc.getWithType(sublist, 3), ["str", "c"]) */ - str = AMpush(&stack, - AMlistGet(doc, sublist, 3, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, sublist, 3, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); /* assert.deepEqual(doc.length(sublist), 4) */ assert_int_equal(AMobjSize(doc, sublist, NULL), 4); /* */ /* doc.put(sublist, 2, "b v2"); */ - AMfree(AMlistPutStr(doc, sublist, 2, false, AMstr("b v2"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 2, false, AMstr("b v2")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* assert.deepEqual(doc.getWithType(sublist, 2), ["str", "b v2"]) */ - str = AMpush(&stack, - AMlistGet(doc, sublist, 2, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, sublist, 2, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "b v2", str.count); /* assert.deepEqual(doc.length(sublist), 4) */ @@ -334,233 +335,217 @@ static void test_should_be_able_to_make_lists(void** state) { * \brief lists have insert, set, splice, and push ops */ static void test_lists_have_insert_set_splice_and_push_ops(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root" */ /* */ /* const sublist = doc.putObject(root, "letters", []) */ - AMobjId const* const sublist = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("letters"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const sublist = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("letters"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* doc.insert(sublist, 0, "a"); */ - AMfree(AMlistPutStr(doc, sublist, 0, true, AMstr("a"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 0, true, AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.insert(sublist, 0, "b"); */ - AMfree(AMlistPutStr(doc, sublist, 0, true, AMstr("b"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 0, true, AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.materialize(), { letters: ["b", "a"] }) */ - AMmapItems doc_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItem const* doc_item = AMmapItemsNext(&doc_items, 1); - AMbyteSpan key = AMmapItemKey(doc_item); + AMitem* doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&list_items), 2); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - assert_null(AMlistItemsNext(&list_items, 1)); + assert_null(AMitemsNext(&list_items, 1)); } /* doc.push(sublist, "c"); */ - AMfree(AMlistPutStr(doc, sublist, SIZE_MAX, true, AMstr("c"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, SIZE_MAX, true, AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const heads = doc.getHeads() */ - AMchangeHashes const heads = AMpush(&stack, - AMgetHeads(doc), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const heads = AMstackItems(stack_ptr, AMgetHeads(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* assert.deepEqual(doc.materialize(), { letters: ["b", "a", "c"] }) */ - doc_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&list_items), 3); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - assert_null(AMlistItemsNext(&list_items, 1)); + assert_null(AMitemsNext(&list_items, 1)); } /* doc.push(sublist, 3, "timestamp"); */ - AMfree(AMlistPutTimestamp(doc, sublist, SIZE_MAX, true, 3)); - /* assert.deepEqual(doc.materialize(), { letters: ["b", "a", "c", new Date(3)] } */ - doc_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + AMstackItem(NULL, AMlistPutTimestamp(doc, sublist, SIZE_MAX, true, 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + /* assert.deepEqual(doc.materialize(), { letters: ["b", "a", "c", new + * Date(3)] } */ + doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR | AM_VAL_TYPE_TIMESTAMP)); + assert_int_equal(AMitemsSize(&list_items), 4); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).timestamp, - 3); - assert_null(AMlistItemsNext(&list_items, 1)); + int64_t timestamp; + assert_true(AMitemToTimestamp(AMitemsNext(&list_items, 1), ×tamp)); + assert_int_equal(timestamp, 3); + assert_null(AMitemsNext(&list_items, 1)); } /* doc.splice(sublist, 1, 1, ["d", "e", "f"]); */ - static AMvalue const DATA[] = {{.str_tag = AM_VALUE_STR, .str = {.src = "d", .count = 1}}, - {.str_tag = AM_VALUE_STR, .str = {.src = "e", .count = 1}}, - {.str_tag = AM_VALUE_STR, .str = {.src = "f", .count = 1}}}; - AMfree(AMsplice(doc, sublist, 1, 1, DATA, sizeof(DATA)/sizeof(AMvalue))); - /* assert.deepEqual(doc.materialize(), { letters: ["b", "d", "e", "f", "c", new Date(3)] } */ - doc_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + AMresult* data = AMstackResult( + stack_ptr, AMresultFrom(3, AMitemFromStr(AMstr("d")), AMitemFromStr(AMstr("e")), AMitemFromStr(AMstr("f"))), + NULL, NULL); + AMstackItem(NULL, AMsplice(doc, sublist, 1, 1, AMresultItems(data)), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + /* assert.deepEqual(doc.materialize(), { letters: ["b", "d", "e", "f", "c", + * new Date(3)] } */ + doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR | AM_VAL_TYPE_TIMESTAMP)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "d", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "e", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "f", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).timestamp, - 3); - assert_null(AMlistItemsNext(&list_items, 1)); + int64_t timestamp; + assert_true(AMitemToTimestamp(AMitemsNext(&list_items, 1), ×tamp)); + assert_int_equal(timestamp, 3); + assert_null(AMitemsNext(&list_items, 1)); } /* doc.put(sublist, 0, "z"); */ - AMfree(AMlistPutStr(doc, sublist, 0, false, AMstr("z"))); - /* assert.deepEqual(doc.materialize(), { letters: ["z", "d", "e", "f", "c", new Date(3)] } */ - doc_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 0, false, AMstr("z")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + /* assert.deepEqual(doc.materialize(), { letters: ["z", "d", "e", "f", "c", + * new Date(3)] } */ + doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR | AM_VAL_TYPE_TIMESTAMP)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "z", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "d", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "e", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "f", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).timestamp, - 3); - assert_null(AMlistItemsNext(&list_items, 1)); + int64_t timestamp; + assert_true(AMitemToTimestamp(AMitemsNext(&list_items, 1), ×tamp)); + assert_int_equal(timestamp, 3); + assert_null(AMitemsNext(&list_items, 1)); } - /* assert.deepEqual(doc.materialize(sublist), ["z", "d", "e", "f", "c", new Date(3)] */ - AMlistItems sublist_items = AMpush( - &stack, - AMlistRange(doc, sublist, 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).str; + /* assert.deepEqual(doc.materialize(sublist), ["z", "d", "e", "f", "c", new + * Date(3)] */ + AMitems sublist_items = AMstackItems(stack_ptr, AMlistRange(doc, sublist, 0, SIZE_MAX, NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR | AM_VAL_TYPE_TIMESTAMP)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&sublist_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "z", str.count); - str = AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&sublist_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "d", str.count); - str = AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&sublist_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "e", str.count); - str = AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&sublist_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "f", str.count); - str = AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&sublist_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - assert_int_equal(AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).timestamp, - 3); - assert_null(AMlistItemsNext(&sublist_items, 1)); + int64_t timestamp; + assert_true(AMitemToTimestamp(AMitemsNext(&sublist_items, 1), ×tamp)); + assert_int_equal(timestamp, 3); + assert_null(AMitemsNext(&sublist_items, 1)); /* assert.deepEqual(doc.length(sublist), 6) */ assert_int_equal(AMobjSize(doc, sublist, NULL), 6); - /* assert.deepEqual(doc.materialize("/", heads), { letters: ["b", "a", "c"] } */ - doc_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + /* assert.deepEqual(doc.materialize("/", heads), { letters: ["b", "a", "c"] + * } */ + doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, &heads), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, &heads), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - assert_null(AMlistItemsNext(&list_items, 1)); + assert_null(AMitemsNext(&list_items, 1)); } } @@ -568,67 +553,54 @@ static void test_lists_have_insert_set_splice_and_push_ops(void** state) { * \brief should be able to delete non-existent props */ static void test_should_be_able_to_delete_non_existent_props(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* */ /* doc.put("_root", "foo", "bar") */ - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("foo"), AMstr("bar"))); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("foo"), AMstr("bar")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put("_root", "bip", "bap") */ - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("bip"), AMstr("bap"))); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("bip"), AMstr("bap")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const hash1 = doc.commit() */ - AMchangeHashes const hash1 = AMpush(&stack, - AMcommit(doc, AMstr(NULL), NULL), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const hash1 = + AMstackItems(stack_ptr, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* assert.deepEqual(doc.keys("_root"), ["bip", "foo"]) */ - AMstrs keys = AMpush(&stack, - AMkeys(doc, AM_ROOT, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - AMbyteSpan str = AMstrsNext(&keys, 1); + AMitems keys = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bip", str.count); - str = AMstrsNext(&keys, 1); + assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "foo", str.count); /* */ /* doc.delete("_root", "foo") */ - AMfree(AMmapDelete(doc, AM_ROOT, AMstr("foo"))); + AMstackItem(NULL, AMmapDelete(doc, AM_ROOT, AMstr("foo")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.delete("_root", "baz") */ - AMfree(AMmapDelete(doc, AM_ROOT, AMstr("baz"))); + AMstackItem(NULL, AMmapDelete(doc, AM_ROOT, AMstr("baz")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const hash2 = doc.commit() */ - AMchangeHashes const hash2 = AMpush(&stack, - AMcommit(doc, AMstr(NULL), NULL), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const hash2 = + AMstackItems(stack_ptr, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* assert.deepEqual(doc.keys("_root"), ["bip"]) */ - keys = AMpush(&stack, - AMkeys(doc, AM_ROOT, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - str = AMstrsNext(&keys, 1); + keys = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bip", str.count); /* assert.deepEqual(doc.keys("_root", [hash1]), ["bip", "foo"]) */ - keys = AMpush(&stack, - AMkeys(doc, AM_ROOT, &hash1), - AM_VALUE_STRS, - cmocka_cb).strs; - str = AMstrsNext(&keys, 1); + keys = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, &hash1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bip", str.count); - str = AMstrsNext(&keys, 1); + assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "foo", str.count); /* assert.deepEqual(doc.keys("_root", [hash2]), ["bip"]) */ - keys = AMpush(&stack, - AMkeys(doc, AM_ROOT, &hash2), - AM_VALUE_STRS, - cmocka_cb).strs; - str = AMstrsNext(&keys, 1); + keys = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, &hash2), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bip", str.count); } @@ -636,123 +608,114 @@ static void test_should_be_able_to_delete_non_existent_props(void** state) { /** * \brief should be able to del */ -static void test_should_be_able_to_del(void **state) { - AMresultStack* stack = *state; +static void test_should_be_able_to_del(void** state) { + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root" */ /* */ /* doc.put(root, "xxx", "xxx"); */ - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("xxx"), AMstr("xxx"))); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("xxx"), AMstr("xxx")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.getWithType(root, "xxx"), ["str", "xxx"]) */ - AMbyteSpan const str = AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("xxx"), NULL), - AM_VALUE_STR, - cmocka_cb).str; + AMbyteSpan str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("xxx"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), + &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "xxx", str.count); /* doc.delete(root, "xxx"); */ - AMfree(AMmapDelete(doc, AM_ROOT, AMstr("xxx"))); + AMstackItem(NULL, AMmapDelete(doc, AM_ROOT, AMstr("xxx")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.getWithType(root, "xxx"), undefined) */ - AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("xxx"), NULL), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMmapGet(doc, AM_ROOT, AMstr("xxx"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); } /** * \brief should be able to use counters */ static void test_should_be_able_to_use_counters(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root" */ /* */ /* doc.put(root, "counter", 10, "counter"); */ - AMfree(AMmapPutCounter(doc, AM_ROOT, AMstr("counter"), 10)); + AMstackItem(NULL, AMmapPutCounter(doc, AM_ROOT, AMstr("counter"), 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.getWithType(root, "counter"), ["counter", 10]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), - AM_VALUE_COUNTER, - cmocka_cb).counter, 10); + int64_t counter; + assert_true(AMitemToCounter(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_COUNTER)), + &counter)); + assert_int_equal(counter, 10); /* doc.increment(root, "counter", 10); */ - AMfree(AMmapIncrement(doc, AM_ROOT, AMstr("counter"), 10)); + AMstackItem(NULL, AMmapIncrement(doc, AM_ROOT, AMstr("counter"), 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.getWithType(root, "counter"), ["counter", 20]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), - AM_VALUE_COUNTER, - cmocka_cb).counter, 20); + assert_true(AMitemToCounter(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_COUNTER)), + &counter)); + assert_int_equal(counter, 20); /* doc.increment(root, "counter", -5); */ - AMfree(AMmapIncrement(doc, AM_ROOT, AMstr("counter"), -5)); + AMstackItem(NULL, AMmapIncrement(doc, AM_ROOT, AMstr("counter"), -5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.getWithType(root, "counter"), ["counter", 15]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), - AM_VALUE_COUNTER, - cmocka_cb).counter, 15); + assert_true(AMitemToCounter(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_COUNTER)), + &counter)); + assert_int_equal(counter, 15); } /** * \brief should be able to splice text */ static void test_should_be_able_to_splice_text(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root"; */ /* */ /* const text = doc.putObject(root, "text", ""); */ - AMobjId const* const text = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const text = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* doc.splice(text, 0, 0, "hello ") */ - AMfree(AMspliceText(doc, text, 0, 0, AMstr("hello "))); + AMstackItem(NULL, AMspliceText(doc, text, 0, 0, AMstr("hello ")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.splice(text, 6, 0, "world") */ - AMfree(AMspliceText(doc, text, 6, 0, AMstr("world"))); + AMstackItem(NULL, AMspliceText(doc, text, 6, 0, AMstr("world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.splice(text, 11, 0, "!?") */ - AMfree(AMspliceText(doc, text, 11, 0, AMstr("!?"))); + AMstackItem(NULL, AMspliceText(doc, text, 11, 0, AMstr("!?")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.getWithType(text, 0), ["str", "h"]) */ - AMbyteSpan str = AMpush(&stack, - AMlistGet(doc, text, 0, NULL), - AM_VALUE_STR, - cmocka_cb).str; + AMbyteSpan str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, AMlistGet(doc, text, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "h", str.count); /* assert.deepEqual(doc.getWithType(text, 1), ["str", "e"]) */ - str = AMpush(&stack, - AMlistGet(doc, text, 1, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, AMlistGet(doc, text, 1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "e", str.count); /* assert.deepEqual(doc.getWithType(text, 9), ["str", "l"]) */ - str = AMpush(&stack, - AMlistGet(doc, text, 9, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, AMlistGet(doc, text, 9, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "l", str.count); /* assert.deepEqual(doc.getWithType(text, 10), ["str", "d"]) */ - str = AMpush(&stack, - AMlistGet(doc, text, 10, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, text, 10, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "d", str.count); /* assert.deepEqual(doc.getWithType(text, 11), ["str", "!"]) */ - str = AMpush(&stack, - AMlistGet(doc, text, 11, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, text, 11, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "!", str.count); /* assert.deepEqual(doc.getWithType(text, 12), ["str", "?"]) */ - str = AMpush(&stack, - AMlistGet(doc, text, 12, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, text, 12, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "?", str.count); } @@ -761,52 +724,45 @@ static void test_should_be_able_to_splice_text(void** state) { * \brief should be able to save all or incrementally */ static void test_should_be_able_to_save_all_or_incrementally(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* */ /* doc.put("_root", "foo", 1) */ - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("foo"), 1)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("foo"), 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* const save1 = doc.save() */ - AMbyteSpan const save1 = AMpush(&stack, - AMsave(doc), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan save1; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save1)); /* */ /* doc.put("_root", "bar", 2) */ - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("bar"), 2)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("bar"), 2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* const saveMidway = doc.clone().save(); */ - AMbyteSpan const saveMidway = AMpush(&stack, - AMsave( - AMpush(&stack, - AMclone(doc), - AM_VALUE_DOC, - cmocka_cb).doc), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMdoc* doc_clone; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMclone(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc_clone)); + AMbyteSpan saveMidway; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc_clone), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &saveMidway)); /* */ /* const save2 = doc.saveIncremental(); */ - AMbyteSpan const save2 = AMpush(&stack, - AMsaveIncremental(doc), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan save2; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsaveIncremental(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save2)); /* */ /* doc.put("_root", "baz", 3); */ - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("baz"), 3)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("baz"), 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* const save3 = doc.saveIncremental(); */ - AMbyteSpan const save3 = AMpush(&stack, - AMsaveIncremental(doc), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan save3; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsaveIncremental(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save3)); /* */ /* const saveA = doc.save(); */ - AMbyteSpan const saveA = AMpush(&stack, - AMsave(doc), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan saveA; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &saveA)); /* const saveB = new Uint8Array([...save1, ...save2, ...save3]); */ size_t const saveB_count = save1.count + save2.count + save3.count; uint8_t* const saveB_src = test_malloc(saveB_count); @@ -818,104 +774,83 @@ static void test_should_be_able_to_save_all_or_incrementally(void** state) { assert_memory_not_equal(saveA.src, saveB_src, saveA.count); /* */ /* const docA = load(saveA); */ - AMdoc* const docA = AMpush(&stack, - AMload(saveA.src, saveA.count), - AM_VALUE_DOC, - cmocka_cb).doc; + AMdoc* docA; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(saveA.src, saveA.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &docA)); /* const docB = load(saveB); */ - AMdoc* const docB = AMpush(&stack, - AMload(saveB_src, saveB_count), - AM_VALUE_DOC, - cmocka_cb).doc; + AMdoc* docB; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(saveB_src, saveB_count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &docB)); test_free(saveB_src); /* const docC = load(saveMidway) */ - AMdoc* const docC = AMpush(&stack, - AMload(saveMidway.src, saveMidway.count), - AM_VALUE_DOC, - cmocka_cb).doc; + AMdoc* docC; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(saveMidway.src, saveMidway.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &docC)); /* docC.loadIncremental(save3) */ - AMfree(AMloadIncremental(docC, save3.src, save3.count)); + AMstackItem(NULL, AMloadIncremental(docC, save3.src, save3.count), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT)); /* */ /* assert.deepEqual(docA.keys("_root"), docB.keys("_root")); */ - AMstrs const keysA = AMpush(&stack, - AMkeys(docA, AM_ROOT, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - AMstrs const keysB = AMpush(&stack, - AMkeys(docB, AM_ROOT, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - assert_int_equal(AMstrsCmp(&keysA, &keysB), 0); + AMitems const keysA = AMstackItems(stack_ptr, AMkeys(docA, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMitems const keysB = AMstackItems(stack_ptr, AMkeys(docB, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_true(AMitemsEqual(&keysA, &keysB)); /* assert.deepEqual(docA.save(), docB.save()); */ - AMbyteSpan const save = AMpush(&stack, - AMsave(docA), - AM_VALUE_BYTES, - cmocka_cb).bytes; - assert_memory_equal(save.src, - AMpush(&stack, - AMsave(docB), - AM_VALUE_BYTES, - cmocka_cb).bytes.src, - save.count); + AMbyteSpan docA_save; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsave(docA), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &docA_save)); + AMbyteSpan docB_save; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsave(docB), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &docB_save)); + assert_int_equal(docA_save.count, docB_save.count); + assert_memory_equal(docA_save.src, docB_save.src, docA_save.count); /* assert.deepEqual(docA.save(), docC.save()); */ - assert_memory_equal(save.src, - AMpush(&stack, - AMsave(docC), - AM_VALUE_BYTES, - cmocka_cb).bytes.src, - save.count); + AMbyteSpan docC_save; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsave(docC), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &docC_save)); + assert_int_equal(docA_save.count, docC_save.count); + assert_memory_equal(docA_save.src, docC_save.src, docA_save.count); } /** * \brief should be able to splice text #2 */ static void test_should_be_able_to_splice_text_2(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const text = doc.putObject("_root", "text", ""); */ - AMobjId const* const text = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const text = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* doc.splice(text, 0, 0, "hello world"); */ - AMfree(AMspliceText(doc, text, 0, 0, AMstr("hello world"))); + AMstackItem(NULL, AMspliceText(doc, text, 0, 0, AMstr("hello world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const hash1 = doc.commit(); */ - AMchangeHashes const hash1 = AMpush(&stack, - AMcommit(doc, AMstr(NULL), NULL), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const hash1 = + AMstackItems(stack_ptr, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* doc.splice(text, 6, 0, "big bad "); */ - AMfree(AMspliceText(doc, text, 6, 0, AMstr("big bad "))); + AMstackItem(NULL, AMspliceText(doc, text, 6, 0, AMstr("big bad ")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const hash2 = doc.commit(); */ - AMchangeHashes const hash2 = AMpush(&stack, - AMcommit(doc, AMstr(NULL), NULL), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const hash2 = + AMstackItems(stack_ptr, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* assert.strictEqual(doc.text(text), "hello big bad world") */ - AMbyteSpan str = AMpush(&stack, - AMtext(doc, text, NULL), - AM_VALUE_STR, - cmocka_cb).str; + AMbyteSpan str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, AMtext(doc, text, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, strlen("hello big bad world")); assert_memory_equal(str.src, "hello big bad world", str.count); /* assert.strictEqual(doc.length(text), 19) */ assert_int_equal(AMobjSize(doc, text, NULL), 19); /* assert.strictEqual(doc.text(text, [hash1]), "hello world") */ - str = AMpush(&stack, - AMtext(doc, text, &hash1), - AM_VALUE_STR, - cmocka_cb).str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, AMtext(doc, text, &hash1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, strlen("hello world")); assert_memory_equal(str.src, "hello world", str.count); /* assert.strictEqual(doc.length(text, [hash1]), 11) */ assert_int_equal(AMobjSize(doc, text, &hash1), 11); /* assert.strictEqual(doc.text(text, [hash2]), "hello big bad world") */ - str = AMpush(&stack, - AMtext(doc, text, &hash2), - AM_VALUE_STR, - cmocka_cb).str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, AMtext(doc, text, &hash2), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, strlen("hello big bad world")); assert_memory_equal(str.src, "hello big bad world", str.count); /* assert.strictEqual(doc.length(text, [hash2]), 19) */ @@ -926,266 +861,234 @@ static void test_should_be_able_to_splice_text_2(void** state) { * \brief local inc increments all visible counters in a map */ static void test_local_inc_increments_all_visible_counters_in_a_map(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc1 = create("aaaa") */ - AMdoc* const doc1 = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aaaa")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); /* doc1.put("_root", "hello", "world") */ - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("hello"), AMstr("world"))); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("hello"), AMstr("world")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* const doc2 = load(doc1.save(), "bbbb"); */ - AMbyteSpan const save = AMpush(&stack, - AMsave(doc1), - AM_VALUE_BYTES, - cmocka_cb).bytes; - AMdoc* const doc2 = AMpush(&stack, - AMload(save.src, save.count), - AM_VALUE_DOC, - cmocka_cb).doc; - AMfree(AMsetActorId(doc2, AMpush(&stack, - AMactorIdInitStr(AMstr("bbbb")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); + AMbyteSpan save; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save)); + AMdoc* doc2; + assert_true( + AMitemToDoc(AMstackItem(stack_ptr, AMload(save.src, save.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("bbbb")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMstackItem(NULL, AMsetActorId(doc2, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const doc3 = load(doc1.save(), "cccc"); */ - AMdoc* const doc3 = AMpush(&stack, - AMload(save.src, save.count), - AM_VALUE_DOC, - cmocka_cb).doc; - AMfree(AMsetActorId(doc3, AMpush(&stack, - AMactorIdInitStr(AMstr("cccc")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); + AMdoc* doc3; + assert_true( + AMitemToDoc(AMstackItem(stack_ptr, AMload(save.src, save.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc3)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("cccc")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMstackItem(NULL, AMsetActorId(doc3, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* let heads = doc1.getHeads() */ - AMchangeHashes const heads1 = AMpush(&stack, - AMgetHeads(doc1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const heads1 = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* doc1.put("_root", "cnt", 20) */ - AMfree(AMmapPutInt(doc1, AM_ROOT, AMstr("cnt"), 20)); + AMstackItem(NULL, AMmapPutInt(doc1, AM_ROOT, AMstr("cnt"), 20), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc2.put("_root", "cnt", 0, "counter") */ - AMfree(AMmapPutCounter(doc2, AM_ROOT, AMstr("cnt"), 0)); + AMstackItem(NULL, AMmapPutCounter(doc2, AM_ROOT, AMstr("cnt"), 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc3.put("_root", "cnt", 10, "counter") */ - AMfree(AMmapPutCounter(doc3, AM_ROOT, AMstr("cnt"), 10)); + AMstackItem(NULL, AMmapPutCounter(doc3, AM_ROOT, AMstr("cnt"), 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc1.applyChanges(doc2.getChanges(heads)) */ - AMchanges const changes2 = AMpush(&stack, - AMgetChanges(doc2, &heads1), - AM_VALUE_CHANGES, - cmocka_cb).changes; - AMfree(AMapplyChanges(doc1, &changes2)); + AMitems const changes2 = + AMstackItems(stack_ptr, AMgetChanges(doc2, &heads1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMstackItem(NULL, AMapplyChanges(doc1, &changes2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc1.applyChanges(doc3.getChanges(heads)) */ - AMchanges const changes3 = AMpush(&stack, - AMgetChanges(doc3, &heads1), - AM_VALUE_CHANGES, - cmocka_cb).changes; - AMfree(AMapplyChanges(doc1, &changes3)); + AMitems const changes3 = + AMstackItems(stack_ptr, AMgetChanges(doc3, &heads1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMstackItem(NULL, AMapplyChanges(doc1, &changes3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* let result = doc1.getAll("_root", "cnt") */ - AMobjItems result = AMpush(&stack, - AMmapGetAll(doc1, AM_ROOT, AMstr("cnt"), NULL), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; + AMitems result = AMstackItems(stack_ptr, AMmapGetAll(doc1, AM_ROOT, AMstr("cnt"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_COUNTER | AM_VAL_TYPE_INT | AM_VAL_TYPE_STR)); /* assert.deepEqual(result, [ ['int', 20, '2@aaaa'], ['counter', 0, '2@bbbb'], ['counter', 10, '2@cccc'], ]) */ - AMobjItem const* result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).int_, 20); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 2); - AMbyteSpan str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + AMitem* result_item = AMitemsNext(&result, 1); + int64_t int_; + assert_true(AMitemToInt(result_item, &int_)); + assert_int_equal(int_, 20); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 2); + AMbyteSpan str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "aaaa", str.count); - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 0); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 2); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + int64_t counter; + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 0); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 2); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "bbbb", str.count); - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 10); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 2); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 10); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 2); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "cccc", str.count); /* doc1.increment("_root", "cnt", 5) */ - AMfree(AMmapIncrement(doc1, AM_ROOT, AMstr("cnt"), 5)); + AMstackItem(NULL, AMmapIncrement(doc1, AM_ROOT, AMstr("cnt"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* result = doc1.getAll("_root", "cnt") */ - result = AMpush(&stack, - AMmapGetAll(doc1, AM_ROOT, AMstr("cnt"), NULL), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; + result = AMstackItems(stack_ptr, AMmapGetAll(doc1, AM_ROOT, AMstr("cnt"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_COUNTER)); /* assert.deepEqual(result, [ ['counter', 5, '2@bbbb'], ['counter', 15, '2@cccc'], ]) */ - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 5); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 2); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 5); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 2); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "bbbb", str.count); - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 15); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 2); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 15); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 2); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "cccc", str.count); /* */ /* const save1 = doc1.save() */ - AMbyteSpan const save1 = AMpush(&stack, - AMsave(doc1), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan save1; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save1)); /* const doc4 = load(save1) */ - AMdoc* const doc4 = AMpush(&stack, - AMload(save1.src, save1.count), - AM_VALUE_DOC, - cmocka_cb).doc; + AMdoc* doc4; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(save1.src, save1.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc4)); /* assert.deepEqual(doc4.save(), save1); */ - assert_memory_equal(AMpush(&stack, - AMsave(doc4), - AM_VALUE_BYTES, - cmocka_cb).bytes.src, - save1.src, - save1.count); + AMbyteSpan doc4_save; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc4), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &doc4_save)); + assert_int_equal(doc4_save.count, save1.count); + assert_memory_equal(doc4_save.src, save1.src, doc4_save.count); } /** * \brief local inc increments all visible counters in a sequence */ static void test_local_inc_increments_all_visible_counters_in_a_sequence(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc1 = create("aaaa") */ - AMdoc* const doc1 = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aaaa")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); /* const seq = doc1.putObject("_root", "seq", []) */ - AMobjId const* const seq = AMpush( - &stack, - AMmapPutObject(doc1, AM_ROOT, AMstr("seq"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const seq = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc1, AM_ROOT, AMstr("seq"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* doc1.insert(seq, 0, "hello") */ - AMfree(AMlistPutStr(doc1, seq, 0, true, AMstr("hello"))); + AMstackItem(NULL, AMlistPutStr(doc1, seq, 0, true, AMstr("hello")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const doc2 = load(doc1.save(), "bbbb"); */ - AMbyteSpan const save1 = AMpush(&stack, - AMsave(doc1), - AM_VALUE_BYTES, - cmocka_cb).bytes; - AMdoc* const doc2 = AMpush(&stack, - AMload(save1.src, save1.count), - AM_VALUE_DOC, - cmocka_cb).doc; - AMfree(AMsetActorId(doc2, AMpush(&stack, - AMactorIdInitStr(AMstr("bbbb")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); + AMbyteSpan save1; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save1)); + AMdoc* doc2; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(save1.src, save1.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("bbbb")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMstackItem(NULL, AMsetActorId(doc2, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const doc3 = load(doc1.save(), "cccc"); */ - AMdoc* const doc3 = AMpush(&stack, - AMload(save1.src, save1.count), - AM_VALUE_DOC, - cmocka_cb).doc; - AMfree(AMsetActorId(doc3, AMpush(&stack, - AMactorIdInitStr(AMstr("cccc")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); + AMdoc* doc3; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(save1.src, save1.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc3)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("cccc")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMstackItem(NULL, AMsetActorId(doc3, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* let heads = doc1.getHeads() */ - AMchangeHashes const heads1 = AMpush(&stack, - AMgetHeads(doc1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const heads1 = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* doc1.put(seq, 0, 20) */ - AMfree(AMlistPutInt(doc1, seq, 0, false, 20)); + AMstackItem(NULL, AMlistPutInt(doc1, seq, 0, false, 20), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc2.put(seq, 0, 0, "counter") */ - AMfree(AMlistPutCounter(doc2, seq, 0, false, 0)); + AMstackItem(NULL, AMlistPutCounter(doc2, seq, 0, false, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc3.put(seq, 0, 10, "counter") */ - AMfree(AMlistPutCounter(doc3, seq, 0, false, 10)); + AMstackItem(NULL, AMlistPutCounter(doc3, seq, 0, false, 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc1.applyChanges(doc2.getChanges(heads)) */ - AMchanges const changes2 = AMpush(&stack, - AMgetChanges(doc2, &heads1), - AM_VALUE_CHANGES, - cmocka_cb).changes; - AMfree(AMapplyChanges(doc1, &changes2)); + AMitems const changes2 = + AMstackItems(stack_ptr, AMgetChanges(doc2, &heads1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMstackItem(NULL, AMapplyChanges(doc1, &changes2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc1.applyChanges(doc3.getChanges(heads)) */ - AMchanges const changes3 = AMpush(&stack, - AMgetChanges(doc3, &heads1), - AM_VALUE_CHANGES, - cmocka_cb).changes; - AMfree(AMapplyChanges(doc1, &changes3)); + AMitems const changes3 = + AMstackItems(stack_ptr, AMgetChanges(doc3, &heads1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMstackItem(NULL, AMapplyChanges(doc1, &changes3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* let result = doc1.getAll(seq, 0) */ - AMobjItems result = AMpush(&stack, - AMlistGetAll(doc1, seq, 0, NULL), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; + AMitems result = AMstackItems(stack_ptr, AMlistGetAll(doc1, seq, 0, NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_COUNTER | AM_VAL_TYPE_INT)); /* assert.deepEqual(result, [ ['int', 20, '3@aaaa'], ['counter', 0, '3@bbbb'], ['counter', 10, '3@cccc'], ]) */ - AMobjItem const* result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).int_, 20); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 3); - AMbyteSpan str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + AMitem* result_item = AMitemsNext(&result, 1); + int64_t int_; + assert_true(AMitemToInt(result_item, &int_)); + assert_int_equal(int_, 20); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 3); + AMbyteSpan str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "aaaa", str.count); - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 0); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 3); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + int64_t counter; + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 0); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 3); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_memory_equal(str.src, "bbbb", str.count); - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 10); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 3); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 10); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 3); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "cccc", str.count); /* doc1.increment(seq, 0, 5) */ - AMfree(AMlistIncrement(doc1, seq, 0, 5)); + AMstackItem(NULL, AMlistIncrement(doc1, seq, 0, 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* result = doc1.getAll(seq, 0) */ - result = AMpush(&stack, - AMlistGetAll(doc1, seq, 0, NULL), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; + result = AMstackItems(stack_ptr, AMlistGetAll(doc1, seq, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_COUNTER)); /* assert.deepEqual(result, [ ['counter', 5, '3@bbbb'], ['counter', 15, '3@cccc'], ]) */ - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 5); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 3); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 5); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 3); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "bbbb", str.count); - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 15); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 3); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 15); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 3); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_memory_equal(str.src, "cccc", str.count); /* */ /* const save = doc1.save() */ - AMbyteSpan const save = AMpush(&stack, - AMsave(doc1), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan save; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save)); /* const doc4 = load(save) */ - AMdoc* const doc4 = AMpush(&stack, - AMload(save.src, save.count), - AM_VALUE_DOC, - cmocka_cb).doc; + AMdoc* doc4; + assert_true( + AMitemToDoc(AMstackItem(stack_ptr, AMload(save.src, save.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc4)); /* assert.deepEqual(doc4.save(), save); */ - assert_memory_equal(AMpush(&stack, - AMsave(doc4), - AM_VALUE_BYTES, - cmocka_cb).bytes.src, - save.src, - save.count); + AMbyteSpan doc4_save; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc4), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &doc4_save)); + assert_int_equal(doc4_save.count, save.count); + assert_memory_equal(doc4_save.src, save.src, doc4_save.count); } /** @@ -1197,314 +1100,269 @@ static void test_paths_can_be_used_instead_of_objids(void** state); * \brief should be able to fetch changes by hash */ static void test_should_be_able_to_fetch_changes_by_hash(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc1 = create("aaaa") */ - AMdoc* const doc1 = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aaaa")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); /* const doc2 = create("bbbb") */ - AMdoc* const doc2 = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("bbbb")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("bbbb")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMdoc* doc2; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); /* doc1.put("/", "a", "b") */ - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("a"), AMstr("b"))); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("a"), AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc2.put("/", "b", "c") */ - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("b"), AMstr("c"))); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("b"), AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const head1 = doc1.getHeads() */ - AMchangeHashes head1 = AMpush(&stack, - AMgetHeads(doc1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems head1 = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* const head2 = doc2.getHeads() */ - AMchangeHashes head2 = AMpush(&stack, - AMgetHeads(doc2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems head2 = AMstackItems(stack_ptr, AMgetHeads(doc2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* const change1 = doc1.getChangeByHash(head1[0]) - if (change1 === null) { throw new RangeError("change1 should not be null") */ - AMbyteSpan const change_hash1 = AMchangeHashesNext(&head1, 1); - AMchanges change1 = AMpush( - &stack, - AMgetChangeByHash(doc1, change_hash1.src, change_hash1.count), - AM_VALUE_CHANGES, - cmocka_cb).changes; + if (change1 === null) { throw new RangeError("change1 should not be + null") */ + AMbyteSpan change_hash1; + assert_true(AMitemToChangeHash(AMitemsNext(&head1, 1), &change_hash1)); + AMchange const* change1; + assert_true(AMitemToChange(AMstackItem(stack_ptr, AMgetChangeByHash(doc1, change_hash1.src, change_hash1.count), + cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)), + &change1)); /* const change2 = doc1.getChangeByHash(head2[0]) assert.deepEqual(change2, null) */ - AMbyteSpan const change_hash2 = AMchangeHashesNext(&head2, 1); - AMpush(&stack, - AMgetChangeByHash(doc1, change_hash2.src, change_hash2.count), - AM_VALUE_VOID, - cmocka_cb); + AMbyteSpan change_hash2; + assert_true(AMitemToChangeHash(AMitemsNext(&head2, 1), &change_hash2)); + AMstackItem(NULL, AMgetChangeByHash(doc1, change_hash2.src, change_hash2.count), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(decodeChange(change1).hash, head1[0]) */ - assert_memory_equal(AMchangeHash(AMchangesNext(&change1, 1)).src, - change_hash1.src, - change_hash1.count); + assert_memory_equal(AMchangeHash(change1).src, change_hash1.src, change_hash1.count); } /** * \brief recursive sets are possible */ static void test_recursive_sets_are_possible(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create("aaaa") */ - AMdoc* const doc = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aaaa")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const l1 = doc.putObject("_root", "list", [{ foo: "bar" }, [1, 2, 3]] */ - AMobjId const* const l1 = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const l1 = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); { - AMobjId const* const map = AMpush( - &stack, - AMlistPutObject(doc, l1, 0, true, AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; - AMfree(AMmapPutStr(doc, map, AMstr("foo"), AMstr("bar"))); - AMobjId const* const list = AMpush( - &stack, - AMlistPutObject(doc, l1, SIZE_MAX, true, AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const map = AMitemObjId(AMstackItem( + stack_ptr, AMlistPutObject(doc, l1, 0, true, AM_OBJ_TYPE_MAP), cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMmapPutStr(doc, map, AMstr("foo"), AMstr("bar")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMlistPutObject(doc, l1, SIZE_MAX, true, AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); for (int value = 1; value != 4; ++value) { - AMfree(AMlistPutInt(doc, list, SIZE_MAX, true, value)); + AMstackItem(NULL, AMlistPutInt(doc, list, SIZE_MAX, true, value), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); } } /* const l2 = doc.insertObject(l1, 0, { zip: ["a", "b"] }) */ - AMobjId const* const l2 = AMpush( - &stack, - AMlistPutObject(doc, l1, 0, true, AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const l2 = AMitemObjId(AMstackItem(stack_ptr, AMlistPutObject(doc, l1, 0, true, AM_OBJ_TYPE_MAP), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); { - AMobjId const* const list = AMpush( - &stack, - AMmapPutObject(doc, l2, AMstr("zip"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; - AMfree(AMlistPutStr(doc, list, SIZE_MAX, true, AMstr("a"))); - AMfree(AMlistPutStr(doc, list, SIZE_MAX, true, AMstr("b"))); + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, l2, AMstr("zip"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMlistPutStr(doc, list, SIZE_MAX, true, AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc, list, SIZE_MAX, true, AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); } - /* const l3 = doc.putObject("_root", "info1", "hello world") // 'text' object */ - AMobjId const* const l3 = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("info1"), AM_OBJ_TYPE_TEXT), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; - AMfree(AMspliceText(doc, l3, 0, 0, AMstr("hello world"))); + /* const l3 = doc.putObject("_root", "info1", "hello world") // 'text' + * object */ + AMobjId const* const l3 = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("info1"), AM_OBJ_TYPE_TEXT), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMspliceText(doc, l3, 0, 0, AMstr("hello world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put("_root", "info2", "hello world") // 'str' */ - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("info2"), AMstr("hello world"))); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("info2"), AMstr("hello world")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* const l4 = doc.putObject("_root", "info3", "hello world") */ - AMobjId const* const l4 = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("info3"), AM_OBJ_TYPE_TEXT), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; - AMfree(AMspliceText(doc, l4, 0, 0, AMstr("hello world"))); + AMobjId const* const l4 = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("info3"), AM_OBJ_TYPE_TEXT), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMspliceText(doc, l4, 0, 0, AMstr("hello world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.materialize(), { "list": [{ zip: ["a", "b"] }, { foo: "bar" }, [1, 2, 3]], "info1": "hello world", "info2": "hello world", "info3": "hello world", - }) */ - AMmapItems doc_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItem const* doc_item = AMmapItemsNext(&doc_items, 1); - AMbyteSpan key = AMmapItemKey(doc_item); + }) */ + AMitems doc_items = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE | AM_VAL_TYPE_STR)); + AMitem* doc_item = AMitemsNext(&doc_items, 1); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("info1")); assert_memory_equal(key.src, "info1", key.count); - AMbyteSpan str = AMpush(&stack, - AMtext(doc, AMmapItemObjId(doc_item), NULL), - AM_VALUE_STR, - cmocka_cb).str; + AMbyteSpan str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMtext(doc, AMitemObjId(doc_item), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, strlen("hello world")); assert_memory_equal(str.src, "hello world", str.count); - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + doc_item = AMitemsNext(&doc_items, 1); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("info2")); assert_memory_equal(key.src, "info2", key.count); - str = AMmapItemValue(doc_item).str; + assert_true(AMitemToStr(doc_item, &str)); assert_int_equal(str.count, strlen("hello world")); assert_memory_equal(str.src, "hello world", str.count); - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + doc_item = AMitemsNext(&doc_items, 1); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("info3")); assert_memory_equal(key.src, "info3", key.count); - str = AMpush(&stack, - AMtext(doc, AMmapItemObjId(doc_item), NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMtext(doc, AMitemObjId(doc_item), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, strlen("hello world")); assert_memory_equal(str.src, "hello world", str.count); - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + doc_item = AMitemsNext(&doc_items, 1); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("list")); assert_memory_equal(key.src, "list", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMlistItem const* list_item = AMlistItemsNext(&list_items, 1); + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + AMitem const* list_item = AMitemsNext(&list_items, 1); { - AMmapItems map_items = AMpush( - &stack, - AMmapRange(doc, AMlistItemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItem const* map_item = AMmapItemsNext(&map_items, 1); - AMbyteSpan const key = AMmapItemKey(map_item); + AMitems map_items = + AMstackItems(stack_ptr, AMmapRange(doc, AMitemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + AMitem const* map_item = AMitemsNext(&map_items, 1); + assert_int_equal(AMitemIdxType(map_item), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(map_item, &key)); assert_int_equal(key.count, strlen("zip")); assert_memory_equal(key.src, "zip", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(map_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(map_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE | AM_VAL_TYPE_STR)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); } } - list_item = AMlistItemsNext(&list_items, 1); + list_item = AMitemsNext(&list_items, 1); { - AMmapItems map_items = AMpush( - &stack, - AMmapRange(doc, AMlistItemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItem const* map_item = AMmapItemsNext(&map_items, 1); - AMbyteSpan const key = AMmapItemKey(map_item); + AMitems map_items = + AMstackItems(stack_ptr, AMmapRange(doc, AMitemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE | AM_VAL_TYPE_STR)); + AMitem* map_item = AMitemsNext(&map_items, 1); + assert_int_equal(AMitemIdxType(map_item), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(map_item, &key)); assert_int_equal(key.count, strlen("foo")); assert_memory_equal(key.src, "foo", key.count); - AMbyteSpan const str = AMmapItemValue(map_item).str; + AMbyteSpan str; + assert_true(AMitemToStr(map_item, &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bar", str.count); } - list_item = AMlistItemsNext(&list_items, 1); + list_item = AMitemsNext(&list_items, 1); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMlistItemObjId(list_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - assert_int_equal(AMlistItemValue( - AMlistItemsNext(&list_items, 1)).int_, - 1); - assert_int_equal(AMlistItemValue( - AMlistItemsNext(&list_items, 1)).int_, - 2); - assert_int_equal(AMlistItemValue( - AMlistItemsNext(&list_items, 1)).int_, - 3); + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(list_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_INT)); + int64_t int_; + assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); + assert_int_equal(int_, 1); + assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); + assert_int_equal(int_, 2); + assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); + assert_int_equal(int_, 3); } } /* assert.deepEqual(doc.materialize(l2), { zip: ["a", "b"] }) */ - AMmapItems map_items = AMpush( - &stack, - AMmapRange(doc, l2, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItem const* map_item = AMmapItemsNext(&map_items, 1); - key = AMmapItemKey(map_item); + AMitems map_items = AMstackItems(stack_ptr, AMmapRange(doc, l2, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + AMitem const* map_item = AMitemsNext(&map_items, 1); + assert_int_equal(AMitemIdxType(map_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(map_item, &key)); assert_int_equal(key.count, strlen("zip")); assert_memory_equal(key.src, "zip", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(map_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(map_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); } - /* assert.deepEqual(doc.materialize(l1), [{ zip: ["a", "b"] }, { foo: "bar" }, [1, 2, 3]] */ - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, l1, 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMlistItem const* list_item = AMlistItemsNext(&list_items, 1); + /* assert.deepEqual(doc.materialize(l1), [{ zip: ["a", "b"] }, { foo: "bar" + * }, [1, 2, 3]] */ + AMitems list_items = + AMstackItems(stack_ptr, AMlistRange(doc, l1, 0, SIZE_MAX, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + AMitem const* list_item = AMitemsNext(&list_items, 1); { - AMmapItems map_items = AMpush( - &stack, - AMmapRange(doc, AMlistItemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItem const* map_item = AMmapItemsNext(&map_items, 1); - AMbyteSpan const key = AMmapItemKey(map_item); + AMitems map_items = + AMstackItems(stack_ptr, AMmapRange(doc, AMitemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + AMitem const* map_item = AMitemsNext(&map_items, 1); + assert_int_equal(AMitemIdxType(map_item), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(map_item, &key)); assert_int_equal(key.count, strlen("zip")); assert_memory_equal(key.src, "zip", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(map_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(map_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); } } - list_item = AMlistItemsNext(&list_items, 1); + list_item = AMitemsNext(&list_items, 1); { - AMmapItems map_items = AMpush( - &stack, - AMmapRange(doc, AMlistItemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItem const* map_item = AMmapItemsNext(&map_items, 1); - AMbyteSpan const key = AMmapItemKey(map_item); + AMitems map_items = + AMstackItems(stack_ptr, AMmapRange(doc, AMitemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + AMitem* map_item = AMitemsNext(&map_items, 1); + assert_int_equal(AMitemIdxType(map_item), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(map_item, &key)); assert_int_equal(key.count, strlen("foo")); assert_memory_equal(key.src, "foo", key.count); - AMbyteSpan const str = AMmapItemValue(map_item).str; + AMbyteSpan str; + assert_true(AMitemToStr(map_item, &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bar", str.count); } - list_item = AMlistItemsNext(&list_items, 1); + list_item = AMitemsNext(&list_items, 1); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMlistItemObjId(list_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).int_, - 1); - assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).int_, - 2); - assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).int_, - 3); + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(list_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_INT)); + int64_t int_; + assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); + assert_int_equal(int_, 1); + assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); + assert_int_equal(int_, 2); + assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); + assert_int_equal(int_, 3); } /* assert.deepEqual(doc.materialize(l4), "hello world") */ - str = AMpush(&stack, AMtext(doc, l4, NULL), AM_VALUE_STR, cmocka_cb).str; + assert_true(AMitemToStr(AMstackItem(stack_ptr, AMtext(doc, l4, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, strlen("hello world")); assert_memory_equal(str.src, "hello world", str.count); } @@ -1513,65 +1371,41 @@ static void test_recursive_sets_are_possible(void** state) { * \brief only returns an object id when objects are created */ static void test_only_returns_an_object_id_when_objects_are_created(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create("aaaa") */ - AMdoc* const doc = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aaaa")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const r1 = doc.put("_root", "foo", "bar") assert.deepEqual(r1, null); */ - AMpush(&stack, - AMmapPutStr(doc, AM_ROOT, AMstr("foo"), AMstr("bar")), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("foo"), AMstr("bar")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const r2 = doc.putObject("_root", "list", []) */ - AMobjId const* const r2 = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const r2 = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* const r3 = doc.put("_root", "counter", 10, "counter") assert.deepEqual(r3, null); */ - AMpush(&stack, - AMmapPutCounter(doc, AM_ROOT, AMstr("counter"), 10), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMmapPutCounter(doc, AM_ROOT, AMstr("counter"), 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const r4 = doc.increment("_root", "counter", 1) assert.deepEqual(r4, null); */ - AMpush(&stack, - AMmapIncrement(doc, AM_ROOT, AMstr("counter"), 1), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMmapIncrement(doc, AM_ROOT, AMstr("counter"), 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const r5 = doc.delete("_root", "counter") assert.deepEqual(r5, null); */ - AMpush(&stack, - AMmapDelete(doc, AM_ROOT, AMstr("counter")), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMmapDelete(doc, AM_ROOT, AMstr("counter")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const r6 = doc.insert(r2, 0, 10); assert.deepEqual(r6, null); */ - AMpush(&stack, - AMlistPutInt(doc, r2, 0, true, 10), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMlistPutInt(doc, r2, 0, true, 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const r7 = doc.insertObject(r2, 0, {}); */ - AMobjId const* const r7 = AMpush( - &stack, - AMlistPutObject(doc, r2, 0, true, AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const r7 = AMitemObjId(AMstackItem(stack_ptr, AMlistPutObject(doc, r2, 0, true, AM_OBJ_TYPE_LIST), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* const r8 = doc.splice(r2, 1, 0, ["a", "b", "c"]); */ - AMvalue const STRS[] = {{.str_tag = AM_VALUE_STR, .str = {.src = "a", .count = 1}}, - {.str_tag = AM_VALUE_STR, .str = {.src = "b", .count = 1}}, - {.str_tag = AM_VALUE_STR, .str = {.src = "c", .count = 1}}}; - AMpush(&stack, - AMsplice(doc, r2, 1, 0, STRS, sizeof(STRS)/sizeof(AMvalue)), - AM_VALUE_VOID, - cmocka_cb); + AMresult* data = AMstackResult( + stack_ptr, AMresultFrom(3, AMitemFromStr(AMstr("a")), AMitemFromStr(AMstr("b")), AMitemFromStr(AMstr("c"))), + NULL, NULL); + AMstackItem(NULL, AMsplice(doc, r2, 1, 0, AMresultItems(data)), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(r2, "2@aaaa"); */ assert_int_equal(AMobjIdCounter(r2), 2); AMbyteSpan str = AMactorIdStr(AMobjIdActorId(r2)); @@ -1587,75 +1421,58 @@ static void test_only_returns_an_object_id_when_objects_are_created(void** state * \brief objects without properties are preserved */ static void test_objects_without_properties_are_preserved(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc1 = create("aaaa") */ - AMdoc* const doc1 = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aaaa")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); /* const a = doc1.putObject("_root", "a", {}); */ - AMobjId const* const a = AMpush( - &stack, - AMmapPutObject(doc1, AM_ROOT, AMstr("a"), AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const a = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc1, AM_ROOT, AMstr("a"), AM_OBJ_TYPE_MAP), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* const b = doc1.putObject("_root", "b", {}); */ - AMobjId const* const b = AMpush( - &stack, - AMmapPutObject(doc1, AM_ROOT, AMstr("b"), AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const b = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc1, AM_ROOT, AMstr("b"), AM_OBJ_TYPE_MAP), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* const c = doc1.putObject("_root", "c", {}); */ - AMobjId const* const c = AMpush( - &stack, - AMmapPutObject(doc1, AM_ROOT, AMstr("c"), AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const c = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc1, AM_ROOT, AMstr("c"), AM_OBJ_TYPE_MAP), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* const d = doc1.put(c, "d", "dd"); */ - AMfree(AMmapPutStr(doc1, c, AMstr("d"), AMstr("dd"))); + AMstackItem(NULL, AMmapPutStr(doc1, c, AMstr("d"), AMstr("dd")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const saved = doc1.save(); */ - AMbyteSpan const saved = AMpush(&stack, - AMsave(doc1), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan saved; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &saved)); /* const doc2 = load(saved); */ - AMdoc* const doc2 = AMpush(&stack, - AMload(saved.src, saved.count), - AM_VALUE_DOC, - cmocka_cb).doc; + AMdoc* doc2; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(saved.src, saved.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); /* assert.deepEqual(doc2.getWithType("_root", "a"), ["map", a]) */ - AMmapItems doc_items = AMpush(&stack, - AMmapRange(doc2, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - assert_true(AMobjIdEqual(AMmapItemObjId(AMmapItemsNext(&doc_items, 1)), a)); + AMitems doc_items = AMstackItems(stack_ptr, AMmapRange(doc2, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + assert_true(AMobjIdEqual(AMitemObjId(AMitemsNext(&doc_items, 1)), a)); /* assert.deepEqual(doc2.keys(a), []) */ - AMstrs keys = AMpush(&stack, - AMkeys(doc1, a, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - assert_int_equal(AMstrsSize(&keys), 0); + AMitems keys = AMstackItems(stack_ptr, AMkeys(doc1, a, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&keys), 0); /* assert.deepEqual(doc2.getWithType("_root", "b"), ["map", b]) */ - assert_true(AMobjIdEqual(AMmapItemObjId(AMmapItemsNext(&doc_items, 1)), b)); + assert_true(AMobjIdEqual(AMitemObjId(AMitemsNext(&doc_items, 1)), b)); /* assert.deepEqual(doc2.keys(b), []) */ - keys = AMpush(&stack, AMkeys(doc1, b, NULL), AM_VALUE_STRS, cmocka_cb).strs; - assert_int_equal(AMstrsSize(&keys), 0); + keys = AMstackItems(stack_ptr, AMkeys(doc1, b, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&keys), 0); /* assert.deepEqual(doc2.getWithType("_root", "c"), ["map", c]) */ - assert_true(AMobjIdEqual(AMmapItemObjId(AMmapItemsNext(&doc_items, 1)), c)); + assert_true(AMobjIdEqual(AMitemObjId(AMitemsNext(&doc_items, 1)), c)); /* assert.deepEqual(doc2.keys(c), ["d"]) */ - keys = AMpush(&stack, AMkeys(doc1, c, NULL), AM_VALUE_STRS, cmocka_cb).strs; - AMbyteSpan str = AMstrsNext(&keys, 1); + keys = AMstackItems(stack_ptr, AMkeys(doc1, c, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "d", str.count); /* assert.deepEqual(doc2.getWithType(c, "d"), ["str", "dd"]) */ - AMobjItems obj_items = AMpush(&stack, - AMobjValues(doc1, c, NULL), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; - str = AMobjItemValue(AMobjItemsNext(&obj_items, 1)).str; + AMitems obj_items = AMstackItems(stack_ptr, AMobjItems(doc1, c, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_true(AMitemToStr(AMitemsNext(&obj_items, 1), &str)); assert_int_equal(str.count, 2); assert_memory_equal(str.src, "dd", str.count); } @@ -1664,177 +1481,162 @@ static void test_objects_without_properties_are_preserved(void** state) { * \brief should allow you to forkAt a heads */ static void test_should_allow_you_to_forkAt_a_heads(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const A = create("aaaaaa") */ - AMdoc* const A = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aaaaaa")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMdoc* A; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &A)); /* A.put("/", "key1", "val1"); */ - AMfree(AMmapPutStr(A, AM_ROOT, AMstr("key1"), AMstr("val1"))); + AMstackItem(NULL, AMmapPutStr(A, AM_ROOT, AMstr("key1"), AMstr("val1")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* A.put("/", "key2", "val2"); */ - AMfree(AMmapPutStr(A, AM_ROOT, AMstr("key2"), AMstr("val2"))); + AMstackItem(NULL, AMmapPutStr(A, AM_ROOT, AMstr("key2"), AMstr("val2")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const heads1 = A.getHeads(); */ - AMchangeHashes const heads1 = AMpush(&stack, - AMgetHeads(A), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const heads1 = AMstackItems(stack_ptr, AMgetHeads(A), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* const B = A.fork("bbbbbb") */ - AMdoc* const B = AMpush(&stack, AMfork(A, NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMfree(AMsetActorId(B, AMpush(&stack, - AMactorIdInitStr(AMstr("bbbbbb")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); + AMdoc* B; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMfork(A, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &B)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("bbbbbb")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMstackItem(NULL, AMsetActorId(B, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* A.put("/", "key3", "val3"); */ - AMfree(AMmapPutStr(A, AM_ROOT, AMstr("key3"), AMstr("val3"))); + AMstackItem(NULL, AMmapPutStr(A, AM_ROOT, AMstr("key3"), AMstr("val3")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* B.put("/", "key4", "val4"); */ - AMfree(AMmapPutStr(B, AM_ROOT, AMstr("key4"), AMstr("val4"))); + AMstackItem(NULL, AMmapPutStr(B, AM_ROOT, AMstr("key4"), AMstr("val4")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* A.merge(B) */ - AMfree(AMmerge(A, B)); + AMstackItem(NULL, AMmerge(A, B), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* const heads2 = A.getHeads(); */ - AMchangeHashes const heads2 = AMpush(&stack, - AMgetHeads(A), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const heads2 = AMstackItems(stack_ptr, AMgetHeads(A), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* A.put("/", "key5", "val5"); */ - AMfree(AMmapPutStr(A, AM_ROOT, AMstr("key5"), AMstr("val5"))); - /* assert.deepEqual(A.forkAt(heads1).materialize("/"), A.materialize("/", heads1) */ - AMmapItems AforkAt1_items = AMpush( - &stack, - AMmapRange( - AMpush(&stack, AMfork(A, &heads1), AM_VALUE_DOC, cmocka_cb).doc, - AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItems A1_items = AMpush(&stack, - AMmapRange(A, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads1), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - assert_true(AMmapItemsEqual(&AforkAt1_items, &A1_items)); - /* assert.deepEqual(A.forkAt(heads2).materialize("/"), A.materialize("/", heads2) */ - AMmapItems AforkAt2_items = AMpush( - &stack, - AMmapRange( - AMpush(&stack, AMfork(A, &heads2), AM_VALUE_DOC, cmocka_cb).doc, - AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItems A2_items = AMpush(&stack, - AMmapRange(A, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads2), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - assert_true(AMmapItemsEqual(&AforkAt2_items, &A2_items)); + AMstackItem(NULL, AMmapPutStr(A, AM_ROOT, AMstr("key5"), AMstr("val5")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + /* assert.deepEqual(A.forkAt(heads1).materialize("/"), A.materialize("/", + * heads1) */ + AMdoc* A_forkAt1; + assert_true( + AMitemToDoc(AMstackItem(stack_ptr, AMfork(A, &heads1), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &A_forkAt1)); + AMitems AforkAt1_items = AMstackItems(stack_ptr, AMmapRange(A_forkAt1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMitems A1_items = AMstackItems(stack_ptr, AMmapRange(A, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads1), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + assert_true(AMitemsEqual(&AforkAt1_items, &A1_items)); + /* assert.deepEqual(A.forkAt(heads2).materialize("/"), A.materialize("/", + * heads2) */ + AMdoc* A_forkAt2; + assert_true( + AMitemToDoc(AMstackItem(stack_ptr, AMfork(A, &heads2), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &A_forkAt2)); + AMitems AforkAt2_items = AMstackItems(stack_ptr, AMmapRange(A_forkAt2, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMitems A2_items = AMstackItems(stack_ptr, AMmapRange(A, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads2), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + assert_true(AMitemsEqual(&AforkAt2_items, &A2_items)); } /** * \brief should handle merging text conflicts then saving & loading */ static void test_should_handle_merging_text_conflicts_then_saving_and_loading(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const A = create("aabbcc") */ - AMdoc* const A = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aabbcc")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aabbcc")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMdoc* A; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &A)); /* const At = A.putObject('_root', 'text', "") */ - AMobjId const* const At = AMpush( - &stack, - AMmapPutObject(A, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const At = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(A, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* A.splice(At, 0, 0, 'hello') */ - AMfree(AMspliceText(A, At, 0, 0, AMstr("hello"))); + AMstackItem(NULL, AMspliceText(A, At, 0, 0, AMstr("hello")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* const B = A.fork() */ - AMdoc* const B = AMpush(&stack, AMfork(A, NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* B; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMfork(A, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &B)); /* */ /* assert.deepEqual(B.getWithType("_root", "text"), ["text", At]) */ - AMbyteSpan str = AMpush(&stack, - AMtext(B, - AMpush(&stack, - AMmapGet(B, AM_ROOT, AMstr("text"), NULL), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id, - NULL), - AM_VALUE_STR, - cmocka_cb).str; - AMbyteSpan const str2 = AMpush(&stack, - AMtext(A, At, NULL), - AM_VALUE_STR, - cmocka_cb).str; + AMbyteSpan str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, + AMtext(B, + AMitemObjId(AMstackItem(stack_ptr, AMmapGet(B, AM_ROOT, AMstr("text"), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))), + NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), + &str)); + AMbyteSpan str2; + assert_true(AMitemToStr(AMstackItem(stack_ptr, AMtext(A, At, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str2)); assert_int_equal(str.count, str2.count); assert_memory_equal(str.src, str2.src, str.count); /* */ /* B.splice(At, 4, 1) */ - AMfree(AMspliceText(B, At, 4, 1, AMstr(NULL))); + AMstackItem(NULL, AMspliceText(B, At, 4, 1, AMstr(NULL)), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* B.splice(At, 4, 0, '!') */ - AMfree(AMspliceText(B, At, 4, 0, AMstr("!"))); + AMstackItem(NULL, AMspliceText(B, At, 4, 0, AMstr("!")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* B.splice(At, 5, 0, ' ') */ - AMfree(AMspliceText(B, At, 5, 0, AMstr(" "))); + AMstackItem(NULL, AMspliceText(B, At, 5, 0, AMstr(" ")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* B.splice(At, 6, 0, 'world') */ - AMfree(AMspliceText(B, At, 6, 0, AMstr("world"))); + AMstackItem(NULL, AMspliceText(B, At, 6, 0, AMstr("world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* A.merge(B) */ - AMfree(AMmerge(A, B)); + AMstackItem(NULL, AMmerge(A, B), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* const binary = A.save() */ - AMbyteSpan const binary = AMpush(&stack, - AMsave(A), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan binary; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(A), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &binary)); /* */ /* const C = load(binary) */ - AMdoc* const C = AMpush(&stack, - AMload(binary.src, binary.count), - AM_VALUE_DOC, - cmocka_cb).doc; + AMdoc* C; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(binary.src, binary.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &C)); /* */ /* assert.deepEqual(C.getWithType('_root', 'text'), ['text', '1@aabbcc'] */ - AMobjId const* const C_text = AMpush(&stack, - AMmapGet(C, AM_ROOT, AMstr("text"), NULL), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const C_text = AMitemObjId( + AMstackItem(stack_ptr, AMmapGet(C, AM_ROOT, AMstr("text"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); assert_int_equal(AMobjIdCounter(C_text), 1); str = AMactorIdStr(AMobjIdActorId(C_text)); assert_int_equal(str.count, strlen("aabbcc")); assert_memory_equal(str.src, "aabbcc", str.count); /* assert.deepEqual(C.text(At), 'hell! world') */ - str = AMpush(&stack, AMtext(C, At, NULL), AM_VALUE_STR, cmocka_cb).str; + assert_true(AMitemToStr(AMstackItem(stack_ptr, AMtext(C, At, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, strlen("hell! world")); assert_memory_equal(str.src, "hell! world", str.count); } int run_ported_wasm_basic_tests(void) { const struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(test_create_clone_and_free, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_start_and_commit, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_getting_a_nonexistent_prop_does_not_throw_an_error, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_set_and_get_a_simple_value, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_use_bytes, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_make_subobjects, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_make_lists, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_lists_have_insert_set_splice_and_push_ops, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_delete_non_existent_props, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_del, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_use_counters, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_splice_text, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_save_all_or_incrementally, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_splice_text_2, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_local_inc_increments_all_visible_counters_in_a_map, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_local_inc_increments_all_visible_counters_in_a_sequence, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_fetch_changes_by_hash, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_recursive_sets_are_possible, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_only_returns_an_object_id_when_objects_are_created, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_objects_without_properties_are_preserved, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_allow_you_to_forkAt_a_heads, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_handle_merging_text_conflicts_then_saving_and_loading, setup_stack, teardown_stack) - }; + cmocka_unit_test_setup_teardown(test_create_clone_and_free, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_start_and_commit, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_getting_a_nonexistent_prop_does_not_throw_an_error, setup_base, + teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_set_and_get_a_simple_value, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_use_bytes, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_make_subobjects, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_make_lists, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_lists_have_insert_set_splice_and_push_ops, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_delete_non_existent_props, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_del, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_use_counters, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_splice_text, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_save_all_or_incrementally, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_splice_text_2, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_local_inc_increments_all_visible_counters_in_a_map, setup_base, + teardown_base), + cmocka_unit_test_setup_teardown(test_local_inc_increments_all_visible_counters_in_a_sequence, setup_base, + teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_fetch_changes_by_hash, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_recursive_sets_are_possible, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_only_returns_an_object_id_when_objects_are_created, setup_base, + teardown_base), + cmocka_unit_test_setup_teardown(test_objects_without_properties_are_preserved, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_allow_you_to_forkAt_a_heads, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_handle_merging_text_conflicts_then_saving_and_loading, setup_base, + teardown_base)}; return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/rust/automerge-c/test/ported_wasm/suite.c b/rust/automerge-c/test/ported_wasm/suite.c index fc10fadc..440ed899 100644 --- a/rust/automerge-c/test/ported_wasm/suite.c +++ b/rust/automerge-c/test/ported_wasm/suite.c @@ -1,6 +1,6 @@ +#include #include #include -#include #include /* third-party */ @@ -11,8 +11,5 @@ extern int run_ported_wasm_basic_tests(void); extern int run_ported_wasm_sync_tests(void); int run_ported_wasm_suite(void) { - return ( - run_ported_wasm_basic_tests() + - run_ported_wasm_sync_tests() - ); + return (run_ported_wasm_basic_tests() + run_ported_wasm_sync_tests()); } diff --git a/rust/automerge-c/test/ported_wasm/sync_tests.c b/rust/automerge-c/test/ported_wasm/sync_tests.c index a1ddbf3c..099f8dbf 100644 --- a/rust/automerge-c/test/ported_wasm/sync_tests.c +++ b/rust/automerge-c/test/ported_wasm/sync_tests.c @@ -9,10 +9,12 @@ /* local */ #include -#include "../stack_utils.h" +#include +#include "../base_state.h" +#include "../cmocka_utils.h" typedef struct { - AMresultStack* stack; + BaseState* base_state; AMdoc* n1; AMdoc* n2; AMsyncState* s1; @@ -21,43 +23,35 @@ typedef struct { static int setup(void** state) { TestState* test_state = test_calloc(1, sizeof(TestState)); - test_state->n1 = AMpush(&test_state->stack, - AMcreate(AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("01234567")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; - test_state->n2 = AMpush(&test_state->stack, - AMcreate(AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("89abcdef")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; - test_state->s1 = AMpush(&test_state->stack, - AMsyncStateInit(), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; - test_state->s2 = AMpush(&test_state->stack, - AMsyncStateInit(), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + setup_base((void**)&test_state->base_state); + AMstack** stack_ptr = &test_state->base_state->stack; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("01234567")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + assert_true( + AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &test_state->n1)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("89abcdef")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + assert_true( + AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &test_state->n2)); + assert_true(AMitemToSyncState( + AMstackItem(stack_ptr, AMsyncStateInit(), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_STATE)), &test_state->s1)); + assert_true(AMitemToSyncState( + AMstackItem(stack_ptr, AMsyncStateInit(), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_STATE)), &test_state->s2)); *state = test_state; return 0; } static int teardown(void** state) { TestState* test_state = *state; - AMfreeStack(&test_state->stack); + teardown_base((void**)&test_state->base_state); test_free(test_state); return 0; } -static void sync(AMdoc* a, - AMdoc* b, - AMsyncState* a_sync_state, - AMsyncState* b_sync_state) { +static void sync(AMdoc* a, AMdoc* b, AMsyncState* a_sync_state, AMsyncState* b_sync_state) { static size_t const MAX_ITER = 10; AMsyncMessage const* a2b_msg = NULL; @@ -66,29 +60,35 @@ static void sync(AMdoc* a, do { AMresult* a2b_msg_result = AMgenerateSyncMessage(a, a_sync_state); AMresult* b2a_msg_result = AMgenerateSyncMessage(b, b_sync_state); - AMvalue value = AMresultValue(a2b_msg_result); - switch (value.tag) { - case AM_VALUE_SYNC_MESSAGE: { - a2b_msg = value.sync_message; - AMfree(AMreceiveSyncMessage(b, b_sync_state, a2b_msg)); - } - break; - case AM_VALUE_VOID: a2b_msg = NULL; break; + AMitem* item = AMresultItem(a2b_msg_result); + switch (AMitemValType(item)) { + case AM_VAL_TYPE_SYNC_MESSAGE: { + AMitemToSyncMessage(item, &a2b_msg); + AMstackResult(NULL, AMreceiveSyncMessage(b, b_sync_state, a2b_msg), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + } break; + case AM_VAL_TYPE_VOID: + a2b_msg = NULL; + break; } - value = AMresultValue(b2a_msg_result); - switch (value.tag) { - case AM_VALUE_SYNC_MESSAGE: { - b2a_msg = value.sync_message; - AMfree(AMreceiveSyncMessage(a, a_sync_state, b2a_msg)); - } - break; - case AM_VALUE_VOID: b2a_msg = NULL; break; + item = AMresultItem(b2a_msg_result); + switch (AMitemValType(item)) { + case AM_VAL_TYPE_SYNC_MESSAGE: { + AMitemToSyncMessage(item, &b2a_msg); + AMstackResult(NULL, AMreceiveSyncMessage(a, a_sync_state, b2a_msg), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + } break; + case AM_VAL_TYPE_VOID: + b2a_msg = NULL; + break; } if (++iter > MAX_ITER) { - fail_msg("Did not synchronize within %d iterations. " - "Do you have a bug causing an infinite loop?", MAX_ITER); + fail_msg( + "Did not synchronize within %d iterations. " + "Do you have a bug causing an infinite loop?", + MAX_ITER); } - } while(a2b_msg || b2a_msg); + } while (a2b_msg || b2a_msg); } static time_t const TIME_0 = 0; @@ -96,151 +96,135 @@ static time_t const TIME_0 = 0; /** * \brief should send a sync message implying no local data */ -static void test_should_send_a_sync_message_implying_no_local_data(void **state) { +static void test_should_send_a_sync_message_implying_no_local_data(void** state) { /* const doc = create() const s1 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* const m1 = doc.generateSyncMessage(s1) if (m1 === null) { throw new RangeError("message should not be null") } const message: DecodedSyncMessage = decodeSyncMessage(m1) */ - AMsyncMessage const* const m1 = AMpush(&test_state->stack, - AMgenerateSyncMessage( - test_state->n1, - test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + AMsyncMessage const* m1; + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &m1)); /* assert.deepStrictEqual(message.heads, []) */ - AMchangeHashes heads = AMsyncMessageHeads(m1); - assert_int_equal(AMchangeHashesSize(&heads), 0); + AMitems heads = AMstackItems(stack_ptr, AMsyncMessageHeads(m1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_int_equal(AMitemsSize(&heads), 0); /* assert.deepStrictEqual(message.need, []) */ - AMchangeHashes needs = AMsyncMessageNeeds(m1); - assert_int_equal(AMchangeHashesSize(&needs), 0); + AMitems needs = AMstackItems(stack_ptr, AMsyncMessageNeeds(m1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_int_equal(AMitemsSize(&needs), 0); /* assert.deepStrictEqual(message.have.length, 1) */ - AMsyncHaves haves = AMsyncMessageHaves(m1); - assert_int_equal(AMsyncHavesSize(&haves), 1); + AMitems haves = AMstackItems(stack_ptr, AMsyncMessageHaves(m1), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_HAVE)); + assert_int_equal(AMitemsSize(&haves), 1); /* assert.deepStrictEqual(message.have[0].lastSync, []) */ - AMsyncHave const* have0 = AMsyncHavesNext(&haves, 1); - AMchangeHashes last_sync = AMsyncHaveLastSync(have0); - assert_int_equal(AMchangeHashesSize(&last_sync), 0); + AMsyncHave const* have0; + assert_true(AMitemToSyncHave(AMitemsNext(&haves, 1), &have0)); + AMitems last_sync = + AMstackItems(stack_ptr, AMsyncHaveLastSync(have0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_int_equal(AMitemsSize(&last_sync), 0); /* assert.deepStrictEqual(message.have[0].bloom.byteLength, 0) assert.deepStrictEqual(message.changes, []) */ - AMchanges changes = AMsyncMessageChanges(m1); - assert_int_equal(AMchangesSize(&changes), 0); + AMitems changes = AMstackItems(stack_ptr, AMsyncMessageChanges(m1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&changes), 0); } /** * \brief should not reply if we have no data as well */ -static void test_should_not_reply_if_we_have_no_data_as_well(void **state) { +static void test_should_not_reply_if_we_have_no_data_as_well(void** state) { /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* const m1 = n1.generateSyncMessage(s1) if (m1 === null) { throw new RangeError("message should not be null") */ - AMsyncMessage const* const m1 = AMpush(&test_state->stack, - AMgenerateSyncMessage( - test_state->n1, - test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + AMsyncMessage const* m1; + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &m1)); /* n2.receiveSyncMessage(s2, m1) */ - AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, m1)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, m1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const m2 = n2.generateSyncMessage(s2) assert.deepStrictEqual(m2, null) */ - AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMgenerateSyncMessage(test_state->n2, test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); } /** * \brief repos with equal heads do not need a reply message */ -static void test_repos_with_equal_heads_do_not_need_a_reply_message(void **state) { +static void test_repos_with_equal_heads_do_not_need_a_reply_message(void** state) { /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* make two nodes with the same changes */ /* const list = n1.putObject("_root", "n", []) */ - AMobjId const* const list = AMpush(&test_state->stack, - AMmapPutObject(test_state->n1, - AM_ROOT, - AMstr("n"), - AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(test_state->n1, AM_ROOT, AMstr("n"), AM_OBJ_TYPE_LIST), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.insert(list, i, i) */ - AMfree(AMlistPutUint(test_state->n1, AM_ROOT, i, true, i)); + AMstackItem(NULL, AMlistPutUint(test_state->n1, list, i, true, i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* n2.applyChanges(n1.getChanges([])) */ - AMchanges const changes = AMpush(&test_state->stack, - AMgetChanges(test_state->n1, NULL), - AM_VALUE_CHANGES, - cmocka_cb).changes; - AMfree(AMapplyChanges(test_state->n2, &changes)); + AMitems const items = + AMstackItems(stack_ptr, AMgetChanges(test_state->n1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMstackItem(NULL, AMapplyChanges(test_state->n2, &items), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); /* */ /* generate a naive sync message */ /* const m1 = n1.generateSyncMessage(s1) if (m1 === null) { throw new RangeError("message should not be null") */ - AMsyncMessage const* m1 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, - test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + AMsyncMessage const* m1; + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &m1)); /* assert.deepStrictEqual(s1.lastSentHeads, n1.getHeads()) */ - AMchangeHashes const last_sent_heads = AMsyncStateLastSentHeads( - test_state->s1 - ); - AMchangeHashes const heads = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&last_sent_heads, &heads), 0); + AMitems const last_sent_heads = + AMstackItems(stack_ptr, AMsyncStateLastSentHeads(test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems const heads = + AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&last_sent_heads, &heads)); /* */ /* heads are equal so this message should be null */ /* n2.receiveSyncMessage(s2, m1) */ - AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, m1)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, m1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const m2 = n2.generateSyncMessage(s2) assert.strictEqual(m2, null) */ - AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); } /** * \brief n1 should offer all changes to n2 when starting from nothing */ -static void test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing(void **state) { +static void test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing(void** state) { /* const n1 = create(), n2 = create() */ TestState* test_state = *state; - + AMstack** stack_ptr = &test_state->base_state->stack; /* make changes for n1 that n2 should request */ /* const list = n1.putObject("_root", "n", []) */ - AMobjId const* const list = AMpush( - &test_state->stack, - AMmapPutObject(test_state->n1, AM_ROOT, AMstr("n"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(test_state->n1, AM_ROOT, AMstr("n"), AM_OBJ_TYPE_LIST), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.insert(list, i, i) */ - AMfree(AMlistPutUint(test_state->n1, AM_ROOT, i, true, i)); + AMstackItem(NULL, AMlistPutUint(test_state->n1, list, i, true, i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ @@ -254,26 +238,24 @@ static void test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing(vo /** * \brief should sync peers where one has commits the other does not */ -static void test_should_sync_peers_where_one_has_commits_the_other_does_not(void **state) { +static void test_should_sync_peers_where_one_has_commits_the_other_does_not(void** state) { /* const n1 = create(), n2 = create() */ TestState* test_state = *state; - + AMstack** stack_ptr = &test_state->base_state->stack; /* make changes for n1 that n2 should request */ /* const list = n1.putObject("_root", "n", []) */ - AMobjId const* const list = AMpush( - &test_state->stack, - AMmapPutObject(test_state->n1, AM_ROOT, AMstr("n"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(test_state->n1, AM_ROOT, AMstr("n"), AM_OBJ_TYPE_LIST), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.insert(list, i, i) */ - AMfree(AMlistPutUint(test_state->n1, AM_ROOT, i, true, i)); + AMstackItem(NULL, AMlistPutUint(test_state->n1, list, i, true, i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ @@ -287,19 +269,20 @@ static void test_should_sync_peers_where_one_has_commits_the_other_does_not(void /** * \brief should work with prior sync state */ -static void test_should_work_with_prior_sync_state(void **state) { +static void test_should_work_with_prior_sync_state(void** state) { /* create & synchronize two nodes */ /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); @@ -308,10 +291,10 @@ static void test_should_work_with_prior_sync_state(void **state) { /* for (let i = 5; i < 10; i++) { */ for (size_t i = 5; i != 10; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ @@ -325,326 +308,333 @@ static void test_should_work_with_prior_sync_state(void **state) { /** * \brief should not generate messages once synced */ -static void test_should_not_generate_messages_once_synced(void **state) { +static void test_should_not_generate_messages_once_synced(void** state) { /* create & synchronize two nodes */ /* const n1 = create('abc123'), n2 = create('def456') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("abc123")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); - AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("def456")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); + AMstack** stack_ptr = &test_state->base_state->stack; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("abc123")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMstackItem(NULL, AMsetActorId(test_state->n1, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("def456")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMstackItem(NULL, AMsetActorId(test_state->n2, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* let message, patch for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n2.put("_root", "y", i) */ - AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("y"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("y"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n2.commit("", 0) */ - AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* n1 reports what it has */ /* message = n1.generateSyncMessage(s1) - if (message === null) { throw new RangeError("message should not be null") */ - AMsyncMessage const* message = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, - test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (message === null) { throw new RangeError("message should not be + null") */ + AMsyncMessage const* message; + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &message)); /* */ /* n2 receives that message and sends changes along with what it has */ /* n2.receiveSyncMessage(s2, message) */ - AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, message)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, message), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* message = n2.generateSyncMessage(s2) - if (message === null) { throw new RangeError("message should not be null") */ - message = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; - AMchanges message_changes = AMsyncMessageChanges(message); - assert_int_equal(AMchangesSize(&message_changes), 5); + if (message === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &message)); + AMitems message_changes = + AMstackItems(stack_ptr, AMsyncMessageChanges(message), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&message_changes), 5); /* */ /* n1 receives the changes and replies with the changes it now knows that * n2 needs */ /* n1.receiveSyncMessage(s1, message) */ - AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, message)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n1, test_state->s1, message), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* message = n2.generateSyncMessage(s2) - if (message === null) { throw new RangeError("message should not be null") */ - message = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; - message_changes = AMsyncMessageChanges(message); - assert_int_equal(AMchangesSize(&message_changes), 5); + if (message === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &message)); + message_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(message), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&message_changes), 5); /* */ /* n2 applies the changes and sends confirmation ending the exchange */ /* n2.receiveSyncMessage(s2, message) */ - AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, message)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, message), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* message = n2.generateSyncMessage(s2) - if (message === null) { throw new RangeError("message should not be null") */ - message = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (message === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &message)); /* */ /* n1 receives the message and has nothing more to say */ /* n1.receiveSyncMessage(s1, message) */ - AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, message)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n1, test_state->s1, message), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* message = n1.generateSyncMessage(s1) assert.deepStrictEqual(message, null) */ - AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMgenerateSyncMessage(test_state->n1, test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* //assert.deepStrictEqual(patch, null) // no changes arrived */ /* */ /* n2 also has nothing left to say */ /* message = n2.generateSyncMessage(s2) assert.deepStrictEqual(message, null) */ - AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMgenerateSyncMessage(test_state->n2, test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); } /** * \brief should allow simultaneous messages during synchronization */ -static void test_should_allow_simultaneous_messages_during_synchronization(void **state) { +static void test_should_allow_simultaneous_messages_during_synchronization(void** state) { /* create & synchronize two nodes */ /* const n1 = create('abc123'), n2 = create('def456') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("abc123")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); - AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("def456")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); + AMstack** stack_ptr = &test_state->base_state->stack; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("abc123")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMstackItem(NULL, AMsetActorId(test_state->n1, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("def456")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMstackItem(NULL, AMsetActorId(test_state->n2, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n2.put("_root", "y", i) */ - AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("y"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("y"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n2.commit("", 0) */ - AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* const head1 = n1.getHeads()[0], head2 = n2.getHeads()[0] */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMbyteSpan const head1 = AMchangeHashesNext(&heads1, 1); - AMchangeHashes heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMbyteSpan const head2 = AMchangeHashesNext(&heads2, 1); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMbyteSpan head1; + assert_true(AMitemToChangeHash(AMitemsNext(&heads1, 1), &head1)); + AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMbyteSpan head2; + assert_true(AMitemToChangeHash(AMitemsNext(&heads2, 1), &head2)); /* */ /* both sides report what they have but have no shared peer state */ /* let msg1to2, msg2to1 msg1to2 = n1.generateSyncMessage(s1) - if (msg1to2 === null) { throw new RangeError("message should not be null") */ - AMsyncMessage const* msg1to2 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, - test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (msg1to2 === null) { throw new RangeError("message should not be + null") */ + AMsyncMessage const* msg1to2; + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &msg1to2)); /* msg2to1 = n2.generateSyncMessage(s2) - if (msg2to1 === null) { throw new RangeError("message should not be null") */ - AMsyncMessage const* msg2to1 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, - test_state->s2), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (msg2to1 === null) { throw new RangeError("message should not be + null") */ + AMsyncMessage const* msg2to1; + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &msg2to1)); /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).changes.length, 0) */ - AMchanges msg1to2_changes = AMsyncMessageChanges(msg1to2); - assert_int_equal(AMchangesSize(&msg1to2_changes), 0); - /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).have[0].lastSync.length, 0 */ - AMsyncHaves msg1to2_haves = AMsyncMessageHaves(msg1to2); - AMsyncHave const* msg1to2_have = AMsyncHavesNext(&msg1to2_haves, 1); - AMchangeHashes msg1to2_last_sync = AMsyncHaveLastSync(msg1to2_have); - assert_int_equal(AMchangeHashesSize(&msg1to2_last_sync), 0); + AMitems msg1to2_changes = + AMstackItems(stack_ptr, AMsyncMessageChanges(msg1to2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&msg1to2_changes), 0); + /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).have[0].lastSync.length, + * 0 */ + AMitems msg1to2_haves = + AMstackItems(stack_ptr, AMsyncMessageHaves(msg1to2), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_HAVE)); + AMsyncHave const* msg1to2_have; + assert_true(AMitemToSyncHave(AMitemsNext(&msg1to2_haves, 1), &msg1to2_have)); + AMitems msg1to2_last_sync = + AMstackItems(stack_ptr, AMsyncHaveLastSync(msg1to2_have), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_int_equal(AMitemsSize(&msg1to2_last_sync), 0); /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).changes.length, 0) */ - AMchanges msg2to1_changes = AMsyncMessageChanges(msg2to1); - assert_int_equal(AMchangesSize(&msg2to1_changes), 0); - /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).have[0].lastSync.length, 0 */ - AMsyncHaves msg2to1_haves = AMsyncMessageHaves(msg2to1); - AMsyncHave const* msg2to1_have = AMsyncHavesNext(&msg2to1_haves, 1); - AMchangeHashes msg2to1_last_sync = AMsyncHaveLastSync(msg2to1_have); - assert_int_equal(AMchangeHashesSize(&msg2to1_last_sync), 0); + AMitems msg2to1_changes = + AMstackItems(stack_ptr, AMsyncMessageChanges(msg2to1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&msg2to1_changes), 0); + /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).have[0].lastSync.length, + * 0 */ + AMitems msg2to1_haves = + AMstackItems(stack_ptr, AMsyncMessageHaves(msg2to1), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_HAVE)); + AMsyncHave const* msg2to1_have; + assert_true(AMitemToSyncHave(AMitemsNext(&msg2to1_haves, 1), &msg2to1_have)); + AMitems msg2to1_last_sync = + AMstackItems(stack_ptr, AMsyncHaveLastSync(msg2to1_have), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_int_equal(AMitemsSize(&msg2to1_last_sync), 0); /* */ /* n1 and n2 receive that message and update sync state but make no patc */ /* n1.receiveSyncMessage(s1, msg2to1) */ - AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* n2.receiveSyncMessage(s2, msg1to2) */ - AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* */ /* now both reply with their local changes that the other lacks * (standard warning that 1% of the time this will result in a "needs" * message) */ /* msg1to2 = n1.generateSyncMessage(s1) - if (msg1to2 === null) { throw new RangeError("message should not be null") */ - msg1to2 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (msg1to2 === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &msg1to2)); /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).changes.length, 5) */ - msg1to2_changes = AMsyncMessageChanges(msg1to2); - assert_int_equal(AMchangesSize(&msg1to2_changes), 5); + msg1to2_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(msg1to2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&msg1to2_changes), 5); /* msg2to1 = n2.generateSyncMessage(s2) - if (msg2to1 === null) { throw new RangeError("message should not be null") */ - msg2to1 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (msg2to1 === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &msg2to1)); /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).changes.length, 5) */ - msg2to1_changes = AMsyncMessageChanges(msg2to1); - assert_int_equal(AMchangesSize(&msg2to1_changes), 5); + msg2to1_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(msg2to1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&msg2to1_changes), 5); /* */ /* both should now apply the changes and update the frontend */ /* n1.receiveSyncMessage(s1, msg2to1) */ - AMfree(AMreceiveSyncMessage(test_state->n1, - test_state->s1, - msg2to1)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepStrictEqual(n1.getMissingDeps(), []) */ - AMchangeHashes missing_deps = AMpush(&test_state->stack, - AMgetMissingDeps(test_state->n1, NULL), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesSize(&missing_deps), 0); + AMitems missing_deps = + AMstackItems(stack_ptr, AMgetMissingDeps(test_state->n1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_int_equal(AMitemsSize(&missing_deps), 0); /* //assert.notDeepStrictEqual(patch1, null) assert.deepStrictEqual(n1.materialize(), { x: 4, y: 4 }) */ - assert_int_equal(AMpush(&test_state->stack, - AMmapGet(test_state->n1, AM_ROOT, AMstr("x"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, 4); - assert_int_equal(AMpush(&test_state->stack, - AMmapGet(test_state->n1, AM_ROOT, AMstr("y"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, 4); + uint64_t uint; + assert_true(AMitemToUint(AMstackItem(stack_ptr, AMmapGet(test_state->n1, AM_ROOT, AMstr("x"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)), + &uint)); + assert_int_equal(uint, 4); + assert_true(AMitemToUint(AMstackItem(stack_ptr, AMmapGet(test_state->n1, AM_ROOT, AMstr("y"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)), + &uint)); + assert_int_equal(uint, 4); /* */ /* n2.receiveSyncMessage(s2, msg1to2) */ - AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepStrictEqual(n2.getMissingDeps(), []) */ - missing_deps = AMpush(&test_state->stack, - AMgetMissingDeps(test_state->n2, NULL), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesSize(&missing_deps), 0); + missing_deps = + AMstackItems(stack_ptr, AMgetMissingDeps(test_state->n2, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_int_equal(AMitemsSize(&missing_deps), 0); /* //assert.notDeepStrictEqual(patch2, null) assert.deepStrictEqual(n2.materialize(), { x: 4, y: 4 }) */ - assert_int_equal(AMpush(&test_state->stack, - AMmapGet(test_state->n2, AM_ROOT, AMstr("x"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, 4); - assert_int_equal(AMpush(&test_state->stack, - AMmapGet(test_state->n2, AM_ROOT, AMstr("y"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, 4); + assert_true(AMitemToUint(AMstackItem(stack_ptr, AMmapGet(test_state->n2, AM_ROOT, AMstr("x"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)), + &uint)); + assert_int_equal(uint, 4); + assert_true(AMitemToUint(AMstackItem(stack_ptr, AMmapGet(test_state->n2, AM_ROOT, AMstr("y"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)), + &uint)); + assert_int_equal(uint, 4); /* */ /* The response acknowledges the changes received and sends no further * changes */ /* msg1to2 = n1.generateSyncMessage(s1) - if (msg1to2 === null) { throw new RangeError("message should not be null") */ - msg1to2 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (msg1to2 === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &msg1to2)); /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).changes.length, 0) */ - msg1to2_changes = AMsyncMessageChanges(msg1to2); - assert_int_equal(AMchangesSize(&msg1to2_changes), 0); + msg1to2_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(msg1to2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&msg1to2_changes), 0); /* msg2to1 = n2.generateSyncMessage(s2) - if (msg2to1 === null) { throw new RangeError("message should not be null") */ - msg2to1 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (msg2to1 === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &msg2to1)); /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).changes.length, 0) */ - msg2to1_changes = AMsyncMessageChanges(msg2to1); - assert_int_equal(AMchangesSize(&msg2to1_changes), 0); + msg2to1_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(msg2to1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&msg2to1_changes), 0); /* */ /* After receiving acknowledgements, their shared heads should be equal */ /* n1.receiveSyncMessage(s1, msg2to1) */ - AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* n2.receiveSyncMessage(s2, msg1to2) */ - AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepStrictEqual(s1.sharedHeads, [head1, head2].sort()) */ - AMchangeHashes s1_shared_heads = AMsyncStateSharedHeads(test_state->s1); - assert_memory_equal(AMchangeHashesNext(&s1_shared_heads, 1).src, - head1.src, - head1.count); - assert_memory_equal(AMchangeHashesNext(&s1_shared_heads, 1).src, - head2.src, - head2.count); + AMitems s1_shared_heads = + AMstackItems(stack_ptr, AMsyncStateSharedHeads(test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMbyteSpan s1_shared_change_hash; + assert_true(AMitemToChangeHash(AMitemsNext(&s1_shared_heads, 1), &s1_shared_change_hash)); + assert_memory_equal(s1_shared_change_hash.src, head1.src, head1.count); + assert_true(AMitemToChangeHash(AMitemsNext(&s1_shared_heads, 1), &s1_shared_change_hash)); + assert_memory_equal(s1_shared_change_hash.src, head2.src, head2.count); /* assert.deepStrictEqual(s2.sharedHeads, [head1, head2].sort()) */ - AMchangeHashes s2_shared_heads = AMsyncStateSharedHeads(test_state->s2); - assert_memory_equal(AMchangeHashesNext(&s2_shared_heads, 1).src, - head1.src, - head1.count); - assert_memory_equal(AMchangeHashesNext(&s2_shared_heads, 1).src, - head2.src, - head2.count); + AMitems s2_shared_heads = + AMstackItems(stack_ptr, AMsyncStateSharedHeads(test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMbyteSpan s2_shared_change_hash; + assert_true(AMitemToChangeHash(AMitemsNext(&s2_shared_heads, 1), &s2_shared_change_hash)); + assert_memory_equal(s2_shared_change_hash.src, head1.src, head1.count); + assert_true(AMitemToChangeHash(AMitemsNext(&s2_shared_heads, 1), &s2_shared_change_hash)); + assert_memory_equal(s2_shared_change_hash.src, head2.src, head2.count); /* //assert.deepStrictEqual(patch1, null) //assert.deepStrictEqual(patch2, null) */ /* */ /* We're in sync, no more messages required */ /* msg1to2 = n1.generateSyncMessage(s1) assert.deepStrictEqual(msg1to2, null) */ - AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMgenerateSyncMessage(test_state->n1, test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* msg2to1 = n2.generateSyncMessage(s2) assert.deepStrictEqual(msg2to1, null) */ - AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMgenerateSyncMessage(test_state->n2, test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* If we make one more change and start another sync then its lastSync * should be updated */ /* n1.put("_root", "x", 5) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 5)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* msg1to2 = n1.generateSyncMessage(s1) - if (msg1to2 === null) { throw new RangeError("message should not be null") */ - msg1to2 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; - /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).have[0].lastSync, [head1, head2].sort( */ - msg1to2_haves = AMsyncMessageHaves(msg1to2); - msg1to2_have = AMsyncHavesNext(&msg1to2_haves, 1); - msg1to2_last_sync = AMsyncHaveLastSync(msg1to2_have); - AMbyteSpan msg1to2_last_sync_next = AMchangeHashesNext(&msg1to2_last_sync, 1); + if (msg1to2 === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &msg1to2)); + /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).have[0].lastSync, + * [head1, head2].sort( */ + msg1to2_haves = AMstackItems(stack_ptr, AMsyncMessageHaves(msg1to2), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_HAVE)); + assert_true(AMitemToSyncHave(AMitemsNext(&msg1to2_haves, 1), &msg1to2_have)); + msg1to2_last_sync = + AMstackItems(stack_ptr, AMsyncHaveLastSync(msg1to2_have), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMbyteSpan msg1to2_last_sync_next; + assert_true(AMitemToChangeHash(AMitemsNext(&msg1to2_last_sync, 1), &msg1to2_last_sync_next)); assert_int_equal(msg1to2_last_sync_next.count, head1.count); assert_memory_equal(msg1to2_last_sync_next.src, head1.src, head1.count); - msg1to2_last_sync_next = AMchangeHashesNext(&msg1to2_last_sync, 1); + assert_true(AMitemToChangeHash(AMitemsNext(&msg1to2_last_sync, 1), &msg1to2_last_sync_next)); assert_int_equal(msg1to2_last_sync_next.count, head2.count); assert_memory_equal(msg1to2_last_sync_next.src, head2.src, head2.count); } @@ -652,87 +642,89 @@ static void test_should_allow_simultaneous_messages_during_synchronization(void /** * \brief should assume sent changes were received until we hear otherwise */ -static void test_should_assume_sent_changes_were_received_until_we_hear_otherwise(void **state) { +static void test_should_assume_sent_changes_were_received_until_we_hear_otherwise(void** state) { /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* let message = null */ /* */ /* const items = n1.putObject("_root", "items", []) */ - AMobjId const* items = AMpush(&test_state->stack, - AMmapPutObject(test_state->n1, - AM_ROOT, - AMstr("items"), - AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const items = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(test_state->n1, AM_ROOT, AMstr("items"), AM_OBJ_TYPE_LIST), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* n1.push(items, "x") */ - AMfree(AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("x"))); + AMstackItem(NULL, AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("x")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* message = n1.generateSyncMessage(s1) - if (message === null) { throw new RangeError("message should not be null") */ - AMsyncMessage const* message = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, - test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (message === null) { throw new RangeError("message should not be null") + */ + AMsyncMessage const* message; + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &message)); /* assert.deepStrictEqual(decodeSyncMessage(message).changes.length, 1) */ - AMchanges message_changes = AMsyncMessageChanges(message); - assert_int_equal(AMchangesSize(&message_changes), 1); + AMitems message_changes = + AMstackItems(stack_ptr, AMsyncMessageChanges(message), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&message_changes), 1); /* */ /* n1.push(items, "y") */ - AMfree(AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("y"))); + AMstackItem(NULL, AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("y")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* message = n1.generateSyncMessage(s1) - if (message === null) { throw new RangeError("message should not be null") */ - message = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (message === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &message)); /* assert.deepStrictEqual(decodeSyncMessage(message).changes.length, 1) */ - message_changes = AMsyncMessageChanges(message); - assert_int_equal(AMchangesSize(&message_changes), 1); + message_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(message), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&message_changes), 1); /* */ /* n1.push(items, "z") */ - AMfree(AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("z"))); + AMstackItem(NULL, AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("z")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* message = n1.generateSyncMessage(s1) - if (message === null) { throw new RangeError("message should not be null") */ - message = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (message === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &message)); /* assert.deepStrictEqual(decodeSyncMessage(message).changes.length, 1) */ - message_changes = AMsyncMessageChanges(message); - assert_int_equal(AMchangesSize(&message_changes), 1); + message_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(message), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&message_changes), 1); } /** * \brief should work regardless of who initiates the exchange */ -static void test_should_work_regardless_of_who_initiates_the_exchange(void **state) { +static void test_should_work_regardless_of_who_initiates_the_exchange(void** state) { /* create & synchronize two nodes */ /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ @@ -742,10 +734,10 @@ static void test_should_work_regardless_of_who_initiates_the_exchange(void **sta /* for (let i = 5; i < 10; i++) { */ for (size_t i = 5; i != 10; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ @@ -759,24 +751,26 @@ static void test_should_work_regardless_of_who_initiates_the_exchange(void **sta /** * \brief should work without prior sync state */ -static void test_should_work_without_prior_sync_state(void **state) { - /* Scenario: ,-- c10 <-- c11 <-- c12 <-- c13 <-- c14 - * c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 <-- c6 <-- c7 <-- c8 <-- c9 <-+ - * `-- c15 <-- c16 <-- c17 - * lastSync is undefined. */ +static void test_should_work_without_prior_sync_state(void** state) { + /* Scenario: ,-- + * c10 <-- c11 <-- c12 <-- c13 <-- c14 c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 + * <-- c6 <-- c7 <-- c8 <-- c9 <-+ + * `-- + * c15 <-- c16 <-- c17 lastSync is undefined. */ /* */ /* create two peers both with divergent commits */ /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* sync(n1, n2) */ @@ -785,19 +779,19 @@ static void test_should_work_without_prior_sync_state(void **state) { /* for (let i = 10; i < 15; i++) { */ for (size_t i = 10; i != 15; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* for (let i = 15; i < 18; i++) { */ for (size_t i = 15; i != 18; ++i) { /* n2.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n2.commit("", 0) */ - AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ @@ -805,15 +799,9 @@ static void test_should_work_without_prior_sync_state(void **state) { /* sync(n1, n2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads2)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } @@ -821,25 +809,27 @@ static void test_should_work_without_prior_sync_state(void **state) { /** * \brief should work with prior sync state */ -static void test_should_work_with_prior_sync_state_2(void **state) { +static void test_should_work_with_prior_sync_state_2(void** state) { /* Scenario: - * ,-- c10 <-- c11 <-- c12 <-- c13 <-- c14 - * c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 <-- c6 <-- c7 <-- c8 <-- c9 <-+ - * `-- c15 <-- c16 <-- c17 - * lastSync is c9. */ + * ,-- + * c10 <-- c11 <-- c12 <-- c13 <-- c14 c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 + * <-- c6 <-- c7 <-- c8 <-- c9 <-+ + * `-- + * c15 <-- c16 <-- c17 lastSync is c9. */ /* */ /* create two peers both with divergent commits */ /* const n1 = create('01234567'), n2 = create('89abcdef') let s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ @@ -848,54 +838,44 @@ static void test_should_work_with_prior_sync_state_2(void **state) { /* for (let i = 10; i < 15; i++) { */ for (size_t i = 10; i != 15; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* for (let i = 15; i < 18; i++) { */ for (size_t i = 15; i != 18; ++i) { /* n2.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n2.commit("", 0) */ - AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* s1 = decodeSyncState(encodeSyncState(s1)) */ - AMbyteSpan encoded = AMpush(&test_state->stack, - AMsyncStateEncode(test_state->s1), - AM_VALUE_BYTES, - cmocka_cb).bytes; - AMsyncState* s1 = AMpush(&test_state->stack, - AMsyncStateDecode(encoded.src, encoded.count), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + AMbyteSpan encoded; + assert_true(AMitemToBytes( + AMstackItem(stack_ptr, AMsyncStateEncode(test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &encoded)); + AMsyncState* s1; + assert_true(AMitemToSyncState(AMstackItem(stack_ptr, AMsyncStateDecode(encoded.src, encoded.count), cmocka_cb, + AMexpect(AM_VAL_TYPE_SYNC_STATE)), + &s1)); /* s2 = decodeSyncState(encodeSyncState(s2)) */ - encoded = AMpush(&test_state->stack, - AMsyncStateEncode(test_state->s2), - AM_VALUE_BYTES, - cmocka_cb).bytes; - AMsyncState* s2 = AMpush(&test_state->stack, - AMsyncStateDecode(encoded.src, - encoded.count), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + assert_true(AMitemToBytes( + AMstackItem(stack_ptr, AMsyncStateEncode(test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &encoded)); + AMsyncState* s2; + assert_true(AMitemToSyncState(AMstackItem(stack_ptr, AMsyncStateDecode(encoded.src, encoded.count), cmocka_cb, + AMexpect(AM_VAL_TYPE_SYNC_STATE)), + &s2)); /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ assert_false(AMequal(test_state->n1, test_state->n2)); /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, s1, s2); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads2)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } @@ -903,39 +883,39 @@ static void test_should_work_with_prior_sync_state_2(void **state) { /** * \brief should ensure non-empty state after sync */ -static void test_should_ensure_non_empty_state_after_sync(void **state) { +static void test_should_ensure_non_empty_state_after_sync(void** state) { /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* for (let i = 0; i < 3; i++) { */ for (size_t i = 0; i != 3; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* assert.deepStrictEqual(s1.sharedHeads, n1.getHeads()) */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes shared_heads1 = AMsyncStateSharedHeads(test_state->s1); - assert_int_equal(AMchangeHashesCmp(&shared_heads1, &heads1), 0); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems shared_heads1 = + AMstackItems(stack_ptr, AMsyncStateSharedHeads(test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&shared_heads1, &heads1)); /* assert.deepStrictEqual(s2.sharedHeads, n1.getHeads()) */ - AMchangeHashes shared_heads2 = AMsyncStateSharedHeads(test_state->s2); - assert_int_equal(AMchangeHashesCmp(&shared_heads2, &heads1), 0); + AMitems shared_heads2 = + AMstackItems(stack_ptr, AMsyncStateSharedHeads(test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&shared_heads2, &heads1)); } /** * \brief should re-sync after one node crashed with data loss */ -static void test_should_resync_after_one_node_crashed_with_data_loss(void **state) { +static void test_should_resync_after_one_node_crashed_with_data_loss(void** state) { /* Scenario: (r) (n2) (n1) * c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 <-- c6 <-- c7 <-- c8 * n2 has changes {c0, c1, c2}, n1's lastSync is c5, and n2's lastSync @@ -946,15 +926,16 @@ static void test_should_resync_after_one_node_crashed_with_data_loss(void **stat let s1 = initSyncState() const s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* n1 makes three changes, which we sync to n2 */ /* for (let i = 0; i < 3; i++) { */ for (size_t i = 0; i != 3; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); @@ -963,28 +944,25 @@ static void test_should_resync_after_one_node_crashed_with_data_loss(void **stat /* let r let rSyncState ;[r, rSyncState] = [n2.clone(), s2.clone()] */ - AMdoc* r = AMpush(&test_state->stack, - AMclone(test_state->n2), - AM_VALUE_DOC, - cmocka_cb).doc; - AMbyteSpan const encoded_s2 = AMpush(&test_state->stack, - AMsyncStateEncode(test_state->s2), - AM_VALUE_BYTES, - cmocka_cb).bytes; - AMsyncState* sync_state_r = AMpush(&test_state->stack, - AMsyncStateDecode(encoded_s2.src, - encoded_s2.count), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + AMdoc* r; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMclone(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &r)); + AMbyteSpan encoded_s2; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsyncStateEncode(test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), + &encoded_s2)); + AMsyncState* sync_state_r; + assert_true(AMitemToSyncState(AMstackItem(stack_ptr, AMsyncStateDecode(encoded_s2.src, encoded_s2.count), cmocka_cb, + AMexpect(AM_VAL_TYPE_SYNC_STATE)), + &sync_state_r)); /* */ /* sync another few commits */ /* for (let i = 3; i < 6; i++) { */ for (size_t i = 3; i != 6; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ @@ -992,15 +970,9 @@ static void test_should_resync_after_one_node_crashed_with_data_loss(void **stat /* */ /* everyone should be on the same page here */ /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads2)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); /* */ @@ -1009,132 +981,106 @@ static void test_should_resync_after_one_node_crashed_with_data_loss(void **stat /* for (let i = 6; i < 9; i++) { */ for (size_t i = 6; i != 9; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* s1 = decodeSyncState(encodeSyncState(s1)) */ - AMbyteSpan const encoded_s1 = AMpush(&test_state->stack, - AMsyncStateEncode(test_state->s1), - AM_VALUE_BYTES, - cmocka_cb).bytes; - AMsyncState* const s1 = AMpush(&test_state->stack, - AMsyncStateDecode(encoded_s1.src, - encoded_s1.count), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + AMbyteSpan encoded_s1; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsyncStateEncode(test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), + &encoded_s1)); + AMsyncState* s1; + assert_true(AMitemToSyncState(AMstackItem(stack_ptr, AMsyncStateDecode(encoded_s1.src, encoded_s1.count), cmocka_cb, + AMexpect(AM_VAL_TYPE_SYNC_STATE)), + &s1)); /* rSyncState = decodeSyncState(encodeSyncState(rSyncState)) */ - AMbyteSpan const encoded_r = AMpush(&test_state->stack, - AMsyncStateEncode(sync_state_r), - AM_VALUE_BYTES, - cmocka_cb).bytes; - sync_state_r = AMpush(&test_state->stack, - AMsyncStateDecode(encoded_r.src, encoded_r.count), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + AMbyteSpan encoded_r; + assert_true(AMitemToBytes( + AMstackItem(stack_ptr, AMsyncStateEncode(sync_state_r), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &encoded_r)); + assert_true(AMitemToSyncState(AMstackItem(stack_ptr, AMsyncStateDecode(encoded_r.src, encoded_r.count), cmocka_cb, + AMexpect(AM_VAL_TYPE_SYNC_STATE)), + &sync_state_r)); /* */ /* assert.notDeepStrictEqual(n1.getHeads(), r.getHeads()) */ - heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes heads_r = AMpush(&test_state->stack, - AMgetHeads(r), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_not_equal(AMchangeHashesCmp(&heads1, &heads_r), 0); + heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems heads_r = AMstackItems(stack_ptr, AMgetHeads(r), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_false(AMitemsEqual(&heads1, &heads_r)); /* assert.notDeepStrictEqual(n1.materialize(), r.materialize()) */ assert_false(AMequal(test_state->n1, r)); /* assert.deepStrictEqual(n1.materialize(), { x: 8 }) */ - assert_int_equal(AMpush(&test_state->stack, - AMmapGet(test_state->n1, AM_ROOT, AMstr("x"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, 8); + uint64_t uint; + assert_true(AMitemToUint(AMstackItem(stack_ptr, AMmapGet(test_state->n1, AM_ROOT, AMstr("x"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)), + &uint)); + assert_int_equal(uint, 8); /* assert.deepStrictEqual(r.materialize(), { x: 2 }) */ - assert_int_equal(AMpush(&test_state->stack, - AMmapGet(r, AM_ROOT, AMstr("x"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, 2); + assert_true(AMitemToUint( + AMstackItem(stack_ptr, AMmapGet(r, AM_ROOT, AMstr("x"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT)), &uint)); + assert_int_equal(uint, 2); /* sync(n1, r, s1, rSyncState) */ sync(test_state->n1, r, test_state->s1, sync_state_r); /* assert.deepStrictEqual(n1.getHeads(), r.getHeads()) */ - heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - heads_r = AMpush(&test_state->stack, - AMgetHeads(r), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads_r), 0); + heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + heads_r = AMstackItems(stack_ptr, AMgetHeads(r), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads_r)); /* assert.deepStrictEqual(n1.materialize(), r.materialize()) */ assert_true(AMequal(test_state->n1, r)); } /** - * \brief should re-sync after one node experiences data loss without disconnecting + * \brief should re-sync after one node experiences data loss without + * disconnecting */ -static void test_should_resync_after_one_node_experiences_data_loss_without_disconnecting(void **state) { +static void test_should_resync_after_one_node_experiences_data_loss_without_disconnecting(void** state) { /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* n1 makes three changes which we sync to n2 */ /* for (let i = 0; i < 3; i++) { */ for (size_t i = 0; i != 3; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads2)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); /* */ /* const n2AfterDataLoss = create('89abcdef') */ - AMdoc* n2_after_data_loss = AMpush(&test_state->stack, - AMcreate(AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("89abcdef")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("89abcdef")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMdoc* n2_after_data_loss; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), + &n2_after_data_loss)); /* */ /* "n2" now has no data, but n1 still thinks it does. Note we don't do * decodeSyncState(encodeSyncState(s1)) in order to simulate data loss * without disconnecting */ /* sync(n1, n2AfterDataLoss, s1, initSyncState()) */ - AMsyncState* s2_after_data_loss = AMpush(&test_state->stack, - AMsyncStateInit(), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + AMsyncState* s2_after_data_loss; + assert_true(AMitemToSyncState( + AMstackItem(stack_ptr, AMsyncStateInit(), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_STATE)), &s2_after_data_loss)); sync(test_state->n1, n2_after_data_loss, test_state->s1, s2_after_data_loss); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); + heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads2)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } @@ -1142,33 +1088,33 @@ static void test_should_resync_after_one_node_experiences_data_loss_without_disc /** * \brief should handle changes concurrent to the last sync heads */ -static void test_should_handle_changes_concurrrent_to_the_last_sync_heads(void **state) { - /* const n1 = create('01234567'), n2 = create('89abcdef'), n3 = create('fedcba98' */ +static void test_should_handle_changes_concurrrent_to_the_last_sync_heads(void** state) { + /* const n1 = create('01234567'), n2 = create('89abcdef'), n3 = + * create('fedcba98' */ TestState* test_state = *state; - AMdoc* n3 = AMpush(&test_state->stack, - AMcreate(AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("fedcba98")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; - /* const s12 = initSyncState(), s21 = initSyncState(), s23 = initSyncState(), s32 = initSyncState( */ + AMstack** stack_ptr = &test_state->base_state->stack; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("fedcba98")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMdoc* n3; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &n3)); + /* const s12 = initSyncState(), s21 = initSyncState(), s23 = + * initSyncState(), s32 = initSyncState( */ AMsyncState* s12 = test_state->s1; AMsyncState* s21 = test_state->s2; - AMsyncState* s23 = AMpush(&test_state->stack, - AMsyncStateInit(), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; - AMsyncState* s32 = AMpush(&test_state->stack, - AMsyncStateInit(), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + AMsyncState* s23; + assert_true(AMitemToSyncState( + AMstackItem(stack_ptr, AMsyncStateInit(), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_STATE)), &s23)); + AMsyncState* s32; + assert_true(AMitemToSyncState( + AMstackItem(stack_ptr, AMsyncStateInit(), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_STATE)), &s32)); /* */ /* Change 1 is known to all three nodes */ /* //n1 = Automerge.change(n1, {time: 0}, doc => doc.x = 1) */ /* n1.put("_root", "x", 1); n1.commit("", 0) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 1)); - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* sync(n1, n2, s12, s21) */ sync(test_state->n1, test_state->n2, s12, s21); @@ -1177,47 +1123,38 @@ static void test_should_handle_changes_concurrrent_to_the_last_sync_heads(void * /* */ /* Change 2 is known to n1 and n2 */ /* n1.put("_root", "x", 2); n1.commit("", 0) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 2)); - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* sync(n1, n2, s12, s21) */ sync(test_state->n1, test_state->n2, s12, s21); /* */ /* Each of the three nodes makes one change (changes 3, 4, 5) */ /* n1.put("_root", "x", 3); n1.commit("", 0) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 3)); - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* n2.put("_root", "x", 4); n2.commit("", 0) */ - AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), 4)); - AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), 4), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* n3.put("_root", "x", 5); n3.commit("", 0) */ - AMfree(AMmapPutUint(n3, AM_ROOT, AMstr("x"), 5)); - AMfree(AMcommit(n3, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(n3, AM_ROOT, AMstr("x"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(n3, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* Apply n3's latest change to n2. */ /* let change = n3.getLastLocalChange() if (change === null) throw new RangeError("no local change") */ - AMchanges changes = AMpush(&test_state->stack, - AMgetLastLocalChange(n3), - AM_VALUE_CHANGES, - cmocka_cb).changes; + AMitems changes = AMstackItems(stack_ptr, AMgetLastLocalChange(n3), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); /* n2.applyChanges([change]) */ - AMfree(AMapplyChanges(test_state->n2, &changes)); + AMstackItem(NULL, AMapplyChanges(test_state->n2, &changes), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* Now sync n1 and n2. n3's change is concurrent to n1 and n2's last sync * heads */ /* sync(n1, n2, s12, s21) */ sync(test_state->n1, test_state->n2, s12, s21); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads2)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } @@ -1225,39 +1162,35 @@ static void test_should_handle_changes_concurrrent_to_the_last_sync_heads(void * /** * \brief should handle histories with lots of branching and merging */ -static void test_should_handle_histories_with_lots_of_branching_and_merging(void **state) { - /* const n1 = create('01234567'), n2 = create('89abcdef'), n3 = create('fedcba98') - const s1 = initSyncState(), s2 = initSyncState() */ +static void test_should_handle_histories_with_lots_of_branching_and_merging(void** state) { + /* const n1 = create('01234567'), n2 = create('89abcdef'), n3 = + create('fedcba98') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMdoc* n3 = AMpush(&test_state->stack, - AMcreate(AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("fedcba98")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMstack** stack_ptr = &test_state->base_state->stack; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("fedcba98")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMdoc* n3; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &n3)); /* n1.put("_root", "x", 0); n1.commit("", 0) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 0)); - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* let change1 = n1.getLastLocalChange() if (change1 === null) throw new RangeError("no local change") */ - AMchanges change1 = AMpush(&test_state->stack, - AMgetLastLocalChange(test_state->n1), - AM_VALUE_CHANGES, - cmocka_cb).changes; + AMitems change1 = + AMstackItems(stack_ptr, AMgetLastLocalChange(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); /* n2.applyChanges([change1]) */ - AMfree(AMapplyChanges(test_state->n2, &change1)); + AMstackItem(NULL, AMapplyChanges(test_state->n2, &change1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* let change2 = n1.getLastLocalChange() if (change2 === null) throw new RangeError("no local change") */ - AMchanges change2 = AMpush(&test_state->stack, - AMgetLastLocalChange(test_state->n1), - AM_VALUE_CHANGES, - cmocka_cb).changes; + AMitems change2 = + AMstackItems(stack_ptr, AMgetLastLocalChange(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); /* n3.applyChanges([change2]) */ - AMfree(AMapplyChanges(n3, &change2)); + AMstackItem(NULL, AMapplyChanges(n3, &change2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n3.put("_root", "x", 1); n3.commit("", 0) */ - AMfree(AMmapPutUint(n3, AM_ROOT, AMstr("x"), 1)); - AMfree(AMcommit(n3, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(n3, AM_ROOT, AMstr("x"), 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(n3, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* - n1c1 <------ n1c2 <------ n1c3 <-- etc. <-- n1c20 <------ n1c21 * / \/ \/ \/ @@ -1269,28 +1202,24 @@ static void test_should_handle_histories_with_lots_of_branching_and_merging(void /* for (let i = 1; i < 20; i++) { */ for (size_t i = 1; i != 20; ++i) { /* n1.put("_root", "n1", i); n1.commit("", 0) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("n1"), i)); - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("n1"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* n2.put("_root", "n2", i); n2.commit("", 0) */ - AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("n2"), i)); - AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("n2"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* const change1 = n1.getLastLocalChange() if (change1 === null) throw new RangeError("no local change") */ - AMchanges change1 = AMpush(&test_state->stack, - AMgetLastLocalChange(test_state->n1), - AM_VALUE_CHANGES, - cmocka_cb).changes; + AMitems change1 = + AMstackItems(stack_ptr, AMgetLastLocalChange(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); /* const change2 = n2.getLastLocalChange() if (change2 === null) throw new RangeError("no local change") */ - AMchanges change2 = AMpush(&test_state->stack, - AMgetLastLocalChange(test_state->n2), - AM_VALUE_CHANGES, - cmocka_cb).changes; + AMitems change2 = + AMstackItems(stack_ptr, AMgetLastLocalChange(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); /* n1.applyChanges([change2]) */ - AMfree(AMapplyChanges(test_state->n1, &change2)); + AMstackItem(NULL, AMapplyChanges(test_state->n1, &change2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n2.applyChanges([change1]) */ - AMfree(AMapplyChanges(test_state->n2, &change1)); - /* { */ + AMstackItem(NULL, AMapplyChanges(test_state->n2, &change1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ @@ -1300,31 +1229,24 @@ static void test_should_handle_histories_with_lots_of_branching_and_merging(void * the slower code path */ /* const change3 = n2.getLastLocalChange() if (change3 === null) throw new RangeError("no local change") */ - AMchanges change3 = AMpush(&test_state->stack, - AMgetLastLocalChange(n3), - AM_VALUE_CHANGES, - cmocka_cb).changes; + AMitems change3 = AMstackItems(stack_ptr, AMgetLastLocalChange(n3), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); /* n2.applyChanges([change3]) */ - AMfree(AMapplyChanges(test_state->n2, &change3)); + AMstackItem(NULL, AMapplyChanges(test_state->n2, &change3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.put("_root", "n1", "final"); n1.commit("", 0) */ - AMfree(AMmapPutStr(test_state->n1, AM_ROOT, AMstr("n1"), AMstr("final"))); - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutStr(test_state->n1, AM_ROOT, AMstr("n1"), AMstr("final")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* n2.put("_root", "n2", "final"); n2.commit("", 0) */ - AMfree(AMmapPutStr(test_state->n2, AM_ROOT, AMstr("n2"), AMstr("final"))); - AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutStr(test_state->n2, AM_ROOT, AMstr("n2"), AMstr("final")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads2)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } @@ -1334,20 +1256,26 @@ int run_ported_wasm_sync_tests(void) { cmocka_unit_test_setup_teardown(test_should_send_a_sync_message_implying_no_local_data, setup, teardown), cmocka_unit_test_setup_teardown(test_should_not_reply_if_we_have_no_data_as_well, setup, teardown), cmocka_unit_test_setup_teardown(test_repos_with_equal_heads_do_not_need_a_reply_message, setup, teardown), - cmocka_unit_test_setup_teardown(test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing, setup, teardown), - cmocka_unit_test_setup_teardown(test_should_sync_peers_where_one_has_commits_the_other_does_not, setup, teardown), + cmocka_unit_test_setup_teardown(test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing, setup, + teardown), + cmocka_unit_test_setup_teardown(test_should_sync_peers_where_one_has_commits_the_other_does_not, setup, + teardown), cmocka_unit_test_setup_teardown(test_should_work_with_prior_sync_state, setup, teardown), cmocka_unit_test_setup_teardown(test_should_not_generate_messages_once_synced, setup, teardown), - cmocka_unit_test_setup_teardown(test_should_allow_simultaneous_messages_during_synchronization, setup, teardown), - cmocka_unit_test_setup_teardown(test_should_assume_sent_changes_were_received_until_we_hear_otherwise, setup, teardown), + cmocka_unit_test_setup_teardown(test_should_allow_simultaneous_messages_during_synchronization, setup, + teardown), + cmocka_unit_test_setup_teardown(test_should_assume_sent_changes_were_received_until_we_hear_otherwise, setup, + teardown), cmocka_unit_test_setup_teardown(test_should_work_regardless_of_who_initiates_the_exchange, setup, teardown), cmocka_unit_test_setup_teardown(test_should_work_without_prior_sync_state, setup, teardown), cmocka_unit_test_setup_teardown(test_should_work_with_prior_sync_state_2, setup, teardown), cmocka_unit_test_setup_teardown(test_should_ensure_non_empty_state_after_sync, setup, teardown), cmocka_unit_test_setup_teardown(test_should_resync_after_one_node_crashed_with_data_loss, setup, teardown), - cmocka_unit_test_setup_teardown(test_should_resync_after_one_node_experiences_data_loss_without_disconnecting, setup, teardown), + cmocka_unit_test_setup_teardown(test_should_resync_after_one_node_experiences_data_loss_without_disconnecting, + setup, teardown), cmocka_unit_test_setup_teardown(test_should_handle_changes_concurrrent_to_the_last_sync_heads, setup, teardown), - cmocka_unit_test_setup_teardown(test_should_handle_histories_with_lots_of_branching_and_merging, setup, teardown), + cmocka_unit_test_setup_teardown(test_should_handle_histories_with_lots_of_branching_and_merging, setup, + teardown), }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/rust/automerge-c/test/stack_utils.c b/rust/automerge-c/test/stack_utils.c deleted file mode 100644 index f65ea2e5..00000000 --- a/rust/automerge-c/test/stack_utils.c +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include -#include - -/* third-party */ -#include - -/* local */ -#include "cmocka_utils.h" -#include "stack_utils.h" - -void cmocka_cb(AMresultStack** stack, uint8_t discriminant) { - assert_non_null(stack); - assert_non_null(*stack); - assert_non_null((*stack)->result); - if (AMresultStatus((*stack)->result) != AM_STATUS_OK) { - fail_msg_view("%s", AMerrorMessage((*stack)->result)); - } - assert_int_equal(AMresultValue((*stack)->result).tag, discriminant); -} - -int setup_stack(void** state) { - *state = NULL; - return 0; -} - -int teardown_stack(void** state) { - AMresultStack* stack = *state; - AMfreeStack(&stack); - return 0; -} diff --git a/rust/automerge-c/test/stack_utils.h b/rust/automerge-c/test/stack_utils.h deleted file mode 100644 index 473feebc..00000000 --- a/rust/automerge-c/test/stack_utils.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef STACK_UTILS_H -#define STACK_UTILS_H - -#include - -/* local */ -#include - -/** - * \brief Reports an error through a cmocka assertion. - * - * \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct. - * \param[in] discriminant An `AMvalueVariant` enum tag. - * \pre \p stack` != NULL`. - */ -void cmocka_cb(AMresultStack** stack, uint8_t discriminant); - -/** - * \brief Allocates a result stack for storing the results allocated during one - * or more test cases. - * - * \param[in,out] state A pointer to a pointer to an `AMresultStack` struct. - * \pre \p state` != NULL`. - * \warning The `AMresultStack` struct returned through \p state must be - * deallocated with `teardown_stack()` in order to prevent memory leaks. - */ -int setup_stack(void** state); - -/** - * \brief Deallocates a result stack after deallocating any results that were - * stored in it by one or more test cases. - * - * \param[in] state A pointer to a pointer to an `AMresultStack` struct. - * \pre \p state` != NULL`. - */ -int teardown_stack(void** state); - -#endif /* STACK_UTILS_H */ diff --git a/rust/automerge-c/test/str_utils.c b/rust/automerge-c/test/str_utils.c index cc923cb4..2937217a 100644 --- a/rust/automerge-c/test/str_utils.c +++ b/rust/automerge-c/test/str_utils.c @@ -1,5 +1,5 @@ -#include #include +#include /* local */ #include "str_utils.h" diff --git a/rust/automerge-c/test/str_utils.h b/rust/automerge-c/test/str_utils.h index b9985683..14a4af73 100644 --- a/rust/automerge-c/test/str_utils.h +++ b/rust/automerge-c/test/str_utils.h @@ -1,14 +1,17 @@ -#ifndef STR_UTILS_H -#define STR_UTILS_H +#ifndef TESTS_STR_UTILS_H +#define TESTS_STR_UTILS_H /** - * \brief Converts a hexadecimal string into a sequence of bytes. + * \brief Converts a hexadecimal string into an array of bytes. * - * \param[in] hex_str A string. - * \param[in] src A pointer to a contiguous sequence of bytes. - * \param[in] count The number of bytes to copy to \p src. - * \pre \p count `<=` length of \p src. + * \param[in] hex_str A hexadecimal string. + * \param[in] src A pointer to an array of bytes. + * \param[in] count The count of bytes to copy into the array pointed to by + * \p src. + * \pre \p src `!= NULL` + * \pre `sizeof(`\p src `) > 0` + * \pre \p count `<= sizeof(`\p src `)` */ void hex_to_bytes(char const* hex_str, uint8_t* src, size_t const count); -#endif /* STR_UTILS_H */ +#endif /* TESTS_STR_UTILS_H */ diff --git a/rust/automerge/src/error.rs b/rust/automerge/src/error.rs index 57a87167..68b8ec65 100644 --- a/rust/automerge/src/error.rs +++ b/rust/automerge/src/error.rs @@ -1,3 +1,4 @@ +use crate::change::LoadError as LoadChangeError; use crate::storage::load::Error as LoadError; use crate::types::{ActorId, ScalarValue}; use crate::value::DataType; @@ -18,6 +19,8 @@ pub enum AutomergeError { Fail, #[error("invalid actor ID `{0}`")] InvalidActorId(String), + #[error(transparent)] + InvalidChangeHashBytes(#[from] InvalidChangeHashSlice), #[error("invalid UTF-8 character at {0}")] InvalidCharacter(usize), #[error("invalid hash {0}")] @@ -39,6 +42,8 @@ pub enum AutomergeError { }, #[error(transparent)] Load(#[from] LoadError), + #[error(transparent)] + LoadChangeError(#[from] LoadChangeError), #[error("increment operations must be against a counter value")] MissingCounter, #[error("hash {0} does not correspond to a change in this document")] diff --git a/scripts/ci/cmake-build b/scripts/ci/cmake-build index f6f9f9b1..25a69756 100755 --- a/scripts/ci/cmake-build +++ b/scripts/ci/cmake-build @@ -16,4 +16,4 @@ C_PROJECT=$THIS_SCRIPT/../../rust/automerge-c; mkdir -p $C_PROJECT/build; cd $C_PROJECT/build; cmake --log-level=ERROR -B . -S .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_SHARED_LIBS=$SHARED_TOGGLE; -cmake --build . --target test_automerge; +cmake --build . --target automerge_test; From 44fa7ac41647fa465ee7baa0bc0ee64e811dded8 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 27 Feb 2023 13:12:09 -0700 Subject: [PATCH 11/16] Don't panic on missing deps of change chunks (#538) * Fix doubly-reported ops in load of change chunks Since c3c04128f5f1703007f650ea3104d98334334aab, observers have been called twice when calling Automerge::load() with change chunks. * Better handle change chunks with missing deps Before this change Automerge::load would panic if you passed a change chunk that was missing a dependency, or multiple change chunks not in strict dependency order. After this change these cases will error instead. --- rust/automerge/src/automerge.rs | 38 +++++++++--------- rust/automerge/src/automerge/current_state.rs | 29 ++++++++++++- rust/automerge/src/error.rs | 2 + .../fixtures/two_change_chunks.automerge | Bin 0 -> 177 bytes .../two_change_chunks_compressed.automerge | Bin 0 -> 192 bytes .../two_change_chunks_out_of_order.automerge | Bin 0 -> 177 bytes .../fuzz-crashers/missing_deps.automerge | Bin 0 -> 224 bytes .../missing_deps_compressed.automerge | Bin 0 -> 120 bytes .../missing_deps_subsequent.automerge | Bin 0 -> 180 bytes rust/automerge/tests/test.rs | 13 ++++++ 10 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 rust/automerge/tests/fixtures/two_change_chunks.automerge create mode 100644 rust/automerge/tests/fixtures/two_change_chunks_compressed.automerge create mode 100644 rust/automerge/tests/fixtures/two_change_chunks_out_of_order.automerge create mode 100644 rust/automerge/tests/fuzz-crashers/missing_deps.automerge create mode 100644 rust/automerge/tests/fuzz-crashers/missing_deps_compressed.automerge create mode 100644 rust/automerge/tests/fuzz-crashers/missing_deps_subsequent.automerge diff --git a/rust/automerge/src/automerge.rs b/rust/automerge/src/automerge.rs index 09c3cc9d..9c45ec51 100644 --- a/rust/automerge/src/automerge.rs +++ b/rust/automerge/src/automerge.rs @@ -464,6 +464,7 @@ impl Automerge { return Err(load::Error::BadChecksum.into()); } + let mut change: Option = None; let mut am = match first_chunk { storage::Chunk::Document(d) => { tracing::trace!("first chunk is document chunk, inflating"); @@ -501,30 +502,31 @@ impl Automerge { } } storage::Chunk::Change(stored_change) => { - tracing::trace!("first chunk is change chunk, applying"); - let change = Change::new_from_unverified(stored_change.into_owned(), None) - .map_err(|e| load::Error::InvalidChangeColumns(Box::new(e)))?; - let mut am = Self::new(); - am.apply_change(change, &mut observer); - am + tracing::trace!("first chunk is change chunk"); + change = Some( + Change::new_from_unverified(stored_change.into_owned(), None) + .map_err(|e| load::Error::InvalidChangeColumns(Box::new(e)))?, + ); + Self::new() } storage::Chunk::CompressedChange(stored_change, compressed) => { - tracing::trace!("first chunk is compressed change, decompressing and applying"); - let change = Change::new_from_unverified( - stored_change.into_owned(), - Some(compressed.into_owned()), - ) - .map_err(|e| load::Error::InvalidChangeColumns(Box::new(e)))?; - let mut am = Self::new(); - am.apply_change(change, &mut observer); - am + tracing::trace!("first chunk is compressed change"); + change = Some( + Change::new_from_unverified( + stored_change.into_owned(), + Some(compressed.into_owned()), + ) + .map_err(|e| load::Error::InvalidChangeColumns(Box::new(e)))?, + ); + Self::new() } }; - tracing::trace!("first chunk loaded, loading remaining chunks"); + tracing::trace!("loading change chunks"); match load::load_changes(remaining.reset()) { load::LoadedChanges::Complete(c) => { - for change in c { - am.apply_change(change, &mut observer); + am.apply_changes(change.into_iter().chain(c))?; + if !am.queue.is_empty() { + return Err(AutomergeError::MissingDeps); } } load::LoadedChanges::Partial { error, .. } => { diff --git a/rust/automerge/src/automerge/current_state.rs b/rust/automerge/src/automerge/current_state.rs index 1c1bceed..3f7f4afc 100644 --- a/rust/automerge/src/automerge/current_state.rs +++ b/rust/automerge/src/automerge/current_state.rs @@ -338,9 +338,9 @@ impl<'a, I: Iterator>> Iterator for TextActions<'a, I> { #[cfg(test)] mod tests { - use std::borrow::Cow; + use std::{borrow::Cow, fs}; - use crate::{transaction::Transactable, ObjType, OpObserver, Prop, ReadDoc, Value}; + use crate::{transaction::Transactable, Automerge, ObjType, OpObserver, Prop, ReadDoc, Value}; // Observer ops often carry a "tagged value", which is a value and the OpID of the op which // created that value. For a lot of values (i.e. any scalar value) we don't care about the @@ -887,4 +887,29 @@ mod tests { ]) ); } + + #[test] + fn test_load_changes() { + fn fixture(name: &str) -> Vec { + fs::read("./tests/fixtures/".to_owned() + name).unwrap() + } + + let mut obs = ObserverStub::new(); + let _doc = Automerge::load_with( + &fixture("counter_value_is_ok.automerge"), + crate::OnPartialLoad::Error, + crate::storage::VerificationMode::Check, + Some(&mut obs), + ); + + assert_eq!( + Calls(obs.ops), + Calls(vec![ObserverCall::Put { + obj: crate::ROOT, + prop: "a".into(), + value: ObservedValue::Untagged(crate::ScalarValue::Counter(2000.into()).into()), + conflict: false, + },]) + ); + } } diff --git a/rust/automerge/src/error.rs b/rust/automerge/src/error.rs index 68b8ec65..86dbe9f3 100644 --- a/rust/automerge/src/error.rs +++ b/rust/automerge/src/error.rs @@ -48,6 +48,8 @@ pub enum AutomergeError { MissingCounter, #[error("hash {0} does not correspond to a change in this document")] MissingHash(ChangeHash), + #[error("change's deps should already be in the document")] + MissingDeps, #[error("compressed chunk was not a change")] NonChangeCompressed, #[error("id was not an object id")] diff --git a/rust/automerge/tests/fixtures/two_change_chunks.automerge b/rust/automerge/tests/fixtures/two_change_chunks.automerge new file mode 100644 index 0000000000000000000000000000000000000000..1a84b363ccab6161890367b7b6fadd84091acc1a GIT binary patch literal 177 zcmZq8_iCPX___h3C4;~%u8ahNZ6`uDR*U$arwyk>-a69LX7pdFiPNh77Et z%qEOZOkqp~O!bV3jP(p4*a|da`E&KFj46yDlRhQgGJP()b>hw!qP#CRXsF%#9>DfV qvr}yCn>=m|E0~y2tuSKXU}R!qf>{&J2(*Zyo)K&rW4%~XJp%xrEC}cVUk{s_*GfwAzq7vd=R$+BrLv*EZRo)X zjiFO+wp{Gg>{;2ca-QMDjq?~;7#SHD{{L?UnzQ`5`cUCz3gfK9*9|@;-7W5 zAx_SoEv*boUq4)P)0c_q;Jzcx4-GhyGZORCQx%LDI2f6jm_(UP7@e5Hn8FzgnCcno q8S5Dnfw*2Qsh*(~XdB2HMoR_^(-;|1O*3R*g_#622V@2V2m%1g@ISTy literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/fuzz-crashers/missing_deps.automerge b/rust/automerge/tests/fuzz-crashers/missing_deps.automerge new file mode 100644 index 0000000000000000000000000000000000000000..8a57a0f4c8a82541f9236c878cd22599aefbcce2 GIT binary patch literal 224 zcmZq8_i8>FcHEBfDkJ0W>J_a(?qU6^Yf=o1{~5o&kywAoaLxb!s;Hm{4n%=~4@7_f dT+w7W3mbz0J3uIvfiW3@Dq(DLXvVLM3jvQ0EVKXs literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/fuzz-crashers/missing_deps_compressed.automerge b/rust/automerge/tests/fuzz-crashers/missing_deps_compressed.automerge new file mode 100644 index 0000000000000000000000000000000000000000..2c7b123b6805032546ec438597e31a03245b5a79 GIT binary patch literal 120 zcmV-;0EhpDZ%TvQ<(umQZUAHeoBsjf#K?6GIWE|lwH=|kchIwRB>mYqPdl0|$S{b; zlZl!T#tysb@0Cu7tB#1rhSmZA0s_mq^zPs=2xDkrZf9j6G5`nx0s;aR12h3b0#*W7 a0dN9;0Dl300bv1u0e==^e*ggh0RR6R126Ib literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/fuzz-crashers/missing_deps_subsequent.automerge b/rust/automerge/tests/fuzz-crashers/missing_deps_subsequent.automerge new file mode 100644 index 0000000000000000000000000000000000000000..2fe439afd0c7792801f52a5325a2582478efdd1d GIT binary patch literal 180 zcmZq8_iE-b7ZG8!VGvl5pXAtm&!MU9#x>WYe^O^NGTz(X^8SGVM{-7DUV5s6F$0?@ zvk9XUQy5b?V*yh=Vc^1qZv~et#%p2bkvx1Prjyk;Lcr*+ew Date: Fri, 3 Mar 2023 17:42:40 -0500 Subject: [PATCH 12/16] Suppress clippy warning in parse.rs + bump toolchain (#542) * Fix rust error in parse.rs * Bump toolchain to 1.67.0 --- .github/workflows/ci.yaml | 14 +++++++------- rust/automerge/src/storage/parse.rs | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bfa31bd5..0263f408 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -14,7 +14,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.66.0 + toolchain: 1.67.0 default: true components: rustfmt - uses: Swatinem/rust-cache@v1 @@ -28,7 +28,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.66.0 + toolchain: 1.67.0 default: true components: clippy - uses: Swatinem/rust-cache@v1 @@ -42,7 +42,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.66.0 + toolchain: 1.67.0 default: true - uses: Swatinem/rust-cache@v1 - name: Build rust docs @@ -118,7 +118,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.66.0 + toolchain: 1.67.0 default: true - uses: Swatinem/rust-cache@v1 - name: Install CMocka @@ -136,7 +136,7 @@ jobs: strategy: matrix: toolchain: - - 1.66.0 + - 1.67.0 steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -155,7 +155,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.66.0 + toolchain: 1.67.0 default: true - uses: Swatinem/rust-cache@v1 - run: ./scripts/ci/build-test @@ -168,7 +168,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.66.0 + toolchain: 1.67.0 default: true - uses: Swatinem/rust-cache@v1 - run: ./scripts/ci/build-test diff --git a/rust/automerge/src/storage/parse.rs b/rust/automerge/src/storage/parse.rs index 54668da4..6751afb4 100644 --- a/rust/automerge/src/storage/parse.rs +++ b/rust/automerge/src/storage/parse.rs @@ -308,6 +308,7 @@ impl<'a> Input<'a> { } /// The bytes behind this input - including bytes which have been consumed + #[allow(clippy::misnamed_getters)] pub(crate) fn bytes(&self) -> &'a [u8] { self.original } From 2c1970f6641ea3fe10976721316ae6d07765e4a1 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 4 Mar 2023 05:09:08 -0700 Subject: [PATCH 13/16] Fix panic on invalid action (#541) We make the validation on parsing operations in the encoded changes stricter to avoid a possible panic when applying changes. --- rust/automerge/src/automerge.rs | 2 +- rust/automerge/src/change.rs | 2 +- .../src/columnar/encoding/col_error.rs | 2 +- rust/automerge/src/error.rs | 2 +- .../src/storage/change/change_op_columns.rs | 20 ++++++++- rust/automerge/src/types.rs | 40 ++++++++++++------ .../fuzz-crashers/action-is-48.automerge | Bin 0 -> 58 bytes 7 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 rust/automerge/tests/fuzz-crashers/action-is-48.automerge diff --git a/rust/automerge/src/automerge.rs b/rust/automerge/src/automerge.rs index 9c45ec51..0dd82253 100644 --- a/rust/automerge/src/automerge.rs +++ b/rust/automerge/src/automerge.rs @@ -723,7 +723,7 @@ impl Automerge { obj, Op { id, - action: OpType::from_index_and_value(c.action, c.val).unwrap(), + action: OpType::from_action_and_value(c.action, c.val), key, succ: Default::default(), pred, diff --git a/rust/automerge/src/change.rs b/rust/automerge/src/change.rs index b5cae7df..be467a84 100644 --- a/rust/automerge/src/change.rs +++ b/rust/automerge/src/change.rs @@ -278,7 +278,7 @@ impl From<&Change> for crate::ExpandedChange { let operations = c .iter_ops() .map(|o| crate::legacy::Op { - action: crate::types::OpType::from_index_and_value(o.action, o.val).unwrap(), + action: crate::types::OpType::from_action_and_value(o.action, o.val), insert: o.insert, key: match o.key { StoredKey::Elem(e) if e.is_head() => { diff --git a/rust/automerge/src/columnar/encoding/col_error.rs b/rust/automerge/src/columnar/encoding/col_error.rs index c8d5c5c0..089556b6 100644 --- a/rust/automerge/src/columnar/encoding/col_error.rs +++ b/rust/automerge/src/columnar/encoding/col_error.rs @@ -1,5 +1,5 @@ #[derive(Clone, Debug)] -pub(crate) struct DecodeColumnError { +pub struct DecodeColumnError { path: Path, error: DecodeColErrorKind, } diff --git a/rust/automerge/src/error.rs b/rust/automerge/src/error.rs index 86dbe9f3..62a7b72f 100644 --- a/rust/automerge/src/error.rs +++ b/rust/automerge/src/error.rs @@ -99,7 +99,7 @@ pub struct InvalidElementId(pub String); pub struct InvalidOpId(pub String); #[derive(Error, Debug)] -pub(crate) enum InvalidOpType { +pub enum InvalidOpType { #[error("unrecognized action index {0}")] UnknownAction(u64), #[error("non numeric argument for inc op")] diff --git a/rust/automerge/src/storage/change/change_op_columns.rs b/rust/automerge/src/storage/change/change_op_columns.rs index 7c3a65ec..cd1cb150 100644 --- a/rust/automerge/src/storage/change/change_op_columns.rs +++ b/rust/automerge/src/storage/change/change_op_columns.rs @@ -14,6 +14,7 @@ use crate::{ }, }, convert, + error::InvalidOpType, storage::{ change::AsChangeOp, columns::{ @@ -22,6 +23,7 @@ use crate::{ RawColumns, }, types::{ElemId, ObjId, OpId, ScalarValue}, + OpType, }; const OBJ_COL_ID: ColumnId = ColumnId::new(0); @@ -276,7 +278,12 @@ impl ChangeOpsColumns { #[derive(thiserror::Error, Debug)] #[error(transparent)] -pub struct ReadChangeOpError(#[from] DecodeColumnError); +pub enum ReadChangeOpError { + #[error(transparent)] + DecodeError(#[from] DecodeColumnError), + #[error(transparent)] + InvalidOpType(#[from] InvalidOpType), +} #[derive(Clone)] pub(crate) struct ChangeOpsIter<'a> { @@ -308,6 +315,11 @@ impl<'a> ChangeOpsIter<'a> { let action = self.action.next_in_col("action")?; let val = self.val.next_in_col("value")?; let pred = self.pred.next_in_col("pred")?; + + // This check is necessary to ensure that OpType::from_action_and_value + // cannot panic later in the process. + OpType::validate_action_and_value(action, &val)?; + Ok(Some(ChangeOp { obj, key, @@ -458,10 +470,14 @@ mod tests { action in 0_u64..6, obj in opid(), insert in any::()) -> ChangeOp { + + let val = if action == 5 && !(value.is_int() || value.is_uint()) { + ScalarValue::Uint(0) + } else { value }; ChangeOp { obj: obj.into(), key, - val: value, + val, pred, action, insert, diff --git a/rust/automerge/src/types.rs b/rust/automerge/src/types.rs index 870569e9..2978aa97 100644 --- a/rust/automerge/src/types.rs +++ b/rust/automerge/src/types.rs @@ -216,23 +216,35 @@ impl OpType { } } - pub(crate) fn from_index_and_value( - index: u64, - value: ScalarValue, - ) -> Result { - match index { - 0 => Ok(Self::Make(ObjType::Map)), - 1 => Ok(Self::Put(value)), - 2 => Ok(Self::Make(ObjType::List)), - 3 => Ok(Self::Delete), - 4 => Ok(Self::Make(ObjType::Text)), + pub(crate) fn validate_action_and_value( + action: u64, + value: &ScalarValue, + ) -> Result<(), error::InvalidOpType> { + match action { + 0..=4 => Ok(()), 5 => match value { - ScalarValue::Int(i) => Ok(Self::Increment(i)), - ScalarValue::Uint(i) => Ok(Self::Increment(i as i64)), + ScalarValue::Int(_) | ScalarValue::Uint(_) => Ok(()), _ => Err(error::InvalidOpType::NonNumericInc), }, - 6 => Ok(Self::Make(ObjType::Table)), - other => Err(error::InvalidOpType::UnknownAction(other)), + 6 => Ok(()), + _ => Err(error::InvalidOpType::UnknownAction(action)), + } + } + + pub(crate) fn from_action_and_value(action: u64, value: ScalarValue) -> OpType { + match action { + 0 => Self::Make(ObjType::Map), + 1 => Self::Put(value), + 2 => Self::Make(ObjType::List), + 3 => Self::Delete, + 4 => Self::Make(ObjType::Text), + 5 => match value { + ScalarValue::Int(i) => Self::Increment(i), + ScalarValue::Uint(i) => Self::Increment(i as i64), + _ => unreachable!("validate_action_and_value returned NonNumericInc"), + }, + 6 => Self::Make(ObjType::Table), + _ => unreachable!("validate_action_and_value returned UnknownAction"), } } } diff --git a/rust/automerge/tests/fuzz-crashers/action-is-48.automerge b/rust/automerge/tests/fuzz-crashers/action-is-48.automerge new file mode 100644 index 0000000000000000000000000000000000000000..16e6f719a13dd6b1d9eff8488ee651ab7f72bfc3 GIT binary patch literal 58 vcmZq8_i8>{b9^SF0fT@6CSYJ-6J<7GbYco)N@OZvGGH_SqI$Lq{Phd~tz-

Date: Tue, 7 Mar 2023 09:49:04 -0700 Subject: [PATCH 14/16] Error instead of corrupt large op counters (#543) Since b78211ca6, OpIds have been silently truncated to 2**32. This causes corruption in the case the op id overflows. This change converts the silent error to a panic, and guards against the panic on the codepath found by the fuzzer. --- .../automerge/src/columnar/column_range/opid.rs | 6 +++--- .../src/columnar/encoding/properties.rs | 2 +- rust/automerge/src/storage/change.rs | 3 +++ .../src/storage/change/change_op_columns.rs | 2 ++ rust/automerge/src/types.rs | 6 +++--- rust/automerge/src/types/opids.rs | 2 +- .../fixtures/64bit_obj_id_change.automerge | Bin 0 -> 73 bytes .../tests/fixtures/64bit_obj_id_doc.automerge | Bin 0 -> 147 bytes rust/automerge/tests/test.rs | 16 ++++++++++++++++ 9 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 rust/automerge/tests/fixtures/64bit_obj_id_change.automerge create mode 100644 rust/automerge/tests/fixtures/64bit_obj_id_doc.automerge diff --git a/rust/automerge/src/columnar/column_range/opid.rs b/rust/automerge/src/columnar/column_range/opid.rs index ae95d758..d2cdce79 100644 --- a/rust/automerge/src/columnar/column_range/opid.rs +++ b/rust/automerge/src/columnar/column_range/opid.rs @@ -104,11 +104,11 @@ impl<'a> OpIdIter<'a> { .transpose() .map_err(|e| DecodeColumnError::decode_raw("counter", e))?; match (actor, counter) { - (Some(Some(a)), Some(Some(c))) => match c.try_into() { - Ok(c) => Ok(Some(OpId::new(c, a as usize))), + (Some(Some(a)), Some(Some(c))) => match u32::try_from(c) { + Ok(c) => Ok(Some(OpId::new(c as u64, a as usize))), Err(_) => Err(DecodeColumnError::invalid_value( "counter", - "negative value encountered", + "negative or large value encountered", )), }, (Some(None), _) => Err(DecodeColumnError::unexpected_null("actor")), diff --git a/rust/automerge/src/columnar/encoding/properties.rs b/rust/automerge/src/columnar/encoding/properties.rs index a3bf1ed0..30f1169d 100644 --- a/rust/automerge/src/columnar/encoding/properties.rs +++ b/rust/automerge/src/columnar/encoding/properties.rs @@ -139,7 +139,7 @@ pub(crate) fn option_splice_scenario< } pub(crate) fn opid() -> impl Strategy + Clone { - (0..(i64::MAX as usize), 0..(i64::MAX as u64)).prop_map(|(actor, ctr)| OpId::new(ctr, actor)) + (0..(u32::MAX as usize), 0..(u32::MAX as u64)).prop_map(|(actor, ctr)| OpId::new(ctr, actor)) } pub(crate) fn elemid() -> impl Strategy + Clone { diff --git a/rust/automerge/src/storage/change.rs b/rust/automerge/src/storage/change.rs index ff3cc9ab..61db0b00 100644 --- a/rust/automerge/src/storage/change.rs +++ b/rust/automerge/src/storage/change.rs @@ -177,6 +177,9 @@ impl<'a> Change<'a, Unverified> { for op in self.iter_ops() { f(op?); } + if u32::try_from(u64::from(self.start_op)).is_err() { + return Err(ReadChangeOpError::CounterTooLarge); + } Ok(Change { bytes: self.bytes, header: self.header, diff --git a/rust/automerge/src/storage/change/change_op_columns.rs b/rust/automerge/src/storage/change/change_op_columns.rs index cd1cb150..86ec59c2 100644 --- a/rust/automerge/src/storage/change/change_op_columns.rs +++ b/rust/automerge/src/storage/change/change_op_columns.rs @@ -283,6 +283,8 @@ pub enum ReadChangeOpError { DecodeError(#[from] DecodeColumnError), #[error(transparent)] InvalidOpType(#[from] InvalidOpType), + #[error("counter too large")] + CounterTooLarge, } #[derive(Clone)] diff --git a/rust/automerge/src/types.rs b/rust/automerge/src/types.rs index 2978aa97..468986ec 100644 --- a/rust/automerge/src/types.rs +++ b/rust/automerge/src/types.rs @@ -439,17 +439,17 @@ pub(crate) struct OpId(u32, u32); impl OpId { pub(crate) fn new(counter: u64, actor: usize) -> Self { - Self(counter as u32, actor as u32) + Self(counter.try_into().unwrap(), actor.try_into().unwrap()) } #[inline] pub(crate) fn counter(&self) -> u64 { - self.0 as u64 + self.0.into() } #[inline] pub(crate) fn actor(&self) -> usize { - self.1 as usize + self.1.try_into().unwrap() } #[inline] diff --git a/rust/automerge/src/types/opids.rs b/rust/automerge/src/types/opids.rs index eaeed471..a81ccb36 100644 --- a/rust/automerge/src/types/opids.rs +++ b/rust/automerge/src/types/opids.rs @@ -129,7 +129,7 @@ mod tests { fn gen_opid(actors: Vec) -> impl Strategy { (0..actors.len()).prop_flat_map(|actor_idx| { - (Just(actor_idx), 0..u64::MAX) + (Just(actor_idx), 0..(u32::MAX as u64)) .prop_map(|(actor_idx, counter)| OpId::new(counter, actor_idx)) }) } diff --git a/rust/automerge/tests/fixtures/64bit_obj_id_change.automerge b/rust/automerge/tests/fixtures/64bit_obj_id_change.automerge new file mode 100644 index 0000000000000000000000000000000000000000..700342a2df71772d78f0373385f44aae9eb88c7b GIT binary patch literal 73 zcmZq8_i9cmO}NHr&meG%DY?GbS?DGk_of5L_6# literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/fixtures/64bit_obj_id_doc.automerge b/rust/automerge/tests/fixtures/64bit_obj_id_doc.automerge new file mode 100644 index 0000000000000000000000000000000000000000..6beb57fe9ad7d5428d5b854c0e39f8bb57dcfdf7 GIT binary patch literal 147 zcmZq8_i7GNJ@|p4gOO3-7FS4!le1?_E5p*)57*rEWlSnfxVFRCa9`J~6^(1kJz9Nc zT|0UFe#&#R(pL)KvpP?)GcqwV33Dj3n{qiYg)y; Date: Thu, 9 Mar 2023 08:09:43 -0700 Subject: [PATCH 15/16] smaller automerge c (#545) * Fix automerge-c tests on mac * Generate significantly smaller automerge-c builds This cuts the size of libautomerge_core.a from 25Mb to 1.6Mb on macOS and 53Mb to 2.7Mb on Linux. As a side-effect of setting codegen-units = 1 for all release builds the optimized wasm files are also 100kb smaller. --- .github/workflows/ci.yaml | 8 +++++--- README.md | 5 ++++- rust/Cargo.toml | 9 ++------- rust/automerge-c/CMakeLists.txt | 26 +++++++++++++++++++++---- rust/automerge-c/test/byte_span_tests.c | 1 + 5 files changed, 34 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0263f408..8519ac5e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,10 +2,10 @@ name: CI on: push: branches: - - main + - main pull_request: branches: - - main + - main jobs: fmt: runs-on: ubuntu-latest @@ -118,7 +118,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.67.0 + toolchain: nightly-2023-01-26 default: true - uses: Swatinem/rust-cache@v1 - name: Install CMocka @@ -127,6 +127,8 @@ jobs: uses: jwlawson/actions-setup-cmake@v1.12 with: cmake-version: latest + - name: Install rust-src + run: rustup component add rust-src - name: Build and test C bindings run: ./scripts/ci/cmake-build Release Static shell: bash diff --git a/README.md b/README.md index 76d48ddd..ad174da4 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,6 @@ to figure out how to use it. If you are looking to build rust applications which use automerge you may want to look into [autosurgeon](https://github.com/alexjg/autosurgeon) - ## Repository Organisation - `./rust` - the rust rust implementation and also the Rust components of @@ -119,6 +118,10 @@ yarn --cwd ./javascript # install rust dependencies cargo install wasm-bindgen-cli wasm-opt cargo-deny +# get nightly rust to produce optimized automerge-c builds +rustup toolchain install nightly +rustup component add rust-src --toolchain nightly + # add wasm target in addition to current architecture rustup target add wasm32-unknown-unknown diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 938100cf..5d29fc9f 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -10,13 +10,8 @@ members = [ resolver = "2" [profile.release] -debug = true lto = true -opt-level = 3 +codegen-units = 1 [profile.bench] -debug = true - -[profile.release.package.automerge-wasm] -debug = false -opt-level = 3 +debug = true \ No newline at end of file diff --git a/rust/automerge-c/CMakeLists.txt b/rust/automerge-c/CMakeLists.txt index 056d111b..0c35eebd 100644 --- a/rust/automerge-c/CMakeLists.txt +++ b/rust/automerge-c/CMakeLists.txt @@ -43,19 +43,37 @@ endif() string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_LOWER) +# In order to build with -Z build-std, we need to pass target explicitly. +# https://doc.rust-lang.org/cargo/reference/unstable.html#build-std +execute_process ( + COMMAND rustc -vV + OUTPUT_VARIABLE RUSTC_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE +) +string(REGEX REPLACE ".*host: ([^ \n]*).*" "\\1" + CARGO_TARGET + ${RUSTC_VERSION} +) + if(BUILD_TYPE_LOWER STREQUAL debug) set(CARGO_BUILD_TYPE "debug") - set(CARGO_FLAG "") + set(CARGO_FLAG --target=${CARGO_TARGET}) else() set(CARGO_BUILD_TYPE "release") - set(CARGO_FLAG "--release") + if (NOT RUSTC_VERSION MATCHES "nightly") + set(RUSTUP_TOOLCHAIN nightly) + endif() + + set(RUSTFLAGS -C\ panic=abort) + + set(CARGO_FLAG -Z build-std=std,panic_abort --release --target=${CARGO_TARGET}) endif() set(CARGO_FEATURES "") -set(CARGO_BINARY_DIR "${CARGO_TARGET_DIR}/${CARGO_BUILD_TYPE}") +set(CARGO_BINARY_DIR "${CARGO_TARGET_DIR}/${CARGO_TARGET}/${CARGO_BUILD_TYPE}") set(BINDINGS_NAME "${LIBRARY_NAME}_core") @@ -90,7 +108,7 @@ add_custom_command( # configuration file has been updated. ${CMAKE_COMMAND} -DCONDITION=NOT_EXISTS -P ${CMAKE_SOURCE_DIR}/cmake/file-touch.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CMAKE_SOURCE_DIR}/cbindgen.toml COMMAND - ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} CBINDGEN_TARGET_DIR=${CBINDGEN_TARGET_DIR} ${CARGO_CMD} build ${CARGO_FLAG} ${CARGO_FEATURES} + ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} CBINDGEN_TARGET_DIR=${CBINDGEN_TARGET_DIR} RUSTUP_TOOLCHAIN=${RUSTUP_TOOLCHAIN} RUSTFLAGS=${RUSTFLAGS} ${CARGO_CMD} build ${CARGO_FLAG} ${CARGO_FEATURES} COMMAND # Compensate for cbindgen's translation of consecutive uppercase letters to "ScreamingSnakeCase". ${CMAKE_COMMAND} -DMATCH_REGEX=A_M\([^_]+\)_ -DREPLACE_EXPR=AM_\\1_ -P ${CMAKE_SOURCE_DIR}/cmake/file-regex-replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h diff --git a/rust/automerge-c/test/byte_span_tests.c b/rust/automerge-c/test/byte_span_tests.c index 43856f3b..0b1c86a1 100644 --- a/rust/automerge-c/test/byte_span_tests.c +++ b/rust/automerge-c/test/byte_span_tests.c @@ -3,6 +3,7 @@ #include #include #include +#include /* third-party */ #include From cb409b6ffe2cec15ce7724c291cf91d383b4c19b Mon Sep 17 00:00:00 2001 From: alexjg Date: Thu, 9 Mar 2023 18:10:23 +0000 Subject: [PATCH 16/16] docs: timestamp -> time in automerge.change examples (#548) --- javascript/src/stable.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/src/stable.ts b/javascript/src/stable.ts index 74410346..e83b127f 100644 --- a/javascript/src/stable.ts +++ b/javascript/src/stable.ts @@ -305,7 +305,7 @@ export function from>( * @example A change with a message and a timestamp * * ``` - * doc1 = automerge.change(doc1, {message: "add another value", timestamp: 1640995200}, d => { + * doc1 = automerge.change(doc1, {message: "add another value", time: 1640995200}, d => { * d.key2 = "value2" * }) * ``` @@ -316,7 +316,7 @@ export function from>( * let patchCallback = patch => { * patchedPath = patch.path * } - * doc1 = automerge.change(doc1, {message, "add another value", timestamp: 1640995200, patchCallback}, d => { + * doc1 = automerge.change(doc1, {message, "add another value", time: 1640995200, patchCallback}, d => { * d.key2 = "value2" * }) * assert.equal(patchedPath, ["key2"])