diff --git a/aztec-nargo/compile_then_postprocess.sh b/aztec-nargo/compile_then_postprocess.sh index 53185f925a68..d4e78ec51300 100755 --- a/aztec-nargo/compile_then_postprocess.sh +++ b/aztec-nargo/compile_then_postprocess.sh @@ -40,7 +40,7 @@ for artifact in $artifacts_to_process; do echo "Generating verification key for function $fn_name" # BB outputs the verification key to stdout as raw bytes, however, we need to base64 encode it before storing it in the artifact - verification_key=$($BB write_vk_for_ivc -b ${fn_artifact_path} -o - | base64) + verification_key=$($BB write_vk --scheme client_ivc -b ${fn_artifact_path} -o - | base64) rm $fn_artifact_path jq ".functions[$fn_index].verification_key = \"$verification_key\"" $artifact > $artifact.tmp mv $artifact.tmp $artifact diff --git a/barretenberg/README.md b/barretenberg/README.md index ad6012e513d3..d7bc7366618c 100644 --- a/barretenberg/README.md +++ b/barretenberg/README.md @@ -30,7 +30,7 @@ Barretenberg (or `bb` for short) is an optimized elliptic curve library for the - [Testing locally in docker](#testing-locally-in-docker) - [Docs Build](#docs-build) - [Benchmarks](#benchmarks) - - [x86\_64](#x86_64) + - [x86_64](#x86_64) - [WASM](#wasm) - [How to run](#how-to-run) - [Debugging](#debugging) @@ -38,8 +38,7 @@ Barretenberg (or `bb` for short) is an optimized elliptic curve library for the - [Improving LLDB Debugging](#improving-lldb-debugging) - [Using Tracy to Profile Memory/CPU](#using-tracy-to-profile-memorycpu) -> [!CAUTION] -> **This code is highly experimental, use at your own risk!** +> [!CAUTION] > **This code is highly experimental, use at your own risk!** ## Installation @@ -66,14 +65,14 @@ All available `bb` commands: Prove the valid execution of your program: ```bash -bb prove_ultra_honk -b ./target/hello_world.json -w ./target/witness-name.gz -o ./target/proof +bb prove --scheme ultra_honk -b ./target/hello_world.json -w ./target/witness-name.gz -o ./target/proof ``` You can then compute the verification key for your Noir program and verify the proof: ```bash -bb write_vk_ultra_honk -b ./target/hello_world.json -o ./target/vk -bb verify_ultra_honk -k ./target/vk -p ./target/proof +bb write_vk --scheme ultra_honk -b ./target/hello_world.json -o ./target/vk +bb verify --scheme ultra_honk -k ./target/vk -p ./target/proof ``` @@ -83,7 +82,7 @@ If successful, the verification will complete in silence. The usage with MegaHonk is similar to the above UltraHonk. Refer to all the available `bb` commands, using the `bb _mega_honk` syntax. ->[!WARNING] +> [!WARNING] > MegaHonk generates insecure recursion circuits when Goblin recursive verifiers are not present. ### Solidity verifier @@ -93,21 +92,20 @@ Barretenberg can generate a smart contract that verifies proofs in Solidity (i.e First, prove the valid execution of your Noir program and export the verification key: ```bash -bb prove_ultra_keccak_honk -b ./target/hello_world.json -w ./target/witness-name.gz -o ./target/proof -bb write_vk_ultra_honk -b ./target/hello_world.json -o ./target/vk +bb prove --sceme ultra_honk -b ./target/hello_world.json -w ./target/witness-name.gz -o ./target/proof +bb write_vk --scheme ultra_honk -b ./target/hello_world.json -o ./target/vk ``` -> [!IMPORTANT] -> `prove_ultra_keccak_honk` is used to generate UltraHonk proofs with Keccak hashes, making them gas-efficient. `prove_ultra_honk` in comparison generates proofs with Poseidon hashes, more efficient in recursions but not on-chain verifications. +> [!IMPORTANT] > `prove --scheme ultra_honk --oracle_hash keccak` is used to generate UltraHonk proofs with Keccak hashes, making them gas-efficient. `prove --scheme ultra_honk` in comparison generates proofs with Poseidon hashes, more efficient in recursions but not on-chain verifications. You can now use the verification key to generate a Solidity verifier contract: ```bash -bb contract_ultra_honk -k ./target/vk -c $CRS_PATH -b ./target/hello_world.json -o ./target/Verifier.sol +bb write_contract --scheme ultra_honk -k ./target/vk -c $CRS_PATH -b ./target/hello_world.json -o ./target/Verifier.sol ``` ->[!CAUTION] -> Solidity verifier contracts are work-in-progress. Expect significant optimizations and breaking changes, and *do NOT use it in production!* +> [!CAUTION] +> Solidity verifier contracts are work-in-progress. Expect significant optimizations and breaking changes, and _do NOT use it in production!_ ## Development @@ -270,7 +268,7 @@ Alternatively you can build separate test binaries, e.g. honk_tests or numeric_t Code is formatted using `clang-format` and the `./cpp/format.sh` script which is called via a git pre-commit hook. ->[!TIP] +> [!TIP] > A default configuration for VS Code is provided by the file [`barretenberg.code-workspace`](barretenberg.code-workspace). These settings can be overridden by placing configuration files in `.vscode/`. > If you've installed the C++ Vscode extension, configure it to format on save! @@ -410,7 +408,7 @@ Usage instructions: Caveats: - This works best for code that is not overly generic, i.e. where just the sequence of function calls carries a lot of information. It is possible to tag extra data along with the stack trace, this can be done as a followup, please leave feedback if desired. -- There are certain functions like `assert_equals` that can cause gates that occur *before* them to fail. If this would be useful to automatically report, please leave feedback. +- There are certain functions like `assert_equals` that can cause gates that occur _before_ them to fail. If this would be useful to automatically report, please leave feedback. Example: diff --git a/barretenberg/acir_tests/bootstrap.sh b/barretenberg/acir_tests/bootstrap.sh index 984968a0978b..17fba9756c91 100755 --- a/barretenberg/acir_tests/bootstrap.sh +++ b/barretenberg/acir_tests/bootstrap.sh @@ -36,8 +36,9 @@ function build { echo "Regenerating verify_honk_proof and verify_rollup_honk_proof recursive inputs." local bb=$(realpath ../cpp/build/bin/bb) (cd ./acir_tests/assert_statement && \ - $bb write_recursion_inputs_ultra_honk -b ./target/program.json -o ../verify_honk_proof --recursive && \ - $bb write_recursion_inputs_rollup_honk -b ./target/program.json -o ../verify_rollup_honk_proof --recursive) + # TODO(https://github.com/AztecProtocol/barretenberg/issues/1253) Deprecate command and construct TOML (e.g., via yq or via conversion from a JSON) + $bb OLD_API write_recursion_inputs_ultra_honk -b ./target/program.json -o ../verify_honk_proof --recursive && \ + $bb OLD_API write_recursion_inputs_ultra_honk --ipa_accumulation -b ./target/program.json -o ../verify_rollup_honk_proof --recursive) cache_upload $tests_tar acir_tests fi @@ -69,9 +70,9 @@ function test_cmds { # Paths are all relative to the repository root. function test_cmds_internal { local plonk_tests=$(find ./acir_tests -maxdepth 1 -mindepth 1 -type d | \ - grep -vE 'verify_honk_proof|double_verify_honk_proof|verify_rollup_honk_proof') + grep -vE 'verify_honk_proof|double_verify_honk_proof|verify_rollup_honk_proof|fold') local honk_tests=$(find ./acir_tests -maxdepth 1 -mindepth 1 -type d | \ - grep -vE 'single_verify_proof|double_verify_proof|double_verify_nested_proof|verify_rollup_honk_proof') + grep -vE 'single_verify_proof|double_verify_proof|double_verify_nested_proof|verify_rollup_honk_proof|fold') local run_test=$(realpath --relative-to=$root ./run_test.sh) local run_test_browser=$(realpath --relative-to=$root ./run_test_browser.sh) @@ -99,25 +100,23 @@ function test_cmds_internal { # echo ecdsa_secp256r1_3x through bb.js on node to check 256k support. echo BIN=$bbjs_bin FLOW=prove_then_verify $run_test ecdsa_secp256r1_3x # echo the prove then verify flow for UltraHonk. This makes sure we have the same circuit for different witness inputs. - echo BIN=$bbjs_bin SYS=ultra_honk FLOW=prove_then_verify $run_test 6_array - # echo a single arbitrary test not involving recursion through bb.js for MegaHonk - echo BIN=$bbjs_bin SYS=mega_honk FLOW=prove_and_verify $run_test 6_array + echo BIN=$bbjs_bin SYS=ultra_honk_deprecated FLOW=prove_then_verify $run_test 6_array # echo 1_mul through bb.js build, all_cmds flow, to test all cli args. echo BIN=$bbjs_bin FLOW=all_cmds $run_test 1_mul # barretenberg-acir-tests-bb: - # Fold and verify an ACIR program stack using ClientIvc, recursively verify as part of the Tube circuit and produce and verify a Honk proof + # Fold and verify an ACIR program stack using ClientIVC, recursively verify as part of the Tube circuit and produce and verify a Honk proof echo FLOW=prove_then_verify_tube $run_test 6_array # echo 1_mul through native bb build, all_cmds flow, to test all cli args. - echo FLOW=all_cmds $run_test 1_mul + echo NATIVE=1 FLOW=all_cmds $run_test 1_mul # barretenberg-acir-tests-bb-ultra-plonk: # Exclude honk tests. for t in $plonk_tests; do - echo FLOW=prove_then_verify $run_test $(basename $t) + echo SYS=ultra_plonk_deprecated FLOW=prove_then_verify $run_test $(basename $t) done - echo FLOW=prove_then_verify RECURSIVE=true $run_test assert_statement - echo FLOW=prove_then_verify RECURSIVE=true $run_test double_verify_proof + echo SYS=ultra_plonk_deprecated FLOW=prove_then_verify RECURSIVE=true $run_test assert_statement + echo SYS=ultra_plonk_deprecated FLOW=prove_then_verify RECURSIVE=true $run_test double_verify_proof # barretenberg-acir-tests-bb-ultra-honk: # Exclude plonk tests. @@ -126,8 +125,8 @@ function test_cmds_internal { done echo SYS=ultra_honk FLOW=prove_then_verify RECURSIVE=true $run_test assert_statement echo SYS=ultra_honk FLOW=prove_then_verify RECURSIVE=true $run_test double_verify_honk_proof - echo SYS=ultra_honk FLOW=prove_and_verify_program $run_test merkle_insert - echo SYS=ultra_rollup_honk FLOW=prove_then_verify $run_test verify_rollup_honk_proof + echo SYS=ultra_honk FLOW=prove_then_verify HASH=keccak $run_test assert_statement + echo SYS=ultra_honk FLOW=prove_then_verify ROLLUP=true $run_test verify_rollup_honk_proof # barretenberg-acir-tests-bb-client-ivc: echo FLOW=prove_then_verify_client_ivc $run_test 6_array @@ -135,6 +134,7 @@ function test_cmds_internal { echo FLOW=prove_then_verify_client_ivc $run_test databus_two_calldata } +# TODO(https://github.com/AztecProtocol/barretenberg/issues/1254): More complete testing, including failure tests function bench { # TODO: Move to scripts dir along with run_test.sh. LOG_FILE=bench-acir.jsonl ./bench_acir_tests.sh diff --git a/barretenberg/acir_tests/flows/all_cmds.sh b/barretenberg/acir_tests/flows/all_cmds.sh index c912613302c6..7d1a9722d538 100755 --- a/barretenberg/acir_tests/flows/all_cmds.sh +++ b/barretenberg/acir_tests/flows/all_cmds.sh @@ -4,19 +4,23 @@ set -eu VFLAG=${VERBOSE:+-v} BFLAG="-b ./target/program.json" FLAGS="-c $CRS_PATH $VFLAG" +# the commands called here are subcommands of the OLD_API command in the native bb binary, +# but no such refactoring was done to the node binary. This is because the node binary is +# deprecated and UltraPlonk is also deprecated. +MAYBE_OLD_API=${NATIVE:+OLD_API} # Test we can perform the proof/verify flow. -$BIN gates $FLAGS $BFLAG > /dev/null -$BIN prove -o proof $FLAGS $BFLAG -$BIN write_vk -o vk $FLAGS $BFLAG -$BIN write_pk -o pk $FLAGS $BFLAG -$BIN verify -k vk -p proof $FLAGS +$BIN $MAYBE_OLD_API gates $FLAGS $BFLAG > /dev/null +$BIN $MAYBE_OLD_API prove -o proof $FLAGS $BFLAG +$BIN $MAYBE_OLD_API write_vk -o vk $FLAGS $BFLAG +$BIN $MAYBE_OLD_API write_pk -o pk $FLAGS $BFLAG +$BIN $MAYBE_OLD_API verify -k vk -p proof $FLAGS # Check supplemental functions. # Grep to determine success. -$BIN contract -k vk $BFLAG -o - | grep "Verification Key Hash" > /dev/null +$BIN $MAYBE_OLD_API contract -k vk $BFLAG -o - | grep "Verification Key Hash" > /dev/null # Use jq to determine success, and also check result not empty. -OUTPUT=$($BIN proof_as_fields -k vk -p proof -o - | jq .) +OUTPUT=$($BIN $MAYBE_OLD_API proof_as_fields -p proof -k vk -o - | jq .) [ -n "$OUTPUT" ] || exit 1 -OUTPUT=$($BIN vk_as_fields -k vk -o - | jq .) +OUTPUT=$($BIN $MAYBE_OLD_API vk_as_fields -k vk -o - | jq .) [ -n "$OUTPUT" ] || exit 1 diff --git a/barretenberg/acir_tests/flows/prove_and_verify.sh b/barretenberg/acir_tests/flows/prove_and_verify.sh index a3ff42313f71..1022f82a63a1 100755 --- a/barretenberg/acir_tests/flows/prove_and_verify.sh +++ b/barretenberg/acir_tests/flows/prove_and_verify.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # prove_and_verify produces no output, so is parallel safe. set -eu @@ -11,7 +11,11 @@ case ${SYS:-} in ;; "client_ivc") cmd=prove_and_verify - flags+=" --scheme client_ivc --input_type ${INPUT_TYPE:-compiletime_stack}" + flags+=" --scheme client_ivc ${INPUT_TYPE:---input_type compiletime_stack}" + ;; + "ultra_honk") + cmd=prove_and_verify + flags+=" --scheme ultra_honk ${INPUT_TYPE:---input_type compiletime_stack}" ;; *) cmd=prove_and_verify_$SYS diff --git a/barretenberg/acir_tests/flows/prove_then_verify.sh b/barretenberg/acir_tests/flows/prove_then_verify.sh index b51fca8d8537..ccd38b253b4d 100755 --- a/barretenberg/acir_tests/flows/prove_then_verify.sh +++ b/barretenberg/acir_tests/flows/prove_then_verify.sh @@ -5,7 +5,6 @@ set -eu BFLAG="-b ./target/program.json" FLAGS="-c $CRS_PATH ${VERBOSE:+-v}" [ "${RECURSIVE}" = "true" ] && FLAGS+=" --recursive" -[ -n "${SYS:-}" ] && SYS="_$SYS" || SYS="" # TODO: Use this when client ivc support write_vk. Currently it keeps its own flow. # case ${SYS:-} in @@ -16,7 +15,7 @@ FLAGS="-c $CRS_PATH ${VERBOSE:+-v}" # "client_ivc") # prove_cmd=prove # verify_cmd=verify -# flags+=" --scheme client_ivc --input_type ${INPUT_TYPE:-compiletime_stack}" +# flags+=" --scheme client_ivc --input_type ${INPUT_TYPE:-single_circuit}" # ;; # *) # prove_cmd=prove_$SYS @@ -27,6 +26,40 @@ FLAGS="-c $CRS_PATH ${VERBOSE:+-v}" # Test we can perform the proof/verify flow. # This ensures we test independent pk construction through real/garbage witness data paths. # We use process substitution pipes to avoid temporary files, which need cleanup, and can collide with parallelism. -$BIN verify$SYS $FLAGS \ - -k <($BIN write_vk$SYS -o - $FLAGS $BFLAG) \ - -p <($BIN prove$SYS -o - $FLAGS $BFLAG) + +case ${SYS:-} in + "") + # Deprecated; used for old node cli + [ -n "${SYS:-}" ] && SYS="_$SYS" || SYS="" + $BIN verify$SYS $FLAGS \ + -k <($BIN write_vk$SYS -o - $FLAGS $BFLAG) \ + -p <($BIN prove$SYS -o - $FLAGS $BFLAG) + ;; + "ultra_plonk_deprecated") + # This used to be the default but now it's on its way out. + $BIN OLD_API verify $FLAGS \ + -k <($BIN OLD_API write_vk -o - $FLAGS $BFLAG) \ + -p <($BIN OLD_API prove -o - $FLAGS $BFLAG) + ;; + "ultra_honk") + FLAGS+=" --scheme $SYS --oracle_hash ${HASH:-poseidon2}" + [ "${ROLLUP:-false}" = "true" ] && FLAGS+=" --ipa_accumulation" + [ "${RECURSIVE}" = "true" ] && FLAGS+=" --init_kzg_accumulator" + $BIN verify $FLAGS \ + -k <($BIN write_vk $FLAGS $BFLAG -o - ) \ + -p <($BIN prove $FLAGS $BFLAG -o - ) + ;; + "ultra_honk_deprecated") + # deprecated flow is necessary until we finish C++ api refactor and then align ts api + SYS_DEP=_ultra_honk + $BIN verify$SYS_DEP $FLAGS \ + -k <($BIN write_vk$SYS_DEP -o - $FLAGS $BFLAG) \ + -p <($BIN prove$SYS_DEP -o - $FLAGS $BFLAG) + ;; + *) + [ -n "${SYS:-}" ] && SYS="_$SYS" || SYS="" + $BIN verify$SYS $FLAGS \ + -k <($BIN write_vk$SYS -o - $FLAGS $BFLAG) \ + -p <($BIN prove$SYS -o - $FLAGS $BFLAG) + ;; +esac diff --git a/barretenberg/acir_tests/flows/prove_then_verify_client_ivc.sh b/barretenberg/acir_tests/flows/prove_then_verify_client_ivc.sh index fa82e8a5200c..494faa18b9e6 100755 --- a/barretenberg/acir_tests/flows/prove_then_verify_client_ivc.sh +++ b/barretenberg/acir_tests/flows/prove_then_verify_client_ivc.sh @@ -1,6 +1,6 @@ #!/bin/bash # Create intermediate state in a directory. Uses a temp dir to ensure parallel safe and cleansup on exit. -set -eu +set -eux CRS_PATH=${CRS_PATH:-$HOME/.bb-crs} BIN=$(realpath ${BIN:-../cpp/build/bin/bb}) @@ -10,7 +10,7 @@ BIN=$(realpath ${BIN:-../cpp/build/bin/bb}) outdir=$(mktemp -d) trap "rm -rf $outdir" EXIT -flags="--scheme client_ivc -c $CRS_PATH ${VERBOSE:+-v} -o $outdir" +flags="--scheme client_ivc -c $CRS_PATH ${VERBOSE:+-v}" -$BIN prove $flags -b ./target/program.json --input_type ${INPUT_TYPE:-compiletime_stack} -$BIN verify $flags +$BIN prove $flags -b ./target/program.json ${INPUT_TYPE:---input_type compiletime_stack} -o $outdir +$BIN verify $flags -p $outdir/proof -k $outdir/vk diff --git a/barretenberg/acir_tests/flows/prove_then_verify_tube.sh b/barretenberg/acir_tests/flows/prove_then_verify_tube.sh index 24c08cd36d3e..2c5c3fd100e2 100755 --- a/barretenberg/acir_tests/flows/prove_then_verify_tube.sh +++ b/barretenberg/acir_tests/flows/prove_then_verify_tube.sh @@ -13,6 +13,7 @@ trap "rm -rf $outdir" EXIT flags="-c $CRS_PATH ${VERBOSE:+-v} -o $outdir" -$BIN write_arbitrary_valid_proof_and_vk_to_file --scheme client_ivc $flags +# TODO(https://github.com/AztecProtocol/barretenberg/issues/1252): deprecate in favor of normal proving flow +$BIN OLD_API write_arbitrary_valid_client_ivc_proof_and_vk_to_file $flags $BIN prove_tube $flags -$BIN verify_tube $flags \ No newline at end of file +$BIN verify_tube $flags diff --git a/barretenberg/acir_tests/flows/sol.sh b/barretenberg/acir_tests/flows/sol.sh index ed7ca513efa6..bcb06e9fe392 100755 --- a/barretenberg/acir_tests/flows/sol.sh +++ b/barretenberg/acir_tests/flows/sol.sh @@ -10,10 +10,10 @@ export PROOF_AS_FIELDS="$PWD/sol_proof_fields.json" export VK="$PWD/sol_vk" # Create a proof, write the solidity contract, write the proof as fields in order to extract the public inputs -$BIN prove -o $PROOF $FLAGS -$BIN write_vk -o $VK $FLAGS -$BIN proof_as_fields -k $VK $FLAGS -p $PROOF -$BIN contract -k $VK $FLAGS $BFLAG -o Key.sol +$BIN OLD_API prove -o $PROOF $FLAGS +$BIN OLD_API write_vk -o $VK $FLAGS +$BIN OLD_API proof_as_fields -k $VK $FLAGS -p $PROOF +$BIN OLD_API contract -k $VK $FLAGS $BFLAG -o Key.sol # Export the paths to the environment variables for the js test runner export KEY_PATH="$PWD/Key.sol" diff --git a/barretenberg/acir_tests/flows/sol_honk.sh b/barretenberg/acir_tests/flows/sol_honk.sh index 15bbdf6e0246..3a716d3b92a1 100755 --- a/barretenberg/acir_tests/flows/sol_honk.sh +++ b/barretenberg/acir_tests/flows/sol_honk.sh @@ -1,28 +1,34 @@ #!/bin/sh -set -eux +set -eu VFLAG=${VERBOSE:+-v} BFLAG="-b ./target/program.json" -FLAGS="-c $CRS_PATH $VFLAG" +FLAGS="-c $CRS_PATH $VFLAG --scheme ultra_honk" +[ "${RECURSIVE}" = "true" ] && FLAGS+=" --recursive" +PROVE_FLAGS="$FLAGS $BFLAG --oracle_hash keccak --output_data bytes_and_fields --output_content proof_and_vk --input_type single_circuit" +VERIFY_FLAGS="$FLAGS --oracle_hash keccak" -export PROOF="$PWD/sol_honk_proof" -export PROOF_AS_FIELDS="$PWD/sol_honk_proof_fields.json" -export VK="$PWD/sol_honk_vk" +outdir=$(mktemp -d) +trap "rm -rf $outdir" EXIT + +# Export the paths to the environment variables for the js test runner +export PROOF="$outdir/proof" +export PROOF_AS_FIELDS="$outdir/proof_fields.json" +export VK="$outdir/vk" +export VERIFIER_CONTRACT="$outdir/Verifier.sol" # Create a proof, write the solidity contract, write the proof as fields in order to extract the public inputs -$BIN prove_ultra_keccak_honk -o $PROOF $FLAGS $BFLAG -$BIN write_vk_ultra_keccak_honk -o $VK $FLAGS $BFLAG -$BIN verify_ultra_keccak_honk -k $VK -p $PROOF $FLAGS -$BIN proof_as_fields_honk $FLAGS -p $PROOF -o $PROOF_AS_FIELDS -$BIN contract_ultra_honk -k $VK $FLAGS -o Verifier.sol +$BIN prove $PROVE_FLAGS -o $outdir +$BIN verify $VERIFY_FLAGS -k $VK -p $PROOF +$BIN write_contract $FLAGS -k $VK -o $VERIFIER_CONTRACT # Export the paths to the environment variables for the js test runner -export VERIFIER_PATH="$PWD/Verifier.sol" +export VERIFIER_PATH="$outdir/Verifier.sol" export TEST_PATH=$(realpath "../../sol-test/HonkTest.sol") export TESTING_HONK="true" # Use solcjs to compile the generated key contract with the template verifier and test contract # index.js will start an anvil, on a random port # Deploy the verifier then send a test transaction -export TEST_NAME=$(basename $PWD) +export TEST_NAME=$(basename $outdir) node ../../sol-test/src/index.js diff --git a/barretenberg/acir_tests/flows/sol_honk_zk.sh b/barretenberg/acir_tests/flows/sol_honk_zk.sh index 7d728914f6bb..b231f5f99af5 100755 --- a/barretenberg/acir_tests/flows/sol_honk_zk.sh +++ b/barretenberg/acir_tests/flows/sol_honk_zk.sh @@ -3,22 +3,27 @@ set -eux VFLAG=${VERBOSE:+-v} BFLAG="-b ./target/program.json" -FLAGS="-c $CRS_PATH $VFLAG" +FLAGS="-c $CRS_PATH $VFLAG --scheme ultra_honk --zk" +[ "${RECURSIVE}" = "true" ] && FLAGS+=" --recursive" +PROTOCOL_FLAGS=" --honk_recursion 1 --oracle_hash keccak" -export PROOF="$PWD/sol_honk_zk_proof" -export PROOF_AS_FIELDS="$PWD/sol_honk_zk_proof_fields.json" -export VK="$PWD/sol_honk_zk_vk" +outdir=$(mktemp -d) +trap "rm -rf $outdir" EXIT + +# Export the paths to the environment variables for the js test runner +export PROOF="$outdir/proof" +export PROOF_AS_FIELDS="$outdir/proof_fields.json" +export VK="$outdir/vk" +export VERIFIER_CONTRACT="$outdir/Verifier.sol" # Create a proof, write the solidity contract, write the proof as fields in order to extract the public inputs -$BIN prove_ultra_keccak_honk_zk -o $PROOF $FLAGS $BFLAG -$BIN write_vk_ultra_keccak_honk -o $VK $FLAGS $BFLAG -$BIN verify_ultra_keccak_honk_zk -k $VK -p $PROOF $FLAGS -$BIN proof_as_fields_honk $FLAGS -p $PROOF -o $PROOF_AS_FIELDS -$BIN contract_ultra_honk_zk -k $VK $FLAGS -o ZKVerifier.sol +$BIN prove -o $outdir $FLAGS $BFLAG $PROTOCOL_FLAGS --output_data bytes_and_fields --output_content proof_and_vk +$BIN verify -k $VK -p $PROOF $FLAGS $PROTOCOL_FLAGS +$BIN write_contract $FLAGS -k $VK -o $VERIFIER_CONTRACT --zk # Export the paths to the environment variables for the js test runner -export VERIFIER_PATH="$PWD/ZKVerifier.sol" -export TEST_PATH=$(realpath "../../sol-test/ZKHonkTest.sol") +export VERIFIER_PATH="$outdir/Verifier.sol" +export TEST_PATH=$(realpath "../../sol-test/HonkTest.sol") export TESTING_HONK="true" export HAS_ZK="true" @@ -26,5 +31,5 @@ export HAS_ZK="true" # Use solcjs to compile the generated key contract with the template verifier and test contract # index.js will start an anvil, on a random port # Deploy the verifier then send a test transaction -export TEST_NAME=$(basename $PWD) +export TEST_NAME=$(basename $outdir) node ../../sol-test/src/index.js diff --git a/barretenberg/acir_tests/flows/write_contract.sh b/barretenberg/acir_tests/flows/write_contract.sh deleted file mode 100755 index 52669901035e..000000000000 --- a/barretenberg/acir_tests/flows/write_contract.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -set -eu - -export TEST_NAME=$(basename $(pwd)) - -$BIN write_vk -o vk -$BIN contract -k vk -c $CRS_PATH -b ./target/program.json -o $TEST_NAME.sol diff --git a/barretenberg/acir_tests/gen_inner_proof_inputs.sh b/barretenberg/acir_tests/gen_inner_proof_inputs.sh deleted file mode 100755 index 9b68f0a1998e..000000000000 --- a/barretenberg/acir_tests/gen_inner_proof_inputs.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -# Env var overrides: -# BIN: to specify a different binary to test with (e.g. bb.js or bb.js-dev). -set -eu - -BIN=${BIN:-../cpp/build/bin/bb} -CRS_PATH=~/.bb-crs -BRANCH=master -VERBOSE=${VERBOSE:-} -RECURSIVE=true -PROOF_NAME="proof_a" - -if [ -f $BIN ]; then - BIN=$(realpath $BIN) -else - BIN=$(realpath $(which $BIN)) -fi - -export BRANCH - -./clone_test_vectors.sh - -cd acir_tests/assert_statement - -PROOF_DIR=$PWD/proofs -PROOF_PATH=$PROOF_DIR/$PROOF_NAME -VFLAG=${VERBOSE:+-v} -RFLAG=${RECURSIVE:+-r} - -echo "Write VK to file for assert_statement..." -$BIN write_vk $VFLAG -c $CRS_PATH -o ./target/vk --recursive - -echo "Write VK as fields for recursion..." -$BIN vk_as_fields $VFLAG -c $CRS_PATH -k ./target/vk -o ./target/vk_fields.json - -echo "Generate proof to file..." -[ -d "$PROOF_DIR" ] || mkdir $PWD/proofs -[ -e "$PROOF_PATH" ] || touch $PROOF_PATH -$BIN prove $VFLAG -c $CRS_PATH -b ./target/program.json -o "./proofs/$PROOF_NAME" --recursive - -echo "Write proof as fields for recursion..." -$BIN proof_as_fields $VFLAG -c $CRS_PATH -p "./proofs/$PROOF_NAME" -k ./target/vk -o "./proofs/${PROOF_NAME}_fields.json" - -cat ./proofs/${PROOF_NAME}_fields.json -echo diff --git a/barretenberg/acir_tests/sol-test/src/index.js b/barretenberg/acir_tests/sol-test/src/index.js index 7800eeabe0bc..6606cf15842a 100644 --- a/barretenberg/acir_tests/sol-test/src/index.js +++ b/barretenberg/acir_tests/sol-test/src/index.js @@ -60,14 +60,13 @@ const [test, verifier] = await Promise.all([ const testingHonk = getEnvVarCanBeUndefined("TESTING_HONK"); const hasZK = getEnvVarCanBeUndefined("HAS_ZK"); -const verifierContract = hasZK ? "ZKVerifier.sol" : "Verifier.sol"; export const compilationInput = { language: "Solidity", sources: { "Test.sol": { content: test, }, - [verifierContract]: { + "Verifier.sol": { content: verifier, }, }, @@ -262,7 +261,7 @@ try { switch (errorType) { case WRONG_PROOF_LENGTH: throw new Error( - "Proof length wrong. Check the constant and the proof surgery." + "Proof length wrong. Possibile culprits: the NUMBER_OF_FIELDS_IN_* constants; number of public inputs; proof surgery; zk/non-zk discrepancy." ); case WRONG_PUBLIC_INPUTS_LENGTH: throw new Error("Number of inputs in the proof is wrong"); diff --git a/barretenberg/cpp/CMakePresets.json b/barretenberg/cpp/CMakePresets.json index 04600388986a..876d5772901e 100644 --- a/barretenberg/cpp/CMakePresets.json +++ b/barretenberg/cpp/CMakePresets.json @@ -59,6 +59,7 @@ "CMAKE_EXE_LINKER_FLAGS": "-fuse-ld=/opt/osxcross/bin/arm64-apple-darwin23-ld" }, "environment": { + "CXXFLAGS": "-Wno-deprecated-declarations", "OSXCROSS_HOST": "arm64-apple-darwin23", "OSXCROSS_TARGET_DIR": "/opt/osxcross", "OSXCROSS_TARGET": "darwin23", @@ -74,6 +75,7 @@ "CMAKE_EXE_LINKER_FLAGS": "-fuse-ld=/opt/osxcross/bin/x86_64-apple-darwin23-ld" }, "environment": { + "CXXFLAGS": "-Wno-deprecated-declarations", "OSXCROSS_HOST": "x86_64-apple-darwin23" } }, diff --git a/barretenberg/cpp/cmake/libdeflate.cmake b/barretenberg/cpp/cmake/libdeflate.cmake index fd151996e069..58a9e9fef43b 100644 --- a/barretenberg/cpp/cmake/libdeflate.cmake +++ b/barretenberg/cpp/cmake/libdeflate.cmake @@ -1,5 +1,9 @@ include(FetchContent) +# Find the path where we will download the Tracy github repository +# we need this to find where the Tracy header files are for inclusion. +set(LIBDEFLATE_INCLUDE "${CMAKE_BINARY_DIR}/_deps/libdeflate-src/") + FetchContent_Declare( libdeflate GIT_REPOSITORY https://github.com/ebiggers/libdeflate.git @@ -7,4 +11,4 @@ FetchContent_Declare( ) # Download and populate libdeflate -FetchContent_MakeAvailable(libdeflate) \ No newline at end of file +FetchContent_MakeAvailable(libdeflate) diff --git a/barretenberg/cpp/scripts/analyze_compile_time.sh b/barretenberg/cpp/scripts/analyze_compile_time.sh index 154a2ec336b7..9f8f4a1e02b1 100755 --- a/barretenberg/cpp/scripts/analyze_compile_time.sh +++ b/barretenberg/cpp/scripts/analyze_compile_time.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash set -eu -PRESET="${1:-wasm-threads}" -TARGET="${2:-barretenberg.wasm}" +PRESET="${PRESET:-wasm-threads}" +TARGET="${TARGET:-barretenberg.wasm}" # Move above script dir. cd $(dirname $0)/.. diff --git a/barretenberg/cpp/src/CMakeLists.txt b/barretenberg/cpp/src/CMakeLists.txt index a2141fc106a9..1020ddd61eb3 100644 --- a/barretenberg/cpp/src/CMakeLists.txt +++ b/barretenberg/cpp/src/CMakeLists.txt @@ -45,7 +45,7 @@ if(WASM) add_link_options(-Wl,--export-memory,--import-memory,--stack-first,-z,stack-size=1048576,--max-memory=4294967296) endif() -include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${MSGPACK_INCLUDE} ${TRACY_INCLUDE} ${LMDB_INCLUDE}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${MSGPACK_INCLUDE} ${TRACY_INCLUDE} ${LMDB_INCLUDE} ${LIBDEFLATE_INCLUDE}) # I feel this should be limited to ecc, however it's currently used in headers that go across libraries, # and there currently isn't an easy way to inherit the DDISABLE_ASM parameter. @@ -63,6 +63,7 @@ if (ENABLE_PIC AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_subdirectory(barretenberg/nodejs_module) endif() +add_subdirectory(barretenberg/api) add_subdirectory(barretenberg/bb) add_subdirectory(barretenberg/boomerang_value_detection) add_subdirectory(barretenberg/circuit_checker) @@ -182,6 +183,7 @@ if(NOT WASM) # enable merkle trees and lmdb list(APPEND BARRETENBERG_TARGET_OBJECTS $) list(APPEND BARRETENBERG_TARGET_OBJECTS $) + list(APPEND BARRETENBERG_TARGET_OBJECTS $) endif() add_library( diff --git a/barretenberg/cpp/src/barretenberg/api/CMakeLists.txt b/barretenberg/cpp/src/barretenberg/api/CMakeLists.txt new file mode 100644 index 000000000000..fac59cc38f4a --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/CMakeLists.txt @@ -0,0 +1,11 @@ +set(API_DEPENDENCIES + client_ivc + ultra_vanilla_client_ivc + dsl) + +if (NOT WASM) + list(APPEND API_DEPENDENCIES libdeflate::libdeflate_static) +endif() + + +barretenberg_module(api ${API_DEPENDENCIES}) diff --git a/barretenberg/cpp/src/barretenberg/bb/acir_format_getters.hpp b/barretenberg/cpp/src/barretenberg/api/acir_format_getters.cpp similarity index 91% rename from barretenberg/cpp/src/barretenberg/bb/acir_format_getters.hpp rename to barretenberg/cpp/src/barretenberg/api/acir_format_getters.cpp index 4b68ef085225..8db4333da78f 100644 --- a/barretenberg/cpp/src/barretenberg/bb/acir_format_getters.hpp +++ b/barretenberg/cpp/src/barretenberg/api/acir_format_getters.cpp @@ -1,6 +1,5 @@ -#pragma once -#include "barretenberg/bb/file_io.hpp" -#include "barretenberg/bb/get_bytecode.hpp" +#include "barretenberg/api/file_io.hpp" +#include "barretenberg/api/get_bytecode.hpp" #include "barretenberg/dsl/acir_format/acir_format.hpp" #include "barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp" diff --git a/barretenberg/cpp/src/barretenberg/api/acir_format_getters.hpp b/barretenberg/cpp/src/barretenberg/api/acir_format_getters.hpp new file mode 100644 index 000000000000..1dd4e7d33e45 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/acir_format_getters.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "barretenberg/dsl/acir_format/acir_format.hpp" +#include "barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp" + +namespace bb { +acir_format::WitnessVector get_witness(std::string const& witness_path); +acir_format::AcirFormat get_constraint_system(std::string const& bytecode_path, uint32_t honk_recursion); +acir_format::WitnessVectorStack get_witness_stack(std::string const& witness_path); +std::vector get_constraint_systems(std::string const& bytecode_path, uint32_t honk_recursion); +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/api/api.hpp b/barretenberg/cpp/src/barretenberg/api/api.hpp new file mode 100644 index 000000000000..86994f80488c --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/api.hpp @@ -0,0 +1,75 @@ +#pragma once +#include +#include + +namespace bb { + +class API { + public: + // see the setting of these flags in bb/main.cpp for more information + struct Flags { + bool verbose{ false }; // more logging + bool debug{ false }; // even more logging + bool zk{ false }; // use a zk version of the protocol + std::filesystem::path crs_path{ "" }; // the location of reference strings for commitment schemes + bool recursive{ false }; // deprecated flag indicating that a circuit is to be recursively verified + bool init_kzg_accumulator{ false }; // stripped down version fo `recursive` in the UltraHonk; also deprecated? + uint32_t honk_recursion{ 0 }; // flag that differentiates between different recursion modes; deprecated? + bool ipa_accumulation{ false }; // indicate whether the command is doing IPA proof aggregation + std::string scheme; // the proving system or IVC scheme + std::string input_type; // is the input bytecode a single circuit or a stack of circuits? + std::string oracle_hash_type; // which hash function does the prover use as a random oracle? + std::string output_data_type; // output bytes, fields, both, or a msgpack buffer of fields + std::string output_content_type; // output a proof, a verification key, or both + + friend std::ostream& operator<<(std::ostream& os, const Flags& flags) + { + os << "flags: [\n" + << " verbose: " << flags.verbose << "\n" + << " debug: " << flags.debug << "\n" + << " zk: " << flags.zk << "\n" + << " crs_path: " << flags.crs_path << "\n" + << " recursive: " << flags.recursive << "\n" + << " init_kzg_accumulator: " << flags.init_kzg_accumulator << "\n" + << " honk_recursion: " << flags.honk_recursion << "\n" + << " ipa_accumulation: " << flags.ipa_accumulation << "\n" + << " scheme: " << flags.scheme << "\n" + << " input_type: " << flags.input_type << "\n" + << " oracle_hash_type: " << flags.oracle_hash_type << "\n" + << " output_data_type: " << flags.output_data_type << "\n" + << " output_content_type: " << flags.output_content_type << "\n" + << "]" << std::endl; + return os; + } + }; + + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1256): Implement + virtual bool check(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& witness_path) = 0; + + virtual void prove(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& witness_path, + const std::filesystem::path& output_dir) = 0; + + virtual bool verify(const Flags& flags, + const std::filesystem::path& proof_path, + const std::filesystem::path& vk_path) = 0; + + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1255): deprecate or include as flag to `prove` + virtual bool prove_and_verify(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& witness_path) = 0; + + virtual void write_vk(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& output_path) = 0; + + virtual void gates(const Flags& flags, const std::filesystem::path& bytecode_path) = 0; + + virtual void write_contract(const Flags& flags, + const std::filesystem::path& output_path, + const std::filesystem::path& vk_path) = 0; +}; +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/api/api_avm.cpp b/barretenberg/cpp/src/barretenberg/api/api_avm.cpp new file mode 100644 index 000000000000..a03a3d7442d7 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/api_avm.cpp @@ -0,0 +1,198 @@ +#ifndef DISABLE_AZTEC_VM +#include "barretenberg/api/api_avm.hpp" +#include "barretenberg/api/file_io.hpp" +#include "barretenberg/api/init_srs.hpp" +#include "barretenberg/common/map.hpp" +#include "barretenberg/vm/avm/generated/flavor.hpp" +#include "barretenberg/vm/avm/trace/common.hpp" +#include "barretenberg/vm/avm/trace/execution.hpp" +#include "barretenberg/vm/avm/trace/public_inputs.hpp" +#include "barretenberg/vm/aztec_constants.hpp" +#include "barretenberg/vm/stats.hpp" +#include "barretenberg/vm2/avm_api.hpp" +#include "barretenberg/vm2/common/aztec_types.hpp" +#include "barretenberg/vm2/common/constants.hpp" +#include + +namespace bb { +void print_avm_stats() +{ +#ifdef AVM_TRACK_STATS + info("------- STATS -------"); + const auto& stats = avm_trace::Stats::get(); + const int levels = std::getenv("AVM_STATS_DEPTH") != nullptr ? std::stoi(std::getenv("AVM_STATS_DEPTH")) : 2; + info(stats.to_string(levels)); +#endif +} + +/** + * @brief Performs "check circuit" on the AVM circuit for the given public inputs and hints. + * + * @param public_inputs_path Path to the file containing the serialised avm public inputs + * @param hints_path Path to the file containing the serialised avm circuit hints + */ +void avm_check_circuit(const std::filesystem::path& public_inputs_path, const std::filesystem::path& hints_path) +{ + + const auto avm_public_inputs = AvmPublicInputs::from(read_file(public_inputs_path)); + const auto avm_hints = bb::avm_trace::ExecutionHints::from(read_file(hints_path)); + avm_hints.print_sizes(); + + vinfo("initializing crs with size: ", avm_trace::Execution::SRS_SIZE); + init_bn254_crs(avm_trace::Execution::SRS_SIZE); + + avm_trace::Execution::check_circuit(avm_public_inputs, avm_hints); + + print_avm_stats(); +} + +/** + * @brief Writes an avm proof and corresponding (incomplete) verification key to files. + * + * Communication: + * - Filesystem: The proof and vk are written to the paths output_path/proof and output_path/{vk, vk_fields.json} + * + * @param public_inputs_path Path to the file containing the serialised avm public inputs + * @param hints_path Path to the file containing the serialised avm circuit hints + * @param output_path Path (directory) to write the output proof and verification keys + */ +void avm_prove(const std::filesystem::path& public_inputs_path, + const std::filesystem::path& hints_path, + const std::filesystem::path& output_path) +{ + + const auto avm_public_inputs = AvmPublicInputs::from(read_file(public_inputs_path)); + const auto avm_hints = bb::avm_trace::ExecutionHints::from(read_file(hints_path)); + avm_hints.print_sizes(); + + vinfo("initializing crs with size: ", avm_trace::Execution::SRS_SIZE); + init_bn254_crs(avm_trace::Execution::SRS_SIZE); + + // Prove execution and return vk + auto const [verification_key, proof] = + AVM_TRACK_TIME_V("prove/all", avm_trace::Execution::prove(avm_public_inputs, avm_hints)); + + std::vector vk_as_fields = verification_key.to_field_elements(); + + vinfo("vk fields size: ", vk_as_fields.size()); + vinfo("circuit size: ", static_cast(vk_as_fields[0])); + vinfo("num of pub inputs: ", static_cast(vk_as_fields[1])); + + const auto to_json = [](const std::vector& data) { + return format("[", join(map(data, [](auto fr) { return format("\"", fr, "\""); })), "]"); + }; + std::string vk_json = to_json(vk_as_fields); + const auto proof_path = output_path / "proof"; + const auto vk_path = output_path / "vk"; + const auto vk_fields_path = output_path / "vk_fields.json"; + + write_file(proof_path, to_buffer(proof)); + vinfo("proof written to: ", proof_path); + write_file(vk_path, to_buffer(vk_as_fields)); + vinfo("vk written to: ", vk_path); + write_file(vk_fields_path, { vk_json.begin(), vk_json.end() }); + vinfo("vk as fields written to: ", vk_fields_path); + + print_avm_stats(); +} + +void avm2_prove(const std::filesystem::path& inputs_path, const std::filesystem::path& output_path) +{ + avm2::AvmAPI avm; + auto inputs = avm2::AvmAPI::ProvingInputs::from(read_file(inputs_path)); + + // This is bigger than CIRCUIT_SUBGROUP_SIZE because of BB inefficiencies. + init_bn254_crs(avm2::CIRCUIT_SUBGROUP_SIZE * 2); + auto [proof, vk] = avm.prove(inputs); + + // NOTE: As opposed to Avm1 and other proof systems, the public inputs are NOT part of the proof. + write_file(output_path / "proof", to_buffer(proof)); + write_file(output_path / "vk", vk); + + print_avm_stats(); +} + +void avm2_check_circuit(const std::filesystem::path& inputs_path) +{ + avm2::AvmAPI avm; + auto inputs = avm2::AvmAPI::ProvingInputs::from(read_file(inputs_path)); + + bool res = avm.check_circuit(inputs); + info("circuit check: ", res ? "success" : "failure"); + + print_avm_stats(); +} + +/** + * @brief Verifies an avm proof and writes the result to stdout + * + * Communication: + * - proc_exit: A boolean value is returned indicating whether the proof is valid. + * an exit code of 0 will be returned for success and 1 for failure. + * + * @param proof_path Path to the file containing the serialized proof + * @param vk_path Path to the file containing the serialized verification key + * @return true If the proof is valid + * @return false If the proof is invalid + */ +bool avm_verify(const std::filesystem::path& proof_path, const std::filesystem::path& vk_path) +{ + using Commitment = bb::avm::AvmFlavorSettings::Commitment; + std::vector const proof = many_from_buffer(read_file(proof_path)); + std::vector vk_bytes = read_file(vk_path); + std::vector vk_as_fields = many_from_buffer(vk_bytes); + + vinfo("initializing crs with size: ", 1); + init_bn254_crs(1); + + auto circuit_size = uint64_t(vk_as_fields[0]); + auto num_public_inputs = uint64_t(vk_as_fields[1]); + std::span vk_span(vk_as_fields); + + vinfo("vk fields size: ", vk_as_fields.size()); + vinfo("circuit size: ", circuit_size, " (next or eq power: 2^", numeric::round_up_power_2(circuit_size), ")"); + vinfo("num of pub inputs: ", num_public_inputs); + + if (vk_as_fields.size() != AVM_VERIFICATION_KEY_LENGTH_IN_FIELDS) { + info("The supplied avm vk has incorrect size. Number of fields: ", + vk_as_fields.size(), + " but expected: ", + AVM_VERIFICATION_KEY_LENGTH_IN_FIELDS); + return false; + } + + std::array precomputed_cmts; + for (size_t i = 0; i < bb::avm::AvmFlavor::NUM_PRECOMPUTED_ENTITIES; i++) { + // Start at offset 2 and adds 4 (NUM_FRS_COM) fr elements per commitment. Therefore, index = 4 * i + 2. + precomputed_cmts[i] = field_conversion::convert_from_bn254_frs( + vk_span.subspan(bb::avm::AvmFlavor::NUM_FRS_COM * i + 2, bb::avm::AvmFlavor::NUM_FRS_COM)); + } + + auto vk = bb::avm::AvmFlavor::VerificationKey(circuit_size, num_public_inputs, precomputed_cmts); + + const bool verified = AVM_TRACK_TIME_V("verify/all", avm_trace::Execution::verify(vk, proof)); + vinfo("verified: ", verified); + + print_avm_stats(); + return verified; +} + +// NOTE: The proof should NOT include the public inputs. +bool avm2_verify(const std::filesystem::path& proof_path, + const std::filesystem::path& public_inputs_path, + const std::filesystem::path& vk_path) +{ + const auto proof = many_from_buffer(read_file(proof_path)); + std::vector vk_bytes = read_file(vk_path); + auto public_inputs = avm2::PublicInputs::from(read_file(public_inputs_path)); + + init_bn254_crs(1); + avm2::AvmAPI avm; + bool res = avm.verify(proof, public_inputs, vk_bytes); + info("verification: ", res ? "success" : "failure"); + + print_avm_stats(); + return res; +} +} // namespace bb +#endif diff --git a/barretenberg/cpp/src/barretenberg/api/api_avm.hpp b/barretenberg/cpp/src/barretenberg/api/api_avm.hpp new file mode 100644 index 000000000000..9ac4323b0d4b --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/api_avm.hpp @@ -0,0 +1,51 @@ +#ifndef DISABLE_AZTEC_VM +#include + +namespace bb { + +/** + * @brief Performs "check circuit" on the AVM circuit for the given public inputs and hints. + * + * @param public_inputs_path Path to the file containing the serialised avm public inputs + * @param hints_path Path to the file containing the serialised avm circuit hints + */ +void avm_check_circuit(const std::filesystem::path& public_inputs_path, const std::filesystem::path& hints_path); + +/** + * @brief Writes an avm proof and corresponding (incomplete) verification key to files. + * + * Communication: + * - Filesystem: The proof and vk are written to the paths output_path/proof and output_path/{vk, vk_fields.json} + * + * @param public_inputs_path Path to the file containing the serialised avm public inputs + * @param hints_path Path to the file containing the serialised avm circuit hints + * @param output_path Path (directory) to write the output proof and verification keys + */ +void avm_prove(const std::filesystem::path& public_inputs_path, + const std::filesystem::path& hints_path, + const std::filesystem::path& output_path); + +void avm2_prove(const std::filesystem::path& inputs_path, const std::filesystem::path& output_path); + +void avm2_check_circuit(const std::filesystem::path& inputs_path); + +/** + * @brief Verifies an avm proof and writes the result to stdout + * + * Communication: + * - proc_exit: A boolean value is returned indicating whether the proof is valid. + * an exit code of 0 will be returned for success and 1 for failure. + * + * @param proof_path Path to the file containing the serialized proof + * @param vk_path Path to the file containing the serialized verification key + * @return true If the proof is valid + * @return false If the proof is invalid + */ +bool avm_verify(const std::filesystem::path& proof_path, const std::filesystem::path& vk_path); + +// NOTE: The proof should NOT include the public inputs. +bool avm2_verify(const std::filesystem::path& proof_path, + const std::filesystem::path& public_inputs_path, + const std::filesystem::path& vk_path); +} // namespace bb +#endif diff --git a/barretenberg/cpp/src/barretenberg/api/api_client_ivc.cpp b/barretenberg/cpp/src/barretenberg/api/api_client_ivc.cpp new file mode 100644 index 000000000000..160d31d3daec --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/api_client_ivc.cpp @@ -0,0 +1,369 @@ +#include "api_client_ivc.hpp" +#include "barretenberg/api/file_io.hpp" +#include "barretenberg/api/init_srs.hpp" +#include "barretenberg/api/log.hpp" +#include "barretenberg/api/write_prover_output.hpp" +#include "barretenberg/client_ivc/mock_circuit_producer.hpp" +#include "barretenberg/common/map.hpp" +#include "barretenberg/common/throw_or_abort.hpp" +#include "barretenberg/dsl/acir_format/ivc_recursion_constraint.hpp" +#include "libdeflate.h" + +namespace bb { + +acir_format::WitnessVector witness_map_to_witness_vector(std::map const& witness_map) +{ + acir_format::WitnessVector wv; + size_t index = 0; + for (auto& e : witness_map) { + uint64_t value = std::stoull(e.first); + // ACIR uses a sparse format for WitnessMap where unused witness indices may be left unassigned. + // To ensure that witnesses sit at the correct indices in the `WitnessVector`, we fill any indices + // which do not exist within the `WitnessMap` with the dummy value of zero. + while (index < value) { + wv.push_back(fr(0)); + index++; + } + wv.push_back(fr(uint256_t(e.second))); + index++; + } + return wv; +} + +std::vector decompress(const void* bytes, size_t size) +{ + std::vector content; + // initial size guess + content.resize(1024ULL * 128ULL); + for (;;) { + auto decompressor = std::unique_ptr{ + libdeflate_alloc_decompressor(), libdeflate_free_decompressor + }; + size_t actual_size = 0; + libdeflate_result decompress_result = libdeflate_gzip_decompress( + decompressor.get(), bytes, size, std::data(content), std::size(content), &actual_size); + if (decompress_result == LIBDEFLATE_INSUFFICIENT_SPACE) { + // need a bigger buffer + content.resize(content.size() * 2); + continue; + } + if (decompress_result == LIBDEFLATE_BAD_DATA) { + throw std::invalid_argument("bad gzip data in bb main"); + } + content.resize(actual_size); + break; + } + return content; +} + +template std::shared_ptr read_to_shared_ptr(const std::filesystem::path& path) +{ + return std::make_shared(from_buffer(read_file(path))); +}; + +template T unpack_from_file(const std::filesystem::path& filename) +{ + std::ifstream fin; + fin.open(filename, std::ios::ate | std::ios::binary); + if (!fin.is_open()) { + throw std::invalid_argument("file not found"); + } + if (fin.tellg() == -1) { + throw std::invalid_argument("something went wrong"); + } + + uint64_t fsize = static_cast(fin.tellg()); + fin.seekg(0, std::ios_base::beg); + + T result; + char* encoded_data = new char[fsize]; + fin.read(encoded_data, static_cast(fsize)); + msgpack::unpack(encoded_data, fsize).get().convert(result); + return result; +} + +/** + * @brief Compute and write to file a MegaHonk VK for a circuit to be accumulated in the IVC + * @note This method differes from write_vk_honk in that it handles kernel circuits which require special + * treatment (i.e. construction of mock IVC state to correctly complete the kernel logic). + * + * @param bytecode_path + * @param witness_path + */ +void write_vk_for_ivc(const std::string& output_data_type, + const std::string& bytecode_path, + const std::string& output_path) +{ + using Builder = ClientIVC::ClientCircuit; + using Prover = ClientIVC::MegaProver; + using DeciderProvingKey = ClientIVC::DeciderProvingKey; + using VerificationKey = ClientIVC::MegaVerificationKey; + using Program = acir_format::AcirProgram; + using ProgramMetadata = acir_format::ProgramMetadata; + + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1163) set these dynamically + init_bn254_crs(1 << CONST_PG_LOG_N); + init_grumpkin_crs(1 << CONST_ECCVM_LOG_N); + + Program program{ get_constraint_system(bytecode_path, /*honk_recursion=*/0), /*witness=*/{} }; + auto& ivc_constraints = program.constraints.ivc_recursion_constraints; + + TraceSettings trace_settings{ E2E_FULL_TEST_STRUCTURE }; + + const ProgramMetadata metadata{ .ivc = ivc_constraints.empty() + ? nullptr + : create_mock_ivc_from_constraints(ivc_constraints, trace_settings) }; + Builder builder = acir_format::create_circuit(program, metadata); + + // Add public inputs corresponding to pairing point accumulator + builder.add_pairing_point_accumulator(stdlib::recursion::init_default_agg_obj_indices(builder)); + + // Construct the verification key via the prover-constructed proving key with the proper trace settings + auto proving_key = std::make_shared(builder, trace_settings); + Prover prover{ proving_key }; + init_bn254_crs(prover.proving_key->proving_key.circuit_size); + ProofAndKey to_write{ {}, std::make_shared(prover.proving_key->proving_key) }; + + write(to_write, output_data_type, "vk", output_path); +} + +std::vector _build_folding_stack(const std::string& input_type, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& witness_path) +{ + using namespace acir_format; + + std::vector folding_stack; + + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1162): Efficiently unify ACIR stack parsing + if (input_type == "compiletime_stack") { + auto program_stack = acir_format::get_acir_program_stack(bytecode_path, witness_path, /*honk_recursion=*/0); + // Accumulate the entire program stack into the IVC + while (!program_stack.empty()) { + auto stack_item = program_stack.back(); + folding_stack.push_back(AcirProgram{ stack_item.constraints, stack_item.witness }); + program_stack.pop_back(); + } + } + + if (input_type == "runtime_stack") { + std::vector gzipped_bincodes = unpack_from_file>(bytecode_path); + std::vector witness_data = unpack_from_file>(witness_path); + for (auto [bincode, wit] : zip_view(gzipped_bincodes, witness_data)) { + // TODO(#7371) there is a lot of copying going on in bincode, we should make sure this writes as a + // buffer in the future + std::vector constraint_buf = decompress(bincode.data(), bincode.size()); // NOLINT + std::vector witness_buf = decompress(wit.data(), wit.size()); // NOLINT + + AcirFormat constraints = circuit_buf_to_acir_format(constraint_buf, /*honk_recursion=*/0); + WitnessVector witness = witness_buf_to_witness_data(witness_buf); + + folding_stack.push_back(AcirProgram{ constraints, witness }); + } + } + + return folding_stack; +} + +std::shared_ptr _accumulate(std::vector& folding_stack) +{ + using Builder = MegaCircuitBuilder; + using Program = acir_format::AcirProgram; + using namespace acir_format; + + TraceSettings trace_settings{ E2E_FULL_TEST_STRUCTURE }; + auto ivc = std::make_shared(trace_settings); + + const ProgramMetadata metadata{ ivc }; + + // Accumulate the entire program stack into the IVC + for (Program& program : folding_stack) { + // Construct a bberg circuit from the acir representation then accumulate it into the IVC + Builder circuit = acir_format::create_circuit(program, metadata); + + // Do one step of ivc accumulator or, if there is only one circuit in the stack, prove that circuit. In this + // case, no work is added to the Goblin opqueue, but VM proofs for trivials inputs are produced. + ivc->accumulate(circuit, /*one_circuit=*/folding_stack.size() == 1); + } + + return ivc; +} + +void ClientIVCAPI::prove(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& witness_path, + const std::filesystem::path& output_dir) +{ + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1163) set these dynamically + init_bn254_crs(1 << CONST_PG_LOG_N); + init_grumpkin_crs(1 << CONST_ECCVM_LOG_N); + + std::vector folding_stack = + _build_folding_stack(flags.input_type, bytecode_path, witness_path); + + std::shared_ptr ivc = _accumulate(folding_stack); + ClientIVC::Proof proof = ivc->prove(); + + // Write the proof and verification keys into the working directory in 'binary' format (in practice it seems + // this directory is passed by bb.js) + vinfo("writing ClientIVC proof and vk in directory ", output_dir); + write_file(output_dir / "proof", to_buffer(proof)); + + auto eccvm_vk = std::make_shared(ivc->goblin.get_eccvm_proving_key()); + auto translator_vk = std::make_shared(ivc->goblin.get_translator_proving_key()); + write_file(output_dir / "vk", to_buffer(ClientIVC::VerificationKey{ ivc->honk_vk, eccvm_vk, translator_vk })); +} + +bool ClientIVCAPI::verify([[maybe_unused]] const Flags& flags, + const std::filesystem::path& proof_path, + const std::filesystem::path& vk_path) +{ + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1163): Set these dynamically + init_bn254_crs(1); + init_grumpkin_crs(1 << CONST_ECCVM_LOG_N); + + const auto proof = from_buffer(read_file(proof_path)); + const auto vk = from_buffer(read_file(vk_path)); + + vk.mega->pcs_verification_key = std::make_shared>(); + vk.eccvm->pcs_verification_key = + std::make_shared>(vk.eccvm->circuit_size + 1); + vk.translator->pcs_verification_key = std::make_shared>(); + + const bool verified = ClientIVC::verify(proof, vk); + return verified; +} + +bool ClientIVCAPI::prove_and_verify(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& witness_path) +{ + if (!(flags.input_type == "compiletime_stack" || flags.input_type == "runtime_stack")) { + throw_or_abort("No input_type or input_type not supported"); + } + + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1163) set these dynamically + init_bn254_crs(1 << CONST_PG_LOG_N); + init_grumpkin_crs(1 << CONST_ECCVM_LOG_N); + + std::vector folding_stack = + _build_folding_stack(flags.input_type, bytecode_path, witness_path); + std::shared_ptr ivc = _accumulate(folding_stack); + const bool verified = ivc->prove_and_verify(); + return verified; +} + +void ClientIVCAPI::gates([[maybe_unused]] const Flags& flags, + [[maybe_unused]] const std::filesystem::path& bytecode_path) +{ + throw_or_abort("API function gates not implemented"); +} + +void ClientIVCAPI::write_contract([[maybe_unused]] const Flags& flags, + [[maybe_unused]] const std::filesystem::path& output_path, + [[maybe_unused]] const std::filesystem::path& vk_path) +{ + throw_or_abort("API function contract not implemented"); +} + +void ClientIVCAPI::write_vk(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& output_path) +{ + write_vk_for_ivc(flags.output_data_type, bytecode_path, output_path); +} + +bool ClientIVCAPI::check([[maybe_unused]] const Flags& flags, + [[maybe_unused]] const std::filesystem::path& bytecode_path, + [[maybe_unused]] const std::filesystem::path& witness_path) +{ + throw_or_abort("API function check_witness not implemented"); + return false; +} + +void gate_count_for_ivc(const std::string& bytecode_path) +{ + // All circuit reports will be built into the string below + std::string functions_string = "{\"functions\": [\n "; + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1181): Use enum for honk_recursion. + auto constraint_systems = get_constraint_systems(bytecode_path, /*honk_recursion=*/0); + + // Initialize an SRS to make the ClientIVC constructor happy + init_bn254_crs(1 << CONST_PG_LOG_N); + init_grumpkin_crs(1 << CONST_ECCVM_LOG_N); + TraceSettings trace_settings{ E2E_FULL_TEST_STRUCTURE }; + + size_t i = 0; + for (const auto& constraint_system : constraint_systems) { + acir_format::AcirProgram program{ constraint_system }; + const auto& ivc_constraints = constraint_system.ivc_recursion_constraints; + acir_format::ProgramMetadata metadata{ .ivc = ivc_constraints.empty() ? nullptr + : create_mock_ivc_from_constraints( + ivc_constraints, trace_settings), + .collect_gates_per_opcode = true }; + + auto builder = acir_format::create_circuit(program, metadata); + builder.finalize_circuit(/*ensure_nonzero=*/true); + size_t circuit_size = builder.num_gates; + + // Print the details of the gate types within the structured execution trace + builder.blocks.set_fixed_block_sizes(trace_settings); + builder.blocks.summarize(); + + // Build individual circuit report + std::string gates_per_opcode_str; + for (size_t j = 0; j < program.constraints.gates_per_opcode.size(); j++) { + gates_per_opcode_str += std::to_string(program.constraints.gates_per_opcode[j]); + if (j != program.constraints.gates_per_opcode.size() - 1) { + gates_per_opcode_str += ","; + } + } + + auto result_string = format("{\n \"acir_opcodes\": ", + program.constraints.num_acir_opcodes, + ",\n \"circuit_size\": ", + circuit_size, + ",\n \"gates_per_opcode\": [", + gates_per_opcode_str, + "]\n }"); + + // Attach a comma if there are more circuit reports to generate + if (i != (constraint_systems.size() - 1)) { + result_string = format(result_string, ","); + } + + functions_string = format(functions_string, result_string); + + i++; + } + std::cout << format(functions_string, "\n]}"); +} + +void write_arbitrary_valid_client_ivc_proof_and_vk_to_file(const std::filesystem::path& output_dir) +{ + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1163) set these dynamically + init_bn254_crs(1 << CONST_PG_LOG_N); + init_grumpkin_crs(1 << CONST_ECCVM_LOG_N); + + ClientIVC ivc{ { CLIENT_IVC_BENCH_STRUCTURE } }; + + // Construct and accumulate a series of mocked private function execution circuits + PrivateFunctionExecutionMockCircuitProducer circuit_producer; + size_t NUM_CIRCUITS = 2; + for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { + auto circuit = circuit_producer.create_next_circuit(ivc); + ivc.accumulate(circuit); + } + + ClientIVC::Proof proof = ivc.prove(); + + // Write the proof and verification keys into the working directory in 'binary' format + vinfo("writing ClientIVC proof and vk..."); + write_file(output_dir / "proof", to_buffer(proof)); + + auto eccvm_vk = std::make_shared(ivc.goblin.get_eccvm_proving_key()); + auto translator_vk = std::make_shared(ivc.goblin.get_translator_proving_key()); + write_file(output_dir / "vk", to_buffer(ClientIVC::VerificationKey{ ivc.honk_vk, eccvm_vk, translator_vk })); +} + +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/api/api_client_ivc.hpp b/barretenberg/cpp/src/barretenberg/api/api_client_ivc.hpp new file mode 100644 index 000000000000..fc649a306cf9 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/api_client_ivc.hpp @@ -0,0 +1,50 @@ +#pragma once +#include "barretenberg/api/acir_format_getters.hpp" +#include "barretenberg/api/api.hpp" +#include +#include +#include + +namespace bb { + +class ClientIVCAPI : public API { + + public: + void prove(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& witness_path, + const std::filesystem::path& output_dir) override; + + bool verify(const Flags& flags, + const std::filesystem::path& proof_path, + const std::filesystem::path& vk_path) override; + + bool prove_and_verify(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& witness_path) override; + + void gates(const Flags& flags, const std::filesystem::path& bytecode_path) override; + + void write_contract(const Flags& flags, + const std::filesystem::path& output_path, + const std::filesystem::path& vk_path) override; + + void write_vk(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& output_path) override; + + bool check(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& witness_path) override; +}; + +void gate_count_for_ivc(const std::string& bytecode_path); + +void write_arbitrary_valid_client_ivc_proof_and_vk_to_file(const std::filesystem::path& output_dir); + +acir_format::WitnessVector witness_map_to_witness_vector(std::map const& witness_map); + +std::vector decompress(const void* bytes, size_t size); + +void write_vk_for_ivc(const bool output_fields, const std::string& bytecode_path, const std::string& output_path); +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/api/api_ultra_honk.cpp b/barretenberg/cpp/src/barretenberg/api/api_ultra_honk.cpp new file mode 100644 index 000000000000..c17388fa0bb9 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/api_ultra_honk.cpp @@ -0,0 +1,263 @@ +#include "api_ultra_honk.hpp" + +#include "barretenberg/api/acir_format_getters.hpp" +#include "barretenberg/api/get_bn254_crs.hpp" +#include "barretenberg/api/init_srs.hpp" +#include "barretenberg/api/write_prover_output.hpp" +#include "barretenberg/common/map.hpp" +#include "barretenberg/common/throw_or_abort.hpp" +#include "barretenberg/dsl/acir_format/proof_surgeon.hpp" +#include "barretenberg/dsl/acir_proofs/honk_contract.hpp" +#include "barretenberg/dsl/acir_proofs/honk_zk_contract.hpp" +#include "barretenberg/srs/global_crs.hpp" +#include "barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp" + +namespace bb { + +extern std::string CRS_PATH; + +template +Circuit _compute_circuit(const std::string& bytecode_path, + const std::string& witness_path, + const bool init_kzg_accumulator) +{ + uint32_t honk_recursion = 0; + if constexpr (IsAnyOf) { + honk_recursion = 1; + } else if constexpr (IsAnyOf) { + honk_recursion = 2; + } + + // TODO: Don't init grumpkin crs when unnecessary. + init_grumpkin_crs(1 << CONST_ECCVM_LOG_N); + + const acir_format::ProgramMetadata metadata{ .recursive = init_kzg_accumulator, .honk_recursion = honk_recursion }; + acir_format::AcirProgram program{ get_constraint_system(bytecode_path, metadata.honk_recursion) }; + + if (!witness_path.empty()) { + program.witness = get_witness(witness_path); + } + return acir_format::create_circuit(program, metadata); +} + +template +UltraProver_ _compute_prover(const std::string& bytecode_path, + const std::string& witness_path, + const bool init_kzg_accumulator) +{ + auto prover = UltraProver_{ _compute_circuit(bytecode_path, witness_path, init_kzg_accumulator) }; + + size_t required_crs_size = prover.proving_key->proving_key.circuit_size; + if constexpr (Flavor::HasZK) { + required_crs_size = std::max(required_crs_size, curve::BN254::SUBGROUP_SIZE * 2); + } + init_bn254_crs(required_crs_size); + return prover; +} + +template +ProofAndKey _compute_vk(const bool init_kzg_accumulator, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& witness_path) +{ + auto prover = _compute_prover(bytecode_path.string(), witness_path.string(), init_kzg_accumulator); + return { HonkProof{}, std::make_shared(prover.proving_key->proving_key) }; +} + +template +ProofAndKey _prove(const bool compute_vk, + const bool init_kzg_accumulator, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& witness_path) +{ + auto prover = _compute_prover(bytecode_path.string(), witness_path.string(), init_kzg_accumulator); + return { prover.construct_proof(), compute_vk ? std::make_shared(prover.proving_key->proving_key) : nullptr }; +} + +template +bool _verify(const bool honk_recursion_2, const std::filesystem::path& proof_path, const std::filesystem::path& vk_path) +{ + using VerificationKey = typename Flavor::VerificationKey; + using Verifier = UltraVerifier_; + + auto g2_data = get_bn254_g2_data(CRS_PATH); + srs::init_crs_factory({}, g2_data); + auto proof = from_buffer>(read_file(proof_path)); + auto vk = std::make_shared(from_buffer(read_file(vk_path))); + vk->pcs_verification_key = std::make_shared>(); + + std::shared_ptr> ipa_verification_key; + if (honk_recursion_2) { + init_grumpkin_crs(1 << CONST_ECCVM_LOG_N); + ipa_verification_key = std::make_shared>(1 << CONST_ECCVM_LOG_N); + } + + Verifier verifier{ vk, ipa_verification_key }; + + bool verified; + if (honk_recursion_2) { + const size_t HONK_PROOF_LENGTH = Flavor::PROOF_LENGTH_WITHOUT_PUB_INPUTS - IPA_PROOF_LENGTH; + const size_t num_public_inputs = static_cast(uint64_t(proof[1])); + // The extra calculation is for the IPA proof length. + ASSERT(proof.size() == HONK_PROOF_LENGTH + IPA_PROOF_LENGTH + num_public_inputs); + const std::ptrdiff_t honk_proof_with_pub_inputs_length = + static_cast(HONK_PROOF_LENGTH + num_public_inputs); + auto ipa_proof = HonkProof(proof.begin() + honk_proof_with_pub_inputs_length, proof.end()); + auto tube_honk_proof = HonkProof(proof.begin(), proof.begin() + honk_proof_with_pub_inputs_length); + verified = verifier.verify_proof(proof, ipa_proof); + } else { + verified = verifier.verify_proof(proof); + } + + return verified; +} + +bool UltraHonkAPI::check([[maybe_unused]] const Flags& flags, + [[maybe_unused]] const std::filesystem::path& bytecode_path, + [[maybe_unused]] const std::filesystem::path& witness_path) +{ + throw_or_abort("API function check_witness not implemented"); + return false; +} + +void UltraHonkAPI::prove(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& witness_path, + const std::filesystem::path& output_dir) +{ + const auto _write = [&](auto&& _prove_output) { + write(_prove_output, flags.output_data_type, flags.output_content_type, output_dir); + }; + + const bool init = flags.init_kzg_accumulator; + const bool compute_vk = flags.output_content_type == "proof_and_vk"; + + if (flags.ipa_accumulation) { + _write(_prove(compute_vk, init, bytecode_path, witness_path)); + } else if (flags.oracle_hash_type == "poseidon2") { + _write(_prove(compute_vk, init, bytecode_path, witness_path)); + } else if (flags.oracle_hash_type == "keccak" && !flags.zk) { + _write(_prove(compute_vk, init, bytecode_path, witness_path)); + } else if (flags.oracle_hash_type == "keccak" && flags.zk) { + _write(_prove(compute_vk, init, bytecode_path, witness_path)); + } else { + throw_or_abort("Invalid proving options specified in _prove"); + } +} + +bool UltraHonkAPI::verify(const Flags& flags, + const std::filesystem::path& proof_path, + const std::filesystem::path& vk_path) +{ + const bool ipa_accumulation = flags.ipa_accumulation; + if (ipa_accumulation) { + return _verify(ipa_accumulation, proof_path, vk_path); + } + if (flags.zk) { + return _verify(ipa_accumulation, proof_path, vk_path); + } + if (flags.oracle_hash_type == "poseidon2") { + return _verify(ipa_accumulation, proof_path, vk_path); + } + if (flags.oracle_hash_type == "keccak") { + return _verify(ipa_accumulation, proof_path, vk_path); + } + return false; +} + +bool UltraHonkAPI::prove_and_verify([[maybe_unused]] const Flags& flags, + [[maybe_unused]] const std::filesystem::path& bytecode_path, + [[maybe_unused]] const std::filesystem::path& witness_path) +{ + throw_or_abort("API function prove_and_verify not implemented"); + return false; +} + +void UltraHonkAPI::write_vk(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& output_path) +{ + const auto _write = [&](auto&& _prove_output) { write(_prove_output, flags.output_data_type, "vk", output_path); }; + + const bool init = flags.init_kzg_accumulator; + + if (flags.ipa_accumulation) { + _write(_compute_vk(init, bytecode_path, "")); + } else if (flags.oracle_hash_type == "poseidon2") { + _write(_compute_vk(init, bytecode_path, "")); + } else if (flags.oracle_hash_type == "keccak" && !flags.zk) { + _write(_compute_vk(init, bytecode_path, "")); + } else if (flags.oracle_hash_type == "keccak" && flags.zk) { + _write(_compute_vk(init, bytecode_path, "")); + } else { + throw_or_abort("Invalid proving options specified in _prove"); + } +} + +void UltraHonkAPI::gates([[maybe_unused]] const Flags& flags, + [[maybe_unused]] const std::filesystem::path& bytecode_path) +{ + ASSERT("API function not implemented"); +} + +void UltraHonkAPI::write_contract(const Flags& flags, + const std::filesystem::path& output_path, + const std::filesystem::path& vk_path) +{ + using VK = UltraKeccakFlavor::VerificationKey; + auto vk = std::make_shared(from_buffer(read_file(vk_path))); + std::string contract = flags.zk ? get_honk_zk_solidity_verifier(vk) : get_honk_solidity_verifier(vk); + + if (output_path == "-") { + std::cout << contract; + } else { + write_file(output_path, { contract.begin(), contract.end() }); + } +} + +template +void write_recursion_inputs_ultra_honk(const std::string& bytecode_path, + const std::string& witness_path, + const std::string& output_path) +{ + using Builder = typename Flavor::CircuitBuilder; + using Prover = UltraProver_; + using VerificationKey = typename Flavor::VerificationKey; + using FF = typename Flavor::FF; + + uint32_t honk_recursion = 0; + bool ipa_accumulation = false; + if constexpr (IsAnyOf) { + honk_recursion = 1; + } else if constexpr (IsAnyOf) { + honk_recursion = 2; + init_grumpkin_crs(1 << CONST_ECCVM_LOG_N); + ipa_accumulation = true; + } + const acir_format::ProgramMetadata metadata{ .recursive = true, .honk_recursion = honk_recursion }; + + acir_format::AcirProgram program; + program.constraints = get_constraint_system(bytecode_path, metadata.honk_recursion); + program.witness = get_witness(witness_path); + auto builder = acir_format::create_circuit(program, metadata); + + Prover prover{ builder }; + init_bn254_crs(prover.proving_key->proving_key.circuit_size); + std::vector proof = prover.construct_proof(); + VerificationKey verification_key(prover.proving_key->proving_key); + + const std::string toml_content = + acir_format::ProofSurgeon::construct_recursion_inputs_toml_data(proof, verification_key, ipa_accumulation); + + const std::string toml_path = output_path + "/Prover.toml"; + write_file(toml_path, { toml_content.begin(), toml_content.end() }); +} + +template void write_recursion_inputs_ultra_honk(const std::string& bytecode_path, + const std::string& witness_path, + const std::string& output_path); + +template void write_recursion_inputs_ultra_honk(const std::string& bytecode_path, + const std::string& witness_path, + const std::string& output_path); +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/api/api_ultra_honk.hpp b/barretenberg/cpp/src/barretenberg/api/api_ultra_honk.hpp new file mode 100644 index 000000000000..48ad8b918742 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/api_ultra_honk.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "barretenberg/api/api.hpp" +#include "barretenberg/stdlib_circuit_builders/ultra_flavor.hpp" +#include "barretenberg/stdlib_circuit_builders/ultra_rollup_flavor.hpp" +#include +#include + +namespace bb { + +class UltraHonkAPI : public API { + public: + bool check(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& witness_path) override; + + void prove(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& witness_path, + const std::filesystem::path& output_dir) override; + + bool verify(const Flags& flags, + const std::filesystem::path& proof_path, + const std::filesystem::path& vk_path) override; + + bool prove_and_verify(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& witness_path) override; + + void write_vk(const Flags& flags, + const std::filesystem::path& bytecode_path, + const std::filesystem::path& output_path) override; + + void gates(const Flags& flags, const std::filesystem::path& bytecode_path) override; + + void write_contract(const Flags& flags, + const std::filesystem::path& output_path, + const std::filesystem::path& vk_path) override; +}; + +template +void write_recursion_inputs_ultra_honk(const std::string& bytecode_path, + const std::string& witness_path, + const std::string& output_path); + +extern template void write_recursion_inputs_ultra_honk(const std::string& bytecode_path, + const std::string& witness_path, + const std::string& output_path); + +extern template void write_recursion_inputs_ultra_honk(const std::string& bytecode_path, + const std::string& witness_path, + const std::string& output_path); + +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/api/api_ultra_plonk.cpp b/barretenberg/cpp/src/barretenberg/api/api_ultra_plonk.cpp new file mode 100644 index 000000000000..856decd74ede --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/api_ultra_plonk.cpp @@ -0,0 +1,353 @@ +#include "api_ultra_plonk.hpp" +#include "barretenberg/api/acir_format_getters.hpp" +#include "barretenberg/api/get_bn254_crs.hpp" +#include "barretenberg/api/init_srs.hpp" +#include "barretenberg/common/benchmark.hpp" +#include "barretenberg/common/map.hpp" +#include "barretenberg/common/timer.hpp" +#include "barretenberg/dsl/acir_format/acir_format.hpp" +#include "barretenberg/dsl/acir_proofs/acir_composer.hpp" +#include "barretenberg/plonk/proof_system/proving_key/serialize.hpp" +#include "barretenberg/srs/global_crs.hpp" + +namespace bb { +extern std::string CRS_PATH; + +// Initializes without loading G1 +// TODO(https://github.com/AztecProtocol/barretenberg/issues/811) adapt for grumpkin +acir_proofs::AcirComposer verifier_init() +{ + acir_proofs::AcirComposer acir_composer(0, verbose_logging); + auto g2_data = get_bn254_g2_data(CRS_PATH); + srs::init_crs_factory({}, g2_data); + return acir_composer; +} + +std::string to_json(const std::vector& data) +{ + return format("[", join(map(data, [](auto fr) { return format("\"", fr, "\""); })), "]"); +} + +std::string vk_to_json(std::vector const& data) +{ + // We need to move vk_hash to the front... + std::vector rotated(data.begin(), data.end() - 1); + rotated.insert(rotated.begin(), data.at(data.size() - 1)); + + return format("[", join(map(rotated, [](auto fr) { return format("\"", fr, "\""); })), "]"); +} + +/** + * @brief Creates a proof for an ACIR circuit + * + * Communication: + * - stdout: The proof is written to stdout as a byte array + * - Filesystem: The proof is written to the path specified by output_path + * + * @param bytecode_path Path to the file containing the serialized circuit + * @param witness_path Path to the file containing the serialized witness + * @param output_path Path to write the proof to + * @param recursive Whether to use recursive proof generation or non-recursive + */ +void prove_ultra_plonk(const std::string& bytecode_path, + const std::string& witness_path, + const std::string& output_path, + const bool recursive) +{ + auto constraint_system = get_constraint_system(bytecode_path, /*honk_recursion=*/0); + auto witness = get_witness(witness_path); + + acir_proofs::AcirComposer acir_composer{ 0, verbose_logging }; + acir_composer.create_finalized_circuit(constraint_system, recursive, witness); + init_bn254_crs(acir_composer.get_finalized_dyadic_circuit_size()); + acir_composer.init_proving_key(); + auto proof = acir_composer.create_proof(); + + if (output_path == "-") { + write_bytes_to_stdout(proof); + vinfo("proof written to stdout"); + } else { + write_file(output_path, proof); + vinfo("proof written to: ", output_path); + } +} + +/** + * @brief Creates a proof for an ACIR circuit, outputs the proof and verification key in binary and 'field' format + * + * Communication: + * - Filesystem: The proof is written to the path specified by output_path + * + * @param bytecode_path Path to the file containing the serialized circuit + * @param witness_path Path to the file containing the serialized witness + * @param output_path Directory into which we write the proof and verification key data + * @param recursive Whether to a build SNARK friendly proof + */ +void prove_output_all_ultra_plonk(const std::string& bytecode_path, + const std::string& witness_path, + const std::string& output_path, + const bool recursive) +{ + auto constraint_system = get_constraint_system(bytecode_path, /*honk_recursion=*/0); + auto witness = get_witness(witness_path); + + acir_proofs::AcirComposer acir_composer{ 0, verbose_logging }; + acir_composer.create_finalized_circuit(constraint_system, recursive, witness); + acir_composer.finalize_circuit(); + init_bn254_crs(acir_composer.get_finalized_dyadic_circuit_size()); + acir_composer.init_proving_key(); + auto proof = acir_composer.create_proof(); + + // We have been given a directory, we will write the proof and verification key + // into the directory in both 'binary' and 'fields' formats + std::string vk_output_path = output_path + "/vk"; + std::string proof_path = output_path + "/proof"; + std::string vk_fields_output_path = output_path + "/vk_fields.json"; + std::string proof_field_path = output_path + "/proof_fields.json"; + + std::shared_ptr vk = acir_composer.init_verification_key(); + + // Write the 'binary' proof + write_file(proof_path, proof); + vinfo("proof written to: ", proof_path); + + // Write the proof as fields + auto proofAsFields = acir_composer.serialize_proof_into_fields(proof, vk->as_data().num_public_inputs); + std::string proof_json = to_json(proofAsFields); + write_file(proof_field_path, { proof_json.begin(), proof_json.end() }); + info("proof as fields written to: ", proof_field_path); + + // Write the vk as binary + auto serialized_vk = to_buffer(*vk); + write_file(vk_output_path, serialized_vk); + vinfo("vk written to: ", vk_output_path); + + // Write the vk as fields + auto data = acir_composer.serialize_verification_key_into_fields(); + std::string vk_json = vk_to_json(data); + write_file(vk_fields_output_path, { vk_json.begin(), vk_json.end() }); + vinfo("vk as fields written to: ", vk_fields_output_path); +} + +/** + * @brief Verifies a proof for an ACIR circuit + * + * Note: The fact that the proof was computed originally by parsing an ACIR circuit is not of importance + * because this method uses the verification key to verify the proof. + * + * Communication: + * - proc_exit: A boolean value is returned indicating whether the proof is valid. + * an exit code of 0 will be returned for success and 1 for failure. + * + * @param proof_path Path to the file containing the serialized proof + * @param vk_path Path to the file containing the serialized verification key + * @return true If the proof is valid + * @return false If the proof is invalid + */ +bool verify_ultra_plonk(const std::string& proof_path, const std::string& vk_path) +{ + auto acir_composer = verifier_init(); + auto vk_data = from_buffer(read_file(vk_path)); + acir_composer.load_verification_key(std::move(vk_data)); + auto verified = acir_composer.verify_proof(read_file(proof_path)); + + vinfo("verified: ", verified); + return verified; +} + +/** + * @brief Proves and verifies an ACIR circuit + * + * Communication: + * - proc_exit: A boolean value is returned indicating whether the proof is valid. + * an exit code of 0 will be returned for success and 1 for failure. + * + * @param bytecode_path Path to the file containing the serialized circuit + * @param witness_path Path to the file containing the serialized witness + * @param recursive Whether to use recursive proof generation or non-recursive + * @return true if the proof is valid + * @return false if the proof is invalid + */ +bool prove_and_verify_ultra_plonk(const std::string& bytecode_path, + const bool recursive, + const std::string& witness_path) +{ + auto constraint_system = get_constraint_system(bytecode_path, /*honk_recursion=*/0); + auto witness = get_witness(witness_path); + + acir_proofs::AcirComposer acir_composer{ 0, verbose_logging }; + acir_composer.create_finalized_circuit(constraint_system, recursive, witness); + init_bn254_crs(acir_composer.get_finalized_dyadic_circuit_size()); + + Timer pk_timer; + acir_composer.init_proving_key(); + + const std::filesystem::path current_path = std::filesystem::current_path(); + const auto current_dir = current_path.filename().string(); + write_benchmark("pk_construction_time", pk_timer.milliseconds(), "acir_test", current_dir); + + write_benchmark("gate_count", acir_composer.get_finalized_total_circuit_size(), "acir_test", current_dir); + write_benchmark("subgroup_size", acir_composer.get_finalized_dyadic_circuit_size(), "acir_test", current_dir); + + Timer proof_timer; + auto proof = acir_composer.create_proof(); + write_benchmark("proof_construction_time", proof_timer.milliseconds(), "acir_test", current_dir); + + Timer vk_timer; + acir_composer.init_verification_key(); + write_benchmark("vk_construction_time", vk_timer.milliseconds(), "acir_test", current_dir); + + auto verified = acir_composer.verify_proof(proof); + + return verified; +} + +/** + * @brief Writes a Solidity verifier contract for an ACIR circuit to a file + * + * Communication: + * - stdout: The Solidity verifier contract is written to stdout as a string + * - Filesystem: The Solidity verifier contract is written to the path specified by output_path + * + * Note: The fact that the contract was computed is for an ACIR circuit is not of importance + * because this method uses the verification key to compute the Solidity verifier contract + * + * @param output_path Path to write the contract to + * @param vk_path Path to the file containing the serialized verification key + */ +void contract_ultra_plonk(const std::string& output_path, const std::string& vk_path) +{ + auto acir_composer = verifier_init(); + auto vk_data = from_buffer(read_file(vk_path)); + acir_composer.load_verification_key(std::move(vk_data)); + auto contract = acir_composer.get_solidity_verifier(); + + if (output_path == "-") { + std::cout << contract; + vinfo("contract written to stdout"); + } else { + write_file(output_path, { contract.begin(), contract.end() }); + vinfo("contract written to: ", output_path); + } +} + +/** + * @brief Writes a verification key for an ACIR circuit to a file + * + * Communication: + * - stdout: The verification key is written to stdout as a byte array + * - Filesystem: The verification key is written to the path specified by output_path + * + * @param bytecode_path Path to the file containing the serialized circuit + * @param output_path Path to write the verification key to + * @param recursive Whether to create a SNARK friendly circuit and key + */ +void write_vk_ultra_plonk(const std::string& bytecode_path, const std::string& output_path, const bool recursive) +{ + auto constraint_system = get_constraint_system(bytecode_path, false); + acir_proofs::AcirComposer acir_composer{ 0, verbose_logging }; + acir_composer.create_finalized_circuit(constraint_system, recursive); + acir_composer.finalize_circuit(); + init_bn254_crs(acir_composer.get_finalized_dyadic_circuit_size()); + acir_composer.init_proving_key(); + auto vk = acir_composer.init_verification_key(); + auto serialized_vk = to_buffer(*vk); + if (output_path == "-") { + write_bytes_to_stdout(serialized_vk); + vinfo("vk written to stdout"); + } else { + write_file(output_path, serialized_vk); + vinfo("vk written to: ", output_path); + } +} + +void write_pk_ultra_plonk(const std::string& bytecode_path, const std::string& output_path, const bool recursive) +{ + auto constraint_system = get_constraint_system(bytecode_path, /*honk_recursion=*/0); + acir_proofs::AcirComposer acir_composer{ 0, verbose_logging }; + acir_composer.create_finalized_circuit(constraint_system, recursive); + acir_composer.finalize_circuit(); + init_bn254_crs(acir_composer.get_finalized_dyadic_circuit_size()); + auto pk = acir_composer.init_proving_key(); + auto serialized_pk = to_buffer(*pk); + + if (output_path == "-") { + write_bytes_to_stdout(serialized_pk); + vinfo("pk written to stdout"); + } else { + write_file(output_path, serialized_pk); + vinfo("pk written to: ", output_path); + } +} + +/** + * @brief Converts a proof from a byte array into a list of field elements + * + * Why is this needed? + * + * The proof computed by the non-recursive proof system is a byte array. This is fine since the proof will be + * verified either natively or in a Solidity verifier. For the recursive proof system, the proof is verified in a + * circuit where it is cheaper to work with field elements than byte arrays. This method converts the proof into a + * list of field elements which can be used in the recursive proof system. + * + * This is an optimization which unfortunately leaks through the API. The repercussions of this are that users need + * to convert proofs which are byte arrays to proofs which are lists of field elements, using the below method. + * + * Ideally, we find out what is the cost to convert this in the circuit and if it is not too expensive, we pass the + * byte array directly to the circuit and convert it there. This also applies to the `vkAsFields` method. + * + * Communication: + * - stdout: The proof as a list of field elements is written to stdout as a string + * - Filesystem: The proof as a list of field elements is written to the path specified by output_path + * + * + * @param proof_path Path to the file containing the serialized proof + * @param vk_path Path to the file containing the serialized verification key + * @param output_path Path to write the proof to + */ +void proof_as_fields(const std::string& proof_path, std::string const& vk_path, const std::string& output_path) +{ + auto acir_composer = verifier_init(); + auto vk_data = from_buffer(read_file(vk_path)); + auto data = acir_composer.serialize_proof_into_fields(read_file(proof_path), vk_data.num_public_inputs); + auto json = to_json(data); + + if (output_path == "-") { + std::cout << json; + vinfo("proof as fields written to stdout"); + } else { + write_file(output_path, { json.begin(), json.end() }); + vinfo("proof as fields written to: ", output_path); + } +} + +/** + * @brief Converts a verification key from a byte array into a list of field elements + * + * Why is this needed? + * This follows the same rationale as `proofAsFields`. + * + * Communication: + * - stdout: The verification key as a list of field elements is written to stdout as a string + * - Filesystem: The verification key as a list of field elements is written to the path specified by output_path + * + * @param vk_path Path to the file containing the serialized verification key + * @param output_path Path to write the verification key to + */ +void vk_as_fields(const std::string& vk_path, const std::string& output_path) +{ + auto acir_composer = verifier_init(); + auto vk_data = from_buffer(read_file(vk_path)); + acir_composer.load_verification_key(std::move(vk_data)); + auto data = acir_composer.serialize_verification_key_into_fields(); + + auto json = vk_to_json(data); + if (output_path == "-") { + std::cout << json; + vinfo("vk as fields written to stdout"); + } else { + write_file(output_path, { json.begin(), json.end() }); + vinfo("vk as fields written to: ", output_path); + } +} +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/api/api_ultra_plonk.hpp b/barretenberg/cpp/src/barretenberg/api/api_ultra_plonk.hpp new file mode 100644 index 000000000000..db703336db60 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/api_ultra_plonk.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +// UltraPlonk is deprecated. If it weren't then we would make this all conform to the API interface class. +namespace bb { +void prove_ultra_plonk(const std::string& bytecode_path, + const std::string& witness_path, + const std::string& output_path, + const bool recursive); +void prove_output_all_ultra_plonk(const std::string& bytecode_path, + const std::string& witness_path, + const std::string& output_path, + const bool recursive); +bool verify_ultra_plonk(const std::string& proof_path, const std::string& vk_path); +bool prove_and_verify_ultra_plonk(const std::string& bytecode_path, + const bool recursive, + const std::string& witness_path); +void contract_ultra_plonk(const std::string& output_path, const std::string& vk_path); +void write_vk_ultra_plonk(const std::string& bytecode_path, const std::string& output_path, const bool recursive); +void write_pk_ultra_plonk(const std::string& bytecode_path, const std::string& output_path, const bool recursive); +void proof_as_fields(const std::string& proof_path, std::string const& vk_path, const std::string& output_path); +void vk_as_fields(const std::string& vk_path, const std::string& output_path); +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/bb/exec_pipe.hpp b/barretenberg/cpp/src/barretenberg/api/exec_pipe.hpp similarity index 96% rename from barretenberg/cpp/src/barretenberg/bb/exec_pipe.hpp rename to barretenberg/cpp/src/barretenberg/api/exec_pipe.hpp index bd8f7cda1868..8f096671dc3e 100644 --- a/barretenberg/cpp/src/barretenberg/bb/exec_pipe.hpp +++ b/barretenberg/cpp/src/barretenberg/api/exec_pipe.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include diff --git a/barretenberg/cpp/src/barretenberg/bb/file_io.hpp b/barretenberg/cpp/src/barretenberg/api/file_io.hpp similarity index 97% rename from barretenberg/cpp/src/barretenberg/bb/file_io.hpp rename to barretenberg/cpp/src/barretenberg/api/file_io.hpp index 8771ae35a574..7a429d2babe9 100644 --- a/barretenberg/cpp/src/barretenberg/bb/file_io.hpp +++ b/barretenberg/cpp/src/barretenberg/api/file_io.hpp @@ -1,9 +1,10 @@ #pragma once -#include +#include "barretenberg/common/log.hpp" #include #include #include #include +#include #include #include #include @@ -78,4 +79,4 @@ inline void write_file(const std::string& filename, std::vector const& file.write((char*)data.data(), (std::streamsize)data.size()); file.close(); } -} \ No newline at end of file +} diff --git a/barretenberg/cpp/src/barretenberg/api/gate_count.hpp b/barretenberg/cpp/src/barretenberg/api/gate_count.hpp new file mode 100644 index 000000000000..af6c3bf3fd20 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/gate_count.hpp @@ -0,0 +1,67 @@ +#pragma once +#include "barretenberg/api/acir_format_getters.hpp" +#include "barretenberg/api/init_srs.hpp" +#include "barretenberg/constants.hpp" + +namespace bb { +/** + * @brief Computes the number of Barretenberg specific gates needed to create a proof for the specific ACIR circuit. + * + * Communication: + * - stdout: A JSON string of the number of ACIR opcodes and final backend circuit size. + * TODO(https://github.com/AztecProtocol/barretenberg/issues/1126): split this into separate Plonk and Honk functions as + * their gate count differs + * + * @param bytecode_path Path to the file containing the serialized circuit + */ +template +void gate_count(const std::string& bytecode_path, bool recursive, uint32_t honk_recursion) +{ + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1180): Try to only do this when necessary. + init_grumpkin_crs(1 << CONST_ECCVM_LOG_N); + + // All circuit reports will be built into the string below + std::string functions_string = "{\"functions\": [\n "; + auto constraint_systems = get_constraint_systems(bytecode_path, honk_recursion); + + const acir_format::ProgramMetadata metadata{ .recursive = recursive, + .honk_recursion = honk_recursion, + .collect_gates_per_opcode = true }; + size_t i = 0; + for (const auto& constraint_system : constraint_systems) { + acir_format::AcirProgram program{ constraint_system }; + auto builder = acir_format::create_circuit(program, metadata); + builder.finalize_circuit(/*ensure_nonzero=*/true); + size_t circuit_size = builder.num_gates; + vinfo("Calculated circuit size in gate_count: ", circuit_size); + + // Build individual circuit report + std::string gates_per_opcode_str; + for (size_t j = 0; j < program.constraints.gates_per_opcode.size(); j++) { + gates_per_opcode_str += std::to_string(program.constraints.gates_per_opcode[j]); + if (j != program.constraints.gates_per_opcode.size() - 1) { + gates_per_opcode_str += ","; + } + } + + auto result_string = format("{\n \"acir_opcodes\": ", + program.constraints.num_acir_opcodes, + ",\n \"circuit_size\": ", + circuit_size, + ",\n \"gates_per_opcode\": [", + gates_per_opcode_str, + "]\n }"); + + // Attach a comma if there are more circuit reports to generate + if (i != (constraint_systems.size() - 1)) { + result_string = format(result_string, ","); + } + + functions_string = format(functions_string, result_string); + + i++; + } + std::cout << format(functions_string, "\n]}"); +} + +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/bb/get_bn254_crs.cpp b/barretenberg/cpp/src/barretenberg/api/get_bn254_crs.cpp similarity index 98% rename from barretenberg/cpp/src/barretenberg/bb/get_bn254_crs.cpp rename to barretenberg/cpp/src/barretenberg/api/get_bn254_crs.cpp index e0eb99beb050..f00c2218b69b 100644 --- a/barretenberg/cpp/src/barretenberg/bb/get_bn254_crs.cpp +++ b/barretenberg/cpp/src/barretenberg/api/get_bn254_crs.cpp @@ -1,5 +1,5 @@ #include "get_bn254_crs.hpp" -#include "barretenberg/bb/file_io.hpp" +#include "barretenberg/api/file_io.hpp" namespace { std::vector download_bn254_g1_data(size_t num_points) diff --git a/barretenberg/cpp/src/barretenberg/bb/get_bn254_crs.hpp b/barretenberg/cpp/src/barretenberg/api/get_bn254_crs.hpp similarity index 100% rename from barretenberg/cpp/src/barretenberg/bb/get_bn254_crs.hpp rename to barretenberg/cpp/src/barretenberg/api/get_bn254_crs.hpp diff --git a/barretenberg/cpp/src/barretenberg/bb/get_bytecode.hpp b/barretenberg/cpp/src/barretenberg/api/get_bytecode.hpp similarity index 100% rename from barretenberg/cpp/src/barretenberg/bb/get_bytecode.hpp rename to barretenberg/cpp/src/barretenberg/api/get_bytecode.hpp diff --git a/barretenberg/cpp/src/barretenberg/bb/get_grumpkin_crs.cpp b/barretenberg/cpp/src/barretenberg/api/get_grumpkin_crs.cpp similarity index 100% rename from barretenberg/cpp/src/barretenberg/bb/get_grumpkin_crs.cpp rename to barretenberg/cpp/src/barretenberg/api/get_grumpkin_crs.cpp diff --git a/barretenberg/cpp/src/barretenberg/bb/get_grumpkin_crs.hpp b/barretenberg/cpp/src/barretenberg/api/get_grumpkin_crs.hpp similarity index 100% rename from barretenberg/cpp/src/barretenberg/bb/get_grumpkin_crs.hpp rename to barretenberg/cpp/src/barretenberg/api/get_grumpkin_crs.hpp diff --git a/barretenberg/cpp/src/barretenberg/bb/init_srs.hpp b/barretenberg/cpp/src/barretenberg/api/init_srs.cpp similarity index 92% rename from barretenberg/cpp/src/barretenberg/bb/init_srs.hpp rename to barretenberg/cpp/src/barretenberg/api/init_srs.cpp index 8d8780f251ee..9c36aafcd188 100644 --- a/barretenberg/cpp/src/barretenberg/bb/init_srs.hpp +++ b/barretenberg/cpp/src/barretenberg/api/init_srs.cpp @@ -1,15 +1,17 @@ +#include "init_srs.hpp" +#include "barretenberg/srs/global_crs.hpp" #include "get_bn254_crs.hpp" #include "get_grumpkin_crs.hpp" namespace bb { +std::string CRS_PATH = getHomeDir() + "/.bb-crs"; + std::string getHomeDir() { char* home = std::getenv("HOME"); return home != nullptr ? std::string(home) : "./"; } -std::string CRS_PATH = getHomeDir() + "/.bb-crs"; - /** * @brief Initialize the global crs_factory for bn254 based on a known dyadic circuit size * @@ -34,4 +36,4 @@ void init_grumpkin_crs(size_t eccvm_dyadic_circuit_size) auto grumpkin_g1_data = get_grumpkin_g1_data(CRS_PATH, eccvm_dyadic_circuit_size + 1); srs::init_grumpkin_crs_factory(grumpkin_g1_data); } -} // namespace bb \ No newline at end of file +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/api/init_srs.hpp b/barretenberg/cpp/src/barretenberg/api/init_srs.hpp new file mode 100644 index 000000000000..1c278a317db2 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/init_srs.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#include + +namespace bb { +std::string getHomeDir(); + +/** + * @brief Initialize the global crs_factory for bn254 based on a known dyadic circuit size + * + * @param dyadic_circuit_size power-of-2 circuit size + */ +void init_bn254_crs(size_t dyadic_circuit_size); + +/** + * @brief Initialize the global crs_factory for grumpkin based on a known dyadic circuit size + * @details Grumpkin crs is required only for the ECCVM + * + * @param dyadic_circuit_size power-of-2 circuit size + */ +void init_grumpkin_crs(size_t eccvm_dyadic_circuit_size); +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/bb/log.hpp b/barretenberg/cpp/src/barretenberg/api/log.hpp similarity index 78% rename from barretenberg/cpp/src/barretenberg/bb/log.hpp rename to barretenberg/cpp/src/barretenberg/api/log.hpp index d7c5f082945f..4d8927a7ef07 100644 --- a/barretenberg/cpp/src/barretenberg/bb/log.hpp +++ b/barretenberg/cpp/src/barretenberg/api/log.hpp @@ -18,7 +18,7 @@ * * @param data The raw bytes that we want to write to stdout */ -inline void writeRawBytesToStdout(const std::vector& data) +inline void write_bytes_to_stdout(const std::vector& data) { for (auto byte : data) { // Safety: a byte and a char occupy one byte @@ -43,17 +43,5 @@ inline void writeUint64AsRawBytesToStdout(uint64_t value) value >>= 8; } - writeRawBytesToStdout(bytes); + write_bytes_to_stdout(bytes); } - -/** - * @brief Writes a sting to stdout - * - * @param str The raw string to write to stdout - */ -inline void writeStringToStdout(const std::string& str) -{ - for (char ch : str) { - std::cout.put(ch); - } -} \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/api/prove_tube.cpp b/barretenberg/cpp/src/barretenberg/api/prove_tube.cpp new file mode 100644 index 000000000000..14c9103d79b1 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/prove_tube.cpp @@ -0,0 +1,109 @@ +#include "prove_tube.hpp" +#include "barretenberg/api/file_io.hpp" +#include "barretenberg/api/init_srs.hpp" +#include "barretenberg/common/map.hpp" +#include "barretenberg/stdlib/client_ivc_verifier/client_ivc_recursive_verifier.hpp" + +namespace bb { +/** + * @brief Creates a Honk Proof for the Tube circuit responsible for recursively verifying a ClientIVC proof. + * + * @param output_path the working directory from which the proof and verification data are read + * @param num_unused_public_inputs + */ +void prove_tube(const std::string& output_path) +{ + using namespace stdlib::recursion::honk; + + using Builder = UltraCircuitBuilder; + using GrumpkinVk = bb::VerifierCommitmentKey; + + std::string vkPath = output_path + "/vk"; + std::string proof_path = output_path + "/proof"; + + // Note: this could be decreased once we optimise the size of the ClientIVC recursive verifier + init_bn254_crs(1 << 25); + init_grumpkin_crs(1 << 18); + + // Read the proof and verification data from given files + auto proof = from_buffer(read_file(proof_path)); + auto vk = from_buffer(read_file(vkPath)); + + // We don't serialise and deserialise the Grumkin SRS so initialise with circuit_size + 1 to be able to recursively + // verify IPA. The + 1 is to satisfy IPA verification key requirements. + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1025) + vk.eccvm->pcs_verification_key = std::make_shared(vk.eccvm->circuit_size + 1); + + auto builder = std::make_shared(); + + // Preserve the public inputs that should be passed to the base rollup by making them public inputs to the tube + // circuit + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1048): INSECURE - make this tube proof actually use + // these public inputs by turning proof into witnesses and calling set_public on each witness + auto num_inner_public_inputs = static_cast(static_cast(proof.mega_proof[1])); + num_inner_public_inputs -= bb::PAIRING_POINT_ACCUMULATOR_SIZE; // don't add the agg object + + for (size_t i = 0; i < num_inner_public_inputs; i++) { + auto offset = bb::HONK_PROOF_PUBLIC_INPUT_OFFSET; + builder->add_public_variable(proof.mega_proof[i + offset]); + } + ClientIVCRecursiveVerifier verifier{ builder, vk }; + + ClientIVCRecursiveVerifier::Output client_ivc_rec_verifier_output = verifier.verify(proof); + + PairingPointAccumulatorIndices current_aggregation_object = + stdlib::recursion::init_default_agg_obj_indices(*builder); + + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1069): Add aggregation to goblin recursive verifiers. + // This is currently just setting the aggregation object to the default one. + builder->add_pairing_point_accumulator(current_aggregation_object); + + // The tube only calls an IPA recursive verifier once, so we can just add this IPA claim and proof + builder->add_ipa_claim(client_ivc_rec_verifier_output.opening_claim.get_witness_indices()); + builder->ipa_proof = convert_stdlib_proof_to_native(client_ivc_rec_verifier_output.ipa_transcript->proof_data); + ASSERT(builder->ipa_proof.size() && "IPA proof should not be empty"); + + using Prover = UltraProver_; + using Verifier = UltraVerifier_; + Prover tube_prover{ *builder }; + auto tube_proof = tube_prover.construct_proof(); + std::string tubeProofPath = output_path + "/proof"; + write_file(tubeProofPath, to_buffer(tube_proof)); + + std::string tubeProofAsFieldsPath = output_path + "/proof_fields.json"; + const auto to_json = [](const std::vector& data) { + return format("[", join(map(data, [](auto fr) { return format("\"", fr, "\""); })), "]"); + }; + auto proof_data = to_json(tube_proof); + write_file(tubeProofAsFieldsPath, { proof_data.begin(), proof_data.end() }); + + std::string tubeVkPath = output_path + "/vk"; + auto tube_verification_key = + std::make_shared(tube_prover.proving_key->proving_key); + write_file(tubeVkPath, to_buffer(tube_verification_key)); + + std::string tubeAsFieldsVkPath = output_path + "/vk_fields.json"; + auto field_els = tube_verification_key->to_field_elements(); + info("verificaton key length in fields:", field_els.size()); + auto data = to_json(field_els); + write_file(tubeAsFieldsVkPath, { data.begin(), data.end() }); + + info("Native verification of the tube_proof"); + auto ipa_verification_key = std::make_shared>(1 << CONST_ECCVM_LOG_N); + Verifier tube_verifier(tube_verification_key, ipa_verification_key); + + // Break up the tube proof into the honk portion and the ipa portion + const size_t HONK_PROOF_LENGTH_WITHOUT_INNER_PUB_INPUTS = + UltraRollupFlavor::PROOF_LENGTH_WITHOUT_PUB_INPUTS + PAIRING_POINT_ACCUMULATOR_SIZE + IPA_CLAIM_SIZE; + // The extra calculation is for the IPA proof length. + ASSERT(tube_proof.size() == HONK_PROOF_LENGTH_WITHOUT_INNER_PUB_INPUTS + num_inner_public_inputs); + // split out the ipa proof + const std::ptrdiff_t honk_proof_with_pub_inputs_length = static_cast( + HONK_PROOF_LENGTH_WITHOUT_INNER_PUB_INPUTS - IPA_PROOF_LENGTH + num_inner_public_inputs); + auto ipa_proof = HonkProof(tube_proof.begin() + honk_proof_with_pub_inputs_length, tube_proof.end()); + auto tube_honk_proof = HonkProof(tube_proof.begin(), tube_proof.end() + honk_proof_with_pub_inputs_length); + bool verified = tube_verifier.verify_proof(tube_honk_proof, ipa_proof); + info("Tube proof verification: ", verified); +} + +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/api/prove_tube.hpp b/barretenberg/cpp/src/barretenberg/api/prove_tube.hpp new file mode 100644 index 000000000000..3093123f9527 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/prove_tube.hpp @@ -0,0 +1,13 @@ +#pragma once +#include + +namespace bb { +/** + * @brief Creates a Honk Proof for the Tube circuit responsible for recursively verifying a ClientIVC proof. + * + * @param output_path the working directory from which the proof and verification data are read + * @param num_unused_public_inputs + */ +void prove_tube(const std::string& output_path); + +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/api/write_prover_output.hpp b/barretenberg/cpp/src/barretenberg/api/write_prover_output.hpp new file mode 100644 index 000000000000..d3193e469080 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/api/write_prover_output.hpp @@ -0,0 +1,131 @@ +#pragma once +#include "barretenberg/common/container.hpp" +#include "barretenberg/common/log.hpp" +#include "barretenberg/common/map.hpp" +#include "barretenberg/ecc/curves/bn254/fr.hpp" +#include "barretenberg/honk/proof_system/types/proof.hpp" +#include + +namespace bb { + +template struct ProofAndKey { + HonkProof proof; + std::shared_ptr key; +}; + +template +void write(const ProverOutput& prover_output, + const std::string& output_data_type, + const std::string& output_content, + const std::filesystem::path& output_dir) +{ + enum class ObjectToWrite : size_t { PROOF, VK }; + const bool output_to_stdout = output_dir == "-"; + + const auto to_json = [](const std::vector& data) { + return format("[", join(map(data, [](auto fr) { return format("\"", fr, "\""); })), "]"); + }; + + const auto write_bytes = [&](const ObjectToWrite& obj) { + switch (obj) { + case ObjectToWrite::PROOF: { + info("case ObjectToWrite::PROOF: "); + const auto buf = to_buffer(prover_output.proof); + if (output_to_stdout) { + write_bytes_to_stdout(buf); + } else { + write_file(output_dir / "proof", buf); + } + break; + } + case ObjectToWrite::VK: { + info("case ObjectToWrite::VK: "); + const auto buf = to_buffer(prover_output.key); + if (output_to_stdout) { + write_bytes_to_stdout(buf); + } else { + write_file(output_dir / "vk", buf); + } + break; + } + } + }; + + const auto write_fields = [&](const ObjectToWrite& obj) { + switch (obj) { + case ObjectToWrite::PROOF: { + info("case ObjectToWrite::PROOF: "); + const std::string proof_json = to_json(prover_output.proof); + if (output_to_stdout) { + std::cout << proof_json; + } else { + info("writing proof as fields to ", output_dir / "proof_fields.json"); + write_file(output_dir / "proof_fields.json", { proof_json.begin(), proof_json.end() }); + } + break; + } + case ObjectToWrite::VK: { + info("case ObjectToWrite::VK: "); + const std::string vk_json = to_json(prover_output.key->to_field_elements()); + if (output_to_stdout) { + std::cout << vk_json; + } else { + info("writing vk as fields to ", output_dir / "vk_fields.json"); + write_file(output_dir / "vk_fields.json", { vk_json.begin(), vk_json.end() }); + } + break; + } + } + }; + + if (output_content == "proof") { + if (output_data_type == "bytes") { + info("case bytes: "); + write_bytes(ObjectToWrite::PROOF); + } else if (output_data_type == "fields") { + info("case fields: "); + write_fields(ObjectToWrite::PROOF); + } else if (output_data_type == "bytes_and_fields") { + info("case bytes_and_fields: "); + write_bytes(ObjectToWrite::PROOF); + write_fields(ObjectToWrite::PROOF); + } else { + ASSERT("Invalid std::string for PROOF"); + } + } else if (output_content == "vk") { + if (output_data_type == "bytes") { + info("case bytes: "); + write_bytes(ObjectToWrite::VK); + } else if (output_data_type == "fields") { + info("case fields: "); + write_fields(ObjectToWrite::VK); + } else if (output_data_type == "bytes_and_fields") { + info("case bytes_and_fields: "); + write_bytes(ObjectToWrite::VK); + write_fields(ObjectToWrite::VK); + } else { + ASSERT("Invalid std::string for VK"); + } + } else if (output_content == "proof_and_vk") { + if (output_data_type == "bytes") { + info("case bytes: "); + write_bytes(ObjectToWrite::PROOF); + write_bytes(ObjectToWrite::VK); + } else if (output_data_type == "fields") { + info("case fields: "); + write_fields(ObjectToWrite::PROOF); + write_fields(ObjectToWrite::VK); + } else if (output_data_type == "bytes_and_fields") { + info("case bytes_and_fields: "); + write_bytes(ObjectToWrite::PROOF); + write_fields(ObjectToWrite::PROOF); + write_bytes(ObjectToWrite::VK); + write_fields(ObjectToWrite::VK); + } else { + throw_or_abort("Invalid std::string for PROOF_AND_VK"); + } + } else { + throw_or_abort("Invalid std::string"); + } +} +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/bb/CLI11.hpp b/barretenberg/cpp/src/barretenberg/bb/CLI11.hpp new file mode 100644 index 000000000000..902ba4fa3339 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/bb/CLI11.hpp @@ -0,0 +1,11000 @@ +// clang-format off + +// CLI11: Version 2.4.2 +// Originally designed by Henry Schreiner +// https://github.com/CLIUtils/CLI11 +// +// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts +// from: v2.4.2 +// +// CLI11 2.4.2 Copyright (c) 2017-2024 University of Cincinnati, developed by Henry +// Schreiner under NSF AWARD 1414736. All rights reserved. +// +// Redistribution and use in source and binary forms of CLI11, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +// Standard combined includes: +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define CLI11_VERSION_MAJOR 2 +#define CLI11_VERSION_MINOR 4 +#define CLI11_VERSION_PATCH 2 +#define CLI11_VERSION "2.4.2" + + + + +// The following version macro is very similar to the one in pybind11 +#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) +#if __cplusplus >= 201402L +#define CLI11_CPP14 +#if __cplusplus >= 201703L +#define CLI11_CPP17 +#if __cplusplus > 201703L +#define CLI11_CPP20 +#endif +#endif +#endif +#elif defined(_MSC_VER) && __cplusplus == 199711L +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) +// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer +#if _MSVC_LANG >= 201402L +#define CLI11_CPP14 +#if _MSVC_LANG > 201402L && _MSC_VER >= 1910 +#define CLI11_CPP17 +#if _MSVC_LANG > 201703L && _MSC_VER >= 1910 +#define CLI11_CPP20 +#endif +#endif +#endif +#endif + +#if defined(CLI11_CPP14) +#define CLI11_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(_MSC_VER) +#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) +#endif + +// GCC < 10 doesn't ignore this in unevaluated contexts +#if !defined(CLI11_CPP17) || \ + (defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 10 && __GNUC__ > 4) +#define CLI11_NODISCARD +#else +#define CLI11_NODISCARD [[nodiscard]] +#endif + +/** detection of rtti */ +#ifndef CLI11_USE_STATIC_RTTI +#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI) +#define CLI11_USE_STATIC_RTTI 1 +#elif defined(__cpp_rtti) +#if(defined(_CPPRTTI) && _CPPRTTI == 0) +#define CLI11_USE_STATIC_RTTI 1 +#else +#define CLI11_USE_STATIC_RTTI 0 +#endif +#elif(defined(__GCC_RTTI) && __GXX_RTTI) +#define CLI11_USE_STATIC_RTTI 0 +#else +#define CLI11_USE_STATIC_RTTI 1 +#endif +#endif + +/** availability */ +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM +#if __has_include() +// Filesystem cannot be used if targeting macOS < 10.15 +#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 +#define CLI11_HAS_FILESYSTEM 0 +#elif defined(__wasi__) +// As of wasi-sdk-14, filesystem is not implemented +#define CLI11_HAS_FILESYSTEM 0 +#else +#include +#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 +#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 +#define CLI11_HAS_FILESYSTEM 1 +#elif defined(__GLIBCXX__) +// if we are using gcc and Version <9 default to no filesystem +#define CLI11_HAS_FILESYSTEM 0 +#else +#define CLI11_HAS_FILESYSTEM 1 +#endif +#else +#define CLI11_HAS_FILESYSTEM 0 +#endif +#endif +#endif +#endif + +/** availability */ +#if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5 +#define CLI11_HAS_CODECVT 0 +#else +#define CLI11_HAS_CODECVT 1 +#include +#endif + +/** disable deprecations */ +#if defined(__GNUC__) // GCC or clang +#define CLI11_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") +#define CLI11_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") + +#elif defined(_MSC_VER) +#define CLI11_DIAGNOSTIC_PUSH __pragma(warning(push)) +#define CLI11_DIAGNOSTIC_POP __pragma(warning(pop)) + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED __pragma(warning(disable : 4996)) + +#else +#define CLI11_DIAGNOSTIC_PUSH +#define CLI11_DIAGNOSTIC_POP + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +#endif + +/** Inline macro **/ +#ifdef CLI11_COMPILE +#define CLI11_INLINE +#else +#define CLI11_INLINE inline +#endif + + + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include // NOLINT(build/include) +#else +#include +#include +#endif + + + + +#ifdef CLI11_CPP17 +#include +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include +#include // NOLINT(build/include) +#endif // CLI11_HAS_FILESYSTEM + + + +#if defined(_WIN32) +#if !(defined(_AMD64_) || defined(_X86_) || defined(_ARM_)) +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || \ + defined(_M_AMD64) +#define _AMD64_ +#elif defined(i386) || defined(__i386) || defined(__i386__) || defined(__i386__) || defined(_M_IX86) +#define _X86_ +#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARMT) +#define _ARM_ +#elif defined(__aarch64__) || defined(_M_ARM64) +#define _ARM64_ +#elif defined(_M_ARM64EC) +#define _ARM64EC_ +#endif +#endif + +// first +#ifndef NOMINMAX +// if NOMINMAX is already defined we don't want to mess with that either way +#define NOMINMAX +#include +#undef NOMINMAX +#else +#include +#endif + +// second +#include +// third +#include +#include +#endif + + +namespace CLI { + + +/// Convert a wide string to a narrow string. +CLI11_INLINE std::string narrow(const std::wstring &str); +CLI11_INLINE std::string narrow(const wchar_t *str); +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t size); + +/// Convert a narrow string to a wide string. +CLI11_INLINE std::wstring widen(const std::string &str); +CLI11_INLINE std::wstring widen(const char *str); +CLI11_INLINE std::wstring widen(const char *str, std::size_t size); + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str); +CLI11_INLINE std::wstring widen(std::string_view str); +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +/// Convert a char-string to a native path correctly. +CLI11_INLINE std::filesystem::path to_path(std::string_view str); +#endif // CLI11_HAS_FILESYSTEM + + + + +namespace detail { + +#if !CLI11_HAS_CODECVT +/// Attempt to set one of the acceptable unicode locales for conversion +CLI11_INLINE void set_unicode_locale() { + static const std::array unicode_locales{{"C.UTF-8", "en_US.UTF-8", ".UTF-8"}}; + + for(const auto &locale_name : unicode_locales) { + if(std::setlocale(LC_ALL, locale_name) != nullptr) { + return; + } + } + throw std::runtime_error("CLI::narrow: could not set locale to C.UTF-8"); +} + +template struct scope_guard_t { + F closure; + + explicit scope_guard_t(F closure_) : closure(closure_) {} + ~scope_guard_t() { closure(); } +}; + +template CLI11_NODISCARD CLI11_INLINE scope_guard_t scope_guard(F &&closure) { + return scope_guard_t{std::forward(closure)}; +} + +#endif // !CLI11_HAS_CODECVT + +CLI11_DIAGNOSTIC_PUSH +CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +CLI11_INLINE std::string narrow_impl(const wchar_t *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().to_bytes(str, str + str_size); + +#else + return std::wstring_convert>().to_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const wchar_t *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::wcsrtombs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::narrow: conversion error in std::wcsrtombs at offset " + + std::to_string(it - str)); + } + std::string result(new_size, '\0'); + std::wcsrtombs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_INLINE std::wstring widen_impl(const char *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().from_bytes(str, str + str_size); + +#else + return std::wstring_convert>().from_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const char *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::mbsrtowcs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::widen: conversion error in std::mbsrtowcs at offset " + + std::to_string(it - str)); + } + std::wstring result(new_size, L'\0'); + std::mbsrtowcs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_DIAGNOSTIC_POP + +} // namespace detail + +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t str_size) { return detail::narrow_impl(str, str_size); } +CLI11_INLINE std::string narrow(const std::wstring &str) { return detail::narrow_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::string narrow(const wchar_t *str) { return detail::narrow_impl(str, std::wcslen(str)); } + +CLI11_INLINE std::wstring widen(const char *str, std::size_t str_size) { return detail::widen_impl(str, str_size); } +CLI11_INLINE std::wstring widen(const std::string &str) { return detail::widen_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::wstring widen(const char *str) { return detail::widen_impl(str, std::strlen(str)); } + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str) { return detail::narrow_impl(str.data(), str.size()); } +CLI11_INLINE std::wstring widen(std::string_view str) { return detail::widen_impl(str.data(), str.size()); } +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE std::filesystem::path to_path(std::string_view str) { + return std::filesystem::path{ +#ifdef _WIN32 + widen(str) +#else + str +#endif // _WIN32 + }; +} +#endif // CLI11_HAS_FILESYSTEM + + + + +namespace detail { +#ifdef _WIN32 +/// Decode and return UTF-8 argv from GetCommandLineW. +CLI11_INLINE std::vector compute_win32_argv(); +#endif +} // namespace detail + + + +namespace detail { + +#ifdef _WIN32 +CLI11_INLINE std::vector compute_win32_argv() { + std::vector result; + int argc = 0; + + auto deleter = [](wchar_t **ptr) { LocalFree(ptr); }; + // NOLINTBEGIN(*-avoid-c-arrays) + auto wargv = std::unique_ptr(CommandLineToArgvW(GetCommandLineW(), &argc), deleter); + // NOLINTEND(*-avoid-c-arrays) + + if(wargv == nullptr) { + throw std::runtime_error("CommandLineToArgvW failed with code " + std::to_string(GetLastError())); + } + + result.reserve(static_cast(argc)); + for(size_t i = 0; i < static_cast(argc); ++i) { + result.push_back(narrow(wargv[i])); + } + + return result; +} +#endif + +} // namespace detail + + + + +/// Include the items in this namespace to get free conversion of enums to/from streams. +/// (This is available inside CLI as well, so CLI11 will use this without a using statement). +namespace enums { + +/// output streaming for enumerations +template ::value>::type> +std::ostream &operator<<(std::ostream &in, const T &item) { + // make sure this is out of the detail namespace otherwise it won't be found when needed + return in << static_cast::type>(item); +} + +} // namespace enums + +/// Export to CLI namespace +using enums::operator<<; + +namespace detail { +/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not +/// produce overflow for some expected uses +constexpr int expected_max_vector_size{1 << 29}; +// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c +/// Split a string by a delim +CLI11_INLINE std::vector split(const std::string &s, char delim); + +/// Simple function to join a string +template std::string join(const T &v, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + if(beg != end) + s << *beg++; + while(beg != end) { + s << delim << *beg++; + } + return s.str(); +} + +/// Simple function to join a string from processed elements +template ::value>::type> +std::string join(const T &v, Callable func, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + auto loc = s.tellp(); + while(beg != end) { + auto nloc = s.tellp(); + if(nloc > loc) { + s << delim; + loc = nloc; + } + s << func(*beg++); + } + return s.str(); +} + +/// Join a string in reverse order +template std::string rjoin(const T &v, std::string delim = ",") { + std::ostringstream s; + for(std::size_t start = 0; start < v.size(); start++) { + if(start > 0) + s << delim; + s << v[v.size() - start - 1]; + } + return s.str(); +} + +// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string + +/// Trim whitespace from left of string +CLI11_INLINE std::string <rim(std::string &str); + +/// Trim anything from left of string +CLI11_INLINE std::string <rim(std::string &str, const std::string &filter); + +/// Trim whitespace from right of string +CLI11_INLINE std::string &rtrim(std::string &str); + +/// Trim anything from right of string +CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter); + +/// Trim whitespace from string +inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } + +/// Trim anything from string +inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } + +/// Make a copy of the string and then trim it +inline std::string trim_copy(const std::string &str) { + std::string s = str; + return trim(s); +} + +/// remove quotes at the front and back of a string either '"' or '\'' +CLI11_INLINE std::string &remove_quotes(std::string &str); + +/// remove quotes from all elements of a string vector and process escaped components +CLI11_INLINE void remove_quotes(std::vector &args); + +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input); + +/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) +inline std::string trim_copy(const std::string &str, const std::string &filter) { + std::string s = str; + return trim(s, filter); +} +/// Print a two part "help" string +CLI11_INLINE std::ostream & +format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid); + +/// Print subcommand aliases +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid); + +/// Verify the first character of an option +/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with +template bool valid_first_char(T c) { + return ((c != '-') && (static_cast(c) > 33)); // space and '!' not allowed +} + +/// Verify following characters of an option +template bool valid_later_char(T c) { + // = and : are value separators, { has special meaning for option defaults, + // and control codes other than tab would just be annoying to deal with in many places allowing space here has too + // much potential for inadvertent entry errors and bugs + return ((c != '=') && (c != ':') && (c != '{') && ((static_cast(c) > 32) || c == '\t')); +} + +/// Verify an option/subcommand name +CLI11_INLINE bool valid_name_string(const std::string &str); + +/// Verify an app name +inline bool valid_alias_name_string(const std::string &str) { + static const std::string badChars(std::string("\n") + '\0'); + return (str.find_first_of(badChars) == std::string::npos); +} + +/// check if a string is a container segment separator (empty or "%%") +inline bool is_separator(const std::string &str) { + static const std::string sep("%%"); + return (str.empty() || str == sep); +} + +/// Verify that str consists of letters only +inline bool isalpha(const std::string &str) { + return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); +} + +/// Return a lower case version of a string +inline std::string to_lower(std::string str) { + std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { + return std::tolower(x, std::locale()); + }); + return str; +} + +/// remove underscores from a string +inline std::string remove_underscore(std::string str) { + str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); + return str; +} + +/// Find and replace a substring with another substring +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to); + +/// check if the flag definitions has possible false flags +inline bool has_default_flag_values(const std::string &flags) { + return (flags.find_first_of("{!") != std::string::npos); +} + +CLI11_INLINE void remove_default_flag_values(std::string &flags); + +/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores +CLI11_INLINE std::ptrdiff_t find_member(std::string name, + const std::vector names, + bool ignore_case = false, + bool ignore_underscore = false); + +/// Find a trigger string and call a modify callable function that takes the current string and starting position of the +/// trigger and returns the position in the string to search for the next trigger string +template inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { + std::size_t start_pos = 0; + while((start_pos = str.find(trigger, start_pos)) != std::string::npos) { + start_pos = modify(str, start_pos); + } + return str; +} + +/// close a sequence of characters indicated by a closure character. Brackets allows sub sequences +/// recognized bracket sequences include "'`[(<{ other closure characters are assumed to be literal strings +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char); + +/// Split a string '"one two" "three"' into 'one two', 'three' +/// Quote characters can be ` ' or " or bracket characters [{(< with matching to the matching bracket +CLI11_INLINE std::vector split_up(std::string str, char delimiter = '\0'); + +/// get the value of an environmental variable or empty string if empty +CLI11_INLINE std::string get_environment_value(const std::string &env_name); + +/// This function detects an equal or colon followed by an escaped quote after an argument +/// then modifies the string to replace the equality with a space. This is needed +/// to allow the split up function to work properly and is intended to be used with the find_and_modify function +/// the return value is the offset+1 which is required by the find_and_modify function. +CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset); + +/// @brief detect if a string has escapable characters +/// @param str the string to do the detection on +/// @return true if the string has escapable characters +CLI11_INLINE bool has_escapable_character(const std::string &str); + +/// @brief escape all escapable characters +/// @param str the string to escape +/// @return a string with the escapble characters escaped with '\' +CLI11_INLINE std::string add_escaped_characters(const std::string &str); + +/// @brief replace the escaped characters with their equivalent +CLI11_INLINE std::string remove_escaped_characters(const std::string &str); + +/// generate a string with all non printable characters escaped to hex codes +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape); + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string); + +/// extract an escaped binary_string +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string); + +/// process a quoted string, remove the quotes and if appropriate handle escaped characters +CLI11_INLINE bool process_quoted_string(std::string &str, char string_char = '\"', char literal_char = '\''); + +} // namespace detail + + + + +namespace detail { +CLI11_INLINE std::vector split(const std::string &s, char delim) { + std::vector elems; + // Check to see if empty string, give consistent result + if(s.empty()) { + elems.emplace_back(); + } else { + std::stringstream ss; + ss.str(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); + } + } + return elems; +} + +CLI11_INLINE std::string <rim(std::string &str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(str.begin(), it); + return str; +} + +CLI11_INLINE std::string <rim(std::string &str, const std::string &filter) { + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(str.begin(), it); + return str; +} + +CLI11_INLINE std::string &rtrim(std::string &str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(it.base(), str.end()); + return str; +} + +CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter) { + auto it = + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(it.base(), str.end()); + return str; +} + +CLI11_INLINE std::string &remove_quotes(std::string &str) { + if(str.length() > 1 && (str.front() == '"' || str.front() == '\'' || str.front() == '`')) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +CLI11_INLINE std::string &remove_outer(std::string &str, char key) { + if(str.length() > 1 && (str.front() == key)) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find('\n', n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + +CLI11_INLINE std::ostream & +format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) { + name = " " + name; + out << std::setw(static_cast(wid)) << std::left << name; + if(!description.empty()) { + if(name.length() >= wid) + out << "\n" << std::setw(static_cast(wid)) << ""; + for(const char c : description) { + out.put(c); + if(c == '\n') { + out << std::setw(static_cast(wid)) << ""; + } + } + } + out << "\n"; + return out; +} + +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid) { + if(!aliases.empty()) { + out << std::setw(static_cast(wid)) << " aliases: "; + bool front = true; + for(const auto &alias : aliases) { + if(!front) { + out << ", "; + } else { + front = false; + } + out << detail::fix_newlines(" ", alias); + } + out << "\n"; + } + return out; +} + +CLI11_INLINE bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) { + return false; + } + auto e = str.end(); + for(auto c = str.begin() + 1; c != e; ++c) + if(!valid_later_char(*c)) + return false; + return true; +} + +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to) { + + std::size_t start_pos = 0; + + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; +} + +CLI11_INLINE void remove_default_flag_values(std::string &flags) { + auto loc = flags.find_first_of('{', 2); + while(loc != std::string::npos) { + auto finish = flags.find_first_of("},", loc + 1); + if((finish != std::string::npos) && (flags[finish] == '}')) { + flags.erase(flags.begin() + static_cast(loc), + flags.begin() + static_cast(finish) + 1); + } + loc = flags.find_first_of('{', loc + 1); + } + flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end()); +} + +CLI11_INLINE std::ptrdiff_t +find_member(std::string name, const std::vector names, bool ignore_case, bool ignore_underscore) { + auto it = std::end(names); + if(ignore_case) { + if(ignore_underscore) { + name = detail::to_lower(detail::remove_underscore(name)); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(detail::remove_underscore(local_name)) == name; + }); + } else { + name = detail::to_lower(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(local_name) == name; + }); + } + + } else if(ignore_underscore) { + name = detail::remove_underscore(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::remove_underscore(local_name) == name; + }); + } else { + it = std::find(std::begin(names), std::end(names), name); + } + + return (it != std::end(names)) ? (it - std::begin(names)) : (-1); +} + +static const std::string escapedChars("\b\t\n\f\r\"\\"); +static const std::string escapedCharsCode("btnfr\"\\"); +static const std::string bracketChars{"\"'`[(<{"}; +static const std::string matchBracketChars("\"'`])>}"); + +CLI11_INLINE bool has_escapable_character(const std::string &str) { + return (str.find_first_of(escapedChars) != std::string::npos); +} + +CLI11_INLINE std::string add_escaped_characters(const std::string &str) { + std::string out; + out.reserve(str.size() + 4); + for(char s : str) { + auto sloc = escapedChars.find_first_of(s); + if(sloc != std::string::npos) { + out.push_back('\\'); + out.push_back(escapedCharsCode[sloc]); + } else { + out.push_back(s); + } + } + return out; +} + +CLI11_INLINE std::uint32_t hexConvert(char hc) { + int hcode{0}; + if(hc >= '0' && hc <= '9') { + hcode = (hc - '0'); + } else if(hc >= 'A' && hc <= 'F') { + hcode = (hc - 'A' + 10); + } else if(hc >= 'a' && hc <= 'f') { + hcode = (hc - 'a' + 10); + } else { + hcode = -1; + } + return static_cast(hcode); +} + +CLI11_INLINE char make_char(std::uint32_t code) { return static_cast(static_cast(code)); } + +CLI11_INLINE void append_codepoint(std::string &str, std::uint32_t code) { + if(code < 0x80) { // ascii code equivalent + str.push_back(static_cast(code)); + } else if(code < 0x800) { // \u0080 to \u07FF + // 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111 + str.push_back(make_char(0xC0 | code >> 6)); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x10000) { // U+0800...U+FFFF + if(0xD800 <= code && code <= 0xDFFF) { + throw std::invalid_argument("[0xD800, 0xDFFF] are not valid UTF-8."); + } + // 1110yyyy 10yxxxxx 10xxxxxx + str.push_back(make_char(0xE0 | code >> 12)); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x110000) { // U+010000 ... U+10FFFF + // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx + str.push_back(make_char(0xF0 | code >> 18)); + str.push_back(make_char(0x80 | (code >> 12 & 0x3F))); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } +} + +CLI11_INLINE std::string remove_escaped_characters(const std::string &str) { + + std::string out; + out.reserve(str.size()); + for(auto loc = str.begin(); loc < str.end(); ++loc) { + if(*loc == '\\') { + if(str.end() - loc < 2) { + throw std::invalid_argument("invalid escape sequence " + str); + } + auto ecloc = escapedCharsCode.find_first_of(*(loc + 1)); + if(ecloc != std::string::npos) { + out.push_back(escapedChars[ecloc]); + ++loc; + } else if(*(loc + 1) == 'u') { + // must have 4 hex characters + if(str.end() - loc < 6) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16}; + for(int ii = 2; ii < 6; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 5; + } else if(*(loc + 1) == 'U') { + // must have 8 hex characters + if(str.end() - loc < 10) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16 * 16 * 16 * 16 * 16}; + for(int ii = 2; ii < 10; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 9; + } else if(*(loc + 1) == '0') { + out.push_back('\0'); + ++loc; + } else { + throw std::invalid_argument(std::string("unrecognized escape sequence \\") + *(loc + 1) + " in " + str); + } + } else { + out.push_back(*loc); + } + } + return out; +} + +CLI11_INLINE std::size_t close_string_quote(const std::string &str, std::size_t start, char closure_char) { + std::size_t loc{0}; + for(loc = start + 1; loc < str.size(); ++loc) { + if(str[loc] == closure_char) { + break; + } + if(str[loc] == '\\') { + // skip the next character for escaped sequences + ++loc; + } + } + return loc; +} + +CLI11_INLINE std::size_t close_literal_quote(const std::string &str, std::size_t start, char closure_char) { + auto loc = str.find_first_of(closure_char, start + 1); + return (loc != std::string::npos ? loc : str.size()); +} + +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char) { + + auto bracket_loc = matchBracketChars.find(closure_char); + switch(bracket_loc) { + case 0: + return close_string_quote(str, start, closure_char); + case 1: + case 2: + case std::string::npos: + return close_literal_quote(str, start, closure_char); + default: + break; + } + + std::string closures(1, closure_char); + auto loc = start + 1; + + while(loc < str.size()) { + if(str[loc] == closures.back()) { + closures.pop_back(); + if(closures.empty()) { + return loc; + } + } + bracket_loc = bracketChars.find(str[loc]); + if(bracket_loc != std::string::npos) { + switch(bracket_loc) { + case 0: + loc = close_string_quote(str, loc, str[loc]); + break; + case 1: + case 2: + loc = close_literal_quote(str, loc, str[loc]); + break; + default: + closures.push_back(matchBracketChars[bracket_loc]); + break; + } + } + ++loc; + } + if(loc > str.size()) { + loc = str.size(); + } + return loc; +} + +CLI11_INLINE std::vector split_up(std::string str, char delimiter) { + + auto find_ws = [delimiter](char ch) { + return (delimiter == '\0') ? std::isspace(ch, std::locale()) : (ch == delimiter); + }; + trim(str); + + std::vector output; + while(!str.empty()) { + if(bracketChars.find_first_of(str[0]) != std::string::npos) { + auto bracketLoc = bracketChars.find_first_of(str[0]); + auto end = close_sequence(str, 0, matchBracketChars[bracketLoc]); + if(end >= str.size()) { + output.push_back(std::move(str)); + str.clear(); + } else { + output.push_back(str.substr(0, end + 1)); + if(end + 2 < str.size()) { + str = str.substr(end + 2); + } else { + str.clear(); + } + } + + } else { + auto it = std::find_if(std::begin(str), std::end(str), find_ws); + if(it != std::end(str)) { + std::string value = std::string(str.begin(), it); + output.push_back(value); + str = std::string(it + 1, str.end()); + } else { + output.push_back(str); + str.clear(); + } + } + trim(str); + } + return output; +} + +CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) { + auto next = str[offset + 1]; + if((next == '\"') || (next == '\'') || (next == '`')) { + auto astart = str.find_last_of("-/ \"\'`", offset - 1); + if(astart != std::string::npos) { + if(str[astart] == ((str[offset] == '=') ? '-' : '/')) + str[offset] = ' '; // interpret this as a space so the split_up works properly + } + } + return offset + 1; +} + +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape) { + // s is our escaped output string + std::string escaped_string{}; + // loop through all characters + for(char c : string_to_escape) { + // check if a given character is printable + // the cast is necessary to avoid undefined behaviour + if(isprint(static_cast(c)) == 0) { + std::stringstream stream; + // if the character is not printable + // we'll convert it to a hex string using a stringstream + // note that since char is signed we have to cast it to unsigned first + stream << std::hex << static_cast(static_cast(c)); + std::string code = stream.str(); + escaped_string += std::string("\\x") + (code.size() < 2 ? "0" : "") + code; + + } else { + escaped_string.push_back(c); + } + } + if(escaped_string != string_to_escape) { + auto sqLoc = escaped_string.find('\''); + while(sqLoc != std::string::npos) { + escaped_string.replace(sqLoc, sqLoc + 1, "\\x27"); + sqLoc = escaped_string.find('\''); + } + escaped_string.insert(0, "'B\"("); + escaped_string.push_back(')'); + escaped_string.push_back('"'); + escaped_string.push_back('\''); + } + return escaped_string; +} + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string) { + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + return true; + } + return (escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0); +} + +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string) { + std::size_t start{0}; + std::size_t tail{0}; + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + start = 3; + tail = 2; + } else if(escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0) { + start = 4; + tail = 3; + } + + if(start == 0) { + return escaped_string; + } + std::string outstring; + + outstring.reserve(ssize - start - tail); + std::size_t loc = start; + while(loc < ssize - tail) { + // ssize-2 to skip )" at the end + if(escaped_string[loc] == '\\' && (escaped_string[loc + 1] == 'x' || escaped_string[loc + 1] == 'X')) { + auto c1 = escaped_string[loc + 2]; + auto c2 = escaped_string[loc + 3]; + + std::uint32_t res1 = hexConvert(c1); + std::uint32_t res2 = hexConvert(c2); + if(res1 <= 0x0F && res2 <= 0x0F) { + loc += 4; + outstring.push_back(static_cast(res1 * 16 + res2)); + continue; + } + } + outstring.push_back(escaped_string[loc]); + ++loc; + } + return outstring; +} + +CLI11_INLINE void remove_quotes(std::vector &args) { + for(auto &arg : args) { + if(arg.front() == '\"' && arg.back() == '\"') { + remove_quotes(arg); + // only remove escaped for string arguments not literal strings + arg = remove_escaped_characters(arg); + } else { + remove_quotes(arg); + } + } +} + +CLI11_INLINE bool process_quoted_string(std::string &str, char string_char, char literal_char) { + if(str.size() <= 1) { + return false; + } + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + return true; + } + if(str.front() == string_char && str.back() == string_char) { + detail::remove_outer(str, string_char); + if(str.find_first_of('\\') != std::string::npos) { + str = detail::remove_escaped_characters(str); + } + return true; + } + if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) { + detail::remove_outer(str, str.front()); + return true; + } + return false; +} + +std::string get_environment_value(const std::string &env_name) { + char *buffer = nullptr; + std::string ename_string; + +#ifdef _MSC_VER + // Windows version + std::size_t sz = 0; + if(_dupenv_s(&buffer, &sz, env_name.c_str()) == 0 && buffer != nullptr) { + ename_string = std::string(buffer); + free(buffer); + } +#else + // This also works on Windows, but gives a warning + buffer = std::getenv(env_name.c_str()); + if(buffer != nullptr) { + ename_string = std::string(buffer); + } +#endif + return ename_string; +} + +} // namespace detail + + + +// Use one of these on all error classes. +// These are temporary and are undef'd at the end of this file. +#define CLI11_ERROR_DEF(parent, name) \ + protected: \ + name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ + name(std::string ename, std::string msg, ExitCodes exit_code) \ + : parent(std::move(ename), std::move(msg), exit_code) {} \ + \ + public: \ + name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ + name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} + +// This is added after the one above if a class is used directly and builds its own message +#define CLI11_ERROR_SIMPLE(name) \ + explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} + +/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, +/// int values from e.get_error_code(). +enum class ExitCodes { + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 +}; + +// Error definitions + +/// @defgroup error_group Errors +/// @brief Errors thrown by CLI11 +/// +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. +/// @{ + +/// All errors derive from this one +class Error : public std::runtime_error { + int actual_exit_code; + std::string error_name{"Error"}; + + public: + CLI11_NODISCARD int get_exit_code() const { return actual_exit_code; } + + CLI11_NODISCARD std::string get_name() const { return error_name; } + + Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) + : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} + + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} +}; + +// Note: Using Error::Error constructors does not work on GCC 4.7 + +/// Construction errors (not in parsing) +class ConstructionError : public Error { + CLI11_ERROR_DEF(Error, ConstructionError) +}; + +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +class IncorrectConstruction : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) + CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional"); + } + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); + } + static IncorrectConstruction SetFlag(std::string name) { + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + } + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + } + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction( + name + ": You can't change expected arguments after you've changed the multi option policy!"); + } + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined"); + } + static IncorrectConstruction MultiOptionPolicy(std::string name) { + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); + } +}; + +/// Thrown on construction of a bad name +class BadNameString : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString MissingDash(std::string name) { + return BadNameString("Long names strings require 2 dashes " + name); + } + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString BadPositionalName(std::string name) { + return BadNameString("Invalid positional Name: " + name); + } + static BadNameString DashesOnly(std::string name) { + return BadNameString("Must have a name, not just dashes: " + name); + } + static BadNameString MultiPositionalNames(std::string name) { + return BadNameString("Only one positional name allowed, remove: " + name); + } +}; + +/// Thrown when an option already exists +class OptionAlreadyAdded : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) + explicit OptionAlreadyAdded(std::string name) + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} + static OptionAlreadyAdded Requires(std::string name, std::string other) { + return {name + " requires " + other, ExitCodes::OptionAlreadyAdded}; + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return {name + " excludes " + other, ExitCodes::OptionAlreadyAdded}; + } +}; + +// Parsing errors + +/// Anything that can error in Parse +class ParseError : public Error { + CLI11_ERROR_DEF(Error, ParseError) +}; + +// Not really "errors" + +/// This is a successful completion on parsing, supposed to exit +class Success : public ParseError { + CLI11_ERROR_DEF(ParseError, Success) + Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} +}; + +/// -h or --help on command line +class CallForHelp : public Success { + CLI11_ERROR_DEF(Success, CallForHelp) + CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Usually something like --help-all on command line +class CallForAllHelp : public Success { + CLI11_ERROR_DEF(Success, CallForAllHelp) + CallForAllHelp() + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// -v or --version on command line +class CallForVersion : public Success { + CLI11_ERROR_DEF(Success, CallForVersion) + CallForVersion() + : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code. +class RuntimeError : public ParseError { + CLI11_ERROR_DEF(ParseError, RuntimeError) + explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} +}; + +/// Thrown when parsing an INI file and it is missing +class FileError : public ParseError { + CLI11_ERROR_DEF(ParseError, FileError) + CLI11_ERROR_SIMPLE(FileError) + static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } +}; + +/// Thrown when conversion call back fails, such as when an int fails to coerce to a string +class ConversionError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConversionError) + CLI11_ERROR_SIMPLE(ConversionError) + ConversionError(std::string member, std::string name) + : ConversionError("The value " + member + " is not an allowed value for " + name) {} + ConversionError(std::string name, std::vector results) + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} + static ConversionError TooManyInputsFlag(std::string name) { + return ConversionError(name + ": too many inputs for a flag"); + } + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number"); + } +}; + +/// Thrown when validation of results fails +class ValidationError : public ParseError { + CLI11_ERROR_DEF(ParseError, ValidationError) + CLI11_ERROR_SIMPLE(ValidationError) + explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} +}; + +/// Thrown when a required option is missing +class RequiredError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiredError) + explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} + static RequiredError Subcommand(std::size_t min_subcom) { + if(min_subcom == 1) { + return RequiredError("A subcommand"); + } + return {"Requires at least " + std::to_string(min_subcom) + " subcommands", ExitCodes::RequiredError}; + } + static RequiredError + Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) { + if((min_option == 1) && (max_option == 1) && (used == 0)) + return RequiredError("Exactly 1 option from [" + option_list + "]"); + if((min_option == 1) && (max_option == 1) && (used > 1)) { + return {"Exactly 1 option from [" + option_list + "] is required but " + std::to_string(used) + + " were given", + ExitCodes::RequiredError}; + } + if((min_option == 1) && (used == 0)) + return RequiredError("At least 1 option from [" + option_list + "]"); + if(used < min_option) { + return {"Requires at least " + std::to_string(min_option) + " options used but only " + + std::to_string(used) + " were given from [" + option_list + "]", + ExitCodes::RequiredError}; + } + if(max_option == 1) + return {"Requires at most 1 options be given from [" + option_list + "]", ExitCodes::RequiredError}; + + return {"Requires at most " + std::to_string(max_option) + " options be used but " + std::to_string(used) + + " were given from [" + option_list + "]", + ExitCodes::RequiredError}; + } +}; + +/// Thrown when the wrong number of arguments has been received +class ArgumentMismatch : public ParseError { + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_SIMPLE(ArgumentMismatch) + ArgumentMismatch(std::string name, int expected, std::size_t received) + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + + ", got " + std::to_string(received)) + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + + ", got " + std::to_string(received)), + ExitCodes::ArgumentMismatch) {} + + static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); + } + static ArgumentMismatch FlagOverride(std::string name) { + return ArgumentMismatch(name + " was given a disallowed flag override"); + } + static ArgumentMismatch PartialType(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) + + " required for each element"); + } +}; + +/// Thrown when a requires option is missing +class RequiresError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiresError) + RequiresError(std::string curname, std::string subname) + : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} +}; + +/// Thrown when an excludes option is present +class ExcludesError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExcludesError) + ExcludesError(std::string curname, std::string subname) + : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} +}; + +/// Thrown when too many positionals or options are found +class ExtrasError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExtrasError) + explicit ExtrasError(std::vector args) + : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} + ExtrasError(const std::string &name, std::vector args) + : ExtrasError(name, + (args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} +}; + +/// Thrown when extra values are found in an INI file +class ConfigError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConfigError) + CLI11_ERROR_SIMPLE(ConfigError) + static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } + static ConfigError NotConfigurable(std::string item) { + return ConfigError(item + ": This option is not allowed in a configuration file"); + } +}; + +/// Thrown when validation fails before parsing +class InvalidError : public ParseError { + CLI11_ERROR_DEF(ParseError, InvalidError) + explicit InvalidError(std::string name) + : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { + } +}; + +/// This is just a safety check to verify selection and parsing match - you should not ever see it +/// Strings are directly added to this error, but again, it should never be seen. +class HorribleError : public ParseError { + CLI11_ERROR_DEF(ParseError, HorribleError) + CLI11_ERROR_SIMPLE(HorribleError) +}; + +// After parsing + +/// Thrown when counting a non-existent option +class OptionNotFound : public Error { + CLI11_ERROR_DEF(Error, OptionNotFound) + explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} +}; + +#undef CLI11_ERROR_DEF +#undef CLI11_ERROR_SIMPLE + +/// @} + + + + +// Type tools + +// Utilities for type enabling +namespace detail { +// Based generally on https://rmf.io/cxx11/almost-static-if +/// Simple empty scoped class +enum class enabler {}; + +/// An instance to use in EnableIf +constexpr enabler dummy = {}; +} // namespace detail + +/// A copy of enable_if_t from C++14, compatible with C++11. +/// +/// We could check to see if C++14 is being used, but it does not hurt to redefine this +/// (even Google does this: https://github.com/google/skia/blob/main/include/private/SkTLogic.h) +/// It is not in the std namespace anyway, so no harm done. +template using enable_if_t = typename std::enable_if::type; + +/// A copy of std::void_t from C++17 (helper for C++11 and C++14) +template struct make_void { + using type = void; +}; + +/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine +template using void_t = typename make_void::type; + +/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine +template using conditional_t = typename std::conditional::type; + +/// Check to see if something is bool (fail check by default) +template struct is_bool : std::false_type {}; + +/// Check to see if something is bool (true if actually a bool) +template <> struct is_bool : std::true_type {}; + +/// Check to see if something is a shared pointer +template struct is_shared_ptr : std::false_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is copyable pointer +template struct is_copyable_ptr { + static bool const value = is_shared_ptr::value || std::is_pointer::value; +}; + +/// This can be specialized to override the type deduction for IsMember. +template struct IsMemberType { + using type = T; +}; + +/// The main custom type needed here is const char * should be a string. +template <> struct IsMemberType { + using type = std::string; +}; + +namespace adl_detail { +/// Check for existence of user-supplied lexical_cast. +/// +/// This struct has to be in a separate namespace so that it doesn't see our lexical_cast overloads in CLI::detail. +/// Standard says it shouldn't see them if it's defined before the corresponding lexical_cast declarations, but this +/// requires a working implementation of two-phase lookup, and not all compilers can boast that (msvc, ahem). +template class is_lexical_castable { + template + static auto test(int) -> decltype(lexical_cast(std::declval(), std::declval()), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; +} // namespace adl_detail + +namespace detail { + +// These are utilities for IsMember and other transforming objects + +/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that +/// pointer_traits be valid. + +/// not a pointer +template struct element_type { + using type = T; +}; + +template struct element_type::value>::type> { + using type = typename std::pointer_traits::element_type; +}; + +/// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of +/// the container +template struct element_value_type { + using type = typename element_type::type::value_type; +}; + +/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing. +template struct pair_adaptor : std::false_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } +}; + +/// Adaptor for map-like structure (true version, must have key_type and mapped_type). +/// This wraps a mapped container in a few utilities access it in a general way. +template +struct pair_adaptor< + T, + conditional_t, void>> + : std::true_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward(pair_value))) { + return std::get<0>(std::forward(pair_value)); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward(pair_value))) { + return std::get<1>(std::forward(pair_value)); + } +}; + +// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning +// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in +// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a +// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out. +// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be +// suppressed +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnarrowing" +#endif +// check for constructibility from a specific type and copy assignable used in the parse detection +template class is_direct_constructible { + template + static auto test(int, std::true_type) -> decltype( +// NVCC warns about narrowing conversions here +#ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_suppress 2361 +#else +#pragma diag_suppress 2361 +#endif +#endif + TT{std::declval()} +#ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_default 2361 +#else +#pragma diag_default 2361 +#endif +#endif + , + std::is_move_assignable()); + + template static auto test(int, std::false_type) -> std::false_type; + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0, typename std::is_constructible::type()))::value; +}; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +// Check for output streamability +// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream + +template class is_ostreamable { + template + static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for input streamability +template class is_istreamable { + template + static auto test(int) -> decltype(std::declval() >> std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for complex +template class is_complex { + template + static auto test(int) -> decltype(std::declval().real(), std::declval().imag(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Templated operation to get a value from a stream +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string &istring, T &obj) { + std::istringstream is; + is.str(istring); + is >> obj; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string & /*istring*/, T & /*obj*/) { + return false; +} + +// check to see if an object is a mutable container (fail by default) +template struct is_mutable_container : std::false_type {}; + +/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and +/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed +/// from a std::string +template +struct is_mutable_container< + T, + conditional_t().end()), + decltype(std::declval().clear()), + decltype(std::declval().insert(std::declval().end())>(), + std::declval()))>, + void>> : public conditional_t::value || + std::is_constructible::value, + std::false_type, + std::true_type> {}; + +// check to see if an object is a mutable container (fail by default) +template struct is_readable_container : std::false_type {}; + +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an end +/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from +/// a std::string +template +struct is_readable_container< + T, + conditional_t().end()), decltype(std::declval().begin())>, void>> + : public std::true_type {}; + +// check to see if an object is a wrapper (fail by default) +template struct is_wrapper : std::false_type {}; + +// check if an object is a wrapper (it has a value_type defined) +template +struct is_wrapper, void>> : public std::true_type {}; + +// Check for tuple like types, as in classes with a tuple_size type trait +template class is_tuple_like { + template + // static auto test(int) + // -> decltype(std::conditional<(std::tuple_size::value > 0), std::true_type, std::false_type>::type()); + static auto test(int) -> decltype(std::tuple_size::type>::value, std::true_type{}); + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Convert an object to a string (directly forward if this can become a string) +template ::value, detail::enabler> = detail::dummy> +auto to_string(T &&value) -> decltype(std::forward(value)) { + return std::forward(value); +} + +/// Construct a string from the object +template ::value && !std::is_convertible::value, + detail::enabler> = detail::dummy> +std::string to_string(const T &value) { + return std::string(value); // NOLINT(google-readability-casting) +} + +/// Convert an object to a string (streaming must be supported for that type) +template ::value && !std::is_constructible::value && + is_ostreamable::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&value) { + std::stringstream stream; + stream << value; + return stream.str(); +} + +/// If conversion is not supported, return an empty string (streaming is not supported for that type) +template ::value && !is_ostreamable::value && + !is_readable_container::type>::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&) { + return {}; +} + +/// convert a readable container to a string +template ::value && !is_ostreamable::value && + is_readable_container::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&variable) { + auto cval = variable.begin(); + auto end = variable.end(); + if(cval == end) { + return {"{}"}; + } + std::vector defaults; + while(cval != end) { + defaults.emplace_back(CLI::detail::to_string(*cval)); + ++cval; + } + return {"[" + detail::join(defaults) + "]"}; +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +auto checked_to_string(T &&value) -> decltype(to_string(std::forward(value))) { + return to_string(std::forward(value)); +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +std::string checked_to_string(T &&) { + return std::string{}; +} +/// get a string as a convertible value for arithmetic types +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(value); +} +/// get a string as a convertible value for enumerations +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(static_cast::type>(value)); +} +/// for other types just use the regular to_string function +template ::value && !std::is_arithmetic::value, detail::enabler> = detail::dummy> +auto value_string(const T &value) -> decltype(to_string(value)) { + return to_string(value); +} + +/// template to get the underlying value type if it exists or use a default +template struct wrapped_type { + using type = def; +}; + +/// Type size for regular object types that do not look like a tuple +template struct wrapped_type::value>::type> { + using type = typename T::value_type; +}; + +/// This will only trigger for actual void type +template struct type_count_base { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_base::value && !is_mutable_container::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// the base tuple size +template +struct type_count_base::value && !is_mutable_container::value>::type> { + static constexpr int value{std::tuple_size::value}; +}; + +/// Type count base for containers is the type_count_base of the individual element +template struct type_count_base::value>::type> { + static constexpr int value{type_count_base::value}; +}; + +/// Set of overloads to get the type size of an object + +/// forward declare the subtype_count structure +template struct subtype_count; + +/// forward declare the subtype_count_min structure +template struct subtype_count_min; + +/// This will only trigger for actual void type +template struct type_count { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count::value && !is_tuple_like::value && !is_complex::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count::value>::type> { + static constexpr int value{2}; +}; + +/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template struct type_count::value>::type> { + static constexpr int value{subtype_count::value}; +}; + +/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes) +template +struct type_count::value && !is_complex::value && !is_tuple_like::value && + !is_mutable_container::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size() { + return subtype_count::type>::value + tuple_type_size(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count::value>::type> { + static constexpr int value{tuple_type_size()}; +}; + +/// definition of subtype count +template struct subtype_count { + static constexpr int value{is_mutable_container::value ? expected_max_vector_size : type_count::value}; +}; + +/// This will only trigger for actual void type +template struct type_count_min { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_min< + T, + typename std::enable_if::value && !is_tuple_like::value && !is_wrapper::value && + !is_complex::value && !std::is_void::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count_min::value>::type> { + static constexpr int value{1}; +}; + +/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template +struct type_count_min< + T, + typename std::enable_if::value && !is_complex::value && !is_tuple_like::value>::type> { + static constexpr int value{subtype_count_min::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size_min() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size_min() { + return subtype_count_min::type>::value + tuple_type_size_min(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count_min::value>::type> { + static constexpr int value{tuple_type_size_min()}; +}; + +/// definition of subtype count +template struct subtype_count_min { + static constexpr int value{is_mutable_container::value + ? ((type_count::value < expected_max_vector_size) ? type_count::value : 0) + : type_count_min::value}; +}; + +/// This will only trigger for actual void type +template struct expected_count { + static const int value{0}; +}; + +/// For most types the number of expected items is 1 +template +struct expected_count::value && !is_wrapper::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; +/// number of expected items in a vector +template struct expected_count::value>::type> { + static constexpr int value{expected_max_vector_size}; +}; + +/// number of expected items in a vector +template +struct expected_count::value && is_wrapper::value>::type> { + static constexpr int value{expected_count::value}; +}; + +// Enumeration of the different supported categorizations of objects +enum class object_category : int { + char_value = 1, + integral_value = 2, + unsigned_integral = 4, + enumeration = 6, + boolean_value = 8, + floating_point = 10, + number_constructible = 12, + double_constructible = 14, + integer_constructible = 16, + // string like types + string_assignable = 23, + string_constructible = 24, + wstring_assignable = 25, + wstring_constructible = 26, + other = 45, + // special wrapper or container types + wrapper_value = 50, + complex_number = 60, + tuple_value = 70, + container_value = 80, + +}; + +/// Set of overloads to classify an object according to type + +/// some type that is not otherwise recognized +template struct classify_object { + static constexpr object_category value{object_category::other}; +}; + +/// Signed integers +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_same::value && std::is_signed::value && + !is_bool::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::integral_value}; +}; + +/// Unsigned integers +template +struct classify_object::value && std::is_unsigned::value && + !std::is_same::value && !is_bool::value>::type> { + static constexpr object_category value{object_category::unsigned_integral}; +}; + +/// single character values +template +struct classify_object::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::char_value}; +}; + +/// Boolean values +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::boolean_value}; +}; + +/// Floats +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::floating_point}; +}; +#if defined _MSC_VER +// in MSVC wstring should take precedence if available this isn't as useful on other compilers due to the broader use of +// utf-8 encoding +#define WIDE_STRING_CHECK \ + !std::is_assignable::value && !std::is_constructible::value +#define STRING_CHECK true +#else +#define WIDE_STRING_CHECK true +#define STRING_CHECK !std::is_assignable::value && !std::is_constructible::value +#endif + +/// String and similar direct assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && WIDE_STRING_CHECK && + std::is_assignable::value>::type> { + static constexpr object_category value{object_category::string_assignable}; +}; + +/// String and similar constructible and copy assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + WIDE_STRING_CHECK && std::is_constructible::value>::type> { + static constexpr object_category value{object_category::string_constructible}; +}; + +/// Wide strings +template +struct classify_object::value && !std::is_integral::value && + STRING_CHECK && std::is_assignable::value>::type> { + static constexpr object_category value{object_category::wstring_assignable}; +}; + +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + STRING_CHECK && std::is_constructible::value>::type> { + static constexpr object_category value{object_category::wstring_constructible}; +}; + +/// Enumerations +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::enumeration}; +}; + +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::complex_number}; +}; + +/// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, +/// vectors, and enumerations +template struct uncommon_type { + using type = typename std::conditional< + !std::is_floating_point::value && !std::is_integral::value && + !std::is_assignable::value && !std::is_constructible::value && + !std::is_assignable::value && !std::is_constructible::value && + !is_complex::value && !is_mutable_container::value && !std::is_enum::value, + std::true_type, + std::false_type>::type; + static constexpr bool value = type::value; +}; + +/// wrapper type +template +struct classify_object::value && is_wrapper::value && + !is_tuple_like::value && uncommon_type::value)>::type> { + static constexpr object_category value{object_category::wrapper_value}; +}; + +/// Assignable from double or int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::number_constructible}; +}; + +/// Assignable from int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && !is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::integer_constructible}; +}; + +/// Assignable from double +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + !is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::double_constructible}; +}; + +/// Tuple type +template +struct classify_object< + T, + typename std::enable_if::value && + ((type_count::value >= 2 && !is_wrapper::value) || + (uncommon_type::value && !is_direct_constructible::value && + !is_direct_constructible::value) || + (uncommon_type::value && type_count::value >= 2))>::type> { + static constexpr object_category value{object_category::tuple_value}; + // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be + // constructed from just the first element so tuples of can be constructed from a string, which + // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2 + // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out + // those cases that are caught by other object classifications +}; + +/// container type +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::container_value}; +}; + +// Type name print + +/// Was going to be based on +/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template +/// But this is cleaner and works better in this case + +template ::value == object_category::char_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "CHAR"; +} + +template ::value == object_category::integral_value || + classify_object::value == object_category::integer_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "INT"; +} + +template ::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "UINT"; +} + +template ::value == object_category::floating_point || + classify_object::value == object_category::number_constructible || + classify_object::value == object_category::double_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "FLOAT"; +} + +/// Print name for enumeration types +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "ENUM"; +} + +/// Print name for enumeration types +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "BOOLEAN"; +} + +/// Print name for enumeration types +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "COMPLEX"; +} + +/// Print for all other types +template ::value >= object_category::string_assignable && + classify_object::value <= object_category::other, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "TEXT"; +} +/// typename for tuple value +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Generate type name for a wrapper or container value +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Print name for single element tuple types +template ::value == object_category::tuple_value && type_count_base::value == 1, + detail::enabler> = detail::dummy> +inline std::string type_name() { + return type_name::type>::type>(); +} + +/// Empty string if the index > tuple size +template +inline typename std::enable_if::value, std::string>::type tuple_name() { + return std::string{}; +} + +/// Recursively generate the tuple type name +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_name() { + auto str = std::string{type_name::type>::type>()} + ',' + + tuple_name(); + if(str.back() == ',') + str.pop_back(); + return str; +} + +/// Print type name for tuples with 2 or more elements +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler>> +inline std::string type_name() { + auto tname = std::string(1, '[') + tuple_name(); + tname.push_back(']'); + return tname; +} + +/// get the type name for a type that has a value_type member +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler>> +inline std::string type_name() { + return type_name(); +} + +// Lexical cast + +/// Convert to an unsigned integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty() || input.front() == '-') { + return false; + } + char *val{nullptr}; + errno = 0; + std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + val = nullptr; + std::int64_t output_sll = std::strtoll(input.c_str(), &val, 0); + if(val == (input.c_str() + input.size())) { + output = (output_sll < 0) ? static_cast(0) : static_cast(output_sll); + return (static_cast(output) == output_sll); + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return integral_conversion(nstring, output); + } + if(input.compare(0, 2, "0o") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + return false; +} + +/// Convert to a signed integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty()) { + return false; + } + char *val = nullptr; + errno = 0; + std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + if(input == "true") { + // this is to deal with a few oddities with flags and wrapper int types + output = static_cast(1); + return true; + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return integral_conversion(nstring, output); + } + if(input.compare(0, 2, "0o") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + return false; +} + +/// Convert a flag into an integer value typically binary flags sets errno to nonzero if conversion failed +inline std::int64_t to_flag_value(std::string val) noexcept { + static const std::string trueString("true"); + static const std::string falseString("false"); + if(val == trueString) { + return 1; + } + if(val == falseString) { + return -1; + } + val = detail::to_lower(val); + std::int64_t ret = 0; + if(val.size() == 1) { + if(val[0] >= '1' && val[0] <= '9') { + return (static_cast(val[0]) - '0'); + } + switch(val[0]) { + case '0': + case 'f': + case 'n': + case '-': + ret = -1; + break; + case 't': + case 'y': + case '+': + ret = 1; + break; + default: + errno = EINVAL; + return -1; + } + return ret; + } + if(val == trueString || val == "on" || val == "yes" || val == "enable") { + ret = 1; + } else if(val == falseString || val == "off" || val == "no" || val == "disable") { + ret = -1; + } else { + char *loc_ptr{nullptr}; + ret = std::strtoll(val.c_str(), &loc_ptr, 0); + if(loc_ptr != (val.c_str() + val.size()) && errno == 0) { + errno = EINVAL; + } + } + return ret; +} + +/// Integer conversion +template ::value == object_category::integral_value || + classify_object::value == object_category::unsigned_integral, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + return integral_conversion(input, output); +} + +/// char values +template ::value == object_category::char_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.size() == 1) { + output = static_cast(input[0]); + return true; + } + return integral_conversion(input, output); +} + +/// Boolean values +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + errno = 0; + auto out = to_flag_value(input); + if(errno == 0) { + output = (out > 0); + } else if(errno == ERANGE) { + output = (input[0] != '-'); + } else { + return false; + } + return true; +} + +/// Floats +template ::value == object_category::floating_point, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.empty()) { + return false; + } + char *val = nullptr; + auto output_ld = std::strtold(input.c_str(), &val); + output = static_cast(output_ld); + if(val == (input.c_str() + input.size())) { + return true; + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return lexical_cast(nstring, output); + } + return false; +} + +/// complex +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + using XC = typename wrapped_type::type; + XC x{0.0}, y{0.0}; + auto str1 = input; + bool worked = false; + auto nloc = str1.find_last_of("+-"); + if(nloc != std::string::npos && nloc > 0) { + worked = lexical_cast(str1.substr(0, nloc), x); + str1 = str1.substr(nloc); + if(str1.back() == 'i' || str1.back() == 'j') + str1.pop_back(); + worked = worked && lexical_cast(str1, y); + } else { + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + worked = lexical_cast(str1, y); + x = XC{0}; + } else { + worked = lexical_cast(str1, x); + y = XC{0}; + } + } + if(worked) { + output = T{x, y}; + return worked; + } + return from_stream(input, output); +} + +/// String and similar direct assignment +template ::value == object_category::string_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = input; + return true; +} + +/// String and similar constructible and copy assignment +template < + typename T, + enable_if_t::value == object_category::string_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T(input); + return true; +} + +/// Wide strings +template < + typename T, + enable_if_t::value == object_category::wstring_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = widen(input); + return true; +} + +template < + typename T, + enable_if_t::value == object_category::wstring_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T{widen(input)}; + return true; +} + +/// Enumerations +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename std::underlying_type::type val; + if(!integral_conversion(input, val)) { + return false; + } + output = static_cast(val); + return true; +} + +/// wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return from_stream(input, output); +} + +template ::value == object_category::wrapper_value && + !std::is_assignable::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Assignable from double or int +template < + typename T, + enable_if_t::value == object_category::number_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } + + double dval = 0.0; + if(lexical_cast(input, dval)) { + output = T{dval}; + return true; + } + + return from_stream(input, output); +} + +/// Assignable from int +template < + typename T, + enable_if_t::value == object_category::integer_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } + return from_stream(input, output); +} + +/// Assignable from double +template < + typename T, + enable_if_t::value == object_category::double_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + double val = 0.0; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Non-string convertible from an int +template ::value == object_category::other && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) +#endif + // with Atomic this could produce a warning due to the conversion but if atomic gets here it is an old style + // so will most likely still work + output = val; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + return true; + } + // LCOV_EXCL_START + // This version of cast is only used for odd cases in an older compilers the fail over + // from_stream is tested elsewhere an not relevant for coverage here + return from_stream(input, output); + // LCOV_EXCL_STOP +} + +/// Non-string parsable by a stream +template ::value == object_category::other && !std::is_assignable::value && + is_istreamable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + return from_stream(input, output); +} + +/// Fallback overload that prints a human-readable error for types that we don't recognize and that don't have a +/// user-supplied lexical_cast overload. +template ::value == object_category::other && !std::is_assignable::value && + !is_istreamable::value && !adl_detail::is_lexical_castable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string & /*input*/, T & /*output*/) { + static_assert(!std::is_same::value, // Can't just write false here. + "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it " + "is convertible from another type use the add_option(...) with XC being the known type"); + return false; +} + +/// Assign a value through lexical cast operations +/// Strings can be empty so we need to do a little different +template ::value && + (classify_object::value == object_category::string_assignable || + classify_object::value == object_category::string_constructible || + classify_object::value == object_category::wstring_assignable || + classify_object::value == object_category::wstring_constructible), + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && std::is_assignable::value && + classify_object::value != object_category::string_assignable && + classify_object::value != object_category::string_constructible && + classify_object::value != object_category::wstring_assignable && + classify_object::value != object_category::wstring_constructible, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = AssignTo{}; + return true; + } + + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && !std::is_assignable::value && + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + typename AssignTo::value_type emptyVal{}; + output = emptyVal; + return true; + } + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations for int compatible values +/// mainly for atomic operations on some compilers +template ::value && !std::is_assignable::value && + classify_object::value != object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = 0; + return true; + } + int val{0}; + if(lexical_cast(input, val)) { +#if defined(__clang__) +/* on some older clang compilers */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-conversion" +#endif + output = val; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + return true; + } + return false; +} + +/// Assign a value converted from a string in lexical cast to the output value directly +template ::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; + if(parse_result) { + output = val; + } + return parse_result; +} + +/// Assign a value from a lexical cast through constructing a value and move assigning it +template < + typename AssignTo, + typename ConvertTo, + enable_if_t::value && !std::is_assignable::value && + std::is_move_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = input.empty() ? true : lexical_cast(input, val); + if(parse_result) { + output = AssignTo(val); // use () form of constructor to allow some implicit conversions + } + return parse_result; +} + +/// primary lexical conversion operation, 1 string to 1 type of some kind +template ::value <= object_category::other && + classify_object::value <= object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + return lexical_assign(strings[0], output); +} + +/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element +/// constructor +template ::value <= 2) && expected_count::value == 1 && + is_tuple_like::value && type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + // the remove const is to handle pair types coming from a container + using FirstType = typename std::remove_const::type>::type; + using SecondType = typename std::tuple_element<1, ConvertTo>::type; + FirstType v1; + SecondType v2; + bool retval = lexical_assign(strings[0], v1); + retval = retval && lexical_assign((strings.size() > 1) ? strings[1] : std::string{}, v2); + if(retval) { + output = AssignTo{v1, v2}; + } + return retval; +} + +/// Lexical conversion of a container types of single elements +template ::value && is_mutable_container::value && + type_count::value == 1, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + output.erase(output.begin(), output.end()); + if(strings.empty()) { + return true; + } + if(strings.size() == 1 && strings[0] == "{}") { + return true; + } + bool skip_remaining = false; + if(strings.size() == 2 && strings[0] == "{}" && is_separator(strings[1])) { + skip_remaining = true; + } + for(const auto &elem : strings) { + typename AssignTo::value_type out; + bool retval = lexical_assign(elem, out); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(out)); + if(skip_remaining) { + break; + } + } + return (!output.empty()); +} + +/// Lexical conversion for complex types +template ::value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() >= 2 && !strings[1].empty()) { + using XC2 = typename wrapped_type::type; + XC2 x{0.0}, y{0.0}; + auto str1 = strings[1]; + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + } + auto worked = lexical_cast(strings[0], x) && lexical_cast(str1, y); + if(worked) { + output = ConvertTo{x, y}; + } + return worked; + } + return lexical_assign(strings[0], output); +} + +/// Conversion to a vector type using a particular single type as the conversion type +template ::value && (expected_count::value == 1) && + (type_count::value == 1), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + output.reserve(strings.size()); + for(const auto &elem : strings) { + + output.emplace_back(); + retval = retval && lexical_assign(elem, output.back()); + } + return (!output.empty()) && retval; +} + +// forward declaration + +/// Lexical conversion of a container types with conversion type of two elements +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(std::vector strings, AssignTo &output); + +/// Lexical conversion of a vector types with type_size >2 forward declaration +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); + +/// Conversion for tuples +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); // forward declaration + +/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large +/// tuple +template ::value && !is_mutable_container::value && + classify_object::value != object_category::wrapper_value && + (is_mutable_container::value || type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { + ConvertTo val; + auto retval = lexical_conversion(strings, val); + output = AssignTo{val}; + return retval; + } + output = AssignTo{}; + return true; +} + +/// function template for converting tuples if the static Index is greater than the tuple size +template +inline typename std::enable_if<(I >= type_count_base::value), bool>::type +tuple_conversion(const std::vector &, AssignTo &) { + return true; +} + +/// Conversion of a tuple element where the type size ==1 and not a mutable container +template +inline typename std::enable_if::value && type_count::value == 1, bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_assign(strings[0], output); + strings.erase(strings.begin()); + return retval; +} + +/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container +template +inline typename std::enable_if::value && (type_count::value > 1) && + type_count::value == type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_conversion(strings, output); + strings.erase(strings.begin(), strings.begin() + type_count::value); + return retval; +} + +/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes +template +inline typename std::enable_if::value || + type_count::value != type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + + std::size_t index{subtype_count_min::value}; + const std::size_t mx_count{subtype_count::value}; + const std::size_t mx{(std::min)(mx_count, strings.size() - 1)}; + + while(index < mx) { + if(is_separator(strings[index])) { + break; + } + ++index; + } + bool retval = lexical_conversion( + std::vector(strings.begin(), strings.begin() + static_cast(index)), output); + if(strings.size() > index) { + strings.erase(strings.begin(), strings.begin() + static_cast(index) + 1); + } else { + strings.clear(); + } + return retval; +} + +/// Tuple conversion operation +template +inline typename std::enable_if<(I < type_count_base::value), bool>::type +tuple_conversion(std::vector strings, AssignTo &output) { + bool retval = true; + using ConvertToElement = typename std:: + conditional::value, typename std::tuple_element::type, ConvertTo>::type; + if(!strings.empty()) { + retval = retval && tuple_type_conversion::type, ConvertToElement>( + strings, std::get(output)); + } + retval = retval && tuple_conversion(std::move(strings), output); + return retval; +} + +/// Lexical conversion of a container types with tuple elements of size 2 +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler>> +bool lexical_conversion(std::vector strings, AssignTo &output) { + output.clear(); + while(!strings.empty()) { + + typename std::remove_const::type>::type v1; + typename std::tuple_element<1, typename ConvertTo::value_type>::type v2; + bool retval = tuple_type_conversion(strings, v1); + if(!strings.empty()) { + retval = retval && tuple_type_conversion(strings, v2); + } + if(retval) { + output.insert(output.end(), typename AssignTo::value_type{v1, v2}); + } else { + return false; + } + } + return (!output.empty()); +} + +/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2 +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + static_assert( + !is_tuple_like::value || type_count_base::value == type_count_base::value, + "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); + return tuple_conversion(strings, output); +} + +/// Lexical conversion of a vector types for everything but tuples of two elements and types of size 1 +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + std::vector temp; + std::size_t ii{0}; + std::size_t icount{0}; + std::size_t xcm{type_count::value}; + auto ii_max = strings.size(); + while(ii < ii_max) { + temp.push_back(strings[ii]); + ++ii; + ++icount; + if(icount == xcm || is_separator(temp.back()) || ii == ii_max) { + if(static_cast(xcm) > type_count_min::value && is_separator(temp.back())) { + temp.pop_back(); + } + typename AssignTo::value_type temp_out; + retval = retval && + lexical_conversion(temp, temp_out); + temp.clear(); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(temp_out)); + icount = 0; + } + } + return retval; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + if(strings.empty() || strings.front().empty()) { + output = ConvertTo{}; + return true; + } + typename ConvertTo::value_type val; + if(lexical_conversion(strings, val)) { + output = ConvertTo{val}; + return true; + } + return false; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + using ConvertType = typename ConvertTo::value_type; + if(strings.empty() || strings.front().empty()) { + output = ConvertType{}; + return true; + } + ConvertType val; + if(lexical_conversion(strings, val)) { + output = val; + return true; + } + return false; +} + +/// Sum a vector of strings +inline std::string sum_string_vector(const std::vector &values) { + double val{0.0}; + bool fail{false}; + std::string output; + for(const auto &arg : values) { + double tv{0.0}; + auto comp = lexical_cast(arg, tv); + if(!comp) { + errno = 0; + auto fv = detail::to_flag_value(arg); + fail = (errno != 0); + if(fail) { + break; + } + tv = static_cast(fv); + } + val += tv; + } + if(fail) { + for(const auto &arg : values) { + output.append(arg); + } + } else { + std::ostringstream out; + out.precision(16); + out << val; + output = out.str(); + } + return output; +} + +} // namespace detail + + + +namespace detail { + +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true +CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest); + +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value); + +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true +CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value); + +// Splits a string into multiple long and short names +CLI11_INLINE std::vector split_names(std::string current); + +/// extract default flag values either {def} or starting with a ! +CLI11_INLINE std::vector> get_default_flag_values(const std::string &str); + +/// Get a vector of short names, one of long names, and a single name +CLI11_INLINE std::tuple, std::vector, std::string> +get_names(const std::vector &input); + +} // namespace detail + + + +namespace detail { + +CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest) { + if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { + name = current.substr(1, 1); + rest = current.substr(2); + return true; + } + return false; +} + +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.compare(0, 2, "--") == 0 && valid_first_char(current[2])) { + auto loc = current.find_first_of('='); + if(loc != std::string::npos) { + name = current.substr(2, loc - 2); + value = current.substr(loc + 1); + } else { + name = current.substr(2); + value = ""; + } + return true; + } + return false; +} + +CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { + auto loc = current.find_first_of(':'); + if(loc != std::string::npos) { + name = current.substr(1, loc - 1); + value = current.substr(loc + 1); + } else { + name = current.substr(1); + value = ""; + } + return true; + } + return false; +} + +CLI11_INLINE std::vector split_names(std::string current) { + std::vector output; + std::size_t val = 0; + while((val = current.find(',')) != std::string::npos) { + output.push_back(trim_copy(current.substr(0, val))); + current = current.substr(val + 1); + } + output.push_back(trim_copy(current)); + return output; +} + +CLI11_INLINE std::vector> get_default_flag_values(const std::string &str) { + std::vector flags = split_names(str); + flags.erase(std::remove_if(flags.begin(), + flags.end(), + [](const std::string &name) { + return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) && + (name.back() == '}')) || + (name[0] == '!')))); + }), + flags.end()); + std::vector> output; + output.reserve(flags.size()); + for(auto &flag : flags) { + auto def_start = flag.find_first_of('{'); + std::string defval = "false"; + if((def_start != std::string::npos) && (flag.back() == '}')) { + defval = flag.substr(def_start + 1); + defval.pop_back(); + flag.erase(def_start, std::string::npos); // NOLINT(readability-suspicious-call-argument) + } + flag.erase(0, flag.find_first_not_of("-!")); + output.emplace_back(flag, defval); + } + return output; +} + +CLI11_INLINE std::tuple, std::vector, std::string> +get_names(const std::vector &input) { + + std::vector short_names; + std::vector long_names; + std::string pos_name; + for(std::string name : input) { + if(name.length() == 0) { + continue; + } + if(name.length() > 1 && name[0] == '-' && name[1] != '-') { + if(name.length() == 2 && valid_first_char(name[1])) + short_names.emplace_back(1, name[1]); + else if(name.length() > 2) + throw BadNameString::MissingDash(name); + else + throw BadNameString::OneCharName(name); + } else if(name.length() > 2 && name.substr(0, 2) == "--") { + name = name.substr(2); + if(valid_name_string(name)) + long_names.push_back(name); + else + throw BadNameString::BadLongName(name); + } else if(name == "-" || name == "--") { + throw BadNameString::DashesOnly(name); + } else { + if(!pos_name.empty()) + throw BadNameString::MultiPositionalNames(name); + if(valid_name_string(name)) { + pos_name = name; + } else { + throw BadNameString::BadPositionalName(name); + } + } + } + return std::make_tuple(short_names, long_names, pos_name); +} + +} // namespace detail + + + +class App; + +/// Holds values to load into Options +struct ConfigItem { + /// This is the list of parents + std::vector parents{}; + + /// This is the name + std::string name{}; + /// Listing of inputs + std::vector inputs{}; + + /// The list of parents and name joined by "." + CLI11_NODISCARD std::string fullname() const { + std::vector tmp = parents; + tmp.emplace_back(name); + return detail::join(tmp, "."); + } +}; + +/// This class provides a converter for configuration files. +class Config { + protected: + std::vector items{}; + + public: + /// Convert an app into a configuration + virtual std::string to_config(const App *, bool, bool, std::string) const = 0; + + /// Convert a configuration into an app + virtual std::vector from_config(std::istream &) const = 0; + + /// Get a flag value + CLI11_NODISCARD virtual std::string to_flag(const ConfigItem &item) const { + if(item.inputs.size() == 1) { + return item.inputs.at(0); + } + if(item.inputs.empty()) { + return "{}"; + } + throw ConversionError::TooManyInputsFlag(item.fullname()); // LCOV_EXCL_LINE + } + + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + CLI11_NODISCARD std::vector from_file(const std::string &name) const { + std::ifstream input{name}; + if(!input.good()) + throw FileError::Missing(name); + + return from_config(input); + } + + /// Virtual destructor + virtual ~Config() = default; +}; + +/// This converter works with INI/TOML files; to write INI files use ConfigINI +class ConfigBase : public Config { + protected: + /// the character used for comments + char commentChar = '#'; + /// the character used to start an array '\0' is a default to not use + char arrayStart = '['; + /// the character used to end an array '\0' is a default to not use + char arrayEnd = ']'; + /// the character used to separate elements in an array + char arraySeparator = ','; + /// the character used separate the name from the value + char valueDelimiter = '='; + /// the character to use around strings + char stringQuote = '"'; + /// the character to use around single characters and literal strings + char literalQuote = '\''; + /// the maximum number of layers to allow + uint8_t maximumLayers{255}; + /// the separator used to separator parent layers + char parentSeparatorChar{'.'}; + /// Specify the configuration index to use for arrayed sections + int16_t configIndex{-1}; + /// Specify the configuration section that should be used + std::string configSection{}; + + public: + std::string + to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override; + + std::vector from_config(std::istream &input) const override; + /// Specify the configuration for comment characters + ConfigBase *comment(char cchar) { + commentChar = cchar; + return this; + } + /// Specify the start and end characters for an array + ConfigBase *arrayBounds(char aStart, char aEnd) { + arrayStart = aStart; + arrayEnd = aEnd; + return this; + } + /// Specify the delimiter character for an array + ConfigBase *arrayDelimiter(char aSep) { + arraySeparator = aSep; + return this; + } + /// Specify the delimiter between a name and value + ConfigBase *valueSeparator(char vSep) { + valueDelimiter = vSep; + return this; + } + /// Specify the quote characters used around strings and literal strings + ConfigBase *quoteCharacter(char qString, char literalChar) { + stringQuote = qString; + literalQuote = literalChar; + return this; + } + /// Specify the maximum number of parents + ConfigBase *maxLayers(uint8_t layers) { + maximumLayers = layers; + return this; + } + /// Specify the separator to use for parent layers + ConfigBase *parentSeparator(char sep) { + parentSeparatorChar = sep; + return this; + } + /// get a reference to the configuration section + std::string §ionRef() { return configSection; } + /// get the section + CLI11_NODISCARD const std::string §ion() const { return configSection; } + /// specify a particular section of the configuration file to use + ConfigBase *section(const std::string §ionName) { + configSection = sectionName; + return this; + } + + /// get a reference to the configuration index + int16_t &indexRef() { return configIndex; } + /// get the section index + CLI11_NODISCARD int16_t index() const { return configIndex; } + /// specify a particular index in the section to use (-1) for all sections to use + ConfigBase *index(int16_t sectionIndex) { + configIndex = sectionIndex; + return this; + } +}; + +/// the default Config is the TOML file format +using ConfigTOML = ConfigBase; + +/// ConfigINI generates a "standard" INI compliant output +class ConfigINI : public ConfigTOML { + + public: + ConfigINI() { + commentChar = ';'; + arrayStart = '\0'; + arrayEnd = '\0'; + arraySeparator = ' '; + valueDelimiter = '='; + } +}; + + + +class Option; + +/// @defgroup validator_group Validators + +/// @brief Some validators that are provided +/// +/// These are simple `std::string(const std::string&)` validators that are useful. They return +/// a string if the validation fails. A custom struct is provided, as well, with the same user +/// semantics, but with the ability to provide a new type name. +/// @{ + +/// +class Validator { + protected: + /// This is the description function, if empty the description_ will be used + std::function desc_function_{[]() { return std::string{}; }}; + + /// This is the base function that is to be called. + /// Returns a string error message if validation fails. + std::function func_{[](std::string &) { return std::string{}; }}; + /// The name for search purposes of the Validator + std::string name_{}; + /// A Validator will only apply to an indexed value (-1 is all elements) + int application_index_ = -1; + /// Enable for Validator to allow it to be disabled if need be + bool active_{true}; + /// specify that a validator should not modify the input + bool non_modifying_{false}; + + Validator(std::string validator_desc, std::function func) + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(func)) {} + + public: + Validator() = default; + /// Construct a Validator with just the description string + explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {} + /// Construct Validator from basic information + Validator(std::function op, std::string validator_desc, std::string validator_name = "") + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)), + name_(std::move(validator_name)) {} + /// Set the Validator operation function + Validator &operation(std::function op) { + func_ = std::move(op); + return *this; + } + /// This is the required operator for a Validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(std::string &str) const; + + /// This is the required operator for a Validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(const std::string &str) const { + std::string value = str; + return (active_) ? func_(value) : std::string{}; + } + + /// Specify the type string + Validator &description(std::string validator_desc) { + desc_function_ = [validator_desc]() { return validator_desc; }; + return *this; + } + /// Specify the type string + CLI11_NODISCARD Validator description(std::string validator_desc) const; + + /// Generate type description information for the Validator + CLI11_NODISCARD std::string get_description() const { + if(active_) { + return desc_function_(); + } + return std::string{}; + } + /// Specify the type string + Validator &name(std::string validator_name) { + name_ = std::move(validator_name); + return *this; + } + /// Specify the type string + CLI11_NODISCARD Validator name(std::string validator_name) const { + Validator newval(*this); + newval.name_ = std::move(validator_name); + return newval; + } + /// Get the name of the Validator + CLI11_NODISCARD const std::string &get_name() const { return name_; } + /// Specify whether the Validator is active or not + Validator &active(bool active_val = true) { + active_ = active_val; + return *this; + } + /// Specify whether the Validator is active or not + CLI11_NODISCARD Validator active(bool active_val = true) const { + Validator newval(*this); + newval.active_ = active_val; + return newval; + } + + /// Specify whether the Validator can be modifying or not + Validator &non_modifying(bool no_modify = true) { + non_modifying_ = no_modify; + return *this; + } + /// Specify the application index of a validator + Validator &application_index(int app_index) { + application_index_ = app_index; + return *this; + } + /// Specify the application index of a validator + CLI11_NODISCARD Validator application_index(int app_index) const { + Validator newval(*this); + newval.application_index_ = app_index; + return newval; + } + /// Get the current value of the application index + CLI11_NODISCARD int get_application_index() const { return application_index_; } + /// Get a boolean if the validator is active + CLI11_NODISCARD bool get_active() const { return active_; } + + /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input + CLI11_NODISCARD bool get_modifying() const { return !non_modifying_; } + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator&(const Validator &other) const; + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator|(const Validator &other) const; + + /// Create a validator that fails when a given validator succeeds + Validator operator!() const; + + private: + void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger); +}; + +/// Class wrapping some of the accessors of Validator +class CustomValidator : public Validator { + public: +}; +// The implementation of the built in validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// CLI enumeration of different file types +enum class path_type { nonexistent, file, directory }; + +/// get the type of the path from a file name +CLI11_INLINE path_type check_path(const char *file) noexcept; + +/// Check for an existing file (returns error message if check fails) +class ExistingFileValidator : public Validator { + public: + ExistingFileValidator(); +}; + +/// Check for an existing directory (returns error message if check fails) +class ExistingDirectoryValidator : public Validator { + public: + ExistingDirectoryValidator(); +}; + +/// Check for an existing path +class ExistingPathValidator : public Validator { + public: + ExistingPathValidator(); +}; + +/// Check for an non-existing path +class NonexistentPathValidator : public Validator { + public: + NonexistentPathValidator(); +}; + +/// Validate the given string is a legal ipv4 address +class IPV4Validator : public Validator { + public: + IPV4Validator(); +}; + +class EscapedStringTransformer : public Validator { + public: + EscapedStringTransformer(); +}; + +} // namespace detail + +// Static is not needed here, because global const implies static. + +/// Check for existing file (returns error message if check fails) +const detail::ExistingFileValidator ExistingFile; + +/// Check for an existing directory (returns error message if check fails) +const detail::ExistingDirectoryValidator ExistingDirectory; + +/// Check for an existing path +const detail::ExistingPathValidator ExistingPath; + +/// Check for an non-existing path +const detail::NonexistentPathValidator NonexistentPath; + +/// Check for an IP4 address +const detail::IPV4Validator ValidIPV4; + +/// convert escaped characters into their associated values +const detail::EscapedStringTransformer EscapedString; + +/// Validate the input as a particular type +template class TypeValidator : public Validator { + public: + explicit TypeValidator(const std::string &validator_name) + : Validator(validator_name, [](std::string &input_string) { + using CLI::detail::lexical_cast; + auto val = DesiredType(); + if(!lexical_cast(input_string, val)) { + return std::string("Failed parsing ") + input_string + " as a " + detail::type_name(); + } + return std::string(); + }) {} + TypeValidator() : TypeValidator(detail::type_name()) {} +}; + +/// Check for a number +const TypeValidator Number("NUMBER"); + +/// Modify a path if the file is a particular default location, can be used as Check or transform +/// with the error return optionally disabled +class FileOnDefaultPath : public Validator { + public: + explicit FileOnDefaultPath(std::string default_path, bool enableErrorReturn = true); +}; + +/// Produce a range (factory). Min and max are inclusive. +class Range : public Validator { + public: + /// This produces a range with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template + Range(T min_val, T max_val, const std::string &validator_name = std::string{}) : Validator(validator_name) { + if(validator_name.empty()) { + std::stringstream out; + out << detail::type_name() << " in [" << min_val << " - " << max_val << "]"; + description(out.str()); + } + + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if((!converted) || (val < min_val || val > max_val)) { + std::stringstream out; + out << "Value " << input << " not in range ["; + out << min_val << " - " << max_val << "]"; + return out.str(); + } + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template + explicit Range(T max_val, const std::string &validator_name = std::string{}) + : Range(static_cast(0), max_val, validator_name) {} +}; + +/// Check for a non negative number +const Range NonNegativeNumber((std::numeric_limits::max)(), "NONNEGATIVE"); + +/// Check for a positive valued number (val>0.0), ::min here is the smallest positive number +const Range PositiveNumber((std::numeric_limits::min)(), (std::numeric_limits::max)(), "POSITIVE"); + +/// Produce a bounded range (factory). Min and max are inclusive. +class Bound : public Validator { + public: + /// This bounds a value with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template Bound(T min_val, T max_val) { + std::stringstream out; + out << detail::type_name() << " bounded to [" << min_val << " - " << max_val << "]"; + description(out.str()); + + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if(!converted) { + return std::string("Value ") + input + " could not be converted"; + } + if(val < min_val) + input = detail::to_string(min_val); + else if(val > max_val) + input = detail::to_string(max_val); + + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template explicit Bound(T max_val) : Bound(static_cast(0), max_val) {} +}; + +namespace detail { +template ::type>::value, detail::enabler> = detail::dummy> +auto smart_deref(T value) -> decltype(*value) { + return *value; +} + +template < + typename T, + enable_if_t::type>::value, detail::enabler> = detail::dummy> +typename std::remove_reference::type &smart_deref(T &value) { + return value; +} +/// Generate a string representation of a set +template std::string generate_set(const T &set) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(set), + [](const iteration_type_t &v) { return detail::pair_adaptor::first(v); }, + ",")); + out.push_back('}'); + return out; +} + +/// Generate a string representation of a map +template std::string generate_map(const T &map, bool key_only = false) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(map), + [key_only](const iteration_type_t &v) { + std::string res{detail::to_string(detail::pair_adaptor::first(v))}; + + if(!key_only) { + res.append("->"); + res += detail::to_string(detail::pair_adaptor::second(v)); + } + return res; + }, + ",")); + out.push_back('}'); + return out; +} + +template struct has_find { + template + static auto test(int) -> decltype(std::declval().find(std::declval()), std::true_type()); + template static auto test(...) -> decltype(std::false_type()); + + static const auto value = decltype(test(0))::value; + using type = std::integral_constant; +}; + +/// A search function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + using element_t = typename detail::element_type::type; + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) { + return (detail::pair_adaptor::first(v) == val); + }); + return {(it != std::end(setref)), it}; +} + +/// A search function that uses the built in find function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + auto &setref = detail::smart_deref(set); + auto it = setref.find(val); + return {(it != std::end(setref)), it}; +} + +/// A search function with a filter function +template +auto search(const T &set, const V &val, const std::function &filter_function) + -> std::pair { + using element_t = typename detail::element_type::type; + // do the potentially faster first search + auto res = search(set, val); + if((res.first) || (!(filter_function))) { + return res; + } + // if we haven't found it do the longer linear search with all the element translations + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { + V a{detail::pair_adaptor::first(v)}; + a = filter_function(a); + return (a == val); + }); + return {(it != std::end(setref)), it}; +} + +// the following suggestion was made by Nikita Ofitserov(@himikof) +// done in templates to prevent compiler warnings on negation of unsigned numbers + +/// Do a check for overflow on signed numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + if((a > 0) == (b > 0)) { + return ((std::numeric_limits::max)() / (std::abs)(a) < (std::abs)(b)); + } + return ((std::numeric_limits::min)() / (std::abs)(a) > -(std::abs)(b)); +} +/// Do a check for overflow on unsigned numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + return ((std::numeric_limits::max)() / a < b); +} + +/// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise. +template typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + if(a == 0 || b == 0 || a == 1 || b == 1) { + a *= b; + return true; + } + if(a == (std::numeric_limits::min)() || b == (std::numeric_limits::min)()) { + return false; + } + if(overflowCheck(a, b)) { + return false; + } + a *= b; + return true; +} + +/// Performs a *= b; if it doesn't equal infinity. Returns false otherwise. +template +typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + T c = a * b; + if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) { + return false; + } + a = c; + return true; +} + +} // namespace detail +/// Verify items are in a set +class IsMember : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction using an initializer list + template + IsMember(std::initializer_list values, Args &&...args) + : IsMember(std::vector(values), std::forward(args)...) {} + + /// This checks to see if an item is in a set (empty function) + template explicit IsMember(T &&set) : IsMember(std::forward(set), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit IsMember(T set, F filter_function) { + + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + + using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); }; + + // This is the function that validates + // It stores a copy of the set pointer-like, so shared_ptr will stay alive + func_ = [set, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + throw ValidationError(input); // name is added later + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(set, b, filter_fn); + if(res.first) { + // Make sure the version in the input string is identical to the one in the set + if(filter_fn) { + input = detail::value_string(detail::pair_adaptor::first(*(res.second))); + } + + // Return empty error string (success) + return std::string{}; + } + + // If you reach this point, the result was not found + return input + " not in " + detail::generate_set(detail::smart_deref(set)); + }; + } + + /// You can pass in as many filter functions as you like, they nest (string only currently) + template + IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : IsMember( + std::forward(set), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// definition of the default transformation object +template using TransformPairs = std::vector>; + +/// Translate named items to other or a value set +class Transformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + Transformer(std::initializer_list> values, Args &&...args) + : Transformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit Transformer(T &&mapping) : Transformer(std::forward(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit Transformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; + + func_ = [mapping, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + return std::string(); + // there is no possible way we can match anything in the mapping if we can't convert so just return + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + } + return std::string{}; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : Transformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// translate named items to other or a value set +class CheckedTransformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + CheckedTransformer(std::initializer_list> values, Args &&...args) + : CheckedTransformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit CheckedTransformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + auto tfunc = [mapping]() { + std::string out("value in "); + out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; + out += detail::join( + detail::smart_deref(mapping), + [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor::second(v)); }, + ","); + out.push_back('}'); + return out; + }; + + desc_function_ = tfunc; + + func_ = [mapping, tfunc, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + bool converted = lexical_cast(input, b); + if(converted) { + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + return std::string{}; + } + } + for(const auto &v : detail::smart_deref(mapping)) { + auto output_string = detail::value_string(detail::pair_adaptor::second(v)); + if(output_string == input) { + return std::string(); + } + } + + return "Check " + input + " " + tfunc() + " FAILED"; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : CheckedTransformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// Helper function to allow ignore_case to be passed to IsMember or Transform +inline std::string ignore_case(std::string item) { return detail::to_lower(item); } + +/// Helper function to allow ignore_underscore to be passed to IsMember or Transform +inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); } + +/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform +inline std::string ignore_space(std::string item) { + item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item)); + item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item)); + return item; +} + +/// Multiply a number by a factor using given mapping. +/// Can be used to write transforms for SIZE or DURATION inputs. +/// +/// Example: +/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}` +/// one can recognize inputs like "100", "12kb", "100 MB", +/// that will be automatically transformed to 100, 14448, 104857600. +/// +/// Output number type matches the type in the provided mapping. +/// Therefore, if it is required to interpret real inputs like "0.42 s", +/// the mapping should be of a type or . +class AsNumberWithUnit : public Validator { + public: + /// Adjust AsNumberWithUnit behavior. + /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched. + /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError + /// if UNIT_REQUIRED is set and unit literal is not found. + enum Options { + CASE_SENSITIVE = 0, + CASE_INSENSITIVE = 1, + UNIT_OPTIONAL = 0, + UNIT_REQUIRED = 2, + DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL + }; + + template + explicit AsNumberWithUnit(std::map mapping, + Options opts = DEFAULT, + const std::string &unit_name = "UNIT") { + description(generate_description(unit_name, opts)); + validate_mapping(mapping, opts); + + // transform function + func_ = [mapping, opts](std::string &input) -> std::string { + Number num{}; + + detail::rtrim(input); + if(input.empty()) { + throw ValidationError("Input is empty"); + } + + // Find split position between number and prefix + auto unit_begin = input.end(); + while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { + --unit_begin; + } + + std::string unit{unit_begin, input.end()}; + input.resize(static_cast(std::distance(input.begin(), unit_begin))); + detail::trim(input); + + if(opts & UNIT_REQUIRED && unit.empty()) { + throw ValidationError("Missing mandatory unit"); + } + if(opts & CASE_INSENSITIVE) { + unit = detail::to_lower(unit); + } + if(unit.empty()) { + using CLI::detail::lexical_cast; + if(!lexical_cast(input, num)) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // No need to modify input if no unit passed + return {}; + } + + // find corresponding factor + auto it = mapping.find(unit); + if(it == mapping.end()) { + throw ValidationError(unit + + " unit not recognized. " + "Allowed values: " + + detail::generate_map(mapping, true)); + } + + if(!input.empty()) { + using CLI::detail::lexical_cast; + bool converted = lexical_cast(input, num); + if(!converted) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // perform safe multiplication + bool ok = detail::checked_multiply(num, it->second); + if(!ok) { + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + + " factor would cause number overflow. Use smaller value."); + } + } else { + num = static_cast(it->second); + } + + input = detail::to_string(num); + + return {}; + }; + } + + private: + /// Check that mapping contains valid units. + /// Update mapping for CASE_INSENSITIVE mode. + template static void validate_mapping(std::map &mapping, Options opts) { + for(auto &kv : mapping) { + if(kv.first.empty()) { + throw ValidationError("Unit must not be empty."); + } + if(!detail::isalpha(kv.first)) { + throw ValidationError("Unit must contain only letters."); + } + } + + // make all units lowercase if CASE_INSENSITIVE + if(opts & CASE_INSENSITIVE) { + std::map lower_mapping; + for(auto &kv : mapping) { + auto s = detail::to_lower(kv.first); + if(lower_mapping.count(s)) { + throw ValidationError(std::string("Several matching lowercase unit representations are found: ") + + s); + } + lower_mapping[detail::to_lower(kv.first)] = kv.second; + } + mapping = std::move(lower_mapping); + } + } + + /// Generate description like this: NUMBER [UNIT] + template static std::string generate_description(const std::string &name, Options opts) { + std::stringstream out; + out << detail::type_name() << ' '; + if(opts & UNIT_REQUIRED) { + out << name; + } else { + out << '[' << name << ']'; + } + return out.str(); + } +}; + +inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, const AsNumberWithUnit::Options &b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +/// Converts a human-readable size string (with unit literal) to uin64_t size. +/// Example: +/// "100" => 100 +/// "1 b" => 100 +/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024) +/// "10 KB" => 10240 +/// "10 kb" => 10240 +/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024) +/// "10kb" => 10240 +/// "2 MB" => 2097152 +/// "2 EiB" => 2^61 // Units up to exibyte are supported +class AsSizeValue : public AsNumberWithUnit { + public: + using result_t = std::uint64_t; + + /// If kb_is_1000 is true, + /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024 + /// (same applies to higher order units as well). + /// Otherwise, interpret all literals as factors of 1024. + /// The first option is formally correct, but + /// the second interpretation is more wide-spread + /// (see https://en.wikipedia.org/wiki/Binary_prefix). + explicit AsSizeValue(bool kb_is_1000); + + private: + /// Get mapping + static std::map init_mapping(bool kb_is_1000); + + /// Cache calculated mapping + static std::map get_mapping(bool kb_is_1000); +}; + +namespace detail { +/// Split a string into a program name and command line arguments +/// the string is assumed to contain a file name followed by other arguments +/// the return value contains is a pair with the first argument containing the program name and the second +/// everything else. +CLI11_INLINE std::pair split_program_name(std::string commandline); + +} // namespace detail +/// @} + + + + +CLI11_INLINE std::string Validator::operator()(std::string &str) const { + std::string retstring; + if(active_) { + if(non_modifying_) { + std::string value = str; + retstring = func_(value); + } else { + retstring = func_(str); + } + } + return retstring; +} + +CLI11_NODISCARD CLI11_INLINE Validator Validator::description(std::string validator_desc) const { + Validator newval(*this); + newval.desc_function_ = [validator_desc]() { return validator_desc; }; + return newval; +} + +CLI11_INLINE Validator Validator::operator&(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " AND "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(!s1.empty() && !s2.empty()) + return std::string("(") + s1 + ") AND (" + s2 + ")"; + return s1 + s2; + }; + + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator|(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " OR "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(s1.empty() || s2.empty()) + return std::string(); + + return std::string("(") + s1 + ") OR (" + s2 + ")"; + }; + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator!() const { + Validator newval; + const std::function &dfunc1 = desc_function_; + newval.desc_function_ = [dfunc1]() { + auto str = dfunc1(); + return (!str.empty()) ? std::string("NOT ") + str : std::string{}; + }; + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + + newval.func_ = [f1, dfunc1](std::string &test) -> std::string { + std::string s1 = f1(test); + if(s1.empty()) { + return std::string("check ") + dfunc1() + " succeeded improperly"; + } + return std::string{}; + }; + newval.active_ = active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE void +Validator::_merge_description(const Validator &val1, const Validator &val2, const std::string &merger) { + + const std::function &dfunc1 = val1.desc_function_; + const std::function &dfunc2 = val2.desc_function_; + + desc_function_ = [=]() { + std::string f1 = dfunc1(); + std::string f2 = dfunc2(); + if((f1.empty()) || (f2.empty())) { + return f1 + f2; + } + return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; + }; +} + +namespace detail { + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE path_type check_path(const char *file) noexcept { + std::error_code ec; + auto stat = std::filesystem::status(to_path(file), ec); + if(ec) { + return path_type::nonexistent; + } + switch(stat.type()) { + case std::filesystem::file_type::none: // LCOV_EXCL_LINE + case std::filesystem::file_type::not_found: + return path_type::nonexistent; // LCOV_EXCL_LINE + case std::filesystem::file_type::directory: + return path_type::directory; + case std::filesystem::file_type::symlink: + case std::filesystem::file_type::block: + case std::filesystem::file_type::character: + case std::filesystem::file_type::fifo: + case std::filesystem::file_type::socket: + case std::filesystem::file_type::regular: + case std::filesystem::file_type::unknown: + default: + return path_type::file; + } +} +#else +CLI11_INLINE path_type check_path(const char *file) noexcept { +#if defined(_MSC_VER) + struct __stat64 buffer; + if(_stat64(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#else + struct stat buffer; + if(stat(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#endif + return path_type::nonexistent; +} +#endif + +CLI11_INLINE ExistingFileValidator::ExistingFileValidator() : Validator("FILE") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "File does not exist: " + filename; + } + if(path_result == path_type::directory) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingDirectoryValidator::ExistingDirectoryValidator() : Validator("DIR") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Directory does not exist: " + filename; + } + if(path_result == path_type::file) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingPathValidator::ExistingPathValidator() : Validator("PATH(existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Path does not exist: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE NonexistentPathValidator::NonexistentPathValidator() : Validator("PATH(non-existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result != path_type::nonexistent) { + return "Path already exists: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { + func_ = [](std::string &ip_addr) { + auto result = CLI::detail::split(ip_addr, '.'); + if(result.size() != 4) { + return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')'; + } + int num = 0; + for(const auto &var : result) { + using CLI::detail::lexical_cast; + bool retval = lexical_cast(var, num); + if(!retval) { + return std::string("Failed parsing number (") + var + ')'; + } + if(num < 0 || num > 255) { + return std::string("Each IP number must be between 0 and 255 ") + var; + } + } + return std::string{}; + }; +} + +CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() { + func_ = [](std::string &str) { + try { + if(str.size() > 1 && (str.front() == '\"' || str.front() == '\'' || str.front() == '`') && + str.front() == str.back()) { + process_quoted_string(str); + } else if(str.find_first_of('\\') != std::string::npos) { + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + } else { + str = remove_escaped_characters(str); + } + } + return std::string{}; + } catch(const std::invalid_argument &ia) { + return std::string(ia.what()); + } + }; +} +} // namespace detail + +CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn) + : Validator("FILE") { + func_ = [default_path, enableErrorReturn](std::string &filename) { + auto path_result = detail::check_path(filename.c_str()); + if(path_result == detail::path_type::nonexistent) { + std::string test_file_path = default_path; + if(default_path.back() != '/' && default_path.back() != '\\') { + // Add folder separator + test_file_path += '/'; + } + test_file_path.append(filename); + path_result = detail::check_path(test_file_path.c_str()); + if(path_result == detail::path_type::file) { + filename = test_file_path; + } else { + if(enableErrorReturn) { + return "File does not exist: " + filename; + } + } + } + return std::string{}; + }; +} + +CLI11_INLINE AsSizeValue::AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) { + if(kb_is_1000) { + description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); + } else { + description("SIZE [b, kb(=1024b), ...]"); + } +} + +CLI11_INLINE std::map AsSizeValue::init_mapping(bool kb_is_1000) { + std::map m; + result_t k_factor = kb_is_1000 ? 1000 : 1024; + result_t ki_factor = 1024; + result_t k = 1; + result_t ki = 1; + m["b"] = 1; + for(std::string p : {"k", "m", "g", "t", "p", "e"}) { + k *= k_factor; + ki *= ki_factor; + m[p] = k; + m[p + "b"] = k; + m[p + "i"] = ki; + m[p + "ib"] = ki; + } + return m; +} + +CLI11_INLINE std::map AsSizeValue::get_mapping(bool kb_is_1000) { + if(kb_is_1000) { + static auto m = init_mapping(true); + return m; + } + static auto m = init_mapping(false); + return m; +} + +namespace detail { + +CLI11_INLINE std::pair split_program_name(std::string commandline) { + // try to determine the programName + std::pair vals; + trim(commandline); + auto esp = commandline.find_first_of(' ', 1); + while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { + esp = commandline.find_first_of(' ', esp + 1); + if(esp == std::string::npos) { + // if we have reached the end and haven't found a valid file just assume the first argument is the + // program name + if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') { + bool embeddedQuote = false; + auto keyChar = commandline[0]; + auto end = commandline.find_first_of(keyChar, 1); + while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes + end = commandline.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + vals.first = commandline.substr(1, end - 1); + esp = end + 1; + if(embeddedQuote) { + vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar)); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + + break; + } + } + if(vals.first.empty()) { + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + } + + // strip the program name + vals.second = (esp < commandline.length() - 1) ? commandline.substr(esp + 1) : std::string{}; + ltrim(vals.second); + return vals; +} + +} // namespace detail +/// @} + + + + +class Option; +class App; + +/// This enum signifies the type of help requested +/// +/// This is passed in by App; all user classes must accept this as +/// the second argument. + +enum class AppFormatMode { + Normal, ///< The normal, detailed help + All, ///< A fully expanded help + Sub, ///< Used when printed as part of expanded subcommand +}; + +/// This is the minimum requirements to run a formatter. +/// +/// A user can subclass this is if they do not care at all +/// about the structure in CLI::Formatter. +class FormatterBase { + protected: + /// @name Options + ///@{ + + /// The width of the first column + std::size_t column_width_{30}; + + /// @brief The required help printout labels (user changeable) + /// Values are Needs, Excludes, etc. + std::map labels_{}; + + ///@} + /// @name Basic + ///@{ + + public: + FormatterBase() = default; + FormatterBase(const FormatterBase &) = default; + FormatterBase(FormatterBase &&) = default; + FormatterBase &operator=(const FormatterBase &) = default; + FormatterBase &operator=(FormatterBase &&) = default; + + /// Adding a destructor in this form to work around bug in GCC 4.7 + virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) + + /// This is the key method that puts together help + virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; + + ///@} + /// @name Setters + ///@{ + + /// Set the "REQUIRED" label + void label(std::string key, std::string val) { labels_[key] = val; } + + /// Set the column width + void column_width(std::size_t val) { column_width_ = val; } + + ///@} + /// @name Getters + ///@{ + + /// Get the current value of a name (REQUIRED, etc.) + CLI11_NODISCARD std::string get_label(std::string key) const { + if(labels_.find(key) == labels_.end()) + return key; + return labels_.at(key); + } + + /// Get the current column width + CLI11_NODISCARD std::size_t get_column_width() const { return column_width_; } + + ///@} +}; + +/// This is a specialty override for lambda functions +class FormatterLambda final : public FormatterBase { + using funct_t = std::function; + + /// The lambda to hold and run + funct_t lambda_; + + public: + /// Create a FormatterLambda with a lambda function + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} + + /// Adding a destructor (mostly to make GCC 4.7 happy) + ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) + + /// This will simply call the lambda function + std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { + return lambda_(app, name, mode); + } +}; + +/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few +/// overridable methods, to be highly customizable with minimal effort. +class Formatter : public FormatterBase { + public: + Formatter() = default; + Formatter(const Formatter &) = default; + Formatter(Formatter &&) = default; + Formatter &operator=(const Formatter &) = default; + Formatter &operator=(Formatter &&) = default; + + /// @name Overridables + ///@{ + + /// This prints out a group of options with title + /// + CLI11_NODISCARD virtual std::string + make_group(std::string group, bool is_positional, std::vector opts) const; + + /// This prints out just the positionals "group" + virtual std::string make_positionals(const App *app) const; + + /// This prints out all the groups of options + std::string make_groups(const App *app, AppFormatMode mode) const; + + /// This prints out all the subcommands + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; + + /// This prints out a subcommand + virtual std::string make_subcommand(const App *sub) const; + + /// This prints out a subcommand in help-all + virtual std::string make_expanded(const App *sub) const; + + /// This prints out all the groups of options + virtual std::string make_footer(const App *app) const; + + /// This displays the description line + virtual std::string make_description(const App *app) const; + + /// This displays the usage line + virtual std::string make_usage(const App *app, std::string name) const; + + /// This puts everything together + std::string make_help(const App * /*app*/, std::string, AppFormatMode) const override; + + ///@} + /// @name Options + ///@{ + + /// This prints out an option help line, either positional or optional form + virtual std::string make_option(const Option *opt, bool is_positional) const { + std::stringstream out; + detail::format_help( + out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); + return out.str(); + } + + /// @brief This is the name part of an option, Default: left column + virtual std::string make_option_name(const Option *, bool) const; + + /// @brief This is the options part of the name, Default: combined into left column + virtual std::string make_option_opts(const Option *) const; + + /// @brief This is the description. Default: Right column, on new line if left column too large + virtual std::string make_option_desc(const Option *) const; + + /// @brief This is used to print the name on the USAGE line + virtual std::string make_option_usage(const Option *opt) const; + + ///@} +}; + + + + +using results_t = std::vector; +/// callback function definition +using callback_t = std::function; + +class Option; +class App; + +using Option_p = std::unique_ptr