Compare commits
No commits in common. "main" and "faster_sync" have entirely different histories.
main
...
faster_syn
196 changed files with 11208 additions and 13701 deletions
18
.github/workflows/ci.yaml
vendored
18
.github/workflows/ci.yaml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.67.0
|
||||
toolchain: 1.66.0
|
||||
default: true
|
||||
components: rustfmt
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
|
@ -28,7 +28,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.67.0
|
||||
toolchain: 1.66.0
|
||||
default: true
|
||||
components: clippy
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
|
@ -42,7 +42,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.67.0
|
||||
toolchain: 1.66.0
|
||||
default: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Build rust docs
|
||||
|
@ -118,7 +118,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly-2023-01-26
|
||||
toolchain: 1.66.0
|
||||
default: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install CMocka
|
||||
|
@ -127,8 +127,6 @@ jobs:
|
|||
uses: jwlawson/actions-setup-cmake@v1.12
|
||||
with:
|
||||
cmake-version: latest
|
||||
- name: Install rust-src
|
||||
run: rustup component add rust-src
|
||||
- name: Build and test C bindings
|
||||
run: ./scripts/ci/cmake-build Release Static
|
||||
shell: bash
|
||||
|
@ -138,7 +136,9 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
toolchain:
|
||||
- 1.67.0
|
||||
- 1.66.0
|
||||
- nightly
|
||||
continue-on-error: ${{ matrix.toolchain == 'nightly' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
|
@ -157,7 +157,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.67.0
|
||||
toolchain: 1.66.0
|
||||
default: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- run: ./scripts/ci/build-test
|
||||
|
@ -170,7 +170,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.67.0
|
||||
toolchain: 1.66.0
|
||||
default: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- run: ./scripts/ci/build-test
|
||||
|
|
18
README.md
18
README.md
|
@ -42,10 +42,9 @@ In general we try and respect semver.
|
|||
|
||||
### JavaScript
|
||||
|
||||
A stable release of the javascript package is currently available as
|
||||
`@automerge/automerge@2.0.0` where. pre-release verisions of the `2.0.1` are
|
||||
available as `2.0.1-alpha.n`. `2.0.1*` packages are also available for Deno at
|
||||
https://deno.land/x/automerge
|
||||
An alpha release of the javascript package is currently available as
|
||||
`@automerge/automerge@2.0.0-alpha.n` where `n` is an integer. We are gathering
|
||||
feedback on the API and looking to release a `2.0.0` in the next few weeks.
|
||||
|
||||
### Rust
|
||||
|
||||
|
@ -53,9 +52,7 @@ The rust codebase is currently oriented around producing a performant backend
|
|||
for the Javascript wrapper and as such the API for Rust code is low level and
|
||||
not well documented. We will be returning to this over the next few months but
|
||||
for now you will need to be comfortable reading the tests and asking questions
|
||||
to figure out how to use it. If you are looking to build rust applications which
|
||||
use automerge you may want to look into
|
||||
[autosurgeon](https://github.com/alexjg/autosurgeon)
|
||||
to figure out how to use it.
|
||||
|
||||
## Repository Organisation
|
||||
|
||||
|
@ -112,16 +109,9 @@ brew install cmake node cmocka
|
|||
# install yarn
|
||||
npm install --global yarn
|
||||
|
||||
# install javascript dependencies
|
||||
yarn --cwd ./javascript
|
||||
|
||||
# install rust dependencies
|
||||
cargo install wasm-bindgen-cli wasm-opt cargo-deny
|
||||
|
||||
# get nightly rust to produce optimized automerge-c builds
|
||||
rustup toolchain install nightly
|
||||
rustup component add rust-src --toolchain nightly
|
||||
|
||||
# add wasm target in addition to current architecture
|
||||
rustup target add wasm32-unknown-unknown
|
||||
|
||||
|
|
|
@ -54,7 +54,6 @@
|
|||
|
||||
nodejs
|
||||
yarn
|
||||
deno
|
||||
|
||||
# c deps
|
||||
cmake
|
||||
|
|
|
@ -3,13 +3,4 @@ module.exports = {
|
|||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["@typescript-eslint"],
|
||||
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -5845,9 +5845,9 @@ json-stable-stringify-without-jsonify@^1.0.1:
|
|||
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
|
||||
|
||||
json5@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
|
||||
integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
|
||||
version "1.0.1"
|
||||
resolved "http://localhost:4873/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
|
||||
integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
|
||||
dependencies:
|
||||
minimist "^1.2.0"
|
||||
|
||||
|
@ -6165,9 +6165,9 @@ minimatch@^5.0.1:
|
|||
brace-expansion "^2.0.1"
|
||||
|
||||
minimist@^1.2.0, minimist@^1.2.6:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
|
||||
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
|
||||
version "1.2.6"
|
||||
resolved "http://localhost:4873/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
mkdirp@~0.5.1:
|
||||
version "0.5.6"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Orion Henry <orion@inkandswitch.com>",
|
||||
"Martin Kleppmann"
|
||||
],
|
||||
"version": "2.0.2",
|
||||
"version": "2.0.1-alpha.5",
|
||||
"description": "Javascript implementation of automerge, backed by @automerge/automerge-wasm",
|
||||
"homepage": "https://github.com/automerge/automerge-rs/tree/main/wrappers/javascript",
|
||||
"repository": "github:automerge/automerge-rs",
|
||||
|
@ -47,7 +47,7 @@
|
|||
"typescript": "^4.9.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@automerge/automerge-wasm": "0.1.25",
|
||||
"@automerge/automerge-wasm": "0.1.22",
|
||||
"uuid": "^9.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
import { Counter, type AutomergeValue } from "./types"
|
||||
import { Text } from "./text"
|
||||
import { type AutomergeValue as UnstableAutomergeValue } from "./unstable_types"
|
||||
import { type Target, Text1Target, Text2Target } from "./proxies"
|
||||
import { mapProxy, listProxy, ValueType } from "./proxies"
|
||||
import type { Prop, ObjID } from "@automerge/automerge-wasm"
|
||||
import { Automerge } from "@automerge/automerge-wasm"
|
||||
|
||||
export type ConflictsF<T extends Target> = { [key: string]: ValueType<T> }
|
||||
export type Conflicts = ConflictsF<Text1Target>
|
||||
export type UnstableConflicts = ConflictsF<Text2Target>
|
||||
|
||||
export function stableConflictAt(
|
||||
context: Automerge,
|
||||
objectId: ObjID,
|
||||
prop: Prop
|
||||
): Conflicts | undefined {
|
||||
return conflictAt<Text1Target>(
|
||||
context,
|
||||
objectId,
|
||||
prop,
|
||||
true,
|
||||
(context: Automerge, conflictId: ObjID): AutomergeValue => {
|
||||
return new Text(context.text(conflictId))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export function unstableConflictAt(
|
||||
context: Automerge,
|
||||
objectId: ObjID,
|
||||
prop: Prop
|
||||
): UnstableConflicts | undefined {
|
||||
return conflictAt<Text2Target>(
|
||||
context,
|
||||
objectId,
|
||||
prop,
|
||||
true,
|
||||
(context: Automerge, conflictId: ObjID): UnstableAutomergeValue => {
|
||||
return context.text(conflictId)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function conflictAt<T extends Target>(
|
||||
context: Automerge,
|
||||
objectId: ObjID,
|
||||
prop: Prop,
|
||||
textV2: boolean,
|
||||
handleText: (a: Automerge, conflictId: ObjID) => ValueType<T>
|
||||
): ConflictsF<T> | undefined {
|
||||
const values = context.getAll(objectId, prop)
|
||||
if (values.length <= 1) {
|
||||
return
|
||||
}
|
||||
const result: ConflictsF<T> = {}
|
||||
for (const fullVal of values) {
|
||||
switch (fullVal[0]) {
|
||||
case "map":
|
||||
result[fullVal[1]] = mapProxy<T>(
|
||||
context,
|
||||
fullVal[1],
|
||||
textV2,
|
||||
[prop],
|
||||
true
|
||||
)
|
||||
break
|
||||
case "list":
|
||||
result[fullVal[1]] = listProxy<T>(
|
||||
context,
|
||||
fullVal[1],
|
||||
textV2,
|
||||
[prop],
|
||||
true
|
||||
)
|
||||
break
|
||||
case "text":
|
||||
result[fullVal[1]] = handleText(context, fullVal[1] as ObjID)
|
||||
break
|
||||
case "str":
|
||||
case "uint":
|
||||
case "int":
|
||||
case "f64":
|
||||
case "boolean":
|
||||
case "bytes":
|
||||
case "null":
|
||||
result[fullVal[2]] = fullVal[1] as ValueType<T>
|
||||
break
|
||||
case "counter":
|
||||
result[fullVal[2]] = new Counter(fullVal[1]) as ValueType<T>
|
||||
break
|
||||
case "timestamp":
|
||||
result[fullVal[2]] = new Date(fullVal[1]) as ValueType<T>
|
||||
break
|
||||
default:
|
||||
throw RangeError(`datatype ${fullVal[0]} unimplemented`)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
|
@ -100,7 +100,7 @@ export function getWriteableCounter(
|
|||
path: Prop[],
|
||||
objectId: ObjID,
|
||||
key: Prop
|
||||
): WriteableCounter {
|
||||
) {
|
||||
return new WriteableCounter(value, context, path, objectId, key)
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ export type { ChangeToEncode } from "@automerge/automerge-wasm"
|
|||
|
||||
export function UseApi(api: API) {
|
||||
for (const k in api) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-extra-semi,@typescript-eslint/no-explicit-any
|
||||
;(ApiHandler as any)[k] = (api as any)[k]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Text } from "./text"
|
||||
import {
|
||||
Automerge,
|
||||
|
@ -7,12 +6,13 @@ import {
|
|||
type Prop,
|
||||
} from "@automerge/automerge-wasm"
|
||||
|
||||
import type { AutomergeValue, ScalarValue, MapValue, ListValue } from "./types"
|
||||
import {
|
||||
type AutomergeValue as UnstableAutomergeValue,
|
||||
MapValue as UnstableMapValue,
|
||||
ListValue as UnstableListValue,
|
||||
} from "./unstable_types"
|
||||
import type {
|
||||
AutomergeValue,
|
||||
ScalarValue,
|
||||
MapValue,
|
||||
ListValue,
|
||||
TextValue,
|
||||
} from "./types"
|
||||
import { Counter, getWriteableCounter } from "./counter"
|
||||
import {
|
||||
STATE,
|
||||
|
@ -26,38 +26,19 @@ import {
|
|||
} from "./constants"
|
||||
import { RawString } from "./raw_string"
|
||||
|
||||
type TargetCommon = {
|
||||
type Target = {
|
||||
context: Automerge
|
||||
objectId: ObjID
|
||||
path: Array<Prop>
|
||||
readonly: boolean
|
||||
heads?: Array<string>
|
||||
cache: object
|
||||
cache: {}
|
||||
trace?: any
|
||||
frozen: boolean
|
||||
textV2: boolean
|
||||
}
|
||||
|
||||
export type Text2Target = TargetCommon & { textV2: true }
|
||||
export type Text1Target = TargetCommon & { textV2: false }
|
||||
export type Target = Text1Target | Text2Target
|
||||
|
||||
export type ValueType<T extends Target> = T extends Text2Target
|
||||
? UnstableAutomergeValue
|
||||
: T extends Text1Target
|
||||
? AutomergeValue
|
||||
: never
|
||||
type MapValueType<T extends Target> = T extends Text2Target
|
||||
? UnstableMapValue
|
||||
: T extends Text1Target
|
||||
? MapValue
|
||||
: never
|
||||
type ListValueType<T extends Target> = T extends Text2Target
|
||||
? UnstableListValue
|
||||
: T extends Text1Target
|
||||
? ListValue
|
||||
: never
|
||||
|
||||
function parseListIndex(key: any) {
|
||||
function parseListIndex(key) {
|
||||
if (typeof key === "string" && /^[0-9]+$/.test(key)) key = parseInt(key, 10)
|
||||
if (typeof key !== "number") {
|
||||
return key
|
||||
|
@ -68,10 +49,7 @@ function parseListIndex(key: any) {
|
|||
return key
|
||||
}
|
||||
|
||||
function valueAt<T extends Target>(
|
||||
target: T,
|
||||
prop: Prop
|
||||
): ValueType<T> | undefined {
|
||||
function valueAt(target: Target, prop: Prop): AutomergeValue | undefined {
|
||||
const { context, objectId, path, readonly, heads, textV2 } = target
|
||||
const value = context.getWithType(objectId, prop, heads)
|
||||
if (value === null) {
|
||||
|
@ -83,7 +61,7 @@ function valueAt<T extends Target>(
|
|||
case undefined:
|
||||
return
|
||||
case "map":
|
||||
return mapProxy<T>(
|
||||
return mapProxy(
|
||||
context,
|
||||
val as ObjID,
|
||||
textV2,
|
||||
|
@ -92,7 +70,7 @@ function valueAt<T extends Target>(
|
|||
heads
|
||||
)
|
||||
case "list":
|
||||
return listProxy<T>(
|
||||
return listProxy(
|
||||
context,
|
||||
val as ObjID,
|
||||
textV2,
|
||||
|
@ -102,7 +80,7 @@ function valueAt<T extends Target>(
|
|||
)
|
||||
case "text":
|
||||
if (textV2) {
|
||||
return context.text(val as ObjID, heads) as ValueType<T>
|
||||
return context.text(val as ObjID, heads)
|
||||
} else {
|
||||
return textProxy(
|
||||
context,
|
||||
|
@ -110,36 +88,29 @@ function valueAt<T extends Target>(
|
|||
[...path, prop],
|
||||
readonly,
|
||||
heads
|
||||
) as unknown as ValueType<T>
|
||||
)
|
||||
}
|
||||
case "str":
|
||||
return val as ValueType<T>
|
||||
return val
|
||||
case "uint":
|
||||
return val as ValueType<T>
|
||||
return val
|
||||
case "int":
|
||||
return val as ValueType<T>
|
||||
return val
|
||||
case "f64":
|
||||
return val as ValueType<T>
|
||||
return val
|
||||
case "boolean":
|
||||
return val as ValueType<T>
|
||||
return val
|
||||
case "null":
|
||||
return null as ValueType<T>
|
||||
return null
|
||||
case "bytes":
|
||||
return val as ValueType<T>
|
||||
return val
|
||||
case "timestamp":
|
||||
return val as ValueType<T>
|
||||
return val
|
||||
case "counter": {
|
||||
if (readonly) {
|
||||
return new Counter(val as number) as ValueType<T>
|
||||
return new Counter(val as number)
|
||||
} else {
|
||||
const counter: Counter = getWriteableCounter(
|
||||
val as number,
|
||||
context,
|
||||
path,
|
||||
objectId,
|
||||
prop
|
||||
)
|
||||
return counter as ValueType<T>
|
||||
return getWriteableCounter(val as number, context, path, objectId, prop)
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
@ -147,21 +118,7 @@ function valueAt<T extends Target>(
|
|||
}
|
||||
}
|
||||
|
||||
type ImportedValue =
|
||||
| [null, "null"]
|
||||
| [number, "uint"]
|
||||
| [number, "int"]
|
||||
| [number, "f64"]
|
||||
| [number, "counter"]
|
||||
| [number, "timestamp"]
|
||||
| [string, "str"]
|
||||
| [Text | string, "text"]
|
||||
| [Uint8Array, "bytes"]
|
||||
| [Array<any>, "list"]
|
||||
| [Record<string, any>, "map"]
|
||||
| [boolean, "boolean"]
|
||||
|
||||
function import_value(value: any, textV2: boolean): ImportedValue {
|
||||
function import_value(value: any, textV2: boolean) {
|
||||
switch (typeof value) {
|
||||
case "object":
|
||||
if (value == null) {
|
||||
|
@ -213,10 +170,7 @@ function import_value(value: any, textV2: boolean): ImportedValue {
|
|||
}
|
||||
|
||||
const MapHandler = {
|
||||
get<T extends Target>(
|
||||
target: T,
|
||||
key: any
|
||||
): ValueType<T> | ObjID | boolean | { handle: Automerge } {
|
||||
get(target: Target, key): AutomergeValue | { handle: Automerge } {
|
||||
const { context, objectId, cache } = target
|
||||
if (key === Symbol.toStringTag) {
|
||||
return target[Symbol.toStringTag]
|
||||
|
@ -231,7 +185,7 @@ const MapHandler = {
|
|||
return cache[key]
|
||||
},
|
||||
|
||||
set(target: Target, key: any, val: any) {
|
||||
set(target: Target, key, val) {
|
||||
const { context, objectId, path, readonly, frozen, textV2 } = target
|
||||
target.cache = {} // reset cache on set
|
||||
if (val && val[OBJECT_ID]) {
|
||||
|
@ -267,10 +221,8 @@ const MapHandler = {
|
|||
}
|
||||
case "text": {
|
||||
if (textV2) {
|
||||
assertString(value)
|
||||
context.putObject(objectId, key, value)
|
||||
} else {
|
||||
assertText(value)
|
||||
const text = context.putObject(objectId, key, "")
|
||||
const proxyText = textProxy(context, text, [...path, key], readonly)
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
|
@ -299,7 +251,7 @@ const MapHandler = {
|
|||
return true
|
||||
},
|
||||
|
||||
deleteProperty(target: Target, key: any) {
|
||||
deleteProperty(target: Target, key) {
|
||||
const { context, objectId, readonly } = target
|
||||
target.cache = {} // reset cache on delete
|
||||
if (readonly) {
|
||||
|
@ -309,12 +261,12 @@ const MapHandler = {
|
|||
return true
|
||||
},
|
||||
|
||||
has(target: Target, key: any) {
|
||||
has(target: Target, key) {
|
||||
const value = this.get(target, key)
|
||||
return value !== undefined
|
||||
},
|
||||
|
||||
getOwnPropertyDescriptor(target: Target, key: any) {
|
||||
getOwnPropertyDescriptor(target: Target, key) {
|
||||
// const { context, objectId } = target
|
||||
const value = this.get(target, key)
|
||||
if (typeof value !== "undefined") {
|
||||
|
@ -335,20 +287,11 @@ const MapHandler = {
|
|||
}
|
||||
|
||||
const ListHandler = {
|
||||
get<T extends Target>(
|
||||
target: T,
|
||||
index: any
|
||||
):
|
||||
| ValueType<T>
|
||||
| boolean
|
||||
| ObjID
|
||||
| { handle: Automerge }
|
||||
| number
|
||||
| ((_: any) => boolean) {
|
||||
get(target: Target, index) {
|
||||
const { context, objectId, heads } = target
|
||||
index = parseListIndex(index)
|
||||
if (index === Symbol.hasInstance) {
|
||||
return (instance: any) => {
|
||||
return instance => {
|
||||
return Array.isArray(instance)
|
||||
}
|
||||
}
|
||||
|
@ -361,13 +304,13 @@ const ListHandler = {
|
|||
if (index === STATE) return { handle: context }
|
||||
if (index === "length") return context.length(objectId, heads)
|
||||
if (typeof index === "number") {
|
||||
return valueAt(target, index) as ValueType<T>
|
||||
return valueAt(target, index)
|
||||
} else {
|
||||
return listMethods(target)[index]
|
||||
}
|
||||
},
|
||||
|
||||
set(target: Target, index: any, val: any) {
|
||||
set(target: Target, index, val) {
|
||||
const { context, objectId, path, readonly, frozen, textV2 } = target
|
||||
index = parseListIndex(index)
|
||||
if (val && val[OBJECT_ID]) {
|
||||
|
@ -391,7 +334,7 @@ const ListHandler = {
|
|||
}
|
||||
switch (datatype) {
|
||||
case "list": {
|
||||
let list: ObjID
|
||||
let list
|
||||
if (index >= context.length(objectId)) {
|
||||
list = context.insertObject(objectId, index, [])
|
||||
} else {
|
||||
|
@ -409,15 +352,13 @@ const ListHandler = {
|
|||
}
|
||||
case "text": {
|
||||
if (textV2) {
|
||||
assertString(value)
|
||||
if (index >= context.length(objectId)) {
|
||||
context.insertObject(objectId, index, value)
|
||||
} else {
|
||||
context.putObject(objectId, index, value)
|
||||
}
|
||||
} else {
|
||||
let text: ObjID
|
||||
assertText(value)
|
||||
let text
|
||||
if (index >= context.length(objectId)) {
|
||||
text = context.insertObject(objectId, index, "")
|
||||
} else {
|
||||
|
@ -429,7 +370,7 @@ const ListHandler = {
|
|||
break
|
||||
}
|
||||
case "map": {
|
||||
let map: ObjID
|
||||
let map
|
||||
if (index >= context.length(objectId)) {
|
||||
map = context.insertObject(objectId, index, {})
|
||||
} else {
|
||||
|
@ -457,7 +398,7 @@ const ListHandler = {
|
|||
return true
|
||||
},
|
||||
|
||||
deleteProperty(target: Target, index: any) {
|
||||
deleteProperty(target: Target, index) {
|
||||
const { context, objectId } = target
|
||||
index = parseListIndex(index)
|
||||
const elem = context.get(objectId, index)
|
||||
|
@ -470,7 +411,7 @@ const ListHandler = {
|
|||
return true
|
||||
},
|
||||
|
||||
has(target: Target, index: any) {
|
||||
has(target: Target, index) {
|
||||
const { context, objectId, heads } = target
|
||||
index = parseListIndex(index)
|
||||
if (typeof index === "number") {
|
||||
|
@ -479,7 +420,7 @@ const ListHandler = {
|
|||
return index === "length"
|
||||
},
|
||||
|
||||
getOwnPropertyDescriptor(target: Target, index: any) {
|
||||
getOwnPropertyDescriptor(target: Target, index) {
|
||||
const { context, objectId, heads } = target
|
||||
|
||||
if (index === "length")
|
||||
|
@ -493,7 +434,7 @@ const ListHandler = {
|
|||
return { configurable: true, enumerable: true, value }
|
||||
},
|
||||
|
||||
getPrototypeOf(target: Target) {
|
||||
getPrototypeOf(target) {
|
||||
return Object.getPrototypeOf(target)
|
||||
},
|
||||
ownKeys(/*target*/): string[] {
|
||||
|
@ -535,14 +476,14 @@ const TextHandler = Object.assign({}, ListHandler, {
|
|||
},
|
||||
})
|
||||
|
||||
export function mapProxy<T extends Target>(
|
||||
export function mapProxy(
|
||||
context: Automerge,
|
||||
objectId: ObjID,
|
||||
textV2: boolean,
|
||||
path?: Prop[],
|
||||
readonly?: boolean,
|
||||
heads?: Heads
|
||||
): MapValueType<T> {
|
||||
): MapValue {
|
||||
const target: Target = {
|
||||
context,
|
||||
objectId,
|
||||
|
@ -555,19 +496,19 @@ export function mapProxy<T extends Target>(
|
|||
}
|
||||
const proxied = {}
|
||||
Object.assign(proxied, target)
|
||||
const result = new Proxy(proxied, MapHandler)
|
||||
let result = new Proxy(proxied, MapHandler)
|
||||
// conversion through unknown is necessary because the types are so different
|
||||
return result as unknown as MapValueType<T>
|
||||
return result as unknown as MapValue
|
||||
}
|
||||
|
||||
export function listProxy<T extends Target>(
|
||||
export function listProxy(
|
||||
context: Automerge,
|
||||
objectId: ObjID,
|
||||
textV2: boolean,
|
||||
path?: Prop[],
|
||||
readonly?: boolean,
|
||||
heads?: Heads
|
||||
): ListValueType<T> {
|
||||
): ListValue {
|
||||
const target: Target = {
|
||||
context,
|
||||
objectId,
|
||||
|
@ -580,22 +521,17 @@ export function listProxy<T extends Target>(
|
|||
}
|
||||
const proxied = []
|
||||
Object.assign(proxied, target)
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
return new Proxy(proxied, ListHandler) as unknown as ListValue
|
||||
}
|
||||
|
||||
interface TextProxy extends Text {
|
||||
splice: (index: any, del: any, ...vals: any[]) => void
|
||||
}
|
||||
|
||||
export function textProxy(
|
||||
context: Automerge,
|
||||
objectId: ObjID,
|
||||
path?: Prop[],
|
||||
readonly?: boolean,
|
||||
heads?: Heads
|
||||
): TextProxy {
|
||||
): TextValue {
|
||||
const target: Target = {
|
||||
context,
|
||||
objectId,
|
||||
|
@ -606,9 +542,7 @@ export function textProxy(
|
|||
cache: {},
|
||||
textV2: false,
|
||||
}
|
||||
const proxied = {}
|
||||
Object.assign(proxied, target)
|
||||
return new Proxy(proxied, TextHandler) as unknown as TextProxy
|
||||
return new Proxy(target, TextHandler) as unknown as TextValue
|
||||
}
|
||||
|
||||
export function rootProxy<T>(
|
||||
|
@ -620,10 +554,10 @@ export function rootProxy<T>(
|
|||
return <any>mapProxy(context, "_root", textV2, [], !!readonly)
|
||||
}
|
||||
|
||||
function listMethods<T extends Target>(target: T) {
|
||||
function listMethods(target: Target) {
|
||||
const { context, objectId, path, readonly, frozen, heads, textV2 } = target
|
||||
const methods = {
|
||||
deleteAt(index: number, numDelete: number) {
|
||||
deleteAt(index, numDelete) {
|
||||
if (typeof numDelete === "number") {
|
||||
context.splice(objectId, index, numDelete)
|
||||
} else {
|
||||
|
@ -638,20 +572,8 @@ function listMethods<T extends Target>(target: T) {
|
|||
start = parseListIndex(start || 0)
|
||||
end = parseListIndex(end || length)
|
||||
for (let i = start; i < Math.min(end, length); i++) {
|
||||
if (datatype === "list" || datatype === "map") {
|
||||
if (datatype === "text" || datatype === "list" || datatype === "map") {
|
||||
context.putObject(objectId, i, value)
|
||||
} else if (datatype === "text") {
|
||||
if (textV2) {
|
||||
assertString(value)
|
||||
context.putObject(objectId, i, value)
|
||||
} else {
|
||||
assertText(value)
|
||||
const text = context.putObject(objectId, i, "")
|
||||
const proxyText = textProxy(context, text, [...path, i], readonly)
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
proxyText[i] = value.get(i)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
context.put(objectId, i, value, datatype)
|
||||
}
|
||||
|
@ -659,7 +581,7 @@ function listMethods<T extends Target>(target: T) {
|
|||
return this
|
||||
},
|
||||
|
||||
indexOf(o: any, start = 0) {
|
||||
indexOf(o, start = 0) {
|
||||
const length = context.length(objectId)
|
||||
for (let i = start; i < length; i++) {
|
||||
const value = context.getWithType(objectId, i, heads)
|
||||
|
@ -670,7 +592,7 @@ function listMethods<T extends Target>(target: T) {
|
|||
return -1
|
||||
},
|
||||
|
||||
insertAt(index: number, ...values: any[]) {
|
||||
insertAt(index, ...values) {
|
||||
this.splice(index, 0, ...values)
|
||||
return this
|
||||
},
|
||||
|
@ -685,7 +607,7 @@ function listMethods<T extends Target>(target: T) {
|
|||
return last
|
||||
},
|
||||
|
||||
push(...values: any[]) {
|
||||
push(...values) {
|
||||
const len = context.length(objectId)
|
||||
this.splice(len, 0, ...values)
|
||||
return context.length(objectId)
|
||||
|
@ -698,7 +620,7 @@ function listMethods<T extends Target>(target: T) {
|
|||
return first
|
||||
},
|
||||
|
||||
splice(index: any, del: any, ...vals: any[]) {
|
||||
splice(index, del, ...vals) {
|
||||
index = parseListIndex(index)
|
||||
del = parseListIndex(del)
|
||||
for (const val of vals) {
|
||||
|
@ -716,9 +638,9 @@ function listMethods<T extends Target>(target: T) {
|
|||
"Sequence object cannot be modified outside of a change block"
|
||||
)
|
||||
}
|
||||
const result: ValueType<T>[] = []
|
||||
const result: AutomergeValue[] = []
|
||||
for (let i = 0; i < del; i++) {
|
||||
const value = valueAt<T>(target, index)
|
||||
const value = valueAt(target, index)
|
||||
if (value !== undefined) {
|
||||
result.push(value)
|
||||
}
|
||||
|
@ -741,7 +663,6 @@ function listMethods<T extends Target>(target: T) {
|
|||
}
|
||||
case "text": {
|
||||
if (textV2) {
|
||||
assertString(value)
|
||||
context.insertObject(objectId, index, value)
|
||||
} else {
|
||||
const text = context.insertObject(objectId, index, "")
|
||||
|
@ -777,7 +698,7 @@ function listMethods<T extends Target>(target: T) {
|
|||
return result
|
||||
},
|
||||
|
||||
unshift(...values: any) {
|
||||
unshift(...values) {
|
||||
this.splice(0, 0, ...values)
|
||||
return context.length(objectId)
|
||||
},
|
||||
|
@ -828,11 +749,11 @@ function listMethods<T extends Target>(target: T) {
|
|||
return iterator
|
||||
},
|
||||
|
||||
toArray(): ValueType<T>[] {
|
||||
const list: Array<ValueType<T>> = []
|
||||
let value: ValueType<T> | undefined
|
||||
toArray(): AutomergeValue[] {
|
||||
const list: AutomergeValue = []
|
||||
let value
|
||||
do {
|
||||
value = valueAt<T>(target, list.length)
|
||||
value = valueAt(target, list.length)
|
||||
if (value !== undefined) {
|
||||
list.push(value)
|
||||
}
|
||||
|
@ -841,7 +762,7 @@ function listMethods<T extends Target>(target: T) {
|
|||
return list
|
||||
},
|
||||
|
||||
map<U>(f: (_a: ValueType<T>, _n: number) => U): U[] {
|
||||
map<T>(f: (AutomergeValue, number) => T): T[] {
|
||||
return this.toArray().map(f)
|
||||
},
|
||||
|
||||
|
@ -853,26 +774,24 @@ function listMethods<T extends Target>(target: T) {
|
|||
return this.toArray().toLocaleString()
|
||||
},
|
||||
|
||||
forEach(f: (_a: ValueType<T>, _n: number) => undefined) {
|
||||
forEach(f: (AutomergeValue, number) => undefined) {
|
||||
return this.toArray().forEach(f)
|
||||
},
|
||||
|
||||
// todo: real concat function is different
|
||||
concat(other: ValueType<T>[]): ValueType<T>[] {
|
||||
concat(other: AutomergeValue[]): AutomergeValue[] {
|
||||
return this.toArray().concat(other)
|
||||
},
|
||||
|
||||
every(f: (_a: ValueType<T>, _n: number) => boolean): boolean {
|
||||
every(f: (AutomergeValue, number) => boolean): boolean {
|
||||
return this.toArray().every(f)
|
||||
},
|
||||
|
||||
filter(f: (_a: ValueType<T>, _n: number) => boolean): ValueType<T>[] {
|
||||
filter(f: (AutomergeValue, number) => boolean): AutomergeValue[] {
|
||||
return this.toArray().filter(f)
|
||||
},
|
||||
|
||||
find(
|
||||
f: (_a: ValueType<T>, _n: number) => boolean
|
||||
): ValueType<T> | undefined {
|
||||
find(f: (AutomergeValue, number) => boolean): AutomergeValue | undefined {
|
||||
let index = 0
|
||||
for (const v of this) {
|
||||
if (f(v, index)) {
|
||||
|
@ -882,7 +801,7 @@ function listMethods<T extends Target>(target: T) {
|
|||
}
|
||||
},
|
||||
|
||||
findIndex(f: (_a: ValueType<T>, _n: number) => boolean): number {
|
||||
findIndex(f: (AutomergeValue, number) => boolean): number {
|
||||
let index = 0
|
||||
for (const v of this) {
|
||||
if (f(v, index)) {
|
||||
|
@ -893,7 +812,7 @@ function listMethods<T extends Target>(target: T) {
|
|||
return -1
|
||||
},
|
||||
|
||||
includes(elem: ValueType<T>): boolean {
|
||||
includes(elem: AutomergeValue): boolean {
|
||||
return this.find(e => e === elem) !== undefined
|
||||
},
|
||||
|
||||
|
@ -901,30 +820,29 @@ function listMethods<T extends Target>(target: T) {
|
|||
return this.toArray().join(sep)
|
||||
},
|
||||
|
||||
reduce<U>(
|
||||
f: (acc: U, currentValue: ValueType<T>) => U,
|
||||
initialValue: U
|
||||
): U | undefined {
|
||||
return this.toArray().reduce<U>(f, initialValue)
|
||||
// todo: remove the any
|
||||
reduce<T>(f: (any, AutomergeValue) => T, initalValue?: T): T | undefined {
|
||||
return this.toArray().reduce(f, initalValue)
|
||||
},
|
||||
|
||||
reduceRight<U>(
|
||||
f: (acc: U, item: ValueType<T>) => U,
|
||||
initialValue: U
|
||||
): U | undefined {
|
||||
return this.toArray().reduceRight(f, initialValue)
|
||||
// todo: remove the any
|
||||
reduceRight<T>(
|
||||
f: (any, AutomergeValue) => T,
|
||||
initalValue?: T
|
||||
): T | undefined {
|
||||
return this.toArray().reduceRight(f, initalValue)
|
||||
},
|
||||
|
||||
lastIndexOf(search: ValueType<T>, fromIndex = +Infinity): number {
|
||||
lastIndexOf(search: AutomergeValue, fromIndex = +Infinity): number {
|
||||
// this can be faster
|
||||
return this.toArray().lastIndexOf(search, fromIndex)
|
||||
},
|
||||
|
||||
slice(index?: number, num?: number): ValueType<T>[] {
|
||||
slice(index?: number, num?: number): AutomergeValue[] {
|
||||
return this.toArray().slice(index, num)
|
||||
},
|
||||
|
||||
some(f: (v: ValueType<T>, i: number) => boolean): boolean {
|
||||
some(f: (AutomergeValue, number) => boolean): boolean {
|
||||
let index = 0
|
||||
for (const v of this) {
|
||||
if (f(v, index)) {
|
||||
|
@ -951,7 +869,7 @@ function listMethods<T extends Target>(target: T) {
|
|||
function textMethods(target: Target) {
|
||||
const { context, objectId, heads } = target
|
||||
const methods = {
|
||||
set(index: number, value: any) {
|
||||
set(index: number, value) {
|
||||
return (this[index] = value)
|
||||
},
|
||||
get(index: number): AutomergeValue {
|
||||
|
@ -984,22 +902,10 @@ function textMethods(target: Target) {
|
|||
toJSON(): string {
|
||||
return this.toString()
|
||||
},
|
||||
indexOf(o: any, start = 0) {
|
||||
indexOf(o, start = 0) {
|
||||
const text = context.text(objectId)
|
||||
return text.indexOf(o, start)
|
||||
},
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
function assertText(value: Text | string): asserts value is Text {
|
||||
if (!(value instanceof Text)) {
|
||||
throw new Error("value was not a Text instance")
|
||||
}
|
||||
}
|
||||
|
||||
function assertString(value: Text | string): asserts value is string {
|
||||
if (typeof value !== "string") {
|
||||
throw new Error("value was not a string")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/** @hidden **/
|
||||
export { /** @hidden */ uuid } from "./uuid"
|
||||
|
||||
import { rootProxy } from "./proxies"
|
||||
import { rootProxy, listProxy, mapProxy, textProxy } from "./proxies"
|
||||
import { STATE } from "./constants"
|
||||
|
||||
import {
|
||||
|
@ -20,13 +20,13 @@ export {
|
|||
type Patch,
|
||||
type PatchCallback,
|
||||
type ScalarValue,
|
||||
Text,
|
||||
} from "./types"
|
||||
|
||||
import { Text } from "./text"
|
||||
export { Text } from "./text"
|
||||
|
||||
import type {
|
||||
API as WasmAPI,
|
||||
API,
|
||||
Actor as ActorId,
|
||||
Prop,
|
||||
ObjID,
|
||||
|
@ -34,29 +34,17 @@ import type {
|
|||
DecodedChange,
|
||||
Heads,
|
||||
MaterializeValue,
|
||||
JsSyncState,
|
||||
JsSyncState as SyncState,
|
||||
SyncMessage,
|
||||
DecodedSyncMessage,
|
||||
} from "@automerge/automerge-wasm"
|
||||
export type {
|
||||
PutPatch,
|
||||
DelPatch,
|
||||
SpliceTextPatch,
|
||||
InsertPatch,
|
||||
SplicePatch,
|
||||
IncPatch,
|
||||
SyncMessage,
|
||||
} from "@automerge/automerge-wasm"
|
||||
|
||||
/** @hidden **/
|
||||
type API = WasmAPI
|
||||
|
||||
const SyncStateSymbol = Symbol("_syncstate")
|
||||
|
||||
/**
|
||||
* An opaque type tracking the state of sync with a remote peer
|
||||
*/
|
||||
type SyncState = JsSyncState & { _opaque: typeof SyncStateSymbol }
|
||||
|
||||
import { ApiHandler, type ChangeToEncode, UseApi } from "./low_level"
|
||||
|
||||
import { Automerge } from "@automerge/automerge-wasm"
|
||||
|
@ -65,8 +53,6 @@ import { RawString } from "./raw_string"
|
|||
|
||||
import { _state, _is_proxy, _trace, _obj } from "./internal_state"
|
||||
|
||||
import { stableConflictAt } from "./conflicts"
|
||||
|
||||
/** Options passed to {@link change}, and {@link emptyChange}
|
||||
* @typeParam T - The type of value contained in the document
|
||||
*/
|
||||
|
@ -84,36 +70,13 @@ export type ChangeOptions<T> = {
|
|||
*/
|
||||
export type ApplyOptions<T> = { patchCallback?: PatchCallback<T> }
|
||||
|
||||
/**
|
||||
* A List is an extended Array that adds the two helper methods `deleteAt` and `insertAt`.
|
||||
*/
|
||||
export interface List<T> extends Array<T> {
|
||||
insertAt(index: number, ...args: T[]): List<T>
|
||||
deleteAt(index: number, numDelete?: number): List<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* To extend an arbitrary type, we have to turn any arrays that are part of the type's definition into Lists.
|
||||
* So we recurse through the properties of T, turning any Arrays we find into Lists.
|
||||
*/
|
||||
export type Extend<T> =
|
||||
// is it an array? make it a list (we recursively extend the type of the array's elements as well)
|
||||
T extends Array<infer T>
|
||||
? List<Extend<T>>
|
||||
: // is it an object? recursively extend all of its properties
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
T extends Object
|
||||
? { [P in keyof T]: Extend<T[P]> }
|
||||
: // otherwise leave the type alone
|
||||
T
|
||||
|
||||
/**
|
||||
* Function which is called by {@link change} when making changes to a `Doc<T>`
|
||||
* @typeParam T - The type of value contained in the document
|
||||
*
|
||||
* This function may mutate `doc`
|
||||
*/
|
||||
export type ChangeFn<T> = (doc: Extend<T>) => void
|
||||
export type ChangeFn<T> = (doc: T) => void
|
||||
|
||||
/** @hidden **/
|
||||
export interface State<T> {
|
||||
|
@ -172,12 +135,11 @@ export function init<T>(_opts?: ActorId | InitOptions<T>): Doc<T> {
|
|||
const handle = ApiHandler.create(opts.enableTextV2 || false, opts.actor)
|
||||
handle.enablePatches(true)
|
||||
handle.enableFreeze(!!opts.freeze)
|
||||
handle.registerDatatype("counter", (n: number) => new Counter(n))
|
||||
const textV2 = opts.enableTextV2 || false
|
||||
handle.registerDatatype("counter", (n: any) => new Counter(n))
|
||||
let textV2 = opts.enableTextV2 || false
|
||||
if (textV2) {
|
||||
handle.registerDatatype("str", (n: string) => new RawString(n))
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
handle.registerDatatype("text", (n: any) => new Text(n))
|
||||
}
|
||||
const doc = handle.materialize("/", undefined, {
|
||||
|
@ -241,7 +203,7 @@ export function clone<T>(
|
|||
|
||||
// `change` uses the presence of state.heads to determine if we are in a view
|
||||
// set it to undefined to indicate that this is a full fat document
|
||||
const { heads: _oldHeads, ...stateSansHeads } = state
|
||||
const { heads: oldHeads, ...stateSansHeads } = state
|
||||
return handle.applyPatches(doc, { ...stateSansHeads, handle })
|
||||
}
|
||||
|
||||
|
@ -305,7 +267,7 @@ export function from<T extends Record<string, unknown>>(
|
|||
* @example A change with a message and a timestamp
|
||||
*
|
||||
* ```
|
||||
* doc1 = automerge.change(doc1, {message: "add another value", time: 1640995200}, d => {
|
||||
* doc1 = automerge.change(doc1, {message: "add another value", timestamp: 1640995200}, d => {
|
||||
* d.key2 = "value2"
|
||||
* })
|
||||
* ```
|
||||
|
@ -316,7 +278,7 @@ export function from<T extends Record<string, unknown>>(
|
|||
* let patchCallback = patch => {
|
||||
* patchedPath = patch.path
|
||||
* }
|
||||
* doc1 = automerge.change(doc1, {message, "add another value", time: 1640995200, patchCallback}, d => {
|
||||
* doc1 = automerge.change(doc1, {message, "add another value", timestamp: 1640995200, patchCallback}, d => {
|
||||
* d.key2 = "value2"
|
||||
* })
|
||||
* assert.equal(patchedPath, ["key2"])
|
||||
|
@ -380,7 +342,7 @@ function _change<T>(
|
|||
try {
|
||||
state.heads = heads
|
||||
const root: T = rootProxy(state.handle, state.textV2)
|
||||
callback(root as Extend<T>)
|
||||
callback(root)
|
||||
if (state.handle.pendingOps() === 0) {
|
||||
state.heads = undefined
|
||||
return doc
|
||||
|
@ -578,6 +540,62 @@ export function getActorId<T>(doc: Doc<T>): ActorId {
|
|||
*/
|
||||
type Conflicts = { [key: string]: AutomergeValue }
|
||||
|
||||
function conflictAt(
|
||||
context: Automerge,
|
||||
objectId: ObjID,
|
||||
prop: Prop,
|
||||
textV2: boolean
|
||||
): Conflicts | undefined {
|
||||
const values = context.getAll(objectId, prop)
|
||||
if (values.length <= 1) {
|
||||
return
|
||||
}
|
||||
const result: Conflicts = {}
|
||||
for (const fullVal of values) {
|
||||
switch (fullVal[0]) {
|
||||
case "map":
|
||||
result[fullVal[1]] = mapProxy(context, fullVal[1], textV2, [prop], true)
|
||||
break
|
||||
case "list":
|
||||
result[fullVal[1]] = listProxy(
|
||||
context,
|
||||
fullVal[1],
|
||||
textV2,
|
||||
[prop],
|
||||
true
|
||||
)
|
||||
break
|
||||
case "text":
|
||||
if (textV2) {
|
||||
result[fullVal[1]] = context.text(fullVal[1])
|
||||
} else {
|
||||
result[fullVal[1]] = textProxy(context, objectId, [prop], true)
|
||||
}
|
||||
break
|
||||
//case "table":
|
||||
//case "cursor":
|
||||
case "str":
|
||||
case "uint":
|
||||
case "int":
|
||||
case "f64":
|
||||
case "boolean":
|
||||
case "bytes":
|
||||
case "null":
|
||||
result[fullVal[2]] = fullVal[1]
|
||||
break
|
||||
case "counter":
|
||||
result[fullVal[2]] = new Counter(fullVal[1])
|
||||
break
|
||||
case "timestamp":
|
||||
result[fullVal[2]] = new Date(fullVal[1])
|
||||
break
|
||||
default:
|
||||
throw RangeError(`datatype ${fullVal[0]} unimplemented`)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the conflicts associated with a property
|
||||
*
|
||||
|
@ -627,12 +645,9 @@ export function getConflicts<T>(
|
|||
prop: Prop
|
||||
): Conflicts | undefined {
|
||||
const state = _state(doc, false)
|
||||
if (state.textV2) {
|
||||
throw new Error("use unstable.getConflicts for an unstable document")
|
||||
}
|
||||
const objectId = _obj(doc)
|
||||
if (objectId != null) {
|
||||
return stableConflictAt(state.handle, objectId, prop)
|
||||
return conflictAt(state.handle, objectId, prop, state.textV2)
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
|
@ -656,7 +671,6 @@ export function getLastLocalChange<T>(doc: Doc<T>): Change | undefined {
|
|||
* This is useful to determine if something is actually an automerge document,
|
||||
* if `doc` is not an automerge document this will return null.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function getObjectId(doc: any, prop?: Prop): ObjID | null {
|
||||
if (prop) {
|
||||
const state = _state(doc, false)
|
||||
|
@ -783,7 +797,7 @@ export function decodeSyncState(state: Uint8Array): SyncState {
|
|||
const sync = ApiHandler.decodeSyncState(state)
|
||||
const result = ApiHandler.exportSyncState(sync)
|
||||
sync.free()
|
||||
return result as SyncState
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -804,7 +818,7 @@ export function generateSyncMessage<T>(
|
|||
const state = _state(doc)
|
||||
const syncState = ApiHandler.importSyncState(inState)
|
||||
const message = state.handle.generateSyncMessage(syncState)
|
||||
const outState = ApiHandler.exportSyncState(syncState) as SyncState
|
||||
const outState = ApiHandler.exportSyncState(syncState)
|
||||
return [outState, message]
|
||||
}
|
||||
|
||||
|
@ -846,7 +860,7 @@ export function receiveSyncMessage<T>(
|
|||
}
|
||||
const heads = state.handle.getHeads()
|
||||
state.handle.receiveSyncMessage(syncState, message)
|
||||
const outSyncState = ApiHandler.exportSyncState(syncState) as SyncState
|
||||
const outSyncState = ApiHandler.exportSyncState(syncState)
|
||||
return [
|
||||
progressDocument(doc, heads, opts.patchCallback || state.patchCallback),
|
||||
outSyncState,
|
||||
|
@ -863,7 +877,7 @@ export function receiveSyncMessage<T>(
|
|||
* @group sync
|
||||
*/
|
||||
export function initSyncState(): SyncState {
|
||||
return ApiHandler.exportSyncState(ApiHandler.initSyncState()) as SyncState
|
||||
return ApiHandler.exportSyncState(ApiHandler.initSyncState())
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
|
|
|
@ -3,12 +3,9 @@ import { TEXT, STATE } from "./constants"
|
|||
import type { InternalState } from "./internal_state"
|
||||
|
||||
export class Text {
|
||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
elems: Array<any>
|
||||
str: string | undefined
|
||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
spans: Array<any> | undefined;
|
||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[STATE]?: InternalState<any>
|
||||
|
||||
constructor(text?: string | string[] | Value[]) {
|
||||
|
@ -28,7 +25,6 @@ export class Text {
|
|||
return this.elems.length
|
||||
}
|
||||
|
||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
get(index: number): any {
|
||||
return this.elems[index]
|
||||
}
|
||||
|
@ -77,7 +73,7 @@ export class Text {
|
|||
* For example, the value `['a', 'b', {x: 3}, 'c', 'd']` has spans:
|
||||
* `=> ['ab', {x: 3}, 'cd']`
|
||||
*/
|
||||
toSpans(): Array<Value | object> {
|
||||
toSpans(): Array<Value | Object> {
|
||||
if (!this.spans) {
|
||||
this.spans = []
|
||||
let chars = ""
|
||||
|
@ -122,7 +118,7 @@ export class Text {
|
|||
/**
|
||||
* Inserts new list items `values` starting at position `index`.
|
||||
*/
|
||||
insertAt(index: number, ...values: Array<Value | object>) {
|
||||
insertAt(index: number, ...values: Array<Value | Object>) {
|
||||
if (this[STATE]) {
|
||||
throw new RangeError(
|
||||
"object cannot be modified outside of a change block"
|
||||
|
@ -144,7 +140,7 @@ export class Text {
|
|||
this.elems.splice(index, numDelete)
|
||||
}
|
||||
|
||||
map<T>(callback: (e: Value | object) => T) {
|
||||
map<T>(callback: (e: Value | Object) => T) {
|
||||
this.elems.map(callback)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
export { Text } from "./text"
|
||||
import { Text } from "./text"
|
||||
export { Counter } from "./counter"
|
||||
export { Int, Uint, Float64 } from "./numbers"
|
||||
|
||||
|
@ -11,9 +10,9 @@ export type AutomergeValue =
|
|||
| ScalarValue
|
||||
| { [key: string]: AutomergeValue }
|
||||
| Array<AutomergeValue>
|
||||
| Text
|
||||
export type MapValue = { [key: string]: AutomergeValue }
|
||||
export type ListValue = Array<AutomergeValue>
|
||||
export type TextValue = Array<AutomergeValue>
|
||||
export type ScalarValue =
|
||||
| string
|
||||
| number
|
||||
|
|
|
@ -22,9 +22,9 @@
|
|||
* This leads to the following differences from `stable`:
|
||||
*
|
||||
* * There is no `unstable.Text` class, all strings are text objects
|
||||
* * Reading strings in an `unstable` document is the same as reading any other
|
||||
* * Reading strings in a `future` document is the same as reading any other
|
||||
* javascript string
|
||||
* * To modify strings in an `unstable` document use {@link splice}
|
||||
* * To modify strings in a `future` document use {@link splice}
|
||||
* * The {@link AutomergeValue} type does not include the {@link Text}
|
||||
* class but the {@link RawString} class is included in the {@link ScalarValue}
|
||||
* type
|
||||
|
@ -35,6 +35,7 @@
|
|||
*
|
||||
* @module
|
||||
*/
|
||||
import { Counter } from "./types"
|
||||
|
||||
export {
|
||||
Counter,
|
||||
|
@ -44,20 +45,32 @@ export {
|
|||
Float64,
|
||||
type Patch,
|
||||
type PatchCallback,
|
||||
type AutomergeValue,
|
||||
type ScalarValue,
|
||||
} from "./unstable_types"
|
||||
} from "./types"
|
||||
|
||||
import type { PatchCallback } from "./stable"
|
||||
|
||||
import { type UnstableConflicts as Conflicts } from "./conflicts"
|
||||
import { unstableConflictAt } from "./conflicts"
|
||||
export type AutomergeValue =
|
||||
| ScalarValue
|
||||
| { [key: string]: AutomergeValue }
|
||||
| Array<AutomergeValue>
|
||||
export type MapValue = { [key: string]: AutomergeValue }
|
||||
export type ListValue = Array<AutomergeValue>
|
||||
export type ScalarValue =
|
||||
| string
|
||||
| number
|
||||
| null
|
||||
| boolean
|
||||
| Date
|
||||
| Counter
|
||||
| Uint8Array
|
||||
| RawString
|
||||
|
||||
export type Conflicts = { [key: string]: AutomergeValue }
|
||||
|
||||
export type {
|
||||
PutPatch,
|
||||
DelPatch,
|
||||
SpliceTextPatch,
|
||||
InsertPatch,
|
||||
SplicePatch,
|
||||
IncPatch,
|
||||
SyncMessage,
|
||||
} from "@automerge/automerge-wasm"
|
||||
|
@ -111,6 +124,7 @@ export { RawString } from "./raw_string"
|
|||
export const getBackend = stable.getBackend
|
||||
|
||||
import { _is_proxy, _state, _obj } from "./internal_state"
|
||||
import { RawString } from "./raw_string"
|
||||
|
||||
/**
|
||||
* Create a new automerge document
|
||||
|
@ -122,7 +136,7 @@ import { _is_proxy, _state, _obj } from "./internal_state"
|
|||
* random actor ID
|
||||
*/
|
||||
export function init<T>(_opts?: ActorId | InitOptions<T>): Doc<T> {
|
||||
const opts = importOpts(_opts)
|
||||
let opts = importOpts(_opts)
|
||||
opts.enableTextV2 = true
|
||||
return stable.init(opts)
|
||||
}
|
||||
|
@ -146,7 +160,7 @@ export function clone<T>(
|
|||
doc: Doc<T>,
|
||||
_opts?: ActorId | InitOptions<T>
|
||||
): Doc<T> {
|
||||
const opts = importOpts(_opts)
|
||||
let opts = importOpts(_opts)
|
||||
opts.enableTextV2 = true
|
||||
return stable.clone(doc, opts)
|
||||
}
|
||||
|
@ -281,14 +295,6 @@ export function getConflicts<T>(
|
|||
doc: Doc<T>,
|
||||
prop: stable.Prop
|
||||
): Conflicts | undefined {
|
||||
const state = _state(doc, false)
|
||||
if (!state.textV2) {
|
||||
throw new Error("use getConflicts for a stable document")
|
||||
}
|
||||
const objectId = _obj(doc)
|
||||
if (objectId != null) {
|
||||
return unstableConflictAt(state.handle, objectId, prop)
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
// this function only exists to get the types to line up with future.AutomergeValue
|
||||
return stable.getConflicts(doc, prop)
|
||||
}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import { Counter } from "./types"
|
||||
|
||||
export {
|
||||
Counter,
|
||||
type Doc,
|
||||
Int,
|
||||
Uint,
|
||||
Float64,
|
||||
type Patch,
|
||||
type PatchCallback,
|
||||
} from "./types"
|
||||
|
||||
import { RawString } from "./raw_string"
|
||||
export { RawString } from "./raw_string"
|
||||
|
||||
export type AutomergeValue =
|
||||
| ScalarValue
|
||||
| { [key: string]: AutomergeValue }
|
||||
| Array<AutomergeValue>
|
||||
export type MapValue = { [key: string]: AutomergeValue }
|
||||
export type ListValue = Array<AutomergeValue>
|
||||
export type ScalarValue =
|
||||
| string
|
||||
| number
|
||||
| null
|
||||
| boolean
|
||||
| Date
|
||||
| Counter
|
||||
| Uint8Array
|
||||
| RawString
|
|
@ -58,22 +58,6 @@ describe("Automerge", () => {
|
|||
})
|
||||
})
|
||||
|
||||
it("should be able to insert and delete a large number of properties", () => {
|
||||
let doc = Automerge.init<any>()
|
||||
|
||||
doc = Automerge.change(doc, doc => {
|
||||
doc["k1"] = true
|
||||
})
|
||||
|
||||
for (let idx = 1; idx <= 200; idx++) {
|
||||
doc = Automerge.change(doc, doc => {
|
||||
delete doc["k" + idx]
|
||||
doc["k" + (idx + 1)] = true
|
||||
assert(Object.keys(doc).length == 1)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it("can detect an automerge doc with isAutomerge()", () => {
|
||||
const doc1 = Automerge.from({ sub: { object: true } })
|
||||
assert(Automerge.isAutomerge(doc1))
|
||||
|
@ -283,6 +267,7 @@ describe("Automerge", () => {
|
|||
})
|
||||
assert.deepEqual(doc5, { list: [2, 1, 9, 10, 3, 11, 12] })
|
||||
let doc6 = Automerge.change(doc5, d => {
|
||||
// @ts-ignore
|
||||
d.list.insertAt(3, 100, 101)
|
||||
})
|
||||
assert.deepEqual(doc6, { list: [2, 1, 9, 100, 101, 10, 3, 11, 12] })
|
||||
|
|
|
@ -461,12 +461,12 @@ describe("Automerge", () => {
|
|||
s1 = Automerge.change(s1, "set foo", doc => {
|
||||
doc.foo = "bar"
|
||||
})
|
||||
let deleted: any
|
||||
let deleted
|
||||
s1 = Automerge.change(s1, "del foo", doc => {
|
||||
deleted = delete doc.foo
|
||||
})
|
||||
assert.strictEqual(deleted, true)
|
||||
let deleted2: any
|
||||
let deleted2
|
||||
assert.doesNotThrow(() => {
|
||||
s1 = Automerge.change(s1, "del baz", doc => {
|
||||
deleted2 = delete doc.baz
|
||||
|
@ -515,7 +515,7 @@ describe("Automerge", () => {
|
|||
s1 = Automerge.change(s1, doc => {
|
||||
doc.nested = {}
|
||||
})
|
||||
Automerge.getObjectId(s1.nested)
|
||||
let id = Automerge.getObjectId(s1.nested)
|
||||
assert.strictEqual(
|
||||
OPID_PATTERN.test(Automerge.getObjectId(s1.nested)!),
|
||||
true
|
||||
|
@ -975,7 +975,6 @@ describe("Automerge", () => {
|
|||
it("should allow adding and removing list elements in the same change callback", () => {
|
||||
let s1 = Automerge.change(
|
||||
Automerge.init<{ noodles: Array<string> }>(),
|
||||
// @ts-ignore
|
||||
doc => (doc.noodles = [])
|
||||
)
|
||||
s1 = Automerge.change(s1, doc => {
|
||||
|
@ -1849,8 +1848,9 @@ describe("Automerge", () => {
|
|||
})
|
||||
assert.deepStrictEqual(patches, [
|
||||
{ action: "put", path: ["birds"], value: [] },
|
||||
{ action: "insert", path: ["birds", 0], values: ["", ""] },
|
||||
{ action: "insert", path: ["birds", 0], values: [""] },
|
||||
{ action: "splice", path: ["birds", 0, 0], value: "Goldfinch" },
|
||||
{ action: "insert", path: ["birds", 1], values: [""] },
|
||||
{ action: "splice", path: ["birds", 1, 0], value: "Chaffinch" },
|
||||
])
|
||||
})
|
||||
|
|
|
@ -38,62 +38,4 @@ describe("stable/unstable interop", () => {
|
|||
stableDoc = unstable.merge(stableDoc, unstableDoc)
|
||||
assert.deepStrictEqual(stableDoc.text, "abc")
|
||||
})
|
||||
|
||||
it("should show conflicts on text objects", () => {
|
||||
let doc1 = stable.from({ text: new stable.Text("abc") }, "bb")
|
||||
let doc2 = stable.from({ text: new stable.Text("def") }, "aa")
|
||||
doc1 = stable.merge(doc1, doc2)
|
||||
let conflicts = stable.getConflicts(doc1, "text")!
|
||||
assert.equal(conflicts["1@bb"]!.toString(), "abc")
|
||||
assert.equal(conflicts["1@aa"]!.toString(), "def")
|
||||
|
||||
let unstableDoc = unstable.init<any>()
|
||||
unstableDoc = unstable.merge(unstableDoc, doc1)
|
||||
let conflicts2 = unstable.getConflicts(unstableDoc, "text")!
|
||||
assert.equal(conflicts2["1@bb"]!.toString(), "abc")
|
||||
assert.equal(conflicts2["1@aa"]!.toString(), "def")
|
||||
})
|
||||
|
||||
it("should allow filling a list with text in stable", () => {
|
||||
let doc = stable.from<{ list: Array<stable.Text | null> }>({
|
||||
list: [null, null, null],
|
||||
})
|
||||
doc = stable.change(doc, doc => {
|
||||
doc.list.fill(new stable.Text("abc"), 0, 3)
|
||||
})
|
||||
assert.deepStrictEqual(doc.list, [
|
||||
new stable.Text("abc"),
|
||||
new stable.Text("abc"),
|
||||
new stable.Text("abc"),
|
||||
])
|
||||
})
|
||||
|
||||
it("should allow filling a list with text in unstable", () => {
|
||||
let doc = unstable.from<{ list: Array<string | null> }>({
|
||||
list: [null, null, null],
|
||||
})
|
||||
doc = stable.change(doc, doc => {
|
||||
doc.list.fill("abc", 0, 3)
|
||||
})
|
||||
assert.deepStrictEqual(doc.list, ["abc", "abc", "abc"])
|
||||
})
|
||||
|
||||
it("should allow splicing text into a list on stable", () => {
|
||||
let doc = stable.from<{ list: Array<stable.Text> }>({ list: [] })
|
||||
doc = stable.change(doc, doc => {
|
||||
doc.list.splice(0, 0, new stable.Text("abc"), new stable.Text("def"))
|
||||
})
|
||||
assert.deepStrictEqual(doc.list, [
|
||||
new stable.Text("abc"),
|
||||
new stable.Text("def"),
|
||||
])
|
||||
})
|
||||
|
||||
it("should allow splicing text into a list on unstable", () => {
|
||||
let doc = unstable.from<{ list: Array<string> }>({ list: [] })
|
||||
doc = unstable.change(doc, doc => {
|
||||
doc.list.splice(0, 0, "abc", "def")
|
||||
})
|
||||
assert.deepStrictEqual(doc.list, ["abc", "def"])
|
||||
})
|
||||
})
|
||||
|
|
|
@ -10,8 +10,13 @@ members = [
|
|||
resolver = "2"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
|
||||
[profile.bench]
|
||||
debug = true
|
||||
|
||||
[profile.release.package.automerge-wasm]
|
||||
debug = false
|
||||
opt-level = 3
|
||||
|
|
|
@ -1,250 +0,0 @@
|
|||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: Chromium
|
||||
AccessModifierOffset: -1
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: true
|
||||
BinPackParameters: false
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 120
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
QualifierAlignment: Leave
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
PackConstructorInitializers: NextLine
|
||||
BasedOnStyle: ''
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IfMacros:
|
||||
- KJ_IF_MAYBE
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '^<ext/.*\.h>'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^<.*'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseLabels: true
|
||||
IndentCaseBlocks: false
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentRequiresClause: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertBraces: false
|
||||
InsertTrailingCommas: None
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
LambdaBodyIndentation: Signature
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Never
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PointerAlignment: Left
|
||||
PPIndentWidth: -1
|
||||
RawStringFormats:
|
||||
- Language: Cpp
|
||||
Delimiters:
|
||||
- cc
|
||||
- CC
|
||||
- cpp
|
||||
- Cpp
|
||||
- CPP
|
||||
- 'c++'
|
||||
- 'C++'
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
- Language: TextProto
|
||||
Delimiters:
|
||||
- pb
|
||||
- PB
|
||||
- proto
|
||||
- PROTO
|
||||
EnclosingFunctions:
|
||||
- EqualsProto
|
||||
- EquivToProto
|
||||
- PARSE_PARTIAL_TEXT_PROTO
|
||||
- PARSE_TEST_PROTO
|
||||
- PARSE_TEXT_PROTO
|
||||
- ParseTextOrDie
|
||||
- ParseTextProtoOrDie
|
||||
- ParseTestProto
|
||||
- ParsePartialTestProto
|
||||
CanonicalDelimiter: pb
|
||||
BasedOnStyle: google
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: true
|
||||
RemoveBracesLLVM: false
|
||||
RequiresClausePosition: OwnLine
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SortIncludes: CaseSensitive
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterForeachMacros: true
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterIfMacros: true
|
||||
AfterOverloadedOperator: false
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: Never
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
BitFieldColonSpacing: Both
|
||||
Standard: Auto
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 8
|
||||
UseCRLF: false
|
||||
UseTab: Never
|
||||
WhitespaceSensitiveMacros:
|
||||
- STRINGIZE
|
||||
- PP_STRINGIZE
|
||||
- BOOST_PP_STRINGIZE
|
||||
- NS_SWIFT_NAME
|
||||
- CF_SWIFT_NAME
|
||||
...
|
||||
|
8
rust/automerge-c/.gitignore
vendored
8
rust/automerge-c/.gitignore
vendored
|
@ -1,10 +1,10 @@
|
|||
automerge
|
||||
automerge.h
|
||||
automerge.o
|
||||
build/
|
||||
CMakeCache.txt
|
||||
*.cmake
|
||||
CMakeFiles
|
||||
CMakePresets.json
|
||||
Makefile
|
||||
DartConfiguration.tcl
|
||||
out/
|
||||
config.h
|
||||
CMakeCache.txt
|
||||
Cargo
|
||||
|
|
|
@ -1,297 +1,97 @@
|
|||
cmake_minimum_required(VERSION 3.23 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||
|
||||
project(automerge-c VERSION 0.1.0
|
||||
LANGUAGES C
|
||||
DESCRIPTION "C bindings for the Automerge Rust library.")
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||
|
||||
set(LIBRARY_NAME "automerge")
|
||||
# Parse the library name, project name and project version out of Cargo's TOML file.
|
||||
set(CARGO_LIB_SECTION OFF)
|
||||
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
set(LIBRARY_NAME "")
|
||||
|
||||
option(BUILD_SHARED_LIBS "Enable the choice of a shared or static library.")
|
||||
set(CARGO_PKG_SECTION OFF)
|
||||
|
||||
set(CARGO_PKG_NAME "")
|
||||
|
||||
set(CARGO_PKG_VERSION "")
|
||||
|
||||
file(READ Cargo.toml TOML_STRING)
|
||||
|
||||
string(REPLACE ";" "\\\\;" TOML_STRING "${TOML_STRING}")
|
||||
|
||||
string(REPLACE "\n" ";" TOML_LINES "${TOML_STRING}")
|
||||
|
||||
foreach(TOML_LINE IN ITEMS ${TOML_LINES})
|
||||
string(REGEX MATCH "^\\[(lib|package)\\]$" _ ${TOML_LINE})
|
||||
|
||||
if(CMAKE_MATCH_1 STREQUAL "lib")
|
||||
set(CARGO_LIB_SECTION ON)
|
||||
|
||||
set(CARGO_PKG_SECTION OFF)
|
||||
elseif(CMAKE_MATCH_1 STREQUAL "package")
|
||||
set(CARGO_LIB_SECTION OFF)
|
||||
|
||||
set(CARGO_PKG_SECTION ON)
|
||||
endif()
|
||||
|
||||
string(REGEX MATCH "^name += +\"([^\"]+)\"$" _ ${TOML_LINE})
|
||||
|
||||
if(CMAKE_MATCH_1 AND (CARGO_LIB_SECTION AND NOT CARGO_PKG_SECTION))
|
||||
set(LIBRARY_NAME "${CMAKE_MATCH_1}")
|
||||
elseif(CMAKE_MATCH_1 AND (NOT CARGO_LIB_SECTION AND CARGO_PKG_SECTION))
|
||||
set(CARGO_PKG_NAME "${CMAKE_MATCH_1}")
|
||||
endif()
|
||||
|
||||
string(REGEX MATCH "^version += +\"([^\"]+)\"$" _ ${TOML_LINE})
|
||||
|
||||
if(CMAKE_MATCH_1 AND CARGO_PKG_SECTION)
|
||||
set(CARGO_PKG_VERSION "${CMAKE_MATCH_1}")
|
||||
endif()
|
||||
|
||||
if(LIBRARY_NAME AND (CARGO_PKG_NAME AND CARGO_PKG_VERSION))
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
project(${CARGO_PKG_NAME} VERSION 0.0.1 LANGUAGES C DESCRIPTION "C bindings for the Automerge Rust backend.")
|
||||
|
||||
include(CTest)
|
||||
|
||||
option(BUILD_SHARED_LIBS "Enable the choice of a shared or static library.")
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||
|
||||
string(MAKE_C_IDENTIFIER ${PROJECT_NAME} SYMBOL_PREFIX)
|
||||
|
||||
string(TOUPPER ${SYMBOL_PREFIX} SYMBOL_PREFIX)
|
||||
|
||||
set(CARGO_TARGET_DIR "${CMAKE_BINARY_DIR}/Cargo/target")
|
||||
set(CARGO_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/Cargo/target")
|
||||
|
||||
set(CBINDGEN_INCLUDEDIR "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
set(CBINDGEN_INCLUDEDIR "${CARGO_TARGET_DIR}/${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
|
||||
set(CBINDGEN_TARGET_DIR "${CBINDGEN_INCLUDEDIR}/${PROJECT_NAME}")
|
||||
|
||||
find_program (
|
||||
CARGO_CMD
|
||||
"cargo"
|
||||
PATHS "$ENV{CARGO_HOME}/bin"
|
||||
DOC "The Cargo command"
|
||||
)
|
||||
add_subdirectory(src)
|
||||
|
||||
if(NOT CARGO_CMD)
|
||||
message(FATAL_ERROR "Cargo (Rust package manager) not found! "
|
||||
"Please install it and/or set the CARGO_HOME "
|
||||
"environment variable to its path.")
|
||||
endif()
|
||||
|
||||
string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_LOWER)
|
||||
|
||||
# In order to build with -Z build-std, we need to pass target explicitly.
|
||||
# https://doc.rust-lang.org/cargo/reference/unstable.html#build-std
|
||||
execute_process (
|
||||
COMMAND rustc -vV
|
||||
OUTPUT_VARIABLE RUSTC_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
string(REGEX REPLACE ".*host: ([^ \n]*).*" "\\1"
|
||||
CARGO_TARGET
|
||||
${RUSTC_VERSION}
|
||||
)
|
||||
|
||||
if(BUILD_TYPE_LOWER STREQUAL debug)
|
||||
set(CARGO_BUILD_TYPE "debug")
|
||||
|
||||
set(CARGO_FLAG --target=${CARGO_TARGET})
|
||||
else()
|
||||
set(CARGO_BUILD_TYPE "release")
|
||||
|
||||
if (NOT RUSTC_VERSION MATCHES "nightly")
|
||||
set(RUSTUP_TOOLCHAIN nightly)
|
||||
endif()
|
||||
|
||||
set(RUSTFLAGS -C\ panic=abort)
|
||||
|
||||
set(CARGO_FLAG -Z build-std=std,panic_abort --release --target=${CARGO_TARGET})
|
||||
endif()
|
||||
|
||||
set(CARGO_FEATURES "")
|
||||
|
||||
set(CARGO_BINARY_DIR "${CARGO_TARGET_DIR}/${CARGO_TARGET}/${CARGO_BUILD_TYPE}")
|
||||
|
||||
set(BINDINGS_NAME "${LIBRARY_NAME}_core")
|
||||
|
||||
configure_file(
|
||||
${CMAKE_MODULE_PATH}/Cargo.toml.in
|
||||
${CMAKE_SOURCE_DIR}/Cargo.toml
|
||||
@ONLY
|
||||
NEWLINE_STYLE LF
|
||||
)
|
||||
|
||||
set(INCLUDE_GUARD_PREFIX "${SYMBOL_PREFIX}")
|
||||
|
||||
configure_file(
|
||||
${CMAKE_MODULE_PATH}/cbindgen.toml.in
|
||||
${CMAKE_SOURCE_DIR}/cbindgen.toml
|
||||
@ONLY
|
||||
NEWLINE_STYLE LF
|
||||
)
|
||||
|
||||
set(CARGO_OUTPUT
|
||||
${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
|
||||
${CARGO_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${BINDINGS_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}
|
||||
)
|
||||
|
||||
# \note cbindgen's naming behavior isn't fully configurable and it ignores
|
||||
# `const fn` calls (https://github.com/eqrion/cbindgen/issues/252).
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${CARGO_OUTPUT}
|
||||
COMMAND
|
||||
# \note cbindgen won't regenerate its output header file after it's been removed but it will after its
|
||||
# configuration file has been updated.
|
||||
${CMAKE_COMMAND} -DCONDITION=NOT_EXISTS -P ${CMAKE_SOURCE_DIR}/cmake/file-touch.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CMAKE_SOURCE_DIR}/cbindgen.toml
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} CBINDGEN_TARGET_DIR=${CBINDGEN_TARGET_DIR} RUSTUP_TOOLCHAIN=${RUSTUP_TOOLCHAIN} RUSTFLAGS=${RUSTFLAGS} ${CARGO_CMD} build ${CARGO_FLAG} ${CARGO_FEATURES}
|
||||
COMMAND
|
||||
# Compensate for cbindgen's translation of consecutive uppercase letters to "ScreamingSnakeCase".
|
||||
${CMAKE_COMMAND} -DMATCH_REGEX=A_M\([^_]+\)_ -DREPLACE_EXPR=AM_\\1_ -P ${CMAKE_SOURCE_DIR}/cmake/file-regex-replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
|
||||
COMMAND
|
||||
# Compensate for cbindgen ignoring `std:mem::size_of<usize>()` calls.
|
||||
${CMAKE_COMMAND} -DMATCH_REGEX=USIZE_ -DREPLACE_EXPR=\+${CMAKE_SIZEOF_VOID_P} -P ${CMAKE_SOURCE_DIR}/cmake/file-regex-replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
|
||||
MAIN_DEPENDENCY
|
||||
src/lib.rs
|
||||
DEPENDS
|
||||
src/actor_id.rs
|
||||
src/byte_span.rs
|
||||
src/change.rs
|
||||
src/doc.rs
|
||||
src/doc/list.rs
|
||||
src/doc/map.rs
|
||||
src/doc/utils.rs
|
||||
src/index.rs
|
||||
src/item.rs
|
||||
src/items.rs
|
||||
src/obj.rs
|
||||
src/result.rs
|
||||
src/sync.rs
|
||||
src/sync/have.rs
|
||||
src/sync/message.rs
|
||||
src/sync/state.rs
|
||||
${CMAKE_SOURCE_DIR}/build.rs
|
||||
${CMAKE_MODULE_PATH}/Cargo.toml.in
|
||||
${CMAKE_MODULE_PATH}/cbindgen.toml.in
|
||||
WORKING_DIRECTORY
|
||||
${CMAKE_SOURCE_DIR}
|
||||
COMMENT
|
||||
"Producing the bindings' artifacts with Cargo..."
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(${BINDINGS_NAME}_artifacts ALL
|
||||
DEPENDS ${CARGO_OUTPUT}
|
||||
)
|
||||
|
||||
add_library(${BINDINGS_NAME} STATIC IMPORTED GLOBAL)
|
||||
|
||||
target_include_directories(${BINDINGS_NAME} INTERFACE "${CBINDGEN_INCLUDEDIR}")
|
||||
|
||||
set_target_properties(
|
||||
${BINDINGS_NAME}
|
||||
PROPERTIES
|
||||
# \note Cargo writes a debug build into a nested directory instead of
|
||||
# decorating its name.
|
||||
DEBUG_POSTFIX ""
|
||||
DEFINE_SYMBOL ""
|
||||
IMPORTED_IMPLIB ""
|
||||
IMPORTED_LOCATION "${CARGO_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${BINDINGS_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}"
|
||||
IMPORTED_NO_SONAME "TRUE"
|
||||
IMPORTED_SONAME ""
|
||||
LINKER_LANGUAGE C
|
||||
PUBLIC_HEADER "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h"
|
||||
SOVERSION "${PROJECT_VERSION_MAJOR}"
|
||||
VERSION "${PROJECT_VERSION}"
|
||||
# \note Cargo exports all of the symbols automatically.
|
||||
WINDOWS_EXPORT_ALL_SYMBOLS "TRUE"
|
||||
)
|
||||
|
||||
target_compile_definitions(${BINDINGS_NAME} INTERFACE $<TARGET_PROPERTY:${BINDINGS_NAME},DEFINE_SYMBOL>)
|
||||
|
||||
set(UTILS_SUBDIR "utils")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h
|
||||
${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -DPROJECT_NAME=${PROJECT_NAME} -DLIBRARY_NAME=${LIBRARY_NAME} -DSUBDIR=${UTILS_SUBDIR} -P ${CMAKE_SOURCE_DIR}/cmake/enum-string-functions-gen.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c
|
||||
MAIN_DEPENDENCY
|
||||
${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
|
||||
DEPENDS
|
||||
${CMAKE_SOURCE_DIR}/cmake/enum-string-functions-gen.cmake
|
||||
WORKING_DIRECTORY
|
||||
${CMAKE_SOURCE_DIR}
|
||||
COMMENT
|
||||
"Generating the enum string functions with CMake..."
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(${LIBRARY_NAME}_utilities
|
||||
DEPENDS ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h
|
||||
${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c
|
||||
)
|
||||
|
||||
add_library(${LIBRARY_NAME})
|
||||
|
||||
target_compile_features(${LIBRARY_NAME} PRIVATE c_std_99)
|
||||
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||
|
||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
set(LIBRARY_DEPENDENCIES Threads::Threads ${CMAKE_DL_LIBS})
|
||||
|
||||
if(WIN32)
|
||||
list(APPEND LIBRARY_DEPENDENCIES Bcrypt userenv ws2_32)
|
||||
else()
|
||||
list(APPEND LIBRARY_DEPENDENCIES m)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${LIBRARY_NAME}
|
||||
PUBLIC ${BINDINGS_NAME}
|
||||
${LIBRARY_DEPENDENCIES}
|
||||
)
|
||||
|
||||
# \note An imported library's INTERFACE_INCLUDE_DIRECTORIES property can't
|
||||
# contain a non-existent path so its build-time include directory
|
||||
# must be specified for all of its dependent targets instead.
|
||||
target_include_directories(${LIBRARY_NAME}
|
||||
PUBLIC "$<BUILD_INTERFACE:${CBINDGEN_INCLUDEDIR};${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}>"
|
||||
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
|
||||
)
|
||||
|
||||
add_dependencies(${LIBRARY_NAME} ${BINDINGS_NAME}_artifacts)
|
||||
|
||||
# Generate the configuration header.
|
||||
# Generate and install the configuration header.
|
||||
math(EXPR INTEGER_PROJECT_VERSION_MAJOR "${PROJECT_VERSION_MAJOR} * 100000")
|
||||
|
||||
math(EXPR INTEGER_PROJECT_VERSION_MINOR "${PROJECT_VERSION_MINOR} * 100")
|
||||
|
||||
math(EXPR INTEGER_PROJECT_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
|
||||
|
||||
math(EXPR INTEGER_PROJECT_VERSION "${INTEGER_PROJECT_VERSION_MAJOR} + \
|
||||
${INTEGER_PROJECT_VERSION_MINOR} + \
|
||||
${INTEGER_PROJECT_VERSION_PATCH}")
|
||||
math(EXPR INTEGER_PROJECT_VERSION "${INTEGER_PROJECT_VERSION_MAJOR} + ${INTEGER_PROJECT_VERSION_MINOR} + ${INTEGER_PROJECT_VERSION_PATCH}")
|
||||
|
||||
configure_file(
|
||||
${CMAKE_MODULE_PATH}/config.h.in
|
||||
${CBINDGEN_TARGET_DIR}/config.h
|
||||
config.h
|
||||
@ONLY
|
||||
NEWLINE_STYLE LF
|
||||
)
|
||||
|
||||
target_sources(${LIBRARY_NAME}
|
||||
PRIVATE
|
||||
src/${UTILS_SUBDIR}/result.c
|
||||
src/${UTILS_SUBDIR}/stack_callback_data.c
|
||||
src/${UTILS_SUBDIR}/stack.c
|
||||
src/${UTILS_SUBDIR}/string.c
|
||||
${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c
|
||||
PUBLIC
|
||||
FILE_SET api TYPE HEADERS
|
||||
BASE_DIRS
|
||||
${CBINDGEN_INCLUDEDIR}
|
||||
${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}
|
||||
FILES
|
||||
${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
|
||||
${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h
|
||||
${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/result.h
|
||||
${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack_callback_data.h
|
||||
${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack.h
|
||||
${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/string.h
|
||||
INTERFACE
|
||||
FILE_SET config TYPE HEADERS
|
||||
BASE_DIRS
|
||||
${CBINDGEN_INCLUDEDIR}
|
||||
FILES
|
||||
${CBINDGEN_TARGET_DIR}/config.h
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS ${LIBRARY_NAME}
|
||||
EXPORT ${PROJECT_NAME}-config
|
||||
FILE_SET api
|
||||
FILE_SET config
|
||||
)
|
||||
|
||||
# \note Install the Cargo-built core bindings to enable direct linkage.
|
||||
install(
|
||||
FILES $<TARGET_PROPERTY:${BINDINGS_NAME},IMPORTED_LOCATION>
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
)
|
||||
|
||||
install(EXPORT ${PROJECT_NAME}-config
|
||||
FILE ${PROJECT_NAME}-config.cmake
|
||||
NAMESPACE "${PROJECT_NAME}::"
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${LIB}
|
||||
FILES ${CMAKE_BINARY_DIR}/config.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}
|
||||
)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
|
@ -300,6 +100,42 @@ if(BUILD_TESTING)
|
|||
enable_testing()
|
||||
endif()
|
||||
|
||||
add_subdirectory(docs)
|
||||
|
||||
add_subdirectory(examples EXCLUDE_FROM_ALL)
|
||||
|
||||
# Generate and install .cmake files
|
||||
set(PROJECT_CONFIG_NAME "${PROJECT_NAME}-config")
|
||||
|
||||
set(PROJECT_CONFIG_VERSION_NAME "${PROJECT_CONFIG_NAME}-version")
|
||||
|
||||
write_basic_package_version_file(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_VERSION_NAME}.cmake
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY ExactVersion
|
||||
)
|
||||
|
||||
# The namespace label starts with the title-cased library name.
|
||||
string(SUBSTRING ${LIBRARY_NAME} 0 1 NS_FIRST)
|
||||
|
||||
string(SUBSTRING ${LIBRARY_NAME} 1 -1 NS_REST)
|
||||
|
||||
string(TOUPPER ${NS_FIRST} NS_FIRST)
|
||||
|
||||
string(TOLOWER ${NS_REST} NS_REST)
|
||||
|
||||
string(CONCAT NAMESPACE ${NS_FIRST} ${NS_REST} "::")
|
||||
|
||||
# \note CMake doesn't automate the exporting of an imported library's targets
|
||||
# so the package configuration script must do it.
|
||||
configure_package_config_file(
|
||||
${CMAKE_MODULE_PATH}/${PROJECT_CONFIG_NAME}.cmake.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_NAME}.cmake
|
||||
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
|
||||
)
|
||||
|
||||
install(
|
||||
FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_NAME}.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_VERSION_NAME}.cmake
|
||||
DESTINATION
|
||||
${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
|
||||
)
|
||||
|
|
|
@ -7,8 +7,8 @@ license = "MIT"
|
|||
rust-version = "1.57.0"
|
||||
|
||||
[lib]
|
||||
name = "automerge_core"
|
||||
crate-type = ["staticlib"]
|
||||
name = "automerge"
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
bench = false
|
||||
doc = false
|
||||
|
||||
|
|
|
@ -1,29 +1,22 @@
|
|||
# Overview
|
||||
automerge-c exposes an API to C that can either be used directly or as a basis
|
||||
for other language bindings that have good support for calling into C functions.
|
||||
|
||||
automerge-c exposes a C API that can either be used directly or as the basis
|
||||
for other language bindings that have good support for calling C functions.
|
||||
# Building
|
||||
|
||||
# Installing
|
||||
See the main README for instructions on getting your environment set up, then
|
||||
you can use `./scripts/ci/cmake-build Release static` to build automerge-c.
|
||||
|
||||
See the main README for instructions on getting your environment set up and then
|
||||
you can build the automerge-c library and install its constituent files within
|
||||
a root directory of your choosing (e.g. "/usr/local") like so:
|
||||
```shell
|
||||
cmake -E make_directory automerge-c/build
|
||||
cmake -S automerge-c -B automerge-c/build
|
||||
cmake --build automerge-c/build
|
||||
cmake --install automerge-c/build --prefix "/usr/local"
|
||||
```
|
||||
Installation is important because the name, location and structure of CMake's
|
||||
out-of-source build subdirectory is subject to change based on the platform and
|
||||
the release version; generated headers like `automerge-c/config.h` and
|
||||
`automerge-c/utils/enum_string.h` are only sure to be found within their
|
||||
installed locations.
|
||||
It will output two files:
|
||||
|
||||
It's not obvious because they are versioned but the `Cargo.toml` and
|
||||
`cbindgen.toml` configuration files are also generated in order to ensure that
|
||||
the project name, project version and library name that they contain match those
|
||||
specified within the top-level `CMakeLists.txt` file.
|
||||
- ./build/Cargo/target/include/automerge-c/automerge.h
|
||||
- ./build/Cargo/target/release/libautomerge.a
|
||||
|
||||
To use these in your application you must arrange for your C compiler to find
|
||||
these files, either by moving them to the right location on your computer, or
|
||||
by configuring the compiler to reference these directories.
|
||||
|
||||
- `export LDFLAGS=-L./build/Cargo/target/release -lautomerge`
|
||||
- `export CFLAGS=-I./build/Cargo/target/include`
|
||||
|
||||
If you'd like to cross compile the library for different platforms you can do so
|
||||
using [cross](https://github.com/cross-rs/cross). For example:
|
||||
|
@ -32,176 +25,134 @@ using [cross](https://github.com/cross-rs/cross). For example:
|
|||
|
||||
This will output a shared library in the directory `rust/target/aarch64-unknown-linux-gnu/release/`.
|
||||
|
||||
You can replace `aarch64-unknown-linux-gnu` with any
|
||||
[cross supported targets](https://github.com/cross-rs/cross#supported-targets).
|
||||
The targets below are known to work, though other targets are expected to work
|
||||
too:
|
||||
You can replace `aarch64-unknown-linux-gnu` with any [cross supported targets](https://github.com/cross-rs/cross#supported-targets). The targets below are known to work, though other targets are expected to work too:
|
||||
|
||||
- `x86_64-apple-darwin`
|
||||
- `aarch64-apple-darwin`
|
||||
- `x86_64-unknown-linux-gnu`
|
||||
- `aarch64-unknown-linux-gnu`
|
||||
|
||||
As a caveat, CMake generates the `automerge.h` header file in terms of the
|
||||
processor architecture of the computer on which it was built so, for example,
|
||||
don't use a header generated for a 64-bit processor if your target is a 32-bit
|
||||
processor.
|
||||
As a caveat, the header file is currently 32/64-bit dependant. You can re-use it
|
||||
for all 64-bit architectures, but you must generate a specific header for 32-bit
|
||||
targets.
|
||||
|
||||
# Usage
|
||||
|
||||
You can build and view the C API's HTML reference documentation like so:
|
||||
```shell
|
||||
cmake -E make_directory automerge-c/build
|
||||
cmake -S automerge-c -B automerge-c/build
|
||||
cmake --build automerge-c/build --target automerge_docs
|
||||
firefox automerge-c/build/src/html/index.html
|
||||
```
|
||||
|
||||
To get started quickly, look at the
|
||||
For full reference, read through `automerge.h`, or to get started quickly look
|
||||
at the
|
||||
[examples](https://github.com/automerge/automerge-rs/tree/main/rust/automerge-c/examples).
|
||||
|
||||
Almost all operations in automerge-c act on an Automerge document
|
||||
(`AMdoc` struct) which is structurally similar to a JSON document.
|
||||
Almost all operations in automerge-c act on an AMdoc struct which you can get
|
||||
from `AMcreate()` or `AMload()`. Operations on a given doc are not thread safe
|
||||
so you must use a mutex or similar to avoid calling more than one function with
|
||||
the same AMdoc pointer concurrently.
|
||||
|
||||
You can get a document by calling either `AMcreate()` or `AMload()`. Operations
|
||||
on a given document are not thread-safe so you must use a mutex or similar to
|
||||
avoid calling more than one function on the same one concurrently.
|
||||
As with all functions that either allocate memory, or could fail if given
|
||||
invalid input, `AMcreate()` returns an `AMresult`. The `AMresult` contains the
|
||||
returned doc (or error message), and must be freed with `AMfree()` after you are
|
||||
done to avoid leaking memory.
|
||||
|
||||
A C API function that could succeed or fail returns a result (`AMresult` struct)
|
||||
containing a status code (`AMstatus` enum) and either a sequence of at least one
|
||||
item (`AMitem` struct) or a read-only view onto a UTF-8 error message string
|
||||
(`AMbyteSpan` struct).
|
||||
An item contains up to three components: an index within its parent object
|
||||
(`AMbyteSpan` struct or `size_t`), a unique identifier (`AMobjId` struct) and a
|
||||
value.
|
||||
The result of a successful function call that doesn't produce any values will
|
||||
contain a single item that is void (`AM_VAL_TYPE_VOID`).
|
||||
A returned result **must** be passed to `AMresultFree()` once the item(s) or
|
||||
error message it contains is no longer needed in order to avoid a memory leak.
|
||||
```
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <automerge-c/automerge.h>
|
||||
#include <automerge-c/utils/string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
AMresult *docResult = AMcreate(NULL);
|
||||
|
||||
if (AMresultStatus(docResult) != AM_STATUS_OK) {
|
||||
char* const err_msg = AMstrdup(AMresultError(docResult), NULL);
|
||||
printf("failed to create doc: %s", err_msg);
|
||||
free(err_msg);
|
||||
printf("failed to create doc: %s", AMerrorMessage(docResult).src);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
AMdoc *doc;
|
||||
AMitemToDoc(AMresultItem(docResult), &doc);
|
||||
AMdoc *doc = AMresultValue(docResult).doc;
|
||||
|
||||
// useful code goes here!
|
||||
|
||||
cleanup:
|
||||
AMresultFree(docResult);
|
||||
AMfree(docResult);
|
||||
}
|
||||
```
|
||||
|
||||
If you are writing an application in C, the `AMstackItem()`, `AMstackItems()`
|
||||
and `AMstackResult()` functions enable the lifetimes of anonymous results to be
|
||||
centrally managed and allow the same validation logic to be reused without
|
||||
relying upon the `goto` statement (see examples/quickstart.c).
|
||||
If you are writing code in C directly, you can use the `AMpush()` helper
|
||||
function to reduce the boilerplate of error handling and freeing for you (see
|
||||
examples/quickstart.c).
|
||||
|
||||
If you are wrapping automerge-c in another language, particularly one that has a
|
||||
garbage collector, you can call the `AMresultFree()` function within a finalizer
|
||||
to ensure that memory is reclaimed when it is no longer needed.
|
||||
garbage collector, you can call `AMfree` within a finalizer to ensure that memory
|
||||
is reclaimed when it is no longer needed.
|
||||
|
||||
Automerge documents consist of a mutable root which is always a map from string
|
||||
keys to values. A value can be one of the following types:
|
||||
An AMdoc wraps an automerge document which are very similar to JSON documents.
|
||||
Automerge documents consist of a mutable root, which is always a map from string
|
||||
keys to values. Values can have the following types:
|
||||
|
||||
- A number of type double / int64_t / uint64_t
|
||||
- An explicit true / false / null
|
||||
- An immutable UTF-8 string (`AMbyteSpan`).
|
||||
- An immutable array of arbitrary bytes (`AMbyteSpan`).
|
||||
- A mutable map from string keys to values.
|
||||
- A mutable list of values.
|
||||
- A mutable UTF-8 string.
|
||||
- An explicit true / false / nul
|
||||
- An immutable utf-8 string (AMbyteSpan)
|
||||
- An immutable array of arbitrary bytes (AMbyteSpan)
|
||||
- A mutable map from string keys to values (AMmap)
|
||||
- A mutable list of values (AMlist)
|
||||
- A mutable string (AMtext)
|
||||
|
||||
If you read from a location in the document with no value, an item with type
|
||||
`AM_VAL_TYPE_VOID` will be returned, but you cannot write such a value
|
||||
explicitly.
|
||||
If you read from a location in the document with no value a value with
|
||||
`.tag == AM_VALUE_VOID` will be returned, but you cannot write such a value explicitly.
|
||||
|
||||
Under the hood, automerge references a mutable object by its object identifier
|
||||
where `AM_ROOT` signifies a document's root map object.
|
||||
Under the hood, automerge references mutable objects by the internal object id,
|
||||
and `AM_ROOT` is always the object id of the root value.
|
||||
|
||||
There are functions to put each type of value into either a map or a list, and
|
||||
functions to read the current or a historical value from a map or a list. As (in general) collaborators
|
||||
There is a function to put each type of value into either a map or a list, and a
|
||||
function to read the current value from a list. As (in general) collaborators
|
||||
may edit the document at any time, you cannot guarantee that the type of the
|
||||
value at a given part of the document will stay the same. As a result, reading
|
||||
from the document will return an `AMitem` struct that you can inspect to
|
||||
determine the type of value that it contains.
|
||||
value at a given part of the document will stay the same. As a result reading
|
||||
from the document will return an `AMvalue` union that you can inspect to
|
||||
determine its type.
|
||||
|
||||
Strings in automerge-c are represented using an `AMbyteSpan` which contains a
|
||||
pointer and a length. Strings must be valid UTF-8 and may contain NUL (`0`)
|
||||
characters.
|
||||
For your convenience, you can call `AMstr()` to get the `AMbyteSpan` struct
|
||||
equivalent of a null-terminated byte string or `AMstrdup()` to get the
|
||||
representation of an `AMbyteSpan` struct as a null-terminated byte string
|
||||
wherein its NUL characters have been removed/replaced as you choose.
|
||||
pointer and a length. Strings must be valid utf-8 and may contain null bytes.
|
||||
As a convenience you can use `AMstr()` to get the representation of a
|
||||
null-terminated C string as an `AMbyteSpan`.
|
||||
|
||||
Putting all of that together, to read and write from the root of the document
|
||||
you can do this:
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <automerge-c/automerge.h>
|
||||
#include <automerge-c/utils/string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// ...previous example...
|
||||
AMdoc *doc;
|
||||
AMitemToDoc(AMresultItem(docResult), &doc);
|
||||
AMdoc *doc = AMresultValue(docResult).doc;
|
||||
|
||||
AMresult *putResult = AMmapPutStr(doc, AM_ROOT, AMstr("key"), AMstr("value"));
|
||||
if (AMresultStatus(putResult) != AM_STATUS_OK) {
|
||||
char* const err_msg = AMstrdup(AMresultError(putResult), NULL);
|
||||
printf("failed to put: %s", err_msg);
|
||||
free(err_msg);
|
||||
printf("failed to put: %s", AMerrorMessage(putResult).src);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
AMresult *getResult = AMmapGet(doc, AM_ROOT, AMstr("key"), NULL);
|
||||
if (AMresultStatus(getResult) != AM_STATUS_OK) {
|
||||
char* const err_msg = AMstrdup(AMresultError(putResult), NULL);
|
||||
printf("failed to get: %s", err_msg);
|
||||
free(err_msg);
|
||||
printf("failed to get: %s", AMerrorMessage(getResult).src);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
AMbyteSpan got;
|
||||
if (AMitemToStr(AMresultItem(getResult), &got)) {
|
||||
char* const c_str = AMstrdup(got, NULL);
|
||||
printf("Got %zu-character string \"%s\"", got.count, c_str);
|
||||
free(c_str);
|
||||
} else {
|
||||
AMvalue got = AMresultValue(getResult);
|
||||
if (got.tag != AM_VALUE_STR) {
|
||||
printf("expected to read a string!");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
printf("Got %zu-character string `%s`", got.str.count, got.str.src);
|
||||
|
||||
cleanup:
|
||||
AMresultFree(getResult);
|
||||
AMresultFree(putResult);
|
||||
AMresultFree(docResult);
|
||||
AMfree(getResult);
|
||||
AMfree(putResult);
|
||||
AMfree(docResult);
|
||||
}
|
||||
```
|
||||
|
||||
Functions that do not return an `AMresult` (for example `AMitemKey()`) do
|
||||
not allocate memory but rather reference memory that was previously
|
||||
allocated. It's therefore important to keep the original `AMresult` alive (in
|
||||
this case the one returned by `AMmapRange()`) until after you are finished with
|
||||
the items that it contains. However, the memory for an individual `AMitem` can
|
||||
be shared with a new `AMresult` by calling `AMitemResult()` on it. In other
|
||||
words, a select group of items can be filtered out of a collection and only each
|
||||
one's corresponding `AMresult` must be kept alive from that point forward; the
|
||||
originating collection's `AMresult` can be safely freed.
|
||||
Functions that do not return an `AMresult` (for example `AMmapItemValue()`) do
|
||||
not allocate memory, but continue to reference memory that was previously
|
||||
allocated. It's thus important to keep the original `AMresult` alive (in this
|
||||
case the one returned by `AMmapRange()`) until after you are done with the return
|
||||
values of these functions.
|
||||
|
||||
Beyond that, good luck!
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
after_includes = """\n
|
||||
/**
|
||||
* \\defgroup enumerations Public Enumerations
|
||||
* Symbolic names for integer constants.
|
||||
Symbolic names for integer constants.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -12,23 +12,21 @@ after_includes = """\n
|
|||
#define AM_ROOT NULL
|
||||
|
||||
/**
|
||||
* \\memberof AMdoc
|
||||
* \\memberof AMchangeHash
|
||||
* \\def AM_CHANGE_HASH_SIZE
|
||||
* \\brief The count of bytes in a change hash.
|
||||
*/
|
||||
#define AM_CHANGE_HASH_SIZE 32
|
||||
"""
|
||||
autogen_warning = """
|
||||
/**
|
||||
* \\file
|
||||
* \\brief All constants, functions and types in the core Automerge C API.
|
||||
*
|
||||
* \\warning This file is auto-generated by cbindgen.
|
||||
*/
|
||||
"""
|
||||
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
|
||||
documentation = true
|
||||
documentation_style = "doxy"
|
||||
include_guard = "AUTOMERGE_C_H"
|
||||
header = """
|
||||
/** \\file
|
||||
* All constants, functions and types in the Automerge library's C API.
|
||||
*/
|
||||
"""
|
||||
include_guard = "AUTOMERGE_H"
|
||||
includes = []
|
||||
language = "C"
|
||||
line_length = 140
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
[package]
|
||||
name = "@PROJECT_NAME@"
|
||||
version = "@PROJECT_VERSION@"
|
||||
authors = ["Orion Henry <orion.henry@gmail.com>", "Jason Kankiewicz <jason.kankiewicz@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.57.0"
|
||||
|
||||
[lib]
|
||||
name = "@BINDINGS_NAME@"
|
||||
crate-type = ["staticlib"]
|
||||
bench = false
|
||||
doc = false
|
||||
|
||||
[dependencies]
|
||||
@LIBRARY_NAME@ = { path = "../@LIBRARY_NAME@" }
|
||||
hex = "^0.4.3"
|
||||
libc = "^0.2"
|
||||
smol_str = "^0.1.21"
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "^0.24"
|
|
@ -1,48 +0,0 @@
|
|||
after_includes = """\n
|
||||
/**
|
||||
* \\defgroup enumerations Public Enumerations
|
||||
* Symbolic names for integer constants.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \\memberof AMdoc
|
||||
* \\def AM_ROOT
|
||||
* \\brief The root object of a document.
|
||||
*/
|
||||
#define AM_ROOT NULL
|
||||
|
||||
/**
|
||||
* \\memberof AMdoc
|
||||
* \\def AM_CHANGE_HASH_SIZE
|
||||
* \\brief The count of bytes in a change hash.
|
||||
*/
|
||||
#define AM_CHANGE_HASH_SIZE 32
|
||||
"""
|
||||
autogen_warning = """
|
||||
/**
|
||||
* \\file
|
||||
* \\brief All constants, functions and types in the core Automerge C API.
|
||||
*
|
||||
* \\warning This file is auto-generated by cbindgen.
|
||||
*/
|
||||
"""
|
||||
documentation = true
|
||||
documentation_style = "doxy"
|
||||
include_guard = "@INCLUDE_GUARD_PREFIX@_H"
|
||||
includes = []
|
||||
language = "C"
|
||||
line_length = 140
|
||||
no_includes = true
|
||||
style = "both"
|
||||
sys_includes = ["stdbool.h", "stddef.h", "stdint.h", "time.h"]
|
||||
usize_is_size_t = true
|
||||
|
||||
[enum]
|
||||
derive_const_casts = true
|
||||
enum_class = true
|
||||
must_use = "MUST_USE_ENUM"
|
||||
prefix_with_name = true
|
||||
rename_variants = "ScreamingSnakeCase"
|
||||
|
||||
[export]
|
||||
item_types = ["constants", "enums", "functions", "opaque", "structs", "typedefs"]
|
|
@ -1,35 +1,14 @@
|
|||
#ifndef @INCLUDE_GUARD_PREFIX@_CONFIG_H
|
||||
#define @INCLUDE_GUARD_PREFIX@_CONFIG_H
|
||||
/**
|
||||
* \file
|
||||
* \brief Configuration pararameters defined by the build system.
|
||||
*
|
||||
* \warning This file is auto-generated by CMake.
|
||||
*/
|
||||
#ifndef @SYMBOL_PREFIX@_CONFIG_H
|
||||
#define @SYMBOL_PREFIX@_CONFIG_H
|
||||
|
||||
/* This header is auto-generated by CMake. */
|
||||
|
||||
/**
|
||||
* \def @SYMBOL_PREFIX@_VERSION
|
||||
* \brief Denotes a semantic version of the form {MAJOR}{MINOR}{PATCH} as three,
|
||||
* two-digit decimal numbers without leading zeros (e.g. 100 is 0.1.0).
|
||||
*/
|
||||
#define @SYMBOL_PREFIX@_VERSION @INTEGER_PROJECT_VERSION@
|
||||
|
||||
/**
|
||||
* \def @SYMBOL_PREFIX@_MAJOR_VERSION
|
||||
* \brief Denotes a semantic major version as a decimal number.
|
||||
*/
|
||||
#define @SYMBOL_PREFIX@_MAJOR_VERSION (@SYMBOL_PREFIX@_VERSION / 100000)
|
||||
|
||||
/**
|
||||
* \def @SYMBOL_PREFIX@_MINOR_VERSION
|
||||
* \brief Denotes a semantic minor version as a decimal number.
|
||||
*/
|
||||
#define @SYMBOL_PREFIX@_MINOR_VERSION ((@SYMBOL_PREFIX@_VERSION / 100) % 1000)
|
||||
|
||||
/**
|
||||
* \def @SYMBOL_PREFIX@_PATCH_VERSION
|
||||
* \brief Denotes a semantic patch version as a decimal number.
|
||||
*/
|
||||
#define @SYMBOL_PREFIX@_PATCH_VERSION (@SYMBOL_PREFIX@_VERSION % 100)
|
||||
|
||||
#endif /* @INCLUDE_GUARD_PREFIX@_CONFIG_H */
|
||||
#endif /* @SYMBOL_PREFIX@_CONFIG_H */
|
||||
|
|
|
@ -1,183 +0,0 @@
|
|||
# This CMake script is used to generate a header and a source file for utility
|
||||
# functions that convert the tags of generated enum types into strings and
|
||||
# strings into the tags of generated enum types.
|
||||
cmake_minimum_required(VERSION 3.23 FATAL_ERROR)
|
||||
|
||||
# Seeks the starting line of the source enum's declaration.
|
||||
macro(seek_enum_mode)
|
||||
if (line MATCHES "^(typedef[ \t]+)?enum ")
|
||||
string(REGEX REPLACE "^enum ([0-9a-zA-Z_]+).*$" "\\1" enum_name "${line}")
|
||||
set(mode "read_tags")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
# Scans the input for the current enum's tags.
|
||||
macro(read_tags_mode)
|
||||
if(line MATCHES "^}")
|
||||
set(mode "generate")
|
||||
elseif(line MATCHES "^[A-Z0-9_]+.*$")
|
||||
string(REGEX REPLACE "^([A-Za-z0-9_]+).*$" "\\1" tmp "${line}")
|
||||
list(APPEND enum_tags "${tmp}")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro(write_header_file)
|
||||
# Generate a to-string function declaration.
|
||||
list(APPEND header_body
|
||||
"/**\n"
|
||||
" * \\ingroup enumerations\n"
|
||||
" * \\brief Gets the string representation of an `${enum_name}` enum tag.\n"
|
||||
" *\n"
|
||||
" * \\param[in] tag An `${enum_name}` enum tag.\n"
|
||||
" * \\return A null-terminated byte string.\n"
|
||||
" */\n"
|
||||
"char const* ${enum_name}ToString(${enum_name} const tag)\;\n"
|
||||
"\n")
|
||||
# Generate a from-string function declaration.
|
||||
list(APPEND header_body
|
||||
"/**\n"
|
||||
" * \\ingroup enumerations\n"
|
||||
" * \\brief Gets an `${enum_name}` enum tag from its string representation.\n"
|
||||
" *\n"
|
||||
" * \\param[out] dest An `${enum_name}` enum tag pointer.\n"
|
||||
" * \\param[in] src A null-terminated byte string.\n"
|
||||
" * \\return `true` if \\p src matches the string representation of an\n"
|
||||
" * `${enum_name}` enum tag, `false` otherwise.\n"
|
||||
" */\n"
|
||||
"bool ${enum_name}FromString(${enum_name}* dest, char const* const src)\;\n"
|
||||
"\n")
|
||||
endmacro()
|
||||
|
||||
macro(write_source_file)
|
||||
# Generate a to-string function implementation.
|
||||
list(APPEND source_body
|
||||
"char const* ${enum_name}ToString(${enum_name} const tag) {\n"
|
||||
" switch (tag) {\n"
|
||||
" default:\n"
|
||||
" return \"???\"\;\n")
|
||||
foreach(label IN LISTS enum_tags)
|
||||
list(APPEND source_body
|
||||
" case ${label}:\n"
|
||||
" return \"${label}\"\;\n")
|
||||
endforeach()
|
||||
list(APPEND source_body
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n")
|
||||
# Generate a from-string function implementation.
|
||||
list(APPEND source_body
|
||||
"bool ${enum_name}FromString(${enum_name}* dest, char const* const src) {\n")
|
||||
foreach(label IN LISTS enum_tags)
|
||||
list(APPEND source_body
|
||||
" if (!strcmp(src, \"${label}\")) {\n"
|
||||
" *dest = ${label}\;\n"
|
||||
" return true\;\n"
|
||||
" }\n")
|
||||
endforeach()
|
||||
list(APPEND source_body
|
||||
" return false\;\n"
|
||||
"}\n"
|
||||
"\n")
|
||||
endmacro()
|
||||
|
||||
function(main)
|
||||
set(header_body "")
|
||||
# File header and includes.
|
||||
list(APPEND header_body
|
||||
"#ifndef ${include_guard}\n"
|
||||
"#define ${include_guard}\n"
|
||||
"/**\n"
|
||||
" * \\file\n"
|
||||
" * \\brief Utility functions for converting enum tags into null-terminated\n"
|
||||
" * byte strings and vice versa.\n"
|
||||
" *\n"
|
||||
" * \\warning This file is auto-generated by CMake.\n"
|
||||
" */\n"
|
||||
"\n"
|
||||
"#include <stdbool.h>\n"
|
||||
"\n"
|
||||
"#include <${library_include}>\n"
|
||||
"\n")
|
||||
set(source_body "")
|
||||
# File includes.
|
||||
list(APPEND source_body
|
||||
"/** \\warning This file is auto-generated by CMake. */\n"
|
||||
"\n"
|
||||
"#include \"stdio.h\"\n"
|
||||
"#include \"string.h\"\n"
|
||||
"\n"
|
||||
"#include <${header_include}>\n"
|
||||
"\n")
|
||||
set(enum_name "")
|
||||
set(enum_tags "")
|
||||
set(mode "seek_enum")
|
||||
file(STRINGS "${input_path}" lines)
|
||||
foreach(line IN LISTS lines)
|
||||
string(REGEX REPLACE "^(.+)(//.*)?" "\\1" line "${line}")
|
||||
string(STRIP "${line}" line)
|
||||
if(mode STREQUAL "seek_enum")
|
||||
seek_enum_mode()
|
||||
elseif(mode STREQUAL "read_tags")
|
||||
read_tags_mode()
|
||||
else()
|
||||
# The end of the enum declaration was reached.
|
||||
if(NOT enum_name)
|
||||
# The end of the file was reached.
|
||||
return()
|
||||
endif()
|
||||
if(NOT enum_tags)
|
||||
message(FATAL_ERROR "No tags found for `${enum_name}`.")
|
||||
endif()
|
||||
string(TOLOWER "${enum_name}" output_stem_prefix)
|
||||
string(CONCAT output_stem "${output_stem_prefix}" "_string")
|
||||
cmake_path(REPLACE_EXTENSION output_stem "h" OUTPUT_VARIABLE output_header_basename)
|
||||
write_header_file()
|
||||
write_source_file()
|
||||
set(enum_name "")
|
||||
set(enum_tags "")
|
||||
set(mode "seek_enum")
|
||||
endif()
|
||||
endforeach()
|
||||
# File footer.
|
||||
list(APPEND header_body
|
||||
"#endif /* ${include_guard} */\n")
|
||||
message(STATUS "Generating header file \"${output_header_path}\"...")
|
||||
file(WRITE "${output_header_path}" ${header_body})
|
||||
message(STATUS "Generating source file \"${output_source_path}\"...")
|
||||
file(WRITE "${output_source_path}" ${source_body})
|
||||
endfunction()
|
||||
|
||||
if(NOT DEFINED PROJECT_NAME)
|
||||
message(FATAL_ERROR "Variable PROJECT_NAME is not defined.")
|
||||
elseif(NOT DEFINED LIBRARY_NAME)
|
||||
message(FATAL_ERROR "Variable LIBRARY_NAME is not defined.")
|
||||
elseif(NOT DEFINED SUBDIR)
|
||||
message(FATAL_ERROR "Variable SUBDIR is not defined.")
|
||||
elseif(${CMAKE_ARGC} LESS 9)
|
||||
message(FATAL_ERROR "Too few arguments.")
|
||||
elseif(${CMAKE_ARGC} GREATER 10)
|
||||
message(FATAL_ERROR "Too many arguments.")
|
||||
elseif(NOT EXISTS ${CMAKE_ARGV5})
|
||||
message(FATAL_ERROR "Input header \"${CMAKE_ARGV7}\" not found.")
|
||||
endif()
|
||||
cmake_path(CONVERT "${CMAKE_ARGV7}" TO_CMAKE_PATH_LIST input_path NORMALIZE)
|
||||
cmake_path(CONVERT "${CMAKE_ARGV8}" TO_CMAKE_PATH_LIST output_header_path NORMALIZE)
|
||||
cmake_path(CONVERT "${CMAKE_ARGV9}" TO_CMAKE_PATH_LIST output_source_path NORMALIZE)
|
||||
string(TOLOWER "${PROJECT_NAME}" project_root)
|
||||
cmake_path(CONVERT "${SUBDIR}" TO_CMAKE_PATH_LIST project_subdir NORMALIZE)
|
||||
string(TOLOWER "${project_subdir}" project_subdir)
|
||||
string(TOLOWER "${LIBRARY_NAME}" library_stem)
|
||||
cmake_path(REPLACE_EXTENSION library_stem "h" OUTPUT_VARIABLE library_basename)
|
||||
string(JOIN "/" library_include "${project_root}" "${library_basename}")
|
||||
string(TOUPPER "${PROJECT_NAME}" project_name_upper)
|
||||
string(TOUPPER "${project_subdir}" include_guard_infix)
|
||||
string(REGEX REPLACE "/" "_" include_guard_infix "${include_guard_infix}")
|
||||
string(REGEX REPLACE "-" "_" include_guard_prefix "${project_name_upper}")
|
||||
string(JOIN "_" include_guard_prefix "${include_guard_prefix}" "${include_guard_infix}")
|
||||
string(JOIN "/" output_header_prefix "${project_root}" "${project_subdir}")
|
||||
cmake_path(GET output_header_path STEM output_header_stem)
|
||||
string(TOUPPER "${output_header_stem}" include_guard_stem)
|
||||
string(JOIN "_" include_guard "${include_guard_prefix}" "${include_guard_stem}" "H")
|
||||
cmake_path(GET output_header_path FILENAME output_header_basename)
|
||||
string(JOIN "/" header_include "${output_header_prefix}" "${output_header_basename}")
|
||||
main()
|
|
@ -1,6 +1,4 @@
|
|||
# This CMake script is used to perform string substitutions within a generated
|
||||
# file.
|
||||
cmake_minimum_required(VERSION 3.23 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||
|
||||
if(NOT DEFINED MATCH_REGEX)
|
||||
message(FATAL_ERROR "Variable \"MATCH_REGEX\" is not defined.")
|
|
@ -1,6 +1,4 @@
|
|||
# This CMake script is used to force Cargo to regenerate the header file for the
|
||||
# core bindings after the out-of-source build directory has been cleaned.
|
||||
cmake_minimum_required(VERSION 3.23 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||
|
||||
if(NOT DEFINED CONDITION)
|
||||
message(FATAL_ERROR "Variable \"CONDITION\" is not defined.")
|
|
@ -1,35 +0,0 @@
|
|||
find_package(Doxygen OPTIONAL_COMPONENTS dot)
|
||||
|
||||
if(DOXYGEN_FOUND)
|
||||
set(DOXYGEN_ALIASES "installed_headerfile=\\headerfile ${LIBRARY_NAME}.h <${PROJECT_NAME}/${LIBRARY_NAME}.h>")
|
||||
|
||||
set(DOXYGEN_GENERATE_LATEX YES)
|
||||
|
||||
set(DOXYGEN_PDF_HYPERLINKS YES)
|
||||
|
||||
set(DOXYGEN_PROJECT_LOGO "${CMAKE_CURRENT_SOURCE_DIR}/img/brandmark.png")
|
||||
|
||||
set(DOXYGEN_SORT_BRIEF_DOCS YES)
|
||||
|
||||
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md")
|
||||
|
||||
doxygen_add_docs(
|
||||
${LIBRARY_NAME}_docs
|
||||
"${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h"
|
||||
"${CBINDGEN_TARGET_DIR}/config.h"
|
||||
"${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h"
|
||||
"${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/result.h"
|
||||
"${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack_callback_data.h"
|
||||
"${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack.h"
|
||||
"${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/string.h"
|
||||
"${CMAKE_SOURCE_DIR}/README.md"
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
COMMENT "Producing documentation with Doxygen..."
|
||||
)
|
||||
|
||||
# \note A Doxygen input file isn't a file-level dependency so the Doxygen
|
||||
# command must instead depend upon a target that either outputs the
|
||||
# file or depends upon it also or it will just output an error message
|
||||
# when it can't be found.
|
||||
add_dependencies(${LIBRARY_NAME}_docs ${BINDINGS_NAME}_artifacts ${LIBRARY_NAME}_utilities)
|
||||
endif()
|
|
@ -1,39 +1,41 @@
|
|||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||
|
||||
add_executable(
|
||||
${LIBRARY_NAME}_quickstart
|
||||
example_quickstart
|
||||
quickstart.c
|
||||
)
|
||||
|
||||
set_target_properties(${LIBRARY_NAME}_quickstart PROPERTIES LINKER_LANGUAGE C)
|
||||
set_target_properties(example_quickstart PROPERTIES LINKER_LANGUAGE C)
|
||||
|
||||
# \note An imported library's INTERFACE_INCLUDE_DIRECTORIES property can't
|
||||
# contain a non-existent path so its build-time include directory
|
||||
# must be specified for all of its dependent targets instead.
|
||||
target_include_directories(
|
||||
${LIBRARY_NAME}_quickstart
|
||||
example_quickstart
|
||||
PRIVATE "$<BUILD_INTERFACE:${CBINDGEN_INCLUDEDIR}>"
|
||||
)
|
||||
|
||||
target_link_libraries(${LIBRARY_NAME}_quickstart PRIVATE ${LIBRARY_NAME})
|
||||
target_link_libraries(example_quickstart PRIVATE ${LIBRARY_NAME})
|
||||
|
||||
add_dependencies(${LIBRARY_NAME}_quickstart ${BINDINGS_NAME}_artifacts)
|
||||
add_dependencies(example_quickstart ${LIBRARY_NAME}_artifacts)
|
||||
|
||||
if(BUILD_SHARED_LIBS AND WIN32)
|
||||
add_custom_command(
|
||||
TARGET ${LIBRARY_NAME}_quickstart
|
||||
TARGET example_quickstart
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}
|
||||
${CMAKE_BINARY_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Copying the DLL built by Cargo into the examples directory..."
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${LIBRARY_NAME}_quickstart
|
||||
TARGET example_quickstart
|
||||
POST_BUILD
|
||||
COMMAND
|
||||
${LIBRARY_NAME}_quickstart
|
||||
example_quickstart
|
||||
COMMENT
|
||||
"Running the example quickstart..."
|
||||
VERBATIM
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
```shell
|
||||
cmake -E make_directory automerge-c/build
|
||||
cmake -S automerge-c -B automerge-c/build
|
||||
cmake --build automerge-c/build --target automerge_quickstart
|
||||
cmake --build automerge-c/build --target example_quickstart
|
||||
```
|
||||
|
|
|
@ -3,127 +3,152 @@
|
|||
#include <string.h>
|
||||
|
||||
#include <automerge-c/automerge.h>
|
||||
#include <automerge-c/utils/enum_string.h>
|
||||
#include <automerge-c/utils/stack.h>
|
||||
#include <automerge-c/utils/stack_callback_data.h>
|
||||
#include <automerge-c/utils/string.h>
|
||||
|
||||
static bool abort_cb(AMstack**, void*);
|
||||
static void abort_cb(AMresultStack**, uint8_t);
|
||||
|
||||
/**
|
||||
* \brief Based on https://automerge.github.io/docs/quickstart
|
||||
*/
|
||||
int main(int argc, char** argv) {
|
||||
AMstack* stack = NULL;
|
||||
AMdoc* doc1;
|
||||
AMitemToDoc(AMstackItem(&stack, AMcreate(NULL), abort_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1);
|
||||
AMobjId const* const cards =
|
||||
AMitemObjId(AMstackItem(&stack, AMmapPutObject(doc1, AM_ROOT, AMstr("cards"), AM_OBJ_TYPE_LIST), abort_cb,
|
||||
AMexpect(AM_VAL_TYPE_OBJ_TYPE)));
|
||||
AMobjId const* const card1 =
|
||||
AMitemObjId(AMstackItem(&stack, AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), abort_cb,
|
||||
AMexpect(AM_VAL_TYPE_OBJ_TYPE)));
|
||||
AMstackItem(NULL, AMmapPutStr(doc1, card1, AMstr("title"), AMstr("Rewrite everything in Clojure")), abort_cb,
|
||||
AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMstackItem(NULL, AMmapPutBool(doc1, card1, AMstr("done"), false), abort_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMobjId const* const card2 =
|
||||
AMitemObjId(AMstackItem(&stack, AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), abort_cb,
|
||||
AMexpect(AM_VAL_TYPE_OBJ_TYPE)));
|
||||
AMstackItem(NULL, AMmapPutStr(doc1, card2, AMstr("title"), AMstr("Rewrite everything in Haskell")), abort_cb,
|
||||
AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMstackItem(NULL, AMmapPutBool(doc1, card2, AMstr("done"), false), abort_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMstackItem(NULL, AMcommit(doc1, AMstr("Add card"), NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH));
|
||||
AMresultStack* stack = NULL;
|
||||
AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, abort_cb).doc;
|
||||
AMobjId const* const cards = AMpush(&stack,
|
||||
AMmapPutObject(doc1, AM_ROOT, AMstr("cards"), AM_OBJ_TYPE_LIST),
|
||||
AM_VALUE_OBJ_ID,
|
||||
abort_cb).obj_id;
|
||||
AMobjId const* const card1 = AMpush(&stack,
|
||||
AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP),
|
||||
AM_VALUE_OBJ_ID,
|
||||
abort_cb).obj_id;
|
||||
AMfree(AMmapPutStr(doc1, card1, AMstr("title"), AMstr("Rewrite everything in Clojure")));
|
||||
AMfree(AMmapPutBool(doc1, card1, AMstr("done"), false));
|
||||
AMobjId const* const card2 = AMpush(&stack,
|
||||
AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP),
|
||||
AM_VALUE_OBJ_ID,
|
||||
abort_cb).obj_id;
|
||||
AMfree(AMmapPutStr(doc1, card2, AMstr("title"), AMstr("Rewrite everything in Haskell")));
|
||||
AMfree(AMmapPutBool(doc1, card2, AMstr("done"), false));
|
||||
AMfree(AMcommit(doc1, AMstr("Add card"), NULL));
|
||||
|
||||
AMdoc* doc2;
|
||||
AMitemToDoc(AMstackItem(&stack, AMcreate(NULL), abort_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2);
|
||||
AMstackItem(NULL, AMmerge(doc2, doc1), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH));
|
||||
AMdoc* doc2 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, abort_cb).doc;
|
||||
AMfree(AMmerge(doc2, doc1));
|
||||
|
||||
AMbyteSpan binary;
|
||||
AMitemToBytes(AMstackItem(&stack, AMsave(doc1), abort_cb, AMexpect(AM_VAL_TYPE_BYTES)), &binary);
|
||||
AMitemToDoc(AMstackItem(&stack, AMload(binary.src, binary.count), abort_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2);
|
||||
AMbyteSpan const binary = AMpush(&stack, AMsave(doc1), AM_VALUE_BYTES, abort_cb).bytes;
|
||||
doc2 = AMpush(&stack, AMload(binary.src, binary.count), AM_VALUE_DOC, abort_cb).doc;
|
||||
|
||||
AMstackItem(NULL, AMmapPutBool(doc1, card1, AMstr("done"), true), abort_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMstackItem(NULL, AMcommit(doc1, AMstr("Mark card as done"), NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH));
|
||||
AMfree(AMmapPutBool(doc1, card1, AMstr("done"), true));
|
||||
AMfree(AMcommit(doc1, AMstr("Mark card as done"), NULL));
|
||||
|
||||
AMstackItem(NULL, AMlistDelete(doc2, cards, 0), abort_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMstackItem(NULL, AMcommit(doc2, AMstr("Delete card"), NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH));
|
||||
AMfree(AMlistDelete(doc2, cards, 0));
|
||||
AMfree(AMcommit(doc2, AMstr("Delete card"), NULL));
|
||||
|
||||
AMstackItem(NULL, AMmerge(doc1, doc2), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH));
|
||||
AMfree(AMmerge(doc1, doc2));
|
||||
|
||||
AMitems changes = AMstackItems(&stack, AMgetChanges(doc1, NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE));
|
||||
AMitem* item = NULL;
|
||||
while ((item = AMitemsNext(&changes, 1)) != NULL) {
|
||||
AMchange const* change;
|
||||
AMitemToChange(item, &change);
|
||||
AMitems const heads = AMstackItems(&stack, AMitemFromChangeHash(AMchangeHash(change)), abort_cb,
|
||||
AMexpect(AM_VAL_TYPE_CHANGE_HASH));
|
||||
char* const c_msg = AMstrdup(AMchangeMessage(change), NULL);
|
||||
printf("%s %zu\n", c_msg, AMobjSize(doc1, cards, &heads));
|
||||
AMchanges changes = AMpush(&stack, AMgetChanges(doc1, NULL), AM_VALUE_CHANGES, abort_cb).changes;
|
||||
AMchange const* change = NULL;
|
||||
while ((change = AMchangesNext(&changes, 1)) != NULL) {
|
||||
AMbyteSpan const change_hash = AMchangeHash(change);
|
||||
AMchangeHashes const heads = AMpush(&stack,
|
||||
AMchangeHashesInit(&change_hash, 1),
|
||||
AM_VALUE_CHANGE_HASHES,
|
||||
abort_cb).change_hashes;
|
||||
AMbyteSpan const msg = AMchangeMessage(change);
|
||||
char* const c_msg = calloc(1, msg.count + 1);
|
||||
strncpy(c_msg, msg.src, msg.count);
|
||||
printf("%s %ld\n", c_msg, AMobjSize(doc1, cards, &heads));
|
||||
free(c_msg);
|
||||
}
|
||||
AMstackFree(&stack);
|
||||
AMfreeStack(&stack);
|
||||
}
|
||||
|
||||
static char const* discriminant_suffix(AMvalueVariant const);
|
||||
|
||||
/**
|
||||
* \brief Examines the result at the top of the given stack and, if it's
|
||||
* invalid, prints an error message to `stderr`, deallocates all results
|
||||
* in the stack and exits.
|
||||
* \brief Prints an error message to `stderr`, deallocates all results in the
|
||||
* given stack and exits.
|
||||
*
|
||||
* \param[in,out] stack A pointer to a pointer to an `AMstack` struct.
|
||||
* \param[in] data A pointer to an owned `AMstackCallbackData` struct or `NULL`.
|
||||
* \return `true` if the top `AMresult` in \p stack is valid, `false` otherwise.
|
||||
* \pre \p stack `!= NULL`.
|
||||
* \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct.
|
||||
* \param[in] discriminant An `AMvalueVariant` enum tag.
|
||||
* \pre \p stack` != NULL`.
|
||||
* \post `*stack == NULL`.
|
||||
*/
|
||||
static bool abort_cb(AMstack** stack, void* data) {
|
||||
static void abort_cb(AMresultStack** stack, uint8_t discriminant) {
|
||||
static char buffer[512] = {0};
|
||||
|
||||
char const* suffix = NULL;
|
||||
if (!stack) {
|
||||
suffix = "Stack*";
|
||||
} else if (!*stack) {
|
||||
}
|
||||
else if (!*stack) {
|
||||
suffix = "Stack";
|
||||
} else if (!(*stack)->result) {
|
||||
}
|
||||
else if (!(*stack)->result) {
|
||||
suffix = "";
|
||||
}
|
||||
if (suffix) {
|
||||
fprintf(stderr, "Null `AMresult%s*`.\n", suffix);
|
||||
AMstackFree(stack);
|
||||
fprintf(stderr, "Null `AMresult%s*`.", suffix);
|
||||
AMfreeStack(stack);
|
||||
exit(EXIT_FAILURE);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
AMstatus const status = AMresultStatus((*stack)->result);
|
||||
switch (status) {
|
||||
case AM_STATUS_ERROR:
|
||||
strcpy(buffer, "Error");
|
||||
break;
|
||||
case AM_STATUS_INVALID_RESULT:
|
||||
strcpy(buffer, "Invalid result");
|
||||
break;
|
||||
case AM_STATUS_OK:
|
||||
break;
|
||||
default:
|
||||
sprintf(buffer, "Unknown `AMstatus` tag %d", status);
|
||||
case AM_STATUS_ERROR: strcpy(buffer, "Error"); break;
|
||||
case AM_STATUS_INVALID_RESULT: strcpy(buffer, "Invalid result"); break;
|
||||
case AM_STATUS_OK: break;
|
||||
default: sprintf(buffer, "Unknown `AMstatus` tag %d", status);
|
||||
}
|
||||
if (buffer[0]) {
|
||||
char* const c_msg = AMstrdup(AMresultError((*stack)->result), NULL);
|
||||
fprintf(stderr, "%s; %s.\n", buffer, c_msg);
|
||||
AMbyteSpan const msg = AMerrorMessage((*stack)->result);
|
||||
char* const c_msg = calloc(1, msg.count + 1);
|
||||
strncpy(c_msg, msg.src, msg.count);
|
||||
fprintf(stderr, "%s; %s.", buffer, c_msg);
|
||||
free(c_msg);
|
||||
AMstackFree(stack);
|
||||
AMfreeStack(stack);
|
||||
exit(EXIT_FAILURE);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
if (data) {
|
||||
AMstackCallbackData* sc_data = (AMstackCallbackData*)data;
|
||||
AMvalType const tag = AMitemValType(AMresultItem((*stack)->result));
|
||||
if (tag != sc_data->bitmask) {
|
||||
fprintf(stderr, "Unexpected tag `%s` (%d) instead of `%s` at %s:%d.\n", AMvalTypeToString(tag), tag,
|
||||
AMvalTypeToString(sc_data->bitmask), sc_data->file, sc_data->line);
|
||||
free(sc_data);
|
||||
AMstackFree(stack);
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
free(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Gets the suffix for a discriminant's corresponding string
|
||||
* representation.
|
||||
*
|
||||
* \param[in] discriminant An `AMvalueVariant` enum tag.
|
||||
* \return A UTF-8 string.
|
||||
*/
|
||||
static char const* discriminant_suffix(AMvalueVariant const discriminant) {
|
||||
char const* suffix = NULL;
|
||||
switch (discriminant) {
|
||||
case AM_VALUE_ACTOR_ID: suffix = "ACTOR_ID"; break;
|
||||
case AM_VALUE_BOOLEAN: suffix = "BOOLEAN"; break;
|
||||
case AM_VALUE_BYTES: suffix = "BYTES"; break;
|
||||
case AM_VALUE_CHANGE_HASHES: suffix = "CHANGE_HASHES"; break;
|
||||
case AM_VALUE_CHANGES: suffix = "CHANGES"; break;
|
||||
case AM_VALUE_COUNTER: suffix = "COUNTER"; break;
|
||||
case AM_VALUE_DOC: suffix = "DOC"; break;
|
||||
case AM_VALUE_F64: suffix = "F64"; break;
|
||||
case AM_VALUE_INT: suffix = "INT"; break;
|
||||
case AM_VALUE_LIST_ITEMS: suffix = "LIST_ITEMS"; break;
|
||||
case AM_VALUE_MAP_ITEMS: suffix = "MAP_ITEMS"; break;
|
||||
case AM_VALUE_NULL: suffix = "NULL"; break;
|
||||
case AM_VALUE_OBJ_ID: suffix = "OBJ_ID"; break;
|
||||
case AM_VALUE_OBJ_ITEMS: suffix = "OBJ_ITEMS"; break;
|
||||
case AM_VALUE_STR: suffix = "STR"; break;
|
||||
case AM_VALUE_STRS: suffix = "STRINGS"; break;
|
||||
case AM_VALUE_SYNC_MESSAGE: suffix = "SYNC_MESSAGE"; break;
|
||||
case AM_VALUE_SYNC_STATE: suffix = "SYNC_STATE"; break;
|
||||
case AM_VALUE_TIMESTAMP: suffix = "TIMESTAMP"; break;
|
||||
case AM_VALUE_UINT: suffix = "UINT"; break;
|
||||
case AM_VALUE_VOID: suffix = "VOID"; break;
|
||||
default: suffix = "...";
|
||||
}
|
||||
return suffix;
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
@ -1,30 +0,0 @@
|
|||
#ifndef AUTOMERGE_C_UTILS_RESULT_H
|
||||
#define AUTOMERGE_C_UTILS_RESULT_H
|
||||
/**
|
||||
* \file
|
||||
* \brief Utility functions for use with `AMresult` structs.
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#include <automerge-c/automerge.h>
|
||||
|
||||
/**
|
||||
* \brief Transfers the items within an arbitrary list of results into a
|
||||
* new result in their order of specification.
|
||||
* \param[in] count The count of subsequent arguments.
|
||||
* \param[in] ... A \p count list of arguments, each of which is a pointer to
|
||||
* an `AMresult` struct whose items will be transferred out of it
|
||||
* and which is subsequently freed.
|
||||
* \return A pointer to an `AMresult` struct or `NULL`.
|
||||
* \pre `∀𝑥 ∈` \p ... `, AMresultStatus(𝑥) == AM_STATUS_OK`
|
||||
* \post `(∃𝑥 ∈` \p ... `, AMresultStatus(𝑥) != AM_STATUS_OK) -> NULL`
|
||||
* \attention All `AMresult` struct pointer arguments are passed to
|
||||
* `AMresultFree()` regardless of success; use `AMresultCat()`
|
||||
* instead if you wish to pass them to `AMresultFree()` yourself.
|
||||
* \warning The returned `AMresult` struct pointer must be passed to
|
||||
* `AMresultFree()` in order to avoid a memory leak.
|
||||
*/
|
||||
AMresult* AMresultFrom(int count, ...);
|
||||
|
||||
#endif /* AUTOMERGE_C_UTILS_RESULT_H */
|
|
@ -1,130 +0,0 @@
|
|||
#ifndef AUTOMERGE_C_UTILS_STACK_H
|
||||
#define AUTOMERGE_C_UTILS_STACK_H
|
||||
/**
|
||||
* \file
|
||||
* \brief Utility data structures and functions for hiding `AMresult` structs,
|
||||
* managing their lifetimes, and automatically applying custom
|
||||
* validation logic to the `AMitem` structs that they contain.
|
||||
*
|
||||
* \note The `AMstack` struct and its related functions drastically reduce the
|
||||
* need for boilerplate code and/or `goto` statement usage within a C
|
||||
* application but a higher-level programming language offers even better
|
||||
* ways to do the same things.
|
||||
*/
|
||||
|
||||
#include <automerge-c/automerge.h>
|
||||
|
||||
/**
|
||||
* \struct AMstack
|
||||
* \brief A node in a singly-linked list of result pointers.
|
||||
*/
|
||||
typedef struct AMstack {
|
||||
/** A result to be deallocated. */
|
||||
AMresult* result;
|
||||
/** The previous node in the singly-linked list or `NULL`. */
|
||||
struct AMstack* prev;
|
||||
} AMstack;
|
||||
|
||||
/**
|
||||
* \memberof AMstack
|
||||
* \brief The prototype of a function that examines the result at the top of
|
||||
* the given stack in terms of some arbitrary data.
|
||||
*
|
||||
* \param[in,out] stack A pointer to a pointer to an `AMstack` struct.
|
||||
* \param[in] data A pointer to arbitrary data or `NULL`.
|
||||
* \return `true` if the top `AMresult` struct in \p stack is valid, `false`
|
||||
* otherwise.
|
||||
* \pre \p stack `!= NULL`.
|
||||
*/
|
||||
typedef bool (*AMstackCallback)(AMstack** stack, void* data);
|
||||
|
||||
/**
|
||||
* \memberof AMstack
|
||||
* \brief Deallocates the storage for a stack of results.
|
||||
*
|
||||
* \param[in,out] stack A pointer to a pointer to an `AMstack` struct.
|
||||
* \pre \p stack `!= NULL`
|
||||
* \post `*stack == NULL`
|
||||
*/
|
||||
void AMstackFree(AMstack** stack);
|
||||
|
||||
/**
|
||||
* \memberof AMstack
|
||||
* \brief Gets a result from the stack after removing it.
|
||||
*
|
||||
* \param[in,out] stack A pointer to a pointer to an `AMstack` struct.
|
||||
* \param[in] result A pointer to the `AMresult` to be popped or `NULL` to
|
||||
* select the top result in \p stack.
|
||||
* \return A pointer to an `AMresult` struct or `NULL`.
|
||||
* \pre \p stack `!= NULL`
|
||||
* \warning The returned `AMresult` struct pointer must be passed to
|
||||
* `AMresultFree()` in order to avoid a memory leak.
|
||||
*/
|
||||
AMresult* AMstackPop(AMstack** stack, AMresult const* result);
|
||||
|
||||
/**
|
||||
* \memberof AMstack
|
||||
* \brief Pushes the given result onto the given stack, calls the given
|
||||
* callback with the given data to validate it and then either gets the
|
||||
* result if it's valid or gets `NULL` instead.
|
||||
*
|
||||
* \param[in,out] stack A pointer to a pointer to an `AMstack` struct.
|
||||
* \param[in] result A pointer to an `AMresult` struct.
|
||||
* \param[in] callback A pointer to a function with the same signature as
|
||||
* `AMstackCallback()` or `NULL`.
|
||||
* \param[in] data A pointer to arbitrary data or `NULL` which is passed to
|
||||
* \p callback.
|
||||
* \return \p result or `NULL`.
|
||||
* \warning If \p stack `== NULL` then \p result is deallocated in order to
|
||||
* avoid a memory leak.
|
||||
*/
|
||||
AMresult* AMstackResult(AMstack** stack, AMresult* result, AMstackCallback callback, void* data);
|
||||
|
||||
/**
|
||||
* \memberof AMstack
|
||||
* \brief Pushes the given result onto the given stack, calls the given
|
||||
* callback with the given data to validate it and then either gets the
|
||||
* first item in the sequence of items within that result if it's valid
|
||||
* or gets `NULL` instead.
|
||||
*
|
||||
* \param[in,out] stack A pointer to a pointer to an `AMstack` struct.
|
||||
* \param[in] result A pointer to an `AMresult` struct.
|
||||
* \param[in] callback A pointer to a function with the same signature as
|
||||
* `AMstackCallback()` or `NULL`.
|
||||
* \param[in] data A pointer to arbitrary data or `NULL` which is passed to
|
||||
* \p callback.
|
||||
* \return A pointer to an `AMitem` struct or `NULL`.
|
||||
* \warning If \p stack `== NULL` then \p result is deallocated in order to
|
||||
* avoid a memory leak.
|
||||
*/
|
||||
AMitem* AMstackItem(AMstack** stack, AMresult* result, AMstackCallback callback, void* data);
|
||||
|
||||
/**
|
||||
* \memberof AMstack
|
||||
* \brief Pushes the given result onto the given stack, calls the given
|
||||
* callback with the given data to validate it and then either gets an
|
||||
* `AMitems` struct over the sequence of items within that result if it's
|
||||
* valid or gets an empty `AMitems` instead.
|
||||
*
|
||||
* \param[in,out] stack A pointer to a pointer to an `AMstack` struct.
|
||||
* \param[in] result A pointer to an `AMresult` struct.
|
||||
* \param[in] callback A pointer to a function with the same signature as
|
||||
* `AMstackCallback()` or `NULL`.
|
||||
* \param[in] data A pointer to arbitrary data or `NULL` which is passed to
|
||||
* \p callback.
|
||||
* \return An `AMitems` struct.
|
||||
* \warning If \p stack `== NULL` then \p result is deallocated immediately
|
||||
* in order to avoid a memory leak.
|
||||
*/
|
||||
AMitems AMstackItems(AMstack** stack, AMresult* result, AMstackCallback callback, void* data);
|
||||
|
||||
/**
|
||||
* \memberof AMstack
|
||||
* \brief Gets the count of results that have been pushed onto the stack.
|
||||
*
|
||||
* \param[in,out] stack A pointer to an `AMstack` struct.
|
||||
* \return A 64-bit unsigned integer.
|
||||
*/
|
||||
size_t AMstackSize(AMstack const* const stack);
|
||||
|
||||
#endif /* AUTOMERGE_C_UTILS_STACK_H */
|
|
@ -1,53 +0,0 @@
|
|||
#ifndef AUTOMERGE_C_UTILS_PUSH_CALLBACK_DATA_H
|
||||
#define AUTOMERGE_C_UTILS_PUSH_CALLBACK_DATA_H
|
||||
/**
|
||||
* \file
|
||||
* \brief Utility data structures, functions and macros for supplying
|
||||
* parameters to the custom validation logic applied to `AMitem`
|
||||
* structs.
|
||||
*/
|
||||
|
||||
#include <automerge-c/automerge.h>
|
||||
|
||||
/**
|
||||
* \struct AMstackCallbackData
|
||||
* \brief A data structure for passing the parameters of an item value test
|
||||
* to an implementation of the `AMstackCallback` function prototype.
|
||||
*/
|
||||
typedef struct {
|
||||
/** A bitmask of `AMvalType` tags. */
|
||||
AMvalType bitmask;
|
||||
/** A null-terminated file path string. */
|
||||
char const* file;
|
||||
/** The ordinal number of a line within a file. */
|
||||
int line;
|
||||
} AMstackCallbackData;
|
||||
|
||||
/**
|
||||
* \memberof AMstackCallbackData
|
||||
* \brief Allocates a new `AMstackCallbackData` struct and initializes its
|
||||
* members from their corresponding arguments.
|
||||
*
|
||||
* \param[in] bitmask A bitmask of `AMvalType` tags.
|
||||
* \param[in] file A null-terminated file path string.
|
||||
* \param[in] line The ordinal number of a line within a file.
|
||||
* \return A pointer to a disowned `AMstackCallbackData` struct.
|
||||
* \warning The returned pointer must be passed to `free()` to avoid a memory
|
||||
* leak.
|
||||
*/
|
||||
AMstackCallbackData* AMstackCallbackDataInit(AMvalType const bitmask, char const* const file, int const line);
|
||||
|
||||
/**
|
||||
* \memberof AMstackCallbackData
|
||||
* \def AMexpect
|
||||
* \brief Allocates a new `AMstackCallbackData` struct and initializes it from
|
||||
* an `AMvalueType` bitmask.
|
||||
*
|
||||
* \param[in] bitmask A bitmask of `AMvalType` tags.
|
||||
* \return A pointer to a disowned `AMstackCallbackData` struct.
|
||||
* \warning The returned pointer must be passed to `free()` to avoid a memory
|
||||
* leak.
|
||||
*/
|
||||
#define AMexpect(bitmask) AMstackCallbackDataInit(bitmask, __FILE__, __LINE__)
|
||||
|
||||
#endif /* AUTOMERGE_C_UTILS_PUSH_CALLBACK_DATA_H */
|
|
@ -1,29 +0,0 @@
|
|||
#ifndef AUTOMERGE_C_UTILS_STRING_H
|
||||
#define AUTOMERGE_C_UTILS_STRING_H
|
||||
/**
|
||||
* \file
|
||||
* \brief Utility functions for use with `AMbyteSpan` structs that provide
|
||||
* UTF-8 string views.
|
||||
*/
|
||||
|
||||
#include <automerge-c/automerge.h>
|
||||
|
||||
/**
|
||||
* \memberof AMbyteSpan
|
||||
* \brief Returns a pointer to a null-terminated byte string which is a
|
||||
* duplicate of the given UTF-8 string view except for the substitution
|
||||
* of its NUL (0) characters with the specified null-terminated byte
|
||||
* string.
|
||||
*
|
||||
* \param[in] str A UTF-8 string view as an `AMbyteSpan` struct.
|
||||
* \param[in] nul A null-terminated byte string to substitute for NUL characters
|
||||
* or `NULL` to substitute `"\\0"` for NUL characters.
|
||||
* \return A disowned null-terminated byte string.
|
||||
* \pre \p str.src `!= NULL`
|
||||
* \pre \p str.count `<= sizeof(`\p str.src `)`
|
||||
* \warning The returned pointer must be passed to `free()` to avoid a memory
|
||||
* leak.
|
||||
*/
|
||||
char* AMstrdup(AMbyteSpan const str, char const* nul);
|
||||
|
||||
#endif /* AUTOMERGE_C_UTILS_STRING_H */
|
250
rust/automerge-c/src/CMakeLists.txt
Normal file
250
rust/automerge-c/src/CMakeLists.txt
Normal file
|
@ -0,0 +1,250 @@
|
|||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||
|
||||
find_program (
|
||||
CARGO_CMD
|
||||
"cargo"
|
||||
PATHS "$ENV{CARGO_HOME}/bin"
|
||||
DOC "The Cargo command"
|
||||
)
|
||||
|
||||
if(NOT CARGO_CMD)
|
||||
message(FATAL_ERROR "Cargo (Rust package manager) not found! Install it and/or set the CARGO_HOME environment variable.")
|
||||
endif()
|
||||
|
||||
string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_LOWER)
|
||||
|
||||
if(BUILD_TYPE_LOWER STREQUAL debug)
|
||||
set(CARGO_BUILD_TYPE "debug")
|
||||
|
||||
set(CARGO_FLAG "")
|
||||
else()
|
||||
set(CARGO_BUILD_TYPE "release")
|
||||
|
||||
set(CARGO_FLAG "--release")
|
||||
endif()
|
||||
|
||||
set(CARGO_FEATURES "")
|
||||
|
||||
set(CARGO_CURRENT_BINARY_DIR "${CARGO_TARGET_DIR}/${CARGO_BUILD_TYPE}")
|
||||
|
||||
set(
|
||||
CARGO_OUTPUT
|
||||
${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
|
||||
${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}
|
||||
${CARGO_CURRENT_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
# \note The basename of an import library output by Cargo is the filename
|
||||
# of its corresponding shared library.
|
||||
list(APPEND CARGO_OUTPUT ${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX})
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${CARGO_OUTPUT}
|
||||
COMMAND
|
||||
# \note cbindgen won't regenerate its output header file after it's
|
||||
# been removed but it will after its configuration file has been
|
||||
# updated.
|
||||
${CMAKE_COMMAND} -DCONDITION=NOT_EXISTS -P ${CMAKE_SOURCE_DIR}/cmake/file_touch.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CMAKE_SOURCE_DIR}/cbindgen.toml
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} CBINDGEN_TARGET_DIR=${CBINDGEN_TARGET_DIR} ${CARGO_CMD} build ${CARGO_FLAG} ${CARGO_FEATURES}
|
||||
MAIN_DEPENDENCY
|
||||
lib.rs
|
||||
DEPENDS
|
||||
actor_id.rs
|
||||
byte_span.rs
|
||||
change_hashes.rs
|
||||
change.rs
|
||||
changes.rs
|
||||
doc.rs
|
||||
doc/list.rs
|
||||
doc/list/item.rs
|
||||
doc/list/items.rs
|
||||
doc/map.rs
|
||||
doc/map/item.rs
|
||||
doc/map/items.rs
|
||||
doc/utils.rs
|
||||
obj.rs
|
||||
obj/item.rs
|
||||
obj/items.rs
|
||||
result.rs
|
||||
result_stack.rs
|
||||
strs.rs
|
||||
sync.rs
|
||||
sync/have.rs
|
||||
sync/haves.rs
|
||||
sync/message.rs
|
||||
sync/state.rs
|
||||
${CMAKE_SOURCE_DIR}/build.rs
|
||||
${CMAKE_SOURCE_DIR}/Cargo.toml
|
||||
${CMAKE_SOURCE_DIR}/cbindgen.toml
|
||||
WORKING_DIRECTORY
|
||||
${CMAKE_SOURCE_DIR}
|
||||
COMMENT
|
||||
"Producing the library artifacts with Cargo..."
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
${LIBRARY_NAME}_artifacts ALL
|
||||
DEPENDS ${CARGO_OUTPUT}
|
||||
)
|
||||
|
||||
# \note cbindgen's naming behavior isn't fully configurable and it ignores
|
||||
# `const fn` calls (https://github.com/eqrion/cbindgen/issues/252).
|
||||
add_custom_command(
|
||||
TARGET ${LIBRARY_NAME}_artifacts
|
||||
POST_BUILD
|
||||
COMMAND
|
||||
# Compensate for cbindgen's variant struct naming.
|
||||
${CMAKE_COMMAND} -DMATCH_REGEX=AM\([^_]+_[^_]+\)_Body -DREPLACE_EXPR=AM\\1 -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
|
||||
COMMAND
|
||||
# Compensate for cbindgen's union tag enum type naming.
|
||||
${CMAKE_COMMAND} -DMATCH_REGEX=AM\([^_]+\)_Tag -DREPLACE_EXPR=AM\\1Variant -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
|
||||
COMMAND
|
||||
# Compensate for cbindgen's translation of consecutive uppercase letters to "ScreamingSnakeCase".
|
||||
${CMAKE_COMMAND} -DMATCH_REGEX=A_M\([^_]+\)_ -DREPLACE_EXPR=AM_\\1_ -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
|
||||
COMMAND
|
||||
# Compensate for cbindgen ignoring `std:mem::size_of<usize>()` calls.
|
||||
${CMAKE_COMMAND} -DMATCH_REGEX=USIZE_ -DREPLACE_EXPR=\+${CMAKE_SIZEOF_VOID_P} -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
|
||||
WORKING_DIRECTORY
|
||||
${CMAKE_SOURCE_DIR}
|
||||
COMMENT
|
||||
"Compensating for cbindgen deficits..."
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
if(BUILD_SHARED_LIBS)
|
||||
if(WIN32)
|
||||
set(LIBRARY_DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
else()
|
||||
set(LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
||||
endif()
|
||||
|
||||
set(LIBRARY_DEFINE_SYMBOL "${SYMBOL_PREFIX}_EXPORTS")
|
||||
|
||||
# \note The basename of an import library output by Cargo is the filename
|
||||
# of its corresponding shared library.
|
||||
set(LIBRARY_IMPLIB "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}")
|
||||
|
||||
set(LIBRARY_LOCATION "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}")
|
||||
|
||||
set(LIBRARY_NO_SONAME "${WIN32}")
|
||||
|
||||
set(LIBRARY_SONAME "${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}")
|
||||
|
||||
set(LIBRARY_TYPE "SHARED")
|
||||
else()
|
||||
set(LIBRARY_DEFINE_SYMBOL "")
|
||||
|
||||
set(LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
||||
|
||||
set(LIBRARY_IMPLIB "")
|
||||
|
||||
set(LIBRARY_LOCATION "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}")
|
||||
|
||||
set(LIBRARY_NO_SONAME "TRUE")
|
||||
|
||||
set(LIBRARY_SONAME "")
|
||||
|
||||
set(LIBRARY_TYPE "STATIC")
|
||||
endif()
|
||||
|
||||
add_library(${LIBRARY_NAME} ${LIBRARY_TYPE} IMPORTED GLOBAL)
|
||||
|
||||
set_target_properties(
|
||||
${LIBRARY_NAME}
|
||||
PROPERTIES
|
||||
# \note Cargo writes a debug build into a nested directory instead of
|
||||
# decorating its name.
|
||||
DEBUG_POSTFIX ""
|
||||
DEFINE_SYMBOL "${LIBRARY_DEFINE_SYMBOL}"
|
||||
IMPORTED_IMPLIB "${LIBRARY_IMPLIB}"
|
||||
IMPORTED_LOCATION "${LIBRARY_LOCATION}"
|
||||
IMPORTED_NO_SONAME "${LIBRARY_NO_SONAME}"
|
||||
IMPORTED_SONAME "${LIBRARY_SONAME}"
|
||||
LINKER_LANGUAGE C
|
||||
PUBLIC_HEADER "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h"
|
||||
SOVERSION "${PROJECT_VERSION_MAJOR}"
|
||||
VERSION "${PROJECT_VERSION}"
|
||||
# \note Cargo exports all of the symbols automatically.
|
||||
WINDOWS_EXPORT_ALL_SYMBOLS "TRUE"
|
||||
)
|
||||
|
||||
target_compile_definitions(${LIBRARY_NAME} INTERFACE $<TARGET_PROPERTY:${LIBRARY_NAME},DEFINE_SYMBOL>)
|
||||
|
||||
target_include_directories(
|
||||
${LIBRARY_NAME}
|
||||
INTERFACE
|
||||
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>"
|
||||
)
|
||||
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||
|
||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
set(LIBRARY_DEPENDENCIES Threads::Threads ${CMAKE_DL_LIBS})
|
||||
|
||||
if(WIN32)
|
||||
list(APPEND LIBRARY_DEPENDENCIES Bcrypt userenv ws2_32)
|
||||
else()
|
||||
list(APPEND LIBRARY_DEPENDENCIES m)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${LIBRARY_NAME} INTERFACE ${LIBRARY_DEPENDENCIES})
|
||||
|
||||
install(
|
||||
FILES $<TARGET_PROPERTY:${LIBRARY_NAME},IMPORTED_IMPLIB>
|
||||
TYPE LIB
|
||||
# \note The basename of an import library output by Cargo is the filename
|
||||
# of its corresponding shared library.
|
||||
RENAME "${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}"
|
||||
OPTIONAL
|
||||
)
|
||||
|
||||
set(LIBRARY_FILE_NAME "${CMAKE_${LIBRARY_TYPE}_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_${LIBRARY_TYPE}_LIBRARY_SUFFIX}")
|
||||
|
||||
install(
|
||||
FILES $<TARGET_PROPERTY:${LIBRARY_NAME},IMPORTED_LOCATION>
|
||||
RENAME "${LIBRARY_FILE_NAME}"
|
||||
DESTINATION ${LIBRARY_DESTINATION}
|
||||
)
|
||||
|
||||
install(
|
||||
FILES $<TARGET_PROPERTY:${LIBRARY_NAME},PUBLIC_HEADER>
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}
|
||||
)
|
||||
|
||||
find_package(Doxygen OPTIONAL_COMPONENTS dot)
|
||||
|
||||
if(DOXYGEN_FOUND)
|
||||
set(DOXYGEN_ALIASES "installed_headerfile=\\headerfile ${LIBRARY_NAME}.h <${PROJECT_NAME}/${LIBRARY_NAME}.h>")
|
||||
|
||||
set(DOXYGEN_GENERATE_LATEX YES)
|
||||
|
||||
set(DOXYGEN_PDF_HYPERLINKS YES)
|
||||
|
||||
set(DOXYGEN_PROJECT_LOGO "${CMAKE_SOURCE_DIR}/img/brandmark.png")
|
||||
|
||||
set(DOXYGEN_SORT_BRIEF_DOCS YES)
|
||||
|
||||
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md")
|
||||
|
||||
doxygen_add_docs(
|
||||
${LIBRARY_NAME}_docs
|
||||
"${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h"
|
||||
"${CMAKE_SOURCE_DIR}/README.md"
|
||||
USE_STAMP_FILE
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
COMMENT "Producing documentation with Doxygen..."
|
||||
)
|
||||
|
||||
# \note A Doxygen input file isn't a file-level dependency so the Doxygen
|
||||
# command must instead depend upon a target that outputs the file or
|
||||
# it will just output an error message when it can't be found.
|
||||
add_dependencies(${LIBRARY_NAME}_docs ${LIBRARY_NAME}_artifacts)
|
||||
endif()
|
|
@ -1,5 +1,4 @@
|
|||
use automerge as am;
|
||||
use libc::c_int;
|
||||
use std::cell::RefCell;
|
||||
use std::cmp::Ordering;
|
||||
use std::str::FromStr;
|
||||
|
@ -12,7 +11,7 @@ macro_rules! to_actor_id {
|
|||
let handle = $handle.as_ref();
|
||||
match handle {
|
||||
Some(b) => b,
|
||||
None => return AMresult::error("Invalid `AMactorId*`").into(),
|
||||
None => return AMresult::err("Invalid AMactorId pointer").into(),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
@ -58,11 +57,11 @@ impl AsRef<am::ActorId> for AMactorId {
|
|||
}
|
||||
|
||||
/// \memberof AMactorId
|
||||
/// \brief Gets the value of an actor identifier as an array of bytes.
|
||||
/// \brief Gets the value of an actor identifier as a sequence of bytes.
|
||||
///
|
||||
/// \param[in] actor_id A pointer to an `AMactorId` struct.
|
||||
/// \return An `AMbyteSpan` struct for an array of bytes.
|
||||
/// \pre \p actor_id `!= NULL`
|
||||
/// \pre \p actor_id `!= NULL`.
|
||||
/// \return An `AMbyteSpan` struct.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -83,8 +82,8 @@ pub unsafe extern "C" fn AMactorIdBytes(actor_id: *const AMactorId) -> AMbyteSpa
|
|||
/// \return `-1` if \p actor_id1 `<` \p actor_id2, `0` if
|
||||
/// \p actor_id1 `==` \p actor_id2 and `1` if
|
||||
/// \p actor_id1 `>` \p actor_id2.
|
||||
/// \pre \p actor_id1 `!= NULL`
|
||||
/// \pre \p actor_id2 `!= NULL`
|
||||
/// \pre \p actor_id1 `!= NULL`.
|
||||
/// \pre \p actor_id2 `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
|
@ -94,7 +93,7 @@ pub unsafe extern "C" fn AMactorIdBytes(actor_id: *const AMactorId) -> AMbyteSpa
|
|||
pub unsafe extern "C" fn AMactorIdCmp(
|
||||
actor_id1: *const AMactorId,
|
||||
actor_id2: *const AMactorId,
|
||||
) -> c_int {
|
||||
) -> isize {
|
||||
match (actor_id1.as_ref(), actor_id2.as_ref()) {
|
||||
(Some(actor_id1), Some(actor_id2)) => match actor_id1.as_ref().cmp(actor_id2.as_ref()) {
|
||||
Ordering::Less => -1,
|
||||
|
@ -102,69 +101,65 @@ pub unsafe extern "C" fn AMactorIdCmp(
|
|||
Ordering::Greater => 1,
|
||||
},
|
||||
(None, Some(_)) => -1,
|
||||
(None, None) => 0,
|
||||
(Some(_), None) => 1,
|
||||
(None, None) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMactorId
|
||||
/// \brief Allocates a new actor identifier and initializes it from a random
|
||||
/// UUID value.
|
||||
/// \brief Allocates a new actor identifier and initializes it with a random
|
||||
/// UUID.
|
||||
///
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item.
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||
/// `AMactorId` struct.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMactorIdInit() -> *mut AMresult {
|
||||
to_result(Ok::<am::ActorId, am::AutomergeError>(am::ActorId::random()))
|
||||
}
|
||||
|
||||
/// \memberof AMactorId
|
||||
/// \brief Allocates a new actor identifier and initializes it from an array of
|
||||
/// bytes value.
|
||||
/// \brief Allocates a new actor identifier and initializes it from a sequence
|
||||
/// of bytes.
|
||||
///
|
||||
/// \param[in] src A pointer to an array of bytes.
|
||||
/// \param[in] count The count of bytes to copy from the array pointed to by
|
||||
/// \p src.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item.
|
||||
/// \pre \p src `!= NULL`
|
||||
/// \pre `sizeof(`\p src `) > 0`
|
||||
/// \pre \p count `<= sizeof(`\p src `)`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] src A pointer to a contiguous sequence of bytes.
|
||||
/// \param[in] count The number of bytes to copy from \p src.
|
||||
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||
/// `AMactorId` struct.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// src must be a byte array of length `>= count`
|
||||
/// src must be a byte array of size `>= count`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMactorIdFromBytes(src: *const u8, count: usize) -> *mut AMresult {
|
||||
if !src.is_null() {
|
||||
let value = std::slice::from_raw_parts(src, count);
|
||||
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(
|
||||
value,
|
||||
slice,
|
||||
)))
|
||||
} else {
|
||||
AMresult::error("Invalid uint8_t*").into()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMactorId
|
||||
/// \brief Allocates a new actor identifier and initializes it from a
|
||||
/// hexadecimal UTF-8 string view value.
|
||||
/// hexadecimal string.
|
||||
///
|
||||
/// \param[in] value A UTF-8 string view as an `AMbyteSpan` struct.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item.
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] hex_str A UTF-8 string view as an `AMbyteSpan` struct.
|
||||
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||
/// `AMactorId` struct.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// hex_str must be a valid pointer to an AMbyteSpan
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMactorIdFromStr(value: AMbyteSpan) -> *mut AMresult {
|
||||
pub unsafe extern "C" fn AMactorIdInitStr(hex_str: AMbyteSpan) -> *mut AMresult {
|
||||
use am::AutomergeError::InvalidActorId;
|
||||
|
||||
to_result(match (&value).try_into() {
|
||||
to_result(match (&hex_str).try_into() {
|
||||
Ok(s) => match am::ActorId::from_str(s) {
|
||||
Ok(actor_id) => Ok(actor_id),
|
||||
Err(_) => Err(InvalidActorId(String::from(s))),
|
||||
|
@ -174,12 +169,11 @@ pub unsafe extern "C" fn AMactorIdFromStr(value: AMbyteSpan) -> *mut AMresult {
|
|||
}
|
||||
|
||||
/// \memberof AMactorId
|
||||
/// \brief Gets the value of an actor identifier as a UTF-8 hexadecimal string
|
||||
/// view.
|
||||
/// \brief Gets the value of an actor identifier as a hexadecimal string.
|
||||
///
|
||||
/// \param[in] actor_id A pointer to an `AMactorId` struct.
|
||||
/// \pre \p actor_id `!= NULL`.
|
||||
/// \return A UTF-8 string view as an `AMbyteSpan` struct.
|
||||
/// \pre \p actor_id `!= NULL`
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
use automerge as am;
|
||||
use std::cmp::Ordering;
|
||||
use libc::strlen;
|
||||
use std::convert::TryFrom;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
use libc::{c_int, strlen};
|
||||
use smol_str::SmolStr;
|
||||
|
||||
macro_rules! to_str {
|
||||
($byte_span:expr) => {{
|
||||
let result: Result<&str, am::AutomergeError> = (&$byte_span).try_into();
|
||||
($span:expr) => {{
|
||||
let result: Result<&str, am::AutomergeError> = (&$span).try_into();
|
||||
match result {
|
||||
Ok(s) => s,
|
||||
Err(e) => return AMresult::error(&e.to_string()).into(),
|
||||
Err(e) => return AMresult::err(&e.to_string()).into(),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
@ -20,17 +17,16 @@ pub(crate) use to_str;
|
|||
|
||||
/// \struct AMbyteSpan
|
||||
/// \installed_headerfile
|
||||
/// \brief A view onto an array of bytes.
|
||||
/// \brief A view onto a contiguous sequence of bytes.
|
||||
#[repr(C)]
|
||||
pub struct AMbyteSpan {
|
||||
/// A pointer to the first byte of an array of bytes.
|
||||
/// \warning \p src is only valid until the array of bytes to which it
|
||||
/// points is freed.
|
||||
/// \note If the `AMbyteSpan` came from within an `AMitem` struct then
|
||||
/// \p src will be freed when the pointer to the `AMresult` struct
|
||||
/// containing the `AMitem` struct is passed to `AMresultFree()`.
|
||||
/// A pointer to an array of bytes.
|
||||
/// \attention <b>NEVER CALL `free()` ON \p src!</b>
|
||||
/// \warning \p src is only valid until the `AMfree()` function is called
|
||||
/// on the `AMresult` struct that stores the array of bytes to
|
||||
/// which it points.
|
||||
pub src: *const u8,
|
||||
/// The count of bytes in the array.
|
||||
/// The number of bytes in the array.
|
||||
pub count: usize,
|
||||
}
|
||||
|
||||
|
@ -56,7 +52,9 @@ impl PartialEq for AMbyteSpan {
|
|||
} else if self.src == other.src {
|
||||
return true;
|
||||
}
|
||||
<&[u8]>::from(self) == <&[u8]>::from(other)
|
||||
let slice = unsafe { std::slice::from_raw_parts(self.src, self.count) };
|
||||
let other_slice = unsafe { std::slice::from_raw_parts(other.src, other.count) };
|
||||
slice == other_slice
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,15 +72,10 @@ impl From<&am::ActorId> for AMbyteSpan {
|
|||
|
||||
impl From<&mut am::ActorId> for AMbyteSpan {
|
||||
fn from(actor: &mut am::ActorId) -> Self {
|
||||
actor.as_ref().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&am::ChangeHash> for AMbyteSpan {
|
||||
fn from(change_hash: &am::ChangeHash) -> Self {
|
||||
let slice = actor.to_bytes();
|
||||
Self {
|
||||
src: change_hash.0.as_ptr(),
|
||||
count: change_hash.0.len(),
|
||||
src: slice.as_ptr(),
|
||||
count: slice.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,9 +93,12 @@ impl From<*const c_char> for AMbyteSpan {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&SmolStr> for AMbyteSpan {
|
||||
fn from(smol_str: &SmolStr) -> Self {
|
||||
smol_str.as_bytes().into()
|
||||
impl From<&am::ChangeHash> for AMbyteSpan {
|
||||
fn from(change_hash: &am::ChangeHash) -> Self {
|
||||
Self {
|
||||
src: change_hash.0.as_ptr(),
|
||||
count: change_hash.0.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,39 +111,13 @@ impl From<&[u8]> for AMbyteSpan {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&AMbyteSpan> for &[u8] {
|
||||
fn from(byte_span: &AMbyteSpan) -> Self {
|
||||
unsafe { std::slice::from_raw_parts(byte_span.src, byte_span.count) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AMbyteSpan> for Vec<u8> {
|
||||
fn from(byte_span: &AMbyteSpan) -> Self {
|
||||
<&[u8]>::from(byte_span).to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&AMbyteSpan> for am::ChangeHash {
|
||||
type Error = am::AutomergeError;
|
||||
|
||||
fn try_from(byte_span: &AMbyteSpan) -> Result<Self, Self::Error> {
|
||||
use am::AutomergeError::InvalidChangeHashBytes;
|
||||
|
||||
let slice: &[u8] = byte_span.into();
|
||||
match slice.try_into() {
|
||||
Ok(change_hash) => Ok(change_hash),
|
||||
Err(e) => Err(InvalidChangeHashBytes(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&AMbyteSpan> for &str {
|
||||
type Error = am::AutomergeError;
|
||||
|
||||
fn try_from(byte_span: &AMbyteSpan) -> Result<Self, Self::Error> {
|
||||
fn try_from(span: &AMbyteSpan) -> Result<Self, Self::Error> {
|
||||
use am::AutomergeError::InvalidCharacter;
|
||||
|
||||
let slice = byte_span.into();
|
||||
let slice = unsafe { std::slice::from_raw_parts(span.src, span.count) };
|
||||
match std::str::from_utf8(slice) {
|
||||
Ok(str_) => Ok(str_),
|
||||
Err(e) => Err(InvalidCharacter(e.valid_up_to())),
|
||||
|
@ -155,69 +125,17 @@ impl TryFrom<&AMbyteSpan> for &str {
|
|||
}
|
||||
}
|
||||
|
||||
/// \memberof AMbyteSpan
|
||||
/// \brief Creates a view onto an array of bytes.
|
||||
/// \brief Creates an AMbyteSpan from a pointer + length
|
||||
///
|
||||
/// \param[in] src A pointer to an array of bytes or `NULL`.
|
||||
/// \param[in] count The count of bytes to view from the array pointed to by
|
||||
/// \p src.
|
||||
/// \return An `AMbyteSpan` struct.
|
||||
/// \pre \p count `<= sizeof(`\p src `)`
|
||||
/// \post `(`\p src `== NULL) -> (AMbyteSpan){NULL, 0}`
|
||||
/// \param[in] src A pointer to a span of bytes
|
||||
/// \param[in] count The number of bytes in the span
|
||||
/// \return An `AMbyteSpan` struct
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// src must be a byte array of length `>= count` or `std::ptr::null()`
|
||||
/// AMbytes does not retain the underlying storage, so you must discard the
|
||||
/// return value before freeing the bytes.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMbytes(src: *const u8, count: usize) -> AMbyteSpan {
|
||||
AMbyteSpan {
|
||||
src,
|
||||
count: if src.is_null() { 0 } else { count },
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMbyteSpan
|
||||
/// \brief Creates a view onto a C string.
|
||||
///
|
||||
/// \param[in] c_str A null-terminated byte string or `NULL`.
|
||||
/// \return An `AMbyteSpan` struct.
|
||||
/// \pre Each byte in \p c_str encodes one UTF-8 character.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// c_str must be a null-terminated array of `std::os::raw::c_char` or `std::ptr::null()`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMstr(c_str: *const c_char) -> AMbyteSpan {
|
||||
c_str.into()
|
||||
}
|
||||
|
||||
/// \memberof AMbyteSpan
|
||||
/// \brief Compares two UTF-8 string views lexicographically.
|
||||
///
|
||||
/// \param[in] lhs A UTF-8 string view as an `AMbyteSpan` struct.
|
||||
/// \param[in] rhs A UTF-8 string view as an `AMbyteSpan` struct.
|
||||
/// \return Negative value if \p lhs appears before \p rhs in lexicographical order.
|
||||
/// Zero if \p lhs and \p rhs compare equal.
|
||||
/// Positive value if \p lhs appears after \p rhs in lexicographical order.
|
||||
/// \pre \p lhs.src `!= NULL`
|
||||
/// \pre \p lhs.count `<= sizeof(`\p lhs.src `)`
|
||||
/// \pre \p rhs.src `!= NULL`
|
||||
/// \pre \p rhs.count `<= sizeof(`\p rhs.src `)`
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// lhs.src must be a byte array of length >= lhs.count
|
||||
/// rhs.src must be a a byte array of length >= rhs.count
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMstrCmp(lhs: AMbyteSpan, rhs: AMbyteSpan) -> c_int {
|
||||
match (<&str>::try_from(&lhs), <&str>::try_from(&rhs)) {
|
||||
(Ok(lhs), Ok(rhs)) => match lhs.cmp(rhs) {
|
||||
Ordering::Less => -1,
|
||||
Ordering::Equal => 0,
|
||||
Ordering::Greater => 1,
|
||||
},
|
||||
(Err(_), Ok(_)) => -1,
|
||||
(Err(_), Err(_)) => 0,
|
||||
(Ok(_), Err(_)) => 1,
|
||||
}
|
||||
AMbyteSpan { src, count }
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use automerge as am;
|
|||
use std::cell::RefCell;
|
||||
|
||||
use crate::byte_span::AMbyteSpan;
|
||||
use crate::change_hashes::AMchangeHashes;
|
||||
use crate::result::{to_result, AMresult};
|
||||
|
||||
macro_rules! to_change {
|
||||
|
@ -9,7 +10,7 @@ macro_rules! to_change {
|
|||
let handle = $handle.as_ref();
|
||||
match handle {
|
||||
Some(b) => b,
|
||||
None => return AMresult::error("Invalid `AMchange*`").into(),
|
||||
None => return AMresult::err("Invalid AMchange pointer").into(),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
@ -20,14 +21,14 @@ macro_rules! to_change {
|
|||
#[derive(Eq, PartialEq)]
|
||||
pub struct AMchange {
|
||||
body: *mut am::Change,
|
||||
change_hash: RefCell<Option<am::ChangeHash>>,
|
||||
changehash: RefCell<Option<am::ChangeHash>>,
|
||||
}
|
||||
|
||||
impl AMchange {
|
||||
pub fn new(change: &mut am::Change) -> Self {
|
||||
Self {
|
||||
body: change,
|
||||
change_hash: Default::default(),
|
||||
changehash: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,12 +40,12 @@ impl AMchange {
|
|||
}
|
||||
|
||||
pub fn hash(&self) -> AMbyteSpan {
|
||||
let mut change_hash = self.change_hash.borrow_mut();
|
||||
if let Some(change_hash) = change_hash.as_ref() {
|
||||
change_hash.into()
|
||||
let mut changehash = self.changehash.borrow_mut();
|
||||
if let Some(changehash) = changehash.as_ref() {
|
||||
changehash.into()
|
||||
} else {
|
||||
let hash = unsafe { (*self.body).hash() };
|
||||
let ptr = change_hash.insert(hash);
|
||||
let ptr = changehash.insert(hash);
|
||||
AMbyteSpan {
|
||||
src: ptr.0.as_ptr(),
|
||||
count: hash.as_ref().len(),
|
||||
|
@ -69,10 +70,11 @@ impl AsRef<am::Change> for AMchange {
|
|||
/// \brief Gets the first referenced actor identifier in a change.
|
||||
///
|
||||
/// \param[in] change A pointer to an `AMchange` struct.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item.
|
||||
/// \pre \p change `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \pre \p change `!= NULL`.
|
||||
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||
/// `AMactorId` struct.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -88,8 +90,8 @@ pub unsafe extern "C" fn AMchangeActorId(change: *const AMchange) -> *mut AMresu
|
|||
/// \memberof AMchange
|
||||
/// \brief Compresses the raw bytes of a change.
|
||||
///
|
||||
/// \param[in] change A pointer to an `AMchange` struct.
|
||||
/// \pre \p change `!= NULL`
|
||||
/// \param[in,out] change A pointer to an `AMchange` struct.
|
||||
/// \pre \p change `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -105,20 +107,18 @@ pub unsafe extern "C" fn AMchangeCompress(change: *mut AMchange) {
|
|||
/// \brief Gets the dependencies of a change.
|
||||
///
|
||||
/// \param[in] change A pointer to an `AMchange` struct.
|
||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items.
|
||||
/// \pre \p change `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMchangeHashes` struct or `NULL`.
|
||||
/// \pre \p change `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// change must be a valid pointer to an AMchange
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangeDeps(change: *const AMchange) -> *mut AMresult {
|
||||
to_result(match change.as_ref() {
|
||||
Some(change) => change.as_ref().deps(),
|
||||
pub unsafe extern "C" fn AMchangeDeps(change: *const AMchange) -> AMchangeHashes {
|
||||
match change.as_ref() {
|
||||
Some(change) => AMchangeHashes::new(change.as_ref().deps()),
|
||||
None => Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMchange
|
||||
|
@ -126,7 +126,7 @@ pub unsafe extern "C" fn AMchangeDeps(change: *const AMchange) -> *mut AMresult
|
|||
///
|
||||
/// \param[in] change A pointer to an `AMchange` struct.
|
||||
/// \return An `AMbyteSpan` struct.
|
||||
/// \pre \p change `!= NULL`
|
||||
/// \pre \p change `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -141,33 +141,32 @@ pub unsafe extern "C" fn AMchangeExtraBytes(change: *const AMchange) -> AMbyteSp
|
|||
}
|
||||
|
||||
/// \memberof AMchange
|
||||
/// \brief Allocates a new change and initializes it from an array of bytes value.
|
||||
/// \brief Loads a sequence of bytes into a change.
|
||||
///
|
||||
/// \param[in] src A pointer to an array of bytes.
|
||||
/// \param[in] count The count of bytes to load from the array pointed to by
|
||||
/// \p src.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_CHANGE` item.
|
||||
/// \pre \p src `!= NULL`
|
||||
/// \pre `sizeof(`\p src `) > 0`
|
||||
/// \pre \p count `<= sizeof(`\p src `)`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] count The number of bytes in \p src to load.
|
||||
/// \return A pointer to an `AMresult` struct containing an `AMchange` struct.
|
||||
/// \pre \p src `!= NULL`.
|
||||
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// src must be a byte array of length `>= count`
|
||||
/// src must be a byte array of size `>= count`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangeFromBytes(src: *const u8, count: usize) -> *mut AMresult {
|
||||
let data = std::slice::from_raw_parts(src, count);
|
||||
to_result(am::Change::from_bytes(data.to_vec()))
|
||||
let mut data = Vec::new();
|
||||
data.extend_from_slice(std::slice::from_raw_parts(src, count));
|
||||
to_result(am::Change::from_bytes(data))
|
||||
}
|
||||
|
||||
/// \memberof AMchange
|
||||
/// \brief Gets the hash of a change.
|
||||
///
|
||||
/// \param[in] change A pointer to an `AMchange` struct.
|
||||
/// \return An `AMbyteSpan` struct for a change hash.
|
||||
/// \pre \p change `!= NULL`
|
||||
/// \return A change hash as an `AMbyteSpan` struct.
|
||||
/// \pre \p change `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -184,8 +183,8 @@ pub unsafe extern "C" fn AMchangeHash(change: *const AMchange) -> AMbyteSpan {
|
|||
/// \brief Tests the emptiness of a change.
|
||||
///
|
||||
/// \param[in] change A pointer to an `AMchange` struct.
|
||||
/// \return `true` if \p change is empty, `false` otherwise.
|
||||
/// \pre \p change `!= NULL`
|
||||
/// \return A boolean.
|
||||
/// \pre \p change `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -199,37 +198,12 @@ pub unsafe extern "C" fn AMchangeIsEmpty(change: *const AMchange) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// \memberof AMchange
|
||||
/// \brief Loads a document into a sequence of changes.
|
||||
///
|
||||
/// \param[in] src A pointer to an array of bytes.
|
||||
/// \param[in] count The count of bytes to load from the array pointed to by
|
||||
/// \p src.
|
||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE` items.
|
||||
/// \pre \p src `!= NULL`
|
||||
/// \pre `sizeof(`\p src `) > 0`
|
||||
/// \pre \p count `<= sizeof(`\p src `)`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// src must be a byte array of length `>= count`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangeLoadDocument(src: *const u8, count: usize) -> *mut AMresult {
|
||||
let data = std::slice::from_raw_parts(src, count);
|
||||
to_result::<Result<Vec<am::Change>, _>>(
|
||||
am::Automerge::load(data)
|
||||
.and_then(|d| d.get_changes(&[]).map(|c| c.into_iter().cloned().collect())),
|
||||
)
|
||||
}
|
||||
|
||||
/// \memberof AMchange
|
||||
/// \brief Gets the maximum operation index of a change.
|
||||
///
|
||||
/// \param[in] change A pointer to an `AMchange` struct.
|
||||
/// \return A 64-bit unsigned integer.
|
||||
/// \pre \p change `!= NULL`
|
||||
/// \pre \p change `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -247,8 +221,8 @@ pub unsafe extern "C" fn AMchangeMaxOp(change: *const AMchange) -> u64 {
|
|||
/// \brief Gets the message of a change.
|
||||
///
|
||||
/// \param[in] change A pointer to an `AMchange` struct.
|
||||
/// \return An `AMbyteSpan` struct for a UTF-8 string.
|
||||
/// \pre \p change `!= NULL`
|
||||
/// \return A UTF-8 string view as an `AMbyteSpan` struct.
|
||||
/// \pre \p change `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -266,7 +240,7 @@ pub unsafe extern "C" fn AMchangeMessage(change: *const AMchange) -> AMbyteSpan
|
|||
///
|
||||
/// \param[in] change A pointer to an `AMchange` struct.
|
||||
/// \return A 64-bit unsigned integer.
|
||||
/// \pre \p change `!= NULL`
|
||||
/// \pre \p change `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -285,7 +259,7 @@ pub unsafe extern "C" fn AMchangeSeq(change: *const AMchange) -> u64 {
|
|||
///
|
||||
/// \param[in] change A pointer to an `AMchange` struct.
|
||||
/// \return A 64-bit unsigned integer.
|
||||
/// \pre \p change `!= NULL`
|
||||
/// \pre \p change `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -293,9 +267,10 @@ pub unsafe extern "C" fn AMchangeSeq(change: *const AMchange) -> u64 {
|
|||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangeSize(change: *const AMchange) -> usize {
|
||||
if let Some(change) = change.as_ref() {
|
||||
return change.as_ref().len();
|
||||
}
|
||||
change.as_ref().len()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMchange
|
||||
|
@ -303,7 +278,7 @@ pub unsafe extern "C" fn AMchangeSize(change: *const AMchange) -> usize {
|
|||
///
|
||||
/// \param[in] change A pointer to an `AMchange` struct.
|
||||
/// \return A 64-bit unsigned integer.
|
||||
/// \pre \p change `!= NULL`
|
||||
/// \pre \p change `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -322,7 +297,7 @@ pub unsafe extern "C" fn AMchangeStartOp(change: *const AMchange) -> u64 {
|
|||
///
|
||||
/// \param[in] change A pointer to an `AMchange` struct.
|
||||
/// \return A 64-bit signed integer.
|
||||
/// \pre \p change `!= NULL`
|
||||
/// \pre \p change `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -340,8 +315,8 @@ pub unsafe extern "C" fn AMchangeTime(change: *const AMchange) -> i64 {
|
|||
/// \brief Gets the raw bytes of a change.
|
||||
///
|
||||
/// \param[in] change A pointer to an `AMchange` struct.
|
||||
/// \return An `AMbyteSpan` struct for an array of bytes.
|
||||
/// \pre \p change `!= NULL`
|
||||
/// \return An `AMbyteSpan` struct.
|
||||
/// \pre \p change `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -354,3 +329,28 @@ pub unsafe extern "C" fn AMchangeRawBytes(change: *const AMchange) -> AMbyteSpan
|
|||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMchange
|
||||
/// \brief Loads a document into a sequence of changes.
|
||||
///
|
||||
/// \param[in] src A pointer to an array of bytes.
|
||||
/// \param[in] count The number of bytes in \p src to load.
|
||||
/// \return A pointer to an `AMresult` struct containing a sequence of
|
||||
/// `AMchange` structs.
|
||||
/// \pre \p src `!= NULL`.
|
||||
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// src must be a byte array of size `>= count`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangeLoadDocument(src: *const u8, count: usize) -> *mut AMresult {
|
||||
let mut data = Vec::new();
|
||||
data.extend_from_slice(std::slice::from_raw_parts(src, count));
|
||||
to_result::<Result<Vec<am::Change>, _>>(
|
||||
am::Automerge::load(&data)
|
||||
.and_then(|d| d.get_changes(&[]).map(|c| c.into_iter().cloned().collect())),
|
||||
)
|
||||
}
|
||||
|
|
400
rust/automerge-c/src/change_hashes.rs
Normal file
400
rust/automerge-c/src/change_hashes.rs
Normal file
|
@ -0,0 +1,400 @@
|
|||
use automerge as am;
|
||||
use std::cmp::Ordering;
|
||||
use std::ffi::c_void;
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::byte_span::AMbyteSpan;
|
||||
use crate::result::{to_result, AMresult};
|
||||
|
||||
#[repr(C)]
|
||||
struct Detail {
|
||||
len: usize,
|
||||
offset: isize,
|
||||
ptr: *const c_void,
|
||||
}
|
||||
|
||||
/// \note cbindgen won't propagate the value of a `std::mem::size_of<T>()` call
|
||||
/// (https://github.com/eqrion/cbindgen/issues/252) but it will
|
||||
/// propagate the name of a constant initialized from it so if the
|
||||
/// constant's name is a symbolic representation of the value it can be
|
||||
/// converted into a number by post-processing the header it generated.
|
||||
pub const USIZE_USIZE_USIZE_: usize = size_of::<Detail>();
|
||||
|
||||
impl Detail {
|
||||
fn new(change_hashes: &[am::ChangeHash], offset: isize) -> Self {
|
||||
Self {
|
||||
len: change_hashes.len(),
|
||||
offset,
|
||||
ptr: change_hashes.as_ptr() as *const c_void,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
let len = self.len as isize;
|
||||
self.offset = if self.offset < 0 {
|
||||
// It's reversed.
|
||||
let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN);
|
||||
if unclipped >= 0 {
|
||||
// Clip it to the forward stop.
|
||||
len
|
||||
} else {
|
||||
std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1)
|
||||
}
|
||||
} else {
|
||||
let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX);
|
||||
if unclipped < 0 {
|
||||
// Clip it to the reverse stop.
|
||||
-(len + 1)
|
||||
} else {
|
||||
std::cmp::max(0, std::cmp::min(unclipped, len))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_index(&self) -> usize {
|
||||
(self.offset
|
||||
+ if self.offset < 0 {
|
||||
self.len as isize
|
||||
} else {
|
||||
0
|
||||
}) as usize
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<&am::ChangeHash> {
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &[am::ChangeHash] =
|
||||
unsafe { std::slice::from_raw_parts(self.ptr as *const am::ChangeHash, self.len) };
|
||||
let value = &slice[self.get_index()];
|
||||
self.advance(n);
|
||||
Some(value)
|
||||
}
|
||||
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
let len = self.len as isize;
|
||||
self.offset < -len || self.offset == len
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<&am::ChangeHash> {
|
||||
self.advance(-n);
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &[am::ChangeHash] =
|
||||
unsafe { std::slice::from_raw_parts(self.ptr as *const am::ChangeHash, self.len) };
|
||||
Some(&slice[self.get_index()])
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: -(self.offset + 1),
|
||||
ptr: self.ptr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: if self.offset < 0 { -1 } else { 0 },
|
||||
ptr: self.ptr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Detail> for [u8; USIZE_USIZE_USIZE_] {
|
||||
fn from(detail: Detail) -> Self {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_)
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \struct AMchangeHashes
|
||||
/// \installed_headerfile
|
||||
/// \brief A random-access iterator over a sequence of change hashes.
|
||||
#[repr(C)]
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub struct AMchangeHashes {
|
||||
/// An implementation detail that is intentionally opaque.
|
||||
/// \warning Modifying \p detail will cause undefined behavior.
|
||||
/// \note The actual size of \p detail will vary by platform, this is just
|
||||
/// the one for the platform this documentation was built on.
|
||||
detail: [u8; USIZE_USIZE_USIZE_],
|
||||
}
|
||||
|
||||
impl AMchangeHashes {
|
||||
pub fn new(change_hashes: &[am::ChangeHash]) -> Self {
|
||||
Self {
|
||||
detail: Detail::new(change_hashes, 0).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.advance(n);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
detail.len
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<&am::ChangeHash> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.next(n)
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<&am::ChangeHash> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.prev(n)
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.reversed().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.rewound().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[am::ChangeHash]> for AMchangeHashes {
|
||||
fn as_ref(&self) -> &[am::ChangeHash] {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
unsafe { std::slice::from_raw_parts(detail.ptr as *const am::ChangeHash, detail.len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AMchangeHashes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
detail: [0; USIZE_USIZE_USIZE_],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMchangeHashes
|
||||
/// \brief Advances an iterator over a sequence of change hashes by at most
|
||||
/// \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction.
|
||||
///
|
||||
/// \param[in,out] change_hashes A pointer to an `AMchangeHashes` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \pre \p change_hashes `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// change_hashes must be a valid pointer to an AMchangeHashes
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangeHashesAdvance(change_hashes: *mut AMchangeHashes, n: isize) {
|
||||
if let Some(change_hashes) = change_hashes.as_mut() {
|
||||
change_hashes.advance(n);
|
||||
};
|
||||
}
|
||||
|
||||
/// \memberof AMchangeHashes
|
||||
/// \brief Compares the sequences of change hashes underlying a pair of
|
||||
/// iterators.
|
||||
///
|
||||
/// \param[in] change_hashes1 A pointer to an `AMchangeHashes` struct.
|
||||
/// \param[in] change_hashes2 A pointer to an `AMchangeHashes` struct.
|
||||
/// \return `-1` if \p change_hashes1 `<` \p change_hashes2, `0` if
|
||||
/// \p change_hashes1 `==` \p change_hashes2 and `1` if
|
||||
/// \p change_hashes1 `>` \p change_hashes2.
|
||||
/// \pre \p change_hashes1 `!= NULL`.
|
||||
/// \pre \p change_hashes2 `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// change_hashes1 must be a valid pointer to an AMchangeHashes
|
||||
/// change_hashes2 must be a valid pointer to an AMchangeHashes
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangeHashesCmp(
|
||||
change_hashes1: *const AMchangeHashes,
|
||||
change_hashes2: *const AMchangeHashes,
|
||||
) -> isize {
|
||||
match (change_hashes1.as_ref(), change_hashes2.as_ref()) {
|
||||
(Some(change_hashes1), Some(change_hashes2)) => {
|
||||
match change_hashes1.as_ref().cmp(change_hashes2.as_ref()) {
|
||||
Ordering::Less => -1,
|
||||
Ordering::Equal => 0,
|
||||
Ordering::Greater => 1,
|
||||
}
|
||||
}
|
||||
(None, Some(_)) => -1,
|
||||
(Some(_), None) => 1,
|
||||
(None, None) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMchangeHashes
|
||||
/// \brief Allocates an iterator over a sequence of change hashes and
|
||||
/// initializes it from a sequence of byte spans.
|
||||
///
|
||||
/// \param[in] src A pointer to an array of `AMbyteSpan` structs.
|
||||
/// \param[in] count The number of `AMbyteSpan` structs to copy from \p src.
|
||||
/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes`
|
||||
/// struct.
|
||||
/// \pre \p src `!= NULL`.
|
||||
/// \pre `0 <` \p count `<= sizeof(`\p src`) / sizeof(AMbyteSpan)`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// src must be an AMbyteSpan array of size `>= count`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangeHashesInit(src: *const AMbyteSpan, count: usize) -> *mut AMresult {
|
||||
let mut change_hashes = Vec::<am::ChangeHash>::new();
|
||||
for n in 0..count {
|
||||
let byte_span = &*src.add(n);
|
||||
let slice = std::slice::from_raw_parts(byte_span.src, byte_span.count);
|
||||
match slice.try_into() {
|
||||
Ok(change_hash) => {
|
||||
change_hashes.push(change_hash);
|
||||
}
|
||||
Err(e) => {
|
||||
return to_result(Err(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
to_result(Ok::<Vec<am::ChangeHash>, am::InvalidChangeHashSlice>(
|
||||
change_hashes,
|
||||
))
|
||||
}
|
||||
|
||||
/// \memberof AMchangeHashes
|
||||
/// \brief Gets the change hash at the current position of an iterator over a
|
||||
/// sequence of change hashes and then advances it by at most \p |n|
|
||||
/// positions where the sign of \p n is relative to the iterator's
|
||||
/// direction.
|
||||
///
|
||||
/// \param[in,out] change_hashes A pointer to an `AMchangeHashes` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return An `AMbyteSpan` struct with `.src == NULL` when \p change_hashes
|
||||
/// was previously advanced past its forward/reverse limit.
|
||||
/// \pre \p change_hashes `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// change_hashes must be a valid pointer to an AMchangeHashes
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangeHashesNext(
|
||||
change_hashes: *mut AMchangeHashes,
|
||||
n: isize,
|
||||
) -> AMbyteSpan {
|
||||
if let Some(change_hashes) = change_hashes.as_mut() {
|
||||
if let Some(change_hash) = change_hashes.next(n) {
|
||||
return change_hash.into();
|
||||
}
|
||||
}
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// \memberof AMchangeHashes
|
||||
/// \brief Advances an iterator over a sequence of change hashes by at most
|
||||
/// \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction and then gets the change hash at its new
|
||||
/// position.
|
||||
///
|
||||
/// \param[in,out] change_hashes A pointer to an `AMchangeHashes` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return An `AMbyteSpan` struct with `.src == NULL` when \p change_hashes is
|
||||
/// presently advanced past its forward/reverse limit.
|
||||
/// \pre \p change_hashes `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// change_hashes must be a valid pointer to an AMchangeHashes
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangeHashesPrev(
|
||||
change_hashes: *mut AMchangeHashes,
|
||||
n: isize,
|
||||
) -> AMbyteSpan {
|
||||
if let Some(change_hashes) = change_hashes.as_mut() {
|
||||
if let Some(change_hash) = change_hashes.prev(n) {
|
||||
return change_hash.into();
|
||||
}
|
||||
}
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// \memberof AMchangeHashes
|
||||
/// \brief Gets the size of the sequence of change hashes underlying an
|
||||
/// iterator.
|
||||
///
|
||||
/// \param[in] change_hashes A pointer to an `AMchangeHashes` struct.
|
||||
/// \return The count of values in \p change_hashes.
|
||||
/// \pre \p change_hashes `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// change_hashes must be a valid pointer to an AMchangeHashes
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangeHashesSize(change_hashes: *const AMchangeHashes) -> usize {
|
||||
if let Some(change_hashes) = change_hashes.as_ref() {
|
||||
change_hashes.len()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMchangeHashes
|
||||
/// \brief Creates an iterator over the same sequence of change hashes as the
|
||||
/// given one but with the opposite position and direction.
|
||||
///
|
||||
/// \param[in] change_hashes A pointer to an `AMchangeHashes` struct.
|
||||
/// \return An `AMchangeHashes` struct
|
||||
/// \pre \p change_hashes `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// change_hashes must be a valid pointer to an AMchangeHashes
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangeHashesReversed(
|
||||
change_hashes: *const AMchangeHashes,
|
||||
) -> AMchangeHashes {
|
||||
if let Some(change_hashes) = change_hashes.as_ref() {
|
||||
change_hashes.reversed()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMchangeHashes
|
||||
/// \brief Creates an iterator at the starting position over the same sequence
|
||||
/// of change hashes as the given one.
|
||||
///
|
||||
/// \param[in] change_hashes A pointer to an `AMchangeHashes` struct.
|
||||
/// \return An `AMchangeHashes` struct
|
||||
/// \pre \p change_hashes `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// change_hashes must be a valid pointer to an AMchangeHashes
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangeHashesRewound(
|
||||
change_hashes: *const AMchangeHashes,
|
||||
) -> AMchangeHashes {
|
||||
if let Some(change_hashes) = change_hashes.as_ref() {
|
||||
change_hashes.rewound()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
399
rust/automerge-c/src/changes.rs
Normal file
399
rust/automerge-c/src/changes.rs
Normal file
|
@ -0,0 +1,399 @@
|
|||
use automerge as am;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ffi::c_void;
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::byte_span::AMbyteSpan;
|
||||
use crate::change::AMchange;
|
||||
use crate::result::{to_result, AMresult};
|
||||
|
||||
#[repr(C)]
|
||||
struct Detail {
|
||||
len: usize,
|
||||
offset: isize,
|
||||
ptr: *const c_void,
|
||||
storage: *mut c_void,
|
||||
}
|
||||
|
||||
/// \note cbindgen won't propagate the value of a `std::mem::size_of<T>()` call
|
||||
/// (https://github.com/eqrion/cbindgen/issues/252) but it will
|
||||
/// propagate the name of a constant initialized from it so if the
|
||||
/// constant's name is a symbolic representation of the value it can be
|
||||
/// converted into a number by post-processing the header it generated.
|
||||
pub const USIZE_USIZE_USIZE_USIZE_: usize = size_of::<Detail>();
|
||||
|
||||
impl Detail {
|
||||
fn new(changes: &[am::Change], offset: isize, storage: &mut BTreeMap<usize, AMchange>) -> Self {
|
||||
let storage: *mut BTreeMap<usize, AMchange> = storage;
|
||||
Self {
|
||||
len: changes.len(),
|
||||
offset,
|
||||
ptr: changes.as_ptr() as *const c_void,
|
||||
storage: storage as *mut c_void,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
let len = self.len as isize;
|
||||
self.offset = if self.offset < 0 {
|
||||
// It's reversed.
|
||||
let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN);
|
||||
if unclipped >= 0 {
|
||||
// Clip it to the forward stop.
|
||||
len
|
||||
} else {
|
||||
std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1)
|
||||
}
|
||||
} else {
|
||||
let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX);
|
||||
if unclipped < 0 {
|
||||
// Clip it to the reverse stop.
|
||||
-(len + 1)
|
||||
} else {
|
||||
std::cmp::max(0, std::cmp::min(unclipped, len))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_index(&self) -> usize {
|
||||
(self.offset
|
||||
+ if self.offset < 0 {
|
||||
self.len as isize
|
||||
} else {
|
||||
0
|
||||
}) as usize
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<*const AMchange> {
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &mut [am::Change] =
|
||||
unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut am::Change, self.len) };
|
||||
let storage = unsafe { &mut *(self.storage as *mut BTreeMap<usize, AMchange>) };
|
||||
let index = self.get_index();
|
||||
let value = match storage.get_mut(&index) {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
storage.insert(index, AMchange::new(&mut slice[index]));
|
||||
storage.get_mut(&index).unwrap()
|
||||
}
|
||||
};
|
||||
self.advance(n);
|
||||
Some(value)
|
||||
}
|
||||
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
let len = self.len as isize;
|
||||
self.offset < -len || self.offset == len
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<*const AMchange> {
|
||||
self.advance(-n);
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &mut [am::Change] =
|
||||
unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut am::Change, self.len) };
|
||||
let storage = unsafe { &mut *(self.storage as *mut BTreeMap<usize, AMchange>) };
|
||||
let index = self.get_index();
|
||||
Some(match storage.get_mut(&index) {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
storage.insert(index, AMchange::new(&mut slice[index]));
|
||||
storage.get_mut(&index).unwrap()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: -(self.offset + 1),
|
||||
ptr: self.ptr,
|
||||
storage: self.storage,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: if self.offset < 0 { -1 } else { 0 },
|
||||
ptr: self.ptr,
|
||||
storage: self.storage,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Detail> for [u8; USIZE_USIZE_USIZE_USIZE_] {
|
||||
fn from(detail: Detail) -> Self {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
(&detail as *const Detail) as *const u8,
|
||||
USIZE_USIZE_USIZE_USIZE_,
|
||||
)
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \struct AMchanges
|
||||
/// \installed_headerfile
|
||||
/// \brief A random-access iterator over a sequence of changes.
|
||||
#[repr(C)]
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub struct AMchanges {
|
||||
/// An implementation detail that is intentionally opaque.
|
||||
/// \warning Modifying \p detail will cause undefined behavior.
|
||||
/// \note The actual size of \p detail will vary by platform, this is just
|
||||
/// the one for the platform this documentation was built on.
|
||||
detail: [u8; USIZE_USIZE_USIZE_USIZE_],
|
||||
}
|
||||
|
||||
impl AMchanges {
|
||||
pub fn new(changes: &[am::Change], storage: &mut BTreeMap<usize, AMchange>) -> Self {
|
||||
Self {
|
||||
detail: Detail::new(changes, 0, &mut *storage).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.advance(n);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
detail.len
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<*const AMchange> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.next(n)
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<*const AMchange> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.prev(n)
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.reversed().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.rewound().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[am::Change]> for AMchanges {
|
||||
fn as_ref(&self) -> &[am::Change] {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
unsafe { std::slice::from_raw_parts(detail.ptr as *const am::Change, detail.len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AMchanges {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
detail: [0; USIZE_USIZE_USIZE_USIZE_],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMchanges
|
||||
/// \brief Advances an iterator over a sequence of changes by at most \p |n|
|
||||
/// positions where the sign of \p n is relative to the iterator's
|
||||
/// direction.
|
||||
///
|
||||
/// \param[in,out] changes A pointer to an `AMchanges` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \pre \p changes `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// changes must be a valid pointer to an AMchanges
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangesAdvance(changes: *mut AMchanges, n: isize) {
|
||||
if let Some(changes) = changes.as_mut() {
|
||||
changes.advance(n);
|
||||
};
|
||||
}
|
||||
|
||||
/// \memberof AMchanges
|
||||
/// \brief Tests the equality of two sequences of changes underlying a pair of
|
||||
/// iterators.
|
||||
///
|
||||
/// \param[in] changes1 A pointer to an `AMchanges` struct.
|
||||
/// \param[in] changes2 A pointer to an `AMchanges` struct.
|
||||
/// \return `true` if \p changes1 `==` \p changes2 and `false` otherwise.
|
||||
/// \pre \p changes1 `!= NULL`.
|
||||
/// \pre \p changes2 `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// changes1 must be a valid pointer to an AMchanges
|
||||
/// changes2 must be a valid pointer to an AMchanges
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangesEqual(
|
||||
changes1: *const AMchanges,
|
||||
changes2: *const AMchanges,
|
||||
) -> bool {
|
||||
match (changes1.as_ref(), changes2.as_ref()) {
|
||||
(Some(changes1), Some(changes2)) => changes1.as_ref() == changes2.as_ref(),
|
||||
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMchanges
|
||||
/// \brief Allocates an iterator over a sequence of changes and initializes it
|
||||
/// from a sequence of byte spans.
|
||||
///
|
||||
/// \param[in] src A pointer to an array of `AMbyteSpan` structs.
|
||||
/// \param[in] count The number of `AMbyteSpan` structs to copy from \p src.
|
||||
/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct.
|
||||
/// \pre \p src `!= NULL`.
|
||||
/// \pre `0 <` \p count `<= sizeof(`\p src`) / sizeof(AMbyteSpan)`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// src must be an AMbyteSpan array of size `>= count`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangesInit(src: *const AMbyteSpan, count: usize) -> *mut AMresult {
|
||||
let mut changes = Vec::<am::Change>::new();
|
||||
for n in 0..count {
|
||||
let byte_span = &*src.add(n);
|
||||
let slice = std::slice::from_raw_parts(byte_span.src, byte_span.count);
|
||||
match slice.try_into() {
|
||||
Ok(change) => {
|
||||
changes.push(change);
|
||||
}
|
||||
Err(e) => {
|
||||
return to_result(Err::<Vec<am::Change>, am::LoadChangeError>(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
to_result(Ok::<Vec<am::Change>, am::LoadChangeError>(changes))
|
||||
}
|
||||
|
||||
/// \memberof AMchanges
|
||||
/// \brief Gets the change at the current position of an iterator over a
|
||||
/// sequence of changes and then advances it by at most \p |n| positions
|
||||
/// where the sign of \p n is relative to the iterator's direction.
|
||||
///
|
||||
/// \param[in,out] changes A pointer to an `AMchanges` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return A pointer to an `AMchange` struct that's `NULL` when \p changes was
|
||||
/// previously advanced past its forward/reverse limit.
|
||||
/// \pre \p changes `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// changes must be a valid pointer to an AMchanges
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangesNext(changes: *mut AMchanges, n: isize) -> *const AMchange {
|
||||
if let Some(changes) = changes.as_mut() {
|
||||
if let Some(change) = changes.next(n) {
|
||||
return change;
|
||||
}
|
||||
}
|
||||
std::ptr::null()
|
||||
}
|
||||
|
||||
/// \memberof AMchanges
|
||||
/// \brief Advances an iterator over a sequence of changes by at most \p |n|
|
||||
/// positions where the sign of \p n is relative to the iterator's
|
||||
/// direction and then gets the change at its new position.
|
||||
///
|
||||
/// \param[in,out] changes A pointer to an `AMchanges` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return A pointer to an `AMchange` struct that's `NULL` when \p changes is
|
||||
/// presently advanced past its forward/reverse limit.
|
||||
/// \pre \p changes `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// changes must be a valid pointer to an AMchanges
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangesPrev(changes: *mut AMchanges, n: isize) -> *const AMchange {
|
||||
if let Some(changes) = changes.as_mut() {
|
||||
if let Some(change) = changes.prev(n) {
|
||||
return change;
|
||||
}
|
||||
}
|
||||
std::ptr::null()
|
||||
}
|
||||
|
||||
/// \memberof AMchanges
|
||||
/// \brief Gets the size of the sequence of changes underlying an iterator.
|
||||
///
|
||||
/// \param[in] changes A pointer to an `AMchanges` struct.
|
||||
/// \return The count of values in \p changes.
|
||||
/// \pre \p changes `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// changes must be a valid pointer to an AMchanges
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangesSize(changes: *const AMchanges) -> usize {
|
||||
if let Some(changes) = changes.as_ref() {
|
||||
changes.len()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMchanges
|
||||
/// \brief Creates an iterator over the same sequence of changes as the given
|
||||
/// one but with the opposite position and direction.
|
||||
///
|
||||
/// \param[in] changes A pointer to an `AMchanges` struct.
|
||||
/// \return An `AMchanges` struct.
|
||||
/// \pre \p changes `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// changes must be a valid pointer to an AMchanges
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangesReversed(changes: *const AMchanges) -> AMchanges {
|
||||
if let Some(changes) = changes.as_ref() {
|
||||
changes.reversed()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMchanges
|
||||
/// \brief Creates an iterator at the starting position over the same sequence
|
||||
/// of changes as the given one.
|
||||
///
|
||||
/// \param[in] changes A pointer to an `AMchanges` struct.
|
||||
/// \return An `AMchanges` struct
|
||||
/// \pre \p changes `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// changes must be a valid pointer to an AMchanges
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMchangesRewound(changes: *const AMchanges) -> AMchanges {
|
||||
if let Some(changes) = changes.as_ref() {
|
||||
changes.rewound()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,46 +1,48 @@
|
|||
use automerge as am;
|
||||
use automerge::transaction::Transactable;
|
||||
use automerge::ReadDoc;
|
||||
|
||||
use crate::byte_span::{to_str, AMbyteSpan};
|
||||
use crate::doc::{to_doc, to_doc_mut, AMdoc};
|
||||
use crate::items::AMitems;
|
||||
use crate::obj::{to_obj_id, to_obj_type, AMobjId, AMobjType};
|
||||
use crate::change_hashes::AMchangeHashes;
|
||||
use crate::doc::{to_doc, to_doc_mut, to_obj_id, AMdoc};
|
||||
use crate::obj::{to_obj_type, AMobjId, AMobjType};
|
||||
use crate::result::{to_result, AMresult};
|
||||
|
||||
pub mod item;
|
||||
pub mod items;
|
||||
|
||||
macro_rules! adjust {
|
||||
($pos:expr, $insert:expr, $len:expr) => {{
|
||||
($index:expr, $insert:expr, $len:expr) => {{
|
||||
// An empty object can only be inserted into.
|
||||
let insert = $insert || $len == 0;
|
||||
let end = if insert { $len } else { $len - 1 };
|
||||
if $pos > end && $pos != usize::MAX {
|
||||
return AMresult::error(&format!("Invalid pos {}", $pos)).into();
|
||||
if $index > end && $index != usize::MAX {
|
||||
return AMresult::err(&format!("Invalid index {}", $index)).into();
|
||||
}
|
||||
(std::cmp::min($pos, end), insert)
|
||||
(std::cmp::min($index, end), insert)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! to_range {
|
||||
($begin:expr, $end:expr) => {{
|
||||
if $begin > $end {
|
||||
return AMresult::error(&format!("Invalid range [{}-{})", $begin, $end)).into();
|
||||
return AMresult::err(&format!("Invalid range [{}-{})", $begin, $end)).into();
|
||||
};
|
||||
($begin..$end)
|
||||
}};
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Deletes an item from a list object.
|
||||
/// \brief Deletes an index in a list object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] pos The position of an item within the list object identified by
|
||||
/// \p obj_id or `SIZE_MAX` to indicate its last item.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||
/// `SIZE_MAX` to indicate its last index.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -50,109 +52,101 @@ macro_rules! to_range {
|
|||
pub unsafe extern "C" fn AMlistDelete(
|
||||
doc: *mut AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
pos: usize,
|
||||
index: usize,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc_mut!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let (pos, _) = adjust!(pos, false, doc.length(obj_id));
|
||||
to_result(doc.delete(obj_id, pos))
|
||||
let (index, _) = adjust!(index, false, doc.length(obj_id));
|
||||
to_result(doc.delete(obj_id, index))
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Gets a current or historical item within a list object.
|
||||
/// \brief Gets the current or historical value at an index in a list object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] pos The position of an item within the list object identified by
|
||||
/// \p obj_id or `SIZE_MAX` to indicate its last item.
|
||||
/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH`
|
||||
/// items to select a historical item at \p pos or `NULL`
|
||||
/// to select the current item at \p pos.
|
||||
/// \return A pointer to an `AMresult` struct with an `AMitem` struct.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||
/// `SIZE_MAX` to indicate its last index.
|
||||
/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical
|
||||
/// value or `NULL` for the current value.
|
||||
/// \return A pointer to an `AMresult` struct that doesn't contain a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// heads must be a valid pointer to an AMitems or std::ptr::null()
|
||||
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMlistGet(
|
||||
doc: *const AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
pos: usize,
|
||||
heads: *const AMitems,
|
||||
index: usize,
|
||||
heads: *const AMchangeHashes,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let (pos, _) = adjust!(pos, false, doc.length(obj_id));
|
||||
match heads.as_ref() {
|
||||
None => to_result(doc.get(obj_id, pos)),
|
||||
Some(heads) => match <Vec<am::ChangeHash>>::try_from(heads) {
|
||||
Ok(heads) => to_result(doc.get_at(obj_id, pos, &heads)),
|
||||
Err(e) => AMresult::error(&e.to_string()).into(),
|
||||
},
|
||||
}
|
||||
let (index, _) = adjust!(index, false, doc.length(obj_id));
|
||||
to_result(match heads.as_ref() {
|
||||
None => doc.get(obj_id, index),
|
||||
Some(heads) => doc.get_at(obj_id, index, heads.as_ref()),
|
||||
})
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Gets all of the historical items at a position within a list object
|
||||
/// until its current one or a specific one.
|
||||
/// \brief Gets all of the historical values at an index in a list object until
|
||||
/// its current one or a specific one.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] pos The position of an item within the list object identified by
|
||||
/// \p obj_id or `SIZE_MAX` to indicate its last item.
|
||||
/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH`
|
||||
/// items to select a historical last item or `NULL` to select
|
||||
/// the current last item.
|
||||
/// \return A pointer to an `AMresult` struct with an `AMitems` struct.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||
/// `SIZE_MAX` to indicate its last index.
|
||||
/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical
|
||||
/// last value or `NULL` for the current last value.
|
||||
/// \return A pointer to an `AMresult` struct containing an `AMobjItems` struct.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// heads must be a valid pointer to an AMitems or std::ptr::null()
|
||||
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMlistGetAll(
|
||||
doc: *const AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
pos: usize,
|
||||
heads: *const AMitems,
|
||||
index: usize,
|
||||
heads: *const AMchangeHashes,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let (pos, _) = adjust!(pos, false, doc.length(obj_id));
|
||||
let (index, _) = adjust!(index, false, doc.length(obj_id));
|
||||
match heads.as_ref() {
|
||||
None => to_result(doc.get_all(obj_id, pos)),
|
||||
Some(heads) => match <Vec<am::ChangeHash>>::try_from(heads) {
|
||||
Ok(heads) => to_result(doc.get_all_at(obj_id, pos, &heads)),
|
||||
Err(e) => AMresult::error(&e.to_string()).into(),
|
||||
},
|
||||
None => to_result(doc.get_all(obj_id, index)),
|
||||
Some(heads) => to_result(doc.get_all_at(obj_id, index, heads.as_ref())),
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Increments a counter value in an item within a list object by the
|
||||
/// given value.
|
||||
/// \brief Increments a counter at an index in a list object by the given
|
||||
/// value.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] pos The position of an item within the list object identified by
|
||||
/// \p obj_id or `SIZE_MAX` to indicate its last item.
|
||||
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||
/// `SIZE_MAX` to indicate its last index.
|
||||
/// \param[in] value A 64-bit signed integer.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -162,33 +156,32 @@ pub unsafe extern "C" fn AMlistGetAll(
|
|||
pub unsafe extern "C" fn AMlistIncrement(
|
||||
doc: *mut AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
pos: usize,
|
||||
index: usize,
|
||||
value: i64,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc_mut!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let (pos, _) = adjust!(pos, false, doc.length(obj_id));
|
||||
to_result(doc.increment(obj_id, pos, value))
|
||||
let (index, _) = adjust!(index, false, doc.length(obj_id));
|
||||
to_result(doc.increment(obj_id, index, value))
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Puts a boolean value into an item within a list object.
|
||||
/// \brief Puts a boolean as the value at an index in a list object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] pos The position of an item within the list object identified by
|
||||
/// \p obj_id or `SIZE_MAX` to indicate its last item if
|
||||
/// \p insert `== false` or one past its last item if
|
||||
/// \p insert `== true`.
|
||||
/// \param[in] insert A flag for inserting a new item for \p value before
|
||||
/// \p pos instead of putting \p value into the item at
|
||||
/// \p pos.
|
||||
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||
/// `== false` or one past its last index if \p insert
|
||||
/// `== true`.
|
||||
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||
/// writing \p value over \p index.
|
||||
/// \param[in] value A boolean.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -198,85 +191,84 @@ pub unsafe extern "C" fn AMlistIncrement(
|
|||
pub unsafe extern "C" fn AMlistPutBool(
|
||||
doc: *mut AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
pos: usize,
|
||||
index: usize,
|
||||
insert: bool,
|
||||
value: bool,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc_mut!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
|
||||
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||
let value = am::ScalarValue::Boolean(value);
|
||||
to_result(if insert {
|
||||
doc.insert(obj_id, pos, value)
|
||||
doc.insert(obj_id, index, value)
|
||||
} else {
|
||||
doc.put(obj_id, pos, value)
|
||||
doc.put(obj_id, index, value)
|
||||
})
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Puts an array of bytes value at a position within a list object.
|
||||
/// \brief Puts a sequence of bytes as the value at an index in a list object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] pos The position of an item within the list object identified by
|
||||
/// \p obj_id or `SIZE_MAX` to indicate its last item if
|
||||
/// \p insert `== false` or one past its last item if
|
||||
/// \p insert `== true`.
|
||||
/// \param[in] insert A flag for inserting a new item for \p value before
|
||||
/// \p pos instead of putting \p value into the item at
|
||||
/// \p pos.
|
||||
/// \param[in] value A view onto the array of bytes to copy from as an
|
||||
/// `AMbyteSpan` struct.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX`
|
||||
/// \pre \p value.src `!= NULL`
|
||||
/// \pre `0 <` \p value.count `<= sizeof(`\p value.src `)`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||
/// `== false` or one past its last index if \p insert
|
||||
/// `== true`.
|
||||
/// \param[in] insert A flag to insert \p src before \p index instead of
|
||||
/// writing \p src over \p index.
|
||||
/// \param[in] src A pointer to an array of bytes.
|
||||
/// \param[in] count The number of bytes to copy from \p src.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||
/// \pre \p src `!= NULL`.
|
||||
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// value.src must be a byte array of length >= value.count
|
||||
/// src must be a byte array of size `>= count`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMlistPutBytes(
|
||||
doc: *mut AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
pos: usize,
|
||||
index: usize,
|
||||
insert: bool,
|
||||
value: AMbyteSpan,
|
||||
val: AMbyteSpan,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc_mut!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
|
||||
let value: Vec<u8> = (&value).into();
|
||||
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||
let mut value = Vec::new();
|
||||
value.extend_from_slice(std::slice::from_raw_parts(val.src, val.count));
|
||||
to_result(if insert {
|
||||
doc.insert(obj_id, pos, value)
|
||||
doc.insert(obj_id, index, value)
|
||||
} else {
|
||||
doc.put(obj_id, pos, value)
|
||||
doc.put(obj_id, index, value)
|
||||
})
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Puts a CRDT counter value into an item within a list object.
|
||||
/// \brief Puts a CRDT counter as the value at an index in a list object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] pos The position of an item within the list object identified by
|
||||
/// \p obj_id or `SIZE_MAX` to indicate its last item if
|
||||
/// \p insert `== false` or one past its last item if
|
||||
/// \p insert `== true`.
|
||||
/// \param[in] insert A flag for inserting a new item for \p value before
|
||||
/// \p pos instead of putting \p value into the item at
|
||||
/// \p pos.
|
||||
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||
/// `== false` or one past its last index if \p insert
|
||||
/// `== true`.
|
||||
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||
/// writing \p value over \p index.
|
||||
/// \param[in] value A 64-bit signed integer.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -286,39 +278,38 @@ pub unsafe extern "C" fn AMlistPutBytes(
|
|||
pub unsafe extern "C" fn AMlistPutCounter(
|
||||
doc: *mut AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
pos: usize,
|
||||
index: usize,
|
||||
insert: bool,
|
||||
value: i64,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc_mut!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
|
||||
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||
let value = am::ScalarValue::Counter(value.into());
|
||||
to_result(if insert {
|
||||
doc.insert(obj_id, pos, value)
|
||||
doc.insert(obj_id, index, value)
|
||||
} else {
|
||||
doc.put(obj_id, pos, value)
|
||||
doc.put(obj_id, index, value)
|
||||
})
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Puts a float value into an item within a list object.
|
||||
/// \brief Puts a float as the value at an index in a list object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] pos The position of an item within the list object identified by
|
||||
/// \p obj_id or `SIZE_MAX` to indicate its last item if
|
||||
/// \p insert `== false` or one past its last item if
|
||||
/// \p insert `== true`.
|
||||
/// \param[in] insert A flag for inserting a new item for \p value before
|
||||
/// \p pos instead of putting \p value into the item at
|
||||
/// \p pos.
|
||||
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||
/// `== false` or one past its last index if \p insert
|
||||
/// `== true`.
|
||||
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||
/// writing \p value over \p index.
|
||||
/// \param[in] value A 64-bit float.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -328,38 +319,37 @@ pub unsafe extern "C" fn AMlistPutCounter(
|
|||
pub unsafe extern "C" fn AMlistPutF64(
|
||||
doc: *mut AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
pos: usize,
|
||||
index: usize,
|
||||
insert: bool,
|
||||
value: f64,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc_mut!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
|
||||
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||
to_result(if insert {
|
||||
doc.insert(obj_id, pos, value)
|
||||
doc.insert(obj_id, index, value)
|
||||
} else {
|
||||
doc.put(obj_id, pos, value)
|
||||
doc.put(obj_id, index, value)
|
||||
})
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Puts a signed integer value into an item within a list object.
|
||||
/// \brief Puts a signed integer as the value at an index in a list object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] pos The position of an item within the list object identified by
|
||||
/// \p obj_id or `SIZE_MAX` to indicate its last item if
|
||||
/// \p insert `== false` or one past its last item if
|
||||
/// \p insert `== true`.
|
||||
/// \param[in] insert A flag for inserting a new item for \p value before
|
||||
/// \p pos instead of putting \p value into the item at
|
||||
/// \p pos.
|
||||
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||
/// `== false` or one past its last index if \p insert
|
||||
/// `== true`.
|
||||
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||
/// writing \p value over \p index.
|
||||
/// \param[in] value A 64-bit signed integer.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -369,37 +359,36 @@ pub unsafe extern "C" fn AMlistPutF64(
|
|||
pub unsafe extern "C" fn AMlistPutInt(
|
||||
doc: *mut AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
pos: usize,
|
||||
index: usize,
|
||||
insert: bool,
|
||||
value: i64,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc_mut!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
|
||||
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||
to_result(if insert {
|
||||
doc.insert(obj_id, pos, value)
|
||||
doc.insert(obj_id, index, value)
|
||||
} else {
|
||||
doc.put(obj_id, pos, value)
|
||||
doc.put(obj_id, index, value)
|
||||
})
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Puts a null value into an item within a list object.
|
||||
/// \brief Puts null as the value at an index in a list object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] pos The position of an item within the list object identified by
|
||||
/// \p obj_id or `SIZE_MAX` to indicate its last item if
|
||||
/// \p insert `== false` or one past its last item if
|
||||
/// \p insert `== true`.
|
||||
/// \param[in] insert A flag for inserting a new item for \p value before
|
||||
/// \p pos instead of putting \p value into the item at
|
||||
/// \p pos.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||
/// `== false` or one past its last index if \p insert
|
||||
/// `== true`.
|
||||
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||
/// writing \p value over \p index.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -409,37 +398,38 @@ pub unsafe extern "C" fn AMlistPutInt(
|
|||
pub unsafe extern "C" fn AMlistPutNull(
|
||||
doc: *mut AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
pos: usize,
|
||||
index: usize,
|
||||
insert: bool,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc_mut!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
|
||||
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||
to_result(if insert {
|
||||
doc.insert(obj_id, pos, ())
|
||||
doc.insert(obj_id, index, ())
|
||||
} else {
|
||||
doc.put(obj_id, pos, ())
|
||||
doc.put(obj_id, index, ())
|
||||
})
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Puts an empty object value into an item within a list object.
|
||||
/// \brief Puts an empty object as the value at an index in a list object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] pos The position of an item within the list object identified by
|
||||
/// \p obj_id or `SIZE_MAX` to indicate its last item if
|
||||
/// \p insert `== false` or one past its last item if
|
||||
/// \p insert `== true`.
|
||||
/// \param[in] insert A flag for inserting a new item for \p value before
|
||||
/// \p pos instead of putting \p value into the item at
|
||||
/// \p pos.
|
||||
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||
/// `== false` or one past its last index if \p insert
|
||||
/// `== true`.
|
||||
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||
/// writing \p value over \p index.
|
||||
/// \param[in] obj_type An `AMobjIdType` enum tag.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_OBJ_TYPE` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||
/// `AMobjId` struct.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||
/// \pre \p obj_type != `AM_OBJ_TYPE_VOID`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -449,85 +439,82 @@ pub unsafe extern "C" fn AMlistPutNull(
|
|||
pub unsafe extern "C" fn AMlistPutObject(
|
||||
doc: *mut AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
pos: usize,
|
||||
index: usize,
|
||||
insert: bool,
|
||||
obj_type: AMobjType,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc_mut!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
|
||||
let obj_type = to_obj_type!(obj_type);
|
||||
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||
let object = to_obj_type!(obj_type);
|
||||
to_result(if insert {
|
||||
(doc.insert_object(obj_id, pos, obj_type), obj_type)
|
||||
doc.insert_object(obj_id, index, object)
|
||||
} else {
|
||||
(doc.put_object(obj_id, pos, obj_type), obj_type)
|
||||
doc.put_object(obj_id, index, object)
|
||||
})
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Puts a UTF-8 string value into an item within a list object.
|
||||
/// \brief Puts a UTF-8 string as the value at an index in a list object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] pos The position of an item within the list object identified by
|
||||
/// \p obj_id or `SIZE_MAX` to indicate its last item if
|
||||
/// \p insert `== false` or one past its last item if
|
||||
/// \p insert `== true`.
|
||||
/// \param[in] insert A flag for inserting a new item for \p value before
|
||||
/// \p pos instead of putting \p value into the item at
|
||||
/// \p pos.
|
||||
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||
/// `== false` or one past its last index if \p insert
|
||||
/// `== true`.
|
||||
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||
/// writing \p value over \p index.
|
||||
/// \param[in] value A UTF-8 string view as an `AMbyteSpan` struct.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX`
|
||||
/// \pre \p value.src `!= NULL`
|
||||
/// \pre `0 <` \p value.count `<= sizeof(`\p value.src `)`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||
/// \pre \p value `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// value.src must be a byte array of length >= value.count
|
||||
/// value must be a null-terminated array of `c_char`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMlistPutStr(
|
||||
doc: *mut AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
pos: usize,
|
||||
index: usize,
|
||||
insert: bool,
|
||||
value: AMbyteSpan,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc_mut!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
|
||||
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||
let value = to_str!(value);
|
||||
to_result(if insert {
|
||||
doc.insert(obj_id, pos, value)
|
||||
doc.insert(obj_id, index, value)
|
||||
} else {
|
||||
doc.put(obj_id, pos, value)
|
||||
doc.put(obj_id, index, value)
|
||||
})
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Puts a *nix timestamp (milliseconds) value into an item within a
|
||||
/// \brief Puts a *nix timestamp (milliseconds) as the value at an index in a
|
||||
/// list object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] pos The position of an item within the list object identified by
|
||||
/// \p obj_id or `SIZE_MAX` to indicate its last item if
|
||||
/// \p insert `== false` or one past its last item if
|
||||
/// \p insert `== true`.
|
||||
/// \param[in] insert A flag for inserting a new item for \p value before
|
||||
/// \p pos instead of putting \p value into the item at
|
||||
/// \p pos.
|
||||
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||
/// `== false` or one past its last index if \p insert
|
||||
/// `== true`.
|
||||
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||
/// writing \p value over \p index.
|
||||
/// \param[in] value A 64-bit signed integer.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -537,39 +524,38 @@ pub unsafe extern "C" fn AMlistPutStr(
|
|||
pub unsafe extern "C" fn AMlistPutTimestamp(
|
||||
doc: *mut AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
pos: usize,
|
||||
index: usize,
|
||||
insert: bool,
|
||||
value: i64,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc_mut!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
|
||||
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||
let value = am::ScalarValue::Timestamp(value);
|
||||
to_result(if insert {
|
||||
doc.insert(obj_id, pos, value)
|
||||
doc.insert(obj_id, index, value)
|
||||
} else {
|
||||
doc.put(obj_id, pos, value)
|
||||
doc.put(obj_id, index, value)
|
||||
})
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Puts an unsigned integer value into an item within a list object.
|
||||
/// \brief Puts an unsigned integer as the value at an index in a list object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] pos The position of an item within the list object identified by
|
||||
/// \p obj_id or `SIZE_MAX` to indicate its last item if
|
||||
/// \p insert `== false` or one past its last item if
|
||||
/// \p insert `== true`.
|
||||
/// \param[in] insert A flag for inserting a new item for \p value before
|
||||
/// \p pos instead of putting \p value into the item at
|
||||
/// \p pos.
|
||||
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||
/// `== false` or one past its last index if \p insert
|
||||
/// `== true`.
|
||||
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||
/// writing \p value over \p index.
|
||||
/// \param[in] value A 64-bit unsigned integer.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -579,58 +565,56 @@ pub unsafe extern "C" fn AMlistPutTimestamp(
|
|||
pub unsafe extern "C" fn AMlistPutUint(
|
||||
doc: *mut AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
pos: usize,
|
||||
index: usize,
|
||||
insert: bool,
|
||||
value: u64,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc_mut!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let (pos, insert) = adjust!(pos, insert, doc.length(obj_id));
|
||||
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||
to_result(if insert {
|
||||
doc.insert(obj_id, pos, value)
|
||||
doc.insert(obj_id, index, value)
|
||||
} else {
|
||||
doc.put(obj_id, pos, value)
|
||||
doc.put(obj_id, index, value)
|
||||
})
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Gets the current or historical items in the list object within the
|
||||
/// given range.
|
||||
/// \brief Gets the current or historical indices and values of the list object
|
||||
/// within the given range.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] begin The first pos in a range of indices.
|
||||
/// \param[in] end At least one past the last pos in a range of indices.
|
||||
/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH`
|
||||
/// items to select historical items or `NULL` to select
|
||||
/// current items.
|
||||
/// \return A pointer to an `AMresult` struct with an `AMitems` struct.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre \p begin `<=` \p end `<= SIZE_MAX`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] begin The first index in a range of indices.
|
||||
/// \param[in] end At least one past the last index in a range of indices.
|
||||
/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical
|
||||
/// indices and values or `NULL` for current indices and
|
||||
/// values.
|
||||
/// \return A pointer to an `AMresult` struct containing an `AMlistItems`
|
||||
/// struct.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre \p begin `<=` \p end `<= SIZE_MAX`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// heads must be a valid pointer to an AMitems or std::ptr::null()
|
||||
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMlistRange(
|
||||
doc: *const AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
begin: usize,
|
||||
end: usize,
|
||||
heads: *const AMitems,
|
||||
heads: *const AMchangeHashes,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let range = to_range!(begin, end);
|
||||
match heads.as_ref() {
|
||||
None => to_result(doc.list_range(obj_id, range)),
|
||||
Some(heads) => match <Vec<am::ChangeHash>>::try_from(heads) {
|
||||
Ok(heads) => to_result(doc.list_range_at(obj_id, range, &heads)),
|
||||
Err(e) => AMresult::error(&e.to_string()).into(),
|
||||
},
|
||||
Some(heads) => to_result(doc.list_range_at(obj_id, range, heads.as_ref())),
|
||||
}
|
||||
}
|
||||
|
|
97
rust/automerge-c/src/doc/list/item.rs
Normal file
97
rust/automerge-c/src/doc/list/item.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
use automerge as am;
|
||||
|
||||
use crate::obj::AMobjId;
|
||||
use crate::result::AMvalue;
|
||||
|
||||
/// \struct AMlistItem
|
||||
/// \installed_headerfile
|
||||
/// \brief An item in a list object.
|
||||
pub struct AMlistItem {
|
||||
/// The index of an item in a list object.
|
||||
index: usize,
|
||||
/// The object identifier of an item in a list object.
|
||||
obj_id: AMobjId,
|
||||
/// The value of an item in a list object.
|
||||
value: am::Value<'static>,
|
||||
}
|
||||
|
||||
impl AMlistItem {
|
||||
pub fn new(index: usize, value: am::Value<'static>, obj_id: am::ObjId) -> Self {
|
||||
Self {
|
||||
index,
|
||||
obj_id: AMobjId::new(obj_id),
|
||||
value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AMlistItem {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.index == other.index && self.obj_id == other.obj_id && self.value == other.value
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl From<&AMlistItem> for (usize, am::Value<'static>, am::ObjId) {
|
||||
fn from(list_item: &AMlistItem) -> Self {
|
||||
(list_item.index, list_item.value.0.clone(), list_item.obj_id.as_ref().clone())
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/// \memberof AMlistItem
|
||||
/// \brief Gets the index of an item in a list object.
|
||||
///
|
||||
/// \param[in] list_item A pointer to an `AMlistItem` struct.
|
||||
/// \return A 64-bit unsigned integer.
|
||||
/// \pre \p list_item `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// list_item must be a valid pointer to an AMlistItem
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMlistItemIndex(list_item: *const AMlistItem) -> usize {
|
||||
if let Some(list_item) = list_item.as_ref() {
|
||||
list_item.index
|
||||
} else {
|
||||
usize::MAX
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMlistItem
|
||||
/// \brief Gets the object identifier of an item in a list object.
|
||||
///
|
||||
/// \param[in] list_item A pointer to an `AMlistItem` struct.
|
||||
/// \return A pointer to an `AMobjId` struct.
|
||||
/// \pre \p list_item `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// list_item must be a valid pointer to an AMlistItem
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMlistItemObjId(list_item: *const AMlistItem) -> *const AMobjId {
|
||||
if let Some(list_item) = list_item.as_ref() {
|
||||
&list_item.obj_id
|
||||
} else {
|
||||
std::ptr::null()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMlistItem
|
||||
/// \brief Gets the value of an item in a list object.
|
||||
///
|
||||
/// \param[in] list_item A pointer to an `AMlistItem` struct.
|
||||
/// \return An `AMvalue` struct.
|
||||
/// \pre \p list_item `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// list_item must be a valid pointer to an AMlistItem
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMlistItemValue<'a>(list_item: *const AMlistItem) -> AMvalue<'a> {
|
||||
if let Some(list_item) = list_item.as_ref() {
|
||||
(&list_item.value).into()
|
||||
} else {
|
||||
AMvalue::Void
|
||||
}
|
||||
}
|
348
rust/automerge-c/src/doc/list/items.rs
Normal file
348
rust/automerge-c/src/doc/list/items.rs
Normal file
|
@ -0,0 +1,348 @@
|
|||
use std::ffi::c_void;
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::doc::list::item::AMlistItem;
|
||||
|
||||
#[repr(C)]
|
||||
struct Detail {
|
||||
len: usize,
|
||||
offset: isize,
|
||||
ptr: *const c_void,
|
||||
}
|
||||
|
||||
/// \note cbindgen won't propagate the value of a `std::mem::size_of<T>()` call
|
||||
/// (https://github.com/eqrion/cbindgen/issues/252) but it will
|
||||
/// propagate the name of a constant initialized from it so if the
|
||||
/// constant's name is a symbolic representation of the value it can be
|
||||
/// converted into a number by post-processing the header it generated.
|
||||
pub const USIZE_USIZE_USIZE_: usize = size_of::<Detail>();
|
||||
|
||||
impl Detail {
|
||||
fn new(list_items: &[AMlistItem], offset: isize) -> Self {
|
||||
Self {
|
||||
len: list_items.len(),
|
||||
offset,
|
||||
ptr: list_items.as_ptr() as *const c_void,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
let len = self.len as isize;
|
||||
self.offset = if self.offset < 0 {
|
||||
// It's reversed.
|
||||
let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN);
|
||||
if unclipped >= 0 {
|
||||
// Clip it to the forward stop.
|
||||
len
|
||||
} else {
|
||||
std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1)
|
||||
}
|
||||
} else {
|
||||
let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX);
|
||||
if unclipped < 0 {
|
||||
// Clip it to the reverse stop.
|
||||
-(len + 1)
|
||||
} else {
|
||||
std::cmp::max(0, std::cmp::min(unclipped, len))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_index(&self) -> usize {
|
||||
(self.offset
|
||||
+ if self.offset < 0 {
|
||||
self.len as isize
|
||||
} else {
|
||||
0
|
||||
}) as usize
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<&AMlistItem> {
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &[AMlistItem] =
|
||||
unsafe { std::slice::from_raw_parts(self.ptr as *const AMlistItem, self.len) };
|
||||
let value = &slice[self.get_index()];
|
||||
self.advance(n);
|
||||
Some(value)
|
||||
}
|
||||
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
let len = self.len as isize;
|
||||
self.offset < -len || self.offset == len
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<&AMlistItem> {
|
||||
self.advance(-n);
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &[AMlistItem] =
|
||||
unsafe { std::slice::from_raw_parts(self.ptr as *const AMlistItem, self.len) };
|
||||
Some(&slice[self.get_index()])
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: -(self.offset + 1),
|
||||
ptr: self.ptr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: if self.offset < 0 { -1 } else { 0 },
|
||||
ptr: self.ptr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Detail> for [u8; USIZE_USIZE_USIZE_] {
|
||||
fn from(detail: Detail) -> Self {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_)
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \struct AMlistItems
|
||||
/// \installed_headerfile
|
||||
/// \brief A random-access iterator over a sequence of list object items.
|
||||
#[repr(C)]
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub struct AMlistItems {
|
||||
/// An implementation detail that is intentionally opaque.
|
||||
/// \warning Modifying \p detail will cause undefined behavior.
|
||||
/// \note The actual size of \p detail will vary by platform, this is just
|
||||
/// the one for the platform this documentation was built on.
|
||||
detail: [u8; USIZE_USIZE_USIZE_],
|
||||
}
|
||||
|
||||
impl AMlistItems {
|
||||
pub fn new(list_items: &[AMlistItem]) -> Self {
|
||||
Self {
|
||||
detail: Detail::new(list_items, 0).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.advance(n);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
detail.len
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<&AMlistItem> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.next(n)
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<&AMlistItem> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.prev(n)
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.reversed().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.rewound().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[AMlistItem]> for AMlistItems {
|
||||
fn as_ref(&self) -> &[AMlistItem] {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
unsafe { std::slice::from_raw_parts(detail.ptr as *const AMlistItem, detail.len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AMlistItems {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
detail: [0; USIZE_USIZE_USIZE_],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMlistItems
|
||||
/// \brief Advances an iterator over a sequence of list object items by at most
|
||||
/// \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction.
|
||||
///
|
||||
/// \param[in,out] list_items A pointer to an `AMlistItems` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \pre \p list_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// list_items must be a valid pointer to an AMlistItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMlistItemsAdvance(list_items: *mut AMlistItems, n: isize) {
|
||||
if let Some(list_items) = list_items.as_mut() {
|
||||
list_items.advance(n);
|
||||
};
|
||||
}
|
||||
|
||||
/// \memberof AMlistItems
|
||||
/// \brief Tests the equality of two sequences of list object items underlying
|
||||
/// a pair of iterators.
|
||||
///
|
||||
/// \param[in] list_items1 A pointer to an `AMlistItems` struct.
|
||||
/// \param[in] list_items2 A pointer to an `AMlistItems` struct.
|
||||
/// \return `true` if \p list_items1 `==` \p list_items2 and `false` otherwise.
|
||||
/// \pre \p list_items1 `!= NULL`.
|
||||
/// \pre \p list_items2 `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// list_items1 must be a valid pointer to an AMlistItems
|
||||
/// list_items2 must be a valid pointer to an AMlistItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMlistItemsEqual(
|
||||
list_items1: *const AMlistItems,
|
||||
list_items2: *const AMlistItems,
|
||||
) -> bool {
|
||||
match (list_items1.as_ref(), list_items2.as_ref()) {
|
||||
(Some(list_items1), Some(list_items2)) => list_items1.as_ref() == list_items2.as_ref(),
|
||||
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMlistItems
|
||||
/// \brief Gets the list object item at the current position of an iterator
|
||||
/// over a sequence of list object items and then advances it by at most
|
||||
/// \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction.
|
||||
///
|
||||
/// \param[in,out] list_items A pointer to an `AMlistItems` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return A pointer to an `AMlistItem` struct that's `NULL` when
|
||||
/// \p list_items was previously advanced past its forward/reverse
|
||||
/// limit.
|
||||
/// \pre \p list_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// list_items must be a valid pointer to an AMlistItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMlistItemsNext(
|
||||
list_items: *mut AMlistItems,
|
||||
n: isize,
|
||||
) -> *const AMlistItem {
|
||||
if let Some(list_items) = list_items.as_mut() {
|
||||
if let Some(list_item) = list_items.next(n) {
|
||||
return list_item;
|
||||
}
|
||||
}
|
||||
std::ptr::null()
|
||||
}
|
||||
|
||||
/// \memberof AMlistItems
|
||||
/// \brief Advances an iterator over a sequence of list object items by at most
|
||||
/// \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction and then gets the list object item at its new
|
||||
/// position.
|
||||
///
|
||||
/// \param[in,out] list_items A pointer to an `AMlistItems` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return A pointer to an `AMlistItem` struct that's `NULL` when
|
||||
/// \p list_items is presently advanced past its forward/reverse limit.
|
||||
/// \pre \p list_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// list_items must be a valid pointer to an AMlistItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMlistItemsPrev(
|
||||
list_items: *mut AMlistItems,
|
||||
n: isize,
|
||||
) -> *const AMlistItem {
|
||||
if let Some(list_items) = list_items.as_mut() {
|
||||
if let Some(list_item) = list_items.prev(n) {
|
||||
return list_item;
|
||||
}
|
||||
}
|
||||
std::ptr::null()
|
||||
}
|
||||
|
||||
/// \memberof AMlistItems
|
||||
/// \brief Gets the size of the sequence of list object items underlying an
|
||||
/// iterator.
|
||||
///
|
||||
/// \param[in] list_items A pointer to an `AMlistItems` struct.
|
||||
/// \return The count of values in \p list_items.
|
||||
/// \pre \p list_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// list_items must be a valid pointer to an AMlistItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMlistItemsSize(list_items: *const AMlistItems) -> usize {
|
||||
if let Some(list_items) = list_items.as_ref() {
|
||||
list_items.len()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMlistItems
|
||||
/// \brief Creates an iterator over the same sequence of list object items as
|
||||
/// the given one but with the opposite position and direction.
|
||||
///
|
||||
/// \param[in] list_items A pointer to an `AMlistItems` struct.
|
||||
/// \return An `AMlistItems` struct
|
||||
/// \pre \p list_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// list_items must be a valid pointer to an AMlistItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMlistItemsReversed(list_items: *const AMlistItems) -> AMlistItems {
|
||||
if let Some(list_items) = list_items.as_ref() {
|
||||
list_items.reversed()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMlistItems
|
||||
/// \brief Creates an iterator at the starting position over the same sequence
|
||||
/// of list object items as the given one.
|
||||
///
|
||||
/// \param[in] list_items A pointer to an `AMlistItems` struct.
|
||||
/// \return An `AMlistItems` struct
|
||||
/// \pre \p list_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// list_items must be a valid pointer to an AMlistItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMlistItemsRewound(list_items: *const AMlistItems) -> AMlistItems {
|
||||
if let Some(list_items) = list_items.as_ref() {
|
||||
list_items.rewound()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
|
@ -1,31 +1,32 @@
|
|||
use automerge as am;
|
||||
use automerge::transaction::Transactable;
|
||||
use automerge::ReadDoc;
|
||||
|
||||
use crate::byte_span::{to_str, AMbyteSpan};
|
||||
use crate::doc::{to_doc, to_doc_mut, AMdoc};
|
||||
use crate::items::AMitems;
|
||||
use crate::obj::{to_obj_id, to_obj_type, AMobjId, AMobjType};
|
||||
use crate::change_hashes::AMchangeHashes;
|
||||
use crate::doc::{to_doc, to_doc_mut, to_obj_id, AMdoc};
|
||||
use crate::obj::{to_obj_type, AMobjId, AMobjType};
|
||||
use crate::result::{to_result, AMresult};
|
||||
|
||||
pub mod item;
|
||||
pub mod items;
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Deletes an item from a map object.
|
||||
/// \brief Deletes a key in a map object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] key The UTF-8 string view key of an item within the map object
|
||||
/// identified by \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre \p key.src `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] key A UTF-8 string view key for the map object identified by
|
||||
/// \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre \p key `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// key.src must be a byte array of length >= key.count
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapDelete(
|
||||
doc: *mut AMdoc,
|
||||
|
@ -38,107 +39,96 @@ pub unsafe extern "C" fn AMmapDelete(
|
|||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Gets a current or historical item within a map object.
|
||||
/// \brief Gets the current or historical value for a key in a map object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] key The UTF-8 string view key of an item within the map object
|
||||
/// identified by \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH`
|
||||
/// items to select a historical item at \p key or `NULL`
|
||||
/// to select the current item at \p key.
|
||||
/// \return A pointer to an `AMresult` struct with an `AMitem` struct.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre \p key.src `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] key A UTF-8 string view key for the map object identified by
|
||||
/// \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical
|
||||
/// value or `NULL` for the current value.
|
||||
/// \return A pointer to an `AMresult` struct that doesn't contain a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre \p key `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// key.src must be a byte array of length >= key.count
|
||||
/// heads must be a valid pointer to an AMitems or std::ptr::null()
|
||||
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapGet(
|
||||
doc: *const AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
key: AMbyteSpan,
|
||||
heads: *const AMitems,
|
||||
heads: *const AMchangeHashes,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let key = to_str!(key);
|
||||
match heads.as_ref() {
|
||||
None => to_result(doc.get(obj_id, key)),
|
||||
Some(heads) => match <Vec<am::ChangeHash>>::try_from(heads) {
|
||||
Ok(heads) => to_result(doc.get_at(obj_id, key, &heads)),
|
||||
Err(e) => AMresult::error(&e.to_string()).into(),
|
||||
},
|
||||
Some(heads) => to_result(doc.get_at(obj_id, key, heads.as_ref())),
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Gets all of the historical items at a key within a map object until
|
||||
/// \brief Gets all of the historical values for a key in a map object until
|
||||
/// its current one or a specific one.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] key The UTF-8 string view key of an item within the map object
|
||||
/// identified by \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH`
|
||||
/// items to select a historical last item or `NULL` to
|
||||
/// select the current last item.
|
||||
/// \return A pointer to an `AMresult` struct with an `AMItems` struct.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre \p key.src `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] key A UTF-8 string view key for the map object identified by
|
||||
/// \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical
|
||||
/// last value or `NULL` for the current last value.
|
||||
/// \return A pointer to an `AMresult` struct containing an `AMobjItems` struct.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre \p key `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// key.src must be a byte array of length >= key.count
|
||||
/// heads must be a valid pointer to an AMitems or std::ptr::null()
|
||||
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapGetAll(
|
||||
doc: *const AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
key: AMbyteSpan,
|
||||
heads: *const AMitems,
|
||||
heads: *const AMchangeHashes,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let key = to_str!(key);
|
||||
match heads.as_ref() {
|
||||
None => to_result(doc.get_all(obj_id, key)),
|
||||
Some(heads) => match <Vec<am::ChangeHash>>::try_from(heads) {
|
||||
Ok(heads) => to_result(doc.get_all_at(obj_id, key, &heads)),
|
||||
Err(e) => AMresult::error(&e.to_string()).into(),
|
||||
},
|
||||
Some(heads) => to_result(doc.get_all_at(obj_id, key, heads.as_ref())),
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Increments a counter at a key in a map object by the given value.
|
||||
/// \brief Increments a counter for a key in a map object by the given value.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] key The UTF-8 string view key of an item within the map object
|
||||
/// identified by \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] key A UTF-8 string view key for the map object identified by
|
||||
/// \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] value A 64-bit signed integer.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre \p key.src `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre \p key `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// key.src must be a byte array of length >= key.count
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapIncrement(
|
||||
doc: *mut AMdoc,
|
||||
|
@ -154,22 +144,21 @@ pub unsafe extern "C" fn AMmapIncrement(
|
|||
/// \memberof AMdoc
|
||||
/// \brief Puts a boolean as the value of a key in a map object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] key The UTF-8 string view key of an item within the map object
|
||||
/// identified by \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] key A UTF-8 string view key for the map object identified by
|
||||
/// \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] value A boolean.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre \p key.src `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre \p key `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// key.src must be a byte array of length >= key.count
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapPutBool(
|
||||
doc: *mut AMdoc,
|
||||
|
@ -183,58 +172,59 @@ pub unsafe extern "C" fn AMmapPutBool(
|
|||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Puts an array of bytes value at a key in a map object.
|
||||
/// \brief Puts a sequence of bytes as the value of a key in a map object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] key The UTF-8 string view key of an item within the map object
|
||||
/// identified by \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] value A view onto an array of bytes as an `AMbyteSpan` struct.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre \p key.src `!= NULL`
|
||||
/// \pre \p value.src `!= NULL`
|
||||
/// \pre `0 <` \p value.count `<= sizeof(`\p value.src `)`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] key A UTF-8 string view key for the map object identified by
|
||||
/// \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] src A pointer to an array of bytes.
|
||||
/// \param[in] count The number of bytes to copy from \p src.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre \p key `!= NULL`.
|
||||
/// \pre \p src `!= NULL`.
|
||||
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// key.src must be a byte array of length >= key.count
|
||||
/// value.src must be a byte array of length >= value.count
|
||||
/// src must be a byte array of size `>= count`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapPutBytes(
|
||||
doc: *mut AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
key: AMbyteSpan,
|
||||
value: AMbyteSpan,
|
||||
val: AMbyteSpan,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc_mut!(doc);
|
||||
let key = to_str!(key);
|
||||
to_result(doc.put(to_obj_id!(obj_id), key, Vec::<u8>::from(&value)))
|
||||
let mut vec = Vec::new();
|
||||
vec.extend_from_slice(std::slice::from_raw_parts(val.src, val.count));
|
||||
to_result(doc.put(to_obj_id!(obj_id), key, vec))
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Puts a CRDT counter as the value of a key in a map object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] key A UTF-8 string view key for the map object identified by
|
||||
/// \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] value A 64-bit signed integer.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre \p key.src `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre \p key `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// key.src must be a byte array of length >= key.count
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapPutCounter(
|
||||
doc: *mut AMdoc,
|
||||
|
@ -254,21 +244,20 @@ pub unsafe extern "C" fn AMmapPutCounter(
|
|||
/// \memberof AMdoc
|
||||
/// \brief Puts null as the value of a key in a map object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] key A UTF-8 string view key for the map object identified by
|
||||
/// \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre \p key.src `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre \p key `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// key.src must be a byte array of length >= key.count
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapPutNull(
|
||||
doc: *mut AMdoc,
|
||||
|
@ -283,22 +272,23 @@ pub unsafe extern "C" fn AMmapPutNull(
|
|||
/// \memberof AMdoc
|
||||
/// \brief Puts an empty object as the value of a key in a map object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] key A UTF-8 string view key for the map object identified by
|
||||
/// \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] obj_type An `AMobjIdType` enum tag.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_OBJ_TYPE` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre \p key.src `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||
/// `AMobjId` struct.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre \p key `!= NULL`.
|
||||
/// \pre \p obj_type != `AM_OBJ_TYPE_VOID`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// key.src must be a byte array of length >= key.count
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapPutObject(
|
||||
doc: *mut AMdoc,
|
||||
|
@ -308,29 +298,27 @@ pub unsafe extern "C" fn AMmapPutObject(
|
|||
) -> *mut AMresult {
|
||||
let doc = to_doc_mut!(doc);
|
||||
let key = to_str!(key);
|
||||
let obj_type = to_obj_type!(obj_type);
|
||||
to_result((doc.put_object(to_obj_id!(obj_id), key, obj_type), obj_type))
|
||||
to_result(doc.put_object(to_obj_id!(obj_id), key, to_obj_type!(obj_type)))
|
||||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Puts a float as the value of a key in a map object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] key A UTF-8 string view key for the map object identified by
|
||||
/// \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] value A 64-bit float.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre \p key.src `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre \p key `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// key.src must be a byte array of length >= key.count
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapPutF64(
|
||||
doc: *mut AMdoc,
|
||||
|
@ -346,22 +334,21 @@ pub unsafe extern "C" fn AMmapPutF64(
|
|||
/// \memberof AMdoc
|
||||
/// \brief Puts a signed integer as the value of a key in a map object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] key A UTF-8 string view key for the map object identified by
|
||||
/// \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] value A 64-bit signed integer.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre \p key.src `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre \p key `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// key.src must be a byte array of length >= key.count
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapPutInt(
|
||||
doc: *mut AMdoc,
|
||||
|
@ -377,22 +364,21 @@ pub unsafe extern "C" fn AMmapPutInt(
|
|||
/// \memberof AMdoc
|
||||
/// \brief Puts a UTF-8 string as the value of a key in a map object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] key A UTF-8 string view key for the map object identified by
|
||||
/// \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] value A UTF-8 string view as an `AMbyteSpan` struct.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre \p key.src `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre \p key `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// key.src must be a byte array of length >= key.count
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapPutStr(
|
||||
doc: *mut AMdoc,
|
||||
|
@ -408,22 +394,21 @@ pub unsafe extern "C" fn AMmapPutStr(
|
|||
/// \brief Puts a *nix timestamp (milliseconds) as the value of a key in a map
|
||||
/// object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] key A UTF-8 string view key for the map object identified by
|
||||
/// \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] value A 64-bit signed integer.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre \p key.src `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre \p key `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// key.src must be a byte array of length >= key.count
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapPutTimestamp(
|
||||
doc: *mut AMdoc,
|
||||
|
@ -439,22 +424,21 @@ pub unsafe extern "C" fn AMmapPutTimestamp(
|
|||
/// \memberof AMdoc
|
||||
/// \brief Puts an unsigned integer as the value of a key in a map object.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] key A UTF-8 string view key for the map object identified by
|
||||
/// \p obj_id as an `AMbyteSpan` struct.
|
||||
/// \param[in] value A 64-bit unsigned integer.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \pre \p key.src `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a void.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \pre \p key `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// key.src must be a byte array of length >= key.count
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapPutUint(
|
||||
doc: *mut AMdoc,
|
||||
|
@ -468,82 +452,71 @@ pub unsafe extern "C" fn AMmapPutUint(
|
|||
}
|
||||
|
||||
/// \memberof AMdoc
|
||||
/// \brief Gets the current or historical items of the map object within the
|
||||
/// given range.
|
||||
/// \brief Gets the current or historical keys and values of the map object
|
||||
/// within the given range.
|
||||
///
|
||||
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||
/// \param[in] begin The first key in a subrange or `AMstr(NULL)` to indicate the
|
||||
/// absolute first key.
|
||||
/// \param[in] end The key one past the last key in a subrange or `AMstr(NULL)`
|
||||
/// to indicate one past the absolute last key.
|
||||
/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH`
|
||||
/// items to select historical items or `NULL` to select
|
||||
/// current items.
|
||||
/// \return A pointer to an `AMresult` struct with an `AMitems` struct.
|
||||
/// \pre \p doc `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] end The key one past the last key in a subrange or `AMstr(NULL)` to
|
||||
/// indicate one past the absolute last key.
|
||||
/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical
|
||||
/// keys and values or `NULL` for current keys and values.
|
||||
/// \return A pointer to an `AMresult` struct containing an `AMmapItems`
|
||||
/// struct.
|
||||
/// \pre \p doc `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// doc must be a valid pointer to an AMdoc
|
||||
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||
/// begin.src must be a byte array of length >= begin.count or std::ptr::null()
|
||||
/// end.src must be a byte array of length >= end.count or std::ptr::null()
|
||||
/// heads must be a valid pointer to an AMitems or std::ptr::null()
|
||||
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapRange(
|
||||
doc: *const AMdoc,
|
||||
obj_id: *const AMobjId,
|
||||
begin: AMbyteSpan,
|
||||
end: AMbyteSpan,
|
||||
heads: *const AMitems,
|
||||
heads: *const AMchangeHashes,
|
||||
) -> *mut AMresult {
|
||||
let doc = to_doc!(doc);
|
||||
let obj_id = to_obj_id!(obj_id);
|
||||
let heads = match heads.as_ref() {
|
||||
None => None,
|
||||
Some(heads) => match <Vec<am::ChangeHash>>::try_from(heads) {
|
||||
Ok(heads) => Some(heads),
|
||||
Err(e) => {
|
||||
return AMresult::error(&e.to_string()).into();
|
||||
}
|
||||
},
|
||||
};
|
||||
match (begin.is_null(), end.is_null()) {
|
||||
(false, false) => {
|
||||
let (begin, end) = (to_str!(begin).to_string(), to_str!(end).to_string());
|
||||
if begin > end {
|
||||
return AMresult::error(&format!("Invalid range [{}-{})", begin, end)).into();
|
||||
return AMresult::err(&format!("Invalid range [{}-{})", begin, end)).into();
|
||||
};
|
||||
let bounds = begin..end;
|
||||
if let Some(heads) = heads {
|
||||
to_result(doc.map_range_at(obj_id, bounds, &heads))
|
||||
if let Some(heads) = heads.as_ref() {
|
||||
to_result(doc.map_range_at(obj_id, bounds, heads.as_ref()))
|
||||
} else {
|
||||
to_result(doc.map_range(obj_id, bounds))
|
||||
}
|
||||
}
|
||||
(false, true) => {
|
||||
let bounds = to_str!(begin).to_string()..;
|
||||
if let Some(heads) = heads {
|
||||
to_result(doc.map_range_at(obj_id, bounds, &heads))
|
||||
if let Some(heads) = heads.as_ref() {
|
||||
to_result(doc.map_range_at(obj_id, bounds, heads.as_ref()))
|
||||
} else {
|
||||
to_result(doc.map_range(obj_id, bounds))
|
||||
}
|
||||
}
|
||||
(true, false) => {
|
||||
let bounds = ..to_str!(end).to_string();
|
||||
if let Some(heads) = heads {
|
||||
to_result(doc.map_range_at(obj_id, bounds, &heads))
|
||||
if let Some(heads) = heads.as_ref() {
|
||||
to_result(doc.map_range_at(obj_id, bounds, heads.as_ref()))
|
||||
} else {
|
||||
to_result(doc.map_range(obj_id, bounds))
|
||||
}
|
||||
}
|
||||
(true, true) => {
|
||||
let bounds = ..;
|
||||
if let Some(heads) = heads {
|
||||
to_result(doc.map_range_at(obj_id, bounds, &heads))
|
||||
if let Some(heads) = heads.as_ref() {
|
||||
to_result(doc.map_range_at(obj_id, bounds, heads.as_ref()))
|
||||
} else {
|
||||
to_result(doc.map_range(obj_id, bounds))
|
||||
}
|
||||
|
|
98
rust/automerge-c/src/doc/map/item.rs
Normal file
98
rust/automerge-c/src/doc/map/item.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
use automerge as am;
|
||||
|
||||
use crate::byte_span::AMbyteSpan;
|
||||
use crate::obj::AMobjId;
|
||||
use crate::result::AMvalue;
|
||||
|
||||
/// \struct AMmapItem
|
||||
/// \installed_headerfile
|
||||
/// \brief An item in a map object.
|
||||
pub struct AMmapItem {
|
||||
/// The key of an item in a map object.
|
||||
key: String,
|
||||
/// The object identifier of an item in a map object.
|
||||
obj_id: AMobjId,
|
||||
/// The value of an item in a map object.
|
||||
value: am::Value<'static>,
|
||||
}
|
||||
|
||||
impl AMmapItem {
|
||||
pub fn new(key: &'static str, value: am::Value<'static>, obj_id: am::ObjId) -> Self {
|
||||
Self {
|
||||
key: key.to_string(),
|
||||
obj_id: AMobjId::new(obj_id),
|
||||
value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AMmapItem {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.key == other.key && self.obj_id == other.obj_id && self.value == other.value
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl From<&AMmapItem> for (String, am::Value<'static>, am::ObjId) {
|
||||
fn from(map_item: &AMmapItem) -> Self {
|
||||
(map_item.key.into_string().unwrap(), map_item.value.0.clone(), map_item.obj_id.as_ref().clone())
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/// \memberof AMmapItem
|
||||
/// \brief Gets the key of an item in a map object.
|
||||
///
|
||||
/// \param[in] map_item A pointer to an `AMmapItem` struct.
|
||||
/// \return An `AMbyteSpan` view of a UTF-8 string.
|
||||
/// \pre \p map_item `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// map_item must be a valid pointer to an AMmapItem
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapItemKey(map_item: *const AMmapItem) -> AMbyteSpan {
|
||||
if let Some(map_item) = map_item.as_ref() {
|
||||
map_item.key.as_bytes().into()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMmapItem
|
||||
/// \brief Gets the object identifier of an item in a map object.
|
||||
///
|
||||
/// \param[in] map_item A pointer to an `AMmapItem` struct.
|
||||
/// \return A pointer to an `AMobjId` struct.
|
||||
/// \pre \p map_item `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// map_item must be a valid pointer to an AMmapItem
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapItemObjId(map_item: *const AMmapItem) -> *const AMobjId {
|
||||
if let Some(map_item) = map_item.as_ref() {
|
||||
&map_item.obj_id
|
||||
} else {
|
||||
std::ptr::null()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMmapItem
|
||||
/// \brief Gets the value of an item in a map object.
|
||||
///
|
||||
/// \param[in] map_item A pointer to an `AMmapItem` struct.
|
||||
/// \return An `AMvalue` struct.
|
||||
/// \pre \p map_item `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// map_item must be a valid pointer to an AMmapItem
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapItemValue<'a>(map_item: *const AMmapItem) -> AMvalue<'a> {
|
||||
if let Some(map_item) = map_item.as_ref() {
|
||||
(&map_item.value).into()
|
||||
} else {
|
||||
AMvalue::Void
|
||||
}
|
||||
}
|
340
rust/automerge-c/src/doc/map/items.rs
Normal file
340
rust/automerge-c/src/doc/map/items.rs
Normal file
|
@ -0,0 +1,340 @@
|
|||
use std::ffi::c_void;
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::doc::map::item::AMmapItem;
|
||||
|
||||
#[repr(C)]
|
||||
struct Detail {
|
||||
len: usize,
|
||||
offset: isize,
|
||||
ptr: *const c_void,
|
||||
}
|
||||
|
||||
/// \note cbindgen won't propagate the value of a `std::mem::size_of<T>()` call
|
||||
/// (https://github.com/eqrion/cbindgen/issues/252) but it will
|
||||
/// propagate the name of a constant initialized from it so if the
|
||||
/// constant's name is a symbolic representation of the value it can be
|
||||
/// converted into a number by post-processing the header it generated.
|
||||
pub const USIZE_USIZE_USIZE_: usize = size_of::<Detail>();
|
||||
|
||||
impl Detail {
|
||||
fn new(map_items: &[AMmapItem], offset: isize) -> Self {
|
||||
Self {
|
||||
len: map_items.len(),
|
||||
offset,
|
||||
ptr: map_items.as_ptr() as *const c_void,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
let len = self.len as isize;
|
||||
self.offset = if self.offset < 0 {
|
||||
// It's reversed.
|
||||
let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN);
|
||||
if unclipped >= 0 {
|
||||
// Clip it to the forward stop.
|
||||
len
|
||||
} else {
|
||||
std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1)
|
||||
}
|
||||
} else {
|
||||
let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX);
|
||||
if unclipped < 0 {
|
||||
// Clip it to the reverse stop.
|
||||
-(len + 1)
|
||||
} else {
|
||||
std::cmp::max(0, std::cmp::min(unclipped, len))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_index(&self) -> usize {
|
||||
(self.offset
|
||||
+ if self.offset < 0 {
|
||||
self.len as isize
|
||||
} else {
|
||||
0
|
||||
}) as usize
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<&AMmapItem> {
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &[AMmapItem] =
|
||||
unsafe { std::slice::from_raw_parts(self.ptr as *const AMmapItem, self.len) };
|
||||
let value = &slice[self.get_index()];
|
||||
self.advance(n);
|
||||
Some(value)
|
||||
}
|
||||
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
let len = self.len as isize;
|
||||
self.offset < -len || self.offset == len
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<&AMmapItem> {
|
||||
self.advance(-n);
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &[AMmapItem] =
|
||||
unsafe { std::slice::from_raw_parts(self.ptr as *const AMmapItem, self.len) };
|
||||
Some(&slice[self.get_index()])
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: -(self.offset + 1),
|
||||
ptr: self.ptr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: if self.offset < 0 { -1 } else { 0 },
|
||||
ptr: self.ptr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Detail> for [u8; USIZE_USIZE_USIZE_] {
|
||||
fn from(detail: Detail) -> Self {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_)
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \struct AMmapItems
|
||||
/// \installed_headerfile
|
||||
/// \brief A random-access iterator over a sequence of map object items.
|
||||
#[repr(C)]
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub struct AMmapItems {
|
||||
/// An implementation detail that is intentionally opaque.
|
||||
/// \warning Modifying \p detail will cause undefined behavior.
|
||||
/// \note The actual size of \p detail will vary by platform, this is just
|
||||
/// the one for the platform this documentation was built on.
|
||||
detail: [u8; USIZE_USIZE_USIZE_],
|
||||
}
|
||||
|
||||
impl AMmapItems {
|
||||
pub fn new(map_items: &[AMmapItem]) -> Self {
|
||||
Self {
|
||||
detail: Detail::new(map_items, 0).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.advance(n);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
detail.len
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<&AMmapItem> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.next(n)
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<&AMmapItem> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.prev(n)
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.reversed().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.rewound().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[AMmapItem]> for AMmapItems {
|
||||
fn as_ref(&self) -> &[AMmapItem] {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
unsafe { std::slice::from_raw_parts(detail.ptr as *const AMmapItem, detail.len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AMmapItems {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
detail: [0; USIZE_USIZE_USIZE_],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMmapItems
|
||||
/// \brief Advances an iterator over a sequence of map object items by at most
|
||||
/// \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction.
|
||||
///
|
||||
/// \param[in,out] map_items A pointer to an `AMmapItems` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \pre \p map_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// map_items must be a valid pointer to an AMmapItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapItemsAdvance(map_items: *mut AMmapItems, n: isize) {
|
||||
if let Some(map_items) = map_items.as_mut() {
|
||||
map_items.advance(n);
|
||||
};
|
||||
}
|
||||
|
||||
/// \memberof AMmapItems
|
||||
/// \brief Tests the equality of two sequences of map object items underlying
|
||||
/// a pair of iterators.
|
||||
///
|
||||
/// \param[in] map_items1 A pointer to an `AMmapItems` struct.
|
||||
/// \param[in] map_items2 A pointer to an `AMmapItems` struct.
|
||||
/// \return `true` if \p map_items1 `==` \p map_items2 and `false` otherwise.
|
||||
/// \pre \p map_items1 `!= NULL`.
|
||||
/// \pre \p map_items2 `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// map_items1 must be a valid pointer to an AMmapItems
|
||||
/// map_items2 must be a valid pointer to an AMmapItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapItemsEqual(
|
||||
map_items1: *const AMmapItems,
|
||||
map_items2: *const AMmapItems,
|
||||
) -> bool {
|
||||
match (map_items1.as_ref(), map_items2.as_ref()) {
|
||||
(Some(map_items1), Some(map_items2)) => map_items1.as_ref() == map_items2.as_ref(),
|
||||
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMmapItems
|
||||
/// \brief Gets the map object item at the current position of an iterator
|
||||
/// over a sequence of map object items and then advances it by at most
|
||||
/// \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction.
|
||||
///
|
||||
/// \param[in,out] map_items A pointer to an `AMmapItems` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return A pointer to an `AMmapItem` struct that's `NULL` when \p map_items
|
||||
/// was previously advanced past its forward/reverse limit.
|
||||
/// \pre \p map_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// map_items must be a valid pointer to an AMmapItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapItemsNext(map_items: *mut AMmapItems, n: isize) -> *const AMmapItem {
|
||||
if let Some(map_items) = map_items.as_mut() {
|
||||
if let Some(map_item) = map_items.next(n) {
|
||||
return map_item;
|
||||
}
|
||||
}
|
||||
std::ptr::null()
|
||||
}
|
||||
|
||||
/// \memberof AMmapItems
|
||||
/// \brief Advances an iterator over a sequence of map object items by at most
|
||||
/// \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction and then gets the map object item at its new
|
||||
/// position.
|
||||
///
|
||||
/// \param[in,out] map_items A pointer to an `AMmapItems` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return A pointer to an `AMmapItem` struct that's `NULL` when \p map_items
|
||||
/// is presently advanced past its forward/reverse limit.
|
||||
/// \pre \p map_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// map_items must be a valid pointer to an AMmapItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapItemsPrev(map_items: *mut AMmapItems, n: isize) -> *const AMmapItem {
|
||||
if let Some(map_items) = map_items.as_mut() {
|
||||
if let Some(map_item) = map_items.prev(n) {
|
||||
return map_item;
|
||||
}
|
||||
}
|
||||
std::ptr::null()
|
||||
}
|
||||
|
||||
/// \memberof AMmapItems
|
||||
/// \brief Gets the size of the sequence of map object items underlying an
|
||||
/// iterator.
|
||||
///
|
||||
/// \param[in] map_items A pointer to an `AMmapItems` struct.
|
||||
/// \return The count of values in \p map_items.
|
||||
/// \pre \p map_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// map_items must be a valid pointer to an AMmapItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapItemsSize(map_items: *const AMmapItems) -> usize {
|
||||
if let Some(map_items) = map_items.as_ref() {
|
||||
map_items.len()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMmapItems
|
||||
/// \brief Creates an iterator over the same sequence of map object items as
|
||||
/// the given one but with the opposite position and direction.
|
||||
///
|
||||
/// \param[in] map_items A pointer to an `AMmapItems` struct.
|
||||
/// \return An `AMmapItems` struct
|
||||
/// \pre \p map_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// map_items must be a valid pointer to an AMmapItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapItemsReversed(map_items: *const AMmapItems) -> AMmapItems {
|
||||
if let Some(map_items) = map_items.as_ref() {
|
||||
map_items.reversed()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMmapItems
|
||||
/// \brief Creates an iterator at the starting position over the same sequence of map object items as the given one.
|
||||
///
|
||||
/// \param[in] map_items A pointer to an `AMmapItems` struct.
|
||||
/// \return An `AMmapItems` struct
|
||||
/// \pre \p map_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// map_items must be a valid pointer to an AMmapItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMmapItemsRewound(map_items: *const AMmapItems) -> AMmapItems {
|
||||
if let Some(map_items) = map_items.as_ref() {
|
||||
map_items.rewound()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
|
@ -1,20 +1,9 @@
|
|||
macro_rules! clamp {
|
||||
($index:expr, $len:expr, $param_name:expr) => {{
|
||||
if $index > $len && $index != usize::MAX {
|
||||
return AMresult::error(&format!("Invalid {} {}", $param_name, $index)).into();
|
||||
}
|
||||
std::cmp::min($index, $len)
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use clamp;
|
||||
|
||||
macro_rules! to_doc {
|
||||
($handle:expr) => {{
|
||||
let handle = $handle.as_ref();
|
||||
match handle {
|
||||
Some(b) => b,
|
||||
None => return AMresult::error("Invalid `AMdoc*`").into(),
|
||||
None => return AMresult::err("Invalid AMdoc pointer").into(),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
@ -26,21 +15,9 @@ macro_rules! to_doc_mut {
|
|||
let handle = $handle.as_mut();
|
||||
match handle {
|
||||
Some(b) => b,
|
||||
None => return AMresult::error("Invalid `AMdoc*`").into(),
|
||||
None => return AMresult::err("Invalid AMdoc pointer").into(),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use to_doc_mut;
|
||||
|
||||
macro_rules! to_items {
|
||||
($handle:expr) => {{
|
||||
let handle = $handle.as_ref();
|
||||
match handle {
|
||||
Some(b) => b,
|
||||
None => return AMresult::error("Invalid `AMitems*`").into(),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use to_items;
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
use automerge as am;
|
||||
|
||||
use std::any::type_name;
|
||||
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use crate::byte_span::AMbyteSpan;
|
||||
|
||||
/// \struct AMindex
|
||||
/// \installed_headerfile
|
||||
/// \brief An item index.
|
||||
#[derive(PartialEq)]
|
||||
pub enum AMindex {
|
||||
/// A UTF-8 string key variant.
|
||||
Key(SmolStr),
|
||||
/// A 64-bit unsigned integer position variant.
|
||||
Pos(usize),
|
||||
}
|
||||
|
||||
impl TryFrom<&AMindex> for AMbyteSpan {
|
||||
type Error = am::AutomergeError;
|
||||
|
||||
fn try_from(item: &AMindex) -> Result<Self, Self::Error> {
|
||||
use am::AutomergeError::InvalidValueType;
|
||||
use AMindex::*;
|
||||
|
||||
if let Key(key) = item {
|
||||
return Ok(key.into());
|
||||
}
|
||||
Err(InvalidValueType {
|
||||
expected: type_name::<SmolStr>().to_string(),
|
||||
unexpected: type_name::<usize>().to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&AMindex> for usize {
|
||||
type Error = am::AutomergeError;
|
||||
|
||||
fn try_from(item: &AMindex) -> Result<Self, Self::Error> {
|
||||
use am::AutomergeError::InvalidValueType;
|
||||
use AMindex::*;
|
||||
|
||||
if let Pos(pos) = item {
|
||||
return Ok(*pos);
|
||||
}
|
||||
Err(InvalidValueType {
|
||||
expected: type_name::<usize>().to_string(),
|
||||
unexpected: type_name::<SmolStr>().to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// \ingroup enumerations
|
||||
/// \enum AMidxType
|
||||
/// \installed_headerfile
|
||||
/// \brief The type of an item's index.
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum AMidxType {
|
||||
/// The default tag, not a type signifier.
|
||||
Default = 0,
|
||||
/// A UTF-8 string view key.
|
||||
Key,
|
||||
/// A 64-bit unsigned integer position.
|
||||
Pos,
|
||||
}
|
||||
|
||||
impl Default for AMidxType {
|
||||
fn default() -> Self {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AMindex> for AMidxType {
|
||||
fn from(index: &AMindex) -> Self {
|
||||
use AMindex::*;
|
||||
|
||||
match index {
|
||||
Key(_) => Self::Key,
|
||||
Pos(_) => Self::Pos,
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,401 +0,0 @@
|
|||
use automerge as am;
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::item::AMitem;
|
||||
use crate::result::AMresult;
|
||||
|
||||
#[repr(C)]
|
||||
struct Detail {
|
||||
len: usize,
|
||||
offset: isize,
|
||||
ptr: *const c_void,
|
||||
}
|
||||
|
||||
/// \note cbindgen won't propagate the value of a `std::mem::size_of<T>()` call
|
||||
/// (https://github.com/eqrion/cbindgen/issues/252) but it will
|
||||
/// propagate the name of a constant initialized from it so if the
|
||||
/// constant's name is a symbolic representation of the value it can be
|
||||
/// converted into a number by post-processing the header it generated.
|
||||
pub const USIZE_USIZE_USIZE_: usize = size_of::<Detail>();
|
||||
|
||||
impl Detail {
|
||||
fn new(items: &[AMitem], offset: isize) -> Self {
|
||||
Self {
|
||||
len: items.len(),
|
||||
offset,
|
||||
ptr: items.as_ptr() as *mut c_void,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
let len = self.len as isize;
|
||||
self.offset = if self.offset < 0 {
|
||||
// It's reversed.
|
||||
let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN);
|
||||
if unclipped >= 0 {
|
||||
// Clip it to the forward stop.
|
||||
len
|
||||
} else {
|
||||
std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1)
|
||||
}
|
||||
} else {
|
||||
let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX);
|
||||
if unclipped < 0 {
|
||||
// Clip it to the reverse stop.
|
||||
-(len + 1)
|
||||
} else {
|
||||
std::cmp::max(0, std::cmp::min(unclipped, len))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_index(&self) -> usize {
|
||||
(self.offset
|
||||
+ if self.offset < 0 {
|
||||
self.len as isize
|
||||
} else {
|
||||
0
|
||||
}) as usize
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<&mut AMitem> {
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &mut [AMitem] =
|
||||
unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut AMitem, self.len) };
|
||||
let value = &mut slice[self.get_index()];
|
||||
self.advance(n);
|
||||
Some(value)
|
||||
}
|
||||
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
let len = self.len as isize;
|
||||
self.offset < -len || self.offset == len
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<&mut AMitem> {
|
||||
self.advance(-n);
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &mut [AMitem] =
|
||||
unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut AMitem, self.len) };
|
||||
Some(&mut slice[self.get_index()])
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: -(self.offset + 1),
|
||||
ptr: self.ptr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: if self.offset < 0 { -1 } else { 0 },
|
||||
ptr: self.ptr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Detail> for [u8; USIZE_USIZE_USIZE_] {
|
||||
fn from(detail: Detail) -> Self {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_)
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \struct AMitems
|
||||
/// \installed_headerfile
|
||||
/// \brief A random-access iterator over a sequence of `AMitem` structs.
|
||||
#[repr(C)]
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub struct AMitems<'a> {
|
||||
/// An implementation detail that is intentionally opaque.
|
||||
/// \warning Modifying \p detail will cause undefined behavior.
|
||||
/// \note The actual size of \p detail will vary by platform, this is just
|
||||
/// the one for the platform this documentation was built on.
|
||||
detail: [u8; USIZE_USIZE_USIZE_],
|
||||
phantom: PhantomData<&'a mut AMresult>,
|
||||
}
|
||||
|
||||
impl<'a> AMitems<'a> {
|
||||
pub fn new(items: &[AMitem]) -> Self {
|
||||
Self {
|
||||
detail: Detail::new(items, 0).into(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.advance(n);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
detail.len
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<&mut AMitem> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.next(n)
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<&mut AMitem> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.prev(n)
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.reversed().into(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.rewound().into(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsRef<[AMitem]> for AMitems<'a> {
|
||||
fn as_ref(&self) -> &[AMitem] {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
unsafe { std::slice::from_raw_parts(detail.ptr as *const AMitem, detail.len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for AMitems<'a> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
detail: [0; USIZE_USIZE_USIZE_],
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&AMitems<'_>> for Vec<am::Change> {
|
||||
type Error = am::AutomergeError;
|
||||
|
||||
fn try_from(items: &AMitems<'_>) -> Result<Self, Self::Error> {
|
||||
let mut changes = Vec::<am::Change>::with_capacity(items.len());
|
||||
for item in items.as_ref().iter() {
|
||||
match <&am::Change>::try_from(item.as_ref()) {
|
||||
Ok(change) => {
|
||||
changes.push(change.clone());
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(changes)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&AMitems<'_>> for Vec<am::ChangeHash> {
|
||||
type Error = am::AutomergeError;
|
||||
|
||||
fn try_from(items: &AMitems<'_>) -> Result<Self, Self::Error> {
|
||||
let mut change_hashes = Vec::<am::ChangeHash>::with_capacity(items.len());
|
||||
for item in items.as_ref().iter() {
|
||||
match <&am::ChangeHash>::try_from(item.as_ref()) {
|
||||
Ok(change_hash) => {
|
||||
change_hashes.push(*change_hash);
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(change_hashes)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&AMitems<'_>> for Vec<am::ScalarValue> {
|
||||
type Error = am::AutomergeError;
|
||||
|
||||
fn try_from(items: &AMitems<'_>) -> Result<Self, Self::Error> {
|
||||
let mut scalars = Vec::<am::ScalarValue>::with_capacity(items.len());
|
||||
for item in items.as_ref().iter() {
|
||||
match <&am::ScalarValue>::try_from(item.as_ref()) {
|
||||
Ok(scalar) => {
|
||||
scalars.push(scalar.clone());
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(scalars)
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMitems
|
||||
/// \brief Advances an iterator over a sequence of object items by at most
|
||||
/// \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction.
|
||||
///
|
||||
/// \param[in] items A pointer to an `AMitems` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \pre \p items `!= NULL`
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// items must be a valid pointer to an AMitems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMitemsAdvance(items: *mut AMitems, n: isize) {
|
||||
if let Some(items) = items.as_mut() {
|
||||
items.advance(n);
|
||||
};
|
||||
}
|
||||
|
||||
/// \memberof AMitems
|
||||
/// \brief Tests the equality of two sequences of object items underlying a
|
||||
/// pair of iterators.
|
||||
///
|
||||
/// \param[in] items1 A pointer to an `AMitems` struct.
|
||||
/// \param[in] items2 A pointer to an `AMitems` struct.
|
||||
/// \return `true` if \p items1 `==` \p items2 and `false` otherwise.
|
||||
/// \pre \p items1 `!= NULL`
|
||||
/// \pre \p items1 `!= NULL`
|
||||
/// \post `!(`\p items1 `&&` \p items2 `) -> false`
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// items1 must be a valid pointer to an AMitems
|
||||
/// items2 must be a valid pointer to an AMitems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMitemsEqual(items1: *const AMitems, items2: *const AMitems) -> bool {
|
||||
match (items1.as_ref(), items2.as_ref()) {
|
||||
(Some(items1), Some(items2)) => items1.as_ref() == items2.as_ref(),
|
||||
(None, None) | (None, Some(_)) | (Some(_), None) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMitems
|
||||
/// \brief Gets the object item at the current position of an iterator over a
|
||||
/// sequence of object items and then advances it by at most \p |n|
|
||||
/// positions where the sign of \p n is relative to the iterator's
|
||||
/// direction.
|
||||
///
|
||||
/// \param[in] items A pointer to an `AMitems` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return A pointer to an `AMitem` struct that's `NULL` when \p items
|
||||
/// was previously advanced past its forward/reverse limit.
|
||||
/// \pre \p items `!= NULL`
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// items must be a valid pointer to an AMitems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMitemsNext(items: *mut AMitems, n: isize) -> *mut AMitem {
|
||||
if let Some(items) = items.as_mut() {
|
||||
if let Some(item) = items.next(n) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
/// \memberof AMitems
|
||||
/// \brief Advances an iterator over a sequence of object items by at most
|
||||
/// \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction and then gets the object item at its new
|
||||
/// position.
|
||||
///
|
||||
/// \param[in] items A pointer to an `AMitems` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return A pointer to an `AMitem` struct that's `NULL` when \p items
|
||||
/// is presently advanced past its forward/reverse limit.
|
||||
/// \pre \p items `!= NULL`
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// items must be a valid pointer to an AMitems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMitemsPrev(items: *mut AMitems, n: isize) -> *mut AMitem {
|
||||
if let Some(items) = items.as_mut() {
|
||||
if let Some(obj_item) = items.prev(n) {
|
||||
return obj_item;
|
||||
}
|
||||
}
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
/// \memberof AMitems
|
||||
/// \brief Gets the size of the sequence underlying an iterator.
|
||||
///
|
||||
/// \param[in] items A pointer to an `AMitems` struct.
|
||||
/// \return The count of items in \p items.
|
||||
/// \pre \p items `!= NULL`
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// items must be a valid pointer to an AMitems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMitemsSize(items: *const AMitems) -> usize {
|
||||
if let Some(items) = items.as_ref() {
|
||||
return items.len();
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
/// \memberof AMitems
|
||||
/// \brief Creates an iterator over the same sequence of items as the
|
||||
/// given one but with the opposite position and direction.
|
||||
///
|
||||
/// \param[in] items A pointer to an `AMitems` struct.
|
||||
/// \return An `AMitems` struct
|
||||
/// \pre \p items `!= NULL`
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// items must be a valid pointer to an AMitems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMitemsReversed(items: *const AMitems) -> AMitems {
|
||||
if let Some(items) = items.as_ref() {
|
||||
return items.reversed();
|
||||
}
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// \memberof AMitems
|
||||
/// \brief Creates an iterator at the starting position over the same sequence
|
||||
/// of items as the given one.
|
||||
///
|
||||
/// \param[in] items A pointer to an `AMitems` struct.
|
||||
/// \return An `AMitems` struct
|
||||
/// \pre \p items `!= NULL`
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// items must be a valid pointer to an AMitems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMitemsRewound(items: *const AMitems) -> AMitems {
|
||||
if let Some(items) = items.as_ref() {
|
||||
return items.rewound();
|
||||
}
|
||||
Default::default()
|
||||
}
|
|
@ -1,12 +1,11 @@
|
|||
mod actor_id;
|
||||
mod byte_span;
|
||||
mod change;
|
||||
mod change_hashes;
|
||||
mod changes;
|
||||
mod doc;
|
||||
mod index;
|
||||
mod item;
|
||||
mod items;
|
||||
mod obj;
|
||||
mod result;
|
||||
mod result_stack;
|
||||
mod strs;
|
||||
mod sync;
|
||||
|
||||
// include!(concat!(env!("OUT_DIR"), "/enum_string_functions.rs"));
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
use automerge as am;
|
||||
use std::any::type_name;
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
|
||||
use crate::actor_id::AMactorId;
|
||||
|
||||
pub mod item;
|
||||
pub mod items;
|
||||
|
||||
macro_rules! to_obj_id {
|
||||
($handle:expr) => {{
|
||||
match $handle.as_ref() {
|
||||
|
@ -17,11 +19,12 @@ macro_rules! to_obj_id {
|
|||
pub(crate) use to_obj_id;
|
||||
|
||||
macro_rules! to_obj_type {
|
||||
($c_obj_type:expr) => {{
|
||||
let result: Result<am::ObjType, am::AutomergeError> = (&$c_obj_type).try_into();
|
||||
match result {
|
||||
Ok(obj_type) => obj_type,
|
||||
Err(e) => return AMresult::error(&e.to_string()).into(),
|
||||
($am_obj_type:expr) => {{
|
||||
match $am_obj_type {
|
||||
AMobjType::Map => am::ObjType::Map,
|
||||
AMobjType::List => am::ObjType::List,
|
||||
AMobjType::Text => am::ObjType::Text,
|
||||
AMobjType::Void => return AMresult::err("Invalid AMobjType value").into(),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
@ -76,11 +79,11 @@ impl Deref for AMobjId {
|
|||
}
|
||||
|
||||
/// \memberof AMobjId
|
||||
/// \brief Gets the actor identifier component of an object identifier.
|
||||
/// \brief Gets the actor identifier of an object identifier.
|
||||
///
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct.
|
||||
/// \return A pointer to an `AMactorId` struct or `NULL`.
|
||||
/// \pre \p obj_id `!= NULL`
|
||||
/// \pre \p obj_id `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -94,11 +97,11 @@ pub unsafe extern "C" fn AMobjIdActorId(obj_id: *const AMobjId) -> *const AMacto
|
|||
}
|
||||
|
||||
/// \memberof AMobjId
|
||||
/// \brief Gets the counter component of an object identifier.
|
||||
/// \brief Gets the counter of an object identifier.
|
||||
///
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct.
|
||||
/// \return A 64-bit unsigned integer.
|
||||
/// \pre \p obj_id `!= NULL`
|
||||
/// \pre \p obj_id `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -121,9 +124,8 @@ pub unsafe extern "C" fn AMobjIdCounter(obj_id: *const AMobjId) -> u64 {
|
|||
/// \param[in] obj_id1 A pointer to an `AMobjId` struct.
|
||||
/// \param[in] obj_id2 A pointer to an `AMobjId` struct.
|
||||
/// \return `true` if \p obj_id1 `==` \p obj_id2 and `false` otherwise.
|
||||
/// \pre \p obj_id1 `!= NULL`
|
||||
/// \pre \p obj_id1 `!= NULL`
|
||||
/// \post `!(`\p obj_id1 `&&` \p obj_id2 `) -> false`
|
||||
/// \pre \p obj_id1 `!= NULL`.
|
||||
/// \pre \p obj_id2 `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
|
@ -133,28 +135,26 @@ pub unsafe extern "C" fn AMobjIdCounter(obj_id: *const AMobjId) -> u64 {
|
|||
pub unsafe extern "C" fn AMobjIdEqual(obj_id1: *const AMobjId, obj_id2: *const AMobjId) -> bool {
|
||||
match (obj_id1.as_ref(), obj_id2.as_ref()) {
|
||||
(Some(obj_id1), Some(obj_id2)) => obj_id1 == obj_id2,
|
||||
(None, None) | (None, Some(_)) | (Some(_), None) => false,
|
||||
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMobjId
|
||||
/// \brief Gets the index component of an object identifier.
|
||||
/// \brief Gets the index of an object identifier.
|
||||
///
|
||||
/// \param[in] obj_id A pointer to an `AMobjId` struct.
|
||||
/// \return A 64-bit unsigned integer.
|
||||
/// \pre \p obj_id `!= NULL`
|
||||
/// \pre \p obj_id `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// obj_id must be a valid pointer to an AMobjId
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMobjIdIndex(obj_id: *const AMobjId) -> usize {
|
||||
use am::ObjId::*;
|
||||
|
||||
if let Some(obj_id) = obj_id.as_ref() {
|
||||
match obj_id.as_ref() {
|
||||
Id(_, _, index) => *index,
|
||||
Root => 0,
|
||||
am::ObjId::Id(_, _, index) => *index,
|
||||
am::ObjId::Root => 0,
|
||||
}
|
||||
} else {
|
||||
usize::MAX
|
||||
|
@ -163,54 +163,26 @@ pub unsafe extern "C" fn AMobjIdIndex(obj_id: *const AMobjId) -> usize {
|
|||
|
||||
/// \ingroup enumerations
|
||||
/// \enum AMobjType
|
||||
/// \installed_headerfile
|
||||
/// \brief The type of an object value.
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum AMobjType {
|
||||
/// The default tag, not a type signifier.
|
||||
Default = 0,
|
||||
/// A void.
|
||||
/// \note This tag is unalphabetized to evaluate as false.
|
||||
Void = 0,
|
||||
/// A list.
|
||||
List = 1,
|
||||
List,
|
||||
/// A key-value map.
|
||||
Map,
|
||||
/// A list of Unicode graphemes.
|
||||
Text,
|
||||
}
|
||||
|
||||
impl Default for AMobjType {
|
||||
fn default() -> Self {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&am::ObjType> for AMobjType {
|
||||
fn from(o: &am::ObjType) -> Self {
|
||||
use am::ObjType::*;
|
||||
|
||||
impl From<am::ObjType> for AMobjType {
|
||||
fn from(o: am::ObjType) -> Self {
|
||||
match o {
|
||||
List => Self::List,
|
||||
Map | Table => Self::Map,
|
||||
Text => Self::Text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&AMobjType> for am::ObjType {
|
||||
type Error = am::AutomergeError;
|
||||
|
||||
fn try_from(c_obj_type: &AMobjType) -> Result<Self, Self::Error> {
|
||||
use am::AutomergeError::InvalidValueType;
|
||||
use AMobjType::*;
|
||||
|
||||
match c_obj_type {
|
||||
List => Ok(Self::List),
|
||||
Map => Ok(Self::Map),
|
||||
Text => Ok(Self::Text),
|
||||
_ => Err(InvalidValueType {
|
||||
expected: type_name::<Self>().to_string(),
|
||||
unexpected: type_name::<AMobjType>().to_string(),
|
||||
}),
|
||||
am::ObjType::Map | am::ObjType::Table => AMobjType::Map,
|
||||
am::ObjType::List => AMobjType::List,
|
||||
am::ObjType::Text => AMobjType::Text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
73
rust/automerge-c/src/obj/item.rs
Normal file
73
rust/automerge-c/src/obj/item.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use automerge as am;
|
||||
|
||||
use crate::obj::AMobjId;
|
||||
use crate::result::AMvalue;
|
||||
|
||||
/// \struct AMobjItem
|
||||
/// \installed_headerfile
|
||||
/// \brief An item in an object.
|
||||
pub struct AMobjItem {
|
||||
/// The object identifier of an item in an object.
|
||||
obj_id: AMobjId,
|
||||
/// The value of an item in an object.
|
||||
value: am::Value<'static>,
|
||||
}
|
||||
|
||||
impl AMobjItem {
|
||||
pub fn new(value: am::Value<'static>, obj_id: am::ObjId) -> Self {
|
||||
Self {
|
||||
obj_id: AMobjId::new(obj_id),
|
||||
value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AMobjItem {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.obj_id == other.obj_id && self.value == other.value
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AMobjItem> for (am::Value<'static>, am::ObjId) {
|
||||
fn from(obj_item: &AMobjItem) -> Self {
|
||||
(obj_item.value.clone(), obj_item.obj_id.as_ref().clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMobjItem
|
||||
/// \brief Gets the object identifier of an item in an object.
|
||||
///
|
||||
/// \param[in] obj_item A pointer to an `AMobjItem` struct.
|
||||
/// \return A pointer to an `AMobjId` struct.
|
||||
/// \pre \p obj_item `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// obj_item must be a valid pointer to an AMobjItem
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMobjItemObjId(obj_item: *const AMobjItem) -> *const AMobjId {
|
||||
if let Some(obj_item) = obj_item.as_ref() {
|
||||
&obj_item.obj_id
|
||||
} else {
|
||||
std::ptr::null()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMobjItem
|
||||
/// \brief Gets the value of an item in an object.
|
||||
///
|
||||
/// \param[in] obj_item A pointer to an `AMobjItem` struct.
|
||||
/// \return An `AMvalue` struct.
|
||||
/// \pre \p obj_item `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// obj_item must be a valid pointer to an AMobjItem
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMobjItemValue<'a>(obj_item: *const AMobjItem) -> AMvalue<'a> {
|
||||
if let Some(obj_item) = obj_item.as_ref() {
|
||||
(&obj_item.value).into()
|
||||
} else {
|
||||
AMvalue::Void
|
||||
}
|
||||
}
|
341
rust/automerge-c/src/obj/items.rs
Normal file
341
rust/automerge-c/src/obj/items.rs
Normal file
|
@ -0,0 +1,341 @@
|
|||
use std::ffi::c_void;
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::obj::item::AMobjItem;
|
||||
|
||||
#[repr(C)]
|
||||
struct Detail {
|
||||
len: usize,
|
||||
offset: isize,
|
||||
ptr: *const c_void,
|
||||
}
|
||||
|
||||
/// \note cbindgen won't propagate the value of a `std::mem::size_of<T>()` call
|
||||
/// (https://github.com/eqrion/cbindgen/issues/252) but it will
|
||||
/// propagate the name of a constant initialized from it so if the
|
||||
/// constant's name is a symbolic representation of the value it can be
|
||||
/// converted into a number by post-processing the header it generated.
|
||||
pub const USIZE_USIZE_USIZE_: usize = size_of::<Detail>();
|
||||
|
||||
impl Detail {
|
||||
fn new(obj_items: &[AMobjItem], offset: isize) -> Self {
|
||||
Self {
|
||||
len: obj_items.len(),
|
||||
offset,
|
||||
ptr: obj_items.as_ptr() as *const c_void,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
let len = self.len as isize;
|
||||
self.offset = if self.offset < 0 {
|
||||
// It's reversed.
|
||||
let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN);
|
||||
if unclipped >= 0 {
|
||||
// Clip it to the forward stop.
|
||||
len
|
||||
} else {
|
||||
std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1)
|
||||
}
|
||||
} else {
|
||||
let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX);
|
||||
if unclipped < 0 {
|
||||
// Clip it to the reverse stop.
|
||||
-(len + 1)
|
||||
} else {
|
||||
std::cmp::max(0, std::cmp::min(unclipped, len))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_index(&self) -> usize {
|
||||
(self.offset
|
||||
+ if self.offset < 0 {
|
||||
self.len as isize
|
||||
} else {
|
||||
0
|
||||
}) as usize
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<&AMobjItem> {
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &[AMobjItem] =
|
||||
unsafe { std::slice::from_raw_parts(self.ptr as *const AMobjItem, self.len) };
|
||||
let value = &slice[self.get_index()];
|
||||
self.advance(n);
|
||||
Some(value)
|
||||
}
|
||||
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
let len = self.len as isize;
|
||||
self.offset < -len || self.offset == len
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<&AMobjItem> {
|
||||
self.advance(-n);
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &[AMobjItem] =
|
||||
unsafe { std::slice::from_raw_parts(self.ptr as *const AMobjItem, self.len) };
|
||||
Some(&slice[self.get_index()])
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: -(self.offset + 1),
|
||||
ptr: self.ptr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: if self.offset < 0 { -1 } else { 0 },
|
||||
ptr: self.ptr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Detail> for [u8; USIZE_USIZE_USIZE_] {
|
||||
fn from(detail: Detail) -> Self {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_)
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \struct AMobjItems
|
||||
/// \installed_headerfile
|
||||
/// \brief A random-access iterator over a sequence of object items.
|
||||
#[repr(C)]
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub struct AMobjItems {
|
||||
/// An implementation detail that is intentionally opaque.
|
||||
/// \warning Modifying \p detail will cause undefined behavior.
|
||||
/// \note The actual size of \p detail will vary by platform, this is just
|
||||
/// the one for the platform this documentation was built on.
|
||||
detail: [u8; USIZE_USIZE_USIZE_],
|
||||
}
|
||||
|
||||
impl AMobjItems {
|
||||
pub fn new(obj_items: &[AMobjItem]) -> Self {
|
||||
Self {
|
||||
detail: Detail::new(obj_items, 0).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.advance(n);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
detail.len
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<&AMobjItem> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.next(n)
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<&AMobjItem> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.prev(n)
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.reversed().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.rewound().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[AMobjItem]> for AMobjItems {
|
||||
fn as_ref(&self) -> &[AMobjItem] {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
unsafe { std::slice::from_raw_parts(detail.ptr as *const AMobjItem, detail.len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AMobjItems {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
detail: [0; USIZE_USIZE_USIZE_],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMobjItems
|
||||
/// \brief Advances an iterator over a sequence of object items by at most
|
||||
/// \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction.
|
||||
///
|
||||
/// \param[in,out] obj_items A pointer to an `AMobjItems` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \pre \p obj_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// obj_items must be a valid pointer to an AMobjItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMobjItemsAdvance(obj_items: *mut AMobjItems, n: isize) {
|
||||
if let Some(obj_items) = obj_items.as_mut() {
|
||||
obj_items.advance(n);
|
||||
};
|
||||
}
|
||||
|
||||
/// \memberof AMobjItems
|
||||
/// \brief Tests the equality of two sequences of object items underlying a
|
||||
/// pair of iterators.
|
||||
///
|
||||
/// \param[in] obj_items1 A pointer to an `AMobjItems` struct.
|
||||
/// \param[in] obj_items2 A pointer to an `AMobjItems` struct.
|
||||
/// \return `true` if \p obj_items1 `==` \p obj_items2 and `false` otherwise.
|
||||
/// \pre \p obj_items1 `!= NULL`.
|
||||
/// \pre \p obj_items2 `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// obj_items1 must be a valid pointer to an AMobjItems
|
||||
/// obj_items2 must be a valid pointer to an AMobjItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMobjItemsEqual(
|
||||
obj_items1: *const AMobjItems,
|
||||
obj_items2: *const AMobjItems,
|
||||
) -> bool {
|
||||
match (obj_items1.as_ref(), obj_items2.as_ref()) {
|
||||
(Some(obj_items1), Some(obj_items2)) => obj_items1.as_ref() == obj_items2.as_ref(),
|
||||
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMobjItems
|
||||
/// \brief Gets the object item at the current position of an iterator over a
|
||||
/// sequence of object items and then advances it by at most \p |n|
|
||||
/// positions where the sign of \p n is relative to the iterator's
|
||||
/// direction.
|
||||
///
|
||||
/// \param[in,out] obj_items A pointer to an `AMobjItems` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return A pointer to an `AMobjItem` struct that's `NULL` when \p obj_items
|
||||
/// was previously advanced past its forward/reverse limit.
|
||||
/// \pre \p obj_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// obj_items must be a valid pointer to an AMobjItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMobjItemsNext(obj_items: *mut AMobjItems, n: isize) -> *const AMobjItem {
|
||||
if let Some(obj_items) = obj_items.as_mut() {
|
||||
if let Some(obj_item) = obj_items.next(n) {
|
||||
return obj_item;
|
||||
}
|
||||
}
|
||||
std::ptr::null()
|
||||
}
|
||||
|
||||
/// \memberof AMobjItems
|
||||
/// \brief Advances an iterator over a sequence of object items by at most
|
||||
/// \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction and then gets the object item at its new
|
||||
/// position.
|
||||
///
|
||||
/// \param[in,out] obj_items A pointer to an `AMobjItems` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return A pointer to an `AMobjItem` struct that's `NULL` when \p obj_items
|
||||
/// is presently advanced past its forward/reverse limit.
|
||||
/// \pre \p obj_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// obj_items must be a valid pointer to an AMobjItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMobjItemsPrev(obj_items: *mut AMobjItems, n: isize) -> *const AMobjItem {
|
||||
if let Some(obj_items) = obj_items.as_mut() {
|
||||
if let Some(obj_item) = obj_items.prev(n) {
|
||||
return obj_item;
|
||||
}
|
||||
}
|
||||
std::ptr::null()
|
||||
}
|
||||
|
||||
/// \memberof AMobjItems
|
||||
/// \brief Gets the size of the sequence of object items underlying an
|
||||
/// iterator.
|
||||
///
|
||||
/// \param[in] obj_items A pointer to an `AMobjItems` struct.
|
||||
/// \return The count of values in \p obj_items.
|
||||
/// \pre \p obj_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// obj_items must be a valid pointer to an AMobjItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMobjItemsSize(obj_items: *const AMobjItems) -> usize {
|
||||
if let Some(obj_items) = obj_items.as_ref() {
|
||||
obj_items.len()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMobjItems
|
||||
/// \brief Creates an iterator over the same sequence of object items as the
|
||||
/// given one but with the opposite position and direction.
|
||||
///
|
||||
/// \param[in] obj_items A pointer to an `AMobjItems` struct.
|
||||
/// \return An `AMobjItems` struct
|
||||
/// \pre \p obj_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// obj_items must be a valid pointer to an AMobjItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMobjItemsReversed(obj_items: *const AMobjItems) -> AMobjItems {
|
||||
if let Some(obj_items) = obj_items.as_ref() {
|
||||
obj_items.reversed()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMobjItems
|
||||
/// \brief Creates an iterator at the starting position over the same sequence
|
||||
/// of object items as the given one.
|
||||
///
|
||||
/// \param[in] obj_items A pointer to an `AMobjItems` struct.
|
||||
/// \return An `AMobjItems` struct
|
||||
/// \pre \p obj_items `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// obj_items must be a valid pointer to an AMobjItems
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMobjItemsRewound(obj_items: *const AMobjItems) -> AMobjItems {
|
||||
if let Some(obj_items) = obj_items.as_ref() {
|
||||
obj_items.rewound()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
156
rust/automerge-c/src/result_stack.rs
Normal file
156
rust/automerge-c/src/result_stack.rs
Normal file
|
@ -0,0 +1,156 @@
|
|||
use crate::result::{AMfree, AMresult, AMresultStatus, AMresultValue, AMstatus, AMvalue};
|
||||
|
||||
/// \struct AMresultStack
|
||||
/// \installed_headerfile
|
||||
/// \brief A node in a singly-linked list of result pointers.
|
||||
///
|
||||
/// \note Using this data structure is purely optional because its only purpose
|
||||
/// is to make memory management tolerable for direct usage of this API
|
||||
/// in C, C++ and Objective-C.
|
||||
#[repr(C)]
|
||||
pub struct AMresultStack {
|
||||
/// A result to be deallocated.
|
||||
pub result: *mut AMresult,
|
||||
/// The next node in the singly-linked list or `NULL`.
|
||||
pub next: *mut AMresultStack,
|
||||
}
|
||||
|
||||
impl AMresultStack {
|
||||
pub fn new(result: *mut AMresult, next: *mut AMresultStack) -> Self {
|
||||
Self { result, next }
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMresultStack
|
||||
/// \brief Deallocates the storage for a stack of results.
|
||||
///
|
||||
/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct.
|
||||
/// \return The number of `AMresult` structs freed.
|
||||
/// \pre \p stack `!= NULL`.
|
||||
/// \post `*stack == NULL`.
|
||||
/// \note Calling this function is purely optional because its only purpose is
|
||||
/// to make memory management tolerable for direct usage of this API in
|
||||
/// C, C++ and Objective-C.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// stack must be a valid AMresultStack pointer pointer
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMfreeStack(stack: *mut *mut AMresultStack) -> usize {
|
||||
if stack.is_null() {
|
||||
return 0;
|
||||
}
|
||||
let mut count: usize = 0;
|
||||
while !(*stack).is_null() {
|
||||
AMfree(AMpop(stack));
|
||||
count += 1;
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
/// \memberof AMresultStack
|
||||
/// \brief Gets the topmost result from the stack after removing it.
|
||||
///
|
||||
/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct.
|
||||
/// \return A pointer to an `AMresult` struct or `NULL`.
|
||||
/// \pre \p stack `!= NULL`.
|
||||
/// \post `*stack == NULL`.
|
||||
/// \note Calling this function is purely optional because its only purpose is
|
||||
/// to make memory management tolerable for direct usage of this API in
|
||||
/// C, C++ and Objective-C.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// stack must be a valid AMresultStack pointer pointer
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMpop(stack: *mut *mut AMresultStack) -> *mut AMresult {
|
||||
if stack.is_null() || (*stack).is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
let top = Box::from_raw(*stack);
|
||||
*stack = top.next;
|
||||
let result = top.result;
|
||||
drop(top);
|
||||
result
|
||||
}
|
||||
|
||||
/// \memberof AMresultStack
|
||||
/// \brief The prototype of a function to be called when a value matching the
|
||||
/// given discriminant cannot be extracted from the result at the top of
|
||||
/// the given stack.
|
||||
///
|
||||
/// \note Implementing this function is purely optional because its only purpose
|
||||
/// is to make memory management tolerable for direct usage of this API
|
||||
/// in C, C++ and Objective-C.
|
||||
pub type AMpushCallback =
|
||||
Option<extern "C" fn(stack: *mut *mut AMresultStack, discriminant: u8) -> ()>;
|
||||
|
||||
/// \memberof AMresultStack
|
||||
/// \brief Pushes the given result onto the given stack and then either extracts
|
||||
/// a value matching the given discriminant from that result or,
|
||||
/// failing that, calls the given function and gets a void value instead.
|
||||
///
|
||||
/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct.
|
||||
/// \param[in] result A pointer to an `AMresult` struct.
|
||||
/// \param[in] discriminant An `AMvalue` variant's corresponding enum tag.
|
||||
/// \param[in] callback A pointer to a function with the same signature as
|
||||
/// `AMpushCallback()` or `NULL`.
|
||||
/// \return An `AMvalue` struct.
|
||||
/// \pre \p stack `!= NULL`.
|
||||
/// \pre \p result `!= NULL`.
|
||||
/// \warning If \p stack `== NULL` then \p result is deallocated in order to
|
||||
/// prevent a memory leak.
|
||||
/// \note Calling this function is purely optional because its only purpose is
|
||||
/// to make memory management tolerable for direct usage of this API in
|
||||
/// C, C++ and Objective-C.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// stack must be a valid AMresultStack pointer pointer
|
||||
/// result must be a valid AMresult pointer
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMpush<'a>(
|
||||
stack: *mut *mut AMresultStack,
|
||||
result: *mut AMresult,
|
||||
discriminant: u8,
|
||||
callback: AMpushCallback,
|
||||
) -> AMvalue<'a> {
|
||||
if stack.is_null() {
|
||||
// There's no stack to push the result onto so it has to be freed in
|
||||
// order to prevent a memory leak.
|
||||
AMfree(result);
|
||||
if let Some(callback) = callback {
|
||||
callback(stack, discriminant);
|
||||
}
|
||||
return AMvalue::Void;
|
||||
} else if result.is_null() {
|
||||
if let Some(callback) = callback {
|
||||
callback(stack, discriminant);
|
||||
}
|
||||
return AMvalue::Void;
|
||||
}
|
||||
// Always push the result onto the stack, even if it's wrong, so that the
|
||||
// given callback can retrieve it.
|
||||
let node = Box::new(AMresultStack::new(result, *stack));
|
||||
let top = Box::into_raw(node);
|
||||
*stack = top;
|
||||
// Test that the result contains a value.
|
||||
match AMresultStatus(result) {
|
||||
AMstatus::Ok => {}
|
||||
_ => {
|
||||
if let Some(callback) = callback {
|
||||
callback(stack, discriminant);
|
||||
}
|
||||
return AMvalue::Void;
|
||||
}
|
||||
}
|
||||
// Test that the result's value matches the given discriminant.
|
||||
let value = AMresultValue(result);
|
||||
if discriminant != u8::from(&value) {
|
||||
if let Some(callback) = callback {
|
||||
callback(stack, discriminant);
|
||||
}
|
||||
return AMvalue::Void;
|
||||
}
|
||||
value
|
||||
}
|
359
rust/automerge-c/src/strs.rs
Normal file
359
rust/automerge-c/src/strs.rs
Normal file
|
@ -0,0 +1,359 @@
|
|||
use std::cmp::Ordering;
|
||||
use std::ffi::c_void;
|
||||
use std::mem::size_of;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
use crate::byte_span::AMbyteSpan;
|
||||
|
||||
/// \brief Creates a string view from a C string.
|
||||
///
|
||||
/// \param[in] c_str A UTF-8 C string.
|
||||
/// \return A UTF-8 string view as an `AMbyteSpan` struct.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// c_str must be a null-terminated array of `c_char`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMstr(c_str: *const c_char) -> AMbyteSpan {
|
||||
c_str.into()
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct Detail {
|
||||
len: usize,
|
||||
offset: isize,
|
||||
ptr: *const c_void,
|
||||
}
|
||||
|
||||
/// \note cbindgen won't propagate the value of a `std::mem::size_of<T>()` call
|
||||
/// (https://github.com/eqrion/cbindgen/issues/252) but it will
|
||||
/// propagate the name of a constant initialized from it so if the
|
||||
/// constant's name is a symbolic representation of the value it can be
|
||||
/// converted into a number by post-processing the header it generated.
|
||||
pub const USIZE_USIZE_USIZE_: usize = size_of::<Detail>();
|
||||
|
||||
impl Detail {
|
||||
fn new(strings: &[String], offset: isize) -> Self {
|
||||
Self {
|
||||
len: strings.len(),
|
||||
offset,
|
||||
ptr: strings.as_ptr() as *const c_void,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
let len = self.len as isize;
|
||||
self.offset = if self.offset < 0 {
|
||||
// It's reversed.
|
||||
let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN);
|
||||
if unclipped >= 0 {
|
||||
// Clip it to the forward stop.
|
||||
len
|
||||
} else {
|
||||
std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1)
|
||||
}
|
||||
} else {
|
||||
let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX);
|
||||
if unclipped < 0 {
|
||||
// Clip it to the reverse stop.
|
||||
-(len + 1)
|
||||
} else {
|
||||
std::cmp::max(0, std::cmp::min(unclipped, len))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_index(&self) -> usize {
|
||||
(self.offset
|
||||
+ if self.offset < 0 {
|
||||
self.len as isize
|
||||
} else {
|
||||
0
|
||||
}) as usize
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<AMbyteSpan> {
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &[String] =
|
||||
unsafe { std::slice::from_raw_parts(self.ptr as *const String, self.len) };
|
||||
let value = slice[self.get_index()].as_bytes().into();
|
||||
self.advance(n);
|
||||
Some(value)
|
||||
}
|
||||
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
let len = self.len as isize;
|
||||
self.offset < -len || self.offset == len
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<AMbyteSpan> {
|
||||
self.advance(-n);
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &[String] =
|
||||
unsafe { std::slice::from_raw_parts(self.ptr as *const String, self.len) };
|
||||
Some(slice[self.get_index()].as_bytes().into())
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: -(self.offset + 1),
|
||||
ptr: self.ptr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: if self.offset < 0 { -1 } else { 0 },
|
||||
ptr: self.ptr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Detail> for [u8; USIZE_USIZE_USIZE_] {
|
||||
fn from(detail: Detail) -> Self {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_)
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \struct AMstrs
|
||||
/// \installed_headerfile
|
||||
/// \brief A random-access iterator over a sequence of UTF-8 strings.
|
||||
#[repr(C)]
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub struct AMstrs {
|
||||
/// An implementation detail that is intentionally opaque.
|
||||
/// \warning Modifying \p detail will cause undefined behavior.
|
||||
/// \note The actual size of \p detail will vary by platform, this is just
|
||||
/// the one for the platform this documentation was built on.
|
||||
detail: [u8; USIZE_USIZE_USIZE_],
|
||||
}
|
||||
|
||||
impl AMstrs {
|
||||
pub fn new(strings: &[String]) -> Self {
|
||||
Self {
|
||||
detail: Detail::new(strings, 0).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.advance(n);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
detail.len
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<AMbyteSpan> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.next(n)
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<AMbyteSpan> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.prev(n)
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.reversed().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.rewound().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[String]> for AMstrs {
|
||||
fn as_ref(&self) -> &[String] {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
unsafe { std::slice::from_raw_parts(detail.ptr as *const String, detail.len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AMstrs {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
detail: [0; USIZE_USIZE_USIZE_],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMstrs
|
||||
/// \brief Advances an iterator over a sequence of UTF-8 strings by at most
|
||||
/// \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction.
|
||||
///
|
||||
/// \param[in,out] strs A pointer to an `AMstrs` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \pre \p strs `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// strs must be a valid pointer to an AMstrs
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMstrsAdvance(strs: *mut AMstrs, n: isize) {
|
||||
if let Some(strs) = strs.as_mut() {
|
||||
strs.advance(n);
|
||||
};
|
||||
}
|
||||
|
||||
/// \memberof AMstrs
|
||||
/// \brief Compares the sequences of UTF-8 strings underlying a pair of
|
||||
/// iterators.
|
||||
///
|
||||
/// \param[in] strs1 A pointer to an `AMstrs` struct.
|
||||
/// \param[in] strs2 A pointer to an `AMstrs` struct.
|
||||
/// \return `-1` if \p strs1 `<` \p strs2, `0` if
|
||||
/// \p strs1 `==` \p strs2 and `1` if
|
||||
/// \p strs1 `>` \p strs2.
|
||||
/// \pre \p strs1 `!= NULL`.
|
||||
/// \pre \p strs2 `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// strs1 must be a valid pointer to an AMstrs
|
||||
/// strs2 must be a valid pointer to an AMstrs
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMstrsCmp(strs1: *const AMstrs, strs2: *const AMstrs) -> isize {
|
||||
match (strs1.as_ref(), strs2.as_ref()) {
|
||||
(Some(strs1), Some(strs2)) => match strs1.as_ref().cmp(strs2.as_ref()) {
|
||||
Ordering::Less => -1,
|
||||
Ordering::Equal => 0,
|
||||
Ordering::Greater => 1,
|
||||
},
|
||||
(None, Some(_)) => -1,
|
||||
(Some(_), None) => 1,
|
||||
(None, None) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMstrs
|
||||
/// \brief Gets the key at the current position of an iterator over a sequence
|
||||
/// of UTF-8 strings and then advances it by at most \p |n| positions
|
||||
/// where the sign of \p n is relative to the iterator's direction.
|
||||
///
|
||||
/// \param[in,out] strs A pointer to an `AMstrs` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return A UTF-8 string view as an `AMbyteSpan` struct that's `AMstr(NULL)`
|
||||
/// when \p strs was previously advanced past its forward/reverse limit.
|
||||
/// \pre \p strs `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// strs must be a valid pointer to an AMstrs
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMstrsNext(strs: *mut AMstrs, n: isize) -> AMbyteSpan {
|
||||
if let Some(strs) = strs.as_mut() {
|
||||
if let Some(key) = strs.next(n) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// \memberof AMstrs
|
||||
/// \brief Advances an iterator over a sequence of UTF-8 strings by at most
|
||||
/// \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction and then gets the key at its new position.
|
||||
///
|
||||
/// \param[in,out] strs A pointer to an `AMstrs` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return A UTF-8 string view as an `AMbyteSpan` struct that's `AMstr(NULL)`
|
||||
/// when \p strs is presently advanced past its forward/reverse limit.
|
||||
/// \pre \p strs `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// strs must be a valid pointer to an AMstrs
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMstrsPrev(strs: *mut AMstrs, n: isize) -> AMbyteSpan {
|
||||
if let Some(strs) = strs.as_mut() {
|
||||
if let Some(key) = strs.prev(n) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// \memberof AMstrs
|
||||
/// \brief Gets the size of the sequence of UTF-8 strings underlying an
|
||||
/// iterator.
|
||||
///
|
||||
/// \param[in] strs A pointer to an `AMstrs` struct.
|
||||
/// \return The count of values in \p strs.
|
||||
/// \pre \p strs `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// strs must be a valid pointer to an AMstrs
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMstrsSize(strs: *const AMstrs) -> usize {
|
||||
if let Some(strs) = strs.as_ref() {
|
||||
strs.len()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMstrs
|
||||
/// \brief Creates an iterator over the same sequence of UTF-8 strings as the
|
||||
/// given one but with the opposite position and direction.
|
||||
///
|
||||
/// \param[in] strs A pointer to an `AMstrs` struct.
|
||||
/// \return An `AMstrs` struct.
|
||||
/// \pre \p strs `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// strs must be a valid pointer to an AMstrs
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMstrsReversed(strs: *const AMstrs) -> AMstrs {
|
||||
if let Some(strs) = strs.as_ref() {
|
||||
strs.reversed()
|
||||
} else {
|
||||
AMstrs::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMstrs
|
||||
/// \brief Creates an iterator at the starting position over the same sequence
|
||||
/// of UTF-8 strings as the given one.
|
||||
///
|
||||
/// \param[in] strs A pointer to an `AMstrs` struct.
|
||||
/// \return An `AMstrs` struct
|
||||
/// \pre \p strs `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// strs must be a valid pointer to an AMstrs
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMstrsRewound(strs: *const AMstrs) -> AMstrs {
|
||||
if let Some(strs) = strs.as_ref() {
|
||||
strs.rewound()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
mod have;
|
||||
mod haves;
|
||||
mod message;
|
||||
mod state;
|
||||
|
||||
pub(crate) use have::AMsyncHave;
|
||||
pub(crate) use message::{to_sync_message, AMsyncMessage};
|
||||
pub(crate) use state::AMsyncState;
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
use automerge as am;
|
||||
|
||||
use crate::result::{to_result, AMresult};
|
||||
use crate::change_hashes::AMchangeHashes;
|
||||
|
||||
/// \struct AMsyncHave
|
||||
/// \installed_headerfile
|
||||
/// \brief A summary of the changes that the sender of a synchronization
|
||||
/// message already has.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct AMsyncHave(am::sync::Have);
|
||||
pub struct AMsyncHave(*const am::sync::Have);
|
||||
|
||||
impl AMsyncHave {
|
||||
pub fn new(have: am::sync::Have) -> Self {
|
||||
pub fn new(have: &am::sync::Have) -> Self {
|
||||
Self(have)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<am::sync::Have> for AMsyncHave {
|
||||
fn as_ref(&self) -> &am::sync::Have {
|
||||
&self.0
|
||||
unsafe { &*self.0 }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,18 +25,17 @@ impl AsRef<am::sync::Have> for AMsyncHave {
|
|||
/// \brief Gets the heads of the sender.
|
||||
///
|
||||
/// \param[in] sync_have A pointer to an `AMsyncHave` struct.
|
||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items.
|
||||
/// \pre \p sync_have `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return An `AMchangeHashes` struct.
|
||||
/// \pre \p sync_have `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// sync_have must be a valid pointer to an AMsyncHave
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncHaveLastSync(sync_have: *const AMsyncHave) -> *mut AMresult {
|
||||
to_result(match sync_have.as_ref() {
|
||||
Some(sync_have) => sync_have.as_ref().last_sync.as_slice(),
|
||||
None => Default::default(),
|
||||
})
|
||||
pub unsafe extern "C" fn AMsyncHaveLastSync(sync_have: *const AMsyncHave) -> AMchangeHashes {
|
||||
if let Some(sync_have) = sync_have.as_ref() {
|
||||
AMchangeHashes::new(&sync_have.as_ref().last_sync)
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
|
378
rust/automerge-c/src/sync/haves.rs
Normal file
378
rust/automerge-c/src/sync/haves.rs
Normal file
|
@ -0,0 +1,378 @@
|
|||
use automerge as am;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ffi::c_void;
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::sync::have::AMsyncHave;
|
||||
|
||||
#[repr(C)]
|
||||
struct Detail {
|
||||
len: usize,
|
||||
offset: isize,
|
||||
ptr: *const c_void,
|
||||
storage: *mut c_void,
|
||||
}
|
||||
|
||||
/// \note cbindgen won't propagate the value of a `std::mem::size_of<T>()` call
|
||||
/// (https://github.com/eqrion/cbindgen/issues/252) but it will
|
||||
/// propagate the name of a constant initialized from it so if the
|
||||
/// constant's name is a symbolic representation of the value it can be
|
||||
/// converted into a number by post-processing the header it generated.
|
||||
pub const USIZE_USIZE_USIZE_USIZE_: usize = size_of::<Detail>();
|
||||
|
||||
impl Detail {
|
||||
fn new(
|
||||
haves: &[am::sync::Have],
|
||||
offset: isize,
|
||||
storage: &mut BTreeMap<usize, AMsyncHave>,
|
||||
) -> Self {
|
||||
let storage: *mut BTreeMap<usize, AMsyncHave> = storage;
|
||||
Self {
|
||||
len: haves.len(),
|
||||
offset,
|
||||
ptr: haves.as_ptr() as *const c_void,
|
||||
storage: storage as *mut c_void,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
let len = self.len as isize;
|
||||
self.offset = if self.offset < 0 {
|
||||
// It's reversed.
|
||||
let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN);
|
||||
if unclipped >= 0 {
|
||||
// Clip it to the forward stop.
|
||||
len
|
||||
} else {
|
||||
std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1)
|
||||
}
|
||||
} else {
|
||||
let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX);
|
||||
if unclipped < 0 {
|
||||
// Clip it to the reverse stop.
|
||||
-(len + 1)
|
||||
} else {
|
||||
std::cmp::max(0, std::cmp::min(unclipped, len))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_index(&self) -> usize {
|
||||
(self.offset
|
||||
+ if self.offset < 0 {
|
||||
self.len as isize
|
||||
} else {
|
||||
0
|
||||
}) as usize
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<*const AMsyncHave> {
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &[am::sync::Have] =
|
||||
unsafe { std::slice::from_raw_parts(self.ptr as *const am::sync::Have, self.len) };
|
||||
let storage = unsafe { &mut *(self.storage as *mut BTreeMap<usize, AMsyncHave>) };
|
||||
let index = self.get_index();
|
||||
let value = match storage.get_mut(&index) {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
storage.insert(index, AMsyncHave::new(&slice[index]));
|
||||
storage.get_mut(&index).unwrap()
|
||||
}
|
||||
};
|
||||
self.advance(n);
|
||||
Some(value)
|
||||
}
|
||||
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
let len = self.len as isize;
|
||||
self.offset < -len || self.offset == len
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<*const AMsyncHave> {
|
||||
self.advance(-n);
|
||||
if self.is_stopped() {
|
||||
return None;
|
||||
}
|
||||
let slice: &[am::sync::Have] =
|
||||
unsafe { std::slice::from_raw_parts(self.ptr as *const am::sync::Have, self.len) };
|
||||
let storage = unsafe { &mut *(self.storage as *mut BTreeMap<usize, AMsyncHave>) };
|
||||
let index = self.get_index();
|
||||
Some(match storage.get_mut(&index) {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
storage.insert(index, AMsyncHave::new(&slice[index]));
|
||||
storage.get_mut(&index).unwrap()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: -(self.offset + 1),
|
||||
ptr: self.ptr,
|
||||
storage: self.storage,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
Self {
|
||||
len: self.len,
|
||||
offset: if self.offset < 0 { -1 } else { 0 },
|
||||
ptr: self.ptr,
|
||||
storage: self.storage,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Detail> for [u8; USIZE_USIZE_USIZE_USIZE_] {
|
||||
fn from(detail: Detail) -> Self {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
(&detail as *const Detail) as *const u8,
|
||||
USIZE_USIZE_USIZE_USIZE_,
|
||||
)
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \struct AMsyncHaves
|
||||
/// \installed_headerfile
|
||||
/// \brief A random-access iterator over a sequence of synchronization haves.
|
||||
#[repr(C)]
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub struct AMsyncHaves {
|
||||
/// An implementation detail that is intentionally opaque.
|
||||
/// \warning Modifying \p detail will cause undefined behavior.
|
||||
/// \note The actual size of \p detail will vary by platform, this is just
|
||||
/// the one for the platform this documentation was built on.
|
||||
detail: [u8; USIZE_USIZE_USIZE_USIZE_],
|
||||
}
|
||||
|
||||
impl AMsyncHaves {
|
||||
pub fn new(haves: &[am::sync::Have], storage: &mut BTreeMap<usize, AMsyncHave>) -> Self {
|
||||
Self {
|
||||
detail: Detail::new(haves, 0, storage).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, n: isize) {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.advance(n);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
detail.len
|
||||
}
|
||||
|
||||
pub fn next(&mut self, n: isize) -> Option<*const AMsyncHave> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.next(n)
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, n: isize) -> Option<*const AMsyncHave> {
|
||||
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||
detail.prev(n)
|
||||
}
|
||||
|
||||
pub fn reversed(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.reversed().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewound(&self) -> Self {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
Self {
|
||||
detail: detail.rewound().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[am::sync::Have]> for AMsyncHaves {
|
||||
fn as_ref(&self) -> &[am::sync::Have] {
|
||||
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||
unsafe { std::slice::from_raw_parts(detail.ptr as *const am::sync::Have, detail.len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AMsyncHaves {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
detail: [0; USIZE_USIZE_USIZE_USIZE_],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMsyncHaves
|
||||
/// \brief Advances an iterator over a sequence of synchronization haves by at
|
||||
/// most \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction.
|
||||
///
|
||||
/// \param[in,out] sync_haves A pointer to an `AMsyncHaves` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \pre \p sync_haves `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// sync_haves must be a valid pointer to an AMsyncHaves
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncHavesAdvance(sync_haves: *mut AMsyncHaves, n: isize) {
|
||||
if let Some(sync_haves) = sync_haves.as_mut() {
|
||||
sync_haves.advance(n);
|
||||
};
|
||||
}
|
||||
|
||||
/// \memberof AMsyncHaves
|
||||
/// \brief Tests the equality of two sequences of synchronization haves
|
||||
/// underlying a pair of iterators.
|
||||
///
|
||||
/// \param[in] sync_haves1 A pointer to an `AMsyncHaves` struct.
|
||||
/// \param[in] sync_haves2 A pointer to an `AMsyncHaves` struct.
|
||||
/// \return `true` if \p sync_haves1 `==` \p sync_haves2 and `false` otherwise.
|
||||
/// \pre \p sync_haves1 `!= NULL`.
|
||||
/// \pre \p sync_haves2 `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// sync_haves1 must be a valid pointer to an AMsyncHaves
|
||||
/// sync_haves2 must be a valid pointer to an AMsyncHaves
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncHavesEqual(
|
||||
sync_haves1: *const AMsyncHaves,
|
||||
sync_haves2: *const AMsyncHaves,
|
||||
) -> bool {
|
||||
match (sync_haves1.as_ref(), sync_haves2.as_ref()) {
|
||||
(Some(sync_haves1), Some(sync_haves2)) => sync_haves1.as_ref() == sync_haves2.as_ref(),
|
||||
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMsyncHaves
|
||||
/// \brief Gets the synchronization have at the current position of an iterator
|
||||
/// over a sequence of synchronization haves and then advances it by at
|
||||
/// most \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction.
|
||||
///
|
||||
/// \param[in,out] sync_haves A pointer to an `AMsyncHaves` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return A pointer to an `AMsyncHave` struct that's `NULL` when
|
||||
/// \p sync_haves was previously advanced past its forward/reverse
|
||||
/// limit.
|
||||
/// \pre \p sync_haves `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// sync_haves must be a valid pointer to an AMsyncHaves
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncHavesNext(
|
||||
sync_haves: *mut AMsyncHaves,
|
||||
n: isize,
|
||||
) -> *const AMsyncHave {
|
||||
if let Some(sync_haves) = sync_haves.as_mut() {
|
||||
if let Some(sync_have) = sync_haves.next(n) {
|
||||
return sync_have;
|
||||
}
|
||||
}
|
||||
std::ptr::null()
|
||||
}
|
||||
|
||||
/// \memberof AMsyncHaves
|
||||
/// \brief Advances an iterator over a sequence of synchronization haves by at
|
||||
/// most \p |n| positions where the sign of \p n is relative to the
|
||||
/// iterator's direction and then gets the synchronization have at its
|
||||
/// new position.
|
||||
///
|
||||
/// \param[in,out] sync_haves A pointer to an `AMsyncHaves` struct.
|
||||
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||
/// number of positions to advance.
|
||||
/// \return A pointer to an `AMsyncHave` struct that's `NULL` when
|
||||
/// \p sync_haves is presently advanced past its forward/reverse limit.
|
||||
/// \pre \p sync_haves `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// sync_haves must be a valid pointer to an AMsyncHaves
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncHavesPrev(
|
||||
sync_haves: *mut AMsyncHaves,
|
||||
n: isize,
|
||||
) -> *const AMsyncHave {
|
||||
if let Some(sync_haves) = sync_haves.as_mut() {
|
||||
if let Some(sync_have) = sync_haves.prev(n) {
|
||||
return sync_have;
|
||||
}
|
||||
}
|
||||
std::ptr::null()
|
||||
}
|
||||
|
||||
/// \memberof AMsyncHaves
|
||||
/// \brief Gets the size of the sequence of synchronization haves underlying an
|
||||
/// iterator.
|
||||
///
|
||||
/// \param[in] sync_haves A pointer to an `AMsyncHaves` struct.
|
||||
/// \return The count of values in \p sync_haves.
|
||||
/// \pre \p sync_haves `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// sync_haves must be a valid pointer to an AMsyncHaves
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncHavesSize(sync_haves: *const AMsyncHaves) -> usize {
|
||||
if let Some(sync_haves) = sync_haves.as_ref() {
|
||||
sync_haves.len()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMsyncHaves
|
||||
/// \brief Creates an iterator over the same sequence of synchronization haves
|
||||
/// as the given one but with the opposite position and direction.
|
||||
///
|
||||
/// \param[in] sync_haves A pointer to an `AMsyncHaves` struct.
|
||||
/// \return An `AMsyncHaves` struct
|
||||
/// \pre \p sync_haves `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// sync_haves must be a valid pointer to an AMsyncHaves
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncHavesReversed(sync_haves: *const AMsyncHaves) -> AMsyncHaves {
|
||||
if let Some(sync_haves) = sync_haves.as_ref() {
|
||||
sync_haves.reversed()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMsyncHaves
|
||||
/// \brief Creates an iterator at the starting position over the same sequence
|
||||
/// of synchronization haves as the given one.
|
||||
///
|
||||
/// \param[in] sync_haves A pointer to an `AMsyncHaves` struct.
|
||||
/// \return An `AMsyncHaves` struct
|
||||
/// \pre \p sync_haves `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
/// sync_haves must be a valid pointer to an AMsyncHaves
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncHavesRewound(sync_haves: *const AMsyncHaves) -> AMsyncHaves {
|
||||
if let Some(sync_haves) = sync_haves.as_ref() {
|
||||
sync_haves.rewound()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
|
@ -3,15 +3,18 @@ use std::cell::RefCell;
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::change::AMchange;
|
||||
use crate::change_hashes::AMchangeHashes;
|
||||
use crate::changes::AMchanges;
|
||||
use crate::result::{to_result, AMresult};
|
||||
use crate::sync::have::AMsyncHave;
|
||||
use crate::sync::haves::AMsyncHaves;
|
||||
|
||||
macro_rules! to_sync_message {
|
||||
($handle:expr) => {{
|
||||
let handle = $handle.as_ref();
|
||||
match handle {
|
||||
Some(b) => b,
|
||||
None => return AMresult::error("Invalid `AMsyncMessage*`").into(),
|
||||
None => return AMresult::err("Invalid AMsyncMessage pointer").into(),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
@ -48,52 +51,55 @@ impl AsRef<am::sync::Message> for AMsyncMessage {
|
|||
/// \brief Gets the changes for the recipient to apply.
|
||||
///
|
||||
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE` items.
|
||||
/// \pre \p sync_message `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return An `AMchanges` struct.
|
||||
/// \pre \p sync_message `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// sync_message must be a valid pointer to an AMsyncMessage
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncMessageChanges(sync_message: *const AMsyncMessage) -> *mut AMresult {
|
||||
to_result(match sync_message.as_ref() {
|
||||
Some(sync_message) => sync_message.body.changes.as_slice(),
|
||||
None => Default::default(),
|
||||
})
|
||||
pub unsafe extern "C" fn AMsyncMessageChanges(sync_message: *const AMsyncMessage) -> AMchanges {
|
||||
if let Some(sync_message) = sync_message.as_ref() {
|
||||
AMchanges::new(
|
||||
&sync_message.body.changes,
|
||||
&mut sync_message.changes_storage.borrow_mut(),
|
||||
)
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMsyncMessage
|
||||
/// \brief Decodes an array of bytes into a synchronization message.
|
||||
/// \brief Decodes a sequence of bytes into a synchronization message.
|
||||
///
|
||||
/// \param[in] src A pointer to an array of bytes.
|
||||
/// \param[in] count The count of bytes to decode from the array pointed to by
|
||||
/// \p src.
|
||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_SYNC_MESSAGE` item.
|
||||
/// \pre \p src `!= NULL`
|
||||
/// \pre `sizeof(`\p src `) > 0`
|
||||
/// \pre \p count `<= sizeof(`\p src `)`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] count The number of bytes in \p src to decode.
|
||||
/// \return A pointer to an `AMresult` struct containing an `AMsyncMessage`
|
||||
/// struct.
|
||||
/// \pre \p src `!= NULL`.
|
||||
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// src must be a byte array of length `>= count`
|
||||
/// src must be a byte array of size `>= count`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncMessageDecode(src: *const u8, count: usize) -> *mut AMresult {
|
||||
let data = std::slice::from_raw_parts(src, count);
|
||||
to_result(am::sync::Message::decode(data))
|
||||
let mut data = Vec::new();
|
||||
data.extend_from_slice(std::slice::from_raw_parts(src, count));
|
||||
to_result(am::sync::Message::decode(&data))
|
||||
}
|
||||
|
||||
/// \memberof AMsyncMessage
|
||||
/// \brief Encodes a synchronization message as an array of bytes.
|
||||
/// \brief Encodes a synchronization message as a sequence of bytes.
|
||||
///
|
||||
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTES` item.
|
||||
/// \pre \p sync_message `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing an array of bytes as
|
||||
/// an `AMbyteSpan` struct.
|
||||
/// \pre \p sync_message `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -108,40 +114,41 @@ pub unsafe extern "C" fn AMsyncMessageEncode(sync_message: *const AMsyncMessage)
|
|||
/// \brief Gets a summary of the changes that the sender already has.
|
||||
///
|
||||
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
||||
/// \return A pointer to an `AMresult` struct with `AM_SYNC_HAVE` items.
|
||||
/// \pre \p sync_message `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return An `AMhaves` struct.
|
||||
/// \pre \p sync_message `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// sync_message must be a valid pointer to an AMsyncMessage
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncMessageHaves(sync_message: *const AMsyncMessage) -> *mut AMresult {
|
||||
to_result(match sync_message.as_ref() {
|
||||
Some(sync_message) => sync_message.as_ref().have.as_slice(),
|
||||
None => Default::default(),
|
||||
})
|
||||
pub unsafe extern "C" fn AMsyncMessageHaves(sync_message: *const AMsyncMessage) -> AMsyncHaves {
|
||||
if let Some(sync_message) = sync_message.as_ref() {
|
||||
AMsyncHaves::new(
|
||||
&sync_message.as_ref().have,
|
||||
&mut sync_message.haves_storage.borrow_mut(),
|
||||
)
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMsyncMessage
|
||||
/// \brief Gets the heads of the sender.
|
||||
///
|
||||
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items.
|
||||
/// \pre \p sync_message `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return An `AMchangeHashes` struct.
|
||||
/// \pre \p sync_message `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// sync_message must be a valid pointer to an AMsyncMessage
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncMessageHeads(sync_message: *const AMsyncMessage) -> *mut AMresult {
|
||||
to_result(match sync_message.as_ref() {
|
||||
Some(sync_message) => sync_message.as_ref().heads.as_slice(),
|
||||
None => Default::default(),
|
||||
})
|
||||
pub unsafe extern "C" fn AMsyncMessageHeads(sync_message: *const AMsyncMessage) -> AMchangeHashes {
|
||||
if let Some(sync_message) = sync_message.as_ref() {
|
||||
AMchangeHashes::new(&sync_message.as_ref().heads)
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMsyncMessage
|
||||
|
@ -149,18 +156,17 @@ pub unsafe extern "C" fn AMsyncMessageHeads(sync_message: *const AMsyncMessage)
|
|||
/// by the recipient.
|
||||
///
|
||||
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items.
|
||||
/// \pre \p sync_message `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return An `AMchangeHashes` struct.
|
||||
/// \pre \p sync_message `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// sync_message must be a valid pointer to an AMsyncMessage
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncMessageNeeds(sync_message: *const AMsyncMessage) -> *mut AMresult {
|
||||
to_result(match sync_message.as_ref() {
|
||||
Some(sync_message) => sync_message.as_ref().need.as_slice(),
|
||||
None => Default::default(),
|
||||
})
|
||||
pub unsafe extern "C" fn AMsyncMessageNeeds(sync_message: *const AMsyncMessage) -> AMchangeHashes {
|
||||
if let Some(sync_message) = sync_message.as_ref() {
|
||||
AMchangeHashes::new(&sync_message.as_ref().need)
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,17 @@ use automerge as am;
|
|||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::change_hashes::AMchangeHashes;
|
||||
use crate::result::{to_result, AMresult};
|
||||
use crate::sync::have::AMsyncHave;
|
||||
use crate::sync::haves::AMsyncHaves;
|
||||
|
||||
macro_rules! to_sync_state {
|
||||
($handle:expr) => {{
|
||||
let handle = $handle.as_ref();
|
||||
match handle {
|
||||
Some(b) => b,
|
||||
None => return AMresult::error("Invalid `AMsyncState*`").into(),
|
||||
None => return AMresult::err("Invalid AMsyncState pointer").into(),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
@ -54,35 +56,36 @@ impl From<AMsyncState> for *mut AMsyncState {
|
|||
}
|
||||
|
||||
/// \memberof AMsyncState
|
||||
/// \brief Decodes an array of bytes into a synchronization state.
|
||||
/// \brief Decodes a sequence of bytes into a synchronization state.
|
||||
///
|
||||
/// \param[in] src A pointer to an array of bytes.
|
||||
/// \param[in] count The count of bytes to decode from the array pointed to by
|
||||
/// \p src.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_SYNC_STATE` item.
|
||||
/// \pre \p src `!= NULL`
|
||||
/// \pre `sizeof(`\p src `) > 0`
|
||||
/// \pre \p count `<= sizeof(`\p src `)`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \param[in] count The number of bytes in \p src to decode.
|
||||
/// \return A pointer to an `AMresult` struct containing an `AMsyncState`
|
||||
/// struct.
|
||||
/// \pre \p src `!= NULL`.
|
||||
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// src must be a byte array of length `>= count`
|
||||
/// src must be a byte array of size `>= count`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncStateDecode(src: *const u8, count: usize) -> *mut AMresult {
|
||||
let data = std::slice::from_raw_parts(src, count);
|
||||
to_result(am::sync::State::decode(data))
|
||||
let mut data = Vec::new();
|
||||
data.extend_from_slice(std::slice::from_raw_parts(src, count));
|
||||
to_result(am::sync::State::decode(&data))
|
||||
}
|
||||
|
||||
/// \memberof AMsyncState
|
||||
/// \brief Encodes a synchronization state as an array of bytes.
|
||||
/// \brief Encodes a synchronizaton state as a sequence of bytes.
|
||||
///
|
||||
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTE_SPAN` item.
|
||||
/// \pre \p sync_state `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing an array of bytes as
|
||||
/// an `AMbyteSpan` struct.
|
||||
/// \pre \p sync_state `!= NULL`.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -99,9 +102,8 @@ pub unsafe extern "C" fn AMsyncStateEncode(sync_state: *const AMsyncState) -> *m
|
|||
/// \param[in] sync_state1 A pointer to an `AMsyncState` struct.
|
||||
/// \param[in] sync_state2 A pointer to an `AMsyncState` struct.
|
||||
/// \return `true` if \p sync_state1 `==` \p sync_state2 and `false` otherwise.
|
||||
/// \pre \p sync_state1 `!= NULL`
|
||||
/// \pre \p sync_state2 `!= NULL`
|
||||
/// \post `!(`\p sync_state1 `&&` \p sync_state2 `) -> false`
|
||||
/// \pre \p sync_state1 `!= NULL`.
|
||||
/// \pre \p sync_state2 `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// #Safety
|
||||
|
@ -114,17 +116,18 @@ pub unsafe extern "C" fn AMsyncStateEqual(
|
|||
) -> bool {
|
||||
match (sync_state1.as_ref(), sync_state2.as_ref()) {
|
||||
(Some(sync_state1), Some(sync_state2)) => sync_state1.as_ref() == sync_state2.as_ref(),
|
||||
(None, None) | (None, Some(_)) | (Some(_), None) => false,
|
||||
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMsyncState
|
||||
/// \brief Allocates a new synchronization state and initializes it from
|
||||
/// default values.
|
||||
/// \brief Allocates a new synchronization state and initializes it with
|
||||
/// defaults.
|
||||
///
|
||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_SYNC_STATE` item.
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||
/// `AMsyncState` struct.
|
||||
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||
/// in order to prevent a memory leak.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn AMsyncStateInit() -> *mut AMresult {
|
||||
to_result(am::sync::State::new())
|
||||
|
@ -134,36 +137,40 @@ pub extern "C" fn AMsyncStateInit() -> *mut AMresult {
|
|||
/// \brief Gets the heads that are shared by both peers.
|
||||
///
|
||||
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items.
|
||||
/// \pre \p sync_state `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return An `AMchangeHashes` struct.
|
||||
/// \pre \p sync_state `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// sync_state must be a valid pointer to an AMsyncState
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncStateSharedHeads(sync_state: *const AMsyncState) -> *mut AMresult {
|
||||
let sync_state = to_sync_state!(sync_state);
|
||||
to_result(sync_state.as_ref().shared_heads.as_slice())
|
||||
pub unsafe extern "C" fn AMsyncStateSharedHeads(sync_state: *const AMsyncState) -> AMchangeHashes {
|
||||
if let Some(sync_state) = sync_state.as_ref() {
|
||||
AMchangeHashes::new(&sync_state.as_ref().shared_heads)
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMsyncState
|
||||
/// \brief Gets the heads that were last sent by this peer.
|
||||
///
|
||||
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items.
|
||||
/// \pre \p sync_state `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
/// \return An `AMchangeHashes` struct.
|
||||
/// \pre \p sync_state `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// sync_state must be a valid pointer to an AMsyncState
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncStateLastSentHeads(sync_state: *const AMsyncState) -> *mut AMresult {
|
||||
let sync_state = to_sync_state!(sync_state);
|
||||
to_result(sync_state.as_ref().last_sent_heads.as_slice())
|
||||
pub unsafe extern "C" fn AMsyncStateLastSentHeads(
|
||||
sync_state: *const AMsyncState,
|
||||
) -> AMchangeHashes {
|
||||
if let Some(sync_state) = sync_state.as_ref() {
|
||||
AMchangeHashes::new(&sync_state.as_ref().last_sent_heads)
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// \memberof AMsyncState
|
||||
|
@ -171,13 +178,11 @@ pub unsafe extern "C" fn AMsyncStateLastSentHeads(sync_state: *const AMsyncState
|
|||
///
|
||||
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
||||
/// \param[out] has_value A pointer to a boolean flag that is set to `true` if
|
||||
/// the returned `AMitems` struct is relevant, `false` otherwise.
|
||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_SYNC_HAVE` items.
|
||||
/// \pre \p sync_state `!= NULL`
|
||||
/// \pre \p has_value `!= NULL`
|
||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
||||
/// `AMresultFree()` in order to avoid a memory leak.
|
||||
//// \internal
|
||||
/// the returned `AMhaves` struct is relevant, `false` otherwise.
|
||||
/// \return An `AMhaves` struct.
|
||||
/// \pre \p sync_state `!= NULL`.
|
||||
/// \pre \p has_value `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// sync_state must be a valid pointer to an AMsyncState
|
||||
|
@ -186,15 +191,15 @@ pub unsafe extern "C" fn AMsyncStateLastSentHeads(sync_state: *const AMsyncState
|
|||
pub unsafe extern "C" fn AMsyncStateTheirHaves(
|
||||
sync_state: *const AMsyncState,
|
||||
has_value: *mut bool,
|
||||
) -> *mut AMresult {
|
||||
) -> AMsyncHaves {
|
||||
if let Some(sync_state) = sync_state.as_ref() {
|
||||
if let Some(haves) = &sync_state.as_ref().their_have {
|
||||
*has_value = true;
|
||||
return to_result(haves.as_slice());
|
||||
}
|
||||
return AMsyncHaves::new(haves, &mut sync_state.their_haves_storage.borrow_mut());
|
||||
};
|
||||
};
|
||||
*has_value = false;
|
||||
to_result(Vec::<am::sync::Have>::new())
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// \memberof AMsyncState
|
||||
|
@ -202,31 +207,29 @@ pub unsafe extern "C" fn AMsyncStateTheirHaves(
|
|||
///
|
||||
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
||||
/// \param[out] has_value A pointer to a boolean flag that is set to `true` if
|
||||
/// the returned `AMitems` struct is relevant, `false`
|
||||
/// the returned `AMchangeHashes` 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.
|
||||
/// \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`
|
||||
/// the returned `AMchangeHashes` 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.
|
||||
/// \return An `AMchangeHashes` struct.
|
||||
/// \pre \p sync_state `!= NULL`.
|
||||
/// \pre \p has_value `!= NULL`.
|
||||
/// \internal
|
||||
///
|
||||
/// # Safety
|
||||
/// sync_state must be a valid pointer to an AMsyncState
|
||||
/// has_value must be a valid pointer to a bool
|
||||
/// has_value must be a valid pointer to a bool.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn AMsyncStateTheirNeeds(
|
||||
sync_state: *const AMsyncState,
|
||||
has_value: *mut bool,
|
||||
) -> *mut AMresult {
|
||||
) -> AMchangeHashes {
|
||||
if let Some(sync_state) = sync_state.as_ref() {
|
||||
if let Some(change_hashes) = &sync_state.as_ref().their_need {
|
||||
*has_value = true;
|
||||
return to_result(change_hashes.as_slice());
|
||||
return AMchangeHashes::new(change_hashes);
|
||||
}
|
||||
};
|
||||
*has_value = false;
|
||||
to_result(Vec::<am::ChangeHash>::new())
|
||||
Default::default()
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
#include <stdarg.h>
|
||||
|
||||
#include <automerge-c/utils/result.h>
|
||||
|
||||
AMresult* AMresultFrom(int count, ...) {
|
||||
AMresult* result = NULL;
|
||||
bool is_ok = true;
|
||||
va_list args;
|
||||
va_start(args, count);
|
||||
for (int i = 0; i != count; ++i) {
|
||||
AMresult* src = va_arg(args, AMresult*);
|
||||
AMresult* dest = result;
|
||||
is_ok = (AMresultStatus(src) == AM_STATUS_OK);
|
||||
if (is_ok) {
|
||||
if (dest) {
|
||||
result = AMresultCat(dest, src);
|
||||
is_ok = (AMresultStatus(result) == AM_STATUS_OK);
|
||||
AMresultFree(dest);
|
||||
AMresultFree(src);
|
||||
} else {
|
||||
result = src;
|
||||
}
|
||||
} else {
|
||||
AMresultFree(src);
|
||||
}
|
||||
}
|
||||
va_end(args);
|
||||
if (!is_ok) {
|
||||
AMresultFree(result);
|
||||
result = NULL;
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <automerge-c/utils/stack.h>
|
||||
#include <automerge-c/utils/string.h>
|
||||
|
||||
void AMstackFree(AMstack** stack) {
|
||||
if (stack) {
|
||||
while (*stack) {
|
||||
AMresultFree(AMstackPop(stack, NULL));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AMresult* AMstackPop(AMstack** stack, const AMresult* result) {
|
||||
if (!stack) {
|
||||
return NULL;
|
||||
}
|
||||
AMstack** prev = stack;
|
||||
if (result) {
|
||||
while (*prev && ((*prev)->result != result)) {
|
||||
*prev = (*prev)->prev;
|
||||
}
|
||||
}
|
||||
if (!*prev) {
|
||||
return NULL;
|
||||
}
|
||||
AMstack* target = *prev;
|
||||
*prev = target->prev;
|
||||
AMresult* popped = target->result;
|
||||
free(target);
|
||||
return popped;
|
||||
}
|
||||
|
||||
AMresult* AMstackResult(AMstack** stack, AMresult* result, AMstackCallback callback, void* data) {
|
||||
if (!stack) {
|
||||
if (callback) {
|
||||
/* Create a local stack so that the callback can still examine the
|
||||
* result. */
|
||||
AMstack node = {.result = result, .prev = NULL};
|
||||
AMstack* stack = &node;
|
||||
callback(&stack, data);
|
||||
} else {
|
||||
/* \note There is no reason to call this function when both the
|
||||
* stack and the callback are null. */
|
||||
fprintf(stderr, "ERROR: NULL AMstackCallback!\n");
|
||||
}
|
||||
/* \note Nothing can be returned without a stack regardless of
|
||||
* whether or not the callback validated the result. */
|
||||
AMresultFree(result);
|
||||
return NULL;
|
||||
}
|
||||
/* Always push the result onto the stack, even if it's null, so that the
|
||||
* callback can examine it. */
|
||||
AMstack* next = calloc(1, sizeof(AMstack));
|
||||
*next = (AMstack){.result = result, .prev = *stack};
|
||||
AMstack* top = next;
|
||||
*stack = top;
|
||||
if (callback) {
|
||||
if (!callback(stack, data)) {
|
||||
/* The result didn't pass the callback's examination. */
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
/* Report an obvious error. */
|
||||
if (result) {
|
||||
AMbyteSpan const err_msg = AMresultError(result);
|
||||
if (err_msg.src && err_msg.count) {
|
||||
/* \note The callback may be null because the result is supposed
|
||||
* to be examined externally so return it despite an
|
||||
* error. */
|
||||
char* const cstr = AMstrdup(err_msg, NULL);
|
||||
fprintf(stderr, "WARNING: %s.\n", cstr);
|
||||
free(cstr);
|
||||
}
|
||||
} else {
|
||||
/* \note There's no reason to call this function when both the
|
||||
* result and the callback are null. */
|
||||
fprintf(stderr, "ERROR: NULL AMresult*!\n");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
AMitem* AMstackItem(AMstack** stack, AMresult* result, AMstackCallback callback, void* data) {
|
||||
AMitems items = AMstackItems(stack, result, callback, data);
|
||||
return AMitemsNext(&items, 1);
|
||||
}
|
||||
|
||||
AMitems AMstackItems(AMstack** stack, AMresult* result, AMstackCallback callback, void* data) {
|
||||
return (AMstackResult(stack, result, callback, data)) ? AMresultItems(result) : (AMitems){0};
|
||||
}
|
||||
|
||||
size_t AMstackSize(AMstack const* const stack) {
|
||||
if (!stack) {
|
||||
return 0;
|
||||
}
|
||||
size_t count = 0;
|
||||
AMstack const* prev = stack;
|
||||
while (prev) {
|
||||
++count;
|
||||
prev = prev->prev;
|
||||
}
|
||||
return count;
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include <automerge-c/utils/stack_callback_data.h>
|
||||
|
||||
AMstackCallbackData* AMstackCallbackDataInit(AMvalType const bitmask, char const* const file, int const line) {
|
||||
AMstackCallbackData* data = malloc(sizeof(AMstackCallbackData));
|
||||
*data = (AMstackCallbackData){.bitmask = bitmask, .file = file, .line = line};
|
||||
return data;
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <automerge-c/utils/string.h>
|
||||
|
||||
char* AMstrdup(AMbyteSpan const str, char const* nul) {
|
||||
if (!str.src) {
|
||||
return NULL;
|
||||
} else if (!str.count) {
|
||||
return strdup("");
|
||||
}
|
||||
nul = (nul) ? nul : "\\0";
|
||||
size_t const nul_len = strlen(nul);
|
||||
char* dup = NULL;
|
||||
size_t dup_len = 0;
|
||||
char const* begin = str.src;
|
||||
char const* end = begin;
|
||||
for (size_t i = 0; i != str.count; ++i, ++end) {
|
||||
if (!*end) {
|
||||
size_t const len = end - begin;
|
||||
size_t const alloc_len = dup_len + len + nul_len;
|
||||
if (dup) {
|
||||
dup = realloc(dup, alloc_len + 1);
|
||||
} else {
|
||||
dup = malloc(alloc_len + 1);
|
||||
}
|
||||
memcpy(dup + dup_len, begin, len);
|
||||
memcpy(dup + dup_len + len, nul, nul_len);
|
||||
dup[alloc_len] = '\0';
|
||||
begin = end + 1;
|
||||
dup_len = alloc_len;
|
||||
}
|
||||
}
|
||||
if (begin != end) {
|
||||
size_t const len = end - begin;
|
||||
size_t const alloc_len = dup_len + len;
|
||||
if (dup) {
|
||||
dup = realloc(dup, alloc_len + 1);
|
||||
} else {
|
||||
dup = malloc(alloc_len + 1);
|
||||
}
|
||||
memcpy(dup + dup_len, begin, len);
|
||||
dup[alloc_len] = '\0';
|
||||
}
|
||||
return dup;
|
||||
}
|
|
@ -1,51 +1,53 @@
|
|||
find_package(cmocka CONFIG REQUIRED)
|
||||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||
|
||||
find_package(cmocka REQUIRED)
|
||||
|
||||
add_executable(
|
||||
${LIBRARY_NAME}_test
|
||||
test_${LIBRARY_NAME}
|
||||
actor_id_tests.c
|
||||
base_state.c
|
||||
byte_span_tests.c
|
||||
cmocka_utils.c
|
||||
enum_string_tests.c
|
||||
doc_state.c
|
||||
doc_tests.c
|
||||
item_tests.c
|
||||
group_state.c
|
||||
list_tests.c
|
||||
macro_utils.c
|
||||
main.c
|
||||
map_tests.c
|
||||
stack_utils.c
|
||||
str_utils.c
|
||||
ported_wasm/basic_tests.c
|
||||
ported_wasm/suite.c
|
||||
ported_wasm/sync_tests.c
|
||||
)
|
||||
|
||||
set_target_properties(${LIBRARY_NAME}_test PROPERTIES LINKER_LANGUAGE C)
|
||||
set_target_properties(test_${LIBRARY_NAME} PROPERTIES LINKER_LANGUAGE C)
|
||||
|
||||
if(WIN32)
|
||||
set(CMOCKA "cmocka::cmocka")
|
||||
else()
|
||||
set(CMOCKA "cmocka")
|
||||
endif()
|
||||
# \note An imported library's INTERFACE_INCLUDE_DIRECTORIES property can't
|
||||
# contain a non-existent path so its build-time include directory
|
||||
# must be specified for all of its dependent targets instead.
|
||||
target_include_directories(
|
||||
test_${LIBRARY_NAME}
|
||||
PRIVATE "$<BUILD_INTERFACE:${CBINDGEN_INCLUDEDIR}>"
|
||||
)
|
||||
|
||||
target_link_libraries(${LIBRARY_NAME}_test PRIVATE ${CMOCKA} ${LIBRARY_NAME})
|
||||
target_link_libraries(test_${LIBRARY_NAME} PRIVATE cmocka ${LIBRARY_NAME})
|
||||
|
||||
add_dependencies(${LIBRARY_NAME}_test ${BINDINGS_NAME}_artifacts)
|
||||
add_dependencies(test_${LIBRARY_NAME} ${LIBRARY_NAME}_artifacts)
|
||||
|
||||
if(BUILD_SHARED_LIBS AND WIN32)
|
||||
add_custom_command(
|
||||
TARGET ${LIBRARY_NAME}_test
|
||||
TARGET test_${LIBRARY_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:${LIBRARY_NAME}> $<TARGET_FILE_DIR:${LIBRARY_NAME}_test>
|
||||
COMMENT "Copying the DLL into the tests directory..."
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Copying the DLL built by Cargo into the test directory..."
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
add_test(NAME ${LIBRARY_NAME}_test COMMAND ${LIBRARY_NAME}_test)
|
||||
add_test(NAME test_${LIBRARY_NAME} COMMAND test_${LIBRARY_NAME})
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${LIBRARY_NAME}_test
|
||||
TARGET test_${LIBRARY_NAME}
|
||||
POST_BUILD
|
||||
COMMAND
|
||||
${CMAKE_CTEST_COMMAND} --config $<CONFIG> --output-on-failure
|
||||
|
|
|
@ -14,126 +14,99 @@
|
|||
#include "cmocka_utils.h"
|
||||
#include "str_utils.h"
|
||||
|
||||
/**
|
||||
* \brief State for a group of cmocka test cases.
|
||||
*/
|
||||
typedef struct {
|
||||
/** An actor ID as an array of bytes. */
|
||||
uint8_t* src;
|
||||
/** The count of bytes in \p src. */
|
||||
size_t count;
|
||||
/** A stack of results. */
|
||||
AMstack* stack;
|
||||
/** An actor ID as a hexadecimal string. */
|
||||
AMbyteSpan str;
|
||||
} DocState;
|
||||
size_t count;
|
||||
} GroupState;
|
||||
|
||||
static int group_setup(void** state) {
|
||||
DocState* doc_state = test_calloc(1, sizeof(DocState));
|
||||
doc_state->str = AMstr("000102030405060708090a0b0c0d0e0f");
|
||||
doc_state->count = doc_state->str.count / 2;
|
||||
doc_state->src = test_calloc(doc_state->count, sizeof(uint8_t));
|
||||
hex_to_bytes(doc_state->str.src, doc_state->src, doc_state->count);
|
||||
*state = doc_state;
|
||||
GroupState* group_state = test_calloc(1, sizeof(GroupState));
|
||||
group_state->str.src = "000102030405060708090a0b0c0d0e0f";
|
||||
group_state->str.count = strlen(group_state->str.src);
|
||||
group_state->count = group_state->str.count / 2;
|
||||
group_state->src = test_malloc(group_state->count);
|
||||
hex_to_bytes(group_state->str.src, group_state->src, group_state->count);
|
||||
*state = group_state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int group_teardown(void** state) {
|
||||
DocState* doc_state = *state;
|
||||
test_free(doc_state->src);
|
||||
AMstackFree(&doc_state->stack);
|
||||
test_free(doc_state);
|
||||
GroupState* group_state = *state;
|
||||
test_free(group_state->src);
|
||||
test_free(group_state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_AMactorIdFromBytes(void** state) {
|
||||
DocState* doc_state = *state;
|
||||
AMstack** stack_ptr = &doc_state->stack;
|
||||
/* Non-empty string. */
|
||||
AMresult* result = AMstackResult(stack_ptr, AMactorIdFromBytes(doc_state->src, doc_state->count), NULL, NULL);
|
||||
if (AMresultStatus(result) != AM_STATUS_OK) {
|
||||
fail_msg_view("%s", AMresultError(result));
|
||||
}
|
||||
assert_int_equal(AMresultSize(result), 1);
|
||||
AMitem* const item = AMresultItem(result);
|
||||
assert_int_equal(AMitemValType(item), AM_VAL_TYPE_ACTOR_ID);
|
||||
AMactorId const* actor_id;
|
||||
assert_true(AMitemToActorId(item, &actor_id));
|
||||
AMbyteSpan const bytes = AMactorIdBytes(actor_id);
|
||||
assert_int_equal(bytes.count, doc_state->count);
|
||||
assert_memory_equal(bytes.src, doc_state->src, bytes.count);
|
||||
/* Empty array. */
|
||||
/** \todo Find out if this is intentionally allowed. */
|
||||
result = AMstackResult(stack_ptr, AMactorIdFromBytes(doc_state->src, 0), NULL, NULL);
|
||||
if (AMresultStatus(result) != AM_STATUS_OK) {
|
||||
fail_msg_view("%s", AMresultError(result));
|
||||
}
|
||||
/* NULL array. */
|
||||
result = AMstackResult(stack_ptr, AMactorIdFromBytes(NULL, doc_state->count), NULL, NULL);
|
||||
if (AMresultStatus(result) == AM_STATUS_OK) {
|
||||
fail_msg("AMactorId from NULL.");
|
||||
}
|
||||
}
|
||||
|
||||
static void test_AMactorIdFromStr(void** state) {
|
||||
DocState* doc_state = *state;
|
||||
AMstack** stack_ptr = &doc_state->stack;
|
||||
AMresult* result = AMstackResult(stack_ptr, AMactorIdFromStr(doc_state->str), NULL, NULL);
|
||||
if (AMresultStatus(result) != AM_STATUS_OK) {
|
||||
fail_msg_view("%s", AMresultError(result));
|
||||
}
|
||||
assert_int_equal(AMresultSize(result), 1);
|
||||
AMitem* const item = AMresultItem(result);
|
||||
assert_int_equal(AMitemValType(item), AM_VAL_TYPE_ACTOR_ID);
|
||||
/* The hexadecimal string should've been decoded as identical bytes. */
|
||||
AMactorId const* actor_id;
|
||||
assert_true(AMitemToActorId(item, &actor_id));
|
||||
AMbyteSpan const bytes = AMactorIdBytes(actor_id);
|
||||
assert_int_equal(bytes.count, doc_state->count);
|
||||
assert_memory_equal(bytes.src, doc_state->src, bytes.count);
|
||||
/* The bytes should've been encoded as an identical hexadecimal string. */
|
||||
assert_true(AMitemToActorId(item, &actor_id));
|
||||
AMbyteSpan const str = AMactorIdStr(actor_id);
|
||||
assert_int_equal(str.count, doc_state->str.count);
|
||||
assert_memory_equal(str.src, doc_state->str.src, str.count);
|
||||
}
|
||||
|
||||
static void test_AMactorIdInit(void** state) {
|
||||
DocState* doc_state = *state;
|
||||
AMstack** stack_ptr = &doc_state->stack;
|
||||
static void test_AMactorIdInit() {
|
||||
AMresult* prior_result = NULL;
|
||||
AMbyteSpan prior_bytes = {NULL, 0};
|
||||
AMbyteSpan prior_str = {NULL, 0};
|
||||
AMresult* result = NULL;
|
||||
for (size_t i = 0; i != 11; ++i) {
|
||||
AMresult* result = AMstackResult(stack_ptr, AMactorIdInit(), NULL, NULL);
|
||||
result = AMactorIdInit();
|
||||
if (AMresultStatus(result) != AM_STATUS_OK) {
|
||||
fail_msg_view("%s", AMresultError(result));
|
||||
fail_msg_view("%s", AMerrorMessage(result));
|
||||
}
|
||||
assert_int_equal(AMresultSize(result), 1);
|
||||
AMitem* const item = AMresultItem(result);
|
||||
assert_int_equal(AMitemValType(item), AM_VAL_TYPE_ACTOR_ID);
|
||||
AMactorId const* actor_id;
|
||||
assert_true(AMitemToActorId(item, &actor_id));
|
||||
AMbyteSpan const bytes = AMactorIdBytes(actor_id);
|
||||
assert_true(AMitemToActorId(item, &actor_id));
|
||||
AMbyteSpan const str = AMactorIdStr(actor_id);
|
||||
AMvalue const value = AMresultValue(result);
|
||||
assert_int_equal(value.tag, AM_VALUE_ACTOR_ID);
|
||||
AMbyteSpan const bytes = AMactorIdBytes(value.actor_id);
|
||||
AMbyteSpan const str = AMactorIdStr(value.actor_id);
|
||||
if (prior_result) {
|
||||
size_t const max_byte_count = fmax(bytes.count, prior_bytes.count);
|
||||
assert_memory_not_equal(bytes.src, prior_bytes.src, max_byte_count);
|
||||
size_t const max_char_count = fmax(str.count, prior_str.count);
|
||||
assert_memory_not_equal(str.src, prior_str.src, max_char_count);
|
||||
AMfree(prior_result);
|
||||
}
|
||||
prior_result = result;
|
||||
prior_bytes = bytes;
|
||||
prior_str = str;
|
||||
}
|
||||
AMfree(result);
|
||||
}
|
||||
|
||||
static void test_AMactorIdInitBytes(void **state) {
|
||||
GroupState* group_state = *state;
|
||||
AMresult* const result = AMactorIdInitBytes(group_state->src, group_state->count);
|
||||
if (AMresultStatus(result) != AM_STATUS_OK) {
|
||||
fail_msg_view("%s", AMerrorMessage(result));
|
||||
}
|
||||
assert_int_equal(AMresultSize(result), 1);
|
||||
AMvalue const value = AMresultValue(result);
|
||||
assert_int_equal(value.tag, AM_VALUE_ACTOR_ID);
|
||||
AMbyteSpan const bytes = AMactorIdBytes(value.actor_id);
|
||||
assert_int_equal(bytes.count, group_state->count);
|
||||
assert_memory_equal(bytes.src, group_state->src, bytes.count);
|
||||
AMfree(result);
|
||||
}
|
||||
|
||||
static void test_AMactorIdInitStr(void **state) {
|
||||
GroupState* group_state = *state;
|
||||
AMresult* const result = AMactorIdInitStr(group_state->str);
|
||||
if (AMresultStatus(result) != AM_STATUS_OK) {
|
||||
fail_msg_view("%s", AMerrorMessage(result));
|
||||
}
|
||||
assert_int_equal(AMresultSize(result), 1);
|
||||
AMvalue const value = AMresultValue(result);
|
||||
assert_int_equal(value.tag, AM_VALUE_ACTOR_ID);
|
||||
/* The hexadecimal string should've been decoded as identical bytes. */
|
||||
AMbyteSpan const bytes = AMactorIdBytes(value.actor_id);
|
||||
assert_int_equal(bytes.count, group_state->count);
|
||||
assert_memory_equal(bytes.src, group_state->src, bytes.count);
|
||||
/* The bytes should've been encoded as an identical hexadecimal string. */
|
||||
AMbyteSpan const str = AMactorIdStr(value.actor_id);
|
||||
assert_int_equal(str.count, group_state->str.count);
|
||||
assert_memory_equal(str.src, group_state->str.src, str.count);
|
||||
AMfree(result);
|
||||
}
|
||||
|
||||
int run_actor_id_tests(void) {
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_AMactorIdFromBytes),
|
||||
cmocka_unit_test(test_AMactorIdFromStr),
|
||||
cmocka_unit_test(test_AMactorIdInit),
|
||||
cmocka_unit_test(test_AMactorIdInitBytes),
|
||||
cmocka_unit_test(test_AMactorIdInitStr),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, group_setup, group_teardown);
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
/* local */
|
||||
#include "base_state.h"
|
||||
|
||||
int setup_base(void** state) {
|
||||
BaseState* base_state = calloc(1, sizeof(BaseState));
|
||||
*state = base_state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int teardown_base(void** state) {
|
||||
BaseState* base_state = *state;
|
||||
AMstackFree(&base_state->stack);
|
||||
free(base_state);
|
||||
return 0;
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
#ifndef TESTS_BASE_STATE_H
|
||||
#define TESTS_BASE_STATE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* local */
|
||||
#include <automerge-c/automerge.h>
|
||||
#include <automerge-c/utils/stack.h>
|
||||
|
||||
/**
|
||||
* \struct BaseState
|
||||
* \brief The shared state for one or more cmocka test cases.
|
||||
*/
|
||||
typedef struct {
|
||||
/** A stack of results. */
|
||||
AMstack* stack;
|
||||
} BaseState;
|
||||
|
||||
/**
|
||||
* \memberof BaseState
|
||||
* \brief Sets up the shared state for one or more cmocka test cases.
|
||||
*
|
||||
* \param[in,out] state A pointer to a pointer to a `BaseState` struct.
|
||||
* \pre \p state `!= NULL`.
|
||||
* \warning The `BaseState` struct returned through \p state must be
|
||||
* passed to `teardown_base()` in order to avoid a memory leak.
|
||||
*/
|
||||
int setup_base(void** state);
|
||||
|
||||
/**
|
||||
* \memberof BaseState
|
||||
* \brief Tears down the shared state for one or more cmocka test cases.
|
||||
*
|
||||
* \param[in] state A pointer to a pointer to a `BaseState` struct.
|
||||
* \pre \p state `!= NULL`.
|
||||
*/
|
||||
int teardown_base(void** state);
|
||||
|
||||
#endif /* TESTS_BASE_STATE_H */
|
|
@ -1,119 +0,0 @@
|
|||
#include <setjmp.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* third-party */
|
||||
#include <cmocka.h>
|
||||
|
||||
/* local */
|
||||
#include <automerge-c/automerge.h>
|
||||
#include <automerge-c/utils/string.h>
|
||||
|
||||
static void test_AMbytes(void** state) {
|
||||
static char const DATA[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
|
||||
|
||||
AMbyteSpan bytes = AMbytes(DATA, sizeof(DATA));
|
||||
assert_int_equal(bytes.count, sizeof(DATA));
|
||||
assert_memory_equal(bytes.src, DATA, bytes.count);
|
||||
assert_ptr_equal(bytes.src, DATA);
|
||||
/* Empty view */
|
||||
bytes = AMbytes(DATA, 0);
|
||||
assert_int_equal(bytes.count, 0);
|
||||
assert_ptr_equal(bytes.src, DATA);
|
||||
/* Invalid array */
|
||||
bytes = AMbytes(NULL, SIZE_MAX);
|
||||
assert_int_not_equal(bytes.count, SIZE_MAX);
|
||||
assert_int_equal(bytes.count, 0);
|
||||
assert_ptr_equal(bytes.src, NULL);
|
||||
}
|
||||
|
||||
static void test_AMstr(void** state) {
|
||||
AMbyteSpan str = AMstr("abcdefghijkl");
|
||||
assert_int_equal(str.count, strlen("abcdefghijkl"));
|
||||
assert_memory_equal(str.src, "abcdefghijkl", str.count);
|
||||
/* Empty string */
|
||||
static char const* const EMPTY = "";
|
||||
|
||||
str = AMstr(EMPTY);
|
||||
assert_int_equal(str.count, 0);
|
||||
assert_ptr_equal(str.src, EMPTY);
|
||||
/* Invalid string */
|
||||
str = AMstr(NULL);
|
||||
assert_int_equal(str.count, 0);
|
||||
assert_ptr_equal(str.src, NULL);
|
||||
}
|
||||
|
||||
static void test_AMstrCmp(void** state) {
|
||||
/* Length ordering */
|
||||
assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("abcdefghijkl")), -1);
|
||||
assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("abcdefghijkl")), 0);
|
||||
assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("abcdef")), 1);
|
||||
/* Lexicographical ordering */
|
||||
assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("ghijkl")), -1);
|
||||
assert_int_equal(AMstrCmp(AMstr("ghijkl"), AMstr("abcdef")), 1);
|
||||
/* Case ordering */
|
||||
assert_int_equal(AMstrCmp(AMstr("ABCDEFGHIJKL"), AMstr("abcdefghijkl")), -1);
|
||||
assert_int_equal(AMstrCmp(AMstr("ABCDEFGHIJKL"), AMstr("ABCDEFGHIJKL")), 0);
|
||||
assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("ABCDEFGHIJKL")), 1);
|
||||
assert_int_equal(AMstrCmp(AMstr("ABCDEFGHIJKL"), AMstr("abcdef")), -1);
|
||||
assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("ABCDEFGHIJKL")), 1);
|
||||
assert_int_equal(AMstrCmp(AMstr("GHIJKL"), AMstr("abcdef")), -1);
|
||||
assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("GHIJKL")), 1);
|
||||
/* NUL character inclusion */
|
||||
static char const SRC[] = {'a', 'b', 'c', 'd', 'e', 'f', '\0', 'g', 'h', 'i', 'j', 'k', 'l'};
|
||||
static AMbyteSpan const NUL_STR = {.src = SRC, .count = 13};
|
||||
|
||||
assert_int_equal(AMstrCmp(AMstr("abcdef"), NUL_STR), -1);
|
||||
assert_int_equal(AMstrCmp(NUL_STR, NUL_STR), 0);
|
||||
assert_int_equal(AMstrCmp(NUL_STR, AMstr("abcdef")), 1);
|
||||
/* Empty string */
|
||||
assert_int_equal(AMstrCmp(AMstr(""), AMstr("abcdefghijkl")), -1);
|
||||
assert_int_equal(AMstrCmp(AMstr(""), AMstr("")), 0);
|
||||
assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("")), 1);
|
||||
/* Invalid string */
|
||||
assert_int_equal(AMstrCmp(AMstr(NULL), AMstr("abcdefghijkl")), -1);
|
||||
assert_int_equal(AMstrCmp(AMstr(NULL), AMstr(NULL)), 0);
|
||||
assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr(NULL)), 1);
|
||||
}
|
||||
|
||||
static void test_AMstrdup(void** state) {
|
||||
static char const SRC[] = {'a', 'b', 'c', '\0', 'd', 'e', 'f', '\0', 'g', 'h', 'i', '\0', 'j', 'k', 'l'};
|
||||
static AMbyteSpan const NUL_STR = {.src = SRC, .count = 15};
|
||||
|
||||
/* Default substitution ("\\0") for NUL */
|
||||
char* dup = AMstrdup(NUL_STR, NULL);
|
||||
assert_int_equal(strlen(dup), 18);
|
||||
assert_string_equal(dup, "abc\\0def\\0ghi\\0jkl");
|
||||
free(dup);
|
||||
/* Arbitrary substitution for NUL */
|
||||
dup = AMstrdup(NUL_STR, ":-O");
|
||||
assert_int_equal(strlen(dup), 21);
|
||||
assert_string_equal(dup, "abc:-Odef:-Oghi:-Ojkl");
|
||||
free(dup);
|
||||
/* Empty substitution for NUL */
|
||||
dup = AMstrdup(NUL_STR, "");
|
||||
assert_int_equal(strlen(dup), 12);
|
||||
assert_string_equal(dup, "abcdefghijkl");
|
||||
free(dup);
|
||||
/* Empty string */
|
||||
dup = AMstrdup(AMstr(""), NULL);
|
||||
assert_int_equal(strlen(dup), 0);
|
||||
assert_string_equal(dup, "");
|
||||
free(dup);
|
||||
/* Invalid string */
|
||||
assert_null(AMstrdup(AMstr(NULL), NULL));
|
||||
}
|
||||
|
||||
int run_byte_span_tests(void) {
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_AMbytes),
|
||||
cmocka_unit_test(test_AMstr),
|
||||
cmocka_unit_test(test_AMstrCmp),
|
||||
cmocka_unit_test(test_AMstrdup),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
#include <setjmp.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* third-party */
|
||||
#include <automerge-c/utils/enum_string.h>
|
||||
#include <automerge-c/utils/stack_callback_data.h>
|
||||
#include <automerge-c/utils/string.h>
|
||||
#include <cmocka.h>
|
||||
|
||||
/* local */
|
||||
#include "cmocka_utils.h"
|
||||
|
||||
/**
|
||||
* \brief Assert that the given expression is true and report failure in terms
|
||||
* of a line number within a file.
|
||||
*
|
||||
* \param[in] c An expression.
|
||||
* \param[in] file A file's full path string.
|
||||
* \param[in] line A line number.
|
||||
*/
|
||||
#define assert_true_where(c, file, line) _assert_true(cast_ptr_to_largest_integral_type(c), #c, file, line)
|
||||
|
||||
/**
|
||||
* \brief Assert that the given pointer is non-NULL and report failure in terms
|
||||
* of a line number within a file.
|
||||
*
|
||||
* \param[in] c An expression.
|
||||
* \param[in] file A file's full path string.
|
||||
* \param[in] line A line number.
|
||||
*/
|
||||
#define assert_non_null_where(c, file, line) assert_true_where(c, file, line)
|
||||
|
||||
/**
|
||||
* \brief Forces the test to fail immediately and quit, printing the reason in
|
||||
* terms of a line number within a file.
|
||||
*
|
||||
* \param[in] msg A message string into which \p str is interpolated.
|
||||
* \param[in] str An owned string.
|
||||
* \param[in] file A file's full path string.
|
||||
* \param[in] line A line number.
|
||||
*/
|
||||
#define fail_msg_where(msg, str, file, line) \
|
||||
do { \
|
||||
print_error("ERROR: " msg "\n", str); \
|
||||
_fail(file, line); \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* \brief Forces the test to fail immediately and quit, printing the reason in
|
||||
* terms of a line number within a file.
|
||||
*
|
||||
* \param[in] msg A message string into which \p view.src is interpolated.
|
||||
* \param[in] view A UTF-8 string view as an `AMbyteSpan` struct.
|
||||
* \param[in] file A file's full path string.
|
||||
* \param[in] line A line number.
|
||||
*/
|
||||
#define fail_msg_view_where(msg, view, file, line) \
|
||||
do { \
|
||||
char* const str = AMstrdup(view, NULL); \
|
||||
print_error("ERROR: " msg "\n", str); \
|
||||
free(str); \
|
||||
_fail(file, line); \
|
||||
} while (0)
|
||||
|
||||
bool cmocka_cb(AMstack** stack, void* data) {
|
||||
assert_non_null(data);
|
||||
AMstackCallbackData* const sc_data = (AMstackCallbackData*)data;
|
||||
assert_non_null_where(stack, sc_data->file, sc_data->line);
|
||||
assert_non_null_where(*stack, sc_data->file, sc_data->line);
|
||||
assert_non_null_where((*stack)->result, sc_data->file, sc_data->line);
|
||||
if (AMresultStatus((*stack)->result) != AM_STATUS_OK) {
|
||||
fail_msg_view_where("%s", AMresultError((*stack)->result), sc_data->file, sc_data->line);
|
||||
return false;
|
||||
}
|
||||
/* Test that the types of all item values are members of the mask. */
|
||||
AMitems items = AMresultItems((*stack)->result);
|
||||
AMitem* item = NULL;
|
||||
while ((item = AMitemsNext(&items, 1)) != NULL) {
|
||||
AMvalType const tag = AMitemValType(item);
|
||||
if (!(tag & sc_data->bitmask)) {
|
||||
fail_msg_where("Unexpected value type `%s`.", AMvalTypeToString(tag), sc_data->file, sc_data->line);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -1,42 +1,22 @@
|
|||
#ifndef TESTS_CMOCKA_UTILS_H
|
||||
#define TESTS_CMOCKA_UTILS_H
|
||||
#ifndef CMOCKA_UTILS_H
|
||||
#define CMOCKA_UTILS_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* third-party */
|
||||
#include <automerge-c/utils/string.h>
|
||||
#include <cmocka.h>
|
||||
|
||||
/* local */
|
||||
#include "base_state.h"
|
||||
|
||||
/**
|
||||
* \brief Forces the test to fail immediately and quit, printing the reason.
|
||||
*
|
||||
* \param[in] msg A message string into which \p view.src is interpolated.
|
||||
* \param[in] view A UTF-8 string view as an `AMbyteSpan` struct.
|
||||
* \param[in] view A string view as an `AMbyteSpan` struct.
|
||||
*/
|
||||
#define fail_msg_view(msg, view) \
|
||||
do { \
|
||||
char* const c_str = AMstrdup(view, NULL); \
|
||||
print_error("ERROR: " msg "\n", c_str); \
|
||||
free(c_str); \
|
||||
#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)
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* \brief Validates the top result in a stack based upon the parameters
|
||||
* specified within the given data structure and reports violations
|
||||
* using cmocka assertions.
|
||||
*
|
||||
* \param[in,out] stack A pointer to a pointer to an `AMstack` struct.
|
||||
* \param[in] data A pointer to an owned `AMpushData` struct.
|
||||
* \return `true` if the top `AMresult` struct in \p stack is valid, `false`
|
||||
* otherwise.
|
||||
* \pre \p stack `!= NULL`.
|
||||
* \pre \p data `!= NULL`.
|
||||
*/
|
||||
bool cmocka_cb(AMstack** stack, void* data);
|
||||
|
||||
#endif /* TESTS_CMOCKA_UTILS_H */
|
||||
#endif /* CMOCKA_UTILS_H */
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
#include <setjmp.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* third-party */
|
||||
#include <cmocka.h>
|
||||
|
||||
/* local */
|
||||
#include <automerge-c/utils/stack_callback_data.h>
|
||||
#include "cmocka_utils.h"
|
||||
#include "doc_state.h"
|
||||
|
||||
int setup_doc(void** state) {
|
||||
DocState* doc_state = test_calloc(1, sizeof(DocState));
|
||||
setup_base((void**)&doc_state->base_state);
|
||||
AMitemToDoc(AMstackItem(&doc_state->base_state->stack, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)),
|
||||
&doc_state->doc);
|
||||
*state = doc_state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int teardown_doc(void** state) {
|
||||
DocState* doc_state = *state;
|
||||
teardown_base((void**)&doc_state->base_state);
|
||||
test_free(doc_state);
|
||||
return 0;
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
#ifndef TESTS_DOC_STATE_H
|
||||
#define TESTS_DOC_STATE_H
|
||||
|
||||
/* local */
|
||||
#include <automerge-c/automerge.h>
|
||||
#include "base_state.h"
|
||||
|
||||
typedef struct {
|
||||
BaseState* base_state;
|
||||
AMdoc* doc;
|
||||
} DocState;
|
||||
|
||||
int setup_doc(void** state);
|
||||
|
||||
int teardown_doc(void** state);
|
||||
|
||||
#endif /* TESTS_DOC_STATE_H */
|
|
@ -9,14 +9,12 @@
|
|||
|
||||
/* local */
|
||||
#include <automerge-c/automerge.h>
|
||||
#include <automerge-c/utils/stack_callback_data.h>
|
||||
#include "base_state.h"
|
||||
#include "cmocka_utils.h"
|
||||
#include "doc_state.h"
|
||||
#include "group_state.h"
|
||||
#include "stack_utils.h"
|
||||
#include "str_utils.h"
|
||||
|
||||
typedef struct {
|
||||
DocState* doc_state;
|
||||
GroupState* group_state;
|
||||
AMbyteSpan actor_id_str;
|
||||
uint8_t* actor_id_bytes;
|
||||
size_t actor_id_size;
|
||||
|
@ -24,7 +22,7 @@ typedef struct {
|
|||
|
||||
static int setup(void** state) {
|
||||
TestState* test_state = test_calloc(1, sizeof(TestState));
|
||||
setup_doc((void**)&test_state->doc_state);
|
||||
group_setup((void**)&test_state->group_state);
|
||||
test_state->actor_id_str.src = "000102030405060708090a0b0c0d0e0f";
|
||||
test_state->actor_id_str.count = strlen(test_state->actor_id_str.src);
|
||||
test_state->actor_id_size = test_state->actor_id_str.count / 2;
|
||||
|
@ -36,195 +34,204 @@ static int setup(void** state) {
|
|||
|
||||
static int teardown(void** state) {
|
||||
TestState* test_state = *state;
|
||||
teardown_doc((void**)&test_state->doc_state);
|
||||
group_teardown((void**)&test_state->group_state);
|
||||
test_free(test_state->actor_id_bytes);
|
||||
test_free(test_state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_AMkeys_empty(void** state) {
|
||||
TestState* test_state = *state;
|
||||
AMstack** stack_ptr = &test_state->doc_state->base_state->stack;
|
||||
AMdoc* doc;
|
||||
assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc));
|
||||
AMitems forward = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
assert_int_equal(AMitemsSize(&forward), 0);
|
||||
AMitems reverse = AMitemsReversed(&forward);
|
||||
assert_int_equal(AMitemsSize(&reverse), 0);
|
||||
assert_null(AMitemsNext(&forward, 1));
|
||||
assert_null(AMitemsPrev(&forward, 1));
|
||||
assert_null(AMitemsNext(&reverse, 1));
|
||||
assert_null(AMitemsPrev(&reverse, 1));
|
||||
static void test_AMkeys_empty() {
|
||||
AMresultStack* stack = NULL;
|
||||
AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc;
|
||||
AMstrs forward = AMpush(&stack,
|
||||
AMkeys(doc, AM_ROOT, NULL),
|
||||
AM_VALUE_STRS,
|
||||
cmocka_cb).strs;
|
||||
assert_int_equal(AMstrsSize(&forward), 0);
|
||||
AMstrs reverse = AMstrsReversed(&forward);
|
||||
assert_int_equal(AMstrsSize(&reverse), 0);
|
||||
assert_null(AMstrsNext(&forward, 1).src);
|
||||
assert_null(AMstrsPrev(&forward, 1).src);
|
||||
assert_null(AMstrsNext(&reverse, 1).src);
|
||||
assert_null(AMstrsPrev(&reverse, 1).src);
|
||||
AMfreeStack(&stack);
|
||||
}
|
||||
|
||||
static void test_AMkeys_list(void** state) {
|
||||
TestState* test_state = *state;
|
||||
AMstack** stack_ptr = &test_state->doc_state->base_state->stack;
|
||||
AMdoc* doc;
|
||||
assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc));
|
||||
AMobjId const* const list =
|
||||
AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb,
|
||||
AMexpect(AM_VAL_TYPE_OBJ_TYPE)));
|
||||
AMstackItem(NULL, AMlistPutInt(doc, list, 0, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMstackItem(NULL, AMlistPutInt(doc, list, 1, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMstackItem(NULL, AMlistPutInt(doc, list, 2, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMitems forward = AMstackItems(stack_ptr, AMkeys(doc, list, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR));
|
||||
assert_int_equal(AMitemsSize(&forward), 3);
|
||||
AMitems reverse = AMitemsReversed(&forward);
|
||||
assert_int_equal(AMitemsSize(&reverse), 3);
|
||||
static void test_AMkeys_list() {
|
||||
AMresultStack* stack = NULL;
|
||||
AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc;
|
||||
AMobjId const* const list = AMpush(
|
||||
&stack,
|
||||
AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),
|
||||
AM_VALUE_OBJ_ID,
|
||||
cmocka_cb).obj_id;
|
||||
AMfree(AMlistPutInt(doc, list, 0, true, 0));
|
||||
AMfree(AMlistPutInt(doc, list, 1, true, 0));
|
||||
AMfree(AMlistPutInt(doc, list, 2, true, 0));
|
||||
AMstrs forward = AMpush(&stack,
|
||||
AMkeys(doc, list, NULL),
|
||||
AM_VALUE_STRS,
|
||||
cmocka_cb).strs;
|
||||
assert_int_equal(AMstrsSize(&forward), 3);
|
||||
AMstrs reverse = AMstrsReversed(&forward);
|
||||
assert_int_equal(AMstrsSize(&reverse), 3);
|
||||
/* Forward iterator forward. */
|
||||
AMbyteSpan str;
|
||||
assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str));
|
||||
AMbyteSpan str = AMstrsNext(&forward, 1);
|
||||
assert_ptr_equal(strstr(str.src, "2@"), str.src);
|
||||
assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str));
|
||||
str = AMstrsNext(&forward, 1);
|
||||
assert_ptr_equal(strstr(str.src, "3@"), str.src);
|
||||
assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str));
|
||||
str = AMstrsNext(&forward, 1);
|
||||
assert_ptr_equal(strstr(str.src, "4@"), str.src);
|
||||
assert_null(AMitemsNext(&forward, 1));
|
||||
assert_null(AMstrsNext(&forward, 1).src);
|
||||
// /* Forward iterator reverse. */
|
||||
assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str));
|
||||
str = AMstrsPrev(&forward, 1);
|
||||
assert_ptr_equal(strstr(str.src, "4@"), str.src);
|
||||
assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str));
|
||||
str = AMstrsPrev(&forward, 1);
|
||||
assert_ptr_equal(strstr(str.src, "3@"), str.src);
|
||||
assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str));
|
||||
str = AMstrsPrev(&forward, 1);
|
||||
assert_ptr_equal(strstr(str.src, "2@"), str.src);
|
||||
assert_null(AMitemsPrev(&forward, 1));
|
||||
assert_null(AMstrsPrev(&forward, 1).src);
|
||||
/* Reverse iterator forward. */
|
||||
assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str));
|
||||
str = AMstrsNext(&reverse, 1);
|
||||
assert_ptr_equal(strstr(str.src, "4@"), str.src);
|
||||
assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str));
|
||||
str = AMstrsNext(&reverse, 1);
|
||||
assert_ptr_equal(strstr(str.src, "3@"), str.src);
|
||||
assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str));
|
||||
str = AMstrsNext(&reverse, 1);
|
||||
assert_ptr_equal(strstr(str.src, "2@"), str.src);
|
||||
assert_null(AMitemsNext(&reverse, 1));
|
||||
assert_null(AMstrsNext(&reverse, 1).src);
|
||||
/* Reverse iterator reverse. */
|
||||
assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str));
|
||||
str = AMstrsPrev(&reverse, 1);
|
||||
assert_ptr_equal(strstr(str.src, "2@"), str.src);
|
||||
assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str));
|
||||
str = AMstrsPrev(&reverse, 1);
|
||||
assert_ptr_equal(strstr(str.src, "3@"), str.src);
|
||||
assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str));
|
||||
str = AMstrsPrev(&reverse, 1);
|
||||
assert_ptr_equal(strstr(str.src, "4@"), str.src);
|
||||
assert_null(AMitemsPrev(&reverse, 1));
|
||||
assert_null(AMstrsPrev(&reverse, 1).src);
|
||||
AMfreeStack(&stack);
|
||||
}
|
||||
|
||||
static void test_AMkeys_map(void** state) {
|
||||
TestState* test_state = *state;
|
||||
AMstack** stack_ptr = &test_state->doc_state->base_state->stack;
|
||||
AMdoc* doc;
|
||||
assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc));
|
||||
AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("one"), 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("two"), 2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("three"), 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMitems forward = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR));
|
||||
assert_int_equal(AMitemsSize(&forward), 3);
|
||||
AMitems reverse = AMitemsReversed(&forward);
|
||||
assert_int_equal(AMitemsSize(&reverse), 3);
|
||||
static void test_AMkeys_map() {
|
||||
AMresultStack* stack = NULL;
|
||||
AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc;
|
||||
AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("one"), 1));
|
||||
AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("two"), 2));
|
||||
AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("three"), 3));
|
||||
AMstrs forward = AMpush(&stack,
|
||||
AMkeys(doc, AM_ROOT, NULL),
|
||||
AM_VALUE_STRS,
|
||||
cmocka_cb).strs;
|
||||
assert_int_equal(AMstrsSize(&forward), 3);
|
||||
AMstrs reverse = AMstrsReversed(&forward);
|
||||
assert_int_equal(AMstrsSize(&reverse), 3);
|
||||
/* Forward iterator forward. */
|
||||
AMbyteSpan str;
|
||||
assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str));
|
||||
AMbyteSpan str = AMstrsNext(&forward, 1);
|
||||
assert_int_equal(str.count, 3);
|
||||
assert_memory_equal(str.src, "one", str.count);
|
||||
assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str));
|
||||
str = AMstrsNext(&forward, 1);
|
||||
assert_int_equal(str.count, 5);
|
||||
assert_memory_equal(str.src, "three", str.count);
|
||||
assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str));
|
||||
str = AMstrsNext(&forward, 1);
|
||||
assert_int_equal(str.count, 3);
|
||||
assert_memory_equal(str.src, "two", str.count);
|
||||
assert_null(AMitemsNext(&forward, 1));
|
||||
assert_null(AMstrsNext(&forward, 1).src);
|
||||
/* Forward iterator reverse. */
|
||||
assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str));
|
||||
str = AMstrsPrev(&forward, 1);
|
||||
assert_int_equal(str.count, 3);
|
||||
assert_memory_equal(str.src, "two", str.count);
|
||||
assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str));
|
||||
str = AMstrsPrev(&forward, 1);
|
||||
assert_int_equal(str.count, 5);
|
||||
assert_memory_equal(str.src, "three", str.count);
|
||||
assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str));
|
||||
str = AMstrsPrev(&forward, 1);
|
||||
assert_int_equal(str.count, 3);
|
||||
assert_memory_equal(str.src, "one", str.count);
|
||||
assert_null(AMitemsPrev(&forward, 1));
|
||||
assert_null(AMstrsPrev(&forward, 1).src);
|
||||
/* Reverse iterator forward. */
|
||||
assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str));
|
||||
str = AMstrsNext(&reverse, 1);
|
||||
assert_int_equal(str.count, 3);
|
||||
assert_memory_equal(str.src, "two", str.count);
|
||||
assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str));
|
||||
str = AMstrsNext(&reverse, 1);
|
||||
assert_int_equal(str.count, 5);
|
||||
assert_memory_equal(str.src, "three", str.count);
|
||||
assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str));
|
||||
str = AMstrsNext(&reverse, 1);
|
||||
assert_int_equal(str.count, 3);
|
||||
assert_memory_equal(str.src, "one", str.count);
|
||||
assert_null(AMitemsNext(&reverse, 1));
|
||||
assert_null(AMstrsNext(&reverse, 1).src);
|
||||
/* Reverse iterator reverse. */
|
||||
assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str));
|
||||
str = AMstrsPrev(&reverse, 1);
|
||||
assert_int_equal(str.count, 3);
|
||||
assert_memory_equal(str.src, "one", str.count);
|
||||
assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str));
|
||||
str = AMstrsPrev(&reverse, 1);
|
||||
assert_int_equal(str.count, 5);
|
||||
assert_memory_equal(str.src, "three", str.count);
|
||||
assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str));
|
||||
str = AMstrsPrev(&reverse, 1);
|
||||
assert_int_equal(str.count, 3);
|
||||
assert_memory_equal(str.src, "two", str.count);
|
||||
assert_null(AMitemsPrev(&reverse, 1));
|
||||
assert_null(AMstrsPrev(&reverse, 1).src);
|
||||
AMfreeStack(&stack);
|
||||
}
|
||||
|
||||
static void test_AMputActor_bytes(void** state) {
|
||||
static void test_AMputActor_bytes(void **state) {
|
||||
TestState* test_state = *state;
|
||||
AMstack** stack_ptr = &test_state->doc_state->base_state->stack;
|
||||
AMactorId const* actor_id;
|
||||
assert_true(AMitemToActorId(
|
||||
AMstackItem(stack_ptr, AMactorIdFromBytes(test_state->actor_id_bytes, test_state->actor_id_size), cmocka_cb,
|
||||
AMexpect(AM_VAL_TYPE_ACTOR_ID)),
|
||||
&actor_id));
|
||||
AMstackItem(NULL, AMsetActorId(test_state->doc_state->doc, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
assert_true(AMitemToActorId(
|
||||
AMstackItem(stack_ptr, AMgetActorId(test_state->doc_state->doc), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)),
|
||||
&actor_id));
|
||||
AMactorId const* actor_id = AMpush(&test_state->group_state->stack,
|
||||
AMactorIdInitBytes(
|
||||
test_state->actor_id_bytes,
|
||||
test_state->actor_id_size),
|
||||
AM_VALUE_ACTOR_ID,
|
||||
cmocka_cb).actor_id;
|
||||
AMfree(AMsetActorId(test_state->group_state->doc, actor_id));
|
||||
actor_id = AMpush(&test_state->group_state->stack,
|
||||
AMgetActorId(test_state->group_state->doc),
|
||||
AM_VALUE_ACTOR_ID,
|
||||
cmocka_cb).actor_id;
|
||||
AMbyteSpan const bytes = AMactorIdBytes(actor_id);
|
||||
assert_int_equal(bytes.count, test_state->actor_id_size);
|
||||
assert_memory_equal(bytes.src, test_state->actor_id_bytes, bytes.count);
|
||||
}
|
||||
|
||||
static void test_AMputActor_str(void** state) {
|
||||
static void test_AMputActor_str(void **state) {
|
||||
TestState* test_state = *state;
|
||||
AMstack** stack_ptr = &test_state->doc_state->base_state->stack;
|
||||
AMactorId const* actor_id;
|
||||
assert_true(AMitemToActorId(
|
||||
AMstackItem(stack_ptr, AMactorIdFromStr(test_state->actor_id_str), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)),
|
||||
&actor_id));
|
||||
AMstackItem(NULL, AMsetActorId(test_state->doc_state->doc, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
assert_true(AMitemToActorId(
|
||||
AMstackItem(stack_ptr, AMgetActorId(test_state->doc_state->doc), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)),
|
||||
&actor_id));
|
||||
AMactorId const* actor_id = AMpush(&test_state->group_state->stack,
|
||||
AMactorIdInitStr(test_state->actor_id_str),
|
||||
AM_VALUE_ACTOR_ID,
|
||||
cmocka_cb).actor_id;
|
||||
AMfree(AMsetActorId(test_state->group_state->doc, actor_id));
|
||||
actor_id = AMpush(&test_state->group_state->stack,
|
||||
AMgetActorId(test_state->group_state->doc),
|
||||
AM_VALUE_ACTOR_ID,
|
||||
cmocka_cb).actor_id;
|
||||
AMbyteSpan const str = AMactorIdStr(actor_id);
|
||||
assert_int_equal(str.count, test_state->actor_id_str.count);
|
||||
assert_memory_equal(str.src, test_state->actor_id_str.src, str.count);
|
||||
}
|
||||
|
||||
static void test_AMspliceText(void** state) {
|
||||
TestState* test_state = *state;
|
||||
AMstack** stack_ptr = &test_state->doc_state->base_state->stack;
|
||||
AMdoc* doc;
|
||||
assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc));
|
||||
AMobjId const* const text =
|
||||
AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), cmocka_cb,
|
||||
AMexpect(AM_VAL_TYPE_OBJ_TYPE)));
|
||||
AMstackItem(NULL, AMspliceText(doc, text, 0, 0, AMstr("one + ")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMstackItem(NULL, AMspliceText(doc, text, 4, 2, AMstr("two = ")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMstackItem(NULL, AMspliceText(doc, text, 8, 2, AMstr("three")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMbyteSpan str;
|
||||
assert_true(
|
||||
AMitemToStr(AMstackItem(stack_ptr, AMtext(doc, text, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str));
|
||||
assert_int_equal(str.count, strlen("one two three"));
|
||||
assert_memory_equal(str.src, "one two three", str.count);
|
||||
static void test_AMspliceText() {
|
||||
AMresultStack* stack = NULL;
|
||||
AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc;
|
||||
AMobjId const* const text = AMpush(&stack,
|
||||
AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT),
|
||||
AM_VALUE_OBJ_ID,
|
||||
cmocka_cb).obj_id;
|
||||
AMfree(AMspliceText(doc, text, 0, 0, AMstr("one + ")));
|
||||
AMfree(AMspliceText(doc, text, 4, 2, AMstr("two = ")));
|
||||
AMfree(AMspliceText(doc, text, 8, 2, AMstr("three")));
|
||||
AMbyteSpan const str = AMpush(&stack,
|
||||
AMtext(doc, text, NULL),
|
||||
AM_VALUE_STR,
|
||||
cmocka_cb).str;
|
||||
static char const* const STR_VALUE = "one two three";
|
||||
assert_int_equal(str.count, strlen(STR_VALUE));
|
||||
assert_memory_equal(str.src, STR_VALUE, str.count);
|
||||
AMfreeStack(&stack);
|
||||
}
|
||||
|
||||
int run_doc_tests(void) {
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test_setup_teardown(test_AMkeys_empty, setup, teardown),
|
||||
cmocka_unit_test_setup_teardown(test_AMkeys_list, setup, teardown),
|
||||
cmocka_unit_test_setup_teardown(test_AMkeys_map, setup, teardown),
|
||||
cmocka_unit_test(test_AMkeys_empty),
|
||||
cmocka_unit_test(test_AMkeys_list),
|
||||
cmocka_unit_test(test_AMkeys_map),
|
||||
cmocka_unit_test_setup_teardown(test_AMputActor_bytes, setup, teardown),
|
||||
cmocka_unit_test_setup_teardown(test_AMputActor_str, setup, teardown),
|
||||
cmocka_unit_test_setup_teardown(test_AMspliceText, setup, teardown),
|
||||
cmocka_unit_test(test_AMspliceText),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
|
|
|
@ -1,148 +0,0 @@
|
|||
#include <setjmp.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* third-party */
|
||||
#include <cmocka.h>
|
||||
|
||||
/* local */
|
||||
#include <automerge-c/automerge.h>
|
||||
#include <automerge-c/utils/enum_string.h>
|
||||
|
||||
#define assert_to_string(function, tag) assert_string_equal(function(tag), #tag)
|
||||
|
||||
#define assert_from_string(function, type, tag) \
|
||||
do { \
|
||||
type out; \
|
||||
assert_true(function(&out, #tag)); \
|
||||
assert_int_equal(out, tag); \
|
||||
} while (0)
|
||||
|
||||
static void test_AMidxTypeToString(void** state) {
|
||||
assert_to_string(AMidxTypeToString, AM_IDX_TYPE_DEFAULT);
|
||||
assert_to_string(AMidxTypeToString, AM_IDX_TYPE_KEY);
|
||||
assert_to_string(AMidxTypeToString, AM_IDX_TYPE_POS);
|
||||
/* Zero tag */
|
||||
assert_string_equal(AMidxTypeToString(0), "AM_IDX_TYPE_DEFAULT");
|
||||
/* Invalid tag */
|
||||
assert_string_equal(AMidxTypeToString(-1), "???");
|
||||
}
|
||||
|
||||
static void test_AMidxTypeFromString(void** state) {
|
||||
assert_from_string(AMidxTypeFromString, AMidxType, AM_IDX_TYPE_DEFAULT);
|
||||
assert_from_string(AMidxTypeFromString, AMidxType, AM_IDX_TYPE_KEY);
|
||||
assert_from_string(AMidxTypeFromString, AMidxType, AM_IDX_TYPE_POS);
|
||||
/* Invalid tag */
|
||||
AMidxType out = -1;
|
||||
assert_false(AMidxTypeFromString(&out, "???"));
|
||||
assert_int_equal(out, (AMidxType)-1);
|
||||
}
|
||||
|
||||
static void test_AMobjTypeToString(void** state) {
|
||||
assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_DEFAULT);
|
||||
assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_LIST);
|
||||
assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_MAP);
|
||||
assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_TEXT);
|
||||
/* Zero tag */
|
||||
assert_string_equal(AMobjTypeToString(0), "AM_OBJ_TYPE_DEFAULT");
|
||||
/* Invalid tag */
|
||||
assert_string_equal(AMobjTypeToString(-1), "???");
|
||||
}
|
||||
|
||||
static void test_AMobjTypeFromString(void** state) {
|
||||
assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_DEFAULT);
|
||||
assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_LIST);
|
||||
assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_MAP);
|
||||
assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_TEXT);
|
||||
/* Invalid tag */
|
||||
AMobjType out = -1;
|
||||
assert_false(AMobjTypeFromString(&out, "???"));
|
||||
assert_int_equal(out, (AMobjType)-1);
|
||||
}
|
||||
|
||||
static void test_AMstatusToString(void** state) {
|
||||
assert_to_string(AMstatusToString, AM_STATUS_ERROR);
|
||||
assert_to_string(AMstatusToString, AM_STATUS_INVALID_RESULT);
|
||||
assert_to_string(AMstatusToString, AM_STATUS_OK);
|
||||
/* Zero tag */
|
||||
assert_string_equal(AMstatusToString(0), "AM_STATUS_OK");
|
||||
/* Invalid tag */
|
||||
assert_string_equal(AMstatusToString(-1), "???");
|
||||
}
|
||||
|
||||
static void test_AMstatusFromString(void** state) {
|
||||
assert_from_string(AMstatusFromString, AMstatus, AM_STATUS_ERROR);
|
||||
assert_from_string(AMstatusFromString, AMstatus, AM_STATUS_INVALID_RESULT);
|
||||
assert_from_string(AMstatusFromString, AMstatus, AM_STATUS_OK);
|
||||
/* Invalid tag */
|
||||
AMstatus out = -1;
|
||||
assert_false(AMstatusFromString(&out, "???"));
|
||||
assert_int_equal(out, (AMstatus)-1);
|
||||
}
|
||||
|
||||
static void test_AMvalTypeToString(void** state) {
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_ACTOR_ID);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_BOOL);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_BYTES);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_CHANGE);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_CHANGE_HASH);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_COUNTER);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_DEFAULT);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_DOC);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_F64);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_INT);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_NULL);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_OBJ_TYPE);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_STR);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_SYNC_HAVE);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_SYNC_MESSAGE);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_SYNC_STATE);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_TIMESTAMP);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_UINT);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_UNKNOWN);
|
||||
assert_to_string(AMvalTypeToString, AM_VAL_TYPE_VOID);
|
||||
/* Zero tag */
|
||||
assert_string_equal(AMvalTypeToString(0), "AM_VAL_TYPE_DEFAULT");
|
||||
/* Invalid tag */
|
||||
assert_string_equal(AMvalTypeToString(-1), "???");
|
||||
}
|
||||
|
||||
static void test_AMvalTypeFromString(void** state) {
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_ACTOR_ID);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_BOOL);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_BYTES);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_CHANGE);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_CHANGE_HASH);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_COUNTER);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_DEFAULT);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_DOC);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_F64);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_INT);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_NULL);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_OBJ_TYPE);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_STR);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_SYNC_HAVE);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_SYNC_MESSAGE);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_SYNC_STATE);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_TIMESTAMP);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_UINT);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_UNKNOWN);
|
||||
assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_VOID);
|
||||
/* Invalid tag */
|
||||
AMvalType out = -1;
|
||||
assert_false(AMvalTypeFromString(&out, "???"));
|
||||
assert_int_equal(out, (AMvalType)-1);
|
||||
}
|
||||
|
||||
int run_enum_string_tests(void) {
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_AMidxTypeToString), cmocka_unit_test(test_AMidxTypeFromString),
|
||||
cmocka_unit_test(test_AMobjTypeToString), cmocka_unit_test(test_AMobjTypeFromString),
|
||||
cmocka_unit_test(test_AMstatusToString), cmocka_unit_test(test_AMstatusFromString),
|
||||
cmocka_unit_test(test_AMvalTypeToString), cmocka_unit_test(test_AMvalTypeFromString),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
27
rust/automerge-c/test/group_state.c
Normal file
27
rust/automerge-c/test/group_state.c
Normal file
|
@ -0,0 +1,27 @@
|
|||
#include <setjmp.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* third-party */
|
||||
#include <cmocka.h>
|
||||
|
||||
/* local */
|
||||
#include "group_state.h"
|
||||
#include "stack_utils.h"
|
||||
|
||||
int group_setup(void** state) {
|
||||
GroupState* group_state = test_calloc(1, sizeof(GroupState));
|
||||
group_state->doc = AMpush(&group_state->stack,
|
||||
AMcreate(NULL),
|
||||
AM_VALUE_DOC,
|
||||
cmocka_cb).doc;
|
||||
*state = group_state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int group_teardown(void** state) {
|
||||
GroupState* group_state = *state;
|
||||
AMfreeStack(&group_state->stack);
|
||||
test_free(group_state);
|
||||
return 0;
|
||||
}
|
16
rust/automerge-c/test/group_state.h
Normal file
16
rust/automerge-c/test/group_state.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#ifndef GROUP_STATE_H
|
||||
#define GROUP_STATE_H
|
||||
|
||||
/* local */
|
||||
#include <automerge-c/automerge.h>
|
||||
|
||||
typedef struct {
|
||||
AMresultStack* stack;
|
||||
AMdoc* doc;
|
||||
} GroupState;
|
||||
|
||||
int group_setup(void** state);
|
||||
|
||||
int group_teardown(void** state);
|
||||
|
||||
#endif /* GROUP_STATE_H */
|
|
@ -1,94 +0,0 @@
|
|||
#include <setjmp.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* third-party */
|
||||
#include <cmocka.h>
|
||||
|
||||
/* local */
|
||||
#include <automerge-c/automerge.h>
|
||||
#include <automerge-c/utils/stack_callback_data.h>
|
||||
#include "cmocka_utils.h"
|
||||
#include "doc_state.h"
|
||||
|
||||
static void test_AMitemResult(void** state) {
|
||||
enum { ITEM_COUNT = 1000 };
|
||||
|
||||
DocState* doc_state = *state;
|
||||
AMstack** stack_ptr = &doc_state->base_state->stack;
|
||||
/* Append the strings to a list so that they'll be in numerical order. */
|
||||
AMobjId const* const list =
|
||||
AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),
|
||||
cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE)));
|
||||
for (size_t pos = 0; pos != ITEM_COUNT; ++pos) {
|
||||
size_t const count = snprintf(NULL, 0, "%zu", pos);
|
||||
char* const src = test_calloc(count + 1, sizeof(char));
|
||||
assert_int_equal(sprintf(src, "%zu", pos), count);
|
||||
AMstackItem(NULL, AMlistPutStr(doc_state->doc, list, pos, true, AMbytes(src, count)), cmocka_cb,
|
||||
AMexpect(AM_VAL_TYPE_VOID));
|
||||
test_free(src);
|
||||
}
|
||||
/* Get an item iterator. */
|
||||
AMitems items = AMstackItems(stack_ptr, AMlistRange(doc_state->doc, list, 0, SIZE_MAX, NULL), cmocka_cb,
|
||||
AMexpect(AM_VAL_TYPE_STR));
|
||||
/* Get the item iterator's result so that it can be freed later. */
|
||||
AMresult const* const items_result = (*stack_ptr)->result;
|
||||
/* Iterate over all of the items and copy their pointers into an array. */
|
||||
AMitem* item_ptrs[ITEM_COUNT] = {NULL};
|
||||
AMitem* item = NULL;
|
||||
for (size_t pos = 0; (item = AMitemsNext(&items, 1)) != NULL; ++pos) {
|
||||
/* The item's reference count should be 1. */
|
||||
assert_int_equal(AMitemRefCount(item), 1);
|
||||
if (pos & 1) {
|
||||
/* Create a redundant result for an odd item. */
|
||||
AMitem* const new_item = AMstackItem(stack_ptr, AMitemResult(item), cmocka_cb, AMexpect(AM_VAL_TYPE_STR));
|
||||
/* The item's old and new pointers will never match. */
|
||||
assert_ptr_not_equal(new_item, item);
|
||||
/* The item's reference count will have been incremented. */
|
||||
assert_int_equal(AMitemRefCount(item), 2);
|
||||
assert_int_equal(AMitemRefCount(new_item), 2);
|
||||
/* The item's old and new indices should match. */
|
||||
assert_int_equal(AMitemIdxType(item), AMitemIdxType(new_item));
|
||||
assert_int_equal(AMitemIdxType(item), AM_IDX_TYPE_POS);
|
||||
size_t pos, new_pos;
|
||||
assert_true(AMitemPos(item, &pos));
|
||||
assert_true(AMitemPos(new_item, &new_pos));
|
||||
assert_int_equal(pos, new_pos);
|
||||
/* The item's old and new object IDs should match. */
|
||||
AMobjId const* const obj_id = AMitemObjId(item);
|
||||
AMobjId const* const new_obj_id = AMitemObjId(new_item);
|
||||
assert_true(AMobjIdEqual(obj_id, new_obj_id));
|
||||
/* The item's old and new value types should match. */
|
||||
assert_int_equal(AMitemValType(item), AMitemValType(new_item));
|
||||
/* The item's old and new string values should match. */
|
||||
AMbyteSpan str;
|
||||
assert_true(AMitemToStr(item, &str));
|
||||
AMbyteSpan new_str;
|
||||
assert_true(AMitemToStr(new_item, &new_str));
|
||||
assert_int_equal(str.count, new_str.count);
|
||||
assert_memory_equal(str.src, new_str.src, new_str.count);
|
||||
/* The item's old and new object IDs are one and the same. */
|
||||
assert_ptr_equal(obj_id, new_obj_id);
|
||||
/* The item's old and new string values are one and the same. */
|
||||
assert_ptr_equal(str.src, new_str.src);
|
||||
/* Save the item's new pointer. */
|
||||
item_ptrs[pos] = new_item;
|
||||
}
|
||||
}
|
||||
/* Free the item iterator's result. */
|
||||
AMresultFree(AMstackPop(stack_ptr, items_result));
|
||||
/* An odd item's reference count should be 1 again. */
|
||||
for (size_t pos = 1; pos < ITEM_COUNT; pos += 2) {
|
||||
assert_int_equal(AMitemRefCount(item_ptrs[pos]), 1);
|
||||
}
|
||||
}
|
||||
|
||||
int run_item_tests(void) {
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_AMitemResult),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, setup_doc, teardown_doc);
|
||||
}
|
|
@ -11,416 +11,366 @@
|
|||
|
||||
/* 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 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)); \
|
||||
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); \
|
||||
AMresultFree(AMstackPop(stack_ptr, NULL)); \
|
||||
}
|
||||
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); \
|
||||
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", AMresultError(result)); \
|
||||
fail_msg_view("%s", AMerrorMessage(result)); \
|
||||
} \
|
||||
assert_int_equal(AMresultSize(result), 1); \
|
||||
assert_int_equal(AMitemValType(AMresultItem(result)), AM_VAL_TYPE_NULL); \
|
||||
AMresultFree(AMstackPop(stack_ptr, NULL)); \
|
||||
}
|
||||
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))); \
|
||||
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(doc_state->doc, obj_id), obj_type); \
|
||||
assert_int_equal(AMobjSize(doc_state->doc, obj_id, NULL), 0); \
|
||||
AMresultFree(AMstackPop(stack_ptr, NULL)); \
|
||||
}
|
||||
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)); \
|
||||
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); \
|
||||
AMresultFree(AMstackPop(stack_ptr, NULL)); \
|
||||
}
|
||||
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
|
||||
/** \brief A JavaScript application can introduce NUL (`\0`) characters into a
|
||||
* list object's string value which will truncate it in a C application.
|
||||
*/
|
||||
static void test_get_NUL_string_value(void** state) {
|
||||
|
@ -431,52 +381,60 @@ static void test_get_NUL_string_value(void** state) {
|
|||
doc[0] = 'o\0ps';
|
||||
});
|
||||
const bytes = Automerge.save(doc);
|
||||
console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([],
|
||||
bytes).join(", ") + "};");
|
||||
console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], bytes).join(", ") + "};");
|
||||
*/
|
||||
static uint8_t const OOPS_VALUE[] = {'o', '\0', 'p', 's'};
|
||||
static size_t const OOPS_SIZE = sizeof(OOPS_VALUE) / sizeof(uint8_t);
|
||||
|
||||
static uint8_t const SAVED_DOC[] = {
|
||||
133, 111, 74, 131, 224, 28, 197, 17, 0, 113, 1, 16, 246, 137, 63, 193, 255, 181, 76, 79, 129,
|
||||
213, 133, 29, 214, 158, 164, 15, 1, 207, 184, 14, 57, 1, 194, 79, 247, 82, 160, 134, 227, 144,
|
||||
5, 241, 136, 205, 238, 250, 251, 54, 34, 250, 210, 96, 204, 132, 153, 203, 110, 109, 6, 6, 1,
|
||||
2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 3, 33, 2, 35, 2, 52, 1, 66,
|
||||
2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, 0, 127, 7, 127,
|
||||
1, 48, 127, 0, 127, 1, 1, 127, 1, 127, 70, 111, 0, 112, 115, 127, 0, 0};
|
||||
133, 111, 74, 131, 224, 28, 197, 17, 0, 113, 1, 16, 246, 137, 63, 193,
|
||||
255, 181, 76, 79, 129, 213, 133, 29, 214, 158, 164, 15, 1, 207, 184,
|
||||
14, 57, 1, 194, 79, 247, 82, 160, 134, 227, 144, 5, 241, 136, 205,
|
||||
238, 250, 251, 54, 34, 250, 210, 96, 204, 132, 153, 203, 110, 109, 6,
|
||||
6, 1, 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 3, 33, 2, 35, 2, 52,
|
||||
1, 66, 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0,
|
||||
127, 0, 127, 7, 127, 1, 48, 127, 0, 127, 1, 1, 127, 1, 127, 70, 111,
|
||||
0, 112, 115, 127, 0, 0};
|
||||
static size_t const SAVED_DOC_SIZE = sizeof(SAVED_DOC) / sizeof(uint8_t);
|
||||
|
||||
BaseState* base_state = *state;
|
||||
AMstack** stack_ptr = &base_state->stack;
|
||||
AMdoc* doc;
|
||||
assert_true(AMitemToDoc(
|
||||
AMstackItem(stack_ptr, AMload(SAVED_DOC, SAVED_DOC_SIZE), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc));
|
||||
AMbyteSpan str;
|
||||
assert_true(AMitemToStr(
|
||||
AMstackItem(stack_ptr, AMlistGet(doc, AM_ROOT, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str));
|
||||
AMresultStack* stack = *state;
|
||||
AMdoc* const doc = AMpush(&stack,
|
||||
AMload(SAVED_DOC, SAVED_DOC_SIZE),
|
||||
AM_VALUE_DOC,
|
||||
cmocka_cb).doc;
|
||||
AMbyteSpan const str = AMpush(&stack,
|
||||
AMlistGet(doc, AM_ROOT, 0, NULL),
|
||||
AM_VALUE_STR,
|
||||
cmocka_cb).str;
|
||||
assert_int_not_equal(str.count, strlen(OOPS_VALUE));
|
||||
assert_int_equal(str.count, OOPS_SIZE);
|
||||
assert_memory_equal(str.src, OOPS_VALUE, str.count);
|
||||
}
|
||||
|
||||
static void test_insert_at_index(void** state) {
|
||||
BaseState* base_state = *state;
|
||||
AMstack** stack_ptr = &base_state->stack;
|
||||
AMdoc* doc;
|
||||
assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc));
|
||||
AMobjId const* const list =
|
||||
AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb,
|
||||
AMexpect(AM_VAL_TYPE_OBJ_TYPE)));
|
||||
AMresultStack* stack = *state;
|
||||
AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc;
|
||||
|
||||
AMobjId const* const list = AMpush(
|
||||
&stack,
|
||||
AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),
|
||||
AM_VALUE_OBJ_ID,
|
||||
cmocka_cb).obj_id;
|
||||
/* Insert both at the same index. */
|
||||
AMstackItem(NULL, AMlistPutUint(doc, list, 0, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMstackItem(NULL, AMlistPutUint(doc, list, 0, true, 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID));
|
||||
AMfree(AMlistPutUint(doc, list, 0, true, 0));
|
||||
AMfree(AMlistPutUint(doc, list, 0, true, 1));
|
||||
|
||||
assert_int_equal(AMobjSize(doc, list, NULL), 2);
|
||||
AMitems const keys = AMstackItems(stack_ptr, AMkeys(doc, list, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR));
|
||||
assert_int_equal(AMitemsSize(&keys), 2);
|
||||
AMitems const range =
|
||||
AMstackItems(stack_ptr, AMlistRange(doc, list, 0, SIZE_MAX, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT));
|
||||
assert_int_equal(AMitemsSize(&range), 2);
|
||||
AMstrs const keys = AMpush(&stack,
|
||||
AMkeys(doc, list, NULL),
|
||||
AM_VALUE_STRS,
|
||||
cmocka_cb).strs;
|
||||
assert_int_equal(AMstrsSize(&keys), 2);
|
||||
AMlistItems const range = AMpush(&stack,
|
||||
AMlistRange(doc, list, 0, SIZE_MAX, NULL),
|
||||
AM_VALUE_LIST_ITEMS,
|
||||
cmocka_cb).list_items;
|
||||
assert_int_equal(AMlistItemsSize(&range), 2);
|
||||
}
|
||||
|
||||
int run_list_tests(void) {
|
||||
|
@ -500,16 +458,18 @@ int run_list_tests(void) {
|
|||
cmocka_unit_test(test_AMlistPutObject(Map, update)),
|
||||
cmocka_unit_test(test_AMlistPutObject(Text, insert)),
|
||||
cmocka_unit_test(test_AMlistPutObject(Text, update)),
|
||||
cmocka_unit_test(test_AMlistPutObject(Void, insert)),
|
||||
cmocka_unit_test(test_AMlistPutObject(Void, update)),
|
||||
cmocka_unit_test(test_AMlistPutStr(insert)),
|
||||
cmocka_unit_test(test_AMlistPutStr(update)),
|
||||
cmocka_unit_test(test_AMlistPut(Timestamp, insert)),
|
||||
cmocka_unit_test(test_AMlistPut(Timestamp, update)),
|
||||
cmocka_unit_test(test_AMlistPut(Uint, insert)),
|
||||
cmocka_unit_test(test_AMlistPut(Uint, update)),
|
||||
cmocka_unit_test_setup_teardown(test_get_range_values, setup_base, teardown_base),
|
||||
cmocka_unit_test_setup_teardown(test_get_NUL_string_value, setup_base, teardown_base),
|
||||
cmocka_unit_test_setup_teardown(test_insert_at_index, setup_base, teardown_base),
|
||||
cmocka_unit_test_setup_teardown(test_get_list_values, setup_stack, teardown_stack),
|
||||
cmocka_unit_test_setup_teardown(test_get_NUL_string_value, setup_stack, teardown_stack),
|
||||
cmocka_unit_test_setup_teardown(test_insert_at_index, setup_stack, teardown_stack),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, setup_doc, teardown_doc);
|
||||
return cmocka_run_group_tests(tests, group_setup, group_teardown);
|
||||
}
|
||||
|
|
|
@ -3,36 +3,23 @@
|
|||
/* local */
|
||||
#include "macro_utils.h"
|
||||
|
||||
AMobjType suffix_to_obj_type(char const* obj_type_label) {
|
||||
if (!strcmp(obj_type_label, "List"))
|
||||
return AM_OBJ_TYPE_LIST;
|
||||
else if (!strcmp(obj_type_label, "Map"))
|
||||
return AM_OBJ_TYPE_MAP;
|
||||
else if (!strcmp(obj_type_label, "Text"))
|
||||
return AM_OBJ_TYPE_TEXT;
|
||||
else
|
||||
return AM_OBJ_TYPE_DEFAULT;
|
||||
AMvalueVariant AMvalue_discriminant(char const* suffix) {
|
||||
if (!strcmp(suffix, "Bool")) return AM_VALUE_BOOLEAN;
|
||||
else if (!strcmp(suffix, "Bytes")) return AM_VALUE_BYTES;
|
||||
else if (!strcmp(suffix, "Counter")) return AM_VALUE_COUNTER;
|
||||
else if (!strcmp(suffix, "F64")) return AM_VALUE_F64;
|
||||
else if (!strcmp(suffix, "Int")) return AM_VALUE_INT;
|
||||
else if (!strcmp(suffix, "Null")) return AM_VALUE_NULL;
|
||||
else if (!strcmp(suffix, "Str")) return AM_VALUE_STR;
|
||||
else if (!strcmp(suffix, "Timestamp")) return AM_VALUE_TIMESTAMP;
|
||||
else if (!strcmp(suffix, "Uint")) return AM_VALUE_UINT;
|
||||
else return AM_VALUE_VOID;
|
||||
}
|
||||
|
||||
AMvalType suffix_to_val_type(char const* suffix) {
|
||||
if (!strcmp(suffix, "Bool"))
|
||||
return AM_VAL_TYPE_BOOL;
|
||||
else if (!strcmp(suffix, "Bytes"))
|
||||
return AM_VAL_TYPE_BYTES;
|
||||
else if (!strcmp(suffix, "Counter"))
|
||||
return AM_VAL_TYPE_COUNTER;
|
||||
else if (!strcmp(suffix, "F64"))
|
||||
return AM_VAL_TYPE_F64;
|
||||
else if (!strcmp(suffix, "Int"))
|
||||
return AM_VAL_TYPE_INT;
|
||||
else if (!strcmp(suffix, "Null"))
|
||||
return AM_VAL_TYPE_NULL;
|
||||
else if (!strcmp(suffix, "Str"))
|
||||
return AM_VAL_TYPE_STR;
|
||||
else if (!strcmp(suffix, "Timestamp"))
|
||||
return AM_VAL_TYPE_TIMESTAMP;
|
||||
else if (!strcmp(suffix, "Uint"))
|
||||
return AM_VAL_TYPE_UINT;
|
||||
else
|
||||
return AM_VAL_TYPE_DEFAULT;
|
||||
AMobjType AMobjType_tag(char const* obj_type_label) {
|
||||
if (!strcmp(obj_type_label, "List")) return AM_OBJ_TYPE_LIST;
|
||||
else if (!strcmp(obj_type_label, "Map")) return AM_OBJ_TYPE_MAP;
|
||||
else if (!strcmp(obj_type_label, "Text")) return AM_OBJ_TYPE_TEXT;
|
||||
else if (!strcmp(obj_type_label, "Void")) return AM_OBJ_TYPE_VOID;
|
||||
else return 0;
|
||||
}
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
#ifndef TESTS_MACRO_UTILS_H
|
||||
#define TESTS_MACRO_UTILS_H
|
||||
#ifndef MACRO_UTILS_H
|
||||
#define MACRO_UTILS_H
|
||||
|
||||
/* local */
|
||||
#include <automerge-c/automerge.h>
|
||||
|
||||
/**
|
||||
* \brief Gets the object type tag corresponding to an object type suffix.
|
||||
* \brief Gets the result value discriminant corresponding to a function name
|
||||
* suffix.
|
||||
*
|
||||
* \param[in] suffix An object type suffix string.
|
||||
* \return An `AMobjType` enum tag.
|
||||
* \param[in] suffix A string.
|
||||
* \return An `AMvalue` struct discriminant.
|
||||
*/
|
||||
AMobjType suffix_to_obj_type(char const* suffix);
|
||||
AMvalueVariant AMvalue_discriminant(char const* suffix);
|
||||
|
||||
/**
|
||||
* \brief Gets the value type tag corresponding to a value type suffix.
|
||||
* \brief Gets the object type tag corresponding to an object type label.
|
||||
*
|
||||
* \param[in] suffix A value type suffix string.
|
||||
* \return An `AMvalType` enum tag.
|
||||
* \param[in] obj_type_label A string.
|
||||
* \return An `AMobjType` enum tag.
|
||||
*/
|
||||
AMvalType suffix_to_val_type(char const* suffix);
|
||||
AMobjType AMobjType_tag(char const* obj_type_label);
|
||||
|
||||
#endif /* TESTS_MACRO_UTILS_H */
|
||||
#endif /* MACRO_UTILS_H */
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include <setjmp.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* third-party */
|
||||
|
@ -8,14 +8,8 @@
|
|||
|
||||
extern int run_actor_id_tests(void);
|
||||
|
||||
extern int run_byte_span_tests(void);
|
||||
|
||||
extern int run_doc_tests(void);
|
||||
|
||||
extern int run_enum_string_tests(void);
|
||||
|
||||
extern int run_item_tests(void);
|
||||
|
||||
extern int run_list_tests(void);
|
||||
|
||||
extern int run_map_tests(void);
|
||||
|
@ -23,6 +17,11 @@ extern int run_map_tests(void);
|
|||
extern int run_ported_wasm_suite(void);
|
||||
|
||||
int main(void) {
|
||||
return (run_actor_id_tests() + run_byte_span_tests() + run_doc_tests() + run_enum_string_tests() +
|
||||
run_item_tests() + run_list_tests() + run_map_tests() + run_ported_wasm_suite());
|
||||
return (
|
||||
run_actor_id_tests() +
|
||||
run_doc_tests() +
|
||||
run_list_tests() +
|
||||
run_map_tests() +
|
||||
run_ported_wasm_suite()
|
||||
);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
#include <setjmp.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* third-party */
|
||||
|
@ -11,5 +11,8 @@ extern int run_ported_wasm_basic_tests(void);
|
|||
extern int run_ported_wasm_sync_tests(void);
|
||||
|
||||
int run_ported_wasm_suite(void) {
|
||||
return (run_ported_wasm_basic_tests() + run_ported_wasm_sync_tests());
|
||||
return (
|
||||
run_ported_wasm_basic_tests() +
|
||||
run_ported_wasm_sync_tests()
|
||||
);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
31
rust/automerge-c/test/stack_utils.c
Normal file
31
rust/automerge-c/test/stack_utils.c
Normal file
|
@ -0,0 +1,31 @@
|
|||
#include <setjmp.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* third-party */
|
||||
#include <cmocka.h>
|
||||
|
||||
/* local */
|
||||
#include "cmocka_utils.h"
|
||||
#include "stack_utils.h"
|
||||
|
||||
void cmocka_cb(AMresultStack** stack, uint8_t discriminant) {
|
||||
assert_non_null(stack);
|
||||
assert_non_null(*stack);
|
||||
assert_non_null((*stack)->result);
|
||||
if (AMresultStatus((*stack)->result) != AM_STATUS_OK) {
|
||||
fail_msg_view("%s", AMerrorMessage((*stack)->result));
|
||||
}
|
||||
assert_int_equal(AMresultValue((*stack)->result).tag, discriminant);
|
||||
}
|
||||
|
||||
int setup_stack(void** state) {
|
||||
*state = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int teardown_stack(void** state) {
|
||||
AMresultStack* stack = *state;
|
||||
AMfreeStack(&stack);
|
||||
return 0;
|
||||
}
|
38
rust/automerge-c/test/stack_utils.h
Normal file
38
rust/automerge-c/test/stack_utils.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef STACK_UTILS_H
|
||||
#define STACK_UTILS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* local */
|
||||
#include <automerge-c/automerge.h>
|
||||
|
||||
/**
|
||||
* \brief Reports an error through a cmocka assertion.
|
||||
*
|
||||
* \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct.
|
||||
* \param[in] discriminant An `AMvalueVariant` enum tag.
|
||||
* \pre \p stack` != NULL`.
|
||||
*/
|
||||
void cmocka_cb(AMresultStack** stack, uint8_t discriminant);
|
||||
|
||||
/**
|
||||
* \brief Allocates a result stack for storing the results allocated during one
|
||||
* or more test cases.
|
||||
*
|
||||
* \param[in,out] state A pointer to a pointer to an `AMresultStack` struct.
|
||||
* \pre \p state` != NULL`.
|
||||
* \warning The `AMresultStack` struct returned through \p state must be
|
||||
* deallocated with `teardown_stack()` in order to prevent memory leaks.
|
||||
*/
|
||||
int setup_stack(void** state);
|
||||
|
||||
/**
|
||||
* \brief Deallocates a result stack after deallocating any results that were
|
||||
* stored in it by one or more test cases.
|
||||
*
|
||||
* \param[in] state A pointer to a pointer to an `AMresultStack` struct.
|
||||
* \pre \p state` != NULL`.
|
||||
*/
|
||||
int teardown_stack(void** state);
|
||||
|
||||
#endif /* STACK_UTILS_H */
|
|
@ -1,5 +1,5 @@
|
|||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* local */
|
||||
#include "str_utils.h"
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
#ifndef TESTS_STR_UTILS_H
|
||||
#define TESTS_STR_UTILS_H
|
||||
#ifndef STR_UTILS_H
|
||||
#define STR_UTILS_H
|
||||
|
||||
/**
|
||||
* \brief Converts a hexadecimal string into an array of bytes.
|
||||
* \brief Converts a hexadecimal string into a sequence of bytes.
|
||||
*
|
||||
* \param[in] hex_str A hexadecimal string.
|
||||
* \param[in] src A pointer to an array of bytes.
|
||||
* \param[in] count The count of bytes to copy into the array pointed to by
|
||||
* \p src.
|
||||
* \pre \p src `!= NULL`
|
||||
* \pre `sizeof(`\p src `) > 0`
|
||||
* \pre \p count `<= sizeof(`\p src `)`
|
||||
* \param[in] hex_str A string.
|
||||
* \param[in] src A pointer to a contiguous sequence of bytes.
|
||||
* \param[in] count The number of bytes to copy to \p src.
|
||||
* \pre \p count `<=` length of \p src.
|
||||
*/
|
||||
void hex_to_bytes(char const* hex_str, uint8_t* src, size_t const count);
|
||||
|
||||
#endif /* TESTS_STR_UTILS_H */
|
||||
#endif /* STR_UTILS_H */
|
||||
|
|
857
rust/automerge-cli/Cargo.lock
generated
Normal file
857
rust/automerge-cli/Cargo.lock
generated
Normal file
|
@ -0,0 +1,857 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "automerge"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"fxhash",
|
||||
"hex",
|
||||
"itertools",
|
||||
"js-sys",
|
||||
"leb128",
|
||||
"nonzero_ext",
|
||||
"rand",
|
||||
"serde",
|
||||
"sha2",
|
||||
"smol_str",
|
||||
"thiserror",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"unicode-segmentation",
|
||||
"uuid",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "automerge-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"atty",
|
||||
"automerge",
|
||||
"clap",
|
||||
"colored_json",
|
||||
"combine",
|
||||
"duct",
|
||||
"maplit",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"os_str_bytes",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored_json"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd32eb54d016e203b7c2600e3a7802c75843a92e38ccc4869aefeca21771a64"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"libc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "duct"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fc6a0a59ed0888e0041cf708e66357b7ae1a82f1c67247e1f93b5e0818f7d8d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"once_cell",
|
||||
"os_pipe",
|
||||
"shared_child",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crc32fast",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nonzero_ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44a1290799eababa63ea60af0cbc3f03363e328e58f32fb0294798ed3e85f444"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.136"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.136"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_child"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6be9f7d5565b1483af3e72975e2dee33879b3b86bd48c0929fccf6585d79e65a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
|
||||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61d15c83e300cce35b7c8cd39ff567c1ef42dde6d4a1a38dbdbf9a59902261bd"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue