diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8519ac5e..c2d469d5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,10 +2,10 @@ name: CI on: push: branches: - - main + - main pull_request: branches: - - main + - main jobs: fmt: runs-on: ubuntu-latest @@ -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 diff --git a/README.md b/README.md index ad174da4..94e1bbb8 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ to figure out how to use it. If you are looking to build rust applications which use automerge you may want to look into [autosurgeon](https://github.com/alexjg/autosurgeon) + ## Repository Organisation - `./rust` - the rust rust implementation and also the Rust components of @@ -112,16 +113,9 @@ brew install cmake node cmocka # install yarn npm install --global yarn -# install javascript dependencies -yarn --cwd ./javascript - # install rust dependencies cargo install wasm-bindgen-cli wasm-opt cargo-deny -# get nightly rust to produce optimized automerge-c builds -rustup toolchain install nightly -rustup component add rust-src --toolchain nightly - # add wasm target in addition to current architecture rustup target add wasm32-unknown-unknown diff --git a/flake.nix b/flake.nix index 37835738..4f9ba1fe 100644 --- a/flake.nix +++ b/flake.nix @@ -54,7 +54,6 @@ nodejs yarn - deno # c deps cmake diff --git a/javascript/examples/create-react-app/yarn.lock b/javascript/examples/create-react-app/yarn.lock index ec83af3b..d6e5d93f 100644 --- a/javascript/examples/create-react-app/yarn.lock +++ b/javascript/examples/create-react-app/yarn.lock @@ -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" diff --git a/javascript/package.json b/javascript/package.json index 79309907..017c5a54 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -4,7 +4,7 @@ "Orion Henry ", "Martin Kleppmann" ], - "version": "2.0.2", + "version": "2.0.1", "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.23", "uuid": "^9.0.0" } } diff --git a/javascript/src/stable.ts b/javascript/src/stable.ts index e83b127f..3b328240 100644 --- a/javascript/src/stable.ts +++ b/javascript/src/stable.ts @@ -26,7 +26,7 @@ import { Text } from "./text" export { Text } from "./text" import type { - API as WasmAPI, + API, Actor as ActorId, Prop, ObjID, @@ -34,7 +34,7 @@ import type { DecodedChange, Heads, MaterializeValue, - JsSyncState, + JsSyncState as SyncState, SyncMessage, DecodedSyncMessage, } from "@automerge/automerge-wasm" @@ -46,17 +46,6 @@ export type { 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" @@ -305,7 +294,7 @@ export function from>( * @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 +305,7 @@ export function from>( * 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"]) @@ -783,7 +772,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 +793,7 @@ export function generateSyncMessage( 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 +835,7 @@ export function receiveSyncMessage( } 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 +852,7 @@ export function receiveSyncMessage( * @group sync */ export function initSyncState(): SyncState { - return ApiHandler.exportSyncState(ApiHandler.initSyncState()) as SyncState + return ApiHandler.exportSyncState(ApiHandler.initSyncState()) } /** @hidden */ diff --git a/javascript/test/basic_test.ts b/javascript/test/basic_test.ts index e34484c4..5aa1ac34 100644 --- a/javascript/test/basic_test.ts +++ b/javascript/test/basic_test.ts @@ -58,22 +58,6 @@ describe("Automerge", () => { }) }) - it("should be able to insert and delete a large number of properties", () => { - let doc = Automerge.init() - - doc = Automerge.change(doc, doc => { - doc["k1"] = true - }) - - for (let idx = 1; idx <= 200; idx++) { - doc = Automerge.change(doc, doc => { - delete doc["k" + idx] - doc["k" + (idx + 1)] = true - assert(Object.keys(doc).length == 1) - }) - } - }) - it("can detect an automerge doc with isAutomerge()", () => { const doc1 = Automerge.from({ sub: { object: true } }) assert(Automerge.isAutomerge(doc1)) diff --git a/javascript/test/legacy_tests.ts b/javascript/test/legacy_tests.ts index 8c2e552e..90c731d9 100644 --- a/javascript/test/legacy_tests.ts +++ b/javascript/test/legacy_tests.ts @@ -1849,8 +1849,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" }, ]) }) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 5d29fc9f..938100cf 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -10,8 +10,13 @@ members = [ resolver = "2" [profile.release] +debug = true lto = true -codegen-units = 1 +opt-level = 3 [profile.bench] -debug = true \ No newline at end of file +debug = true + +[profile.release.package.automerge-wasm] +debug = false +opt-level = 3 diff --git a/rust/automerge-c/.clang-format b/rust/automerge-c/.clang-format deleted file mode 100644 index dbf16c21..00000000 --- a/rust/automerge-c/.clang-format +++ /dev/null @@ -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: '^' - Priority: 2 - SortPriority: 0 - CaseSensitive: false - - Regex: '^<.*\.h>' - Priority: 1 - SortPriority: 0 - CaseSensitive: false - - Regex: '^<.*' - Priority: 2 - SortPriority: 0 - CaseSensitive: false - - Regex: '.*' - Priority: 3 - SortPriority: 0 - CaseSensitive: false -IncludeIsMainRegex: '([-_](test|unittest))?$' -IncludeIsMainSourceRegex: '' -IndentAccessModifiers: false -IndentCaseLabels: true -IndentCaseBlocks: false -IndentGotoLabels: true -IndentPPDirectives: None -IndentExternBlock: AfterExternBlock -IndentRequiresClause: true -IndentWidth: 4 -IndentWrappedFunctionNames: false -InsertBraces: false -InsertTrailingCommas: None -JavaScriptQuotes: Leave -JavaScriptWrapImports: true -KeepEmptyLinesAtTheStartOfBlocks: false -LambdaBodyIndentation: Signature -MacroBlockBegin: '' -MacroBlockEnd: '' -MaxEmptyLinesToKeep: 1 -NamespaceIndentation: None -ObjCBinPackProtocolList: Never -ObjCBlockIndentWidth: 2 -ObjCBreakBeforeNestedBlockParam: true -ObjCSpaceAfterProperty: false -ObjCSpaceBeforeProtocolList: true -PenaltyBreakAssignment: 2 -PenaltyBreakBeforeFirstCallParameter: 1 -PenaltyBreakComment: 300 -PenaltyBreakFirstLessLess: 120 -PenaltyBreakOpenParenthesis: 0 -PenaltyBreakString: 1000 -PenaltyBreakTemplateDeclaration: 10 -PenaltyExcessCharacter: 1000000 -PenaltyReturnTypeOnItsOwnLine: 200 -PenaltyIndentedWhitespace: 0 -PointerAlignment: Left -PPIndentWidth: -1 -RawStringFormats: - - Language: Cpp - Delimiters: - - cc - - CC - - cpp - - Cpp - - CPP - - 'c++' - - 'C++' - CanonicalDelimiter: '' - BasedOnStyle: google - - Language: TextProto - Delimiters: - - pb - - PB - - proto - - PROTO - EnclosingFunctions: - - EqualsProto - - EquivToProto - - PARSE_PARTIAL_TEXT_PROTO - - PARSE_TEST_PROTO - - PARSE_TEXT_PROTO - - ParseTextOrDie - - ParseTextProtoOrDie - - ParseTestProto - - ParsePartialTestProto - CanonicalDelimiter: pb - BasedOnStyle: google -ReferenceAlignment: Pointer -ReflowComments: true -RemoveBracesLLVM: false -RequiresClausePosition: OwnLine -SeparateDefinitionBlocks: Leave -ShortNamespaceLines: 1 -SortIncludes: CaseSensitive -SortJavaStaticImport: Before -SortUsingDeclarations: true -SpaceAfterCStyleCast: false -SpaceAfterLogicalNot: false -SpaceAfterTemplateKeyword: true -SpaceBeforeAssignmentOperators: true -SpaceBeforeCaseColon: false -SpaceBeforeCpp11BracedList: false -SpaceBeforeCtorInitializerColon: true -SpaceBeforeInheritanceColon: true -SpaceBeforeParens: ControlStatements -SpaceBeforeParensOptions: - AfterControlStatements: true - AfterForeachMacros: true - AfterFunctionDefinitionName: false - AfterFunctionDeclarationName: false - AfterIfMacros: true - AfterOverloadedOperator: false - AfterRequiresInClause: false - AfterRequiresInExpression: false - BeforeNonEmptyParentheses: false -SpaceAroundPointerQualifiers: Default -SpaceBeforeRangeBasedForLoopColon: true -SpaceInEmptyBlock: false -SpaceInEmptyParentheses: false -SpacesBeforeTrailingComments: 2 -SpacesInAngles: Never -SpacesInConditionalStatement: false -SpacesInContainerLiterals: true -SpacesInCStyleCastParentheses: false -SpacesInLineCommentPrefix: - Minimum: 1 - Maximum: -1 -SpacesInParentheses: false -SpacesInSquareBrackets: false -SpaceBeforeSquareBrackets: false -BitFieldColonSpacing: Both -Standard: Auto -StatementAttributeLikeMacros: - - Q_EMIT -StatementMacros: - - Q_UNUSED - - QT_REQUIRE_VERSION -TabWidth: 8 -UseCRLF: false -UseTab: Never -WhitespaceSensitiveMacros: - - STRINGIZE - - PP_STRINGIZE - - BOOST_PP_STRINGIZE - - NS_SWIFT_NAME - - CF_SWIFT_NAME -... - diff --git a/rust/automerge-c/.gitignore b/rust/automerge-c/.gitignore index 14d74973..f04de582 100644 --- a/rust/automerge-c/.gitignore +++ b/rust/automerge-c/.gitignore @@ -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 diff --git a/rust/automerge-c/CMakeLists.txt b/rust/automerge-c/CMakeLists.txt index 0c35eebd..1b68669a 100644 --- a/rust/automerge-c/CMakeLists.txt +++ b/rust/automerge-c/CMakeLists.txt @@ -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()` calls. - ${CMAKE_COMMAND} -DMATCH_REGEX=USIZE_ -DREPLACE_EXPR=\+${CMAKE_SIZEOF_VOID_P} -P ${CMAKE_SOURCE_DIR}/cmake/file-regex-replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h - MAIN_DEPENDENCY - src/lib.rs - DEPENDS - src/actor_id.rs - src/byte_span.rs - src/change.rs - src/doc.rs - src/doc/list.rs - src/doc/map.rs - src/doc/utils.rs - src/index.rs - src/item.rs - src/items.rs - src/obj.rs - src/result.rs - src/sync.rs - src/sync/have.rs - src/sync/message.rs - src/sync/state.rs - ${CMAKE_SOURCE_DIR}/build.rs - ${CMAKE_MODULE_PATH}/Cargo.toml.in - ${CMAKE_MODULE_PATH}/cbindgen.toml.in - WORKING_DIRECTORY - ${CMAKE_SOURCE_DIR} - COMMENT - "Producing the bindings' artifacts with Cargo..." - VERBATIM -) - -add_custom_target(${BINDINGS_NAME}_artifacts ALL - DEPENDS ${CARGO_OUTPUT} -) - -add_library(${BINDINGS_NAME} STATIC IMPORTED GLOBAL) - -target_include_directories(${BINDINGS_NAME} INTERFACE "${CBINDGEN_INCLUDEDIR}") - -set_target_properties( - ${BINDINGS_NAME} - PROPERTIES - # \note Cargo writes a debug build into a nested directory instead of - # decorating its name. - DEBUG_POSTFIX "" - DEFINE_SYMBOL "" - IMPORTED_IMPLIB "" - IMPORTED_LOCATION "${CARGO_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${BINDINGS_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}" - IMPORTED_NO_SONAME "TRUE" - IMPORTED_SONAME "" - LINKER_LANGUAGE C - PUBLIC_HEADER "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h" - SOVERSION "${PROJECT_VERSION_MAJOR}" - VERSION "${PROJECT_VERSION}" - # \note Cargo exports all of the symbols automatically. - WINDOWS_EXPORT_ALL_SYMBOLS "TRUE" -) - -target_compile_definitions(${BINDINGS_NAME} INTERFACE $) - -set(UTILS_SUBDIR "utils") - -add_custom_command( - OUTPUT - ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h - ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c - COMMAND - ${CMAKE_COMMAND} -DPROJECT_NAME=${PROJECT_NAME} -DLIBRARY_NAME=${LIBRARY_NAME} -DSUBDIR=${UTILS_SUBDIR} -P ${CMAKE_SOURCE_DIR}/cmake/enum-string-functions-gen.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c - MAIN_DEPENDENCY - ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h - DEPENDS - ${CMAKE_SOURCE_DIR}/cmake/enum-string-functions-gen.cmake - WORKING_DIRECTORY - ${CMAKE_SOURCE_DIR} - COMMENT - "Generating the enum string functions with CMake..." - VERBATIM -) - -add_custom_target(${LIBRARY_NAME}_utilities - DEPENDS ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h - ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c -) - -add_library(${LIBRARY_NAME}) - -target_compile_features(${LIBRARY_NAME} PRIVATE c_std_99) - -set(CMAKE_THREAD_PREFER_PTHREAD TRUE) - -set(THREADS_PREFER_PTHREAD_FLAG TRUE) - -find_package(Threads REQUIRED) - -set(LIBRARY_DEPENDENCIES Threads::Threads ${CMAKE_DL_LIBS}) - -if(WIN32) - list(APPEND LIBRARY_DEPENDENCIES Bcrypt userenv ws2_32) -else() - list(APPEND LIBRARY_DEPENDENCIES m) -endif() - -target_link_libraries(${LIBRARY_NAME} - PUBLIC ${BINDINGS_NAME} - ${LIBRARY_DEPENDENCIES} -) - -# \note An imported library's INTERFACE_INCLUDE_DIRECTORIES property can't -# contain a non-existent path so its build-time include directory -# must be specified for all of its dependent targets instead. -target_include_directories(${LIBRARY_NAME} - PUBLIC "$" - "$" -) - -add_dependencies(${LIBRARY_NAME} ${BINDINGS_NAME}_artifacts) - -# Generate the configuration header. +# 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 $ - 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} +) diff --git a/rust/automerge-c/Cargo.toml b/rust/automerge-c/Cargo.toml index 95a3a29c..d039e460 100644 --- a/rust/automerge-c/Cargo.toml +++ b/rust/automerge-c/Cargo.toml @@ -7,8 +7,8 @@ license = "MIT" rust-version = "1.57.0" [lib] -name = "automerge_core" -crate-type = ["staticlib"] +name = "automerge" +crate-type = ["cdylib", "staticlib"] bench = false doc = false diff --git a/rust/automerge-c/README.md b/rust/automerge-c/README.md index 1fbca3df..a9f097e2 100644 --- a/rust/automerge-c/README.md +++ b/rust/automerge-c/README.md @@ -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 -#include #include -#include +#include 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 -#include #include -#include +#include 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! diff --git a/rust/automerge-c/cbindgen.toml b/rust/automerge-c/cbindgen.toml index 21eaaadd..ada7f48d 100644 --- a/rust/automerge-c/cbindgen.toml +++ b/rust/automerge-c/cbindgen.toml @@ -1,7 +1,7 @@ after_includes = """\n /** * \\defgroup enumerations Public Enumerations - * Symbolic names for integer constants. + Symbolic names for integer constants. */ /** @@ -12,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 diff --git a/rust/automerge-c/cmake/Cargo.toml.in b/rust/automerge-c/cmake/Cargo.toml.in deleted file mode 100644 index 781e2fef..00000000 --- a/rust/automerge-c/cmake/Cargo.toml.in +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "@PROJECT_NAME@" -version = "@PROJECT_VERSION@" -authors = ["Orion Henry ", "Jason Kankiewicz "] -edition = "2021" -license = "MIT" -rust-version = "1.57.0" - -[lib] -name = "@BINDINGS_NAME@" -crate-type = ["staticlib"] -bench = false -doc = false - -[dependencies] -@LIBRARY_NAME@ = { path = "../@LIBRARY_NAME@" } -hex = "^0.4.3" -libc = "^0.2" -smol_str = "^0.1.21" - -[build-dependencies] -cbindgen = "^0.24" diff --git a/rust/automerge-c/cmake/cbindgen.toml.in b/rust/automerge-c/cmake/cbindgen.toml.in deleted file mode 100644 index 5122b75c..00000000 --- a/rust/automerge-c/cmake/cbindgen.toml.in +++ /dev/null @@ -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"] diff --git a/rust/automerge-c/cmake/config.h.in b/rust/automerge-c/cmake/config.h.in index 40482cb9..44ba5213 100644 --- a/rust/automerge-c/cmake/config.h.in +++ b/rust/automerge-c/cmake/config.h.in @@ -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 */ diff --git a/rust/automerge-c/cmake/enum-string-functions-gen.cmake b/rust/automerge-c/cmake/enum-string-functions-gen.cmake deleted file mode 100644 index 77080e8d..00000000 --- a/rust/automerge-c/cmake/enum-string-functions-gen.cmake +++ /dev/null @@ -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 \n" - "\n" - "#include <${library_include}>\n" - "\n") - set(source_body "") - # File includes. - list(APPEND source_body - "/** \\warning This file is auto-generated by CMake. */\n" - "\n" - "#include \"stdio.h\"\n" - "#include \"string.h\"\n" - "\n" - "#include <${header_include}>\n" - "\n") - set(enum_name "") - set(enum_tags "") - set(mode "seek_enum") - file(STRINGS "${input_path}" lines) - foreach(line IN LISTS lines) - string(REGEX REPLACE "^(.+)(//.*)?" "\\1" line "${line}") - string(STRIP "${line}" line) - if(mode STREQUAL "seek_enum") - seek_enum_mode() - elseif(mode STREQUAL "read_tags") - read_tags_mode() - else() - # The end of the enum declaration was reached. - if(NOT enum_name) - # The end of the file was reached. - return() - endif() - if(NOT enum_tags) - message(FATAL_ERROR "No tags found for `${enum_name}`.") - endif() - string(TOLOWER "${enum_name}" output_stem_prefix) - string(CONCAT output_stem "${output_stem_prefix}" "_string") - cmake_path(REPLACE_EXTENSION output_stem "h" OUTPUT_VARIABLE output_header_basename) - write_header_file() - write_source_file() - set(enum_name "") - set(enum_tags "") - set(mode "seek_enum") - endif() - endforeach() - # File footer. - list(APPEND header_body - "#endif /* ${include_guard} */\n") - message(STATUS "Generating header file \"${output_header_path}\"...") - file(WRITE "${output_header_path}" ${header_body}) - message(STATUS "Generating source file \"${output_source_path}\"...") - file(WRITE "${output_source_path}" ${source_body}) -endfunction() - -if(NOT DEFINED PROJECT_NAME) - message(FATAL_ERROR "Variable PROJECT_NAME is not defined.") -elseif(NOT DEFINED LIBRARY_NAME) - message(FATAL_ERROR "Variable LIBRARY_NAME is not defined.") -elseif(NOT DEFINED SUBDIR) - message(FATAL_ERROR "Variable SUBDIR is not defined.") -elseif(${CMAKE_ARGC} LESS 9) - message(FATAL_ERROR "Too few arguments.") -elseif(${CMAKE_ARGC} GREATER 10) - message(FATAL_ERROR "Too many arguments.") -elseif(NOT EXISTS ${CMAKE_ARGV5}) - message(FATAL_ERROR "Input header \"${CMAKE_ARGV7}\" not found.") -endif() -cmake_path(CONVERT "${CMAKE_ARGV7}" TO_CMAKE_PATH_LIST input_path NORMALIZE) -cmake_path(CONVERT "${CMAKE_ARGV8}" TO_CMAKE_PATH_LIST output_header_path NORMALIZE) -cmake_path(CONVERT "${CMAKE_ARGV9}" TO_CMAKE_PATH_LIST output_source_path NORMALIZE) -string(TOLOWER "${PROJECT_NAME}" project_root) -cmake_path(CONVERT "${SUBDIR}" TO_CMAKE_PATH_LIST project_subdir NORMALIZE) -string(TOLOWER "${project_subdir}" project_subdir) -string(TOLOWER "${LIBRARY_NAME}" library_stem) -cmake_path(REPLACE_EXTENSION library_stem "h" OUTPUT_VARIABLE library_basename) -string(JOIN "/" library_include "${project_root}" "${library_basename}") -string(TOUPPER "${PROJECT_NAME}" project_name_upper) -string(TOUPPER "${project_subdir}" include_guard_infix) -string(REGEX REPLACE "/" "_" include_guard_infix "${include_guard_infix}") -string(REGEX REPLACE "-" "_" include_guard_prefix "${project_name_upper}") -string(JOIN "_" include_guard_prefix "${include_guard_prefix}" "${include_guard_infix}") -string(JOIN "/" output_header_prefix "${project_root}" "${project_subdir}") -cmake_path(GET output_header_path STEM output_header_stem) -string(TOUPPER "${output_header_stem}" include_guard_stem) -string(JOIN "_" include_guard "${include_guard_prefix}" "${include_guard_stem}" "H") -cmake_path(GET output_header_path FILENAME output_header_basename) -string(JOIN "/" header_include "${output_header_prefix}" "${output_header_basename}") -main() diff --git a/rust/automerge-c/cmake/file-regex-replace.cmake b/rust/automerge-c/cmake/file_regex_replace.cmake similarity index 87% rename from rust/automerge-c/cmake/file-regex-replace.cmake rename to rust/automerge-c/cmake/file_regex_replace.cmake index 09005bc2..27306458 100644 --- a/rust/automerge-c/cmake/file-regex-replace.cmake +++ b/rust/automerge-c/cmake/file_regex_replace.cmake @@ -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.") diff --git a/rust/automerge-c/cmake/file-touch.cmake b/rust/automerge-c/cmake/file_touch.cmake similarity index 82% rename from rust/automerge-c/cmake/file-touch.cmake rename to rust/automerge-c/cmake/file_touch.cmake index 2c196755..087d59b6 100644 --- a/rust/automerge-c/cmake/file-touch.cmake +++ b/rust/automerge-c/cmake/file_touch.cmake @@ -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.") diff --git a/rust/automerge-c/docs/CMakeLists.txt b/rust/automerge-c/docs/CMakeLists.txt deleted file mode 100644 index 1d94c872..00000000 --- a/rust/automerge-c/docs/CMakeLists.txt +++ /dev/null @@ -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() diff --git a/rust/automerge-c/examples/CMakeLists.txt b/rust/automerge-c/examples/CMakeLists.txt index f080237b..3395124c 100644 --- a/rust/automerge-c/examples/CMakeLists.txt +++ b/rust/automerge-c/examples/CMakeLists.txt @@ -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 "$" ) -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 diff --git a/rust/automerge-c/examples/README.md b/rust/automerge-c/examples/README.md index 17e69412..17aa2227 100644 --- a/rust/automerge-c/examples/README.md +++ b/rust/automerge-c/examples/README.md @@ -5,5 +5,5 @@ ```shell cmake -E make_directory automerge-c/build cmake -S automerge-c -B automerge-c/build -cmake --build automerge-c/build --target automerge_quickstart +cmake --build automerge-c/build --target example_quickstart ``` diff --git a/rust/automerge-c/examples/quickstart.c b/rust/automerge-c/examples/quickstart.c index ab6769ef..bc418511 100644 --- a/rust/automerge-c/examples/quickstart.c +++ b/rust/automerge-c/examples/quickstart.c @@ -3,127 +3,152 @@ #include #include -#include -#include -#include -#include -static bool abort_cb(AMstack**, void*); +static void abort_cb(AMresultStack**, uint8_t); /** * \brief Based on https://automerge.github.io/docs/quickstart */ int main(int argc, char** argv) { - AMstack* stack = NULL; - AMdoc* doc1; - AMitemToDoc(AMstackItem(&stack, AMcreate(NULL), abort_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1); - AMobjId const* const cards = - AMitemObjId(AMstackItem(&stack, AMmapPutObject(doc1, AM_ROOT, AMstr("cards"), AM_OBJ_TYPE_LIST), abort_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); - AMobjId const* const card1 = - AMitemObjId(AMstackItem(&stack, AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), abort_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); - AMstackItem(NULL, AMmapPutStr(doc1, card1, AMstr("title"), AMstr("Rewrite everything in Clojure")), abort_cb, - AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutBool(doc1, card1, AMstr("done"), false), abort_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMobjId const* const card2 = - AMitemObjId(AMstackItem(&stack, AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), abort_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); - AMstackItem(NULL, AMmapPutStr(doc1, card2, AMstr("title"), AMstr("Rewrite everything in Haskell")), abort_cb, - AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutBool(doc1, card2, AMstr("done"), false), abort_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(doc1, AMstr("Add card"), NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMresultStack* stack = NULL; + AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, abort_cb).doc; + AMobjId const* const cards = AMpush(&stack, + AMmapPutObject(doc1, AM_ROOT, AMstr("cards"), AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + abort_cb).obj_id; + AMobjId const* const card1 = AMpush(&stack, + AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), + AM_VALUE_OBJ_ID, + abort_cb).obj_id; + AMfree(AMmapPutStr(doc1, card1, AMstr("title"), AMstr("Rewrite everything in Clojure"))); + AMfree(AMmapPutBool(doc1, card1, AMstr("done"), false)); + AMobjId const* const card2 = AMpush(&stack, + AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), + AM_VALUE_OBJ_ID, + abort_cb).obj_id; + AMfree(AMmapPutStr(doc1, card2, AMstr("title"), AMstr("Rewrite everything in Haskell"))); + AMfree(AMmapPutBool(doc1, card2, AMstr("done"), false)); + AMfree(AMcommit(doc1, AMstr("Add card"), NULL)); - AMdoc* doc2; - AMitemToDoc(AMstackItem(&stack, AMcreate(NULL), abort_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2); - AMstackItem(NULL, AMmerge(doc2, doc1), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMdoc* doc2 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, abort_cb).doc; + AMfree(AMmerge(doc2, doc1)); - AMbyteSpan binary; - AMitemToBytes(AMstackItem(&stack, AMsave(doc1), abort_cb, AMexpect(AM_VAL_TYPE_BYTES)), &binary); - AMitemToDoc(AMstackItem(&stack, AMload(binary.src, binary.count), abort_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2); + AMbyteSpan const binary = AMpush(&stack, AMsave(doc1), AM_VALUE_BYTES, abort_cb).bytes; + doc2 = AMpush(&stack, AMload(binary.src, binary.count), AM_VALUE_DOC, abort_cb).doc; - AMstackItem(NULL, AMmapPutBool(doc1, card1, AMstr("done"), true), abort_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(doc1, AMstr("Mark card as done"), NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmapPutBool(doc1, card1, AMstr("done"), true)); + AMfree(AMcommit(doc1, AMstr("Mark card as done"), NULL)); - AMstackItem(NULL, AMlistDelete(doc2, cards, 0), abort_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(doc2, AMstr("Delete card"), NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMlistDelete(doc2, cards, 0)); + AMfree(AMcommit(doc2, AMstr("Delete card"), NULL)); - AMstackItem(NULL, AMmerge(doc1, doc2), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmerge(doc1, doc2)); - AMitems changes = AMstackItems(&stack, AMgetChanges(doc1, NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - AMitem* item = NULL; - while ((item = AMitemsNext(&changes, 1)) != NULL) { - AMchange const* change; - AMitemToChange(item, &change); - AMitems const heads = AMstackItems(&stack, AMitemFromChangeHash(AMchangeHash(change)), abort_cb, - AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - char* const c_msg = AMstrdup(AMchangeMessage(change), NULL); - printf("%s %zu\n", c_msg, AMobjSize(doc1, cards, &heads)); + AMchanges changes = AMpush(&stack, AMgetChanges(doc1, NULL), AM_VALUE_CHANGES, abort_cb).changes; + AMchange const* change = NULL; + while ((change = AMchangesNext(&changes, 1)) != NULL) { + AMbyteSpan const change_hash = AMchangeHash(change); + AMchangeHashes const heads = AMpush(&stack, + AMchangeHashesInit(&change_hash, 1), + AM_VALUE_CHANGE_HASHES, + abort_cb).change_hashes; + AMbyteSpan const msg = AMchangeMessage(change); + char* const c_msg = calloc(1, msg.count + 1); + strncpy(c_msg, msg.src, msg.count); + printf("%s %ld\n", c_msg, AMobjSize(doc1, cards, &heads)); free(c_msg); } - AMstackFree(&stack); + AMfreeStack(&stack); } +static char const* discriminant_suffix(AMvalueVariant const); + /** - * \brief Examines the result at the top of the given stack and, if it's - * invalid, prints an error message to `stderr`, deallocates all results - * in the stack and exits. + * \brief Prints an error message to `stderr`, deallocates all results in the + * given stack and exits. * - * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. - * \param[in] data A pointer to an owned `AMstackCallbackData` struct or `NULL`. - * \return `true` if the top `AMresult` in \p stack is valid, `false` otherwise. - * \pre \p stack `!= NULL`. + * \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct. + * \param[in] discriminant An `AMvalueVariant` enum tag. + * \pre \p stack` != NULL`. + * \post `*stack == NULL`. */ -static bool abort_cb(AMstack** stack, void* data) { +static void abort_cb(AMresultStack** stack, uint8_t discriminant) { static char buffer[512] = {0}; char const* suffix = NULL; if (!stack) { suffix = "Stack*"; - } else if (!*stack) { + } + else if (!*stack) { suffix = "Stack"; - } else if (!(*stack)->result) { + } + else if (!(*stack)->result) { suffix = ""; } if (suffix) { - fprintf(stderr, "Null `AMresult%s*`.\n", suffix); - AMstackFree(stack); + fprintf(stderr, "Null `AMresult%s*`.", suffix); + AMfreeStack(stack); exit(EXIT_FAILURE); - return false; + return; } AMstatus const status = AMresultStatus((*stack)->result); switch (status) { - case AM_STATUS_ERROR: - strcpy(buffer, "Error"); - break; - case AM_STATUS_INVALID_RESULT: - strcpy(buffer, "Invalid result"); - break; - case AM_STATUS_OK: - break; - default: - sprintf(buffer, "Unknown `AMstatus` tag %d", status); + case AM_STATUS_ERROR: strcpy(buffer, "Error"); break; + case AM_STATUS_INVALID_RESULT: strcpy(buffer, "Invalid result"); break; + case AM_STATUS_OK: break; + default: sprintf(buffer, "Unknown `AMstatus` tag %d", status); } if (buffer[0]) { - char* const c_msg = AMstrdup(AMresultError((*stack)->result), NULL); - fprintf(stderr, "%s; %s.\n", buffer, c_msg); + AMbyteSpan const msg = AMerrorMessage((*stack)->result); + char* const c_msg = calloc(1, msg.count + 1); + strncpy(c_msg, msg.src, msg.count); + fprintf(stderr, "%s; %s.", buffer, c_msg); free(c_msg); - AMstackFree(stack); + AMfreeStack(stack); exit(EXIT_FAILURE); - return false; + return; } - if (data) { - AMstackCallbackData* sc_data = (AMstackCallbackData*)data; - AMvalType const tag = AMitemValType(AMresultItem((*stack)->result)); - if (tag != sc_data->bitmask) { - fprintf(stderr, "Unexpected tag `%s` (%d) instead of `%s` at %s:%d.\n", AMvalTypeToString(tag), tag, - AMvalTypeToString(sc_data->bitmask), sc_data->file, sc_data->line); - free(sc_data); - AMstackFree(stack); - exit(EXIT_FAILURE); - return false; - } - } - free(data); - return true; + AMvalue const value = AMresultValue((*stack)->result); + fprintf(stderr, "Unexpected tag `AM_VALUE_%s` (%d); expected `AM_VALUE_%s`.", + discriminant_suffix(value.tag), + value.tag, + discriminant_suffix(discriminant)); + AMfreeStack(stack); + exit(EXIT_FAILURE); +} + +/** + * \brief Gets the suffix for a discriminant's corresponding string + * representation. + * + * \param[in] discriminant An `AMvalueVariant` enum tag. + * \return A UTF-8 string. + */ +static char const* discriminant_suffix(AMvalueVariant const discriminant) { + char const* suffix = NULL; + switch (discriminant) { + case AM_VALUE_ACTOR_ID: suffix = "ACTOR_ID"; break; + case AM_VALUE_BOOLEAN: suffix = "BOOLEAN"; break; + case AM_VALUE_BYTES: suffix = "BYTES"; break; + case AM_VALUE_CHANGE_HASHES: suffix = "CHANGE_HASHES"; break; + case AM_VALUE_CHANGES: suffix = "CHANGES"; break; + case AM_VALUE_COUNTER: suffix = "COUNTER"; break; + case AM_VALUE_DOC: suffix = "DOC"; break; + case AM_VALUE_F64: suffix = "F64"; break; + case AM_VALUE_INT: suffix = "INT"; break; + case AM_VALUE_LIST_ITEMS: suffix = "LIST_ITEMS"; break; + case AM_VALUE_MAP_ITEMS: suffix = "MAP_ITEMS"; break; + case AM_VALUE_NULL: suffix = "NULL"; break; + case AM_VALUE_OBJ_ID: suffix = "OBJ_ID"; break; + case AM_VALUE_OBJ_ITEMS: suffix = "OBJ_ITEMS"; break; + case AM_VALUE_STR: suffix = "STR"; break; + case AM_VALUE_STRS: suffix = "STRINGS"; break; + case AM_VALUE_SYNC_MESSAGE: suffix = "SYNC_MESSAGE"; break; + case AM_VALUE_SYNC_STATE: suffix = "SYNC_STATE"; break; + case AM_VALUE_TIMESTAMP: suffix = "TIMESTAMP"; break; + case AM_VALUE_UINT: suffix = "UINT"; break; + case AM_VALUE_VOID: suffix = "VOID"; break; + default: suffix = "..."; + } + return suffix; } diff --git a/rust/automerge-c/docs/img/brandmark.png b/rust/automerge-c/img/brandmark.png similarity index 100% rename from rust/automerge-c/docs/img/brandmark.png rename to rust/automerge-c/img/brandmark.png diff --git a/rust/automerge-c/include/automerge-c/utils/result.h b/rust/automerge-c/include/automerge-c/utils/result.h deleted file mode 100644 index ab8a2f93..00000000 --- a/rust/automerge-c/include/automerge-c/utils/result.h +++ /dev/null @@ -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 - -#include - -/** - * \brief Transfers the items within an arbitrary list of results into a - * new result in their order of specification. - * \param[in] count The count of subsequent arguments. - * \param[in] ... A \p count list of arguments, each of which is a pointer to - * an `AMresult` struct whose items will be transferred out of it - * and which is subsequently freed. - * \return A pointer to an `AMresult` struct or `NULL`. - * \pre `∀𝑥 ∈` \p ... `, AMresultStatus(𝑥) == AM_STATUS_OK` - * \post `(∃𝑥 ∈` \p ... `, AMresultStatus(𝑥) != AM_STATUS_OK) -> NULL` - * \attention All `AMresult` struct pointer arguments are passed to - * `AMresultFree()` regardless of success; use `AMresultCat()` - * instead if you wish to pass them to `AMresultFree()` yourself. - * \warning The returned `AMresult` struct pointer must be passed to - * `AMresultFree()` in order to avoid a memory leak. - */ -AMresult* AMresultFrom(int count, ...); - -#endif /* AUTOMERGE_C_UTILS_RESULT_H */ diff --git a/rust/automerge-c/include/automerge-c/utils/stack.h b/rust/automerge-c/include/automerge-c/utils/stack.h deleted file mode 100644 index a8e9fd08..00000000 --- a/rust/automerge-c/include/automerge-c/utils/stack.h +++ /dev/null @@ -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 - -/** - * \struct AMstack - * \brief A node in a singly-linked list of result pointers. - */ -typedef struct AMstack { - /** A result to be deallocated. */ - AMresult* result; - /** The previous node in the singly-linked list or `NULL`. */ - struct AMstack* prev; -} AMstack; - -/** - * \memberof AMstack - * \brief The prototype of a function that examines the result at the top of - * the given stack in terms of some arbitrary data. - * - * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. - * \param[in] data A pointer to arbitrary data or `NULL`. - * \return `true` if the top `AMresult` struct in \p stack is valid, `false` - * otherwise. - * \pre \p stack `!= NULL`. - */ -typedef bool (*AMstackCallback)(AMstack** stack, void* data); - -/** - * \memberof AMstack - * \brief Deallocates the storage for a stack of results. - * - * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. - * \pre \p stack `!= NULL` - * \post `*stack == NULL` - */ -void AMstackFree(AMstack** stack); - -/** - * \memberof AMstack - * \brief Gets a result from the stack after removing it. - * - * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. - * \param[in] result A pointer to the `AMresult` to be popped or `NULL` to - * select the top result in \p stack. - * \return A pointer to an `AMresult` struct or `NULL`. - * \pre \p stack `!= NULL` - * \warning The returned `AMresult` struct pointer must be passed to - * `AMresultFree()` in order to avoid a memory leak. - */ -AMresult* AMstackPop(AMstack** stack, AMresult const* result); - -/** - * \memberof AMstack - * \brief Pushes the given result onto the given stack, calls the given - * callback with the given data to validate it and then either gets the - * result if it's valid or gets `NULL` instead. - * - * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. - * \param[in] result A pointer to an `AMresult` struct. - * \param[in] callback A pointer to a function with the same signature as - * `AMstackCallback()` or `NULL`. - * \param[in] data A pointer to arbitrary data or `NULL` which is passed to - * \p callback. - * \return \p result or `NULL`. - * \warning If \p stack `== NULL` then \p result is deallocated in order to - * avoid a memory leak. - */ -AMresult* AMstackResult(AMstack** stack, AMresult* result, AMstackCallback callback, void* data); - -/** - * \memberof AMstack - * \brief Pushes the given result onto the given stack, calls the given - * callback with the given data to validate it and then either gets the - * first item in the sequence of items within that result if it's valid - * or gets `NULL` instead. - * - * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. - * \param[in] result A pointer to an `AMresult` struct. - * \param[in] callback A pointer to a function with the same signature as - * `AMstackCallback()` or `NULL`. - * \param[in] data A pointer to arbitrary data or `NULL` which is passed to - * \p callback. - * \return A pointer to an `AMitem` struct or `NULL`. - * \warning If \p stack `== NULL` then \p result is deallocated in order to - * avoid a memory leak. - */ -AMitem* AMstackItem(AMstack** stack, AMresult* result, AMstackCallback callback, void* data); - -/** - * \memberof AMstack - * \brief Pushes the given result onto the given stack, calls the given - * callback with the given data to validate it and then either gets an - * `AMitems` struct over the sequence of items within that result if it's - * valid or gets an empty `AMitems` instead. - * - * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. - * \param[in] result A pointer to an `AMresult` struct. - * \param[in] callback A pointer to a function with the same signature as - * `AMstackCallback()` or `NULL`. - * \param[in] data A pointer to arbitrary data or `NULL` which is passed to - * \p callback. - * \return An `AMitems` struct. - * \warning If \p stack `== NULL` then \p result is deallocated immediately - * in order to avoid a memory leak. - */ -AMitems AMstackItems(AMstack** stack, AMresult* result, AMstackCallback callback, void* data); - -/** - * \memberof AMstack - * \brief Gets the count of results that have been pushed onto the stack. - * - * \param[in,out] stack A pointer to an `AMstack` struct. - * \return A 64-bit unsigned integer. - */ -size_t AMstackSize(AMstack const* const stack); - -#endif /* AUTOMERGE_C_UTILS_STACK_H */ diff --git a/rust/automerge-c/include/automerge-c/utils/stack_callback_data.h b/rust/automerge-c/include/automerge-c/utils/stack_callback_data.h deleted file mode 100644 index 6f9f1edb..00000000 --- a/rust/automerge-c/include/automerge-c/utils/stack_callback_data.h +++ /dev/null @@ -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 - -/** - * \struct AMstackCallbackData - * \brief A data structure for passing the parameters of an item value test - * to an implementation of the `AMstackCallback` function prototype. - */ -typedef struct { - /** A bitmask of `AMvalType` tags. */ - AMvalType bitmask; - /** A null-terminated file path string. */ - char const* file; - /** The ordinal number of a line within a file. */ - int line; -} AMstackCallbackData; - -/** - * \memberof AMstackCallbackData - * \brief Allocates a new `AMstackCallbackData` struct and initializes its - * members from their corresponding arguments. - * - * \param[in] bitmask A bitmask of `AMvalType` tags. - * \param[in] file A null-terminated file path string. - * \param[in] line The ordinal number of a line within a file. - * \return A pointer to a disowned `AMstackCallbackData` struct. - * \warning The returned pointer must be passed to `free()` to avoid a memory - * leak. - */ -AMstackCallbackData* AMstackCallbackDataInit(AMvalType const bitmask, char const* const file, int const line); - -/** - * \memberof AMstackCallbackData - * \def AMexpect - * \brief Allocates a new `AMstackCallbackData` struct and initializes it from - * an `AMvalueType` bitmask. - * - * \param[in] bitmask A bitmask of `AMvalType` tags. - * \return A pointer to a disowned `AMstackCallbackData` struct. - * \warning The returned pointer must be passed to `free()` to avoid a memory - * leak. - */ -#define AMexpect(bitmask) AMstackCallbackDataInit(bitmask, __FILE__, __LINE__) - -#endif /* AUTOMERGE_C_UTILS_PUSH_CALLBACK_DATA_H */ diff --git a/rust/automerge-c/include/automerge-c/utils/string.h b/rust/automerge-c/include/automerge-c/utils/string.h deleted file mode 100644 index 4d61c2e9..00000000 --- a/rust/automerge-c/include/automerge-c/utils/string.h +++ /dev/null @@ -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 - -/** - * \memberof AMbyteSpan - * \brief Returns a pointer to a null-terminated byte string which is a - * duplicate of the given UTF-8 string view except for the substitution - * of its NUL (0) characters with the specified null-terminated byte - * string. - * - * \param[in] str A UTF-8 string view as an `AMbyteSpan` struct. - * \param[in] nul A null-terminated byte string to substitute for NUL characters - * or `NULL` to substitute `"\\0"` for NUL characters. - * \return A disowned null-terminated byte string. - * \pre \p str.src `!= NULL` - * \pre \p str.count `<= sizeof(`\p str.src `)` - * \warning The returned pointer must be passed to `free()` to avoid a memory - * leak. - */ -char* AMstrdup(AMbyteSpan const str, char const* nul); - -#endif /* AUTOMERGE_C_UTILS_STRING_H */ diff --git a/rust/automerge-c/src/CMakeLists.txt b/rust/automerge-c/src/CMakeLists.txt new file mode 100644 index 00000000..e02c0a96 --- /dev/null +++ b/rust/automerge-c/src/CMakeLists.txt @@ -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()` calls. + ${CMAKE_COMMAND} -DMATCH_REGEX=USIZE_ -DREPLACE_EXPR=\+${CMAKE_SIZEOF_VOID_P} -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h + WORKING_DIRECTORY + ${CMAKE_SOURCE_DIR} + COMMENT + "Compensating for cbindgen deficits..." + VERBATIM +) + +if(BUILD_SHARED_LIBS) + if(WIN32) + set(LIBRARY_DESTINATION "${CMAKE_INSTALL_BINDIR}") + else() + set(LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}") + endif() + + set(LIBRARY_DEFINE_SYMBOL "${SYMBOL_PREFIX}_EXPORTS") + + # \note The basename of an import library output by Cargo is the filename + # of its corresponding shared library. + set(LIBRARY_IMPLIB "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}") + + set(LIBRARY_LOCATION "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}") + + set(LIBRARY_NO_SONAME "${WIN32}") + + set(LIBRARY_SONAME "${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}") + + set(LIBRARY_TYPE "SHARED") +else() + set(LIBRARY_DEFINE_SYMBOL "") + + set(LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}") + + set(LIBRARY_IMPLIB "") + + set(LIBRARY_LOCATION "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}") + + set(LIBRARY_NO_SONAME "TRUE") + + set(LIBRARY_SONAME "") + + set(LIBRARY_TYPE "STATIC") +endif() + +add_library(${LIBRARY_NAME} ${LIBRARY_TYPE} IMPORTED GLOBAL) + +set_target_properties( + ${LIBRARY_NAME} + PROPERTIES + # \note Cargo writes a debug build into a nested directory instead of + # decorating its name. + DEBUG_POSTFIX "" + DEFINE_SYMBOL "${LIBRARY_DEFINE_SYMBOL}" + IMPORTED_IMPLIB "${LIBRARY_IMPLIB}" + IMPORTED_LOCATION "${LIBRARY_LOCATION}" + IMPORTED_NO_SONAME "${LIBRARY_NO_SONAME}" + IMPORTED_SONAME "${LIBRARY_SONAME}" + LINKER_LANGUAGE C + PUBLIC_HEADER "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h" + SOVERSION "${PROJECT_VERSION_MAJOR}" + VERSION "${PROJECT_VERSION}" + # \note Cargo exports all of the symbols automatically. + WINDOWS_EXPORT_ALL_SYMBOLS "TRUE" +) + +target_compile_definitions(${LIBRARY_NAME} INTERFACE $) + +target_include_directories( + ${LIBRARY_NAME} + INTERFACE + "$" +) + +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) + +set(THREADS_PREFER_PTHREAD_FLAG TRUE) + +find_package(Threads REQUIRED) + +set(LIBRARY_DEPENDENCIES Threads::Threads ${CMAKE_DL_LIBS}) + +if(WIN32) + list(APPEND LIBRARY_DEPENDENCIES Bcrypt userenv ws2_32) +else() + list(APPEND LIBRARY_DEPENDENCIES m) +endif() + +target_link_libraries(${LIBRARY_NAME} INTERFACE ${LIBRARY_DEPENDENCIES}) + +install( + FILES $ + TYPE LIB + # \note The basename of an import library output by Cargo is the filename + # of its corresponding shared library. + RENAME "${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}" + OPTIONAL +) + +set(LIBRARY_FILE_NAME "${CMAKE_${LIBRARY_TYPE}_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_${LIBRARY_TYPE}_LIBRARY_SUFFIX}") + +install( + FILES $ + RENAME "${LIBRARY_FILE_NAME}" + DESTINATION ${LIBRARY_DESTINATION} +) + +install( + FILES $ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} +) + +find_package(Doxygen OPTIONAL_COMPONENTS dot) + +if(DOXYGEN_FOUND) + set(DOXYGEN_ALIASES "installed_headerfile=\\headerfile ${LIBRARY_NAME}.h <${PROJECT_NAME}/${LIBRARY_NAME}.h>") + + set(DOXYGEN_GENERATE_LATEX YES) + + set(DOXYGEN_PDF_HYPERLINKS YES) + + set(DOXYGEN_PROJECT_LOGO "${CMAKE_SOURCE_DIR}/img/brandmark.png") + + set(DOXYGEN_SORT_BRIEF_DOCS YES) + + set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md") + + doxygen_add_docs( + ${LIBRARY_NAME}_docs + "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h" + "${CMAKE_SOURCE_DIR}/README.md" + USE_STAMP_FILE + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Producing documentation with Doxygen..." + ) + + # \note A Doxygen input file isn't a file-level dependency so the Doxygen + # command must instead depend upon a target that outputs the file or + # it will just output an error message when it can't be found. + add_dependencies(${LIBRARY_NAME}_docs ${LIBRARY_NAME}_artifacts) +endif() diff --git a/rust/automerge-c/src/actor_id.rs b/rust/automerge-c/src/actor_id.rs index 5a28959e..bc86d5ef 100644 --- a/rust/automerge-c/src/actor_id.rs +++ b/rust/automerge-c/src/actor_id.rs @@ -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 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::random())) } /// \memberof AMactorId -/// \brief Allocates a new actor identifier and initializes it from an array of -/// bytes value. +/// \brief Allocates a new actor identifier and initializes it from a sequence +/// of bytes. /// -/// \param[in] src A pointer to an array of bytes. -/// \param[in] count The count of bytes to copy from the array pointed to by -/// \p src. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item. -/// \pre \p src `!= NULL` -/// \pre `sizeof(`\p src `) > 0` -/// \pre \p count `<= sizeof(`\p src `)` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in] src A pointer to a contiguous sequence of bytes. +/// \param[in] count The number of bytes to copy from \p src. +/// \pre `0 <` \p count `<= sizeof(`\p src`)`. +/// \return A pointer to an `AMresult` struct containing a pointer to an +/// `AMactorId` struct. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety -/// src must be a byte array of length `>= count` +/// src must be a byte array of size `>= count` #[no_mangle] -pub unsafe extern "C" fn AMactorIdFromBytes(src: *const u8, count: usize) -> *mut AMresult { - if !src.is_null() { - let value = std::slice::from_raw_parts(src, count); - to_result(Ok::(am::ActorId::from( - value, - ))) - } else { - AMresult::error("Invalid uint8_t*").into() - } +pub unsafe extern "C" fn AMactorIdInitBytes(src: *const u8, count: usize) -> *mut AMresult { + let slice = std::slice::from_raw_parts(src, count); + to_result(Ok::(am::ActorId::from( + slice, + ))) } /// \memberof AMactorId /// \brief Allocates a new actor identifier and initializes it from a -/// hexadecimal UTF-8 string view value. +/// hexadecimal string. /// -/// \param[in] value A UTF-8 string view as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item. -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in] hex_str A UTF-8 string view as an `AMbyteSpan` struct. +/// \return A pointer to an `AMresult` struct containing a pointer to an +/// `AMactorId` struct. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety /// hex_str must be a valid pointer to an AMbyteSpan #[no_mangle] -pub unsafe extern "C" fn AMactorIdFromStr(value: AMbyteSpan) -> *mut AMresult { +pub unsafe extern "C" fn AMactorIdInitStr(hex_str: AMbyteSpan) -> *mut AMresult { use am::AutomergeError::InvalidActorId; - to_result(match (&value).try_into() { + to_result(match (&hex_str).try_into() { Ok(s) => match am::ActorId::from_str(s) { Ok(actor_id) => Ok(actor_id), Err(_) => Err(InvalidActorId(String::from(s))), @@ -174,12 +169,11 @@ pub unsafe extern "C" fn AMactorIdFromStr(value: AMbyteSpan) -> *mut AMresult { } /// \memberof AMactorId -/// \brief Gets the value of an actor identifier as a UTF-8 hexadecimal string -/// view. +/// \brief Gets the value of an actor identifier as a hexadecimal string. /// /// \param[in] actor_id A pointer to an `AMactorId` struct. +/// \pre \p actor_id `!= NULL`. /// \return A UTF-8 string view as an `AMbyteSpan` struct. -/// \pre \p actor_id `!= NULL` /// \internal /// /// # Safety diff --git a/rust/automerge-c/src/byte_span.rs b/rust/automerge-c/src/byte_span.rs index 5855cfc7..fd4c3ca0 100644 --- a/rust/automerge-c/src/byte_span.rs +++ b/rust/automerge-c/src/byte_span.rs @@ -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 NEVER CALL `free()` ON \p src! + /// \warning \p src is only valid until the `AMfree()` function is called + /// on the `AMresult` struct that stores the array of bytes to + /// which it points. 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 { - fn from(byte_span: &AMbyteSpan) -> Self { - <&[u8]>::from(byte_span).to_vec() - } -} - -impl TryFrom<&AMbyteSpan> for am::ChangeHash { - type Error = am::AutomergeError; - - fn try_from(byte_span: &AMbyteSpan) -> Result { - use am::AutomergeError::InvalidChangeHashBytes; - - let slice: &[u8] = byte_span.into(); - match slice.try_into() { - Ok(change_hash) => Ok(change_hash), - Err(e) => Err(InvalidChangeHashBytes(e)), - } - } -} - impl TryFrom<&AMbyteSpan> for &str { type Error = am::AutomergeError; - fn try_from(byte_span: &AMbyteSpan) -> Result { + fn try_from(span: &AMbyteSpan) -> Result { 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 } } diff --git a/rust/automerge-c/src/change.rs b/rust/automerge-c/src/change.rs index 8529ed94..d64a2635 100644 --- a/rust/automerge-c/src/change.rs +++ b/rust/automerge-c/src/change.rs @@ -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>, + changehash: RefCell>, } 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 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::, _>>( - am::Automerge::load(data) - .and_then(|d| d.get_changes(&[]).map(|c| c.into_iter().cloned().collect())), - ) -} - /// \memberof AMchange /// \brief Gets the maximum operation index of a change. /// /// \param[in] change A pointer to an `AMchange` struct. /// \return A 64-bit unsigned integer. -/// \pre \p change `!= NULL` +/// \pre \p change `!= NULL`. /// \internal /// /// # Safety @@ -247,8 +221,8 @@ pub unsafe extern "C" fn AMchangeMaxOp(change: *const AMchange) -> u64 { /// \brief Gets the message of a change. /// /// \param[in] change A pointer to an `AMchange` struct. -/// \return An `AMbyteSpan` struct for a UTF-8 string. -/// \pre \p change `!= NULL` +/// \return A UTF-8 string view as an `AMbyteSpan` struct. +/// \pre \p change `!= NULL`. /// \internal /// /// # Safety @@ -266,7 +240,7 @@ pub unsafe extern "C" fn AMchangeMessage(change: *const AMchange) -> AMbyteSpan /// /// \param[in] change A pointer to an `AMchange` struct. /// \return A 64-bit unsigned integer. -/// \pre \p change `!= NULL` +/// \pre \p change `!= NULL`. /// \internal /// /// # Safety @@ -285,7 +259,7 @@ pub unsafe extern "C" fn AMchangeSeq(change: *const AMchange) -> u64 { /// /// \param[in] change A pointer to an `AMchange` struct. /// \return A 64-bit unsigned integer. -/// \pre \p change `!= NULL` +/// \pre \p change `!= NULL`. /// \internal /// /// # Safety @@ -293,9 +267,10 @@ pub unsafe extern "C" fn AMchangeSeq(change: *const AMchange) -> u64 { #[no_mangle] pub unsafe extern "C" fn AMchangeSize(change: *const AMchange) -> usize { if let Some(change) = change.as_ref() { - return change.as_ref().len(); + change.as_ref().len() + } else { + 0 } - 0 } /// \memberof AMchange @@ -303,7 +278,7 @@ pub unsafe extern "C" fn AMchangeSize(change: *const AMchange) -> usize { /// /// \param[in] change A pointer to an `AMchange` struct. /// \return A 64-bit unsigned integer. -/// \pre \p change `!= NULL` +/// \pre \p change `!= NULL`. /// \internal /// /// # Safety @@ -322,7 +297,7 @@ pub unsafe extern "C" fn AMchangeStartOp(change: *const AMchange) -> u64 { /// /// \param[in] change A pointer to an `AMchange` struct. /// \return A 64-bit signed integer. -/// \pre \p change `!= NULL` +/// \pre \p change `!= NULL`. /// \internal /// /// # Safety @@ -340,8 +315,8 @@ pub unsafe extern "C" fn AMchangeTime(change: *const AMchange) -> i64 { /// \brief Gets the raw bytes of a change. /// /// \param[in] change A pointer to an `AMchange` struct. -/// \return An `AMbyteSpan` struct for an array of bytes. -/// \pre \p change `!= NULL` +/// \return An `AMbyteSpan` struct. +/// \pre \p change `!= NULL`. /// \internal /// /// # Safety @@ -354,3 +329,28 @@ pub unsafe extern "C" fn AMchangeRawBytes(change: *const AMchange) -> AMbyteSpan Default::default() } } + +/// \memberof AMchange +/// \brief Loads a document into a sequence of changes. +/// +/// \param[in] src A pointer to an array of bytes. +/// \param[in] count The number of bytes in \p src to load. +/// \return A pointer to an `AMresult` struct containing a sequence of +/// `AMchange` structs. +/// \pre \p src `!= NULL`. +/// \pre `0 <` \p count `<= sizeof(`\p src`)`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. +/// \internal +/// +/// # Safety +/// src must be a byte array of size `>= count` +#[no_mangle] +pub unsafe extern "C" fn AMchangeLoadDocument(src: *const u8, count: usize) -> *mut AMresult { + let mut data = Vec::new(); + data.extend_from_slice(std::slice::from_raw_parts(src, count)); + to_result::, _>>( + am::Automerge::load(&data) + .and_then(|d| d.get_changes(&[]).map(|c| c.into_iter().cloned().collect())), + ) +} diff --git a/rust/automerge-c/src/change_hashes.rs b/rust/automerge-c/src/change_hashes.rs new file mode 100644 index 00000000..029612e9 --- /dev/null +++ b/rust/automerge-c/src/change_hashes.rs @@ -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()` call +/// (https://github.com/eqrion/cbindgen/issues/252) but it will +/// propagate the name of a constant initialized from it so if the +/// constant's name is a symbolic representation of the value it can be +/// converted into a number by post-processing the header it generated. +pub const USIZE_USIZE_USIZE_: usize = size_of::(); + +impl Detail { + fn new(change_hashes: &[am::ChangeHash], offset: isize) -> Self { + Self { + len: change_hashes.len(), + offset, + ptr: change_hashes.as_ptr() as *const c_void, + } + } + + pub fn advance(&mut self, n: isize) { + if n == 0 { + return; + } + let len = self.len as isize; + self.offset = if self.offset < 0 { + // It's reversed. + let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); + if unclipped >= 0 { + // Clip it to the forward stop. + len + } else { + std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) + } + } else { + let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); + if unclipped < 0 { + // Clip it to the reverse stop. + -(len + 1) + } else { + std::cmp::max(0, std::cmp::min(unclipped, len)) + } + } + } + + pub fn get_index(&self) -> usize { + (self.offset + + if self.offset < 0 { + self.len as isize + } else { + 0 + }) as usize + } + + pub fn next(&mut self, n: isize) -> Option<&am::ChangeHash> { + if self.is_stopped() { + return None; + } + let slice: &[am::ChangeHash] = + unsafe { std::slice::from_raw_parts(self.ptr as *const am::ChangeHash, self.len) }; + let value = &slice[self.get_index()]; + self.advance(n); + Some(value) + } + + pub fn is_stopped(&self) -> bool { + let len = self.len as isize; + self.offset < -len || self.offset == len + } + + pub fn prev(&mut self, n: isize) -> Option<&am::ChangeHash> { + self.advance(-n); + if self.is_stopped() { + return None; + } + let slice: &[am::ChangeHash] = + unsafe { std::slice::from_raw_parts(self.ptr as *const am::ChangeHash, self.len) }; + Some(&slice[self.get_index()]) + } + + pub fn reversed(&self) -> Self { + Self { + len: self.len, + offset: -(self.offset + 1), + ptr: self.ptr, + } + } + + pub fn rewound(&self) -> Self { + Self { + len: self.len, + offset: if self.offset < 0 { -1 } else { 0 }, + ptr: self.ptr, + } + } +} + +impl From for [u8; USIZE_USIZE_USIZE_] { + fn from(detail: Detail) -> Self { + unsafe { + std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) + .try_into() + .unwrap() + } + } +} + +/// \struct AMchangeHashes +/// \installed_headerfile +/// \brief A random-access iterator over a sequence of change hashes. +#[repr(C)] +#[derive(Eq, PartialEq)] +pub struct AMchangeHashes { + /// An implementation detail that is intentionally opaque. + /// \warning Modifying \p detail will cause undefined behavior. + /// \note The actual size of \p detail will vary by platform, this is just + /// the one for the platform this documentation was built on. + detail: [u8; USIZE_USIZE_USIZE_], +} + +impl AMchangeHashes { + pub fn new(change_hashes: &[am::ChangeHash]) -> Self { + Self { + detail: Detail::new(change_hashes, 0).into(), + } + } + + pub fn advance(&mut self, n: isize) { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.advance(n); + } + + pub fn len(&self) -> usize { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + detail.len + } + + pub fn next(&mut self, n: isize) -> Option<&am::ChangeHash> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.next(n) + } + + pub fn prev(&mut self, n: isize) -> Option<&am::ChangeHash> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.prev(n) + } + + pub fn reversed(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.reversed().into(), + } + } + + pub fn rewound(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.rewound().into(), + } + } +} + +impl AsRef<[am::ChangeHash]> for AMchangeHashes { + fn as_ref(&self) -> &[am::ChangeHash] { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + unsafe { std::slice::from_raw_parts(detail.ptr as *const am::ChangeHash, detail.len) } + } +} + +impl Default for AMchangeHashes { + fn default() -> Self { + Self { + detail: [0; USIZE_USIZE_USIZE_], + } + } +} + +/// \memberof AMchangeHashes +/// \brief Advances an iterator over a sequence of change hashes by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction. +/// +/// \param[in,out] change_hashes A pointer to an `AMchangeHashes` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \pre \p change_hashes `!= NULL`. +/// \internal +/// +/// #Safety +/// change_hashes must be a valid pointer to an AMchangeHashes +#[no_mangle] +pub unsafe extern "C" fn AMchangeHashesAdvance(change_hashes: *mut AMchangeHashes, n: isize) { + if let Some(change_hashes) = change_hashes.as_mut() { + change_hashes.advance(n); + }; +} + +/// \memberof AMchangeHashes +/// \brief Compares the sequences of change hashes underlying a pair of +/// iterators. +/// +/// \param[in] change_hashes1 A pointer to an `AMchangeHashes` struct. +/// \param[in] change_hashes2 A pointer to an `AMchangeHashes` struct. +/// \return `-1` if \p change_hashes1 `<` \p change_hashes2, `0` if +/// \p change_hashes1 `==` \p change_hashes2 and `1` if +/// \p change_hashes1 `>` \p change_hashes2. +/// \pre \p change_hashes1 `!= NULL`. +/// \pre \p change_hashes2 `!= NULL`. +/// \internal +/// +/// #Safety +/// change_hashes1 must be a valid pointer to an AMchangeHashes +/// change_hashes2 must be a valid pointer to an AMchangeHashes +#[no_mangle] +pub unsafe extern "C" fn AMchangeHashesCmp( + change_hashes1: *const AMchangeHashes, + change_hashes2: *const AMchangeHashes, +) -> isize { + match (change_hashes1.as_ref(), change_hashes2.as_ref()) { + (Some(change_hashes1), Some(change_hashes2)) => { + match change_hashes1.as_ref().cmp(change_hashes2.as_ref()) { + Ordering::Less => -1, + Ordering::Equal => 0, + Ordering::Greater => 1, + } + } + (None, Some(_)) => -1, + (Some(_), None) => 1, + (None, None) => 0, + } +} + +/// \memberof AMchangeHashes +/// \brief Allocates an iterator over a sequence of change hashes and +/// initializes it from a sequence of byte spans. +/// +/// \param[in] src A pointer to an array of `AMbyteSpan` structs. +/// \param[in] count The number of `AMbyteSpan` structs to copy from \p src. +/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` +/// struct. +/// \pre \p src `!= NULL`. +/// \pre `0 <` \p count `<= sizeof(`\p src`) / sizeof(AMbyteSpan)`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. +/// \internal +/// +/// # Safety +/// src must be an AMbyteSpan array of size `>= count` +#[no_mangle] +pub unsafe extern "C" fn AMchangeHashesInit(src: *const AMbyteSpan, count: usize) -> *mut AMresult { + let mut change_hashes = Vec::::new(); + for n in 0..count { + let byte_span = &*src.add(n); + let slice = std::slice::from_raw_parts(byte_span.src, byte_span.count); + match slice.try_into() { + Ok(change_hash) => { + change_hashes.push(change_hash); + } + Err(e) => { + return to_result(Err(e)); + } + } + } + to_result(Ok::, am::InvalidChangeHashSlice>( + change_hashes, + )) +} + +/// \memberof AMchangeHashes +/// \brief Gets the change hash at the current position of an iterator over a +/// sequence of change hashes and then advances it by at most \p |n| +/// positions where the sign of \p n is relative to the iterator's +/// direction. +/// +/// \param[in,out] change_hashes A pointer to an `AMchangeHashes` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return An `AMbyteSpan` struct with `.src == NULL` when \p change_hashes +/// was previously advanced past its forward/reverse limit. +/// \pre \p change_hashes `!= NULL`. +/// \internal +/// +/// #Safety +/// change_hashes must be a valid pointer to an AMchangeHashes +#[no_mangle] +pub unsafe extern "C" fn AMchangeHashesNext( + change_hashes: *mut AMchangeHashes, + n: isize, +) -> AMbyteSpan { + if let Some(change_hashes) = change_hashes.as_mut() { + if let Some(change_hash) = change_hashes.next(n) { + return change_hash.into(); + } + } + Default::default() +} + +/// \memberof AMchangeHashes +/// \brief Advances an iterator over a sequence of change hashes by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction and then gets the change hash at its new +/// position. +/// +/// \param[in,out] change_hashes A pointer to an `AMchangeHashes` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return An `AMbyteSpan` struct with `.src == NULL` when \p change_hashes is +/// presently advanced past its forward/reverse limit. +/// \pre \p change_hashes `!= NULL`. +/// \internal +/// +/// #Safety +/// change_hashes must be a valid pointer to an AMchangeHashes +#[no_mangle] +pub unsafe extern "C" fn AMchangeHashesPrev( + change_hashes: *mut AMchangeHashes, + n: isize, +) -> AMbyteSpan { + if let Some(change_hashes) = change_hashes.as_mut() { + if let Some(change_hash) = change_hashes.prev(n) { + return change_hash.into(); + } + } + Default::default() +} + +/// \memberof AMchangeHashes +/// \brief Gets the size of the sequence of change hashes underlying an +/// iterator. +/// +/// \param[in] change_hashes A pointer to an `AMchangeHashes` struct. +/// \return The count of values in \p change_hashes. +/// \pre \p change_hashes `!= NULL`. +/// \internal +/// +/// #Safety +/// change_hashes must be a valid pointer to an AMchangeHashes +#[no_mangle] +pub unsafe extern "C" fn AMchangeHashesSize(change_hashes: *const AMchangeHashes) -> usize { + if let Some(change_hashes) = change_hashes.as_ref() { + change_hashes.len() + } else { + 0 + } +} + +/// \memberof AMchangeHashes +/// \brief Creates an iterator over the same sequence of change hashes as the +/// given one but with the opposite position and direction. +/// +/// \param[in] change_hashes A pointer to an `AMchangeHashes` struct. +/// \return An `AMchangeHashes` struct +/// \pre \p change_hashes `!= NULL`. +/// \internal +/// +/// #Safety +/// change_hashes must be a valid pointer to an AMchangeHashes +#[no_mangle] +pub unsafe extern "C" fn AMchangeHashesReversed( + change_hashes: *const AMchangeHashes, +) -> AMchangeHashes { + if let Some(change_hashes) = change_hashes.as_ref() { + change_hashes.reversed() + } else { + Default::default() + } +} + +/// \memberof AMchangeHashes +/// \brief Creates an iterator at the starting position over the same sequence +/// of change hashes as the given one. +/// +/// \param[in] change_hashes A pointer to an `AMchangeHashes` struct. +/// \return An `AMchangeHashes` struct +/// \pre \p change_hashes `!= NULL`. +/// \internal +/// +/// #Safety +/// change_hashes must be a valid pointer to an AMchangeHashes +#[no_mangle] +pub unsafe extern "C" fn AMchangeHashesRewound( + change_hashes: *const AMchangeHashes, +) -> AMchangeHashes { + if let Some(change_hashes) = change_hashes.as_ref() { + change_hashes.rewound() + } else { + Default::default() + } +} diff --git a/rust/automerge-c/src/changes.rs b/rust/automerge-c/src/changes.rs new file mode 100644 index 00000000..1bff35c8 --- /dev/null +++ b/rust/automerge-c/src/changes.rs @@ -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()` call +/// (https://github.com/eqrion/cbindgen/issues/252) but it will +/// propagate the name of a constant initialized from it so if the +/// constant's name is a symbolic representation of the value it can be +/// converted into a number by post-processing the header it generated. +pub const USIZE_USIZE_USIZE_USIZE_: usize = size_of::(); + +impl Detail { + fn new(changes: &[am::Change], offset: isize, storage: &mut BTreeMap) -> Self { + let storage: *mut BTreeMap = storage; + Self { + len: changes.len(), + offset, + ptr: changes.as_ptr() as *const c_void, + storage: storage as *mut c_void, + } + } + + pub fn advance(&mut self, n: isize) { + if n == 0 { + return; + } + let len = self.len as isize; + self.offset = if self.offset < 0 { + // It's reversed. + let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); + if unclipped >= 0 { + // Clip it to the forward stop. + len + } else { + std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) + } + } else { + let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); + if unclipped < 0 { + // Clip it to the reverse stop. + -(len + 1) + } else { + std::cmp::max(0, std::cmp::min(unclipped, len)) + } + } + } + + pub fn get_index(&self) -> usize { + (self.offset + + if self.offset < 0 { + self.len as isize + } else { + 0 + }) as usize + } + + pub fn next(&mut self, n: isize) -> Option<*const AMchange> { + if self.is_stopped() { + return None; + } + let slice: &mut [am::Change] = + unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut am::Change, self.len) }; + let storage = unsafe { &mut *(self.storage as *mut BTreeMap) }; + let index = self.get_index(); + let value = match storage.get_mut(&index) { + Some(value) => value, + None => { + storage.insert(index, AMchange::new(&mut slice[index])); + storage.get_mut(&index).unwrap() + } + }; + self.advance(n); + Some(value) + } + + pub fn is_stopped(&self) -> bool { + let len = self.len as isize; + self.offset < -len || self.offset == len + } + + pub fn prev(&mut self, n: isize) -> Option<*const AMchange> { + self.advance(-n); + if self.is_stopped() { + return None; + } + let slice: &mut [am::Change] = + unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut am::Change, self.len) }; + let storage = unsafe { &mut *(self.storage as *mut BTreeMap) }; + let index = self.get_index(); + Some(match storage.get_mut(&index) { + Some(value) => value, + None => { + storage.insert(index, AMchange::new(&mut slice[index])); + storage.get_mut(&index).unwrap() + } + }) + } + + pub fn reversed(&self) -> Self { + Self { + len: self.len, + offset: -(self.offset + 1), + ptr: self.ptr, + storage: self.storage, + } + } + + pub fn rewound(&self) -> Self { + Self { + len: self.len, + offset: if self.offset < 0 { -1 } else { 0 }, + ptr: self.ptr, + storage: self.storage, + } + } +} + +impl From for [u8; USIZE_USIZE_USIZE_USIZE_] { + fn from(detail: Detail) -> Self { + unsafe { + std::slice::from_raw_parts( + (&detail as *const Detail) as *const u8, + USIZE_USIZE_USIZE_USIZE_, + ) + .try_into() + .unwrap() + } + } +} + +/// \struct AMchanges +/// \installed_headerfile +/// \brief A random-access iterator over a sequence of changes. +#[repr(C)] +#[derive(Eq, PartialEq)] +pub struct AMchanges { + /// An implementation detail that is intentionally opaque. + /// \warning Modifying \p detail will cause undefined behavior. + /// \note The actual size of \p detail will vary by platform, this is just + /// the one for the platform this documentation was built on. + detail: [u8; USIZE_USIZE_USIZE_USIZE_], +} + +impl AMchanges { + pub fn new(changes: &[am::Change], storage: &mut BTreeMap) -> Self { + Self { + detail: Detail::new(changes, 0, &mut *storage).into(), + } + } + + pub fn advance(&mut self, n: isize) { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.advance(n); + } + + pub fn len(&self) -> usize { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + detail.len + } + + pub fn next(&mut self, n: isize) -> Option<*const AMchange> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.next(n) + } + + pub fn prev(&mut self, n: isize) -> Option<*const AMchange> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.prev(n) + } + + pub fn reversed(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.reversed().into(), + } + } + + pub fn rewound(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.rewound().into(), + } + } +} + +impl AsRef<[am::Change]> for AMchanges { + fn as_ref(&self) -> &[am::Change] { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + unsafe { std::slice::from_raw_parts(detail.ptr as *const am::Change, detail.len) } + } +} + +impl Default for AMchanges { + fn default() -> Self { + Self { + detail: [0; USIZE_USIZE_USIZE_USIZE_], + } + } +} + +/// \memberof AMchanges +/// \brief Advances an iterator over a sequence of changes by at most \p |n| +/// positions where the sign of \p n is relative to the iterator's +/// direction. +/// +/// \param[in,out] changes A pointer to an `AMchanges` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \pre \p changes `!= NULL`. +/// \internal +/// +/// #Safety +/// changes must be a valid pointer to an AMchanges +#[no_mangle] +pub unsafe extern "C" fn AMchangesAdvance(changes: *mut AMchanges, n: isize) { + if let Some(changes) = changes.as_mut() { + changes.advance(n); + }; +} + +/// \memberof AMchanges +/// \brief Tests the equality of two sequences of changes underlying a pair of +/// iterators. +/// +/// \param[in] changes1 A pointer to an `AMchanges` struct. +/// \param[in] changes2 A pointer to an `AMchanges` struct. +/// \return `true` if \p changes1 `==` \p changes2 and `false` otherwise. +/// \pre \p changes1 `!= NULL`. +/// \pre \p changes2 `!= NULL`. +/// \internal +/// +/// #Safety +/// changes1 must be a valid pointer to an AMchanges +/// changes2 must be a valid pointer to an AMchanges +#[no_mangle] +pub unsafe extern "C" fn AMchangesEqual( + changes1: *const AMchanges, + changes2: *const AMchanges, +) -> bool { + match (changes1.as_ref(), changes2.as_ref()) { + (Some(changes1), Some(changes2)) => changes1.as_ref() == changes2.as_ref(), + (None, Some(_)) | (Some(_), None) | (None, None) => false, + } +} + +/// \memberof AMchanges +/// \brief Allocates an iterator over a sequence of changes and initializes it +/// from a sequence of byte spans. +/// +/// \param[in] src A pointer to an array of `AMbyteSpan` structs. +/// \param[in] count The number of `AMbyteSpan` structs to copy from \p src. +/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct. +/// \pre \p src `!= NULL`. +/// \pre `0 <` \p count `<= sizeof(`\p src`) / sizeof(AMbyteSpan)`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. +/// \internal +/// +/// # Safety +/// src must be an AMbyteSpan array of size `>= count` +#[no_mangle] +pub unsafe extern "C" fn AMchangesInit(src: *const AMbyteSpan, count: usize) -> *mut AMresult { + let mut changes = Vec::::new(); + for n in 0..count { + let byte_span = &*src.add(n); + let slice = std::slice::from_raw_parts(byte_span.src, byte_span.count); + match slice.try_into() { + Ok(change) => { + changes.push(change); + } + Err(e) => { + return to_result(Err::, am::LoadChangeError>(e)); + } + } + } + to_result(Ok::, am::LoadChangeError>(changes)) +} + +/// \memberof AMchanges +/// \brief Gets the change at the current position of an iterator over a +/// sequence of changes and then advances it by at most \p |n| positions +/// where the sign of \p n is relative to the iterator's direction. +/// +/// \param[in,out] changes A pointer to an `AMchanges` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A pointer to an `AMchange` struct that's `NULL` when \p changes was +/// previously advanced past its forward/reverse limit. +/// \pre \p changes `!= NULL`. +/// \internal +/// +/// #Safety +/// changes must be a valid pointer to an AMchanges +#[no_mangle] +pub unsafe extern "C" fn AMchangesNext(changes: *mut AMchanges, n: isize) -> *const AMchange { + if let Some(changes) = changes.as_mut() { + if let Some(change) = changes.next(n) { + return change; + } + } + std::ptr::null() +} + +/// \memberof AMchanges +/// \brief Advances an iterator over a sequence of changes by at most \p |n| +/// positions where the sign of \p n is relative to the iterator's +/// direction and then gets the change at its new position. +/// +/// \param[in,out] changes A pointer to an `AMchanges` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A pointer to an `AMchange` struct that's `NULL` when \p changes is +/// presently advanced past its forward/reverse limit. +/// \pre \p changes `!= NULL`. +/// \internal +/// +/// #Safety +/// changes must be a valid pointer to an AMchanges +#[no_mangle] +pub unsafe extern "C" fn AMchangesPrev(changes: *mut AMchanges, n: isize) -> *const AMchange { + if let Some(changes) = changes.as_mut() { + if let Some(change) = changes.prev(n) { + return change; + } + } + std::ptr::null() +} + +/// \memberof AMchanges +/// \brief Gets the size of the sequence of changes underlying an iterator. +/// +/// \param[in] changes A pointer to an `AMchanges` struct. +/// \return The count of values in \p changes. +/// \pre \p changes `!= NULL`. +/// \internal +/// +/// #Safety +/// changes must be a valid pointer to an AMchanges +#[no_mangle] +pub unsafe extern "C" fn AMchangesSize(changes: *const AMchanges) -> usize { + if let Some(changes) = changes.as_ref() { + changes.len() + } else { + 0 + } +} + +/// \memberof AMchanges +/// \brief Creates an iterator over the same sequence of changes as the given +/// one but with the opposite position and direction. +/// +/// \param[in] changes A pointer to an `AMchanges` struct. +/// \return An `AMchanges` struct. +/// \pre \p changes `!= NULL`. +/// \internal +/// +/// #Safety +/// changes must be a valid pointer to an AMchanges +#[no_mangle] +pub unsafe extern "C" fn AMchangesReversed(changes: *const AMchanges) -> AMchanges { + if let Some(changes) = changes.as_ref() { + changes.reversed() + } else { + Default::default() + } +} + +/// \memberof AMchanges +/// \brief Creates an iterator at the starting position over the same sequence +/// of changes as the given one. +/// +/// \param[in] changes A pointer to an `AMchanges` struct. +/// \return An `AMchanges` struct +/// \pre \p changes `!= NULL`. +/// \internal +/// +/// #Safety +/// changes must be a valid pointer to an AMchanges +#[no_mangle] +pub unsafe extern "C" fn AMchangesRewound(changes: *const AMchanges) -> AMchanges { + if let Some(changes) = changes.as_ref() { + changes.rewound() + } else { + Default::default() + } +} diff --git a/rust/automerge-c/src/doc.rs b/rust/automerge-c/src/doc.rs index 82f52bf7..f02c01bf 100644 --- a/rust/automerge-c/src/doc.rs +++ b/rust/automerge-c/src/doc.rs @@ -6,23 +6,43 @@ use std::ops::{Deref, DerefMut}; use crate::actor_id::{to_actor_id, AMactorId}; use crate::byte_span::{to_str, AMbyteSpan}; -use crate::items::AMitems; +use crate::change_hashes::AMchangeHashes; use crate::obj::{to_obj_id, AMobjId, AMobjType}; -use crate::result::{to_result, AMresult}; +use crate::result::{to_result, AMresult, AMvalue}; use crate::sync::{to_sync_message, AMsyncMessage, AMsyncState}; pub mod list; pub mod map; pub mod utils; -use crate::doc::utils::{clamp, to_doc, to_doc_mut, to_items}; +use crate::changes::AMchanges; +use crate::doc::utils::{to_doc, to_doc_mut}; + +macro_rules! to_changes { + ($handle:expr) => {{ + let handle = $handle.as_ref(); + match handle { + Some(b) => b, + None => return AMresult::err("Invalid AMchanges pointer").into(), + } + }}; +} + +macro_rules! to_index { + ($index:expr, $len:expr, $param_name:expr) => {{ + if $index > $len && $index != usize::MAX { + return AMresult::err(&format!("Invalid {} {}", $param_name, $index)).into(); + } + std::cmp::min($index, $len) + }}; +} macro_rules! to_sync_state_mut { ($handle:expr) => {{ let handle = $handle.as_mut(); match handle { Some(b) => b, - None => return AMresult::error("Invalid `AMsyncState*`").into(), + None => return AMresult::err("Invalid AMsyncState pointer").into(), } }}; } @@ -37,10 +57,6 @@ impl AMdoc { pub fn new(auto_commit: am::AutoCommit) -> Self { Self(auto_commit) } - - pub fn is_equal_to(&mut self, other: &mut Self) -> bool { - self.document().get_heads() == other.document().get_heads() - } } impl AsRef for AMdoc { @@ -66,38 +82,38 @@ impl DerefMut for AMdoc { /// \memberof AMdoc /// \brief Applies a sequence of changes to a document. /// -/// \param[in] doc A pointer to an `AMdoc` struct. -/// \param[in] items A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE` -/// items. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. -/// \pre \p doc `!= NULL` -/// \pre \p items `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] changes A pointer to an `AMchanges` struct. +/// \pre \p doc `!= NULL`. +/// \pre \p changes `!= NULL`. +/// \return A pointer to an `AMresult` struct containing a void. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc -/// items must be a valid pointer to an AMitems. +/// changes must be a valid pointer to an AMchanges. #[no_mangle] -pub unsafe extern "C" fn AMapplyChanges(doc: *mut AMdoc, items: *const AMitems) -> *mut AMresult { +pub unsafe extern "C" fn AMapplyChanges( + doc: *mut AMdoc, + changes: *const AMchanges, +) -> *mut AMresult { let doc = to_doc_mut!(doc); - let items = to_items!(items); - match Vec::::try_from(items) { - Ok(changes) => to_result(doc.apply_changes(changes)), - Err(e) => AMresult::error(&e.to_string()).into(), - } + let changes = to_changes!(changes); + to_result(doc.apply_changes(changes.as_ref().to_vec())) } /// \memberof AMdoc /// \brief Allocates storage for a document and initializes it by duplicating /// the given document. /// -/// \param[in] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_DOC` item. -/// \pre \p doc `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct containing a pointer to an +/// `AMdoc` struct. +/// \pre \p doc `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety @@ -113,9 +129,10 @@ pub unsafe extern "C" fn AMclone(doc: *const AMdoc) -> *mut AMresult { /// /// \param[in] actor_id A pointer to an `AMactorId` struct or `NULL` for a /// random one. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_DOC` item. -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \return A pointer to an `AMresult` struct containing a pointer to an +/// `AMdoc` struct. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety @@ -132,15 +149,15 @@ pub unsafe extern "C" fn AMcreate(actor_id: *const AMactorId) -> *mut AMresult { /// \brief Commits the current operations on a document with an optional /// message and/or *nix timestamp (milliseconds). /// -/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in,out] doc A pointer to an `AMdoc` struct. /// \param[in] message A UTF-8 string view as an `AMbyteSpan` struct. /// \param[in] timestamp A pointer to a 64-bit integer or `NULL`. -/// \return A pointer to an `AMresult` struct with one `AM_VAL_TYPE_CHANGE_HASH` -/// item if there were operations to commit or an `AM_VAL_TYPE_VOID` item -/// if there were no operations to commit. -/// \pre \p doc `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` +/// with one element if there were operations to commit, or void if +/// there were no operations to commit. +/// \pre \p doc `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety @@ -166,24 +183,24 @@ pub unsafe extern "C" fn AMcommit( /// \brief Creates an empty change with an optional message and/or *nix /// timestamp (milliseconds). /// -/// \details This is useful if you wish to create a "merge commit" which has as -/// its dependents the current heads of the document but you don't have -/// any operations to add to the document. +/// This is useful if you wish to create a "merge commit" which has as its +/// dependents the current heads of the document but you don't have any +/// operations to add to the document. /// /// \note If there are outstanding uncommitted changes to the document -/// then two changes will be created: one for creating the outstanding -/// changes and one for the empty change. The empty change will always be -/// the latest change in the document after this call and the returned -/// hash will be the hash of that empty change. +/// then two changes will be created: one for creating the outstanding changes +/// and one for the empty change. The empty change will always be the +/// latest change in the document after this call and the returned hash will be +/// the hash of that empty change. /// -/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in,out] doc A pointer to an `AMdoc` struct. /// \param[in] message A UTF-8 string view as an `AMbyteSpan` struct. /// \param[in] timestamp A pointer to a 64-bit integer or `NULL`. -/// \return A pointer to an `AMresult` struct with one `AM_VAL_TYPE_CHANGE_HASH` -/// item. -/// \pre \p doc `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` +/// with one element. +/// \pre \p doc `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety @@ -209,11 +226,11 @@ pub unsafe extern "C" fn AMemptyChange( /// \brief Tests the equality of two documents after closing their respective /// transactions. /// -/// \param[in] doc1 A pointer to an `AMdoc` struct. -/// \param[in] doc2 A pointer to an `AMdoc` struct. +/// \param[in,out] doc1 An `AMdoc` struct. +/// \param[in,out] doc2 An `AMdoc` struct. /// \return `true` if \p doc1 `==` \p doc2 and `false` otherwise. -/// \pre \p doc1 `!= NULL` -/// \pre \p doc2 `!= NULL` +/// \pre \p doc1 `!= NULL`. +/// \pre \p doc2 `!= NULL`. /// \internal /// /// #Safety @@ -222,36 +239,33 @@ pub unsafe extern "C" fn AMemptyChange( #[no_mangle] pub unsafe extern "C" fn AMequal(doc1: *mut AMdoc, doc2: *mut AMdoc) -> bool { match (doc1.as_mut(), doc2.as_mut()) { - (Some(doc1), Some(doc2)) => doc1.is_equal_to(doc2), - (None, None) | (None, Some(_)) | (Some(_), None) => false, + (Some(doc1), Some(doc2)) => doc1.document().get_heads() == doc2.document().get_heads(), + (None, Some(_)) | (Some(_), None) | (None, None) => false, } } /// \memberof AMdoc -/// \brief Forks this document at its current or a historical point for use by +/// \brief Forks this document at the current or a historical point for use by /// a different actor. -/// \param[in] doc A pointer to an `AMdoc` struct. -/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` -/// items to select a historical point or `NULL` to select its -/// current point. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. -/// \pre \p doc `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical +/// point or `NULL` for the current point. +/// \return A pointer to an `AMresult` struct containing a pointer to an +/// `AMdoc` struct. +/// \pre \p doc `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc -/// 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 AMfork(doc: *mut AMdoc, heads: *const AMitems) -> *mut AMresult { +pub unsafe extern "C" fn AMfork(doc: *mut AMdoc, heads: *const AMchangeHashes) -> *mut AMresult { let doc = to_doc_mut!(doc); match heads.as_ref() { None => to_result(doc.fork()), - Some(heads) => match >::try_from(heads) { - Ok(heads) => to_result(doc.fork_at(&heads)), - Err(e) => AMresult::error(&e.to_string()).into(), - }, + Some(heads) => to_result(doc.fork_at(heads.as_ref())), } } @@ -259,14 +273,14 @@ pub unsafe extern "C" fn AMfork(doc: *mut AMdoc, heads: *const AMitems) -> *mut /// \brief Generates a synchronization message for a peer based upon the given /// synchronization state. /// -/// \param[in] doc A pointer to an `AMdoc` struct. -/// \param[in] sync_state A pointer to an `AMsyncState` struct. -/// \return A pointer to an `AMresult` struct with either an -/// `AM_VAL_TYPE_SYNC_MESSAGE` or `AM_VAL_TYPE_VOID` item. -/// \pre \p doc `!= NULL` -/// \pre \p sync_state `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in,out] sync_state A pointer to an `AMsyncState` struct. +/// \return A pointer to an `AMresult` struct containing either a pointer to an +/// `AMsyncMessage` struct or a void. +/// \pre \p doc `!= NULL`. +/// \pre \p sync_state `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety @@ -286,10 +300,11 @@ pub unsafe extern "C" fn AMgenerateSyncMessage( /// \brief Gets a document's actor identifier. /// /// \param[in] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item. -/// \pre \p doc `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \return A pointer to an `AMresult` struct containing a pointer to an +/// `AMactorId` struct. +/// \pre \p doc `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety @@ -305,22 +320,20 @@ pub unsafe extern "C" fn AMgetActorId(doc: *const AMdoc) -> *mut AMresult { /// \memberof AMdoc /// \brief Gets the change added to a document by its respective hash. /// -/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in,out] doc A pointer to an `AMdoc` struct. /// \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_CHANGE` item. -/// \pre \p doc `!= NULL` -/// \pre \p src `!= NULL` -/// \pre `sizeof(`\p src') >= AM_CHANGE_HASH_SIZE` -/// \pre \p count `<= sizeof(`\p src `)` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in] count The number of bytes in \p src. +/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct. +/// \pre \p doc `!= NULL`. +/// \pre \p src `!= NULL`. +/// \pre \p count `>= AM_CHANGE_HASH_SIZE`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc -/// src must be a byte array of length `>= automerge::types::HASH_SIZE` +/// src must be a byte array of size `>= automerge::types::HASH_SIZE` #[no_mangle] pub unsafe extern "C" fn AMgetChangeByHash( doc: *mut AMdoc, @@ -331,48 +344,48 @@ pub unsafe extern "C" fn AMgetChangeByHash( let slice = std::slice::from_raw_parts(src, count); match slice.try_into() { Ok(change_hash) => to_result(doc.get_change_by_hash(&change_hash)), - Err(e) => AMresult::error(&e.to_string()).into(), + Err(e) => AMresult::err(&e.to_string()).into(), } } /// \memberof AMdoc /// \brief Gets the changes added to a document by their respective hashes. /// -/// \param[in] doc A pointer to an `AMdoc` struct. -/// \param[in] have_deps A pointer to an `AMitems` struct with -/// `AM_VAL_TYPE_CHANGE_HASH` items or `NULL`. -/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE` items. -/// \pre \p doc `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] have_deps A pointer to an `AMchangeHashes` struct or `NULL`. +/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct. +/// \pre \p doc `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc #[no_mangle] -pub unsafe extern "C" fn AMgetChanges(doc: *mut AMdoc, have_deps: *const AMitems) -> *mut AMresult { +pub unsafe extern "C" fn AMgetChanges( + doc: *mut AMdoc, + have_deps: *const AMchangeHashes, +) -> *mut AMresult { let doc = to_doc_mut!(doc); + let empty_deps = Vec::::new(); let have_deps = match have_deps.as_ref() { - Some(have_deps) => match Vec::::try_from(have_deps) { - Ok(change_hashes) => change_hashes, - Err(e) => return AMresult::error(&e.to_string()).into(), - }, - None => Vec::::new(), + Some(have_deps) => have_deps.as_ref(), + None => &empty_deps, }; - to_result(doc.get_changes(&have_deps)) + to_result(doc.get_changes(have_deps)) } /// \memberof AMdoc /// \brief Gets the changes added to a second document that weren't added to /// a first document. /// -/// \param[in] doc1 A pointer to an `AMdoc` struct. -/// \param[in] doc2 A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE` items. -/// \pre \p doc1 `!= NULL` -/// \pre \p doc2 `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in,out] doc1 An `AMdoc` struct. +/// \param[in,out] doc2 An `AMdoc` struct. +/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct. +/// \pre \p doc1 `!= NULL`. +/// \pre \p doc2 `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety @@ -388,11 +401,12 @@ pub unsafe extern "C" fn AMgetChangesAdded(doc1: *mut AMdoc, doc2: *mut AMdoc) - /// \memberof AMdoc /// \brief Gets the current heads of a document. /// -/// \param[in] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. -/// \pre \p doc `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` +/// struct. +/// \pre \p doc `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety @@ -409,42 +423,41 @@ pub unsafe extern "C" fn AMgetHeads(doc: *mut AMdoc) -> *mut AMresult { /// \brief Gets the hashes of the changes in a document that aren't transitive /// dependencies of the given hashes of changes. /// -/// \param[in] doc A pointer to an `AMdoc` struct. -/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` -/// items or `NULL`. -/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. -/// \pre \p doc `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] heads A pointer to an `AMchangeHashes` struct or `NULL`. +/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` +/// struct. +/// \pre \p doc `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc -/// 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 AMgetMissingDeps(doc: *mut AMdoc, heads: *const AMitems) -> *mut AMresult { +pub unsafe extern "C" fn AMgetMissingDeps( + doc: *mut AMdoc, + heads: *const AMchangeHashes, +) -> *mut AMresult { let doc = to_doc_mut!(doc); + let empty_heads = Vec::::new(); let heads = match heads.as_ref() { - None => Vec::::new(), - Some(heads) => match >::try_from(heads) { - Ok(heads) => heads, - Err(e) => { - return AMresult::error(&e.to_string()).into(); - } - }, + Some(heads) => heads.as_ref(), + None => &empty_heads, }; - to_result(doc.get_missing_deps(heads.as_slice())) + to_result(doc.get_missing_deps(heads)) } /// \memberof AMdoc /// \brief Gets the last change made to a document. /// -/// \param[in] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing either an -/// `AM_VAL_TYPE_CHANGE` or `AM_VAL_TYPE_VOID` item. -/// \pre \p doc `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct containing either an `AMchange` +/// struct or a void. +/// \pre \p doc `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety @@ -460,33 +473,29 @@ pub unsafe extern "C" fn AMgetLastLocalChange(doc: *mut AMdoc) -> *mut AMresult /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` -/// items to select historical keys or `NULL` to select current -/// keys. -/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_STR` items. -/// \pre \p doc `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical +/// keys or `NULL` for current keys. +/// \return A pointer to an `AMresult` struct containing an `AMstrs` struct. +/// \pre \p doc `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \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 AMkeys( doc: *const AMdoc, obj_id: *const AMobjId, - heads: *const AMitems, + heads: *const AMchangeHashes, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); match heads.as_ref() { None => to_result(doc.keys(obj_id)), - Some(heads) => match >::try_from(heads) { - Ok(heads) => to_result(doc.keys_at(obj_id, &heads)), - Err(e) => AMresult::error(&e.to_string()).into(), - }, + Some(heads) => to_result(doc.keys_at(obj_id, heads.as_ref())), } } @@ -495,43 +504,42 @@ pub unsafe extern "C" fn AMkeys( /// form of an incremental save. /// /// \param[in] src A pointer to an array of bytes. -/// \param[in] count The count of bytes to load from the array pointed to by -/// \p src. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_DOC` item. -/// \pre \p src `!= NULL` -/// \pre `sizeof(`\p src `) > 0` -/// \pre \p count `<= sizeof(`\p src `)` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in] count The number of bytes in \p src to load. +/// \return A pointer to an `AMresult` struct containing a pointer to an +/// `AMdoc` struct. +/// \pre \p src `!= NULL`. +/// \pre `0 <` \p count `<= sizeof(`\p src`)`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \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 AMload(src: *const u8, count: usize) -> *mut AMresult { - let data = std::slice::from_raw_parts(src, count); - to_result(am::AutoCommit::load(data)) + let mut data = Vec::new(); + data.extend_from_slice(std::slice::from_raw_parts(src, count)); + to_result(am::AutoCommit::load(&data)) } /// \memberof AMdoc /// \brief Loads the compact form of an incremental save into a document. /// -/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in,out] doc A pointer to an `AMdoc` struct. /// \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_UINT` item. -/// \pre \p doc `!= NULL` -/// \pre \p src `!= NULL` -/// \pre `sizeof(`\p src `) > 0` -/// \pre \p count `<= sizeof(`\p src `)` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in] count The number of bytes in \p src to load. +/// \return A pointer to an `AMresult` struct containing the number of +/// operations loaded from \p src. +/// \pre \p doc `!= NULL`. +/// \pre \p src `!= NULL`. +/// \pre `0 <` \p count `<= sizeof(`\p src`)`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc -/// 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 AMloadIncremental( doc: *mut AMdoc, @@ -539,21 +547,23 @@ pub unsafe extern "C" fn AMloadIncremental( count: usize, ) -> *mut AMresult { let doc = to_doc_mut!(doc); - let data = std::slice::from_raw_parts(src, count); - to_result(doc.load_incremental(data)) + let mut data = Vec::new(); + data.extend_from_slice(std::slice::from_raw_parts(src, count)); + to_result(doc.load_incremental(&data)) } /// \memberof AMdoc /// \brief Applies all of the changes in \p src which are not in \p dest to /// \p dest. /// -/// \param[in] dest A pointer to an `AMdoc` struct. -/// \param[in] src A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. -/// \pre \p dest `!= NULL` -/// \pre \p src `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in,out] dest A pointer to an `AMdoc` struct. +/// \param[in,out] src A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` +/// struct. +/// \pre \p dest `!= NULL`. +/// \pre \p src `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety @@ -570,37 +580,31 @@ pub unsafe extern "C" fn AMmerge(dest: *mut AMdoc, src: *mut AMdoc) -> *mut AMre /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` -/// items to select a historical size or `NULL` to select its -/// current size. -/// \return The count of items in the object identified by \p obj_id. -/// \pre \p doc `!= NULL` +/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical +/// size or `NULL` for current size. +/// \return A 64-bit unsigned integer. +/// \pre \p doc `!= NULL`. /// \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 AMobjSize( doc: *const AMdoc, obj_id: *const AMobjId, - heads: *const AMitems, + heads: *const AMchangeHashes, ) -> usize { if let Some(doc) = doc.as_ref() { let obj_id = to_obj_id!(obj_id); match heads.as_ref() { - None => { - return doc.length(obj_id); - } - Some(heads) => { - if let Ok(heads) = >::try_from(heads) { - return doc.length_at(obj_id, &heads); - } - } + None => doc.length(obj_id), + Some(heads) => doc.length_at(obj_id, heads.as_ref()), } + } else { + 0 } - 0 } /// \memberof AMdoc @@ -608,9 +612,8 @@ pub unsafe extern "C" fn AMobjSize( /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \return An `AMobjType` tag or `0`. -/// \pre \p doc `!= NULL` -/// \pre \p obj_id `!= NULL` +/// \return An `AMobjType`. +/// \pre \p doc `!= NULL`. /// \internal /// /// # Safety @@ -620,45 +623,44 @@ pub unsafe extern "C" fn AMobjSize( pub unsafe extern "C" fn AMobjObjType(doc: *const AMdoc, obj_id: *const AMobjId) -> AMobjType { if let Some(doc) = doc.as_ref() { let obj_id = to_obj_id!(obj_id); - if let Ok(obj_type) = doc.object_type(obj_id) { - return (&obj_type).into(); + match doc.object_type(obj_id) { + Err(_) => AMobjType::Void, + Ok(obj_type) => obj_type.into(), } + } else { + AMobjType::Void } - Default::default() } /// \memberof AMdoc -/// \brief Gets the current or historical items of an entire object. +/// \brief Gets the current or historical values of an object within its entire +/// 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] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` -/// items to select its historical items or `NULL` to select -/// its current items. -/// \return A pointer to an `AMresult` struct with an `AMitems` struct. -/// \pre \p doc `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical +/// items or `NULL` for current items. +/// \return A pointer to an `AMresult` struct containing an `AMobjItems` struct. +/// \pre \p doc `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \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 AMobjItems( +pub unsafe extern "C" fn AMobjValues( doc: *const AMdoc, obj_id: *const AMobjId, - heads: *const AMitems, + heads: *const AMchangeHashes, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); match heads.as_ref() { None => to_result(doc.values(obj_id)), - Some(heads) => match >::try_from(heads) { - Ok(heads) => to_result(doc.values_at(obj_id, &heads)), - Err(e) => AMresult::error(&e.to_string()).into(), - }, + Some(heads) => to_result(doc.values_at(obj_id, heads.as_ref())), } } @@ -668,7 +670,7 @@ pub unsafe extern "C" fn AMobjItems( /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \return The count of pending operations for \p doc. -/// \pre \p doc `!= NULL` +/// \pre \p doc `!= NULL`. /// \internal /// /// # Safety @@ -676,22 +678,23 @@ pub unsafe extern "C" fn AMobjItems( #[no_mangle] pub unsafe extern "C" fn AMpendingOps(doc: *const AMdoc) -> usize { if let Some(doc) = doc.as_ref() { - return doc.pending_ops(); + doc.pending_ops() + } else { + 0 } - 0 } /// \memberof AMdoc /// \brief Receives a synchronization message from a peer based upon a given /// synchronization state. /// -/// \param[in] doc A pointer to an `AMdoc` struct. -/// \param[in] sync_state A pointer to an `AMsyncState` struct. +/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in,out] sync_state A pointer to an `AMsyncState` struct. /// \param[in] sync_message A pointer to an `AMsyncMessage` struct. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. -/// \pre \p doc `!= NULL` -/// \pre \p sync_state `!= NULL` -/// \pre \p sync_message `!= NULL` +/// \return A pointer to an `AMresult` struct containing a void. +/// \pre \p doc `!= NULL`. +/// \pre \p sync_state `!= NULL`. +/// \pre \p sync_message `!= NULL`. /// \internal /// /// # Safety @@ -717,9 +720,9 @@ pub unsafe extern "C" fn AMreceiveSyncMessage( /// \brief Cancels the pending operations added during a document's current /// transaction and gets the number of cancellations. /// -/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in,out] doc A pointer to an `AMdoc` struct. /// \return The count of pending operations for \p doc that were cancelled. -/// \pre \p doc `!= NULL` +/// \pre \p doc `!= NULL`. /// \internal /// /// # Safety @@ -727,19 +730,21 @@ pub unsafe extern "C" fn AMreceiveSyncMessage( #[no_mangle] pub unsafe extern "C" fn AMrollback(doc: *mut AMdoc) -> usize { if let Some(doc) = doc.as_mut() { - return doc.rollback(); + doc.rollback() + } else { + 0 } - 0 } /// \memberof AMdoc /// \brief Saves the entirety of a document into a compact form. /// -/// \param[in] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTES` item. -/// \pre \p doc `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct containing an array of bytes as +/// an `AMbyteSpan` struct. +/// \pre \p doc `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety @@ -754,11 +759,12 @@ pub unsafe extern "C" fn AMsave(doc: *mut AMdoc) -> *mut AMresult { /// \brief Saves the changes to a document since its last save into a compact /// form. /// -/// \param[in] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTES` item. -/// \pre \p doc `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct containing an array of bytes as +/// an `AMbyteSpan` struct. +/// \pre \p doc `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety @@ -772,13 +778,13 @@ pub unsafe extern "C" fn AMsaveIncremental(doc: *mut AMdoc) -> *mut AMresult { /// \memberof AMdoc /// \brief Puts the actor identifier of a document. /// -/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in,out] doc A pointer to an `AMdoc` struct. /// \param[in] actor_id A pointer to an `AMactorId` struct. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. -/// \pre \p doc `!= NULL` -/// \pre \p actor_id `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \return A pointer to an `AMresult` struct containing a void. +/// \pre \p doc `!= NULL`. +/// \pre \p actor_id `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety @@ -799,65 +805,76 @@ pub unsafe extern "C" fn AMsetActorId( /// \brief Splices values into and/or removes values from the identified object /// at a given position within it. /// -/// \param[in] 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 A position in the object identified by \p obj_id or /// `SIZE_MAX` to indicate one past its end. -/// \param[in] del The number of values to delete or `SIZE_MAX` to indicate +/// \param[in] del The number of characters to delete or `SIZE_MAX` to indicate /// all of them. -/// \param[in] values A copy of an `AMitems` struct from which values will be -/// spliced starting at its current position; call -/// `AMitemsRewound()` on a used `AMitems` first to ensure -/// that all of its values are spliced in. Pass `(AMitems){0}` -/// when zero values should be spliced in. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. -/// \pre \p doc `!= NULL` -/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` -/// \pre `0 <=` \p del `<= AMobjSize(`\p obj_id `)` or \p del `== SIZE_MAX` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in] src A pointer to an array of `AMvalue` structs. +/// \param[in] count The number of `AMvalue` structs in \p src to load. +/// \return A pointer to an `AMresult` struct containing a void. +/// \pre \p doc `!= NULL`. +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id`)` or \p pos `== SIZE_MAX`. +/// \pre `0 <=` \p del `<= AMobjSize(`\p obj_id`)` or \p del `== SIZE_MAX`. +/// \pre `(`\p src `!= NULL and 1 <=` \p count `<= sizeof(`\p src`)/ +/// sizeof(AMvalue)) or `\p src `== NULL or `\p count `== 0`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \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() -/// values must be a valid pointer to an AMitems or std::ptr::null() +/// src must be an AMvalue array of size `>= count` or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMsplice( doc: *mut AMdoc, obj_id: *const AMobjId, pos: usize, del: usize, - values: AMitems, + src: *const AMvalue, + count: usize, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); let len = doc.length(obj_id); - let pos = clamp!(pos, len, "pos"); - let del = clamp!(del, len, "del"); - match Vec::::try_from(&values) { - Ok(vals) => to_result(doc.splice(obj_id, pos, del, vals)), - Err(e) => AMresult::error(&e.to_string()).into(), + let pos = to_index!(pos, len, "pos"); + let del = to_index!(del, len, "del"); + let mut vals: Vec = vec![]; + if !(src.is_null() || count == 0) { + let c_vals = std::slice::from_raw_parts(src, count); + for c_val in c_vals { + match c_val.try_into() { + Ok(s) => { + vals.push(s); + } + Err(e) => { + return AMresult::err(&e.to_string()).into(); + } + } + } } + to_result(doc.splice(obj_id, pos, del, vals)) } /// \memberof AMdoc /// \brief Splices characters into and/or removes characters from the /// identified object at a given position within it. /// -/// \param[in] 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 A position in the text object identified by \p obj_id or /// `SIZE_MAX` to indicate one past its end. /// \param[in] del The number of characters to delete or `SIZE_MAX` to indicate /// all of them. /// \param[in] text A UTF-8 string view as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. -/// \pre \p doc `!= NULL` -/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` -/// \pre `0 <=` \p del `<= AMobjSize(`\p obj_id `)` or \p del `== SIZE_MAX` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \return A pointer to an `AMresult` struct containing a void. +/// \pre \p doc `!= NULL`. +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id`)` or \p pos `== SIZE_MAX`. +/// \pre `0 <=` \p del `<= AMobjSize(`\p obj_id`)` or \p del `== SIZE_MAX`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \internal /// /// # Safety @@ -874,8 +891,8 @@ pub unsafe extern "C" fn AMspliceText( let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); let len = doc.length(obj_id); - let pos = clamp!(pos, len, "pos"); - let del = clamp!(del, len, "del"); + let pos = to_index!(pos, len, "pos"); + let del = to_index!(del, len, "del"); to_result(doc.splice_text(obj_id, pos, del, to_str!(text))) } @@ -884,32 +901,28 @@ pub unsafe extern "C" fn AMspliceText( /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] heads A pointer to an `AMitems` struct containing -/// `AM_VAL_TYPE_CHANGE_HASH` items to select a historical string -/// or `NULL` to select the current string. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_STR` item. -/// \pre \p doc `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical +/// keys or `NULL` for current keys. +/// \return A pointer to an `AMresult` struct containing a UTF-8 string. +/// \pre \p doc `!= NULL`. +/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` +/// in order to prevent a memory leak. /// \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 AMtext( doc: *const AMdoc, obj_id: *const AMobjId, - heads: *const AMitems, + heads: *const AMchangeHashes, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); match heads.as_ref() { None => to_result(doc.text(obj_id)), - Some(heads) => match >::try_from(heads) { - Ok(heads) => to_result(doc.text_at(obj_id, &heads)), - Err(e) => AMresult::error(&e.to_string()).into(), - }, + Some(heads) => to_result(doc.text_at(obj_id, heads.as_ref())), } } diff --git a/rust/automerge-c/src/doc/list.rs b/rust/automerge-c/src/doc/list.rs index c4503322..6bcdeabf 100644 --- a/rust/automerge-c/src/doc/list.rs +++ b/rust/automerge-c/src/doc/list.rs @@ -3,44 +3,47 @@ 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 +53,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 >::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 >::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 +157,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 +192,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 = (&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 +279,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 +320,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 +360,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 +399,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 +440,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 +525,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 +566,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 >::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())), } } diff --git a/rust/automerge-c/src/doc/list/item.rs b/rust/automerge-c/src/doc/list/item.rs new file mode 100644 index 00000000..7a3869f3 --- /dev/null +++ b/rust/automerge-c/src/doc/list/item.rs @@ -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 + } +} diff --git a/rust/automerge-c/src/doc/list/items.rs b/rust/automerge-c/src/doc/list/items.rs new file mode 100644 index 00000000..5b4a11fd --- /dev/null +++ b/rust/automerge-c/src/doc/list/items.rs @@ -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()` call +/// (https://github.com/eqrion/cbindgen/issues/252) but it will +/// propagate the name of a constant initialized from it so if the +/// constant's name is a symbolic representation of the value it can be +/// converted into a number by post-processing the header it generated. +pub const USIZE_USIZE_USIZE_: usize = size_of::(); + +impl Detail { + fn new(list_items: &[AMlistItem], offset: isize) -> Self { + Self { + len: list_items.len(), + offset, + ptr: list_items.as_ptr() as *const c_void, + } + } + + pub fn advance(&mut self, n: isize) { + if n == 0 { + return; + } + let len = self.len as isize; + self.offset = if self.offset < 0 { + // It's reversed. + let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); + if unclipped >= 0 { + // Clip it to the forward stop. + len + } else { + std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) + } + } else { + let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); + if unclipped < 0 { + // Clip it to the reverse stop. + -(len + 1) + } else { + std::cmp::max(0, std::cmp::min(unclipped, len)) + } + } + } + + pub fn get_index(&self) -> usize { + (self.offset + + if self.offset < 0 { + self.len as isize + } else { + 0 + }) as usize + } + + pub fn next(&mut self, n: isize) -> Option<&AMlistItem> { + if self.is_stopped() { + return None; + } + let slice: &[AMlistItem] = + unsafe { std::slice::from_raw_parts(self.ptr as *const AMlistItem, self.len) }; + let value = &slice[self.get_index()]; + self.advance(n); + Some(value) + } + + pub fn is_stopped(&self) -> bool { + let len = self.len as isize; + self.offset < -len || self.offset == len + } + + pub fn prev(&mut self, n: isize) -> Option<&AMlistItem> { + self.advance(-n); + if self.is_stopped() { + return None; + } + let slice: &[AMlistItem] = + unsafe { std::slice::from_raw_parts(self.ptr as *const AMlistItem, self.len) }; + Some(&slice[self.get_index()]) + } + + pub fn reversed(&self) -> Self { + Self { + len: self.len, + offset: -(self.offset + 1), + ptr: self.ptr, + } + } + + pub fn rewound(&self) -> Self { + Self { + len: self.len, + offset: if self.offset < 0 { -1 } else { 0 }, + ptr: self.ptr, + } + } +} + +impl From for [u8; USIZE_USIZE_USIZE_] { + fn from(detail: Detail) -> Self { + unsafe { + std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) + .try_into() + .unwrap() + } + } +} + +/// \struct AMlistItems +/// \installed_headerfile +/// \brief A random-access iterator over a sequence of list object items. +#[repr(C)] +#[derive(Eq, PartialEq)] +pub struct AMlistItems { + /// An implementation detail that is intentionally opaque. + /// \warning Modifying \p detail will cause undefined behavior. + /// \note The actual size of \p detail will vary by platform, this is just + /// the one for the platform this documentation was built on. + detail: [u8; USIZE_USIZE_USIZE_], +} + +impl AMlistItems { + pub fn new(list_items: &[AMlistItem]) -> Self { + Self { + detail: Detail::new(list_items, 0).into(), + } + } + + pub fn advance(&mut self, n: isize) { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.advance(n); + } + + pub fn len(&self) -> usize { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + detail.len + } + + pub fn next(&mut self, n: isize) -> Option<&AMlistItem> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.next(n) + } + + pub fn prev(&mut self, n: isize) -> Option<&AMlistItem> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.prev(n) + } + + pub fn reversed(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.reversed().into(), + } + } + + pub fn rewound(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.rewound().into(), + } + } +} + +impl AsRef<[AMlistItem]> for AMlistItems { + fn as_ref(&self) -> &[AMlistItem] { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + unsafe { std::slice::from_raw_parts(detail.ptr as *const AMlistItem, detail.len) } + } +} + +impl Default for AMlistItems { + fn default() -> Self { + Self { + detail: [0; USIZE_USIZE_USIZE_], + } + } +} + +/// \memberof AMlistItems +/// \brief Advances an iterator over a sequence of list object items by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction. +/// +/// \param[in,out] list_items A pointer to an `AMlistItems` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \pre \p list_items `!= NULL`. +/// \internal +/// +/// #Safety +/// list_items must be a valid pointer to an AMlistItems +#[no_mangle] +pub unsafe extern "C" fn AMlistItemsAdvance(list_items: *mut AMlistItems, n: isize) { + if let Some(list_items) = list_items.as_mut() { + list_items.advance(n); + }; +} + +/// \memberof AMlistItems +/// \brief Tests the equality of two sequences of list object items underlying +/// a pair of iterators. +/// +/// \param[in] list_items1 A pointer to an `AMlistItems` struct. +/// \param[in] list_items2 A pointer to an `AMlistItems` struct. +/// \return `true` if \p list_items1 `==` \p list_items2 and `false` otherwise. +/// \pre \p list_items1 `!= NULL`. +/// \pre \p list_items2 `!= NULL`. +/// \internal +/// +/// #Safety +/// list_items1 must be a valid pointer to an AMlistItems +/// list_items2 must be a valid pointer to an AMlistItems +#[no_mangle] +pub unsafe extern "C" fn AMlistItemsEqual( + list_items1: *const AMlistItems, + list_items2: *const AMlistItems, +) -> bool { + match (list_items1.as_ref(), list_items2.as_ref()) { + (Some(list_items1), Some(list_items2)) => list_items1.as_ref() == list_items2.as_ref(), + (None, Some(_)) | (Some(_), None) | (None, None) => false, + } +} + +/// \memberof AMlistItems +/// \brief Gets the list object item at the current position of an iterator +/// over a sequence of list object items and then advances it by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction. +/// +/// \param[in,out] list_items A pointer to an `AMlistItems` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A pointer to an `AMlistItem` struct that's `NULL` when +/// \p list_items was previously advanced past its forward/reverse +/// limit. +/// \pre \p list_items `!= NULL`. +/// \internal +/// +/// #Safety +/// list_items must be a valid pointer to an AMlistItems +#[no_mangle] +pub unsafe extern "C" fn AMlistItemsNext( + list_items: *mut AMlistItems, + n: isize, +) -> *const AMlistItem { + if let Some(list_items) = list_items.as_mut() { + if let Some(list_item) = list_items.next(n) { + return list_item; + } + } + std::ptr::null() +} + +/// \memberof AMlistItems +/// \brief Advances an iterator over a sequence of list object items by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction and then gets the list object item at its new +/// position. +/// +/// \param[in,out] list_items A pointer to an `AMlistItems` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A pointer to an `AMlistItem` struct that's `NULL` when +/// \p list_items is presently advanced past its forward/reverse limit. +/// \pre \p list_items `!= NULL`. +/// \internal +/// +/// #Safety +/// list_items must be a valid pointer to an AMlistItems +#[no_mangle] +pub unsafe extern "C" fn AMlistItemsPrev( + list_items: *mut AMlistItems, + n: isize, +) -> *const AMlistItem { + if let Some(list_items) = list_items.as_mut() { + if let Some(list_item) = list_items.prev(n) { + return list_item; + } + } + std::ptr::null() +} + +/// \memberof AMlistItems +/// \brief Gets the size of the sequence of list object items underlying an +/// iterator. +/// +/// \param[in] list_items A pointer to an `AMlistItems` struct. +/// \return The count of values in \p list_items. +/// \pre \p list_items `!= NULL`. +/// \internal +/// +/// #Safety +/// list_items must be a valid pointer to an AMlistItems +#[no_mangle] +pub unsafe extern "C" fn AMlistItemsSize(list_items: *const AMlistItems) -> usize { + if let Some(list_items) = list_items.as_ref() { + list_items.len() + } else { + 0 + } +} + +/// \memberof AMlistItems +/// \brief Creates an iterator over the same sequence of list object items as +/// the given one but with the opposite position and direction. +/// +/// \param[in] list_items A pointer to an `AMlistItems` struct. +/// \return An `AMlistItems` struct +/// \pre \p list_items `!= NULL`. +/// \internal +/// +/// #Safety +/// list_items must be a valid pointer to an AMlistItems +#[no_mangle] +pub unsafe extern "C" fn AMlistItemsReversed(list_items: *const AMlistItems) -> AMlistItems { + if let Some(list_items) = list_items.as_ref() { + list_items.reversed() + } else { + Default::default() + } +} + +/// \memberof AMlistItems +/// \brief Creates an iterator at the starting position over the same sequence +/// of list object items as the given one. +/// +/// \param[in] list_items A pointer to an `AMlistItems` struct. +/// \return An `AMlistItems` struct +/// \pre \p list_items `!= NULL`. +/// \internal +/// +/// #Safety +/// list_items must be a valid pointer to an AMlistItems +#[no_mangle] +pub unsafe extern "C" fn AMlistItemsRewound(list_items: *const AMlistItems) -> AMlistItems { + if let Some(list_items) = list_items.as_ref() { + list_items.rewound() + } else { + Default::default() + } +} diff --git a/rust/automerge-c/src/doc/map.rs b/rust/automerge-c/src/doc/map.rs index b2f7db02..86c6b4a2 100644 --- a/rust/automerge-c/src/doc/map.rs +++ b/rust/automerge-c/src/doc/map.rs @@ -3,29 +3,31 @@ 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 +40,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 >::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 >::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 +145,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 +173,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::::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 +245,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 +273,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 +299,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 +335,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 +365,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 +395,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 +425,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 +453,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 >::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)) } diff --git a/rust/automerge-c/src/doc/map/item.rs b/rust/automerge-c/src/doc/map/item.rs new file mode 100644 index 00000000..7914fdc4 --- /dev/null +++ b/rust/automerge-c/src/doc/map/item.rs @@ -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 + } +} diff --git a/rust/automerge-c/src/doc/map/items.rs b/rust/automerge-c/src/doc/map/items.rs new file mode 100644 index 00000000..cd305971 --- /dev/null +++ b/rust/automerge-c/src/doc/map/items.rs @@ -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()` call +/// (https://github.com/eqrion/cbindgen/issues/252) but it will +/// propagate the name of a constant initialized from it so if the +/// constant's name is a symbolic representation of the value it can be +/// converted into a number by post-processing the header it generated. +pub const USIZE_USIZE_USIZE_: usize = size_of::(); + +impl Detail { + fn new(map_items: &[AMmapItem], offset: isize) -> Self { + Self { + len: map_items.len(), + offset, + ptr: map_items.as_ptr() as *const c_void, + } + } + + pub fn advance(&mut self, n: isize) { + if n == 0 { + return; + } + let len = self.len as isize; + self.offset = if self.offset < 0 { + // It's reversed. + let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); + if unclipped >= 0 { + // Clip it to the forward stop. + len + } else { + std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) + } + } else { + let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); + if unclipped < 0 { + // Clip it to the reverse stop. + -(len + 1) + } else { + std::cmp::max(0, std::cmp::min(unclipped, len)) + } + } + } + + pub fn get_index(&self) -> usize { + (self.offset + + if self.offset < 0 { + self.len as isize + } else { + 0 + }) as usize + } + + pub fn next(&mut self, n: isize) -> Option<&AMmapItem> { + if self.is_stopped() { + return None; + } + let slice: &[AMmapItem] = + unsafe { std::slice::from_raw_parts(self.ptr as *const AMmapItem, self.len) }; + let value = &slice[self.get_index()]; + self.advance(n); + Some(value) + } + + pub fn is_stopped(&self) -> bool { + let len = self.len as isize; + self.offset < -len || self.offset == len + } + + pub fn prev(&mut self, n: isize) -> Option<&AMmapItem> { + self.advance(-n); + if self.is_stopped() { + return None; + } + let slice: &[AMmapItem] = + unsafe { std::slice::from_raw_parts(self.ptr as *const AMmapItem, self.len) }; + Some(&slice[self.get_index()]) + } + + pub fn reversed(&self) -> Self { + Self { + len: self.len, + offset: -(self.offset + 1), + ptr: self.ptr, + } + } + + pub fn rewound(&self) -> Self { + Self { + len: self.len, + offset: if self.offset < 0 { -1 } else { 0 }, + ptr: self.ptr, + } + } +} + +impl From for [u8; USIZE_USIZE_USIZE_] { + fn from(detail: Detail) -> Self { + unsafe { + std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) + .try_into() + .unwrap() + } + } +} + +/// \struct AMmapItems +/// \installed_headerfile +/// \brief A random-access iterator over a sequence of map object items. +#[repr(C)] +#[derive(Eq, PartialEq)] +pub struct AMmapItems { + /// An implementation detail that is intentionally opaque. + /// \warning Modifying \p detail will cause undefined behavior. + /// \note The actual size of \p detail will vary by platform, this is just + /// the one for the platform this documentation was built on. + detail: [u8; USIZE_USIZE_USIZE_], +} + +impl AMmapItems { + pub fn new(map_items: &[AMmapItem]) -> Self { + Self { + detail: Detail::new(map_items, 0).into(), + } + } + + pub fn advance(&mut self, n: isize) { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.advance(n); + } + + pub fn len(&self) -> usize { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + detail.len + } + + pub fn next(&mut self, n: isize) -> Option<&AMmapItem> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.next(n) + } + + pub fn prev(&mut self, n: isize) -> Option<&AMmapItem> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.prev(n) + } + + pub fn reversed(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.reversed().into(), + } + } + + pub fn rewound(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.rewound().into(), + } + } +} + +impl AsRef<[AMmapItem]> for AMmapItems { + fn as_ref(&self) -> &[AMmapItem] { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + unsafe { std::slice::from_raw_parts(detail.ptr as *const AMmapItem, detail.len) } + } +} + +impl Default for AMmapItems { + fn default() -> Self { + Self { + detail: [0; USIZE_USIZE_USIZE_], + } + } +} + +/// \memberof AMmapItems +/// \brief Advances an iterator over a sequence of map object items by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction. +/// +/// \param[in,out] map_items A pointer to an `AMmapItems` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \pre \p map_items `!= NULL`. +/// \internal +/// +/// #Safety +/// map_items must be a valid pointer to an AMmapItems +#[no_mangle] +pub unsafe extern "C" fn AMmapItemsAdvance(map_items: *mut AMmapItems, n: isize) { + if let Some(map_items) = map_items.as_mut() { + map_items.advance(n); + }; +} + +/// \memberof AMmapItems +/// \brief Tests the equality of two sequences of map object items underlying +/// a pair of iterators. +/// +/// \param[in] map_items1 A pointer to an `AMmapItems` struct. +/// \param[in] map_items2 A pointer to an `AMmapItems` struct. +/// \return `true` if \p map_items1 `==` \p map_items2 and `false` otherwise. +/// \pre \p map_items1 `!= NULL`. +/// \pre \p map_items2 `!= NULL`. +/// \internal +/// +/// #Safety +/// map_items1 must be a valid pointer to an AMmapItems +/// map_items2 must be a valid pointer to an AMmapItems +#[no_mangle] +pub unsafe extern "C" fn AMmapItemsEqual( + map_items1: *const AMmapItems, + map_items2: *const AMmapItems, +) -> bool { + match (map_items1.as_ref(), map_items2.as_ref()) { + (Some(map_items1), Some(map_items2)) => map_items1.as_ref() == map_items2.as_ref(), + (None, Some(_)) | (Some(_), None) | (None, None) => false, + } +} + +/// \memberof AMmapItems +/// \brief Gets the map object item at the current position of an iterator +/// over a sequence of map object items and then advances it by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction. +/// +/// \param[in,out] map_items A pointer to an `AMmapItems` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A pointer to an `AMmapItem` struct that's `NULL` when \p map_items +/// was previously advanced past its forward/reverse limit. +/// \pre \p map_items `!= NULL`. +/// \internal +/// +/// #Safety +/// map_items must be a valid pointer to an AMmapItems +#[no_mangle] +pub unsafe extern "C" fn AMmapItemsNext(map_items: *mut AMmapItems, n: isize) -> *const AMmapItem { + if let Some(map_items) = map_items.as_mut() { + if let Some(map_item) = map_items.next(n) { + return map_item; + } + } + std::ptr::null() +} + +/// \memberof AMmapItems +/// \brief Advances an iterator over a sequence of map object items by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction and then gets the map object item at its new +/// position. +/// +/// \param[in,out] map_items A pointer to an `AMmapItems` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A pointer to an `AMmapItem` struct that's `NULL` when \p map_items +/// is presently advanced past its forward/reverse limit. +/// \pre \p map_items `!= NULL`. +/// \internal +/// +/// #Safety +/// map_items must be a valid pointer to an AMmapItems +#[no_mangle] +pub unsafe extern "C" fn AMmapItemsPrev(map_items: *mut AMmapItems, n: isize) -> *const AMmapItem { + if let Some(map_items) = map_items.as_mut() { + if let Some(map_item) = map_items.prev(n) { + return map_item; + } + } + std::ptr::null() +} + +/// \memberof AMmapItems +/// \brief Gets the size of the sequence of map object items underlying an +/// iterator. +/// +/// \param[in] map_items A pointer to an `AMmapItems` struct. +/// \return The count of values in \p map_items. +/// \pre \p map_items `!= NULL`. +/// \internal +/// +/// #Safety +/// map_items must be a valid pointer to an AMmapItems +#[no_mangle] +pub unsafe extern "C" fn AMmapItemsSize(map_items: *const AMmapItems) -> usize { + if let Some(map_items) = map_items.as_ref() { + map_items.len() + } else { + 0 + } +} + +/// \memberof AMmapItems +/// \brief Creates an iterator over the same sequence of map object items as +/// the given one but with the opposite position and direction. +/// +/// \param[in] map_items A pointer to an `AMmapItems` struct. +/// \return An `AMmapItems` struct +/// \pre \p map_items `!= NULL`. +/// \internal +/// +/// #Safety +/// map_items must be a valid pointer to an AMmapItems +#[no_mangle] +pub unsafe extern "C" fn AMmapItemsReversed(map_items: *const AMmapItems) -> AMmapItems { + if let Some(map_items) = map_items.as_ref() { + map_items.reversed() + } else { + Default::default() + } +} + +/// \memberof AMmapItems +/// \brief Creates an iterator at the starting position over the same sequence of map object items as the given one. +/// +/// \param[in] map_items A pointer to an `AMmapItems` struct. +/// \return An `AMmapItems` struct +/// \pre \p map_items `!= NULL`. +/// \internal +/// +/// #Safety +/// map_items must be a valid pointer to an AMmapItems +#[no_mangle] +pub unsafe extern "C" fn AMmapItemsRewound(map_items: *const AMmapItems) -> AMmapItems { + if let Some(map_items) = map_items.as_ref() { + map_items.rewound() + } else { + Default::default() + } +} diff --git a/rust/automerge-c/src/doc/utils.rs b/rust/automerge-c/src/doc/utils.rs index ce465b84..d98a9a8b 100644 --- a/rust/automerge-c/src/doc/utils.rs +++ b/rust/automerge-c/src/doc/utils.rs @@ -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; diff --git a/rust/automerge-c/src/index.rs b/rust/automerge-c/src/index.rs deleted file mode 100644 index f1ea153b..00000000 --- a/rust/automerge-c/src/index.rs +++ /dev/null @@ -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 { - use am::AutomergeError::InvalidValueType; - use AMindex::*; - - if let Key(key) = item { - return Ok(key.into()); - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }) - } -} - -impl TryFrom<&AMindex> for usize { - type Error = am::AutomergeError; - - fn try_from(item: &AMindex) -> Result { - use am::AutomergeError::InvalidValueType; - use AMindex::*; - - if let Pos(pos) = item { - return Ok(*pos); - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }) - } -} - -/// \ingroup enumerations -/// \enum AMidxType -/// \installed_headerfile -/// \brief The type of an item's index. -#[derive(PartialEq, Eq)] -#[repr(u8)] -pub enum AMidxType { - /// The default tag, not a type signifier. - Default = 0, - /// A UTF-8 string view key. - Key, - /// A 64-bit unsigned integer position. - Pos, -} - -impl Default for AMidxType { - fn default() -> Self { - Self::Default - } -} - -impl From<&AMindex> for AMidxType { - fn from(index: &AMindex) -> Self { - use AMindex::*; - - match index { - Key(_) => Self::Key, - Pos(_) => Self::Pos, - } - } -} diff --git a/rust/automerge-c/src/item.rs b/rust/automerge-c/src/item.rs deleted file mode 100644 index 94735464..00000000 --- a/rust/automerge-c/src/item.rs +++ /dev/null @@ -1,1963 +0,0 @@ -use automerge as am; - -use std::any::type_name; -use std::borrow::Cow; -use std::cell::{RefCell, UnsafeCell}; -use std::rc::Rc; - -use crate::actor_id::AMactorId; -use crate::byte_span::{to_str, AMbyteSpan}; -use crate::change::AMchange; -use crate::doc::AMdoc; -use crate::index::{AMidxType, AMindex}; -use crate::obj::AMobjId; -use crate::result::{to_result, AMresult}; -use crate::sync::{AMsyncHave, AMsyncMessage, AMsyncState}; - -/// \struct AMunknownValue -/// \installed_headerfile -/// \brief A value (typically for a `set` operation) whose type is unknown. -#[derive(Default, Eq, PartialEq)] -#[repr(C)] -pub struct AMunknownValue { - /// The value's raw bytes. - bytes: AMbyteSpan, - /// The value's encoded type identifier. - type_code: u8, -} - -pub enum Value { - ActorId(am::ActorId, UnsafeCell>), - Change(Box, UnsafeCell>), - ChangeHash(am::ChangeHash), - Doc(RefCell), - SyncHave(AMsyncHave), - SyncMessage(AMsyncMessage), - SyncState(RefCell), - Value(am::Value<'static>), -} - -impl Value { - pub fn try_into_bytes(&self) -> Result { - use am::AutomergeError::InvalidValueType; - use am::ScalarValue::*; - use am::Value::*; - - if let Self::Value(Scalar(scalar)) = &self { - if let Bytes(vector) = scalar.as_ref() { - return Ok(vector.as_slice().into()); - } - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }) - } - - pub fn try_into_change_hash(&self) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Self::ChangeHash(change_hash) = &self { - return Ok(change_hash.into()); - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }) - } - - pub fn try_into_counter(&self) -> Result { - use am::AutomergeError::InvalidValueType; - use am::ScalarValue::*; - use am::Value::*; - - if let Self::Value(Scalar(scalar)) = &self { - if let Counter(counter) = scalar.as_ref() { - return Ok(counter.into()); - } - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }) - } - - pub fn try_into_int(&self) -> Result { - use am::AutomergeError::InvalidValueType; - use am::ScalarValue::*; - use am::Value::*; - - if let Self::Value(Scalar(scalar)) = &self { - if let Int(int) = scalar.as_ref() { - return Ok(*int); - } - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }) - } - - pub fn try_into_str(&self) -> Result { - use am::AutomergeError::InvalidValueType; - use am::ScalarValue::*; - use am::Value::*; - - if let Self::Value(Scalar(scalar)) = &self { - if let Str(smol_str) = scalar.as_ref() { - return Ok(smol_str.into()); - } - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }) - } - - pub fn try_into_timestamp(&self) -> Result { - use am::AutomergeError::InvalidValueType; - use am::ScalarValue::*; - use am::Value::*; - - if let Self::Value(Scalar(scalar)) = &self { - if let Timestamp(timestamp) = scalar.as_ref() { - return Ok(*timestamp); - } - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }) - } -} - -impl From for Value { - fn from(actor_id: am::ActorId) -> Self { - Self::ActorId(actor_id, Default::default()) - } -} - -impl From for Value { - fn from(auto_commit: am::AutoCommit) -> Self { - Self::Doc(RefCell::new(AMdoc::new(auto_commit))) - } -} - -impl From for Value { - fn from(change: am::Change) -> Self { - Self::Change(Box::new(change), Default::default()) - } -} - -impl From for Value { - fn from(change_hash: am::ChangeHash) -> Self { - Self::ChangeHash(change_hash) - } -} - -impl From for Value { - fn from(have: am::sync::Have) -> Self { - Self::SyncHave(AMsyncHave::new(have)) - } -} - -impl From for Value { - fn from(message: am::sync::Message) -> Self { - Self::SyncMessage(AMsyncMessage::new(message)) - } -} - -impl From for Value { - fn from(state: am::sync::State) -> Self { - Self::SyncState(RefCell::new(AMsyncState::new(state))) - } -} - -impl From> for Value { - fn from(value: am::Value<'static>) -> Self { - Self::Value(value) - } -} - -impl From for Value { - fn from(string: String) -> Self { - Self::Value(am::Value::Scalar(Cow::Owned(am::ScalarValue::Str( - string.into(), - )))) - } -} - -impl<'a> TryFrom<&'a Value> for &'a am::Change { - type Error = am::AutomergeError; - - fn try_from(value: &'a Value) -> Result { - use self::Value::*; - use am::AutomergeError::InvalidValueType; - - match value { - Change(change, _) => Ok(change), - _ => Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }), - } - } -} - -impl<'a> TryFrom<&'a Value> for &'a am::ChangeHash { - type Error = am::AutomergeError; - - fn try_from(value: &'a Value) -> Result { - use self::Value::*; - use am::AutomergeError::InvalidValueType; - - match value { - ChangeHash(change_hash) => Ok(change_hash), - _ => Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }), - } - } -} - -impl<'a> TryFrom<&'a Value> for &'a am::ScalarValue { - type Error = am::AutomergeError; - - fn try_from(value: &'a Value) -> Result { - use self::Value::*; - use am::AutomergeError::InvalidValueType; - use am::Value::*; - - if let Value(Scalar(scalar)) = value { - return Ok(scalar.as_ref()); - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }) - } -} - -impl<'a> TryFrom<&'a Value> for &'a AMactorId { - type Error = am::AutomergeError; - - fn try_from(value: &'a Value) -> Result { - use self::Value::*; - use am::AutomergeError::InvalidValueType; - - match value { - ActorId(actor_id, c_actor_id) => unsafe { - Ok((*c_actor_id.get()).get_or_insert(AMactorId::new(actor_id))) - }, - _ => Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }), - } - } -} - -impl<'a> TryFrom<&'a mut Value> for &'a mut AMchange { - type Error = am::AutomergeError; - - fn try_from(value: &'a mut Value) -> Result { - use self::Value::*; - use am::AutomergeError::InvalidValueType; - - match value { - Change(change, c_change) => unsafe { - Ok((*c_change.get()).get_or_insert(AMchange::new(change))) - }, - _ => Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }), - } - } -} - -impl<'a> TryFrom<&'a mut Value> for &'a mut AMdoc { - type Error = am::AutomergeError; - - fn try_from(value: &'a mut Value) -> Result { - use self::Value::*; - use am::AutomergeError::InvalidValueType; - - match value { - Doc(doc) => Ok(doc.get_mut()), - _ => Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }), - } - } -} - -impl<'a> TryFrom<&'a Value> for &'a AMsyncHave { - type Error = am::AutomergeError; - - fn try_from(value: &'a Value) -> Result { - use self::Value::*; - use am::AutomergeError::InvalidValueType; - - match value { - SyncHave(sync_have) => Ok(sync_have), - _ => Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }), - } - } -} - -impl<'a> TryFrom<&'a Value> for &'a AMsyncMessage { - type Error = am::AutomergeError; - - fn try_from(value: &'a Value) -> Result { - use self::Value::*; - use am::AutomergeError::InvalidValueType; - - match value { - SyncMessage(sync_message) => Ok(sync_message), - _ => Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }), - } - } -} - -impl<'a> TryFrom<&'a mut Value> for &'a mut AMsyncState { - type Error = am::AutomergeError; - - fn try_from(value: &'a mut Value) -> Result { - use self::Value::*; - use am::AutomergeError::InvalidValueType; - - match value { - SyncState(sync_state) => Ok(sync_state.get_mut()), - _ => Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }), - } - } -} - -impl TryFrom<&Value> for bool { - type Error = am::AutomergeError; - - fn try_from(value: &Value) -> Result { - use self::Value::*; - use am::AutomergeError::InvalidValueType; - use am::ScalarValue::*; - use am::Value::*; - - if let Value(Scalar(scalar)) = value { - if let Boolean(boolean) = scalar.as_ref() { - return Ok(*boolean); - } - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }) - } -} - -impl TryFrom<&Value> for f64 { - type Error = am::AutomergeError; - - fn try_from(value: &Value) -> Result { - use self::Value::*; - use am::AutomergeError::InvalidValueType; - use am::ScalarValue::*; - use am::Value::*; - - if let Value(Scalar(scalar)) = value { - if let F64(float) = scalar.as_ref() { - return Ok(*float); - } - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }) - } -} - -impl TryFrom<&Value> for u64 { - type Error = am::AutomergeError; - - fn try_from(value: &Value) -> Result { - use self::Value::*; - use am::AutomergeError::InvalidValueType; - use am::ScalarValue::*; - use am::Value::*; - - if let Value(Scalar(scalar)) = value { - if let Uint(uint) = scalar.as_ref() { - return Ok(*uint); - } - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }) - } -} - -impl TryFrom<&Value> for AMunknownValue { - type Error = am::AutomergeError; - - fn try_from(value: &Value) -> Result { - use self::Value::*; - use am::AutomergeError::InvalidValueType; - use am::ScalarValue::*; - use am::Value::*; - - if let Value(Scalar(scalar)) = value { - if let Unknown { bytes, type_code } = scalar.as_ref() { - return Ok(Self { - bytes: bytes.as_slice().into(), - type_code: *type_code, - }); - } - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }) - } -} - -impl PartialEq for Value { - fn eq(&self, other: &Self) -> bool { - use self::Value::*; - - match (self, other) { - (ActorId(lhs, _), ActorId(rhs, _)) => *lhs == *rhs, - (Change(lhs, _), Change(rhs, _)) => lhs == rhs, - (ChangeHash(lhs), ChangeHash(rhs)) => lhs == rhs, - (Doc(lhs), Doc(rhs)) => lhs.as_ptr() == rhs.as_ptr(), - (SyncMessage(lhs), SyncMessage(rhs)) => *lhs == *rhs, - (SyncState(lhs), SyncState(rhs)) => *lhs == *rhs, - (Value(lhs), Value(rhs)) => lhs == rhs, - _ => false, - } - } -} - -#[derive(Default)] -pub struct Item { - /// The item's index. - index: Option, - /// The item's identifier. - obj_id: Option, - /// The item's value. - value: Option, -} - -impl Item { - pub fn try_into_bytes(&self) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &self.value { - return value.try_into_bytes(); - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - - pub fn try_into_change_hash(&self) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &self.value { - return value.try_into_change_hash(); - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - - pub fn try_into_counter(&self) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &self.value { - return value.try_into_counter(); - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - - pub fn try_into_int(&self) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &self.value { - return value.try_into_int(); - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - - pub fn try_into_str(&self) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &self.value { - return value.try_into_str(); - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - - pub fn try_into_timestamp(&self) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &self.value { - return value.try_into_timestamp(); - } - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } -} - -impl From for Item { - fn from(actor_id: am::ActorId) -> Self { - Value::from(actor_id).into() - } -} - -impl From for Item { - fn from(auto_commit: am::AutoCommit) -> Self { - Value::from(auto_commit).into() - } -} - -impl From for Item { - fn from(change: am::Change) -> Self { - Value::from(change).into() - } -} - -impl From for Item { - fn from(change_hash: am::ChangeHash) -> Self { - Value::from(change_hash).into() - } -} - -impl From<(am::ObjId, am::ObjType)> for Item { - fn from((obj_id, obj_type): (am::ObjId, am::ObjType)) -> Self { - Self { - index: None, - obj_id: Some(AMobjId::new(obj_id)), - value: Some(am::Value::Object(obj_type).into()), - } - } -} - -impl From for Item { - fn from(have: am::sync::Have) -> Self { - Value::from(have).into() - } -} - -impl From for Item { - fn from(message: am::sync::Message) -> Self { - Value::from(message).into() - } -} - -impl From for Item { - fn from(state: am::sync::State) -> Self { - Value::from(state).into() - } -} - -impl From> for Item { - fn from(value: am::Value<'static>) -> Self { - Value::from(value).into() - } -} - -impl From for Item { - fn from(string: String) -> Self { - Value::from(string).into() - } -} - -impl From for Item { - fn from(value: Value) -> Self { - Self { - index: None, - obj_id: None, - value: Some(value), - } - } -} - -impl PartialEq for Item { - fn eq(&self, other: &Self) -> bool { - self.index == other.index && self.obj_id == other.obj_id && self.value == other.value - } -} - -impl<'a> TryFrom<&'a Item> for &'a am::Change { - type Error = am::AutomergeError; - - fn try_from(item: &'a Item) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &item.value { - value.try_into() - } else { - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - } -} - -impl<'a> TryFrom<&'a Item> for &'a am::ChangeHash { - type Error = am::AutomergeError; - - fn try_from(item: &'a Item) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &item.value { - value.try_into() - } else { - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - } -} - -impl<'a> TryFrom<&'a Item> for &'a am::ScalarValue { - type Error = am::AutomergeError; - - fn try_from(item: &'a Item) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &item.value { - value.try_into() - } else { - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - } -} - -impl<'a> TryFrom<&'a Item> for &'a AMactorId { - type Error = am::AutomergeError; - - fn try_from(item: &'a Item) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &item.value { - value.try_into() - } else { - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - } -} - -impl<'a> TryFrom<&'a mut Item> for &'a mut AMchange { - type Error = am::AutomergeError; - - fn try_from(item: &'a mut Item) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &mut item.value { - value.try_into() - } else { - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - } -} - -impl<'a> TryFrom<&'a mut Item> for &'a mut AMdoc { - type Error = am::AutomergeError; - - fn try_from(item: &'a mut Item) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &mut item.value { - value.try_into() - } else { - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - } -} - -impl From<&Item> for AMidxType { - fn from(item: &Item) -> Self { - if let Some(index) = &item.index { - return index.into(); - } - Default::default() - } -} - -impl<'a> TryFrom<&'a Item> for &'a AMsyncHave { - type Error = am::AutomergeError; - - fn try_from(item: &'a Item) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &item.value { - value.try_into() - } else { - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - } -} - -impl<'a> TryFrom<&'a Item> for &'a AMsyncMessage { - type Error = am::AutomergeError; - - fn try_from(item: &'a Item) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &item.value { - value.try_into() - } else { - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - } -} - -impl<'a> TryFrom<&'a mut Item> for &'a mut AMsyncState { - type Error = am::AutomergeError; - - fn try_from(item: &'a mut Item) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &mut item.value { - value.try_into() - } else { - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - } -} - -impl TryFrom<&Item> for bool { - type Error = am::AutomergeError; - - fn try_from(item: &Item) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &item.value { - value.try_into() - } else { - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - } -} - -impl TryFrom<&Item> for f64 { - type Error = am::AutomergeError; - - fn try_from(item: &Item) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &item.value { - value.try_into() - } else { - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - } -} - -impl TryFrom<&Item> for u64 { - type Error = am::AutomergeError; - - fn try_from(item: &Item) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &item.value { - value.try_into() - } else { - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - } -} - -impl TryFrom<&Item> for AMunknownValue { - type Error = am::AutomergeError; - - fn try_from(item: &Item) -> Result { - use am::AutomergeError::InvalidValueType; - - if let Some(value) = &item.value { - value.try_into() - } else { - Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::>().to_string(), - }) - } - } -} - -impl TryFrom<&Item> for (am::Value<'static>, am::ObjId) { - type Error = am::AutomergeError; - - fn try_from(item: &Item) -> Result { - use self::Value::*; - use am::AutomergeError::InvalidObjId; - use am::AutomergeError::InvalidValueType; - - let expected = type_name::().to_string(); - match (&item.obj_id, &item.value) { - (None, None) | (None, Some(_)) => Err(InvalidObjId("".to_string())), - (Some(_), None) => Err(InvalidValueType { - expected, - unexpected: type_name::>().to_string(), - }), - (Some(obj_id), Some(value)) => match value { - ActorId(_, _) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - ChangeHash(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - Change(_, _) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - Doc(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - SyncHave(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - SyncMessage(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - SyncState(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - Value(v) => Ok((v.clone(), obj_id.as_ref().clone())), - }, - } - } -} - -/// \struct AMitem -/// \installed_headerfile -/// \brief An item within a result. -#[derive(Clone)] -pub struct AMitem(Rc); - -impl AMitem { - pub fn exact(obj_id: am::ObjId, value: Value) -> Self { - Self(Rc::new(Item { - index: None, - obj_id: Some(AMobjId::new(obj_id)), - value: Some(value), - })) - } - - pub fn indexed(index: AMindex, obj_id: am::ObjId, value: Value) -> Self { - Self(Rc::new(Item { - index: Some(index), - obj_id: Some(AMobjId::new(obj_id)), - value: Some(value), - })) - } -} - -impl AsRef for AMitem { - fn as_ref(&self) -> &Item { - self.0.as_ref() - } -} - -impl Default for AMitem { - fn default() -> Self { - Self(Rc::new(Item { - index: None, - obj_id: None, - value: None, - })) - } -} - -impl From for AMitem { - fn from(actor_id: am::ActorId) -> Self { - Value::from(actor_id).into() - } -} - -impl From for AMitem { - fn from(auto_commit: am::AutoCommit) -> Self { - Value::from(auto_commit).into() - } -} - -impl From for AMitem { - fn from(change: am::Change) -> Self { - Value::from(change).into() - } -} - -impl From for AMitem { - fn from(change_hash: am::ChangeHash) -> Self { - Value::from(change_hash).into() - } -} - -impl From<(am::ObjId, am::ObjType)> for AMitem { - fn from((obj_id, obj_type): (am::ObjId, am::ObjType)) -> Self { - Self(Rc::new(Item::from((obj_id, obj_type)))) - } -} - -impl From for AMitem { - fn from(have: am::sync::Have) -> Self { - Value::from(have).into() - } -} - -impl From for AMitem { - fn from(message: am::sync::Message) -> Self { - Value::from(message).into() - } -} - -impl From for AMitem { - fn from(state: am::sync::State) -> Self { - Value::from(state).into() - } -} - -impl From> for AMitem { - fn from(value: am::Value<'static>) -> Self { - Value::from(value).into() - } -} - -impl From for AMitem { - fn from(string: String) -> Self { - Value::from(string).into() - } -} - -impl From for AMitem { - fn from(value: Value) -> Self { - Self(Rc::new(Item::from(value))) - } -} - -impl PartialEq for AMitem { - fn eq(&self, other: &Self) -> bool { - self.as_ref() == other.as_ref() - } -} - -impl<'a> TryFrom<&'a AMitem> for &'a am::Change { - type Error = am::AutomergeError; - - fn try_from(item: &'a AMitem) -> Result { - item.as_ref().try_into() - } -} - -impl<'a> TryFrom<&'a AMitem> for &'a am::ChangeHash { - type Error = am::AutomergeError; - - fn try_from(item: &'a AMitem) -> Result { - item.as_ref().try_into() - } -} - -impl<'a> TryFrom<&'a AMitem> for &'a am::ScalarValue { - type Error = am::AutomergeError; - - fn try_from(item: &'a AMitem) -> Result { - item.as_ref().try_into() - } -} - -impl<'a> TryFrom<&'a AMitem> for &'a AMactorId { - type Error = am::AutomergeError; - - fn try_from(item: &'a AMitem) -> Result { - item.as_ref().try_into() - } -} - -impl<'a> TryFrom<&'a mut AMitem> for &'a mut AMchange { - type Error = am::AutomergeError; - - fn try_from(item: &'a mut AMitem) -> Result { - if let Some(item) = Rc::get_mut(&mut item.0) { - item.try_into() - } else { - Err(Self::Error::Fail) - } - } -} - -impl<'a> TryFrom<&'a mut AMitem> for &'a mut AMdoc { - type Error = am::AutomergeError; - - fn try_from(item: &'a mut AMitem) -> Result { - if let Some(item) = Rc::get_mut(&mut item.0) { - item.try_into() - } else { - Err(Self::Error::Fail) - } - } -} - -impl<'a> TryFrom<&'a AMitem> for &'a AMsyncHave { - type Error = am::AutomergeError; - - fn try_from(item: &'a AMitem) -> Result { - item.as_ref().try_into() - } -} - -impl<'a> TryFrom<&'a AMitem> for &'a AMsyncMessage { - type Error = am::AutomergeError; - - fn try_from(item: &'a AMitem) -> Result { - item.as_ref().try_into() - } -} - -impl<'a> TryFrom<&'a mut AMitem> for &'a mut AMsyncState { - type Error = am::AutomergeError; - - fn try_from(item: &'a mut AMitem) -> Result { - if let Some(item) = Rc::get_mut(&mut item.0) { - item.try_into() - } else { - Err(Self::Error::Fail) - } - } -} - -impl TryFrom<&AMitem> for bool { - type Error = am::AutomergeError; - - fn try_from(item: &AMitem) -> Result { - item.as_ref().try_into() - } -} - -impl TryFrom<&AMitem> for f64 { - type Error = am::AutomergeError; - - fn try_from(item: &AMitem) -> Result { - item.as_ref().try_into() - } -} - -impl TryFrom<&AMitem> for u64 { - type Error = am::AutomergeError; - - fn try_from(item: &AMitem) -> Result { - item.as_ref().try_into() - } -} - -impl TryFrom<&AMitem> for AMunknownValue { - type Error = am::AutomergeError; - - fn try_from(item: &AMitem) -> Result { - item.as_ref().try_into() - } -} - -impl TryFrom<&AMitem> for (am::Value<'static>, am::ObjId) { - type Error = am::AutomergeError; - - fn try_from(item: &AMitem) -> Result { - item.as_ref().try_into() - } -} - -/// \ingroup enumerations -/// \enum AMvalType -/// \installed_headerfile -/// \brief The type of an item's value. -#[derive(PartialEq, Eq)] -#[repr(u32)] -pub enum AMvalType { - /// An actor identifier value. - ActorId = 1 << 1, - /// A boolean value. - Bool = 1 << 2, - /// A view onto an array of bytes value. - Bytes = 1 << 3, - /// A change value. - Change = 1 << 4, - /// A change hash value. - ChangeHash = 1 << 5, - /// A CRDT counter value. - Counter = 1 << 6, - /// The default tag, not a type signifier. - Default = 0, - /// A document value. - Doc = 1 << 7, - /// A 64-bit float value. - F64 = 1 << 8, - /// A 64-bit signed integer value. - Int = 1 << 9, - /// A null value. - Null = 1 << 10, - /// An object type value. - ObjType = 1 << 11, - /// A UTF-8 string view value. - Str = 1 << 12, - /// A synchronization have value. - SyncHave = 1 << 13, - /// A synchronization message value. - SyncMessage = 1 << 14, - /// A synchronization state value. - SyncState = 1 << 15, - /// A *nix timestamp (milliseconds) value. - Timestamp = 1 << 16, - /// A 64-bit unsigned integer value. - Uint = 1 << 17, - /// An unknown type of value. - Unknown = 1 << 18, - /// A void. - Void = 1 << 0, -} - -impl Default for AMvalType { - fn default() -> Self { - Self::Default - } -} - -impl From<&am::Value<'static>> for AMvalType { - fn from(value: &am::Value<'static>) -> Self { - use am::ScalarValue::*; - use am::Value::*; - - match value { - Object(_) => Self::ObjType, - Scalar(scalar) => match scalar.as_ref() { - Boolean(_) => Self::Bool, - Bytes(_) => Self::Bytes, - Counter(_) => Self::Counter, - F64(_) => Self::F64, - Int(_) => Self::Int, - Null => Self::Null, - Str(_) => Self::Str, - Timestamp(_) => Self::Timestamp, - Uint(_) => Self::Uint, - Unknown { .. } => Self::Unknown, - }, - } - } -} - -impl From<&Value> for AMvalType { - fn from(value: &Value) -> Self { - use self::Value::*; - - match value { - ActorId(_, _) => Self::ActorId, - Change(_, _) => Self::Change, - ChangeHash(_) => Self::ChangeHash, - Doc(_) => Self::Doc, - SyncHave(_) => Self::SyncHave, - SyncMessage(_) => Self::SyncMessage, - SyncState(_) => Self::SyncState, - Value(v) => v.into(), - } - } -} - -impl From<&Item> for AMvalType { - fn from(item: &Item) -> Self { - if let Some(value) = &item.value { - return value.into(); - } - Self::Void - } -} - -/// \memberof AMitem -/// \brief Tests the equality of two items. -/// -/// \param[in] item1 A pointer to an `AMitem` struct. -/// \param[in] item2 A pointer to an `AMitem` struct. -/// \return `true` if \p item1 `==` \p item2 and `false` otherwise. -/// \pre \p item1 `!= NULL` -/// \pre \p item2 `!= NULL` -/// \post `!(`\p item1 `&&` \p item2 `) -> false` -/// \internal -/// -/// #Safety -/// item1 must be a valid AMitem pointer -/// item2 must be a valid AMitem pointer -#[no_mangle] -pub unsafe extern "C" fn AMitemEqual(item1: *const AMitem, item2: *const AMitem) -> bool { - match (item1.as_ref(), item2.as_ref()) { - (Some(item1), Some(item2)) => *item1 == *item2, - (None, None) | (None, Some(_)) | (Some(_), None) => false, - } -} - -/// \memberof AMitem -/// \brief Allocates a new item and initializes it from a boolean value. -/// -/// \param[in] value A boolean. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BOOL` item. -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. -#[no_mangle] -pub unsafe extern "C" fn AMitemFromBool(value: bool) -> *mut AMresult { - AMresult::item(am::Value::from(value).into()).into() -} - -/// \memberof AMitem -/// \brief Allocates a new item and initializes it from an array of bytes value. -/// -/// \param[in] src A pointer to an array of bytes. -/// \param[in] count The count of bytes to copy from the array pointed to by -/// \p src. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTES` item. -/// \pre \p src `!= NULL` -/// \pre `sizeof(`\p src `) > 0` -/// \pre \p count `<= sizeof(`\p src `)` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. -/// \internal -/// -/// # Safety -/// value.src must be a byte array of length >= value.count -#[no_mangle] -pub unsafe extern "C" fn AMitemFromBytes(src: *const u8, count: usize) -> *mut AMresult { - let value = std::slice::from_raw_parts(src, count); - AMresult::item(am::Value::bytes(value.to_vec()).into()).into() -} - -/// \memberof AMitem -/// \brief Allocates a new item and initializes it from a change hash value. -/// -/// \param[in] value A change hash as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_CHANGE_HASH` item. -/// \pre \p value.src `!= NULL` -/// \pre `0 <` \p value.count `<= sizeof(`\p value.src `)` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. -/// \internal -/// -/// # Safety -/// value.src must be a byte array of length >= value.count -#[no_mangle] -pub unsafe extern "C" fn AMitemFromChangeHash(value: AMbyteSpan) -> *mut AMresult { - to_result(am::ChangeHash::try_from(&value)) -} - -/// \memberof AMitem -/// \brief Allocates a new item and initializes it from a CRDT counter value. -/// -/// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_COUNTER` item. -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. -#[no_mangle] -pub unsafe extern "C" fn AMitemFromCounter(value: i64) -> *mut AMresult { - AMresult::item(am::Value::counter(value).into()).into() -} - -/// \memberof AMitem -/// \brief Allocates a new item and initializes it from a float value. -/// -/// \param[in] value A 64-bit float. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_F64` item. -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. -#[no_mangle] -pub unsafe extern "C" fn AMitemFromF64(value: f64) -> *mut AMresult { - AMresult::item(am::Value::f64(value).into()).into() -} - -/// \memberof AMitem -/// \brief Allocates a new item and initializes it from a signed integer value. -/// -/// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_INT` item. -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. -#[no_mangle] -pub unsafe extern "C" fn AMitemFromInt(value: i64) -> *mut AMresult { - AMresult::item(am::Value::int(value).into()).into() -} - -/// \memberof AMitem -/// \brief Allocates a new item and initializes it from a null value. -/// -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_NULL` item. -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. -#[no_mangle] -pub unsafe extern "C" fn AMitemFromNull() -> *mut AMresult { - AMresult::item(am::Value::from(()).into()).into() -} - -/// \memberof AMitem -/// \brief Allocates a new item and initializes it from a UTF-8 string value. -/// -/// \param[in] value A UTF-8 string view as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_STR` item. -/// \pre \p value.src `!= NULL` -/// \pre `0 <` \p value.count `<= sizeof(`\p value.src `)` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. -/// \internal -/// -/// # Safety -/// value.src must be a byte array of length >= value.count -#[no_mangle] -pub unsafe extern "C" fn AMitemFromStr(value: AMbyteSpan) -> *mut AMresult { - AMresult::item(am::Value::str(to_str!(value)).into()).into() -} - -/// \memberof AMitem -/// \brief Allocates a new item and initializes it from a *nix timestamp -/// (milliseconds) value. -/// -/// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_TIMESTAMP` item. -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. -#[no_mangle] -pub unsafe extern "C" fn AMitemFromTimestamp(value: i64) -> *mut AMresult { - AMresult::item(am::Value::timestamp(value).into()).into() -} - -/// \memberof AMitem -/// \brief Allocates a new item and initializes it from an unsigned integer value. -/// -/// \param[in] value A 64-bit unsigned integer. -/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_UINT` item. -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. -#[no_mangle] -pub unsafe extern "C" fn AMitemFromUint(value: u64) -> *mut AMresult { - AMresult::item(am::Value::uint(value).into()).into() -} - -/// \memberof AMitem -/// \brief Gets the type of an item's index. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \return An `AMidxType` enum tag. -/// \pre \p item `!= NULL` -/// \post `(`\p item `== NULL) -> 0` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemIdxType(item: *const AMitem) -> AMidxType { - if let Some(item) = item.as_ref() { - return item.0.as_ref().into(); - } - Default::default() -} - -/// \memberof AMitem -/// \brief Gets the object identifier of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \return A pointer to an `AMobjId` struct. -/// \pre \p item `!= NULL` -/// \post `(`\p item `== NULL) -> NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemObjId(item: *const AMitem) -> *const AMobjId { - if let Some(item) = item.as_ref() { - if let Some(obj_id) = &item.as_ref().obj_id { - return obj_id; - } - } - std::ptr::null() -} - -/// \memberof AMitem -/// \brief Gets the UTF-8 string view key index of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to a UTF-8 string view as an `AMbyteSpan` struct. -/// \return `true` if `AMitemIdxType(`\p item `) == AM_IDX_TYPE_KEY` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemKey(item: *const AMitem, value: *mut AMbyteSpan) -> bool { - if let Some(item) = item.as_ref() { - if let Some(index) = &item.as_ref().index { - if let Ok(key) = index.try_into() { - if !value.is_null() { - *value = key; - return true; - } - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the unsigned integer position index of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to a `size_t`. -/// \return `true` if `AMitemIdxType(`\p item `) == AM_IDX_TYPE_POS` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemPos(item: *const AMitem, value: *mut usize) -> bool { - if let Some(item) = item.as_ref() { - if let Some(index) = &item.as_ref().index { - if let Ok(pos) = index.try_into() { - if !value.is_null() { - *value = pos; - return true; - } - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the reference count of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \return A 64-bit unsigned integer. -/// \pre \p item `!= NULL` -/// \post `(`\p item `== NULL) -> 0` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemRefCount(item: *const AMitem) -> usize { - if let Some(item) = item.as_ref() { - return Rc::strong_count(&item.0); - } - 0 -} - -/// \memberof AMitem -/// \brief Gets a new result for an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \return A pointer to an `AMresult` struct. -/// \pre \p item `!= NULL` -/// \post `(`\p item `== NULL) -> NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemResult(item: *const AMitem) -> *mut AMresult { - if let Some(item) = item.as_ref() { - return AMresult::item(item.clone()).into(); - } - std::ptr::null_mut() -} - -/// \memberof AMitem -/// \brief Gets the actor identifier value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to an `AMactorId` struct pointer. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_ACTOR_ID` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToActorId( - item: *const AMitem, - value: *mut *const AMactorId, -) -> bool { - if let Some(item) = item.as_ref() { - if let Ok(actor_id) = <&AMactorId>::try_from(item) { - if !value.is_null() { - *value = actor_id; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the boolean value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to a boolean. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_BOOL` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToBool(item: *const AMitem, value: *mut bool) -> bool { - if let Some(item) = item.as_ref() { - if let Ok(boolean) = item.try_into() { - if !value.is_null() { - *value = boolean; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the array of bytes value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to an `AMbyteSpan` struct. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_BYTES` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToBytes(item: *const AMitem, value: *mut AMbyteSpan) -> bool { - if let Some(item) = item.as_ref() { - if let Ok(bytes) = item.as_ref().try_into_bytes() { - if !value.is_null() { - *value = bytes; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the change value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to an `AMchange` struct pointer. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_CHANGE` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToChange(item: *mut AMitem, value: *mut *mut AMchange) -> bool { - if let Some(item) = item.as_mut() { - if let Ok(change) = <&mut AMchange>::try_from(item) { - if !value.is_null() { - *value = change; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the change hash value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to an `AMbyteSpan` struct. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_CHANGE_HASH` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToChangeHash(item: *const AMitem, value: *mut AMbyteSpan) -> bool { - if let Some(item) = item.as_ref() { - if let Ok(change_hash) = item.as_ref().try_into_change_hash() { - if !value.is_null() { - *value = change_hash; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the CRDT counter value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to a signed 64-bit integer. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_COUNTER` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToCounter(item: *const AMitem, value: *mut i64) -> bool { - if let Some(item) = item.as_ref() { - if let Ok(counter) = item.as_ref().try_into_counter() { - if !value.is_null() { - *value = counter; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the document value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to an `AMdoc` struct pointer. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_DOC` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToDoc(item: *mut AMitem, value: *mut *const AMdoc) -> bool { - if let Some(item) = item.as_mut() { - if let Ok(doc) = <&mut AMdoc>::try_from(item) { - if !value.is_null() { - *value = doc; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the float value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to a 64-bit float. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_F64` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToF64(item: *const AMitem, value: *mut f64) -> bool { - if let Some(item) = item.as_ref() { - if let Ok(float) = item.try_into() { - if !value.is_null() { - *value = float; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the integer value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to a signed 64-bit integer. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_INT` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToInt(item: *const AMitem, value: *mut i64) -> bool { - if let Some(item) = item.as_ref() { - if let Ok(int) = item.as_ref().try_into_int() { - if !value.is_null() { - *value = int; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the UTF-8 string view value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to a UTF-8 string view as an `AMbyteSpan` struct. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_STR` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToStr(item: *const AMitem, value: *mut AMbyteSpan) -> bool { - if let Some(item) = item.as_ref() { - if let Ok(str) = item.as_ref().try_into_str() { - if !value.is_null() { - *value = str; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the synchronization have value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to an `AMsyncHave` struct pointer. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_SYNC_HAVE` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToSyncHave( - item: *const AMitem, - value: *mut *const AMsyncHave, -) -> bool { - if let Some(item) = item.as_ref() { - if let Ok(sync_have) = <&AMsyncHave>::try_from(item) { - if !value.is_null() { - *value = sync_have; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the synchronization message value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to an `AMsyncMessage` struct pointer. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_SYNC_MESSAGE` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToSyncMessage( - item: *const AMitem, - value: *mut *const AMsyncMessage, -) -> bool { - if let Some(item) = item.as_ref() { - if let Ok(sync_message) = <&AMsyncMessage>::try_from(item) { - if !value.is_null() { - *value = sync_message; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the synchronization state value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to an `AMsyncState` struct pointer. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_SYNC_STATE` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToSyncState( - item: *mut AMitem, - value: *mut *mut AMsyncState, -) -> bool { - if let Some(item) = item.as_mut() { - if let Ok(sync_state) = <&mut AMsyncState>::try_from(item) { - if !value.is_null() { - *value = sync_state; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the *nix timestamp (milliseconds) value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to a signed 64-bit integer. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_TIMESTAMP` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToTimestamp(item: *const AMitem, value: *mut i64) -> bool { - if let Some(item) = item.as_ref() { - if let Ok(timestamp) = item.as_ref().try_into_timestamp() { - if !value.is_null() { - *value = timestamp; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the unsigned integer value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to a unsigned 64-bit integer. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_UINT` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToUint(item: *const AMitem, value: *mut u64) -> bool { - if let Some(item) = item.as_ref() { - if let Ok(uint) = item.try_into() { - if !value.is_null() { - *value = uint; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the unknown type of value of an item. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \param[out] value A pointer to an `AMunknownValue` struct. -/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_UNKNOWN` and -/// \p *value has been reassigned, `false` otherwise. -/// \pre \p item `!= NULL` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemToUnknown(item: *const AMitem, value: *mut AMunknownValue) -> bool { - if let Some(item) = item.as_ref() { - if let Ok(unknown) = item.try_into() { - if !value.is_null() { - *value = unknown; - return true; - } - } - } - false -} - -/// \memberof AMitem -/// \brief Gets the type of an item's value. -/// -/// \param[in] item A pointer to an `AMitem` struct. -/// \return An `AMvalType` enum tag. -/// \pre \p item `!= NULL` -/// \post `(`\p item `== NULL) -> 0` -/// \internal -/// -/// # Safety -/// item must be a valid pointer to an AMitem -#[no_mangle] -pub unsafe extern "C" fn AMitemValType(item: *const AMitem) -> AMvalType { - if let Some(item) = item.as_ref() { - return item.0.as_ref().into(); - } - Default::default() -} diff --git a/rust/automerge-c/src/items.rs b/rust/automerge-c/src/items.rs deleted file mode 100644 index 361078b3..00000000 --- a/rust/automerge-c/src/items.rs +++ /dev/null @@ -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()` call -/// (https://github.com/eqrion/cbindgen/issues/252) but it will -/// propagate the name of a constant initialized from it so if the -/// constant's name is a symbolic representation of the value it can be -/// converted into a number by post-processing the header it generated. -pub const USIZE_USIZE_USIZE_: usize = size_of::(); - -impl Detail { - fn new(items: &[AMitem], offset: isize) -> Self { - Self { - len: items.len(), - offset, - ptr: items.as_ptr() as *mut c_void, - } - } - - pub fn advance(&mut self, n: isize) { - if n == 0 { - return; - } - let len = self.len as isize; - self.offset = if self.offset < 0 { - // It's reversed. - let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); - if unclipped >= 0 { - // Clip it to the forward stop. - len - } else { - std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) - } - } else { - let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); - if unclipped < 0 { - // Clip it to the reverse stop. - -(len + 1) - } else { - std::cmp::max(0, std::cmp::min(unclipped, len)) - } - } - } - - pub fn get_index(&self) -> usize { - (self.offset - + if self.offset < 0 { - self.len as isize - } else { - 0 - }) as usize - } - - pub fn next(&mut self, n: isize) -> Option<&mut AMitem> { - if self.is_stopped() { - return None; - } - let slice: &mut [AMitem] = - unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut AMitem, self.len) }; - let value = &mut slice[self.get_index()]; - self.advance(n); - Some(value) - } - - pub fn is_stopped(&self) -> bool { - let len = self.len as isize; - self.offset < -len || self.offset == len - } - - pub fn prev(&mut self, n: isize) -> Option<&mut AMitem> { - self.advance(-n); - if self.is_stopped() { - return None; - } - let slice: &mut [AMitem] = - unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut AMitem, self.len) }; - Some(&mut slice[self.get_index()]) - } - - pub fn reversed(&self) -> Self { - Self { - len: self.len, - offset: -(self.offset + 1), - ptr: self.ptr, - } - } - - pub fn rewound(&self) -> Self { - Self { - len: self.len, - offset: if self.offset < 0 { -1 } else { 0 }, - ptr: self.ptr, - } - } -} - -impl From for [u8; USIZE_USIZE_USIZE_] { - fn from(detail: Detail) -> Self { - unsafe { - std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) - .try_into() - .unwrap() - } - } -} - -/// \struct AMitems -/// \installed_headerfile -/// \brief A random-access iterator over a sequence of `AMitem` structs. -#[repr(C)] -#[derive(Eq, PartialEq)] -pub struct AMitems<'a> { - /// An implementation detail that is intentionally opaque. - /// \warning Modifying \p detail will cause undefined behavior. - /// \note The actual size of \p detail will vary by platform, this is just - /// the one for the platform this documentation was built on. - detail: [u8; USIZE_USIZE_USIZE_], - phantom: PhantomData<&'a mut AMresult>, -} - -impl<'a> AMitems<'a> { - pub fn new(items: &[AMitem]) -> Self { - Self { - detail: Detail::new(items, 0).into(), - phantom: PhantomData, - } - } - - pub fn advance(&mut self, n: isize) { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.advance(n); - } - - pub fn len(&self) -> usize { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - detail.len - } - - pub fn next(&mut self, n: isize) -> Option<&mut AMitem> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.next(n) - } - - pub fn prev(&mut self, n: isize) -> Option<&mut AMitem> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.prev(n) - } - - pub fn reversed(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.reversed().into(), - phantom: PhantomData, - } - } - - pub fn rewound(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.rewound().into(), - phantom: PhantomData, - } - } -} - -impl<'a> AsRef<[AMitem]> for AMitems<'a> { - fn as_ref(&self) -> &[AMitem] { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - unsafe { std::slice::from_raw_parts(detail.ptr as *const AMitem, detail.len) } - } -} - -impl<'a> Default for AMitems<'a> { - fn default() -> Self { - Self { - detail: [0; USIZE_USIZE_USIZE_], - phantom: PhantomData, - } - } -} - -impl TryFrom<&AMitems<'_>> for Vec { - type Error = am::AutomergeError; - - fn try_from(items: &AMitems<'_>) -> Result { - let mut changes = Vec::::with_capacity(items.len()); - for item in items.as_ref().iter() { - match <&am::Change>::try_from(item.as_ref()) { - Ok(change) => { - changes.push(change.clone()); - } - Err(e) => { - return Err(e); - } - } - } - Ok(changes) - } -} - -impl TryFrom<&AMitems<'_>> for Vec { - type Error = am::AutomergeError; - - fn try_from(items: &AMitems<'_>) -> Result { - let mut change_hashes = Vec::::with_capacity(items.len()); - for item in items.as_ref().iter() { - match <&am::ChangeHash>::try_from(item.as_ref()) { - Ok(change_hash) => { - change_hashes.push(*change_hash); - } - Err(e) => { - return Err(e); - } - } - } - Ok(change_hashes) - } -} - -impl TryFrom<&AMitems<'_>> for Vec { - type Error = am::AutomergeError; - - fn try_from(items: &AMitems<'_>) -> Result { - let mut scalars = Vec::::with_capacity(items.len()); - for item in items.as_ref().iter() { - match <&am::ScalarValue>::try_from(item.as_ref()) { - Ok(scalar) => { - scalars.push(scalar.clone()); - } - Err(e) => { - return Err(e); - } - } - } - Ok(scalars) - } -} - -/// \memberof AMitems -/// \brief Advances an iterator over a sequence of object items by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in] items A pointer to an `AMitems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \pre \p items `!= NULL` -/// \internal -/// -/// #Safety -/// items must be a valid pointer to an AMitems -#[no_mangle] -pub unsafe extern "C" fn AMitemsAdvance(items: *mut AMitems, n: isize) { - if let Some(items) = items.as_mut() { - items.advance(n); - }; -} - -/// \memberof AMitems -/// \brief Tests the equality of two sequences of object items underlying a -/// pair of iterators. -/// -/// \param[in] items1 A pointer to an `AMitems` struct. -/// \param[in] items2 A pointer to an `AMitems` struct. -/// \return `true` if \p items1 `==` \p items2 and `false` otherwise. -/// \pre \p items1 `!= NULL` -/// \pre \p items1 `!= NULL` -/// \post `!(`\p items1 `&&` \p items2 `) -> false` -/// \internal -/// -/// #Safety -/// items1 must be a valid pointer to an AMitems -/// items2 must be a valid pointer to an AMitems -#[no_mangle] -pub unsafe extern "C" fn AMitemsEqual(items1: *const AMitems, items2: *const AMitems) -> bool { - match (items1.as_ref(), items2.as_ref()) { - (Some(items1), Some(items2)) => items1.as_ref() == items2.as_ref(), - (None, None) | (None, Some(_)) | (Some(_), None) => false, - } -} - -/// \memberof AMitems -/// \brief Gets the object item at the current position of an iterator over a -/// sequence of object items and then advances it by at most \p |n| -/// positions where the sign of \p n is relative to the iterator's -/// direction. -/// -/// \param[in] items A pointer to an `AMitems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMitem` struct that's `NULL` when \p items -/// was previously advanced past its forward/reverse limit. -/// \pre \p items `!= NULL` -/// \internal -/// -/// #Safety -/// items must be a valid pointer to an AMitems -#[no_mangle] -pub unsafe extern "C" fn AMitemsNext(items: *mut AMitems, n: isize) -> *mut AMitem { - if let Some(items) = items.as_mut() { - if let Some(item) = items.next(n) { - return item; - } - } - std::ptr::null_mut() -} - -/// \memberof AMitems -/// \brief Advances an iterator over a sequence of object items by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction and then gets the object item at its new -/// position. -/// -/// \param[in] items A pointer to an `AMitems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMitem` struct that's `NULL` when \p items -/// is presently advanced past its forward/reverse limit. -/// \pre \p items `!= NULL` -/// \internal -/// -/// #Safety -/// items must be a valid pointer to an AMitems -#[no_mangle] -pub unsafe extern "C" fn AMitemsPrev(items: *mut AMitems, n: isize) -> *mut AMitem { - if let Some(items) = items.as_mut() { - if let Some(obj_item) = items.prev(n) { - return obj_item; - } - } - std::ptr::null_mut() -} - -/// \memberof AMitems -/// \brief Gets the size of the sequence underlying an iterator. -/// -/// \param[in] items A pointer to an `AMitems` struct. -/// \return The count of items in \p items. -/// \pre \p items `!= NULL` -/// \internal -/// -/// #Safety -/// items must be a valid pointer to an AMitems -#[no_mangle] -pub unsafe extern "C" fn AMitemsSize(items: *const AMitems) -> usize { - if let Some(items) = items.as_ref() { - return items.len(); - } - 0 -} - -/// \memberof AMitems -/// \brief Creates an iterator over the same sequence of items as the -/// given one but with the opposite position and direction. -/// -/// \param[in] items A pointer to an `AMitems` struct. -/// \return An `AMitems` struct -/// \pre \p items `!= NULL` -/// \internal -/// -/// #Safety -/// items must be a valid pointer to an AMitems -#[no_mangle] -pub unsafe extern "C" fn AMitemsReversed(items: *const AMitems) -> AMitems { - if let Some(items) = items.as_ref() { - return items.reversed(); - } - Default::default() -} - -/// \memberof AMitems -/// \brief Creates an iterator at the starting position over the same sequence -/// of items as the given one. -/// -/// \param[in] items A pointer to an `AMitems` struct. -/// \return An `AMitems` struct -/// \pre \p items `!= NULL` -/// \internal -/// -/// #Safety -/// items must be a valid pointer to an AMitems -#[no_mangle] -pub unsafe extern "C" fn AMitemsRewound(items: *const AMitems) -> AMitems { - if let Some(items) = items.as_ref() { - return items.rewound(); - } - Default::default() -} diff --git a/rust/automerge-c/src/lib.rs b/rust/automerge-c/src/lib.rs index 1ee1a85d..6418bd33 100644 --- a/rust/automerge-c/src/lib.rs +++ b/rust/automerge-c/src/lib.rs @@ -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")); diff --git a/rust/automerge-c/src/obj.rs b/rust/automerge-c/src/obj.rs index 3d52286c..46ff617b 100644 --- a/rust/automerge-c/src/obj.rs +++ b/rust/automerge-c/src/obj.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 = (&$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 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 { - use am::AutomergeError::InvalidValueType; - use AMobjType::*; - - match c_obj_type { - List => Ok(Self::List), - Map => Ok(Self::Map), - Text => Ok(Self::Text), - _ => Err(InvalidValueType { - expected: type_name::().to_string(), - unexpected: type_name::().to_string(), - }), + am::ObjType::Map | am::ObjType::Table => AMobjType::Map, + am::ObjType::List => AMobjType::List, + am::ObjType::Text => AMobjType::Text, } } } diff --git a/rust/automerge-c/src/obj/item.rs b/rust/automerge-c/src/obj/item.rs new file mode 100644 index 00000000..a2e99d06 --- /dev/null +++ b/rust/automerge-c/src/obj/item.rs @@ -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 + } +} diff --git a/rust/automerge-c/src/obj/items.rs b/rust/automerge-c/src/obj/items.rs new file mode 100644 index 00000000..d6b847cf --- /dev/null +++ b/rust/automerge-c/src/obj/items.rs @@ -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()` call +/// (https://github.com/eqrion/cbindgen/issues/252) but it will +/// propagate the name of a constant initialized from it so if the +/// constant's name is a symbolic representation of the value it can be +/// converted into a number by post-processing the header it generated. +pub const USIZE_USIZE_USIZE_: usize = size_of::(); + +impl Detail { + fn new(obj_items: &[AMobjItem], offset: isize) -> Self { + Self { + len: obj_items.len(), + offset, + ptr: obj_items.as_ptr() as *const c_void, + } + } + + pub fn advance(&mut self, n: isize) { + if n == 0 { + return; + } + let len = self.len as isize; + self.offset = if self.offset < 0 { + // It's reversed. + let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); + if unclipped >= 0 { + // Clip it to the forward stop. + len + } else { + std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) + } + } else { + let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); + if unclipped < 0 { + // Clip it to the reverse stop. + -(len + 1) + } else { + std::cmp::max(0, std::cmp::min(unclipped, len)) + } + } + } + + pub fn get_index(&self) -> usize { + (self.offset + + if self.offset < 0 { + self.len as isize + } else { + 0 + }) as usize + } + + pub fn next(&mut self, n: isize) -> Option<&AMobjItem> { + if self.is_stopped() { + return None; + } + let slice: &[AMobjItem] = + unsafe { std::slice::from_raw_parts(self.ptr as *const AMobjItem, self.len) }; + let value = &slice[self.get_index()]; + self.advance(n); + Some(value) + } + + pub fn is_stopped(&self) -> bool { + let len = self.len as isize; + self.offset < -len || self.offset == len + } + + pub fn prev(&mut self, n: isize) -> Option<&AMobjItem> { + self.advance(-n); + if self.is_stopped() { + return None; + } + let slice: &[AMobjItem] = + unsafe { std::slice::from_raw_parts(self.ptr as *const AMobjItem, self.len) }; + Some(&slice[self.get_index()]) + } + + pub fn reversed(&self) -> Self { + Self { + len: self.len, + offset: -(self.offset + 1), + ptr: self.ptr, + } + } + + pub fn rewound(&self) -> Self { + Self { + len: self.len, + offset: if self.offset < 0 { -1 } else { 0 }, + ptr: self.ptr, + } + } +} + +impl From for [u8; USIZE_USIZE_USIZE_] { + fn from(detail: Detail) -> Self { + unsafe { + std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) + .try_into() + .unwrap() + } + } +} + +/// \struct AMobjItems +/// \installed_headerfile +/// \brief A random-access iterator over a sequence of object items. +#[repr(C)] +#[derive(Eq, PartialEq)] +pub struct AMobjItems { + /// An implementation detail that is intentionally opaque. + /// \warning Modifying \p detail will cause undefined behavior. + /// \note The actual size of \p detail will vary by platform, this is just + /// the one for the platform this documentation was built on. + detail: [u8; USIZE_USIZE_USIZE_], +} + +impl AMobjItems { + pub fn new(obj_items: &[AMobjItem]) -> Self { + Self { + detail: Detail::new(obj_items, 0).into(), + } + } + + pub fn advance(&mut self, n: isize) { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.advance(n); + } + + pub fn len(&self) -> usize { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + detail.len + } + + pub fn next(&mut self, n: isize) -> Option<&AMobjItem> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.next(n) + } + + pub fn prev(&mut self, n: isize) -> Option<&AMobjItem> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.prev(n) + } + + pub fn reversed(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.reversed().into(), + } + } + + pub fn rewound(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.rewound().into(), + } + } +} + +impl AsRef<[AMobjItem]> for AMobjItems { + fn as_ref(&self) -> &[AMobjItem] { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + unsafe { std::slice::from_raw_parts(detail.ptr as *const AMobjItem, detail.len) } + } +} + +impl Default for AMobjItems { + fn default() -> Self { + Self { + detail: [0; USIZE_USIZE_USIZE_], + } + } +} + +/// \memberof AMobjItems +/// \brief Advances an iterator over a sequence of object items by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction. +/// +/// \param[in,out] obj_items A pointer to an `AMobjItems` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \pre \p obj_items `!= NULL`. +/// \internal +/// +/// #Safety +/// obj_items must be a valid pointer to an AMobjItems +#[no_mangle] +pub unsafe extern "C" fn AMobjItemsAdvance(obj_items: *mut AMobjItems, n: isize) { + if let Some(obj_items) = obj_items.as_mut() { + obj_items.advance(n); + }; +} + +/// \memberof AMobjItems +/// \brief Tests the equality of two sequences of object items underlying a +/// pair of iterators. +/// +/// \param[in] obj_items1 A pointer to an `AMobjItems` struct. +/// \param[in] obj_items2 A pointer to an `AMobjItems` struct. +/// \return `true` if \p obj_items1 `==` \p obj_items2 and `false` otherwise. +/// \pre \p obj_items1 `!= NULL`. +/// \pre \p obj_items2 `!= NULL`. +/// \internal +/// +/// #Safety +/// obj_items1 must be a valid pointer to an AMobjItems +/// obj_items2 must be a valid pointer to an AMobjItems +#[no_mangle] +pub unsafe extern "C" fn AMobjItemsEqual( + obj_items1: *const AMobjItems, + obj_items2: *const AMobjItems, +) -> bool { + match (obj_items1.as_ref(), obj_items2.as_ref()) { + (Some(obj_items1), Some(obj_items2)) => obj_items1.as_ref() == obj_items2.as_ref(), + (None, Some(_)) | (Some(_), None) | (None, None) => false, + } +} + +/// \memberof AMobjItems +/// \brief Gets the object item at the current position of an iterator over a +/// sequence of object items and then advances it by at most \p |n| +/// positions where the sign of \p n is relative to the iterator's +/// direction. +/// +/// \param[in,out] obj_items A pointer to an `AMobjItems` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A pointer to an `AMobjItem` struct that's `NULL` when \p obj_items +/// was previously advanced past its forward/reverse limit. +/// \pre \p obj_items `!= NULL`. +/// \internal +/// +/// #Safety +/// obj_items must be a valid pointer to an AMobjItems +#[no_mangle] +pub unsafe extern "C" fn AMobjItemsNext(obj_items: *mut AMobjItems, n: isize) -> *const AMobjItem { + if let Some(obj_items) = obj_items.as_mut() { + if let Some(obj_item) = obj_items.next(n) { + return obj_item; + } + } + std::ptr::null() +} + +/// \memberof AMobjItems +/// \brief Advances an iterator over a sequence of object items by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction and then gets the object item at its new +/// position. +/// +/// \param[in,out] obj_items A pointer to an `AMobjItems` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A pointer to an `AMobjItem` struct that's `NULL` when \p obj_items +/// is presently advanced past its forward/reverse limit. +/// \pre \p obj_items `!= NULL`. +/// \internal +/// +/// #Safety +/// obj_items must be a valid pointer to an AMobjItems +#[no_mangle] +pub unsafe extern "C" fn AMobjItemsPrev(obj_items: *mut AMobjItems, n: isize) -> *const AMobjItem { + if let Some(obj_items) = obj_items.as_mut() { + if let Some(obj_item) = obj_items.prev(n) { + return obj_item; + } + } + std::ptr::null() +} + +/// \memberof AMobjItems +/// \brief Gets the size of the sequence of object items underlying an +/// iterator. +/// +/// \param[in] obj_items A pointer to an `AMobjItems` struct. +/// \return The count of values in \p obj_items. +/// \pre \p obj_items `!= NULL`. +/// \internal +/// +/// #Safety +/// obj_items must be a valid pointer to an AMobjItems +#[no_mangle] +pub unsafe extern "C" fn AMobjItemsSize(obj_items: *const AMobjItems) -> usize { + if let Some(obj_items) = obj_items.as_ref() { + obj_items.len() + } else { + 0 + } +} + +/// \memberof AMobjItems +/// \brief Creates an iterator over the same sequence of object items as the +/// given one but with the opposite position and direction. +/// +/// \param[in] obj_items A pointer to an `AMobjItems` struct. +/// \return An `AMobjItems` struct +/// \pre \p obj_items `!= NULL`. +/// \internal +/// +/// #Safety +/// obj_items must be a valid pointer to an AMobjItems +#[no_mangle] +pub unsafe extern "C" fn AMobjItemsReversed(obj_items: *const AMobjItems) -> AMobjItems { + if let Some(obj_items) = obj_items.as_ref() { + obj_items.reversed() + } else { + Default::default() + } +} + +/// \memberof AMobjItems +/// \brief Creates an iterator at the starting position over the same sequence +/// of object items as the given one. +/// +/// \param[in] obj_items A pointer to an `AMobjItems` struct. +/// \return An `AMobjItems` struct +/// \pre \p obj_items `!= NULL`. +/// \internal +/// +/// #Safety +/// obj_items must be a valid pointer to an AMobjItems +#[no_mangle] +pub unsafe extern "C" fn AMobjItemsRewound(obj_items: *const AMobjItems) -> AMobjItems { + if let Some(obj_items) = obj_items.as_ref() { + obj_items.rewound() + } else { + Default::default() + } +} diff --git a/rust/automerge-c/src/result.rs b/rust/automerge-c/src/result.rs index 2975f38b..599ada96 100644 --- a/rust/automerge-c/src/result.rs +++ b/rust/automerge-c/src/result.rs @@ -1,85 +1,513 @@ use automerge as am; +use smol_str::SmolStr; +use std::any::type_name; +use std::collections::BTreeMap; use std::ops::{Range, RangeFrom, RangeFull, RangeTo}; +use crate::actor_id::AMactorId; use crate::byte_span::AMbyteSpan; -use crate::index::AMindex; -use crate::item::AMitem; -use crate::items::AMitems; +use crate::change::AMchange; +use crate::change_hashes::AMchangeHashes; +use crate::changes::AMchanges; +use crate::doc::list::{item::AMlistItem, items::AMlistItems}; +use crate::doc::map::{item::AMmapItem, items::AMmapItems}; +use crate::doc::AMdoc; +use crate::obj::item::AMobjItem; +use crate::obj::items::AMobjItems; +use crate::obj::AMobjId; +use crate::strs::AMstrs; +use crate::sync::{AMsyncMessage, AMsyncState}; + +/// \struct AMvalue +/// \installed_headerfile +/// \brief A discriminated union of value type variants for a result. +/// +/// \enum AMvalueVariant +/// \brief A value type discriminant. +/// +/// \var AMvalue::actor_id +/// An actor identifier as a pointer to an `AMactorId` struct. +/// +/// \var AMvalue::boolean +/// A boolean. +/// +/// \var AMvalue::bytes +/// A sequence of bytes as an `AMbyteSpan` struct. +/// +/// \var AMvalue::change_hashes +/// A sequence of change hashes as an `AMchangeHashes` struct. +/// +/// \var AMvalue::changes +/// A sequence of changes as an `AMchanges` struct. +/// +/// \var AMvalue::counter +/// A CRDT counter. +/// +/// \var AMvalue::doc +/// A document as a pointer to an `AMdoc` struct. +/// +/// \var AMvalue::f64 +/// A 64-bit float. +/// +/// \var AMvalue::int_ +/// A 64-bit signed integer. +/// +/// \var AMvalue::list_items +/// A sequence of list object items as an `AMlistItems` struct. +/// +/// \var AMvalue::map_items +/// A sequence of map object items as an `AMmapItems` struct. +/// +/// \var AMvalue::obj_id +/// An object identifier as a pointer to an `AMobjId` struct. +/// +/// \var AMvalue::obj_items +/// A sequence of object items as an `AMobjItems` struct. +/// +/// \var AMvalue::str +/// A UTF-8 string view as an `AMbyteSpan` struct. +/// +/// \var AMvalue::strs +/// A sequence of UTF-8 strings as an `AMstrs` struct. +/// +/// \var AMvalue::sync_message +/// A synchronization message as a pointer to an `AMsyncMessage` struct. +/// +/// \var AMvalue::sync_state +/// A synchronization state as a pointer to an `AMsyncState` struct. +/// +/// \var AMvalue::tag +/// The variant discriminator. +/// +/// \var AMvalue::timestamp +/// A *nix timestamp (milliseconds). +/// +/// \var AMvalue::uint +/// A 64-bit unsigned integer. +/// +/// \var AMvalue::unknown +/// A value of unknown type as an `AMunknownValue` struct. +#[repr(u8)] +pub enum AMvalue<'a> { + /// A void variant. + /// \note This tag is unalphabetized so that a zeroed struct will have it. + Void, + /// An actor identifier variant. + ActorId(&'a AMactorId), + /// A boolean variant. + Boolean(bool), + /// A byte array variant. + Bytes(AMbyteSpan), + /// A change hashes variant. + ChangeHashes(AMchangeHashes), + /// A changes variant. + Changes(AMchanges), + /// A CRDT counter variant. + Counter(i64), + /// A document variant. + Doc(*mut AMdoc), + /// A 64-bit float variant. + F64(f64), + /// A 64-bit signed integer variant. + Int(i64), + /// A list items variant. + ListItems(AMlistItems), + /// A map items variant. + MapItems(AMmapItems), + /// A null variant. + Null, + /// An object identifier variant. + ObjId(&'a AMobjId), + /// An object items variant. + ObjItems(AMobjItems), + /// A UTF-8 string view variant. + Str(AMbyteSpan), + /// A UTF-8 string views variant. + Strs(AMstrs), + /// A synchronization message variant. + SyncMessage(&'a AMsyncMessage), + /// A synchronization state variant. + SyncState(&'a mut AMsyncState), + /// A *nix timestamp (milliseconds) variant. + Timestamp(i64), + /// A 64-bit unsigned integer variant. + Uint(u64), + /// An unknown type of scalar value variant. + Unknown(AMunknownValue), +} + +impl<'a> PartialEq for AMvalue<'a> { + fn eq(&self, other: &Self) -> bool { + use AMvalue::*; + + match (self, other) { + (ActorId(lhs), ActorId(rhs)) => *lhs == *rhs, + (Boolean(lhs), Boolean(rhs)) => lhs == rhs, + (Bytes(lhs), Bytes(rhs)) => lhs == rhs, + (ChangeHashes(lhs), ChangeHashes(rhs)) => lhs == rhs, + (Changes(lhs), Changes(rhs)) => lhs == rhs, + (Counter(lhs), Counter(rhs)) => lhs == rhs, + (Doc(lhs), Doc(rhs)) => *lhs == *rhs, + (F64(lhs), F64(rhs)) => lhs == rhs, + (Int(lhs), Int(rhs)) => lhs == rhs, + (ListItems(lhs), ListItems(rhs)) => lhs == rhs, + (MapItems(lhs), MapItems(rhs)) => lhs == rhs, + (ObjId(lhs), ObjId(rhs)) => *lhs == *rhs, + (ObjItems(lhs), ObjItems(rhs)) => lhs == rhs, + (Str(lhs), Str(rhs)) => lhs == rhs, + (Strs(lhs), Strs(rhs)) => lhs == rhs, + (SyncMessage(lhs), SyncMessage(rhs)) => *lhs == *rhs, + (SyncState(lhs), SyncState(rhs)) => *lhs == *rhs, + (Timestamp(lhs), Timestamp(rhs)) => lhs == rhs, + (Uint(lhs), Uint(rhs)) => lhs == rhs, + (Unknown(lhs), Unknown(rhs)) => lhs == rhs, + (Null, Null) | (Void, Void) => true, + _ => false, + } + } +} + +impl From<&am::Value<'_>> for AMvalue<'_> { + fn from(value: &am::Value<'_>) -> Self { + match value { + am::Value::Scalar(scalar) => match scalar.as_ref() { + am::ScalarValue::Boolean(flag) => AMvalue::Boolean(*flag), + am::ScalarValue::Bytes(bytes) => AMvalue::Bytes(bytes.as_slice().into()), + am::ScalarValue::Counter(counter) => AMvalue::Counter(counter.into()), + am::ScalarValue::F64(float) => AMvalue::F64(*float), + am::ScalarValue::Int(int) => AMvalue::Int(*int), + am::ScalarValue::Null => AMvalue::Null, + am::ScalarValue::Str(smol_str) => AMvalue::Str(smol_str.as_bytes().into()), + am::ScalarValue::Timestamp(timestamp) => AMvalue::Timestamp(*timestamp), + am::ScalarValue::Uint(uint) => AMvalue::Uint(*uint), + am::ScalarValue::Unknown { bytes, type_code } => AMvalue::Unknown(AMunknownValue { + bytes: bytes.as_slice().into(), + type_code: *type_code, + }), + }, + // \todo Confirm that an object variant should be ignored + // when there's no object ID variant. + am::Value::Object(_) => AMvalue::Void, + } + } +} + +impl From<&AMvalue<'_>> for u8 { + fn from(value: &AMvalue) -> Self { + use AMvalue::*; + + // \warning These numbers must correspond to the order in which the + // variants of an AMvalue are declared within it. + match value { + ActorId(_) => 1, + Boolean(_) => 2, + Bytes(_) => 3, + ChangeHashes(_) => 4, + Changes(_) => 5, + Counter(_) => 6, + Doc(_) => 7, + F64(_) => 8, + Int(_) => 9, + ListItems(_) => 10, + MapItems(_) => 11, + Null => 12, + ObjId(_) => 13, + ObjItems(_) => 14, + Str(_) => 15, + Strs(_) => 16, + SyncMessage(_) => 17, + SyncState(_) => 18, + Timestamp(_) => 19, + Uint(_) => 20, + Unknown(..) => 21, + Void => 0, + } + } +} + +impl TryFrom<&AMvalue<'_>> for am::ScalarValue { + type Error = am::AutomergeError; + + fn try_from(c_value: &AMvalue) -> Result { + use am::AutomergeError::InvalidValueType; + use AMvalue::*; + + let expected = type_name::().to_string(); + match c_value { + Boolean(b) => Ok(am::ScalarValue::Boolean(*b)), + Bytes(span) => { + let slice = unsafe { std::slice::from_raw_parts(span.src, span.count) }; + Ok(am::ScalarValue::Bytes(slice.to_vec())) + } + Counter(c) => Ok(am::ScalarValue::Counter(c.into())), + F64(f) => Ok(am::ScalarValue::F64(*f)), + Int(i) => Ok(am::ScalarValue::Int(*i)), + Str(span) => { + let result: Result<&str, am::AutomergeError> = span.try_into(); + match result { + Ok(str_) => Ok(am::ScalarValue::Str(SmolStr::new(str_))), + Err(e) => Err(e), + } + } + Timestamp(t) => Ok(am::ScalarValue::Timestamp(*t)), + Uint(u) => Ok(am::ScalarValue::Uint(*u)), + Null => Ok(am::ScalarValue::Null), + Unknown(AMunknownValue { bytes, type_code }) => { + let slice = unsafe { std::slice::from_raw_parts(bytes.src, bytes.count) }; + Ok(am::ScalarValue::Unknown { + bytes: slice.to_vec(), + type_code: *type_code, + }) + } + ActorId(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + ChangeHashes(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + Changes(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + Doc(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + ListItems(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + MapItems(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + ObjId(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + ObjItems(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + Strs(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + SyncMessage(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + SyncState(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + Void => Err(InvalidValueType { + expected, + unexpected: type_name::<()>().to_string(), + }), + } + } +} + +/// \memberof AMvalue +/// \brief Tests the equality of two values. +/// +/// \param[in] value1 A pointer to an `AMvalue` struct. +/// \param[in] value2 A pointer to an `AMvalue` struct. +/// \return `true` if \p value1 `==` \p value2 and `false` otherwise. +/// \pre \p value1 `!= NULL`. +/// \pre \p value2 `!= NULL`. +/// \internal +/// +/// #Safety +/// value1 must be a valid AMvalue pointer +/// value2 must be a valid AMvalue pointer +#[no_mangle] +pub unsafe extern "C" fn AMvalueEqual(value1: *const AMvalue, value2: *const AMvalue) -> bool { + match (value1.as_ref(), value2.as_ref()) { + (Some(value1), Some(value2)) => *value1 == *value2, + (None, Some(_)) | (Some(_), None) | (None, None) => false, + } +} /// \struct AMresult /// \installed_headerfile /// \brief A discriminated union of result variants. pub enum AMresult { - Items(Vec), + ActorId(am::ActorId, Option), + ChangeHashes(Vec), + Changes(Vec, Option>), + Doc(Box), Error(String), + ListItems(Vec), + MapItems(Vec), + ObjId(AMobjId), + ObjItems(Vec), + String(String), + Strings(Vec), + SyncMessage(AMsyncMessage), + SyncState(Box), + Value(am::Value<'static>), + Void, } impl AMresult { - pub(crate) fn error(s: &str) -> Self { - Self::Error(s.to_string()) - } - - pub(crate) fn item(item: AMitem) -> Self { - Self::Items(vec![item]) - } - - pub(crate) fn items(items: Vec) -> Self { - Self::Items(items) - } -} - -impl Default for AMresult { - fn default() -> Self { - Self::Items(vec![]) + pub(crate) fn err(s: &str) -> Self { + AMresult::Error(s.to_string()) } } impl From for AMresult { fn from(auto_commit: am::AutoCommit) -> Self { - Self::item(AMitem::exact(am::ROOT, auto_commit.into())) - } -} - -impl From for AMresult { - fn from(change: am::Change) -> Self { - Self::item(change.into()) + AMresult::Doc(Box::new(AMdoc::new(auto_commit))) } } impl From for AMresult { fn from(change_hash: am::ChangeHash) -> Self { - Self::item(change_hash.into()) + AMresult::ChangeHashes(vec![change_hash]) } } impl From> for AMresult { - fn from(maybe: Option) -> Self { - match maybe { - Some(change_hash) => change_hash.into(), - None => Self::item(Default::default()), + fn from(c: Option) -> Self { + match c { + Some(c) => c.into(), + None => AMresult::Void, } } } -impl From> for AMresult { - fn from(maybe: Result) -> Self { - match maybe { - Ok(change_hash) => change_hash.into(), - Err(e) => Self::error(&e.to_string()), - } +impl From> for AMresult { + fn from(keys: am::Keys<'_, '_>) -> Self { + AMresult::Strings(keys.collect()) + } +} + +impl From> for AMresult { + fn from(keys: am::KeysAt<'_, '_>) -> Self { + AMresult::Strings(keys.collect()) + } +} + +impl From>> for AMresult { + fn from(list_range: am::ListRange<'static, Range>) -> Self { + AMresult::ListItems( + list_range + .map(|(i, v, o)| AMlistItem::new(i, v.clone(), o)) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(list_range: am::ListRangeAt<'static, Range>) -> Self { + AMresult::ListItems( + list_range + .map(|(i, v, o)| AMlistItem::new(i, v.clone(), o)) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRange<'static, Range>) -> Self { + let map_items: Vec = map_range + .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) + .collect(); + AMresult::MapItems(map_items) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRangeAt<'static, Range>) -> Self { + let map_items: Vec = map_range + .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) + .collect(); + AMresult::MapItems(map_items) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRange<'static, RangeFrom>) -> Self { + let map_items: Vec = map_range + .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) + .collect(); + AMresult::MapItems(map_items) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRangeAt<'static, RangeFrom>) -> Self { + let map_items: Vec = map_range + .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) + .collect(); + AMresult::MapItems(map_items) + } +} + +impl From> for AMresult { + fn from(map_range: am::MapRange<'static, RangeFull>) -> Self { + let map_items: Vec = map_range + .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) + .collect(); + AMresult::MapItems(map_items) + } +} + +impl From> for AMresult { + fn from(map_range: am::MapRangeAt<'static, RangeFull>) -> Self { + let map_items: Vec = map_range + .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) + .collect(); + AMresult::MapItems(map_items) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRange<'static, RangeTo>) -> Self { + let map_items: Vec = map_range + .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) + .collect(); + AMresult::MapItems(map_items) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRangeAt<'static, RangeTo>) -> Self { + let map_items: Vec = map_range + .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) + .collect(); + AMresult::MapItems(map_items) } } impl From for AMresult { fn from(state: am::sync::State) -> Self { - Self::item(state.into()) + AMresult::SyncState(Box::new(AMsyncState::new(state))) } } impl From> for AMresult { fn from(pairs: am::Values<'static>) -> Self { - Self::items(pairs.map(|(v, o)| AMitem::exact(o, v.into())).collect()) + AMresult::ObjItems(pairs.map(|(v, o)| AMobjItem::new(v.clone(), o)).collect()) + } +} + +impl From, am::ObjId)>, am::AutomergeError>> for AMresult { + fn from(maybe: Result, am::ObjId)>, am::AutomergeError>) -> Self { + match maybe { + Ok(pairs) => AMresult::ObjItems( + pairs + .into_iter() + .map(|(v, o)| AMobjItem::new(v, o)) + .collect(), + ), + Err(e) => AMresult::err(&e.to_string()), + } } } @@ -89,150 +517,37 @@ impl From for *mut AMresult { } } -impl From> for AMresult { - fn from(keys: am::Keys<'_, '_>) -> Self { - Self::items(keys.map(|s| s.into()).collect()) - } -} - -impl From> for AMresult { - fn from(keys: am::KeysAt<'_, '_>) -> Self { - Self::items(keys.map(|s| s.into()).collect()) - } -} - -impl From>> for AMresult { - fn from(list_range: am::ListRange<'static, Range>) -> Self { - Self::items( - list_range - .map(|(i, v, o)| AMitem::indexed(AMindex::Pos(i), o, v.into())) - .collect(), - ) - } -} - -impl From>> for AMresult { - fn from(list_range: am::ListRangeAt<'static, Range>) -> Self { - Self::items( - list_range - .map(|(i, v, o)| AMitem::indexed(AMindex::Pos(i), o, v.into())) - .collect(), - ) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRange<'static, Range>) -> Self { - Self::items( - map_range - .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) - .collect(), - ) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRangeAt<'static, Range>) -> Self { - Self::items( - map_range - .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) - .collect(), - ) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRange<'static, RangeFrom>) -> Self { - Self::items( - map_range - .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) - .collect(), - ) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRangeAt<'static, RangeFrom>) -> Self { - Self::items( - map_range - .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) - .collect(), - ) - } -} - -impl From> for AMresult { - fn from(map_range: am::MapRange<'static, RangeFull>) -> Self { - Self::items( - map_range - .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) - .collect(), - ) - } -} - -impl From> for AMresult { - fn from(map_range: am::MapRangeAt<'static, RangeFull>) -> Self { - Self::items( - map_range - .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) - .collect(), - ) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRange<'static, RangeTo>) -> Self { - Self::items( - map_range - .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) - .collect(), - ) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRangeAt<'static, RangeTo>) -> Self { - Self::items( - map_range - .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) - .collect(), - ) - } -} - impl From> for AMresult { fn from(maybe: Option<&am::Change>) -> Self { - Self::item(match maybe { - Some(change) => change.clone().into(), - None => Default::default(), - }) + match maybe { + Some(change) => AMresult::Changes(vec![change.clone()], None), + None => AMresult::Void, + } } } impl From> for AMresult { fn from(maybe: Option) -> Self { - Self::item(match maybe { - Some(message) => message.into(), - None => Default::default(), - }) + match maybe { + Some(message) => AMresult::SyncMessage(AMsyncMessage::new(message)), + None => AMresult::Void, + } } } impl From> for AMresult { fn from(maybe: Result<(), am::AutomergeError>) -> Self { match maybe { - Ok(()) => Self::item(Default::default()), - Err(e) => Self::error(&e.to_string()), + Ok(()) => AMresult::Void, + Err(e) => AMresult::err(&e.to_string()), } } } - impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(actor_id) => Self::item(actor_id.into()), - Err(e) => Self::error(&e.to_string()), + Ok(actor_id) => AMresult::ActorId(actor_id, None), + Err(e) => AMresult::err(&e.to_string()), } } } @@ -240,8 +555,8 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(actor_id) => Self::item(actor_id.into()), - Err(e) => Self::error(&e.to_string()), + Ok(actor_id) => AMresult::ActorId(actor_id, None), + Err(e) => AMresult::err(&e.to_string()), } } } @@ -249,8 +564,8 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(auto_commit) => Self::item(auto_commit.into()), - Err(e) => Self::error(&e.to_string()), + Ok(auto_commit) => AMresult::Doc(Box::new(AMdoc::new(auto_commit))), + Err(e) => AMresult::err(&e.to_string()), } } } @@ -258,17 +573,17 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(change) => Self::item(change.into()), - Err(e) => Self::error(&e.to_string()), + Ok(change) => AMresult::Changes(vec![change], None), + Err(e) => AMresult::err(&e.to_string()), } } } -impl From<(Result, am::ObjType)> for AMresult { - fn from(tuple: (Result, am::ObjType)) -> Self { - match tuple { - (Ok(obj_id), obj_type) => Self::item((obj_id, obj_type).into()), - (Err(e), _) => Self::error(&e.to_string()), +impl From> for AMresult { + fn from(maybe: Result) -> Self { + match maybe { + Ok(obj_id) => AMresult::ObjId(AMobjId::new(obj_id)), + Err(e) => AMresult::err(&e.to_string()), } } } @@ -276,8 +591,8 @@ impl From<(Result, am::ObjType)> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(message) => Self::item(message.into()), - Err(e) => Self::error(&e.to_string()), + Ok(message) => AMresult::SyncMessage(AMsyncMessage::new(message)), + Err(e) => AMresult::err(&e.to_string()), } } } @@ -285,8 +600,8 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(state) => Self::item(state.into()), - Err(e) => Self::error(&e.to_string()), + Ok(state) => AMresult::SyncState(Box::new(AMsyncState::new(state))), + Err(e) => AMresult::err(&e.to_string()), } } } @@ -294,8 +609,8 @@ impl From> for AMresult { impl From, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::AutomergeError>) -> Self { match maybe { - Ok(value) => Self::item(value.into()), - Err(e) => Self::error(&e.to_string()), + Ok(value) => AMresult::Value(value), + Err(e) => AMresult::err(&e.to_string()), } } } @@ -303,9 +618,12 @@ impl From, am::AutomergeError>> for AMresult { impl From, am::ObjId)>, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::ObjId)>, am::AutomergeError>) -> Self { match maybe { - Ok(Some((value, obj_id))) => Self::item(AMitem::exact(obj_id, value.into())), - Ok(None) => Self::item(Default::default()), - Err(e) => Self::error(&e.to_string()), + Ok(Some((value, obj_id))) => match value { + am::Value::Object(_) => AMresult::ObjId(AMobjId::new(obj_id)), + _ => AMresult::Value(value), + }, + Ok(None) => AMresult::Void, + Err(e) => AMresult::err(&e.to_string()), } } } @@ -313,8 +631,8 @@ impl From, am::ObjId)>, am::AutomergeError>> f impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(string) => Self::item(string.into()), - Err(e) => Self::error(&e.to_string()), + Ok(string) => AMresult::String(string), + Err(e) => AMresult::err(&e.to_string()), } } } @@ -322,8 +640,8 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(size) => Self::item(am::Value::uint(size as u64).into()), - Err(e) => Self::error(&e.to_string()), + Ok(size) => AMresult::Value(am::Value::uint(size as u64)), + Err(e) => AMresult::err(&e.to_string()), } } } @@ -331,22 +649,8 @@ impl From> for AMresult { impl From, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::AutomergeError>) -> Self { match maybe { - Ok(changes) => Self::items(changes.into_iter().map(|change| change.into()).collect()), - Err(e) => Self::error(&e.to_string()), - } - } -} - -impl From, am::AutomergeError>> for AMresult { - fn from(maybe: Result, am::AutomergeError>) -> Self { - match maybe { - Ok(changes) => Self::items( - changes - .into_iter() - .map(|change| change.clone().into()) - .collect(), - ), - Err(e) => Self::error(&e.to_string()), + Ok(changes) => AMresult::Changes(changes, None), + Err(e) => AMresult::err(&e.to_string()), } } } @@ -354,8 +658,21 @@ impl From, am::AutomergeError>> for AMresult { impl From, am::LoadChangeError>> for AMresult { fn from(maybe: Result, am::LoadChangeError>) -> Self { match maybe { - Ok(changes) => Self::items(changes.into_iter().map(|change| change.into()).collect()), - Err(e) => Self::error(&e.to_string()), + Ok(changes) => AMresult::Changes(changes, None), + Err(e) => AMresult::err(&e.to_string()), + } + } +} + +impl From, am::AutomergeError>> for AMresult { + fn from(maybe: Result, am::AutomergeError>) -> Self { + match maybe { + Ok(changes) => { + let changes: Vec = + changes.iter().map(|&change| change.clone()).collect(); + AMresult::Changes(changes, None) + } + Err(e) => AMresult::err(&e.to_string()), } } } @@ -363,13 +680,8 @@ impl From, am::LoadChangeError>> for AMresult { impl From, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::AutomergeError>) -> Self { match maybe { - Ok(change_hashes) => Self::items( - change_hashes - .into_iter() - .map(|change_hash| change_hash.into()) - .collect(), - ), - Err(e) => Self::error(&e.to_string()), + Ok(change_hashes) => AMresult::ChangeHashes(change_hashes), + Err(e) => AMresult::err(&e.to_string()), } } } @@ -377,27 +689,8 @@ impl From, am::AutomergeError>> for AMresult { impl From, am::InvalidChangeHashSlice>> for AMresult { fn from(maybe: Result, am::InvalidChangeHashSlice>) -> Self { match maybe { - Ok(change_hashes) => Self::items( - change_hashes - .into_iter() - .map(|change_hash| change_hash.into()) - .collect(), - ), - Err(e) => Self::error(&e.to_string()), - } - } -} - -impl From, am::ObjId)>, am::AutomergeError>> for AMresult { - fn from(maybe: Result, am::ObjId)>, am::AutomergeError>) -> Self { - match maybe { - Ok(pairs) => Self::items( - pairs - .into_iter() - .map(|(v, o)| AMitem::exact(o, v.into())) - .collect(), - ), - Err(e) => Self::error(&e.to_string()), + Ok(change_hashes) => AMresult::ChangeHashes(change_hashes), + Err(e) => AMresult::err(&e.to_string()), } } } @@ -405,66 +698,28 @@ impl From, am::ObjId)>, am::AutomergeError>> for impl From, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::AutomergeError>) -> Self { match maybe { - Ok(bytes) => Self::item(am::Value::bytes(bytes).into()), - Err(e) => Self::error(&e.to_string()), + Ok(bytes) => AMresult::Value(am::Value::bytes(bytes)), + Err(e) => AMresult::err(&e.to_string()), } } } -impl From<&[am::Change]> for AMresult { - fn from(changes: &[am::Change]) -> Self { - Self::items(changes.iter().map(|change| change.clone().into()).collect()) - } -} - impl From> for AMresult { fn from(changes: Vec<&am::Change>) -> Self { - Self::items( - changes - .into_iter() - .map(|change| change.clone().into()) - .collect(), - ) - } -} - -impl From<&[am::ChangeHash]> for AMresult { - fn from(change_hashes: &[am::ChangeHash]) -> Self { - Self::items( - change_hashes - .iter() - .map(|change_hash| (*change_hash).into()) - .collect(), - ) - } -} - -impl From<&[am::sync::Have]> for AMresult { - fn from(haves: &[am::sync::Have]) -> Self { - Self::items(haves.iter().map(|have| have.clone().into()).collect()) + let changes: Vec = changes.iter().map(|&change| change.clone()).collect(); + AMresult::Changes(changes, None) } } impl From> for AMresult { fn from(change_hashes: Vec) -> Self { - Self::items( - change_hashes - .into_iter() - .map(|change_hash| change_hash.into()) - .collect(), - ) - } -} - -impl From> for AMresult { - fn from(haves: Vec) -> Self { - Self::items(haves.into_iter().map(|have| have.into()).collect()) + AMresult::ChangeHashes(change_hashes) } } impl From> for AMresult { fn from(bytes: Vec) -> Self { - Self::item(am::Value::bytes(bytes).into()) + AMresult::Value(am::Value::bytes(bytes)) } } @@ -474,9 +729,8 @@ pub fn to_result>(r: R) -> *mut AMresult { /// \ingroup enumerations /// \enum AMstatus -/// \installed_headerfile /// \brief The status of an API call. -#[derive(PartialEq, Eq)] +#[derive(Debug)] #[repr(u8)] pub enum AMstatus { /// Success. @@ -488,80 +742,35 @@ pub enum AMstatus { InvalidResult, } -/// \memberof AMresult -/// \brief Concatenates the items from two results. -/// -/// \param[in] dest A pointer to an `AMresult` struct. -/// \param[in] src A pointer to an `AMresult` struct. -/// \return A pointer to an `AMresult` struct with the items from \p dest in -/// their original order followed by the items from \p src in their -/// original order. -/// \pre \p dest `!= NULL` -/// \pre \p src `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. -/// \internal -/// -/// # Safety -/// dest must be a valid pointer to an AMresult -/// src must be a valid pointer to an AMresult -#[no_mangle] -pub unsafe extern "C" fn AMresultCat(dest: *const AMresult, src: *const AMresult) -> *mut AMresult { - use AMresult::*; - - match (dest.as_ref(), src.as_ref()) { - (Some(dest), Some(src)) => match (dest, src) { - (Items(dest_items), Items(src_items)) => { - return AMresult::items( - dest_items - .iter() - .cloned() - .chain(src_items.iter().cloned()) - .collect(), - ) - .into(); - } - (Error(_), Error(_)) | (Error(_), Items(_)) | (Items(_), Error(_)) => { - AMresult::error("Invalid `AMresult`").into() - } - }, - (None, None) | (None, Some(_)) | (Some(_), None) => { - AMresult::error("Invalid `AMresult*`").into() - } - } -} - /// \memberof AMresult /// \brief Gets a result's error message string. /// /// \param[in] result A pointer to an `AMresult` struct. /// \return A UTF-8 string view as an `AMbyteSpan` struct. -/// \pre \p result `!= NULL` +/// \pre \p result `!= NULL`. /// \internal /// /// # Safety /// result must be a valid pointer to an AMresult #[no_mangle] -pub unsafe extern "C" fn AMresultError(result: *const AMresult) -> AMbyteSpan { - use AMresult::*; - - if let Some(Error(message)) = result.as_ref() { - return message.as_bytes().into(); +pub unsafe extern "C" fn AMerrorMessage(result: *const AMresult) -> AMbyteSpan { + match result.as_ref() { + Some(AMresult::Error(s)) => s.as_bytes().into(), + _ => Default::default(), } - Default::default() } /// \memberof AMresult /// \brief Deallocates the storage for a result. /// -/// \param[in] result A pointer to an `AMresult` struct. -/// \pre \p result `!= NULL` +/// \param[in,out] result A pointer to an `AMresult` struct. +/// \pre \p result `!= NULL`. /// \internal /// /// # Safety /// result must be a valid pointer to an AMresult #[no_mangle] -pub unsafe extern "C" fn AMresultFree(result: *mut AMresult) { +pub unsafe extern "C" fn AMfree(result: *mut AMresult) { if !result.is_null() { let result: AMresult = *Box::from_raw(result); drop(result) @@ -569,67 +778,39 @@ pub unsafe extern "C" fn AMresultFree(result: *mut AMresult) { } /// \memberof AMresult -/// \brief Gets a result's first item. +/// \brief Gets the size of a result's value. /// /// \param[in] result A pointer to an `AMresult` struct. -/// \return A pointer to an `AMitem` struct. -/// \pre \p result `!= NULL` -/// \internal -/// -/// # Safety -/// result must be a valid pointer to an AMresult -#[no_mangle] -pub unsafe extern "C" fn AMresultItem(result: *mut AMresult) -> *mut AMitem { - use AMresult::*; - - if let Some(Items(items)) = result.as_mut() { - if !items.is_empty() { - return &mut items[0]; - } - } - std::ptr::null_mut() -} - -/// \memberof AMresult -/// \brief Gets a result's items. -/// -/// \param[in] result A pointer to an `AMresult` struct. -/// \return An `AMitems` struct. -/// \pre \p result `!= NULL` -/// \internal -/// -/// # Safety -/// result must be a valid pointer to an AMresult -#[no_mangle] -pub unsafe extern "C" fn AMresultItems<'a>(result: *mut AMresult) -> AMitems<'a> { - use AMresult::*; - - if let Some(Items(items)) = result.as_mut() { - if !items.is_empty() { - return AMitems::new(items); - } - } - Default::default() -} - -/// \memberof AMresult -/// \brief Gets the size of a result. -/// -/// \param[in] result A pointer to an `AMresult` struct. -/// \return The count of items within \p result. -/// \pre \p result `!= NULL` +/// \return The count of values in \p result. +/// \pre \p result `!= NULL`. /// \internal /// /// # Safety /// result must be a valid pointer to an AMresult #[no_mangle] pub unsafe extern "C" fn AMresultSize(result: *const AMresult) -> usize { - use self::AMresult::*; + if let Some(result) = result.as_ref() { + use AMresult::*; - if let Some(Items(items)) = result.as_ref() { - return items.len(); + match result { + Error(_) | Void => 0, + ActorId(_, _) + | Doc(_) + | ObjId(_) + | String(_) + | SyncMessage(_) + | SyncState(_) + | Value(_) => 1, + ChangeHashes(change_hashes) => change_hashes.len(), + Changes(changes, _) => changes.len(), + ListItems(list_items) => list_items.len(), + MapItems(map_items) => map_items.len(), + ObjItems(obj_items) => obj_items.len(), + Strings(cstrings) => cstrings.len(), + } + } else { + 0 } - 0 } /// \memberof AMresult @@ -637,24 +818,94 @@ pub unsafe extern "C" fn AMresultSize(result: *const AMresult) -> usize { /// /// \param[in] result A pointer to an `AMresult` struct. /// \return An `AMstatus` enum tag. -/// \pre \p result `!= NULL` +/// \pre \p result `!= NULL`. /// \internal /// /// # Safety /// result must be a valid pointer to an AMresult #[no_mangle] pub unsafe extern "C" fn AMresultStatus(result: *const AMresult) -> AMstatus { - use AMresult::*; - - if let Some(result) = result.as_ref() { - match result { - Error(_) => { - return AMstatus::Error; - } - _ => { - return AMstatus::Ok; - } - } + match result.as_ref() { + Some(AMresult::Error(_)) => AMstatus::Error, + None => AMstatus::InvalidResult, + _ => AMstatus::Ok, } - AMstatus::InvalidResult +} + +/// \memberof AMresult +/// \brief Gets a result's value. +/// +/// \param[in] result A pointer to an `AMresult` struct. +/// \return An `AMvalue` struct. +/// \pre \p result `!= NULL`. +/// \internal +/// +/// # Safety +/// result must be a valid pointer to an AMresult +#[no_mangle] +pub unsafe extern "C" fn AMresultValue<'a>(result: *mut AMresult) -> AMvalue<'a> { + let mut content = AMvalue::Void; + if let Some(result) = result.as_mut() { + match result { + AMresult::ActorId(actor_id, c_actor_id) => match c_actor_id { + None => { + content = AMvalue::ActorId(&*c_actor_id.insert(AMactorId::new(&*actor_id))); + } + Some(c_actor_id) => { + content = AMvalue::ActorId(&*c_actor_id); + } + }, + AMresult::ChangeHashes(change_hashes) => { + content = AMvalue::ChangeHashes(AMchangeHashes::new(change_hashes)); + } + AMresult::Changes(changes, storage) => { + content = AMvalue::Changes(AMchanges::new( + changes, + storage.get_or_insert(BTreeMap::new()), + )); + } + AMresult::Doc(doc) => content = AMvalue::Doc(&mut **doc), + AMresult::Error(_) => {} + AMresult::ListItems(list_items) => { + content = AMvalue::ListItems(AMlistItems::new(list_items)); + } + AMresult::MapItems(map_items) => { + content = AMvalue::MapItems(AMmapItems::new(map_items)); + } + AMresult::ObjId(obj_id) => { + content = AMvalue::ObjId(obj_id); + } + AMresult::ObjItems(obj_items) => { + content = AMvalue::ObjItems(AMobjItems::new(obj_items)); + } + AMresult::String(string) => content = AMvalue::Str(string.as_bytes().into()), + AMresult::Strings(strings) => { + content = AMvalue::Strs(AMstrs::new(strings)); + } + AMresult::SyncMessage(sync_message) => { + content = AMvalue::SyncMessage(sync_message); + } + AMresult::SyncState(sync_state) => { + content = AMvalue::SyncState(&mut *sync_state); + } + AMresult::Value(value) => { + content = (&*value).into(); + } + AMresult::Void => {} + } + }; + content +} + +/// \struct AMunknownValue +/// \installed_headerfile +/// \brief A value (typically for a `set` operation) whose type is unknown. +/// +#[derive(Eq, PartialEq)] +#[repr(C)] +pub struct AMunknownValue { + /// The value's raw bytes. + bytes: AMbyteSpan, + /// The value's encoded type identifier. + type_code: u8, } diff --git a/rust/automerge-c/src/result_stack.rs b/rust/automerge-c/src/result_stack.rs new file mode 100644 index 00000000..cfb9c7d2 --- /dev/null +++ b/rust/automerge-c/src/result_stack.rs @@ -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 ()>; + +/// \memberof AMresultStack +/// \brief Pushes the given result onto the given stack and then either extracts +/// a value matching the given discriminant from that result or, +/// failing that, calls the given function and gets a void value instead. +/// +/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct. +/// \param[in] result A pointer to an `AMresult` struct. +/// \param[in] discriminant An `AMvalue` variant's corresponding enum tag. +/// \param[in] callback A pointer to a function with the same signature as +/// `AMpushCallback()` or `NULL`. +/// \return An `AMvalue` struct. +/// \pre \p stack `!= NULL`. +/// \pre \p result `!= NULL`. +/// \warning If \p stack `== NULL` then \p result is deallocated in order to +/// prevent a memory leak. +/// \note Calling this function is purely optional because its only purpose is +/// to make memory management tolerable for direct usage of this API in +/// C, C++ and Objective-C. +/// \internal +/// +/// # Safety +/// stack must be a valid AMresultStack pointer pointer +/// result must be a valid AMresult pointer +#[no_mangle] +pub unsafe extern "C" fn AMpush<'a>( + stack: *mut *mut AMresultStack, + result: *mut AMresult, + discriminant: u8, + callback: AMpushCallback, +) -> AMvalue<'a> { + if stack.is_null() { + // There's no stack to push the result onto so it has to be freed in + // order to prevent a memory leak. + AMfree(result); + if let Some(callback) = callback { + callback(stack, discriminant); + } + return AMvalue::Void; + } else if result.is_null() { + if let Some(callback) = callback { + callback(stack, discriminant); + } + return AMvalue::Void; + } + // Always push the result onto the stack, even if it's wrong, so that the + // given callback can retrieve it. + let node = Box::new(AMresultStack::new(result, *stack)); + let top = Box::into_raw(node); + *stack = top; + // Test that the result contains a value. + match AMresultStatus(result) { + AMstatus::Ok => {} + _ => { + if let Some(callback) = callback { + callback(stack, discriminant); + } + return AMvalue::Void; + } + } + // Test that the result's value matches the given discriminant. + let value = AMresultValue(result); + if discriminant != u8::from(&value) { + if let Some(callback) = callback { + callback(stack, discriminant); + } + return AMvalue::Void; + } + value +} diff --git a/rust/automerge-c/src/strs.rs b/rust/automerge-c/src/strs.rs new file mode 100644 index 00000000..a36861b7 --- /dev/null +++ b/rust/automerge-c/src/strs.rs @@ -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()` call +/// (https://github.com/eqrion/cbindgen/issues/252) but it will +/// propagate the name of a constant initialized from it so if the +/// constant's name is a symbolic representation of the value it can be +/// converted into a number by post-processing the header it generated. +pub const USIZE_USIZE_USIZE_: usize = size_of::(); + +impl Detail { + fn new(strings: &[String], offset: isize) -> Self { + Self { + len: strings.len(), + offset, + ptr: strings.as_ptr() as *const c_void, + } + } + + pub fn advance(&mut self, n: isize) { + if n == 0 { + return; + } + let len = self.len as isize; + self.offset = if self.offset < 0 { + // It's reversed. + let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); + if unclipped >= 0 { + // Clip it to the forward stop. + len + } else { + std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) + } + } else { + let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); + if unclipped < 0 { + // Clip it to the reverse stop. + -(len + 1) + } else { + std::cmp::max(0, std::cmp::min(unclipped, len)) + } + } + } + + pub fn get_index(&self) -> usize { + (self.offset + + if self.offset < 0 { + self.len as isize + } else { + 0 + }) as usize + } + + pub fn next(&mut self, n: isize) -> Option { + if self.is_stopped() { + return None; + } + let slice: &[String] = + unsafe { std::slice::from_raw_parts(self.ptr as *const String, self.len) }; + let value = slice[self.get_index()].as_bytes().into(); + self.advance(n); + Some(value) + } + + pub fn is_stopped(&self) -> bool { + let len = self.len as isize; + self.offset < -len || self.offset == len + } + + pub fn prev(&mut self, n: isize) -> Option { + self.advance(-n); + if self.is_stopped() { + return None; + } + let slice: &[String] = + unsafe { std::slice::from_raw_parts(self.ptr as *const String, self.len) }; + Some(slice[self.get_index()].as_bytes().into()) + } + + pub fn reversed(&self) -> Self { + Self { + len: self.len, + offset: -(self.offset + 1), + ptr: self.ptr, + } + } + + pub fn rewound(&self) -> Self { + Self { + len: self.len, + offset: if self.offset < 0 { -1 } else { 0 }, + ptr: self.ptr, + } + } +} + +impl From for [u8; USIZE_USIZE_USIZE_] { + fn from(detail: Detail) -> Self { + unsafe { + std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) + .try_into() + .unwrap() + } + } +} + +/// \struct AMstrs +/// \installed_headerfile +/// \brief A random-access iterator over a sequence of UTF-8 strings. +#[repr(C)] +#[derive(Eq, PartialEq)] +pub struct AMstrs { + /// An implementation detail that is intentionally opaque. + /// \warning Modifying \p detail will cause undefined behavior. + /// \note The actual size of \p detail will vary by platform, this is just + /// the one for the platform this documentation was built on. + detail: [u8; USIZE_USIZE_USIZE_], +} + +impl AMstrs { + pub fn new(strings: &[String]) -> Self { + Self { + detail: Detail::new(strings, 0).into(), + } + } + + pub fn advance(&mut self, n: isize) { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.advance(n); + } + + pub fn len(&self) -> usize { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + detail.len + } + + pub fn next(&mut self, n: isize) -> Option { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.next(n) + } + + pub fn prev(&mut self, n: isize) -> Option { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.prev(n) + } + + pub fn reversed(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.reversed().into(), + } + } + + pub fn rewound(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.rewound().into(), + } + } +} + +impl AsRef<[String]> for AMstrs { + fn as_ref(&self) -> &[String] { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + unsafe { std::slice::from_raw_parts(detail.ptr as *const String, detail.len) } + } +} + +impl Default for AMstrs { + fn default() -> Self { + Self { + detail: [0; USIZE_USIZE_USIZE_], + } + } +} + +/// \memberof AMstrs +/// \brief Advances an iterator over a sequence of UTF-8 strings by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction. +/// +/// \param[in,out] strs A pointer to an `AMstrs` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \pre \p strs `!= NULL`. +/// \internal +/// +/// #Safety +/// strs must be a valid pointer to an AMstrs +#[no_mangle] +pub unsafe extern "C" fn AMstrsAdvance(strs: *mut AMstrs, n: isize) { + if let Some(strs) = strs.as_mut() { + strs.advance(n); + }; +} + +/// \memberof AMstrs +/// \brief Compares the sequences of UTF-8 strings underlying a pair of +/// iterators. +/// +/// \param[in] strs1 A pointer to an `AMstrs` struct. +/// \param[in] strs2 A pointer to an `AMstrs` struct. +/// \return `-1` if \p strs1 `<` \p strs2, `0` if +/// \p strs1 `==` \p strs2 and `1` if +/// \p strs1 `>` \p strs2. +/// \pre \p strs1 `!= NULL`. +/// \pre \p strs2 `!= NULL`. +/// \internal +/// +/// #Safety +/// strs1 must be a valid pointer to an AMstrs +/// strs2 must be a valid pointer to an AMstrs +#[no_mangle] +pub unsafe extern "C" fn AMstrsCmp(strs1: *const AMstrs, strs2: *const AMstrs) -> isize { + match (strs1.as_ref(), strs2.as_ref()) { + (Some(strs1), Some(strs2)) => match strs1.as_ref().cmp(strs2.as_ref()) { + Ordering::Less => -1, + Ordering::Equal => 0, + Ordering::Greater => 1, + }, + (None, Some(_)) => -1, + (Some(_), None) => 1, + (None, None) => 0, + } +} + +/// \memberof AMstrs +/// \brief Gets the key at the current position of an iterator over a sequence +/// of UTF-8 strings and then advances it by at most \p |n| positions +/// where the sign of \p n is relative to the iterator's direction. +/// +/// \param[in,out] strs A pointer to an `AMstrs` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A UTF-8 string view as an `AMbyteSpan` struct that's `AMstr(NULL)` +/// when \p strs was previously advanced past its forward/reverse limit. +/// \pre \p strs `!= NULL`. +/// \internal +/// +/// #Safety +/// strs must be a valid pointer to an AMstrs +#[no_mangle] +pub unsafe extern "C" fn AMstrsNext(strs: *mut AMstrs, n: isize) -> AMbyteSpan { + if let Some(strs) = strs.as_mut() { + if let Some(key) = strs.next(n) { + return key; + } + } + Default::default() +} + +/// \memberof AMstrs +/// \brief Advances an iterator over a sequence of UTF-8 strings by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction and then gets the key at its new position. +/// +/// \param[in,out] strs A pointer to an `AMstrs` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A UTF-8 string view as an `AMbyteSpan` struct that's `AMstr(NULL)` +/// when \p strs is presently advanced past its forward/reverse limit. +/// \pre \p strs `!= NULL`. +/// \internal +/// +/// #Safety +/// strs must be a valid pointer to an AMstrs +#[no_mangle] +pub unsafe extern "C" fn AMstrsPrev(strs: *mut AMstrs, n: isize) -> AMbyteSpan { + if let Some(strs) = strs.as_mut() { + if let Some(key) = strs.prev(n) { + return key; + } + } + Default::default() +} + +/// \memberof AMstrs +/// \brief Gets the size of the sequence of UTF-8 strings underlying an +/// iterator. +/// +/// \param[in] strs A pointer to an `AMstrs` struct. +/// \return The count of values in \p strs. +/// \pre \p strs `!= NULL`. +/// \internal +/// +/// #Safety +/// strs must be a valid pointer to an AMstrs +#[no_mangle] +pub unsafe extern "C" fn AMstrsSize(strs: *const AMstrs) -> usize { + if let Some(strs) = strs.as_ref() { + strs.len() + } else { + 0 + } +} + +/// \memberof AMstrs +/// \brief Creates an iterator over the same sequence of UTF-8 strings as the +/// given one but with the opposite position and direction. +/// +/// \param[in] strs A pointer to an `AMstrs` struct. +/// \return An `AMstrs` struct. +/// \pre \p strs `!= NULL`. +/// \internal +/// +/// #Safety +/// strs must be a valid pointer to an AMstrs +#[no_mangle] +pub unsafe extern "C" fn AMstrsReversed(strs: *const AMstrs) -> AMstrs { + if let Some(strs) = strs.as_ref() { + strs.reversed() + } else { + AMstrs::default() + } +} + +/// \memberof AMstrs +/// \brief Creates an iterator at the starting position over the same sequence +/// of UTF-8 strings as the given one. +/// +/// \param[in] strs A pointer to an `AMstrs` struct. +/// \return An `AMstrs` struct +/// \pre \p strs `!= NULL`. +/// \internal +/// +/// #Safety +/// strs must be a valid pointer to an AMstrs +#[no_mangle] +pub unsafe extern "C" fn AMstrsRewound(strs: *const AMstrs) -> AMstrs { + if let Some(strs) = strs.as_ref() { + strs.rewound() + } else { + Default::default() + } +} diff --git a/rust/automerge-c/src/sync.rs b/rust/automerge-c/src/sync.rs index fe0332a1..cfed1af5 100644 --- a/rust/automerge-c/src/sync.rs +++ b/rust/automerge-c/src/sync.rs @@ -1,7 +1,7 @@ mod have; +mod haves; mod message; mod state; -pub(crate) use have::AMsyncHave; pub(crate) use message::{to_sync_message, AMsyncMessage}; pub(crate) use state::AMsyncState; diff --git a/rust/automerge-c/src/sync/have.rs b/rust/automerge-c/src/sync/have.rs index 37d2031f..312151e7 100644 --- a/rust/automerge-c/src/sync/have.rs +++ b/rust/automerge-c/src/sync/have.rs @@ -1,23 +1,23 @@ use automerge as am; -use crate::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 for AMsyncHave { fn as_ref(&self) -> &am::sync::Have { - &self.0 + unsafe { &*self.0 } } } @@ -25,18 +25,17 @@ impl AsRef 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() + } } diff --git a/rust/automerge-c/src/sync/haves.rs b/rust/automerge-c/src/sync/haves.rs new file mode 100644 index 00000000..c74b8e96 --- /dev/null +++ b/rust/automerge-c/src/sync/haves.rs @@ -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()` call +/// (https://github.com/eqrion/cbindgen/issues/252) but it will +/// propagate the name of a constant initialized from it so if the +/// constant's name is a symbolic representation of the value it can be +/// converted into a number by post-processing the header it generated. +pub const USIZE_USIZE_USIZE_USIZE_: usize = size_of::(); + +impl Detail { + fn new( + haves: &[am::sync::Have], + offset: isize, + storage: &mut BTreeMap, + ) -> Self { + let storage: *mut BTreeMap = storage; + Self { + len: haves.len(), + offset, + ptr: haves.as_ptr() as *const c_void, + storage: storage as *mut c_void, + } + } + + pub fn advance(&mut self, n: isize) { + if n == 0 { + return; + } + let len = self.len as isize; + self.offset = if self.offset < 0 { + // It's reversed. + let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); + if unclipped >= 0 { + // Clip it to the forward stop. + len + } else { + std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) + } + } else { + let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); + if unclipped < 0 { + // Clip it to the reverse stop. + -(len + 1) + } else { + std::cmp::max(0, std::cmp::min(unclipped, len)) + } + } + } + + pub fn get_index(&self) -> usize { + (self.offset + + if self.offset < 0 { + self.len as isize + } else { + 0 + }) as usize + } + + pub fn next(&mut self, n: isize) -> Option<*const AMsyncHave> { + if self.is_stopped() { + return None; + } + let slice: &[am::sync::Have] = + unsafe { std::slice::from_raw_parts(self.ptr as *const am::sync::Have, self.len) }; + let storage = unsafe { &mut *(self.storage as *mut BTreeMap) }; + let index = self.get_index(); + let value = match storage.get_mut(&index) { + Some(value) => value, + None => { + storage.insert(index, AMsyncHave::new(&slice[index])); + storage.get_mut(&index).unwrap() + } + }; + self.advance(n); + Some(value) + } + + pub fn is_stopped(&self) -> bool { + let len = self.len as isize; + self.offset < -len || self.offset == len + } + + pub fn prev(&mut self, n: isize) -> Option<*const AMsyncHave> { + self.advance(-n); + if self.is_stopped() { + return None; + } + let slice: &[am::sync::Have] = + unsafe { std::slice::from_raw_parts(self.ptr as *const am::sync::Have, self.len) }; + let storage = unsafe { &mut *(self.storage as *mut BTreeMap) }; + let index = self.get_index(); + Some(match storage.get_mut(&index) { + Some(value) => value, + None => { + storage.insert(index, AMsyncHave::new(&slice[index])); + storage.get_mut(&index).unwrap() + } + }) + } + + pub fn reversed(&self) -> Self { + Self { + len: self.len, + offset: -(self.offset + 1), + ptr: self.ptr, + storage: self.storage, + } + } + + pub fn rewound(&self) -> Self { + Self { + len: self.len, + offset: if self.offset < 0 { -1 } else { 0 }, + ptr: self.ptr, + storage: self.storage, + } + } +} + +impl From for [u8; USIZE_USIZE_USIZE_USIZE_] { + fn from(detail: Detail) -> Self { + unsafe { + std::slice::from_raw_parts( + (&detail as *const Detail) as *const u8, + USIZE_USIZE_USIZE_USIZE_, + ) + .try_into() + .unwrap() + } + } +} + +/// \struct AMsyncHaves +/// \installed_headerfile +/// \brief A random-access iterator over a sequence of synchronization haves. +#[repr(C)] +#[derive(Eq, PartialEq)] +pub struct AMsyncHaves { + /// An implementation detail that is intentionally opaque. + /// \warning Modifying \p detail will cause undefined behavior. + /// \note The actual size of \p detail will vary by platform, this is just + /// the one for the platform this documentation was built on. + detail: [u8; USIZE_USIZE_USIZE_USIZE_], +} + +impl AMsyncHaves { + pub fn new(haves: &[am::sync::Have], storage: &mut BTreeMap) -> Self { + Self { + detail: Detail::new(haves, 0, storage).into(), + } + } + + pub fn advance(&mut self, n: isize) { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.advance(n); + } + + pub fn len(&self) -> usize { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + detail.len + } + + pub fn next(&mut self, n: isize) -> Option<*const AMsyncHave> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.next(n) + } + + pub fn prev(&mut self, n: isize) -> Option<*const AMsyncHave> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.prev(n) + } + + pub fn reversed(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.reversed().into(), + } + } + + pub fn rewound(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.rewound().into(), + } + } +} + +impl AsRef<[am::sync::Have]> for AMsyncHaves { + fn as_ref(&self) -> &[am::sync::Have] { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + unsafe { std::slice::from_raw_parts(detail.ptr as *const am::sync::Have, detail.len) } + } +} + +impl Default for AMsyncHaves { + fn default() -> Self { + Self { + detail: [0; USIZE_USIZE_USIZE_USIZE_], + } + } +} + +/// \memberof AMsyncHaves +/// \brief Advances an iterator over a sequence of synchronization haves by at +/// most \p |n| positions where the sign of \p n is relative to the +/// iterator's direction. +/// +/// \param[in,out] sync_haves A pointer to an `AMsyncHaves` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \pre \p sync_haves `!= NULL`. +/// \internal +/// +/// #Safety +/// sync_haves must be a valid pointer to an AMsyncHaves +#[no_mangle] +pub unsafe extern "C" fn AMsyncHavesAdvance(sync_haves: *mut AMsyncHaves, n: isize) { + if let Some(sync_haves) = sync_haves.as_mut() { + sync_haves.advance(n); + }; +} + +/// \memberof AMsyncHaves +/// \brief Tests the equality of two sequences of synchronization haves +/// underlying a pair of iterators. +/// +/// \param[in] sync_haves1 A pointer to an `AMsyncHaves` struct. +/// \param[in] sync_haves2 A pointer to an `AMsyncHaves` struct. +/// \return `true` if \p sync_haves1 `==` \p sync_haves2 and `false` otherwise. +/// \pre \p sync_haves1 `!= NULL`. +/// \pre \p sync_haves2 `!= NULL`. +/// \internal +/// +/// #Safety +/// sync_haves1 must be a valid pointer to an AMsyncHaves +/// sync_haves2 must be a valid pointer to an AMsyncHaves +#[no_mangle] +pub unsafe extern "C" fn AMsyncHavesEqual( + sync_haves1: *const AMsyncHaves, + sync_haves2: *const AMsyncHaves, +) -> bool { + match (sync_haves1.as_ref(), sync_haves2.as_ref()) { + (Some(sync_haves1), Some(sync_haves2)) => sync_haves1.as_ref() == sync_haves2.as_ref(), + (None, Some(_)) | (Some(_), None) | (None, None) => false, + } +} + +/// \memberof AMsyncHaves +/// \brief Gets the synchronization have at the current position of an iterator +/// over a sequence of synchronization haves and then advances it by at +/// most \p |n| positions where the sign of \p n is relative to the +/// iterator's direction. +/// +/// \param[in,out] sync_haves A pointer to an `AMsyncHaves` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A pointer to an `AMsyncHave` struct that's `NULL` when +/// \p sync_haves was previously advanced past its forward/reverse +/// limit. +/// \pre \p sync_haves `!= NULL`. +/// \internal +/// +/// #Safety +/// sync_haves must be a valid pointer to an AMsyncHaves +#[no_mangle] +pub unsafe extern "C" fn AMsyncHavesNext( + sync_haves: *mut AMsyncHaves, + n: isize, +) -> *const AMsyncHave { + if let Some(sync_haves) = sync_haves.as_mut() { + if let Some(sync_have) = sync_haves.next(n) { + return sync_have; + } + } + std::ptr::null() +} + +/// \memberof AMsyncHaves +/// \brief Advances an iterator over a sequence of synchronization haves by at +/// most \p |n| positions where the sign of \p n is relative to the +/// iterator's direction and then gets the synchronization have at its +/// new position. +/// +/// \param[in,out] sync_haves A pointer to an `AMsyncHaves` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A pointer to an `AMsyncHave` struct that's `NULL` when +/// \p sync_haves is presently advanced past its forward/reverse limit. +/// \pre \p sync_haves `!= NULL`. +/// \internal +/// +/// #Safety +/// sync_haves must be a valid pointer to an AMsyncHaves +#[no_mangle] +pub unsafe extern "C" fn AMsyncHavesPrev( + sync_haves: *mut AMsyncHaves, + n: isize, +) -> *const AMsyncHave { + if let Some(sync_haves) = sync_haves.as_mut() { + if let Some(sync_have) = sync_haves.prev(n) { + return sync_have; + } + } + std::ptr::null() +} + +/// \memberof AMsyncHaves +/// \brief Gets the size of the sequence of synchronization haves underlying an +/// iterator. +/// +/// \param[in] sync_haves A pointer to an `AMsyncHaves` struct. +/// \return The count of values in \p sync_haves. +/// \pre \p sync_haves `!= NULL`. +/// \internal +/// +/// #Safety +/// sync_haves must be a valid pointer to an AMsyncHaves +#[no_mangle] +pub unsafe extern "C" fn AMsyncHavesSize(sync_haves: *const AMsyncHaves) -> usize { + if let Some(sync_haves) = sync_haves.as_ref() { + sync_haves.len() + } else { + 0 + } +} + +/// \memberof AMsyncHaves +/// \brief Creates an iterator over the same sequence of synchronization haves +/// as the given one but with the opposite position and direction. +/// +/// \param[in] sync_haves A pointer to an `AMsyncHaves` struct. +/// \return An `AMsyncHaves` struct +/// \pre \p sync_haves `!= NULL`. +/// \internal +/// +/// #Safety +/// sync_haves must be a valid pointer to an AMsyncHaves +#[no_mangle] +pub unsafe extern "C" fn AMsyncHavesReversed(sync_haves: *const AMsyncHaves) -> AMsyncHaves { + if let Some(sync_haves) = sync_haves.as_ref() { + sync_haves.reversed() + } else { + Default::default() + } +} + +/// \memberof AMsyncHaves +/// \brief Creates an iterator at the starting position over the same sequence +/// of synchronization haves as the given one. +/// +/// \param[in] sync_haves A pointer to an `AMsyncHaves` struct. +/// \return An `AMsyncHaves` struct +/// \pre \p sync_haves `!= NULL`. +/// \internal +/// +/// #Safety +/// sync_haves must be a valid pointer to an AMsyncHaves +#[no_mangle] +pub unsafe extern "C" fn AMsyncHavesRewound(sync_haves: *const AMsyncHaves) -> AMsyncHaves { + if let Some(sync_haves) = sync_haves.as_ref() { + sync_haves.rewound() + } else { + Default::default() + } +} diff --git a/rust/automerge-c/src/sync/message.rs b/rust/automerge-c/src/sync/message.rs index bdb1db34..46a6d29a 100644 --- a/rust/automerge-c/src/sync/message.rs +++ b/rust/automerge-c/src/sync/message.rs @@ -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 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() + } } diff --git a/rust/automerge-c/src/sync/state.rs b/rust/automerge-c/src/sync/state.rs index 1d85ed98..1c1d316f 100644 --- a/rust/automerge-c/src/sync/state.rs +++ b/rust/automerge-c/src/sync/state.rs @@ -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 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::::new()) + Default::default() } /// \memberof AMsyncState @@ -202,31 +207,29 @@ pub unsafe extern "C" fn AMsyncStateTheirHaves( /// /// \param[in] sync_state A pointer to an `AMsyncState` struct. /// \param[out] has_value A pointer to a boolean flag that is set to `true` if -/// the returned `AMitems` struct is relevant, `false` -/// otherwise. -/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. -/// \pre \p sync_state `!= NULL` -/// \pre \p has_value `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// the returned `AMchangeHashes` struct is relevant, `false` +/// otherwise. +/// \return An `AMchangeHashes` struct. +/// \pre \p sync_state `!= NULL`. +/// \pre \p has_value `!= NULL`. /// \internal /// /// # Safety /// sync_state must be a valid pointer to an AMsyncState -/// has_value must be a valid pointer to a bool +/// has_value must be a valid pointer to a bool. #[no_mangle] pub unsafe extern "C" fn AMsyncStateTheirHeads( sync_state: *const AMsyncState, has_value: *mut bool, -) -> *mut AMresult { +) -> AMchangeHashes { if let Some(sync_state) = sync_state.as_ref() { if let Some(change_hashes) = &sync_state.as_ref().their_heads { *has_value = true; - return to_result(change_hashes.as_slice()); + return AMchangeHashes::new(change_hashes); } }; *has_value = false; - to_result(Vec::::new()) + Default::default() } /// \memberof AMsyncState @@ -234,29 +237,27 @@ pub unsafe extern "C" fn AMsyncStateTheirHeads( /// /// \param[in] sync_state A pointer to an `AMsyncState` struct. /// \param[out] has_value A pointer to a boolean flag that is set to `true` if -/// the returned `AMitems` struct is relevant, `false` -/// otherwise. -/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. -/// \pre \p sync_state `!= NULL` -/// \pre \p has_value `!= NULL` -/// \warning The returned `AMresult` struct pointer must be passed to -/// `AMresultFree()` in order to avoid a memory leak. +/// the returned `AMchangeHashes` struct is relevant, `false` +/// otherwise. +/// \return An `AMchangeHashes` struct. +/// \pre \p sync_state `!= NULL`. +/// \pre \p has_value `!= NULL`. /// \internal /// /// # Safety /// sync_state must be a valid pointer to an AMsyncState -/// has_value must be a valid pointer to a bool +/// has_value must be a valid pointer to a bool. #[no_mangle] pub unsafe extern "C" fn AMsyncStateTheirNeeds( sync_state: *const AMsyncState, has_value: *mut bool, -) -> *mut AMresult { +) -> AMchangeHashes { if let Some(sync_state) = sync_state.as_ref() { if let Some(change_hashes) = &sync_state.as_ref().their_need { *has_value = true; - return to_result(change_hashes.as_slice()); + return AMchangeHashes::new(change_hashes); } }; *has_value = false; - to_result(Vec::::new()) + Default::default() } diff --git a/rust/automerge-c/src/utils/result.c b/rust/automerge-c/src/utils/result.c deleted file mode 100644 index f922ca31..00000000 --- a/rust/automerge-c/src/utils/result.c +++ /dev/null @@ -1,33 +0,0 @@ -#include - -#include - -AMresult* AMresultFrom(int count, ...) { - AMresult* result = NULL; - bool is_ok = true; - va_list args; - va_start(args, count); - for (int i = 0; i != count; ++i) { - AMresult* src = va_arg(args, AMresult*); - AMresult* dest = result; - is_ok = (AMresultStatus(src) == AM_STATUS_OK); - if (is_ok) { - if (dest) { - result = AMresultCat(dest, src); - is_ok = (AMresultStatus(result) == AM_STATUS_OK); - AMresultFree(dest); - AMresultFree(src); - } else { - result = src; - } - } else { - AMresultFree(src); - } - } - va_end(args); - if (!is_ok) { - AMresultFree(result); - result = NULL; - } - return result; -} diff --git a/rust/automerge-c/src/utils/stack.c b/rust/automerge-c/src/utils/stack.c deleted file mode 100644 index 2cad7c5c..00000000 --- a/rust/automerge-c/src/utils/stack.c +++ /dev/null @@ -1,106 +0,0 @@ -#include -#include - -#include -#include - -void AMstackFree(AMstack** stack) { - if (stack) { - while (*stack) { - AMresultFree(AMstackPop(stack, NULL)); - } - } -} - -AMresult* AMstackPop(AMstack** stack, const AMresult* result) { - if (!stack) { - return NULL; - } - AMstack** prev = stack; - if (result) { - while (*prev && ((*prev)->result != result)) { - *prev = (*prev)->prev; - } - } - if (!*prev) { - return NULL; - } - AMstack* target = *prev; - *prev = target->prev; - AMresult* popped = target->result; - free(target); - return popped; -} - -AMresult* AMstackResult(AMstack** stack, AMresult* result, AMstackCallback callback, void* data) { - if (!stack) { - if (callback) { - /* Create a local stack so that the callback can still examine the - * result. */ - AMstack node = {.result = result, .prev = NULL}; - AMstack* stack = &node; - callback(&stack, data); - } else { - /* \note There is no reason to call this function when both the - * stack and the callback are null. */ - fprintf(stderr, "ERROR: NULL AMstackCallback!\n"); - } - /* \note Nothing can be returned without a stack regardless of - * whether or not the callback validated the result. */ - AMresultFree(result); - return NULL; - } - /* Always push the result onto the stack, even if it's null, so that the - * callback can examine it. */ - AMstack* next = calloc(1, sizeof(AMstack)); - *next = (AMstack){.result = result, .prev = *stack}; - AMstack* top = next; - *stack = top; - if (callback) { - if (!callback(stack, data)) { - /* The result didn't pass the callback's examination. */ - return NULL; - } - } else { - /* Report an obvious error. */ - if (result) { - AMbyteSpan const err_msg = AMresultError(result); - if (err_msg.src && err_msg.count) { - /* \note The callback may be null because the result is supposed - * to be examined externally so return it despite an - * error. */ - char* const cstr = AMstrdup(err_msg, NULL); - fprintf(stderr, "WARNING: %s.\n", cstr); - free(cstr); - } - } else { - /* \note There's no reason to call this function when both the - * result and the callback are null. */ - fprintf(stderr, "ERROR: NULL AMresult*!\n"); - return NULL; - } - } - return result; -} - -AMitem* AMstackItem(AMstack** stack, AMresult* result, AMstackCallback callback, void* data) { - AMitems items = AMstackItems(stack, result, callback, data); - return AMitemsNext(&items, 1); -} - -AMitems AMstackItems(AMstack** stack, AMresult* result, AMstackCallback callback, void* data) { - return (AMstackResult(stack, result, callback, data)) ? AMresultItems(result) : (AMitems){0}; -} - -size_t AMstackSize(AMstack const* const stack) { - if (!stack) { - return 0; - } - size_t count = 0; - AMstack const* prev = stack; - while (prev) { - ++count; - prev = prev->prev; - } - return count; -} \ No newline at end of file diff --git a/rust/automerge-c/src/utils/stack_callback_data.c b/rust/automerge-c/src/utils/stack_callback_data.c deleted file mode 100644 index f1e988d8..00000000 --- a/rust/automerge-c/src/utils/stack_callback_data.c +++ /dev/null @@ -1,9 +0,0 @@ -#include - -#include - -AMstackCallbackData* AMstackCallbackDataInit(AMvalType const bitmask, char const* const file, int const line) { - AMstackCallbackData* data = malloc(sizeof(AMstackCallbackData)); - *data = (AMstackCallbackData){.bitmask = bitmask, .file = file, .line = line}; - return data; -} diff --git a/rust/automerge-c/src/utils/string.c b/rust/automerge-c/src/utils/string.c deleted file mode 100644 index a0d1ebe3..00000000 --- a/rust/automerge-c/src/utils/string.c +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include - -#include - -char* AMstrdup(AMbyteSpan const str, char const* nul) { - if (!str.src) { - return NULL; - } else if (!str.count) { - return strdup(""); - } - nul = (nul) ? nul : "\\0"; - size_t const nul_len = strlen(nul); - char* dup = NULL; - size_t dup_len = 0; - char const* begin = str.src; - char const* end = begin; - for (size_t i = 0; i != str.count; ++i, ++end) { - if (!*end) { - size_t const len = end - begin; - size_t const alloc_len = dup_len + len + nul_len; - if (dup) { - dup = realloc(dup, alloc_len + 1); - } else { - dup = malloc(alloc_len + 1); - } - memcpy(dup + dup_len, begin, len); - memcpy(dup + dup_len + len, nul, nul_len); - dup[alloc_len] = '\0'; - begin = end + 1; - dup_len = alloc_len; - } - } - if (begin != end) { - size_t const len = end - begin; - size_t const alloc_len = dup_len + len; - if (dup) { - dup = realloc(dup, alloc_len + 1); - } else { - dup = malloc(alloc_len + 1); - } - memcpy(dup + dup_len, begin, len); - dup[alloc_len] = '\0'; - } - return dup; -} diff --git a/rust/automerge-c/test/CMakeLists.txt b/rust/automerge-c/test/CMakeLists.txt index 1759f140..704a27da 100644 --- a/rust/automerge-c/test/CMakeLists.txt +++ b/rust/automerge-c/test/CMakeLists.txt @@ -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 "$" +) -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 $ $ - 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 $ --output-on-failure diff --git a/rust/automerge-c/test/actor_id_tests.c b/rust/automerge-c/test/actor_id_tests.c index 918d6213..c98f2554 100644 --- a/rust/automerge-c/test/actor_id_tests.c +++ b/rust/automerge-c/test/actor_id_tests.c @@ -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); diff --git a/rust/automerge-c/test/base_state.c b/rust/automerge-c/test/base_state.c deleted file mode 100644 index 53325a99..00000000 --- a/rust/automerge-c/test/base_state.c +++ /dev/null @@ -1,17 +0,0 @@ -#include - -/* local */ -#include "base_state.h" - -int setup_base(void** state) { - BaseState* base_state = calloc(1, sizeof(BaseState)); - *state = base_state; - return 0; -} - -int teardown_base(void** state) { - BaseState* base_state = *state; - AMstackFree(&base_state->stack); - free(base_state); - return 0; -} diff --git a/rust/automerge-c/test/base_state.h b/rust/automerge-c/test/base_state.h deleted file mode 100644 index 3c4ff01b..00000000 --- a/rust/automerge-c/test/base_state.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef TESTS_BASE_STATE_H -#define TESTS_BASE_STATE_H - -#include - -/* local */ -#include -#include - -/** - * \struct BaseState - * \brief The shared state for one or more cmocka test cases. - */ -typedef struct { - /** A stack of results. */ - AMstack* stack; -} BaseState; - -/** - * \memberof BaseState - * \brief Sets up the shared state for one or more cmocka test cases. - * - * \param[in,out] state A pointer to a pointer to a `BaseState` struct. - * \pre \p state `!= NULL`. - * \warning The `BaseState` struct returned through \p state must be - * passed to `teardown_base()` in order to avoid a memory leak. - */ -int setup_base(void** state); - -/** - * \memberof BaseState - * \brief Tears down the shared state for one or more cmocka test cases. - * - * \param[in] state A pointer to a pointer to a `BaseState` struct. - * \pre \p state `!= NULL`. - */ -int teardown_base(void** state); - -#endif /* TESTS_BASE_STATE_H */ diff --git a/rust/automerge-c/test/byte_span_tests.c b/rust/automerge-c/test/byte_span_tests.c deleted file mode 100644 index 0b1c86a1..00000000 --- a/rust/automerge-c/test/byte_span_tests.c +++ /dev/null @@ -1,119 +0,0 @@ -#include -#include -#include -#include -#include -#include - -/* third-party */ -#include - -/* local */ -#include -#include - -static void test_AMbytes(void** state) { - static char const DATA[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}; - - AMbyteSpan bytes = AMbytes(DATA, sizeof(DATA)); - assert_int_equal(bytes.count, sizeof(DATA)); - assert_memory_equal(bytes.src, DATA, bytes.count); - assert_ptr_equal(bytes.src, DATA); - /* Empty view */ - bytes = AMbytes(DATA, 0); - assert_int_equal(bytes.count, 0); - assert_ptr_equal(bytes.src, DATA); - /* Invalid array */ - bytes = AMbytes(NULL, SIZE_MAX); - assert_int_not_equal(bytes.count, SIZE_MAX); - assert_int_equal(bytes.count, 0); - assert_ptr_equal(bytes.src, NULL); -} - -static void test_AMstr(void** state) { - AMbyteSpan str = AMstr("abcdefghijkl"); - assert_int_equal(str.count, strlen("abcdefghijkl")); - assert_memory_equal(str.src, "abcdefghijkl", str.count); - /* Empty string */ - static char const* const EMPTY = ""; - - str = AMstr(EMPTY); - assert_int_equal(str.count, 0); - assert_ptr_equal(str.src, EMPTY); - /* Invalid string */ - str = AMstr(NULL); - assert_int_equal(str.count, 0); - assert_ptr_equal(str.src, NULL); -} - -static void test_AMstrCmp(void** state) { - /* Length ordering */ - assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("abcdefghijkl")), -1); - assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("abcdefghijkl")), 0); - assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("abcdef")), 1); - /* Lexicographical ordering */ - assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("ghijkl")), -1); - assert_int_equal(AMstrCmp(AMstr("ghijkl"), AMstr("abcdef")), 1); - /* Case ordering */ - assert_int_equal(AMstrCmp(AMstr("ABCDEFGHIJKL"), AMstr("abcdefghijkl")), -1); - assert_int_equal(AMstrCmp(AMstr("ABCDEFGHIJKL"), AMstr("ABCDEFGHIJKL")), 0); - assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("ABCDEFGHIJKL")), 1); - assert_int_equal(AMstrCmp(AMstr("ABCDEFGHIJKL"), AMstr("abcdef")), -1); - assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("ABCDEFGHIJKL")), 1); - assert_int_equal(AMstrCmp(AMstr("GHIJKL"), AMstr("abcdef")), -1); - assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("GHIJKL")), 1); - /* NUL character inclusion */ - static char const SRC[] = {'a', 'b', 'c', 'd', 'e', 'f', '\0', 'g', 'h', 'i', 'j', 'k', 'l'}; - static AMbyteSpan const NUL_STR = {.src = SRC, .count = 13}; - - assert_int_equal(AMstrCmp(AMstr("abcdef"), NUL_STR), -1); - assert_int_equal(AMstrCmp(NUL_STR, NUL_STR), 0); - assert_int_equal(AMstrCmp(NUL_STR, AMstr("abcdef")), 1); - /* Empty string */ - assert_int_equal(AMstrCmp(AMstr(""), AMstr("abcdefghijkl")), -1); - assert_int_equal(AMstrCmp(AMstr(""), AMstr("")), 0); - assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("")), 1); - /* Invalid string */ - assert_int_equal(AMstrCmp(AMstr(NULL), AMstr("abcdefghijkl")), -1); - assert_int_equal(AMstrCmp(AMstr(NULL), AMstr(NULL)), 0); - assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr(NULL)), 1); -} - -static void test_AMstrdup(void** state) { - static char const SRC[] = {'a', 'b', 'c', '\0', 'd', 'e', 'f', '\0', 'g', 'h', 'i', '\0', 'j', 'k', 'l'}; - static AMbyteSpan const NUL_STR = {.src = SRC, .count = 15}; - - /* Default substitution ("\\0") for NUL */ - char* dup = AMstrdup(NUL_STR, NULL); - assert_int_equal(strlen(dup), 18); - assert_string_equal(dup, "abc\\0def\\0ghi\\0jkl"); - free(dup); - /* Arbitrary substitution for NUL */ - dup = AMstrdup(NUL_STR, ":-O"); - assert_int_equal(strlen(dup), 21); - assert_string_equal(dup, "abc:-Odef:-Oghi:-Ojkl"); - free(dup); - /* Empty substitution for NUL */ - dup = AMstrdup(NUL_STR, ""); - assert_int_equal(strlen(dup), 12); - assert_string_equal(dup, "abcdefghijkl"); - free(dup); - /* Empty string */ - dup = AMstrdup(AMstr(""), NULL); - assert_int_equal(strlen(dup), 0); - assert_string_equal(dup, ""); - free(dup); - /* Invalid string */ - assert_null(AMstrdup(AMstr(NULL), NULL)); -} - -int run_byte_span_tests(void) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_AMbytes), - cmocka_unit_test(test_AMstr), - cmocka_unit_test(test_AMstrCmp), - cmocka_unit_test(test_AMstrdup), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/rust/automerge-c/test/cmocka_utils.c b/rust/automerge-c/test/cmocka_utils.c deleted file mode 100644 index 37c57fb1..00000000 --- a/rust/automerge-c/test/cmocka_utils.c +++ /dev/null @@ -1,88 +0,0 @@ -#include -#include -#include -#include - -/* third-party */ -#include -#include -#include -#include - -/* local */ -#include "cmocka_utils.h" - -/** - * \brief Assert that the given expression is true and report failure in terms - * of a line number within a file. - * - * \param[in] c An expression. - * \param[in] file A file's full path string. - * \param[in] line A line number. - */ -#define assert_true_where(c, file, line) _assert_true(cast_ptr_to_largest_integral_type(c), #c, file, line) - -/** - * \brief Assert that the given pointer is non-NULL and report failure in terms - * of a line number within a file. - * - * \param[in] c An expression. - * \param[in] file A file's full path string. - * \param[in] line A line number. - */ -#define assert_non_null_where(c, file, line) assert_true_where(c, file, line) - -/** - * \brief Forces the test to fail immediately and quit, printing the reason in - * terms of a line number within a file. - * - * \param[in] msg A message string into which \p str is interpolated. - * \param[in] str An owned string. - * \param[in] file A file's full path string. - * \param[in] line A line number. - */ -#define fail_msg_where(msg, str, file, line) \ - do { \ - print_error("ERROR: " msg "\n", str); \ - _fail(file, line); \ - } while (0) - -/** - * \brief Forces the test to fail immediately and quit, printing the reason in - * terms of a line number within a file. - * - * \param[in] msg A message string into which \p view.src is interpolated. - * \param[in] view A UTF-8 string view as an `AMbyteSpan` struct. - * \param[in] file A file's full path string. - * \param[in] line A line number. - */ -#define fail_msg_view_where(msg, view, file, line) \ - do { \ - char* const str = AMstrdup(view, NULL); \ - print_error("ERROR: " msg "\n", str); \ - free(str); \ - _fail(file, line); \ - } while (0) - -bool cmocka_cb(AMstack** stack, void* data) { - assert_non_null(data); - AMstackCallbackData* const sc_data = (AMstackCallbackData*)data; - assert_non_null_where(stack, sc_data->file, sc_data->line); - assert_non_null_where(*stack, sc_data->file, sc_data->line); - assert_non_null_where((*stack)->result, sc_data->file, sc_data->line); - if (AMresultStatus((*stack)->result) != AM_STATUS_OK) { - fail_msg_view_where("%s", AMresultError((*stack)->result), sc_data->file, sc_data->line); - return false; - } - /* Test that the types of all item values are members of the mask. */ - AMitems items = AMresultItems((*stack)->result); - AMitem* item = NULL; - while ((item = AMitemsNext(&items, 1)) != NULL) { - AMvalType const tag = AMitemValType(item); - if (!(tag & sc_data->bitmask)) { - fail_msg_where("Unexpected value type `%s`.", AMvalTypeToString(tag), sc_data->file, sc_data->line); - return false; - } - } - return true; -} diff --git a/rust/automerge-c/test/cmocka_utils.h b/rust/automerge-c/test/cmocka_utils.h index b6611bcc..1b488362 100644 --- a/rust/automerge-c/test/cmocka_utils.h +++ b/rust/automerge-c/test/cmocka_utils.h @@ -1,42 +1,22 @@ -#ifndef TESTS_CMOCKA_UTILS_H -#define TESTS_CMOCKA_UTILS_H +#ifndef CMOCKA_UTILS_H +#define CMOCKA_UTILS_H -#include #include /* third-party */ -#include #include -/* local */ -#include "base_state.h" - /** * \brief Forces the test to fail immediately and quit, printing the reason. * - * \param[in] msg A message string into which \p view.src is interpolated. - * \param[in] view A UTF-8 string view as an `AMbyteSpan` struct. + * \param[in] view A string view as an `AMbyteSpan` struct. */ -#define fail_msg_view(msg, view) \ - do { \ - char* const c_str = AMstrdup(view, NULL); \ - print_error("ERROR: " msg "\n", c_str); \ - free(c_str); \ - fail(); \ - } while (0) +#define fail_msg_view(msg, view) do { \ + char* const c_str = test_calloc(1, view.count + 1); \ + strncpy(c_str, view.src, view.count); \ + print_error(msg, c_str); \ + test_free(c_str); \ + fail(); \ +} while (0) -/** - * \brief Validates the top result in a stack based upon the parameters - * specified within the given data structure and reports violations - * using cmocka assertions. - * - * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. - * \param[in] data A pointer to an owned `AMpushData` struct. - * \return `true` if the top `AMresult` struct in \p stack is valid, `false` - * otherwise. - * \pre \p stack `!= NULL`. - * \pre \p data `!= NULL`. - */ -bool cmocka_cb(AMstack** stack, void* data); - -#endif /* TESTS_CMOCKA_UTILS_H */ +#endif /* CMOCKA_UTILS_H */ diff --git a/rust/automerge-c/test/doc_state.c b/rust/automerge-c/test/doc_state.c deleted file mode 100644 index 3cbece50..00000000 --- a/rust/automerge-c/test/doc_state.c +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include -#include - -/* third-party */ -#include - -/* local */ -#include -#include "cmocka_utils.h" -#include "doc_state.h" - -int setup_doc(void** state) { - DocState* doc_state = test_calloc(1, sizeof(DocState)); - setup_base((void**)&doc_state->base_state); - AMitemToDoc(AMstackItem(&doc_state->base_state->stack, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), - &doc_state->doc); - *state = doc_state; - return 0; -} - -int teardown_doc(void** state) { - DocState* doc_state = *state; - teardown_base((void**)&doc_state->base_state); - test_free(doc_state); - return 0; -} diff --git a/rust/automerge-c/test/doc_state.h b/rust/automerge-c/test/doc_state.h deleted file mode 100644 index 525a49fa..00000000 --- a/rust/automerge-c/test/doc_state.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef TESTS_DOC_STATE_H -#define TESTS_DOC_STATE_H - -/* local */ -#include -#include "base_state.h" - -typedef struct { - BaseState* base_state; - AMdoc* doc; -} DocState; - -int setup_doc(void** state); - -int teardown_doc(void** state); - -#endif /* TESTS_DOC_STATE_H */ diff --git a/rust/automerge-c/test/doc_tests.c b/rust/automerge-c/test/doc_tests.c index c1d21928..217a4862 100644 --- a/rust/automerge-c/test/doc_tests.c +++ b/rust/automerge-c/test/doc_tests.c @@ -9,14 +9,12 @@ /* local */ #include -#include -#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); diff --git a/rust/automerge-c/test/enum_string_tests.c b/rust/automerge-c/test/enum_string_tests.c deleted file mode 100644 index 11131e43..00000000 --- a/rust/automerge-c/test/enum_string_tests.c +++ /dev/null @@ -1,148 +0,0 @@ -#include -#include -#include -#include -#include - -/* third-party */ -#include - -/* local */ -#include -#include - -#define assert_to_string(function, tag) assert_string_equal(function(tag), #tag) - -#define assert_from_string(function, type, tag) \ - do { \ - type out; \ - assert_true(function(&out, #tag)); \ - assert_int_equal(out, tag); \ - } while (0) - -static void test_AMidxTypeToString(void** state) { - assert_to_string(AMidxTypeToString, AM_IDX_TYPE_DEFAULT); - assert_to_string(AMidxTypeToString, AM_IDX_TYPE_KEY); - assert_to_string(AMidxTypeToString, AM_IDX_TYPE_POS); - /* Zero tag */ - assert_string_equal(AMidxTypeToString(0), "AM_IDX_TYPE_DEFAULT"); - /* Invalid tag */ - assert_string_equal(AMidxTypeToString(-1), "???"); -} - -static void test_AMidxTypeFromString(void** state) { - assert_from_string(AMidxTypeFromString, AMidxType, AM_IDX_TYPE_DEFAULT); - assert_from_string(AMidxTypeFromString, AMidxType, AM_IDX_TYPE_KEY); - assert_from_string(AMidxTypeFromString, AMidxType, AM_IDX_TYPE_POS); - /* Invalid tag */ - AMidxType out = -1; - assert_false(AMidxTypeFromString(&out, "???")); - assert_int_equal(out, (AMidxType)-1); -} - -static void test_AMobjTypeToString(void** state) { - assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_DEFAULT); - assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_LIST); - assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_MAP); - assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_TEXT); - /* Zero tag */ - assert_string_equal(AMobjTypeToString(0), "AM_OBJ_TYPE_DEFAULT"); - /* Invalid tag */ - assert_string_equal(AMobjTypeToString(-1), "???"); -} - -static void test_AMobjTypeFromString(void** state) { - assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_DEFAULT); - assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_LIST); - assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_MAP); - assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_TEXT); - /* Invalid tag */ - AMobjType out = -1; - assert_false(AMobjTypeFromString(&out, "???")); - assert_int_equal(out, (AMobjType)-1); -} - -static void test_AMstatusToString(void** state) { - assert_to_string(AMstatusToString, AM_STATUS_ERROR); - assert_to_string(AMstatusToString, AM_STATUS_INVALID_RESULT); - assert_to_string(AMstatusToString, AM_STATUS_OK); - /* Zero tag */ - assert_string_equal(AMstatusToString(0), "AM_STATUS_OK"); - /* Invalid tag */ - assert_string_equal(AMstatusToString(-1), "???"); -} - -static void test_AMstatusFromString(void** state) { - assert_from_string(AMstatusFromString, AMstatus, AM_STATUS_ERROR); - assert_from_string(AMstatusFromString, AMstatus, AM_STATUS_INVALID_RESULT); - assert_from_string(AMstatusFromString, AMstatus, AM_STATUS_OK); - /* Invalid tag */ - AMstatus out = -1; - assert_false(AMstatusFromString(&out, "???")); - assert_int_equal(out, (AMstatus)-1); -} - -static void test_AMvalTypeToString(void** state) { - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_ACTOR_ID); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_BOOL); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_BYTES); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_CHANGE); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_CHANGE_HASH); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_COUNTER); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_DEFAULT); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_DOC); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_F64); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_INT); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_NULL); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_OBJ_TYPE); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_STR); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_SYNC_HAVE); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_SYNC_MESSAGE); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_SYNC_STATE); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_TIMESTAMP); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_UINT); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_UNKNOWN); - assert_to_string(AMvalTypeToString, AM_VAL_TYPE_VOID); - /* Zero tag */ - assert_string_equal(AMvalTypeToString(0), "AM_VAL_TYPE_DEFAULT"); - /* Invalid tag */ - assert_string_equal(AMvalTypeToString(-1), "???"); -} - -static void test_AMvalTypeFromString(void** state) { - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_ACTOR_ID); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_BOOL); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_BYTES); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_CHANGE); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_CHANGE_HASH); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_COUNTER); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_DEFAULT); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_DOC); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_F64); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_INT); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_NULL); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_OBJ_TYPE); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_STR); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_SYNC_HAVE); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_SYNC_MESSAGE); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_SYNC_STATE); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_TIMESTAMP); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_UINT); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_UNKNOWN); - assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_VOID); - /* Invalid tag */ - AMvalType out = -1; - assert_false(AMvalTypeFromString(&out, "???")); - assert_int_equal(out, (AMvalType)-1); -} - -int run_enum_string_tests(void) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_AMidxTypeToString), cmocka_unit_test(test_AMidxTypeFromString), - cmocka_unit_test(test_AMobjTypeToString), cmocka_unit_test(test_AMobjTypeFromString), - cmocka_unit_test(test_AMstatusToString), cmocka_unit_test(test_AMstatusFromString), - cmocka_unit_test(test_AMvalTypeToString), cmocka_unit_test(test_AMvalTypeFromString), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/rust/automerge-c/test/group_state.c b/rust/automerge-c/test/group_state.c new file mode 100644 index 00000000..0ee14317 --- /dev/null +++ b/rust/automerge-c/test/group_state.c @@ -0,0 +1,27 @@ +#include +#include +#include + +/* third-party */ +#include + +/* local */ +#include "group_state.h" +#include "stack_utils.h" + +int group_setup(void** state) { + GroupState* group_state = test_calloc(1, sizeof(GroupState)); + group_state->doc = AMpush(&group_state->stack, + AMcreate(NULL), + AM_VALUE_DOC, + cmocka_cb).doc; + *state = group_state; + return 0; +} + +int group_teardown(void** state) { + GroupState* group_state = *state; + AMfreeStack(&group_state->stack); + test_free(group_state); + return 0; +} diff --git a/rust/automerge-c/test/group_state.h b/rust/automerge-c/test/group_state.h new file mode 100644 index 00000000..a71d9dc9 --- /dev/null +++ b/rust/automerge-c/test/group_state.h @@ -0,0 +1,16 @@ +#ifndef GROUP_STATE_H +#define GROUP_STATE_H + +/* local */ +#include + +typedef struct { + AMresultStack* stack; + AMdoc* doc; +} GroupState; + +int group_setup(void** state); + +int group_teardown(void** state); + +#endif /* GROUP_STATE_H */ diff --git a/rust/automerge-c/test/item_tests.c b/rust/automerge-c/test/item_tests.c deleted file mode 100644 index a30b0556..00000000 --- a/rust/automerge-c/test/item_tests.c +++ /dev/null @@ -1,94 +0,0 @@ -#include -#include -#include -#include -#include - -/* third-party */ -#include - -/* local */ -#include -#include -#include "cmocka_utils.h" -#include "doc_state.h" - -static void test_AMitemResult(void** state) { - enum { ITEM_COUNT = 1000 }; - - DocState* doc_state = *state; - AMstack** stack_ptr = &doc_state->base_state->stack; - /* Append the strings to a list so that they'll be in numerical order. */ - AMobjId const* const list = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); - for (size_t pos = 0; pos != ITEM_COUNT; ++pos) { - size_t const count = snprintf(NULL, 0, "%zu", pos); - char* const src = test_calloc(count + 1, sizeof(char)); - assert_int_equal(sprintf(src, "%zu", pos), count); - AMstackItem(NULL, AMlistPutStr(doc_state->doc, list, pos, true, AMbytes(src, count)), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); - test_free(src); - } - /* Get an item iterator. */ - AMitems items = AMstackItems(stack_ptr, AMlistRange(doc_state->doc, list, 0, SIZE_MAX, NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR)); - /* Get the item iterator's result so that it can be freed later. */ - AMresult const* const items_result = (*stack_ptr)->result; - /* Iterate over all of the items and copy their pointers into an array. */ - AMitem* item_ptrs[ITEM_COUNT] = {NULL}; - AMitem* item = NULL; - for (size_t pos = 0; (item = AMitemsNext(&items, 1)) != NULL; ++pos) { - /* The item's reference count should be 1. */ - assert_int_equal(AMitemRefCount(item), 1); - if (pos & 1) { - /* Create a redundant result for an odd item. */ - AMitem* const new_item = AMstackItem(stack_ptr, AMitemResult(item), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - /* The item's old and new pointers will never match. */ - assert_ptr_not_equal(new_item, item); - /* The item's reference count will have been incremented. */ - assert_int_equal(AMitemRefCount(item), 2); - assert_int_equal(AMitemRefCount(new_item), 2); - /* The item's old and new indices should match. */ - assert_int_equal(AMitemIdxType(item), AMitemIdxType(new_item)); - assert_int_equal(AMitemIdxType(item), AM_IDX_TYPE_POS); - size_t pos, new_pos; - assert_true(AMitemPos(item, &pos)); - assert_true(AMitemPos(new_item, &new_pos)); - assert_int_equal(pos, new_pos); - /* The item's old and new object IDs should match. */ - AMobjId const* const obj_id = AMitemObjId(item); - AMobjId const* const new_obj_id = AMitemObjId(new_item); - assert_true(AMobjIdEqual(obj_id, new_obj_id)); - /* The item's old and new value types should match. */ - assert_int_equal(AMitemValType(item), AMitemValType(new_item)); - /* The item's old and new string values should match. */ - AMbyteSpan str; - assert_true(AMitemToStr(item, &str)); - AMbyteSpan new_str; - assert_true(AMitemToStr(new_item, &new_str)); - assert_int_equal(str.count, new_str.count); - assert_memory_equal(str.src, new_str.src, new_str.count); - /* The item's old and new object IDs are one and the same. */ - assert_ptr_equal(obj_id, new_obj_id); - /* The item's old and new string values are one and the same. */ - assert_ptr_equal(str.src, new_str.src); - /* Save the item's new pointer. */ - item_ptrs[pos] = new_item; - } - } - /* Free the item iterator's result. */ - AMresultFree(AMstackPop(stack_ptr, items_result)); - /* An odd item's reference count should be 1 again. */ - for (size_t pos = 1; pos < ITEM_COUNT; pos += 2) { - assert_int_equal(AMitemRefCount(item_ptrs[pos]), 1); - } -} - -int run_item_tests(void) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_AMitemResult), - }; - - return cmocka_run_group_tests(tests, setup_doc, teardown_doc); -} diff --git a/rust/automerge-c/test/list_tests.c b/rust/automerge-c/test/list_tests.c index 723dd038..f9bbb340 100644 --- a/rust/automerge-c/test/list_tests.c +++ b/rust/automerge-c/test/list_tests.c @@ -11,417 +11,367 @@ /* local */ #include -#include -#include "base_state.h" #include "cmocka_utils.h" -#include "doc_state.h" +#include "group_state.h" #include "macro_utils.h" +#include "stack_utils.h" static void test_AMlistIncrement(void** state) { - DocState* doc_state = *state; - AMstack** stack_ptr = &doc_state->base_state->stack; - AMobjId const* const list = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); - AMstackItem(NULL, AMlistPutCounter(doc_state->doc, list, 0, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - int64_t counter; - assert_true(AMitemToCounter( - AMstackItem(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_COUNTER)), - &counter)); - assert_int_equal(counter, 0); - AMresultFree(AMstackPop(stack_ptr, NULL)); - AMstackItem(NULL, AMlistIncrement(doc_state->doc, list, 0, 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - assert_true(AMitemToCounter( - AMstackItem(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_COUNTER)), - &counter)); - assert_int_equal(counter, 3); - AMresultFree(AMstackPop(stack_ptr, NULL)); + GroupState* group_state = *state; + AMobjId const* const list = AMpush( + &group_state->stack, + AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; + AMfree(AMlistPutCounter(group_state->doc, list, 0, true, 0)); + assert_int_equal(AMpush(&group_state->stack, + AMlistGet(group_state->doc, list, 0, NULL), + AM_VALUE_COUNTER, + cmocka_cb).counter, 0); + AMfree(AMpop(&group_state->stack)); + AMfree(AMlistIncrement(group_state->doc, list, 0, 3)); + assert_int_equal(AMpush(&group_state->stack, + AMlistGet(group_state->doc, list, 0, NULL), + AM_VALUE_COUNTER, + cmocka_cb).counter, 3); + AMfree(AMpop(&group_state->stack)); } -#define test_AMlistPut(suffix, mode) test_AMlistPut##suffix##_##mode +#define test_AMlistPut(suffix, mode) test_AMlistPut ## suffix ## _ ## mode -#define static_void_test_AMlistPut(suffix, mode, type, scalar_value) \ - static void test_AMlistPut##suffix##_##mode(void** state) { \ - DocState* doc_state = *state; \ - AMstack** stack_ptr = &doc_state->base_state->stack; \ - AMobjId const* const list = AMitemObjId( \ - AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), \ - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ - AMstackItem(NULL, AMlistPut##suffix(doc_state->doc, list, 0, !strcmp(#mode, "insert"), scalar_value), \ - cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); \ - type value; \ - assert_true(AMitemTo##suffix(AMstackItem(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), cmocka_cb, \ - AMexpect(suffix_to_val_type(#suffix))), \ - &value)); \ - assert_true(value == scalar_value); \ - AMresultFree(AMstackPop(stack_ptr, NULL)); \ - } +#define static_void_test_AMlistPut(suffix, mode, member, scalar_value) \ +static void test_AMlistPut ## suffix ## _ ## mode(void **state) { \ + GroupState* group_state = *state; \ + AMobjId const* const list = AMpush( \ + &group_state->stack, \ + AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),\ + AM_VALUE_OBJ_ID, \ + cmocka_cb).obj_id; \ + AMfree(AMlistPut ## suffix(group_state->doc, \ + list, \ + 0, \ + !strcmp(#mode, "insert"), \ + scalar_value)); \ + assert_true(AMpush( \ + &group_state->stack, \ + AMlistGet(group_state->doc, list, 0, NULL), \ + AMvalue_discriminant(#suffix), \ + cmocka_cb).member == scalar_value); \ + AMfree(AMpop(&group_state->stack)); \ +} -#define test_AMlistPutBytes(mode) test_AMlistPutBytes##_##mode +#define test_AMlistPutBytes(mode) test_AMlistPutBytes ## _ ## mode -#define static_void_test_AMlistPutBytes(mode, bytes_value) \ - static void test_AMlistPutBytes_##mode(void** state) { \ - static size_t const BYTES_SIZE = sizeof(bytes_value) / sizeof(uint8_t); \ - \ - DocState* doc_state = *state; \ - AMstack** stack_ptr = &doc_state->base_state->stack; \ - AMobjId const* const list = AMitemObjId( \ - AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), \ - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ - AMstackItem( \ - NULL, AMlistPutBytes(doc_state->doc, list, 0, !strcmp(#mode, "insert"), AMbytes(bytes_value, BYTES_SIZE)), \ - cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); \ - AMbyteSpan bytes; \ - assert_true(AMitemToBytes( \ - AMstackItem(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), \ - &bytes)); \ - assert_int_equal(bytes.count, BYTES_SIZE); \ - assert_memory_equal(bytes.src, bytes_value, BYTES_SIZE); \ - AMresultFree(AMstackPop(stack_ptr, NULL)); \ - } +#define static_void_test_AMlistPutBytes(mode, bytes_value) \ +static void test_AMlistPutBytes_ ## mode(void **state) { \ + static size_t const BYTES_SIZE = sizeof(bytes_value) / sizeof(uint8_t); \ + \ + GroupState* group_state = *state; \ + AMobjId const* const list = AMpush( \ + &group_state->stack, \ + AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),\ + AM_VALUE_OBJ_ID, \ + cmocka_cb).obj_id; \ + AMfree(AMlistPutBytes(group_state->doc, \ + list, \ + 0, \ + !strcmp(#mode, "insert"), \ + AMbytes(bytes_value, BYTES_SIZE))); \ + AMbyteSpan const bytes = AMpush( \ + &group_state->stack, \ + AMlistGet(group_state->doc, list, 0, NULL), \ + AM_VALUE_BYTES, \ + cmocka_cb).bytes; \ + assert_int_equal(bytes.count, BYTES_SIZE); \ + assert_memory_equal(bytes.src, bytes_value, BYTES_SIZE); \ + AMfree(AMpop(&group_state->stack)); \ +} -#define test_AMlistPutNull(mode) test_AMlistPutNull_##mode +#define test_AMlistPutNull(mode) test_AMlistPutNull_ ## mode -#define static_void_test_AMlistPutNull(mode) \ - static void test_AMlistPutNull_##mode(void** state) { \ - DocState* doc_state = *state; \ - AMstack** stack_ptr = &doc_state->base_state->stack; \ - AMobjId const* const list = AMitemObjId( \ - AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), \ - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ - AMstackItem(NULL, AMlistPutNull(doc_state->doc, list, 0, !strcmp(#mode, "insert")), cmocka_cb, \ - AMexpect(AM_VAL_TYPE_VOID)); \ - AMresult* result = AMstackResult(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), NULL, NULL); \ - if (AMresultStatus(result) != AM_STATUS_OK) { \ - fail_msg_view("%s", AMresultError(result)); \ - } \ - assert_int_equal(AMresultSize(result), 1); \ - assert_int_equal(AMitemValType(AMresultItem(result)), AM_VAL_TYPE_NULL); \ - AMresultFree(AMstackPop(stack_ptr, NULL)); \ - } +#define static_void_test_AMlistPutNull(mode) \ +static void test_AMlistPutNull_ ## mode(void **state) { \ + GroupState* group_state = *state; \ + AMobjId const* const list = AMpush( \ + &group_state->stack, \ + AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),\ + AM_VALUE_OBJ_ID, \ + cmocka_cb).obj_id; \ + AMfree(AMlistPutNull(group_state->doc, \ + list, \ + 0, \ + !strcmp(#mode, "insert"))); \ + AMresult* const result = AMlistGet(group_state->doc, list, 0, NULL); \ + if (AMresultStatus(result) != AM_STATUS_OK) { \ + fail_msg_view("%s", AMerrorMessage(result)); \ + } \ + assert_int_equal(AMresultSize(result), 1); \ + assert_int_equal(AMresultValue(result).tag, AM_VALUE_NULL); \ + AMfree(result); \ +} -#define test_AMlistPutObject(label, mode) test_AMlistPutObject_##label##_##mode +#define test_AMlistPutObject(label, mode) test_AMlistPutObject_ ## label ## _ ## mode -#define static_void_test_AMlistPutObject(label, mode) \ - static void test_AMlistPutObject_##label##_##mode(void** state) { \ - DocState* doc_state = *state; \ - AMstack** stack_ptr = &doc_state->base_state->stack; \ - AMobjId const* const list = AMitemObjId( \ - AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), \ - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ - AMobjType const obj_type = suffix_to_obj_type(#label); \ - AMobjId const* const obj_id = AMitemObjId( \ - AMstackItem(stack_ptr, AMlistPutObject(doc_state->doc, list, 0, !strcmp(#mode, "insert"), obj_type), \ - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ - assert_non_null(obj_id); \ - assert_int_equal(AMobjObjType(doc_state->doc, obj_id), obj_type); \ - assert_int_equal(AMobjSize(doc_state->doc, obj_id, NULL), 0); \ - AMresultFree(AMstackPop(stack_ptr, NULL)); \ - } +#define static_void_test_AMlistPutObject(label, mode) \ +static void test_AMlistPutObject_ ## label ## _ ## mode(void **state) { \ + GroupState* group_state = *state; \ + AMobjId const* const list = AMpush( \ + &group_state->stack, \ + AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),\ + AM_VALUE_OBJ_ID, \ + cmocka_cb).obj_id; \ + AMobjType const obj_type = AMobjType_tag(#label); \ + if (obj_type != AM_OBJ_TYPE_VOID) { \ + AMobjId const* const obj_id = AMpush( \ + &group_state->stack, \ + AMlistPutObject(group_state->doc, \ + list, \ + 0, \ + !strcmp(#mode, "insert"), \ + obj_type), \ + AM_VALUE_OBJ_ID, \ + cmocka_cb).obj_id; \ + assert_non_null(obj_id); \ + assert_int_equal(AMobjObjType(group_state->doc, obj_id), obj_type); \ + assert_int_equal(AMobjSize(group_state->doc, obj_id, NULL), 0); \ + } \ + else { \ + AMpush(&group_state->stack, \ + AMlistPutObject(group_state->doc, \ + list, \ + 0, \ + !strcmp(#mode, "insert"), \ + obj_type), \ + AM_VALUE_VOID, \ + NULL); \ + assert_int_not_equal(AMresultStatus(group_state->stack->result), \ + AM_STATUS_OK); \ + } \ + AMfree(AMpop(&group_state->stack)); \ +} -#define test_AMlistPutStr(mode) test_AMlistPutStr##_##mode +#define test_AMlistPutStr(mode) test_AMlistPutStr ## _ ## mode -#define static_void_test_AMlistPutStr(mode, str_value) \ - static void test_AMlistPutStr_##mode(void** state) { \ - DocState* doc_state = *state; \ - AMstack** stack_ptr = &doc_state->base_state->stack; \ - AMobjId const* const list = AMitemObjId( \ - AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), \ - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ - AMstackItem(NULL, AMlistPutStr(doc_state->doc, list, 0, !strcmp(#mode, "insert"), AMstr(str_value)), \ - cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); \ - AMbyteSpan str; \ - assert_true(AMitemToStr( \ - AMstackItem(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), \ - &str)); \ - assert_int_equal(str.count, strlen(str_value)); \ - assert_memory_equal(str.src, str_value, str.count); \ - AMresultFree(AMstackPop(stack_ptr, NULL)); \ - } +#define static_void_test_AMlistPutStr(mode, str_value) \ +static void test_AMlistPutStr_ ## mode(void **state) { \ + GroupState* group_state = *state; \ + AMobjId const* const list = AMpush( \ + &group_state->stack, \ + AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),\ + AM_VALUE_OBJ_ID, \ + cmocka_cb).obj_id; \ + AMfree(AMlistPutStr(group_state->doc, \ + list, \ + 0, \ + !strcmp(#mode, "insert"), \ + AMstr(str_value))); \ + AMbyteSpan const str = AMpush( \ + &group_state->stack, \ + AMlistGet(group_state->doc, list, 0, NULL), \ + AM_VALUE_STR, \ + cmocka_cb).str; \ + assert_int_equal(str.count, strlen(str_value)); \ + assert_memory_equal(str.src, str_value, str.count); \ + AMfree(AMpop(&group_state->stack)); \ +} -static_void_test_AMlistPut(Bool, insert, bool, true); +static_void_test_AMlistPut(Bool, insert, boolean, true) -static_void_test_AMlistPut(Bool, update, bool, true); +static_void_test_AMlistPut(Bool, update, boolean, true) static uint8_t const BYTES_VALUE[] = {INT8_MIN, INT8_MAX / 2, INT8_MAX}; -static_void_test_AMlistPutBytes(insert, BYTES_VALUE); +static_void_test_AMlistPutBytes(insert, BYTES_VALUE) -static_void_test_AMlistPutBytes(update, BYTES_VALUE); +static_void_test_AMlistPutBytes(update, BYTES_VALUE) -static_void_test_AMlistPut(Counter, insert, int64_t, INT64_MAX); +static_void_test_AMlistPut(Counter, insert, counter, INT64_MAX) -static_void_test_AMlistPut(Counter, update, int64_t, INT64_MAX); +static_void_test_AMlistPut(Counter, update, counter, INT64_MAX) -static_void_test_AMlistPut(F64, insert, double, DBL_MAX); +static_void_test_AMlistPut(F64, insert, f64, DBL_MAX) -static_void_test_AMlistPut(F64, update, double, DBL_MAX); +static_void_test_AMlistPut(F64, update, f64, DBL_MAX) -static_void_test_AMlistPut(Int, insert, int64_t, INT64_MAX); +static_void_test_AMlistPut(Int, insert, int_, INT64_MAX) -static_void_test_AMlistPut(Int, update, int64_t, INT64_MAX); +static_void_test_AMlistPut(Int, update, int_, INT64_MAX) -static_void_test_AMlistPutNull(insert); +static_void_test_AMlistPutNull(insert) -static_void_test_AMlistPutNull(update); +static_void_test_AMlistPutNull(update) -static_void_test_AMlistPutObject(List, insert); +static_void_test_AMlistPutObject(List, insert) -static_void_test_AMlistPutObject(List, update); +static_void_test_AMlistPutObject(List, update) -static_void_test_AMlistPutObject(Map, insert); +static_void_test_AMlistPutObject(Map, insert) -static_void_test_AMlistPutObject(Map, update); +static_void_test_AMlistPutObject(Map, update) -static_void_test_AMlistPutObject(Text, insert); +static_void_test_AMlistPutObject(Text, insert) -static_void_test_AMlistPutObject(Text, update); +static_void_test_AMlistPutObject(Text, update) -static_void_test_AMlistPutStr(insert, - "Hello, " - "world!"); +static_void_test_AMlistPutObject(Void, insert) -static_void_test_AMlistPutStr(update, - "Hello," - " world" - "!"); +static_void_test_AMlistPutObject(Void, update) -static_void_test_AMlistPut(Timestamp, insert, int64_t, INT64_MAX); +static_void_test_AMlistPutStr(insert, "Hello, world!") -static_void_test_AMlistPut(Timestamp, update, int64_t, INT64_MAX); +static_void_test_AMlistPutStr(update, "Hello, world!") -static_void_test_AMlistPut(Uint, insert, uint64_t, UINT64_MAX); +static_void_test_AMlistPut(Timestamp, insert, timestamp, INT64_MAX) -static_void_test_AMlistPut(Uint, update, uint64_t, UINT64_MAX); +static_void_test_AMlistPut(Timestamp, update, timestamp, INT64_MAX) -static void test_get_range_values(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; - AMdoc* doc1; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); - AMobjId const* const list = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc1, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); +static_void_test_AMlistPut(Uint, insert, uint, UINT64_MAX) + +static_void_test_AMlistPut(Uint, update, uint, UINT64_MAX) + +static void test_get_list_values(void** state) { + AMresultStack* stack = *state; + AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMobjId const* const list = AMpush( + &stack, + AMmapPutObject(doc1, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* Insert elements. */ - AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("First")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Second")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Third")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Fourth")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Fifth")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Sixth")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Seventh")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Eighth")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(doc1, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("First"))); + AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Second"))); + AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Third"))); + AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Fourth"))); + AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Fifth"))); + AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Sixth"))); + AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Seventh"))); + AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Eighth"))); + AMfree(AMcommit(doc1, AMstr(NULL), NULL)); - AMitems const v1 = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMdoc* doc2; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMfork(doc1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); + AMchangeHashes const v1 = AMpush(&stack, + AMgetHeads(doc1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + AMdoc* const doc2 = AMpush(&stack, + AMfork(doc1, NULL), + AM_VALUE_DOC, + cmocka_cb).doc; - AMstackItem(NULL, AMlistPutStr(doc1, list, 2, false, AMstr("Third V2")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(doc1, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMlistPutStr(doc1, list, 2, false, AMstr("Third V2"))); + AMfree(AMcommit(doc1, AMstr(NULL), NULL)); - AMstackItem(NULL, AMlistPutStr(doc2, list, 2, false, AMstr("Third V3")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(doc2, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMlistPutStr(doc2, list, 2, false, AMstr("Third V3"))); + AMfree(AMcommit(doc2, AMstr(NULL), NULL)); - AMstackItem(NULL, AMmerge(doc1, doc2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmerge(doc1, doc2)); - /* Forward vs. reverse: complete current list range. */ - AMitems range = - AMstackItems(stack_ptr, AMlistRange(doc1, list, 0, SIZE_MAX, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - size_t size = AMitemsSize(&range); - assert_int_equal(size, 8); - AMitems range_back = AMitemsReversed(&range); - assert_int_equal(AMitemsSize(&range_back), size); - size_t pos; - assert_true(AMitemPos(AMitemsNext(&range, 1), &pos)); - assert_int_equal(pos, 0); - assert_true(AMitemPos(AMitemsNext(&range_back, 1), &pos)); - assert_int_equal(pos, 7); + AMlistItems range = AMpush(&stack, + AMlistRange(doc1, list, 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + assert_int_equal(AMlistItemsSize(&range), 8); - AMitem *item1, *item_back1; - size_t count, middle = size / 2; - range = AMitemsRewound(&range); - range_back = AMitemsRewound(&range_back); - for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; - item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { - size_t pos1, pos_back1; - assert_true(AMitemPos(item1, &pos1)); - assert_true(AMitemPos(item_back1, &pos_back1)); - if ((count == middle) && (middle & 1)) { - /* The iterators are crossing in the middle. */ - assert_int_equal(pos1, pos_back1); - assert_true(AMitemEqual(item1, item_back1)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); - } else { - assert_int_not_equal(pos1, pos_back1); - } - AMitem* item2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos1, NULL), NULL, NULL); - AMitem* item_back2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos_back1, NULL), NULL, NULL); - /** \note An item returned from an `AM...Get()` call doesn't include the - index used to retrieve it. */ - assert_false(AMitemIdxType(item2)); - assert_false(AMitemIdxType(item_back2)); - assert_true(AMitemEqual(item1, item2)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); - assert_true(AMitemEqual(item_back1, item_back2)); - assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); - AMresultFree(AMstackPop(stack_ptr, NULL)); + AMlistItem const* list_item = NULL; + while ((list_item = AMlistItemsNext(&range, 1)) != NULL) { + AMvalue const val1 = AMlistItemValue(list_item); + AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), NULL); + AMvalue const val2 = AMresultValue(result); + assert_true(AMvalueEqual(&val1, &val2)); + assert_non_null(AMlistItemObjId(list_item)); + AMfree(result); } - /* Forward vs. reverse: partial current list range. */ - range = AMstackItems(stack_ptr, AMlistRange(doc1, list, 1, 6, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - size = AMitemsSize(&range); - assert_int_equal(size, 5); - range_back = AMitemsReversed(&range); - assert_int_equal(AMitemsSize(&range_back), size); - assert_true(AMitemPos(AMitemsNext(&range, 1), &pos)); - assert_int_equal(pos, 1); - assert_true(AMitemPos(AMitemsNext(&range_back, 1), &pos)); - assert_int_equal(pos, 5); + range = AMpush(&stack, + AMlistRange(doc1, list, 3, 6, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + AMlistItems range_back = AMlistItemsReversed(&range); + assert_int_equal(AMlistItemsSize(&range), 3); + assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range, 1)), 3); + assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range_back, 1)), 5); - middle = size / 2; - range = AMitemsRewound(&range); - range_back = AMitemsRewound(&range_back); - for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; - item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { - size_t pos1, pos_back1; - assert_true(AMitemPos(item1, &pos1)); - assert_true(AMitemPos(item_back1, &pos_back1)); - if ((count == middle) && (middle & 1)) { - /* The iterators are crossing in the middle. */ - assert_int_equal(pos1, pos_back1); - assert_true(AMitemEqual(item1, item_back1)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); - } else { - assert_int_not_equal(pos1, pos_back1); - } - AMitem* item2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos1, NULL), NULL, NULL); - AMitem* item_back2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos_back1, NULL), NULL, NULL); - /** \note An item returned from an `AMlistGet()` call doesn't include - the index used to retrieve it. */ - assert_int_equal(AMitemIdxType(item2), 0); - assert_int_equal(AMitemIdxType(item_back2), 0); - assert_true(AMitemEqual(item1, item2)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); - assert_true(AMitemEqual(item_back1, item_back2)); - assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); - AMresultFree(AMstackPop(stack_ptr, NULL)); + range = AMlistItemsRewound(&range); + while ((list_item = AMlistItemsNext(&range, 1)) != NULL) { + AMvalue const val1 = AMlistItemValue(list_item); + AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), NULL); + AMvalue const val2 = AMresultValue(result); + assert_true(AMvalueEqual(&val1, &val2)); + assert_non_null(AMlistItemObjId(list_item)); + AMfree(result); } - /* Forward vs. reverse: complete historical map range. */ - range = AMstackItems(stack_ptr, AMlistRange(doc1, list, 0, SIZE_MAX, &v1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - size = AMitemsSize(&range); - assert_int_equal(size, 8); - range_back = AMitemsReversed(&range); - assert_int_equal(AMitemsSize(&range_back), size); - assert_true(AMitemPos(AMitemsNext(&range, 1), &pos)); - assert_int_equal(pos, 0); - assert_true(AMitemPos(AMitemsNext(&range_back, 1), &pos)); - assert_int_equal(pos, 7); - - middle = size / 2; - range = AMitemsRewound(&range); - range_back = AMitemsRewound(&range_back); - for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; - item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { - size_t pos1, pos_back1; - assert_true(AMitemPos(item1, &pos1)); - assert_true(AMitemPos(item_back1, &pos_back1)); - if ((count == middle) && (middle & 1)) { - /* The iterators are crossing in the middle. */ - assert_int_equal(pos1, pos_back1); - assert_true(AMitemEqual(item1, item_back1)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); - } else { - assert_int_not_equal(pos1, pos_back1); - } - AMitem* item2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos1, &v1), NULL, NULL); - AMitem* item_back2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos_back1, &v1), NULL, NULL); - /** \note An item returned from an `AM...Get()` call doesn't include the - index used to retrieve it. */ - assert_false(AMitemIdxType(item2)); - assert_false(AMitemIdxType(item_back2)); - assert_true(AMitemEqual(item1, item2)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); - assert_true(AMitemEqual(item_back1, item_back2)); - assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); - AMresultFree(AMstackPop(stack_ptr, NULL)); + range = AMpush(&stack, + AMlistRange(doc1, list, 0, SIZE_MAX, &v1), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + assert_int_equal(AMlistItemsSize(&range), 8); + while ((list_item = AMlistItemsNext(&range, 1)) != NULL) { + AMvalue const val1 = AMlistItemValue(list_item); + AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), &v1); + AMvalue const val2 = AMresultValue(result); + assert_true(AMvalueEqual(&val1, &val2)); + assert_non_null(AMlistItemObjId(list_item)); + AMfree(result); } - /* Forward vs. reverse: partial historical map range. */ - range = AMstackItems(stack_ptr, AMlistRange(doc1, list, 2, 7, &v1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - size = AMitemsSize(&range); - assert_int_equal(size, 5); - range_back = AMitemsReversed(&range); - assert_int_equal(AMitemsSize(&range_back), size); - assert_true(AMitemPos(AMitemsNext(&range, 1), &pos)); - assert_int_equal(pos, 2); - assert_true(AMitemPos(AMitemsNext(&range_back, 1), &pos)); - assert_int_equal(pos, 6); + range = AMpush(&stack, + AMlistRange(doc1, list, 3, 6, &v1), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + range_back = AMlistItemsReversed(&range); + assert_int_equal(AMlistItemsSize(&range), 3); + assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range, 1)), 3); + assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range_back, 1)), 5); - middle = size / 2; - range = AMitemsRewound(&range); - range_back = AMitemsRewound(&range_back); - for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; - item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { - size_t pos1, pos_back1; - assert_true(AMitemPos(item1, &pos1)); - assert_true(AMitemPos(item_back1, &pos_back1)); - if ((count == middle) && (middle & 1)) { - /* The iterators are crossing in the middle. */ - assert_int_equal(pos1, pos_back1); - assert_true(AMitemEqual(item1, item_back1)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); - } else { - assert_int_not_equal(pos1, pos_back1); - } - AMitem* item2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos1, &v1), NULL, NULL); - AMitem* item_back2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos_back1, &v1), NULL, NULL); - /** \note An item returned from an `AM...Get()` call doesn't include the - index used to retrieve it. */ - assert_false(AMitemIdxType(item2)); - assert_false(AMitemIdxType(item_back2)); - assert_true(AMitemEqual(item1, item2)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); - assert_true(AMitemEqual(item_back1, item_back2)); - assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); - AMresultFree(AMstackPop(stack_ptr, NULL)); + range = AMlistItemsRewound(&range); + while ((list_item = AMlistItemsNext(&range, 1)) != NULL) { + AMvalue const val1 = AMlistItemValue(list_item); + AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), &v1); + AMvalue const val2 = AMresultValue(result); + assert_true(AMvalueEqual(&val1, &val2)); + assert_non_null(AMlistItemObjId(list_item)); + AMfree(result); } - /* List range vs. object range: complete current. */ - range = AMstackItems(stack_ptr, AMlistRange(doc1, list, 0, SIZE_MAX, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - AMitems obj_items = AMstackItems(stack_ptr, AMobjItems(doc1, list, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - assert_int_equal(AMitemsSize(&range), AMitemsSize(&obj_items)); - - AMitem *item, *obj_item; - for (item = NULL, obj_item = NULL; item && obj_item; - item = AMitemsNext(&range, 1), obj_item = AMitemsNext(&obj_items, 1)) { - /** \note Object iteration doesn't yield any item indices. */ - assert_true(AMitemIdxType(item)); - assert_false(AMitemIdxType(obj_item)); - assert_true(AMitemEqual(item, obj_item)); - assert_true(AMobjIdEqual(AMitemObjId(item), AMitemObjId(obj_item))); + range = AMpush(&stack, + AMlistRange(doc1, list, 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + AMobjItems values = AMpush(&stack, + AMobjValues(doc1, list, NULL), + AM_VALUE_OBJ_ITEMS, + cmocka_cb).obj_items; + assert_int_equal(AMlistItemsSize(&range), AMobjItemsSize(&values)); + AMobjItem const* value = NULL; + while ((list_item = AMlistItemsNext(&range, 1)) != NULL && + (value = AMobjItemsNext(&values, 1)) != NULL) { + AMvalue const val1 = AMlistItemValue(list_item); + AMvalue const val2 = AMobjItemValue(value); + assert_true(AMvalueEqual(&val1, &val2)); + assert_true(AMobjIdEqual(AMlistItemObjId(list_item), AMobjItemObjId(value))); } - /* List range vs. object range: complete historical. */ - range = AMstackItems(stack_ptr, AMlistRange(doc1, list, 0, SIZE_MAX, &v1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - obj_items = AMstackItems(stack_ptr, AMobjItems(doc1, list, &v1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - assert_int_equal(AMitemsSize(&range), AMitemsSize(&obj_items)); - - for (item = NULL, obj_item = NULL; item && obj_item; - item = AMitemsNext(&range, 1), obj_item = AMitemsNext(&obj_items, 1)) { - /** \note Object iteration doesn't yield any item indices. */ - assert_true(AMitemIdxType(item)); - assert_false(AMitemIdxType(obj_item)); - assert_true(AMitemEqual(item, obj_item)); - assert_true(AMobjIdEqual(AMitemObjId(item), AMitemObjId(obj_item))); + range = AMpush(&stack, + AMlistRange(doc1, list, 0, SIZE_MAX, &v1), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + values = AMpush(&stack, + AMobjValues(doc1, list, &v1), + AM_VALUE_OBJ_ITEMS, + cmocka_cb).obj_items; + assert_int_equal(AMlistItemsSize(&range), AMobjItemsSize(&values)); + while ((list_item = AMlistItemsNext(&range, 1)) != NULL && + (value = AMobjItemsNext(&values, 1)) != NULL) { + AMvalue const val1 = AMlistItemValue(list_item); + AMvalue const val2 = AMobjItemValue(value); + assert_true(AMvalueEqual(&val1, &val2)); + assert_true(AMobjIdEqual(AMlistItemObjId(list_item), AMobjItemObjId(value))); } } -/** - * \brief A JavaScript application can introduce NUL (`\0`) characters into a - * list object's string value which will truncate it in a C application. +/** \brief A JavaScript application can introduce NUL (`\0`) characters into a + * list object's string value which will truncate it in a C application. */ static void test_get_NUL_string_value(void** state) { /* @@ -431,52 +381,60 @@ static void test_get_NUL_string_value(void** state) { doc[0] = 'o\0ps'; }); const bytes = Automerge.save(doc); - console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], - bytes).join(", ") + "};"); + console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], bytes).join(", ") + "};"); */ static uint8_t const OOPS_VALUE[] = {'o', '\0', 'p', 's'}; static size_t const OOPS_SIZE = sizeof(OOPS_VALUE) / sizeof(uint8_t); static uint8_t const SAVED_DOC[] = { - 133, 111, 74, 131, 224, 28, 197, 17, 0, 113, 1, 16, 246, 137, 63, 193, 255, 181, 76, 79, 129, - 213, 133, 29, 214, 158, 164, 15, 1, 207, 184, 14, 57, 1, 194, 79, 247, 82, 160, 134, 227, 144, - 5, 241, 136, 205, 238, 250, 251, 54, 34, 250, 210, 96, 204, 132, 153, 203, 110, 109, 6, 6, 1, - 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 3, 33, 2, 35, 2, 52, 1, 66, - 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, 0, 127, 7, 127, - 1, 48, 127, 0, 127, 1, 1, 127, 1, 127, 70, 111, 0, 112, 115, 127, 0, 0}; + 133, 111, 74, 131, 224, 28, 197, 17, 0, 113, 1, 16, 246, 137, 63, 193, + 255, 181, 76, 79, 129, 213, 133, 29, 214, 158, 164, 15, 1, 207, 184, + 14, 57, 1, 194, 79, 247, 82, 160, 134, 227, 144, 5, 241, 136, 205, + 238, 250, 251, 54, 34, 250, 210, 96, 204, 132, 153, 203, 110, 109, 6, + 6, 1, 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 3, 33, 2, 35, 2, 52, + 1, 66, 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, + 127, 0, 127, 7, 127, 1, 48, 127, 0, 127, 1, 1, 127, 1, 127, 70, 111, + 0, 112, 115, 127, 0, 0}; static size_t const SAVED_DOC_SIZE = sizeof(SAVED_DOC) / sizeof(uint8_t); - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; - AMdoc* doc; - assert_true(AMitemToDoc( - AMstackItem(stack_ptr, AMload(SAVED_DOC, SAVED_DOC_SIZE), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); - AMbyteSpan str; - assert_true(AMitemToStr( - AMstackItem(stack_ptr, AMlistGet(doc, AM_ROOT, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + AMresultStack* stack = *state; + AMdoc* const doc = AMpush(&stack, + AMload(SAVED_DOC, SAVED_DOC_SIZE), + AM_VALUE_DOC, + cmocka_cb).doc; + AMbyteSpan const str = AMpush(&stack, + AMlistGet(doc, AM_ROOT, 0, NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_not_equal(str.count, strlen(OOPS_VALUE)); assert_int_equal(str.count, OOPS_SIZE); assert_memory_equal(str.src, OOPS_VALUE, str.count); } static void test_insert_at_index(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); - AMobjId const* const list = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMresultStack* stack = *state; + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + + AMobjId const* const list = AMpush( + &stack, + AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* Insert both at the same index. */ - AMstackItem(NULL, AMlistPutUint(doc, list, 0, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMlistPutUint(doc, list, 0, true, 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutUint(doc, list, 0, true, 0)); + AMfree(AMlistPutUint(doc, list, 0, true, 1)); assert_int_equal(AMobjSize(doc, list, NULL), 2); - AMitems const keys = AMstackItems(stack_ptr, AMkeys(doc, list, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - assert_int_equal(AMitemsSize(&keys), 2); - AMitems const range = - AMstackItems(stack_ptr, AMlistRange(doc, list, 0, SIZE_MAX, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT)); - assert_int_equal(AMitemsSize(&range), 2); + AMstrs const keys = AMpush(&stack, + AMkeys(doc, list, NULL), + AM_VALUE_STRS, + cmocka_cb).strs; + assert_int_equal(AMstrsSize(&keys), 2); + AMlistItems const range = AMpush(&stack, + AMlistRange(doc, list, 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + assert_int_equal(AMlistItemsSize(&range), 2); } int run_list_tests(void) { @@ -500,16 +458,18 @@ int run_list_tests(void) { cmocka_unit_test(test_AMlistPutObject(Map, update)), cmocka_unit_test(test_AMlistPutObject(Text, insert)), cmocka_unit_test(test_AMlistPutObject(Text, update)), + cmocka_unit_test(test_AMlistPutObject(Void, insert)), + cmocka_unit_test(test_AMlistPutObject(Void, update)), cmocka_unit_test(test_AMlistPutStr(insert)), cmocka_unit_test(test_AMlistPutStr(update)), cmocka_unit_test(test_AMlistPut(Timestamp, insert)), cmocka_unit_test(test_AMlistPut(Timestamp, update)), cmocka_unit_test(test_AMlistPut(Uint, insert)), cmocka_unit_test(test_AMlistPut(Uint, update)), - cmocka_unit_test_setup_teardown(test_get_range_values, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_get_NUL_string_value, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_insert_at_index, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_get_list_values, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_get_NUL_string_value, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_insert_at_index, setup_stack, teardown_stack), }; - return cmocka_run_group_tests(tests, setup_doc, teardown_doc); + return cmocka_run_group_tests(tests, group_setup, group_teardown); } diff --git a/rust/automerge-c/test/macro_utils.c b/rust/automerge-c/test/macro_utils.c index 3a546eb5..6d7578b6 100644 --- a/rust/automerge-c/test/macro_utils.c +++ b/rust/automerge-c/test/macro_utils.c @@ -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; } diff --git a/rust/automerge-c/test/macro_utils.h b/rust/automerge-c/test/macro_utils.h index e4c2c5b9..62e262ce 100644 --- a/rust/automerge-c/test/macro_utils.h +++ b/rust/automerge-c/test/macro_utils.h @@ -1,23 +1,24 @@ -#ifndef TESTS_MACRO_UTILS_H -#define TESTS_MACRO_UTILS_H +#ifndef MACRO_UTILS_H +#define MACRO_UTILS_H /* local */ #include /** - * \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 */ diff --git a/rust/automerge-c/test/main.c b/rust/automerge-c/test/main.c index 2996c9b3..09b71bd5 100644 --- a/rust/automerge-c/test/main.c +++ b/rust/automerge-c/test/main.c @@ -1,6 +1,6 @@ -#include #include #include +#include #include /* third-party */ @@ -8,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() + ); } diff --git a/rust/automerge-c/test/map_tests.c b/rust/automerge-c/test/map_tests.c index 2ee2e69a..194da2e8 100644 --- a/rust/automerge-c/test/map_tests.c +++ b/rust/automerge-c/test/map_tests.c @@ -11,133 +11,144 @@ /* local */ #include -#include -#include -#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_AMmapIncrement(void** state) { - DocState* doc_state = *state; - AMstack** stack_ptr = &doc_state->base_state->stack; - AMstackItem(NULL, AMmapPutCounter(doc_state->doc, AM_ROOT, AMstr("Counter"), 0), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); - int64_t counter; - assert_true(AMitemToCounter(AMstackItem(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, AMstr("Counter"), NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_COUNTER)), - &counter)); - assert_int_equal(counter, 0); - AMresultFree(AMstackPop(stack_ptr, NULL)); - AMstackItem(NULL, AMmapIncrement(doc_state->doc, AM_ROOT, AMstr("Counter"), 3), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); - assert_true(AMitemToCounter(AMstackItem(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, AMstr("Counter"), NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_COUNTER)), - &counter)); - assert_int_equal(counter, 3); - AMresultFree(AMstackPop(stack_ptr, NULL)); + GroupState* group_state = *state; + AMfree(AMmapPutCounter(group_state->doc, AM_ROOT, AMstr("Counter"), 0)); + assert_int_equal(AMpush(&group_state->stack, + AMmapGet(group_state->doc, AM_ROOT, AMstr("Counter"), NULL), + AM_VALUE_COUNTER, + cmocka_cb).counter, 0); + AMfree(AMpop(&group_state->stack)); + AMfree(AMmapIncrement(group_state->doc, AM_ROOT, AMstr("Counter"), 3)); + assert_int_equal(AMpush(&group_state->stack, + AMmapGet(group_state->doc, AM_ROOT, AMstr("Counter"), NULL), + AM_VALUE_COUNTER, + cmocka_cb).counter, 3); + AMfree(AMpop(&group_state->stack)); } -#define test_AMmapPut(suffix) test_AMmapPut##suffix +#define test_AMmapPut(suffix) test_AMmapPut ## suffix -#define static_void_test_AMmapPut(suffix, type, scalar_value) \ - static void test_AMmapPut##suffix(void** state) { \ - DocState* doc_state = *state; \ - AMstack** stack_ptr = &doc_state->base_state->stack; \ - AMstackItem(NULL, AMmapPut##suffix(doc_state->doc, AM_ROOT, AMstr(#suffix), scalar_value), cmocka_cb, \ - AMexpect(AM_VAL_TYPE_VOID)); \ - type value; \ - assert_true(AMitemTo##suffix(AMstackItem(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, AMstr(#suffix), NULL), \ - cmocka_cb, AMexpect(suffix_to_val_type(#suffix))), \ - &value)); \ - assert_true(value == scalar_value); \ - AMresultFree(AMstackPop(stack_ptr, NULL)); \ - } +#define static_void_test_AMmapPut(suffix, member, scalar_value) \ +static void test_AMmapPut ## suffix(void **state) { \ + GroupState* group_state = *state; \ + AMfree(AMmapPut ## suffix(group_state->doc, \ + AM_ROOT, \ + AMstr(#suffix), \ + scalar_value)); \ + assert_true(AMpush( \ + &group_state->stack, \ + AMmapGet(group_state->doc, AM_ROOT, AMstr(#suffix), NULL), \ + AMvalue_discriminant(#suffix), \ + cmocka_cb).member == scalar_value); \ + AMfree(AMpop(&group_state->stack)); \ +} -static void test_AMmapPutBytes(void** state) { +static void test_AMmapPutBytes(void **state) { static AMbyteSpan const KEY = {"Bytes", 5}; static uint8_t const BYTES_VALUE[] = {INT8_MIN, INT8_MAX / 2, INT8_MAX}; static size_t const BYTES_SIZE = sizeof(BYTES_VALUE) / sizeof(uint8_t); - DocState* doc_state = *state; - AMstack** stack_ptr = &doc_state->base_state->stack; - AMstackItem(NULL, AMmapPutBytes(doc_state->doc, AM_ROOT, KEY, AMbytes(BYTES_VALUE, BYTES_SIZE)), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); - AMbyteSpan bytes; - assert_true(AMitemToBytes( - AMstackItem(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, KEY, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), - &bytes)); + GroupState* group_state = *state; + AMfree(AMmapPutBytes(group_state->doc, + AM_ROOT, + KEY, + AMbytes(BYTES_VALUE, BYTES_SIZE))); + AMbyteSpan const bytes = AMpush(&group_state->stack, + AMmapGet(group_state->doc, AM_ROOT, KEY, NULL), + AM_VALUE_BYTES, + cmocka_cb).bytes; 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)); } -static void test_AMmapPutNull(void** state) { +static void test_AMmapPutNull(void **state) { static AMbyteSpan const KEY = {"Null", 4}; - DocState* doc_state = *state; - AMstack** stack_ptr = &doc_state->base_state->stack; - AMstackItem(NULL, AMmapPutNull(doc_state->doc, AM_ROOT, KEY), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMresult* result = AMstackResult(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, KEY, NULL), NULL, NULL); + GroupState* group_state = *state; + AMfree(AMmapPutNull(group_state->doc, AM_ROOT, KEY)); + AMresult* const result = AMmapGet(group_state->doc, AM_ROOT, KEY, 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); - AMitem* item = AMresultItem(result); - assert_int_equal(AMitemValType(item), AM_VAL_TYPE_NULL); + assert_int_equal(AMresultValue(result).tag, AM_VALUE_NULL); + AMfree(result); } -#define test_AMmapPutObject(label) test_AMmapPutObject_##label +#define test_AMmapPutObject(label) test_AMmapPutObject_ ## label -#define static_void_test_AMmapPutObject(label) \ - static void test_AMmapPutObject_##label(void** state) { \ - DocState* doc_state = *state; \ - AMstack** stack_ptr = &doc_state->base_state->stack; \ - AMobjType const obj_type = suffix_to_obj_type(#label); \ - AMobjId const* const obj_id = \ - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr(#label), obj_type), \ - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ - assert_non_null(obj_id); \ - assert_int_equal(AMobjObjType(doc_state->doc, obj_id), obj_type); \ - assert_int_equal(AMobjSize(doc_state->doc, obj_id, NULL), 0); \ - AMresultFree(AMstackPop(stack_ptr, NULL)); \ - } +#define static_void_test_AMmapPutObject(label) \ +static void test_AMmapPutObject_ ## label(void **state) { \ + GroupState* group_state = *state; \ + AMobjType const obj_type = AMobjType_tag(#label); \ + if (obj_type != AM_OBJ_TYPE_VOID) { \ + AMobjId const* const obj_id = AMpush( \ + &group_state->stack, \ + AMmapPutObject(group_state->doc, \ + AM_ROOT, \ + AMstr(#label), \ + obj_type), \ + AM_VALUE_OBJ_ID, \ + cmocka_cb).obj_id; \ + assert_non_null(obj_id); \ + assert_int_equal(AMobjObjType(group_state->doc, obj_id), obj_type); \ + assert_int_equal(AMobjSize(group_state->doc, obj_id, NULL), 0); \ + } \ + else { \ + AMpush(&group_state->stack, \ + AMmapPutObject(group_state->doc, \ + AM_ROOT, \ + AMstr(#label), \ + obj_type), \ + AM_VALUE_VOID, \ + NULL); \ + assert_int_not_equal(AMresultStatus(group_state->stack->result), \ + AM_STATUS_OK); \ + } \ + AMfree(AMpop(&group_state->stack)); \ +} -static void test_AMmapPutStr(void** state) { - DocState* doc_state = *state; - AMstack** stack_ptr = &doc_state->base_state->stack; - AMstackItem(NULL, AMmapPutStr(doc_state->doc, AM_ROOT, AMstr("Str"), AMstr("Hello, world!")), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); - AMbyteSpan str; - assert_true(AMitemToStr(AMstackItem(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, AMstr("Str"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR)), - &str)); +static void test_AMmapPutStr(void **state) { + GroupState* group_state = *state; + AMfree(AMmapPutStr(group_state->doc, AM_ROOT, AMstr("Str"), AMstr("Hello, world!"))); + AMbyteSpan const str = AMpush(&group_state->stack, + AMmapGet(group_state->doc, AM_ROOT, AMstr("Str"), NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, strlen("Hello, world!")); assert_memory_equal(str.src, "Hello, world!", str.count); - AMresultFree(AMstackPop(stack_ptr, NULL)); + AMfree(AMpop(&group_state->stack)); } -static_void_test_AMmapPut(Bool, bool, true); +static_void_test_AMmapPut(Bool, boolean, true) -static_void_test_AMmapPut(Counter, int64_t, INT64_MAX); +static_void_test_AMmapPut(Counter, counter, INT64_MAX) -static_void_test_AMmapPut(F64, double, DBL_MAX); +static_void_test_AMmapPut(F64, f64, DBL_MAX) -static_void_test_AMmapPut(Int, int64_t, INT64_MAX); +static_void_test_AMmapPut(Int, int_, INT64_MAX) -static_void_test_AMmapPutObject(List); +static_void_test_AMmapPutObject(List) -static_void_test_AMmapPutObject(Map); +static_void_test_AMmapPutObject(Map) -static_void_test_AMmapPutObject(Text); +static_void_test_AMmapPutObject(Text) -static_void_test_AMmapPut(Timestamp, int64_t, INT64_MAX); +static_void_test_AMmapPutObject(Void) -static_void_test_AMmapPut(Uint, int64_t, UINT64_MAX); +static_void_test_AMmapPut(Timestamp, timestamp, INT64_MAX) -/** - * \brief A JavaScript application can introduce NUL (`\0`) characters into - * a map object's key which will truncate it in a C application. +static_void_test_AMmapPut(Uint, uint, UINT64_MAX) + +/** \brief A JavaScript application can introduce NUL (`\0`) characters into a + * map object's key which will truncate it in a C application. */ static void test_get_NUL_key(void** state) { /* @@ -147,37 +158,39 @@ static void test_get_NUL_key(void** state) { doc['o\0ps'] = 'oops'; }); const bytes = Automerge.save(doc); - console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], - bytes).join(", ") + "};"); + console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], bytes).join(", ") + "};"); */ static uint8_t const OOPS_SRC[] = {'o', '\0', 'p', 's'}; static AMbyteSpan const OOPS_KEY = {.src = OOPS_SRC, .count = sizeof(OOPS_SRC) / sizeof(uint8_t)}; static uint8_t const SAVED_DOC[] = { - 133, 111, 74, 131, 233, 150, 60, 244, 0, 116, 1, 16, 223, 253, 146, 193, 58, 122, 66, 134, 151, - 225, 210, 51, 58, 86, 247, 8, 1, 49, 118, 234, 228, 42, 116, 171, 13, 164, 99, 244, 27, 19, - 150, 44, 201, 136, 222, 219, 90, 246, 226, 123, 77, 120, 157, 155, 55, 182, 2, 178, 64, 6, 1, - 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 6, 33, 2, 35, 2, 52, 1, 66, - 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, 0, 127, 7, 127, - 4, 111, 0, 112, 115, 127, 0, 127, 1, 1, 127, 1, 127, 70, 111, 111, 112, 115, 127, 0, 0}; + 133, 111, 74, 131, 233, 150, 60, 244, 0, 116, 1, 16, 223, 253, 146, + 193, 58, 122, 66, 134, 151, 225, 210, 51, 58, 86, 247, 8, 1, 49, 118, + 234, 228, 42, 116, 171, 13, 164, 99, 244, 27, 19, 150, 44, 201, 136, + 222, 219, 90, 246, 226, 123, 77, 120, 157, 155, 55, 182, 2, 178, 64, 6, + 1, 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 6, 33, 2, 35, 2, 52, 1, + 66, 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, 0, + 127, 7, 127, 4, 111, 0, 112, 115, 127, 0, 127, 1, 1, 127, 1, 127, 70, + 111, 111, 112, 115, 127, 0, 0 + }; static size_t const SAVED_DOC_SIZE = sizeof(SAVED_DOC) / sizeof(uint8_t); - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; - AMdoc* doc; - assert_true(AMitemToDoc( - AMstackItem(stack_ptr, AMload(SAVED_DOC, SAVED_DOC_SIZE), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); - AMbyteSpan str; - assert_true(AMitemToStr( - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, OOPS_KEY, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + AMresultStack* stack = *state; + AMdoc* const doc = AMpush(&stack, + AMload(SAVED_DOC, SAVED_DOC_SIZE), + AM_VALUE_DOC, + cmocka_cb).doc; + AMbyteSpan const str = AMpush(&stack, + AMmapGet(doc, AM_ROOT, OOPS_KEY, NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_not_equal(OOPS_KEY.count, strlen(OOPS_KEY.src)); assert_int_equal(str.count, strlen("oops")); assert_memory_equal(str.src, "oops", str.count); } -/** - * \brief A JavaScript application can introduce NUL (`\0`) characters into a - * map object's string value which will truncate it in a C application. +/** \brief A JavaScript application can introduce NUL (`\0`) characters into a + * map object's string value which will truncate it in a C application. */ static void test_get_NUL_string_value(void** state) { /* @@ -187,1369 +200,1209 @@ static void test_get_NUL_string_value(void** state) { doc.oops = 'o\0ps'; }); const bytes = Automerge.save(doc); - console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], - bytes).join(", ") + "};"); + console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], bytes).join(", ") + "};"); */ static uint8_t const OOPS_VALUE[] = {'o', '\0', 'p', 's'}; static size_t const OOPS_SIZE = sizeof(OOPS_VALUE) / sizeof(uint8_t); static uint8_t const SAVED_DOC[] = { - 133, 111, 74, 131, 63, 94, 151, 29, 0, 116, 1, 16, 156, 159, 189, 12, 125, 55, 71, 154, 136, - 104, 237, 186, 45, 224, 32, 22, 1, 36, 163, 164, 222, 81, 42, 1, 247, 231, 156, 54, 222, 76, - 6, 109, 18, 172, 75, 36, 118, 120, 68, 73, 87, 186, 230, 127, 68, 19, 81, 149, 185, 6, 1, - 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 6, 33, 2, 35, 2, 52, 1, 66, - 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, 0, 127, 7, 127, - 4, 111, 111, 112, 115, 127, 0, 127, 1, 1, 127, 1, 127, 70, 111, 0, 112, 115, 127, 0, 0}; + 133, 111, 74, 131, 63, 94, 151, 29, 0, 116, 1, 16, 156, 159, 189, 12, + 125, 55, 71, 154, 136, 104, 237, 186, 45, 224, 32, 22, 1, 36, 163, + 164, 222, 81, 42, 1, 247, 231, 156, 54, 222, 76, 6, 109, 18, 172, 75, + 36, 118, 120, 68, 73, 87, 186, 230, 127, 68, 19, 81, 149, 185, 6, 1, + 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 6, 33, 2, 35, 2, 52, 1, + 66, 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, + 0, 127, 7, 127, 4, 111, 111, 112, 115, 127, 0, 127, 1, 1, 127, 1, 127, + 70, 111, 0, 112, 115, 127, 0, 0 + }; static size_t const SAVED_DOC_SIZE = sizeof(SAVED_DOC) / sizeof(uint8_t); - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; - AMdoc* doc; - assert_true(AMitemToDoc( - AMstackItem(stack_ptr, AMload(SAVED_DOC, SAVED_DOC_SIZE), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); - AMbyteSpan str; - assert_true(AMitemToStr( - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("oops"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), - &str)); + AMresultStack* stack = *state; + AMdoc* const doc = AMpush(&stack, + AMload(SAVED_DOC, SAVED_DOC_SIZE), + AM_VALUE_DOC, + cmocka_cb).doc; + AMbyteSpan const str = AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("oops"), NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_not_equal(str.count, strlen(OOPS_VALUE)); assert_int_equal(str.count, OOPS_SIZE); assert_memory_equal(str.src, OOPS_VALUE, str.count); } static void test_range_iter_map(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); - AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("a"), 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("b"), 4), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("c"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("d"), 6), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("a"), 7), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("a"), 8), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("d"), 9), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMactorId const* actor_id; - assert_true(AMitemToActorId(AMstackItem(stack_ptr, AMgetActorId(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); - AMitems map_items = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_UINT)); - assert_int_equal(AMitemsSize(&map_items), 4); + AMresultStack* stack = *state; + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("a"), 3)); + AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("b"), 4)); + AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("c"), 5)); + AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("d"), 6)); + AMfree(AMcommit(doc, AMstr(NULL), NULL)); + AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("a"), 7)); + AMfree(AMcommit(doc, AMstr(NULL), NULL)); + AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("a"), 8)); + AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("d"), 9)); + AMfree(AMcommit(doc, AMstr(NULL), NULL)); + AMactorId const* const actor_id = AMpush(&stack, + AMgetActorId(doc), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id; + AMmapItems map_items = AMpush(&stack, + AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + assert_int_equal(AMmapItemsSize(&map_items), 4); /* ["b"-"d") */ - AMitems range = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr("b"), AMstr("d"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_UINT)); + AMmapItems range = AMpush(&stack, + AMmapRange(doc, AM_ROOT, AMstr("b"), AMstr("d"), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; /* First */ - AMitem* next = AMitemsNext(&range, 1); + AMmapItem const* next = AMmapItemsNext(&range, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - AMbyteSpan key; - assert_true(AMitemKey(next, &key)); + AMbyteSpan key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "b", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); - uint64_t uint; - assert_true(AMitemToUint(next, &uint)); - assert_int_equal(uint, 4); - AMobjId const* next_obj_id = AMitemObjId(next); + AMvalue next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_UINT); + assert_int_equal(next_value.uint, 4); + AMobjId const* next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMitemsNext(&range, 1); + next = AMmapItemsNext(&range, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "c", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); - assert_true(AMitemToUint(next, &uint)); - assert_int_equal(uint, 5); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_UINT); + assert_int_equal(next_value.uint, 5); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - assert_null(AMitemsNext(&range, 1)); + assert_null(AMmapItemsNext(&range, 1)); /* ["b"-) */ - range = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr("b"), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_UINT)); + range = AMpush(&stack, + AMmapRange(doc, AM_ROOT, AMstr("b"), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; /* First */ - next = AMitemsNext(&range, 1); + next = AMmapItemsNext(&range, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "b", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); - assert_true(AMitemToUint(next, &uint)); - assert_int_equal(uint, 4); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_UINT); + assert_int_equal(next_value.uint, 4); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMitemsNext(&range, 1); + next = AMmapItemsNext(&range, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "c", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); - assert_true(AMitemToUint(next, &uint)); - assert_int_equal(uint, 5); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_UINT); + assert_int_equal(next_value.uint, 5); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next = AMitemsNext(&range, 1); + next = AMmapItemsNext(&range, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "d", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); - assert_true(AMitemToUint(next, &uint)); - assert_int_equal(uint, 9); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_UINT); + assert_int_equal(next_value.uint, 9); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 7); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Fourth */ - assert_null(AMitemsNext(&range, 1)); + assert_null(AMmapItemsNext(&range, 1)); /* [-"d") */ - range = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr("d"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_UINT)); + range = AMpush(&stack, + AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr("d"), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; /* First */ - next = AMitemsNext(&range, 1); + next = AMmapItemsNext(&range, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "a", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); - assert_true(AMitemToUint(next, &uint)); - assert_int_equal(uint, 8); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_UINT); + assert_int_equal(next_value.uint, 8); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 6); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMitemsNext(&range, 1); + next = AMmapItemsNext(&range, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "b", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); - assert_true(AMitemToUint(next, &uint)); - assert_int_equal(uint, 4); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_UINT); + assert_int_equal(next_value.uint, 4); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next = AMitemsNext(&range, 1); + next = AMmapItemsNext(&range, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "c", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); - assert_true(AMitemToUint(next, &uint)); - assert_int_equal(uint, 5); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_UINT); + assert_int_equal(next_value.uint, 5); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Fourth */ - assert_null(AMitemsNext(&range, 1)); + assert_null(AMmapItemsNext(&range, 1)); /* ["a"-) */ - range = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr("a"), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_UINT)); + range = AMpush(&stack, + AMmapRange(doc, AM_ROOT, AMstr("a"), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; /* First */ - next = AMitemsNext(&range, 1); + next = AMmapItemsNext(&range, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "a", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); - assert_true(AMitemToUint(next, &uint)); - assert_int_equal(uint, 8); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_UINT); + assert_int_equal(next_value.uint, 8); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 6); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMitemsNext(&range, 1); + next = AMmapItemsNext(&range, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "b", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); - assert_true(AMitemToUint(next, &uint)); - assert_int_equal(uint, 4); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_UINT); + assert_int_equal(next_value.uint, 4); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next = AMitemsNext(&range, 1); + next = AMmapItemsNext(&range, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "c", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); - assert_true(AMitemToUint(next, &uint)); - assert_int_equal(uint, 5); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_UINT); + assert_int_equal(next_value.uint, 5); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Fourth */ - next = AMitemsNext(&range, 1); + next = AMmapItemsNext(&range, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "d", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); - assert_true(AMitemToUint(next, &uint)); - assert_int_equal(uint, 9); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_UINT); + assert_int_equal(next_value.uint, 9); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 7); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Fifth */ - assert_null(AMitemsNext(&range, 1)); + assert_null(AMmapItemsNext(&range, 1)); } static void test_map_range_back_and_forth_single(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)); - AMactorId const* actor_id; - assert_true(AMitemToActorId(AMstackItem(stack_ptr, AMgetActorId(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); + AMresultStack* stack = *state; + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMactorId const* const actor_id = AMpush(&stack, + AMgetActorId(doc), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id; - AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("1"), AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("2"), AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("3"), AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("1"), AMstr("a"))); + AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("2"), AMstr("b"))); + AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("3"), AMstr("c"))); /* Forward, back, back. */ - AMitems range_all = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR)); + AMmapItems range_all = AMpush(&stack, + AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; /* First */ - AMitem* next = AMitemsNext(&range_all, 1); + AMmapItem const* next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - AMbyteSpan key; - assert_true(AMitemKey(next, &key)); + AMbyteSpan key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - AMbyteSpan str; - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 1); - assert_memory_equal(str.src, "a", str.count); - AMobjId const* next_obj_id = AMitemObjId(next); + AMvalue next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 1); + assert_memory_equal(next_value.str.src, "a", next_value.str.count); + AMobjId const* next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - AMitems range_back_all = AMitemsReversed(&range_all); - range_back_all = AMitemsRewound(&range_back_all); - AMitem* next_back = AMitemsNext(&range_back_all, 1); + AMmapItems range_back_all = AMmapItemsReversed(&range_all); + range_back_all = AMmapItemsRewound(&range_back_all); + AMmapItem const* next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - AMbyteSpan str_back; - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 1); - assert_memory_equal(str_back.src, "c", str_back.count); - AMobjId const* next_back_obj_id = AMitemObjId(next_back); + AMvalue next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 1); + assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); + AMobjId const* next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 1); - assert_memory_equal(str_back.src, "b", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 1); + assert_memory_equal(next_back_value.str.src, "b", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Forward, back, forward. */ - range_all = AMitemsRewound(&range_all); - range_back_all = AMitemsRewound(&range_back_all); + range_all = AMmapItemsRewound(&range_all); + range_back_all = AMmapItemsRewound(&range_back_all); /* First */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - - assert_int_equal(str.count, 1); - assert_memory_equal(str.src, "a", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 1); + assert_memory_equal(next_value.str.src, "a", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 1); - assert_memory_equal(str_back.src, "c", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 1); + assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 1); - assert_memory_equal(str.src, "b", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 1); + assert_memory_equal(next_value.str.src, "b", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Forward, forward, forward. */ - range_all = AMitemsRewound(&range_all); + range_all = AMmapItemsRewound(&range_all); /* First */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 1); - assert_memory_equal(str.src, "a", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 1); + assert_memory_equal(next_value.str.src, "a", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 1); - assert_memory_equal(str.src, "b", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 1); + assert_memory_equal(next_value.str.src, "b", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 1); - assert_memory_equal(str.src, "c", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 1); + assert_memory_equal(next_value.str.src, "c", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Forward stop */ - assert_null(AMitemsNext(&range_all, 1)); + assert_null(AMmapItemsNext(&range_all, 1)); /* Back, back, back. */ - range_back_all = AMitemsRewound(&range_back_all); + range_back_all = AMmapItemsRewound(&range_back_all); /* Third */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 1); - assert_memory_equal(str_back.src, "c", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 1); + assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 1); - assert_memory_equal(str_back.src, "b", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 1); + assert_memory_equal(next_back_value.str.src, "b", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* First */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 1); - assert_memory_equal(str_back.src, "a", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 1); + assert_memory_equal(next_back_value.str.src, "a", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Back stop */ - assert_null(AMitemsNext(&range_back_all, 1)); + assert_null(AMmapItemsNext(&range_back_all, 1)); } static void test_map_range_back_and_forth_double(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)); - AMactorId const* actor_id1; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromBytes("\0", 1), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id1)); - AMstackItem(NULL, AMsetActorId(doc1, actor_id1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMresultStack* stack = *state; + AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMactorId const* const actor_id1= AMpush(&stack, + AMactorIdInitBytes("\0", 1), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id; + AMfree(AMsetActorId(doc1, actor_id1)); - AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("1"), AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("2"), AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("3"), AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("1"), AMstr("a"))); + AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("2"), AMstr("b"))); + AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("3"), AMstr("c"))); /* The second actor should win all conflicts here. */ - AMdoc* doc2; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); - AMactorId const* actor_id2; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromBytes("\1", 1), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id2)); - AMstackItem(NULL, AMsetActorId(doc2, actor_id2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("1"), AMstr("aa")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("2"), AMstr("bb")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("3"), AMstr("cc")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMdoc* const doc2 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMactorId const* const actor_id2 = AMpush(&stack, + AMactorIdInitBytes("\1", 1), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id; + AMfree(AMsetActorId(doc2, actor_id2)); + AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("1"), AMstr("aa"))); + AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("2"), AMstr("bb"))); + AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("3"), AMstr("cc"))); - AMstackItem(NULL, AMmerge(doc1, doc2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmerge(doc1, doc2)); /* Forward, back, back. */ - AMitems range_all = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR)); + AMmapItems range_all = AMpush(&stack, + AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; /* First */ - AMitem* next = AMitemsNext(&range_all, 1); + AMmapItem const* next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - AMbyteSpan key; - assert_true(AMitemKey(next, &key)); + AMbyteSpan key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - AMbyteSpan str; - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 2); - assert_memory_equal(str.src, "aa", str.count); - AMobjId const* next_obj_id = AMitemObjId(next); + AMvalue next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 2); + assert_memory_equal(next_value.str.src, "aa", next_value.str.count); + AMobjId const* next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - AMitems range_back_all = AMitemsReversed(&range_all); - range_back_all = AMitemsRewound(&range_back_all); - AMitem* next_back = AMitemsNext(&range_back_all, 1); + AMmapItems range_back_all = AMmapItemsReversed(&range_all); + range_back_all = AMmapItemsRewound(&range_back_all); + AMmapItem const* next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - AMbyteSpan str_back; - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 2); - assert_memory_equal(str_back.src, "cc", str_back.count); - AMobjId const* next_back_obj_id = AMitemObjId(next_back); + AMvalue next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 2); + assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); + AMobjId const* next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 2); - assert_memory_equal(str_back.src, "bb", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 2); + assert_memory_equal(next_back_value.str.src, "bb", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Forward, back, forward. */ - range_all = AMitemsRewound(&range_all); - range_back_all = AMitemsRewound(&range_back_all); + range_all = AMmapItemsRewound(&range_all); + range_back_all = AMmapItemsRewound(&range_back_all); /* First */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 2); - assert_memory_equal(str.src, "aa", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 2); + assert_memory_equal(next_value.str.src, "aa", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 2); - assert_memory_equal(str_back.src, "cc", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 2); + assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 2); - assert_memory_equal(str.src, "bb", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 2); + assert_memory_equal(next_value.str.src, "bb", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Forward, forward, forward. */ - range_all = AMitemsRewound(&range_all); + range_all = AMmapItemsRewound(&range_all); /* First */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 2); - assert_memory_equal(str.src, "aa", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 2); + assert_memory_equal(next_value.str.src, "aa", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Second */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 2); - assert_memory_equal(str.src, "bb", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 2); + assert_memory_equal(next_value.str.src, "bb", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 2); - assert_memory_equal(str.src, "cc", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 2); + assert_memory_equal(next_value.str.src, "cc", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Forward stop */ - assert_null(AMitemsNext(&range_all, 1)); + assert_null(AMmapItemsNext(&range_all, 1)); /* Back, back, back. */ - range_back_all = AMitemsRewound(&range_back_all); + range_back_all = AMmapItemsRewound(&range_back_all); /* Third */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 2); - assert_memory_equal(str_back.src, "cc", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 2); + assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 2); - assert_memory_equal(str_back.src, "bb", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 2); + assert_memory_equal(next_back_value.str.src, "bb", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* First */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 2); - assert_memory_equal(str_back.src, "aa", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 2); + assert_memory_equal(next_back_value.str.src, "aa", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Back stop */ - assert_null(AMitemsNext(&range_back_all, 1)); + assert_null(AMmapItemsNext(&range_back_all, 1)); } static void test_map_range_at_back_and_forth_single(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)); - AMactorId const* actor_id; - assert_true(AMitemToActorId(AMstackItem(stack_ptr, AMgetActorId(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); + AMresultStack* stack = *state; + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMactorId const* const actor_id = AMpush(&stack, + AMgetActorId(doc), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id; - AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("1"), AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("2"), AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("3"), AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("1"), AMstr("a"))); + AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("2"), AMstr("b"))); + AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("3"), AMstr("c"))); - AMitems const heads = AMstackItems(stack_ptr, AMgetHeads(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMchangeHashes const heads = AMpush(&stack, + AMgetHeads(doc), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; /* Forward, back, back. */ - AMitems range_all = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR)); + AMmapItems range_all = AMpush(&stack, + AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; /* First */ - AMitem* next = AMitemsNext(&range_all, 1); + AMmapItem const* next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - AMbyteSpan key; - assert_true(AMitemKey(next, &key)); + AMbyteSpan key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - AMbyteSpan str; - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 1); - assert_memory_equal(str.src, "a", str.count); - AMobjId const* next_obj_id = AMitemObjId(next); + AMvalue next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 1); + assert_memory_equal(next_value.str.src, "a", next_value.str.count); + AMobjId const* next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - AMitems range_back_all = AMitemsReversed(&range_all); - range_back_all = AMitemsRewound(&range_back_all); - AMitem* next_back = AMitemsNext(&range_back_all, 1); + AMmapItems range_back_all = AMmapItemsReversed(&range_all); + range_back_all = AMmapItemsRewound(&range_back_all); + AMmapItem const* next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - AMbyteSpan str_back; - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 1); - assert_memory_equal(str_back.src, "c", str_back.count); - AMobjId const* next_back_obj_id = AMitemObjId(next_back); + AMvalue next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 1); + assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); + AMobjId const* next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 1); - assert_memory_equal(str_back.src, "b", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 1); + assert_memory_equal(next_back_value.str.src, "b", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Forward, back, forward. */ - range_all = AMitemsRewound(&range_all); - range_back_all = AMitemsRewound(&range_back_all); + range_all = AMmapItemsRewound(&range_all); + range_back_all = AMmapItemsRewound(&range_back_all); /* First */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 1); - assert_memory_equal(str.src, "a", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 1); + assert_memory_equal(next_value.str.src, "a", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 1); - assert_memory_equal(str_back.src, "c", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 1); + assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 1); - assert_memory_equal(str.src, "b", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 1); + assert_memory_equal(next_value.str.src, "b", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Forward, forward, forward. */ - range_all = AMitemsRewound(&range_all); + range_all = AMmapItemsRewound(&range_all); /* First */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 1); - assert_memory_equal(str.src, "a", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 1); + assert_memory_equal(next_value.str.src, "a", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 1); - assert_memory_equal(str.src, "b", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 1); + assert_memory_equal(next_value.str.src, "b", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 1); - assert_memory_equal(str.src, "c", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 1); + assert_memory_equal(next_value.str.src, "c", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Forward stop */ - assert_null(AMitemsNext(&range_all, 1)); + assert_null(AMmapItemsNext(&range_all, 1)); /* Back, back, back. */ - range_back_all = AMitemsRewound(&range_back_all); + range_back_all = AMmapItemsRewound(&range_back_all); /* Third */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 1); - assert_memory_equal(str_back.src, "c", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 1); + assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 1); - assert_memory_equal(str_back.src, "b", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 1); + assert_memory_equal(next_back_value.str.src, "b", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* First */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 1); - assert_memory_equal(str_back.src, "a", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 1); + assert_memory_equal(next_back_value.str.src, "a", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Back stop */ - assert_null(AMitemsNext(&range_back_all, 1)); + assert_null(AMmapItemsNext(&range_back_all, 1)); } static void test_map_range_at_back_and_forth_double(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)); - AMactorId const* actor_id1; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromBytes("\0", 1), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id1)); - AMstackItem(NULL, AMsetActorId(doc1, actor_id1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMresultStack* stack = *state; + AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMactorId const* const actor_id1= AMpush(&stack, + AMactorIdInitBytes("\0", 1), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id; + AMfree(AMsetActorId(doc1, actor_id1)); - AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("1"), AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("2"), AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("3"), AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("1"), AMstr("a"))); + AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("2"), AMstr("b"))); + AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("3"), AMstr("c"))); /* The second actor should win all conflicts here. */ - AMdoc* doc2; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); - AMactorId const* actor_id2; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromBytes("\1", 1), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id2)); - AMstackItem(NULL, AMsetActorId(doc2, actor_id2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("1"), AMstr("aa")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("2"), AMstr("bb")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("3"), AMstr("cc")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMdoc* const doc2 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMactorId const* const actor_id2= AMpush(&stack, + AMactorIdInitBytes("\1", 1), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id; + AMfree(AMsetActorId(doc2, actor_id2)); + AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("1"), AMstr("aa"))); + AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("2"), AMstr("bb"))); + AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("3"), AMstr("cc"))); - AMstackItem(NULL, AMmerge(doc1, doc2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMitems const heads = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmerge(doc1, doc2)); + AMchangeHashes const heads = AMpush(&stack, + AMgetHeads(doc1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; /* Forward, back, back. */ - AMitems range_all = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR)); + AMmapItems range_all = AMpush(&stack, + AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; /* First */ - AMitem* next = AMitemsNext(&range_all, 1); + AMmapItem const* next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - AMbyteSpan key; - assert_true(AMitemKey(next, &key)); + AMbyteSpan key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - AMbyteSpan str; - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 2); - assert_memory_equal(str.src, "aa", str.count); - AMobjId const* next_obj_id = AMitemObjId(next); + AMvalue next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 2); + assert_memory_equal(next_value.str.src, "aa", next_value.str.count); + AMobjId const* next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - AMitems range_back_all = AMitemsReversed(&range_all); - range_back_all = AMitemsRewound(&range_back_all); - AMitem* next_back = AMitemsNext(&range_back_all, 1); + AMmapItems range_back_all = AMmapItemsReversed(&range_all); + range_back_all = AMmapItemsRewound(&range_back_all); + AMmapItem const* next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - AMbyteSpan str_back; - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 2); - assert_memory_equal(str_back.src, "cc", str_back.count); - AMobjId const* next_back_obj_id = AMitemObjId(next_back); + AMvalue next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 2); + assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); + AMobjId const* next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 2); - assert_memory_equal(str_back.src, "bb", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 2); + assert_memory_equal(next_back_value.str.src, "bb", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Forward, back, forward. */ - range_all = AMitemsRewound(&range_all); - range_back_all = AMitemsRewound(&range_back_all); + range_all = AMmapItemsRewound(&range_all); + range_back_all = AMmapItemsRewound(&range_back_all); /* First */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 2); - assert_memory_equal(str.src, "aa", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 2); + assert_memory_equal(next_value.str.src, "aa", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 2); - assert_memory_equal(str_back.src, "cc", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 2); + assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 2); - assert_memory_equal(str.src, "bb", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 2); + assert_memory_equal(next_value.str.src, "bb", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Forward, forward, forward. */ - range_all = AMitemsRewound(&range_all); + range_all = AMmapItemsRewound(&range_all); /* First */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 2); - assert_memory_equal(str.src, "aa", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 2); + assert_memory_equal(next_value.str.src, "aa", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Second */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 2); - assert_memory_equal(str.src, "bb", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 2); + assert_memory_equal(next_value.str.src, "bb", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - next = AMitemsNext(&range_all, 1); + next = AMmapItemsNext(&range_all, 1); assert_non_null(next); - assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next, &key)); + key = AMmapItemKey(next); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next, &str)); - assert_int_equal(str.count, 2); - assert_memory_equal(str.src, "cc", str.count); - next_obj_id = AMitemObjId(next); + next_value = AMmapItemValue(next); + assert_int_equal(next_value.tag, AM_VALUE_STR); + assert_int_equal(next_value.str.count, 2); + assert_memory_equal(next_value.str.src, "cc", next_value.str.count); + next_obj_id = AMmapItemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Forward stop */ - assert_null(AMitemsNext(&range_all, 1)); + assert_null(AMmapItemsNext(&range_all, 1)); /* Back, back, back. */ - range_back_all = AMitemsRewound(&range_back_all); + range_back_all = AMmapItemsRewound(&range_back_all); /* Third */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 2); - assert_memory_equal(str_back.src, "cc", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 2); + assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 2); - assert_memory_equal(str_back.src, "bb", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 2); + assert_memory_equal(next_back_value.str.src, "bb", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* First */ - next_back = AMitemsNext(&range_back_all, 1); + next_back = AMmapItemsNext(&range_back_all, 1); assert_non_null(next_back); - assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(next_back, &key)); + key = AMmapItemKey(next_back); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); - assert_true(AMitemToStr(next_back, &str_back)); - assert_int_equal(str_back.count, 2); - assert_memory_equal(str_back.src, "aa", str_back.count); - next_back_obj_id = AMitemObjId(next_back); + next_back_value = AMmapItemValue(next_back); + assert_int_equal(next_back_value.tag, AM_VALUE_STR); + assert_int_equal(next_back_value.str.count, 2); + assert_memory_equal(next_back_value.str.src, "aa", next_back_value.str.count); + next_back_obj_id = AMmapItemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Back stop */ - assert_null(AMitemsNext(&range_back_all, 1)); + assert_null(AMmapItemsNext(&range_back_all, 1)); } 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)); - AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("aa"), AMstr("aaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("bb"), AMstr("bbb")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("cc"), AMstr("ccc")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("dd"), AMstr("ddd")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(doc1, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMresultStack* stack = *state; + AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("aa"), AMstr("aaa"))); + AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("bb"), AMstr("bbb"))); + AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("cc"), AMstr("ccc"))); + AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("dd"), AMstr("ddd"))); + AMfree(AMcommit(doc1, AMstr(NULL), NULL)); - 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, AMmapPutStr(doc1, AM_ROOT, AMstr("cc"), AMstr("ccc V2")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(doc1, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("cc"), AMstr("ccc V2"))); + AMfree(AMcommit(doc1, AMstr(NULL), NULL)); - AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("cc"), AMstr("ccc V3")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(doc2, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("cc"), AMstr("ccc 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 map range. */ - AMitems range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR)); - size_t size = AMitemsSize(&range); - assert_int_equal(size, 4); - AMitems range_back = AMitemsReversed(&range); - assert_int_equal(AMitemsSize(&range_back), size); - AMbyteSpan key; - assert_true(AMitemKey(AMitemsNext(&range, 1), &key)); - assert_memory_equal(key.src, "aa", key.count); - assert_true(AMitemKey(AMitemsNext(&range_back, 1), &key)); - assert_memory_equal(key.src, "dd", key.count); + AMmapItems range = AMpush(&stack, + AMmapRange(doc1, AM_ROOT, AMstr("b"), AMstr("d"), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + AMmapItems range_back = AMmapItemsReversed(&range); + assert_int_equal(AMmapItemsSize(&range), 2); - AMitem *item1, *item_back1; - size_t count, middle = size / 2; - range = AMitemsRewound(&range); - range_back = AMitemsRewound(&range_back); - for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; - item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { - AMbyteSpan key1, key_back1; - assert_true(AMitemKey(item1, &key1)); - assert_true(AMitemKey(item_back1, &key_back1)); - if ((count == middle) && (middle & 1)) { - /* The iterators are crossing in the middle. */ - assert_int_equal(AMstrCmp(key1, key_back1), 0); - assert_true(AMitemEqual(item1, item_back1)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); - } else { - assert_int_not_equal(AMstrCmp(key1, key_back1), 0); - } - AMitem* item2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key1, NULL), NULL, NULL); - AMitem* item_back2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key_back1, NULL), NULL, NULL); - /** \note An item returned from an `AM...Get()` call doesn't include the - index used to retrieve it. */ - assert_false(AMitemIdxType(item2)); - assert_false(AMitemIdxType(item_back2)); - assert_true(AMitemEqual(item1, item2)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); - assert_true(AMitemEqual(item1, item2)); - assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); - AMresultFree(AMstackPop(stack_ptr, NULL)); + AMmapItem const* map_item = NULL; + while ((map_item = AMmapItemsNext(&range, 1)) != NULL) { + AMvalue const val1 = AMmapItemValue(map_item); + AMresult* result = AMmapGet(doc1, AM_ROOT, AMmapItemKey(map_item), NULL); + AMvalue const val2 = AMresultValue(result); + assert_true(AMvalueEqual(&val1, &val2)); + assert_non_null(AMmapItemObjId(map_item)); + AMfree(result); } - /* Forward vs. reverse: partial current map range. */ - range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr("aa"), AMstr("dd"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR)); - size = AMitemsSize(&range); - assert_int_equal(size, 3); - range_back = AMitemsReversed(&range); - assert_int_equal(AMitemsSize(&range_back), size); - assert_true(AMitemKey(AMitemsNext(&range, 1), &key)); - assert_memory_equal(key.src, "aa", key.count); - assert_true(AMitemKey(AMitemsNext(&range_back, 1), &key)); - assert_memory_equal(key.src, "cc", key.count); + assert_int_equal(AMmapItemsSize(&range_back), 2); - middle = size / 2; - range = AMitemsRewound(&range); - range_back = AMitemsRewound(&range_back); - for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; - item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { - AMbyteSpan key1, key_back1; - assert_true(AMitemKey(item1, &key1)); - assert_true(AMitemKey(item_back1, &key_back1)); - if ((count == middle) && (middle & 1)) { - /* The iterators are crossing in the middle. */ - assert_int_equal(AMstrCmp(key1, key_back1), 0); - assert_true(AMitemEqual(item1, item_back1)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); - } else { - assert_int_not_equal(AMstrCmp(key1, key_back1), 0); - } - AMitem* item2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key1, NULL), NULL, NULL); - AMitem* item_back2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key_back1, NULL), NULL, NULL); - /** \note An item returned from an `AM...Get()` call doesn't include the - index used to retrieve it. */ - assert_false(AMitemIdxType(item2)); - assert_false(AMitemIdxType(item_back2)); - assert_true(AMitemEqual(item1, item2)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); - assert_true(AMitemEqual(item_back1, item_back2)); - assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); - AMresultFree(AMstackPop(stack_ptr, NULL)); + while ((map_item = AMmapItemsNext(&range_back, 1)) != NULL) { + AMvalue const val1 = AMmapItemValue(map_item); + AMresult* result = AMmapGet(doc1, AM_ROOT, AMmapItemKey(map_item), NULL); + AMvalue const val2 = AMresultValue(result); + assert_true(AMvalueEqual(&val1, &val2)); + assert_non_null(AMmapItemObjId(map_item)); + AMfree(result); } - /* Forward vs. reverse: complete historical map range. */ - range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), &v1), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR)); - size = AMitemsSize(&range); - assert_int_equal(size, 4); - range_back = AMitemsReversed(&range); - assert_int_equal(AMitemsSize(&range_back), size); - assert_true(AMitemKey(AMitemsNext(&range, 1), &key)); - assert_memory_equal(key.src, "aa", key.count); - assert_true(AMitemKey(AMitemsNext(&range_back, 1), &key)); - assert_memory_equal(key.src, "dd", key.count); + range = AMpush(&stack, + AMmapRange(doc1, AM_ROOT, AMstr("b"), AMstr("d"), &v1), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + range_back = AMmapItemsReversed(&range); + assert_int_equal(AMmapItemsSize(&range), 2); - middle = size / 2; - range = AMitemsRewound(&range); - range_back = AMitemsRewound(&range_back); - for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; - item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { - AMbyteSpan key1, key_back1; - assert_true(AMitemKey(item1, &key1)); - assert_true(AMitemKey(item_back1, &key_back1)); - if ((count == middle) && (middle & 1)) { - /* The iterators are crossing in the middle. */ - assert_int_equal(AMstrCmp(key1, key_back1), 0); - assert_true(AMitemEqual(item1, item_back1)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); - } else { - assert_int_not_equal(AMstrCmp(key1, key_back1), 0); - } - AMitem* item2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key1, &v1), NULL, NULL); - AMitem* item_back2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key_back1, &v1), NULL, NULL); - /** \note An item returned from an `AM...Get()` call doesn't include the - index used to retrieve it. */ - assert_false(AMitemIdxType(item2)); - assert_false(AMitemIdxType(item_back2)); - assert_true(AMitemEqual(item1, item2)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); - assert_true(AMitemEqual(item_back1, item_back2)); - assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); - AMresultFree(AMstackPop(stack_ptr, NULL)); + while ((map_item = AMmapItemsNext(&range, 1)) != NULL) { + AMvalue const val1 = AMmapItemValue(map_item); + AMresult* result = AMmapGet(doc1, AM_ROOT, AMmapItemKey(map_item), &v1); + AMvalue const val2 = AMresultValue(result); + assert_true(AMvalueEqual(&val1, &val2)); + assert_non_null(AMmapItemObjId(map_item)); + AMfree(result); } - /* Forward vs. reverse: partial historical map range. */ - range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr("bb"), AMstr(NULL), &v1), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR)); - size = AMitemsSize(&range); - assert_int_equal(size, 3); - range_back = AMitemsReversed(&range); - assert_int_equal(AMitemsSize(&range_back), size); - assert_true(AMitemKey(AMitemsNext(&range, 1), &key)); - assert_memory_equal(key.src, "bb", key.count); - assert_true(AMitemKey(AMitemsNext(&range_back, 1), &key)); - assert_memory_equal(key.src, "dd", key.count); + assert_int_equal(AMmapItemsSize(&range_back), 2); - middle = size / 2; - range = AMitemsRewound(&range); - range_back = AMitemsRewound(&range_back); - for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; - item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { - AMbyteSpan key1, key_back1; - assert_true(AMitemKey(item1, &key1)); - assert_true(AMitemKey(item_back1, &key_back1)); - if ((count == middle) && (middle & 1)) { - /* The iterators are crossing in the middle. */ - assert_int_equal(AMstrCmp(key1, key_back1), 0); - assert_true(AMitemEqual(item1, item_back1)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); - } else { - assert_int_not_equal(AMstrCmp(key1, key_back1), 0); - } - AMitem* item2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key1, &v1), NULL, NULL); - AMitem* item_back2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key_back1, &v1), NULL, NULL); - /** \note An item returned from an `AM...Get()` call doesn't include the - index used to retrieve it. */ - assert_false(AMitemIdxType(item2)); - assert_false(AMitemIdxType(item_back2)); - assert_true(AMitemEqual(item1, item2)); - assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); - assert_true(AMitemEqual(item_back1, item_back2)); - assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); - AMresultFree(AMstackPop(stack_ptr, NULL)); + while ((map_item = AMmapItemsNext(&range_back, 1)) != NULL) { + AMvalue const val1 = AMmapItemValue(map_item); + AMresult* result = AMmapGet(doc1, AM_ROOT, AMmapItemKey(map_item), &v1); + AMvalue const val2 = AMresultValue(result); + assert_true(AMvalueEqual(&val1, &val2)); + assert_non_null(AMmapItemObjId(map_item)); + AMfree(result); } - /* Map range vs. object range: complete current. */ - range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR)); - AMitems obj_items = AMstackItems(stack_ptr, AMobjItems(doc1, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - assert_int_equal(AMitemsSize(&range), AMitemsSize(&obj_items)); - - AMitem *item, *obj_item; - for (item = NULL, obj_item = NULL; item && obj_item; - item = AMitemsNext(&range, 1), obj_item = AMitemsNext(&obj_items, 1)) { - /** \note Object iteration doesn't yield any item indices. */ - assert_true(AMitemIdxType(item)); - assert_false(AMitemIdxType(obj_item)); - assert_true(AMitemEqual(item, obj_item)); - assert_true(AMobjIdEqual(AMitemObjId(item), AMitemObjId(obj_item))); + range = AMpush(&stack, + AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + AMobjItems values = AMpush(&stack, + AMobjValues(doc1, AM_ROOT, NULL), + AM_VALUE_OBJ_ITEMS, + cmocka_cb).obj_items; + assert_int_equal(AMmapItemsSize(&range), AMobjItemsSize(&values)); + AMobjItem const* value = NULL; + while ((map_item = AMmapItemsNext(&range, 1)) != NULL && + (value = AMobjItemsNext(&values, 1)) != NULL) { + AMvalue const val1 = AMmapItemValue(map_item); + AMvalue const val2 = AMobjItemValue(value); + assert_true(AMvalueEqual(&val1, &val2)); + assert_true(AMobjIdEqual(AMmapItemObjId(map_item), AMobjItemObjId(value))); } - /* Map range vs. object range: complete historical. */ - range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), &v1), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR)); - obj_items = AMstackItems(stack_ptr, AMobjItems(doc1, AM_ROOT, &v1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - assert_int_equal(AMitemsSize(&range), AMitemsSize(&obj_items)); - - for (item = NULL, obj_item = NULL; item && obj_item; - item = AMitemsNext(&range, 1), obj_item = AMitemsNext(&obj_items, 1)) { - /** \note Object iteration doesn't yield any item indices. */ - assert_true(AMitemIdxType(item)); - assert_false(AMitemIdxType(obj_item)); - assert_true(AMitemEqual(item, obj_item)); - assert_true(AMobjIdEqual(AMitemObjId(item), AMitemObjId(obj_item))); + range = AMpush(&stack, + AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), &v1), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + values = AMpush(&stack, + AMobjValues(doc1, AM_ROOT, &v1), + AM_VALUE_OBJ_ITEMS, + cmocka_cb).obj_items; + assert_int_equal(AMmapItemsSize(&range), AMobjItemsSize(&values)); + while ((map_item = AMmapItemsNext(&range, 1)) != NULL && + (value = AMobjItemsNext(&values, 1)) != NULL) { + AMvalue const val1 = AMmapItemValue(map_item); + AMvalue const val2 = AMobjItemValue(value); + assert_true(AMvalueEqual(&val1, &val2)); + assert_true(AMobjIdEqual(AMmapItemObjId(map_item), AMobjItemObjId(value))); } } @@ -1565,18 +1418,19 @@ int run_map_tests(void) { cmocka_unit_test(test_AMmapPutObject(List)), cmocka_unit_test(test_AMmapPutObject(Map)), cmocka_unit_test(test_AMmapPutObject(Text)), + cmocka_unit_test(test_AMmapPutObject(Void)), cmocka_unit_test(test_AMmapPutStr), cmocka_unit_test(test_AMmapPut(Timestamp)), cmocka_unit_test(test_AMmapPut(Uint)), - cmocka_unit_test_setup_teardown(test_get_NUL_key, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_get_NUL_string_value, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_range_iter_map, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_map_range_back_and_forth_single, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_map_range_back_and_forth_double, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_map_range_at_back_and_forth_single, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_map_range_at_back_and_forth_double, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_get_range_values, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_get_NUL_key, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_get_NUL_string_value, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_range_iter_map, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_map_range_back_and_forth_single, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_map_range_back_and_forth_double, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_map_range_at_back_and_forth_single, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_map_range_at_back_and_forth_double, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_get_range_values, setup_stack, teardown_stack), }; - return cmocka_run_group_tests(tests, setup_doc, teardown_doc); + return cmocka_run_group_tests(tests, group_setup, group_teardown); } diff --git a/rust/automerge-c/test/ported_wasm/basic_tests.c b/rust/automerge-c/test/ported_wasm/basic_tests.c index b83ff132..e2659d62 100644 --- a/rust/automerge-c/test/ported_wasm/basic_tests.c +++ b/rust/automerge-c/test/ported_wasm/basic_tests.c @@ -11,10 +11,7 @@ /* local */ #include -#include -#include -#include "../base_state.h" -#include "../cmocka_utils.h" +#include "../stack_utils.h" /** * \brief default import init() should return a promise @@ -25,171 +22,163 @@ static void test_default_import_init_should_return_a_promise(void** state); * \brief should create, clone and free */ static void test_create_clone_and_free(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc1 = create() */ - AMdoc* doc1; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); + AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; /* const doc2 = doc1.clone() */ - AMdoc* doc2; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMclone(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); + AMdoc* const doc2 = AMpush(&stack, AMclone(doc1), AM_VALUE_DOC, cmocka_cb).doc; } /** * \brief should be able to start and commit */ static void test_start_and_commit(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc = create() */ - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; /* doc.commit() */ - AMstackItems(stack_ptr, AMemptyChange(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMpush(&stack, AMemptyChange(doc, AMstr(NULL), NULL), AM_VALUE_CHANGE_HASHES, cmocka_cb); } /** * \brief getting a nonexistent prop does not throw an error */ static void test_getting_a_nonexistent_prop_does_not_throw_an_error(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc = create() */ - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; /* const root = "_root" */ /* const result = doc.getWithType(root, "hello") */ /* assert.deepEqual(result, undefined) */ - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("hello"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("hello"), NULL), + AM_VALUE_VOID, + cmocka_cb); } /** * \brief should be able to set and get a simple value */ static void test_should_be_able_to_set_and_get_a_simple_value(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc: Automerge = create("aabbcc") */ - AMactorId const* actor_id; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aabbcc")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMdoc* const doc = AMpush(&stack, + AMcreate(AMpush(&stack, + AMactorIdInitStr(AMstr("aabbcc")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id), + AM_VALUE_DOC, + cmocka_cb).doc; /* const root = "_root" */ /* let result */ /* */ /* doc.put(root, "hello", "world") */ - AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("hello"), AMstr("world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("hello"), AMstr("world"))); /* doc.put(root, "number1", 5, "uint") */ - AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("number1"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("number1"), 5)); /* doc.put(root, "number2", 5) */ - AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("number2"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("number2"), 5)); /* doc.put(root, "number3", 5.5) */ - AMstackItem(NULL, AMmapPutF64(doc, AM_ROOT, AMstr("number3"), 5.5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutF64(doc, AM_ROOT, AMstr("number3"), 5.5)); /* doc.put(root, "number4", 5.5, "f64") */ - AMstackItem(NULL, AMmapPutF64(doc, AM_ROOT, AMstr("number4"), 5.5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutF64(doc, AM_ROOT, AMstr("number4"), 5.5)); /* doc.put(root, "number5", 5.5, "int") */ - AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("number5"), 5.5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("number5"), 5.5)); /* doc.put(root, "bool", true) */ - AMstackItem(NULL, AMmapPutBool(doc, AM_ROOT, AMstr("bool"), true), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutBool(doc, AM_ROOT, AMstr("bool"), true)); /* doc.put(root, "time1", 1000, "timestamp") */ - AMstackItem(NULL, AMmapPutTimestamp(doc, AM_ROOT, AMstr("time1"), 1000), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutTimestamp(doc, AM_ROOT, AMstr("time1"), 1000)); /* doc.put(root, "time2", new Date(1001)) */ - AMstackItem(NULL, AMmapPutTimestamp(doc, AM_ROOT, AMstr("time2"), 1001), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutTimestamp(doc, AM_ROOT, AMstr("time2"), 1001)); /* doc.putObject(root, "list", []); */ - AMstackItem(NULL, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + AMfree(AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST)); /* doc.put(root, "null", null) */ - AMstackItem(NULL, AMmapPutNull(doc, AM_ROOT, AMstr("null")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutNull(doc, AM_ROOT, AMstr("null"))); /* */ /* result = doc.getWithType(root, "hello") */ /* assert.deepEqual(result, ["str", "world"]) */ /* assert.deepEqual(doc.get("/", "hello"), "world") */ - AMbyteSpan str; - assert_true(AMitemToStr( - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("hello"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), - &str)); + AMbyteSpan str = AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("hello"), NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, strlen("world")); assert_memory_equal(str.src, "world", str.count); /* assert.deepEqual(doc.get("/", "hello"), "world") */ /* */ /* result = doc.getWithType(root, "number1") */ /* assert.deepEqual(result, ["uint", 5]) */ - uint64_t uint; - assert_true(AMitemToUint( - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("number1"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT)), - &uint)); - assert_int_equal(uint, 5); + assert_int_equal(AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("number1"), NULL), + AM_VALUE_UINT, + cmocka_cb).uint, 5); /* assert.deepEqual(doc.get("/", "number1"), 5) */ /* */ /* result = doc.getWithType(root, "number2") */ /* assert.deepEqual(result, ["int", 5]) */ - int64_t int_; - assert_true(AMitemToInt( - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("number2"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_INT)), - &int_)); - assert_int_equal(int_, 5); + assert_int_equal(AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("number2"), NULL), + AM_VALUE_INT, + cmocka_cb).int_, 5); /* */ /* result = doc.getWithType(root, "number3") */ /* assert.deepEqual(result, ["f64", 5.5]) */ - double f64; - assert_true(AMitemToF64( - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("number3"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_F64)), - &f64)); - assert_float_equal(f64, 5.5, DBL_EPSILON); + assert_float_equal(AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("number3"), NULL), + AM_VALUE_F64, + cmocka_cb).f64, 5.5, DBL_EPSILON); /* */ /* result = doc.getWithType(root, "number4") */ /* assert.deepEqual(result, ["f64", 5.5]) */ - assert_true(AMitemToF64( - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("number4"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_F64)), - &f64)); - assert_float_equal(f64, 5.5, DBL_EPSILON); + assert_float_equal(AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("number4"), NULL), + AM_VALUE_F64, + cmocka_cb).f64, 5.5, DBL_EPSILON); /* */ /* result = doc.getWithType(root, "number5") */ /* assert.deepEqual(result, ["int", 5]) */ - assert_true(AMitemToInt( - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("number5"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_INT)), - &int_)); - assert_int_equal(int_, 5); + assert_int_equal(AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("number5"), NULL), + AM_VALUE_INT, + cmocka_cb).int_, 5); /* */ /* result = doc.getWithType(root, "bool") */ /* assert.deepEqual(result, ["boolean", true]) */ - bool boolean; - assert_true(AMitemToBool( - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("bool"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BOOL)), - &boolean)); - assert_true(boolean); + assert_int_equal(AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("bool"), NULL), + AM_VALUE_BOOLEAN, + cmocka_cb).boolean, true); /* */ /* doc.put(root, "bool", false, "boolean") */ - AMstackItem(NULL, AMmapPutBool(doc, AM_ROOT, AMstr("bool"), false), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutBool(doc, AM_ROOT, AMstr("bool"), false)); /* */ /* result = doc.getWithType(root, "bool") */ /* assert.deepEqual(result, ["boolean", false]) */ - assert_true(AMitemToBool( - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("bool"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BOOL)), - &boolean)); - assert_false(boolean); + assert_int_equal(AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("bool"), NULL), + AM_VALUE_BOOLEAN, + cmocka_cb).boolean, false); /* */ /* result = doc.getWithType(root, "time1") */ /* assert.deepEqual(result, ["timestamp", new Date(1000)]) */ - int64_t timestamp; - assert_true(AMitemToTimestamp(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("time1"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_TIMESTAMP)), - ×tamp)); - assert_int_equal(timestamp, 1000); + assert_int_equal(AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("time1"), NULL), + AM_VALUE_TIMESTAMP, + cmocka_cb).timestamp, 1000); /* */ /* result = doc.getWithType(root, "time2") */ /* assert.deepEqual(result, ["timestamp", new Date(1001)]) */ - assert_true(AMitemToTimestamp(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("time2"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_TIMESTAMP)), - ×tamp)); - assert_int_equal(timestamp, 1001); + assert_int_equal(AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("time2"), NULL), + AM_VALUE_TIMESTAMP, + cmocka_cb).timestamp, 1001); /* */ /* result = doc.getWithType(root, "list") */ /* assert.deepEqual(result, ["list", "10@aabbcc"]); */ - AMobjId const* const list = AMitemObjId( - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("list"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const list = AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("list"), NULL), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; assert_int_equal(AMobjIdCounter(list), 10); str = AMactorIdStr(AMobjIdActorId(list)); assert_int_equal(str.count, strlen("aabbcc")); @@ -197,39 +186,38 @@ static void test_should_be_able_to_set_and_get_a_simple_value(void** state) { /* */ /* result = doc.getWithType(root, "null") */ /* assert.deepEqual(result, ["null", null]); */ - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("null"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_NULL)); + AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("null"), NULL), + AM_VALUE_NULL, + cmocka_cb); } /** * \brief should be able to use bytes */ static void test_should_be_able_to_use_bytes(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc = create() */ - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; /* doc.put("_root", "data1", new Uint8Array([10, 11, 12])); */ static uint8_t const DATA1[] = {10, 11, 12}; - AMstackItem(NULL, AMmapPutBytes(doc, AM_ROOT, AMstr("data1"), AMbytes(DATA1, sizeof(DATA1))), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutBytes(doc, AM_ROOT, AMstr("data1"), AMbytes(DATA1, sizeof(DATA1)))); /* doc.put("_root", "data2", new Uint8Array([13, 14, 15]), "bytes"); */ static uint8_t const DATA2[] = {13, 14, 15}; - AMstackItem(NULL, AMmapPutBytes(doc, AM_ROOT, AMstr("data2"), AMbytes(DATA2, sizeof(DATA2))), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutBytes(doc, AM_ROOT, AMstr("data2"), AMbytes(DATA2, sizeof(DATA2)))); /* const value1 = doc.getWithType("_root", "data1") */ - AMbyteSpan value1; - assert_true(AMitemToBytes( - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("data1"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), - &value1)); + AMbyteSpan const value1 = AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("data1"), NULL), + AM_VALUE_BYTES, + cmocka_cb).bytes; /* assert.deepEqual(value1, ["bytes", new Uint8Array([10, 11, 12])]); */ assert_int_equal(value1.count, sizeof(DATA1)); assert_memory_equal(value1.src, DATA1, sizeof(DATA1)); /* const value2 = doc.getWithType("_root", "data2") */ - AMbyteSpan value2; - assert_true(AMitemToBytes( - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("data2"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), - &value2)); + AMbyteSpan const value2 = AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("data2"), NULL), + AM_VALUE_BYTES, + cmocka_cb).bytes; /* assert.deepEqual(value2, ["bytes", new Uint8Array([13, 14, 15])]); */ assert_int_equal(value2.count, sizeof(DATA2)); assert_memory_equal(value2.src, DATA2, sizeof(DATA2)); @@ -239,92 +227,103 @@ static void test_should_be_able_to_use_bytes(void** state) { * \brief should be able to make subobjects */ static void test_should_be_able_to_make_subobjects(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc = create() */ - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; /* const root = "_root" */ /* let result */ /* */ /* const submap = doc.putObject(root, "submap", {}) */ - AMobjId const* const submap = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("submap"), AM_OBJ_TYPE_MAP), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const submap = AMpush( + &stack, + AMmapPutObject(doc, AM_ROOT, AMstr("submap"), AM_OBJ_TYPE_MAP), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* doc.put(submap, "number", 6, "uint") */ - AMstackItem(NULL, AMmapPutUint(doc, submap, AMstr("number"), 6), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(doc, submap, AMstr("number"), 6)); /* assert.strictEqual(doc.pendingOps(), 2) */ assert_int_equal(AMpendingOps(doc), 2); /* */ /* result = doc.getWithType(root, "submap") */ /* assert.deepEqual(result, ["map", submap]) */ - assert_true(AMobjIdEqual(AMitemObjId(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("submap"), NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))), + assert_true(AMobjIdEqual(AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("submap"), NULL), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id, submap)); /* */ /* result = doc.getWithType(submap, "number") */ /* assert.deepEqual(result, ["uint", 6]) */ - uint64_t uint; - assert_true(AMitemToUint( - AMstackItem(stack_ptr, AMmapGet(doc, submap, AMstr("number"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT)), - &uint)); - assert_int_equal(uint, 6); + assert_int_equal(AMpush(&stack, + AMmapGet(doc, submap, AMstr("number"), NULL), + AM_VALUE_UINT, + cmocka_cb).uint, + 6); } /** * \brief should be able to make lists */ static void test_should_be_able_to_make_lists(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc = create() */ - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; /* const root = "_root" */ /* */ /* const sublist = doc.putObject(root, "numbers", []) */ - AMobjId const* const sublist = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("numbers"), AM_OBJ_TYPE_LIST), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const sublist = AMpush( + &stack, + AMmapPutObject(doc, AM_ROOT, AMstr("numbers"), AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* doc.insert(sublist, 0, "a"); */ - AMstackItem(NULL, AMlistPutStr(doc, sublist, 0, true, AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutStr(doc, sublist, 0, true, AMstr("a"))); /* doc.insert(sublist, 1, "b"); */ - AMstackItem(NULL, AMlistPutStr(doc, sublist, 1, true, AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutStr(doc, sublist, 1, true, AMstr("b"))); /* doc.insert(sublist, 2, "c"); */ - AMstackItem(NULL, AMlistPutStr(doc, sublist, 2, true, AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutStr(doc, sublist, 2, true, AMstr("c"))); /* doc.insert(sublist, 0, "z"); */ - AMstackItem(NULL, AMlistPutStr(doc, sublist, 0, true, AMstr("z")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutStr(doc, sublist, 0, true, AMstr("z"))); /* */ /* assert.deepEqual(doc.getWithType(sublist, 0), ["str", "z"]) */ - AMbyteSpan str; - assert_true(AMitemToStr( - AMstackItem(stack_ptr, AMlistGet(doc, sublist, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + AMbyteSpan str = AMpush(&stack, + AMlistGet(doc, sublist, 0, NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "z", str.count); /* assert.deepEqual(doc.getWithType(sublist, 1), ["str", "a"]) */ - assert_true(AMitemToStr( - AMstackItem(stack_ptr, AMlistGet(doc, sublist, 1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + str = AMpush(&stack, + AMlistGet(doc, sublist, 1, NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); /* assert.deepEqual(doc.getWithType(sublist, 2), ["str", "b"]) */ - assert_true(AMitemToStr( - AMstackItem(stack_ptr, AMlistGet(doc, sublist, 2, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + str = AMpush(&stack, + AMlistGet(doc, sublist, 2, NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); /* assert.deepEqual(doc.getWithType(sublist, 3), ["str", "c"]) */ - assert_true(AMitemToStr( - AMstackItem(stack_ptr, AMlistGet(doc, sublist, 3, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + str = AMpush(&stack, + AMlistGet(doc, sublist, 3, NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); /* assert.deepEqual(doc.length(sublist), 4) */ assert_int_equal(AMobjSize(doc, sublist, NULL), 4); /* */ /* doc.put(sublist, 2, "b v2"); */ - AMstackItem(NULL, AMlistPutStr(doc, sublist, 2, false, AMstr("b v2")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutStr(doc, sublist, 2, false, AMstr("b v2"))); /* */ /* assert.deepEqual(doc.getWithType(sublist, 2), ["str", "b v2"]) */ - assert_true(AMitemToStr( - AMstackItem(stack_ptr, AMlistGet(doc, sublist, 2, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + str = AMpush(&stack, + AMlistGet(doc, sublist, 2, NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, 4); assert_memory_equal(str.src, "b v2", str.count); /* assert.deepEqual(doc.length(sublist), 4) */ @@ -335,217 +334,233 @@ static void test_should_be_able_to_make_lists(void** state) { * \brief lists have insert, set, splice, and push ops */ static void test_lists_have_insert_set_splice_and_push_ops(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc = create() */ - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; /* const root = "_root" */ /* */ /* const sublist = doc.putObject(root, "letters", []) */ - AMobjId const* const sublist = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("letters"), AM_OBJ_TYPE_LIST), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const sublist = AMpush( + &stack, + AMmapPutObject(doc, AM_ROOT, AMstr("letters"), AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* doc.insert(sublist, 0, "a"); */ - AMstackItem(NULL, AMlistPutStr(doc, sublist, 0, true, AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutStr(doc, sublist, 0, true, AMstr("a"))); /* doc.insert(sublist, 0, "b"); */ - AMstackItem(NULL, AMlistPutStr(doc, sublist, 0, true, AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutStr(doc, sublist, 0, true, AMstr("b"))); /* assert.deepEqual(doc.materialize(), { letters: ["b", "a"] }) */ - AMitem* doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE)); - assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); - AMbyteSpan key; - assert_true(AMitemKey(doc_item, &key)); + AMmapItems doc_items = AMpush(&stack, + AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + AMmapItem const* doc_item = AMmapItemsNext(&doc_items, 1); + AMbyteSpan key = AMmapItemKey(doc_item); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - assert_int_equal(AMitemsSize(&list_items), 2); - AMbyteSpan str; - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + AMlistItems list_items = AMpush( + &stack, + AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - assert_null(AMitemsNext(&list_items, 1)); + assert_null(AMlistItemsNext(&list_items, 1)); } /* doc.push(sublist, "c"); */ - AMstackItem(NULL, AMlistPutStr(doc, sublist, SIZE_MAX, true, AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutStr(doc, sublist, SIZE_MAX, true, AMstr("c"))); /* const heads = doc.getHeads() */ - AMitems const heads = AMstackItems(stack_ptr, AMgetHeads(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMchangeHashes const heads = AMpush(&stack, + AMgetHeads(doc), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; /* assert.deepEqual(doc.materialize(), { letters: ["b", "a", "c"] }) */ - doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE)); - assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(doc_item, &key)); + doc_items = AMpush(&stack, + AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + doc_item = AMmapItemsNext(&doc_items, 1); + key = AMmapItemKey(doc_item); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - assert_int_equal(AMitemsSize(&list_items), 3); - AMbyteSpan str; - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + AMlistItems list_items = AMpush( + &stack, + AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - assert_null(AMitemsNext(&list_items, 1)); + assert_null(AMlistItemsNext(&list_items, 1)); } /* doc.push(sublist, 3, "timestamp"); */ - AMstackItem(NULL, AMlistPutTimestamp(doc, sublist, SIZE_MAX, true, 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - /* assert.deepEqual(doc.materialize(), { letters: ["b", "a", "c", new - * Date(3)] } */ - doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE)); - assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(doc_item, &key)); + AMfree(AMlistPutTimestamp(doc, sublist, SIZE_MAX, true, 3)); + /* assert.deepEqual(doc.materialize(), { letters: ["b", "a", "c", new Date(3)] } */ + doc_items = AMpush(&stack, + AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + doc_item = AMmapItemsNext(&doc_items, 1); + key = AMmapItemKey(doc_item); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_STR | AM_VAL_TYPE_TIMESTAMP)); - assert_int_equal(AMitemsSize(&list_items), 4); - AMbyteSpan str; - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + AMlistItems list_items = AMpush( + &stack, + AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - int64_t timestamp; - assert_true(AMitemToTimestamp(AMitemsNext(&list_items, 1), ×tamp)); - assert_int_equal(timestamp, 3); - assert_null(AMitemsNext(&list_items, 1)); + assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).timestamp, + 3); + assert_null(AMlistItemsNext(&list_items, 1)); } /* doc.splice(sublist, 1, 1, ["d", "e", "f"]); */ - AMresult* data = AMstackResult( - stack_ptr, AMresultFrom(3, AMitemFromStr(AMstr("d")), AMitemFromStr(AMstr("e")), AMitemFromStr(AMstr("f"))), - NULL, NULL); - AMstackItem(NULL, AMsplice(doc, sublist, 1, 1, AMresultItems(data)), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - /* assert.deepEqual(doc.materialize(), { letters: ["b", "d", "e", "f", "c", - * new Date(3)] } */ - doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE)); - assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(doc_item, &key)); + static AMvalue const DATA[] = {{.str_tag = AM_VALUE_STR, .str = {.src = "d", .count = 1}}, + {.str_tag = AM_VALUE_STR, .str = {.src = "e", .count = 1}}, + {.str_tag = AM_VALUE_STR, .str = {.src = "f", .count = 1}}}; + AMfree(AMsplice(doc, sublist, 1, 1, DATA, sizeof(DATA)/sizeof(AMvalue))); + /* assert.deepEqual(doc.materialize(), { letters: ["b", "d", "e", "f", "c", new Date(3)] } */ + doc_items = AMpush(&stack, + AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + doc_item = AMmapItemsNext(&doc_items, 1); + key = AMmapItemKey(doc_item); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_STR | AM_VAL_TYPE_TIMESTAMP)); - AMbyteSpan str; - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + AMlistItems list_items = AMpush( + &stack, + AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "d", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "e", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "f", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - int64_t timestamp; - assert_true(AMitemToTimestamp(AMitemsNext(&list_items, 1), ×tamp)); - assert_int_equal(timestamp, 3); - assert_null(AMitemsNext(&list_items, 1)); + assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).timestamp, + 3); + assert_null(AMlistItemsNext(&list_items, 1)); } /* doc.put(sublist, 0, "z"); */ - AMstackItem(NULL, AMlistPutStr(doc, sublist, 0, false, AMstr("z")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - /* assert.deepEqual(doc.materialize(), { letters: ["z", "d", "e", "f", "c", - * new Date(3)] } */ - doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE)); - assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(doc_item, &key)); + AMfree(AMlistPutStr(doc, sublist, 0, false, AMstr("z"))); + /* assert.deepEqual(doc.materialize(), { letters: ["z", "d", "e", "f", "c", new Date(3)] } */ + doc_items = AMpush(&stack, + AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + doc_item = AMmapItemsNext(&doc_items, 1); + key = AMmapItemKey(doc_item); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_STR | AM_VAL_TYPE_TIMESTAMP)); - AMbyteSpan str; - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + AMlistItems list_items = AMpush( + &stack, + AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "z", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "d", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "e", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "f", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - int64_t timestamp; - assert_true(AMitemToTimestamp(AMitemsNext(&list_items, 1), ×tamp)); - assert_int_equal(timestamp, 3); - assert_null(AMitemsNext(&list_items, 1)); + assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).timestamp, + 3); + assert_null(AMlistItemsNext(&list_items, 1)); } - /* assert.deepEqual(doc.materialize(sublist), ["z", "d", "e", "f", "c", new - * Date(3)] */ - AMitems sublist_items = AMstackItems(stack_ptr, AMlistRange(doc, sublist, 0, SIZE_MAX, NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR | AM_VAL_TYPE_TIMESTAMP)); - AMbyteSpan str; - assert_true(AMitemToStr(AMitemsNext(&sublist_items, 1), &str)); + /* assert.deepEqual(doc.materialize(sublist), ["z", "d", "e", "f", "c", new Date(3)] */ + AMlistItems sublist_items = AMpush( + &stack, + AMlistRange(doc, sublist, 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "z", str.count); - assert_true(AMitemToStr(AMitemsNext(&sublist_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "d", str.count); - assert_true(AMitemToStr(AMitemsNext(&sublist_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "e", str.count); - assert_true(AMitemToStr(AMitemsNext(&sublist_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "f", str.count); - assert_true(AMitemToStr(AMitemsNext(&sublist_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - int64_t timestamp; - assert_true(AMitemToTimestamp(AMitemsNext(&sublist_items, 1), ×tamp)); - assert_int_equal(timestamp, 3); - assert_null(AMitemsNext(&sublist_items, 1)); + assert_int_equal(AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).timestamp, + 3); + assert_null(AMlistItemsNext(&sublist_items, 1)); /* assert.deepEqual(doc.length(sublist), 6) */ assert_int_equal(AMobjSize(doc, sublist, NULL), 6); - /* assert.deepEqual(doc.materialize("/", heads), { letters: ["b", "a", "c"] - * } */ - doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE)); - assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(doc_item, &key)); + /* assert.deepEqual(doc.materialize("/", heads), { letters: ["b", "a", "c"] } */ + doc_items = AMpush(&stack, + AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + doc_item = AMmapItemsNext(&doc_items, 1); + key = AMmapItemKey(doc_item); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, &heads), - cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - AMbyteSpan str; - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + AMlistItems list_items = AMpush( + &stack, + AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, &heads), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - assert_null(AMitemsNext(&list_items, 1)); + assert_null(AMlistItemsNext(&list_items, 1)); } } @@ -553,54 +568,67 @@ static void test_lists_have_insert_set_splice_and_push_ops(void** state) { * \brief should be able to delete non-existent props */ static void test_should_be_able_to_delete_non_existent_props(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc = create() */ - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; /* */ /* doc.put("_root", "foo", "bar") */ - AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("foo"), AMstr("bar")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("foo"), AMstr("bar"))); /* doc.put("_root", "bip", "bap") */ - AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("bip"), AMstr("bap")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("bip"), AMstr("bap"))); /* const hash1 = doc.commit() */ - AMitems const hash1 = - AMstackItems(stack_ptr, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMchangeHashes const hash1 = AMpush(&stack, + AMcommit(doc, AMstr(NULL), NULL), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; /* */ /* assert.deepEqual(doc.keys("_root"), ["bip", "foo"]) */ - AMitems keys = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - AMbyteSpan str; - assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); + AMstrs keys = AMpush(&stack, + AMkeys(doc, AM_ROOT, NULL), + AM_VALUE_STRS, + cmocka_cb).strs; + AMbyteSpan str = AMstrsNext(&keys, 1); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bip", str.count); - assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); + str = AMstrsNext(&keys, 1); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "foo", str.count); /* */ /* doc.delete("_root", "foo") */ - AMstackItem(NULL, AMmapDelete(doc, AM_ROOT, AMstr("foo")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapDelete(doc, AM_ROOT, AMstr("foo"))); /* doc.delete("_root", "baz") */ - AMstackItem(NULL, AMmapDelete(doc, AM_ROOT, AMstr("baz")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapDelete(doc, AM_ROOT, AMstr("baz"))); /* const hash2 = doc.commit() */ - AMitems const hash2 = - AMstackItems(stack_ptr, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMchangeHashes const hash2 = AMpush(&stack, + AMcommit(doc, AMstr(NULL), NULL), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; /* */ /* assert.deepEqual(doc.keys("_root"), ["bip"]) */ - keys = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); + keys = AMpush(&stack, + AMkeys(doc, AM_ROOT, NULL), + AM_VALUE_STRS, + cmocka_cb).strs; + str = AMstrsNext(&keys, 1); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bip", str.count); /* assert.deepEqual(doc.keys("_root", [hash1]), ["bip", "foo"]) */ - keys = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, &hash1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); + keys = AMpush(&stack, + AMkeys(doc, AM_ROOT, &hash1), + AM_VALUE_STRS, + cmocka_cb).strs; + str = AMstrsNext(&keys, 1); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bip", str.count); - assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); + str = AMstrsNext(&keys, 1); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "foo", str.count); /* assert.deepEqual(doc.keys("_root", [hash2]), ["bip"]) */ - keys = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, &hash2), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); + keys = AMpush(&stack, + AMkeys(doc, AM_ROOT, &hash2), + AM_VALUE_STRS, + cmocka_cb).strs; + str = AMstrsNext(&keys, 1); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bip", str.count); } @@ -608,114 +636,123 @@ static void test_should_be_able_to_delete_non_existent_props(void** state) { /** * \brief should be able to del */ -static void test_should_be_able_to_del(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; +static void test_should_be_able_to_del(void **state) { + AMresultStack* stack = *state; /* const doc = create() */ - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; /* const root = "_root" */ /* */ /* doc.put(root, "xxx", "xxx"); */ - AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("xxx"), AMstr("xxx")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("xxx"), AMstr("xxx"))); /* assert.deepEqual(doc.getWithType(root, "xxx"), ["str", "xxx"]) */ - AMbyteSpan str; - assert_true(AMitemToStr( - AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("xxx"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), - &str)); + AMbyteSpan const str = AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("xxx"), NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, 3); assert_memory_equal(str.src, "xxx", str.count); /* doc.delete(root, "xxx"); */ - AMstackItem(NULL, AMmapDelete(doc, AM_ROOT, AMstr("xxx")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapDelete(doc, AM_ROOT, AMstr("xxx"))); /* assert.deepEqual(doc.getWithType(root, "xxx"), undefined) */ - AMstackItem(NULL, AMmapGet(doc, AM_ROOT, AMstr("xxx"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("xxx"), NULL), + AM_VALUE_VOID, + cmocka_cb); } /** * \brief should be able to use counters */ static void test_should_be_able_to_use_counters(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc = create() */ - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; /* const root = "_root" */ /* */ /* doc.put(root, "counter", 10, "counter"); */ - AMstackItem(NULL, AMmapPutCounter(doc, AM_ROOT, AMstr("counter"), 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutCounter(doc, AM_ROOT, AMstr("counter"), 10)); /* assert.deepEqual(doc.getWithType(root, "counter"), ["counter", 10]) */ - int64_t counter; - assert_true(AMitemToCounter(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_COUNTER)), - &counter)); - assert_int_equal(counter, 10); + assert_int_equal(AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), + AM_VALUE_COUNTER, + cmocka_cb).counter, 10); /* doc.increment(root, "counter", 10); */ - AMstackItem(NULL, AMmapIncrement(doc, AM_ROOT, AMstr("counter"), 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapIncrement(doc, AM_ROOT, AMstr("counter"), 10)); /* assert.deepEqual(doc.getWithType(root, "counter"), ["counter", 20]) */ - assert_true(AMitemToCounter(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_COUNTER)), - &counter)); - assert_int_equal(counter, 20); + assert_int_equal(AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), + AM_VALUE_COUNTER, + cmocka_cb).counter, 20); /* doc.increment(root, "counter", -5); */ - AMstackItem(NULL, AMmapIncrement(doc, AM_ROOT, AMstr("counter"), -5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapIncrement(doc, AM_ROOT, AMstr("counter"), -5)); /* assert.deepEqual(doc.getWithType(root, "counter"), ["counter", 15]) */ - assert_true(AMitemToCounter(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_COUNTER)), - &counter)); - assert_int_equal(counter, 15); + assert_int_equal(AMpush(&stack, + AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), + AM_VALUE_COUNTER, + cmocka_cb).counter, 15); } /** * \brief should be able to splice text */ static void test_should_be_able_to_splice_text(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc = create() */ - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; /* const root = "_root"; */ /* */ /* const text = doc.putObject(root, "text", ""); */ - 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))); + AMobjId const* const text = AMpush( + &stack, + AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* doc.splice(text, 0, 0, "hello ") */ - AMstackItem(NULL, AMspliceText(doc, text, 0, 0, AMstr("hello ")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMspliceText(doc, text, 0, 0, AMstr("hello "))); /* doc.splice(text, 6, 0, "world") */ - AMstackItem(NULL, AMspliceText(doc, text, 6, 0, AMstr("world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMspliceText(doc, text, 6, 0, AMstr("world"))); /* doc.splice(text, 11, 0, "!?") */ - AMstackItem(NULL, AMspliceText(doc, text, 11, 0, AMstr("!?")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMspliceText(doc, text, 11, 0, AMstr("!?"))); /* assert.deepEqual(doc.getWithType(text, 0), ["str", "h"]) */ - AMbyteSpan str; - assert_true( - AMitemToStr(AMstackItem(stack_ptr, AMlistGet(doc, text, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + AMbyteSpan str = AMpush(&stack, + AMlistGet(doc, text, 0, NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "h", str.count); /* assert.deepEqual(doc.getWithType(text, 1), ["str", "e"]) */ - assert_true( - AMitemToStr(AMstackItem(stack_ptr, AMlistGet(doc, text, 1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + str = AMpush(&stack, + AMlistGet(doc, text, 1, NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "e", str.count); /* assert.deepEqual(doc.getWithType(text, 9), ["str", "l"]) */ - assert_true( - AMitemToStr(AMstackItem(stack_ptr, AMlistGet(doc, text, 9, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + str = AMpush(&stack, + AMlistGet(doc, text, 9, NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "l", str.count); /* assert.deepEqual(doc.getWithType(text, 10), ["str", "d"]) */ - assert_true(AMitemToStr( - AMstackItem(stack_ptr, AMlistGet(doc, text, 10, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + str = AMpush(&stack, + AMlistGet(doc, text, 10, NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "d", str.count); /* assert.deepEqual(doc.getWithType(text, 11), ["str", "!"]) */ - assert_true(AMitemToStr( - AMstackItem(stack_ptr, AMlistGet(doc, text, 11, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + str = AMpush(&stack, + AMlistGet(doc, text, 11, NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "!", str.count); /* assert.deepEqual(doc.getWithType(text, 12), ["str", "?"]) */ - assert_true(AMitemToStr( - AMstackItem(stack_ptr, AMlistGet(doc, text, 12, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + str = AMpush(&stack, + AMlistGet(doc, text, 12, NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "?", str.count); } @@ -724,45 +761,52 @@ static void test_should_be_able_to_splice_text(void** state) { * \brief should be able to save all or incrementally */ static void test_should_be_able_to_save_all_or_incrementally(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc = create() */ - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; /* */ /* doc.put("_root", "foo", 1) */ - AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("foo"), 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("foo"), 1)); /* */ /* const save1 = doc.save() */ - AMbyteSpan save1; - assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save1)); + AMbyteSpan const save1 = AMpush(&stack, + AMsave(doc), + AM_VALUE_BYTES, + cmocka_cb).bytes; /* */ /* doc.put("_root", "bar", 2) */ - AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("bar"), 2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("bar"), 2)); /* */ /* const saveMidway = doc.clone().save(); */ - AMdoc* doc_clone; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMclone(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc_clone)); - AMbyteSpan saveMidway; - assert_true( - AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc_clone), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &saveMidway)); + AMbyteSpan const saveMidway = AMpush(&stack, + AMsave( + AMpush(&stack, + AMclone(doc), + AM_VALUE_DOC, + cmocka_cb).doc), + AM_VALUE_BYTES, + cmocka_cb).bytes; /* */ /* const save2 = doc.saveIncremental(); */ - AMbyteSpan save2; - assert_true( - AMitemToBytes(AMstackItem(stack_ptr, AMsaveIncremental(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save2)); + AMbyteSpan const save2 = AMpush(&stack, + AMsaveIncremental(doc), + AM_VALUE_BYTES, + cmocka_cb).bytes; /* */ /* doc.put("_root", "baz", 3); */ - AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("baz"), 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("baz"), 3)); /* */ /* const save3 = doc.saveIncremental(); */ - AMbyteSpan save3; - assert_true( - AMitemToBytes(AMstackItem(stack_ptr, AMsaveIncremental(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save3)); + AMbyteSpan const save3 = AMpush(&stack, + AMsaveIncremental(doc), + AM_VALUE_BYTES, + cmocka_cb).bytes; /* */ /* const saveA = doc.save(); */ - AMbyteSpan saveA; - assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &saveA)); + AMbyteSpan const saveA = AMpush(&stack, + AMsave(doc), + AM_VALUE_BYTES, + cmocka_cb).bytes; /* const saveB = new Uint8Array([...save1, ...save2, ...save3]); */ size_t const saveB_count = save1.count + save2.count + save3.count; uint8_t* const saveB_src = test_malloc(saveB_count); @@ -774,83 +818,104 @@ static void test_should_be_able_to_save_all_or_incrementally(void** state) { assert_memory_not_equal(saveA.src, saveB_src, saveA.count); /* */ /* const docA = load(saveA); */ - AMdoc* docA; - assert_true(AMitemToDoc( - AMstackItem(stack_ptr, AMload(saveA.src, saveA.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &docA)); + AMdoc* const docA = AMpush(&stack, + AMload(saveA.src, saveA.count), + AM_VALUE_DOC, + cmocka_cb).doc; /* const docB = load(saveB); */ - AMdoc* docB; - assert_true(AMitemToDoc( - AMstackItem(stack_ptr, AMload(saveB_src, saveB_count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &docB)); + AMdoc* const docB = AMpush(&stack, + AMload(saveB_src, saveB_count), + AM_VALUE_DOC, + cmocka_cb).doc; test_free(saveB_src); /* const docC = load(saveMidway) */ - AMdoc* docC; - assert_true(AMitemToDoc( - AMstackItem(stack_ptr, AMload(saveMidway.src, saveMidway.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &docC)); + AMdoc* const docC = AMpush(&stack, + AMload(saveMidway.src, saveMidway.count), + AM_VALUE_DOC, + cmocka_cb).doc; /* docC.loadIncremental(save3) */ - AMstackItem(NULL, AMloadIncremental(docC, save3.src, save3.count), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT)); + AMfree(AMloadIncremental(docC, save3.src, save3.count)); /* */ /* assert.deepEqual(docA.keys("_root"), docB.keys("_root")); */ - AMitems const keysA = AMstackItems(stack_ptr, AMkeys(docA, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - AMitems const keysB = AMstackItems(stack_ptr, AMkeys(docB, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - assert_true(AMitemsEqual(&keysA, &keysB)); + AMstrs const keysA = AMpush(&stack, + AMkeys(docA, AM_ROOT, NULL), + AM_VALUE_STRS, + cmocka_cb).strs; + AMstrs const keysB = AMpush(&stack, + AMkeys(docB, AM_ROOT, NULL), + AM_VALUE_STRS, + cmocka_cb).strs; + assert_int_equal(AMstrsCmp(&keysA, &keysB), 0); /* assert.deepEqual(docA.save(), docB.save()); */ - AMbyteSpan docA_save; - assert_true( - AMitemToBytes(AMstackItem(stack_ptr, AMsave(docA), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &docA_save)); - AMbyteSpan docB_save; - assert_true( - AMitemToBytes(AMstackItem(stack_ptr, AMsave(docB), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &docB_save)); - assert_int_equal(docA_save.count, docB_save.count); - assert_memory_equal(docA_save.src, docB_save.src, docA_save.count); + AMbyteSpan const save = AMpush(&stack, + AMsave(docA), + AM_VALUE_BYTES, + cmocka_cb).bytes; + assert_memory_equal(save.src, + AMpush(&stack, + AMsave(docB), + AM_VALUE_BYTES, + cmocka_cb).bytes.src, + save.count); /* assert.deepEqual(docA.save(), docC.save()); */ - AMbyteSpan docC_save; - assert_true( - AMitemToBytes(AMstackItem(stack_ptr, AMsave(docC), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &docC_save)); - assert_int_equal(docA_save.count, docC_save.count); - assert_memory_equal(docA_save.src, docC_save.src, docA_save.count); + assert_memory_equal(save.src, + AMpush(&stack, + AMsave(docC), + AM_VALUE_BYTES, + cmocka_cb).bytes.src, + save.count); } /** * \brief should be able to splice text #2 */ static void test_should_be_able_to_splice_text_2(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc = create() */ - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; /* const text = doc.putObject("_root", "text", ""); */ - 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))); + AMobjId const* const text = AMpush( + &stack, + AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* doc.splice(text, 0, 0, "hello world"); */ - AMstackItem(NULL, AMspliceText(doc, text, 0, 0, AMstr("hello world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMspliceText(doc, text, 0, 0, AMstr("hello world"))); /* const hash1 = doc.commit(); */ - AMitems const hash1 = - AMstackItems(stack_ptr, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMchangeHashes const hash1 = AMpush(&stack, + AMcommit(doc, AMstr(NULL), NULL), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; /* doc.splice(text, 6, 0, "big bad "); */ - AMstackItem(NULL, AMspliceText(doc, text, 6, 0, AMstr("big bad ")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMspliceText(doc, text, 6, 0, AMstr("big bad "))); /* const hash2 = doc.commit(); */ - AMitems const hash2 = - AMstackItems(stack_ptr, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMchangeHashes const hash2 = AMpush(&stack, + AMcommit(doc, AMstr(NULL), NULL), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; /* assert.strictEqual(doc.text(text), "hello big bad world") */ - AMbyteSpan str; - assert_true( - AMitemToStr(AMstackItem(stack_ptr, AMtext(doc, text, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + AMbyteSpan str = AMpush(&stack, + AMtext(doc, text, NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, strlen("hello big bad world")); assert_memory_equal(str.src, "hello big bad world", str.count); /* assert.strictEqual(doc.length(text), 19) */ assert_int_equal(AMobjSize(doc, text, NULL), 19); /* assert.strictEqual(doc.text(text, [hash1]), "hello world") */ - assert_true( - AMitemToStr(AMstackItem(stack_ptr, AMtext(doc, text, &hash1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + str = AMpush(&stack, + AMtext(doc, text, &hash1), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, strlen("hello world")); assert_memory_equal(str.src, "hello world", str.count); /* assert.strictEqual(doc.length(text, [hash1]), 11) */ assert_int_equal(AMobjSize(doc, text, &hash1), 11); /* assert.strictEqual(doc.text(text, [hash2]), "hello big bad world") */ - assert_true( - AMitemToStr(AMstackItem(stack_ptr, AMtext(doc, text, &hash2), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + str = AMpush(&stack, + AMtext(doc, text, &hash2), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, strlen("hello big bad world")); assert_memory_equal(str.src, "hello big bad world", str.count); /* assert.strictEqual(doc.length(text, [hash2]), 19) */ @@ -861,234 +926,266 @@ static void test_should_be_able_to_splice_text_2(void** state) { * \brief local inc increments all visible counters in a map */ static void test_local_inc_increments_all_visible_counters_in_a_map(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc1 = create("aaaa") */ - AMactorId const* actor_id; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); - AMdoc* doc1; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); + AMdoc* const doc1 = AMpush(&stack, + AMcreate(AMpush(&stack, + AMactorIdInitStr(AMstr("aaaa")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id), + AM_VALUE_DOC, + cmocka_cb).doc; /* doc1.put("_root", "hello", "world") */ - AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("hello"), AMstr("world")), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("hello"), AMstr("world"))); /* const doc2 = load(doc1.save(), "bbbb"); */ - AMbyteSpan save; - assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save)); - AMdoc* doc2; - assert_true( - AMitemToDoc(AMstackItem(stack_ptr, AMload(save.src, save.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("bbbb")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); - AMstackItem(NULL, AMsetActorId(doc2, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMbyteSpan const save = AMpush(&stack, + AMsave(doc1), + AM_VALUE_BYTES, + cmocka_cb).bytes; + AMdoc* const doc2 = AMpush(&stack, + AMload(save.src, save.count), + AM_VALUE_DOC, + cmocka_cb).doc; + AMfree(AMsetActorId(doc2, AMpush(&stack, + AMactorIdInitStr(AMstr("bbbb")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id)); /* const doc3 = load(doc1.save(), "cccc"); */ - AMdoc* doc3; - assert_true( - AMitemToDoc(AMstackItem(stack_ptr, AMload(save.src, save.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc3)); - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("cccc")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); - AMstackItem(NULL, AMsetActorId(doc3, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMdoc* const doc3 = AMpush(&stack, + AMload(save.src, save.count), + AM_VALUE_DOC, + cmocka_cb).doc; + AMfree(AMsetActorId(doc3, AMpush(&stack, + AMactorIdInitStr(AMstr("cccc")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id)); /* let heads = doc1.getHeads() */ - AMitems const heads1 = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMchangeHashes const heads1 = AMpush(&stack, + AMgetHeads(doc1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; /* doc1.put("_root", "cnt", 20) */ - AMstackItem(NULL, AMmapPutInt(doc1, AM_ROOT, AMstr("cnt"), 20), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutInt(doc1, AM_ROOT, AMstr("cnt"), 20)); /* doc2.put("_root", "cnt", 0, "counter") */ - AMstackItem(NULL, AMmapPutCounter(doc2, AM_ROOT, AMstr("cnt"), 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutCounter(doc2, AM_ROOT, AMstr("cnt"), 0)); /* doc3.put("_root", "cnt", 10, "counter") */ - AMstackItem(NULL, AMmapPutCounter(doc3, AM_ROOT, AMstr("cnt"), 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutCounter(doc3, AM_ROOT, AMstr("cnt"), 10)); /* doc1.applyChanges(doc2.getChanges(heads)) */ - AMitems const changes2 = - AMstackItems(stack_ptr, AMgetChanges(doc2, &heads1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - AMstackItem(NULL, AMapplyChanges(doc1, &changes2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMchanges const changes2 = AMpush(&stack, + AMgetChanges(doc2, &heads1), + AM_VALUE_CHANGES, + cmocka_cb).changes; + AMfree(AMapplyChanges(doc1, &changes2)); /* doc1.applyChanges(doc3.getChanges(heads)) */ - AMitems const changes3 = - AMstackItems(stack_ptr, AMgetChanges(doc3, &heads1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - AMstackItem(NULL, AMapplyChanges(doc1, &changes3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMchanges const changes3 = AMpush(&stack, + AMgetChanges(doc3, &heads1), + AM_VALUE_CHANGES, + cmocka_cb).changes; + AMfree(AMapplyChanges(doc1, &changes3)); /* let result = doc1.getAll("_root", "cnt") */ - AMitems result = AMstackItems(stack_ptr, AMmapGetAll(doc1, AM_ROOT, AMstr("cnt"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_COUNTER | AM_VAL_TYPE_INT | AM_VAL_TYPE_STR)); + AMobjItems result = AMpush(&stack, + AMmapGetAll(doc1, AM_ROOT, AMstr("cnt"), NULL), + AM_VALUE_OBJ_ITEMS, + cmocka_cb).obj_items; /* assert.deepEqual(result, [ ['int', 20, '2@aaaa'], ['counter', 0, '2@bbbb'], ['counter', 10, '2@cccc'], ]) */ - AMitem* result_item = AMitemsNext(&result, 1); - int64_t int_; - assert_true(AMitemToInt(result_item, &int_)); - assert_int_equal(int_, 20); - assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 2); - AMbyteSpan str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); + AMobjItem const* result_item = AMobjItemsNext(&result, 1); + assert_int_equal(AMobjItemValue(result_item).int_, 20); + assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 2); + AMbyteSpan str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "aaaa", str.count); - result_item = AMitemsNext(&result, 1); - int64_t counter; - assert_true(AMitemToCounter(result_item, &counter)); - assert_int_equal(counter, 0); - assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 2); - str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); + result_item = AMobjItemsNext(&result, 1); + assert_int_equal(AMobjItemValue(result_item).counter, 0); + assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 2); + str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "bbbb", str.count); - result_item = AMitemsNext(&result, 1); - assert_true(AMitemToCounter(result_item, &counter)); - assert_int_equal(counter, 10); - assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 2); - str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); + result_item = AMobjItemsNext(&result, 1); + assert_int_equal(AMobjItemValue(result_item).counter, 10); + assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 2); + str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "cccc", str.count); /* doc1.increment("_root", "cnt", 5) */ - AMstackItem(NULL, AMmapIncrement(doc1, AM_ROOT, AMstr("cnt"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapIncrement(doc1, AM_ROOT, AMstr("cnt"), 5)); /* result = doc1.getAll("_root", "cnt") */ - result = AMstackItems(stack_ptr, AMmapGetAll(doc1, AM_ROOT, AMstr("cnt"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_COUNTER)); + result = AMpush(&stack, + AMmapGetAll(doc1, AM_ROOT, AMstr("cnt"), NULL), + AM_VALUE_OBJ_ITEMS, + cmocka_cb).obj_items; /* assert.deepEqual(result, [ ['counter', 5, '2@bbbb'], ['counter', 15, '2@cccc'], ]) */ - result_item = AMitemsNext(&result, 1); - assert_true(AMitemToCounter(result_item, &counter)); - assert_int_equal(counter, 5); - assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 2); - str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); + result_item = AMobjItemsNext(&result, 1); + assert_int_equal(AMobjItemValue(result_item).counter, 5); + assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 2); + str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "bbbb", str.count); - result_item = AMitemsNext(&result, 1); - assert_true(AMitemToCounter(result_item, &counter)); - assert_int_equal(counter, 15); - assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 2); - str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); + result_item = AMobjItemsNext(&result, 1); + assert_int_equal(AMobjItemValue(result_item).counter, 15); + assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 2); + str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "cccc", str.count); /* */ /* const save1 = doc1.save() */ - AMbyteSpan save1; - assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save1)); + AMbyteSpan const save1 = AMpush(&stack, + AMsave(doc1), + AM_VALUE_BYTES, + cmocka_cb).bytes; /* const doc4 = load(save1) */ - AMdoc* doc4; - assert_true(AMitemToDoc( - AMstackItem(stack_ptr, AMload(save1.src, save1.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc4)); + AMdoc* const doc4 = AMpush(&stack, + AMload(save1.src, save1.count), + AM_VALUE_DOC, + cmocka_cb).doc; /* assert.deepEqual(doc4.save(), save1); */ - AMbyteSpan doc4_save; - assert_true( - AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc4), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &doc4_save)); - assert_int_equal(doc4_save.count, save1.count); - assert_memory_equal(doc4_save.src, save1.src, doc4_save.count); + assert_memory_equal(AMpush(&stack, + AMsave(doc4), + AM_VALUE_BYTES, + cmocka_cb).bytes.src, + save1.src, + save1.count); } /** * \brief local inc increments all visible counters in a sequence */ static void test_local_inc_increments_all_visible_counters_in_a_sequence(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc1 = create("aaaa") */ - AMactorId const* actor_id; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); - AMdoc* doc1; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); + AMdoc* const doc1 = AMpush(&stack, + AMcreate(AMpush(&stack, + AMactorIdInitStr(AMstr("aaaa")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id), + AM_VALUE_DOC, + cmocka_cb).doc; /* const seq = doc1.putObject("_root", "seq", []) */ - AMobjId const* const seq = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc1, AM_ROOT, AMstr("seq"), AM_OBJ_TYPE_LIST), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const seq = AMpush( + &stack, + AMmapPutObject(doc1, AM_ROOT, AMstr("seq"), AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* doc1.insert(seq, 0, "hello") */ - AMstackItem(NULL, AMlistPutStr(doc1, seq, 0, true, AMstr("hello")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutStr(doc1, seq, 0, true, AMstr("hello"))); /* const doc2 = load(doc1.save(), "bbbb"); */ - AMbyteSpan save1; - assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save1)); - AMdoc* doc2; - assert_true(AMitemToDoc( - AMstackItem(stack_ptr, AMload(save1.src, save1.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("bbbb")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); - AMstackItem(NULL, AMsetActorId(doc2, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMbyteSpan const save1 = AMpush(&stack, + AMsave(doc1), + AM_VALUE_BYTES, + cmocka_cb).bytes; + AMdoc* const doc2 = AMpush(&stack, + AMload(save1.src, save1.count), + AM_VALUE_DOC, + cmocka_cb).doc; + AMfree(AMsetActorId(doc2, AMpush(&stack, + AMactorIdInitStr(AMstr("bbbb")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id)); /* const doc3 = load(doc1.save(), "cccc"); */ - AMdoc* doc3; - assert_true(AMitemToDoc( - AMstackItem(stack_ptr, AMload(save1.src, save1.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc3)); - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("cccc")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); - AMstackItem(NULL, AMsetActorId(doc3, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMdoc* const doc3 = AMpush(&stack, + AMload(save1.src, save1.count), + AM_VALUE_DOC, + cmocka_cb).doc; + AMfree(AMsetActorId(doc3, AMpush(&stack, + AMactorIdInitStr(AMstr("cccc")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id)); /* let heads = doc1.getHeads() */ - AMitems const heads1 = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMchangeHashes const heads1 = AMpush(&stack, + AMgetHeads(doc1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; /* doc1.put(seq, 0, 20) */ - AMstackItem(NULL, AMlistPutInt(doc1, seq, 0, false, 20), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutInt(doc1, seq, 0, false, 20)); /* doc2.put(seq, 0, 0, "counter") */ - AMstackItem(NULL, AMlistPutCounter(doc2, seq, 0, false, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutCounter(doc2, seq, 0, false, 0)); /* doc3.put(seq, 0, 10, "counter") */ - AMstackItem(NULL, AMlistPutCounter(doc3, seq, 0, false, 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutCounter(doc3, seq, 0, false, 10)); /* doc1.applyChanges(doc2.getChanges(heads)) */ - AMitems const changes2 = - AMstackItems(stack_ptr, AMgetChanges(doc2, &heads1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - AMstackItem(NULL, AMapplyChanges(doc1, &changes2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMchanges const changes2 = AMpush(&stack, + AMgetChanges(doc2, &heads1), + AM_VALUE_CHANGES, + cmocka_cb).changes; + AMfree(AMapplyChanges(doc1, &changes2)); /* doc1.applyChanges(doc3.getChanges(heads)) */ - AMitems const changes3 = - AMstackItems(stack_ptr, AMgetChanges(doc3, &heads1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - AMstackItem(NULL, AMapplyChanges(doc1, &changes3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMchanges const changes3 = AMpush(&stack, + AMgetChanges(doc3, &heads1), + AM_VALUE_CHANGES, + cmocka_cb).changes; + AMfree(AMapplyChanges(doc1, &changes3)); /* let result = doc1.getAll(seq, 0) */ - AMitems result = AMstackItems(stack_ptr, AMlistGetAll(doc1, seq, 0, NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_COUNTER | AM_VAL_TYPE_INT)); + AMobjItems result = AMpush(&stack, + AMlistGetAll(doc1, seq, 0, NULL), + AM_VALUE_OBJ_ITEMS, + cmocka_cb).obj_items; /* assert.deepEqual(result, [ ['int', 20, '3@aaaa'], ['counter', 0, '3@bbbb'], ['counter', 10, '3@cccc'], ]) */ - AMitem* result_item = AMitemsNext(&result, 1); - int64_t int_; - assert_true(AMitemToInt(result_item, &int_)); - assert_int_equal(int_, 20); - assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 3); - AMbyteSpan str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); + AMobjItem const* result_item = AMobjItemsNext(&result, 1); + assert_int_equal(AMobjItemValue(result_item).int_, 20); + assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 3); + AMbyteSpan str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "aaaa", str.count); - result_item = AMitemsNext(&result, 1); - int64_t counter; - assert_true(AMitemToCounter(result_item, &counter)); - assert_int_equal(counter, 0); - assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 3); - str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); + result_item = AMobjItemsNext(&result, 1); + assert_int_equal(AMobjItemValue(result_item).counter, 0); + assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 3); + str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); assert_memory_equal(str.src, "bbbb", str.count); - result_item = AMitemsNext(&result, 1); - assert_true(AMitemToCounter(result_item, &counter)); - assert_int_equal(counter, 10); - assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 3); - str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); + result_item = AMobjItemsNext(&result, 1); + assert_int_equal(AMobjItemValue(result_item).counter, 10); + assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 3); + str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "cccc", str.count); /* doc1.increment(seq, 0, 5) */ - AMstackItem(NULL, AMlistIncrement(doc1, seq, 0, 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistIncrement(doc1, seq, 0, 5)); /* result = doc1.getAll(seq, 0) */ - result = AMstackItems(stack_ptr, AMlistGetAll(doc1, seq, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_COUNTER)); + result = AMpush(&stack, + AMlistGetAll(doc1, seq, 0, NULL), + AM_VALUE_OBJ_ITEMS, + cmocka_cb).obj_items; /* assert.deepEqual(result, [ ['counter', 5, '3@bbbb'], ['counter', 15, '3@cccc'], ]) */ - result_item = AMitemsNext(&result, 1); - assert_true(AMitemToCounter(result_item, &counter)); - assert_int_equal(counter, 5); - assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 3); - str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); + result_item = AMobjItemsNext(&result, 1); + assert_int_equal(AMobjItemValue(result_item).counter, 5); + assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 3); + str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "bbbb", str.count); - result_item = AMitemsNext(&result, 1); - assert_true(AMitemToCounter(result_item, &counter)); - assert_int_equal(counter, 15); - assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 3); - str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); + result_item = AMobjItemsNext(&result, 1); + assert_int_equal(AMobjItemValue(result_item).counter, 15); + assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 3); + str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); assert_memory_equal(str.src, "cccc", str.count); /* */ /* const save = doc1.save() */ - AMbyteSpan save; - assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save)); + AMbyteSpan const save = AMpush(&stack, + AMsave(doc1), + AM_VALUE_BYTES, + cmocka_cb).bytes; /* const doc4 = load(save) */ - AMdoc* doc4; - assert_true( - AMitemToDoc(AMstackItem(stack_ptr, AMload(save.src, save.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc4)); + AMdoc* const doc4 = AMpush(&stack, + AMload(save.src, save.count), + AM_VALUE_DOC, + cmocka_cb).doc; /* assert.deepEqual(doc4.save(), save); */ - AMbyteSpan doc4_save; - assert_true( - AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc4), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &doc4_save)); - assert_int_equal(doc4_save.count, save.count); - assert_memory_equal(doc4_save.src, save.src, doc4_save.count); + assert_memory_equal(AMpush(&stack, + AMsave(doc4), + AM_VALUE_BYTES, + cmocka_cb).bytes.src, + save.src, + save.count); } /** @@ -1100,269 +1197,314 @@ static void test_paths_can_be_used_instead_of_objids(void** state); * \brief should be able to fetch changes by hash */ static void test_should_be_able_to_fetch_changes_by_hash(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc1 = create("aaaa") */ - AMactorId const* actor_id; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); - AMdoc* doc1; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); + AMdoc* const doc1 = AMpush(&stack, + AMcreate(AMpush(&stack, + AMactorIdInitStr(AMstr("aaaa")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id), + AM_VALUE_DOC, + cmocka_cb).doc; /* const doc2 = create("bbbb") */ - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("bbbb")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); - AMdoc* doc2; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); + AMdoc* const doc2 = AMpush(&stack, + AMcreate(AMpush(&stack, + AMactorIdInitStr(AMstr("bbbb")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id), + AM_VALUE_DOC, + cmocka_cb).doc; /* doc1.put("/", "a", "b") */ - AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("a"), AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("a"), AMstr("b"))); /* doc2.put("/", "b", "c") */ - AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("b"), AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("b"), AMstr("c"))); /* const head1 = doc1.getHeads() */ - AMitems head1 = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMchangeHashes head1 = AMpush(&stack, + AMgetHeads(doc1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; /* const head2 = doc2.getHeads() */ - AMitems head2 = AMstackItems(stack_ptr, AMgetHeads(doc2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMchangeHashes head2 = AMpush(&stack, + AMgetHeads(doc2), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; /* const change1 = doc1.getChangeByHash(head1[0]) - if (change1 === null) { throw new RangeError("change1 should not be - null") */ - AMbyteSpan change_hash1; - assert_true(AMitemToChangeHash(AMitemsNext(&head1, 1), &change_hash1)); - AMchange const* change1; - assert_true(AMitemToChange(AMstackItem(stack_ptr, AMgetChangeByHash(doc1, change_hash1.src, change_hash1.count), - cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)), - &change1)); + if (change1 === null) { throw new RangeError("change1 should not be null") */ + AMbyteSpan const change_hash1 = AMchangeHashesNext(&head1, 1); + AMchanges change1 = AMpush( + &stack, + AMgetChangeByHash(doc1, change_hash1.src, change_hash1.count), + AM_VALUE_CHANGES, + cmocka_cb).changes; /* const change2 = doc1.getChangeByHash(head2[0]) assert.deepEqual(change2, null) */ - AMbyteSpan change_hash2; - assert_true(AMitemToChangeHash(AMitemsNext(&head2, 1), &change_hash2)); - AMstackItem(NULL, AMgetChangeByHash(doc1, change_hash2.src, change_hash2.count), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMbyteSpan const change_hash2 = AMchangeHashesNext(&head2, 1); + AMpush(&stack, + AMgetChangeByHash(doc1, change_hash2.src, change_hash2.count), + AM_VALUE_VOID, + cmocka_cb); /* assert.deepEqual(decodeChange(change1).hash, head1[0]) */ - assert_memory_equal(AMchangeHash(change1).src, change_hash1.src, change_hash1.count); + assert_memory_equal(AMchangeHash(AMchangesNext(&change1, 1)).src, + change_hash1.src, + change_hash1.count); } /** * \brief recursive sets are possible */ static void test_recursive_sets_are_possible(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc = create("aaaa") */ - AMactorId const* actor_id; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMdoc* const doc = AMpush(&stack, + AMcreate(AMpush(&stack, + AMactorIdInitStr(AMstr("aaaa")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id), + AM_VALUE_DOC, + cmocka_cb).doc; /* const l1 = doc.putObject("_root", "list", [{ foo: "bar" }, [1, 2, 3]] */ - AMobjId const* const l1 = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const l1 = AMpush( + &stack, + AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; { - AMobjId const* const map = AMitemObjId(AMstackItem( - stack_ptr, AMlistPutObject(doc, l1, 0, true, AM_OBJ_TYPE_MAP), cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); - AMstackItem(NULL, AMmapPutStr(doc, map, AMstr("foo"), AMstr("bar")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMobjId const* const list = - AMitemObjId(AMstackItem(stack_ptr, AMlistPutObject(doc, l1, SIZE_MAX, true, AM_OBJ_TYPE_LIST), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const map = AMpush( + &stack, + AMlistPutObject(doc, l1, 0, true, AM_OBJ_TYPE_MAP), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; + AMfree(AMmapPutStr(doc, map, AMstr("foo"), AMstr("bar"))); + AMobjId const* const list = AMpush( + &stack, + AMlistPutObject(doc, l1, SIZE_MAX, true, AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; for (int value = 1; value != 4; ++value) { - AMstackItem(NULL, AMlistPutInt(doc, list, SIZE_MAX, true, value), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutInt(doc, list, SIZE_MAX, true, value)); } } /* const l2 = doc.insertObject(l1, 0, { zip: ["a", "b"] }) */ - AMobjId const* const l2 = AMitemObjId(AMstackItem(stack_ptr, AMlistPutObject(doc, l1, 0, true, AM_OBJ_TYPE_MAP), - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const l2 = AMpush( + &stack, + AMlistPutObject(doc, l1, 0, true, AM_OBJ_TYPE_MAP), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; { - AMobjId const* const list = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, l2, AMstr("zip"), AM_OBJ_TYPE_LIST), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); - AMstackItem(NULL, AMlistPutStr(doc, list, SIZE_MAX, true, AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMlistPutStr(doc, list, SIZE_MAX, true, AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMobjId const* const list = AMpush( + &stack, + AMmapPutObject(doc, l2, AMstr("zip"), AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; + AMfree(AMlistPutStr(doc, list, SIZE_MAX, true, AMstr("a"))); + AMfree(AMlistPutStr(doc, list, SIZE_MAX, true, AMstr("b"))); } - /* const l3 = doc.putObject("_root", "info1", "hello world") // 'text' - * object */ - AMobjId const* const l3 = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("info1"), AM_OBJ_TYPE_TEXT), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); - AMstackItem(NULL, AMspliceText(doc, l3, 0, 0, AMstr("hello world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + /* const l3 = doc.putObject("_root", "info1", "hello world") // 'text' object */ + AMobjId const* const l3 = AMpush( + &stack, + AMmapPutObject(doc, AM_ROOT, AMstr("info1"), AM_OBJ_TYPE_TEXT), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; + AMfree(AMspliceText(doc, l3, 0, 0, AMstr("hello world"))); /* doc.put("_root", "info2", "hello world") // 'str' */ - AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("info2"), AMstr("hello world")), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("info2"), AMstr("hello world"))); /* const l4 = doc.putObject("_root", "info3", "hello world") */ - AMobjId const* const l4 = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("info3"), AM_OBJ_TYPE_TEXT), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); - AMstackItem(NULL, AMspliceText(doc, l4, 0, 0, AMstr("hello world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMobjId const* const l4 = AMpush( + &stack, + AMmapPutObject(doc, AM_ROOT, AMstr("info3"), AM_OBJ_TYPE_TEXT), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; + AMfree(AMspliceText(doc, l4, 0, 0, AMstr("hello world"))); /* assert.deepEqual(doc.materialize(), { "list": [{ zip: ["a", "b"] }, { foo: "bar" }, [1, 2, 3]], "info1": "hello world", "info2": "hello world", "info3": "hello world", - }) */ - AMitems doc_items = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE | AM_VAL_TYPE_STR)); - AMitem* doc_item = AMitemsNext(&doc_items, 1); - assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); - AMbyteSpan key; - assert_true(AMitemKey(doc_item, &key)); + }) */ + AMmapItems doc_items = AMpush(&stack, + AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + AMmapItem const* doc_item = AMmapItemsNext(&doc_items, 1); + AMbyteSpan key = AMmapItemKey(doc_item); assert_int_equal(key.count, strlen("info1")); assert_memory_equal(key.src, "info1", key.count); - AMbyteSpan str; - assert_true(AMitemToStr( - AMstackItem(stack_ptr, AMtext(doc, AMitemObjId(doc_item), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + AMbyteSpan str = AMpush(&stack, + AMtext(doc, AMmapItemObjId(doc_item), NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, strlen("hello world")); assert_memory_equal(str.src, "hello world", str.count); - doc_item = AMitemsNext(&doc_items, 1); - assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(doc_item, &key)); + doc_item = AMmapItemsNext(&doc_items, 1); + key = AMmapItemKey(doc_item); assert_int_equal(key.count, strlen("info2")); assert_memory_equal(key.src, "info2", key.count); - assert_true(AMitemToStr(doc_item, &str)); + str = AMmapItemValue(doc_item).str; assert_int_equal(str.count, strlen("hello world")); assert_memory_equal(str.src, "hello world", str.count); - doc_item = AMitemsNext(&doc_items, 1); - assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(doc_item, &key)); + doc_item = AMmapItemsNext(&doc_items, 1); + key = AMmapItemKey(doc_item); assert_int_equal(key.count, strlen("info3")); assert_memory_equal(key.src, "info3", key.count); - assert_true(AMitemToStr( - AMstackItem(stack_ptr, AMtext(doc, AMitemObjId(doc_item), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + str = AMpush(&stack, + AMtext(doc, AMmapItemObjId(doc_item), NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, strlen("hello world")); assert_memory_equal(str.src, "hello world", str.count); - doc_item = AMitemsNext(&doc_items, 1); - assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(doc_item, &key)); + doc_item = AMmapItemsNext(&doc_items, 1); + key = AMmapItemKey(doc_item); assert_int_equal(key.count, strlen("list")); assert_memory_equal(key.src, "list", key.count); { - AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE)); - AMitem const* list_item = AMitemsNext(&list_items, 1); + AMlistItems list_items = AMpush( + &stack, + AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + AMlistItem const* list_item = AMlistItemsNext(&list_items, 1); { - AMitems map_items = - AMstackItems(stack_ptr, AMmapRange(doc, AMitemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE)); - AMitem const* map_item = AMitemsNext(&map_items, 1); - assert_int_equal(AMitemIdxType(map_item), AM_IDX_TYPE_KEY); - AMbyteSpan key; - assert_true(AMitemKey(map_item, &key)); + AMmapItems map_items = AMpush( + &stack, + AMmapRange(doc, AMlistItemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + AMmapItem const* map_item = AMmapItemsNext(&map_items, 1); + AMbyteSpan const key = AMmapItemKey(map_item); assert_int_equal(key.count, strlen("zip")); assert_memory_equal(key.src, "zip", key.count); { - AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(map_item), 0, SIZE_MAX, NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE | AM_VAL_TYPE_STR)); - AMbyteSpan str; - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + AMlistItems list_items = AMpush( + &stack, + AMlistRange(doc, AMmapItemObjId(map_item), 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); } } - list_item = AMitemsNext(&list_items, 1); + list_item = AMlistItemsNext(&list_items, 1); { - AMitems map_items = - AMstackItems(stack_ptr, AMmapRange(doc, AMitemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE | AM_VAL_TYPE_STR)); - AMitem* map_item = AMitemsNext(&map_items, 1); - assert_int_equal(AMitemIdxType(map_item), AM_IDX_TYPE_KEY); - AMbyteSpan key; - assert_true(AMitemKey(map_item, &key)); + AMmapItems map_items = AMpush( + &stack, + AMmapRange(doc, AMlistItemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + AMmapItem const* map_item = AMmapItemsNext(&map_items, 1); + AMbyteSpan const key = AMmapItemKey(map_item); assert_int_equal(key.count, strlen("foo")); assert_memory_equal(key.src, "foo", key.count); - AMbyteSpan str; - assert_true(AMitemToStr(map_item, &str)); + AMbyteSpan const str = AMmapItemValue(map_item).str; assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bar", str.count); } - list_item = AMitemsNext(&list_items, 1); + list_item = AMlistItemsNext(&list_items, 1); { - AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(list_item), 0, SIZE_MAX, NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_INT)); - int64_t int_; - assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); - assert_int_equal(int_, 1); - assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); - assert_int_equal(int_, 2); - assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); - assert_int_equal(int_, 3); + AMlistItems list_items = AMpush( + &stack, + AMlistRange(doc, AMlistItemObjId(list_item), 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + assert_int_equal(AMlistItemValue( + AMlistItemsNext(&list_items, 1)).int_, + 1); + assert_int_equal(AMlistItemValue( + AMlistItemsNext(&list_items, 1)).int_, + 2); + assert_int_equal(AMlistItemValue( + AMlistItemsNext(&list_items, 1)).int_, + 3); } } /* assert.deepEqual(doc.materialize(l2), { zip: ["a", "b"] }) */ - AMitems map_items = AMstackItems(stack_ptr, AMmapRange(doc, l2, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE)); - AMitem const* map_item = AMitemsNext(&map_items, 1); - assert_int_equal(AMitemIdxType(map_item), AM_IDX_TYPE_KEY); - assert_true(AMitemKey(map_item, &key)); + AMmapItems map_items = AMpush( + &stack, + AMmapRange(doc, l2, AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + AMmapItem const* map_item = AMmapItemsNext(&map_items, 1); + key = AMmapItemKey(map_item); assert_int_equal(key.count, strlen("zip")); assert_memory_equal(key.src, "zip", key.count); { - AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(map_item), 0, SIZE_MAX, NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - AMbyteSpan str; - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + AMlistItems list_items = AMpush( + &stack, + AMlistRange(doc, AMmapItemObjId(map_item), 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); } - /* assert.deepEqual(doc.materialize(l1), [{ zip: ["a", "b"] }, { foo: "bar" - * }, [1, 2, 3]] */ - AMitems list_items = - AMstackItems(stack_ptr, AMlistRange(doc, l1, 0, SIZE_MAX, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE)); - AMitem const* list_item = AMitemsNext(&list_items, 1); + /* assert.deepEqual(doc.materialize(l1), [{ zip: ["a", "b"] }, { foo: "bar" }, [1, 2, 3]] */ + AMlistItems list_items = AMpush( + &stack, + AMlistRange(doc, l1, 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + AMlistItem const* list_item = AMlistItemsNext(&list_items, 1); { - AMitems map_items = - AMstackItems(stack_ptr, AMmapRange(doc, AMitemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE)); - AMitem const* map_item = AMitemsNext(&map_items, 1); - assert_int_equal(AMitemIdxType(map_item), AM_IDX_TYPE_KEY); - AMbyteSpan key; - assert_true(AMitemKey(map_item, &key)); + AMmapItems map_items = AMpush( + &stack, + AMmapRange(doc, AMlistItemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + AMmapItem const* map_item = AMmapItemsNext(&map_items, 1); + AMbyteSpan const key = AMmapItemKey(map_item); assert_int_equal(key.count, strlen("zip")); assert_memory_equal(key.src, "zip", key.count); { - AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(map_item), 0, SIZE_MAX, NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - AMbyteSpan str; - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + AMlistItems list_items = AMpush( + &stack, + AMlistRange(doc, AMmapItemObjId(map_item), 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); + str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); } } - list_item = AMitemsNext(&list_items, 1); + list_item = AMlistItemsNext(&list_items, 1); { - AMitems map_items = - AMstackItems(stack_ptr, AMmapRange(doc, AMitemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR)); - AMitem* map_item = AMitemsNext(&map_items, 1); - assert_int_equal(AMitemIdxType(map_item), AM_IDX_TYPE_KEY); - AMbyteSpan key; - assert_true(AMitemKey(map_item, &key)); + AMmapItems map_items = AMpush( + &stack, + AMmapRange(doc, AMlistItemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + AMmapItem const* map_item = AMmapItemsNext(&map_items, 1); + AMbyteSpan const key = AMmapItemKey(map_item); assert_int_equal(key.count, strlen("foo")); assert_memory_equal(key.src, "foo", key.count); - AMbyteSpan str; - assert_true(AMitemToStr(map_item, &str)); + AMbyteSpan const str = AMmapItemValue(map_item).str; assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bar", str.count); } - list_item = AMitemsNext(&list_items, 1); + list_item = AMlistItemsNext(&list_items, 1); { - AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(list_item), 0, SIZE_MAX, NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_INT)); - int64_t int_; - assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); - assert_int_equal(int_, 1); - assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); - assert_int_equal(int_, 2); - assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); - assert_int_equal(int_, 3); + AMlistItems list_items = AMpush( + &stack, + AMlistRange(doc, AMlistItemObjId(list_item), 0, SIZE_MAX, NULL), + AM_VALUE_LIST_ITEMS, + cmocka_cb).list_items; + assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).int_, + 1); + assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).int_, + 2); + assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).int_, + 3); } /* assert.deepEqual(doc.materialize(l4), "hello world") */ - assert_true(AMitemToStr(AMstackItem(stack_ptr, AMtext(doc, l4, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + str = AMpush(&stack, AMtext(doc, l4, NULL), AM_VALUE_STR, cmocka_cb).str; assert_int_equal(str.count, strlen("hello world")); assert_memory_equal(str.src, "hello world", str.count); } @@ -1371,41 +1513,65 @@ static void test_recursive_sets_are_possible(void** state) { * \brief only returns an object id when objects are created */ static void test_only_returns_an_object_id_when_objects_are_created(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc = create("aaaa") */ - AMactorId const* actor_id; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); - AMdoc* doc; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMdoc* const doc = AMpush(&stack, + AMcreate(AMpush(&stack, + AMactorIdInitStr(AMstr("aaaa")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id), + AM_VALUE_DOC, + cmocka_cb).doc; /* const r1 = doc.put("_root", "foo", "bar") assert.deepEqual(r1, null); */ - AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("foo"), AMstr("bar")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMpush(&stack, + AMmapPutStr(doc, AM_ROOT, AMstr("foo"), AMstr("bar")), + AM_VALUE_VOID, + cmocka_cb); /* const r2 = doc.putObject("_root", "list", []) */ - AMobjId const* const r2 = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const r2 = AMpush( + &stack, + AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* const r3 = doc.put("_root", "counter", 10, "counter") assert.deepEqual(r3, null); */ - AMstackItem(NULL, AMmapPutCounter(doc, AM_ROOT, AMstr("counter"), 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMpush(&stack, + AMmapPutCounter(doc, AM_ROOT, AMstr("counter"), 10), + AM_VALUE_VOID, + cmocka_cb); /* const r4 = doc.increment("_root", "counter", 1) assert.deepEqual(r4, null); */ - AMstackItem(NULL, AMmapIncrement(doc, AM_ROOT, AMstr("counter"), 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMpush(&stack, + AMmapIncrement(doc, AM_ROOT, AMstr("counter"), 1), + AM_VALUE_VOID, + cmocka_cb); /* const r5 = doc.delete("_root", "counter") assert.deepEqual(r5, null); */ - AMstackItem(NULL, AMmapDelete(doc, AM_ROOT, AMstr("counter")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMpush(&stack, + AMmapDelete(doc, AM_ROOT, AMstr("counter")), + AM_VALUE_VOID, + cmocka_cb); /* const r6 = doc.insert(r2, 0, 10); assert.deepEqual(r6, null); */ - AMstackItem(NULL, AMlistPutInt(doc, r2, 0, true, 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMpush(&stack, + AMlistPutInt(doc, r2, 0, true, 10), + AM_VALUE_VOID, + cmocka_cb); /* const r7 = doc.insertObject(r2, 0, {}); */ - AMobjId const* const r7 = AMitemObjId(AMstackItem(stack_ptr, AMlistPutObject(doc, r2, 0, true, AM_OBJ_TYPE_LIST), - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const r7 = AMpush( + &stack, + AMlistPutObject(doc, r2, 0, true, AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* const r8 = doc.splice(r2, 1, 0, ["a", "b", "c"]); */ - AMresult* data = AMstackResult( - stack_ptr, AMresultFrom(3, AMitemFromStr(AMstr("a")), AMitemFromStr(AMstr("b")), AMitemFromStr(AMstr("c"))), - NULL, NULL); - AMstackItem(NULL, AMsplice(doc, r2, 1, 0, AMresultItems(data)), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMvalue const STRS[] = {{.str_tag = AM_VALUE_STR, .str = {.src = "a", .count = 1}}, + {.str_tag = AM_VALUE_STR, .str = {.src = "b", .count = 1}}, + {.str_tag = AM_VALUE_STR, .str = {.src = "c", .count = 1}}}; + AMpush(&stack, + AMsplice(doc, r2, 1, 0, STRS, sizeof(STRS)/sizeof(AMvalue)), + AM_VALUE_VOID, + cmocka_cb); /* assert.deepEqual(r2, "2@aaaa"); */ assert_int_equal(AMobjIdCounter(r2), 2); AMbyteSpan str = AMactorIdStr(AMobjIdActorId(r2)); @@ -1421,58 +1587,75 @@ static void test_only_returns_an_object_id_when_objects_are_created(void** state * \brief objects without properties are preserved */ static void test_objects_without_properties_are_preserved(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const doc1 = create("aaaa") */ - AMactorId const* actor_id; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); - AMdoc* doc1; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); + AMdoc* const doc1 = AMpush(&stack, + AMcreate(AMpush(&stack, + AMactorIdInitStr(AMstr("aaaa")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id), + AM_VALUE_DOC, + cmocka_cb).doc; /* const a = doc1.putObject("_root", "a", {}); */ - AMobjId const* const a = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc1, AM_ROOT, AMstr("a"), AM_OBJ_TYPE_MAP), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const a = AMpush( + &stack, + AMmapPutObject(doc1, AM_ROOT, AMstr("a"), AM_OBJ_TYPE_MAP), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* const b = doc1.putObject("_root", "b", {}); */ - AMobjId const* const b = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc1, AM_ROOT, AMstr("b"), AM_OBJ_TYPE_MAP), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const b = AMpush( + &stack, + AMmapPutObject(doc1, AM_ROOT, AMstr("b"), AM_OBJ_TYPE_MAP), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* const c = doc1.putObject("_root", "c", {}); */ - AMobjId const* const c = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc1, AM_ROOT, AMstr("c"), AM_OBJ_TYPE_MAP), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const c = AMpush( + &stack, + AMmapPutObject(doc1, AM_ROOT, AMstr("c"), AM_OBJ_TYPE_MAP), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* const d = doc1.put(c, "d", "dd"); */ - AMstackItem(NULL, AMmapPutStr(doc1, c, AMstr("d"), AMstr("dd")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(doc1, c, AMstr("d"), AMstr("dd"))); /* const saved = doc1.save(); */ - AMbyteSpan saved; - assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &saved)); + AMbyteSpan const saved = AMpush(&stack, + AMsave(doc1), + AM_VALUE_BYTES, + cmocka_cb).bytes; /* const doc2 = load(saved); */ - AMdoc* doc2; - assert_true(AMitemToDoc( - AMstackItem(stack_ptr, AMload(saved.src, saved.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); + AMdoc* const doc2 = AMpush(&stack, + AMload(saved.src, saved.count), + AM_VALUE_DOC, + cmocka_cb).doc; /* assert.deepEqual(doc2.getWithType("_root", "a"), ["map", a]) */ - AMitems doc_items = AMstackItems(stack_ptr, AMmapRange(doc2, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE)); - assert_true(AMobjIdEqual(AMitemObjId(AMitemsNext(&doc_items, 1)), a)); + AMmapItems doc_items = AMpush(&stack, + AMmapRange(doc2, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + assert_true(AMobjIdEqual(AMmapItemObjId(AMmapItemsNext(&doc_items, 1)), a)); /* assert.deepEqual(doc2.keys(a), []) */ - AMitems keys = AMstackItems(stack_ptr, AMkeys(doc1, a, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - assert_int_equal(AMitemsSize(&keys), 0); + AMstrs keys = AMpush(&stack, + AMkeys(doc1, a, NULL), + AM_VALUE_STRS, + cmocka_cb).strs; + assert_int_equal(AMstrsSize(&keys), 0); /* assert.deepEqual(doc2.getWithType("_root", "b"), ["map", b]) */ - assert_true(AMobjIdEqual(AMitemObjId(AMitemsNext(&doc_items, 1)), b)); + assert_true(AMobjIdEqual(AMmapItemObjId(AMmapItemsNext(&doc_items, 1)), b)); /* assert.deepEqual(doc2.keys(b), []) */ - keys = AMstackItems(stack_ptr, AMkeys(doc1, b, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - assert_int_equal(AMitemsSize(&keys), 0); + keys = AMpush(&stack, AMkeys(doc1, b, NULL), AM_VALUE_STRS, cmocka_cb).strs; + assert_int_equal(AMstrsSize(&keys), 0); /* assert.deepEqual(doc2.getWithType("_root", "c"), ["map", c]) */ - assert_true(AMobjIdEqual(AMitemObjId(AMitemsNext(&doc_items, 1)), c)); + assert_true(AMobjIdEqual(AMmapItemObjId(AMmapItemsNext(&doc_items, 1)), c)); /* assert.deepEqual(doc2.keys(c), ["d"]) */ - keys = AMstackItems(stack_ptr, AMkeys(doc1, c, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - AMbyteSpan str; - assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); + keys = AMpush(&stack, AMkeys(doc1, c, NULL), AM_VALUE_STRS, cmocka_cb).strs; + AMbyteSpan str = AMstrsNext(&keys, 1); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "d", str.count); /* assert.deepEqual(doc2.getWithType(c, "d"), ["str", "dd"]) */ - AMitems obj_items = AMstackItems(stack_ptr, AMobjItems(doc1, c, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - assert_true(AMitemToStr(AMitemsNext(&obj_items, 1), &str)); + AMobjItems obj_items = AMpush(&stack, + AMobjValues(doc1, c, NULL), + AM_VALUE_OBJ_ITEMS, + cmocka_cb).obj_items; + str = AMobjItemValue(AMobjItemsNext(&obj_items, 1)).str; assert_int_equal(str.count, 2); assert_memory_equal(str.src, "dd", str.count); } @@ -1481,162 +1664,177 @@ static void test_objects_without_properties_are_preserved(void** state) { * \brief should allow you to forkAt a heads */ static void test_should_allow_you_to_forkAt_a_heads(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const A = create("aaaaaa") */ - AMactorId const* actor_id; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); - AMdoc* A; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &A)); + AMdoc* const A = AMpush(&stack, + AMcreate(AMpush(&stack, + AMactorIdInitStr(AMstr("aaaaaa")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id), + AM_VALUE_DOC, + cmocka_cb).doc; /* A.put("/", "key1", "val1"); */ - AMstackItem(NULL, AMmapPutStr(A, AM_ROOT, AMstr("key1"), AMstr("val1")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(A, AM_ROOT, AMstr("key1"), AMstr("val1"))); /* A.put("/", "key2", "val2"); */ - AMstackItem(NULL, AMmapPutStr(A, AM_ROOT, AMstr("key2"), AMstr("val2")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(A, AM_ROOT, AMstr("key2"), AMstr("val2"))); /* const heads1 = A.getHeads(); */ - AMitems const heads1 = AMstackItems(stack_ptr, AMgetHeads(A), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMchangeHashes const heads1 = AMpush(&stack, + AMgetHeads(A), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; /* const B = A.fork("bbbbbb") */ - AMdoc* B; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMfork(A, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &B)); - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("bbbbbb")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); - AMstackItem(NULL, AMsetActorId(B, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMdoc* const B = AMpush(&stack, AMfork(A, NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMfree(AMsetActorId(B, AMpush(&stack, + AMactorIdInitStr(AMstr("bbbbbb")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id)); /* A.put("/", "key3", "val3"); */ - AMstackItem(NULL, AMmapPutStr(A, AM_ROOT, AMstr("key3"), AMstr("val3")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(A, AM_ROOT, AMstr("key3"), AMstr("val3"))); /* B.put("/", "key4", "val4"); */ - AMstackItem(NULL, AMmapPutStr(B, AM_ROOT, AMstr("key4"), AMstr("val4")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutStr(B, AM_ROOT, AMstr("key4"), AMstr("val4"))); /* A.merge(B) */ - AMstackItem(NULL, AMmerge(A, B), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmerge(A, B)); /* const heads2 = A.getHeads(); */ - AMitems const heads2 = AMstackItems(stack_ptr, AMgetHeads(A), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMchangeHashes const heads2 = AMpush(&stack, + AMgetHeads(A), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; /* A.put("/", "key5", "val5"); */ - AMstackItem(NULL, AMmapPutStr(A, AM_ROOT, AMstr("key5"), AMstr("val5")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - /* assert.deepEqual(A.forkAt(heads1).materialize("/"), A.materialize("/", - * heads1) */ - AMdoc* A_forkAt1; - assert_true( - AMitemToDoc(AMstackItem(stack_ptr, AMfork(A, &heads1), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &A_forkAt1)); - AMitems AforkAt1_items = AMstackItems(stack_ptr, AMmapRange(A_forkAt1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - AMitems A1_items = AMstackItems(stack_ptr, AMmapRange(A, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads1), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR)); - assert_true(AMitemsEqual(&AforkAt1_items, &A1_items)); - /* assert.deepEqual(A.forkAt(heads2).materialize("/"), A.materialize("/", - * heads2) */ - AMdoc* A_forkAt2; - assert_true( - AMitemToDoc(AMstackItem(stack_ptr, AMfork(A, &heads2), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &A_forkAt2)); - AMitems AforkAt2_items = AMstackItems(stack_ptr, AMmapRange(A_forkAt2, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); - AMitems A2_items = AMstackItems(stack_ptr, AMmapRange(A, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads2), cmocka_cb, - AMexpect(AM_VAL_TYPE_STR)); - assert_true(AMitemsEqual(&AforkAt2_items, &A2_items)); + AMfree(AMmapPutStr(A, AM_ROOT, AMstr("key5"), AMstr("val5"))); + /* assert.deepEqual(A.forkAt(heads1).materialize("/"), A.materialize("/", heads1) */ + AMmapItems AforkAt1_items = AMpush( + &stack, + AMmapRange( + AMpush(&stack, AMfork(A, &heads1), AM_VALUE_DOC, cmocka_cb).doc, + AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + AMmapItems A1_items = AMpush(&stack, + AMmapRange(A, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads1), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + assert_true(AMmapItemsEqual(&AforkAt1_items, &A1_items)); + /* assert.deepEqual(A.forkAt(heads2).materialize("/"), A.materialize("/", heads2) */ + AMmapItems AforkAt2_items = AMpush( + &stack, + AMmapRange( + AMpush(&stack, AMfork(A, &heads2), AM_VALUE_DOC, cmocka_cb).doc, + AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + AMmapItems A2_items = AMpush(&stack, + AMmapRange(A, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads2), + AM_VALUE_MAP_ITEMS, + cmocka_cb).map_items; + assert_true(AMmapItemsEqual(&AforkAt2_items, &A2_items)); } /** * \brief should handle merging text conflicts then saving & loading */ static void test_should_handle_merging_text_conflicts_then_saving_and_loading(void** state) { - BaseState* base_state = *state; - AMstack** stack_ptr = &base_state->stack; + AMresultStack* stack = *state; /* const A = create("aabbcc") */ - AMactorId const* actor_id; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aabbcc")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); - AMdoc* A; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &A)); + AMdoc* const A = AMpush(&stack, + AMcreate(AMpush(&stack, + AMactorIdInitStr(AMstr("aabbcc")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id), + AM_VALUE_DOC, + cmocka_cb).doc; /* const At = A.putObject('_root', 'text', "") */ - AMobjId const* const At = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(A, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), cmocka_cb, - AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const At = AMpush( + &stack, + AMmapPutObject(A, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* A.splice(At, 0, 0, 'hello') */ - AMstackItem(NULL, AMspliceText(A, At, 0, 0, AMstr("hello")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMspliceText(A, At, 0, 0, AMstr("hello"))); /* */ /* const B = A.fork() */ - AMdoc* B; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMfork(A, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &B)); + AMdoc* const B = AMpush(&stack, AMfork(A, NULL), AM_VALUE_DOC, cmocka_cb).doc; /* */ /* assert.deepEqual(B.getWithType("_root", "text"), ["text", At]) */ - AMbyteSpan str; - assert_true( - AMitemToStr(AMstackItem(stack_ptr, - AMtext(B, - AMitemObjId(AMstackItem(stack_ptr, AMmapGet(B, AM_ROOT, AMstr("text"), NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))), - NULL), - cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), - &str)); - AMbyteSpan str2; - assert_true(AMitemToStr(AMstackItem(stack_ptr, AMtext(A, At, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str2)); + AMbyteSpan str = AMpush(&stack, + AMtext(B, + AMpush(&stack, + AMmapGet(B, AM_ROOT, AMstr("text"), NULL), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id, + NULL), + AM_VALUE_STR, + cmocka_cb).str; + AMbyteSpan const str2 = AMpush(&stack, + AMtext(A, At, NULL), + AM_VALUE_STR, + cmocka_cb).str; assert_int_equal(str.count, str2.count); assert_memory_equal(str.src, str2.src, str.count); /* */ /* B.splice(At, 4, 1) */ - AMstackItem(NULL, AMspliceText(B, At, 4, 1, AMstr(NULL)), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMspliceText(B, At, 4, 1, AMstr(NULL))); /* B.splice(At, 4, 0, '!') */ - AMstackItem(NULL, AMspliceText(B, At, 4, 0, AMstr("!")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMspliceText(B, At, 4, 0, AMstr("!"))); /* B.splice(At, 5, 0, ' ') */ - AMstackItem(NULL, AMspliceText(B, At, 5, 0, AMstr(" ")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMspliceText(B, At, 5, 0, AMstr(" "))); /* B.splice(At, 6, 0, 'world') */ - AMstackItem(NULL, AMspliceText(B, At, 6, 0, AMstr("world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMspliceText(B, At, 6, 0, AMstr("world"))); /* */ /* A.merge(B) */ - AMstackItem(NULL, AMmerge(A, B), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmerge(A, B)); /* */ /* const binary = A.save() */ - AMbyteSpan binary; - assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(A), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &binary)); + AMbyteSpan const binary = AMpush(&stack, + AMsave(A), + AM_VALUE_BYTES, + cmocka_cb).bytes; /* */ /* const C = load(binary) */ - AMdoc* C; - assert_true(AMitemToDoc( - AMstackItem(stack_ptr, AMload(binary.src, binary.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &C)); + AMdoc* const C = AMpush(&stack, + AMload(binary.src, binary.count), + AM_VALUE_DOC, + cmocka_cb).doc; /* */ /* assert.deepEqual(C.getWithType('_root', 'text'), ['text', '1@aabbcc'] */ - AMobjId const* const C_text = AMitemObjId( - AMstackItem(stack_ptr, AMmapGet(C, AM_ROOT, AMstr("text"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const C_text = AMpush(&stack, + AMmapGet(C, AM_ROOT, AMstr("text"), NULL), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; assert_int_equal(AMobjIdCounter(C_text), 1); str = AMactorIdStr(AMobjIdActorId(C_text)); assert_int_equal(str.count, strlen("aabbcc")); assert_memory_equal(str.src, "aabbcc", str.count); /* assert.deepEqual(C.text(At), 'hell! world') */ - assert_true(AMitemToStr(AMstackItem(stack_ptr, AMtext(C, At, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + str = AMpush(&stack, AMtext(C, At, NULL), AM_VALUE_STR, cmocka_cb).str; assert_int_equal(str.count, strlen("hell! world")); assert_memory_equal(str.src, "hell! world", str.count); } int run_ported_wasm_basic_tests(void) { const struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(test_create_clone_and_free, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_start_and_commit, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_getting_a_nonexistent_prop_does_not_throw_an_error, setup_base, - teardown_base), - cmocka_unit_test_setup_teardown(test_should_be_able_to_set_and_get_a_simple_value, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_should_be_able_to_use_bytes, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_should_be_able_to_make_subobjects, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_should_be_able_to_make_lists, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_lists_have_insert_set_splice_and_push_ops, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_should_be_able_to_delete_non_existent_props, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_should_be_able_to_del, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_should_be_able_to_use_counters, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_should_be_able_to_splice_text, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_should_be_able_to_save_all_or_incrementally, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_should_be_able_to_splice_text_2, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_local_inc_increments_all_visible_counters_in_a_map, setup_base, - teardown_base), - cmocka_unit_test_setup_teardown(test_local_inc_increments_all_visible_counters_in_a_sequence, setup_base, - teardown_base), - cmocka_unit_test_setup_teardown(test_should_be_able_to_fetch_changes_by_hash, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_recursive_sets_are_possible, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_only_returns_an_object_id_when_objects_are_created, setup_base, - teardown_base), - cmocka_unit_test_setup_teardown(test_objects_without_properties_are_preserved, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_should_allow_you_to_forkAt_a_heads, setup_base, teardown_base), - cmocka_unit_test_setup_teardown(test_should_handle_merging_text_conflicts_then_saving_and_loading, setup_base, - teardown_base)}; + cmocka_unit_test_setup_teardown(test_create_clone_and_free, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_start_and_commit, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_getting_a_nonexistent_prop_does_not_throw_an_error, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_should_be_able_to_set_and_get_a_simple_value, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_should_be_able_to_use_bytes, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_should_be_able_to_make_subobjects, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_should_be_able_to_make_lists, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_lists_have_insert_set_splice_and_push_ops, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_should_be_able_to_delete_non_existent_props, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_should_be_able_to_del, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_should_be_able_to_use_counters, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_should_be_able_to_splice_text, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_should_be_able_to_save_all_or_incrementally, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_should_be_able_to_splice_text_2, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_local_inc_increments_all_visible_counters_in_a_map, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_local_inc_increments_all_visible_counters_in_a_sequence, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_should_be_able_to_fetch_changes_by_hash, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_recursive_sets_are_possible, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_only_returns_an_object_id_when_objects_are_created, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_objects_without_properties_are_preserved, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_should_allow_you_to_forkAt_a_heads, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_should_handle_merging_text_conflicts_then_saving_and_loading, setup_stack, teardown_stack) + }; return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/rust/automerge-c/test/ported_wasm/suite.c b/rust/automerge-c/test/ported_wasm/suite.c index 440ed899..fc10fadc 100644 --- a/rust/automerge-c/test/ported_wasm/suite.c +++ b/rust/automerge-c/test/ported_wasm/suite.c @@ -1,6 +1,6 @@ -#include #include #include +#include #include /* third-party */ @@ -11,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() + ); } diff --git a/rust/automerge-c/test/ported_wasm/sync_tests.c b/rust/automerge-c/test/ported_wasm/sync_tests.c index 099f8dbf..a1ddbf3c 100644 --- a/rust/automerge-c/test/ported_wasm/sync_tests.c +++ b/rust/automerge-c/test/ported_wasm/sync_tests.c @@ -9,12 +9,10 @@ /* local */ #include -#include -#include "../base_state.h" -#include "../cmocka_utils.h" +#include "../stack_utils.h" typedef struct { - BaseState* base_state; + AMresultStack* stack; AMdoc* n1; AMdoc* n2; AMsyncState* s1; @@ -23,35 +21,43 @@ typedef struct { static int setup(void** state) { TestState* test_state = test_calloc(1, sizeof(TestState)); - setup_base((void**)&test_state->base_state); - AMstack** stack_ptr = &test_state->base_state->stack; - AMactorId const* actor_id; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("01234567")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); - assert_true( - AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &test_state->n1)); - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("89abcdef")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); - assert_true( - AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &test_state->n2)); - assert_true(AMitemToSyncState( - AMstackItem(stack_ptr, AMsyncStateInit(), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_STATE)), &test_state->s1)); - assert_true(AMitemToSyncState( - AMstackItem(stack_ptr, AMsyncStateInit(), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_STATE)), &test_state->s2)); + test_state->n1 = AMpush(&test_state->stack, + AMcreate(AMpush(&test_state->stack, + AMactorIdInitStr(AMstr("01234567")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id), + AM_VALUE_DOC, + cmocka_cb).doc; + test_state->n2 = AMpush(&test_state->stack, + AMcreate(AMpush(&test_state->stack, + AMactorIdInitStr(AMstr("89abcdef")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id), + AM_VALUE_DOC, + cmocka_cb).doc; + test_state->s1 = AMpush(&test_state->stack, + AMsyncStateInit(), + AM_VALUE_SYNC_STATE, + cmocka_cb).sync_state; + test_state->s2 = AMpush(&test_state->stack, + AMsyncStateInit(), + AM_VALUE_SYNC_STATE, + cmocka_cb).sync_state; *state = test_state; return 0; } static int teardown(void** state) { TestState* test_state = *state; - teardown_base((void**)&test_state->base_state); + AMfreeStack(&test_state->stack); test_free(test_state); return 0; } -static void sync(AMdoc* a, AMdoc* b, AMsyncState* a_sync_state, AMsyncState* b_sync_state) { +static void sync(AMdoc* a, + AMdoc* b, + AMsyncState* a_sync_state, + AMsyncState* b_sync_state) { static size_t const MAX_ITER = 10; AMsyncMessage const* a2b_msg = NULL; @@ -60,35 +66,29 @@ static void sync(AMdoc* a, AMdoc* b, AMsyncState* a_sync_state, AMsyncState* b_s do { AMresult* a2b_msg_result = AMgenerateSyncMessage(a, a_sync_state); AMresult* b2a_msg_result = AMgenerateSyncMessage(b, b_sync_state); - AMitem* item = AMresultItem(a2b_msg_result); - switch (AMitemValType(item)) { - case AM_VAL_TYPE_SYNC_MESSAGE: { - AMitemToSyncMessage(item, &a2b_msg); - AMstackResult(NULL, AMreceiveSyncMessage(b, b_sync_state, a2b_msg), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); - } break; - case AM_VAL_TYPE_VOID: - a2b_msg = NULL; - break; + AMvalue value = AMresultValue(a2b_msg_result); + switch (value.tag) { + case AM_VALUE_SYNC_MESSAGE: { + a2b_msg = value.sync_message; + AMfree(AMreceiveSyncMessage(b, b_sync_state, a2b_msg)); + } + break; + case AM_VALUE_VOID: a2b_msg = NULL; break; } - item = AMresultItem(b2a_msg_result); - switch (AMitemValType(item)) { - case AM_VAL_TYPE_SYNC_MESSAGE: { - AMitemToSyncMessage(item, &b2a_msg); - AMstackResult(NULL, AMreceiveSyncMessage(a, a_sync_state, b2a_msg), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); - } break; - case AM_VAL_TYPE_VOID: - b2a_msg = NULL; - break; + value = AMresultValue(b2a_msg_result); + switch (value.tag) { + case AM_VALUE_SYNC_MESSAGE: { + b2a_msg = value.sync_message; + AMfree(AMreceiveSyncMessage(a, a_sync_state, b2a_msg)); + } + break; + case AM_VALUE_VOID: b2a_msg = NULL; break; } if (++iter > MAX_ITER) { - fail_msg( - "Did not synchronize within %d iterations. " - "Do you have a bug causing an infinite loop?", - MAX_ITER); + fail_msg("Did not synchronize within %d iterations. " + "Do you have a bug causing an infinite loop?", MAX_ITER); } - } while (a2b_msg || b2a_msg); + } while(a2b_msg || b2a_msg); } static time_t const TIME_0 = 0; @@ -96,135 +96,151 @@ static time_t const TIME_0 = 0; /** * \brief should send a sync message implying no local data */ -static void test_should_send_a_sync_message_implying_no_local_data(void** state) { +static void test_should_send_a_sync_message_implying_no_local_data(void **state) { /* const doc = create() const s1 = initSyncState() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; /* const m1 = doc.generateSyncMessage(s1) if (m1 === null) { throw new RangeError("message should not be null") } const message: DecodedSyncMessage = decodeSyncMessage(m1) */ - AMsyncMessage const* m1; - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &m1)); + AMsyncMessage const* const m1 = AMpush(&test_state->stack, + AMgenerateSyncMessage( + test_state->n1, + test_state->s1), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; /* assert.deepStrictEqual(message.heads, []) */ - AMitems heads = AMstackItems(stack_ptr, AMsyncMessageHeads(m1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_int_equal(AMitemsSize(&heads), 0); + AMchangeHashes heads = AMsyncMessageHeads(m1); + assert_int_equal(AMchangeHashesSize(&heads), 0); /* assert.deepStrictEqual(message.need, []) */ - AMitems needs = AMstackItems(stack_ptr, AMsyncMessageNeeds(m1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_int_equal(AMitemsSize(&needs), 0); + AMchangeHashes needs = AMsyncMessageNeeds(m1); + assert_int_equal(AMchangeHashesSize(&needs), 0); /* assert.deepStrictEqual(message.have.length, 1) */ - AMitems haves = AMstackItems(stack_ptr, AMsyncMessageHaves(m1), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_HAVE)); - assert_int_equal(AMitemsSize(&haves), 1); + AMsyncHaves haves = AMsyncMessageHaves(m1); + assert_int_equal(AMsyncHavesSize(&haves), 1); /* assert.deepStrictEqual(message.have[0].lastSync, []) */ - AMsyncHave const* have0; - assert_true(AMitemToSyncHave(AMitemsNext(&haves, 1), &have0)); - AMitems last_sync = - AMstackItems(stack_ptr, AMsyncHaveLastSync(have0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_int_equal(AMitemsSize(&last_sync), 0); + AMsyncHave const* have0 = AMsyncHavesNext(&haves, 1); + AMchangeHashes last_sync = AMsyncHaveLastSync(have0); + assert_int_equal(AMchangeHashesSize(&last_sync), 0); /* assert.deepStrictEqual(message.have[0].bloom.byteLength, 0) assert.deepStrictEqual(message.changes, []) */ - AMitems changes = AMstackItems(stack_ptr, AMsyncMessageChanges(m1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - assert_int_equal(AMitemsSize(&changes), 0); + AMchanges changes = AMsyncMessageChanges(m1); + assert_int_equal(AMchangesSize(&changes), 0); } /** * \brief should not reply if we have no data as well */ -static void test_should_not_reply_if_we_have_no_data_as_well(void** state) { +static void test_should_not_reply_if_we_have_no_data_as_well(void **state) { /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; /* const m1 = n1.generateSyncMessage(s1) if (m1 === null) { throw new RangeError("message should not be null") */ - AMsyncMessage const* m1; - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &m1)); + AMsyncMessage const* const m1 = AMpush(&test_state->stack, + AMgenerateSyncMessage( + test_state->n1, + test_state->s1), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; /* n2.receiveSyncMessage(s2, m1) */ - AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, m1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, m1)); /* const m2 = n2.generateSyncMessage(s2) assert.deepStrictEqual(m2, null) */ - AMstackItem(NULL, AMgenerateSyncMessage(test_state->n2, test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n2, test_state->s2), + AM_VALUE_VOID, + cmocka_cb); } /** * \brief repos with equal heads do not need a reply message */ -static void test_repos_with_equal_heads_do_not_need_a_reply_message(void** state) { +static void test_repos_with_equal_heads_do_not_need_a_reply_message(void **state) { /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* make two nodes with the same changes */ /* const list = n1.putObject("_root", "n", []) */ - AMobjId const* const list = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(test_state->n1, AM_ROOT, AMstr("n"), AM_OBJ_TYPE_LIST), - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const list = AMpush(&test_state->stack, + AMmapPutObject(test_state->n1, + AM_ROOT, + AMstr("n"), + AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.insert(list, i, i) */ - AMstackItem(NULL, AMlistPutUint(test_state->n1, list, i, true, i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutUint(test_state->n1, AM_ROOT, i, true, i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* n2.applyChanges(n1.getChanges([])) */ - AMitems const items = - AMstackItems(stack_ptr, AMgetChanges(test_state->n1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - AMstackItem(NULL, AMapplyChanges(test_state->n2, &items), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMchanges const changes = AMpush(&test_state->stack, + AMgetChanges(test_state->n1, NULL), + AM_VALUE_CHANGES, + cmocka_cb).changes; + AMfree(AMapplyChanges(test_state->n2, &changes)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); /* */ /* generate a naive sync message */ /* const m1 = n1.generateSyncMessage(s1) if (m1 === null) { throw new RangeError("message should not be null") */ - AMsyncMessage const* m1; - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &m1)); + AMsyncMessage const* m1 = AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n1, + test_state->s1), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; /* assert.deepStrictEqual(s1.lastSentHeads, n1.getHeads()) */ - AMitems const last_sent_heads = - AMstackItems(stack_ptr, AMsyncStateLastSentHeads(test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMitems const heads = - AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_true(AMitemsEqual(&last_sent_heads, &heads)); + AMchangeHashes const last_sent_heads = AMsyncStateLastSentHeads( + test_state->s1 + ); + AMchangeHashes const heads = AMpush(&test_state->stack, + AMgetHeads(test_state->n1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + assert_int_equal(AMchangeHashesCmp(&last_sent_heads, &heads), 0); /* */ /* heads are equal so this message should be null */ /* n2.receiveSyncMessage(s2, m1) */ - AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, m1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, m1)); /* const m2 = n2.generateSyncMessage(s2) assert.strictEqual(m2, null) */ - AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n2, test_state->s2), + AM_VALUE_VOID, + cmocka_cb); } /** * \brief n1 should offer all changes to n2 when starting from nothing */ -static void test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing(void** state) { +static void test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing(void **state) { /* const n1 = create(), n2 = create() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; + /* make changes for n1 that n2 should request */ /* const list = n1.putObject("_root", "n", []) */ - AMobjId const* const list = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(test_state->n1, AM_ROOT, AMstr("n"), AM_OBJ_TYPE_LIST), - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const list = AMpush( + &test_state->stack, + AMmapPutObject(test_state->n1, AM_ROOT, AMstr("n"), AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.insert(list, i, i) */ - AMstackItem(NULL, AMlistPutUint(test_state->n1, list, i, true, i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutUint(test_state->n1, AM_ROOT, i, true, i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ @@ -238,24 +254,26 @@ static void test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing(vo /** * \brief should sync peers where one has commits the other does not */ -static void test_should_sync_peers_where_one_has_commits_the_other_does_not(void** state) { +static void test_should_sync_peers_where_one_has_commits_the_other_does_not(void **state) { /* const n1 = create(), n2 = create() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; + /* make changes for n1 that n2 should request */ /* const list = n1.putObject("_root", "n", []) */ - AMobjId const* const list = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(test_state->n1, AM_ROOT, AMstr("n"), AM_OBJ_TYPE_LIST), - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const list = AMpush( + &test_state->stack, + AMmapPutObject(test_state->n1, AM_ROOT, AMstr("n"), AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.insert(list, i, i) */ - AMstackItem(NULL, AMlistPutUint(test_state->n1, list, i, true, i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutUint(test_state->n1, AM_ROOT, i, true, i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ @@ -269,20 +287,19 @@ static void test_should_sync_peers_where_one_has_commits_the_other_does_not(void /** * \brief should work with prior sync state */ -static void test_should_work_with_prior_sync_state(void** state) { +static void test_should_work_with_prior_sync_state(void **state) { /* create & synchronize two nodes */ /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n1.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); @@ -291,10 +308,10 @@ static void test_should_work_with_prior_sync_state(void** state) { /* for (let i = 5; i < 10; i++) { */ for (size_t i = 5; i != 10; ++i) { /* n1.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ @@ -308,333 +325,326 @@ static void test_should_work_with_prior_sync_state(void** state) { /** * \brief should not generate messages once synced */ -static void test_should_not_generate_messages_once_synced(void** state) { +static void test_should_not_generate_messages_once_synced(void **state) { /* create & synchronize two nodes */ /* const n1 = create('abc123'), n2 = create('def456') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; - AMactorId const* actor_id; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("abc123")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); - AMstackItem(NULL, AMsetActorId(test_state->n1, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("def456")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); - AMstackItem(NULL, AMsetActorId(test_state->n2, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, + AMactorIdInitStr(AMstr("abc123")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id)); + AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, + AMactorIdInitStr(AMstr("def456")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id)); /* */ /* let message, patch for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n1.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n2.put("_root", "y", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("y"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("y"), i)); /* n2.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); + /* { */ } /* */ /* n1 reports what it has */ /* message = n1.generateSyncMessage(s1) - if (message === null) { throw new RangeError("message should not be - null") */ - AMsyncMessage const* message; - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &message)); + if (message === null) { throw new RangeError("message should not be null") */ + AMsyncMessage const* message = AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n1, + test_state->s1), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; /* */ /* n2 receives that message and sends changes along with what it has */ /* n2.receiveSyncMessage(s2, message) */ - AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, message), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, message)); /* message = n2.generateSyncMessage(s2) - if (message === null) { throw new RangeError("message should not be - null") */ - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &message)); - AMitems message_changes = - AMstackItems(stack_ptr, AMsyncMessageChanges(message), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - assert_int_equal(AMitemsSize(&message_changes), 5); + if (message === null) { throw new RangeError("message should not be null") */ + message = AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n2, test_state->s2), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; + AMchanges message_changes = AMsyncMessageChanges(message); + assert_int_equal(AMchangesSize(&message_changes), 5); /* */ /* n1 receives the changes and replies with the changes it now knows that * n2 needs */ /* n1.receiveSyncMessage(s1, message) */ - AMstackItem(NULL, AMreceiveSyncMessage(test_state->n1, test_state->s1, message), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, message)); /* message = n2.generateSyncMessage(s2) - if (message === null) { throw new RangeError("message should not be - null") */ - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &message)); - message_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(message), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - assert_int_equal(AMitemsSize(&message_changes), 5); + if (message === null) { throw new RangeError("message should not be null") */ + message = AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n1, test_state->s1), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; + message_changes = AMsyncMessageChanges(message); + assert_int_equal(AMchangesSize(&message_changes), 5); /* */ /* n2 applies the changes and sends confirmation ending the exchange */ /* n2.receiveSyncMessage(s2, message) */ - AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, message), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, message)); /* message = n2.generateSyncMessage(s2) - if (message === null) { throw new RangeError("message should not be - null") */ - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &message)); + if (message === null) { throw new RangeError("message should not be null") */ + message = AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n2, test_state->s2), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; /* */ /* n1 receives the message and has nothing more to say */ /* n1.receiveSyncMessage(s1, message) */ - AMstackItem(NULL, AMreceiveSyncMessage(test_state->n1, test_state->s1, message), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, message)); /* message = n1.generateSyncMessage(s1) assert.deepStrictEqual(message, null) */ - AMstackItem(NULL, AMgenerateSyncMessage(test_state->n1, test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n1, test_state->s1), + AM_VALUE_VOID, + cmocka_cb); /* //assert.deepStrictEqual(patch, null) // no changes arrived */ /* */ /* n2 also has nothing left to say */ /* message = n2.generateSyncMessage(s2) assert.deepStrictEqual(message, null) */ - AMstackItem(NULL, AMgenerateSyncMessage(test_state->n2, test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n2, test_state->s2), + AM_VALUE_VOID, + cmocka_cb); } /** * \brief should allow simultaneous messages during synchronization */ -static void test_should_allow_simultaneous_messages_during_synchronization(void** state) { +static void test_should_allow_simultaneous_messages_during_synchronization(void **state) { /* create & synchronize two nodes */ /* const n1 = create('abc123'), n2 = create('def456') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; - AMactorId const* actor_id; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("abc123")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); - AMstackItem(NULL, AMsetActorId(test_state->n1, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("def456")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); - AMstackItem(NULL, AMsetActorId(test_state->n2, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, + AMactorIdInitStr(AMstr("abc123")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id)); + AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, + AMactorIdInitStr(AMstr("def456")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id)); /* */ /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n1.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n2.put("_root", "y", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("y"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("y"), i)); /* n2.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); + /* { */ } /* const head1 = n1.getHeads()[0], head2 = n2.getHeads()[0] */ - AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMbyteSpan head1; - assert_true(AMitemToChangeHash(AMitemsNext(&heads1, 1), &head1)); - AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMbyteSpan head2; - assert_true(AMitemToChangeHash(AMitemsNext(&heads2, 1), &head2)); + AMchangeHashes heads1 = AMpush(&test_state->stack, + AMgetHeads(test_state->n1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + AMbyteSpan const head1 = AMchangeHashesNext(&heads1, 1); + AMchangeHashes heads2 = AMpush(&test_state->stack, + AMgetHeads(test_state->n2), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + AMbyteSpan const head2 = AMchangeHashesNext(&heads2, 1); /* */ /* both sides report what they have but have no shared peer state */ /* let msg1to2, msg2to1 msg1to2 = n1.generateSyncMessage(s1) - if (msg1to2 === null) { throw new RangeError("message should not be - null") */ - AMsyncMessage const* msg1to2; - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &msg1to2)); + if (msg1to2 === null) { throw new RangeError("message should not be null") */ + AMsyncMessage const* msg1to2 = AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n1, + test_state->s1), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; /* msg2to1 = n2.generateSyncMessage(s2) - if (msg2to1 === null) { throw new RangeError("message should not be - null") */ - AMsyncMessage const* msg2to1; - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &msg2to1)); + if (msg2to1 === null) { throw new RangeError("message should not be null") */ + AMsyncMessage const* msg2to1 = AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n2, + test_state->s2), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).changes.length, 0) */ - AMitems msg1to2_changes = - AMstackItems(stack_ptr, AMsyncMessageChanges(msg1to2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - assert_int_equal(AMitemsSize(&msg1to2_changes), 0); - /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).have[0].lastSync.length, - * 0 */ - AMitems msg1to2_haves = - AMstackItems(stack_ptr, AMsyncMessageHaves(msg1to2), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_HAVE)); - AMsyncHave const* msg1to2_have; - assert_true(AMitemToSyncHave(AMitemsNext(&msg1to2_haves, 1), &msg1to2_have)); - AMitems msg1to2_last_sync = - AMstackItems(stack_ptr, AMsyncHaveLastSync(msg1to2_have), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_int_equal(AMitemsSize(&msg1to2_last_sync), 0); + AMchanges msg1to2_changes = AMsyncMessageChanges(msg1to2); + assert_int_equal(AMchangesSize(&msg1to2_changes), 0); + /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).have[0].lastSync.length, 0 */ + AMsyncHaves msg1to2_haves = AMsyncMessageHaves(msg1to2); + AMsyncHave const* msg1to2_have = AMsyncHavesNext(&msg1to2_haves, 1); + AMchangeHashes msg1to2_last_sync = AMsyncHaveLastSync(msg1to2_have); + assert_int_equal(AMchangeHashesSize(&msg1to2_last_sync), 0); /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).changes.length, 0) */ - AMitems msg2to1_changes = - AMstackItems(stack_ptr, AMsyncMessageChanges(msg2to1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - assert_int_equal(AMitemsSize(&msg2to1_changes), 0); - /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).have[0].lastSync.length, - * 0 */ - AMitems msg2to1_haves = - AMstackItems(stack_ptr, AMsyncMessageHaves(msg2to1), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_HAVE)); - AMsyncHave const* msg2to1_have; - assert_true(AMitemToSyncHave(AMitemsNext(&msg2to1_haves, 1), &msg2to1_have)); - AMitems msg2to1_last_sync = - AMstackItems(stack_ptr, AMsyncHaveLastSync(msg2to1_have), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_int_equal(AMitemsSize(&msg2to1_last_sync), 0); + AMchanges msg2to1_changes = AMsyncMessageChanges(msg2to1); + assert_int_equal(AMchangesSize(&msg2to1_changes), 0); + /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).have[0].lastSync.length, 0 */ + AMsyncHaves msg2to1_haves = AMsyncMessageHaves(msg2to1); + AMsyncHave const* msg2to1_have = AMsyncHavesNext(&msg2to1_haves, 1); + AMchangeHashes msg2to1_last_sync = AMsyncHaveLastSync(msg2to1_have); + assert_int_equal(AMchangeHashesSize(&msg2to1_last_sync), 0); /* */ /* n1 and n2 receive that message and update sync state but make no patc */ /* n1.receiveSyncMessage(s1, msg2to1) */ - AMstackItem(NULL, AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1)); /* n2.receiveSyncMessage(s2, msg1to2) */ - AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2)); /* */ /* now both reply with their local changes that the other lacks * (standard warning that 1% of the time this will result in a "needs" * message) */ /* msg1to2 = n1.generateSyncMessage(s1) - if (msg1to2 === null) { throw new RangeError("message should not be - null") */ - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &msg1to2)); + if (msg1to2 === null) { throw new RangeError("message should not be null") */ + msg1to2 = AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n1, test_state->s1), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).changes.length, 5) */ - msg1to2_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(msg1to2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - assert_int_equal(AMitemsSize(&msg1to2_changes), 5); + msg1to2_changes = AMsyncMessageChanges(msg1to2); + assert_int_equal(AMchangesSize(&msg1to2_changes), 5); /* msg2to1 = n2.generateSyncMessage(s2) - if (msg2to1 === null) { throw new RangeError("message should not be - null") */ - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &msg2to1)); + if (msg2to1 === null) { throw new RangeError("message should not be null") */ + msg2to1 = AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n2, test_state->s2), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).changes.length, 5) */ - msg2to1_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(msg2to1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - assert_int_equal(AMitemsSize(&msg2to1_changes), 5); + msg2to1_changes = AMsyncMessageChanges(msg2to1); + assert_int_equal(AMchangesSize(&msg2to1_changes), 5); /* */ /* both should now apply the changes and update the frontend */ /* n1.receiveSyncMessage(s1, msg2to1) */ - AMstackItem(NULL, AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMreceiveSyncMessage(test_state->n1, + test_state->s1, + msg2to1)); /* assert.deepStrictEqual(n1.getMissingDeps(), []) */ - AMitems missing_deps = - AMstackItems(stack_ptr, AMgetMissingDeps(test_state->n1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_int_equal(AMitemsSize(&missing_deps), 0); + AMchangeHashes missing_deps = AMpush(&test_state->stack, + AMgetMissingDeps(test_state->n1, NULL), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + assert_int_equal(AMchangeHashesSize(&missing_deps), 0); /* //assert.notDeepStrictEqual(patch1, null) assert.deepStrictEqual(n1.materialize(), { x: 4, y: 4 }) */ - uint64_t uint; - assert_true(AMitemToUint(AMstackItem(stack_ptr, AMmapGet(test_state->n1, AM_ROOT, AMstr("x"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_UINT)), - &uint)); - assert_int_equal(uint, 4); - assert_true(AMitemToUint(AMstackItem(stack_ptr, AMmapGet(test_state->n1, AM_ROOT, AMstr("y"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_UINT)), - &uint)); - assert_int_equal(uint, 4); + assert_int_equal(AMpush(&test_state->stack, + AMmapGet(test_state->n1, AM_ROOT, AMstr("x"), NULL), + AM_VALUE_UINT, + cmocka_cb).uint, 4); + assert_int_equal(AMpush(&test_state->stack, + AMmapGet(test_state->n1, AM_ROOT, AMstr("y"), NULL), + AM_VALUE_UINT, + cmocka_cb).uint, 4); /* */ /* n2.receiveSyncMessage(s2, msg1to2) */ - AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2)); /* assert.deepStrictEqual(n2.getMissingDeps(), []) */ - missing_deps = - AMstackItems(stack_ptr, AMgetMissingDeps(test_state->n2, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_int_equal(AMitemsSize(&missing_deps), 0); + missing_deps = AMpush(&test_state->stack, + AMgetMissingDeps(test_state->n2, NULL), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + assert_int_equal(AMchangeHashesSize(&missing_deps), 0); /* //assert.notDeepStrictEqual(patch2, null) assert.deepStrictEqual(n2.materialize(), { x: 4, y: 4 }) */ - assert_true(AMitemToUint(AMstackItem(stack_ptr, AMmapGet(test_state->n2, AM_ROOT, AMstr("x"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_UINT)), - &uint)); - assert_int_equal(uint, 4); - assert_true(AMitemToUint(AMstackItem(stack_ptr, AMmapGet(test_state->n2, AM_ROOT, AMstr("y"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_UINT)), - &uint)); - assert_int_equal(uint, 4); + assert_int_equal(AMpush(&test_state->stack, + AMmapGet(test_state->n2, AM_ROOT, AMstr("x"), NULL), + AM_VALUE_UINT, + cmocka_cb).uint, 4); + assert_int_equal(AMpush(&test_state->stack, + AMmapGet(test_state->n2, AM_ROOT, AMstr("y"), NULL), + AM_VALUE_UINT, + cmocka_cb).uint, 4); /* */ /* The response acknowledges the changes received and sends no further * changes */ /* msg1to2 = n1.generateSyncMessage(s1) - if (msg1to2 === null) { throw new RangeError("message should not be - null") */ - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &msg1to2)); + if (msg1to2 === null) { throw new RangeError("message should not be null") */ + msg1to2 = AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n1, test_state->s1), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).changes.length, 0) */ - msg1to2_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(msg1to2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - assert_int_equal(AMitemsSize(&msg1to2_changes), 0); + msg1to2_changes = AMsyncMessageChanges(msg1to2); + assert_int_equal(AMchangesSize(&msg1to2_changes), 0); /* msg2to1 = n2.generateSyncMessage(s2) - if (msg2to1 === null) { throw new RangeError("message should not be - null") */ - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &msg2to1)); + if (msg2to1 === null) { throw new RangeError("message should not be null") */ + msg2to1 = AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n2, test_state->s2), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).changes.length, 0) */ - msg2to1_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(msg2to1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - assert_int_equal(AMitemsSize(&msg2to1_changes), 0); + msg2to1_changes = AMsyncMessageChanges(msg2to1); + assert_int_equal(AMchangesSize(&msg2to1_changes), 0); /* */ /* After receiving acknowledgements, their shared heads should be equal */ /* n1.receiveSyncMessage(s1, msg2to1) */ - AMstackItem(NULL, AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1)); /* n2.receiveSyncMessage(s2, msg1to2) */ - AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2)); /* assert.deepStrictEqual(s1.sharedHeads, [head1, head2].sort()) */ - AMitems s1_shared_heads = - AMstackItems(stack_ptr, AMsyncStateSharedHeads(test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMbyteSpan s1_shared_change_hash; - assert_true(AMitemToChangeHash(AMitemsNext(&s1_shared_heads, 1), &s1_shared_change_hash)); - assert_memory_equal(s1_shared_change_hash.src, head1.src, head1.count); - assert_true(AMitemToChangeHash(AMitemsNext(&s1_shared_heads, 1), &s1_shared_change_hash)); - assert_memory_equal(s1_shared_change_hash.src, head2.src, head2.count); + AMchangeHashes s1_shared_heads = AMsyncStateSharedHeads(test_state->s1); + assert_memory_equal(AMchangeHashesNext(&s1_shared_heads, 1).src, + head1.src, + head1.count); + assert_memory_equal(AMchangeHashesNext(&s1_shared_heads, 1).src, + head2.src, + head2.count); /* assert.deepStrictEqual(s2.sharedHeads, [head1, head2].sort()) */ - AMitems s2_shared_heads = - AMstackItems(stack_ptr, AMsyncStateSharedHeads(test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMbyteSpan s2_shared_change_hash; - assert_true(AMitemToChangeHash(AMitemsNext(&s2_shared_heads, 1), &s2_shared_change_hash)); - assert_memory_equal(s2_shared_change_hash.src, head1.src, head1.count); - assert_true(AMitemToChangeHash(AMitemsNext(&s2_shared_heads, 1), &s2_shared_change_hash)); - assert_memory_equal(s2_shared_change_hash.src, head2.src, head2.count); + AMchangeHashes s2_shared_heads = AMsyncStateSharedHeads(test_state->s2); + assert_memory_equal(AMchangeHashesNext(&s2_shared_heads, 1).src, + head1.src, + head1.count); + assert_memory_equal(AMchangeHashesNext(&s2_shared_heads, 1).src, + head2.src, + head2.count); /* //assert.deepStrictEqual(patch1, null) //assert.deepStrictEqual(patch2, null) */ /* */ /* We're in sync, no more messages required */ /* msg1to2 = n1.generateSyncMessage(s1) assert.deepStrictEqual(msg1to2, null) */ - AMstackItem(NULL, AMgenerateSyncMessage(test_state->n1, test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n1, test_state->s1), + AM_VALUE_VOID, + cmocka_cb); /* msg2to1 = n2.generateSyncMessage(s2) assert.deepStrictEqual(msg2to1, null) */ - AMstackItem(NULL, AMgenerateSyncMessage(test_state->n2, test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n2, test_state->s2), + AM_VALUE_VOID, + cmocka_cb); /* */ /* If we make one more change and start another sync then its lastSync * should be updated */ /* n1.put("_root", "x", 5) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 5)); /* msg1to2 = n1.generateSyncMessage(s1) - if (msg1to2 === null) { throw new RangeError("message should not be - null") */ - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &msg1to2)); - /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).have[0].lastSync, - * [head1, head2].sort( */ - msg1to2_haves = AMstackItems(stack_ptr, AMsyncMessageHaves(msg1to2), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_HAVE)); - assert_true(AMitemToSyncHave(AMitemsNext(&msg1to2_haves, 1), &msg1to2_have)); - msg1to2_last_sync = - AMstackItems(stack_ptr, AMsyncHaveLastSync(msg1to2_have), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMbyteSpan msg1to2_last_sync_next; - assert_true(AMitemToChangeHash(AMitemsNext(&msg1to2_last_sync, 1), &msg1to2_last_sync_next)); + if (msg1to2 === null) { throw new RangeError("message should not be null") */ + msg1to2 = AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n1, test_state->s1), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; + /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).have[0].lastSync, [head1, head2].sort( */ + msg1to2_haves = AMsyncMessageHaves(msg1to2); + msg1to2_have = AMsyncHavesNext(&msg1to2_haves, 1); + msg1to2_last_sync = AMsyncHaveLastSync(msg1to2_have); + AMbyteSpan msg1to2_last_sync_next = AMchangeHashesNext(&msg1to2_last_sync, 1); assert_int_equal(msg1to2_last_sync_next.count, head1.count); assert_memory_equal(msg1to2_last_sync_next.src, head1.src, head1.count); - assert_true(AMitemToChangeHash(AMitemsNext(&msg1to2_last_sync, 1), &msg1to2_last_sync_next)); + msg1to2_last_sync_next = AMchangeHashesNext(&msg1to2_last_sync, 1); assert_int_equal(msg1to2_last_sync_next.count, head2.count); assert_memory_equal(msg1to2_last_sync_next.src, head2.src, head2.count); } @@ -642,89 +652,87 @@ static void test_should_allow_simultaneous_messages_during_synchronization(void* /** * \brief should assume sent changes were received until we hear otherwise */ -static void test_should_assume_sent_changes_were_received_until_we_hear_otherwise(void** state) { +static void test_should_assume_sent_changes_were_received_until_we_hear_otherwise(void **state) { /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; /* let message = null */ /* */ /* const items = n1.putObject("_root", "items", []) */ - AMobjId const* const items = - AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(test_state->n1, AM_ROOT, AMstr("items"), AM_OBJ_TYPE_LIST), - cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* items = AMpush(&test_state->stack, + AMmapPutObject(test_state->n1, + AM_ROOT, + AMstr("items"), + AM_OBJ_TYPE_LIST), + AM_VALUE_OBJ_ID, + cmocka_cb).obj_id; /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* n1.push(items, "x") */ - AMstackItem(NULL, AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("x")), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("x"))); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); /* message = n1.generateSyncMessage(s1) - if (message === null) { throw new RangeError("message should not be null") - */ - AMsyncMessage const* message; - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &message)); + if (message === null) { throw new RangeError("message should not be null") */ + AMsyncMessage const* message = AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n1, + test_state->s1), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(message).changes.length, 1) */ - AMitems message_changes = - AMstackItems(stack_ptr, AMsyncMessageChanges(message), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - assert_int_equal(AMitemsSize(&message_changes), 1); + AMchanges message_changes = AMsyncMessageChanges(message); + assert_int_equal(AMchangesSize(&message_changes), 1); /* */ /* n1.push(items, "y") */ - AMstackItem(NULL, AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("y")), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("y"))); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); /* message = n1.generateSyncMessage(s1) - if (message === null) { throw new RangeError("message should not be - null") */ - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &message)); + if (message === null) { throw new RangeError("message should not be null") */ + message = AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n1, test_state->s1), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(message).changes.length, 1) */ - message_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(message), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - assert_int_equal(AMitemsSize(&message_changes), 1); + message_changes = AMsyncMessageChanges(message); + assert_int_equal(AMchangesSize(&message_changes), 1); /* */ /* n1.push(items, "z") */ - AMstackItem(NULL, AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("z")), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("z"))); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); /* */ /* message = n1.generateSyncMessage(s1) - if (message === null) { throw new RangeError("message should not be - null") */ - assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), - cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), - &message)); + if (message === null) { throw new RangeError("message should not be null") */ + message = AMpush(&test_state->stack, + AMgenerateSyncMessage(test_state->n1, test_state->s1), + AM_VALUE_SYNC_MESSAGE, + cmocka_cb).sync_message; /* assert.deepStrictEqual(decodeSyncMessage(message).changes.length, 1) */ - message_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(message), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); - assert_int_equal(AMitemsSize(&message_changes), 1); + message_changes = AMsyncMessageChanges(message); + assert_int_equal(AMchangesSize(&message_changes), 1); } /** * \brief should work regardless of who initiates the exchange */ -static void test_should_work_regardless_of_who_initiates_the_exchange(void** state) { +static void test_should_work_regardless_of_who_initiates_the_exchange(void **state) { /* create & synchronize two nodes */ /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n1.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ @@ -734,10 +742,10 @@ static void test_should_work_regardless_of_who_initiates_the_exchange(void** sta /* for (let i = 5; i < 10; i++) { */ for (size_t i = 5; i != 10; ++i) { /* n1.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ @@ -751,26 +759,24 @@ static void test_should_work_regardless_of_who_initiates_the_exchange(void** sta /** * \brief should work without prior sync state */ -static void test_should_work_without_prior_sync_state(void** state) { - /* Scenario: ,-- - * c10 <-- c11 <-- c12 <-- c13 <-- c14 c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 - * <-- c6 <-- c7 <-- c8 <-- c9 <-+ - * `-- - * c15 <-- c16 <-- c17 lastSync is undefined. */ +static void test_should_work_without_prior_sync_state(void **state) { + /* Scenario: ,-- c10 <-- c11 <-- c12 <-- c13 <-- c14 + * c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 <-- c6 <-- c7 <-- c8 <-- c9 <-+ + * `-- c15 <-- c16 <-- c17 + * lastSync is undefined. */ /* */ /* create two peers both with divergent commits */ /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* */ /* sync(n1, n2) */ @@ -779,19 +785,19 @@ static void test_should_work_without_prior_sync_state(void** state) { /* for (let i = 10; i < 15; i++) { */ for (size_t i = 10; i != 15; ++i) { /* n1.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* */ /* for (let i = 15; i < 18; i++) { */ for (size_t i = 15; i != 18; ++i) { /* n2.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), i)); /* n2.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); + /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ @@ -799,9 +805,15 @@ static void test_should_work_without_prior_sync_state(void** state) { /* sync(n1, n2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_true(AMitemsEqual(&heads1, &heads2)); + AMchangeHashes heads1 = AMpush(&test_state->stack, + AMgetHeads(test_state->n1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + AMchangeHashes heads2 = AMpush(&test_state->stack, + AMgetHeads(test_state->n2), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } @@ -809,27 +821,25 @@ static void test_should_work_without_prior_sync_state(void** state) { /** * \brief should work with prior sync state */ -static void test_should_work_with_prior_sync_state_2(void** state) { +static void test_should_work_with_prior_sync_state_2(void **state) { /* Scenario: - * ,-- - * c10 <-- c11 <-- c12 <-- c13 <-- c14 c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 - * <-- c6 <-- c7 <-- c8 <-- c9 <-+ - * `-- - * c15 <-- c16 <-- c17 lastSync is c9. */ + * ,-- c10 <-- c11 <-- c12 <-- c13 <-- c14 + * c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 <-- c6 <-- c7 <-- c8 <-- c9 <-+ + * `-- c15 <-- c16 <-- c17 + * lastSync is c9. */ /* */ /* create two peers both with divergent commits */ /* const n1 = create('01234567'), n2 = create('89abcdef') let s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ @@ -838,44 +848,54 @@ static void test_should_work_with_prior_sync_state_2(void** state) { /* for (let i = 10; i < 15; i++) { */ for (size_t i = 10; i != 15; ++i) { /* n1.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* for (let i = 15; i < 18; i++) { */ for (size_t i = 15; i != 18; ++i) { /* n2.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), i)); /* n2.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); + /* { */ } /* */ /* s1 = decodeSyncState(encodeSyncState(s1)) */ - AMbyteSpan encoded; - assert_true(AMitemToBytes( - AMstackItem(stack_ptr, AMsyncStateEncode(test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &encoded)); - AMsyncState* s1; - assert_true(AMitemToSyncState(AMstackItem(stack_ptr, AMsyncStateDecode(encoded.src, encoded.count), cmocka_cb, - AMexpect(AM_VAL_TYPE_SYNC_STATE)), - &s1)); + AMbyteSpan encoded = AMpush(&test_state->stack, + AMsyncStateEncode(test_state->s1), + AM_VALUE_BYTES, + cmocka_cb).bytes; + AMsyncState* s1 = AMpush(&test_state->stack, + AMsyncStateDecode(encoded.src, encoded.count), + AM_VALUE_SYNC_STATE, + cmocka_cb).sync_state; /* s2 = decodeSyncState(encodeSyncState(s2)) */ - assert_true(AMitemToBytes( - AMstackItem(stack_ptr, AMsyncStateEncode(test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &encoded)); - AMsyncState* s2; - assert_true(AMitemToSyncState(AMstackItem(stack_ptr, AMsyncStateDecode(encoded.src, encoded.count), cmocka_cb, - AMexpect(AM_VAL_TYPE_SYNC_STATE)), - &s2)); + encoded = AMpush(&test_state->stack, + AMsyncStateEncode(test_state->s2), + AM_VALUE_BYTES, + cmocka_cb).bytes; + AMsyncState* s2 = AMpush(&test_state->stack, + AMsyncStateDecode(encoded.src, + encoded.count), + AM_VALUE_SYNC_STATE, + cmocka_cb).sync_state; /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ assert_false(AMequal(test_state->n1, test_state->n2)); /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, s1, s2); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_true(AMitemsEqual(&heads1, &heads2)); + AMchangeHashes heads1 = AMpush(&test_state->stack, + AMgetHeads(test_state->n1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + AMchangeHashes heads2 = AMpush(&test_state->stack, + AMgetHeads(test_state->n2), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } @@ -883,39 +903,39 @@ static void test_should_work_with_prior_sync_state_2(void** state) { /** * \brief should ensure non-empty state after sync */ -static void test_should_ensure_non_empty_state_after_sync(void** state) { +static void test_should_ensure_non_empty_state_after_sync(void **state) { /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* for (let i = 0; i < 3; i++) { */ for (size_t i = 0; i != 3; ++i) { /* n1.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* assert.deepStrictEqual(s1.sharedHeads, n1.getHeads()) */ - AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMitems shared_heads1 = - AMstackItems(stack_ptr, AMsyncStateSharedHeads(test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_true(AMitemsEqual(&shared_heads1, &heads1)); + AMchangeHashes heads1 = AMpush(&test_state->stack, + AMgetHeads(test_state->n1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + AMchangeHashes shared_heads1 = AMsyncStateSharedHeads(test_state->s1); + assert_int_equal(AMchangeHashesCmp(&shared_heads1, &heads1), 0); /* assert.deepStrictEqual(s2.sharedHeads, n1.getHeads()) */ - AMitems shared_heads2 = - AMstackItems(stack_ptr, AMsyncStateSharedHeads(test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_true(AMitemsEqual(&shared_heads2, &heads1)); + AMchangeHashes shared_heads2 = AMsyncStateSharedHeads(test_state->s2); + assert_int_equal(AMchangeHashesCmp(&shared_heads2, &heads1), 0); } /** * \brief should re-sync after one node crashed with data loss */ -static void test_should_resync_after_one_node_crashed_with_data_loss(void** state) { +static void test_should_resync_after_one_node_crashed_with_data_loss(void **state) { /* Scenario: (r) (n2) (n1) * c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 <-- c6 <-- c7 <-- c8 * n2 has changes {c0, c1, c2}, n1's lastSync is c5, and n2's lastSync @@ -926,16 +946,15 @@ static void test_should_resync_after_one_node_crashed_with_data_loss(void** stat let s1 = initSyncState() const s2 = initSyncState() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* n1 makes three changes, which we sync to n2 */ /* for (let i = 0; i < 3; i++) { */ for (size_t i = 0; i != 3; ++i) { /* n1.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); @@ -944,25 +963,28 @@ static void test_should_resync_after_one_node_crashed_with_data_loss(void** stat /* let r let rSyncState ;[r, rSyncState] = [n2.clone(), s2.clone()] */ - AMdoc* r; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMclone(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &r)); - AMbyteSpan encoded_s2; - assert_true( - AMitemToBytes(AMstackItem(stack_ptr, AMsyncStateEncode(test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), - &encoded_s2)); - AMsyncState* sync_state_r; - assert_true(AMitemToSyncState(AMstackItem(stack_ptr, AMsyncStateDecode(encoded_s2.src, encoded_s2.count), cmocka_cb, - AMexpect(AM_VAL_TYPE_SYNC_STATE)), - &sync_state_r)); + AMdoc* r = AMpush(&test_state->stack, + AMclone(test_state->n2), + AM_VALUE_DOC, + cmocka_cb).doc; + AMbyteSpan const encoded_s2 = AMpush(&test_state->stack, + AMsyncStateEncode(test_state->s2), + AM_VALUE_BYTES, + cmocka_cb).bytes; + AMsyncState* sync_state_r = AMpush(&test_state->stack, + AMsyncStateDecode(encoded_s2.src, + encoded_s2.count), + AM_VALUE_SYNC_STATE, + cmocka_cb).sync_state; /* */ /* sync another few commits */ /* for (let i = 3; i < 6; i++) { */ for (size_t i = 3; i != 6; ++i) { /* n1.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ @@ -970,9 +992,15 @@ static void test_should_resync_after_one_node_crashed_with_data_loss(void** stat /* */ /* everyone should be on the same page here */ /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_true(AMitemsEqual(&heads1, &heads2)); + AMchangeHashes heads1 = AMpush(&test_state->stack, + AMgetHeads(test_state->n1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + AMchangeHashes heads2 = AMpush(&test_state->stack, + AMgetHeads(test_state->n2), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); /* */ @@ -981,106 +1009,132 @@ static void test_should_resync_after_one_node_crashed_with_data_loss(void** stat /* for (let i = 6; i < 9; i++) { */ for (size_t i = 6; i != 9; ++i) { /* n1.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* */ /* s1 = decodeSyncState(encodeSyncState(s1)) */ - AMbyteSpan encoded_s1; - assert_true( - AMitemToBytes(AMstackItem(stack_ptr, AMsyncStateEncode(test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), - &encoded_s1)); - AMsyncState* s1; - assert_true(AMitemToSyncState(AMstackItem(stack_ptr, AMsyncStateDecode(encoded_s1.src, encoded_s1.count), cmocka_cb, - AMexpect(AM_VAL_TYPE_SYNC_STATE)), - &s1)); + AMbyteSpan const encoded_s1 = AMpush(&test_state->stack, + AMsyncStateEncode(test_state->s1), + AM_VALUE_BYTES, + cmocka_cb).bytes; + AMsyncState* const s1 = AMpush(&test_state->stack, + AMsyncStateDecode(encoded_s1.src, + encoded_s1.count), + AM_VALUE_SYNC_STATE, + cmocka_cb).sync_state; /* rSyncState = decodeSyncState(encodeSyncState(rSyncState)) */ - AMbyteSpan encoded_r; - assert_true(AMitemToBytes( - AMstackItem(stack_ptr, AMsyncStateEncode(sync_state_r), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &encoded_r)); - assert_true(AMitemToSyncState(AMstackItem(stack_ptr, AMsyncStateDecode(encoded_r.src, encoded_r.count), cmocka_cb, - AMexpect(AM_VAL_TYPE_SYNC_STATE)), - &sync_state_r)); + AMbyteSpan const encoded_r = AMpush(&test_state->stack, + AMsyncStateEncode(sync_state_r), + AM_VALUE_BYTES, + cmocka_cb).bytes; + sync_state_r = AMpush(&test_state->stack, + AMsyncStateDecode(encoded_r.src, encoded_r.count), + AM_VALUE_SYNC_STATE, + cmocka_cb).sync_state; /* */ /* assert.notDeepStrictEqual(n1.getHeads(), r.getHeads()) */ - heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMitems heads_r = AMstackItems(stack_ptr, AMgetHeads(r), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_false(AMitemsEqual(&heads1, &heads_r)); + heads1 = AMpush(&test_state->stack, + AMgetHeads(test_state->n1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + AMchangeHashes heads_r = AMpush(&test_state->stack, + AMgetHeads(r), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + assert_int_not_equal(AMchangeHashesCmp(&heads1, &heads_r), 0); /* assert.notDeepStrictEqual(n1.materialize(), r.materialize()) */ assert_false(AMequal(test_state->n1, r)); /* assert.deepStrictEqual(n1.materialize(), { x: 8 }) */ - uint64_t uint; - assert_true(AMitemToUint(AMstackItem(stack_ptr, AMmapGet(test_state->n1, AM_ROOT, AMstr("x"), NULL), cmocka_cb, - AMexpect(AM_VAL_TYPE_UINT)), - &uint)); - assert_int_equal(uint, 8); + assert_int_equal(AMpush(&test_state->stack, + AMmapGet(test_state->n1, AM_ROOT, AMstr("x"), NULL), + AM_VALUE_UINT, + cmocka_cb).uint, 8); /* assert.deepStrictEqual(r.materialize(), { x: 2 }) */ - assert_true(AMitemToUint( - AMstackItem(stack_ptr, AMmapGet(r, AM_ROOT, AMstr("x"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT)), &uint)); - assert_int_equal(uint, 2); + assert_int_equal(AMpush(&test_state->stack, + AMmapGet(r, AM_ROOT, AMstr("x"), NULL), + AM_VALUE_UINT, + cmocka_cb).uint, 2); /* sync(n1, r, s1, rSyncState) */ sync(test_state->n1, r, test_state->s1, sync_state_r); /* assert.deepStrictEqual(n1.getHeads(), r.getHeads()) */ - heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - heads_r = AMstackItems(stack_ptr, AMgetHeads(r), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_true(AMitemsEqual(&heads1, &heads_r)); + heads1 = AMpush(&test_state->stack, + AMgetHeads(test_state->n1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + heads_r = AMpush(&test_state->stack, + AMgetHeads(r), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + assert_int_equal(AMchangeHashesCmp(&heads1, &heads_r), 0); /* assert.deepStrictEqual(n1.materialize(), r.materialize()) */ assert_true(AMequal(test_state->n1, r)); } /** - * \brief should re-sync after one node experiences data loss without - * disconnecting + * \brief should re-sync after one node experiences data loss without disconnecting */ -static void test_should_resync_after_one_node_experiences_data_loss_without_disconnecting(void** state) { +static void test_should_resync_after_one_node_experiences_data_loss_without_disconnecting(void **state) { /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* n1 makes three changes which we sync to n2 */ /* for (let i = 0; i < 3; i++) { */ for (size_t i = 0; i != 3; ++i) { /* n1.put("_root", "x", i) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); /* n1.commit("", 0) */ - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - /* { */ + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_true(AMitemsEqual(&heads1, &heads2)); + AMchangeHashes heads1 = AMpush(&test_state->stack, + AMgetHeads(test_state->n1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + AMchangeHashes heads2 = AMpush(&test_state->stack, + AMgetHeads(test_state->n2), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); /* */ /* const n2AfterDataLoss = create('89abcdef') */ - AMactorId const* actor_id; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("89abcdef")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); - AMdoc* n2_after_data_loss; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), - &n2_after_data_loss)); + AMdoc* n2_after_data_loss = AMpush(&test_state->stack, + AMcreate(AMpush(&test_state->stack, + AMactorIdInitStr(AMstr("89abcdef")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id), + AM_VALUE_DOC, + cmocka_cb).doc; /* */ /* "n2" now has no data, but n1 still thinks it does. Note we don't do * decodeSyncState(encodeSyncState(s1)) in order to simulate data loss * without disconnecting */ /* sync(n1, n2AfterDataLoss, s1, initSyncState()) */ - AMsyncState* s2_after_data_loss; - assert_true(AMitemToSyncState( - AMstackItem(stack_ptr, AMsyncStateInit(), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_STATE)), &s2_after_data_loss)); + AMsyncState* s2_after_data_loss = AMpush(&test_state->stack, + AMsyncStateInit(), + AM_VALUE_SYNC_STATE, + cmocka_cb).sync_state; sync(test_state->n1, n2_after_data_loss, test_state->s1, s2_after_data_loss); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_true(AMitemsEqual(&heads1, &heads2)); + heads1 = AMpush(&test_state->stack, + AMgetHeads(test_state->n1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + heads2 = AMpush(&test_state->stack, + AMgetHeads(test_state->n2), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } @@ -1088,33 +1142,33 @@ static void test_should_resync_after_one_node_experiences_data_loss_without_disc /** * \brief should handle changes concurrent to the last sync heads */ -static void test_should_handle_changes_concurrrent_to_the_last_sync_heads(void** state) { - /* const n1 = create('01234567'), n2 = create('89abcdef'), n3 = - * create('fedcba98' */ +static void test_should_handle_changes_concurrrent_to_the_last_sync_heads(void **state) { + /* const n1 = create('01234567'), n2 = create('89abcdef'), n3 = create('fedcba98' */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; - AMactorId const* actor_id; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("fedcba98")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); - AMdoc* n3; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &n3)); - /* const s12 = initSyncState(), s21 = initSyncState(), s23 = - * initSyncState(), s32 = initSyncState( */ + AMdoc* n3 = AMpush(&test_state->stack, + AMcreate(AMpush(&test_state->stack, + AMactorIdInitStr(AMstr("fedcba98")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id), + AM_VALUE_DOC, + cmocka_cb).doc; + /* const s12 = initSyncState(), s21 = initSyncState(), s23 = initSyncState(), s32 = initSyncState( */ AMsyncState* s12 = test_state->s1; AMsyncState* s21 = test_state->s2; - AMsyncState* s23; - assert_true(AMitemToSyncState( - AMstackItem(stack_ptr, AMsyncStateInit(), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_STATE)), &s23)); - AMsyncState* s32; - assert_true(AMitemToSyncState( - AMstackItem(stack_ptr, AMsyncStateInit(), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_STATE)), &s32)); + AMsyncState* s23 = AMpush(&test_state->stack, + AMsyncStateInit(), + AM_VALUE_SYNC_STATE, + cmocka_cb).sync_state; + AMsyncState* s32 = AMpush(&test_state->stack, + AMsyncStateInit(), + AM_VALUE_SYNC_STATE, + cmocka_cb).sync_state; /* */ /* Change 1 is known to all three nodes */ /* //n1 = Automerge.change(n1, {time: 0}, doc => doc.x = 1) */ /* n1.put("_root", "x", 1); n1.commit("", 0) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 1)); + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); /* */ /* sync(n1, n2, s12, s21) */ sync(test_state->n1, test_state->n2, s12, s21); @@ -1123,38 +1177,47 @@ static void test_should_handle_changes_concurrrent_to_the_last_sync_heads(void** /* */ /* Change 2 is known to n1 and n2 */ /* n1.put("_root", "x", 2); n1.commit("", 0) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 2)); + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); /* */ /* sync(n1, n2, s12, s21) */ sync(test_state->n1, test_state->n2, s12, s21); /* */ /* Each of the three nodes makes one change (changes 3, 4, 5) */ /* n1.put("_root", "x", 3); n1.commit("", 0) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 3)); + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); /* n2.put("_root", "x", 4); n2.commit("", 0) */ - AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), 4), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), 4)); + AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); /* n3.put("_root", "x", 5); n3.commit("", 0) */ - AMstackItem(NULL, AMmapPutUint(n3, AM_ROOT, AMstr("x"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(n3, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmapPutUint(n3, AM_ROOT, AMstr("x"), 5)); + AMfree(AMcommit(n3, AMstr(""), &TIME_0)); /* */ /* Apply n3's latest change to n2. */ /* let change = n3.getLastLocalChange() if (change === null) throw new RangeError("no local change") */ - AMitems changes = AMstackItems(stack_ptr, AMgetLastLocalChange(n3), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMchanges changes = AMpush(&test_state->stack, + AMgetLastLocalChange(n3), + AM_VALUE_CHANGES, + cmocka_cb).changes; /* n2.applyChanges([change]) */ - AMstackItem(NULL, AMapplyChanges(test_state->n2, &changes), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMapplyChanges(test_state->n2, &changes)); /* */ /* Now sync n1 and n2. n3's change is concurrent to n1 and n2's last sync * heads */ /* sync(n1, n2, s12, s21) */ sync(test_state->n1, test_state->n2, s12, s21); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_true(AMitemsEqual(&heads1, &heads2)); + AMchangeHashes heads1 = AMpush(&test_state->stack, + AMgetHeads(test_state->n1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + AMchangeHashes heads2 = AMpush(&test_state->stack, + AMgetHeads(test_state->n2), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } @@ -1162,35 +1225,39 @@ static void test_should_handle_changes_concurrrent_to_the_last_sync_heads(void** /** * \brief should handle histories with lots of branching and merging */ -static void test_should_handle_histories_with_lots_of_branching_and_merging(void** state) { - /* const n1 = create('01234567'), n2 = create('89abcdef'), n3 = - create('fedcba98') const s1 = initSyncState(), s2 = initSyncState() */ +static void test_should_handle_histories_with_lots_of_branching_and_merging(void **state) { + /* const n1 = create('01234567'), n2 = create('89abcdef'), n3 = create('fedcba98') + const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMstack** stack_ptr = &test_state->base_state->stack; - AMactorId const* actor_id; - assert_true(AMitemToActorId( - AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("fedcba98")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), - &actor_id)); - AMdoc* n3; - assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &n3)); + AMdoc* n3 = AMpush(&test_state->stack, + AMcreate(AMpush(&test_state->stack, + AMactorIdInitStr(AMstr("fedcba98")), + AM_VALUE_ACTOR_ID, + cmocka_cb).actor_id), + AM_VALUE_DOC, + cmocka_cb).doc; /* n1.put("_root", "x", 0); n1.commit("", 0) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 0)); + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); /* let change1 = n1.getLastLocalChange() if (change1 === null) throw new RangeError("no local change") */ - AMitems change1 = - AMstackItems(stack_ptr, AMgetLastLocalChange(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMchanges change1 = AMpush(&test_state->stack, + AMgetLastLocalChange(test_state->n1), + AM_VALUE_CHANGES, + cmocka_cb).changes; /* n2.applyChanges([change1]) */ - AMstackItem(NULL, AMapplyChanges(test_state->n2, &change1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMapplyChanges(test_state->n2, &change1)); /* let change2 = n1.getLastLocalChange() if (change2 === null) throw new RangeError("no local change") */ - AMitems change2 = - AMstackItems(stack_ptr, AMgetLastLocalChange(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMchanges change2 = AMpush(&test_state->stack, + AMgetLastLocalChange(test_state->n1), + AM_VALUE_CHANGES, + cmocka_cb).changes; /* n3.applyChanges([change2]) */ - AMstackItem(NULL, AMapplyChanges(n3, &change2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMapplyChanges(n3, &change2)); /* n3.put("_root", "x", 1); n3.commit("", 0) */ - AMstackItem(NULL, AMmapPutUint(n3, AM_ROOT, AMstr("x"), 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(n3, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmapPutUint(n3, AM_ROOT, AMstr("x"), 1)); + AMfree(AMcommit(n3, AMstr(""), &TIME_0)); /* */ /* - n1c1 <------ n1c2 <------ n1c3 <-- etc. <-- n1c20 <------ n1c21 * / \/ \/ \/ @@ -1202,24 +1269,28 @@ static void test_should_handle_histories_with_lots_of_branching_and_merging(void /* for (let i = 1; i < 20; i++) { */ for (size_t i = 1; i != 20; ++i) { /* n1.put("_root", "n1", i); n1.commit("", 0) */ - AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("n1"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("n1"), i)); + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); /* n2.put("_root", "n2", i); n2.commit("", 0) */ - AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("n2"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("n2"), i)); + AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); /* const change1 = n1.getLastLocalChange() if (change1 === null) throw new RangeError("no local change") */ - AMitems change1 = - AMstackItems(stack_ptr, AMgetLastLocalChange(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMchanges change1 = AMpush(&test_state->stack, + AMgetLastLocalChange(test_state->n1), + AM_VALUE_CHANGES, + cmocka_cb).changes; /* const change2 = n2.getLastLocalChange() if (change2 === null) throw new RangeError("no local change") */ - AMitems change2 = - AMstackItems(stack_ptr, AMgetLastLocalChange(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMchanges change2 = AMpush(&test_state->stack, + AMgetLastLocalChange(test_state->n2), + AM_VALUE_CHANGES, + cmocka_cb).changes; /* n1.applyChanges([change2]) */ - AMstackItem(NULL, AMapplyChanges(test_state->n1, &change2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMapplyChanges(test_state->n1, &change2)); /* n2.applyChanges([change1]) */ - AMstackItem(NULL, AMapplyChanges(test_state->n2, &change1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - /* { */ + AMfree(AMapplyChanges(test_state->n2, &change1)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ @@ -1229,24 +1300,31 @@ static void test_should_handle_histories_with_lots_of_branching_and_merging(void * the slower code path */ /* const change3 = n2.getLastLocalChange() if (change3 === null) throw new RangeError("no local change") */ - AMitems change3 = AMstackItems(stack_ptr, AMgetLastLocalChange(n3), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMchanges change3 = AMpush(&test_state->stack, + AMgetLastLocalChange(n3), + AM_VALUE_CHANGES, + cmocka_cb).changes; /* n2.applyChanges([change3]) */ - AMstackItem(NULL, AMapplyChanges(test_state->n2, &change3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMfree(AMapplyChanges(test_state->n2, &change3)); /* n1.put("_root", "n1", "final"); n1.commit("", 0) */ - AMstackItem(NULL, AMmapPutStr(test_state->n1, AM_ROOT, AMstr("n1"), AMstr("final")), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmapPutStr(test_state->n1, AM_ROOT, AMstr("n1"), AMstr("final"))); + AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); /* n2.put("_root", "n2", "final"); n2.commit("", 0) */ - AMstackItem(NULL, AMmapPutStr(test_state->n2, AM_ROOT, AMstr("n2"), AMstr("final")), cmocka_cb, - AMexpect(AM_VAL_TYPE_VOID)); - AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMfree(AMmapPutStr(test_state->n2, AM_ROOT, AMstr("n2"), AMstr("final"))); + AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - assert_true(AMitemsEqual(&heads1, &heads2)); + AMchangeHashes heads1 = AMpush(&test_state->stack, + AMgetHeads(test_state->n1), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + AMchangeHashes heads2 = AMpush(&test_state->stack, + AMgetHeads(test_state->n2), + AM_VALUE_CHANGE_HASHES, + cmocka_cb).change_hashes; + assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } @@ -1256,26 +1334,20 @@ int run_ported_wasm_sync_tests(void) { cmocka_unit_test_setup_teardown(test_should_send_a_sync_message_implying_no_local_data, setup, teardown), cmocka_unit_test_setup_teardown(test_should_not_reply_if_we_have_no_data_as_well, setup, teardown), cmocka_unit_test_setup_teardown(test_repos_with_equal_heads_do_not_need_a_reply_message, setup, teardown), - cmocka_unit_test_setup_teardown(test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing, setup, - teardown), - cmocka_unit_test_setup_teardown(test_should_sync_peers_where_one_has_commits_the_other_does_not, setup, - teardown), + cmocka_unit_test_setup_teardown(test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing, setup, teardown), + cmocka_unit_test_setup_teardown(test_should_sync_peers_where_one_has_commits_the_other_does_not, setup, teardown), cmocka_unit_test_setup_teardown(test_should_work_with_prior_sync_state, setup, teardown), cmocka_unit_test_setup_teardown(test_should_not_generate_messages_once_synced, setup, teardown), - cmocka_unit_test_setup_teardown(test_should_allow_simultaneous_messages_during_synchronization, setup, - teardown), - cmocka_unit_test_setup_teardown(test_should_assume_sent_changes_were_received_until_we_hear_otherwise, setup, - teardown), + cmocka_unit_test_setup_teardown(test_should_allow_simultaneous_messages_during_synchronization, setup, teardown), + cmocka_unit_test_setup_teardown(test_should_assume_sent_changes_were_received_until_we_hear_otherwise, setup, teardown), cmocka_unit_test_setup_teardown(test_should_work_regardless_of_who_initiates_the_exchange, setup, teardown), cmocka_unit_test_setup_teardown(test_should_work_without_prior_sync_state, setup, teardown), cmocka_unit_test_setup_teardown(test_should_work_with_prior_sync_state_2, setup, teardown), cmocka_unit_test_setup_teardown(test_should_ensure_non_empty_state_after_sync, setup, teardown), cmocka_unit_test_setup_teardown(test_should_resync_after_one_node_crashed_with_data_loss, setup, teardown), - cmocka_unit_test_setup_teardown(test_should_resync_after_one_node_experiences_data_loss_without_disconnecting, - setup, teardown), + cmocka_unit_test_setup_teardown(test_should_resync_after_one_node_experiences_data_loss_without_disconnecting, setup, teardown), cmocka_unit_test_setup_teardown(test_should_handle_changes_concurrrent_to_the_last_sync_heads, setup, teardown), - cmocka_unit_test_setup_teardown(test_should_handle_histories_with_lots_of_branching_and_merging, setup, - teardown), + cmocka_unit_test_setup_teardown(test_should_handle_histories_with_lots_of_branching_and_merging, setup, teardown), }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/rust/automerge-c/test/stack_utils.c b/rust/automerge-c/test/stack_utils.c new file mode 100644 index 00000000..f65ea2e5 --- /dev/null +++ b/rust/automerge-c/test/stack_utils.c @@ -0,0 +1,31 @@ +#include +#include +#include + +/* third-party */ +#include + +/* local */ +#include "cmocka_utils.h" +#include "stack_utils.h" + +void cmocka_cb(AMresultStack** stack, uint8_t discriminant) { + assert_non_null(stack); + assert_non_null(*stack); + assert_non_null((*stack)->result); + if (AMresultStatus((*stack)->result) != AM_STATUS_OK) { + fail_msg_view("%s", AMerrorMessage((*stack)->result)); + } + assert_int_equal(AMresultValue((*stack)->result).tag, discriminant); +} + +int setup_stack(void** state) { + *state = NULL; + return 0; +} + +int teardown_stack(void** state) { + AMresultStack* stack = *state; + AMfreeStack(&stack); + return 0; +} diff --git a/rust/automerge-c/test/stack_utils.h b/rust/automerge-c/test/stack_utils.h new file mode 100644 index 00000000..473feebc --- /dev/null +++ b/rust/automerge-c/test/stack_utils.h @@ -0,0 +1,38 @@ +#ifndef STACK_UTILS_H +#define STACK_UTILS_H + +#include + +/* local */ +#include + +/** + * \brief Reports an error through a cmocka assertion. + * + * \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct. + * \param[in] discriminant An `AMvalueVariant` enum tag. + * \pre \p stack` != NULL`. + */ +void cmocka_cb(AMresultStack** stack, uint8_t discriminant); + +/** + * \brief Allocates a result stack for storing the results allocated during one + * or more test cases. + * + * \param[in,out] state A pointer to a pointer to an `AMresultStack` struct. + * \pre \p state` != NULL`. + * \warning The `AMresultStack` struct returned through \p state must be + * deallocated with `teardown_stack()` in order to prevent memory leaks. + */ +int setup_stack(void** state); + +/** + * \brief Deallocates a result stack after deallocating any results that were + * stored in it by one or more test cases. + * + * \param[in] state A pointer to a pointer to an `AMresultStack` struct. + * \pre \p state` != NULL`. + */ +int teardown_stack(void** state); + +#endif /* STACK_UTILS_H */ diff --git a/rust/automerge-c/test/str_utils.c b/rust/automerge-c/test/str_utils.c index 2937217a..cc923cb4 100644 --- a/rust/automerge-c/test/str_utils.c +++ b/rust/automerge-c/test/str_utils.c @@ -1,5 +1,5 @@ -#include #include +#include /* local */ #include "str_utils.h" diff --git a/rust/automerge-c/test/str_utils.h b/rust/automerge-c/test/str_utils.h index 14a4af73..b9985683 100644 --- a/rust/automerge-c/test/str_utils.h +++ b/rust/automerge-c/test/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 */ diff --git a/rust/automerge-cli/Cargo.lock b/rust/automerge-cli/Cargo.lock new file mode 100644 index 00000000..a330ee89 --- /dev/null +++ b/rust/automerge-cli/Cargo.lock @@ -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" diff --git a/rust/automerge-wasm/Cargo.toml b/rust/automerge-wasm/Cargo.toml index b6055a7d..3d2fafe4 100644 --- a/rust/automerge-wasm/Cargo.toml +++ b/rust/automerge-wasm/Cargo.toml @@ -57,6 +57,5 @@ features = ["console"] [dev-dependencies] futures = "^0.1" -proptest = { version = "^1.0.0", default-features = false, features = ["std"] } wasm-bindgen-futures = "^0.4" wasm-bindgen-test = "^0.3" diff --git a/rust/automerge-wasm/package.json b/rust/automerge-wasm/package.json index 80b39fd4..cce3199f 100644 --- a/rust/automerge-wasm/package.json +++ b/rust/automerge-wasm/package.json @@ -8,7 +8,7 @@ "description": "wasm-bindgen bindings to the automerge rust implementation", "homepage": "https://github.com/automerge/automerge-rs/tree/main/automerge-wasm", "repository": "github:automerge/automerge-rs", - "version": "0.1.25", + "version": "0.1.23", "license": "MIT", "files": [ "README.md", diff --git a/rust/automerge-wasm/src/lib.rs b/rust/automerge-wasm/src/lib.rs index 09072ca7..b53bf3b9 100644 --- a/rust/automerge-wasm/src/lib.rs +++ b/rust/automerge-wasm/src/lib.rs @@ -41,7 +41,6 @@ use wasm_bindgen::JsCast; mod interop; mod observer; -mod sequence_tree; mod sync; mod value; diff --git a/rust/automerge-wasm/src/observer.rs b/rust/automerge-wasm/src/observer.rs index 2351c762..c0b462a6 100644 --- a/rust/automerge-wasm/src/observer.rs +++ b/rust/automerge-wasm/src/observer.rs @@ -6,12 +6,10 @@ use crate::{ interop::{self, alloc, js_set}, TextRepresentation, }; -use automerge::{ObjId, OpObserver, Prop, ReadDoc, ScalarValue, Value}; +use automerge::{ObjId, OpObserver, Prop, ReadDoc, ScalarValue, SequenceTree, Value}; use js_sys::{Array, Object}; use wasm_bindgen::prelude::*; -use crate::sequence_tree::SequenceTree; - #[derive(Debug, Clone, Default)] pub(crate) struct Observer { enabled: bool, diff --git a/rust/automerge-wasm/test/test.ts b/rust/automerge-wasm/test/test.ts index bb4f71e3..56aaae74 100644 --- a/rust/automerge-wasm/test/test.ts +++ b/rust/automerge-wasm/test/test.ts @@ -1447,7 +1447,7 @@ describe('Automerge', () => { sync(n1, n2, s1, s2) // Having n3's last change concurrent to the last sync heads forces us into the slower code path - const change3 = n3.getLastLocalChange() + const change3 = n2.getLastLocalChange() if (change3 === null) throw new RangeError("no local change") n2.applyChanges([change3]) n1.put("_root", "n1", "final"); n1.commit("", 0) diff --git a/rust/automerge/Cargo.toml b/rust/automerge/Cargo.toml index 0c10cc2b..e5a9125d 100644 --- a/rust/automerge/Cargo.toml +++ b/rust/automerge/Cargo.toml @@ -47,7 +47,6 @@ criterion = "0.4.0" test-log = { version = "0.2.10", features=["trace"], default-features = false} tracing-subscriber = {version = "0.3.9", features = ["fmt", "env-filter"] } automerge-test = { path = "../automerge-test" } -prettytable = "0.10.0" [[bench]] name = "range" diff --git a/rust/automerge/src/autocommit.rs b/rust/automerge/src/autocommit.rs index ae28596e..2c1c3adf 100644 --- a/rust/automerge/src/autocommit.rs +++ b/rust/automerge/src/autocommit.rs @@ -159,7 +159,7 @@ impl AutoCommitWithObs { /// /// This is a cheap operation, it just changes the way indexes are calculated pub fn with_encoding(mut self, encoding: TextEncoding) -> Self { - self.doc = self.doc.with_encoding(encoding); + self.doc.text_encoding = encoding; self } diff --git a/rust/automerge/src/automerge.rs b/rust/automerge/src/automerge.rs index 0dd82253..86aa5f63 100644 --- a/rust/automerge/src/automerge.rs +++ b/rust/automerge/src/automerge.rs @@ -4,7 +4,8 @@ use std::fmt::Debug; use std::num::NonZeroU64; use std::ops::RangeBounds; -use crate::change_graph::ChangeGraph; +use crate::clock::ClockData; +use crate::clocks::Clocks; use crate::columnar::Key as EncodedKey; use crate::exid::ExId; use crate::keys::Keys; @@ -25,8 +26,6 @@ use crate::{ }; use serde::Serialize; -mod current_state; - #[cfg(test)] mod tests; @@ -36,15 +35,6 @@ pub(crate) enum Actor { Cached(usize), } -/// What to do when loading a document partially succeeds -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum OnPartialLoad { - /// Ignore the error and return the loaded changes - Ignore, - /// Fail the entire load - Error, -} - /// An automerge document which does not manage transactions for you. /// /// ## Creating, loading, merging and forking documents @@ -81,26 +71,26 @@ pub enum OnPartialLoad { #[derive(Debug, Clone)] pub struct Automerge { /// The list of unapplied changes that are not causally ready. - queue: Vec, + pub(crate) queue: Vec, /// The history of changes that form this document, topologically sorted too. - history: Vec, + pub(crate) history: Vec, /// Mapping from change hash to index into the history list. - history_index: HashMap, - /// Graph of changes - change_graph: ChangeGraph, + pub(crate) history_index: HashMap, + /// Mapping from change hash to vector clock at this state. + pub(crate) clocks: HashMap, /// Mapping from actor index to list of seqs seen for them. - states: HashMap>, + pub(crate) states: HashMap>, /// Current dependencies of this document (heads hashes). - deps: HashSet, + pub(crate) deps: HashSet, /// Heads at the last save. - saved: Vec, + pub(crate) saved: Vec, /// The set of operations that form this document. - ops: OpSet, + pub(crate) ops: OpSet, /// The current actor. - actor: Actor, + pub(crate) actor: Actor, /// The maximum operation counter this document has seen. - max_op: u64, - text_encoding: TextEncoding, + pub(crate) max_op: u64, + pub(crate) text_encoding: TextEncoding, } impl Automerge { @@ -110,7 +100,7 @@ impl Automerge { queue: vec![], history: vec![], history_index: HashMap::new(), - change_graph: ChangeGraph::new(), + clocks: HashMap::new(), states: HashMap::new(), ops: Default::default(), deps: Default::default(), @@ -121,50 +111,6 @@ impl Automerge { } } - pub(crate) fn ops_mut(&mut self) -> &mut OpSet { - &mut self.ops - } - - pub(crate) fn ops(&self) -> &OpSet { - &self.ops - } - - /// Whether this document has any operations - pub fn is_empty(&self) -> bool { - self.history.is_empty() && self.queue.is_empty() - } - - pub(crate) fn actor_id(&self) -> ActorId { - match &self.actor { - Actor::Unused(id) => id.clone(), - Actor::Cached(idx) => self.ops.m.actors[*idx].clone(), - } - } - - /// Remove the current actor from the opset if it has no ops - /// - /// If the current actor ID has no ops in the opset then remove it from the cache of actor IDs. - /// This us used when rolling back a transaction. If the rolled back ops are the only ops for - /// the current actor then we want to remove that actor from the opset so it doesn't end up in - /// any saved version of the document. - /// - /// # Panics - /// - /// If the last actor in the OpSet is not the actor ID of this document - pub(crate) fn rollback_last_actor(&mut self) { - if let Actor::Cached(actor_idx) = self.actor { - if self.states.get(&actor_idx).is_none() && self.ops.m.actors.len() > 0 { - assert!(self.ops.m.actors.len() == actor_idx + 1); - let actor = self.ops.m.actors.remove_last(); - self.actor = Actor::Unused(actor); - } - } - } - - pub(crate) fn text_encoding(&self) -> TextEncoding { - self.text_encoding - } - /// Change the text encoding of this view of the document /// /// This is a cheap operation, it just changes the way indexes are calculated @@ -430,26 +376,20 @@ impl Automerge { /// Load a document. pub fn load(data: &[u8]) -> Result { - Self::load_with::<()>(data, OnPartialLoad::Error, VerificationMode::Check, None) + Self::load_with::<()>(data, VerificationMode::Check, None) } /// Load a document without verifying the head hashes /// /// This is useful for debugging as it allows you to examine a corrupted document. pub fn load_unverified_heads(data: &[u8]) -> Result { - Self::load_with::<()>( - data, - OnPartialLoad::Error, - VerificationMode::DontCheck, - None, - ) + Self::load_with::<()>(data, VerificationMode::DontCheck, None) } /// Load a document with an observer #[tracing::instrument(skip(data, observer), err)] pub fn load_with( data: &[u8], - on_error: OnPartialLoad, mode: VerificationMode, mut observer: Option<&mut Obs>, ) -> Result { @@ -464,7 +404,6 @@ impl Automerge { return Err(load::Error::BadChecksum.into()); } - let mut change: Option = None; let mut am = match first_chunk { storage::Chunk::Document(d) => { tracing::trace!("first chunk is document chunk, inflating"); @@ -473,18 +412,23 @@ impl Automerge { result: op_set, changes, heads, - } = storage::load::reconstruct_document(&d, mode, OpSet::builder()) - .map_err(|e| load::Error::InflateDocument(Box::new(e)))?; + } = match &mut observer { + Some(o) => { + storage::load::reconstruct_document(&d, mode, OpSet::observed_builder(*o)) + } + None => storage::load::reconstruct_document(&d, mode, OpSet::builder()), + } + .map_err(|e| load::Error::InflateDocument(Box::new(e)))?; let mut hashes_by_index = HashMap::new(); let mut actor_to_history: HashMap> = HashMap::new(); - let mut change_graph = ChangeGraph::new(); + let mut clocks = Clocks::new(); for (index, change) in changes.iter().enumerate() { // SAFETY: This should be fine because we just constructed an opset containing // all the changes let actor_index = op_set.m.actors.lookup(change.actor_id()).unwrap(); actor_to_history.entry(actor_index).or_default().push(index); hashes_by_index.insert(index, change.hash()); - change_graph.add_change(change, actor_index)?; + clocks.add_change(change, actor_index)?; } let history_index = hashes_by_index.into_iter().map(|(k, v)| (v, k)).collect(); Self { @@ -492,7 +436,7 @@ impl Automerge { history: changes, history_index, states: actor_to_history, - change_graph, + clocks: clocks.into(), ops: op_set, deps: heads.into_iter().collect(), saved: Default::default(), @@ -502,41 +446,33 @@ impl Automerge { } } storage::Chunk::Change(stored_change) => { - tracing::trace!("first chunk is change chunk"); - change = Some( - Change::new_from_unverified(stored_change.into_owned(), None) - .map_err(|e| load::Error::InvalidChangeColumns(Box::new(e)))?, - ); - Self::new() + tracing::trace!("first chunk is change chunk, applying"); + let change = Change::new_from_unverified(stored_change.into_owned(), None) + .map_err(|e| load::Error::InvalidChangeColumns(Box::new(e)))?; + let mut am = Self::new(); + am.apply_change(change, &mut observer); + am } storage::Chunk::CompressedChange(stored_change, compressed) => { - tracing::trace!("first chunk is compressed change"); - change = Some( - Change::new_from_unverified( - stored_change.into_owned(), - Some(compressed.into_owned()), - ) - .map_err(|e| load::Error::InvalidChangeColumns(Box::new(e)))?, - ); - Self::new() + tracing::trace!("first chunk is compressed change, decompressing and applying"); + let change = Change::new_from_unverified( + stored_change.into_owned(), + Some(compressed.into_owned()), + ) + .map_err(|e| load::Error::InvalidChangeColumns(Box::new(e)))?; + let mut am = Self::new(); + am.apply_change(change, &mut observer); + am } }; - tracing::trace!("loading change chunks"); + tracing::trace!("first chunk loaded, loading remaining chunks"); match load::load_changes(remaining.reset()) { load::LoadedChanges::Complete(c) => { - am.apply_changes(change.into_iter().chain(c))?; - if !am.queue.is_empty() { - return Err(AutomergeError::MissingDeps); + for change in c { + am.apply_change(change, &mut observer); } } - load::LoadedChanges::Partial { error, .. } => { - if on_error == OnPartialLoad::Error { - return Err(error.into()); - } - } - } - if let Some(observer) = &mut observer { - current_state::observe_current_state(&am, *observer); + load::LoadedChanges::Partial { error, .. } => return Err(error.into()), } Ok(am) } @@ -558,18 +494,6 @@ impl Automerge { data: &[u8], op_observer: Option<&mut Obs>, ) -> Result { - if self.is_empty() { - let mut doc = - Self::load_with::<()>(data, OnPartialLoad::Ignore, VerificationMode::Check, None)?; - doc = doc - .with_encoding(self.text_encoding) - .with_actor(self.actor_id()); - if let Some(obs) = op_observer { - current_state::observe_current_state(&doc, obs); - } - *self = doc; - return Ok(self.ops.len()); - } let changes = match load::load_changes(storage::parse::Input::new(data)) { load::LoadedChanges::Complete(c) => c, load::LoadedChanges::Partial { error, loaded, .. } => { @@ -610,11 +534,6 @@ impl Automerge { changes: I, mut op_observer: Option<&mut Obs>, ) -> Result<(), AutomergeError> { - // Record this so we can avoid observing each individual change and instead just observe - // the final state after all the changes have been applied. We can only do this for an - // empty document right now, once we have logic to produce the diffs between arbitrary - // states of the OpSet we can make this cleaner. - let empty_at_start = self.is_empty(); for c in changes { if !self.history_index.contains_key(&c.hash()) { if self.duplicate_seq(&c) { @@ -624,11 +543,7 @@ impl Automerge { )); } if self.is_causally_ready(&c) { - if empty_at_start { - self.apply_change::<()>(c, &mut None); - } else { - self.apply_change(c, &mut op_observer); - } + self.apply_change(c, &mut op_observer); } else { self.queue.push(c); } @@ -636,16 +551,7 @@ impl Automerge { } while let Some(c) = self.pop_next_causally_ready_change() { if !self.history_index.contains_key(&c.hash()) { - if empty_at_start { - self.apply_change::<()>(c, &mut None); - } else { - self.apply_change(c, &mut op_observer); - } - } - } - if empty_at_start { - if let Some(observer) = &mut op_observer { - current_state::observe_current_state(self, *observer); + self.apply_change(c, &mut op_observer); } } Ok(()) @@ -723,7 +629,7 @@ impl Automerge { obj, Op { id, - action: OpType::from_action_and_value(c.action, c.val), + action: OpType::from_index_and_value(c.action, c.val).unwrap(), key, succ: Default::default(), pred, @@ -766,7 +672,7 @@ impl Automerge { let c = self.history.iter(); let bytes = crate::storage::save::save_document( c, - self.ops.iter().map(|(objid, _, op)| (objid, op)), + self.ops.iter(), &self.ops.m.actors, &self.ops.m.props, &heads, @@ -782,7 +688,7 @@ impl Automerge { let c = self.history.iter(); let bytes = crate::storage::save::save_document( c, - self.ops.iter().map(|(objid, _, op)| (objid, op)), + self.ops.iter(), &self.ops.m.actors, &self.ops.m.props, &heads, @@ -825,8 +731,16 @@ impl Automerge { .filter(|hash| self.history_index.contains_key(hash)) .copied() .collect::>(); + let heads_clock = self.clock_at(&heads)?; - self.change_graph.remove_ancestors(changes, &heads); + // keep the hashes that are concurrent or after the heads + changes.retain(|hash| { + self.clocks + .get(hash) + .unwrap() + .partial_cmp(&heads_clock) + .map_or(true, |o| o == Ordering::Greater) + }); Ok(()) } @@ -834,7 +748,7 @@ impl Automerge { /// Get the changes since `have_deps` in this document using a clock internally. fn get_changes_clock(&self, have_deps: &[ChangeHash]) -> Result, AutomergeError> { // get the clock for the given deps - let clock = self.clock_at(have_deps); + let clock = self.clock_at(have_deps)?; // get the documents current clock @@ -868,8 +782,26 @@ impl Automerge { .find(|c| c.actor_id() == self.get_actor()); } - fn clock_at(&self, heads: &[ChangeHash]) -> Clock { - self.change_graph.clock_for_heads(heads) + fn clock_at(&self, heads: &[ChangeHash]) -> Result { + if let Some(first_hash) = heads.first() { + let mut clock = self + .clocks + .get(first_hash) + .ok_or(AutomergeError::MissingHash(*first_hash))? + .clone(); + + for hash in &heads[1..] { + let c = self + .clocks + .get(hash) + .ok_or(AutomergeError::MissingHash(*hash))?; + clock.merge(c); + } + + Ok(clock) + } else { + Ok(Clock::new()) + } } fn get_hash(&self, actor: usize, seq: u64) -> Result { @@ -895,10 +827,24 @@ impl Automerge { .push(history_index); self.history_index.insert(change.hash(), history_index); - self.change_graph - .add_change(&change, actor_index) - .expect("Change's deps should already be in the document"); + let mut clock = Clock::new(); + for hash in change.deps() { + let c = self + .clocks + .get(hash) + .expect("Change's deps should already be in the document"); + clock.merge(c); + } + clock.include( + actor_index, + ClockData { + max_op: change.max_op(), + seq: change.seq(), + }, + ); + self.clocks.insert(change.hash(), clock); + self.history_index.insert(change.hash(), history_index); self.history.push(change); history_index @@ -955,7 +901,7 @@ impl Automerge { "pred", "succ" ); - for (obj, _, op) in self.ops.iter() { + for (obj, op) in self.ops.iter() { let id = self.to_string(op.id); let obj = self.to_string(obj); let key = match op.key { @@ -1158,8 +1104,9 @@ impl ReadDoc for Automerge { fn keys_at>(&self, obj: O, heads: &[ChangeHash]) -> KeysAt<'_, '_> { if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { - let clock = self.clock_at(heads); - return KeysAt::new(self, self.ops.keys_at(obj, clock)); + if let Ok(clock) = self.clock_at(heads) { + return KeysAt::new(self, self.ops.keys_at(obj, clock)); + } } KeysAt::new(self, None) } @@ -1183,9 +1130,10 @@ impl ReadDoc for Automerge { heads: &[ChangeHash], ) -> MapRangeAt<'_, R> { if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { - let clock = self.clock_at(heads); - let iter_range = self.ops.map_range_at(obj, range, clock); - return MapRangeAt::new(self, iter_range); + if let Ok(clock) = self.clock_at(heads) { + let iter_range = self.ops.map_range_at(obj, range, clock); + return MapRangeAt::new(self, iter_range); + } } MapRangeAt::new(self, None) } @@ -1209,9 +1157,10 @@ impl ReadDoc for Automerge { heads: &[ChangeHash], ) -> ListRangeAt<'_, R> { if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { - let clock = self.clock_at(heads); - let iter_range = self.ops.list_range_at(obj, range, clock); - return ListRangeAt::new(self, iter_range); + if let Ok(clock) = self.clock_at(heads) { + let iter_range = self.ops.list_range_at(obj, range, clock); + return ListRangeAt::new(self, iter_range); + } } ListRangeAt::new(self, None) } @@ -1230,20 +1179,20 @@ impl ReadDoc for Automerge { fn values_at>(&self, obj: O, heads: &[ChangeHash]) -> Values<'_> { if let Ok((obj, obj_type)) = self.exid_to_obj(obj.as_ref()) { - let clock = self.clock_at(heads); - match obj_type { - ObjType::Map | ObjType::Table => { - let iter_range = self.ops.map_range_at(obj, .., clock); - Values::new(self, iter_range) - } - ObjType::List | ObjType::Text => { - let iter_range = self.ops.list_range_at(obj, .., clock); - Values::new(self, iter_range) - } + if let Ok(clock) = self.clock_at(heads) { + return match obj_type { + ObjType::Map | ObjType::Table => { + let iter_range = self.ops.map_range_at(obj, .., clock); + Values::new(self, iter_range) + } + ObjType::List | ObjType::Text => { + let iter_range = self.ops.list_range_at(obj, .., clock); + Values::new(self, iter_range) + } + }; } - } else { - Values::empty(self) } + Values::empty(self) } fn length>(&self, obj: O) -> usize { @@ -1261,18 +1210,18 @@ impl ReadDoc for Automerge { fn length_at>(&self, obj: O, heads: &[ChangeHash]) -> usize { if let Ok((inner_obj, obj_type)) = self.exid_to_obj(obj.as_ref()) { - let clock = self.clock_at(heads); - if obj_type == ObjType::Map || obj_type == ObjType::Table { - self.keys_at(obj, heads).count() - } else { - let encoding = ListEncoding::new(obj_type, self.text_encoding); - self.ops - .search(&inner_obj, query::LenAt::new(clock, encoding)) - .len + if let Ok(clock) = self.clock_at(heads) { + return if obj_type == ObjType::Map || obj_type == ObjType::Table { + self.keys_at(obj, heads).count() + } else { + let encoding = ListEncoding::new(obj_type, self.text_encoding); + self.ops + .search(&inner_obj, query::LenAt::new(clock, encoding)) + .len + }; } - } else { - 0 } + 0 } fn object_type>(&self, obj: O) -> Result { @@ -1296,7 +1245,7 @@ impl ReadDoc for Automerge { heads: &[ChangeHash], ) -> Result { let obj = self.exid_to_obj(obj.as_ref())?.0; - let clock = self.clock_at(heads); + let clock = self.clock_at(heads)?; let query = self.ops.search(&obj, query::ListValsAt::new(clock)); let mut buffer = String::new(); for q in &query.ops { @@ -1371,7 +1320,7 @@ impl ReadDoc for Automerge { ) -> Result, ExId)>, AutomergeError> { let prop = prop.into(); let obj = self.exid_to_obj(obj.as_ref())?.0; - let clock = self.clock_at(heads); + let clock = self.clock_at(heads)?; let result = match prop { Prop::Map(p) => { let prop = self.ops.m.props.lookup(&p); diff --git a/rust/automerge/src/automerge/current_state.rs b/rust/automerge/src/automerge/current_state.rs deleted file mode 100644 index 3f7f4afc..00000000 --- a/rust/automerge/src/automerge/current_state.rs +++ /dev/null @@ -1,915 +0,0 @@ -use std::{borrow::Cow, collections::HashSet, iter::Peekable}; - -use itertools::Itertools; - -use crate::{ - types::{ElemId, Key, ListEncoding, ObjId, Op, OpId}, - ObjType, OpObserver, OpType, ScalarValue, Value, -}; - -/// Traverse the "current" state of the document, notifying `observer` -/// -/// The "current" state of the document is the set of visible operations. This function will -/// traverse that set of operations and call the corresponding methods on the `observer` as it -/// encounters values. The `observer` methods will be called in the order in which they appear in -/// the document. That is to say that the observer will be notified of parent objects before the -/// objects they contain and elements of a sequence will be notified in the order they occur. -/// -/// Due to only notifying of visible operations the observer will only be called with `put`, -/// `insert`, and `splice`, operations. -pub(super) fn observe_current_state(doc: &crate::Automerge, observer: &mut O) { - // The OpSet already exposes operations in the order they appear in the document. - // `OpSet::iter_objs` iterates over the objects in causal order, this means that parent objects - // will always appear before their children. Furthermore, the operations within each object are - // ordered by key (which means by their position in a sequence for sequences). - // - // Effectively then we iterate over each object, then we group the operations in the object by - // key and for each key find the visible operations for that key. Then we notify the observer - // for each of those visible operations. - let mut visible_objs = HashSet::new(); - visible_objs.insert(ObjId::root()); - for (obj, typ, ops) in doc.ops().iter_objs() { - if !visible_objs.contains(obj) { - continue; - } - let ops_by_key = ops.group_by(|o| o.key); - let actions = ops_by_key - .into_iter() - .flat_map(|(key, key_ops)| key_actions(key, key_ops)); - if typ == ObjType::Text && !observer.text_as_seq() { - track_new_objs_and_notify( - &mut visible_objs, - doc, - obj, - typ, - observer, - text_actions(actions), - ) - } else if typ == ObjType::List { - track_new_objs_and_notify( - &mut visible_objs, - doc, - obj, - typ, - observer, - list_actions(actions), - ) - } else { - track_new_objs_and_notify(&mut visible_objs, doc, obj, typ, observer, actions) - } - } -} - -fn track_new_objs_and_notify, O: OpObserver>( - visible_objs: &mut HashSet, - doc: &crate::Automerge, - obj: &ObjId, - typ: ObjType, - observer: &mut O, - actions: I, -) { - let exid = doc.id_to_exid(obj.0); - for action in actions { - if let Some(obj) = action.made_object() { - visible_objs.insert(obj); - } - action.notify_observer(doc, &exid, obj, typ, observer); - } -} - -trait Action { - /// Notify an observer of whatever this action does - fn notify_observer( - self, - doc: &crate::Automerge, - exid: &crate::ObjId, - obj: &ObjId, - typ: ObjType, - observer: &mut O, - ); - - /// If this action created an object, return the ID of that object - fn made_object(&self) -> Option; -} - -fn key_actions<'a, I: Iterator>( - key: Key, - key_ops: I, -) -> impl Iterator> { - #[derive(Clone)] - enum CurrentOp<'a> { - Put { - value: Value<'a>, - id: OpId, - conflicted: bool, - }, - Insert(Value<'a>, OpId), - } - let current_ops = key_ops - .filter(|o| o.visible()) - .filter_map(|o| match o.action { - OpType::Make(obj_type) => { - let value = Value::Object(obj_type); - if o.insert { - Some(CurrentOp::Insert(value, o.id)) - } else { - Some(CurrentOp::Put { - value, - id: o.id, - conflicted: false, - }) - } - } - OpType::Put(ref value) => { - let value = Value::Scalar(Cow::Borrowed(value)); - if o.insert { - Some(CurrentOp::Insert(value, o.id)) - } else { - Some(CurrentOp::Put { - value, - id: o.id, - conflicted: false, - }) - } - } - _ => None, - }); - current_ops - .coalesce(|previous, current| match (previous, current) { - (CurrentOp::Put { .. }, CurrentOp::Put { value, id, .. }) => Ok(CurrentOp::Put { - value, - id, - conflicted: true, - }), - (previous, current) => Err((previous, current)), - }) - .map(move |op| match op { - CurrentOp::Put { - value, - id, - conflicted, - } => SimpleAction::Put { - prop: key, - tagged_value: (value, id), - conflict: conflicted, - }, - CurrentOp::Insert(val, id) => SimpleAction::Insert { - elem_id: ElemId(id), - tagged_value: (val, id), - }, - }) -} - -/// Either a "put" or "insert" action. i.e. not splicing for text values -enum SimpleAction<'a> { - Put { - prop: Key, - tagged_value: (Value<'a>, OpId), - conflict: bool, - }, - Insert { - elem_id: ElemId, - tagged_value: (Value<'a>, OpId), - }, -} - -impl<'a> Action for SimpleAction<'a> { - fn notify_observer( - self, - doc: &crate::Automerge, - exid: &crate::ObjId, - obj: &ObjId, - typ: ObjType, - observer: &mut O, - ) { - let encoding = match typ { - ObjType::Text => ListEncoding::Text(doc.text_encoding()), - _ => ListEncoding::List, - }; - match self { - Self::Put { - prop, - tagged_value, - conflict, - } => { - let tagged_value = (tagged_value.0, doc.id_to_exid(tagged_value.1)); - let prop = doc.ops().export_key(*obj, prop, encoding).unwrap(); - observer.put(doc, exid.clone(), prop, tagged_value, conflict); - } - Self::Insert { - elem_id, - tagged_value: (value, opid), - } => { - let index = doc - .ops() - .search(obj, crate::query::ElemIdPos::new(elem_id, encoding)) - .index() - .unwrap(); - let tagged_value = (value, doc.id_to_exid(opid)); - observer.insert(doc, doc.id_to_exid(obj.0), index, tagged_value); - } - } - } - - fn made_object(&self) -> Option { - match self { - Self::Put { - tagged_value: (Value::Object(_), id), - .. - } => Some((*id).into()), - Self::Insert { - tagged_value: (Value::Object(_), id), - .. - } => Some((*id).into()), - _ => None, - } - } -} - -/// An `Action` which splices for text values -enum TextAction<'a> { - Action(SimpleAction<'a>), - Splice { start: ElemId, chars: String }, -} - -impl<'a> Action for TextAction<'a> { - fn notify_observer( - self, - doc: &crate::Automerge, - exid: &crate::ObjId, - obj: &ObjId, - typ: ObjType, - observer: &mut O, - ) { - match self { - Self::Action(action) => action.notify_observer(doc, exid, obj, typ, observer), - Self::Splice { start, chars } => { - let index = doc - .ops() - .search( - obj, - crate::query::ElemIdPos::new( - start, - ListEncoding::Text(doc.text_encoding()), - ), - ) - .index() - .unwrap(); - observer.splice_text(doc, doc.id_to_exid(obj.0), index, chars.as_str()); - } - } - } - - fn made_object(&self) -> Option { - match self { - Self::Action(action) => action.made_object(), - _ => None, - } - } -} - -fn list_actions<'a, I: Iterator>>( - actions: I, -) -> impl Iterator> { - actions.map(|a| match a { - SimpleAction::Put { - prop: Key::Seq(elem_id), - tagged_value, - .. - } => SimpleAction::Insert { - elem_id, - tagged_value, - }, - a => a, - }) -} - -/// Condense consecutive `SimpleAction::Insert` actions into one `TextAction::Splice` -fn text_actions<'a, I>(actions: I) -> impl Iterator> -where - I: Iterator>, -{ - TextActions { - ops: actions.peekable(), - } -} - -struct TextActions<'a, I: Iterator>> { - ops: Peekable, -} - -impl<'a, I: Iterator>> Iterator for TextActions<'a, I> { - type Item = TextAction<'a>; - - fn next(&mut self) -> Option { - if let Some(SimpleAction::Insert { .. }) = self.ops.peek() { - let (start, value) = match self.ops.next() { - Some(SimpleAction::Insert { - tagged_value: (value, opid), - .. - }) => (opid, value), - _ => unreachable!(), - }; - let mut chars = match value { - Value::Scalar(Cow::Borrowed(ScalarValue::Str(s))) => s.to_string(), - _ => "\u{fffc}".to_string(), - }; - while let Some(SimpleAction::Insert { .. }) = self.ops.peek() { - if let Some(SimpleAction::Insert { - tagged_value: (value, _), - .. - }) = self.ops.next() - { - match value { - Value::Scalar(Cow::Borrowed(ScalarValue::Str(s))) => chars.push_str(s), - _ => chars.push('\u{fffc}'), - } - } - } - Some(TextAction::Splice { - start: ElemId(start), - chars, - }) - } else { - self.ops.next().map(TextAction::Action) - } - } -} - -#[cfg(test)] -mod tests { - use std::{borrow::Cow, fs}; - - use crate::{transaction::Transactable, Automerge, ObjType, OpObserver, Prop, ReadDoc, Value}; - - // Observer ops often carry a "tagged value", which is a value and the OpID of the op which - // created that value. For a lot of values (i.e. any scalar value) we don't care about the - // opid. This type implements `PartialEq` for the `Untagged` variant by ignoring the tag, which - // allows us to express tests which don't care about the tag. - #[derive(Clone, Debug)] - enum ObservedValue { - Tagged(crate::Value<'static>, crate::ObjId), - Untagged(crate::Value<'static>), - } - - impl<'a> From<(Value<'a>, crate::ObjId)> for ObservedValue { - fn from(value: (Value<'a>, crate::ObjId)) -> Self { - Self::Tagged(value.0.into_owned(), value.1) - } - } - - impl PartialEq for ObservedValue { - fn eq(&self, other: &ObservedValue) -> bool { - match (self, other) { - (Self::Tagged(v1, o1), Self::Tagged(v2, o2)) => equal_vals(v1, v2) && o1 == o2, - (Self::Untagged(v1), Self::Untagged(v2)) => equal_vals(v1, v2), - (Self::Tagged(v1, _), Self::Untagged(v2)) => equal_vals(v1, v2), - (Self::Untagged(v1), Self::Tagged(v2, _)) => equal_vals(v1, v2), - } - } - } - - /// Consider counters equal if they have the same current value - fn equal_vals(v1: &Value<'_>, v2: &Value<'_>) -> bool { - match (v1, v2) { - (Value::Scalar(v1), Value::Scalar(v2)) => match (v1.as_ref(), v2.as_ref()) { - (crate::ScalarValue::Counter(c1), crate::ScalarValue::Counter(c2)) => { - c1.current == c2.current - } - _ => v1 == v2, - }, - _ => v1 == v2, - } - } - - #[derive(Debug, Clone, PartialEq)] - enum ObserverCall { - Put { - obj: crate::ObjId, - prop: Prop, - value: ObservedValue, - conflict: bool, - }, - Insert { - obj: crate::ObjId, - index: usize, - value: ObservedValue, - }, - SpliceText { - obj: crate::ObjId, - index: usize, - chars: String, - }, - } - - // A Vec is pretty hard to look at in a test failure. This wrapper prints the - // calls out in a nice table so it's easier to see what's different - #[derive(Clone, PartialEq)] - struct Calls(Vec); - - impl std::fmt::Debug for Calls { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut table = prettytable::Table::new(); - table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); - table.set_titles(prettytable::row![ - "Op", "Object", "Property", "Value", "Conflict" - ]); - for call in &self.0 { - match call { - ObserverCall::Put { - obj, - prop, - value, - conflict, - } => { - table.add_row(prettytable::row![ - "Put", - format!("{}", obj), - prop, - match value { - ObservedValue::Tagged(v, o) => format!("{} ({})", v, o), - ObservedValue::Untagged(v) => format!("{}", v), - }, - conflict - ]); - } - ObserverCall::Insert { obj, index, value } => { - table.add_row(prettytable::row![ - "Insert", - format!("{}", obj), - index, - match value { - ObservedValue::Tagged(v, o) => format!("{} ({})", v, o), - ObservedValue::Untagged(v) => format!("{}", v), - }, - "" - ]); - } - ObserverCall::SpliceText { obj, index, chars } => { - table.add_row(prettytable::row![ - "SpliceText", - format!("{}", obj), - index, - chars, - "" - ]); - } - } - } - let mut out = Vec::new(); - table.print(&mut out).unwrap(); - write!(f, "\n{}\n", String::from_utf8(out).unwrap()) - } - } - - struct ObserverStub { - ops: Vec, - text_as_seq: bool, - } - - impl ObserverStub { - fn new() -> Self { - Self { - ops: Vec::new(), - text_as_seq: true, - } - } - - fn new_text_v2() -> Self { - Self { - ops: Vec::new(), - text_as_seq: false, - } - } - } - - impl OpObserver for ObserverStub { - fn insert( - &mut self, - _doc: &R, - objid: crate::ObjId, - index: usize, - tagged_value: (crate::Value<'_>, crate::ObjId), - ) { - self.ops.push(ObserverCall::Insert { - obj: objid, - index, - value: tagged_value.into(), - }); - } - - fn splice_text( - &mut self, - _doc: &R, - objid: crate::ObjId, - index: usize, - value: &str, - ) { - self.ops.push(ObserverCall::SpliceText { - obj: objid, - index, - chars: value.to_string(), - }); - } - - fn put( - &mut self, - _doc: &R, - objid: crate::ObjId, - prop: crate::Prop, - tagged_value: (crate::Value<'_>, crate::ObjId), - conflict: bool, - ) { - self.ops.push(ObserverCall::Put { - obj: objid, - prop, - value: tagged_value.into(), - conflict, - }); - } - - fn expose( - &mut self, - _doc: &R, - _objid: crate::ObjId, - _prop: crate::Prop, - _tagged_value: (crate::Value<'_>, crate::ObjId), - _conflict: bool, - ) { - panic!("expose not expected"); - } - - fn increment( - &mut self, - _doc: &R, - _objid: crate::ObjId, - _prop: crate::Prop, - _tagged_value: (i64, crate::ObjId), - ) { - panic!("increment not expected"); - } - - fn delete_map(&mut self, _doc: &R, _objid: crate::ObjId, _key: &str) { - panic!("delete not expected"); - } - - fn delete_seq( - &mut self, - _doc: &R, - _objid: crate::ObjId, - _index: usize, - _num: usize, - ) { - panic!("delete not expected"); - } - - fn text_as_seq(&self) -> bool { - self.text_as_seq - } - } - - #[test] - fn basic_test() { - let mut doc = crate::AutoCommit::new(); - doc.put(crate::ROOT, "key", "value").unwrap(); - let map = doc.put_object(crate::ROOT, "map", ObjType::Map).unwrap(); - doc.put(&map, "nested_key", "value").unwrap(); - let list = doc.put_object(crate::ROOT, "list", ObjType::List).unwrap(); - doc.insert(&list, 0, "value").unwrap(); - let text = doc.put_object(crate::ROOT, "text", ObjType::Text).unwrap(); - doc.insert(&text, 0, "a").unwrap(); - - let mut obs = ObserverStub::new(); - super::observe_current_state(doc.document(), &mut obs); - - assert_eq!( - Calls(obs.ops), - Calls(vec![ - ObserverCall::Put { - obj: crate::ROOT, - prop: "key".into(), - value: ObservedValue::Untagged("value".into()), - conflict: false, - }, - ObserverCall::Put { - obj: crate::ROOT, - prop: "list".into(), - value: ObservedValue::Tagged(Value::Object(ObjType::List), list.clone()), - conflict: false, - }, - ObserverCall::Put { - obj: crate::ROOT, - prop: "map".into(), - value: ObservedValue::Tagged(Value::Object(ObjType::Map), map.clone()), - conflict: false, - }, - ObserverCall::Put { - obj: crate::ROOT, - prop: "text".into(), - value: ObservedValue::Tagged(Value::Object(ObjType::Text), text.clone()), - conflict: false, - }, - ObserverCall::Put { - obj: map.clone(), - prop: "nested_key".into(), - value: ObservedValue::Untagged("value".into()), - conflict: false, - }, - ObserverCall::Insert { - obj: list, - index: 0, - value: ObservedValue::Untagged("value".into()), - }, - ObserverCall::Insert { - obj: text, - index: 0, - value: ObservedValue::Untagged("a".into()), - }, - ]) - ); - } - - #[test] - fn test_deleted_ops_omitted() { - let mut doc = crate::AutoCommit::new(); - doc.put(crate::ROOT, "key", "value").unwrap(); - doc.delete(crate::ROOT, "key").unwrap(); - let map = doc.put_object(crate::ROOT, "map", ObjType::Map).unwrap(); - doc.put(&map, "nested_key", "value").unwrap(); - doc.delete(&map, "nested_key").unwrap(); - let list = doc.put_object(crate::ROOT, "list", ObjType::List).unwrap(); - doc.insert(&list, 0, "value").unwrap(); - doc.delete(&list, 0).unwrap(); - let text = doc.put_object(crate::ROOT, "text", ObjType::Text).unwrap(); - doc.insert(&text, 0, "a").unwrap(); - doc.delete(&text, 0).unwrap(); - - doc.put_object(crate::ROOT, "deleted_map", ObjType::Map) - .unwrap(); - doc.delete(crate::ROOT, "deleted_map").unwrap(); - doc.put_object(crate::ROOT, "deleted_list", ObjType::List) - .unwrap(); - doc.delete(crate::ROOT, "deleted_list").unwrap(); - doc.put_object(crate::ROOT, "deleted_text", ObjType::Text) - .unwrap(); - doc.delete(crate::ROOT, "deleted_text").unwrap(); - - let mut obs = ObserverStub::new(); - super::observe_current_state(doc.document(), &mut obs); - - assert_eq!( - Calls(obs.ops), - Calls(vec![ - ObserverCall::Put { - obj: crate::ROOT, - prop: "list".into(), - value: ObservedValue::Tagged(Value::Object(ObjType::List), list.clone()), - conflict: false, - }, - ObserverCall::Put { - obj: crate::ROOT, - prop: "map".into(), - value: ObservedValue::Tagged(Value::Object(ObjType::Map), map.clone()), - conflict: false, - }, - ObserverCall::Put { - obj: crate::ROOT, - prop: "text".into(), - value: ObservedValue::Tagged(Value::Object(ObjType::Text), text.clone()), - conflict: false, - }, - ]) - ); - } - - #[test] - fn test_text_spliced() { - let mut doc = crate::AutoCommit::new(); - let text = doc.put_object(crate::ROOT, "text", ObjType::Text).unwrap(); - doc.insert(&text, 0, "a").unwrap(); - doc.splice_text(&text, 1, 0, "bcdef").unwrap(); - doc.splice_text(&text, 2, 2, "g").unwrap(); - - let mut obs = ObserverStub::new_text_v2(); - super::observe_current_state(doc.document(), &mut obs); - - assert_eq!( - Calls(obs.ops), - Calls(vec![ - ObserverCall::Put { - obj: crate::ROOT, - prop: "text".into(), - value: ObservedValue::Tagged(Value::Object(ObjType::Text), text.clone()), - conflict: false, - }, - ObserverCall::SpliceText { - obj: text, - index: 0, - chars: "abgef".to_string() - } - ]) - ); - } - - #[test] - fn test_counters() { - let actor1 = crate::ActorId::from("aa".as_bytes()); - let actor2 = crate::ActorId::from("bb".as_bytes()); - let mut doc = crate::AutoCommit::new().with_actor(actor2); - - let mut doc2 = doc.fork().with_actor(actor1); - doc2.put(crate::ROOT, "key", "someval").unwrap(); - - doc.put(crate::ROOT, "key", crate::ScalarValue::Counter(1.into())) - .unwrap(); - doc.increment(crate::ROOT, "key", 2).unwrap(); - doc.increment(crate::ROOT, "key", 3).unwrap(); - - doc.merge(&mut doc2).unwrap(); - - let mut obs = ObserverStub::new_text_v2(); - super::observe_current_state(doc.document(), &mut obs); - - assert_eq!( - Calls(obs.ops), - Calls(vec![ObserverCall::Put { - obj: crate::ROOT, - prop: "key".into(), - value: ObservedValue::Untagged(Value::Scalar(Cow::Owned( - crate::ScalarValue::Counter(6.into()) - ))), - conflict: true, - },]) - ); - } - - #[test] - fn test_multiple_list_insertions() { - let mut doc = crate::AutoCommit::new(); - - let list = doc.put_object(crate::ROOT, "list", ObjType::List).unwrap(); - doc.insert(&list, 0, 1).unwrap(); - doc.insert(&list, 1, 2).unwrap(); - - let mut obs = ObserverStub::new_text_v2(); - super::observe_current_state(doc.document(), &mut obs); - - assert_eq!( - Calls(obs.ops), - Calls(vec![ - ObserverCall::Put { - obj: crate::ROOT, - prop: "list".into(), - value: ObservedValue::Tagged(Value::Object(ObjType::List), list.clone()), - conflict: false, - }, - ObserverCall::Insert { - obj: list.clone(), - index: 0, - value: ObservedValue::Untagged(1.into()), - }, - ObserverCall::Insert { - obj: list, - index: 1, - value: ObservedValue::Untagged(2.into()), - }, - ]) - ); - } - - #[test] - fn test_concurrent_insertions_at_same_index() { - let mut doc = crate::AutoCommit::new().with_actor(crate::ActorId::from("aa".as_bytes())); - - let list = doc.put_object(crate::ROOT, "list", ObjType::List).unwrap(); - - let mut doc2 = doc.fork().with_actor(crate::ActorId::from("bb".as_bytes())); - - doc.insert(&list, 0, 1).unwrap(); - doc2.insert(&list, 0, 2).unwrap(); - doc.merge(&mut doc2).unwrap(); - - let mut obs = ObserverStub::new_text_v2(); - super::observe_current_state(doc.document(), &mut obs); - - assert_eq!( - Calls(obs.ops), - Calls(vec![ - ObserverCall::Put { - obj: crate::ROOT, - prop: "list".into(), - value: ObservedValue::Tagged(Value::Object(ObjType::List), list.clone()), - conflict: false, - }, - ObserverCall::Insert { - obj: list.clone(), - index: 0, - value: ObservedValue::Untagged(2.into()), - }, - ObserverCall::Insert { - obj: list, - index: 1, - value: ObservedValue::Untagged(1.into()), - }, - ]) - ); - } - - #[test] - fn test_insert_objects() { - let mut doc = crate::AutoCommit::new().with_actor(crate::ActorId::from("aa".as_bytes())); - - let list = doc.put_object(crate::ROOT, "list", ObjType::List).unwrap(); - - let map = doc.insert_object(&list, 0, ObjType::Map).unwrap(); - doc.put(&map, "key", "value").unwrap(); - - let mut obs = ObserverStub::new_text_v2(); - super::observe_current_state(doc.document(), &mut obs); - - assert_eq!( - Calls(obs.ops), - Calls(vec![ - ObserverCall::Put { - obj: crate::ROOT, - prop: "list".into(), - value: ObservedValue::Tagged(Value::Object(ObjType::List), list.clone()), - conflict: false, - }, - ObserverCall::Insert { - obj: list.clone(), - index: 0, - value: ObservedValue::Tagged(Value::Object(ObjType::Map), map.clone()), - }, - ObserverCall::Put { - obj: map, - prop: "key".into(), - value: ObservedValue::Untagged("value".into()), - conflict: false - }, - ]) - ); - } - - #[test] - fn test_insert_and_update() { - let mut doc = crate::AutoCommit::new(); - - let list = doc.put_object(crate::ROOT, "list", ObjType::List).unwrap(); - - doc.insert(&list, 0, "one").unwrap(); - doc.insert(&list, 1, "two").unwrap(); - doc.put(&list, 0, "three").unwrap(); - doc.put(&list, 1, "four").unwrap(); - - let mut obs = ObserverStub::new_text_v2(); - super::observe_current_state(doc.document(), &mut obs); - - assert_eq!( - Calls(obs.ops), - Calls(vec![ - ObserverCall::Put { - obj: crate::ROOT, - prop: "list".into(), - value: ObservedValue::Tagged(Value::Object(ObjType::List), list.clone()), - conflict: false, - }, - ObserverCall::Insert { - obj: list.clone(), - index: 0, - value: ObservedValue::Untagged("three".into()), - }, - ObserverCall::Insert { - obj: list.clone(), - index: 1, - value: ObservedValue::Untagged("four".into()), - }, - ]) - ); - } - - #[test] - fn test_load_changes() { - fn fixture(name: &str) -> Vec { - fs::read("./tests/fixtures/".to_owned() + name).unwrap() - } - - let mut obs = ObserverStub::new(); - let _doc = Automerge::load_with( - &fixture("counter_value_is_ok.automerge"), - crate::OnPartialLoad::Error, - crate::storage::VerificationMode::Check, - Some(&mut obs), - ); - - assert_eq!( - Calls(obs.ops), - Calls(vec![ObserverCall::Put { - obj: crate::ROOT, - prop: "a".into(), - value: ObservedValue::Untagged(crate::ScalarValue::Counter(2000.into()).into()), - conflict: false, - },]) - ); - } -} diff --git a/rust/automerge/src/automerge/tests.rs b/rust/automerge/src/automerge/tests.rs index 3511c4ed..8d533fed 100644 --- a/rust/automerge/src/automerge/tests.rs +++ b/rust/automerge/src/automerge/tests.rs @@ -1507,11 +1507,6 @@ fn observe_counter_change_application() { let changes = doc.get_changes(&[]).unwrap().into_iter().cloned(); let mut new_doc = AutoCommit::new().with_observer(VecOpObserver::default()); - // make a new change to the doc to stop the empty doc logic from skipping the intermediate - // patches. The is probably not really necessary, we could update this test to just test that - // the correct final state is emitted. For now though, we leave it as is. - new_doc.put(ROOT, "foo", "bar").unwrap(); - new_doc.observer().take_patches(); new_doc.apply_changes(changes).unwrap(); assert_eq!( new_doc.observer().take_patches(), diff --git a/rust/automerge/src/change.rs b/rust/automerge/src/change.rs index be467a84..b5cae7df 100644 --- a/rust/automerge/src/change.rs +++ b/rust/automerge/src/change.rs @@ -278,7 +278,7 @@ impl From<&Change> for crate::ExpandedChange { let operations = c .iter_ops() .map(|o| crate::legacy::Op { - action: crate::types::OpType::from_action_and_value(o.action, o.val), + action: crate::types::OpType::from_index_and_value(o.action, o.val).unwrap(), insert: o.insert, key: match o.key { StoredKey::Elem(e) if e.is_head() => { diff --git a/rust/automerge/src/change_graph.rs b/rust/automerge/src/change_graph.rs deleted file mode 100644 index 01d269d8..00000000 --- a/rust/automerge/src/change_graph.rs +++ /dev/null @@ -1,344 +0,0 @@ -use std::collections::{BTreeMap, BTreeSet}; - -use crate::{ - clock::{Clock, ClockData}, - Change, ChangeHash, -}; - -/// The graph of changes -/// -/// This is a sort of adjacency list based representation, except that instead of using linked -/// lists, we keep all the edges and nodes in two vecs and reference them by index which plays nice -/// with the cache -#[derive(Debug, Clone)] -pub(crate) struct ChangeGraph { - nodes: Vec, - edges: Vec, - hashes: Vec, - nodes_by_hash: BTreeMap, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -struct NodeIdx(u32); - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -struct EdgeIdx(u32); - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -struct HashIdx(u32); - -#[derive(Debug, Clone)] -struct Edge { - // Edges are always child -> parent so we only store the target, the child is implicit - // as you get the edge from the child - target: NodeIdx, - next: Option, -} - -#[derive(Debug, Clone)] -struct ChangeNode { - hash_idx: HashIdx, - actor_index: usize, - seq: u64, - max_op: u64, - parents: Option, -} - -impl ChangeGraph { - pub(crate) fn new() -> Self { - Self { - nodes: Vec::new(), - edges: Vec::new(), - nodes_by_hash: BTreeMap::new(), - hashes: Vec::new(), - } - } - - pub(crate) fn add_change( - &mut self, - change: &Change, - actor_idx: usize, - ) -> Result<(), MissingDep> { - let hash = change.hash(); - if self.nodes_by_hash.contains_key(&hash) { - return Ok(()); - } - let parent_indices = change - .deps() - .iter() - .map(|h| self.nodes_by_hash.get(h).copied().ok_or(MissingDep(*h))) - .collect::, _>>()?; - let node_idx = self.add_node(actor_idx, change); - self.nodes_by_hash.insert(hash, node_idx); - for parent_idx in parent_indices { - self.add_parent(node_idx, parent_idx); - } - Ok(()) - } - - fn add_node(&mut self, actor_index: usize, change: &Change) -> NodeIdx { - let idx = NodeIdx(self.nodes.len() as u32); - let hash_idx = self.add_hash(change.hash()); - self.nodes.push(ChangeNode { - hash_idx, - actor_index, - seq: change.seq(), - max_op: change.max_op(), - parents: None, - }); - idx - } - - fn add_hash(&mut self, hash: ChangeHash) -> HashIdx { - let idx = HashIdx(self.hashes.len() as u32); - self.hashes.push(hash); - idx - } - - fn add_parent(&mut self, child_idx: NodeIdx, parent_idx: NodeIdx) { - let new_edge_idx = EdgeIdx(self.edges.len() as u32); - let new_edge = Edge { - target: parent_idx, - next: None, - }; - self.edges.push(new_edge); - - let child = &mut self.nodes[child_idx.0 as usize]; - if let Some(edge_idx) = child.parents { - let mut edge = &mut self.edges[edge_idx.0 as usize]; - while let Some(next) = edge.next { - edge = &mut self.edges[next.0 as usize]; - } - edge.next = Some(new_edge_idx); - } else { - child.parents = Some(new_edge_idx); - } - } - - fn parents(&self, node_idx: NodeIdx) -> impl Iterator + '_ { - let mut edge_idx = self.nodes[node_idx.0 as usize].parents; - std::iter::from_fn(move || { - let this_edge_idx = edge_idx?; - let edge = &self.edges[this_edge_idx.0 as usize]; - edge_idx = edge.next; - Some(edge.target) - }) - } - - pub(crate) fn clock_for_heads(&self, heads: &[ChangeHash]) -> Clock { - let mut clock = Clock::new(); - - self.traverse_ancestors(heads, |node, _hash| { - clock.include( - node.actor_index, - ClockData { - max_op: node.max_op, - seq: node.seq, - }, - ); - }); - - clock - } - - pub(crate) fn remove_ancestors( - &self, - changes: &mut BTreeSet, - heads: &[ChangeHash], - ) { - self.traverse_ancestors(heads, |_node, hash| { - changes.remove(hash); - }); - } - - /// Call `f` for each (node, hash) in the graph, starting from the given heads - /// - /// No guarantees are made about the order of traversal but each node will only be visited - /// once. - fn traverse_ancestors( - &self, - heads: &[ChangeHash], - mut f: F, - ) { - let mut to_visit = heads - .iter() - .filter_map(|h| self.nodes_by_hash.get(h)) - .copied() - .collect::>(); - - let mut visited = BTreeSet::new(); - - while let Some(idx) = to_visit.pop() { - if visited.contains(&idx) { - continue; - } else { - visited.insert(idx); - } - let node = &self.nodes[idx.0 as usize]; - let hash = &self.hashes[node.hash_idx.0 as usize]; - f(node, hash); - to_visit.extend(self.parents(idx)); - } - } -} - -#[derive(Debug, thiserror::Error)] -#[error("attempted to derive a clock for a change with dependencies we don't have")] -pub struct MissingDep(ChangeHash); - -#[cfg(test)] -mod tests { - use std::{ - num::NonZeroU64, - time::{SystemTime, UNIX_EPOCH}, - }; - - use crate::{ - clock::ClockData, - op_tree::OpSetMetadata, - storage::{change::ChangeBuilder, convert::op_as_actor_id}, - types::{Key, ObjId, Op, OpId, OpIds}, - ActorId, - }; - - use super::*; - - #[test] - fn clock_by_heads() { - let mut builder = TestGraphBuilder::new(); - let actor1 = builder.actor(); - let actor2 = builder.actor(); - let actor3 = builder.actor(); - let change1 = builder.change(&actor1, 10, &[]); - let change2 = builder.change(&actor2, 20, &[change1]); - let change3 = builder.change(&actor3, 30, &[change1]); - let change4 = builder.change(&actor1, 10, &[change2, change3]); - let graph = builder.build(); - - let mut expected_clock = Clock::new(); - expected_clock.include(builder.index(&actor1), ClockData { max_op: 50, seq: 2 }); - expected_clock.include(builder.index(&actor2), ClockData { max_op: 30, seq: 1 }); - expected_clock.include(builder.index(&actor3), ClockData { max_op: 40, seq: 1 }); - - let clock = graph.clock_for_heads(&[change4]); - assert_eq!(clock, expected_clock); - } - - #[test] - fn remove_ancestors() { - let mut builder = TestGraphBuilder::new(); - let actor1 = builder.actor(); - let actor2 = builder.actor(); - let actor3 = builder.actor(); - let change1 = builder.change(&actor1, 10, &[]); - let change2 = builder.change(&actor2, 20, &[change1]); - let change3 = builder.change(&actor3, 30, &[change1]); - let change4 = builder.change(&actor1, 10, &[change2, change3]); - let graph = builder.build(); - - let mut changes = vec![change1, change2, change3, change4] - .into_iter() - .collect::>(); - let heads = vec![change2]; - graph.remove_ancestors(&mut changes, &heads); - - let expected_changes = vec![change3, change4].into_iter().collect::>(); - - assert_eq!(changes, expected_changes); - } - - struct TestGraphBuilder { - actors: Vec, - changes: Vec, - seqs_by_actor: BTreeMap, - } - - impl TestGraphBuilder { - fn new() -> Self { - TestGraphBuilder { - actors: Vec::new(), - changes: Vec::new(), - seqs_by_actor: BTreeMap::new(), - } - } - - fn actor(&mut self) -> ActorId { - let actor = ActorId::random(); - self.actors.push(actor.clone()); - actor - } - - fn index(&self, actor: &ActorId) -> usize { - self.actors.iter().position(|a| a == actor).unwrap() - } - - /// Create a change with `num_new_ops` and `parents` for `actor` - /// - /// The `start_op` and `seq` of the change will be computed from the - /// previous changes for the same actor. - fn change( - &mut self, - actor: &ActorId, - num_new_ops: usize, - parents: &[ChangeHash], - ) -> ChangeHash { - let mut meta = OpSetMetadata::from_actors(self.actors.clone()); - let key = meta.props.cache("key".to_string()); - - let start_op = parents - .iter() - .map(|c| { - self.changes - .iter() - .find(|change| change.hash() == *c) - .unwrap() - .max_op() - }) - .max() - .unwrap_or(0) - + 1; - - let actor_idx = self.index(actor); - let ops = (0..num_new_ops) - .map(|opnum| Op { - id: OpId::new(start_op + opnum as u64, actor_idx), - action: crate::OpType::Put("value".into()), - key: Key::Map(key), - succ: OpIds::empty(), - pred: OpIds::empty(), - insert: false, - }) - .collect::>(); - - let root = ObjId::root(); - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as i64; - let seq = self.seqs_by_actor.entry(actor.clone()).or_insert(1); - let change = Change::new( - ChangeBuilder::new() - .with_dependencies(parents.to_vec()) - .with_start_op(NonZeroU64::new(start_op).unwrap()) - .with_actor(actor.clone()) - .with_seq(*seq) - .with_timestamp(timestamp) - .build(ops.iter().map(|op| op_as_actor_id(&root, op, &meta))) - .unwrap(), - ); - *seq = seq.checked_add(1).unwrap(); - let hash = change.hash(); - self.changes.push(change); - hash - } - - fn build(&self) -> ChangeGraph { - let mut graph = ChangeGraph::new(); - for change in &self.changes { - let actor_idx = self.index(change.actor_id()); - graph.add_change(change, actor_idx).unwrap(); - } - graph - } - } -} diff --git a/rust/automerge/src/clock.rs b/rust/automerge/src/clock.rs index 64d00fcf..79125323 100644 --- a/rust/automerge/src/clock.rs +++ b/rust/automerge/src/clock.rs @@ -71,6 +71,12 @@ impl Clock { self.0.get(actor_index) } + pub(crate) fn merge(&mut self, other: &Self) { + for (actor, data) in &other.0 { + self.include(*actor, *data); + } + } + fn is_greater(&self, other: &Self) -> bool { let mut has_greater = false; diff --git a/rust/automerge/src/clocks.rs b/rust/automerge/src/clocks.rs new file mode 100644 index 00000000..60fc5c71 --- /dev/null +++ b/rust/automerge/src/clocks.rs @@ -0,0 +1,44 @@ +use crate::{ + clock::{Clock, ClockData}, + Change, ChangeHash, +}; +use std::collections::HashMap; + +pub(crate) struct Clocks(HashMap); + +#[derive(Debug, thiserror::Error)] +#[error("attempted to derive a clock for a change with dependencies we don't have")] +pub struct MissingDep(ChangeHash); + +impl Clocks { + pub(crate) fn new() -> Self { + Self(HashMap::new()) + } + + pub(crate) fn add_change( + &mut self, + change: &Change, + actor_index: usize, + ) -> Result<(), MissingDep> { + let mut clock = Clock::new(); + for hash in change.deps() { + let c = self.0.get(hash).ok_or(MissingDep(*hash))?; + clock.merge(c); + } + clock.include( + actor_index, + ClockData { + max_op: change.max_op(), + seq: change.seq(), + }, + ); + self.0.insert(change.hash(), clock); + Ok(()) + } +} + +impl From for HashMap { + fn from(c: Clocks) -> Self { + c.0 + } +} diff --git a/rust/automerge/src/columnar/column_range/opid.rs b/rust/automerge/src/columnar/column_range/opid.rs index d2cdce79..ae95d758 100644 --- a/rust/automerge/src/columnar/column_range/opid.rs +++ b/rust/automerge/src/columnar/column_range/opid.rs @@ -104,11 +104,11 @@ impl<'a> OpIdIter<'a> { .transpose() .map_err(|e| DecodeColumnError::decode_raw("counter", e))?; match (actor, counter) { - (Some(Some(a)), Some(Some(c))) => match u32::try_from(c) { - Ok(c) => Ok(Some(OpId::new(c as u64, a as usize))), + (Some(Some(a)), Some(Some(c))) => match c.try_into() { + Ok(c) => Ok(Some(OpId::new(c, a as usize))), Err(_) => Err(DecodeColumnError::invalid_value( "counter", - "negative or large value encountered", + "negative value encountered", )), }, (Some(None), _) => Err(DecodeColumnError::unexpected_null("actor")), diff --git a/rust/automerge/src/columnar/column_range/value.rs b/rust/automerge/src/columnar/column_range/value.rs index 03a5aa60..43f63437 100644 --- a/rust/automerge/src/columnar/column_range/value.rs +++ b/rust/automerge/src/columnar/column_range/value.rs @@ -4,15 +4,10 @@ use crate::{ columnar::{ encoding::{ leb128::{lebsize, ulebsize}, - raw, DecodeColumnError, DecodeError, RawBytes, RawDecoder, RawEncoder, RleDecoder, - RleEncoder, Sink, + raw, DecodeColumnError, RawBytes, RawDecoder, RawEncoder, RleDecoder, RleEncoder, Sink, }, SpliceError, }, - storage::parse::{ - leb128::{leb128_i64, leb128_u64}, - Input, ParseResult, - }, ScalarValue, }; @@ -222,8 +217,18 @@ impl<'a> Iterator for ValueIter<'a> { ValueType::Null => Some(Ok(ScalarValue::Null)), ValueType::True => Some(Ok(ScalarValue::Boolean(true))), ValueType::False => Some(Ok(ScalarValue::Boolean(false))), - ValueType::Uleb => self.parse_input(val_meta, leb128_u64), - ValueType::Leb => self.parse_input(val_meta, leb128_i64), + ValueType::Uleb => self.parse_raw(val_meta, |mut bytes| { + let val = leb128::read::unsigned(&mut bytes).map_err(|e| { + DecodeColumnError::invalid_value("value", e.to_string()) + })?; + Ok(ScalarValue::Uint(val)) + }), + ValueType::Leb => self.parse_raw(val_meta, |mut bytes| { + let val = leb128::read::signed(&mut bytes).map_err(|e| { + DecodeColumnError::invalid_value("value", e.to_string()) + })?; + Ok(ScalarValue::Int(val)) + }), ValueType::String => self.parse_raw(val_meta, |bytes| { let val = std::str::from_utf8(bytes) .map_err(|e| DecodeColumnError::invalid_value("value", e.to_string()))? @@ -245,11 +250,17 @@ impl<'a> Iterator for ValueIter<'a> { let val = f64::from_le_bytes(raw); Ok(ScalarValue::F64(val)) }), - ValueType::Counter => self.parse_input(val_meta, |input| { - leb128_i64(input).map(|(i, n)| (i, ScalarValue::Counter(n.into()))) + ValueType::Counter => self.parse_raw(val_meta, |mut bytes| { + let val = leb128::read::signed(&mut bytes).map_err(|e| { + DecodeColumnError::invalid_value("value", e.to_string()) + })?; + Ok(ScalarValue::Counter(val.into())) }), - ValueType::Timestamp => self.parse_input(val_meta, |input| { - leb128_i64(input).map(|(i, n)| (i, ScalarValue::Timestamp(n))) + ValueType::Timestamp => self.parse_raw(val_meta, |mut bytes| { + let val = leb128::read::signed(&mut bytes).map_err(|e| { + DecodeColumnError::invalid_value("value", e.to_string()) + })?; + Ok(ScalarValue::Timestamp(val)) }), ValueType::Unknown(code) => self.parse_raw(val_meta, |bytes| { Ok(ScalarValue::Unknown { @@ -273,8 +284,8 @@ impl<'a> Iterator for ValueIter<'a> { } impl<'a> ValueIter<'a> { - fn parse_raw<'b, R, F: Fn(&'b [u8]) -> Result>( - &'b mut self, + fn parse_raw Result>( + &mut self, meta: ValueMeta, f: F, ) -> Option> { @@ -287,24 +298,11 @@ impl<'a> ValueIter<'a> { } Ok(bytes) => bytes, }; - Some(f(raw)) - } - - fn parse_input<'b, R, F: Fn(Input<'b>) -> ParseResult<'b, R, DecodeError>>( - &'b mut self, - meta: ValueMeta, - f: F, - ) -> Option> - where - R: Into, - { - self.parse_raw(meta, |raw| match f(Input::new(raw)) { - Err(e) => Err(DecodeColumnError::invalid_value("value", e.to_string())), - Ok((i, _)) if !i.is_empty() => { - Err(DecodeColumnError::invalid_value("value", "extra bytes")) - } - Ok((_, v)) => Ok(v.into()), - }) + let val = match f(raw) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }; + Some(Ok(val)) } pub(crate) fn done(&self) -> bool { diff --git a/rust/automerge/src/columnar/encoding.rs b/rust/automerge/src/columnar/encoding.rs index c9435448..bbdb34a8 100644 --- a/rust/automerge/src/columnar/encoding.rs +++ b/rust/automerge/src/columnar/encoding.rs @@ -46,8 +46,6 @@ pub(crate) enum DecodeError { FromInt(#[from] std::num::TryFromIntError), #[error("bad leb128")] BadLeb(#[from] ::leb128::read::Error), - #[error(transparent)] - BadLeb128(#[from] crate::storage::parse::leb128::Error), #[error("attempted to allocate {attempted} which is larger than the maximum of {maximum}")] OverlargeAllocation { attempted: usize, maximum: usize }, #[error("invalid string encoding")] diff --git a/rust/automerge/src/columnar/encoding/col_error.rs b/rust/automerge/src/columnar/encoding/col_error.rs index 089556b6..c8d5c5c0 100644 --- a/rust/automerge/src/columnar/encoding/col_error.rs +++ b/rust/automerge/src/columnar/encoding/col_error.rs @@ -1,5 +1,5 @@ #[derive(Clone, Debug)] -pub struct DecodeColumnError { +pub(crate) struct DecodeColumnError { path: Path, error: DecodeColErrorKind, } diff --git a/rust/automerge/src/columnar/encoding/properties.rs b/rust/automerge/src/columnar/encoding/properties.rs index 30f1169d..a3bf1ed0 100644 --- a/rust/automerge/src/columnar/encoding/properties.rs +++ b/rust/automerge/src/columnar/encoding/properties.rs @@ -139,7 +139,7 @@ pub(crate) fn option_splice_scenario< } pub(crate) fn opid() -> impl Strategy + Clone { - (0..(u32::MAX as usize), 0..(u32::MAX as u64)).prop_map(|(actor, ctr)| OpId::new(ctr, actor)) + (0..(i64::MAX as usize), 0..(i64::MAX as u64)).prop_map(|(actor, ctr)| OpId::new(ctr, actor)) } pub(crate) fn elemid() -> impl Strategy + Clone { diff --git a/rust/automerge/src/error.rs b/rust/automerge/src/error.rs index 62a7b72f..0f024d86 100644 --- a/rust/automerge/src/error.rs +++ b/rust/automerge/src/error.rs @@ -1,4 +1,3 @@ -use crate::change::LoadError as LoadChangeError; use crate::storage::load::Error as LoadError; use crate::types::{ActorId, ScalarValue}; use crate::value::DataType; @@ -8,7 +7,7 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum AutomergeError { #[error(transparent)] - ChangeGraph(#[from] crate::change_graph::MissingDep), + Clocks(#[from] crate::clocks::MissingDep), #[error("failed to load compressed data: {0}")] Deflate(#[source] std::io::Error), #[error("duplicate seq {0} found for actor {1}")] @@ -19,8 +18,6 @@ pub enum AutomergeError { Fail, #[error("invalid actor ID `{0}`")] InvalidActorId(String), - #[error(transparent)] - InvalidChangeHashBytes(#[from] InvalidChangeHashSlice), #[error("invalid UTF-8 character at {0}")] InvalidCharacter(usize), #[error("invalid hash {0}")] @@ -42,14 +39,10 @@ pub enum AutomergeError { }, #[error(transparent)] Load(#[from] LoadError), - #[error(transparent)] - LoadChangeError(#[from] LoadChangeError), #[error("increment operations must be against a counter value")] MissingCounter, #[error("hash {0} does not correspond to a change in this document")] MissingHash(ChangeHash), - #[error("change's deps should already be in the document")] - MissingDeps, #[error("compressed chunk was not a change")] NonChangeCompressed, #[error("id was not an object id")] @@ -99,7 +92,7 @@ pub struct InvalidElementId(pub String); pub struct InvalidOpId(pub String); #[derive(Error, Debug)] -pub enum InvalidOpType { +pub(crate) enum InvalidOpType { #[error("unrecognized action index {0}")] UnknownAction(u64), #[error("non numeric argument for inc op")] diff --git a/rust/automerge/src/lib.rs b/rust/automerge/src/lib.rs index cbb535af..bafd8983 100644 --- a/rust/automerge/src/lib.rs +++ b/rust/automerge/src/lib.rs @@ -244,8 +244,8 @@ mod autocommit; mod automerge; mod autoserde; mod change; -mod change_graph; mod clock; +mod clocks; mod columnar; mod convert; mod error; @@ -264,6 +264,7 @@ mod op_tree; mod parents; mod query; mod read; +mod sequence_tree; mod storage; pub mod sync; pub mod transaction; @@ -273,7 +274,7 @@ mod values; #[cfg(feature = "optree-visualisation")] mod visualisation; -pub use crate::automerge::{Automerge, OnPartialLoad}; +pub use crate::automerge::Automerge; pub use autocommit::{AutoCommit, AutoCommitWithObs}; pub use autoserde::AutoSerde; pub use change::{Change, LoadError as LoadChangeError}; @@ -293,6 +294,8 @@ pub use op_observer::Patch; pub use op_observer::VecOpObserver; pub use parents::{Parent, Parents}; pub use read::ReadDoc; +#[doc(hidden)] +pub use sequence_tree::SequenceTree; pub use types::{ActorId, ChangeHash, ObjType, OpType, ParseChangeHashError, Prop, TextEncoding}; pub use value::{ScalarValue, Value}; pub use values::Values; diff --git a/rust/automerge/src/op_set.rs b/rust/automerge/src/op_set.rs index aab8ce74..5b50d2b0 100644 --- a/rust/automerge/src/op_set.rs +++ b/rust/automerge/src/op_set.rs @@ -5,7 +5,7 @@ use crate::op_tree::{self, OpTree}; use crate::parents::Parents; use crate::query::{self, OpIdVisSearch, TreeQuery}; use crate::types::{self, ActorId, Key, ListEncoding, ObjId, Op, OpId, OpIds, OpType, Prop}; -use crate::ObjType; +use crate::{ObjType, OpObserver}; use fxhash::FxBuildHasher; use std::borrow::Borrow; use std::cmp::Ordering; @@ -13,7 +13,7 @@ use std::collections::HashMap; use std::ops::RangeBounds; mod load; -pub(crate) use load::OpSetBuilder; +pub(crate) use load::{ObservedOpSetBuilder, OpSetBuilder}; pub(crate) type OpSet = OpSetInternal; @@ -32,6 +32,12 @@ impl OpSetInternal { OpSetBuilder::new() } + /// Create a builder which passes each operation to `observer`. This will be significantly + /// slower than `OpSetBuilder` + pub(crate) fn observed_builder(observer: &mut O) -> ObservedOpSetBuilder<'_, O> { + ObservedOpSetBuilder::new(observer) + } + pub(crate) fn new() -> Self { let mut trees: HashMap<_, _, _> = Default::default(); trees.insert(ObjId::root(), OpTree::new()); @@ -58,7 +64,7 @@ impl OpSetInternal { } pub(crate) fn iter(&self) -> Iter<'_> { - let mut objs: Vec<_> = self.trees.iter().map(|t| (t.0, t.1.objtype, t.1)).collect(); + let mut objs: Vec<_> = self.trees.iter().collect(); objs.sort_by(|a, b| self.m.lamport_cmp((a.0).0, (b.0).0)); Iter { opset: self, @@ -67,17 +73,6 @@ impl OpSetInternal { } } - /// Iterate over objects in the opset in causal order - pub(crate) fn iter_objs( - &self, - ) -> impl Iterator)> + '_ { - let mut objs: Vec<_> = self.trees.iter().map(|t| (t.0, t.1.objtype, t.1)).collect(); - objs.sort_by(|a, b| self.m.lamport_cmp((a.0).0, (b.0).0)); - IterObjs { - trees: objs.into_iter(), - } - } - pub(crate) fn parents(&self, obj: ObjId) -> Parents<'_> { Parents { obj, ops: self } } @@ -291,7 +286,7 @@ impl Default for OpSetInternal { } impl<'a> IntoIterator for &'a OpSetInternal { - type Item = (&'a ObjId, ObjType, &'a Op); + type Item = (&'a ObjId, &'a Op); type IntoIter = Iter<'a>; @@ -300,41 +295,27 @@ impl<'a> IntoIterator for &'a OpSetInternal { } } -pub(crate) struct IterObjs<'a> { - trees: std::vec::IntoIter<(&'a ObjId, ObjType, &'a op_tree::OpTree)>, -} - -impl<'a> Iterator for IterObjs<'a> { - type Item = (&'a ObjId, ObjType, op_tree::OpTreeIter<'a>); - - fn next(&mut self) -> Option { - self.trees - .next() - .map(|(id, typ, tree)| (id, typ, tree.iter())) - } -} - #[derive(Clone)] pub(crate) struct Iter<'a> { opset: &'a OpSet, - trees: std::vec::IntoIter<(&'a ObjId, ObjType, &'a op_tree::OpTree)>, - current: Option<(&'a ObjId, ObjType, op_tree::OpTreeIter<'a>)>, + trees: std::vec::IntoIter<(&'a ObjId, &'a op_tree::OpTree)>, + current: Option<(&'a ObjId, op_tree::OpTreeIter<'a>)>, } impl<'a> Iterator for Iter<'a> { - type Item = (&'a ObjId, ObjType, &'a Op); + type Item = (&'a ObjId, &'a Op); fn next(&mut self) -> Option { - if let Some((id, typ, tree)) = &mut self.current { + if let Some((id, tree)) = &mut self.current { if let Some(next) = tree.next() { - return Some((id, *typ, next)); + return Some((id, next)); } } loop { - self.current = self.trees.next().map(|o| (o.0, o.1, o.2.iter())); - if let Some((obj, typ, tree)) = &mut self.current { + self.current = self.trees.next().map(|o| (o.0, o.1.iter())); + if let Some((obj, tree)) = &mut self.current { if let Some(next) = tree.next() { - return Some((obj, *typ, next)); + return Some((obj, next)); } } else { return None; diff --git a/rust/automerge/src/op_set/load.rs b/rust/automerge/src/op_set/load.rs index e14f46b7..6cc64e79 100644 --- a/rust/automerge/src/op_set/load.rs +++ b/rust/automerge/src/op_set/load.rs @@ -6,7 +6,8 @@ use super::{OpSet, OpTree}; use crate::{ op_tree::OpTreeInternal, storage::load::{DocObserver, LoadedObject}, - types::ObjId, + types::{ObjId, Op}, + Automerge, OpObserver, }; /// An opset builder which creates an optree for each object as it finishes loading, inserting the @@ -50,3 +51,38 @@ impl DocObserver for OpSetBuilder { } } } + +/// A DocObserver which just accumulates ops until the document has finished reconstructing and +/// then inserts all of the ops using `OpSet::insert_op_with_observer` +pub(crate) struct ObservedOpSetBuilder<'a, O: OpObserver> { + observer: &'a mut O, + ops: Vec<(ObjId, Op)>, +} + +impl<'a, O: OpObserver> ObservedOpSetBuilder<'a, O> { + pub(crate) fn new(observer: &'a mut O) -> Self { + Self { + observer, + ops: Vec::new(), + } + } +} + +impl<'a, O: OpObserver> DocObserver for ObservedOpSetBuilder<'a, O> { + type Output = OpSet; + + fn object_loaded(&mut self, object: LoadedObject) { + self.ops.reserve(object.ops.len()); + for op in object.ops { + self.ops.push((object.id, op)); + } + } + + fn finish(self, _metadata: super::OpSetMetadata) -> Self::Output { + let mut opset = Automerge::new(); + for (obj, op) in self.ops { + opset.insert_op_with_observer(&obj, op, self.observer); + } + opset.ops + } +} diff --git a/rust/automerge/src/op_tree/node.rs b/rust/automerge/src/op_tree/node.rs index ed1b7646..ea7fbf48 100644 --- a/rust/automerge/src/op_tree/node.rs +++ b/rust/automerge/src/op_tree/node.rs @@ -27,67 +27,50 @@ impl OpTreeNode { } } - fn search_element<'a, 'b: 'a, Q>( - &'b self, - query: &mut Q, - m: &OpSetMetadata, - ops: &'a [Op], - index: usize, - ) -> bool - where - Q: TreeQuery<'a>, - { - if let Some(e) = self.elements.get(index) { - if query.query_element_with_metadata(&ops[*e], m) == QueryResult::Finish { - return true; - } - } - false - } - pub(crate) fn search<'a, 'b: 'a, Q>( &'b self, query: &mut Q, m: &OpSetMetadata, ops: &'a [Op], - mut skip: Option, + skip: Option, ) -> bool where Q: TreeQuery<'a>, { if self.is_leaf() { - for e in self.elements.iter().skip(skip.unwrap_or(0)) { + let skip = skip.unwrap_or(0); + for e in self.elements.iter().skip(skip) { if query.query_element_with_metadata(&ops[*e], m) == QueryResult::Finish { return true; } } false } else { + let mut skip = skip.unwrap_or(0); for (child_index, child) in self.children.iter().enumerate() { - match skip { - Some(n) if n > child.len() => { - skip = Some(n - child.len() - 1); + match skip.cmp(&child.len()) { + Ordering::Greater => { + // not in this child at all + // take off the number of elements in the child as well as the next element + skip -= child.len() + 1; } - Some(n) if n == child.len() => { - skip = Some(0); // important to not be None so we never call query_node again - if self.search_element(query, m, ops, child_index) { - return true; + Ordering::Equal => { + // just try the element + skip -= child.len(); + if let Some(e) = self.elements.get(child_index) { + if query.query_element_with_metadata(&ops[*e], m) == QueryResult::Finish + { + return true; + } } } - Some(n) => { - if child.search(query, m, ops, Some(n)) { - return true; - } - skip = Some(0); // important to not be None so we never call query_node again - if self.search_element(query, m, ops, child_index) { - return true; - } - } - None => { + Ordering::Less => { // descend and try find it match query.query_node_with_metadata(child, m, ops) { QueryResult::Descend => { - if child.search(query, m, ops, None) { + // search in the child node, passing in the number of items left to + // skip + if child.search(query, m, ops, Some(skip)) { return true; } } @@ -95,9 +78,14 @@ impl OpTreeNode { QueryResult::Next => (), QueryResult::Skip(_) => panic!("had skip from non-root node"), } - if self.search_element(query, m, ops, child_index) { - return true; + if let Some(e) = self.elements.get(child_index) { + if query.query_element_with_metadata(&ops[*e], m) == QueryResult::Finish + { + return true; + } } + // reset the skip to zero so we continue iterating normally + skip = 0; } } } diff --git a/rust/automerge/src/query/prop.rs b/rust/automerge/src/query/prop.rs index d2a11361..f6062ec6 100644 --- a/rust/automerge/src/query/prop.rs +++ b/rust/automerge/src/query/prop.rs @@ -1,6 +1,6 @@ use crate::op_tree::{OpSetMetadata, OpTreeNode}; use crate::query::{binary_search_by, QueryResult, TreeQuery}; -use crate::types::{Key, Op}; +use crate::types::{Key, ListEncoding, Op}; use std::fmt::Debug; #[derive(Debug, Clone, PartialEq)] @@ -9,6 +9,15 @@ pub(crate) struct Prop<'a> { pub(crate) ops: Vec<&'a Op>, pub(crate) ops_pos: Vec, pub(crate) pos: usize, + start: Option, +} + +#[derive(Debug, Clone, PartialEq)] +struct Start { + /// The index to start searching for in the optree + idx: usize, + /// The total length of the optree + optree_len: usize, } impl<'a> Prop<'a> { @@ -18,6 +27,7 @@ impl<'a> Prop<'a> { ops: vec![], ops_pos: vec![], pos: 0, + start: None, } } } @@ -29,9 +39,38 @@ impl<'a> TreeQuery<'a> for Prop<'a> { m: &OpSetMetadata, ops: &[Op], ) -> QueryResult { - let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.key)); - self.pos = start; - QueryResult::Skip(start) + if let Some(Start { + idx: start, + optree_len, + }) = self.start + { + if self.pos + child.len() >= start { + // skip empty nodes + if child.index.visible_len(ListEncoding::default()) == 0 { + if self.pos + child.len() >= optree_len { + self.pos = optree_len; + QueryResult::Finish + } else { + self.pos += child.len(); + QueryResult::Next + } + } else { + QueryResult::Descend + } + } else { + self.pos += child.len(); + QueryResult::Next + } + } else { + // in the root node find the first op position for the key + let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.key)); + self.start = Some(Start { + idx: start, + optree_len: child.len(), + }); + self.pos = start; + QueryResult::Skip(start) + } } fn query_element(&mut self, op: &'a Op) -> QueryResult { diff --git a/rust/automerge/src/query/seek_op.rs b/rust/automerge/src/query/seek_op.rs index 2ed875d2..22d1f58d 100644 --- a/rust/automerge/src/query/seek_op.rs +++ b/rust/automerge/src/query/seek_op.rs @@ -1,6 +1,6 @@ use crate::op_tree::{OpSetMetadata, OpTreeNode}; use crate::query::{binary_search_by, QueryResult, TreeQuery}; -use crate::types::{Key, Op, HEAD}; +use crate::types::{Key, ListEncoding, Op, HEAD}; use std::cmp::Ordering; use std::fmt::Debug; @@ -14,6 +14,8 @@ pub(crate) struct SeekOp<'a> { pub(crate) succ: Vec, /// whether a position has been found found: bool, + /// The found start position of the key if there is one yet (for map objects). + start: Option, } impl<'a> SeekOp<'a> { @@ -23,6 +25,7 @@ impl<'a> SeekOp<'a> { succ: vec![], pos: 0, found: false, + start: None, } } @@ -69,9 +72,37 @@ impl<'a> TreeQuery<'a> for SeekOp<'a> { } } Key::Map(_) => { - let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.op.key)); - self.pos = start; - QueryResult::Skip(start) + if let Some(start) = self.start { + if self.pos + child.len() >= start { + // skip empty nodes + if child.index.visible_len(ListEncoding::List) == 0 { + let child_contains_key = + child.elements.iter().any(|e| ops[*e].key == self.op.key); + if !child_contains_key { + // If we are in a node which has no visible ops, but none of the + // elements of the node match the key of the op, then we must have + // finished processing and so we can just return. + // See https://github.com/automerge/automerge-rs/pull/480 + QueryResult::Finish + } else { + // Otherwise, we need to proceed to the next node + self.pos += child.len(); + QueryResult::Next + } + } else { + QueryResult::Descend + } + } else { + self.pos += child.len(); + QueryResult::Next + } + } else { + // in the root node find the first op position for the key + let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.op.key)); + self.start = Some(start); + self.pos = start; + QueryResult::Skip(start) + } } } } diff --git a/rust/automerge/src/query/seek_op_with_patch.rs b/rust/automerge/src/query/seek_op_with_patch.rs index cd30f5bb..7cacb032 100644 --- a/rust/automerge/src/query/seek_op_with_patch.rs +++ b/rust/automerge/src/query/seek_op_with_patch.rs @@ -16,6 +16,8 @@ pub(crate) struct SeekOpWithPatch<'a> { last_seen: Option, pub(crate) values: Vec<&'a Op>, pub(crate) had_value_before: bool, + /// The found start position of the key if there is one yet (for map objects). + start: Option, } impl<'a> SeekOpWithPatch<'a> { @@ -31,6 +33,7 @@ impl<'a> SeekOpWithPatch<'a> { last_seen: None, values: vec![], had_value_before: false, + start: None, } } @@ -129,9 +132,38 @@ impl<'a> TreeQuery<'a> for SeekOpWithPatch<'a> { // Updating a map: operations appear in sorted order by key Key::Map(_) => { - let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.op.key)); - self.pos = start; - QueryResult::Skip(start) + if let Some(start) = self.start { + if self.pos + child.len() >= start { + // skip empty nodes + if child.index.visible_len(self.encoding) == 0 { + let child_contains_key = + child.elements.iter().any(|e| ops[*e].key == self.op.key); + if !child_contains_key { + // If we are in a node which has no visible ops, but none of the + // elements of the node match the key of the op, then we must have + // finished processing and so we can just return. + // See https://github.com/automerge/automerge-rs/pull/480 + QueryResult::Finish + } else { + self.pos += child.len(); + QueryResult::Next + } + } else { + QueryResult::Descend + } + } else { + self.pos += child.len(); + QueryResult::Next + } + } else { + // in the root node find the first op position for the key + // Search for the place where we need to insert the new operation. First find the + // first op with a key >= the key we're updating + let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.op.key)); + self.start = Some(start); + self.pos = start; + QueryResult::Skip(start) + } } } } diff --git a/rust/automerge-wasm/src/sequence_tree.rs b/rust/automerge/src/sequence_tree.rs similarity index 87% rename from rust/automerge-wasm/src/sequence_tree.rs rename to rust/automerge/src/sequence_tree.rs index 91b183a2..f95ceab3 100644 --- a/rust/automerge-wasm/src/sequence_tree.rs +++ b/rust/automerge/src/sequence_tree.rs @@ -5,10 +5,10 @@ use std::{ }; pub(crate) const B: usize = 16; -pub(crate) type SequenceTree = SequenceTreeInternal; +pub type SequenceTree = SequenceTreeInternal; #[derive(Clone, Debug)] -pub(crate) struct SequenceTreeInternal { +pub struct SequenceTreeInternal { root_node: Option>, } @@ -24,17 +24,22 @@ where T: Clone + Debug, { /// Construct a new, empty, sequence. - pub(crate) fn new() -> Self { + pub fn new() -> Self { Self { root_node: None } } /// Get the length of the sequence. - pub(crate) fn len(&self) -> usize { + pub fn len(&self) -> usize { self.root_node.as_ref().map_or(0, |n| n.len()) } + /// Check if the sequence is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + /// Create an iterator through the sequence. - pub(crate) fn iter(&self) -> Iter<'_, T> { + pub fn iter(&self) -> Iter<'_, T> { Iter { inner: self, index: 0, @@ -46,7 +51,7 @@ where /// # Panics /// /// Panics if `index > len`. - pub(crate) fn insert(&mut self, index: usize, element: T) { + pub fn insert(&mut self, index: usize, element: T) { let old_len = self.len(); if let Some(root) = self.root_node.as_mut() { #[cfg(debug_assertions)] @@ -89,22 +94,27 @@ where } /// Push the `element` onto the back of the sequence. - pub(crate) fn push(&mut self, element: T) { + pub fn push(&mut self, element: T) { let l = self.len(); self.insert(l, element) } /// Get the `element` at `index` in the sequence. - pub(crate) fn get(&self, index: usize) -> Option<&T> { + pub fn get(&self, index: usize) -> Option<&T> { self.root_node.as_ref().and_then(|n| n.get(index)) } + /// Get the `element` at `index` in the sequence. + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + self.root_node.as_mut().and_then(|n| n.get_mut(index)) + } + /// Removes the element at `index` from the sequence. /// /// # Panics /// /// Panics if `index` is out of bounds. - pub(crate) fn remove(&mut self, index: usize) -> T { + pub fn remove(&mut self, index: usize) -> T { if let Some(root) = self.root_node.as_mut() { #[cfg(debug_assertions)] let len = root.check(); @@ -125,6 +135,15 @@ where panic!("remove from empty tree") } } + + /// Update the `element` at `index` in the sequence, returning the old value. + /// + /// # Panics + /// + /// Panics if `index > len` + pub fn set(&mut self, index: usize, element: T) -> T { + self.root_node.as_mut().unwrap().set(index, element) + } } impl SequenceTreeNode @@ -413,6 +432,30 @@ where assert!(self.is_full()); } + pub(crate) fn set(&mut self, index: usize, element: T) -> T { + if self.is_leaf() { + let old_element = self.elements.get_mut(index).unwrap(); + mem::replace(old_element, element) + } else { + let mut cumulative_len = 0; + for (child_index, child) in self.children.iter_mut().enumerate() { + match (cumulative_len + child.len()).cmp(&index) { + Ordering::Less => { + cumulative_len += child.len() + 1; + } + Ordering::Equal => { + let old_element = self.elements.get_mut(child_index).unwrap(); + return mem::replace(old_element, element); + } + Ordering::Greater => { + return child.set(index - cumulative_len, element); + } + } + } + panic!("Invalid index to set: {} but len was {}", index, self.len()) + } + } + pub(crate) fn get(&self, index: usize) -> Option<&T> { if self.is_leaf() { return self.elements.get(index); @@ -432,6 +475,26 @@ where } None } + + pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut T> { + if self.is_leaf() { + return self.elements.get_mut(index); + } else { + let mut cumulative_len = 0; + for (child_index, child) in self.children.iter_mut().enumerate() { + match (cumulative_len + child.len()).cmp(&index) { + Ordering::Less => { + cumulative_len += child.len() + 1; + } + Ordering::Equal => return self.elements.get_mut(child_index), + Ordering::Greater => { + return child.get_mut(index - cumulative_len); + } + } + } + } + None + } } impl Default for SequenceTreeInternal diff --git a/rust/automerge/src/storage/change.rs b/rust/automerge/src/storage/change.rs index 61db0b00..ff3cc9ab 100644 --- a/rust/automerge/src/storage/change.rs +++ b/rust/automerge/src/storage/change.rs @@ -177,9 +177,6 @@ impl<'a> Change<'a, Unverified> { for op in self.iter_ops() { f(op?); } - if u32::try_from(u64::from(self.start_op)).is_err() { - return Err(ReadChangeOpError::CounterTooLarge); - } Ok(Change { bytes: self.bytes, header: self.header, diff --git a/rust/automerge/src/storage/change/change_op_columns.rs b/rust/automerge/src/storage/change/change_op_columns.rs index 86ec59c2..7c3a65ec 100644 --- a/rust/automerge/src/storage/change/change_op_columns.rs +++ b/rust/automerge/src/storage/change/change_op_columns.rs @@ -14,7 +14,6 @@ use crate::{ }, }, convert, - error::InvalidOpType, storage::{ change::AsChangeOp, columns::{ @@ -23,7 +22,6 @@ use crate::{ RawColumns, }, types::{ElemId, ObjId, OpId, ScalarValue}, - OpType, }; const OBJ_COL_ID: ColumnId = ColumnId::new(0); @@ -278,14 +276,7 @@ impl ChangeOpsColumns { #[derive(thiserror::Error, Debug)] #[error(transparent)] -pub enum ReadChangeOpError { - #[error(transparent)] - DecodeError(#[from] DecodeColumnError), - #[error(transparent)] - InvalidOpType(#[from] InvalidOpType), - #[error("counter too large")] - CounterTooLarge, -} +pub struct ReadChangeOpError(#[from] DecodeColumnError); #[derive(Clone)] pub(crate) struct ChangeOpsIter<'a> { @@ -317,11 +308,6 @@ impl<'a> ChangeOpsIter<'a> { let action = self.action.next_in_col("action")?; let val = self.val.next_in_col("value")?; let pred = self.pred.next_in_col("pred")?; - - // This check is necessary to ensure that OpType::from_action_and_value - // cannot panic later in the process. - OpType::validate_action_and_value(action, &val)?; - Ok(Some(ChangeOp { obj, key, @@ -472,14 +458,10 @@ mod tests { action in 0_u64..6, obj in opid(), insert in any::()) -> ChangeOp { - - let val = if action == 5 && !(value.is_int() || value.is_uint()) { - ScalarValue::Uint(0) - } else { value }; ChangeOp { obj: obj.into(), key, - val, + val: value, pred, action, insert, diff --git a/rust/automerge/src/storage/chunk.rs b/rust/automerge/src/storage/chunk.rs index d0048528..06e31973 100644 --- a/rust/automerge/src/storage/chunk.rs +++ b/rust/automerge/src/storage/chunk.rs @@ -286,7 +286,7 @@ impl Header { fn hash(typ: ChunkType, data: &[u8]) -> ChangeHash { let mut out = vec![u8::from(typ)]; leb128::write::unsigned(&mut out, data.len() as u64).unwrap(); - out.extend(data); + out.extend(data.as_ref()); let hash_result = Sha256::digest(out); let array: [u8; 32] = hash_result.into(); ChangeHash(array) diff --git a/rust/automerge/src/storage/parse.rs b/rust/automerge/src/storage/parse.rs index 6751afb4..64419fda 100644 --- a/rust/automerge/src/storage/parse.rs +++ b/rust/automerge/src/storage/parse.rs @@ -110,7 +110,7 @@ use crate::{ActorId, ChangeHash}; const HASH_SIZE: usize = 32; // 256 bits = 32 bytes #[allow(unused_imports)] -pub(crate) use self::leb128::{leb128_i64, leb128_u32, leb128_u64, nonzero_leb128_u64}; +pub(crate) use self::leb128::{leb128_i32, leb128_i64, leb128_u32, leb128_u64, nonzero_leb128_u64}; pub(crate) type ParseResult<'a, O, E> = Result<(Input<'a>, O), ParseError>; @@ -308,7 +308,6 @@ impl<'a> Input<'a> { } /// The bytes behind this input - including bytes which have been consumed - #[allow(clippy::misnamed_getters)] pub(crate) fn bytes(&self) -> &'a [u8] { self.original } diff --git a/rust/automerge/src/storage/parse/leb128.rs b/rust/automerge/src/storage/parse/leb128.rs index 9f5e72a2..800253c9 100644 --- a/rust/automerge/src/storage/parse/leb128.rs +++ b/rust/automerge/src/storage/parse/leb128.rs @@ -1,3 +1,4 @@ +use core::mem::size_of; use std::num::NonZeroU64; use super::{take1, Input, ParseError, ParseResult}; @@ -6,83 +7,44 @@ use super::{take1, Input, ParseError, ParseResult}; pub(crate) enum Error { #[error("leb128 was too large for the destination type")] Leb128TooLarge, - #[error("leb128 was improperly encoded")] - Leb128Overlong, #[error("leb128 was zero when it was expected to be nonzero")] UnexpectedZero, } -pub(crate) fn leb128_u64(input: Input<'_>) -> ParseResult<'_, u64, E> -where - E: From, -{ - let mut res = 0; - let mut shift = 0; - let mut input = input; +macro_rules! impl_leb { + ($parser_name: ident, $ty: ty) => { + #[allow(dead_code)] + pub(crate) fn $parser_name<'a, E>(input: Input<'a>) -> ParseResult<'a, $ty, E> + where + E: From, + { + let mut res = 0; + let mut shift = 0; - loop { - let (i, byte) = take1(input)?; - input = i; - res |= ((byte & 0x7F) as u64) << shift; - shift += 7; - - if (byte & 0x80) == 0 { - if shift > 64 && byte > 1 { - return Err(ParseError::Error(Error::Leb128TooLarge.into())); - } else if shift > 7 && byte == 0 { - return Err(ParseError::Error(Error::Leb128Overlong.into())); + let mut input = input; + let mut pos = 0; + loop { + let (i, byte) = take1(input)?; + input = i; + if (byte & 0x80) == 0 { + res |= (byte as $ty) << shift; + return Ok((input, res)); + } else if pos == leb128_size::<$ty>() - 1 { + return Err(ParseError::Error(Error::Leb128TooLarge.into())); + } else { + res |= ((byte & 0x7F) as $ty) << shift; + } + pos += 1; + shift += 7; } - return Ok((input, res)); - } else if shift > 64 { - return Err(ParseError::Error(Error::Leb128TooLarge.into())); } - } + }; } -pub(crate) fn leb128_i64(input: Input<'_>) -> ParseResult<'_, i64, E> -where - E: From, -{ - let mut res = 0; - let mut shift = 0; - - let mut input = input; - let mut prev = 0; - loop { - let (i, byte) = take1(input)?; - input = i; - res |= ((byte & 0x7F) as i64) << shift; - shift += 7; - - if (byte & 0x80) == 0 { - if shift > 64 && byte != 0 && byte != 0x7f { - // the 10th byte (if present) must contain only the sign-extended sign bit - return Err(ParseError::Error(Error::Leb128TooLarge.into())); - } else if shift > 7 - && ((byte == 0 && prev & 0x40 == 0) || (byte == 0x7f && prev & 0x40 > 0)) - { - // overlong if the sign bit of penultimate byte has been extended - return Err(ParseError::Error(Error::Leb128Overlong.into())); - } else if shift < 64 && byte & 0x40 > 0 { - // sign extend negative numbers - res |= -1 << shift; - } - return Ok((input, res)); - } else if shift > 64 { - return Err(ParseError::Error(Error::Leb128TooLarge.into())); - } - prev = byte; - } -} - -pub(crate) fn leb128_u32(input: Input<'_>) -> ParseResult<'_, u32, E> -where - E: From, -{ - let (i, num) = leb128_u64(input)?; - let result = u32::try_from(num).map_err(|_| ParseError::Error(Error::Leb128TooLarge.into()))?; - Ok((i, result)) -} +impl_leb!(leb128_u64, u64); +impl_leb!(leb128_u32, u32); +impl_leb!(leb128_i64, i64); +impl_leb!(leb128_i32, i32); /// Parse a LEB128 encoded u64 from the input, throwing an error if it is `0` pub(crate) fn nonzero_leb128_u64(input: Input<'_>) -> ParseResult<'_, NonZeroU64, E> @@ -95,27 +57,38 @@ where Ok((input, result)) } +/// Maximum LEB128-encoded size of an integer type +const fn leb128_size() -> usize { + let bits = size_of::() * 8; + (bits + 6) / 7 // equivalent to ceil(bits/7) w/o floats +} + #[cfg(test)] mod tests { use super::super::Needed; use super::*; - use std::num::NonZeroUsize; + use std::{convert::TryFrom, num::NonZeroUsize}; const NEED_ONE: Needed = Needed::Size(unsafe { NonZeroUsize::new_unchecked(1) }); #[test] - fn leb_128_u64() { + fn leb_128_unsigned() { let one = &[0b00000001_u8]; let one_two_nine = &[0b10000001, 0b00000001]; let one_and_more = &[0b00000001, 0b00000011]; let scenarios: Vec<(&'static [u8], ParseResult<'_, u64, Error>)> = vec![ (one, Ok((Input::with_position(one, 1), 1))), + (&[0b10000001_u8], Err(ParseError::Incomplete(NEED_ONE))), ( one_two_nine, Ok((Input::with_position(one_two_nine, 2), 129)), ), (one_and_more, Ok((Input::with_position(one_and_more, 1), 1))), + ( + &[129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129], + Err(ParseError::Error(Error::Leb128TooLarge)), + ), ]; for (index, (input, expected)) in scenarios.clone().into_iter().enumerate() { let result = leb128_u64(Input::new(input)); @@ -129,174 +102,17 @@ mod tests { } } - let error_cases: Vec<(&'static str, &'static [u8], ParseError<_>)> = vec![ - ( - "too many bytes", - &[129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129], - ParseError::Error(Error::Leb128TooLarge), - ), - ( - "too many bits", - &[129, 129, 129, 129, 129, 129, 129, 129, 129, 2], - ParseError::Error(Error::Leb128TooLarge), - ), - ( - "overlong encoding", - &[129, 0], - ParseError::Error(Error::Leb128Overlong), - ), - ("missing data", &[255], ParseError::Incomplete(NEED_ONE)), - ]; - error_cases.into_iter().for_each(|(desc, input, expected)| { - match leb128_u64::(Input::new(input)) { - Ok((_, x)) => panic!("leb128_u64 should fail with {}, got {}", desc, x), - Err(error) => { - if error != expected { - panic!("leb128_u64 should fail with {}, got {}", expected, error) - } - } + for (index, (input, expected)) in scenarios.into_iter().enumerate() { + let u32_expected = expected.map(|(i, e)| (i, u32::try_from(e).unwrap())); + let result = leb128_u32(Input::new(input)); + if result != u32_expected { + panic!( + "Scenario {} failed for u32: expected {:?} got {:?}", + index + 1, + u32_expected, + result + ); } - }); - - let success_cases: Vec<(&'static [u8], u64)> = vec![ - (&[0], 0), - (&[0x7f], 127), - (&[0x80, 0x01], 128), - (&[0xff, 0x7f], 16383), - ( - &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1], - u64::MAX, - ), - ]; - success_cases.into_iter().for_each(|(input, expected)| { - match leb128_u64::(Input::new(input)) { - Ok((_, x)) => { - if x != expected { - panic!("leb128_u64 should succeed with {}, got {}", expected, x) - } - } - Err(error) => panic!("leb128_u64 should succeed with {}, got {}", expected, error), - } - }); - } - - #[test] - fn leb_128_u32() { - let error_cases: Vec<(&'static str, &'static [u8], ParseError<_>)> = vec![ - ( - "too many bytes", - &[129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129], - ParseError::Error(Error::Leb128TooLarge), - ), - ( - "too many bits", - &[0xff, 0xff, 0xff, 0xff, 0x1f], - ParseError::Error(Error::Leb128TooLarge), - ), - ( - "overlong encoding", - &[129, 0], - ParseError::Error(Error::Leb128Overlong), - ), - ("missing data", &[0xaa], ParseError::Incomplete(NEED_ONE)), - ]; - error_cases.into_iter().for_each(|(desc, input, expected)| { - match leb128_u32::(Input::new(input)) { - Ok((_, x)) => panic!("leb128_u32 should fail with {}, got {}", desc, x), - Err(error) => { - if error != expected { - panic!("leb128_u32 should fail with {}, got {}", expected, error) - } - } - } - }); - - let success_cases: Vec<(&'static [u8], u32)> = vec![ - (&[0], 0), - (&[0x7f], 127), - (&[0x80, 0x01], 128), - (&[0xff, 0x7f], 16383), - (&[0xff, 0xff, 0xff, 0xff, 0x0f], u32::MAX), - ]; - success_cases.into_iter().for_each(|(input, expected)| { - match leb128_u32::(Input::new(input)) { - Ok((_, x)) => { - if x != expected { - panic!("leb128_u32 should succeed with {}, got {}", expected, x) - } - } - Err(error) => panic!("leb128_u64 should succeed with {}, got {}", expected, error), - } - }); - } - - #[test] - fn leb_128_i64() { - let error_cases: Vec<(&'static str, &'static [u8], ParseError<_>)> = vec![ - ( - "too many bytes", - &[129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129], - ParseError::Error(Error::Leb128TooLarge), - ), - ( - "too many positive bits", - &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], - ParseError::Error(Error::Leb128TooLarge), - ), - ( - "too many negative bits", - &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7e], - ParseError::Error(Error::Leb128TooLarge), - ), - ( - "overlong positive encoding", - &[0xbf, 0], - ParseError::Error(Error::Leb128Overlong), - ), - ( - "overlong negative encoding", - &[0x81, 0xff, 0x7f], - ParseError::Error(Error::Leb128Overlong), - ), - ("missing data", &[0x90], ParseError::Incomplete(NEED_ONE)), - ]; - error_cases.into_iter().for_each(|(desc, input, expected)| { - match leb128_i64::(Input::new(input)) { - Ok((_, x)) => panic!("leb128_i64 should fail with {}, got {}", desc, x), - Err(error) => { - if error != expected { - panic!("leb128_i64 should fail with {}, got {}", expected, error) - } - } - } - }); - - let success_cases: Vec<(&'static [u8], i64)> = vec![ - (&[0], 0), - (&[0x7f], -1), - (&[0x3f], 63), - (&[0x40], -64), - (&[0x80, 0x01], 128), - (&[0xff, 0x3f], 8191), - (&[0x80, 0x40], -8192), - ( - &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0], - i64::MAX, - ), - ( - &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f], - i64::MIN, - ), - ]; - success_cases.into_iter().for_each(|(input, expected)| { - match leb128_i64::(Input::new(input)) { - Ok((_, x)) => { - if x != expected { - panic!("leb128_i64 should succeed with {}, got {}", expected, x) - } - } - Err(error) => panic!("leb128_u64 should succeed with {}, got {}", expected, error), - } - }); + } } } diff --git a/rust/automerge/src/sync.rs b/rust/automerge/src/sync.rs index d6dc2580..5d71d989 100644 --- a/rust/automerge/src/sync.rs +++ b/rust/automerge/src/sync.rs @@ -524,7 +524,7 @@ impl Message { encode_many(&mut buf, self.changes.iter_mut(), |buf, change| { leb128::write::unsigned(buf, change.raw_bytes().len() as u64).unwrap(); - buf.extend::<&[u8]>(change.raw_bytes().as_ref()) + buf.extend(change.raw_bytes().as_ref()) }); buf @@ -887,51 +887,6 @@ mod tests { assert_eq!(doc2.get_heads(), all_heads); } - #[test] - fn should_handle_lots_of_branching_and_merging() { - let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("01234567").unwrap()); - let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("89abcdef").unwrap()); - let mut doc3 = crate::AutoCommit::new().with_actor(ActorId::try_from("fedcba98").unwrap()); - let mut s1 = State::new(); - let mut s2 = State::new(); - - doc1.put(crate::ROOT, "x", 0).unwrap(); - let change1 = doc1.get_last_local_change().unwrap().clone(); - - doc2.apply_changes([change1.clone()]).unwrap(); - doc3.apply_changes([change1]).unwrap(); - - doc3.put(crate::ROOT, "x", 1).unwrap(); - - //// - n1c1 <------ n1c2 <------ n1c3 <-- etc. <-- n1c20 <------ n1c21 - //// / \/ \/ \/ - //// / /\ /\ /\ - //// c0 <---- n2c1 <------ n2c2 <------ n2c3 <-- etc. <-- n2c20 <------ n2c21 - //// \ / - //// ---------------------------------------------- n3c1 <----- - for i in 1..20 { - doc1.put(crate::ROOT, "n1", i).unwrap(); - doc2.put(crate::ROOT, "n2", i).unwrap(); - let change1 = doc1.get_last_local_change().unwrap().clone(); - let change2 = doc2.get_last_local_change().unwrap().clone(); - doc1.apply_changes([change2.clone()]).unwrap(); - doc2.apply_changes([change1]).unwrap(); - } - - sync(&mut doc1, &mut doc2, &mut s1, &mut s2); - - //// Having n3's last change concurrent to the last sync heads forces us into the slower code path - let change3 = doc3.get_last_local_change().unwrap().clone(); - doc2.apply_changes([change3]).unwrap(); - - doc1.put(crate::ROOT, "n1", "final").unwrap(); - doc2.put(crate::ROOT, "n1", "final").unwrap(); - - sync(&mut doc1, &mut doc2, &mut s1, &mut s2); - - assert_eq!(doc1.get_heads(), doc2.get_heads()); - } - fn sync( a: &mut crate::AutoCommit, b: &mut crate::AutoCommit, diff --git a/rust/automerge/src/transaction/inner.rs b/rust/automerge/src/transaction/inner.rs index 0fe735d5..7e7db17d 100644 --- a/rust/automerge/src/transaction/inner.rs +++ b/rust/automerge/src/transaction/inner.rs @@ -1,5 +1,6 @@ use std::num::NonZeroU64; +use crate::automerge::Actor; use crate::exid::ExId; use crate::query::{self, OpIdSearch}; use crate::storage::Change as StoredChange; @@ -97,7 +98,7 @@ impl TransactionInner { } let num_ops = self.pending_ops(); - let change = self.export(&doc.ops().m); + let change = self.export(&doc.ops.m); let hash = change.hash(); #[cfg(not(debug_assertions))] tracing::trace!(commit=?hash, deps=?change.deps(), "committing transaction"); @@ -152,16 +153,20 @@ impl TransactionInner { // remove in reverse order so sets are removed before makes etc... for (obj, op) in self.operations.into_iter().rev() { for pred_id in &op.pred { - if let Some(p) = doc.ops().search(&obj, OpIdSearch::new(*pred_id)).index() { - doc.ops_mut().change_vis(&obj, p, |o| o.remove_succ(&op)); + if let Some(p) = doc.ops.search(&obj, OpIdSearch::new(*pred_id)).index() { + doc.ops.change_vis(&obj, p, |o| o.remove_succ(&op)); } } - if let Some(pos) = doc.ops().search(&obj, OpIdSearch::new(op.id)).index() { - doc.ops_mut().remove(&obj, pos); + if let Some(pos) = doc.ops.search(&obj, OpIdSearch::new(op.id)).index() { + doc.ops.remove(&obj, pos); } } - doc.rollback_last_actor(); + // remove the actor from the cache so that it doesn't end up in the saved document + if doc.states.get(&self.actor).is_none() && doc.ops.m.actors.len() > 0 { + let actor = doc.ops.m.actors.remove_last(); + doc.actor = Actor::Unused(actor); + } num } @@ -272,10 +277,10 @@ impl TransactionInner { obj: ObjId, succ_pos: &[usize], ) { - doc.ops_mut().add_succ(&obj, succ_pos, &op); + doc.ops.add_succ(&obj, succ_pos, &op); if !op.is_delete() { - doc.ops_mut().insert(pos, &obj, op.clone()); + doc.ops.insert(pos, &obj, op.clone()); } self.finalize_op(doc, op_observer, obj, prop, op); @@ -327,7 +332,7 @@ impl TransactionInner { let id = self.next_id(); let query = doc - .ops() + .ops .search(&obj, query::InsertNth::new(index, ListEncoding::List)); let key = query.key()?; @@ -341,7 +346,7 @@ impl TransactionInner { insert: true, }; - doc.ops_mut().insert(query.pos(), &obj, op.clone()); + doc.ops.insert(query.pos(), &obj, op.clone()); self.finalize_op(doc, op_observer, obj, Prop::Seq(index), op); @@ -375,8 +380,8 @@ impl TransactionInner { } let id = self.next_id(); - let prop_index = doc.ops_mut().m.props.cache(prop.clone()); - let query = doc.ops().search(&obj, query::Prop::new(prop_index)); + let prop_index = doc.ops.m.props.cache(prop.clone()); + let query = doc.ops.search(&obj, query::Prop::new(prop_index)); // no key present to delete if query.ops.is_empty() && action == OpType::Delete { @@ -393,7 +398,7 @@ impl TransactionInner { return Err(AutomergeError::MissingCounter); } - let pred = doc.ops().m.sorted_opids(query.ops.iter().map(|o| o.id)); + let pred = doc.ops.m.sorted_opids(query.ops.iter().map(|o| o.id)); let op = Op { id, @@ -420,11 +425,11 @@ impl TransactionInner { action: OpType, ) -> Result, AutomergeError> { let query = doc - .ops() + .ops .search(&obj, query::Nth::new(index, ListEncoding::List)); let id = self.next_id(); - let pred = doc.ops().m.sorted_opids(query.ops.iter().map(|o| o.id)); + let pred = doc.ops.m.sorted_opids(query.ops.iter().map(|o| o.id)); let key = query.key()?; if query.ops.len() == 1 && query.ops[0].is_noop(&action) { @@ -485,7 +490,7 @@ impl TransactionInner { index, del: 1, values: vec![], - splice_type: SpliceType::Text("", doc.text_encoding()), + splice_type: SpliceType::Text("", doc.text_encoding), }, )?; } else { @@ -546,7 +551,7 @@ impl TransactionInner { index, del, values, - splice_type: SpliceType::Text(text, doc.text_encoding()), + splice_type: SpliceType::Text(text, doc.text_encoding), }, ) } @@ -563,13 +568,13 @@ impl TransactionInner { splice_type, }: SpliceArgs<'_>, ) -> Result<(), AutomergeError> { - let ex_obj = doc.ops().id_to_exid(obj.0); + let ex_obj = doc.ops.id_to_exid(obj.0); let encoding = splice_type.encoding(); // delete `del` items - performing the query for each one let mut deleted = 0; while deleted < del { // TODO: could do this with a single custom query - let query = doc.ops().search(&obj, query::Nth::new(index, encoding)); + let query = doc.ops.search(&obj, query::Nth::new(index, encoding)); // if we delete in the middle of a multi-character // move cursor back to the beginning and expand the del width @@ -585,10 +590,9 @@ impl TransactionInner { break; }; - let op = self.next_delete(query.key()?, query.pred(doc.ops())); + let op = self.next_delete(query.key()?, query.pred(&doc.ops)); - let ops_pos = query.ops_pos; - doc.ops_mut().add_succ(&obj, &ops_pos, &op); + doc.ops.add_succ(&obj, &query.ops_pos, &op); self.operations.push((obj, op)); @@ -604,9 +608,7 @@ impl TransactionInner { // do the insert query for the first item and then // insert the remaining ops one after the other if !values.is_empty() { - let query = doc - .ops() - .search(&obj, query::InsertNth::new(index, encoding)); + let query = doc.ops.search(&obj, query::InsertNth::new(index, encoding)); let mut pos = query.pos(); let mut key = query.key()?; let mut cursor = index; @@ -615,7 +617,7 @@ impl TransactionInner { for v in &values { let op = self.next_insert(key, v.clone()); - doc.ops_mut().insert(pos, &obj, op.clone()); + doc.ops.insert(pos, &obj, op.clone()); width = op.width(encoding); cursor += width; @@ -625,7 +627,7 @@ impl TransactionInner { self.operations.push((obj, op)); } - doc.ops_mut().hint(&obj, cursor - width, pos - 1); + doc.ops.hint(&obj, cursor - width, pos - 1); // handle the observer if let Some(obs) = op_observer.as_mut() { @@ -637,7 +639,7 @@ impl TransactionInner { let start = self.operations.len() - values.len(); for (offset, v) in values.iter().enumerate() { let op = &self.operations[start + offset].1; - let value = (v.clone().into(), doc.ops().id_to_exid(op.id)); + let value = (v.clone().into(), doc.ops.id_to_exid(op.id)); obs.insert(doc, ex_obj.clone(), index + offset, value) } } @@ -658,19 +660,19 @@ impl TransactionInner { ) { // TODO - id_to_exid should be a noop if not used - change type to Into? if let Some(op_observer) = op_observer { - let ex_obj = doc.ops().id_to_exid(obj.0); + let ex_obj = doc.ops.id_to_exid(obj.0); if op.insert { - let obj_type = doc.ops().object_type(&obj); + let obj_type = doc.ops.object_type(&obj); assert!(obj_type.unwrap().is_sequence()); match (obj_type, prop) { (Some(ObjType::List), Prop::Seq(index)) => { - let value = (op.value(), doc.ops().id_to_exid(op.id)); + let value = (op.value(), doc.ops.id_to_exid(op.id)); op_observer.insert(doc, ex_obj, index, value) } (Some(ObjType::Text), Prop::Seq(index)) => { // FIXME if op_observer.text_as_seq() { - let value = (op.value(), doc.ops().id_to_exid(op.id)); + let value = (op.value(), doc.ops.id_to_exid(op.id)); op_observer.insert(doc, ex_obj, index, value) } else { op_observer.splice_text(doc, ex_obj, index, op.to_str()) @@ -681,9 +683,9 @@ impl TransactionInner { } else if op.is_delete() { op_observer.delete(doc, ex_obj, prop); } else if let Some(value) = op.get_increment_value() { - op_observer.increment(doc, ex_obj, prop, (value, doc.ops().id_to_exid(op.id))); + op_observer.increment(doc, ex_obj, prop, (value, doc.ops.id_to_exid(op.id))); } else { - let value = (op.value(), doc.ops().id_to_exid(op.id)); + let value = (op.value(), doc.ops.id_to_exid(op.id)); op_observer.put(doc, ex_obj, prop, value, false); } } diff --git a/rust/automerge/src/types.rs b/rust/automerge/src/types.rs index 468986ec..870569e9 100644 --- a/rust/automerge/src/types.rs +++ b/rust/automerge/src/types.rs @@ -216,35 +216,23 @@ impl OpType { } } - pub(crate) fn validate_action_and_value( - action: u64, - value: &ScalarValue, - ) -> Result<(), error::InvalidOpType> { - match action { - 0..=4 => Ok(()), + pub(crate) fn from_index_and_value( + index: u64, + value: ScalarValue, + ) -> Result { + match index { + 0 => Ok(Self::Make(ObjType::Map)), + 1 => Ok(Self::Put(value)), + 2 => Ok(Self::Make(ObjType::List)), + 3 => Ok(Self::Delete), + 4 => Ok(Self::Make(ObjType::Text)), 5 => match value { - ScalarValue::Int(_) | ScalarValue::Uint(_) => Ok(()), + ScalarValue::Int(i) => Ok(Self::Increment(i)), + ScalarValue::Uint(i) => Ok(Self::Increment(i as i64)), _ => Err(error::InvalidOpType::NonNumericInc), }, - 6 => Ok(()), - _ => Err(error::InvalidOpType::UnknownAction(action)), - } - } - - pub(crate) fn from_action_and_value(action: u64, value: ScalarValue) -> OpType { - match action { - 0 => Self::Make(ObjType::Map), - 1 => Self::Put(value), - 2 => Self::Make(ObjType::List), - 3 => Self::Delete, - 4 => Self::Make(ObjType::Text), - 5 => match value { - ScalarValue::Int(i) => Self::Increment(i), - ScalarValue::Uint(i) => Self::Increment(i as i64), - _ => unreachable!("validate_action_and_value returned NonNumericInc"), - }, - 6 => Self::Make(ObjType::Table), - _ => unreachable!("validate_action_and_value returned UnknownAction"), + 6 => Ok(Self::Make(ObjType::Table)), + other => Err(error::InvalidOpType::UnknownAction(other)), } } } @@ -439,17 +427,17 @@ pub(crate) struct OpId(u32, u32); impl OpId { pub(crate) fn new(counter: u64, actor: usize) -> Self { - Self(counter.try_into().unwrap(), actor.try_into().unwrap()) + Self(counter as u32, actor as u32) } #[inline] pub(crate) fn counter(&self) -> u64 { - self.0.into() + self.0 as u64 } #[inline] pub(crate) fn actor(&self) -> usize { - self.1.try_into().unwrap() + self.1 as usize } #[inline] diff --git a/rust/automerge/src/types/opids.rs b/rust/automerge/src/types/opids.rs index a81ccb36..eaeed471 100644 --- a/rust/automerge/src/types/opids.rs +++ b/rust/automerge/src/types/opids.rs @@ -129,7 +129,7 @@ mod tests { fn gen_opid(actors: Vec) -> impl Strategy { (0..actors.len()).prop_flat_map(|actor_idx| { - (Just(actor_idx), 0..(u32::MAX as u64)) + (Just(actor_idx), 0..u64::MAX) .prop_map(|(actor_idx, counter)| OpId::new(counter, actor_idx)) }) } diff --git a/rust/automerge/tests/fixtures/64bit_obj_id_change.automerge b/rust/automerge/tests/fixtures/64bit_obj_id_change.automerge deleted file mode 100644 index 700342a2..00000000 Binary files a/rust/automerge/tests/fixtures/64bit_obj_id_change.automerge and /dev/null differ diff --git a/rust/automerge/tests/fixtures/64bit_obj_id_doc.automerge b/rust/automerge/tests/fixtures/64bit_obj_id_doc.automerge deleted file mode 100644 index 6beb57fe..00000000 Binary files a/rust/automerge/tests/fixtures/64bit_obj_id_doc.automerge and /dev/null differ diff --git a/rust/automerge/tests/fixtures/counter_value_has_incorrect_meta.automerge b/rust/automerge/tests/fixtures/counter_value_has_incorrect_meta.automerge deleted file mode 100644 index 2290b446..00000000 Binary files a/rust/automerge/tests/fixtures/counter_value_has_incorrect_meta.automerge and /dev/null differ diff --git a/rust/automerge/tests/fixtures/counter_value_is_ok.automerge b/rust/automerge/tests/fixtures/counter_value_is_ok.automerge deleted file mode 100644 index fdc59896..00000000 Binary files a/rust/automerge/tests/fixtures/counter_value_is_ok.automerge and /dev/null differ diff --git a/rust/automerge/tests/fixtures/counter_value_is_overlong.automerge b/rust/automerge/tests/fixtures/counter_value_is_overlong.automerge deleted file mode 100644 index 831346f7..00000000 Binary files a/rust/automerge/tests/fixtures/counter_value_is_overlong.automerge and /dev/null differ diff --git a/rust/automerge/tests/fixtures/two_change_chunks.automerge b/rust/automerge/tests/fixtures/two_change_chunks.automerge deleted file mode 100644 index 1a84b363..00000000 Binary files a/rust/automerge/tests/fixtures/two_change_chunks.automerge and /dev/null differ diff --git a/rust/automerge/tests/fixtures/two_change_chunks_compressed.automerge b/rust/automerge/tests/fixtures/two_change_chunks_compressed.automerge deleted file mode 100644 index 9e3f305f..00000000 Binary files a/rust/automerge/tests/fixtures/two_change_chunks_compressed.automerge and /dev/null differ diff --git a/rust/automerge/tests/fixtures/two_change_chunks_out_of_order.automerge b/rust/automerge/tests/fixtures/two_change_chunks_out_of_order.automerge deleted file mode 100644 index 9ba0355f..00000000 Binary files a/rust/automerge/tests/fixtures/two_change_chunks_out_of_order.automerge and /dev/null differ diff --git a/rust/automerge/tests/fuzz-crashers/action-is-48.automerge b/rust/automerge/tests/fuzz-crashers/action-is-48.automerge deleted file mode 100644 index 16e6f719..00000000 Binary files a/rust/automerge/tests/fuzz-crashers/action-is-48.automerge and /dev/null differ diff --git a/rust/automerge/tests/fuzz-crashers/missing_deps.automerge b/rust/automerge/tests/fuzz-crashers/missing_deps.automerge deleted file mode 100644 index 8a57a0f4..00000000 Binary files a/rust/automerge/tests/fuzz-crashers/missing_deps.automerge and /dev/null differ diff --git a/rust/automerge/tests/fuzz-crashers/missing_deps_compressed.automerge b/rust/automerge/tests/fuzz-crashers/missing_deps_compressed.automerge deleted file mode 100644 index 2c7b123b..00000000 Binary files a/rust/automerge/tests/fuzz-crashers/missing_deps_compressed.automerge and /dev/null differ diff --git a/rust/automerge/tests/fuzz-crashers/missing_deps_subsequent.automerge b/rust/automerge/tests/fuzz-crashers/missing_deps_subsequent.automerge deleted file mode 100644 index 2fe439af..00000000 Binary files a/rust/automerge/tests/fuzz-crashers/missing_deps_subsequent.automerge and /dev/null differ diff --git a/rust/automerge/tests/test.rs b/rust/automerge/tests/test.rs index 3be6725e..ca6c64c0 100644 --- a/rust/automerge/tests/test.rs +++ b/rust/automerge/tests/test.rs @@ -1412,55 +1412,12 @@ fn fuzz_crashers() { } } -fn fixture(name: &str) -> Vec { - fs::read("./tests/fixtures/".to_owned() + name).unwrap() -} - -#[test] -fn overlong_leb() { - // the value metadata says "2", but the LEB is only 1-byte long and there's an extra 0 - assert!(Automerge::load(&fixture("counter_value_has_incorrect_meta.automerge")).is_err()); - // the LEB is overlong (using 2 bytes where one would have sufficed) - assert!(Automerge::load(&fixture("counter_value_is_overlong.automerge")).is_err()); - // the LEB is correct - assert!(Automerge::load(&fixture("counter_value_is_ok.automerge")).is_ok()); -} - -#[test] -fn load() { - fn check_fixture(name: &str) { - let doc = Automerge::load(&fixture(name)).unwrap(); - let map_id = doc.get(ROOT, "a").unwrap().unwrap().1; - assert_eq!(doc.get(map_id, "a").unwrap().unwrap().0, "b".into()); - } - - check_fixture("two_change_chunks.automerge"); - check_fixture("two_change_chunks_compressed.automerge"); - check_fixture("two_change_chunks_out_of_order.automerge"); -} - #[test] fn negative_64() { let mut doc = Automerge::new(); assert!(doc.transact(|d| { d.put(ROOT, "a", -64_i64) }).is_ok()) } -#[test] -fn obj_id_64bits() { - // this change has an opId of 2**42, which when cast to a 32-bit int gives 0. - // The file should either fail to load (a limit of ~4 billion ops per doc seems reasonable), or be handled correctly. - if let Ok(doc) = Automerge::load(&fixture("64bit_obj_id_change.automerge")) { - let map_id = doc.get(ROOT, "a").unwrap().unwrap().1; - assert!(map_id != ROOT) - } - - // this fixture is the same as the above, but as a document chunk. - if let Ok(doc) = Automerge::load(&fixture("64bit_obj_id_doc.automerge")) { - let map_id = doc.get(ROOT, "a").unwrap().unwrap().1; - assert!(map_id != ROOT) - } -} - #[test] fn bad_change_on_optree_node_boundary() { let mut doc = Automerge::new(); diff --git a/rust/deny.toml b/rust/deny.toml index 473cdae8..54a68a60 100644 --- a/rust/deny.toml +++ b/rust/deny.toml @@ -110,9 +110,6 @@ exceptions = [ # should be revied more fully before release { allow = ["MPL-2.0"], name = "cbindgen" }, { allow = ["BSD-3-Clause"], name = "instant" }, - - # we only use prettytable in tests - { allow = ["BSD-3-Clause"], name = "prettytable" }, ] # Some crates don't have (easily) machine readable licensing information, @@ -175,12 +172,6 @@ deny = [ ] # Certain crates/versions that will be skipped when doing duplicate detection. skip = [ - # duct, which we only depend on for integration tests in automerge-cli, - # pulls in a version of os_pipe which in turn pulls in a version of - # windows-sys which is different to the version in pulled in by is-terminal. - # This is fine to ignore for now because it doesn't end up in downstream - # dependencies. - { name = "windows-sys", version = "0.42.0" } ] # Similarly to `skip` allows you to skip certain crates during duplicate # detection. Unlike skip, it also includes the entire tree of transitive diff --git a/scripts/ci/cmake-build b/scripts/ci/cmake-build index 25a69756..f6f9f9b1 100755 --- a/scripts/ci/cmake-build +++ b/scripts/ci/cmake-build @@ -16,4 +16,4 @@ C_PROJECT=$THIS_SCRIPT/../../rust/automerge-c; mkdir -p $C_PROJECT/build; cd $C_PROJECT/build; cmake --log-level=ERROR -B . -S .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_SHARED_LIBS=$SHARED_TOGGLE; -cmake --build . --target automerge_test; +cmake --build . --target test_automerge;