Compare commits

..

No commits in common. "main" and "faster_sync" have entirely different histories.

196 changed files with 11208 additions and 13701 deletions

View file

@ -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

View file

@ -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

View file

@ -54,7 +54,6 @@
nodejs
yarn
deno
# c deps
cmake

View file

@ -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: "^_",
},
],
},
}

View file

@ -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"

View file

@ -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"
}
}

View file

@ -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
}

View file

@ -100,7 +100,7 @@ export function getWriteableCounter(
path: Prop[],
objectId: ObjID,
key: Prop
): WriteableCounter {
) {
return new WriteableCounter(value, context, path, objectId, key)
}

View file

@ -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]
}
}

View file

@ -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")
}
}

View file

@ -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 */

View file

@ -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)
}

View file

@ -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

View file

@ -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)
}

View file

@ -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

View file

@ -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] })

View file

@ -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" },
])
})

View file

@ -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"])
})
})

View file

@ -10,8 +10,13 @@ members = [
resolver = "2"
[profile.release]
debug = true
lto = true
codegen-units = 1
opt-level = 3
[profile.bench]
debug = true
[profile.release.package.automerge-wasm]
debug = false
opt-level = 3

View file

@ -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
...

View file

@ -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

View file

@ -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}
)

View file

@ -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

View file

@ -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!

View file

@ -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

View file

@ -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"

View file

@ -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"]

View file

@ -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 */

View file

@ -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()

View file

@ -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.")

View file

@ -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.")

View file

@ -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()

View file

@ -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

View file

@ -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
```

View file

@ -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;
}

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -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 */

View file

@ -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 */

View file

@ -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 */

View file

@ -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 */

View 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()

View file

@ -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

View file

@ -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 }
}

View file

@ -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())),
)
}

View 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()
}
}

View 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

View file

@ -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())),
}
}

View 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
}
}

View 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()
}
}

View file

@ -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))
}

View 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
}
}

View 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()
}
}

View file

@ -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;

View file

@ -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

View file

@ -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()
}

View file

@ -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"));

View file

@ -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,
}
}
}

View 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
}
}

View 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

View 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
}

View 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()
}
}

View file

@ -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;

View file

@ -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()
}
}

View 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()
}
}

View file

@ -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()
}
}

View file

@ -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()
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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

View file

@ -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);

View file

@ -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;
}

View file

@ -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 */

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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 */

View file

@ -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;
}

View file

@ -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 */

View file

@ -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);

View file

@ -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);
}

View 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;
}

View 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 */

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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 */

View file

@ -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

View file

@ -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

View 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;
}

View 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 */

View file

@ -1,5 +1,5 @@
#include <stdint.h>
#include <stdio.h>
#include <stdint.h>
/* local */
#include "str_utils.h"

View file

@ -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
View 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