Compare commits
	
		
			No commits in common. "main" and "faster_sync" have entirely different histories.
		
	
	
		
			
				main
			
			...
			
				faster_syn
			
		
	
		
					 196 changed files with 11208 additions and 13701 deletions
				
			
		
							
								
								
									
										22
									
								
								.github/workflows/ci.yaml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/ci.yaml
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -2,10 +2,10 @@ name: CI
 | 
			
		|||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
      - main
 | 
			
		||||
    - main
 | 
			
		||||
  pull_request:
 | 
			
		||||
    branches:
 | 
			
		||||
      - main
 | 
			
		||||
    - main
 | 
			
		||||
jobs:
 | 
			
		||||
  fmt:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +14,7 @@ jobs:
 | 
			
		|||
      - uses: actions-rs/toolchain@v1
 | 
			
		||||
        with:
 | 
			
		||||
          profile: minimal
 | 
			
		||||
          toolchain: 1.67.0
 | 
			
		||||
          toolchain: 1.66.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.67.0
 | 
			
		||||
          toolchain: 1.66.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.67.0
 | 
			
		||||
          toolchain: 1.66.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: nightly-2023-01-26
 | 
			
		||||
          toolchain: 1.66.0
 | 
			
		||||
          default: true
 | 
			
		||||
      - uses: Swatinem/rust-cache@v1
 | 
			
		||||
      - name: Install CMocka
 | 
			
		||||
| 
						 | 
				
			
			@ -127,8 +127,6 @@ 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
 | 
			
		||||
| 
						 | 
				
			
			@ -138,7 +136,9 @@ jobs:
 | 
			
		|||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        toolchain:
 | 
			
		||||
          - 1.67.0
 | 
			
		||||
          - 1.66.0
 | 
			
		||||
          - nightly
 | 
			
		||||
    continue-on-error: ${{ matrix.toolchain == 'nightly' }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions-rs/toolchain@v1
 | 
			
		||||
| 
						 | 
				
			
			@ -157,7 +157,7 @@ jobs:
 | 
			
		|||
      - uses: actions-rs/toolchain@v1
 | 
			
		||||
        with:
 | 
			
		||||
          profile: minimal
 | 
			
		||||
          toolchain: 1.67.0
 | 
			
		||||
          toolchain: 1.66.0
 | 
			
		||||
          default: true
 | 
			
		||||
      - uses: Swatinem/rust-cache@v1
 | 
			
		||||
      - run: ./scripts/ci/build-test
 | 
			
		||||
| 
						 | 
				
			
			@ -170,7 +170,7 @@ jobs:
 | 
			
		|||
      - uses: actions-rs/toolchain@v1
 | 
			
		||||
        with:
 | 
			
		||||
          profile: minimal
 | 
			
		||||
          toolchain: 1.67.0
 | 
			
		||||
          toolchain: 1.66.0
 | 
			
		||||
          default: true
 | 
			
		||||
      - uses: Swatinem/rust-cache@v1
 | 
			
		||||
      - run: ./scripts/ci/build-test
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										18
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										18
									
								
								README.md
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -42,10 +42,9 @@ In general we try and respect semver.
 | 
			
		|||
 | 
			
		||||
### JavaScript
 | 
			
		||||
 | 
			
		||||
A stable release of the javascript package is currently available as
 | 
			
		||||
`@automerge/automerge@2.0.0` where. pre-release verisions of the `2.0.1` are
 | 
			
		||||
available as `2.0.1-alpha.n`. `2.0.1*` packages are also available for Deno at
 | 
			
		||||
https://deno.land/x/automerge
 | 
			
		||||
An alpha release of the javascript package is currently available as
 | 
			
		||||
`@automerge/automerge@2.0.0-alpha.n` where `n` is an integer. We are gathering
 | 
			
		||||
feedback on the API and looking to release a `2.0.0` in the next few weeks.
 | 
			
		||||
 | 
			
		||||
### Rust
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -53,9 +52,7 @@ The rust codebase is currently oriented around producing a performant backend
 | 
			
		|||
for the Javascript wrapper and as such the API for Rust code is low level and
 | 
			
		||||
not well documented. We will be returning to this over the next few months but
 | 
			
		||||
for now you will need to be comfortable reading the tests and asking questions
 | 
			
		||||
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)
 | 
			
		||||
to figure out how to use it.
 | 
			
		||||
 | 
			
		||||
## Repository Organisation
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -112,16 +109,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
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,7 +54,6 @@
 | 
			
		|||
 | 
			
		||||
          nodejs
 | 
			
		||||
          yarn
 | 
			
		||||
          deno
 | 
			
		||||
 | 
			
		||||
          # c deps
 | 
			
		||||
          cmake
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,13 +3,4 @@ module.exports = {
 | 
			
		|||
  parser: "@typescript-eslint/parser",
 | 
			
		||||
  plugins: ["@typescript-eslint"],
 | 
			
		||||
  extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
 | 
			
		||||
  rules: {
 | 
			
		||||
    "@typescript-eslint/no-unused-vars": [
 | 
			
		||||
      "error",
 | 
			
		||||
      {
 | 
			
		||||
        argsIgnorePattern: "^_",
 | 
			
		||||
        varsIgnorePattern: "^_",
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5845,9 +5845,9 @@ json-stable-stringify-without-jsonify@^1.0.1:
 | 
			
		|||
  integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
 | 
			
		||||
 | 
			
		||||
json5@^1.0.1:
 | 
			
		||||
  version "1.0.2"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
 | 
			
		||||
  integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
 | 
			
		||||
  version "1.0.1"
 | 
			
		||||
  resolved "http://localhost:4873/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
 | 
			
		||||
  integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    minimist "^1.2.0"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -6165,9 +6165,9 @@ minimatch@^5.0.1:
 | 
			
		|||
    brace-expansion "^2.0.1"
 | 
			
		||||
 | 
			
		||||
minimist@^1.2.0, minimist@^1.2.6:
 | 
			
		||||
  version "1.2.7"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
 | 
			
		||||
  integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
 | 
			
		||||
  version "1.2.6"
 | 
			
		||||
  resolved "http://localhost:4873/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
 | 
			
		||||
  integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
 | 
			
		||||
 | 
			
		||||
mkdirp@~0.5.1:
 | 
			
		||||
  version "0.5.6"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@
 | 
			
		|||
    "Orion Henry <orion@inkandswitch.com>",
 | 
			
		||||
    "Martin Kleppmann"
 | 
			
		||||
  ],
 | 
			
		||||
  "version": "2.0.2",
 | 
			
		||||
  "version": "2.0.1-alpha.5",
 | 
			
		||||
  "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.25",
 | 
			
		||||
    "@automerge/automerge-wasm": "0.1.22",
 | 
			
		||||
    "uuid": "^9.0.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,100 +0,0 @@
 | 
			
		|||
import { Counter, type AutomergeValue } from "./types"
 | 
			
		||||
import { Text } from "./text"
 | 
			
		||||
import { type AutomergeValue as UnstableAutomergeValue } from "./unstable_types"
 | 
			
		||||
import { type Target, Text1Target, Text2Target } from "./proxies"
 | 
			
		||||
import { mapProxy, listProxy, ValueType } from "./proxies"
 | 
			
		||||
import type { Prop, ObjID } from "@automerge/automerge-wasm"
 | 
			
		||||
import { Automerge } from "@automerge/automerge-wasm"
 | 
			
		||||
 | 
			
		||||
export type ConflictsF<T extends Target> = { [key: string]: ValueType<T> }
 | 
			
		||||
export type Conflicts = ConflictsF<Text1Target>
 | 
			
		||||
export type UnstableConflicts = ConflictsF<Text2Target>
 | 
			
		||||
 | 
			
		||||
export function stableConflictAt(
 | 
			
		||||
  context: Automerge,
 | 
			
		||||
  objectId: ObjID,
 | 
			
		||||
  prop: Prop
 | 
			
		||||
): Conflicts | undefined {
 | 
			
		||||
  return conflictAt<Text1Target>(
 | 
			
		||||
    context,
 | 
			
		||||
    objectId,
 | 
			
		||||
    prop,
 | 
			
		||||
    true,
 | 
			
		||||
    (context: Automerge, conflictId: ObjID): AutomergeValue => {
 | 
			
		||||
      return new Text(context.text(conflictId))
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function unstableConflictAt(
 | 
			
		||||
  context: Automerge,
 | 
			
		||||
  objectId: ObjID,
 | 
			
		||||
  prop: Prop
 | 
			
		||||
): UnstableConflicts | undefined {
 | 
			
		||||
  return conflictAt<Text2Target>(
 | 
			
		||||
    context,
 | 
			
		||||
    objectId,
 | 
			
		||||
    prop,
 | 
			
		||||
    true,
 | 
			
		||||
    (context: Automerge, conflictId: ObjID): UnstableAutomergeValue => {
 | 
			
		||||
      return context.text(conflictId)
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function conflictAt<T extends Target>(
 | 
			
		||||
  context: Automerge,
 | 
			
		||||
  objectId: ObjID,
 | 
			
		||||
  prop: Prop,
 | 
			
		||||
  textV2: boolean,
 | 
			
		||||
  handleText: (a: Automerge, conflictId: ObjID) => ValueType<T>
 | 
			
		||||
): ConflictsF<T> | undefined {
 | 
			
		||||
  const values = context.getAll(objectId, prop)
 | 
			
		||||
  if (values.length <= 1) {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
  const result: ConflictsF<T> = {}
 | 
			
		||||
  for (const fullVal of values) {
 | 
			
		||||
    switch (fullVal[0]) {
 | 
			
		||||
      case "map":
 | 
			
		||||
        result[fullVal[1]] = mapProxy<T>(
 | 
			
		||||
          context,
 | 
			
		||||
          fullVal[1],
 | 
			
		||||
          textV2,
 | 
			
		||||
          [prop],
 | 
			
		||||
          true
 | 
			
		||||
        )
 | 
			
		||||
        break
 | 
			
		||||
      case "list":
 | 
			
		||||
        result[fullVal[1]] = listProxy<T>(
 | 
			
		||||
          context,
 | 
			
		||||
          fullVal[1],
 | 
			
		||||
          textV2,
 | 
			
		||||
          [prop],
 | 
			
		||||
          true
 | 
			
		||||
        )
 | 
			
		||||
        break
 | 
			
		||||
      case "text":
 | 
			
		||||
        result[fullVal[1]] = handleText(context, fullVal[1] as ObjID)
 | 
			
		||||
        break
 | 
			
		||||
      case "str":
 | 
			
		||||
      case "uint":
 | 
			
		||||
      case "int":
 | 
			
		||||
      case "f64":
 | 
			
		||||
      case "boolean":
 | 
			
		||||
      case "bytes":
 | 
			
		||||
      case "null":
 | 
			
		||||
        result[fullVal[2]] = fullVal[1] as ValueType<T>
 | 
			
		||||
        break
 | 
			
		||||
      case "counter":
 | 
			
		||||
        result[fullVal[2]] = new Counter(fullVal[1]) as ValueType<T>
 | 
			
		||||
        break
 | 
			
		||||
      case "timestamp":
 | 
			
		||||
        result[fullVal[2]] = new Date(fullVal[1]) as ValueType<T>
 | 
			
		||||
        break
 | 
			
		||||
      default:
 | 
			
		||||
        throw RangeError(`datatype ${fullVal[0]} unimplemented`)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return result
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -100,7 +100,7 @@ export function getWriteableCounter(
 | 
			
		|||
  path: Prop[],
 | 
			
		||||
  objectId: ObjID,
 | 
			
		||||
  key: Prop
 | 
			
		||||
): WriteableCounter {
 | 
			
		||||
) {
 | 
			
		||||
  return new WriteableCounter(value, context, path, objectId, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,6 @@ export type { ChangeToEncode } from "@automerge/automerge-wasm"
 | 
			
		|||
 | 
			
		||||
export function UseApi(api: API) {
 | 
			
		||||
  for (const k in api) {
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-extra-semi,@typescript-eslint/no-explicit-any
 | 
			
		||||
    ;(ApiHandler as any)[k] = (api as any)[k]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,3 @@
 | 
			
		|||
/* eslint-disable  @typescript-eslint/no-explicit-any */
 | 
			
		||||
import { Text } from "./text"
 | 
			
		||||
import {
 | 
			
		||||
  Automerge,
 | 
			
		||||
| 
						 | 
				
			
			@ -7,12 +6,13 @@ import {
 | 
			
		|||
  type Prop,
 | 
			
		||||
} from "@automerge/automerge-wasm"
 | 
			
		||||
 | 
			
		||||
import type { AutomergeValue, ScalarValue, MapValue, ListValue } from "./types"
 | 
			
		||||
import {
 | 
			
		||||
  type AutomergeValue as UnstableAutomergeValue,
 | 
			
		||||
  MapValue as UnstableMapValue,
 | 
			
		||||
  ListValue as UnstableListValue,
 | 
			
		||||
} from "./unstable_types"
 | 
			
		||||
import type {
 | 
			
		||||
  AutomergeValue,
 | 
			
		||||
  ScalarValue,
 | 
			
		||||
  MapValue,
 | 
			
		||||
  ListValue,
 | 
			
		||||
  TextValue,
 | 
			
		||||
} from "./types"
 | 
			
		||||
import { Counter, getWriteableCounter } from "./counter"
 | 
			
		||||
import {
 | 
			
		||||
  STATE,
 | 
			
		||||
| 
						 | 
				
			
			@ -26,38 +26,19 @@ import {
 | 
			
		|||
} from "./constants"
 | 
			
		||||
import { RawString } from "./raw_string"
 | 
			
		||||
 | 
			
		||||
type TargetCommon = {
 | 
			
		||||
type Target = {
 | 
			
		||||
  context: Automerge
 | 
			
		||||
  objectId: ObjID
 | 
			
		||||
  path: Array<Prop>
 | 
			
		||||
  readonly: boolean
 | 
			
		||||
  heads?: Array<string>
 | 
			
		||||
  cache: object
 | 
			
		||||
  cache: {}
 | 
			
		||||
  trace?: any
 | 
			
		||||
  frozen: boolean
 | 
			
		||||
  textV2: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type Text2Target = TargetCommon & { textV2: true }
 | 
			
		||||
export type Text1Target = TargetCommon & { textV2: false }
 | 
			
		||||
export type Target = Text1Target | Text2Target
 | 
			
		||||
 | 
			
		||||
export type ValueType<T extends Target> = T extends Text2Target
 | 
			
		||||
  ? UnstableAutomergeValue
 | 
			
		||||
  : T extends Text1Target
 | 
			
		||||
  ? AutomergeValue
 | 
			
		||||
  : never
 | 
			
		||||
type MapValueType<T extends Target> = T extends Text2Target
 | 
			
		||||
  ? UnstableMapValue
 | 
			
		||||
  : T extends Text1Target
 | 
			
		||||
  ? MapValue
 | 
			
		||||
  : never
 | 
			
		||||
type ListValueType<T extends Target> = T extends Text2Target
 | 
			
		||||
  ? UnstableListValue
 | 
			
		||||
  : T extends Text1Target
 | 
			
		||||
  ? ListValue
 | 
			
		||||
  : never
 | 
			
		||||
 | 
			
		||||
function parseListIndex(key: any) {
 | 
			
		||||
function parseListIndex(key) {
 | 
			
		||||
  if (typeof key === "string" && /^[0-9]+$/.test(key)) key = parseInt(key, 10)
 | 
			
		||||
  if (typeof key !== "number") {
 | 
			
		||||
    return key
 | 
			
		||||
| 
						 | 
				
			
			@ -68,10 +49,7 @@ function parseListIndex(key: any) {
 | 
			
		|||
  return key
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function valueAt<T extends Target>(
 | 
			
		||||
  target: T,
 | 
			
		||||
  prop: Prop
 | 
			
		||||
): ValueType<T> | undefined {
 | 
			
		||||
function valueAt(target: Target, prop: Prop): AutomergeValue | undefined {
 | 
			
		||||
  const { context, objectId, path, readonly, heads, textV2 } = target
 | 
			
		||||
  const value = context.getWithType(objectId, prop, heads)
 | 
			
		||||
  if (value === null) {
 | 
			
		||||
| 
						 | 
				
			
			@ -83,7 +61,7 @@ function valueAt<T extends Target>(
 | 
			
		|||
    case undefined:
 | 
			
		||||
      return
 | 
			
		||||
    case "map":
 | 
			
		||||
      return mapProxy<T>(
 | 
			
		||||
      return mapProxy(
 | 
			
		||||
        context,
 | 
			
		||||
        val as ObjID,
 | 
			
		||||
        textV2,
 | 
			
		||||
| 
						 | 
				
			
			@ -92,7 +70,7 @@ function valueAt<T extends Target>(
 | 
			
		|||
        heads
 | 
			
		||||
      )
 | 
			
		||||
    case "list":
 | 
			
		||||
      return listProxy<T>(
 | 
			
		||||
      return listProxy(
 | 
			
		||||
        context,
 | 
			
		||||
        val as ObjID,
 | 
			
		||||
        textV2,
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +80,7 @@ function valueAt<T extends Target>(
 | 
			
		|||
      )
 | 
			
		||||
    case "text":
 | 
			
		||||
      if (textV2) {
 | 
			
		||||
        return context.text(val as ObjID, heads) as ValueType<T>
 | 
			
		||||
        return context.text(val as ObjID, heads)
 | 
			
		||||
      } else {
 | 
			
		||||
        return textProxy(
 | 
			
		||||
          context,
 | 
			
		||||
| 
						 | 
				
			
			@ -110,36 +88,29 @@ function valueAt<T extends Target>(
 | 
			
		|||
          [...path, prop],
 | 
			
		||||
          readonly,
 | 
			
		||||
          heads
 | 
			
		||||
        ) as unknown as ValueType<T>
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
    case "str":
 | 
			
		||||
      return val as ValueType<T>
 | 
			
		||||
      return val
 | 
			
		||||
    case "uint":
 | 
			
		||||
      return val as ValueType<T>
 | 
			
		||||
      return val
 | 
			
		||||
    case "int":
 | 
			
		||||
      return val as ValueType<T>
 | 
			
		||||
      return val
 | 
			
		||||
    case "f64":
 | 
			
		||||
      return val as ValueType<T>
 | 
			
		||||
      return val
 | 
			
		||||
    case "boolean":
 | 
			
		||||
      return val as ValueType<T>
 | 
			
		||||
      return val
 | 
			
		||||
    case "null":
 | 
			
		||||
      return null as ValueType<T>
 | 
			
		||||
      return null
 | 
			
		||||
    case "bytes":
 | 
			
		||||
      return val as ValueType<T>
 | 
			
		||||
      return val
 | 
			
		||||
    case "timestamp":
 | 
			
		||||
      return val as ValueType<T>
 | 
			
		||||
      return val
 | 
			
		||||
    case "counter": {
 | 
			
		||||
      if (readonly) {
 | 
			
		||||
        return new Counter(val as number) as ValueType<T>
 | 
			
		||||
        return new Counter(val as number)
 | 
			
		||||
      } else {
 | 
			
		||||
        const counter: Counter = getWriteableCounter(
 | 
			
		||||
          val as number,
 | 
			
		||||
          context,
 | 
			
		||||
          path,
 | 
			
		||||
          objectId,
 | 
			
		||||
          prop
 | 
			
		||||
        )
 | 
			
		||||
        return counter as ValueType<T>
 | 
			
		||||
        return getWriteableCounter(val as number, context, path, objectId, prop)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
| 
						 | 
				
			
			@ -147,21 +118,7 @@ function valueAt<T extends Target>(
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ImportedValue =
 | 
			
		||||
  | [null, "null"]
 | 
			
		||||
  | [number, "uint"]
 | 
			
		||||
  | [number, "int"]
 | 
			
		||||
  | [number, "f64"]
 | 
			
		||||
  | [number, "counter"]
 | 
			
		||||
  | [number, "timestamp"]
 | 
			
		||||
  | [string, "str"]
 | 
			
		||||
  | [Text | string, "text"]
 | 
			
		||||
  | [Uint8Array, "bytes"]
 | 
			
		||||
  | [Array<any>, "list"]
 | 
			
		||||
  | [Record<string, any>, "map"]
 | 
			
		||||
  | [boolean, "boolean"]
 | 
			
		||||
 | 
			
		||||
function import_value(value: any, textV2: boolean): ImportedValue {
 | 
			
		||||
function import_value(value: any, textV2: boolean) {
 | 
			
		||||
  switch (typeof value) {
 | 
			
		||||
    case "object":
 | 
			
		||||
      if (value == null) {
 | 
			
		||||
| 
						 | 
				
			
			@ -213,10 +170,7 @@ function import_value(value: any, textV2: boolean): ImportedValue {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
const MapHandler = {
 | 
			
		||||
  get<T extends Target>(
 | 
			
		||||
    target: T,
 | 
			
		||||
    key: any
 | 
			
		||||
  ): ValueType<T> | ObjID | boolean | { handle: Automerge } {
 | 
			
		||||
  get(target: Target, key): AutomergeValue | { handle: Automerge } {
 | 
			
		||||
    const { context, objectId, cache } = target
 | 
			
		||||
    if (key === Symbol.toStringTag) {
 | 
			
		||||
      return target[Symbol.toStringTag]
 | 
			
		||||
| 
						 | 
				
			
			@ -231,7 +185,7 @@ const MapHandler = {
 | 
			
		|||
    return cache[key]
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  set(target: Target, key: any, val: any) {
 | 
			
		||||
  set(target: Target, key, val) {
 | 
			
		||||
    const { context, objectId, path, readonly, frozen, textV2 } = target
 | 
			
		||||
    target.cache = {} // reset cache on set
 | 
			
		||||
    if (val && val[OBJECT_ID]) {
 | 
			
		||||
| 
						 | 
				
			
			@ -267,10 +221,8 @@ const MapHandler = {
 | 
			
		|||
      }
 | 
			
		||||
      case "text": {
 | 
			
		||||
        if (textV2) {
 | 
			
		||||
          assertString(value)
 | 
			
		||||
          context.putObject(objectId, key, value)
 | 
			
		||||
        } else {
 | 
			
		||||
          assertText(value)
 | 
			
		||||
          const text = context.putObject(objectId, key, "")
 | 
			
		||||
          const proxyText = textProxy(context, text, [...path, key], readonly)
 | 
			
		||||
          for (let i = 0; i < value.length; i++) {
 | 
			
		||||
| 
						 | 
				
			
			@ -299,7 +251,7 @@ const MapHandler = {
 | 
			
		|||
    return true
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  deleteProperty(target: Target, key: any) {
 | 
			
		||||
  deleteProperty(target: Target, key) {
 | 
			
		||||
    const { context, objectId, readonly } = target
 | 
			
		||||
    target.cache = {} // reset cache on delete
 | 
			
		||||
    if (readonly) {
 | 
			
		||||
| 
						 | 
				
			
			@ -309,12 +261,12 @@ const MapHandler = {
 | 
			
		|||
    return true
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  has(target: Target, key: any) {
 | 
			
		||||
  has(target: Target, key) {
 | 
			
		||||
    const value = this.get(target, key)
 | 
			
		||||
    return value !== undefined
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  getOwnPropertyDescriptor(target: Target, key: any) {
 | 
			
		||||
  getOwnPropertyDescriptor(target: Target, key) {
 | 
			
		||||
    // const { context, objectId } = target
 | 
			
		||||
    const value = this.get(target, key)
 | 
			
		||||
    if (typeof value !== "undefined") {
 | 
			
		||||
| 
						 | 
				
			
			@ -335,20 +287,11 @@ const MapHandler = {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
const ListHandler = {
 | 
			
		||||
  get<T extends Target>(
 | 
			
		||||
    target: T,
 | 
			
		||||
    index: any
 | 
			
		||||
  ):
 | 
			
		||||
    | ValueType<T>
 | 
			
		||||
    | boolean
 | 
			
		||||
    | ObjID
 | 
			
		||||
    | { handle: Automerge }
 | 
			
		||||
    | number
 | 
			
		||||
    | ((_: any) => boolean) {
 | 
			
		||||
  get(target: Target, index) {
 | 
			
		||||
    const { context, objectId, heads } = target
 | 
			
		||||
    index = parseListIndex(index)
 | 
			
		||||
    if (index === Symbol.hasInstance) {
 | 
			
		||||
      return (instance: any) => {
 | 
			
		||||
      return instance => {
 | 
			
		||||
        return Array.isArray(instance)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -361,13 +304,13 @@ const ListHandler = {
 | 
			
		|||
    if (index === STATE) return { handle: context }
 | 
			
		||||
    if (index === "length") return context.length(objectId, heads)
 | 
			
		||||
    if (typeof index === "number") {
 | 
			
		||||
      return valueAt(target, index) as ValueType<T>
 | 
			
		||||
      return valueAt(target, index)
 | 
			
		||||
    } else {
 | 
			
		||||
      return listMethods(target)[index]
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  set(target: Target, index: any, val: any) {
 | 
			
		||||
  set(target: Target, index, val) {
 | 
			
		||||
    const { context, objectId, path, readonly, frozen, textV2 } = target
 | 
			
		||||
    index = parseListIndex(index)
 | 
			
		||||
    if (val && val[OBJECT_ID]) {
 | 
			
		||||
| 
						 | 
				
			
			@ -391,7 +334,7 @@ const ListHandler = {
 | 
			
		|||
    }
 | 
			
		||||
    switch (datatype) {
 | 
			
		||||
      case "list": {
 | 
			
		||||
        let list: ObjID
 | 
			
		||||
        let list
 | 
			
		||||
        if (index >= context.length(objectId)) {
 | 
			
		||||
          list = context.insertObject(objectId, index, [])
 | 
			
		||||
        } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -409,15 +352,13 @@ const ListHandler = {
 | 
			
		|||
      }
 | 
			
		||||
      case "text": {
 | 
			
		||||
        if (textV2) {
 | 
			
		||||
          assertString(value)
 | 
			
		||||
          if (index >= context.length(objectId)) {
 | 
			
		||||
            context.insertObject(objectId, index, value)
 | 
			
		||||
          } else {
 | 
			
		||||
            context.putObject(objectId, index, value)
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          let text: ObjID
 | 
			
		||||
          assertText(value)
 | 
			
		||||
          let text
 | 
			
		||||
          if (index >= context.length(objectId)) {
 | 
			
		||||
            text = context.insertObject(objectId, index, "")
 | 
			
		||||
          } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -429,7 +370,7 @@ const ListHandler = {
 | 
			
		|||
        break
 | 
			
		||||
      }
 | 
			
		||||
      case "map": {
 | 
			
		||||
        let map: ObjID
 | 
			
		||||
        let map
 | 
			
		||||
        if (index >= context.length(objectId)) {
 | 
			
		||||
          map = context.insertObject(objectId, index, {})
 | 
			
		||||
        } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -457,7 +398,7 @@ const ListHandler = {
 | 
			
		|||
    return true
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  deleteProperty(target: Target, index: any) {
 | 
			
		||||
  deleteProperty(target: Target, index) {
 | 
			
		||||
    const { context, objectId } = target
 | 
			
		||||
    index = parseListIndex(index)
 | 
			
		||||
    const elem = context.get(objectId, index)
 | 
			
		||||
| 
						 | 
				
			
			@ -470,7 +411,7 @@ const ListHandler = {
 | 
			
		|||
    return true
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  has(target: Target, index: any) {
 | 
			
		||||
  has(target: Target, index) {
 | 
			
		||||
    const { context, objectId, heads } = target
 | 
			
		||||
    index = parseListIndex(index)
 | 
			
		||||
    if (typeof index === "number") {
 | 
			
		||||
| 
						 | 
				
			
			@ -479,7 +420,7 @@ const ListHandler = {
 | 
			
		|||
    return index === "length"
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  getOwnPropertyDescriptor(target: Target, index: any) {
 | 
			
		||||
  getOwnPropertyDescriptor(target: Target, index) {
 | 
			
		||||
    const { context, objectId, heads } = target
 | 
			
		||||
 | 
			
		||||
    if (index === "length")
 | 
			
		||||
| 
						 | 
				
			
			@ -493,7 +434,7 @@ const ListHandler = {
 | 
			
		|||
    return { configurable: true, enumerable: true, value }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  getPrototypeOf(target: Target) {
 | 
			
		||||
  getPrototypeOf(target) {
 | 
			
		||||
    return Object.getPrototypeOf(target)
 | 
			
		||||
  },
 | 
			
		||||
  ownKeys(/*target*/): string[] {
 | 
			
		||||
| 
						 | 
				
			
			@ -535,14 +476,14 @@ const TextHandler = Object.assign({}, ListHandler, {
 | 
			
		|||
  },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export function mapProxy<T extends Target>(
 | 
			
		||||
export function mapProxy(
 | 
			
		||||
  context: Automerge,
 | 
			
		||||
  objectId: ObjID,
 | 
			
		||||
  textV2: boolean,
 | 
			
		||||
  path?: Prop[],
 | 
			
		||||
  readonly?: boolean,
 | 
			
		||||
  heads?: Heads
 | 
			
		||||
): MapValueType<T> {
 | 
			
		||||
): MapValue {
 | 
			
		||||
  const target: Target = {
 | 
			
		||||
    context,
 | 
			
		||||
    objectId,
 | 
			
		||||
| 
						 | 
				
			
			@ -555,19 +496,19 @@ export function mapProxy<T extends Target>(
 | 
			
		|||
  }
 | 
			
		||||
  const proxied = {}
 | 
			
		||||
  Object.assign(proxied, target)
 | 
			
		||||
  const result = new Proxy(proxied, MapHandler)
 | 
			
		||||
  let result = new Proxy(proxied, MapHandler)
 | 
			
		||||
  // conversion through unknown is necessary because the types are so different
 | 
			
		||||
  return result as unknown as MapValueType<T>
 | 
			
		||||
  return result as unknown as MapValue
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function listProxy<T extends Target>(
 | 
			
		||||
export function listProxy(
 | 
			
		||||
  context: Automerge,
 | 
			
		||||
  objectId: ObjID,
 | 
			
		||||
  textV2: boolean,
 | 
			
		||||
  path?: Prop[],
 | 
			
		||||
  readonly?: boolean,
 | 
			
		||||
  heads?: Heads
 | 
			
		||||
): ListValueType<T> {
 | 
			
		||||
): ListValue {
 | 
			
		||||
  const target: Target = {
 | 
			
		||||
    context,
 | 
			
		||||
    objectId,
 | 
			
		||||
| 
						 | 
				
			
			@ -580,22 +521,17 @@ export function listProxy<T extends Target>(
 | 
			
		|||
  }
 | 
			
		||||
  const proxied = []
 | 
			
		||||
  Object.assign(proxied, target)
 | 
			
		||||
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  return new Proxy(proxied, ListHandler) as unknown as ListValue
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface TextProxy extends Text {
 | 
			
		||||
  splice: (index: any, del: any, ...vals: any[]) => void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function textProxy(
 | 
			
		||||
  context: Automerge,
 | 
			
		||||
  objectId: ObjID,
 | 
			
		||||
  path?: Prop[],
 | 
			
		||||
  readonly?: boolean,
 | 
			
		||||
  heads?: Heads
 | 
			
		||||
): TextProxy {
 | 
			
		||||
): TextValue {
 | 
			
		||||
  const target: Target = {
 | 
			
		||||
    context,
 | 
			
		||||
    objectId,
 | 
			
		||||
| 
						 | 
				
			
			@ -606,9 +542,7 @@ export function textProxy(
 | 
			
		|||
    cache: {},
 | 
			
		||||
    textV2: false,
 | 
			
		||||
  }
 | 
			
		||||
  const proxied = {}
 | 
			
		||||
  Object.assign(proxied, target)
 | 
			
		||||
  return new Proxy(proxied, TextHandler) as unknown as TextProxy
 | 
			
		||||
  return new Proxy(target, TextHandler) as unknown as TextValue
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function rootProxy<T>(
 | 
			
		||||
| 
						 | 
				
			
			@ -620,10 +554,10 @@ export function rootProxy<T>(
 | 
			
		|||
  return <any>mapProxy(context, "_root", textV2, [], !!readonly)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function listMethods<T extends Target>(target: T) {
 | 
			
		||||
function listMethods(target: Target) {
 | 
			
		||||
  const { context, objectId, path, readonly, frozen, heads, textV2 } = target
 | 
			
		||||
  const methods = {
 | 
			
		||||
    deleteAt(index: number, numDelete: number) {
 | 
			
		||||
    deleteAt(index, numDelete) {
 | 
			
		||||
      if (typeof numDelete === "number") {
 | 
			
		||||
        context.splice(objectId, index, numDelete)
 | 
			
		||||
      } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -638,20 +572,8 @@ function listMethods<T extends Target>(target: T) {
 | 
			
		|||
      start = parseListIndex(start || 0)
 | 
			
		||||
      end = parseListIndex(end || length)
 | 
			
		||||
      for (let i = start; i < Math.min(end, length); i++) {
 | 
			
		||||
        if (datatype === "list" || datatype === "map") {
 | 
			
		||||
        if (datatype === "text" || datatype === "list" || datatype === "map") {
 | 
			
		||||
          context.putObject(objectId, i, value)
 | 
			
		||||
        } else if (datatype === "text") {
 | 
			
		||||
          if (textV2) {
 | 
			
		||||
            assertString(value)
 | 
			
		||||
            context.putObject(objectId, i, value)
 | 
			
		||||
          } else {
 | 
			
		||||
            assertText(value)
 | 
			
		||||
            const text = context.putObject(objectId, i, "")
 | 
			
		||||
            const proxyText = textProxy(context, text, [...path, i], readonly)
 | 
			
		||||
            for (let i = 0; i < value.length; i++) {
 | 
			
		||||
              proxyText[i] = value.get(i)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          context.put(objectId, i, value, datatype)
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -659,7 +581,7 @@ function listMethods<T extends Target>(target: T) {
 | 
			
		|||
      return this
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    indexOf(o: any, start = 0) {
 | 
			
		||||
    indexOf(o, start = 0) {
 | 
			
		||||
      const length = context.length(objectId)
 | 
			
		||||
      for (let i = start; i < length; i++) {
 | 
			
		||||
        const value = context.getWithType(objectId, i, heads)
 | 
			
		||||
| 
						 | 
				
			
			@ -670,7 +592,7 @@ function listMethods<T extends Target>(target: T) {
 | 
			
		|||
      return -1
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    insertAt(index: number, ...values: any[]) {
 | 
			
		||||
    insertAt(index, ...values) {
 | 
			
		||||
      this.splice(index, 0, ...values)
 | 
			
		||||
      return this
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -685,7 +607,7 @@ function listMethods<T extends Target>(target: T) {
 | 
			
		|||
      return last
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    push(...values: any[]) {
 | 
			
		||||
    push(...values) {
 | 
			
		||||
      const len = context.length(objectId)
 | 
			
		||||
      this.splice(len, 0, ...values)
 | 
			
		||||
      return context.length(objectId)
 | 
			
		||||
| 
						 | 
				
			
			@ -698,7 +620,7 @@ function listMethods<T extends Target>(target: T) {
 | 
			
		|||
      return first
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    splice(index: any, del: any, ...vals: any[]) {
 | 
			
		||||
    splice(index, del, ...vals) {
 | 
			
		||||
      index = parseListIndex(index)
 | 
			
		||||
      del = parseListIndex(del)
 | 
			
		||||
      for (const val of vals) {
 | 
			
		||||
| 
						 | 
				
			
			@ -716,9 +638,9 @@ function listMethods<T extends Target>(target: T) {
 | 
			
		|||
          "Sequence object cannot be modified outside of a change block"
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
      const result: ValueType<T>[] = []
 | 
			
		||||
      const result: AutomergeValue[] = []
 | 
			
		||||
      for (let i = 0; i < del; i++) {
 | 
			
		||||
        const value = valueAt<T>(target, index)
 | 
			
		||||
        const value = valueAt(target, index)
 | 
			
		||||
        if (value !== undefined) {
 | 
			
		||||
          result.push(value)
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -741,7 +663,6 @@ function listMethods<T extends Target>(target: T) {
 | 
			
		|||
          }
 | 
			
		||||
          case "text": {
 | 
			
		||||
            if (textV2) {
 | 
			
		||||
              assertString(value)
 | 
			
		||||
              context.insertObject(objectId, index, value)
 | 
			
		||||
            } else {
 | 
			
		||||
              const text = context.insertObject(objectId, index, "")
 | 
			
		||||
| 
						 | 
				
			
			@ -777,7 +698,7 @@ function listMethods<T extends Target>(target: T) {
 | 
			
		|||
      return result
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    unshift(...values: any) {
 | 
			
		||||
    unshift(...values) {
 | 
			
		||||
      this.splice(0, 0, ...values)
 | 
			
		||||
      return context.length(objectId)
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -828,11 +749,11 @@ function listMethods<T extends Target>(target: T) {
 | 
			
		|||
      return iterator
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    toArray(): ValueType<T>[] {
 | 
			
		||||
      const list: Array<ValueType<T>> = []
 | 
			
		||||
      let value: ValueType<T> | undefined
 | 
			
		||||
    toArray(): AutomergeValue[] {
 | 
			
		||||
      const list: AutomergeValue = []
 | 
			
		||||
      let value
 | 
			
		||||
      do {
 | 
			
		||||
        value = valueAt<T>(target, list.length)
 | 
			
		||||
        value = valueAt(target, list.length)
 | 
			
		||||
        if (value !== undefined) {
 | 
			
		||||
          list.push(value)
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -841,7 +762,7 @@ function listMethods<T extends Target>(target: T) {
 | 
			
		|||
      return list
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    map<U>(f: (_a: ValueType<T>, _n: number) => U): U[] {
 | 
			
		||||
    map<T>(f: (AutomergeValue, number) => T): T[] {
 | 
			
		||||
      return this.toArray().map(f)
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -853,26 +774,24 @@ function listMethods<T extends Target>(target: T) {
 | 
			
		|||
      return this.toArray().toLocaleString()
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    forEach(f: (_a: ValueType<T>, _n: number) => undefined) {
 | 
			
		||||
    forEach(f: (AutomergeValue, number) => undefined) {
 | 
			
		||||
      return this.toArray().forEach(f)
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // todo: real concat function is different
 | 
			
		||||
    concat(other: ValueType<T>[]): ValueType<T>[] {
 | 
			
		||||
    concat(other: AutomergeValue[]): AutomergeValue[] {
 | 
			
		||||
      return this.toArray().concat(other)
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    every(f: (_a: ValueType<T>, _n: number) => boolean): boolean {
 | 
			
		||||
    every(f: (AutomergeValue, number) => boolean): boolean {
 | 
			
		||||
      return this.toArray().every(f)
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    filter(f: (_a: ValueType<T>, _n: number) => boolean): ValueType<T>[] {
 | 
			
		||||
    filter(f: (AutomergeValue, number) => boolean): AutomergeValue[] {
 | 
			
		||||
      return this.toArray().filter(f)
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    find(
 | 
			
		||||
      f: (_a: ValueType<T>, _n: number) => boolean
 | 
			
		||||
    ): ValueType<T> | undefined {
 | 
			
		||||
    find(f: (AutomergeValue, number) => boolean): AutomergeValue | undefined {
 | 
			
		||||
      let index = 0
 | 
			
		||||
      for (const v of this) {
 | 
			
		||||
        if (f(v, index)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -882,7 +801,7 @@ function listMethods<T extends Target>(target: T) {
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    findIndex(f: (_a: ValueType<T>, _n: number) => boolean): number {
 | 
			
		||||
    findIndex(f: (AutomergeValue, number) => boolean): number {
 | 
			
		||||
      let index = 0
 | 
			
		||||
      for (const v of this) {
 | 
			
		||||
        if (f(v, index)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -893,7 +812,7 @@ function listMethods<T extends Target>(target: T) {
 | 
			
		|||
      return -1
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    includes(elem: ValueType<T>): boolean {
 | 
			
		||||
    includes(elem: AutomergeValue): boolean {
 | 
			
		||||
      return this.find(e => e === elem) !== undefined
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -901,30 +820,29 @@ function listMethods<T extends Target>(target: T) {
 | 
			
		|||
      return this.toArray().join(sep)
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    reduce<U>(
 | 
			
		||||
      f: (acc: U, currentValue: ValueType<T>) => U,
 | 
			
		||||
      initialValue: U
 | 
			
		||||
    ): U | undefined {
 | 
			
		||||
      return this.toArray().reduce<U>(f, initialValue)
 | 
			
		||||
    // todo: remove the any
 | 
			
		||||
    reduce<T>(f: (any, AutomergeValue) => T, initalValue?: T): T | undefined {
 | 
			
		||||
      return this.toArray().reduce(f, initalValue)
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    reduceRight<U>(
 | 
			
		||||
      f: (acc: U, item: ValueType<T>) => U,
 | 
			
		||||
      initialValue: U
 | 
			
		||||
    ): U | undefined {
 | 
			
		||||
      return this.toArray().reduceRight(f, initialValue)
 | 
			
		||||
    // todo: remove the any
 | 
			
		||||
    reduceRight<T>(
 | 
			
		||||
      f: (any, AutomergeValue) => T,
 | 
			
		||||
      initalValue?: T
 | 
			
		||||
    ): T | undefined {
 | 
			
		||||
      return this.toArray().reduceRight(f, initalValue)
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    lastIndexOf(search: ValueType<T>, fromIndex = +Infinity): number {
 | 
			
		||||
    lastIndexOf(search: AutomergeValue, fromIndex = +Infinity): number {
 | 
			
		||||
      // this can be faster
 | 
			
		||||
      return this.toArray().lastIndexOf(search, fromIndex)
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    slice(index?: number, num?: number): ValueType<T>[] {
 | 
			
		||||
    slice(index?: number, num?: number): AutomergeValue[] {
 | 
			
		||||
      return this.toArray().slice(index, num)
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    some(f: (v: ValueType<T>, i: number) => boolean): boolean {
 | 
			
		||||
    some(f: (AutomergeValue, number) => boolean): boolean {
 | 
			
		||||
      let index = 0
 | 
			
		||||
      for (const v of this) {
 | 
			
		||||
        if (f(v, index)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -951,7 +869,7 @@ function listMethods<T extends Target>(target: T) {
 | 
			
		|||
function textMethods(target: Target) {
 | 
			
		||||
  const { context, objectId, heads } = target
 | 
			
		||||
  const methods = {
 | 
			
		||||
    set(index: number, value: any) {
 | 
			
		||||
    set(index: number, value) {
 | 
			
		||||
      return (this[index] = value)
 | 
			
		||||
    },
 | 
			
		||||
    get(index: number): AutomergeValue {
 | 
			
		||||
| 
						 | 
				
			
			@ -984,22 +902,10 @@ function textMethods(target: Target) {
 | 
			
		|||
    toJSON(): string {
 | 
			
		||||
      return this.toString()
 | 
			
		||||
    },
 | 
			
		||||
    indexOf(o: any, start = 0) {
 | 
			
		||||
    indexOf(o, start = 0) {
 | 
			
		||||
      const text = context.text(objectId)
 | 
			
		||||
      return text.indexOf(o, start)
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
  return methods
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function assertText(value: Text | string): asserts value is Text {
 | 
			
		||||
  if (!(value instanceof Text)) {
 | 
			
		||||
    throw new Error("value was not a Text instance")
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function assertString(value: Text | string): asserts value is string {
 | 
			
		||||
  if (typeof value !== "string") {
 | 
			
		||||
    throw new Error("value was not a string")
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
/** @hidden **/
 | 
			
		||||
export { /** @hidden */ uuid } from "./uuid"
 | 
			
		||||
 | 
			
		||||
import { rootProxy } from "./proxies"
 | 
			
		||||
import { rootProxy, listProxy, mapProxy, textProxy } from "./proxies"
 | 
			
		||||
import { STATE } from "./constants"
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
| 
						 | 
				
			
			@ -20,13 +20,13 @@ export {
 | 
			
		|||
  type Patch,
 | 
			
		||||
  type PatchCallback,
 | 
			
		||||
  type ScalarValue,
 | 
			
		||||
  Text,
 | 
			
		||||
} from "./types"
 | 
			
		||||
 | 
			
		||||
import { Text } from "./text"
 | 
			
		||||
export { Text } from "./text"
 | 
			
		||||
 | 
			
		||||
import type {
 | 
			
		||||
  API as WasmAPI,
 | 
			
		||||
  API,
 | 
			
		||||
  Actor as ActorId,
 | 
			
		||||
  Prop,
 | 
			
		||||
  ObjID,
 | 
			
		||||
| 
						 | 
				
			
			@ -34,29 +34,17 @@ import type {
 | 
			
		|||
  DecodedChange,
 | 
			
		||||
  Heads,
 | 
			
		||||
  MaterializeValue,
 | 
			
		||||
  JsSyncState,
 | 
			
		||||
  JsSyncState as SyncState,
 | 
			
		||||
  SyncMessage,
 | 
			
		||||
  DecodedSyncMessage,
 | 
			
		||||
} from "@automerge/automerge-wasm"
 | 
			
		||||
export type {
 | 
			
		||||
  PutPatch,
 | 
			
		||||
  DelPatch,
 | 
			
		||||
  SpliceTextPatch,
 | 
			
		||||
  InsertPatch,
 | 
			
		||||
  SplicePatch,
 | 
			
		||||
  IncPatch,
 | 
			
		||||
  SyncMessage,
 | 
			
		||||
} from "@automerge/automerge-wasm"
 | 
			
		||||
 | 
			
		||||
/** @hidden **/
 | 
			
		||||
type API = WasmAPI
 | 
			
		||||
 | 
			
		||||
const SyncStateSymbol = Symbol("_syncstate")
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An opaque type tracking the state of sync with a remote peer
 | 
			
		||||
 */
 | 
			
		||||
type SyncState = JsSyncState & { _opaque: typeof SyncStateSymbol }
 | 
			
		||||
 | 
			
		||||
import { ApiHandler, type ChangeToEncode, UseApi } from "./low_level"
 | 
			
		||||
 | 
			
		||||
import { Automerge } from "@automerge/automerge-wasm"
 | 
			
		||||
| 
						 | 
				
			
			@ -65,8 +53,6 @@ import { RawString } from "./raw_string"
 | 
			
		|||
 | 
			
		||||
import { _state, _is_proxy, _trace, _obj } from "./internal_state"
 | 
			
		||||
 | 
			
		||||
import { stableConflictAt } from "./conflicts"
 | 
			
		||||
 | 
			
		||||
/** Options passed to {@link change}, and {@link emptyChange}
 | 
			
		||||
 * @typeParam T - The type of value contained in the document
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			@ -84,36 +70,13 @@ export type ChangeOptions<T> = {
 | 
			
		|||
 */
 | 
			
		||||
export type ApplyOptions<T> = { patchCallback?: PatchCallback<T> }
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A List is an extended Array that adds the two helper methods `deleteAt` and `insertAt`.
 | 
			
		||||
 */
 | 
			
		||||
export interface List<T> extends Array<T> {
 | 
			
		||||
  insertAt(index: number, ...args: T[]): List<T>
 | 
			
		||||
  deleteAt(index: number, numDelete?: number): List<T>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * To extend an arbitrary type, we have to turn any arrays that are part of the type's definition into Lists.
 | 
			
		||||
 * So we recurse through the properties of T, turning any Arrays we find into Lists.
 | 
			
		||||
 */
 | 
			
		||||
export type Extend<T> =
 | 
			
		||||
  // is it an array? make it a list (we recursively extend the type of the array's elements as well)
 | 
			
		||||
  T extends Array<infer T>
 | 
			
		||||
    ? List<Extend<T>>
 | 
			
		||||
    : // is it an object? recursively extend all of its properties
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/ban-types
 | 
			
		||||
    T extends Object
 | 
			
		||||
    ? { [P in keyof T]: Extend<T[P]> }
 | 
			
		||||
    : // otherwise leave the type alone
 | 
			
		||||
      T
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Function which is called by {@link change} when making changes to a `Doc<T>`
 | 
			
		||||
 * @typeParam T - The type of value contained in the document
 | 
			
		||||
 *
 | 
			
		||||
 * This function may mutate `doc`
 | 
			
		||||
 */
 | 
			
		||||
export type ChangeFn<T> = (doc: Extend<T>) => void
 | 
			
		||||
export type ChangeFn<T> = (doc: T) => void
 | 
			
		||||
 | 
			
		||||
/** @hidden **/
 | 
			
		||||
export interface State<T> {
 | 
			
		||||
| 
						 | 
				
			
			@ -172,12 +135,11 @@ export function init<T>(_opts?: ActorId | InitOptions<T>): Doc<T> {
 | 
			
		|||
  const handle = ApiHandler.create(opts.enableTextV2 || false, opts.actor)
 | 
			
		||||
  handle.enablePatches(true)
 | 
			
		||||
  handle.enableFreeze(!!opts.freeze)
 | 
			
		||||
  handle.registerDatatype("counter", (n: number) => new Counter(n))
 | 
			
		||||
  const textV2 = opts.enableTextV2 || false
 | 
			
		||||
  handle.registerDatatype("counter", (n: any) => new Counter(n))
 | 
			
		||||
  let textV2 = opts.enableTextV2 || false
 | 
			
		||||
  if (textV2) {
 | 
			
		||||
    handle.registerDatatype("str", (n: string) => new RawString(n))
 | 
			
		||||
  } else {
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
    handle.registerDatatype("text", (n: any) => new Text(n))
 | 
			
		||||
  }
 | 
			
		||||
  const doc = handle.materialize("/", undefined, {
 | 
			
		||||
| 
						 | 
				
			
			@ -241,7 +203,7 @@ export function clone<T>(
 | 
			
		|||
 | 
			
		||||
  // `change` uses the presence of state.heads to determine if we are in a view
 | 
			
		||||
  // set it to undefined to indicate that this is a full fat document
 | 
			
		||||
  const { heads: _oldHeads, ...stateSansHeads } = state
 | 
			
		||||
  const { heads: oldHeads, ...stateSansHeads } = state
 | 
			
		||||
  return handle.applyPatches(doc, { ...stateSansHeads, handle })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -305,7 +267,7 @@ export function from<T extends Record<string, unknown>>(
 | 
			
		|||
 * @example A change with a message and a timestamp
 | 
			
		||||
 *
 | 
			
		||||
 * ```
 | 
			
		||||
 * doc1 = automerge.change(doc1, {message: "add another value", time: 1640995200}, d => {
 | 
			
		||||
 * doc1 = automerge.change(doc1, {message: "add another value", timestamp: 1640995200}, d => {
 | 
			
		||||
 *     d.key2 = "value2"
 | 
			
		||||
 * })
 | 
			
		||||
 * ```
 | 
			
		||||
| 
						 | 
				
			
			@ -316,7 +278,7 @@ export function from<T extends Record<string, unknown>>(
 | 
			
		|||
 * let patchCallback = patch => {
 | 
			
		||||
 *    patchedPath = patch.path
 | 
			
		||||
 * }
 | 
			
		||||
 * doc1 = automerge.change(doc1, {message, "add another value", time: 1640995200, patchCallback}, d => {
 | 
			
		||||
 * doc1 = automerge.change(doc1, {message, "add another value", timestamp: 1640995200, patchCallback}, d => {
 | 
			
		||||
 *     d.key2 = "value2"
 | 
			
		||||
 * })
 | 
			
		||||
 * assert.equal(patchedPath, ["key2"])
 | 
			
		||||
| 
						 | 
				
			
			@ -380,7 +342,7 @@ function _change<T>(
 | 
			
		|||
  try {
 | 
			
		||||
    state.heads = heads
 | 
			
		||||
    const root: T = rootProxy(state.handle, state.textV2)
 | 
			
		||||
    callback(root as Extend<T>)
 | 
			
		||||
    callback(root)
 | 
			
		||||
    if (state.handle.pendingOps() === 0) {
 | 
			
		||||
      state.heads = undefined
 | 
			
		||||
      return doc
 | 
			
		||||
| 
						 | 
				
			
			@ -578,6 +540,62 @@ export function getActorId<T>(doc: Doc<T>): ActorId {
 | 
			
		|||
 */
 | 
			
		||||
type Conflicts = { [key: string]: AutomergeValue }
 | 
			
		||||
 | 
			
		||||
function conflictAt(
 | 
			
		||||
  context: Automerge,
 | 
			
		||||
  objectId: ObjID,
 | 
			
		||||
  prop: Prop,
 | 
			
		||||
  textV2: boolean
 | 
			
		||||
): Conflicts | undefined {
 | 
			
		||||
  const values = context.getAll(objectId, prop)
 | 
			
		||||
  if (values.length <= 1) {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
  const result: Conflicts = {}
 | 
			
		||||
  for (const fullVal of values) {
 | 
			
		||||
    switch (fullVal[0]) {
 | 
			
		||||
      case "map":
 | 
			
		||||
        result[fullVal[1]] = mapProxy(context, fullVal[1], textV2, [prop], true)
 | 
			
		||||
        break
 | 
			
		||||
      case "list":
 | 
			
		||||
        result[fullVal[1]] = listProxy(
 | 
			
		||||
          context,
 | 
			
		||||
          fullVal[1],
 | 
			
		||||
          textV2,
 | 
			
		||||
          [prop],
 | 
			
		||||
          true
 | 
			
		||||
        )
 | 
			
		||||
        break
 | 
			
		||||
      case "text":
 | 
			
		||||
        if (textV2) {
 | 
			
		||||
          result[fullVal[1]] = context.text(fullVal[1])
 | 
			
		||||
        } else {
 | 
			
		||||
          result[fullVal[1]] = textProxy(context, objectId, [prop], true)
 | 
			
		||||
        }
 | 
			
		||||
        break
 | 
			
		||||
      //case "table":
 | 
			
		||||
      //case "cursor":
 | 
			
		||||
      case "str":
 | 
			
		||||
      case "uint":
 | 
			
		||||
      case "int":
 | 
			
		||||
      case "f64":
 | 
			
		||||
      case "boolean":
 | 
			
		||||
      case "bytes":
 | 
			
		||||
      case "null":
 | 
			
		||||
        result[fullVal[2]] = fullVal[1]
 | 
			
		||||
        break
 | 
			
		||||
      case "counter":
 | 
			
		||||
        result[fullVal[2]] = new Counter(fullVal[1])
 | 
			
		||||
        break
 | 
			
		||||
      case "timestamp":
 | 
			
		||||
        result[fullVal[2]] = new Date(fullVal[1])
 | 
			
		||||
        break
 | 
			
		||||
      default:
 | 
			
		||||
        throw RangeError(`datatype ${fullVal[0]} unimplemented`)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the conflicts associated with a property
 | 
			
		||||
 *
 | 
			
		||||
| 
						 | 
				
			
			@ -627,12 +645,9 @@ export function getConflicts<T>(
 | 
			
		|||
  prop: Prop
 | 
			
		||||
): Conflicts | undefined {
 | 
			
		||||
  const state = _state(doc, false)
 | 
			
		||||
  if (state.textV2) {
 | 
			
		||||
    throw new Error("use unstable.getConflicts for an unstable document")
 | 
			
		||||
  }
 | 
			
		||||
  const objectId = _obj(doc)
 | 
			
		||||
  if (objectId != null) {
 | 
			
		||||
    return stableConflictAt(state.handle, objectId, prop)
 | 
			
		||||
    return conflictAt(state.handle, objectId, prop, state.textV2)
 | 
			
		||||
  } else {
 | 
			
		||||
    return undefined
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -656,7 +671,6 @@ export function getLastLocalChange<T>(doc: Doc<T>): Change | undefined {
 | 
			
		|||
 * This is useful to determine if something is actually an automerge document,
 | 
			
		||||
 * if `doc` is not an automerge document this will return null.
 | 
			
		||||
 */
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
export function getObjectId(doc: any, prop?: Prop): ObjID | null {
 | 
			
		||||
  if (prop) {
 | 
			
		||||
    const state = _state(doc, false)
 | 
			
		||||
| 
						 | 
				
			
			@ -783,7 +797,7 @@ export function decodeSyncState(state: Uint8Array): SyncState {
 | 
			
		|||
  const sync = ApiHandler.decodeSyncState(state)
 | 
			
		||||
  const result = ApiHandler.exportSyncState(sync)
 | 
			
		||||
  sync.free()
 | 
			
		||||
  return result as SyncState
 | 
			
		||||
  return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -804,7 +818,7 @@ export function generateSyncMessage<T>(
 | 
			
		|||
  const state = _state(doc)
 | 
			
		||||
  const syncState = ApiHandler.importSyncState(inState)
 | 
			
		||||
  const message = state.handle.generateSyncMessage(syncState)
 | 
			
		||||
  const outState = ApiHandler.exportSyncState(syncState) as SyncState
 | 
			
		||||
  const outState = ApiHandler.exportSyncState(syncState)
 | 
			
		||||
  return [outState, message]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -846,7 +860,7 @@ export function receiveSyncMessage<T>(
 | 
			
		|||
  }
 | 
			
		||||
  const heads = state.handle.getHeads()
 | 
			
		||||
  state.handle.receiveSyncMessage(syncState, message)
 | 
			
		||||
  const outSyncState = ApiHandler.exportSyncState(syncState) as SyncState
 | 
			
		||||
  const outSyncState = ApiHandler.exportSyncState(syncState)
 | 
			
		||||
  return [
 | 
			
		||||
    progressDocument(doc, heads, opts.patchCallback || state.patchCallback),
 | 
			
		||||
    outSyncState,
 | 
			
		||||
| 
						 | 
				
			
			@ -863,7 +877,7 @@ export function receiveSyncMessage<T>(
 | 
			
		|||
 * @group sync
 | 
			
		||||
 */
 | 
			
		||||
export function initSyncState(): SyncState {
 | 
			
		||||
  return ApiHandler.exportSyncState(ApiHandler.initSyncState()) as SyncState
 | 
			
		||||
  return ApiHandler.exportSyncState(ApiHandler.initSyncState())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** @hidden */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,12 +3,9 @@ import { TEXT, STATE } from "./constants"
 | 
			
		|||
import type { InternalState } from "./internal_state"
 | 
			
		||||
 | 
			
		||||
export class Text {
 | 
			
		||||
  //eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
  elems: Array<any>
 | 
			
		||||
  str: string | undefined
 | 
			
		||||
  //eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
  spans: Array<any> | undefined;
 | 
			
		||||
  //eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
  [STATE]?: InternalState<any>
 | 
			
		||||
 | 
			
		||||
  constructor(text?: string | string[] | Value[]) {
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +25,6 @@ export class Text {
 | 
			
		|||
    return this.elems.length
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
  get(index: number): any {
 | 
			
		||||
    return this.elems[index]
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -77,7 +73,7 @@ export class Text {
 | 
			
		|||
   * For example, the value `['a', 'b', {x: 3}, 'c', 'd']` has spans:
 | 
			
		||||
   * `=> ['ab', {x: 3}, 'cd']`
 | 
			
		||||
   */
 | 
			
		||||
  toSpans(): Array<Value | object> {
 | 
			
		||||
  toSpans(): Array<Value | Object> {
 | 
			
		||||
    if (!this.spans) {
 | 
			
		||||
      this.spans = []
 | 
			
		||||
      let chars = ""
 | 
			
		||||
| 
						 | 
				
			
			@ -122,7 +118,7 @@ export class Text {
 | 
			
		|||
  /**
 | 
			
		||||
   * Inserts new list items `values` starting at position `index`.
 | 
			
		||||
   */
 | 
			
		||||
  insertAt(index: number, ...values: Array<Value | object>) {
 | 
			
		||||
  insertAt(index: number, ...values: Array<Value | Object>) {
 | 
			
		||||
    if (this[STATE]) {
 | 
			
		||||
      throw new RangeError(
 | 
			
		||||
        "object cannot be modified outside of a change block"
 | 
			
		||||
| 
						 | 
				
			
			@ -144,7 +140,7 @@ export class Text {
 | 
			
		|||
    this.elems.splice(index, numDelete)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  map<T>(callback: (e: Value | object) => T) {
 | 
			
		||||
  map<T>(callback: (e: Value | Object) => T) {
 | 
			
		||||
    this.elems.map(callback)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,4 @@
 | 
			
		|||
export { Text } from "./text"
 | 
			
		||||
import { Text } from "./text"
 | 
			
		||||
export { Counter } from "./counter"
 | 
			
		||||
export { Int, Uint, Float64 } from "./numbers"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -11,9 +10,9 @@ export type AutomergeValue =
 | 
			
		|||
  | ScalarValue
 | 
			
		||||
  | { [key: string]: AutomergeValue }
 | 
			
		||||
  | Array<AutomergeValue>
 | 
			
		||||
  | Text
 | 
			
		||||
export type MapValue = { [key: string]: AutomergeValue }
 | 
			
		||||
export type ListValue = Array<AutomergeValue>
 | 
			
		||||
export type TextValue = Array<AutomergeValue>
 | 
			
		||||
export type ScalarValue =
 | 
			
		||||
  | string
 | 
			
		||||
  | number
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,9 +22,9 @@
 | 
			
		|||
 * This leads to the following differences from `stable`:
 | 
			
		||||
 *
 | 
			
		||||
 * * There is no `unstable.Text` class, all strings are text objects
 | 
			
		||||
 * * Reading strings in an `unstable` document is the same as reading any other
 | 
			
		||||
 * * Reading strings in a `future` document is the same as reading any other
 | 
			
		||||
 *   javascript string
 | 
			
		||||
 * * To modify strings in an `unstable` document use {@link splice}
 | 
			
		||||
 * * To modify strings in a `future` document use {@link splice}
 | 
			
		||||
 * * The {@link AutomergeValue} type does not include the {@link Text}
 | 
			
		||||
 *   class but the  {@link RawString} class is included in the {@link ScalarValue}
 | 
			
		||||
 *   type
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +35,7 @@
 | 
			
		|||
 *
 | 
			
		||||
 * @module
 | 
			
		||||
 */
 | 
			
		||||
import { Counter } from "./types"
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  Counter,
 | 
			
		||||
| 
						 | 
				
			
			@ -44,20 +45,32 @@ export {
 | 
			
		|||
  Float64,
 | 
			
		||||
  type Patch,
 | 
			
		||||
  type PatchCallback,
 | 
			
		||||
  type AutomergeValue,
 | 
			
		||||
  type ScalarValue,
 | 
			
		||||
} from "./unstable_types"
 | 
			
		||||
} from "./types"
 | 
			
		||||
 | 
			
		||||
import type { PatchCallback } from "./stable"
 | 
			
		||||
 | 
			
		||||
import { type UnstableConflicts as Conflicts } from "./conflicts"
 | 
			
		||||
import { unstableConflictAt } from "./conflicts"
 | 
			
		||||
export type AutomergeValue =
 | 
			
		||||
  | ScalarValue
 | 
			
		||||
  | { [key: string]: AutomergeValue }
 | 
			
		||||
  | Array<AutomergeValue>
 | 
			
		||||
export type MapValue = { [key: string]: AutomergeValue }
 | 
			
		||||
export type ListValue = Array<AutomergeValue>
 | 
			
		||||
export type ScalarValue =
 | 
			
		||||
  | string
 | 
			
		||||
  | number
 | 
			
		||||
  | null
 | 
			
		||||
  | boolean
 | 
			
		||||
  | Date
 | 
			
		||||
  | Counter
 | 
			
		||||
  | Uint8Array
 | 
			
		||||
  | RawString
 | 
			
		||||
 | 
			
		||||
export type Conflicts = { [key: string]: AutomergeValue }
 | 
			
		||||
 | 
			
		||||
export type {
 | 
			
		||||
  PutPatch,
 | 
			
		||||
  DelPatch,
 | 
			
		||||
  SpliceTextPatch,
 | 
			
		||||
  InsertPatch,
 | 
			
		||||
  SplicePatch,
 | 
			
		||||
  IncPatch,
 | 
			
		||||
  SyncMessage,
 | 
			
		||||
} from "@automerge/automerge-wasm"
 | 
			
		||||
| 
						 | 
				
			
			@ -111,6 +124,7 @@ export { RawString } from "./raw_string"
 | 
			
		|||
export const getBackend = stable.getBackend
 | 
			
		||||
 | 
			
		||||
import { _is_proxy, _state, _obj } from "./internal_state"
 | 
			
		||||
import { RawString } from "./raw_string"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Create a new automerge document
 | 
			
		||||
| 
						 | 
				
			
			@ -122,7 +136,7 @@ import { _is_proxy, _state, _obj } from "./internal_state"
 | 
			
		|||
 *     random actor ID
 | 
			
		||||
 */
 | 
			
		||||
export function init<T>(_opts?: ActorId | InitOptions<T>): Doc<T> {
 | 
			
		||||
  const opts = importOpts(_opts)
 | 
			
		||||
  let opts = importOpts(_opts)
 | 
			
		||||
  opts.enableTextV2 = true
 | 
			
		||||
  return stable.init(opts)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -146,7 +160,7 @@ export function clone<T>(
 | 
			
		|||
  doc: Doc<T>,
 | 
			
		||||
  _opts?: ActorId | InitOptions<T>
 | 
			
		||||
): Doc<T> {
 | 
			
		||||
  const opts = importOpts(_opts)
 | 
			
		||||
  let opts = importOpts(_opts)
 | 
			
		||||
  opts.enableTextV2 = true
 | 
			
		||||
  return stable.clone(doc, opts)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -281,14 +295,6 @@ export function getConflicts<T>(
 | 
			
		|||
  doc: Doc<T>,
 | 
			
		||||
  prop: stable.Prop
 | 
			
		||||
): Conflicts | undefined {
 | 
			
		||||
  const state = _state(doc, false)
 | 
			
		||||
  if (!state.textV2) {
 | 
			
		||||
    throw new Error("use getConflicts for a stable document")
 | 
			
		||||
  }
 | 
			
		||||
  const objectId = _obj(doc)
 | 
			
		||||
  if (objectId != null) {
 | 
			
		||||
    return unstableConflictAt(state.handle, objectId, prop)
 | 
			
		||||
  } else {
 | 
			
		||||
    return undefined
 | 
			
		||||
  }
 | 
			
		||||
  // this function only exists to get the types to line up with future.AutomergeValue
 | 
			
		||||
  return stable.getConflicts(doc, prop)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,30 +0,0 @@
 | 
			
		|||
import { Counter } from "./types"
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  Counter,
 | 
			
		||||
  type Doc,
 | 
			
		||||
  Int,
 | 
			
		||||
  Uint,
 | 
			
		||||
  Float64,
 | 
			
		||||
  type Patch,
 | 
			
		||||
  type PatchCallback,
 | 
			
		||||
} from "./types"
 | 
			
		||||
 | 
			
		||||
import { RawString } from "./raw_string"
 | 
			
		||||
export { RawString } from "./raw_string"
 | 
			
		||||
 | 
			
		||||
export type AutomergeValue =
 | 
			
		||||
  | ScalarValue
 | 
			
		||||
  | { [key: string]: AutomergeValue }
 | 
			
		||||
  | Array<AutomergeValue>
 | 
			
		||||
export type MapValue = { [key: string]: AutomergeValue }
 | 
			
		||||
export type ListValue = Array<AutomergeValue>
 | 
			
		||||
export type ScalarValue =
 | 
			
		||||
  | string
 | 
			
		||||
  | number
 | 
			
		||||
  | null
 | 
			
		||||
  | boolean
 | 
			
		||||
  | Date
 | 
			
		||||
  | Counter
 | 
			
		||||
  | Uint8Array
 | 
			
		||||
  | RawString
 | 
			
		||||
| 
						 | 
				
			
			@ -58,22 +58,6 @@ describe("Automerge", () => {
 | 
			
		|||
      })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    it("should be able to insert and delete a large number of properties", () => {
 | 
			
		||||
      let doc = Automerge.init<any>()
 | 
			
		||||
 | 
			
		||||
      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))
 | 
			
		||||
| 
						 | 
				
			
			@ -283,6 +267,7 @@ describe("Automerge", () => {
 | 
			
		|||
      })
 | 
			
		||||
      assert.deepEqual(doc5, { list: [2, 1, 9, 10, 3, 11, 12] })
 | 
			
		||||
      let doc6 = Automerge.change(doc5, d => {
 | 
			
		||||
        // @ts-ignore
 | 
			
		||||
        d.list.insertAt(3, 100, 101)
 | 
			
		||||
      })
 | 
			
		||||
      assert.deepEqual(doc6, { list: [2, 1, 9, 100, 101, 10, 3, 11, 12] })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -461,12 +461,12 @@ describe("Automerge", () => {
 | 
			
		|||
        s1 = Automerge.change(s1, "set foo", doc => {
 | 
			
		||||
          doc.foo = "bar"
 | 
			
		||||
        })
 | 
			
		||||
        let deleted: any
 | 
			
		||||
        let deleted
 | 
			
		||||
        s1 = Automerge.change(s1, "del foo", doc => {
 | 
			
		||||
          deleted = delete doc.foo
 | 
			
		||||
        })
 | 
			
		||||
        assert.strictEqual(deleted, true)
 | 
			
		||||
        let deleted2: any
 | 
			
		||||
        let deleted2
 | 
			
		||||
        assert.doesNotThrow(() => {
 | 
			
		||||
          s1 = Automerge.change(s1, "del baz", doc => {
 | 
			
		||||
            deleted2 = delete doc.baz
 | 
			
		||||
| 
						 | 
				
			
			@ -515,7 +515,7 @@ describe("Automerge", () => {
 | 
			
		|||
        s1 = Automerge.change(s1, doc => {
 | 
			
		||||
          doc.nested = {}
 | 
			
		||||
        })
 | 
			
		||||
        Automerge.getObjectId(s1.nested)
 | 
			
		||||
        let id = Automerge.getObjectId(s1.nested)
 | 
			
		||||
        assert.strictEqual(
 | 
			
		||||
          OPID_PATTERN.test(Automerge.getObjectId(s1.nested)!),
 | 
			
		||||
          true
 | 
			
		||||
| 
						 | 
				
			
			@ -975,7 +975,6 @@ describe("Automerge", () => {
 | 
			
		|||
      it("should allow adding and removing list elements in the same change callback", () => {
 | 
			
		||||
        let s1 = Automerge.change(
 | 
			
		||||
          Automerge.init<{ noodles: Array<string> }>(),
 | 
			
		||||
          // @ts-ignore
 | 
			
		||||
          doc => (doc.noodles = [])
 | 
			
		||||
        )
 | 
			
		||||
        s1 = Automerge.change(s1, doc => {
 | 
			
		||||
| 
						 | 
				
			
			@ -1849,8 +1848,9 @@ describe("Automerge", () => {
 | 
			
		|||
      })
 | 
			
		||||
      assert.deepStrictEqual(patches, [
 | 
			
		||||
        { action: "put", path: ["birds"], value: [] },
 | 
			
		||||
        { action: "insert", path: ["birds", 0], values: ["", ""] },
 | 
			
		||||
        { action: "insert", path: ["birds", 0], values: [""] },
 | 
			
		||||
        { action: "splice", path: ["birds", 0, 0], value: "Goldfinch" },
 | 
			
		||||
        { action: "insert", path: ["birds", 1], values: [""] },
 | 
			
		||||
        { action: "splice", path: ["birds", 1, 0], value: "Chaffinch" },
 | 
			
		||||
      ])
 | 
			
		||||
    })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,62 +38,4 @@ describe("stable/unstable interop", () => {
 | 
			
		|||
    stableDoc = unstable.merge(stableDoc, unstableDoc)
 | 
			
		||||
    assert.deepStrictEqual(stableDoc.text, "abc")
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it("should show conflicts on text objects", () => {
 | 
			
		||||
    let doc1 = stable.from({ text: new stable.Text("abc") }, "bb")
 | 
			
		||||
    let doc2 = stable.from({ text: new stable.Text("def") }, "aa")
 | 
			
		||||
    doc1 = stable.merge(doc1, doc2)
 | 
			
		||||
    let conflicts = stable.getConflicts(doc1, "text")!
 | 
			
		||||
    assert.equal(conflicts["1@bb"]!.toString(), "abc")
 | 
			
		||||
    assert.equal(conflicts["1@aa"]!.toString(), "def")
 | 
			
		||||
 | 
			
		||||
    let unstableDoc = unstable.init<any>()
 | 
			
		||||
    unstableDoc = unstable.merge(unstableDoc, doc1)
 | 
			
		||||
    let conflicts2 = unstable.getConflicts(unstableDoc, "text")!
 | 
			
		||||
    assert.equal(conflicts2["1@bb"]!.toString(), "abc")
 | 
			
		||||
    assert.equal(conflicts2["1@aa"]!.toString(), "def")
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it("should allow filling a list with text in stable", () => {
 | 
			
		||||
    let doc = stable.from<{ list: Array<stable.Text | null> }>({
 | 
			
		||||
      list: [null, null, null],
 | 
			
		||||
    })
 | 
			
		||||
    doc = stable.change(doc, doc => {
 | 
			
		||||
      doc.list.fill(new stable.Text("abc"), 0, 3)
 | 
			
		||||
    })
 | 
			
		||||
    assert.deepStrictEqual(doc.list, [
 | 
			
		||||
      new stable.Text("abc"),
 | 
			
		||||
      new stable.Text("abc"),
 | 
			
		||||
      new stable.Text("abc"),
 | 
			
		||||
    ])
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it("should allow filling a list with text in unstable", () => {
 | 
			
		||||
    let doc = unstable.from<{ list: Array<string | null> }>({
 | 
			
		||||
      list: [null, null, null],
 | 
			
		||||
    })
 | 
			
		||||
    doc = stable.change(doc, doc => {
 | 
			
		||||
      doc.list.fill("abc", 0, 3)
 | 
			
		||||
    })
 | 
			
		||||
    assert.deepStrictEqual(doc.list, ["abc", "abc", "abc"])
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it("should allow splicing text into a list on stable", () => {
 | 
			
		||||
    let doc = stable.from<{ list: Array<stable.Text> }>({ list: [] })
 | 
			
		||||
    doc = stable.change(doc, doc => {
 | 
			
		||||
      doc.list.splice(0, 0, new stable.Text("abc"), new stable.Text("def"))
 | 
			
		||||
    })
 | 
			
		||||
    assert.deepStrictEqual(doc.list, [
 | 
			
		||||
      new stable.Text("abc"),
 | 
			
		||||
      new stable.Text("def"),
 | 
			
		||||
    ])
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it("should allow splicing text into a list on unstable", () => {
 | 
			
		||||
    let doc = unstable.from<{ list: Array<string> }>({ list: [] })
 | 
			
		||||
    doc = unstable.change(doc, doc => {
 | 
			
		||||
      doc.list.splice(0, 0, "abc", "def")
 | 
			
		||||
    })
 | 
			
		||||
    assert.deepStrictEqual(doc.list, ["abc", "def"])
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,8 +10,13 @@ members = [
 | 
			
		|||
resolver = "2"
 | 
			
		||||
 | 
			
		||||
[profile.release]
 | 
			
		||||
debug = true
 | 
			
		||||
lto = true
 | 
			
		||||
codegen-units = 1
 | 
			
		||||
opt-level = 3
 | 
			
		||||
 | 
			
		||||
[profile.bench]
 | 
			
		||||
debug = true
 | 
			
		||||
debug = true
 | 
			
		||||
 | 
			
		||||
[profile.release.package.automerge-wasm]
 | 
			
		||||
debug = false
 | 
			
		||||
opt-level = 3
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,250 +0,0 @@
 | 
			
		|||
---
 | 
			
		||||
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:           '^<ext/.*\.h>'
 | 
			
		||||
    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
 | 
			
		||||
...
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								rust/automerge-c/.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								rust/automerge-c/.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -1,10 +1,10 @@
 | 
			
		|||
automerge
 | 
			
		||||
automerge.h
 | 
			
		||||
automerge.o
 | 
			
		||||
build/
 | 
			
		||||
CMakeCache.txt
 | 
			
		||||
*.cmake
 | 
			
		||||
CMakeFiles
 | 
			
		||||
CMakePresets.json
 | 
			
		||||
Makefile
 | 
			
		||||
DartConfiguration.tcl
 | 
			
		||||
out/
 | 
			
		||||
config.h
 | 
			
		||||
CMakeCache.txt
 | 
			
		||||
Cargo
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,297 +1,97 @@
 | 
			
		|||
cmake_minimum_required(VERSION 3.23 FATAL_ERROR)
 | 
			
		||||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
 | 
			
		||||
 | 
			
		||||
project(automerge-c VERSION 0.1.0
 | 
			
		||||
                    LANGUAGES C
 | 
			
		||||
                    DESCRIPTION "C bindings for the Automerge Rust library.")
 | 
			
		||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
 | 
			
		||||
 | 
			
		||||
set(LIBRARY_NAME "automerge")
 | 
			
		||||
# Parse the library name, project name and project version out of Cargo's TOML file.
 | 
			
		||||
set(CARGO_LIB_SECTION OFF)
 | 
			
		||||
 | 
			
		||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
 | 
			
		||||
set(LIBRARY_NAME "")
 | 
			
		||||
 | 
			
		||||
option(BUILD_SHARED_LIBS "Enable the choice of a shared or static library.")
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
option(BUILD_SHARED_LIBS "Enable the choice of a shared or static library.")
 | 
			
		||||
 | 
			
		||||
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_BINARY_DIR}/Cargo/target")
 | 
			
		||||
set(CARGO_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/Cargo/target")
 | 
			
		||||
 | 
			
		||||
set(CBINDGEN_INCLUDEDIR "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_INCLUDEDIR}")
 | 
			
		||||
set(CBINDGEN_INCLUDEDIR "${CARGO_TARGET_DIR}/${CMAKE_INSTALL_INCLUDEDIR}")
 | 
			
		||||
 | 
			
		||||
set(CBINDGEN_TARGET_DIR "${CBINDGEN_INCLUDEDIR}/${PROJECT_NAME}")
 | 
			
		||||
 | 
			
		||||
find_program (
 | 
			
		||||
    CARGO_CMD
 | 
			
		||||
    "cargo"
 | 
			
		||||
    PATHS "$ENV{CARGO_HOME}/bin"
 | 
			
		||||
    DOC "The Cargo command"
 | 
			
		||||
)
 | 
			
		||||
add_subdirectory(src)
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
# 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 --target=${CARGO_TARGET})
 | 
			
		||||
else()
 | 
			
		||||
    set(CARGO_BUILD_TYPE "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_TARGET}/${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} 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
 | 
			
		||||
    COMMAND
 | 
			
		||||
        # Compensate for cbindgen ignoring `std:mem::size_of<usize>()` 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 $<TARGET_PROPERTY:${BINDINGS_NAME},DEFINE_SYMBOL>)
 | 
			
		||||
 | 
			
		||||
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 "$<BUILD_INTERFACE:${CBINDGEN_INCLUDEDIR};${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}>"
 | 
			
		||||
           "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_dependencies(${LIBRARY_NAME} ${BINDINGS_NAME}_artifacts)
 | 
			
		||||
 | 
			
		||||
# Generate the configuration header.
 | 
			
		||||
# Generate and install 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
 | 
			
		||||
    ${CBINDGEN_TARGET_DIR}/config.h
 | 
			
		||||
    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(
 | 
			
		||||
    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 $<TARGET_PROPERTY:${BINDINGS_NAME},IMPORTED_LOCATION>
 | 
			
		||||
    DESTINATION ${CMAKE_INSTALL_LIBDIR}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
install(EXPORT ${PROJECT_NAME}-config
 | 
			
		||||
        FILE ${PROJECT_NAME}-config.cmake
 | 
			
		||||
        NAMESPACE "${PROJECT_NAME}::"
 | 
			
		||||
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${LIB}
 | 
			
		||||
    FILES ${CMAKE_BINARY_DIR}/config.h
 | 
			
		||||
    DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if(BUILD_TESTING)
 | 
			
		||||
| 
						 | 
				
			
			@ -300,6 +100,42 @@ 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}
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,8 +7,8 @@ license = "MIT"
 | 
			
		|||
rust-version = "1.57.0"
 | 
			
		||||
 | 
			
		||||
[lib]
 | 
			
		||||
name = "automerge_core"
 | 
			
		||||
crate-type = ["staticlib"]
 | 
			
		||||
name = "automerge"
 | 
			
		||||
crate-type = ["cdylib", "staticlib"]
 | 
			
		||||
bench = false
 | 
			
		||||
doc = false
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,29 +1,22 @@
 | 
			
		|||
# Overview
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
# Building
 | 
			
		||||
 | 
			
		||||
# Installing
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
It will output two files:
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
- ./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`
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
| 
						 | 
				
			
			@ -32,176 +25,134 @@ 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, 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.
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
# Usage
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
For full reference, read through `automerge.h`, or 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 Automerge document
 | 
			
		||||
(`AMdoc` struct) which is structurally similar to a JSON document.
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
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 <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <automerge-c/automerge.h>
 | 
			
		||||
#include <automerge-c/utils/string.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
int main(int argc, char** argv) {
 | 
			
		||||
  AMresult *docResult = AMcreate(NULL);
 | 
			
		||||
 | 
			
		||||
  if (AMresultStatus(docResult) != AM_STATUS_OK) {
 | 
			
		||||
    char* const err_msg = AMstrdup(AMresultError(docResult), NULL);
 | 
			
		||||
    printf("failed to create doc: %s", err_msg);
 | 
			
		||||
    free(err_msg);
 | 
			
		||||
    printf("failed to create doc: %s", AMerrorMessage(docResult).src);
 | 
			
		||||
    goto cleanup;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  AMdoc *doc;
 | 
			
		||||
  AMitemToDoc(AMresultItem(docResult), &doc);
 | 
			
		||||
  AMdoc *doc = AMresultValue(docResult).doc;
 | 
			
		||||
 | 
			
		||||
  // useful code goes here!
 | 
			
		||||
 | 
			
		||||
cleanup:
 | 
			
		||||
  AMresultFree(docResult);
 | 
			
		||||
  AMfree(docResult);
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
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 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 wrapping automerge-c in another language, particularly one that has a
 | 
			
		||||
garbage collector, you can call the `AMresultFree()` function within a finalizer
 | 
			
		||||
to ensure that memory is reclaimed when it is no longer needed.
 | 
			
		||||
garbage collector, you can call `AMfree` within a finalizer to ensure that memory
 | 
			
		||||
is reclaimed when it is no longer needed.
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
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:
 | 
			
		||||
 | 
			
		||||
- A number of type double / int64_t / uint64_t
 | 
			
		||||
- 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.
 | 
			
		||||
- 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)
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
Under the hood, automerge references a mutable object by its object identifier
 | 
			
		||||
where `AM_ROOT` signifies a document's root map object.
 | 
			
		||||
Under the hood, automerge references mutable objects by the internal object id,
 | 
			
		||||
and `AM_ROOT` is always the object id of the root value.
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
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
 | 
			
		||||
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 `AMitem` struct that you can inspect to
 | 
			
		||||
determine the type of value that it contains.
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
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 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.
 | 
			
		||||
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`.
 | 
			
		||||
 | 
			
		||||
Putting all of that together, to read and write from the root of the document
 | 
			
		||||
you can do this:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <automerge-c/automerge.h>
 | 
			
		||||
#include <automerge-c/utils/string.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
int main(int argc, char** argv) {
 | 
			
		||||
  // ...previous example...
 | 
			
		||||
  AMdoc *doc; 
 | 
			
		||||
  AMitemToDoc(AMresultItem(docResult), &doc);
 | 
			
		||||
  AMdoc *doc = AMresultValue(docResult).doc;
 | 
			
		||||
 | 
			
		||||
  AMresult *putResult = AMmapPutStr(doc, AM_ROOT, AMstr("key"), AMstr("value"));
 | 
			
		||||
  if (AMresultStatus(putResult) != AM_STATUS_OK) {
 | 
			
		||||
    char* const err_msg = AMstrdup(AMresultError(putResult), NULL);
 | 
			
		||||
    printf("failed to put: %s", err_msg);
 | 
			
		||||
    free(err_msg);
 | 
			
		||||
    printf("failed to put: %s", AMerrorMessage(putResult).src);
 | 
			
		||||
    goto cleanup;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  AMresult *getResult = AMmapGet(doc, AM_ROOT, AMstr("key"), NULL);
 | 
			
		||||
  if (AMresultStatus(getResult) != AM_STATUS_OK) {
 | 
			
		||||
    char* const err_msg = AMstrdup(AMresultError(putResult), NULL);
 | 
			
		||||
    printf("failed to get: %s", err_msg);
 | 
			
		||||
    free(err_msg);
 | 
			
		||||
    printf("failed to get: %s", AMerrorMessage(getResult).src);
 | 
			
		||||
    goto cleanup;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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 {
 | 
			
		||||
  AMvalue got = AMresultValue(getResult);
 | 
			
		||||
  if (got.tag != AM_VALUE_STR) {
 | 
			
		||||
    printf("expected to read a string!");
 | 
			
		||||
    goto cleanup;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  printf("Got %zu-character string `%s`", got.str.count, got.str.src);
 | 
			
		||||
 | 
			
		||||
cleanup:
 | 
			
		||||
  AMresultFree(getResult);
 | 
			
		||||
  AMresultFree(putResult);
 | 
			
		||||
  AMresultFree(docResult);
 | 
			
		||||
  AMfree(getResult);
 | 
			
		||||
  AMfree(putResult);
 | 
			
		||||
  AMfree(docResult);
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
Beyond that, good luck!
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
after_includes = """\n
 | 
			
		||||
/**
 | 
			
		||||
 * \\defgroup enumerations Public Enumerations
 | 
			
		||||
 *  Symbolic names for integer constants.
 | 
			
		||||
     Symbolic names for integer constants.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -12,23 +12,21 @@ after_includes = """\n
 | 
			
		|||
#define AM_ROOT NULL
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \\memberof AMdoc
 | 
			
		||||
 * \\memberof AMchangeHash
 | 
			
		||||
 * \\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.
 | 
			
		||||
 */
 | 
			
		||||
"""
 | 
			
		||||
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
 | 
			
		||||
documentation = true
 | 
			
		||||
documentation_style = "doxy"
 | 
			
		||||
include_guard = "AUTOMERGE_C_H"
 | 
			
		||||
header = """
 | 
			
		||||
/** \\file
 | 
			
		||||
 * All constants, functions and types in the Automerge library's C API.
 | 
			
		||||
 */
 | 
			
		||||
 """
 | 
			
		||||
include_guard = "AUTOMERGE_H"
 | 
			
		||||
includes = []
 | 
			
		||||
language = "C"
 | 
			
		||||
line_length = 140
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,22 +0,0 @@
 | 
			
		|||
[package]
 | 
			
		||||
name = "@PROJECT_NAME@"
 | 
			
		||||
version = "@PROJECT_VERSION@"
 | 
			
		||||
authors = ["Orion Henry <orion.henry@gmail.com>", "Jason Kankiewicz <jason.kankiewicz@gmail.com>"]
 | 
			
		||||
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"
 | 
			
		||||
| 
						 | 
				
			
			@ -1,48 +0,0 @@
 | 
			
		|||
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"]
 | 
			
		||||
| 
						 | 
				
			
			@ -1,35 +1,14 @@
 | 
			
		|||
#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.
 | 
			
		||||
 */
 | 
			
		||||
#ifndef @SYMBOL_PREFIX@_CONFIG_H
 | 
			
		||||
#define @SYMBOL_PREFIX@_CONFIG_H
 | 
			
		||||
 | 
			
		||||
/* This header 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 /* @INCLUDE_GUARD_PREFIX@_CONFIG_H */
 | 
			
		||||
#endif  /* @SYMBOL_PREFIX@_CONFIG_H */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,183 +0,0 @@
 | 
			
		|||
# 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 <stdbool.h>\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()
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,4 @@
 | 
			
		|||
# This CMake script is used to perform string substitutions within a generated
 | 
			
		||||
# file.
 | 
			
		||||
cmake_minimum_required(VERSION 3.23 FATAL_ERROR)
 | 
			
		||||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
 | 
			
		||||
 | 
			
		||||
if(NOT DEFINED MATCH_REGEX)
 | 
			
		||||
    message(FATAL_ERROR "Variable \"MATCH_REGEX\" is not defined.")
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,4 @@
 | 
			
		|||
# 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)
 | 
			
		||||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
 | 
			
		||||
 | 
			
		||||
if(NOT DEFINED CONDITION)
 | 
			
		||||
    message(FATAL_ERROR "Variable \"CONDITION\" is not defined.")
 | 
			
		||||
| 
						 | 
				
			
			@ -1,35 +0,0 @@
 | 
			
		|||
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()
 | 
			
		||||
| 
						 | 
				
			
			@ -1,39 +1,41 @@
 | 
			
		|||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
 | 
			
		||||
 | 
			
		||||
add_executable(
 | 
			
		||||
    ${LIBRARY_NAME}_quickstart
 | 
			
		||||
    example_quickstart
 | 
			
		||||
        quickstart.c
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
set_target_properties(${LIBRARY_NAME}_quickstart PROPERTIES LINKER_LANGUAGE C)
 | 
			
		||||
set_target_properties(example_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(
 | 
			
		||||
    ${LIBRARY_NAME}_quickstart
 | 
			
		||||
    example_quickstart
 | 
			
		||||
    PRIVATE "$<BUILD_INTERFACE:${CBINDGEN_INCLUDEDIR}>"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(${LIBRARY_NAME}_quickstart PRIVATE ${LIBRARY_NAME})
 | 
			
		||||
target_link_libraries(example_quickstart PRIVATE ${LIBRARY_NAME})
 | 
			
		||||
 | 
			
		||||
add_dependencies(${LIBRARY_NAME}_quickstart ${BINDINGS_NAME}_artifacts)
 | 
			
		||||
add_dependencies(example_quickstart ${LIBRARY_NAME}_artifacts)
 | 
			
		||||
 | 
			
		||||
if(BUILD_SHARED_LIBS AND WIN32)
 | 
			
		||||
    add_custom_command(
 | 
			
		||||
        TARGET ${LIBRARY_NAME}_quickstart
 | 
			
		||||
        TARGET example_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_BINARY_DIR}
 | 
			
		||||
                ${CMAKE_CURRENT_BINARY_DIR}
 | 
			
		||||
        COMMENT "Copying the DLL built by Cargo into the examples directory..."
 | 
			
		||||
        VERBATIM
 | 
			
		||||
    )
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
add_custom_command(
 | 
			
		||||
    TARGET ${LIBRARY_NAME}_quickstart
 | 
			
		||||
    TARGET example_quickstart
 | 
			
		||||
    POST_BUILD
 | 
			
		||||
    COMMAND
 | 
			
		||||
        ${LIBRARY_NAME}_quickstart
 | 
			
		||||
        example_quickstart
 | 
			
		||||
    COMMENT
 | 
			
		||||
        "Running the example quickstart..."
 | 
			
		||||
    VERBATIM
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 automerge_quickstart
 | 
			
		||||
cmake --build automerge-c/build --target example_quickstart
 | 
			
		||||
```
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,127 +3,152 @@
 | 
			
		|||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <automerge-c/automerge.h>
 | 
			
		||||
#include <automerge-c/utils/enum_string.h>
 | 
			
		||||
#include <automerge-c/utils/stack.h>
 | 
			
		||||
#include <automerge-c/utils/stack_callback_data.h>
 | 
			
		||||
#include <automerge-c/utils/string.h>
 | 
			
		||||
 | 
			
		||||
static bool abort_cb(AMstack**, void*);
 | 
			
		||||
static void abort_cb(AMresultStack**, uint8_t);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \brief Based on https://automerge.github.io/docs/quickstart
 | 
			
		||||
 */
 | 
			
		||||
int main(int argc, char** argv) {
 | 
			
		||||
    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));
 | 
			
		||||
    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));
 | 
			
		||||
 | 
			
		||||
    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));
 | 
			
		||||
    AMdoc* doc2 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, abort_cb).doc;
 | 
			
		||||
    AMfree(AMmerge(doc2, doc1));
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
    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(AMmapPutBool(doc1, card1, AMstr("done"), true));
 | 
			
		||||
    AMfree(AMcommit(doc1, AMstr("Mark card as done"), 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(AMlistDelete(doc2, cards, 0));
 | 
			
		||||
    AMfree(AMcommit(doc2, AMstr("Delete card"), NULL));
 | 
			
		||||
 | 
			
		||||
    AMstackItem(NULL, AMmerge(doc1, doc2), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH));
 | 
			
		||||
    AMfree(AMmerge(doc1, doc2));
 | 
			
		||||
 | 
			
		||||
    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));
 | 
			
		||||
    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));
 | 
			
		||||
        free(c_msg);
 | 
			
		||||
    }
 | 
			
		||||
    AMstackFree(&stack);
 | 
			
		||||
    AMfreeStack(&stack);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char const* discriminant_suffix(AMvalueVariant const);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \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.
 | 
			
		||||
 * \brief Prints an error message to `stderr`, deallocates all results in the
 | 
			
		||||
 *        given stack and exits.
 | 
			
		||||
 *
 | 
			
		||||
 * \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`.
 | 
			
		||||
 * \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`.
 | 
			
		||||
 */
 | 
			
		||||
static bool abort_cb(AMstack** stack, void* data) {
 | 
			
		||||
static void abort_cb(AMresultStack** stack, uint8_t discriminant) {
 | 
			
		||||
    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*`.\n", suffix);
 | 
			
		||||
        AMstackFree(stack);
 | 
			
		||||
        fprintf(stderr, "Null `AMresult%s*`.", suffix);
 | 
			
		||||
        AMfreeStack(stack);
 | 
			
		||||
        exit(EXIT_FAILURE);
 | 
			
		||||
        return false;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    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]) {
 | 
			
		||||
        char* const c_msg = AMstrdup(AMresultError((*stack)->result), NULL);
 | 
			
		||||
        fprintf(stderr, "%s; %s.\n", buffer, c_msg);
 | 
			
		||||
        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);
 | 
			
		||||
        free(c_msg);
 | 
			
		||||
        AMstackFree(stack);
 | 
			
		||||
        AMfreeStack(stack);
 | 
			
		||||
        exit(EXIT_FAILURE);
 | 
			
		||||
        return false;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    free(data);
 | 
			
		||||
    return true;
 | 
			
		||||
    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 = "...";
 | 
			
		||||
    }
 | 
			
		||||
    return suffix;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
		 Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB  | 
| 
						 | 
				
			
			@ -1,30 +0,0 @@
 | 
			
		|||
#ifndef AUTOMERGE_C_UTILS_RESULT_H
 | 
			
		||||
#define AUTOMERGE_C_UTILS_RESULT_H
 | 
			
		||||
/**
 | 
			
		||||
 * \file
 | 
			
		||||
 * \brief Utility functions for use with `AMresult` structs.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <stdarg.h>
 | 
			
		||||
 | 
			
		||||
#include <automerge-c/automerge.h>
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \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 */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,130 +0,0 @@
 | 
			
		|||
#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 <automerge-c/automerge.h>
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \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 */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,53 +0,0 @@
 | 
			
		|||
#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 <automerge-c/automerge.h>
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \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 */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,29 +0,0 @@
 | 
			
		|||
#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 <automerge-c/automerge.h>
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \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 */
 | 
			
		||||
							
								
								
									
										250
									
								
								rust/automerge-c/src/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								rust/automerge-c/src/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,250 @@
 | 
			
		|||
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<usize>()` 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_PROPERTY:${LIBRARY_NAME},DEFINE_SYMBOL>)
 | 
			
		||||
 | 
			
		||||
target_include_directories(
 | 
			
		||||
    ${LIBRARY_NAME}
 | 
			
		||||
    INTERFACE
 | 
			
		||||
        "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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 $<TARGET_PROPERTY:${LIBRARY_NAME},IMPORTED_IMPLIB>
 | 
			
		||||
    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 $<TARGET_PROPERTY:${LIBRARY_NAME},IMPORTED_LOCATION>
 | 
			
		||||
    RENAME "${LIBRARY_FILE_NAME}"
 | 
			
		||||
    DESTINATION ${LIBRARY_DESTINATION}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
install(
 | 
			
		||||
    FILES $<TARGET_PROPERTY:${LIBRARY_NAME},PUBLIC_HEADER>
 | 
			
		||||
    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()
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,4 @@
 | 
			
		|||
use automerge as am;
 | 
			
		||||
use libc::c_int;
 | 
			
		||||
use std::cell::RefCell;
 | 
			
		||||
use std::cmp::Ordering;
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +11,7 @@ macro_rules! to_actor_id {
 | 
			
		|||
        let handle = $handle.as_ref();
 | 
			
		||||
        match handle {
 | 
			
		||||
            Some(b) => b,
 | 
			
		||||
            None => return AMresult::error("Invalid `AMactorId*`").into(),
 | 
			
		||||
            None => return AMresult::err("Invalid AMactorId pointer").into(),
 | 
			
		||||
        }
 | 
			
		||||
    }};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -58,11 +57,11 @@ impl AsRef<am::ActorId> for AMactorId {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMactorId
 | 
			
		||||
/// \brief Gets the value of an actor identifier as an array of bytes.
 | 
			
		||||
/// \brief Gets the value of an actor identifier as a sequence of bytes.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] actor_id A pointer to an `AMactorId` struct.
 | 
			
		||||
/// \return An `AMbyteSpan` struct for an array of bytes.
 | 
			
		||||
/// \pre \p actor_id `!= NULL`
 | 
			
		||||
/// \pre \p actor_id `!= NULL`.
 | 
			
		||||
/// \return An `AMbyteSpan` struct.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -83,8 +82,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
 | 
			
		||||
| 
						 | 
				
			
			@ -94,7 +93,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,
 | 
			
		||||
) -> c_int {
 | 
			
		||||
) -> isize {
 | 
			
		||||
    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,
 | 
			
		||||
| 
						 | 
				
			
			@ -102,69 +101,65 @@ pub unsafe extern "C" fn AMactorIdCmp(
 | 
			
		|||
            Ordering::Greater => 1,
 | 
			
		||||
        },
 | 
			
		||||
        (None, Some(_)) => -1,
 | 
			
		||||
        (None, None) => 0,
 | 
			
		||||
        (Some(_), None) => 1,
 | 
			
		||||
        (None, None) => 0,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMactorId
 | 
			
		||||
/// \brief Allocates a new actor identifier and initializes it from a random
 | 
			
		||||
///        UUID value.
 | 
			
		||||
/// \brief Allocates a new actor identifier and initializes it with a random
 | 
			
		||||
///        UUID.
 | 
			
		||||
///
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub unsafe extern "C" fn AMactorIdInit() -> *mut AMresult {
 | 
			
		||||
    to_result(Ok::<am::ActorId, am::AutomergeError>(am::ActorId::random()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMactorId
 | 
			
		||||
/// \brief Allocates a new actor identifier and initializes it from an array of
 | 
			
		||||
///        bytes value.
 | 
			
		||||
/// \brief Allocates a new actor identifier and initializes it from a sequence
 | 
			
		||||
///        of bytes.
 | 
			
		||||
///
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
/// src must be a byte array of length `>= count`
 | 
			
		||||
/// src must be a byte array of size `>= count`
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
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, am::InvalidActorId>(am::ActorId::from(
 | 
			
		||||
            value,
 | 
			
		||||
        )))
 | 
			
		||||
    } else {
 | 
			
		||||
        AMresult::error("Invalid uint8_t*").into()
 | 
			
		||||
    }
 | 
			
		||||
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, am::InvalidActorId>(am::ActorId::from(
 | 
			
		||||
        slice,
 | 
			
		||||
    )))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMactorId
 | 
			
		||||
/// \brief Allocates a new actor identifier and initializes it from a
 | 
			
		||||
///        hexadecimal UTF-8 string view value.
 | 
			
		||||
///        hexadecimal string.
 | 
			
		||||
///
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
/// hex_str must be a valid pointer to an AMbyteSpan
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub unsafe extern "C" fn AMactorIdFromStr(value: AMbyteSpan) -> *mut AMresult {
 | 
			
		||||
pub unsafe extern "C" fn AMactorIdInitStr(hex_str: AMbyteSpan) -> *mut AMresult {
 | 
			
		||||
    use am::AutomergeError::InvalidActorId;
 | 
			
		||||
 | 
			
		||||
    to_result(match (&value).try_into() {
 | 
			
		||||
    to_result(match (&hex_str).try_into() {
 | 
			
		||||
        Ok(s) => match am::ActorId::from_str(s) {
 | 
			
		||||
            Ok(actor_id) => Ok(actor_id),
 | 
			
		||||
            Err(_) => Err(InvalidActorId(String::from(s))),
 | 
			
		||||
| 
						 | 
				
			
			@ -174,12 +169,11 @@ pub unsafe extern "C" fn AMactorIdFromStr(value: AMbyteSpan) -> *mut AMresult {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMactorId
 | 
			
		||||
/// \brief Gets the value of an actor identifier as a UTF-8 hexadecimal string
 | 
			
		||||
///        view.
 | 
			
		||||
/// \brief Gets the value of an actor identifier as a hexadecimal string.
 | 
			
		||||
///
 | 
			
		||||
/// \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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,17 +1,14 @@
 | 
			
		|||
use automerge as am;
 | 
			
		||||
use std::cmp::Ordering;
 | 
			
		||||
use libc::strlen;
 | 
			
		||||
use std::convert::TryFrom;
 | 
			
		||||
use std::os::raw::c_char;
 | 
			
		||||
 | 
			
		||||
use libc::{c_int, strlen};
 | 
			
		||||
use smol_str::SmolStr;
 | 
			
		||||
 | 
			
		||||
macro_rules! to_str {
 | 
			
		||||
    ($byte_span:expr) => {{
 | 
			
		||||
        let result: Result<&str, am::AutomergeError> = (&$byte_span).try_into();
 | 
			
		||||
    ($span:expr) => {{
 | 
			
		||||
        let result: Result<&str, am::AutomergeError> = (&$span).try_into();
 | 
			
		||||
        match result {
 | 
			
		||||
            Ok(s) => s,
 | 
			
		||||
            Err(e) => return AMresult::error(&e.to_string()).into(),
 | 
			
		||||
            Err(e) => return AMresult::err(&e.to_string()).into(),
 | 
			
		||||
        }
 | 
			
		||||
    }};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -20,17 +17,16 @@ pub(crate) use to_str;
 | 
			
		|||
 | 
			
		||||
/// \struct AMbyteSpan
 | 
			
		||||
/// \installed_headerfile
 | 
			
		||||
/// \brief A view onto an array of bytes.
 | 
			
		||||
/// \brief A view onto a contiguous sequence of bytes.
 | 
			
		||||
#[repr(C)]
 | 
			
		||||
pub struct AMbyteSpan {
 | 
			
		||||
    /// 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()`.
 | 
			
		||||
    /// A pointer to an array of bytes.
 | 
			
		||||
    /// \attention <b>NEVER CALL `free()` ON \p src!</b>
 | 
			
		||||
    /// \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.
 | 
			
		||||
    pub src: *const u8,
 | 
			
		||||
    /// The count of bytes in the array.
 | 
			
		||||
    /// The number of bytes in the array.
 | 
			
		||||
    pub count: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +52,9 @@ impl PartialEq for AMbyteSpan {
 | 
			
		|||
        } else if self.src == other.src {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        <&[u8]>::from(self) == <&[u8]>::from(other)
 | 
			
		||||
        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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -74,15 +72,10 @@ impl From<&am::ActorId> for AMbyteSpan {
 | 
			
		|||
 | 
			
		||||
impl From<&mut am::ActorId> for AMbyteSpan {
 | 
			
		||||
    fn from(actor: &mut am::ActorId) -> Self {
 | 
			
		||||
        actor.as_ref().into()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<&am::ChangeHash> for AMbyteSpan {
 | 
			
		||||
    fn from(change_hash: &am::ChangeHash) -> Self {
 | 
			
		||||
        let slice = actor.to_bytes();
 | 
			
		||||
        Self {
 | 
			
		||||
            src: change_hash.0.as_ptr(),
 | 
			
		||||
            count: change_hash.0.len(),
 | 
			
		||||
            src: slice.as_ptr(),
 | 
			
		||||
            count: slice.len(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -100,9 +93,12 @@ impl From<*const c_char> for AMbyteSpan {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<&SmolStr> for AMbyteSpan {
 | 
			
		||||
    fn from(smol_str: &SmolStr) -> Self {
 | 
			
		||||
        smol_str.as_bytes().into()
 | 
			
		||||
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(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -115,39 +111,13 @@ 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<u8> {
 | 
			
		||||
    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<Self, Self::Error> {
 | 
			
		||||
        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(byte_span: &AMbyteSpan) -> Result<Self, Self::Error> {
 | 
			
		||||
    fn try_from(span: &AMbyteSpan) -> Result<Self, Self::Error> {
 | 
			
		||||
        use am::AutomergeError::InvalidCharacter;
 | 
			
		||||
 | 
			
		||||
        let slice = byte_span.into();
 | 
			
		||||
        let slice = unsafe { std::slice::from_raw_parts(span.src, span.count) };
 | 
			
		||||
        match std::str::from_utf8(slice) {
 | 
			
		||||
            Ok(str_) => Ok(str_),
 | 
			
		||||
            Err(e) => Err(InvalidCharacter(e.valid_up_to())),
 | 
			
		||||
| 
						 | 
				
			
			@ -155,69 +125,17 @@ impl TryFrom<&AMbyteSpan> for &str {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMbyteSpan
 | 
			
		||||
/// \brief Creates a view onto an array of bytes.
 | 
			
		||||
/// \brief Creates an AMbyteSpan from a pointer + length
 | 
			
		||||
///
 | 
			
		||||
/// \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}`
 | 
			
		||||
/// \param[in] src  A pointer to a span of bytes
 | 
			
		||||
/// \param[in] count The number of bytes in the span
 | 
			
		||||
/// \return An `AMbyteSpan` struct
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// #Safety
 | 
			
		||||
/// src must be a byte array of length `>= count` or `std::ptr::null()`
 | 
			
		||||
/// AMbytes does not retain the underlying storage, so you must discard the
 | 
			
		||||
/// return value before freeing the bytes.
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub unsafe extern "C" fn AMbytes(src: *const u8, count: usize) -> AMbyteSpan {
 | 
			
		||||
    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,
 | 
			
		||||
    }
 | 
			
		||||
    AMbyteSpan { src, count }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@ 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 {
 | 
			
		||||
| 
						 | 
				
			
			@ -9,7 +10,7 @@ macro_rules! to_change {
 | 
			
		|||
        let handle = $handle.as_ref();
 | 
			
		||||
        match handle {
 | 
			
		||||
            Some(b) => b,
 | 
			
		||||
            None => return AMresult::error("Invalid `AMchange*`").into(),
 | 
			
		||||
            None => return AMresult::err("Invalid AMchange pointer").into(),
 | 
			
		||||
        }
 | 
			
		||||
    }};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -20,14 +21,14 @@ macro_rules! to_change {
 | 
			
		|||
#[derive(Eq, PartialEq)]
 | 
			
		||||
pub struct AMchange {
 | 
			
		||||
    body: *mut am::Change,
 | 
			
		||||
    change_hash: RefCell<Option<am::ChangeHash>>,
 | 
			
		||||
    changehash: RefCell<Option<am::ChangeHash>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AMchange {
 | 
			
		||||
    pub fn new(change: &mut am::Change) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            body: change,
 | 
			
		||||
            change_hash: Default::default(),
 | 
			
		||||
            changehash: Default::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -39,12 +40,12 @@ impl AMchange {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn hash(&self) -> AMbyteSpan {
 | 
			
		||||
        let mut change_hash = self.change_hash.borrow_mut();
 | 
			
		||||
        if let Some(change_hash) = change_hash.as_ref() {
 | 
			
		||||
            change_hash.into()
 | 
			
		||||
        let mut changehash = self.changehash.borrow_mut();
 | 
			
		||||
        if let Some(changehash) = changehash.as_ref() {
 | 
			
		||||
            changehash.into()
 | 
			
		||||
        } else {
 | 
			
		||||
            let hash = unsafe { (*self.body).hash() };
 | 
			
		||||
            let ptr = change_hash.insert(hash);
 | 
			
		||||
            let ptr = changehash.insert(hash);
 | 
			
		||||
            AMbyteSpan {
 | 
			
		||||
                src: ptr.0.as_ptr(),
 | 
			
		||||
                count: hash.as_ref().len(),
 | 
			
		||||
| 
						 | 
				
			
			@ -69,10 +70,11 @@ impl AsRef<am::Change> for AMchange {
 | 
			
		|||
/// \brief Gets the first referenced actor identifier in a change.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] change A pointer to an `AMchange` struct.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -88,8 +90,8 @@ pub unsafe extern "C" fn AMchangeActorId(change: *const AMchange) -> *mut AMresu
 | 
			
		|||
/// \memberof AMchange
 | 
			
		||||
/// \brief Compresses the raw bytes of a change.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] change A pointer to an `AMchange` struct.
 | 
			
		||||
/// \pre \p change `!= NULL`
 | 
			
		||||
/// \param[in,out] change A pointer to an `AMchange` struct.
 | 
			
		||||
/// \pre \p change `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -105,20 +107,18 @@ 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 `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.
 | 
			
		||||
/// \return A pointer to an `AMchangeHashes` struct or `NULL`.
 | 
			
		||||
/// \pre \p change `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
/// change must be a valid pointer to an AMchange
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub unsafe extern "C" fn AMchangeDeps(change: *const AMchange) -> *mut AMresult {
 | 
			
		||||
    to_result(match change.as_ref() {
 | 
			
		||||
        Some(change) => change.as_ref().deps(),
 | 
			
		||||
pub unsafe extern "C" fn AMchangeDeps(change: *const AMchange) -> AMchangeHashes {
 | 
			
		||||
    match change.as_ref() {
 | 
			
		||||
        Some(change) => AMchangeHashes::new(change.as_ref().deps()),
 | 
			
		||||
        None => Default::default(),
 | 
			
		||||
    })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMchange
 | 
			
		||||
| 
						 | 
				
			
			@ -126,7 +126,7 @@ pub unsafe extern "C" fn AMchangeDeps(change: *const AMchange) -> *mut AMresult
 | 
			
		|||
///
 | 
			
		||||
/// \param[in] change A pointer to an `AMchange` struct.
 | 
			
		||||
/// \return An `AMbyteSpan` struct.
 | 
			
		||||
/// \pre \p change `!= NULL`
 | 
			
		||||
/// \pre \p change `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -141,33 +141,32 @@ pub unsafe extern "C" fn AMchangeExtraBytes(change: *const AMchange) -> AMbyteSp
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMchange
 | 
			
		||||
/// \brief Allocates a new change and initializes it from an array of bytes value.
 | 
			
		||||
/// \brief Loads a sequence of bytes into a change.
 | 
			
		||||
///
 | 
			
		||||
/// \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 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
/// src must be a byte array of length `>= count`
 | 
			
		||||
/// src must be a byte array of size `>= count`
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub unsafe extern "C" fn AMchangeFromBytes(src: *const u8, count: usize) -> *mut AMresult {
 | 
			
		||||
    let data = std::slice::from_raw_parts(src, count);
 | 
			
		||||
    to_result(am::Change::from_bytes(data.to_vec()))
 | 
			
		||||
    let mut data = Vec::new();
 | 
			
		||||
    data.extend_from_slice(std::slice::from_raw_parts(src, count));
 | 
			
		||||
    to_result(am::Change::from_bytes(data))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMchange
 | 
			
		||||
/// \brief Gets the hash of a change.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] change A pointer to an `AMchange` struct.
 | 
			
		||||
/// \return An `AMbyteSpan` struct for a change hash.
 | 
			
		||||
/// \pre \p change `!= NULL`
 | 
			
		||||
/// \return A change hash as an `AMbyteSpan` struct.
 | 
			
		||||
/// \pre \p change `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -184,8 +183,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 `true` if \p change is empty, `false` otherwise.
 | 
			
		||||
/// \pre \p change `!= NULL`
 | 
			
		||||
/// \return A boolean.
 | 
			
		||||
/// \pre \p change `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -199,37 +198,12 @@ 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::<Result<Vec<am::Change>, _>>(
 | 
			
		||||
        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
 | 
			
		||||
| 
						 | 
				
			
			@ -247,8 +221,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 An `AMbyteSpan` struct for a UTF-8 string.
 | 
			
		||||
/// \pre \p change `!= NULL`
 | 
			
		||||
/// \return A UTF-8 string view as an `AMbyteSpan` struct.
 | 
			
		||||
/// \pre \p change `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -266,7 +240,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
 | 
			
		||||
| 
						 | 
				
			
			@ -285,7 +259,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
 | 
			
		||||
| 
						 | 
				
			
			@ -293,9 +267,10 @@ 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() {
 | 
			
		||||
        return change.as_ref().len();
 | 
			
		||||
        change.as_ref().len()
 | 
			
		||||
    } else {
 | 
			
		||||
        0
 | 
			
		||||
    }
 | 
			
		||||
    0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMchange
 | 
			
		||||
| 
						 | 
				
			
			@ -303,7 +278,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
 | 
			
		||||
| 
						 | 
				
			
			@ -322,7 +297,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
 | 
			
		||||
| 
						 | 
				
			
			@ -340,8 +315,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 for an array of bytes.
 | 
			
		||||
/// \pre \p change `!= NULL`
 | 
			
		||||
/// \return An `AMbyteSpan` struct.
 | 
			
		||||
/// \pre \p change `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -354,3 +329,28 @@ 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::<Result<Vec<am::Change>, _>>(
 | 
			
		||||
        am::Automerge::load(&data)
 | 
			
		||||
            .and_then(|d| d.get_changes(&[]).map(|c| c.into_iter().cloned().collect())),
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										400
									
								
								rust/automerge-c/src/change_hashes.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										400
									
								
								rust/automerge-c/src/change_hashes.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,400 @@
 | 
			
		|||
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<T>()` 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::<Detail>();
 | 
			
		||||
 | 
			
		||||
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<Detail> 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::<am::ChangeHash>::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::<Vec<am::ChangeHash>, 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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										399
									
								
								rust/automerge-c/src/changes.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										399
									
								
								rust/automerge-c/src/changes.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,399 @@
 | 
			
		|||
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<T>()` 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::<Detail>();
 | 
			
		||||
 | 
			
		||||
impl Detail {
 | 
			
		||||
    fn new(changes: &[am::Change], offset: isize, storage: &mut BTreeMap<usize, AMchange>) -> Self {
 | 
			
		||||
        let storage: *mut BTreeMap<usize, AMchange> = 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<usize, AMchange>) };
 | 
			
		||||
        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<usize, AMchange>) };
 | 
			
		||||
        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<Detail> 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<usize, AMchange>) -> 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::<am::Change>::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::<Vec<am::Change>, am::LoadChangeError>(e));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    to_result(Ok::<Vec<am::Change>, 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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
				
			
			@ -1,46 +1,48 @@
 | 
			
		|||
use automerge as am;
 | 
			
		||||
use automerge::transaction::Transactable;
 | 
			
		||||
use automerge::ReadDoc;
 | 
			
		||||
 | 
			
		||||
use crate::byte_span::{to_str, AMbyteSpan};
 | 
			
		||||
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::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::result::{to_result, AMresult};
 | 
			
		||||
 | 
			
		||||
pub mod item;
 | 
			
		||||
pub mod items;
 | 
			
		||||
 | 
			
		||||
macro_rules! adjust {
 | 
			
		||||
    ($pos:expr, $insert:expr, $len:expr) => {{
 | 
			
		||||
    ($index: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 $pos > end && $pos != usize::MAX {
 | 
			
		||||
            return AMresult::error(&format!("Invalid pos {}", $pos)).into();
 | 
			
		||||
        if $index > end && $index != usize::MAX {
 | 
			
		||||
            return AMresult::err(&format!("Invalid index {}", $index)).into();
 | 
			
		||||
        }
 | 
			
		||||
        (std::cmp::min($pos, end), insert)
 | 
			
		||||
        (std::cmp::min($index, end), insert)
 | 
			
		||||
    }};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
macro_rules! to_range {
 | 
			
		||||
    ($begin:expr, $end:expr) => {{
 | 
			
		||||
        if $begin > $end {
 | 
			
		||||
            return AMresult::error(&format!("Invalid range [{}-{})", $begin, $end)).into();
 | 
			
		||||
            return AMresult::err(&format!("Invalid range [{}-{})", $begin, $end)).into();
 | 
			
		||||
        };
 | 
			
		||||
        ($begin..$end)
 | 
			
		||||
    }};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Deletes an item from a list object.
 | 
			
		||||
/// \brief Deletes an index in a list object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -50,109 +52,101 @@ macro_rules! to_range {
 | 
			
		|||
pub unsafe extern "C" fn AMlistDelete(
 | 
			
		||||
    doc: *mut AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    pos: usize,
 | 
			
		||||
    index: usize,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc_mut!(doc);
 | 
			
		||||
    let obj_id = to_obj_id!(obj_id);
 | 
			
		||||
    let (pos, _) = adjust!(pos, false, doc.length(obj_id));
 | 
			
		||||
    to_result(doc.delete(obj_id, pos))
 | 
			
		||||
    let (index, _) = adjust!(index, false, doc.length(obj_id));
 | 
			
		||||
    to_result(doc.delete(obj_id, index))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Gets a current or historical item within a list object.
 | 
			
		||||
/// \brief Gets the current or historical value at an index in 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] 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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 AMitems or std::ptr::null()
 | 
			
		||||
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub unsafe extern "C" fn AMlistGet(
 | 
			
		||||
    doc: *const AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    pos: usize,
 | 
			
		||||
    heads: *const AMitems,
 | 
			
		||||
    index: usize,
 | 
			
		||||
    heads: *const AMchangeHashes,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc!(doc);
 | 
			
		||||
    let obj_id = to_obj_id!(obj_id);
 | 
			
		||||
    let (pos, _) = adjust!(pos, false, doc.length(obj_id));
 | 
			
		||||
    match heads.as_ref() {
 | 
			
		||||
        None => to_result(doc.get(obj_id, pos)),
 | 
			
		||||
        Some(heads) => match <Vec<am::ChangeHash>>::try_from(heads) {
 | 
			
		||||
            Ok(heads) => to_result(doc.get_at(obj_id, pos, &heads)),
 | 
			
		||||
            Err(e) => AMresult::error(&e.to_string()).into(),
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
    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()),
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Gets all of the historical items at a position within a list object
 | 
			
		||||
///        until its current one or a specific one.
 | 
			
		||||
/// \brief Gets all of the historical values at an index in 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] 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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 AMitems or std::ptr::null()
 | 
			
		||||
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub unsafe extern "C" fn AMlistGetAll(
 | 
			
		||||
    doc: *const AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    pos: usize,
 | 
			
		||||
    heads: *const AMitems,
 | 
			
		||||
    index: usize,
 | 
			
		||||
    heads: *const AMchangeHashes,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc!(doc);
 | 
			
		||||
    let obj_id = to_obj_id!(obj_id);
 | 
			
		||||
    let (pos, _) = adjust!(pos, false, doc.length(obj_id));
 | 
			
		||||
    let (index, _) = adjust!(index, false, doc.length(obj_id));
 | 
			
		||||
    match heads.as_ref() {
 | 
			
		||||
        None => to_result(doc.get_all(obj_id, pos)),
 | 
			
		||||
        Some(heads) => match <Vec<am::ChangeHash>>::try_from(heads) {
 | 
			
		||||
            Ok(heads) => to_result(doc.get_all_at(obj_id, pos, &heads)),
 | 
			
		||||
            Err(e) => AMresult::error(&e.to_string()).into(),
 | 
			
		||||
        },
 | 
			
		||||
        None => to_result(doc.get_all(obj_id, index)),
 | 
			
		||||
        Some(heads) => to_result(doc.get_all_at(obj_id, index, heads.as_ref())),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Increments a counter value in an item within a list object by the
 | 
			
		||||
///        given value.
 | 
			
		||||
/// \brief Increments a counter at an index in a list object by the given
 | 
			
		||||
///        value.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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] index An index in the list object identified by \p obj_id or
 | 
			
		||||
///                  `SIZE_MAX` to indicate its last index.
 | 
			
		||||
/// \param[in] value A 64-bit signed integer.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -162,33 +156,32 @@ pub unsafe extern "C" fn AMlistGetAll(
 | 
			
		|||
pub unsafe extern "C" fn AMlistIncrement(
 | 
			
		||||
    doc: *mut AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    pos: usize,
 | 
			
		||||
    index: usize,
 | 
			
		||||
    value: i64,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc_mut!(doc);
 | 
			
		||||
    let obj_id = to_obj_id!(obj_id);
 | 
			
		||||
    let (pos, _) = adjust!(pos, false, doc.length(obj_id));
 | 
			
		||||
    to_result(doc.increment(obj_id, pos, value))
 | 
			
		||||
    let (index, _) = adjust!(index, false, doc.length(obj_id));
 | 
			
		||||
    to_result(doc.increment(obj_id, index, value))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Puts a boolean value into an item within a list object.
 | 
			
		||||
/// \brief Puts a boolean as the value at an index in a list object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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] 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] value A boolean.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -198,85 +191,84 @@ pub unsafe extern "C" fn AMlistIncrement(
 | 
			
		|||
pub unsafe extern "C" fn AMlistPutBool(
 | 
			
		||||
    doc: *mut AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    pos: usize,
 | 
			
		||||
    index: usize,
 | 
			
		||||
    insert: bool,
 | 
			
		||||
    value: bool,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc_mut!(doc);
 | 
			
		||||
    let obj_id = to_obj_id!(obj_id);
 | 
			
		||||
    let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
 | 
			
		||||
    let (index, insert) = adjust!(index, insert, doc.length(obj_id));
 | 
			
		||||
    let value = am::ScalarValue::Boolean(value);
 | 
			
		||||
    to_result(if insert {
 | 
			
		||||
        doc.insert(obj_id, pos, value)
 | 
			
		||||
        doc.insert(obj_id, index, value)
 | 
			
		||||
    } else {
 | 
			
		||||
        doc.put(obj_id, pos, value)
 | 
			
		||||
        doc.put(obj_id, index, value)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Puts an array of bytes value at a position within a list object.
 | 
			
		||||
/// \brief Puts a sequence of bytes as the value at an index in a list object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.src must be a byte array of length >= value.count
 | 
			
		||||
/// src must be a byte array of size `>= count`
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub unsafe extern "C" fn AMlistPutBytes(
 | 
			
		||||
    doc: *mut AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    pos: usize,
 | 
			
		||||
    index: usize,
 | 
			
		||||
    insert: bool,
 | 
			
		||||
    value: AMbyteSpan,
 | 
			
		||||
    val: AMbyteSpan,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc_mut!(doc);
 | 
			
		||||
    let obj_id = to_obj_id!(obj_id);
 | 
			
		||||
    let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
 | 
			
		||||
    let value: Vec<u8> = (&value).into();
 | 
			
		||||
    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));
 | 
			
		||||
    to_result(if insert {
 | 
			
		||||
        doc.insert(obj_id, pos, value)
 | 
			
		||||
        doc.insert(obj_id, index, value)
 | 
			
		||||
    } else {
 | 
			
		||||
        doc.put(obj_id, pos, value)
 | 
			
		||||
        doc.put(obj_id, index, value)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Puts a CRDT counter value into an item within a list object.
 | 
			
		||||
/// \brief Puts a CRDT counter as the value at an index in a list object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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] 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] value A 64-bit signed integer.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -286,39 +278,38 @@ pub unsafe extern "C" fn AMlistPutBytes(
 | 
			
		|||
pub unsafe extern "C" fn AMlistPutCounter(
 | 
			
		||||
    doc: *mut AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    pos: usize,
 | 
			
		||||
    index: usize,
 | 
			
		||||
    insert: bool,
 | 
			
		||||
    value: i64,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc_mut!(doc);
 | 
			
		||||
    let obj_id = to_obj_id!(obj_id);
 | 
			
		||||
    let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
 | 
			
		||||
    let (index, insert) = adjust!(index, insert, doc.length(obj_id));
 | 
			
		||||
    let value = am::ScalarValue::Counter(value.into());
 | 
			
		||||
    to_result(if insert {
 | 
			
		||||
        doc.insert(obj_id, pos, value)
 | 
			
		||||
        doc.insert(obj_id, index, value)
 | 
			
		||||
    } else {
 | 
			
		||||
        doc.put(obj_id, pos, value)
 | 
			
		||||
        doc.put(obj_id, index, value)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Puts a float value into an item within a list object.
 | 
			
		||||
/// \brief Puts a float as the value at an index in a list object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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] 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] value A 64-bit float.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -328,38 +319,37 @@ pub unsafe extern "C" fn AMlistPutCounter(
 | 
			
		|||
pub unsafe extern "C" fn AMlistPutF64(
 | 
			
		||||
    doc: *mut AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    pos: usize,
 | 
			
		||||
    index: usize,
 | 
			
		||||
    insert: bool,
 | 
			
		||||
    value: f64,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc_mut!(doc);
 | 
			
		||||
    let obj_id = to_obj_id!(obj_id);
 | 
			
		||||
    let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
 | 
			
		||||
    let (index, insert) = adjust!(index, insert, doc.length(obj_id));
 | 
			
		||||
    to_result(if insert {
 | 
			
		||||
        doc.insert(obj_id, pos, value)
 | 
			
		||||
        doc.insert(obj_id, index, value)
 | 
			
		||||
    } else {
 | 
			
		||||
        doc.put(obj_id, pos, value)
 | 
			
		||||
        doc.put(obj_id, index, value)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Puts a signed integer value into an item within a list object.
 | 
			
		||||
/// \brief Puts a signed integer as the value at an index in a list object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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] 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] value A 64-bit signed integer.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -369,37 +359,36 @@ pub unsafe extern "C" fn AMlistPutF64(
 | 
			
		|||
pub unsafe extern "C" fn AMlistPutInt(
 | 
			
		||||
    doc: *mut AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    pos: usize,
 | 
			
		||||
    index: usize,
 | 
			
		||||
    insert: bool,
 | 
			
		||||
    value: i64,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc_mut!(doc);
 | 
			
		||||
    let obj_id = to_obj_id!(obj_id);
 | 
			
		||||
    let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
 | 
			
		||||
    let (index, insert) = adjust!(index, insert, doc.length(obj_id));
 | 
			
		||||
    to_result(if insert {
 | 
			
		||||
        doc.insert(obj_id, pos, value)
 | 
			
		||||
        doc.insert(obj_id, index, value)
 | 
			
		||||
    } else {
 | 
			
		||||
        doc.put(obj_id, pos, value)
 | 
			
		||||
        doc.put(obj_id, index, value)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Puts a null value into an item within a list object.
 | 
			
		||||
/// \brief Puts null as the value at an index in a list object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -409,37 +398,38 @@ pub unsafe extern "C" fn AMlistPutInt(
 | 
			
		|||
pub unsafe extern "C" fn AMlistPutNull(
 | 
			
		||||
    doc: *mut AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    pos: usize,
 | 
			
		||||
    index: usize,
 | 
			
		||||
    insert: bool,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc_mut!(doc);
 | 
			
		||||
    let obj_id = to_obj_id!(obj_id);
 | 
			
		||||
    let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
 | 
			
		||||
    let (index, insert) = adjust!(index, insert, doc.length(obj_id));
 | 
			
		||||
    to_result(if insert {
 | 
			
		||||
        doc.insert(obj_id, pos, ())
 | 
			
		||||
        doc.insert(obj_id, index, ())
 | 
			
		||||
    } else {
 | 
			
		||||
        doc.put(obj_id, pos, ())
 | 
			
		||||
        doc.put(obj_id, index, ())
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Puts an empty object value into an item within a list object.
 | 
			
		||||
/// \brief Puts an empty object as the value at an index in a list object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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] 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] obj_type An `AMobjIdType` enum tag.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -449,85 +439,82 @@ pub unsafe extern "C" fn AMlistPutNull(
 | 
			
		|||
pub unsafe extern "C" fn AMlistPutObject(
 | 
			
		||||
    doc: *mut AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    pos: usize,
 | 
			
		||||
    index: usize,
 | 
			
		||||
    insert: bool,
 | 
			
		||||
    obj_type: AMobjType,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc_mut!(doc);
 | 
			
		||||
    let obj_id = to_obj_id!(obj_id);
 | 
			
		||||
    let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
 | 
			
		||||
    let obj_type = to_obj_type!(obj_type);
 | 
			
		||||
    let (index, insert) = adjust!(index, insert, doc.length(obj_id));
 | 
			
		||||
    let object = to_obj_type!(obj_type);
 | 
			
		||||
    to_result(if insert {
 | 
			
		||||
        (doc.insert_object(obj_id, pos, obj_type), obj_type)
 | 
			
		||||
        doc.insert_object(obj_id, index, object)
 | 
			
		||||
    } else {
 | 
			
		||||
        (doc.put_object(obj_id, pos, obj_type), obj_type)
 | 
			
		||||
        doc.put_object(obj_id, index, object)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Puts a UTF-8 string value into an item within a list object.
 | 
			
		||||
/// \brief Puts a UTF-8 string as the value at an index in a list object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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] 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] value A UTF-8 string view 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.src must be a byte array of length >= value.count
 | 
			
		||||
/// value must be a null-terminated array of `c_char`
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub unsafe extern "C" fn AMlistPutStr(
 | 
			
		||||
    doc: *mut AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    pos: usize,
 | 
			
		||||
    index: usize,
 | 
			
		||||
    insert: bool,
 | 
			
		||||
    value: AMbyteSpan,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc_mut!(doc);
 | 
			
		||||
    let obj_id = to_obj_id!(obj_id);
 | 
			
		||||
    let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
 | 
			
		||||
    let (index, insert) = adjust!(index, insert, doc.length(obj_id));
 | 
			
		||||
    let value = to_str!(value);
 | 
			
		||||
    to_result(if insert {
 | 
			
		||||
        doc.insert(obj_id, pos, value)
 | 
			
		||||
        doc.insert(obj_id, index, value)
 | 
			
		||||
    } else {
 | 
			
		||||
        doc.put(obj_id, pos, value)
 | 
			
		||||
        doc.put(obj_id, index, value)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Puts a *nix timestamp (milliseconds) value into an item within a
 | 
			
		||||
/// \brief Puts a *nix timestamp (milliseconds) as the value at an index in a
 | 
			
		||||
///        list object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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] 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] value A 64-bit signed integer.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -537,39 +524,38 @@ pub unsafe extern "C" fn AMlistPutStr(
 | 
			
		|||
pub unsafe extern "C" fn AMlistPutTimestamp(
 | 
			
		||||
    doc: *mut AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    pos: usize,
 | 
			
		||||
    index: usize,
 | 
			
		||||
    insert: bool,
 | 
			
		||||
    value: i64,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc_mut!(doc);
 | 
			
		||||
    let obj_id = to_obj_id!(obj_id);
 | 
			
		||||
    let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
 | 
			
		||||
    let (index, insert) = adjust!(index, insert, doc.length(obj_id));
 | 
			
		||||
    let value = am::ScalarValue::Timestamp(value);
 | 
			
		||||
    to_result(if insert {
 | 
			
		||||
        doc.insert(obj_id, pos, value)
 | 
			
		||||
        doc.insert(obj_id, index, value)
 | 
			
		||||
    } else {
 | 
			
		||||
        doc.put(obj_id, pos, value)
 | 
			
		||||
        doc.put(obj_id, index, value)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Puts an unsigned integer value into an item within a list object.
 | 
			
		||||
/// \brief Puts an unsigned integer as the value at an index in a list object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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] 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] value A 64-bit unsigned integer.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -579,58 +565,56 @@ pub unsafe extern "C" fn AMlistPutTimestamp(
 | 
			
		|||
pub unsafe extern "C" fn AMlistPutUint(
 | 
			
		||||
    doc: *mut AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    pos: usize,
 | 
			
		||||
    index: usize,
 | 
			
		||||
    insert: bool,
 | 
			
		||||
    value: u64,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc_mut!(doc);
 | 
			
		||||
    let obj_id = to_obj_id!(obj_id);
 | 
			
		||||
    let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
 | 
			
		||||
    let (index, insert) = adjust!(index, insert, doc.length(obj_id));
 | 
			
		||||
    to_result(if insert {
 | 
			
		||||
        doc.insert(obj_id, pos, value)
 | 
			
		||||
        doc.insert(obj_id, index, value)
 | 
			
		||||
    } else {
 | 
			
		||||
        doc.put(obj_id, pos, value)
 | 
			
		||||
        doc.put(obj_id, index, value)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Gets the current or historical items in the list object within the
 | 
			
		||||
///        given range.
 | 
			
		||||
/// \brief Gets the current or historical indices and values of 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 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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 AMitems or std::ptr::null()
 | 
			
		||||
/// heads must be a valid pointer to an AMchangeHashes 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 AMitems,
 | 
			
		||||
    heads: *const AMchangeHashes,
 | 
			
		||||
) -> *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) => match <Vec<am::ChangeHash>>::try_from(heads) {
 | 
			
		||||
            Ok(heads) => to_result(doc.list_range_at(obj_id, range, &heads)),
 | 
			
		||||
            Err(e) => AMresult::error(&e.to_string()).into(),
 | 
			
		||||
        },
 | 
			
		||||
        Some(heads) => to_result(doc.list_range_at(obj_id, range, heads.as_ref())),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										97
									
								
								rust/automerge-c/src/doc/list/item.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								rust/automerge-c/src/doc/list/item.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,97 @@
 | 
			
		|||
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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										348
									
								
								rust/automerge-c/src/doc/list/items.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										348
									
								
								rust/automerge-c/src/doc/list/items.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,348 @@
 | 
			
		|||
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<T>()` 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::<Detail>();
 | 
			
		||||
 | 
			
		||||
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<Detail> 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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,31 +1,32 @@
 | 
			
		|||
use automerge as am;
 | 
			
		||||
use automerge::transaction::Transactable;
 | 
			
		||||
use automerge::ReadDoc;
 | 
			
		||||
 | 
			
		||||
use crate::byte_span::{to_str, AMbyteSpan};
 | 
			
		||||
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::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::result::{to_result, AMresult};
 | 
			
		||||
 | 
			
		||||
pub mod item;
 | 
			
		||||
pub mod items;
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Deletes an item from a map object.
 | 
			
		||||
/// \brief Deletes a key in a map object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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,
 | 
			
		||||
| 
						 | 
				
			
			@ -38,107 +39,96 @@ pub unsafe extern "C" fn AMmapDelete(
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Gets a current or historical item within a map object.
 | 
			
		||||
/// \brief Gets the current or historical value for a key in 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 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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
 | 
			
		||||
/// heads must be a valid pointer to an AMitems or std::ptr::null()
 | 
			
		||||
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub unsafe extern "C" fn AMmapGet(
 | 
			
		||||
    doc: *const AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    key: AMbyteSpan,
 | 
			
		||||
    heads: *const AMitems,
 | 
			
		||||
    heads: *const AMchangeHashes,
 | 
			
		||||
) -> *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) => match <Vec<am::ChangeHash>>::try_from(heads) {
 | 
			
		||||
            Ok(heads) => to_result(doc.get_at(obj_id, key, &heads)),
 | 
			
		||||
            Err(e) => AMresult::error(&e.to_string()).into(),
 | 
			
		||||
        },
 | 
			
		||||
        Some(heads) => to_result(doc.get_at(obj_id, key, heads.as_ref())),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Gets all of the historical items at a key within a map object until
 | 
			
		||||
/// \brief Gets all of the historical values for a key in 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 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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
 | 
			
		||||
/// heads must be a valid pointer to an AMitems or std::ptr::null()
 | 
			
		||||
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub unsafe extern "C" fn AMmapGetAll(
 | 
			
		||||
    doc: *const AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    key: AMbyteSpan,
 | 
			
		||||
    heads: *const AMitems,
 | 
			
		||||
    heads: *const AMchangeHashes,
 | 
			
		||||
) -> *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) => match <Vec<am::ChangeHash>>::try_from(heads) {
 | 
			
		||||
            Ok(heads) => to_result(doc.get_all_at(obj_id, key, &heads)),
 | 
			
		||||
            Err(e) => AMresult::error(&e.to_string()).into(),
 | 
			
		||||
        },
 | 
			
		||||
        Some(heads) => to_result(doc.get_all_at(obj_id, key, heads.as_ref())),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Increments a counter at a key in a map object by the given value.
 | 
			
		||||
/// \brief Increments a counter for a key in a map object by the given value.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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] 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 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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,
 | 
			
		||||
| 
						 | 
				
			
			@ -154,22 +144,21 @@ pub unsafe extern "C" fn AMmapIncrement(
 | 
			
		|||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Puts a boolean as the value of a key in a map object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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] key A UTF-8 string view key for the map object identified by
 | 
			
		||||
///                \p obj_id as an `AMbyteSpan` struct.
 | 
			
		||||
/// \param[in] value A boolean.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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,
 | 
			
		||||
| 
						 | 
				
			
			@ -183,58 +172,59 @@ pub unsafe extern "C" fn AMmapPutBool(
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Puts an array of bytes value at a key in a map object.
 | 
			
		||||
/// \brief Puts a sequence of bytes as the value of a key in a map object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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
 | 
			
		||||
/// value.src must be a byte array of length >= value.count
 | 
			
		||||
/// src must be a byte array of size `>= count`
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub unsafe extern "C" fn AMmapPutBytes(
 | 
			
		||||
    doc: *mut AMdoc,
 | 
			
		||||
    obj_id: *const AMobjId,
 | 
			
		||||
    key: AMbyteSpan,
 | 
			
		||||
    value: AMbyteSpan,
 | 
			
		||||
    val: AMbyteSpan,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc_mut!(doc);
 | 
			
		||||
    let key = to_str!(key);
 | 
			
		||||
    to_result(doc.put(to_obj_id!(obj_id), key, Vec::<u8>::from(&value)))
 | 
			
		||||
    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))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Puts a CRDT counter as the value of a key in a map object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] 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 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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,
 | 
			
		||||
| 
						 | 
				
			
			@ -254,21 +244,20 @@ pub unsafe extern "C" fn AMmapPutCounter(
 | 
			
		|||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Puts null as the value of a key in a map object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] 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 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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,
 | 
			
		||||
| 
						 | 
				
			
			@ -283,22 +272,23 @@ 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] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] 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 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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,
 | 
			
		||||
| 
						 | 
				
			
			@ -308,29 +298,27 @@ pub unsafe extern "C" fn AMmapPutObject(
 | 
			
		|||
) -> *mut AMresult {
 | 
			
		||||
    let doc = to_doc_mut!(doc);
 | 
			
		||||
    let key = to_str!(key);
 | 
			
		||||
    let obj_type = to_obj_type!(obj_type);
 | 
			
		||||
    to_result((doc.put_object(to_obj_id!(obj_id), key, obj_type), obj_type))
 | 
			
		||||
    to_result(doc.put_object(to_obj_id!(obj_id), key, to_obj_type!(obj_type)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Puts a float as the value of a key in a map object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] 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 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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,
 | 
			
		||||
| 
						 | 
				
			
			@ -346,22 +334,21 @@ 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] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] 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 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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,
 | 
			
		||||
| 
						 | 
				
			
			@ -377,22 +364,21 @@ 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] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] 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 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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,
 | 
			
		||||
| 
						 | 
				
			
			@ -408,22 +394,21 @@ pub unsafe extern "C" fn AMmapPutStr(
 | 
			
		|||
/// \brief Puts a *nix timestamp (milliseconds) as the value of a key in a map
 | 
			
		||||
///        object.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] 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 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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,
 | 
			
		||||
| 
						 | 
				
			
			@ -439,22 +424,21 @@ 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] doc A pointer to an `AMdoc` struct.
 | 
			
		||||
/// \param[in,out] 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 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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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,
 | 
			
		||||
| 
						 | 
				
			
			@ -468,82 +452,71 @@ pub unsafe extern "C" fn AMmapPutUint(
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMdoc
 | 
			
		||||
/// \brief Gets the current or historical items of the map object within the
 | 
			
		||||
///        given range.
 | 
			
		||||
/// \brief Gets the current or historical keys and values 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 `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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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()
 | 
			
		||||
/// 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()
 | 
			
		||||
/// heads must be a valid pointer to an AMchangeHashes 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 AMitems,
 | 
			
		||||
    heads: *const AMchangeHashes,
 | 
			
		||||
) -> *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 <Vec<am::ChangeHash>>::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::error(&format!("Invalid range [{}-{})", begin, end)).into();
 | 
			
		||||
                return AMresult::err(&format!("Invalid range [{}-{})", begin, end)).into();
 | 
			
		||||
            };
 | 
			
		||||
            let bounds = begin..end;
 | 
			
		||||
            if let Some(heads) = heads {
 | 
			
		||||
                to_result(doc.map_range_at(obj_id, bounds, &heads))
 | 
			
		||||
            if let Some(heads) = heads.as_ref() {
 | 
			
		||||
                to_result(doc.map_range_at(obj_id, bounds, heads.as_ref()))
 | 
			
		||||
            } else {
 | 
			
		||||
                to_result(doc.map_range(obj_id, bounds))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        (false, true) => {
 | 
			
		||||
            let bounds = to_str!(begin).to_string()..;
 | 
			
		||||
            if let Some(heads) = heads {
 | 
			
		||||
                to_result(doc.map_range_at(obj_id, bounds, &heads))
 | 
			
		||||
            if let Some(heads) = heads.as_ref() {
 | 
			
		||||
                to_result(doc.map_range_at(obj_id, bounds, heads.as_ref()))
 | 
			
		||||
            } else {
 | 
			
		||||
                to_result(doc.map_range(obj_id, bounds))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        (true, false) => {
 | 
			
		||||
            let bounds = ..to_str!(end).to_string();
 | 
			
		||||
            if let Some(heads) = heads {
 | 
			
		||||
                to_result(doc.map_range_at(obj_id, bounds, &heads))
 | 
			
		||||
            if let Some(heads) = heads.as_ref() {
 | 
			
		||||
                to_result(doc.map_range_at(obj_id, bounds, heads.as_ref()))
 | 
			
		||||
            } else {
 | 
			
		||||
                to_result(doc.map_range(obj_id, bounds))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        (true, true) => {
 | 
			
		||||
            let bounds = ..;
 | 
			
		||||
            if let Some(heads) = heads {
 | 
			
		||||
                to_result(doc.map_range_at(obj_id, bounds, &heads))
 | 
			
		||||
            if let Some(heads) = heads.as_ref() {
 | 
			
		||||
                to_result(doc.map_range_at(obj_id, bounds, heads.as_ref()))
 | 
			
		||||
            } else {
 | 
			
		||||
                to_result(doc.map_range(obj_id, bounds))
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										98
									
								
								rust/automerge-c/src/doc/map/item.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								rust/automerge-c/src/doc/map/item.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,98 @@
 | 
			
		|||
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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										340
									
								
								rust/automerge-c/src/doc/map/items.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										340
									
								
								rust/automerge-c/src/doc/map/items.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,340 @@
 | 
			
		|||
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<T>()` 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::<Detail>();
 | 
			
		||||
 | 
			
		||||
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<Detail> 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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,20 +1,9 @@
 | 
			
		|||
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::error("Invalid `AMdoc*`").into(),
 | 
			
		||||
            None => return AMresult::err("Invalid AMdoc pointer").into(),
 | 
			
		||||
        }
 | 
			
		||||
    }};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -26,21 +15,9 @@ macro_rules! to_doc_mut {
 | 
			
		|||
        let handle = $handle.as_mut();
 | 
			
		||||
        match handle {
 | 
			
		||||
            Some(b) => b,
 | 
			
		||||
            None => return AMresult::error("Invalid `AMdoc*`").into(),
 | 
			
		||||
            None => return AMresult::err("Invalid AMdoc pointer").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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,84 +0,0 @@
 | 
			
		|||
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<Self, Self::Error> {
 | 
			
		||||
        use am::AutomergeError::InvalidValueType;
 | 
			
		||||
        use AMindex::*;
 | 
			
		||||
 | 
			
		||||
        if let Key(key) = item {
 | 
			
		||||
            return Ok(key.into());
 | 
			
		||||
        }
 | 
			
		||||
        Err(InvalidValueType {
 | 
			
		||||
            expected: type_name::<SmolStr>().to_string(),
 | 
			
		||||
            unexpected: type_name::<usize>().to_string(),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TryFrom<&AMindex> for usize {
 | 
			
		||||
    type Error = am::AutomergeError;
 | 
			
		||||
 | 
			
		||||
    fn try_from(item: &AMindex) -> Result<Self, Self::Error> {
 | 
			
		||||
        use am::AutomergeError::InvalidValueType;
 | 
			
		||||
        use AMindex::*;
 | 
			
		||||
 | 
			
		||||
        if let Pos(pos) = item {
 | 
			
		||||
            return Ok(*pos);
 | 
			
		||||
        }
 | 
			
		||||
        Err(InvalidValueType {
 | 
			
		||||
            expected: type_name::<usize>().to_string(),
 | 
			
		||||
            unexpected: type_name::<SmolStr>().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,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
				
			
			@ -1,401 +0,0 @@
 | 
			
		|||
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<T>()` 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::<Detail>();
 | 
			
		||||
 | 
			
		||||
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<Detail> 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<am::Change> {
 | 
			
		||||
    type Error = am::AutomergeError;
 | 
			
		||||
 | 
			
		||||
    fn try_from(items: &AMitems<'_>) -> Result<Self, Self::Error> {
 | 
			
		||||
        let mut changes = Vec::<am::Change>::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<am::ChangeHash> {
 | 
			
		||||
    type Error = am::AutomergeError;
 | 
			
		||||
 | 
			
		||||
    fn try_from(items: &AMitems<'_>) -> Result<Self, Self::Error> {
 | 
			
		||||
        let mut change_hashes = Vec::<am::ChangeHash>::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<am::ScalarValue> {
 | 
			
		||||
    type Error = am::AutomergeError;
 | 
			
		||||
 | 
			
		||||
    fn try_from(items: &AMitems<'_>) -> Result<Self, Self::Error> {
 | 
			
		||||
        let mut scalars = Vec::<am::ScalarValue>::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()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +1,11 @@
 | 
			
		|||
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"));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,12 @@
 | 
			
		|||
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() {
 | 
			
		||||
| 
						 | 
				
			
			@ -17,11 +19,12 @@ macro_rules! to_obj_id {
 | 
			
		|||
pub(crate) use to_obj_id;
 | 
			
		||||
 | 
			
		||||
macro_rules! to_obj_type {
 | 
			
		||||
    ($c_obj_type:expr) => {{
 | 
			
		||||
        let result: Result<am::ObjType, am::AutomergeError> = (&$c_obj_type).try_into();
 | 
			
		||||
        match result {
 | 
			
		||||
            Ok(obj_type) => obj_type,
 | 
			
		||||
            Err(e) => return AMresult::error(&e.to_string()).into(),
 | 
			
		||||
    ($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(),
 | 
			
		||||
        }
 | 
			
		||||
    }};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -76,11 +79,11 @@ impl Deref for AMobjId {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMobjId
 | 
			
		||||
/// \brief Gets the actor identifier component of an object identifier.
 | 
			
		||||
/// \brief Gets the actor identifier 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
 | 
			
		||||
| 
						 | 
				
			
			@ -94,11 +97,11 @@ pub unsafe extern "C" fn AMobjIdActorId(obj_id: *const AMobjId) -> *const AMacto
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMobjId
 | 
			
		||||
/// \brief Gets the counter component of an object identifier.
 | 
			
		||||
/// \brief Gets the counter 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
 | 
			
		||||
| 
						 | 
				
			
			@ -121,9 +124,8 @@ 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_id1 `!= NULL`
 | 
			
		||||
/// \post `!(`\p obj_id1 `&&` \p obj_id2 `) -> false`
 | 
			
		||||
/// \pre \p obj_id1 `!= NULL`.
 | 
			
		||||
/// \pre \p obj_id2 `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// #Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -133,28 +135,26 @@ 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, None) | (None, Some(_)) | (Some(_), None) => false,
 | 
			
		||||
        (None, Some(_)) | (Some(_), None) | (None, None) => false,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMobjId
 | 
			
		||||
/// \brief Gets the index component of an object identifier.
 | 
			
		||||
/// \brief Gets the index 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() {
 | 
			
		||||
            Id(_, _, index) => *index,
 | 
			
		||||
            Root => 0,
 | 
			
		||||
            am::ObjId::Id(_, _, index) => *index,
 | 
			
		||||
            am::ObjId::Root => 0,
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        usize::MAX
 | 
			
		||||
| 
						 | 
				
			
			@ -163,54 +163,26 @@ 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 {
 | 
			
		||||
    /// The default tag, not a type signifier.
 | 
			
		||||
    Default = 0,
 | 
			
		||||
    /// A void.
 | 
			
		||||
    /// \note This tag is unalphabetized to evaluate as false.
 | 
			
		||||
    Void = 0,
 | 
			
		||||
    /// A list.
 | 
			
		||||
    List = 1,
 | 
			
		||||
    List,
 | 
			
		||||
    /// A key-value map.
 | 
			
		||||
    Map,
 | 
			
		||||
    /// A list of Unicode graphemes.
 | 
			
		||||
    Text,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for AMobjType {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::Default
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<&am::ObjType> for AMobjType {
 | 
			
		||||
    fn from(o: &am::ObjType) -> Self {
 | 
			
		||||
        use am::ObjType::*;
 | 
			
		||||
 | 
			
		||||
impl From<am::ObjType> for AMobjType {
 | 
			
		||||
    fn from(o: am::ObjType) -> Self {
 | 
			
		||||
        match o {
 | 
			
		||||
            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<Self, Self::Error> {
 | 
			
		||||
        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::<Self>().to_string(),
 | 
			
		||||
                unexpected: type_name::<AMobjType>().to_string(),
 | 
			
		||||
            }),
 | 
			
		||||
            am::ObjType::Map | am::ObjType::Table => AMobjType::Map,
 | 
			
		||||
            am::ObjType::List => AMobjType::List,
 | 
			
		||||
            am::ObjType::Text => AMobjType::Text,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										73
									
								
								rust/automerge-c/src/obj/item.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								rust/automerge-c/src/obj/item.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,73 @@
 | 
			
		|||
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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										341
									
								
								rust/automerge-c/src/obj/items.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								rust/automerge-c/src/obj/items.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,341 @@
 | 
			
		|||
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<T>()` 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::<Detail>();
 | 
			
		||||
 | 
			
		||||
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<Detail> 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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										156
									
								
								rust/automerge-c/src/result_stack.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								rust/automerge-c/src/result_stack.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,156 @@
 | 
			
		|||
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<extern "C" fn(stack: *mut *mut AMresultStack, discriminant: u8) -> ()>;
 | 
			
		||||
 | 
			
		||||
/// \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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										359
									
								
								rust/automerge-c/src/strs.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										359
									
								
								rust/automerge-c/src/strs.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,359 @@
 | 
			
		|||
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<T>()` 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::<Detail>();
 | 
			
		||||
 | 
			
		||||
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<AMbyteSpan> {
 | 
			
		||||
        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<AMbyteSpan> {
 | 
			
		||||
        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<Detail> 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<AMbyteSpan> {
 | 
			
		||||
        let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
 | 
			
		||||
        detail.next(n)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn prev(&mut self, n: isize) -> Option<AMbyteSpan> {
 | 
			
		||||
        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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,23 +1,23 @@
 | 
			
		|||
use automerge as am;
 | 
			
		||||
 | 
			
		||||
use crate::result::{to_result, AMresult};
 | 
			
		||||
use crate::change_hashes::AMchangeHashes;
 | 
			
		||||
 | 
			
		||||
/// \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(am::sync::Have);
 | 
			
		||||
pub struct AMsyncHave(*const 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<am::sync::Have> for AMsyncHave {
 | 
			
		||||
    fn as_ref(&self) -> &am::sync::Have {
 | 
			
		||||
        &self.0
 | 
			
		||||
        unsafe { &*self.0 }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -25,18 +25,17 @@ impl AsRef<am::sync::Have> for AMsyncHave {
 | 
			
		|||
/// \brief Gets the heads of the sender.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] sync_have A pointer to an `AMsyncHave` struct.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \return An `AMchangeHashes` struct.
 | 
			
		||||
/// \pre \p sync_have `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
/// sync_have must be a valid pointer to an AMsyncHave
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
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(),
 | 
			
		||||
    })
 | 
			
		||||
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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										378
									
								
								rust/automerge-c/src/sync/haves.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										378
									
								
								rust/automerge-c/src/sync/haves.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,378 @@
 | 
			
		|||
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<T>()` 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::<Detail>();
 | 
			
		||||
 | 
			
		||||
impl Detail {
 | 
			
		||||
    fn new(
 | 
			
		||||
        haves: &[am::sync::Have],
 | 
			
		||||
        offset: isize,
 | 
			
		||||
        storage: &mut BTreeMap<usize, AMsyncHave>,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        let storage: *mut BTreeMap<usize, AMsyncHave> = 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<usize, AMsyncHave>) };
 | 
			
		||||
        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<usize, AMsyncHave>) };
 | 
			
		||||
        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<Detail> 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<usize, AMsyncHave>) -> 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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,15 +3,18 @@ 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::error("Invalid `AMsyncMessage*`").into(),
 | 
			
		||||
            None => return AMresult::err("Invalid AMsyncMessage pointer").into(),
 | 
			
		||||
        }
 | 
			
		||||
    }};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -48,52 +51,55 @@ impl AsRef<am::sync::Message> for AMsyncMessage {
 | 
			
		|||
/// \brief Gets the changes for the recipient to apply.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \return An `AMchanges` struct.
 | 
			
		||||
/// \pre \p sync_message `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
/// sync_message must be a valid pointer to an AMsyncMessage
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
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(),
 | 
			
		||||
    })
 | 
			
		||||
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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMsyncMessage
 | 
			
		||||
/// \brief Decodes an array of bytes into a synchronization message.
 | 
			
		||||
/// \brief Decodes a sequence of bytes into a synchronization message.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] src A pointer to an array of bytes.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
/// src must be a byte array of length `>= count`
 | 
			
		||||
/// src must be a byte array of size `>= count`
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub unsafe extern "C" fn AMsyncMessageDecode(src: *const u8, count: usize) -> *mut AMresult {
 | 
			
		||||
    let data = std::slice::from_raw_parts(src, count);
 | 
			
		||||
    to_result(am::sync::Message::decode(data))
 | 
			
		||||
    let mut data = Vec::new();
 | 
			
		||||
    data.extend_from_slice(std::slice::from_raw_parts(src, count));
 | 
			
		||||
    to_result(am::sync::Message::decode(&data))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMsyncMessage
 | 
			
		||||
/// \brief Encodes a synchronization message as an array of bytes.
 | 
			
		||||
/// \brief Encodes a synchronization message as a sequence of bytes.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -108,40 +114,41 @@ 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 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.
 | 
			
		||||
/// \return An `AMhaves` struct.
 | 
			
		||||
/// \pre \p sync_message `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
/// sync_message must be a valid pointer to an AMsyncMessage
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
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(),
 | 
			
		||||
    })
 | 
			
		||||
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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMsyncMessage
 | 
			
		||||
/// \brief Gets the heads of the sender.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \return An `AMchangeHashes` struct.
 | 
			
		||||
/// \pre \p sync_message `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
/// sync_message must be a valid pointer to an AMsyncMessage
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
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(),
 | 
			
		||||
    })
 | 
			
		||||
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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMsyncMessage
 | 
			
		||||
| 
						 | 
				
			
			@ -149,18 +156,17 @@ pub unsafe extern "C" fn AMsyncMessageHeads(sync_message: *const AMsyncMessage)
 | 
			
		|||
///        by the recipient.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \return An `AMchangeHashes` struct.
 | 
			
		||||
/// \pre \p sync_message `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
/// sync_message must be a valid pointer to an AMsyncMessage
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
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(),
 | 
			
		||||
    })
 | 
			
		||||
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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,15 +2,17 @@ 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::error("Invalid `AMsyncState*`").into(),
 | 
			
		||||
            None => return AMresult::err("Invalid AMsyncState pointer").into(),
 | 
			
		||||
        }
 | 
			
		||||
    }};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -54,35 +56,36 @@ impl From<AMsyncState> for *mut AMsyncState {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMsyncState
 | 
			
		||||
/// \brief Decodes an array of bytes into a synchronization state.
 | 
			
		||||
/// \brief Decodes a sequence of bytes into a synchronization state.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] src A pointer to an array of bytes.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
/// src must be a byte array of length `>= count`
 | 
			
		||||
/// src must be a byte array of size `>= count`
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub unsafe extern "C" fn AMsyncStateDecode(src: *const u8, count: usize) -> *mut AMresult {
 | 
			
		||||
    let data = std::slice::from_raw_parts(src, count);
 | 
			
		||||
    to_result(am::sync::State::decode(data))
 | 
			
		||||
    let mut data = Vec::new();
 | 
			
		||||
    data.extend_from_slice(std::slice::from_raw_parts(src, count));
 | 
			
		||||
    to_result(am::sync::State::decode(&data))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMsyncState
 | 
			
		||||
/// \brief Encodes a synchronization state as an array of bytes.
 | 
			
		||||
/// \brief Encodes a synchronizaton state as a sequence of bytes.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -99,9 +102,8 @@ 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`
 | 
			
		||||
/// \post `!(`\p sync_state1 `&&` \p sync_state2 `) -> false`
 | 
			
		||||
/// \pre \p sync_state1 `!= NULL`.
 | 
			
		||||
/// \pre \p sync_state2 `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// #Safety
 | 
			
		||||
| 
						 | 
				
			
			@ -114,17 +116,18 @@ 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, None) | (None, Some(_)) | (Some(_), None) => false,
 | 
			
		||||
        (None, Some(_)) | (Some(_), None) | (None, None) => false,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMsyncState
 | 
			
		||||
/// \brief Allocates a new synchronization state and initializes it from
 | 
			
		||||
///        default values.
 | 
			
		||||
/// \brief Allocates a new synchronization state and initializes it with
 | 
			
		||||
///        defaults.
 | 
			
		||||
///
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \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.
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
pub extern "C" fn AMsyncStateInit() -> *mut AMresult {
 | 
			
		||||
    to_result(am::sync::State::new())
 | 
			
		||||
| 
						 | 
				
			
			@ -134,36 +137,40 @@ 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 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.
 | 
			
		||||
/// \return An `AMchangeHashes` struct.
 | 
			
		||||
/// \pre \p sync_state `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
/// sync_state must be a valid pointer to an AMsyncState
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
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())
 | 
			
		||||
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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMsyncState
 | 
			
		||||
/// \brief Gets the heads that were last sent by this peer.
 | 
			
		||||
///
 | 
			
		||||
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
 | 
			
		||||
/// \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.
 | 
			
		||||
/// \return An `AMchangeHashes` struct.
 | 
			
		||||
/// \pre \p sync_state `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
/// sync_state must be a valid pointer to an AMsyncState
 | 
			
		||||
#[no_mangle]
 | 
			
		||||
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())
 | 
			
		||||
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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMsyncState
 | 
			
		||||
| 
						 | 
				
			
			@ -171,13 +178,11 @@ pub unsafe extern "C" fn AMsyncStateLastSentHeads(sync_state: *const AMsyncState
 | 
			
		|||
///
 | 
			
		||||
/// \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 `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
 | 
			
		||||
///             the returned `AMhaves` struct is relevant, `false` otherwise.
 | 
			
		||||
/// \return An `AMhaves` struct.
 | 
			
		||||
/// \pre \p sync_state `!= NULL`.
 | 
			
		||||
/// \pre \p has_value `!= NULL`.
 | 
			
		||||
/// \internal
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
/// sync_state must be a valid pointer to an AMsyncState
 | 
			
		||||
| 
						 | 
				
			
			@ -186,15 +191,15 @@ pub unsafe extern "C" fn AMsyncStateLastSentHeads(sync_state: *const AMsyncState
 | 
			
		|||
pub unsafe extern "C" fn AMsyncStateTheirHaves(
 | 
			
		||||
    sync_state: *const AMsyncState,
 | 
			
		||||
    has_value: *mut bool,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
) -> AMsyncHaves {
 | 
			
		||||
    if let Some(sync_state) = sync_state.as_ref() {
 | 
			
		||||
        if let Some(haves) = &sync_state.as_ref().their_have {
 | 
			
		||||
            *has_value = true;
 | 
			
		||||
            return to_result(haves.as_slice());
 | 
			
		||||
        }
 | 
			
		||||
            return AMsyncHaves::new(haves, &mut sync_state.their_haves_storage.borrow_mut());
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
    *has_value = false;
 | 
			
		||||
    to_result(Vec::<am::sync::Have>::new())
 | 
			
		||||
    Default::default()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMsyncState
 | 
			
		||||
| 
						 | 
				
			
			@ -202,31 +207,29 @@ 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 `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.
 | 
			
		||||
///             the returned `AMchangeHashes` struct is relevant, `false`
 | 
			
		||||
///             otherwise.
 | 
			
		||||
/// \return An `AMchangeHashes` struct.
 | 
			
		||||
/// \pre \p sync_state `!= NULL`.
 | 
			
		||||
/// \pre \p has_value `!= NULL`.
 | 
			
		||||
/// \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,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
) -> AMchangeHashes {
 | 
			
		||||
    if let Some(sync_state) = sync_state.as_ref() {
 | 
			
		||||
        if let Some(change_hashes) = &sync_state.as_ref().their_heads {
 | 
			
		||||
            *has_value = true;
 | 
			
		||||
            return to_result(change_hashes.as_slice());
 | 
			
		||||
            return AMchangeHashes::new(change_hashes);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    *has_value = false;
 | 
			
		||||
    to_result(Vec::<am::ChangeHash>::new())
 | 
			
		||||
    Default::default()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// \memberof AMsyncState
 | 
			
		||||
| 
						 | 
				
			
			@ -234,29 +237,27 @@ 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 `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.
 | 
			
		||||
///             the returned `AMchangeHashes` struct is relevant, `false`
 | 
			
		||||
///             otherwise.
 | 
			
		||||
/// \return An `AMchangeHashes` struct.
 | 
			
		||||
/// \pre \p sync_state `!= NULL`.
 | 
			
		||||
/// \pre \p has_value `!= NULL`.
 | 
			
		||||
/// \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,
 | 
			
		||||
) -> *mut AMresult {
 | 
			
		||||
) -> AMchangeHashes {
 | 
			
		||||
    if let Some(sync_state) = sync_state.as_ref() {
 | 
			
		||||
        if let Some(change_hashes) = &sync_state.as_ref().their_need {
 | 
			
		||||
            *has_value = true;
 | 
			
		||||
            return to_result(change_hashes.as_slice());
 | 
			
		||||
            return AMchangeHashes::new(change_hashes);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    *has_value = false;
 | 
			
		||||
    to_result(Vec::<am::ChangeHash>::new())
 | 
			
		||||
    Default::default()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,33 +0,0 @@
 | 
			
		|||
#include <stdarg.h>
 | 
			
		||||
 | 
			
		||||
#include <automerge-c/utils/result.h>
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,106 +0,0 @@
 | 
			
		|||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
#include <automerge-c/utils/stack.h>
 | 
			
		||||
#include <automerge-c/utils/string.h>
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +0,0 @@
 | 
			
		|||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
#include <automerge-c/utils/stack_callback_data.h>
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,46 +0,0 @@
 | 
			
		|||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <automerge-c/utils/string.h>
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,51 +1,53 @@
 | 
			
		|||
find_package(cmocka CONFIG REQUIRED)
 | 
			
		||||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
 | 
			
		||||
 | 
			
		||||
find_package(cmocka REQUIRED)
 | 
			
		||||
 | 
			
		||||
add_executable(
 | 
			
		||||
    ${LIBRARY_NAME}_test
 | 
			
		||||
    test_${LIBRARY_NAME}
 | 
			
		||||
        actor_id_tests.c
 | 
			
		||||
        base_state.c
 | 
			
		||||
        byte_span_tests.c
 | 
			
		||||
        cmocka_utils.c
 | 
			
		||||
        enum_string_tests.c
 | 
			
		||||
        doc_state.c
 | 
			
		||||
        doc_tests.c
 | 
			
		||||
        item_tests.c
 | 
			
		||||
        group_state.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(${LIBRARY_NAME}_test PROPERTIES LINKER_LANGUAGE C)
 | 
			
		||||
set_target_properties(test_${LIBRARY_NAME} PROPERTIES LINKER_LANGUAGE C)
 | 
			
		||||
 | 
			
		||||
if(WIN32)
 | 
			
		||||
    set(CMOCKA "cmocka::cmocka")
 | 
			
		||||
else()
 | 
			
		||||
    set(CMOCKA "cmocka")
 | 
			
		||||
endif()
 | 
			
		||||
# \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 "$<BUILD_INTERFACE:${CBINDGEN_INCLUDEDIR}>"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(${LIBRARY_NAME}_test PRIVATE ${CMOCKA} ${LIBRARY_NAME})
 | 
			
		||||
target_link_libraries(test_${LIBRARY_NAME} PRIVATE cmocka ${LIBRARY_NAME})
 | 
			
		||||
 | 
			
		||||
add_dependencies(${LIBRARY_NAME}_test ${BINDINGS_NAME}_artifacts)
 | 
			
		||||
add_dependencies(test_${LIBRARY_NAME} ${LIBRARY_NAME}_artifacts)
 | 
			
		||||
 | 
			
		||||
if(BUILD_SHARED_LIBS AND WIN32)
 | 
			
		||||
    add_custom_command(
 | 
			
		||||
        TARGET ${LIBRARY_NAME}_test
 | 
			
		||||
        TARGET test_${LIBRARY_NAME}
 | 
			
		||||
        POST_BUILD
 | 
			
		||||
        COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:${LIBRARY_NAME}> $<TARGET_FILE_DIR:${LIBRARY_NAME}_test>
 | 
			
		||||
        COMMENT "Copying the DLL into the tests directory..."
 | 
			
		||||
        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..."
 | 
			
		||||
        VERBATIM
 | 
			
		||||
    )
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
add_test(NAME ${LIBRARY_NAME}_test COMMAND ${LIBRARY_NAME}_test)
 | 
			
		||||
add_test(NAME test_${LIBRARY_NAME} COMMAND test_${LIBRARY_NAME})
 | 
			
		||||
 | 
			
		||||
add_custom_command(
 | 
			
		||||
    TARGET ${LIBRARY_NAME}_test
 | 
			
		||||
    TARGET test_${LIBRARY_NAME}
 | 
			
		||||
    POST_BUILD
 | 
			
		||||
    COMMAND
 | 
			
		||||
        ${CMAKE_CTEST_COMMAND} --config $<CONFIG> --output-on-failure
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,126 +14,99 @@
 | 
			
		|||
#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;
 | 
			
		||||
    /** The count of bytes in \p src. */
 | 
			
		||||
    size_t count;
 | 
			
		||||
    /** A stack of results. */
 | 
			
		||||
    AMstack* stack;
 | 
			
		||||
    /** An actor ID as a hexadecimal string. */
 | 
			
		||||
    AMbyteSpan str;
 | 
			
		||||
} DocState;
 | 
			
		||||
    size_t count;
 | 
			
		||||
} GroupState;
 | 
			
		||||
 | 
			
		||||
static int group_setup(void** 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;
 | 
			
		||||
    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;
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int group_teardown(void** state) {
 | 
			
		||||
    DocState* doc_state = *state;
 | 
			
		||||
    test_free(doc_state->src);
 | 
			
		||||
    AMstackFree(&doc_state->stack);
 | 
			
		||||
    test_free(doc_state);
 | 
			
		||||
    GroupState* group_state = *state;
 | 
			
		||||
    test_free(group_state->src);
 | 
			
		||||
    test_free(group_state);
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
static void test_AMactorIdInit() {
 | 
			
		||||
    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) {
 | 
			
		||||
        AMresult* result = AMstackResult(stack_ptr, AMactorIdInit(), NULL, NULL);
 | 
			
		||||
        result = AMactorIdInit();
 | 
			
		||||
        if (AMresultStatus(result) != AM_STATUS_OK) {
 | 
			
		||||
            fail_msg_view("%s", AMresultError(result));
 | 
			
		||||
            fail_msg_view("%s", AMerrorMessage(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_true(AMitemToActorId(item, &actor_id));
 | 
			
		||||
        AMbyteSpan const str = AMactorIdStr(actor_id);
 | 
			
		||||
        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);
 | 
			
		||||
        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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,17 +0,0 @@
 | 
			
		|||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
/* 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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,39 +0,0 @@
 | 
			
		|||
#ifndef TESTS_BASE_STATE_H
 | 
			
		||||
#define TESTS_BASE_STATE_H
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
/* local */
 | 
			
		||||
#include <automerge-c/automerge.h>
 | 
			
		||||
#include <automerge-c/utils/stack.h>
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \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 */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,119 +0,0 @@
 | 
			
		|||
#include <setjmp.h>
 | 
			
		||||
#include <stdarg.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
/* third-party */
 | 
			
		||||
#include <cmocka.h>
 | 
			
		||||
 | 
			
		||||
/* local */
 | 
			
		||||
#include <automerge-c/automerge.h>
 | 
			
		||||
#include <automerge-c/utils/string.h>
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,88 +0,0 @@
 | 
			
		|||
#include <setjmp.h>
 | 
			
		||||
#include <stdarg.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
/* third-party */
 | 
			
		||||
#include <automerge-c/utils/enum_string.h>
 | 
			
		||||
#include <automerge-c/utils/stack_callback_data.h>
 | 
			
		||||
#include <automerge-c/utils/string.h>
 | 
			
		||||
#include <cmocka.h>
 | 
			
		||||
 | 
			
		||||
/* 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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,42 +1,22 @@
 | 
			
		|||
#ifndef TESTS_CMOCKA_UTILS_H
 | 
			
		||||
#define TESTS_CMOCKA_UTILS_H
 | 
			
		||||
#ifndef CMOCKA_UTILS_H
 | 
			
		||||
#define CMOCKA_UTILS_H
 | 
			
		||||
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
/* third-party */
 | 
			
		||||
#include <automerge-c/utils/string.h>
 | 
			
		||||
#include <cmocka.h>
 | 
			
		||||
 | 
			
		||||
/* local */
 | 
			
		||||
#include "base_state.h"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \brief Forces the test to fail immediately and quit, printing the reason.
 | 
			
		||||
 *
 | 
			
		||||
 * \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] view A string view as an `AMbyteSpan` struct.
 | 
			
		||||
 */
 | 
			
		||||
#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)
 | 
			
		||||
#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)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \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 */
 | 
			
		||||
#endif  /* CMOCKA_UTILS_H */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,27 +0,0 @@
 | 
			
		|||
#include <setjmp.h>
 | 
			
		||||
#include <stdarg.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
/* third-party */
 | 
			
		||||
#include <cmocka.h>
 | 
			
		||||
 | 
			
		||||
/* local */
 | 
			
		||||
#include <automerge-c/utils/stack_callback_data.h>
 | 
			
		||||
#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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,17 +0,0 @@
 | 
			
		|||
#ifndef TESTS_DOC_STATE_H
 | 
			
		||||
#define TESTS_DOC_STATE_H
 | 
			
		||||
 | 
			
		||||
/* local */
 | 
			
		||||
#include <automerge-c/automerge.h>
 | 
			
		||||
#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 */
 | 
			
		||||
| 
						 | 
				
			
			@ -9,14 +9,12 @@
 | 
			
		|||
 | 
			
		||||
/* local */
 | 
			
		||||
#include <automerge-c/automerge.h>
 | 
			
		||||
#include <automerge-c/utils/stack_callback_data.h>
 | 
			
		||||
#include "base_state.h"
 | 
			
		||||
#include "cmocka_utils.h"
 | 
			
		||||
#include "doc_state.h"
 | 
			
		||||
#include "group_state.h"
 | 
			
		||||
#include "stack_utils.h"
 | 
			
		||||
#include "str_utils.h"
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    DocState* doc_state;
 | 
			
		||||
    GroupState* group_state;
 | 
			
		||||
    AMbyteSpan actor_id_str;
 | 
			
		||||
    uint8_t* actor_id_bytes;
 | 
			
		||||
    size_t actor_id_size;
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +22,7 @@ typedef struct {
 | 
			
		|||
 | 
			
		||||
static int setup(void** state) {
 | 
			
		||||
    TestState* test_state = test_calloc(1, sizeof(TestState));
 | 
			
		||||
    setup_doc((void**)&test_state->doc_state);
 | 
			
		||||
    group_setup((void**)&test_state->group_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;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,195 +34,204 @@ static int setup(void** state) {
 | 
			
		|||
 | 
			
		||||
static int teardown(void** state) {
 | 
			
		||||
    TestState* test_state = *state;
 | 
			
		||||
    teardown_doc((void**)&test_state->doc_state);
 | 
			
		||||
    group_teardown((void**)&test_state->group_state);
 | 
			
		||||
    test_free(test_state->actor_id_bytes);
 | 
			
		||||
    test_free(test_state);
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void test_AMkeys_empty(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));
 | 
			
		||||
    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_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(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);
 | 
			
		||||
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;
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str));
 | 
			
		||||
    AMbyteSpan str = AMstrsNext(&forward, 1);
 | 
			
		||||
    assert_ptr_equal(strstr(str.src, "2@"), str.src);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str));
 | 
			
		||||
    str = AMstrsNext(&forward, 1);
 | 
			
		||||
    assert_ptr_equal(strstr(str.src, "3@"), str.src);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str));
 | 
			
		||||
    str = AMstrsNext(&forward, 1);
 | 
			
		||||
    assert_ptr_equal(strstr(str.src, "4@"), str.src);
 | 
			
		||||
    assert_null(AMitemsNext(&forward, 1));
 | 
			
		||||
    assert_null(AMstrsNext(&forward, 1).src);
 | 
			
		||||
    // /* Forward iterator reverse. */
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str));
 | 
			
		||||
    str = AMstrsPrev(&forward, 1);
 | 
			
		||||
    assert_ptr_equal(strstr(str.src, "4@"), str.src);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str));
 | 
			
		||||
    str = AMstrsPrev(&forward, 1);
 | 
			
		||||
    assert_ptr_equal(strstr(str.src, "3@"), str.src);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str));
 | 
			
		||||
    str = AMstrsPrev(&forward, 1);
 | 
			
		||||
    assert_ptr_equal(strstr(str.src, "2@"), str.src);
 | 
			
		||||
    assert_null(AMitemsPrev(&forward, 1));
 | 
			
		||||
    assert_null(AMstrsPrev(&forward, 1).src);
 | 
			
		||||
    /* Reverse iterator forward. */
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str));
 | 
			
		||||
    str = AMstrsNext(&reverse, 1);
 | 
			
		||||
    assert_ptr_equal(strstr(str.src, "4@"), str.src);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str));
 | 
			
		||||
    str = AMstrsNext(&reverse, 1);
 | 
			
		||||
    assert_ptr_equal(strstr(str.src, "3@"), str.src);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str));
 | 
			
		||||
    str = AMstrsNext(&reverse, 1);
 | 
			
		||||
    assert_ptr_equal(strstr(str.src, "2@"), str.src);
 | 
			
		||||
    assert_null(AMitemsNext(&reverse, 1));
 | 
			
		||||
    assert_null(AMstrsNext(&reverse, 1).src);
 | 
			
		||||
    /* Reverse iterator reverse. */
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str));
 | 
			
		||||
    str = AMstrsPrev(&reverse, 1);
 | 
			
		||||
    assert_ptr_equal(strstr(str.src, "2@"), str.src);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str));
 | 
			
		||||
    str = AMstrsPrev(&reverse, 1);
 | 
			
		||||
    assert_ptr_equal(strstr(str.src, "3@"), str.src);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str));
 | 
			
		||||
    str = AMstrsPrev(&reverse, 1);
 | 
			
		||||
    assert_ptr_equal(strstr(str.src, "4@"), str.src);
 | 
			
		||||
    assert_null(AMitemsPrev(&reverse, 1));
 | 
			
		||||
    assert_null(AMstrsPrev(&reverse, 1).src);
 | 
			
		||||
    AMfreeStack(&stack);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
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;
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str));
 | 
			
		||||
    AMbyteSpan str = AMstrsNext(&forward, 1);
 | 
			
		||||
    assert_int_equal(str.count, 3);
 | 
			
		||||
    assert_memory_equal(str.src, "one", str.count);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str));
 | 
			
		||||
    str = AMstrsNext(&forward, 1);
 | 
			
		||||
    assert_int_equal(str.count, 5);
 | 
			
		||||
    assert_memory_equal(str.src, "three", str.count);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str));
 | 
			
		||||
    str = AMstrsNext(&forward, 1);
 | 
			
		||||
    assert_int_equal(str.count, 3);
 | 
			
		||||
    assert_memory_equal(str.src, "two", str.count);
 | 
			
		||||
    assert_null(AMitemsNext(&forward, 1));
 | 
			
		||||
    assert_null(AMstrsNext(&forward, 1).src);
 | 
			
		||||
    /* Forward iterator reverse. */
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str));
 | 
			
		||||
    str = AMstrsPrev(&forward, 1);
 | 
			
		||||
    assert_int_equal(str.count, 3);
 | 
			
		||||
    assert_memory_equal(str.src, "two", str.count);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str));
 | 
			
		||||
    str = AMstrsPrev(&forward, 1);
 | 
			
		||||
    assert_int_equal(str.count, 5);
 | 
			
		||||
    assert_memory_equal(str.src, "three", str.count);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str));
 | 
			
		||||
    str = AMstrsPrev(&forward, 1);
 | 
			
		||||
    assert_int_equal(str.count, 3);
 | 
			
		||||
    assert_memory_equal(str.src, "one", str.count);
 | 
			
		||||
    assert_null(AMitemsPrev(&forward, 1));
 | 
			
		||||
    assert_null(AMstrsPrev(&forward, 1).src);
 | 
			
		||||
    /* Reverse iterator forward. */
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str));
 | 
			
		||||
    str = AMstrsNext(&reverse, 1);
 | 
			
		||||
    assert_int_equal(str.count, 3);
 | 
			
		||||
    assert_memory_equal(str.src, "two", str.count);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str));
 | 
			
		||||
    str = AMstrsNext(&reverse, 1);
 | 
			
		||||
    assert_int_equal(str.count, 5);
 | 
			
		||||
    assert_memory_equal(str.src, "three", str.count);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str));
 | 
			
		||||
    str = AMstrsNext(&reverse, 1);
 | 
			
		||||
    assert_int_equal(str.count, 3);
 | 
			
		||||
    assert_memory_equal(str.src, "one", str.count);
 | 
			
		||||
    assert_null(AMitemsNext(&reverse, 1));
 | 
			
		||||
    assert_null(AMstrsNext(&reverse, 1).src);
 | 
			
		||||
    /* Reverse iterator reverse. */
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str));
 | 
			
		||||
    str = AMstrsPrev(&reverse, 1);
 | 
			
		||||
    assert_int_equal(str.count, 3);
 | 
			
		||||
    assert_memory_equal(str.src, "one", str.count);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str));
 | 
			
		||||
    str = AMstrsPrev(&reverse, 1);
 | 
			
		||||
    assert_int_equal(str.count, 5);
 | 
			
		||||
    assert_memory_equal(str.src, "three", str.count);
 | 
			
		||||
    assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str));
 | 
			
		||||
    str = AMstrsPrev(&reverse, 1);
 | 
			
		||||
    assert_int_equal(str.count, 3);
 | 
			
		||||
    assert_memory_equal(str.src, "two", str.count);
 | 
			
		||||
    assert_null(AMitemsPrev(&reverse, 1));
 | 
			
		||||
    assert_null(AMstrsPrev(&reverse, 1).src);
 | 
			
		||||
    AMfreeStack(&stack);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void test_AMputActor_bytes(void** state) {
 | 
			
		||||
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));
 | 
			
		||||
    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;
 | 
			
		||||
    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;
 | 
			
		||||
    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));
 | 
			
		||||
    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;
 | 
			
		||||
    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(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);
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int run_doc_tests(void) {
 | 
			
		||||
    const struct CMUnitTest tests[] = {
 | 
			
		||||
        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(test_AMkeys_empty),
 | 
			
		||||
        cmocka_unit_test(test_AMkeys_list),
 | 
			
		||||
        cmocka_unit_test(test_AMkeys_map),
 | 
			
		||||
        cmocka_unit_test_setup_teardown(test_AMputActor_bytes, setup, teardown),
 | 
			
		||||
        cmocka_unit_test_setup_teardown(test_AMputActor_str, setup, teardown),
 | 
			
		||||
        cmocka_unit_test_setup_teardown(test_AMspliceText, setup, teardown),
 | 
			
		||||
        cmocka_unit_test(test_AMspliceText),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return cmocka_run_group_tests(tests, NULL, NULL);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,148 +0,0 @@
 | 
			
		|||
#include <setjmp.h>
 | 
			
		||||
#include <stdarg.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
/* third-party */
 | 
			
		||||
#include <cmocka.h>
 | 
			
		||||
 | 
			
		||||
/* local */
 | 
			
		||||
#include <automerge-c/automerge.h>
 | 
			
		||||
#include <automerge-c/utils/enum_string.h>
 | 
			
		||||
 | 
			
		||||
#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);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								rust/automerge-c/test/group_state.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								rust/automerge-c/test/group_state.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
#include <setjmp.h>
 | 
			
		||||
#include <stdarg.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
/* third-party */
 | 
			
		||||
#include <cmocka.h>
 | 
			
		||||
 | 
			
		||||
/* 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								rust/automerge-c/test/group_state.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								rust/automerge-c/test/group_state.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
#ifndef GROUP_STATE_H
 | 
			
		||||
#define GROUP_STATE_H
 | 
			
		||||
 | 
			
		||||
/* local */
 | 
			
		||||
#include <automerge-c/automerge.h>
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    AMresultStack* stack;
 | 
			
		||||
    AMdoc* doc;
 | 
			
		||||
} GroupState;
 | 
			
		||||
 | 
			
		||||
int group_setup(void** state);
 | 
			
		||||
 | 
			
		||||
int group_teardown(void** state);
 | 
			
		||||
 | 
			
		||||
#endif  /* GROUP_STATE_H */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,94 +0,0 @@
 | 
			
		|||
#include <setjmp.h>
 | 
			
		||||
#include <stdarg.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
/* third-party */
 | 
			
		||||
#include <cmocka.h>
 | 
			
		||||
 | 
			
		||||
/* local */
 | 
			
		||||
#include <automerge-c/automerge.h>
 | 
			
		||||
#include <automerge-c/utils/stack_callback_data.h>
 | 
			
		||||
#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);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -11,417 +11,367 @@
 | 
			
		|||
 | 
			
		||||
/* local */
 | 
			
		||||
#include <automerge-c/automerge.h>
 | 
			
		||||
#include <automerge-c/utils/stack_callback_data.h>
 | 
			
		||||
#include "base_state.h"
 | 
			
		||||
#include "cmocka_utils.h"
 | 
			
		||||
#include "doc_state.h"
 | 
			
		||||
#include "group_state.h"
 | 
			
		||||
#include "macro_utils.h"
 | 
			
		||||
#include "stack_utils.h"
 | 
			
		||||
 | 
			
		||||
static void test_AMlistIncrement(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, 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));
 | 
			
		||||
    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));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#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, 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 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 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);                                        \
 | 
			
		||||
                                                                                                                       \
 | 
			
		||||
        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 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 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) {                                                    \
 | 
			
		||||
        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 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 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) {                                            \
 | 
			
		||||
        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 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 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) {                                                            \
 | 
			
		||||
        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));                                                                  \
 | 
			
		||||
    }
 | 
			
		||||
#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));                                            \
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static_void_test_AMlistPut(Bool, insert, bool, true);
 | 
			
		||||
static_void_test_AMlistPut(Bool, insert, boolean, true)
 | 
			
		||||
 | 
			
		||||
static_void_test_AMlistPut(Bool, update, bool, true);
 | 
			
		||||
static_void_test_AMlistPut(Bool, update, boolean, 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, int64_t, INT64_MAX);
 | 
			
		||||
static_void_test_AMlistPut(Counter, insert, counter, INT64_MAX)
 | 
			
		||||
 | 
			
		||||
static_void_test_AMlistPut(Counter, update, int64_t, INT64_MAX);
 | 
			
		||||
static_void_test_AMlistPut(Counter, update, counter, INT64_MAX)
 | 
			
		||||
 | 
			
		||||
static_void_test_AMlistPut(F64, insert, double, DBL_MAX);
 | 
			
		||||
static_void_test_AMlistPut(F64, insert, f64, DBL_MAX)
 | 
			
		||||
 | 
			
		||||
static_void_test_AMlistPut(F64, update, double, DBL_MAX);
 | 
			
		||||
static_void_test_AMlistPut(F64, update, f64, DBL_MAX)
 | 
			
		||||
 | 
			
		||||
static_void_test_AMlistPut(Int, insert, int64_t, INT64_MAX);
 | 
			
		||||
static_void_test_AMlistPut(Int, insert, int_, INT64_MAX)
 | 
			
		||||
 | 
			
		||||
static_void_test_AMlistPut(Int, update, int64_t, INT64_MAX);
 | 
			
		||||
static_void_test_AMlistPut(Int, update, int_, 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_AMlistPutStr(insert,
 | 
			
		||||
                              "Hello, "
 | 
			
		||||
                              "world!");
 | 
			
		||||
static_void_test_AMlistPutObject(Void, insert)
 | 
			
		||||
 | 
			
		||||
static_void_test_AMlistPutStr(update,
 | 
			
		||||
                              "Hello,"
 | 
			
		||||
                              " world"
 | 
			
		||||
                              "!");
 | 
			
		||||
static_void_test_AMlistPutObject(Void, update)
 | 
			
		||||
 | 
			
		||||
static_void_test_AMlistPut(Timestamp, insert, int64_t, INT64_MAX);
 | 
			
		||||
static_void_test_AMlistPutStr(insert, "Hello, world!")
 | 
			
		||||
 | 
			
		||||
static_void_test_AMlistPut(Timestamp, update, int64_t, INT64_MAX);
 | 
			
		||||
static_void_test_AMlistPutStr(update, "Hello, world!")
 | 
			
		||||
 | 
			
		||||
static_void_test_AMlistPut(Uint, insert, uint64_t, UINT64_MAX);
 | 
			
		||||
static_void_test_AMlistPut(Timestamp, insert, timestamp, INT64_MAX)
 | 
			
		||||
 | 
			
		||||
static_void_test_AMlistPut(Uint, update, uint64_t, UINT64_MAX);
 | 
			
		||||
static_void_test_AMlistPut(Timestamp, update, timestamp, INT64_MAX)
 | 
			
		||||
 | 
			
		||||
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)));
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
    /* Insert elements. */
 | 
			
		||||
    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));
 | 
			
		||||
    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));
 | 
			
		||||
 | 
			
		||||
    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));
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
    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(doc1, list, 2, false, AMstr("Third V2")));
 | 
			
		||||
    AMfree(AMcommit(doc1, 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(AMlistPutStr(doc2, list, 2, false, AMstr("Third V3")));
 | 
			
		||||
    AMfree(AMcommit(doc2, AMstr(NULL), NULL));
 | 
			
		||||
 | 
			
		||||
    AMstackItem(NULL, AMmerge(doc1, doc2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH));
 | 
			
		||||
    AMfree(AMmerge(doc1, doc2));
 | 
			
		||||
 | 
			
		||||
    /* 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);
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
    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));
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* 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 = 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);
 | 
			
		||||
 | 
			
		||||
    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 = 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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* 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, 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: 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 = 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);
 | 
			
		||||
 | 
			
		||||
    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 = 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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* 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, 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 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)));
 | 
			
		||||
    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)));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \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) {
 | 
			
		||||
    /*
 | 
			
		||||
| 
						 | 
				
			
			@ -431,52 +381,60 @@ 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);
 | 
			
		||||
 | 
			
		||||
    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));
 | 
			
		||||
    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;
 | 
			
		||||
    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) {
 | 
			
		||||
    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)));
 | 
			
		||||
    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;
 | 
			
		||||
    /* Insert both at the same index. */
 | 
			
		||||
    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));
 | 
			
		||||
    AMfree(AMlistPutUint(doc, list, 0, true, 0));
 | 
			
		||||
    AMfree(AMlistPutUint(doc, list, 0, true, 1));
 | 
			
		||||
 | 
			
		||||
    assert_int_equal(AMobjSize(doc, list, NULL), 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);
 | 
			
		||||
    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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int run_list_tests(void) {
 | 
			
		||||
| 
						 | 
				
			
			@ -500,16 +458,18 @@ 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_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),
 | 
			
		||||
        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),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return cmocka_run_group_tests(tests, setup_doc, teardown_doc);
 | 
			
		||||
    return cmocka_run_group_tests(tests, group_setup, group_teardown);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,36 +3,23 @@
 | 
			
		|||
/* local */
 | 
			
		||||
#include "macro_utils.h"
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,23 +1,24 @@
 | 
			
		|||
#ifndef TESTS_MACRO_UTILS_H
 | 
			
		||||
#define TESTS_MACRO_UTILS_H
 | 
			
		||||
#ifndef MACRO_UTILS_H
 | 
			
		||||
#define MACRO_UTILS_H
 | 
			
		||||
 | 
			
		||||
/* local */
 | 
			
		||||
#include <automerge-c/automerge.h>
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \brief Gets the object type tag corresponding to an object type suffix.
 | 
			
		||||
 * \brief Gets the result value discriminant corresponding to a function name
 | 
			
		||||
 *        suffix.
 | 
			
		||||
 *
 | 
			
		||||
 * \param[in] suffix An object type suffix string.
 | 
			
		||||
 * \return An `AMobjType` enum tag.
 | 
			
		||||
 * \param[in] suffix A string.
 | 
			
		||||
 * \return An `AMvalue` struct discriminant.
 | 
			
		||||
 */
 | 
			
		||||
AMobjType suffix_to_obj_type(char const* suffix);
 | 
			
		||||
AMvalueVariant AMvalue_discriminant(char const* suffix);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \brief Gets the value type tag corresponding to a value type suffix.
 | 
			
		||||
 * \brief Gets the object type tag corresponding to an object type label.
 | 
			
		||||
 *
 | 
			
		||||
 * \param[in] suffix A value type suffix string.
 | 
			
		||||
 * \return An `AMvalType` enum tag.
 | 
			
		||||
 * \param[in] obj_type_label A string.
 | 
			
		||||
 * \return An `AMobjType` enum tag.
 | 
			
		||||
 */
 | 
			
		||||
AMvalType suffix_to_val_type(char const* suffix);
 | 
			
		||||
AMobjType AMobjType_tag(char const* obj_type_label);
 | 
			
		||||
 | 
			
		||||
#endif /* TESTS_MACRO_UTILS_H */
 | 
			
		||||
#endif  /* MACRO_UTILS_H */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
#include <setjmp.h>
 | 
			
		||||
#include <stdarg.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <setjmp.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
/* third-party */
 | 
			
		||||
| 
						 | 
				
			
			@ -8,14 +8,8 @@
 | 
			
		|||
 | 
			
		||||
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);
 | 
			
		||||
| 
						 | 
				
			
			@ -23,6 +17,11 @@ extern int run_map_tests(void);
 | 
			
		|||
extern int run_ported_wasm_suite(void);
 | 
			
		||||
 | 
			
		||||
int main(void) {
 | 
			
		||||
    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());
 | 
			
		||||
    return (
 | 
			
		||||
        run_actor_id_tests() +
 | 
			
		||||
        run_doc_tests() +
 | 
			
		||||
        run_list_tests() +
 | 
			
		||||
        run_map_tests() +
 | 
			
		||||
        run_ported_wasm_suite()
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
#include <setjmp.h>
 | 
			
		||||
#include <stdarg.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <setjmp.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
/* third-party */
 | 
			
		||||
| 
						 | 
				
			
			@ -11,5 +11,8 @@ 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()
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										31
									
								
								rust/automerge-c/test/stack_utils.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								rust/automerge-c/test/stack_utils.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,31 @@
 | 
			
		|||
#include <setjmp.h>
 | 
			
		||||
#include <stdarg.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
 | 
			
		||||
/* third-party */
 | 
			
		||||
#include <cmocka.h>
 | 
			
		||||
 | 
			
		||||
/* 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								rust/automerge-c/test/stack_utils.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								rust/automerge-c/test/stack_utils.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
#ifndef STACK_UTILS_H
 | 
			
		||||
#define STACK_UTILS_H
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
/* local */
 | 
			
		||||
#include <automerge-c/automerge.h>
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \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 */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
#include <stdint.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
/* local */
 | 
			
		||||
#include "str_utils.h"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,17 +1,14 @@
 | 
			
		|||
#ifndef TESTS_STR_UTILS_H
 | 
			
		||||
#define TESTS_STR_UTILS_H
 | 
			
		||||
#ifndef STR_UTILS_H
 | 
			
		||||
#define STR_UTILS_H
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \brief Converts a hexadecimal string into an array of bytes.
 | 
			
		||||
 * \brief Converts a hexadecimal string into a sequence of bytes.
 | 
			
		||||
 *
 | 
			
		||||
 * \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 `)`
 | 
			
		||||
 * \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.
 | 
			
		||||
 */
 | 
			
		||||
void hex_to_bytes(char const* hex_str, uint8_t* src, size_t const count);
 | 
			
		||||
 | 
			
		||||
#endif /* TESTS_STR_UTILS_H */
 | 
			
		||||
#endif  /* STR_UTILS_H */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										857
									
								
								rust/automerge-cli/Cargo.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										857
									
								
								rust/automerge-cli/Cargo.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,857 @@
 | 
			
		|||
# This file is automatically @generated by Cargo.
 | 
			
		||||
# It is not intended for manual editing.
 | 
			
		||||
version = 3
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "adler"
 | 
			
		||||
version = "1.0.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "ansi_term"
 | 
			
		||||
version = "0.12.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "winapi",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "anyhow"
 | 
			
		||||
version = "1.0.55"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "atty"
 | 
			
		||||
version = "0.2.14"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "hermit-abi",
 | 
			
		||||
 "libc",
 | 
			
		||||
 "winapi",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "autocfg"
 | 
			
		||||
version = "1.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "automerge"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "flate2",
 | 
			
		||||
 "fxhash",
 | 
			
		||||
 "hex",
 | 
			
		||||
 "itertools",
 | 
			
		||||
 "js-sys",
 | 
			
		||||
 "leb128",
 | 
			
		||||
 "nonzero_ext",
 | 
			
		||||
 "rand",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "sha2",
 | 
			
		||||
 "smol_str",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
 "tinyvec",
 | 
			
		||||
 "tracing",
 | 
			
		||||
 "unicode-segmentation",
 | 
			
		||||
 "uuid",
 | 
			
		||||
 "wasm-bindgen",
 | 
			
		||||
 "web-sys",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "automerge-cli"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "atty",
 | 
			
		||||
 "automerge",
 | 
			
		||||
 "clap",
 | 
			
		||||
 "colored_json",
 | 
			
		||||
 "combine",
 | 
			
		||||
 "duct",
 | 
			
		||||
 "maplit",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
 "tracing-subscriber",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bitflags"
 | 
			
		||||
version = "1.3.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "block-buffer"
 | 
			
		||||
version = "0.10.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "generic-array",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bumpalo"
 | 
			
		||||
version = "3.9.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "byteorder"
 | 
			
		||||
version = "1.4.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bytes"
 | 
			
		||||
version = "1.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cfg-if"
 | 
			
		||||
version = "1.0.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "clap"
 | 
			
		||||
version = "3.1.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "atty",
 | 
			
		||||
 "bitflags",
 | 
			
		||||
 "clap_derive",
 | 
			
		||||
 "indexmap",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "os_str_bytes",
 | 
			
		||||
 "strsim",
 | 
			
		||||
 "termcolor",
 | 
			
		||||
 "textwrap",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "clap_derive"
 | 
			
		||||
version = "3.1.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "heck",
 | 
			
		||||
 "proc-macro-error",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "colored_json"
 | 
			
		||||
version = "2.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1fd32eb54d016e203b7c2600e3a7802c75843a92e38ccc4869aefeca21771a64"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "ansi_term",
 | 
			
		||||
 "atty",
 | 
			
		||||
 "libc",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "combine"
 | 
			
		||||
version = "4.6.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bytes",
 | 
			
		||||
 "memchr",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cpufeatures"
 | 
			
		||||
version = "0.2.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "libc",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crc32fast"
 | 
			
		||||
version = "1.3.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crypto-common"
 | 
			
		||||
version = "0.1.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "generic-array",
 | 
			
		||||
 "typenum",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "digest"
 | 
			
		||||
version = "0.10.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "block-buffer",
 | 
			
		||||
 "crypto-common",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "duct"
 | 
			
		||||
version = "0.13.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0fc6a0a59ed0888e0041cf708e66357b7ae1a82f1c67247e1f93b5e0818f7d8d"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "libc",
 | 
			
		||||
 "once_cell",
 | 
			
		||||
 "os_pipe",
 | 
			
		||||
 "shared_child",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "either"
 | 
			
		||||
version = "1.6.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "flate2"
 | 
			
		||||
version = "1.0.22"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "crc32fast",
 | 
			
		||||
 "libc",
 | 
			
		||||
 "miniz_oxide",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "fxhash"
 | 
			
		||||
version = "0.2.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "byteorder",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "generic-array"
 | 
			
		||||
version = "0.14.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "typenum",
 | 
			
		||||
 "version_check",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "getrandom"
 | 
			
		||||
version = "0.2.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "js-sys",
 | 
			
		||||
 "libc",
 | 
			
		||||
 "wasi",
 | 
			
		||||
 "wasm-bindgen",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "hashbrown"
 | 
			
		||||
version = "0.11.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "heck"
 | 
			
		||||
version = "0.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "hermit-abi"
 | 
			
		||||
version = "0.1.19"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "libc",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "hex"
 | 
			
		||||
version = "0.4.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "indexmap"
 | 
			
		||||
version = "1.8.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "autocfg",
 | 
			
		||||
 "hashbrown",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "itertools"
 | 
			
		||||
version = "0.10.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "either",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "itoa"
 | 
			
		||||
version = "1.0.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "js-sys"
 | 
			
		||||
version = "0.3.56"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "wasm-bindgen",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "lazy_static"
 | 
			
		||||
version = "1.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "leb128"
 | 
			
		||||
version = "0.2.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "libc"
 | 
			
		||||
version = "0.2.119"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "log"
 | 
			
		||||
version = "0.4.14"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "maplit"
 | 
			
		||||
version = "1.0.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "memchr"
 | 
			
		||||
version = "2.4.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "miniz_oxide"
 | 
			
		||||
version = "0.4.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "adler",
 | 
			
		||||
 "autocfg",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "nonzero_ext"
 | 
			
		||||
version = "0.2.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "44a1290799eababa63ea60af0cbc3f03363e328e58f32fb0294798ed3e85f444"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "once_cell"
 | 
			
		||||
version = "1.9.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "os_pipe"
 | 
			
		||||
version = "0.9.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "libc",
 | 
			
		||||
 "winapi",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "os_str_bytes"
 | 
			
		||||
version = "6.0.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "memchr",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "pin-project-lite"
 | 
			
		||||
version = "0.2.8"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "ppv-lite86"
 | 
			
		||||
version = "0.2.16"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "proc-macro-error"
 | 
			
		||||
version = "1.0.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro-error-attr",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn",
 | 
			
		||||
 "version_check",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "proc-macro-error-attr"
 | 
			
		||||
version = "1.0.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "version_check",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "proc-macro2"
 | 
			
		||||
version = "1.0.36"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "unicode-xid",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "quote"
 | 
			
		||||
version = "1.0.15"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rand"
 | 
			
		||||
version = "0.8.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "libc",
 | 
			
		||||
 "rand_chacha",
 | 
			
		||||
 "rand_core",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rand_chacha"
 | 
			
		||||
version = "0.3.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "ppv-lite86",
 | 
			
		||||
 "rand_core",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rand_core"
 | 
			
		||||
version = "0.6.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "getrandom",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "ryu"
 | 
			
		||||
version = "1.0.9"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde"
 | 
			
		||||
version = "1.0.136"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "serde_derive",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde_derive"
 | 
			
		||||
version = "1.0.136"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde_json"
 | 
			
		||||
version = "1.0.79"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "itoa",
 | 
			
		||||
 "ryu",
 | 
			
		||||
 "serde",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "sha2"
 | 
			
		||||
version = "0.10.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "cpufeatures",
 | 
			
		||||
 "digest",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "sharded-slab"
 | 
			
		||||
version = "0.1.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "shared_child"
 | 
			
		||||
version = "0.3.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "6be9f7d5565b1483af3e72975e2dee33879b3b86bd48c0929fccf6585d79e65a"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "libc",
 | 
			
		||||
 "winapi",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "smallvec"
 | 
			
		||||
version = "1.8.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "smol_str"
 | 
			
		||||
version = "0.1.21"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "61d15c83e300cce35b7c8cd39ff567c1ef42dde6d4a1a38dbdbf9a59902261bd"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "serde",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "strsim"
 | 
			
		||||
version = "0.10.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "syn"
 | 
			
		||||
version = "1.0.86"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "unicode-xid",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "termcolor"
 | 
			
		||||
version = "1.1.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "winapi-util",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "textwrap"
 | 
			
		||||
version = "0.15.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "thiserror"
 | 
			
		||||
version = "1.0.30"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "thiserror-impl",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "thiserror-impl"
 | 
			
		||||
version = "1.0.30"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "thread_local"
 | 
			
		||||
version = "1.1.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "once_cell",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tinyvec"
 | 
			
		||||
version = "1.5.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "tinyvec_macros",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tinyvec_macros"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tracing"
 | 
			
		||||
version = "0.1.31"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "log",
 | 
			
		||||
 "pin-project-lite",
 | 
			
		||||
 "tracing-attributes",
 | 
			
		||||
 "tracing-core",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tracing-attributes"
 | 
			
		||||
version = "0.1.19"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tracing-core"
 | 
			
		||||
version = "0.1.22"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "valuable",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tracing-log"
 | 
			
		||||
version = "0.1.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "log",
 | 
			
		||||
 "tracing-core",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tracing-subscriber"
 | 
			
		||||
version = "0.3.9"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "ansi_term",
 | 
			
		||||
 "sharded-slab",
 | 
			
		||||
 "smallvec",
 | 
			
		||||
 "thread_local",
 | 
			
		||||
 "tracing-core",
 | 
			
		||||
 "tracing-log",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "typenum"
 | 
			
		||||
version = "1.15.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "unicode-segmentation"
 | 
			
		||||
version = "1.9.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "unicode-xid"
 | 
			
		||||
version = "0.2.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "uuid"
 | 
			
		||||
version = "0.8.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "getrandom",
 | 
			
		||||
 "serde",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "valuable"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "version_check"
 | 
			
		||||
version = "0.9.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "wasi"
 | 
			
		||||
version = "0.10.2+wasi-snapshot-preview1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "wasm-bindgen"
 | 
			
		||||
version = "0.2.79"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "wasm-bindgen-macro",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "wasm-bindgen-backend"
 | 
			
		||||
version = "0.2.79"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bumpalo",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "log",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn",
 | 
			
		||||
 "wasm-bindgen-shared",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "wasm-bindgen-macro"
 | 
			
		||||
version = "0.2.79"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "quote",
 | 
			
		||||
 "wasm-bindgen-macro-support",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "wasm-bindgen-macro-support"
 | 
			
		||||
version = "0.2.79"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn",
 | 
			
		||||
 "wasm-bindgen-backend",
 | 
			
		||||
 "wasm-bindgen-shared",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "wasm-bindgen-shared"
 | 
			
		||||
version = "0.2.79"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "web-sys"
 | 
			
		||||
version = "0.3.56"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "js-sys",
 | 
			
		||||
 "wasm-bindgen",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "winapi"
 | 
			
		||||
version = "0.3.9"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "winapi-i686-pc-windows-gnu",
 | 
			
		||||
 "winapi-x86_64-pc-windows-gnu",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "winapi-i686-pc-windows-gnu"
 | 
			
		||||
version = "0.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "winapi-util"
 | 
			
		||||
version = "0.1.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "winapi",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "winapi-x86_64-pc-windows-gnu"
 | 
			
		||||
version = "0.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue