diff --git a/.github/workflows/build-arm64-wheels.yml b/.github/workflows/build-arm64-wheels.yml index ba40fe845..6d8a5f72c 100644 --- a/.github/workflows/build-arm64-wheels.yml +++ b/.github/workflows/build-arm64-wheels.yml @@ -5,12 +5,16 @@ on: branches: - main - dev - tags: - - '**' + release: + types: [published] pull_request: branches: - '**' +permissions: + id-token: write + contents: read + jobs: build_wheels: name: Build ARM64 Python Wheels @@ -61,7 +65,7 @@ jobs: name: wheels path: target/wheels/ - - name: Install Twine + - name: Install job deps run: | if [ ! -f "venv" ]; then rm -rf venv; fi sudo apt install python3 python3-pip -y @@ -69,49 +73,46 @@ jobs: if [ ! -f "activate" ]; then ln -s venv/bin/activate; fi . ./activate pip3 install setuptools_rust - pip3 install twine - name: Test for secrets access id: check_secrets shell: bash run: | - unset HAS_SECRET unset HAS_AWS_SECRET - if [ -n "$SECRET" ]; then HAS_SECRET='true' ; fi - echo "HAS_SECRET=${HAS_SECRET}" >>$GITHUB_OUTPUT - if [ -n "$AWS_SECRET" ]; then HAS_AWS_SECRET='true' ; fi echo HAS_AWS_SECRET=${HAS_AWS_SECRET} >>$GITHUB_OUTPUT env: - SECRET: "${{ secrets.test_pypi_password }}" - AWS_SECRET: "${{ secrets.INSTALLER_UPLOAD_KEY }}" + AWS_SECRET: "${{ secrets.CHIA_AWS_ACCOUNT_ID }}" - - name: publish (PyPi) - if: startsWith(github.event.ref, 'refs/tags') && steps.check_secrets.outputs.HAS_SECRET + - name: Set Env + uses: Chia-Network/actions/setjobenv@main env: - TWINE_USERNAME: __token__ - TWINE_NON_INTERACTIVE: 1 - TWINE_PASSWORD: ${{ secrets.pypi_password }} - run: | - . ./activate - twine upload --non-interactive --skip-existing --verbose 'target/wheels/*' + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: publish (PyPi) + if: env.RELEASE == 'true' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: target/wheels/ + skip-existing: true + - name: publish (Test PyPi) - if: steps.check_secrets.outputs.HAS_SECRET - env: - TWINE_REPOSITORY_URL: https://test.pypi.org/legacy/ - TWINE_USERNAME: __token__ - TWINE_NON_INTERACTIVE: 1 - TWINE_PASSWORD: ${{ secrets.test_pypi_password }} - run: | - . ./activate - twine upload --non-interactive --skip-existing --verbose 'target/wheels/*' + if: env.PRE_RELEASE == 'true' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + packages-dir: target/wheels/ + skip-existing: true + + - name: Configure AWS credentials + if: steps.check_secrets.outputs.HAS_AWS_SECRET + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::${{ secrets.CHIA_AWS_ACCOUNT_ID }}:role/installer-upload + aws-region: us-west-2 - name: Publish Dev if: steps.check_secrets.outputs.HAS_AWS_SECRET && github.ref == 'refs/heads/dev' - env: - AWS_ACCESS_KEY_ID: "${{ secrets.INSTALLER_UPLOAD_KEY }}" - AWS_SECRET_ACCESS_KEY: "${{ secrets.INSTALLER_UPLOAD_SECRET }}" - AWS_REGION: us-west-2 run: | FILES=$(find ${{ github.workspace }}/target/wheels -type f -name '*.whl') while IFS= read -r file; do diff --git a/.github/workflows/build-m1-wheel.yml b/.github/workflows/build-m1-wheel.yml index 917451d4f..5831afae7 100644 --- a/.github/workflows/build-m1-wheel.yml +++ b/.github/workflows/build-m1-wheel.yml @@ -5,8 +5,8 @@ on: branches: - main - dev - tags: - - '**' + release: + types: [published] pull_request: branches: - '**' @@ -15,10 +15,14 @@ concurrency: group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}--${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/long_lived/')) && github.sha || '' }} cancel-in-progress: true +permissions: + id-token: write + contents: read + jobs: build_wheels: name: Build wheel on Mac M1 - runs-on: [m1] + runs-on: [MacOS, ARM64] strategy: fail-fast: false @@ -45,43 +49,43 @@ jobs: - name: Build m1 wheels run: | - arch -arm64 python3 -m venv venv + python3 -m venv venv . ./venv/bin/activate export PATH=~/.cargo/bin:$PATH - arch -arm64 pip install maturin==1.1.0 - arch -arm64 maturin build -i python --release --strip - arch -arm64 cargo test + pip install maturin==1.1.0 + maturin build -i python --release --strip + cargo test - name: Install clvm_tools_rs wheel run: | . ./venv/bin/activate ls target/wheels/ - arch -arm64 pip install ./target/wheels/clvm_tools_rs*.whl + pip install ./target/wheels/clvm_tools_rs*.whl - name: Install other wheels run: | . ./venv/bin/activate - arch -arm64 python -m pip install pytest - arch -arm64 python -m pip install blspy + python -m pip install pytest + python -m pip install blspy - name: install clvm & clvm_tools run: | . ./venv/bin/activate - arch -arm64 git clone https://github.com/Chia-Network/clvm.git --branch=main --single-branch - arch -arm64 python -m pip install ./clvm - arch -arm64 python -m pip install clvm_rs + git clone https://github.com/Chia-Network/clvm.git --branch=main --single-branch + python -m pip install ./clvm + python -m pip install clvm_rs - arch -arm64 git clone https://github.com/Chia-Network/clvm_tools.git --branch=main --single-branch - arch -arm64 python -m pip install ./clvm_tools + git clone https://github.com/Chia-Network/clvm_tools.git --branch=main --single-branch + python -m pip install ./clvm_tools - name: Ensure clvm, clvm_rs, clvm_tools are installed run: | . ./venv/bin/activate - arch -arm64 python -c 'import clvm' - arch -arm64 python -c 'import clvm; print(clvm.__file__)' - arch -arm64 python -c 'import clvm_rs; print(clvm_rs.__file__)' - arch -arm64 python -c 'import clvm_tools; print(clvm_tools.__file__)' - arch -arm64 python -c 'import clvm_tools_rs; print(clvm_tools_rs.__file__)' + python -c 'import clvm' + python -c 'import clvm; print(clvm.__file__)' + python -c 'import clvm_rs; print(clvm_rs.__file__)' + python -c 'import clvm_tools; print(clvm_tools.__file__)' + python -c 'import clvm_tools_rs; print(clvm_tools_rs.__file__)' - name: Install pytest run: | @@ -93,13 +97,13 @@ jobs: # run: | # . ./venv/bin/activate # cd clvm -# arch -arm64 python -m py.test tests +# python -m py.test tests - name: Run tests from clvm_tools run: | . ./venv/bin/activate cd clvm_tools - arch -arm64 pytest + pytest - name: Upload wheels uses: actions/upload-artifact@v3 @@ -111,44 +115,42 @@ jobs: id: check_secrets shell: bash run: | - unset HAS_SECRET unset HAS_AWS_SECRET - if [ -n "$SECRET" ]; then HAS_SECRET='true' ; fi - echo "HAS_SECRET=${HAS_SECRET}" >>$GITHUB_OUTPUT - if [ -n "$AWS_SECRET" ]; then HAS_AWS_SECRET='true' ; fi echo HAS_AWS_SECRET=${HAS_AWS_SECRET} >>$GITHUB_OUTPUT env: - SECRET: "${{ secrets.test_pypi_password }}" - AWS_SECRET: "${{ secrets.INSTALLER_UPLOAD_KEY }}" - - - name: Install twine - run: arch -arm64 pip install twine + AWS_SECRET: "${{ secrets.CHIA_AWS_ACCOUNT_ID }}" - - name: Publish distribution to PyPI - if: startsWith(github.event.ref, 'refs/tags') && steps.check_secrets.outputs.HAS_SECRET + - name: Set Env + uses: Chia-Network/actions/setjobenv@main env: - TWINE_USERNAME: __token__ - TWINE_NON_INTERACTIVE: 1 - TWINE_PASSWORD: ${{ secrets.pypi_password }} - run: arch -arm64 twine upload --non-interactive --skip-existing --verbose 'target/wheels/*' + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Publish distribution to Test PyPI - if: steps.check_secrets.outputs.HAS_SECRET - env: - TWINE_REPOSITORY_URL: https://test.pypi.org/legacy/ - TWINE_USERNAME: __token__ - TWINE_NON_INTERACTIVE: 1 - TWINE_PASSWORD: ${{ secrets.test_pypi_password }} - run: arch -arm64 twine upload --non-interactive --skip-existing --verbose 'target/wheels/*' + - name: publish (PyPi) + if: env.RELEASE == 'true' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: target/wheels/ + skip-existing: true + + - name: publish (Test PyPi) + if: env.PRE_RELEASE == 'true' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + packages-dir: target/wheels/ + skip-existing: true + + - name: Configure AWS credentials + if: steps.check_secrets.outputs.HAS_AWS_SECRET + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::${{ secrets.CHIA_AWS_ACCOUNT_ID }}:role/installer-upload + aws-region: us-west-2 - name: Publish Dev if: steps.check_secrets.outputs.HAS_AWS_SECRET && github.ref == 'refs/heads/dev' - env: - AWS_ACCESS_KEY_ID: "${{ secrets.INSTALLER_UPLOAD_KEY }}" - AWS_SECRET_ACCESS_KEY: "${{ secrets.INSTALLER_UPLOAD_SECRET }}" - AWS_REGION: us-west-2 run: | FILES=$(find ${{ github.workspace }}/target/wheels -type f -name '*.whl') while IFS= read -r file; do diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index f56023f04..02c61ab73 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -1,17 +1,21 @@ # Thanks: clvm_rs' github actions. -name: Build Mac, Linux, and Windows wheels +name: Build on: push: branches: - base - dev - tags: - - '**' + release: + types: [published] pull_request: branches: - '**' +permissions: + id-token: write + contents: read + jobs: build_wheels: name: Wheel on ${{ matrix.os }} py-${{ matrix.python }} @@ -85,12 +89,6 @@ jobs: # Ensure an empty .cargo-lock file exists. touch target/release/.cargo-lock - - name: Build alpine wheel via docker - if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.python, '3.8') - run: | - cd resources/alpine && docker build -t clvm-tools-rs-alpine . - docker run -v ${GITHUB_WORKSPACE}:/root/clvm_tools_rs -t clvm-tools-rs-alpine sh /root/build-alpine.sh - - name: Build Windows with maturin on Python ${{ matrix.python }} if: startsWith(matrix.os, 'windows') run: | @@ -116,7 +114,7 @@ jobs: run: | . ./activate python -m pip install pytest - python -m pip install blspy + python -m pip install blspy - name: install clvm & clvm_tools run: | @@ -140,10 +138,13 @@ jobs: python -c 'import clvm; print(clvm.__file__)' python -c 'import clvm_rs; print(clvm_rs.__file__)' python -c 'import clvm_tools_rs; print(clvm_tools_rs.__file__)' + echo "CLVM_TOOLS_RS_VERSION=$(python -c 'import clvm_tools_rs; print(clvm_tools_rs.get_version())')" >> "$GITHUB_ENV" - - name: Verify recompilation of old sources match + + - name: Verify recompilation of old sources match with new compiler if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.python, '3.8') run: | + set -x . ./activate # Build cmd line tools PYO3_PYTHON=`which python` cargo build --no-default-features --release @@ -152,9 +153,16 @@ jobs: rm -rf chia-blockchain git clone https://github.com/Chia-Network/chia-blockchain - # Check recompiles - cp support/recompile_check.py chia-blockchain - (cd chia-blockchain && python recompile_check.py) + # Check that recompiling deployed puzzles match with their deployed hashes + cp support/install_deps.sh support/verify_compiler_version.sh chia-blockchain + (cd chia-blockchain && python -m venv venv && . venv/bin/activate && pip install --upgrade pip && \ + python -m pip install maturin==1.1.0 && \ + cd .. && python support/wheelname.py && \ + cd chia-blockchain && \ + # deps for manage_clvm.py + pip install click typing_extensions chia_rs clvm && \ + export PYTHONPATH=${PYTHONPATH}:$(pwd) && \ + ./verify_compiler_version.sh ${CLVM_TOOLS_RS_VERSION} && ./activated.py python tools/manage_clvm.py check) - name: Test Classic command line tools with pytest if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.python, '3.8') @@ -204,6 +212,10 @@ jobs: if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.python, '3.8') run: cargo test --no-default-features + - name: Exhaustive assign tests + if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.python, '3.8') + run: cargo test -- --include-ignored assign + - name: Check coverage if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.python, '3.8') run: | @@ -217,51 +229,48 @@ jobs: name: wheels path: ./target/wheels/ - - name: Install Twine - run: pip install twine - - name: Test for secrets access id: check_secrets shell: bash run: | - unset HAS_SECRET unset HAS_AWS_SECRET - if [ -n "$SECRET" ]; then HAS_SECRET='true' ; fi - echo "HAS_SECRET=${HAS_SECRET}" >>$GITHUB_OUTPUT - if [ -n "$AWS_SECRET" ]; then HAS_AWS_SECRET='true' ; fi echo HAS_AWS_SECRET=${HAS_AWS_SECRET} >>$GITHUB_OUTPUT + env: + AWS_SECRET: "${{ secrets.CHIA_AWS_ACCOUNT_ID }}" + - name: Set Env + uses: Chia-Network/actions/setjobenv@main env: - SECRET: "${{ secrets.test_pypi_password }}" - AWS_SECRET: "${{ secrets.INSTALLER_UPLOAD_KEY }}" + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: publish (PyPi) - if: startsWith(github.event.ref, 'refs/tags') && steps.check_secrets.outputs.HAS_SECRET - env: - TWINE_USERNAME: __token__ - TWINE_NON_INTERACTIVE: 1 - TWINE_PASSWORD: ${{ secrets.pypi_password }} - run: twine upload --non-interactive --skip-existing --verbose 'target/wheels/*' + if: env.RELEASE == 'true' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: target/wheels/ + skip-existing: true - name: publish (Test PyPi) - if: steps.check_secrets.outputs.HAS_SECRET - env: - TWINE_REPOSITORY_URL: https://test.pypi.org/legacy/ - TWINE_USERNAME: __token__ - TWINE_NON_INTERACTIVE: 1 - TWINE_PASSWORD: ${{ secrets.test_pypi_password }} - run: twine upload --non-interactive --skip-existing --verbose 'target/wheels/*' + if: env.PRE_RELEASE == 'true' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + packages-dir: target/wheels/ + skip-existing: true + + - name: Configure AWS credentials + if: steps.check_secrets.outputs.HAS_AWS_SECRET + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::${{ secrets.CHIA_AWS_ACCOUNT_ID }}:role/installer-upload + aws-region: us-west-2 - name: Publish Dev if: steps.check_secrets.outputs.HAS_AWS_SECRET && github.ref == 'refs/heads/dev' shell: bash working-directory: ./target/wheels - env: - AWS_ACCESS_KEY_ID: "${{ secrets.INSTALLER_UPLOAD_KEY }}" - AWS_SECRET_ACCESS_KEY: "${{ secrets.INSTALLER_UPLOAD_SECRET }}" - AWS_REGION: us-west-2 run: | FILES=$(find . -type f -name '*.whl') while IFS= read -r file; do @@ -269,6 +278,12 @@ jobs: aws --no-progress s3 cp "$file" "s3://download.chia.net/simple-dev/clvm-tools-rs/$filename" done <<< "$FILES" + - name: Build alpine wheel via docker + if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.python, '3.8') + run: | + cd resources/alpine && docker build -t clvm-tools-rs-alpine . + docker run -v ${GITHUB_WORKSPACE}:/root/clvm_tools_rs -t clvm-tools-rs-alpine sh /root/build-alpine.sh + fmt: runs-on: ubuntu-20.04 name: cargo fmt @@ -296,10 +311,10 @@ jobs: override: true - name: clippy run: cargo clippy --all -- -D warnings - - uses: actions-rs/clippy-check@v1 + - uses: giraffate/clippy-action@v1 with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features + reporter: 'github-pr-review' + github_token: ${{ secrets.GITHUB_TOKEN }} unit_tests: runs-on: ubuntu-20.04 diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 395a02866..4b1aa23c9 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -3,26 +3,33 @@ name: npm publish on: push: branches: - - main - - dev - tags: - - '**' + - base + release: + types: [published] pull_request: branches: - '**' +concurrency: + # SHA is added to the end if on `main` to let all main workflows run + group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ (github.ref == 'refs/heads/main') && github.sha || '' }} + cancel-in-progress: true + jobs: build_npm: name: Npm runs-on: ubuntu-latest - strategy: - fail-fast: false steps: - uses: actions/checkout@v3 with: fetch-depth: 0 + - name: Set Env + uses: Chia-Network/actions/setjobenv@main + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Set up rusts uses: actions-rs/toolchain@v1 with: @@ -40,6 +47,13 @@ jobs: with: node-version: '16.x' + # Cargo.toml won't allow an "@" in the name, so we just update the package name this way for NPM + - name: Update package name for npm + working-directory: ${{ github.workspace }}/wasm/pkg + run: | + cp package.json package.json.orig + jq '.name="@chia/chialisp"' package.json > temp.json && mv temp.json package.json + - name: Test wasm run: node wasm/tests/index.js @@ -57,12 +71,14 @@ jobs: if [ -n "$SECRET" ]; then HAS_SECRET='true' ; fi echo "HAS_SECRET=${HAS_SECRET}" >>$GITHUB_OUTPUT env: - SECRET: "${{ secrets.test_pypi_password }}" + SECRET: "${{ secrets.NPM_TOKEN }}" - name: Publish wasm - if: steps.check_secrets.HAS_SECRET - shell: bash + if: env.FULL_RELEASE == 'true' && steps.check_secrets.HAS_SECRET + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + working-directory: ${{ github.workspace }}/wasm/pkg run: | - cd wasm/pkg + echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc rm -f clvm_tools_wasm-*.tgz npm publish --access public diff --git a/CHANGELOG.md b/CHANGELOG.md index a2bbf5116..e50a9fa5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,3 +24,9 @@ Skipped - hierarchial debug was added. - clvm command linetools: supported more command line features in both compiler front-ends. +## 0.1.35 + +- embed-file was added. +- &rest arguments. +- new bls and sec256 operators. + diff --git a/Cargo.lock b/Cargo.lock index 6cc688bc4..2afc51d86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,7 +117,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clvm_tools_rs" -version = "0.1.34" +version = "0.1.35" dependencies = [ "binascii", "bls12_381", @@ -151,9 +151,9 @@ dependencies = [ [[package]] name = "clvmr" -version = "0.2.7" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2890f01537f1be43d2767ae71bbba0d0b3543dbb1ee892092d0ed4d913227fc" +checksum = "9cd344b6dc76235f446025fe9ebe54aa6131e2e59acb49e16be48a3bb3492491" dependencies = [ "bls12_381", "getrandom", diff --git a/Cargo.toml b/Cargo.toml index 63337780e..43ad94367 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clvm_tools_rs" -version = "0.1.34" +version = "0.1.35" edition = "2018" authors = ["Art Yerkes "] description = "tools for working with chialisp language; compiler, repl, python and wasm bindings" @@ -32,7 +32,7 @@ do-notation = "0.1.3" serde_json = "1.0" sha2 = "0.9.5" tempfile = "3.3.0" -clvmr = { version = "0.2.6", features = ["pre-eval"] } +clvmr = { version = "0.3.0", features = ["pre-eval"] } binascii = "0.1.4" yaml-rust = "0.4" linked-hash-map = "0.5.6" diff --git a/pyproject.toml b/pyproject.toml index bd9c56524..6049eb79b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["maturin>=0.11.3,<0.12"] +requires = ["maturin>=1.1.0,<1.3.0"] build-backend = "maturin" [tool.maturin] diff --git a/resources/coverage/run_coverage.py b/resources/coverage/run_coverage.py index 5399cf0c7..4c6b48b06 100644 --- a/resources/coverage/run_coverage.py +++ b/resources/coverage/run_coverage.py @@ -62,7 +62,7 @@ def collect_coverage(): required_percentage = int(args.require_percent) has_required_pct = True - for file in result: + for file in filter(lambda f: '/tests/' not in f['name'], result): have_pct = int(file['coveragePercent']) if have_pct < required_percentage: print(f"{file['name']} lacks required coverage have {have_pct} want {required_percentage}") diff --git a/resources/tests/bls/classic-bls-op-test-1.clsp b/resources/tests/bls/classic-bls-op-test-1.clsp new file mode 100644 index 000000000..c8bbe6662 --- /dev/null +++ b/resources/tests/bls/classic-bls-op-test-1.clsp @@ -0,0 +1,5 @@ +(mod () + (defconstant msg 0x9790635de8740e9a6a6b15fb6b72f3a16afa0973d971979b6ba54761d6e2502c50db76f4d26143f05459a42cfd520d44) + + (g1_map msg) + ) diff --git a/resources/tests/bls/classic-bls-verify-signature.clsp b/resources/tests/bls/classic-bls-verify-signature.clsp new file mode 100644 index 000000000..833d549d0 --- /dev/null +++ b/resources/tests/bls/classic-bls-verify-signature.clsp @@ -0,0 +1,8 @@ +(mod (message) + (defconstant pk_bytes 0x86243290bbcbfd9ae75bdece7981965350208eb5e99b04d5cd24e955ada961f8c0a162dee740be7bdc6c3c0613ba2eb1) + (defconstant signature_bytes 0xb00ab9a8af54804b43067531d96c176710c05980fccf8eee1ae12a4fd543df929cce860273af931fe4fdbc407d495f73114ab7d17ef08922e56625daada0497582340ecde841a9e997f2f557653c21c070119662dd2efa47e2d6c5e2de00eefa) + + ;; Seems like bls_verify should do it + ;; G2 G1 msg + (bls_verify signature_bytes pk_bytes message) + ) diff --git a/resources/tests/bls/coinid-fail.clsp b/resources/tests/bls/coinid-fail.clsp new file mode 100644 index 000000000..d7ef3b836 --- /dev/null +++ b/resources/tests/bls/coinid-fail.clsp @@ -0,0 +1,5 @@ +(mod () + (include *standard-cl-21*) + + (softfork (q . 1432) (q . 0) (q a (i (= (#coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 123456789)) (q . 0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bc)) (q x) (q . 0)) (q . ())) (q . ())) + ) diff --git a/resources/tests/bls/coinid-good.clsp b/resources/tests/bls/coinid-good.clsp new file mode 100644 index 000000000..93f16b02c --- /dev/null +++ b/resources/tests/bls/coinid-good.clsp @@ -0,0 +1,5 @@ +(mod () + (include *standard-cl-21*) + + (softfork (q . 1432) (q . 0) (q a (i (= (#coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 123456789)) (q . 0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bc)) (q . 0) (q x)) (q . ())) (q . ())) + ) diff --git a/resources/tests/bls/modern-bls-op-test-1.clsp b/resources/tests/bls/modern-bls-op-test-1.clsp new file mode 100644 index 000000000..d5f60d38a --- /dev/null +++ b/resources/tests/bls/modern-bls-op-test-1.clsp @@ -0,0 +1,6 @@ +(mod () + (include *standard-cl-21*) + (defconstant msg 0x9790635de8740e9a6a6b15fb6b72f3a16afa0973d971979b6ba54761d6e2502c50db76f4d26143f05459a42cfd520d44) + + (g1_map msg) + ) diff --git a/resources/tests/bls/modern-bls-verify-signature.clsp b/resources/tests/bls/modern-bls-verify-signature.clsp new file mode 100644 index 000000000..ccde5a1f2 --- /dev/null +++ b/resources/tests/bls/modern-bls-verify-signature.clsp @@ -0,0 +1,9 @@ +(mod (message) + (include *standard-cl-21*) + (defconstant pk_bytes 0x86243290bbcbfd9ae75bdece7981965350208eb5e99b04d5cd24e955ada961f8c0a162dee740be7bdc6c3c0613ba2eb1) + (defconstant signature_bytes 0xb00ab9a8af54804b43067531d96c176710c05980fccf8eee1ae12a4fd543df929cce860273af931fe4fdbc407d495f73114ab7d17ef08922e56625daada0497582340ecde841a9e997f2f557653c21c070119662dd2efa47e2d6c5e2de00eefa) + + ;; Seems like bls_verify should do it + ;; G2 G1 msg + (bls_verify signature_bytes pk_bytes message) + ) diff --git a/resources/tests/chia-gaming/last.clinc b/resources/tests/chia-gaming/last.clinc new file mode 100644 index 000000000..5a6ffd73e --- /dev/null +++ b/resources/tests/chia-gaming/last.clinc @@ -0,0 +1,39 @@ +( + (defun last_inner ((next . remainder)) + (if remainder + (last_inner remainder) + next + ) + ) + + (defmacro last ARGS + (defun snoc (L agg) + (if L + (if (r L) + (snoc (r L) (c (f L) agg)) + (c (f L) agg) + ) + (c () ()) + ) + ) + + (defun prefix (L P) + (if L + (c (f L) (prefix (r L) P)) + P + ) + ) + + (if ARGS + (if (r ARGS) + (assign + (final . rest) (snoc ARGS ()) + reversed (prefix rest (list final)) + (qq (last_inner (unquote (c list reversed)))) + ) + (qq (last_inner (unquote (f ARGS)))) + ) + (x "Last takes at least one argument") + ) + ) +) diff --git a/resources/tests/chia-gaming/test-last.clsp b/resources/tests/chia-gaming/test-last.clsp new file mode 100644 index 000000000..eb2076769 --- /dev/null +++ b/resources/tests/chia-gaming/test-last.clsp @@ -0,0 +1,6 @@ +(mod () + (include *standard-cl-21*) + (include last.clinc) + + (last 99 100 101) + ) diff --git a/src/classic/clvm/casts.rs b/src/classic/clvm/casts.rs index 4aeeab5ec..de10c7190 100644 --- a/src/classic/clvm/casts.rs +++ b/src/classic/clvm/casts.rs @@ -13,7 +13,7 @@ pub struct TConvertOption { } pub fn int_from_bytes( - allocator: &mut Allocator, + allocator: &Allocator, b: Bytes, option: Option, ) -> Result { diff --git a/src/classic/clvm/mod.rs b/src/classic/clvm/mod.rs index a0273739e..0c54c0087 100644 --- a/src/classic/clvm/mod.rs +++ b/src/classic/clvm/mod.rs @@ -8,103 +8,294 @@ pub mod serialize; pub mod sexp; pub mod syntax_error; +/// This specifies the latest "version" of the operator set. This will increase +/// each time a new set of operators is included in the primitive set in clvm. +/// We keep track of what was added when so users can specify what version of the +/// tools' output they're expecting when it matters. +pub const OPERATORS_LATEST_VERSION: usize = 1; + struct KwAtomPair { - v: u8, + v: &'static [u8], n: &'static str, + version: usize, } -const KW_PAIRS: [KwAtomPair; 32] = [ - KwAtomPair { v: 0x01, n: "q" }, - KwAtomPair { v: 0x02, n: "a" }, - KwAtomPair { v: 0x03, n: "i" }, - KwAtomPair { v: 0x04, n: "c" }, - KwAtomPair { v: 0x05, n: "f" }, - KwAtomPair { v: 0x06, n: "r" }, - KwAtomPair { v: 0x07, n: "l" }, - KwAtomPair { v: 0x08, n: "x" }, - KwAtomPair { v: 0x09, n: "=" }, - KwAtomPair { v: 0x0a, n: ">s" }, - KwAtomPair { - v: 0x0b, +const KW_PAIRS: [KwAtomPair; 46] = [ + KwAtomPair { + v: &[0x01], + n: "q", + version: 0, + }, + KwAtomPair { + v: &[0x02], + n: "a", + version: 0, + }, + KwAtomPair { + v: &[0x03], + n: "i", + version: 0, + }, + KwAtomPair { + v: &[0x04], + n: "c", + version: 0, + }, + KwAtomPair { + v: &[0x05], + n: "f", + version: 0, + }, + KwAtomPair { + v: &[0x06], + n: "r", + version: 0, + }, + KwAtomPair { + v: &[0x07], + n: "l", + version: 0, + }, + KwAtomPair { + v: &[0x08], + n: "x", + version: 0, + }, + KwAtomPair { + v: &[0x09], + n: "=", + version: 0, + }, + KwAtomPair { + v: &[0x0a], + n: ">s", + version: 0, + }, + KwAtomPair { + v: &[0x0b], n: "sha256", + version: 0, }, KwAtomPair { - v: 0x0c, + v: &[0x0c], n: "substr", + version: 0, }, KwAtomPair { - v: 0x0d, + v: &[0x0d], n: "strlen", + version: 0, }, KwAtomPair { - v: 0x0e, + v: &[0x0e], n: "concat", + version: 0, + }, + KwAtomPair { + v: &[0x10], + n: "+", + version: 0, + }, + KwAtomPair { + v: &[0x11], + n: "-", + version: 0, + }, + KwAtomPair { + v: &[0x12], + n: "*", + version: 0, + }, + KwAtomPair { + v: &[0x13], + n: "/", + version: 0, }, - KwAtomPair { v: 0x10, n: "+" }, - KwAtomPair { v: 0x11, n: "-" }, - KwAtomPair { v: 0x12, n: "*" }, - KwAtomPair { v: 0x13, n: "/" }, KwAtomPair { - v: 0x14, + v: &[0x14], n: "divmod", + version: 0, }, - KwAtomPair { v: 0x15, n: ">" }, - KwAtomPair { v: 0x16, n: "ash" }, - KwAtomPair { v: 0x17, n: "lsh" }, KwAtomPair { - v: 0x18, + v: &[0x15], + n: ">", + version: 0, + }, + KwAtomPair { + v: &[0x16], + n: "ash", + version: 0, + }, + KwAtomPair { + v: &[0x17], + n: "lsh", + version: 0, + }, + KwAtomPair { + v: &[0x18], n: "logand", + version: 0, }, KwAtomPair { - v: 0x19, + v: &[0x19], n: "logior", + version: 0, }, KwAtomPair { - v: 0x1a, + v: &[0x1a], n: "logxor", + version: 0, }, KwAtomPair { - v: 0x1b, + v: &[0x1b], n: "lognot", + version: 0, }, KwAtomPair { - v: 0x1d, + v: &[0x1d], n: "point_add", + version: 0, }, KwAtomPair { - v: 0x1e, + v: &[0x1e], n: "pubkey_for_exp", + version: 0, + }, + KwAtomPair { + v: &[0x20], + n: "not", + version: 0, + }, + KwAtomPair { + v: &[0x21], + n: "any", + version: 0, + }, + KwAtomPair { + v: &[0x22], + n: "all", + version: 0, }, - KwAtomPair { v: 0x20, n: "not" }, - KwAtomPair { v: 0x21, n: "any" }, - KwAtomPair { v: 0x22, n: "all" }, KwAtomPair { - v: 0x24, + v: &[0x24], n: "softfork", + version: 0, + }, + KwAtomPair { + v: &[0x30], + n: "coinid", + version: 1, + }, + KwAtomPair { + v: &[0x31], + n: "g1_subtract", + version: 1, + }, + KwAtomPair { + v: &[0x32], + n: "g1_multiply", + version: 1, + }, + KwAtomPair { + v: &[0x33], + n: "g1_negate", + version: 1, + }, + KwAtomPair { + v: &[0x34], + n: "g2_add", + version: 1, + }, + KwAtomPair { + v: &[0x35], + n: "g2_subtract", + version: 1, + }, + KwAtomPair { + v: &[0x36], + n: "g2_multiply", + version: 1, + }, + KwAtomPair { + v: &[0x37], + n: "g2_negate", + version: 1, + }, + KwAtomPair { + v: &[0x38], + n: "g1_map", + version: 1, + }, + KwAtomPair { + v: &[0x39], + n: "g2_map", + version: 1, + }, + KwAtomPair { + v: &[0x3a], + n: "bls_pairing_identity", + version: 1, + }, + KwAtomPair { + v: &[0x3b], + n: "bls_verify", + version: 1, + }, + KwAtomPair { + v: &[0x13, 0xd6, 0x1f, 0x00], + n: "secp256k1_verify", + version: 1, + }, + KwAtomPair { + v: &[0x1c, 0x3a, 0x8f, 0x00], + n: "secp256r1_verify", + version: 1, }, ]; lazy_static! { - pub static ref KEYWORD_FROM_ATOM_: HashMap, String> = { + pub static ref KEYWORD_FROM_ATOM_0: HashMap, String> = { + let mut result = HashMap::new(); + for pair in KW_PAIRS.iter().filter(|p| p.version == 0) { + result.insert(pair.v.to_vec(), pair.n.to_string()); + } + result + }; + pub static ref KEYWORD_TO_ATOM_0: HashMap> = { + let mut result = HashMap::new(); + for pair in KW_PAIRS.iter().filter(|p| p.version == 0) { + result.insert(pair.n.to_string(), pair.v.to_vec()); + } + result + }; + pub static ref KEYWORD_FROM_ATOM_1: HashMap, String> = { let mut result = HashMap::new(); - for pair in KW_PAIRS { - result.insert(vec![pair.v], pair.n.to_string()); + for pair in KW_PAIRS.iter().filter(|p| p.version <= 1) { + result.insert(pair.v.to_vec(), pair.n.to_string()); } result }; - pub static ref KEYWORD_TO_ATOM_: HashMap> = { + pub static ref KEYWORD_TO_ATOM_1: HashMap> = { let mut result = HashMap::new(); - for pair in KW_PAIRS { - result.insert(pair.n.to_string(), vec![pair.v]); + for pair in KW_PAIRS.iter().filter(|p| p.version <= 1) { + result.insert(pair.n.to_string(), pair.v.to_vec()); } result }; } -pub fn keyword_from_atom() -> &'static HashMap, String> { - &KEYWORD_FROM_ATOM_ +pub fn keyword_from_atom(version: usize) -> &'static HashMap, String> { + if version == 0 { + &KEYWORD_FROM_ATOM_0 + } else { + &KEYWORD_FROM_ATOM_1 + } } -pub fn keyword_to_atom() -> &'static HashMap> { - &KEYWORD_TO_ATOM_ +pub fn keyword_to_atom(version: usize) -> &'static HashMap> { + if version == 0 { + &KEYWORD_TO_ATOM_0 + } else { + &KEYWORD_TO_ATOM_1 + } } diff --git a/src/classic/clvm/serialize.rs b/src/classic/clvm/serialize.rs index 05246789a..f863a42df 100644 --- a/src/classic/clvm/serialize.rs +++ b/src/classic/clvm/serialize.rs @@ -98,7 +98,7 @@ impl<'a> Iterator for SExpToBytesIterator<'a> { fn next(&mut self) -> Option { self.state.pop().and_then(|step| match step { SExpToByteOp::Object(x) => match self.allocator.sexp(x) { - SExp::Atom() => { + SExp::Atom => { // The only node we have in scope is x, so this atom // capture is trivial. let buf = self.allocator.atom(x).to_vec(); diff --git a/src/classic/clvm/sexp.rs b/src/classic/clvm/sexp.rs index ef730eedd..fd26de905 100644 --- a/src/classic/clvm/sexp.rs +++ b/src/classic/clvm/sexp.rs @@ -150,7 +150,7 @@ pub fn to_sexp_type(allocator: &mut Allocator, value: CastableType) -> Result { + SExp::Atom => { return Err(EvalErr( *target_value, "attempt to set_pair in atom".to_string(), @@ -224,7 +224,7 @@ pub fn sexp_as_bin(allocator: &mut Allocator, sexp: NodePtr) -> Bytes { f.get_value() } -pub fn bool_sexp(allocator: &mut Allocator, b: bool) -> NodePtr { +pub fn bool_sexp(allocator: &Allocator, b: bool) -> NodePtr { if b { allocator.one() } else { @@ -332,41 +332,41 @@ pub fn bool_sexp(allocator: &mut Allocator, b: bool) -> NodePtr { // ; // } -pub fn non_nil(allocator: &mut Allocator, sexp: NodePtr) -> bool { +pub fn non_nil(allocator: &Allocator, sexp: NodePtr) -> bool { match allocator.sexp(sexp) { SExp::Pair(_, _) => true, // sexp is the only node in scope, was !is_empty - SExp::Atom() => allocator.atom_len(sexp) != 0, + SExp::Atom => allocator.atom_len(sexp) != 0, } } -pub fn first(allocator: &mut Allocator, sexp: NodePtr) -> Result { +pub fn first(allocator: &Allocator, sexp: NodePtr) -> Result { match allocator.sexp(sexp) { SExp::Pair(f, _) => Ok(f), _ => Err(EvalErr(sexp, "first of non-cons".to_string())), } } -pub fn rest(allocator: &mut Allocator, sexp: NodePtr) -> Result { +pub fn rest(allocator: &Allocator, sexp: NodePtr) -> Result { match allocator.sexp(sexp) { SExp::Pair(_, r) => Ok(r), _ => Err(EvalErr(sexp, "rest of non-cons".to_string())), } } -pub fn atom(allocator: &mut Allocator, sexp: NodePtr) -> Result, EvalErr> { +pub fn atom(allocator: &Allocator, sexp: NodePtr) -> Result, EvalErr> { match allocator.sexp(sexp) { - SExp::Atom() => Ok(allocator.atom(sexp).to_vec()), // only sexp in scope + SExp::Atom => Ok(allocator.atom(sexp).to_vec()), // only sexp in scope _ => Err(EvalErr(sexp, "not an atom".to_string())), } } -pub fn proper_list(allocator: &mut Allocator, sexp: NodePtr, store: bool) -> Option> { +pub fn proper_list(allocator: &Allocator, sexp: NodePtr, store: bool) -> Option> { let mut args = vec![]; let mut args_sexp = sexp; loop { match allocator.sexp(args_sexp) { - SExp::Atom() => { + SExp::Atom => { if !non_nil(allocator, args_sexp) { return Some(args); } else { @@ -454,7 +454,7 @@ pub fn equal_to(allocator: &mut Allocator, first_: NodePtr, second_: NodePtr) -> return true; } match (allocator.sexp(first), allocator.sexp(second)) { - (SExp::Atom(), SExp::Atom()) => { + (SExp::Atom, SExp::Atom) => { // two atoms in scope, both are used return allocator.atom(first) == allocator.atom(second); } @@ -477,7 +477,7 @@ pub fn flatten(allocator: &mut Allocator, tree_: NodePtr, res: &mut Vec loop { match allocator.sexp(tree) { - SExp::Atom() => { + SExp::Atom => { if non_nil(allocator, tree) { res.push(tree); } diff --git a/src/classic/clvm_tools/binutils.rs b/src/classic/clvm_tools/binutils.rs index deac313cc..505123057 100644 --- a/src/classic/clvm_tools/binutils.rs +++ b/src/classic/clvm_tools/binutils.rs @@ -8,6 +8,7 @@ use clvm_rs::allocator::{Allocator, NodePtr, SExp}; use clvm_rs::reduction::EvalErr; use crate::classic::clvm::__type_compatibility__::{Bytes, BytesFromType, Record, Stream}; +use crate::classic::clvm::OPERATORS_LATEST_VERSION; use crate::classic::clvm::{keyword_from_atom, keyword_to_atom}; use crate::classic::clvm_tools::ir::r#type::IRRepr; use crate::classic::clvm_tools::ir::reader::IRReader; @@ -39,7 +40,7 @@ pub fn assemble_from_ir( s_real_name = stripped.to_string(); } - match keyword_to_atom().get(&s_real_name) { + match keyword_to_atom(OPERATORS_LATEST_VERSION).get(&s_real_name) { Some(v) => allocator.new_atom(v), None => { let v: Vec = s_real_name.as_bytes().to_vec(); @@ -111,7 +112,7 @@ pub fn ir_for_atom( * (2 2 (2) (2 3 4)) => (a 2 (a) (a 3 4)) */ pub fn disassemble_to_ir_with_kw( - allocator: &mut Allocator, + allocator: &Allocator, sexp: NodePtr, keyword_from_atom: &Record, String>, mut allow_keyword: bool, @@ -127,7 +128,7 @@ pub fn disassemble_to_ir_with_kw( IRRepr::Cons(Rc::new(v0), Rc::new(v1)) } - SExp::Atom() => { + SExp::Atom => { // sexp is the only node in scope. let bytes = Bytes::new(Some(BytesFromType::Raw(allocator.atom(sexp).to_vec()))); ir_for_atom(&bytes, allow_keyword, keyword_from_atom) @@ -136,17 +137,21 @@ pub fn disassemble_to_ir_with_kw( } pub fn disassemble_with_kw( - allocator: &mut Allocator, + allocator: &Allocator, sexp: NodePtr, keyword_from_atom: &Record, String>, ) -> String { - let with_keywords = !matches!(allocator.sexp(sexp), SExp::Atom()); + let with_keywords = !matches!(allocator.sexp(sexp), SExp::Atom); let symbols = disassemble_to_ir_with_kw(allocator, sexp, keyword_from_atom, with_keywords); write_ir(Rc::new(symbols)) } -pub fn disassemble(allocator: &mut Allocator, sexp: NodePtr) -> String { - return disassemble_with_kw(allocator, sexp, keyword_from_atom()); +pub fn disassemble(allocator: &Allocator, sexp: NodePtr, version: Option) -> String { + disassemble_with_kw( + allocator, + sexp, + keyword_from_atom(version.unwrap_or(OPERATORS_LATEST_VERSION)), + ) } pub fn assemble(allocator: &mut Allocator, s: &str) -> Result { diff --git a/src/classic/clvm_tools/clvmc.rs b/src/classic/clvm_tools/clvmc.rs index bae958b7c..88eabb955 100644 --- a/src/classic/clvm_tools/clvmc.rs +++ b/src/classic/clvm_tools/clvmc.rs @@ -6,12 +6,11 @@ use std::rc::Rc; use tempfile::NamedTempFile; -use clvm_rs::allocator::{Allocator, NodePtr, SExp}; +use clvm_rs::allocator::{Allocator, NodePtr}; use clvm_rs::reduction::EvalErr; use crate::classic::clvm::__type_compatibility__::Stream; use crate::classic::clvm::serialize::sexp_to_stream; -use crate::classic::clvm::sexp::proper_list; use crate::classic::clvm_tools::binutils::{assemble_from_ir, disassemble}; use crate::classic::clvm_tools::ir::reader::read_ir; use crate::classic::clvm_tools::stages::run; @@ -22,31 +21,11 @@ use crate::classic::platform::distutils::dep_util::newer; use crate::compiler::clvm::convert_to_clvm_rs; use crate::compiler::compiler::compile_file; -use crate::compiler::compiler::run_optimizer; -use crate::compiler::compiler::DefaultCompilerOpts; -use crate::compiler::comptypes::CompileErr; -use crate::compiler::comptypes::CompilerOpts; +use crate::compiler::compiler::{run_optimizer, DefaultCompilerOpts}; +use crate::compiler::comptypes::{CompileErr, CompilerOpts}; +use crate::compiler::dialect::detect_modern; use crate::compiler::runtypes::RunFailure; -fn include_dialect( - allocator: &mut Allocator, - dialects: &HashMap, i32>, - e: &[NodePtr], -) -> Option { - // Propogated names from let capture to labeled nodes. - let inc_node = e[0]; - let name_node = e[1]; - if let (SExp::Atom(), SExp::Atom()) = (allocator.sexp(inc_node), allocator.sexp(name_node)) { - if allocator.atom(inc_node) == "include".as_bytes().to_vec() { - if let Some(dialect) = dialects.get(allocator.atom(name_node)) { - return Some(*dialect); - } - } - } - - None -} - pub fn write_sym_output( compiled_lookup: &HashMap, path: &str, @@ -59,38 +38,6 @@ pub fn write_sym_output( .map(|_| ()) } -pub fn detect_modern(allocator: &mut Allocator, sexp: NodePtr) -> Option { - let mut dialects = HashMap::new(); - dialects.insert("*standard-cl-21*".as_bytes().to_vec(), 21); - dialects.insert("*standard-cl-22*".as_bytes().to_vec(), 22); - - proper_list(allocator, sexp, true).and_then(|l| { - for elt in l.iter() { - if let Some(dialect) = detect_modern(allocator, *elt) { - return Some(dialect); - } - - match proper_list(allocator, *elt, true) { - None => { - continue; - } - - Some(e) => { - if e.len() != 2 { - continue; - } - - if let Some(dialect) = include_dialect(allocator, &dialects, &e) { - return Some(dialect); - } - } - } - } - - None - }) -} - pub fn compile_clvm_text_maybe_opt( allocator: &mut Allocator, do_optimize: bool, @@ -103,7 +50,10 @@ pub fn compile_clvm_text_maybe_opt( let ir_src = read_ir(text).map_err(|s| EvalErr(allocator.null(), s.to_string()))?; let assembled_sexp = assemble_from_ir(allocator, Rc::new(ir_src))?; - if let Some(dialect) = detect_modern(allocator, assembled_sexp) { + let dialect = detect_modern(allocator, assembled_sexp); + // Now the stepping is optional (None for classic) but we may communicate + // other information in dialect as well. + if let Some(dialect) = dialect.stepping { let runner = Rc::new(DefaultProgramRunner::new()); let opts = opts .set_optimize(do_optimize) @@ -172,7 +122,13 @@ pub fn compile_clvm_inner( filename, classic_with_opts, ) - .map_err(|x| format!("error {} compiling {}", x.1, disassemble(allocator, x.0)))?; + .map_err(|x| { + format!( + "error {} compiling {}", + x.1, + disassemble(allocator, x.0, opts.disassembly_ver()) + ) + })?; sexp_to_stream(allocator, result, result_stream); Ok(()) } diff --git a/src/classic/clvm_tools/cmds.rs b/src/classic/clvm_tools/cmds.rs index 57bdbfc4e..52d953d40 100644 --- a/src/classic/clvm_tools/cmds.rs +++ b/src/classic/clvm_tools/cmds.rs @@ -26,8 +26,9 @@ use crate::classic::clvm::__type_compatibility__::{ use crate::classic::clvm::keyword_from_atom; use crate::classic::clvm::serialize::{sexp_from_stream, sexp_to_stream, SimpleCreateCLVMObject}; use crate::classic::clvm::sexp::{enlist, proper_list, sexp_as_bin}; +use crate::classic::clvm::OPERATORS_LATEST_VERSION; use crate::classic::clvm_tools::binutils::{assemble_from_ir, disassemble, disassemble_with_kw}; -use crate::classic::clvm_tools::clvmc::{detect_modern, write_sym_output}; +use crate::classic::clvm_tools::clvmc::write_sym_output; use crate::classic::clvm_tools::debug::check_unused; use crate::classic::clvm_tools::debug::{ program_hash_from_program_env_cons, start_log_after, trace_pre_eval, trace_to_table, @@ -41,6 +42,7 @@ use crate::classic::clvm_tools::stages::stage_0::{ }; use crate::classic::clvm_tools::stages::stage_2::operators::run_program_for_search_paths; use crate::classic::platform::PathJoin; +use crate::compiler::dialect::detect_modern; use crate::classic::platform::argparse::{ Argument, ArgumentParser, ArgumentValue, ArgumentValueConv, IntConversion, NArgsSpec, @@ -77,7 +79,7 @@ fn get_tool_description(tool_name: &str) -> Option { } else if tool_name == "opd" { Some(ConversionDesc { desc: "Disassemble a compiled clvm script from hex.", - conv: Box::new(OpdConversion {}), + conv: Box::new(OpdConversion { op_version: None }), }) } else { None @@ -102,6 +104,8 @@ impl ArgumentValueConv for PathOrCodeConv { // } pub trait TConversion { + fn apply_args(&mut self, parsed_args: &HashMap); + fn invoke( &self, allocator: &mut Allocator, @@ -130,7 +134,7 @@ pub fn call_tool( tool_name: &str, input_args: &[String], ) -> Result<(), String> { - let task = + let mut task = get_tool_description(tool_name).ok_or_else(|| format!("unknown tool {tool_name}"))?; let props = TArgumentParserProps { description: task.desc.to_string(), @@ -150,6 +154,12 @@ pub fn call_tool( .set_action(TArgOptionAction::StoreTrue) .set_help("Show only sha256 tree hash of program".to_string()), ); + parser.add_argument( + vec!["--operators-version".to_string()], + Argument::new() + .set_type(Rc::new(OperatorsVersion {})) + .set_default(ArgumentValue::ArgInt(OPERATORS_LATEST_VERSION as i64)), + ); parser.add_argument( vec!["path_or_code".to_string()], Argument::new() @@ -168,6 +178,8 @@ pub fn call_tool( } }; + task.conv.apply_args(&args); + if args.contains_key("version") { let version = version(); println!("{version}"); @@ -214,6 +226,8 @@ pub fn call_tool( pub struct OpcConversion {} impl TConversion for OpcConversion { + fn apply_args(&mut self, _args: &HashMap) {} + fn invoke( &self, allocator: &mut Allocator, @@ -228,9 +242,18 @@ impl TConversion for OpcConversion { } } -pub struct OpdConversion {} +#[derive(Debug)] +pub struct OpdConversion { + pub op_version: Option, +} impl TConversion for OpdConversion { + fn apply_args(&mut self, args: &HashMap) { + if let Some(ArgumentValue::ArgInt(i)) = args.get("operators_version") { + self.op_version = Some(*i as usize); + } + } + fn invoke( &self, allocator: &mut Allocator, @@ -246,7 +269,7 @@ impl TConversion for OpdConversion { sexp_from_stream(allocator, &mut stream, Box::new(SimpleCreateCLVMObject {})) .map_err(|e| e.1) .map(|sexp| { - let disassembled = disassemble(allocator, sexp.1); + let disassembled = disassemble(allocator, sexp.1, self.op_version); t(sexp.1, disassembled) }) } @@ -277,6 +300,17 @@ impl ArgumentValueConv for StageImport { } } +struct OperatorsVersion {} + +impl ArgumentValueConv for OperatorsVersion { + fn convert(&self, arg: &str) -> Result { + let ver = arg + .parse::() + .map_err(|_| format!("expected number 0-{OPERATORS_LATEST_VERSION} but found {arg}"))?; + Ok(ArgumentValue::ArgInt(ver)) + } +} + pub fn run(args: &[String]) { let mut s = Stream::new(None); launch_tool(&mut s, args, "run", 2); @@ -772,6 +806,14 @@ fn fix_log( } } +fn get_disassembly_ver(p: &HashMap) -> Option { + if let Some(ArgumentValue::ArgInt(x)) = p.get("operators_version") { + return Some(*x as usize); + } + + None +} + pub fn launch_tool(stdout: &mut Stream, args: &[String], tool_name: &str, default_stage: u32) { let props = TArgumentParserProps { description: "Execute a clvm script.".to_string(), @@ -910,6 +952,12 @@ pub fn launch_tool(stdout: &mut Stream, args: &[String], tool_name: &str, defaul .set_type(Rc::new(PathJoin {})) .set_default(ArgumentValue::ArgString(None, "main.sym".to_string())), ); + parser.add_argument( + vec!["--operators-version".to_string()], + Argument::new() + .set_type(Rc::new(OperatorsVersion {})) + .set_default(ArgumentValue::ArgInt(OPERATORS_LATEST_VERSION as i64)), + ); if tool_name == "run" { parser.add_argument( @@ -939,9 +987,10 @@ pub fn launch_tool(stdout: &mut Stream, args: &[String], tool_name: &str, defaul let empty_map = HashMap::new(); let keywords = match parsed_args.get("no_keywords") { - None => keyword_from_atom(), Some(ArgumentValue::ArgBool(_b)) => &empty_map, - _ => keyword_from_atom(), + _ => { + keyword_from_atom(get_disassembly_ver(&parsed_args).unwrap_or(OPERATORS_LATEST_VERSION)) + } }; // If extra symbol output is desired (not all keys are hashes, but there's @@ -1024,6 +1073,8 @@ pub fn launch_tool(stdout: &mut Stream, args: &[String], tool_name: &str, defaul let special_runner = run_program_for_search_paths(&reported_input_file, &search_paths, extra_symbol_info); + // Ensure we know the user's wishes about the disassembly version here. + special_runner.set_operators_version(get_disassembly_ver(&parsed_args)); let dpr = special_runner.clone(); let run_program = special_runner; @@ -1145,9 +1196,10 @@ pub fn launch_tool(stdout: &mut Stream, args: &[String], tool_name: &str, defaul .map(|a| matches!(a, ArgumentValue::ArgBool(true))) .unwrap_or(false); - let dialect = input_sexp.and_then(|i| detect_modern(&mut allocator, i)); + // Dialect is now not overall optional. + let dialect = input_sexp.map(|i| detect_modern(&mut allocator, i)); let mut stderr_output = |s: String| { - if dialect.is_some() { + if dialect.as_ref().and_then(|d| d.stepping).is_some() { eprintln!("{s}"); } else { stdout.write_str(&s); @@ -1182,7 +1234,9 @@ pub fn launch_tool(stdout: &mut Stream, args: &[String], tool_name: &str, defaul }) .unwrap_or_else(|| "main.sym".to_string()); - if let Some(dialect) = dialect { + // In testing: short circuit for modern compilation. + // Now stepping is the optional part. + if let Some(dialect) = dialect.and_then(|d| d.stepping) { let do_optimize = parsed_args .get("optimize") .map(|x| matches!(x, ArgumentValue::ArgBool(true))) @@ -1192,7 +1246,8 @@ pub fn launch_tool(stdout: &mut Stream, args: &[String], tool_name: &str, defaul let opts = Rc::new(DefaultCompilerOpts::new(&use_filename)) .set_optimize(do_optimize) .set_search_paths(&search_paths) - .set_frontend_opt(dialect > 21); + .set_frontend_opt(dialect > 21) + .set_disassembly_ver(get_disassembly_ver(&parsed_args)); let mut symbol_table = HashMap::new(); let unopt_res = compile_file( @@ -1424,7 +1479,7 @@ pub fn launch_tool(stdout: &mut Stream, args: &[String], tool_name: &str, defaul )); } - let mut run_output = disassemble_with_kw(&mut allocator, result, keywords); + let mut run_output = disassemble_with_kw(&allocator, result, keywords); if let Some(ArgumentValue::ArgBool(true)) = parsed_args.get("dump") { let mut f = Stream::new(None); sexp_to_stream(&mut allocator, result, &mut f); @@ -1440,10 +1495,13 @@ pub fn launch_tool(stdout: &mut Stream, args: &[String], tool_name: &str, defaul format!( "FAIL: {} {}", ex.1, - disassemble_with_kw(&mut allocator, ex.0, keywords) + disassemble_with_kw(&allocator, ex.0, keywords) ) })); + // Get the disassembly ver we're using based on the user's request. + let disassembly_ver = get_disassembly_ver(&parsed_args); + let compile_sym_out = dpr.get_compiles(); if !compile_sym_out.is_empty() { write_sym_output(&compile_sym_out, &symbol_table_output).ok(); @@ -1476,7 +1534,7 @@ pub fn launch_tool(stdout: &mut Stream, args: &[String], tool_name: &str, defaul only_exn, &log_content, symbol_table, - &disassemble, + &|allocator, p| disassemble(allocator, p, disassembly_ver), ); } else { stdout.write_str("\n"); @@ -1486,7 +1544,7 @@ pub fn launch_tool(stdout: &mut Stream, args: &[String], tool_name: &str, defaul only_exn, &log_content, symbol_table, - &disassemble, + &|allocator, p| disassemble(allocator, p, disassembly_ver), ); } } diff --git a/src/classic/clvm_tools/debug.rs b/src/classic/clvm_tools/debug.rs index bbed5519b..15bd5142a 100644 --- a/src/classic/clvm_tools/debug.rs +++ b/src/classic/clvm_tools/debug.rs @@ -126,7 +126,7 @@ pub fn build_symbol_dump( let args_name_atom = allocator.new_atom(&args_atom)?; let left_env_name_atom = allocator.new_atom(&left_env_atom)?; - let serialized_args = disassemble(allocator, extra.args); + let serialized_args = disassemble(allocator, extra.args, Some(0)); let serialized_args_atom = allocator.new_atom(serialized_args.as_bytes())?; let left_env_value = allocator.new_atom(&[extra.has_constants_tree as u8])?; @@ -180,7 +180,7 @@ fn table_trace( ) { let (sexp, args) = match allocator.sexp(form) { SExp::Pair(sexp, args) => (sexp, args), - SExp::Atom() => (form, allocator.null()), + SExp::Atom => (form, allocator.null()), }; stdout.write_str(&format!("exp: {}\n", disassemble_f(allocator, sexp))); diff --git a/src/classic/clvm_tools/pattern_match.rs b/src/classic/clvm_tools/pattern_match.rs index 0df1345cd..02e894b4e 100644 --- a/src/classic/clvm_tools/pattern_match.rs +++ b/src/classic/clvm_tools/pattern_match.rs @@ -51,7 +51,7 @@ pub fn match_sexp( */ match (allocator.sexp(pattern), allocator.sexp(sexp)) { - (SExp::Atom(), SExp::Atom()) => { + (SExp::Atom, SExp::Atom) => { // Two nodes in scope, both used. if allocator.atom(pattern) == allocator.atom(sexp) { Some(known_bindings) @@ -60,10 +60,10 @@ pub fn match_sexp( } } (SExp::Pair(pleft, pright), _) => match (allocator.sexp(pleft), allocator.sexp(pright)) { - (SExp::Atom(), SExp::Atom()) => { + (SExp::Atom, SExp::Atom) => { let pright_atom = allocator.atom(pright).to_vec(); match allocator.sexp(sexp) { - SExp::Atom() => { + SExp::Atom => { // Expression is ($ . $), sexp is '$', result: no capture. // Avoid double borrow. if allocator.atom(pleft) == ATOM_MATCH { @@ -114,11 +114,11 @@ pub fn match_sexp( } } _ => match allocator.sexp(sexp) { - SExp::Atom() => None, + SExp::Atom => None, SExp::Pair(sleft, sright) => match_sexp(allocator, pleft, sleft, known_bindings) .and_then(|new_bindings| match_sexp(allocator, pright, sright, new_bindings)), }, }, - (SExp::Atom(), _) => None, + (SExp::Atom, _) => None, } } diff --git a/src/classic/clvm_tools/sha256tree.rs b/src/classic/clvm_tools/sha256tree.rs index 0212d0c78..ebbc3197c 100644 --- a/src/classic/clvm_tools/sha256tree.rs +++ b/src/classic/clvm_tools/sha256tree.rs @@ -34,7 +34,7 @@ pub fn sha256tree(allocator: &mut Allocator, v: NodePtr) -> Bytes { .concat(&right), ) } - SExp::Atom() => sha256( + SExp::Atom => sha256( Bytes::new(Some(BytesFromType::Raw(vec![1]))).concat(&Bytes::new(Some( // only v in scope. BytesFromType::Raw(allocator.atom(v).to_vec()), diff --git a/src/classic/clvm_tools/stages/stage_2/compile.rs b/src/classic/clvm_tools/stages/stage_2/compile.rs index d5db2d9ea..8979ec99a 100644 --- a/src/classic/clvm_tools/stages/stage_2/compile.rs +++ b/src/classic/clvm_tools/stages/stage_2/compile.rs @@ -6,6 +6,7 @@ use clvm_rs::reduction::{EvalErr, Reduction, Response}; use crate::classic::clvm::__type_compatibility__::{Bytes, BytesFromType}; use crate::classic::clvm::sexp::{enlist, first, map_m, non_nil, proper_list, rest}; +use crate::classic::clvm::OPERATORS_LATEST_VERSION; use crate::classic::clvm::{keyword_from_atom, keyword_to_atom}; use crate::classic::clvm_tools::binutils::{assemble, disassemble}; @@ -26,10 +27,10 @@ const DIAG_OUTPUT: bool = false; lazy_static! { static ref PASS_THROUGH_OPERATORS: HashSet> = { let mut result = HashSet::new(); - for key in keyword_to_atom().keys() { + for key in keyword_to_atom(OPERATORS_LATEST_VERSION).keys() { result.insert(key.as_bytes().to_vec()); } - for key in keyword_from_atom().keys() { + for key in keyword_from_atom(OPERATORS_LATEST_VERSION).keys() { result.insert(key.to_vec()); } // added by optimize @@ -100,7 +101,7 @@ fn com_qq( sexp: NodePtr, ) -> Result { if DIAG_OUTPUT { - println!("com_qq {} {}", ident, disassemble(allocator, sexp)); + println!("com_qq {} {}", ident, disassemble(allocator, sexp, None)); } do_com_prog(allocator, 110, sexp, macro_lookup, symbol_table, runner).map(|x| x.1) } @@ -127,12 +128,12 @@ pub fn compile_qq( }; match allocator.sexp(sexp) { - SExp::Atom() => { + SExp::Atom => { // (qq ATOM) => (q . ATOM) quote(allocator, sexp) } SExp::Pair(op, sexp_rest) => { - if let SExp::Atom() = allocator.sexp(op) { + if let SExp::Atom = allocator.sexp(op) { // opbuf => op if allocator.atom(op).to_vec() == qq_atom() { return m! { @@ -216,11 +217,11 @@ fn lower_quote_(allocator: &mut Allocator, prog: NodePtr) -> Result Result Result, EvalErr> { @@ -346,7 +347,7 @@ fn get_macro_program( }; match allocator.sexp(mp_list[0]) { - SExp::Atom() => { + SExp::Atom => { // was macro_name, but it's singular and probably // not useful to rename. if allocator.atom(mp_list[0]) == operator { @@ -390,7 +391,7 @@ fn transform_program_atom( let value = if v.len() > 1 { v[1] } else { allocator.null() }; match allocator.sexp(v[0]) { - SExp::Atom() => { + SExp::Atom => { // v[0] is close by, and probably not useful to // rename here. if allocator.atom(v[0]) == a { @@ -439,7 +440,7 @@ fn compile_operator_atom( allocator.new_atom(NodePath::new(None).as_path().data()); let _ = if DIAG_OUTPUT { - print!("COMPILE_BINDINGS {}", disassemble(allocator, quoted_post_prog)); + print!("COMPILE_BINDINGS {}", disassemble(allocator, quoted_post_prog, None)); }; evaluate(allocator, quoted_post_prog, top_atom).map(Some) }; @@ -454,7 +455,7 @@ enum SymbolResult { } fn find_symbol_match( - allocator: &mut Allocator, + allocator: &Allocator, opname: &[u8], r: NodePtr, symbol_table: NodePtr, @@ -467,7 +468,7 @@ fn find_symbol_match( } match allocator.sexp(symdef[0]) { - SExp::Atom() => { + SExp::Atom => { let symbol = symdef[0]; let value = if symdef.len() == 1 { allocator.null() @@ -509,7 +510,7 @@ fn compile_application( prog, format!( "can't compile {}, unknown operator", - disassemble(allocator, prog) + disassemble(allocator, prog, None) ), )); @@ -599,9 +600,9 @@ pub fn do_com_prog( println!( "START COMPILE {}: {} MACRO {} SYMBOLS {}", from, - disassemble(allocator, prog), - disassemble(allocator, macro_lookup), - disassemble(allocator, symbol_table), + disassemble(allocator, prog, None), + disassemble(allocator, macro_lookup, None), + disassemble(allocator, symbol_table, None), ); } do_com_prog_(allocator, prog, macro_lookup, symbol_table, run_program).map(|x| { @@ -609,10 +610,10 @@ pub fn do_com_prog( println!( "DO_COM_PROG {}: {} MACRO {} SYMBOLS {} RESULT {}", from, - disassemble(allocator, prog), - disassemble(allocator, macro_lookup), - disassemble(allocator, symbol_table), - disassemble(allocator, x.1) + disassemble(allocator, prog, None), + disassemble(allocator, macro_lookup, None), + disassemble(allocator, symbol_table, None), + disassemble(allocator, x.1, None) ); } x @@ -643,7 +644,7 @@ fn do_com_prog_( // quote atoms match allocator.sexp(prog) { - SExp::Atom() => { + SExp::Atom => { // Note: can't co-borrow with allocator below. let prog_bytes = allocator.atom(prog).to_vec(); transform_program_atom( @@ -655,7 +656,7 @@ fn do_com_prog_( }, SExp::Pair(operator,prog_rest) => { match allocator.sexp(operator) { - SExp::Atom() => { + SExp::Atom => { // Note: can't co-borrow with allocator below. let opbuf = allocator.atom(operator).to_vec(); get_macro_program(allocator, &opbuf, macro_lookup). @@ -788,7 +789,7 @@ pub fn get_compile_filename( return Ok(None); } - if let SExp::Atom() = allocator.sexp(cvt_prog_result) { + if let SExp::Atom = allocator.sexp(cvt_prog_result) { // only cvt_prog_result in scope. let abuf = allocator.atom(cvt_prog_result).to_vec(); return Ok(Some(Bytes::new(Some(BytesFromType::Raw(abuf))).decode())); @@ -811,7 +812,7 @@ pub fn get_search_paths( let mut res = Vec::new(); if let Some(l) = proper_list(allocator, search_path_result.1, true) { for elt in l.iter().copied() { - if let SExp::Atom() = allocator.sexp(elt) { + if let SExp::Atom = allocator.sexp(elt) { // Only elt in scope. res.push( Bytes::new(Some(BytesFromType::Raw(allocator.atom(elt).to_vec()))).decode(), @@ -893,7 +894,7 @@ pub fn process_compile_file( let r_of_declaration = rest(allocator, declaration_sexp)?; let rr_of_declaration = rest(allocator, r_of_declaration)?; let frr_of_declaration = first(allocator, rr_of_declaration)?; - if let SExp::Atom() = allocator.sexp(frr_of_declaration) { + if let SExp::Atom = allocator.sexp(frr_of_declaration) { // Referenced above. let b_name = allocator.atom(frr_of_declaration).to_vec(); let compiled_output = compile_file( diff --git a/src/classic/clvm_tools/stages/stage_2/inline.rs b/src/classic/clvm_tools/stages/stage_2/inline.rs index e47abbf73..215c3679d 100644 --- a/src/classic/clvm_tools/stages/stage_2/inline.rs +++ b/src/classic/clvm_tools/stages/stage_2/inline.rs @@ -11,11 +11,11 @@ use std::collections::HashMap; // (@ name substructure) // then return name and substructure. pub fn is_at_capture( - allocator: &mut Allocator, + allocator: &Allocator, tree_first: NodePtr, tree_rest: NodePtr, ) -> Option<(NodePtr, NodePtr)> { - if let (SExp::Atom(), Some(spec)) = ( + if let (SExp::Atom, Some(spec)) = ( allocator.sexp(tree_first), proper_list(allocator, tree_rest, true), ) { @@ -88,7 +88,7 @@ fn formulate_path_selections_for_destructuring_arg( SExp::Pair(a, b) => { let next_depth = arg_depth.clone() * 2_u32.to_bigint().unwrap(); if let Some((capture, substructure)) = is_at_capture(allocator, a, b) { - if let SExp::Atom() = allocator.sexp(capture) { + if let SExp::Atom = allocator.sexp(capture) { let (new_arg_path, new_arg_depth, tail) = if let Some(prev_ref) = referenced_from { (arg_path, arg_depth, prev_ref) @@ -147,7 +147,7 @@ fn formulate_path_selections_for_destructuring_arg( ) } } - SExp::Atom() => { + SExp::Atom => { // Note: can't co-borrow with allocator below. let buf = allocator.atom(arg_sexp).to_vec(); if !buf.is_empty() { @@ -225,7 +225,7 @@ pub fn formulate_path_selections_for_destructuring( ) -> Result { if let SExp::Pair(a, b) = allocator.sexp(args_sexp) { if let Some((capture, substructure)) = is_at_capture(allocator, a, b) { - if let SExp::Atom() = allocator.sexp(capture) { + if let SExp::Atom = allocator.sexp(capture) { let quoted_arg_list = wrap_in_unquote(allocator, capture)?; let tail = wrap_in_compile_time_list(allocator, quoted_arg_list)?; // Was: cbuf from capture. diff --git a/src/classic/clvm_tools/stages/stage_2/module.rs b/src/classic/clvm_tools/stages/stage_2/module.rs index 99631223c..428a3b7c6 100644 --- a/src/classic/clvm_tools/stages/stage_2/module.rs +++ b/src/classic/clvm_tools/stages/stage_2/module.rs @@ -131,7 +131,7 @@ fn build_used_constants_names( new_names = HashSet::new(); for name in iterate_names { - let functions_and_macros = vec![functions.get(&name), macro_as_dict.get(&name)]; + let functions_and_macros = [functions.get(&name), macro_as_dict.get(&name)]; let matching_names_1 = functions_and_macros .iter() @@ -147,7 +147,7 @@ fn build_used_constants_names( let matching_names = matching_names_1.iter().filter_map(|v| { // Only v usefully in scope. - if let SExp::Atom() = allocator.sexp(*v) { + if let SExp::Atom = allocator.sexp(*v) { Some(allocator.atom(*v).to_vec()) } else { None @@ -225,7 +225,7 @@ fn unquote_args( matches: &HashMap, NodePtr>, ) -> Result { match allocator.sexp(code) { - SExp::Atom() => { + SExp::Atom => { // Only code in scope. let code_atom = allocator.atom(code); let matching_args = args @@ -287,7 +287,7 @@ fn defun_inline_to_macro( let arg_name_list = arg_atom_list .iter() .filter_map(|x| { - if let SExp::Atom() = allocator.sexp(*x) { + if let SExp::Atom = allocator.sexp(*x) { // only x usefully in scope. Some(allocator.atom(*x)) } else { @@ -327,12 +327,12 @@ fn parse_mod_sexp( let op = match allocator.sexp(op_node) { // op_node in use. - SExp::Atom() => allocator.atom(op_node).to_vec(), + SExp::Atom => allocator.atom(op_node).to_vec(), _ => Vec::new(), }; let name = match allocator.sexp(name_node) { // name_node in use. - SExp::Atom() => allocator.atom(name_node).to_vec(), + SExp::Atom => allocator.atom(name_node).to_vec(), _ => Vec::new(), }; @@ -557,7 +557,7 @@ fn symbol_table_for_tree( } match allocator.sexp(tree) { - SExp::Atom() => Ok(vec![(tree, root_node.as_path().data().to_vec())]), + SExp::Atom => Ok(vec![(tree, root_node.as_path().data().to_vec())]), SExp::Pair(_, _) => { let left_bytes = NodePath::new(None).first(); let right_bytes = NodePath::new(None).rest(); @@ -716,7 +716,7 @@ fn add_main_args( symbols: NodePtr, ) -> Result { let entry_name = allocator.new_atom("__chia__main_arguments".as_bytes())?; - let entry_value_string = disassemble(allocator, args); + let entry_value_string = disassemble(allocator, args, None); let entry_value = allocator.new_atom(entry_value_string.as_bytes())?; let entry_cons = allocator.new_pair(entry_name, entry_value)?; allocator.new_pair(entry_cons, symbols) diff --git a/src/classic/clvm_tools/stages/stage_2/operators.rs b/src/classic/clvm_tools/stages/stage_2/operators.rs index c9913dd62..2e69564d3 100644 --- a/src/classic/clvm_tools/stages/stage_2/operators.rs +++ b/src/classic/clvm_tools/stages/stage_2/operators.rs @@ -12,6 +12,7 @@ use clvm_rs::reduction::{EvalErr, Reduction, Response}; use clvm_rs::run_program::run_program_with_pre_eval; use crate::classic::clvm::__type_compatibility__::{Bytes, BytesFromType, Stream}; +use crate::classic::clvm::OPERATORS_LATEST_VERSION; use crate::classic::clvm::keyword_from_atom; use crate::classic::clvm::sexp::proper_list; @@ -56,6 +57,8 @@ pub struct CompilerOperatorsInternal { // A compiler opts as in the modern compiler. If present, try using its // file system interface to read files. compiler_opts: RefCell>>, + // The version of the operators selected by the user. version 1 includes bls. + operators_version: RefCell>, } /// Given a list of search paths, find a full path to a file whose partial name @@ -132,6 +135,7 @@ impl CompilerOperatorsInternal { runner: RefCell::new(base_runner), opt_memo: RefCell::new(HashMap::new()), compiler_opts: RefCell::new(None), + operators_version: RefCell::new(None), } } @@ -166,6 +170,28 @@ impl CompilerOperatorsInternal { borrow.clone() } + fn get_operators_version(&self) -> Option { + let borrow: Ref<'_, Option> = self.operators_version.borrow(); + *borrow + } + + // Return the extension operator system to use while compiling based on user + // preference. + fn get_operators_extension(&self) -> OperatorSet { + let ops_version = self + .get_operators_version() + .unwrap_or(OPERATORS_LATEST_VERSION); + if ops_version == 0 { + OperatorSet::Default + } else { + OperatorSet::BLS + } + } + + fn set_operators_version(&self, ver: Option) { + self.operators_version.replace(ver); + } + fn read(&self, allocator: &mut Allocator, sexp: NodePtr) -> Response { // Given a string containing the data in the file to parse, parse it or // return EvalErr. @@ -179,7 +205,7 @@ impl CompilerOperatorsInternal { match allocator.sexp(sexp) { SExp::Pair(f, _) => match allocator.sexp(f) { - SExp::Atom() => { + SExp::Atom => { let filename = Bytes::new(Some(BytesFromType::Raw(allocator.atom(f).to_vec()))).decode(); // Use the read interface in CompilerOpts if we have one. @@ -209,14 +235,19 @@ impl CompilerOperatorsInternal { } } - fn write(&self, allocator: &mut Allocator, sexp: NodePtr) -> Response { + fn write(&self, allocator: &Allocator, sexp: NodePtr) -> Response { if let SExp::Pair(filename_sexp, r) = allocator.sexp(sexp) { if let SExp::Pair(data, _) = allocator.sexp(r) { - if let SExp::Atom() = allocator.sexp(filename_sexp) { + if let SExp::Atom = allocator.sexp(filename_sexp) { let filename_buf = allocator.atom(filename_sexp); let filename_bytes = Bytes::new(Some(BytesFromType::Raw(filename_buf.to_vec()))); - let ir = disassemble_to_ir_with_kw(allocator, data, keyword_from_atom(), true); + let ir = disassemble_to_ir_with_kw( + allocator, + data, + keyword_from_atom(self.get_disassembly_ver()), + true, + ); let mut stream = Stream::new(None); write_ir_to_stream(Rc::new(ir), &mut stream); return fs::write(filename_bytes.decode(), stream.get_value().decode()) @@ -254,7 +285,7 @@ impl CompilerOperatorsInternal { }; if let SExp::Pair(l, _r) = allocator.sexp(sexp) { - if let SExp::Atom() = allocator.sexp(l) { + if let SExp::Atom = allocator.sexp(l) { // l most relevant in scope. let filename = Bytes::new(Some(BytesFromType::Raw(allocator.atom(l).to_vec()))).decode(); @@ -280,7 +311,7 @@ impl CompilerOperatorsInternal { pub fn set_symbol_table( &self, - allocator: &mut Allocator, + allocator: &Allocator, table: NodePtr, ) -> Result { if let Some(symtable) = @@ -288,9 +319,7 @@ impl CompilerOperatorsInternal { { for kv in symtable.iter() { if let SExp::Pair(hash, name) = allocator.sexp(*kv) { - if let (SExp::Atom(), SExp::Atom()) = - (allocator.sexp(hash), allocator.sexp(name)) - { + if let (SExp::Atom, SExp::Atom) = (allocator.sexp(hash), allocator.sexp(name)) { // hash and name in scope. let hash_text = Bytes::new(Some(BytesFromType::Raw(allocator.atom(hash).to_vec()))) @@ -311,6 +340,12 @@ impl CompilerOperatorsInternal { Ok(Reduction(1, allocator.null())) } + + fn get_disassembly_ver(&self) -> usize { + self.get_compiler_opts() + .and_then(|o| o.disassembly_ver()) + .unwrap_or(OPERATORS_LATEST_VERSION) + } } impl Dialect for CompilerOperatorsInternal { @@ -341,10 +376,18 @@ impl Dialect for CompilerOperatorsInternal { op: NodePtr, sexp: NodePtr, max_cost: Cost, - extension: OperatorSet, + _extension: OperatorSet, ) -> Response { + // Ensure we have at least the bls extensions available. + // The extension passed in above is based on the state of whether + // we're approaching from within softfork... As the compiler author + // we're overriding this so the user can specify these in the compile + // context... Even when compiling code to go inside softfork, the + // compiler doesn't itself run in a softfork. + let extensions_to_clvmr_during_compile = self.get_operators_extension(); + match allocator.sexp(op) { - SExp::Atom() => { + SExp::Atom => { // use of op obvious. let opbuf = allocator.atom(op); if opbuf == "_read".as_bytes() { @@ -368,13 +411,22 @@ impl Dialect for CompilerOperatorsInternal { } else if opbuf == "_get_source_file".as_bytes() { self.get_source_file(allocator) } else { - self.base_dialect - .op(allocator, op, sexp, max_cost, extension) + self.base_dialect.op( + allocator, + op, + sexp, + max_cost, + extensions_to_clvmr_during_compile, + ) } } - _ => self - .base_dialect - .op(allocator, op, sexp, max_cost, extension), + _ => self.base_dialect.op( + allocator, + op, + sexp, + max_cost, + extensions_to_clvmr_during_compile, + ), } } @@ -397,6 +449,10 @@ impl CompilerOperators { pub fn set_compiler_opts(&self, opts: Option>) { self.parent.set_compiler_opts(opts); } + + pub fn set_operators_version(&self, ver: Option) { + self.parent.set_operators_version(ver); + } } impl TRunProgram for CompilerOperatorsInternal { diff --git a/src/classic/clvm_tools/stages/stage_2/optimize.rs b/src/classic/clvm_tools/stages/stage_2/optimize.rs index 5e1e2306d..9aef8e77c 100644 --- a/src/classic/clvm_tools/stages/stage_2/optimize.rs +++ b/src/classic/clvm_tools/stages/stage_2/optimize.rs @@ -41,7 +41,7 @@ pub fn seems_constant_tail(allocator: &mut Allocator, sexp_: NodePtr) -> bool { sexp = r; } - SExp::Atom() => { + SExp::Atom => { return sexp == allocator.null(); } } @@ -50,12 +50,12 @@ pub fn seems_constant_tail(allocator: &mut Allocator, sexp_: NodePtr) -> bool { pub fn seems_constant(allocator: &mut Allocator, sexp: NodePtr) -> bool { match allocator.sexp(sexp) { - SExp::Atom() => { + SExp::Atom => { return sexp == allocator.null(); } SExp::Pair(operator, r) => { match allocator.sexp(operator) { - SExp::Atom() => { + SExp::Atom => { // Was buf of operator. let atom = allocator.atom(operator); if atom.len() == 1 && atom[0] == 1 { @@ -93,7 +93,7 @@ pub fn constant_optimizer( */ if let SExp::Pair(first, _) = allocator.sexp(r) { // first relevant in scope. - if let SExp::Atom() = allocator.sexp(first) { + if let SExp::Atom = allocator.sexp(first) { let buf = allocator.atom(first); if buf.len() == 1 && buf[0] == 1 { // Short circuit already quoted expression. @@ -107,7 +107,7 @@ pub fn constant_optimizer( if DIAG_OPTIMIZATIONS { println!( "COPT {} SC_R {} NN_R {}", - disassemble(allocator, r), + disassemble(allocator, r, None), sc_r, nn_r ); @@ -124,8 +124,8 @@ pub fn constant_optimizer( let _ = if DIAG_OPTIMIZATIONS { println!( "CONSTANT_OPTIMIZER {} TO {}", - disassemble(allocator, r), - disassemble(allocator, r1) + disassemble(allocator, r, None), + disassemble(allocator, r1, None) ); }; quoted <- quote(allocator, r1); @@ -136,8 +136,8 @@ pub fn constant_optimizer( Ok(r) } -pub fn is_args_call(allocator: &mut Allocator, r: NodePtr) -> bool { - if let SExp::Atom() = allocator.sexp(r) { +pub fn is_args_call(allocator: &Allocator, r: NodePtr) -> bool { + if let SExp::Atom = allocator.sexp(r) { // Only r in scope. let buf = allocator.atom(r); buf.len() == 1 && buf[0] == 1 @@ -220,7 +220,7 @@ fn path_from_args( new_args: NodePtr, ) -> Result { match allocator.sexp(sexp) { - SExp::Atom() => { + SExp::Atom => { // Only sexp in scope. let v = number_from_u8(allocator.atom(sexp)); if v <= bi_one() { @@ -246,7 +246,7 @@ pub fn sub_args( new_args: NodePtr, ) -> Result { match allocator.sexp(sexp) { - SExp::Atom() => path_from_args(allocator, sexp, new_args), + SExp::Atom => path_from_args(allocator, sexp, new_args), SExp::Pair(first_pre, rest) => { let first; @@ -254,7 +254,7 @@ pub fn sub_args( SExp::Pair(_, _) => { first = sub_args(allocator, first_pre, new_args)?; } - SExp::Atom() => { + SExp::Atom => { // Atom is a reflection of first_pre. let atom = allocator.atom(first_pre); if atom.len() == 1 && atom[0] == 1 { @@ -315,7 +315,7 @@ pub fn var_change_optimizer_cons_eval( if DIAG_OPTIMIZATIONS { println!( "XXX ORIGINAL_ARGS {}", - disassemble(allocator, *original_args) + disassemble(allocator, *original_args, None) ); }; let original_call = t1 @@ -325,7 +325,7 @@ pub fn var_change_optimizer_cons_eval( if DIAG_OPTIMIZATIONS { println!( "XXX ORIGINAL_CALL {}", - disassemble(allocator, *original_call) + disassemble(allocator, *original_call, None) ); }; @@ -334,8 +334,8 @@ pub fn var_change_optimizer_cons_eval( if DIAG_OPTIMIZATIONS { println!( "XXX new_eval_sexp_args {} ORIG {}", - disassemble(allocator, new_eval_sexp_args), - disassemble(allocator, *original_args) + disassemble(allocator, new_eval_sexp_args, None), + disassemble(allocator, *original_args, None) ); }; @@ -369,12 +369,12 @@ pub fn var_change_optimizer_cons_eval( println!( "XXX opt_operands {} {}", acc, - disassemble(allocator, val) + disassemble(allocator, val, None) ); } let increment = match allocator.sexp(val) { SExp::Pair(val_first, _) => match allocator.sexp(val_first) { - SExp::Atom() => { + SExp::Atom => { // Atom reflects val_first. let vf_buf = allocator.atom(val_first); (vf_buf.len() != 1 || vf_buf[0] != 1) as i32 @@ -419,7 +419,7 @@ pub fn children_optimizer( if list.is_empty() { return Ok(r); } - if let SExp::Atom() = allocator.sexp(list[0]) { + if let SExp::Atom = allocator.sexp(list[0]) { if allocator.atom(list[0]).to_vec() == vec![1] { return Ok(r); } @@ -683,7 +683,7 @@ pub fn optimize_sexp_( let mut name = "".to_string(); match allocator.sexp(r) { - SExp::Atom() => { + SExp::Atom => { return Ok(r); } SExp::Pair(_, _) => { @@ -718,8 +718,8 @@ pub fn optimize_sexp_( println!( "OPT-{:?}[{}] => {}", name, - disassemble(allocator, start_r), - disassemble(allocator, r) + disassemble(allocator, start_r, None), + disassemble(allocator, r, None) ); } } @@ -735,14 +735,14 @@ pub fn optimize_sexp( let optimized = RefCell::new(HashMap::new()); if DIAG_OPTIMIZATIONS { - println!("START OPTIMIZE {}", disassemble(allocator, r)); + println!("START OPTIMIZE {}", disassemble(allocator, r, None)); } optimize_sexp_(allocator, &optimized, r, eval_f).map(|x| { if DIAG_OPTIMIZATIONS { println!( "OPTIMIZE_SEXP {} GIVING {}", - disassemble(allocator, r), - disassemble(allocator, x) + disassemble(allocator, r, None), + disassemble(allocator, x, None) ); } x diff --git a/src/classic/clvm_tools/stages/stage_2/reader.rs b/src/classic/clvm_tools/stages/stage_2/reader.rs index bc91c89b4..716988f75 100644 --- a/src/classic/clvm_tools/stages/stage_2/reader.rs +++ b/src/classic/clvm_tools/stages/stage_2/reader.rs @@ -90,7 +90,7 @@ pub fn process_embed_file( )); } - if let (SExp::Atom(), SExp::Atom(), SExp::Atom()) = ( + if let (SExp::Atom, SExp::Atom, SExp::Atom) = ( allocator.sexp(l[0]), allocator.sexp(l[1]), allocator.sexp(l[2]), diff --git a/src/compiler/clvm.rs b/src/compiler/clvm.rs index dd4ad017c..f0020efab 100644 --- a/src/compiler/clvm.rs +++ b/src/compiler/clvm.rs @@ -183,20 +183,23 @@ fn eval_args( let mut eval_list: Vec> = Vec::new(); loop { - match sexp.borrow() { - SExp::Nil(_l) => { - return Ok(RunStep::Op(head, context_, sexp, Some(eval_list), parent)); - } - SExp::Cons(_l, a, b) => { - eval_list.push(a.clone()); - sexp = b.clone(); - } - _ => { - return Err(RunFailure::RunErr( - sexp.loc(), - format!("bad argument list {sexp_} {context_}"), - )); - } + // A list of the following forms: + // (x y . 0) + // (x y . "") + // Are properly terminated lists and disassemble to (x y). + // + // This recognizes that our broader value space has more ways + // of expressing nil. + if let SExp::Cons(_l, a, b) = sexp.borrow() { + eval_list.push(a.clone()); + sexp = b.clone(); + } else if !truthy(sexp.clone()) { + return Ok(RunStep::Op(head, context_, sexp, Some(eval_list), parent)); + } else { + return Err(RunFailure::RunErr( + sexp.loc(), + format!("bad argument list {sexp_} {context_}"), + )); } } } @@ -225,9 +228,9 @@ pub fn convert_to_clvm_rs( }) } } - SExp::Cons(_, a, b) => convert_to_clvm_rs(allocator, a.clone()).and_then(|head| { + SExp::Cons(_, a, b) => convert_to_clvm_rs(allocator, a.clone()).and_then(|head_ptr| { convert_to_clvm_rs(allocator, b.clone()).and_then(|tail| { - allocator.new_pair(head, tail).map_err(|_e| { + allocator.new_pair(head_ptr, tail).map_err(|_e| { RunFailure::RunErr(a.loc(), format!("failed to alloc cons {head}")) }) }) @@ -242,7 +245,7 @@ pub fn convert_from_clvm_rs( head: NodePtr, ) -> Result, RunFailure> { match allocator.sexp(head) { - allocator::SExp::Atom() => { + allocator::SExp::Atom => { let atom_data = allocator.atom(head); if atom_data.is_empty() { Ok(Rc::new(SExp::Nil(loc))) @@ -348,7 +351,7 @@ pub fn truthy(sexp: Rc) -> bool { /// more arguments for its operator, one needed argument evaluation is removed /// and the step becomes closer to evaluation. pub fn combine(a: &RunStep, b: &RunStep) -> RunStep { - match (a, b.borrow()) { + match (a, b) { (RunStep::Done(l, x), RunStep::Done(_, _)) => RunStep::Done(l.clone(), x.clone()), (RunStep::Done(l, x), RunStep::Op(head, context, args, Some(remain), parent)) => { RunStep::Op( diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index 60df525d4..fdf651b3e 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -1,6 +1,7 @@ use std::borrow::Borrow; use std::collections::HashMap; use std::collections::HashSet; +use std::mem::swap; use std::rc::Rc; use num_bigint::ToBigInt; @@ -13,13 +14,14 @@ use crate::classic::clvm::__type_compatibility__::bi_one; use crate::compiler::clvm::run; use crate::compiler::compiler::{is_at_capture, run_optimizer}; use crate::compiler::comptypes::{ - fold_m, join_vecs_to_string, list_to_cons, Binding, BodyForm, CallSpec, Callable, CompileErr, - CompileForm, CompiledCode, CompilerOpts, ConstantKind, DefunCall, DefunData, HelperForm, - InlineFunction, LetData, LetFormKind, PrimaryCodegen, RawCallSpec, + fold_m, join_vecs_to_string, list_to_cons, Binding, BindingPattern, BodyForm, CallSpec, + Callable, CompileErr, CompileForm, CompiledCode, CompilerOpts, ConstantKind, DefunCall, + DefunData, HelperForm, InlineFunction, LetData, LetFormInlineHint, LetFormKind, PrimaryCodegen, + RawCallSpec, }; use crate::compiler::debug::{build_swap_table_mut, relabel}; use crate::compiler::evaluate::{Evaluator, EVAL_STACK_LIMIT}; -use crate::compiler::frontend::compile_bodyform; +use crate::compiler::frontend::{compile_bodyform, make_provides_set}; use crate::compiler::gensym::gensym; use crate::compiler::inline::{replace_in_inline, synthesize_args}; use crate::compiler::optimize::optimize_expr; @@ -27,7 +29,7 @@ use crate::compiler::prims::{primapply, primcons, primquote}; use crate::compiler::runtypes::RunFailure; use crate::compiler::sexp::{decode_string, SExp}; use crate::compiler::srcloc::Srcloc; -use crate::util::u8_from_number; +use crate::util::{toposort, u8_from_number, TopoSortItem}; const MACRO_TIME_LIMIT: usize = 1000000; const CONST_EVAL_LIMIT: usize = 1000000; @@ -551,7 +553,7 @@ pub fn generate_expr_code( )) } BodyForm::Value(v) => { - match v.borrow() { + match v { SExp::Atom(l, atom) => { if *atom == "@".as_bytes().to_vec() { Ok(CompiledCode( @@ -781,17 +783,34 @@ pub fn empty_compiler(prim_map: Rc, Rc>>, l: Srcloc) -> Pr } } +pub fn should_inline_let(inline_hint: &Option) -> bool { + matches!(inline_hint, None | Some(LetFormInlineHint::Inline(_))) +} + +#[allow(clippy::too_many_arguments)] fn generate_let_defun( l: Srcloc, kwl: Option, name: &[u8], args: Rc, + // Tells what the user's preference is for inlining. It can be set to None, + // which means use the form's default. + // Some(LetFormInlineHint::NoPreference), meaning the system should choose the + // best inlining strategy, + // Some(LetFormInlineHint::Inline(_)) or Some(LetFormInlineHint::NonInline(_)) + inline_hint: &Option, bindings: Vec>, body: Rc, ) -> HelperForm { let new_arguments: Vec> = bindings .iter() - .map(|b| Rc::new(SExp::Atom(l.clone(), b.name.clone()))) + .map(|b| match &b.pattern { + // This is the classic let form. It doesn't support destructuring. + BindingPattern::Name(name) => Rc::new(SExp::Atom(l.clone(), name.clone())), + // The assign form, which supports destructuring and signals newer + // handling. + BindingPattern::Complex(sexp) => sexp.clone(), + }) .collect(); let inner_function_args = Rc::new(SExp::Cons( @@ -801,7 +820,10 @@ fn generate_let_defun( )); HelperForm::Defun( - true, + // Some forms will be inlined and some as separate functions based on + // binary size, when permitted. Sometimes the user will signal a + // preference. + should_inline_let(inline_hint), DefunData { loc: l.clone(), nl: l, @@ -818,15 +840,160 @@ fn generate_let_args(_l: Srcloc, blist: Vec>) -> Vec> { blist.iter().map(|b| b.body.clone()).collect() } +/// Assign arranges its variable names via need and split into batches that don't +/// add additional dependencies. To illustrate: +/// +/// (assign +/// (X . Y) (F A W) +/// W (G A) +/// Z (H X Y W) +/// Next (H2 X Y W) +/// (doit Y Next) +/// +/// In this case, we have the following dependencies: +/// W depends on A (external) +/// X and Y depend on A (external) and W +/// Z depends on X Y and W +/// Next depends on X Y and W +/// The body depends on Y and Next. +/// +/// So we sort this: +/// W (G A) +/// --- X and Y add a dependency on W --- +/// (X . Y) (F A W) +/// --- Z and Next depend on X Y and W +/// Z (H X Y W) +/// Next (H2 X Y W) +/// --- done sorting, the body has access to all bindings --- +/// +/// We return TopoSortItem> (bytewise names), which is used in the +/// generic toposort function in util. +/// +/// This is used by facilities that need to know the order of the assignments. +/// +/// A good number of languages support reorderable assignment (haskell, elm). +pub fn toposort_assign_bindings( + loc: &Srcloc, + bindings: &[Rc], +) -> Result>>, CompileErr> { + // Topological sort of bindings. + toposort( + bindings, + CompileErr(loc.clone(), "deadlock resolving binding order".to_string()), + // Needs: What this binding relies on. + |possible, b| { + let mut need_set = HashSet::new(); + make_provides_set(&mut need_set, b.body.to_sexp()); + let mut need_set_thats_possible = HashSet::new(); + for need in need_set.intersection(possible) { + need_set_thats_possible.insert(need.clone()); + } + Ok(need_set_thats_possible) + }, + // Has: What this binding provides. + |b| match &b.pattern { + BindingPattern::Name(name) => HashSet::from([name.clone()]), + BindingPattern::Complex(sexp) => { + let mut result_set = HashSet::new(); + make_provides_set(&mut result_set, sexp.clone()); + result_set + } + }, + ) +} + +/// Let forms are "hoisted" (promoted) from being body forms to being functions +/// in the program (either defun or defun-inline). The arguments given are bound +/// in the downstream code, allowing the code generator to re-use functions to +/// allow the inner body forms to use the variable names defined in the assign. +/// This is isolated here from hoist_body_let_binding because it has its own +/// complexity that's separate from the original let features. +/// +/// In the future, things such as lambdas will also desugar along these same +/// routes. +pub fn hoist_assign_form(letdata: &LetData) -> Result { + let sorted_spec = toposort_assign_bindings(&letdata.loc, &letdata.bindings)?; + + // Break up into stages of parallel let forms. + // Track the needed bindings of this level. + // If this becomes broader in a way that doesn't + // match the existing provides, we need to break + // the let binding. + let mut current_provides = HashSet::new(); + let mut binding_lists = Vec::new(); + let mut this_round_bindings = Vec::new(); + let mut new_provides: HashSet> = HashSet::new(); + + for spec in sorted_spec.iter() { + let mut new_needs = spec.needs.difference(¤t_provides).cloned(); + if new_needs.next().is_some() { + // Roll over the set we're accumulating to the finished version. + let mut empty_tmp: Vec> = Vec::new(); + swap(&mut empty_tmp, &mut this_round_bindings); + binding_lists.push(empty_tmp); + for provided in new_provides.iter() { + current_provides.insert(provided.clone()); + } + new_provides.clear(); + } + // Record what we can provide to the next round. + for p in spec.has.iter() { + new_provides.insert(p.clone()); + } + this_round_bindings.push(letdata.bindings[spec.index].clone()); + } + + // Pick up the last ones that didn't add new needs. + if !this_round_bindings.is_empty() { + binding_lists.push(this_round_bindings); + } + + binding_lists.reverse(); + + // Spill let forms as parallel sets to get the best stack we can. + let mut end_bindings = Vec::new(); + swap(&mut end_bindings, &mut binding_lists[0]); + + // build a stack of let forms starting with the inner most bindings. + let mut output_let = BodyForm::Let( + LetFormKind::Parallel, + Box::new(LetData { + bindings: end_bindings, + ..letdata.clone() + }), + ); + + // build rest of the stack. + for binding_list in binding_lists.into_iter().skip(1) { + output_let = BodyForm::Let( + LetFormKind::Parallel, + Box::new(LetData { + bindings: binding_list, + body: Rc::new(output_let), + ..letdata.clone() + }), + ) + } + + Ok(output_let) +} + +/// The main function that, when encountering something that needs to desugar to +/// a function, returns the functions that result (because things inside it may +/// also need to desugar) and rewrites the expression to incorporate that +/// function. +/// +/// We add result here in case something needs extra processing, such as assign +/// form sorting, which can fail if a workable order can't be solved. pub fn hoist_body_let_binding( outer_context: Option>, args: Rc, body: Rc, -) -> (Vec, Rc) { +) -> Result<(Vec, Rc), CompileErr> { match body.borrow() { BodyForm::Let(LetFormKind::Sequential, letdata) => { if letdata.bindings.is_empty() { - return (vec![], letdata.body.clone()); + return Ok((vec![], letdata.body.clone())); } // If we're here, we're in the middle of hoisting. @@ -839,12 +1006,10 @@ pub fn hoist_body_let_binding( let sub_bindings = letdata.bindings.iter().skip(1).cloned().collect(); Rc::new(BodyForm::Let( LetFormKind::Sequential, - LetData { - loc: letdata.loc.clone(), - kw: letdata.kw.clone(), + Box::new(LetData { bindings: sub_bindings, - body: letdata.body.clone(), - }, + ..*letdata.clone() + }), )) }; @@ -853,12 +1018,11 @@ pub fn hoist_body_let_binding( args, Rc::new(BodyForm::Let( LetFormKind::Parallel, - LetData { - loc: letdata.loc.clone(), - kw: letdata.kw.clone(), + Box::new(LetData { bindings: vec![letdata.bindings[0].clone()], body: new_sub_expr, - }, + ..*letdata.clone() + }), )), ) } @@ -869,21 +1033,21 @@ pub fn hoist_body_let_binding( let mut revised_bindings = Vec::new(); for b in letdata.bindings.iter() { let (mut new_helpers, new_binding) = - hoist_body_let_binding(outer_context.clone(), args.clone(), b.body.clone()); + hoist_body_let_binding(outer_context.clone(), args.clone(), b.body.clone())?; out_defuns.append(&mut new_helpers); revised_bindings.push(Rc::new(Binding { loc: b.loc.clone(), nl: b.nl.clone(), - name: b.name.clone(), + pattern: b.pattern.clone(), body: new_binding, })); } - let generated_defun = generate_let_defun( letdata.loc.clone(), None, &defun_name, args, + &letdata.inline_hint, revised_bindings.to_vec(), letdata.body.clone(), ); @@ -915,38 +1079,47 @@ pub fn hoist_body_let_binding( ]; call_args.append(&mut let_args); - // Calling desugared let so we decide what the tail looks like. let final_call = BodyForm::Call(letdata.loc.clone(), call_args, None); - (out_defuns, Rc::new(final_call)) + Ok((out_defuns, Rc::new(final_call))) + } + // New alternative for assign forms. + BodyForm::Let(LetFormKind::Assign, letdata) => { + hoist_body_let_binding(outer_context, args, Rc::new(hoist_assign_form(letdata)?)) } BodyForm::Call(l, list, tail) => { let mut vres = Vec::new(); let mut new_call_list = vec![list[0].clone()]; for i in list.iter().skip(1) { let (mut new_helpers, new_arg) = - hoist_body_let_binding(outer_context.clone(), args.clone(), i.clone()); + hoist_body_let_binding(outer_context.clone(), args.clone(), i.clone())?; new_call_list.push(new_arg); vres.append(&mut new_helpers); } // Ensure that we hoist a let occupying the &rest tail. - let new_tail = tail.as_ref().map(|t| { + let new_tail = if let Some(t) = tail.as_ref() { let (mut new_tail_helpers, new_tail) = - hoist_body_let_binding(outer_context.clone(), args.clone(), t.clone()); + hoist_body_let_binding(outer_context, args, t.clone())?; vres.append(&mut new_tail_helpers); - new_tail - }); + Some(new_tail) + } else { + None + }; - ( + Ok(( vres, Rc::new(BodyForm::Call(l.clone(), new_call_list, new_tail)), - ) + )) } - _ => (Vec::new(), body.clone()), + _ => Ok((Vec::new(), body.clone())), } } -pub fn process_helper_let_bindings(helpers: &[HelperForm]) -> Vec { +/// Turn the helpers for a program into the fully desugared set of helpers for +/// that program. This expands and re-processes the helper set until all +/// desugarable body forms have been transformed to a state where no more +/// desugaring is needed. +pub fn process_helper_let_bindings(helpers: &[HelperForm]) -> Result, CompileErr> { let mut result = helpers.to_owned(); let mut i = 0; @@ -959,7 +1132,7 @@ pub fn process_helper_let_bindings(helpers: &[HelperForm]) -> Vec { None }; let helper_result = - hoist_body_let_binding(context, defun.args.clone(), defun.body.clone()); + hoist_body_let_binding(context, defun.args.clone(), defun.body.clone())?; let hoisted_helpers = helper_result.0; let hoisted_body = helper_result.1.clone(); @@ -988,7 +1161,7 @@ pub fn process_helper_let_bindings(helpers: &[HelperForm]) -> Vec { } } - result + Ok(result) } fn start_codegen( @@ -1005,7 +1178,7 @@ fn start_codegen( // Start compiler with all macros and constants for h in program.helpers.iter() { - code_generator = match h.borrow() { + code_generator = match h { HelperForm::Defconstant(defc) => match defc.kind { ConstantKind::Simple => { let expand_program = SExp::Cons( diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index 687c2fd2e..9b9d23bcc 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -16,6 +16,7 @@ use crate::compiler::codegen::{codegen, hoist_body_let_binding, process_helper_l use crate::compiler::comptypes::{ CompileErr, CompileForm, CompilerOpts, DefunData, HelperForm, PrimaryCodegen, }; +use crate::compiler::dialect::AcceptedDialect; use crate::compiler::evaluate::{build_reflex_captures, Evaluator, EVAL_STACK_LIMIT}; use crate::compiler::frontend::frontend; use crate::compiler::prims; @@ -73,7 +74,9 @@ pub struct DefaultCompilerOpts { pub frontend_opt: bool, pub frontend_check_live: bool, pub start_env: Option>, + pub disassembly_ver: Option, pub prim_map: Rc, Rc>>, + pub dialect: AcceptedDialect, known_dialects: Rc>, } @@ -166,14 +169,14 @@ pub fn compile_pre_forms( }; // Transform let bindings, merging nested let scopes with the top namespace - let hoisted_bindings = hoist_body_let_binding(None, p1.args.clone(), p1.exp.clone()); + let hoisted_bindings = hoist_body_let_binding(None, p1.args.clone(), p1.exp.clone())?; let mut new_helpers = hoisted_bindings.0; let expr = hoisted_bindings.1; // expr is the let-hoisted program // TODO: Distinguish the frontend_helpers and the hoisted_let helpers for later stages let mut combined_helpers = p1.helpers.clone(); combined_helpers.append(&mut new_helpers); - let combined_helpers = process_helper_let_bindings(&combined_helpers); + let combined_helpers = process_helper_let_bindings(&combined_helpers)?; let p2 = CompileForm { loc: p1.loc.clone(), @@ -228,6 +231,9 @@ impl CompilerOpts for DefaultCompilerOpts { fn code_generator(&self) -> Option { self.code_generator.clone() } + fn dialect(&self) -> AcceptedDialect { + self.dialect.clone() + } fn in_defun(&self) -> bool { self.in_defun } @@ -249,10 +255,18 @@ impl CompilerOpts for DefaultCompilerOpts { fn prim_map(&self) -> Rc, Rc>> { self.prim_map.clone() } + fn disassembly_ver(&self) -> Option { + self.disassembly_ver + } fn get_search_paths(&self) -> Vec { self.include_dirs.clone() } + fn set_dialect(&self, dialect: AcceptedDialect) -> Rc { + let mut copy = self.clone(); + copy.dialect = dialect; + Rc::new(copy) + } fn set_search_paths(&self, dirs: &[String]) -> Rc { if dirs.len() == 1 && dirs[0].len() == 1 { todo!(); @@ -261,6 +275,11 @@ impl CompilerOpts for DefaultCompilerOpts { copy.include_dirs = dirs.to_owned(); Rc::new(copy) } + fn set_disassembly_ver(&self, ver: Option) -> Rc { + let mut copy = self.clone(); + copy.disassembly_ver = ver; + Rc::new(copy) + } fn set_in_defun(&self, new_in_defun: bool) -> Rc { let mut copy = self.clone(); copy.in_defun = new_in_defun; @@ -352,7 +371,9 @@ impl DefaultCompilerOpts { frontend_opt: false, frontend_check_live: true, start_env: None, + dialect: AcceptedDialect::default(), prim_map: create_prim_map(), + disassembly_ver: None, known_dialects: Rc::new(KNOWN_DIALECTS.clone()), } } diff --git a/src/compiler/comptypes.rs b/src/compiler/comptypes.rs index 951312588..56ea89a33 100644 --- a/src/compiler/comptypes.rs +++ b/src/compiler/comptypes.rs @@ -10,7 +10,8 @@ use crate::classic::clvm::__type_compatibility__::{Bytes, BytesFromType}; use crate::classic::clvm_tools::stages::stage_0::TRunProgram; use crate::compiler::clvm::sha256tree; -use crate::compiler::sexp::{decode_string, SExp}; +use crate::compiler::dialect::AcceptedDialect; +use crate::compiler::sexp::{decode_string, enlist, SExp}; use crate::compiler::srcloc::Srcloc; /// The basic error type. It contains a Srcloc identifying coordinates of the @@ -81,6 +82,24 @@ pub fn list_to_cons(l: Srcloc, list: &[Rc]) -> SExp { result } +/// Specifies the pattern that is destructured in let bindings. +#[derive(Clone, Debug, Serialize)] +pub enum BindingPattern { + /// The whole expression is bound to this name. + Name(Vec), + /// Specifies a tree of atoms into which the value will be destructured. + Complex(Rc), +} + +/// If present, states an intention for desugaring of this let form to favor +/// inlining or functions. +#[derive(Clone, Debug, Serialize)] +pub enum LetFormInlineHint { + NoChoice, + Inline(Srcloc), + NonInline(Srcloc), +} + /// A binding from a (let ...) form. Specifies the name of the bound variable /// the location of the whole binding form, the location of the name atom (nl) /// and the body as a BodyForm (which are chialisp expressions). @@ -90,8 +109,11 @@ pub struct Binding { pub loc: Srcloc, /// Location of the name atom specifically. pub nl: Srcloc, - /// The name. - pub name: Vec, + /// Specifies the pattern which is extracted from the expression, which can + /// be a Name (a single name names the whole subexpression) or Complex which + /// can destructure and is used in code that extends cl21 past the definition + /// of the language at that point. + pub pattern: BindingPattern, /// The expression the binding refers to. pub body: Rc, } @@ -104,6 +126,7 @@ pub struct Binding { pub enum LetFormKind { Parallel, Sequential, + Assign, } /// Information about a let form. Encapsulates everything except whether it's @@ -114,6 +137,8 @@ pub struct LetData { pub loc: Srcloc, /// The location specifically of the let or let* keyword. pub kw: Option, + /// Inline hint. + pub inline_hint: Option, /// The bindings introduced. pub bindings: Vec>, /// The expression evaluated in the context of all the bindings. @@ -123,7 +148,7 @@ pub struct LetData { #[derive(Clone, Debug, Serialize)] pub enum BodyForm { /// A let or let* form (depending on LetFormKind). - Let(LetFormKind, LetData), + Let(LetFormKind, Box), /// An explicitly quoted constant of some kind. Quoted(SExp), /// An undiferentiated "value" of some kind in the source language. @@ -324,6 +349,10 @@ pub trait CompilerOpts { /// complex constants, and into (com ...) forms. This allows the CompilerOpts /// to carry this info across boundaries into a new context. fn code_generator(&self) -> Option; + /// Get the dialect declared in the toplevel program. + fn dialect(&self) -> AcceptedDialect; + /// Disassembly version (for disassembly style serialization) + fn disassembly_ver(&self) -> Option; /// Specifies whether code is being generated on behalf of an inner defun in /// the program. fn in_defun(&self) -> bool; @@ -348,8 +377,12 @@ pub trait CompilerOpts { /// Specifies the search paths we're carrying. fn get_search_paths(&self) -> Vec; + /// Set the dialect. + fn set_dialect(&self, dialect: AcceptedDialect) -> Rc; /// Set search paths. fn set_search_paths(&self, dirs: &[String]) -> Rc; + /// Set disassembly version for. + fn set_disassembly_ver(&self, ver: Option) -> Rc; /// Set whether we're compiling on behalf of a defun. fn set_in_defun(&self, new_in_defun: bool) -> Rc; /// Set whether to inject the standard environment. @@ -604,6 +637,59 @@ impl HelperForm { } } +fn compose_let(marker: &[u8], letdata: &LetData) -> Rc { + let translated_bindings: Vec> = letdata.bindings.iter().map(|x| x.to_sexp()).collect(); + let bindings_cons = list_to_cons(letdata.loc.clone(), &translated_bindings); + let translated_body = letdata.body.to_sexp(); + let kw_loc = letdata.kw.clone().unwrap_or_else(|| letdata.loc.clone()); + Rc::new(SExp::Cons( + letdata.loc.clone(), + Rc::new(SExp::Atom(kw_loc, marker.to_vec())), + Rc::new(SExp::Cons( + letdata.loc.clone(), + Rc::new(bindings_cons), + Rc::new(SExp::Cons( + letdata.loc.clone(), + translated_body, + Rc::new(SExp::Nil(letdata.loc.clone())), + )), + )), + )) +} + +fn compose_assign(letdata: &LetData) -> Rc { + let mut result = Vec::new(); + let kw_loc = letdata.kw.clone().unwrap_or_else(|| letdata.loc.clone()); + result.push(Rc::new(SExp::Atom(kw_loc, b"assign".to_vec()))); + for b in letdata.bindings.iter() { + // Binding pattern + match &b.pattern { + BindingPattern::Name(v) => { + result.push(Rc::new(SExp::Atom(b.nl.clone(), v.to_vec()))); + } + BindingPattern::Complex(c) => { + result.push(c.clone()); + } + } + + // Binding body. + result.push(b.body.to_sexp()); + } + + result.push(letdata.body.to_sexp()); + Rc::new(enlist(letdata.loc.clone(), &result)) +} + +fn get_let_marker_text(kind: &LetFormKind, letdata: &LetData) -> Vec { + match (kind, letdata.inline_hint.as_ref()) { + (LetFormKind::Sequential, _) => b"let*".to_vec(), + (LetFormKind::Parallel, _) => b"let".to_vec(), + (LetFormKind::Assign, Some(LetFormInlineHint::Inline(_))) => b"assign-inline".to_vec(), + (LetFormKind::Assign, Some(LetFormInlineHint::NonInline(_))) => b"assign-lambda".to_vec(), + (LetFormKind::Assign, _) => b"assign".to_vec(), + } +} + impl BodyForm { /// Get the general location of the BodyForm. pub fn loc(&self) -> Srcloc { @@ -621,29 +707,10 @@ impl BodyForm { /// afterward. pub fn to_sexp(&self) -> Rc { match self { + BodyForm::Let(LetFormKind::Assign, letdata) => compose_assign(letdata), BodyForm::Let(kind, letdata) => { - let translated_bindings: Vec> = - letdata.bindings.iter().map(|x| x.to_sexp()).collect(); - let bindings_cons = list_to_cons(letdata.loc.clone(), &translated_bindings); - let translated_body = letdata.body.to_sexp(); - let marker = match kind { - LetFormKind::Parallel => "let", - LetFormKind::Sequential => "let*", - }; - let kw_loc = letdata.kw.clone().unwrap_or_else(|| letdata.loc.clone()); - Rc::new(SExp::Cons( - letdata.loc.clone(), - Rc::new(SExp::atom_from_string(kw_loc, marker)), - Rc::new(SExp::Cons( - letdata.loc.clone(), - Rc::new(bindings_cons), - Rc::new(SExp::Cons( - letdata.loc.clone(), - translated_body, - Rc::new(SExp::Nil(letdata.loc.clone())), - )), - )), - )) + let marker = get_let_marker_text(kind, letdata); + compose_let(&marker, letdata) } BodyForm::Quoted(body) => Rc::new(SExp::Cons( body.loc(), @@ -671,9 +738,13 @@ impl BodyForm { impl Binding { /// Express the binding as it would be used in a let form. pub fn to_sexp(&self) -> Rc { + let pat = match &self.pattern { + BindingPattern::Name(name) => Rc::new(SExp::atom_from_vec(self.loc.clone(), name)), + BindingPattern::Complex(sexp) => sexp.clone(), + }; Rc::new(SExp::Cons( self.loc.clone(), - Rc::new(SExp::atom_from_vec(self.loc.clone(), &self.name)), + pat, Rc::new(SExp::Cons( self.loc.clone(), self.body.to_sexp(), @@ -770,7 +841,10 @@ pub fn cons_of_string_map( list_to_cons(l, &sorted_converted) } -pub fn map_m(f: &dyn Fn(&T) -> Result, list: &[T]) -> Result, E> { +pub fn map_m(mut f: F, list: &[T]) -> Result, E> +where + F: FnMut(&T) -> Result, +{ let mut result = Vec::new(); for e in list { let val = f(e)?; @@ -779,6 +853,18 @@ pub fn map_m(f: &dyn Fn(&T) -> Result, list: &[T]) -> Result(mut f: F, list: &[T]) -> Result, E> +where + F: FnMut(&T) -> Result, +{ + let mut result = Vec::new(); + for e in list { + let val = f(e)?; + result.push(val); + } + Ok(result.into_iter().rev().collect()) +} + pub fn fold_m(f: &dyn Fn(&R, &T) -> Result, start: R, list: &[T]) -> Result { let mut res: R = start; for elt in list.iter() { diff --git a/src/compiler/dialect.rs b/src/compiler/dialect.rs new file mode 100644 index 000000000..305a0048c --- /dev/null +++ b/src/compiler/dialect.rs @@ -0,0 +1,110 @@ +use std::collections::HashMap; + +use clvmr::allocator::{Allocator, NodePtr, SExp}; + +use crate::classic::clvm::sexp::proper_list; + +use crate::compiler::sexp::decode_string; + +/// Specifying how the language is spoken. +#[derive(Clone, Debug, Default)] +pub struct AcceptedDialect { + pub stepping: Option, +} + +/// A package containing the content we should insert when a dialect include is +/// used, plus the compilation flags. +#[derive(Clone, Debug)] +pub struct DialectDescription { + pub accepted: AcceptedDialect, + pub content: String, +} + +lazy_static! { + pub static ref KNOWN_DIALECTS: HashMap = { + let mut dialects: HashMap = HashMap::new(); + let dialect_list = [ + ( + "*standard-cl-21*", + DialectDescription { + accepted: AcceptedDialect { stepping: Some(21) }, + content: indoc! {"( + (defconstant *chialisp-version* 21) + )"} + .to_string(), + }, + ), + ( + "*standard-cl-22*", + DialectDescription { + accepted: AcceptedDialect { stepping: Some(22) }, + content: indoc! {"( + (defconstant *chialisp-version* 22) + )"} + .to_string(), + }, + ), + ]; + for (n, v) in dialect_list.iter() { + dialects.insert(n.to_string(), v.clone()); + } + dialects + }; +} + +fn include_dialect(allocator: &Allocator, e: &[NodePtr]) -> Option { + let include_keyword_sexp = e[0]; + let name_sexp = e[1]; + if let (SExp::Atom, SExp::Atom) = ( + allocator.sexp(include_keyword_sexp), + allocator.sexp(name_sexp), + ) { + if allocator.atom(include_keyword_sexp) == "include".as_bytes().to_vec() { + if let Some(dialect) = KNOWN_DIALECTS.get(&decode_string(allocator.atom(name_sexp))) { + return Some(dialect.accepted.clone()); + } + } + } + + None +} + +// Now return more parameters about the "modern" dialect, including in the future, +// strictness. This will allow us to support the transition to modern macros which +// in turn allow us to turn on strictness in variable naming. Often multiple moves +// are needed to get from one point to another and there's a tension between +// unitary changes and smaller PRs which do fewer things by themselves. This is +// part of a broader narrative, which many requested that sets us on the path of +// being able to include more information in the dialect result. +pub fn detect_modern(allocator: &mut Allocator, sexp: NodePtr) -> AcceptedDialect { + let mut result = AcceptedDialect::default(); + + if let Some(l) = proper_list(allocator, sexp, true) { + for elt in l.iter() { + let detect_modern_result = detect_modern(allocator, *elt); + if detect_modern_result.stepping.is_some() { + result = detect_modern_result; + break; + } + + match proper_list(allocator, *elt, true) { + None => { + continue; + } + + Some(e) => { + if e.len() != 2 { + continue; + } + + if let Some(dialect) = include_dialect(allocator, &e) { + result = dialect; + break; + } + } + } + } + } + + result +} diff --git a/src/compiler/evaluate.rs b/src/compiler/evaluate.rs index 2eec38783..bf6af75c3 100644 --- a/src/compiler/evaluate.rs +++ b/src/compiler/evaluate.rs @@ -10,11 +10,11 @@ use crate::classic::clvm::__type_compatibility__::{bi_one, bi_zero}; use crate::classic::clvm_tools::stages::stage_0::TRunProgram; use crate::compiler::clvm::run; -use crate::compiler::codegen::codegen; +use crate::compiler::codegen::{codegen, hoist_assign_form}; use crate::compiler::compiler::is_at_capture; use crate::compiler::comptypes::{ - Binding, BodyForm, CallSpec, CompileErr, CompileForm, CompilerOpts, HelperForm, LetData, - LetFormKind, + Binding, BindingPattern, BodyForm, CallSpec, CompileErr, CompileForm, CompilerOpts, HelperForm, + LetData, LetFormInlineHint, LetFormKind, }; use crate::compiler::frontend::frontend; use crate::compiler::runtypes::RunFailure; @@ -106,13 +106,74 @@ fn select_helper(bindings: &[HelperForm], name: &[u8]) -> Option { None } +fn compute_paths_of_destructure( + bindings: &mut Vec<(Vec, Rc)>, + structure: Rc, + path: Number, + mask: Number, + bodyform: Rc, +) { + match structure.atomize() { + SExp::Cons(_, a, b) => { + let next_mask = mask.clone() * 2_u32.to_bigint().unwrap(); + let next_right_path = mask + path.clone(); + compute_paths_of_destructure(bindings, a, path, next_mask.clone(), bodyform.clone()); + compute_paths_of_destructure(bindings, b, next_right_path, next_mask, bodyform); + } + SExp::Atom(_, name) => { + let mut produce_path = path.clone() | mask; + let mut output_form = bodyform.clone(); + + while produce_path > bi_one() { + if path.clone() & produce_path.clone() != bi_zero() { + // Right path + output_form = Rc::new(make_operator1( + &bodyform.loc(), + "r".to_string(), + output_form, + )); + } else { + // Left path + output_form = Rc::new(make_operator1( + &bodyform.loc(), + "f".to_string(), + output_form, + )); + } + + produce_path /= 2_u32.to_bigint().unwrap(); + } + + bindings.push((name, output_form)); + } + _ => {} + } +} + fn update_parallel_bindings( bindings: &HashMap, Rc>, have_bindings: &[Rc], ) -> HashMap, Rc> { let mut new_bindings = bindings.clone(); for b in have_bindings.iter() { - new_bindings.insert(b.name.clone(), b.body.clone()); + match &b.pattern { + BindingPattern::Name(name) => { + new_bindings.insert(name.clone(), b.body.clone()); + } + BindingPattern::Complex(structure) => { + let mut computed_getters = Vec::new(); + compute_paths_of_destructure( + &mut computed_getters, + structure.clone(), + bi_zero(), + bi_one(), + b.body.clone(), + ); + for (name, p) in computed_getters.iter() { + new_bindings.insert(name.clone(), p.clone()); + } + } + } } new_bindings } @@ -586,6 +647,10 @@ fn flatten_expression_to_names(expr: Rc) -> Rc { Rc::new(BodyForm::Call(expr.loc(), call_vec, None)) } +pub fn eval_dont_expand_let(inline_hint: &Option) -> bool { + matches!(inline_hint, Some(LetFormInlineHint::NonInline(_))) +} + impl<'info> Evaluator { pub fn new( opts: Rc, @@ -991,6 +1056,10 @@ impl<'info> Evaluator { let mut visited = VisitedMarker::again(body.loc(), visited_)?; match body.borrow() { BodyForm::Let(LetFormKind::Parallel, letdata) => { + if eval_dont_expand_let(&letdata.inline_hint) && only_inline { + return Ok(body.clone()); + } + let updated_bindings = update_parallel_bindings(env, &letdata.bindings); self.shrink_bodyform_visited( allocator, @@ -1002,6 +1071,10 @@ impl<'info> Evaluator { ) } BodyForm::Let(LetFormKind::Sequential, letdata) => { + if eval_dont_expand_let(&letdata.inline_hint) && only_inline { + return Ok(body.clone()); + } + if letdata.bindings.is_empty() { self.shrink_bodyform_visited( allocator, @@ -1025,17 +1098,29 @@ impl<'info> Evaluator { &updated_bindings, Rc::new(BodyForm::Let( LetFormKind::Sequential, - LetData { - loc: letdata.loc.clone(), - kw: letdata.kw.clone(), + Box::new(LetData { bindings: rest_of_bindings, - body: letdata.body.clone(), - }, + ..*letdata.clone() + }), )), only_inline, ) } } + BodyForm::Let(LetFormKind::Assign, letdata) => { + if eval_dont_expand_let(&letdata.inline_hint) && only_inline { + return Ok(body.clone()); + } + + self.shrink_bodyform_visited( + allocator, + &mut visited, + prog_args, + env, + Rc::new(hoist_assign_form(letdata)?), + only_inline, + ) + } BodyForm::Quoted(_) => Ok(body.clone()), BodyForm::Value(SExp::Atom(l, name)) => { if name == &"@".as_bytes().to_vec() { diff --git a/src/compiler/frontend.rs b/src/compiler/frontend.rs index 2ae6d1385..ad24d0a24 100644 --- a/src/compiler/frontend.rs +++ b/src/compiler/frontend.rs @@ -5,17 +5,17 @@ use std::rc::Rc; use crate::classic::clvm::__type_compatibility__::bi_one; use crate::compiler::comptypes::{ - list_to_cons, ArgsAndTail, Binding, BodyForm, CompileErr, CompileForm, CompilerOpts, - ConstantKind, DefconstData, DefmacData, DefunData, HelperForm, IncludeDesc, LetData, - LetFormKind, ModAccum, + list_to_cons, ArgsAndTail, Binding, BindingPattern, BodyForm, CompileErr, CompileForm, + CompilerOpts, ConstantKind, DefconstData, DefmacData, DefunData, HelperForm, IncludeDesc, + LetData, LetFormInlineHint, LetFormKind, ModAccum, }; use crate::compiler::preprocessor::preprocess; -use crate::compiler::rename::rename_children_compileform; -use crate::compiler::sexp::{enlist, SExp}; +use crate::compiler::rename::{rename_assign_bindings, rename_children_compileform}; +use crate::compiler::sexp::{decode_string, enlist, SExp}; use crate::compiler::srcloc::Srcloc; use crate::util::u8_from_number; -fn collect_used_names_sexp(body: Rc) -> Vec> { +pub fn collect_used_names_sexp(body: Rc) -> Vec> { match body.borrow() { SExp::Atom(_, name) => vec![name.to_vec()], SExp::Cons(_, head, tail) => { @@ -46,7 +46,7 @@ fn collect_used_names_bodyform(body: &BodyForm) -> Vec> { result } BodyForm::Quoted(_) => vec![], - BodyForm::Value(atom) => match atom.borrow() { + BodyForm::Value(atom) => match atom { SExp::Atom(_l, v) => vec![v.to_vec()], SExp::Cons(_l, f, r) => { let mut first_names = collect_used_names_sexp(f.clone()); @@ -89,7 +89,7 @@ fn collect_used_names_helperform(body: &HelperForm) -> Vec> { fn collect_used_names_compileform(body: &CompileForm) -> Vec> { let mut result = Vec::new(); for h in body.helpers.iter() { - let mut helper_list = collect_used_names_helperform(h.borrow()); + let mut helper_list = collect_used_names_helperform(h); result.append(&mut helper_list); } @@ -257,15 +257,16 @@ fn make_let_bindings( SExp::Nil(_) => Ok(vec![]), SExp::Cons(_, head, tl) => head .proper_list() - .map(|x| match &x[..] { - [SExp::Atom(l, name), expr] => { + .filter(|x| x.len() == 2) + .map(|x| match (x[0].atomize(), &x[1]) { + (SExp::Atom(l, name), expr) => { let compiled_body = compile_bodyform(opts.clone(), Rc::new(expr.clone()))?; let mut result = Vec::new(); let mut rest_bindings = make_let_bindings(opts, tl.clone())?; result.push(Rc::new(Binding { loc: l.clone(), - nl: l.clone(), - name: name.to_vec(), + nl: l, + pattern: BindingPattern::Name(name.to_vec()), body: Rc::new(compiled_body), })); result.append(&mut rest_bindings); @@ -278,6 +279,94 @@ fn make_let_bindings( } } +// Make a set of names in this sexp. +pub fn make_provides_set(provides_set: &mut HashSet>, body_sexp: Rc) { + match body_sexp.atomize() { + SExp::Cons(_, a, b) => { + make_provides_set(provides_set, a); + make_provides_set(provides_set, b); + } + SExp::Atom(_, name) => { + provides_set.insert(name); + } + _ => {} + } +} + +fn at_or_above_23(opts: Rc) -> bool { + opts.dialect().stepping.unwrap_or(0) > 22 +} + +fn handle_assign_form( + opts: Rc, + l: Srcloc, + v: &[SExp], + inline_hint: Option, +) -> Result { + if v.len() % 2 == 0 { + return Err(CompileErr( + l, + "assign form should be in pairs of pattern value followed by an expression".to_string(), + )); + } + + let mut bindings = Vec::new(); + let mut check_duplicates = HashSet::new(); + + for idx in (0..(v.len() - 1) / 2).map(|idx| idx * 2) { + let destructure_pattern = Rc::new(v[idx].clone()); + let binding_body = compile_bodyform(opts.clone(), Rc::new(v[idx + 1].clone()))?; + + // Ensure bindings aren't duplicated as we won't be able to + // guarantee their order during toposort. + let mut this_provides = HashSet::new(); + make_provides_set(&mut this_provides, destructure_pattern.clone()); + + for item in this_provides.iter() { + if check_duplicates.contains(item) { + return Err(CompileErr( + destructure_pattern.loc(), + format!("Duplicate binding {}", decode_string(item)), + )); + } + check_duplicates.insert(item.clone()); + } + + bindings.push(Rc::new(Binding { + loc: v[idx].loc().ext(&v[idx + 1].loc()), + nl: destructure_pattern.loc(), + pattern: BindingPattern::Complex(destructure_pattern), + body: Rc::new(binding_body), + })); + } + + let mut compiled_body = compile_bodyform(opts.clone(), Rc::new(v[v.len() - 1].clone()))?; + // We don't need to do much if there were no bindings. + if bindings.is_empty() { + return Ok(compiled_body); + } + + if at_or_above_23(opts.clone()) { + let (new_compiled_body, new_bindings) = + rename_assign_bindings(&l, &bindings, Rc::new(compiled_body))?; + compiled_body = new_compiled_body; + bindings = new_bindings; + }; + + // Return a precise representation of this assign while storing up the work + // we did breaking it down. + Ok(BodyForm::Let( + LetFormKind::Assign, + Box::new(LetData { + loc: l.clone(), + kw: Some(l), + bindings, + inline_hint, + body: Rc::new(compiled_body), + }), + )) +} + pub fn compile_bodyform( opts: Rc, body: Rc, @@ -319,6 +408,9 @@ pub fn compile_bodyform( return Ok(BodyForm::Quoted(tail_copy.clone())); } + let assign_lambda = *atom_name == "assign-lambda".as_bytes().to_vec(); + let assign_inline = *atom_name == "assign-inline".as_bytes().to_vec(); + match tail.proper_list() { Some(v) => { if *atom_name == "let".as_bytes().to_vec() @@ -342,13 +434,30 @@ pub fn compile_bodyform( let compiled_body = compile_bodyform(opts, Rc::new(body))?; Ok(BodyForm::Let( kind, - LetData { + Box::new(LetData { loc: l.clone(), kw: Some(l.clone()), bindings: let_bindings, + inline_hint: None, body: Rc::new(compiled_body), - }, + }), )) + } else if assign_lambda + || assign_inline + || *atom_name == "assign".as_bytes().to_vec() + { + handle_assign_form( + opts.clone(), + l.clone(), + &v, + if assign_lambda { + Some(LetFormInlineHint::NonInline(l.clone())) + } else if assign_inline { + Some(LetFormInlineHint::Inline(l.clone())) + } else { + Some(LetFormInlineHint::NoChoice) + }, + ) } else if *atom_name == "quote".as_bytes().to_vec() { if v.len() != 1 { return finish_err("quote"); @@ -807,7 +916,7 @@ pub fn frontend( } }; - let our_mod = rename_children_compileform(&compiled?); + let our_mod = rename_children_compileform(&compiled?)?; let expr_names: HashSet> = collect_used_names_bodyform(our_mod.exp.borrow()) .iter() diff --git a/src/compiler/inline.rs b/src/compiler/inline.rs index 0d27e8875..604014e2b 100644 --- a/src/compiler/inline.rs +++ b/src/compiler/inline.rs @@ -262,7 +262,7 @@ fn get_inline_callable( /// expressions given in the ultimate original call. This allows inline functions /// to seem to call each other as long as there's no cycle. fn make_args_for_call_from_inline( - visited_inlines: &mut HashSet>, + visited_inlines: &HashSet>, runner: Rc, opts: Rc, compiler: &PrimaryCodegen, diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 32e12ad78..af2c6ff15 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -13,8 +13,9 @@ pub mod compiler; /// - CompileForm - The type of finished (mod ) forms before code generation. /// - HelperForm - The type of declarations like macros, constants and functions. pub mod comptypes; -/// pub mod debug; +/// Utilities for chialisp dialect choice +pub mod dialect; pub mod evaluate; pub mod frontend; pub mod gensym; diff --git a/src/compiler/prims.rs b/src/compiler/prims.rs index e8cc04f71..8a30023b2 100644 --- a/src/compiler/prims.rs +++ b/src/compiler/prims.rs @@ -137,7 +137,63 @@ pub fn prims() -> Vec<(Vec, SExp)> { ), ( "softfork".as_bytes().to_vec(), - SExp::Integer(primloc, 36_u32.to_bigint().unwrap()), + SExp::Integer(primloc.clone(), 36_u32.to_bigint().unwrap()), + ), + ( + "coinid".as_bytes().to_vec(), + SExp::Integer(primloc.clone(), 48_u32.to_bigint().unwrap()), + ), + ( + "g1_subtract".as_bytes().to_vec(), + SExp::Integer(primloc.clone(), 49_u32.to_bigint().unwrap()), + ), + ( + "g1_multiply".as_bytes().to_vec(), + SExp::Integer(primloc.clone(), 50_u32.to_bigint().unwrap()), + ), + ( + "g1_negate".as_bytes().to_vec(), + SExp::Integer(primloc.clone(), 51_u32.to_bigint().unwrap()), + ), + ( + "g2_add".as_bytes().to_vec(), + SExp::Integer(primloc.clone(), 52_u32.to_bigint().unwrap()), + ), + ( + "g2_subtract".as_bytes().to_vec(), + SExp::Integer(primloc.clone(), 53_u32.to_bigint().unwrap()), + ), + ( + "g2_multiply".as_bytes().to_vec(), + SExp::Integer(primloc.clone(), 54_u32.to_bigint().unwrap()), + ), + ( + "g2_negate".as_bytes().to_vec(), + SExp::Integer(primloc.clone(), 55_u32.to_bigint().unwrap()), + ), + ( + "g1_map".as_bytes().to_vec(), + SExp::Integer(primloc.clone(), 56_u32.to_bigint().unwrap()), + ), + ( + "g2_map".as_bytes().to_vec(), + SExp::Integer(primloc.clone(), 57_u32.to_bigint().unwrap()), + ), + ( + "bls_pairing_identity".as_bytes().to_vec(), + SExp::Integer(primloc.clone(), 58_u32.to_bigint().unwrap()), + ), + ( + "bls_verify".as_bytes().to_vec(), + SExp::Integer(primloc.clone(), 59_u32.to_bigint().unwrap()), + ), + ( + "secp256k1_verify".as_bytes().to_vec(), + SExp::Integer(primloc.clone(), 0x13d61f00.to_bigint().unwrap()), + ), + ( + "secp256r1_verify".as_bytes().to_vec(), + SExp::Integer(primloc, 0x1c3a8f00.to_bigint().unwrap()), ), ] } diff --git a/src/compiler/rename.rs b/src/compiler/rename.rs index b549a0b16..833706754 100644 --- a/src/compiler/rename.rs +++ b/src/compiler/rename.rs @@ -2,12 +2,15 @@ use std::borrow::Borrow; use std::collections::HashMap; use std::rc::Rc; +use crate::compiler::codegen::toposort_assign_bindings; use crate::compiler::comptypes::{ - Binding, BodyForm, CompileForm, DefconstData, DefmacData, DefunData, HelperForm, LetData, - LetFormKind, + map_m, map_m_reverse, Binding, BindingPattern, BodyForm, CompileErr, CompileForm, DefconstData, + DefmacData, DefunData, HelperForm, LetData, LetFormKind, }; use crate::compiler::gensym::gensym; use crate::compiler::sexp::SExp; +use crate::compiler::srcloc::Srcloc; +use crate::util::TopoSortItem; /// Rename in a qq form. This searches for (unquote ...) forms inside and performs /// rename inside them, leaving the rest of the qq form as is. @@ -16,7 +19,7 @@ fn rename_in_qq(namemap: &HashMap, Vec>, body: Rc) -> Rc .and_then(|x| { if let [SExp::Atom(_, q), body] = &x[..] { if q == b"unquote" { - return Some(rename_in_cons(namemap, Rc::new(body.clone()))); + return Some(rename_in_cons(namemap, Rc::new(body.clone()), true)); } } @@ -33,7 +36,11 @@ fn rename_in_qq(namemap: &HashMap, Vec>, body: Rc) -> Rc } /* Given a cons cell, rename occurrences of oldname to newname */ -fn rename_in_cons(namemap: &HashMap, Vec>, body: Rc) -> Rc { +pub fn rename_in_cons( + namemap: &HashMap, Vec>, + body: Rc, + qq_handling: bool, +) -> Rc { match body.borrow() { SExp::Atom(l, name) => match namemap.get(name) { Some(v) => Rc::new(SExp::Atom(l.clone(), v.to_vec())), @@ -63,7 +70,7 @@ fn rename_in_cons(namemap: &HashMap, Vec>, body: Rc) -> Rc body.clone(), }) .unwrap_or_else(|| body.clone()); - } else if *q == "qq".as_bytes().to_vec() { + } else if *q == "qq".as_bytes().to_vec() && qq_handling { return r .proper_list() .map(|x| match &x[..] { @@ -76,8 +83,8 @@ fn rename_in_cons(namemap: &HashMap, Vec>, body: Rc) -> Rc body.clone(), @@ -106,73 +113,142 @@ fn invent_new_names_sexp(body: Rc) -> Vec<(Vec, Vec)> { } } -fn make_binding_unique(b: &Binding) -> (Vec, Binding) { - ( - b.name.to_vec(), - Binding { - loc: b.loc.clone(), - nl: b.nl.clone(), - name: gensym(b.name.clone()), - body: b.body.clone(), +#[derive(Debug, Clone)] +struct InnerRenameList { + bindings: HashMap, Vec>, + from_wing: Binding, +} + +fn make_binding_unique(b: &Binding) -> InnerRenameList { + match &b.pattern { + BindingPattern::Name(name) => { + let mut single_name_map = HashMap::new(); + let new_name = gensym(name.clone()); + single_name_map.insert(name.to_vec(), new_name.clone()); + InnerRenameList { + bindings: single_name_map, + from_wing: Binding { + loc: b.loc.clone(), + nl: b.nl.clone(), + pattern: BindingPattern::Name(new_name), + body: b.body.clone(), + }, + } + } + BindingPattern::Complex(pat) => { + let new_names_vec = invent_new_names_sexp(pat.clone()); + let mut new_names = HashMap::new(); + + for (n, v) in new_names_vec.iter() { + new_names.insert(n.clone(), v.clone()); + } + + let renamed_pattern = rename_in_cons(&new_names, pat.clone(), false); + InnerRenameList { + bindings: new_names, + from_wing: Binding { + loc: b.loc.clone(), + nl: b.nl.clone(), + pattern: BindingPattern::Complex(renamed_pattern), + body: b.body.clone(), + }, + } + } + } +} + +pub fn rename_assign_bindings( + l: &Srcloc, + bindings: &[Rc], + body: Rc, +) -> Result<(BodyForm, Vec>), CompileErr> { + // Order the bindings. + let sorted_bindings = toposort_assign_bindings(l, bindings)?; + let mut renames = HashMap::new(); + // Process in reverse order so we rename from inner to outer. + let bindings_to_rename: Vec> = sorted_bindings.to_vec(); + let renamed_bindings = map_m_reverse( + |item: &TopoSortItem<_>| -> Result, CompileErr> { + let b: &Binding = bindings[item.index].borrow(); + if let BindingPattern::Complex(p) = &b.pattern { + let new_names = invent_new_names_sexp(p.clone()); + for (name, renamed) in new_names.iter() { + renames.insert(name.clone(), renamed.clone()); + } + Ok(Rc::new(Binding { + pattern: BindingPattern::Complex(rename_in_cons(&renames, p.clone(), false)), + body: Rc::new(rename_in_bodyform(&renames, b.body.clone())?), + ..b.clone() + })) + } else { + Ok(bindings[item.index].clone()) + } }, - ) + &bindings_to_rename, + )?; + Ok((rename_in_bodyform(&renames, body)?, renamed_bindings)) } -fn rename_in_bodyform(namemap: &HashMap, Vec>, b: Rc) -> BodyForm { +fn rename_in_bodyform( + namemap: &HashMap, Vec>, + b: Rc, +) -> Result { match b.borrow() { BodyForm::Let(kind, letdata) => { - let new_bindings = letdata - .bindings - .iter() - .map(|b| { - Rc::new(Binding { + let new_bindings = map_m( + &|b: &Rc| -> Result, CompileErr> { + Ok(Rc::new(Binding { loc: b.loc(), nl: b.nl.clone(), - name: b.name.clone(), - body: Rc::new(rename_in_bodyform(namemap, b.body.clone())), - }) - }) - .collect(); - let new_body = rename_in_bodyform(namemap, letdata.body.clone()); - BodyForm::Let( + pattern: b.pattern.clone(), + body: Rc::new(rename_in_bodyform(namemap, b.body.clone())?), + })) + }, + &letdata.bindings, + )?; + let new_body = rename_in_bodyform(namemap, letdata.body.clone())?; + Ok(BodyForm::Let( kind.clone(), - LetData { - loc: letdata.loc.clone(), - kw: letdata.kw.clone(), + Box::new(LetData { bindings: new_bindings, body: Rc::new(new_body), - }, - ) + ..*letdata.clone() + }), + )) } - BodyForm::Quoted(atom) => match atom.borrow() { + BodyForm::Quoted(atom) => match atom { SExp::Atom(l, n) => match namemap.get(n) { - Some(named) => BodyForm::Quoted(SExp::Atom(l.clone(), named.to_vec())), - None => BodyForm::Quoted(atom.clone()), + Some(named) => Ok(BodyForm::Quoted(SExp::Atom(l.clone(), named.to_vec()))), + None => Ok(BodyForm::Quoted(atom.clone())), }, - _ => BodyForm::Quoted(atom.clone()), + _ => Ok(BodyForm::Quoted(atom.clone())), }, - BodyForm::Value(atom) => match atom.borrow() { + BodyForm::Value(atom) => match atom { SExp::Atom(l, n) => match namemap.get(n) { - Some(named) => BodyForm::Value(SExp::Atom(l.clone(), named.to_vec())), - None => BodyForm::Value(atom.clone()), + Some(named) => Ok(BodyForm::Value(SExp::Atom(l.clone(), named.to_vec()))), + None => Ok(BodyForm::Value(atom.clone())), }, - _ => BodyForm::Value(atom.clone()), + _ => Ok(BodyForm::Value(atom.clone())), }, BodyForm::Call(l, vs, tail) => { - let new_vs = vs - .iter() - .map(|x| Rc::new(rename_in_bodyform(namemap, x.clone()))) - .collect(); - let new_tail = tail - .as_ref() - .map(|t| Rc::new(rename_in_bodyform(namemap, t.clone()))); - BodyForm::Call(l.clone(), new_vs, new_tail) + let new_vs = map_m( + &|x: &Rc| -> Result, CompileErr> { + Ok(Rc::new(rename_in_bodyform(namemap, x.clone())?)) + }, + vs, + )?; + let new_tail = if let Some(t) = tail.as_ref() { + Some(Rc::new(rename_in_bodyform(namemap, t.clone())?)) + } else { + None + }; + Ok(BodyForm::Call(l.clone(), new_vs, new_tail)) } - BodyForm::Mod(l, prog) => BodyForm::Mod(l.clone(), prog.clone()), + BodyForm::Mod(l, prog) => Ok(BodyForm::Mod(l.clone(), prog.clone())), } } @@ -189,102 +265,128 @@ pub fn desugar_sequential_let_bindings( bindings, &BodyForm::Let( LetFormKind::Parallel, - LetData { + Box::new(LetData { loc: want_binding.loc(), kw: None, bindings: vec![want_binding], + inline_hint: None, body: Rc::new(body.clone()), - }, + }), ), n - 1, ) } } -fn rename_args_bodyform(b: &BodyForm) -> BodyForm { - match b.borrow() { +fn rename_args_bodyform(b: &BodyForm) -> Result { + match b { BodyForm::Let(LetFormKind::Sequential, letdata) => { // Renaming a sequential let is exactly as if the bindings were // nested in separate parallel lets. - rename_args_bodyform(&desugar_sequential_let_bindings( + let res = rename_args_bodyform(&desugar_sequential_let_bindings( &letdata.bindings, letdata.body.borrow(), letdata.bindings.len(), - )) + ))?; + Ok(res) } BodyForm::Let(LetFormKind::Parallel, letdata) => { - let renames: Vec<(Vec, Binding)> = letdata + let renames: Vec = letdata .bindings .iter() .map(|x| make_binding_unique(x.borrow())) .collect(); - let new_renamed_bindings: Vec> = - renames.iter().map(|(_, x)| Rc::new(x.clone())).collect(); + let new_renamed_bindings: Vec> = renames + .iter() + .map(|ir| Rc::new(ir.from_wing.clone())) + .collect(); let mut local_namemap = HashMap::new(); - for x in renames.iter() { - let (oldname, binding) = x; - local_namemap.insert(oldname.to_vec(), binding.name.clone()); + for ir in renames.iter() { + for (oldname, binding_name) in ir.bindings.iter() { + local_namemap.insert(oldname.to_vec(), binding_name.clone()); + } } - let new_bindings = new_renamed_bindings - .iter() - .map(|x| { - Rc::new(Binding { + let new_bindings = map_m( + &|x: &Rc| -> Result, CompileErr> { + Ok(Rc::new(Binding { loc: x.loc.clone(), nl: x.nl.clone(), - name: x.name.clone(), - body: Rc::new(rename_args_bodyform(&x.body)), - }) - }) - .collect(); - let locally_renamed_body = rename_in_bodyform(&local_namemap, letdata.body.clone()); - BodyForm::Let( + pattern: x.pattern.clone(), + body: Rc::new(rename_args_bodyform(&x.body)?), + })) + }, + &new_renamed_bindings, + )?; + let args_renamed = rename_args_bodyform(letdata.body.borrow())?; + let locally_renamed_body = rename_in_bodyform(&local_namemap, Rc::new(args_renamed))?; + let new_form = BodyForm::Let( LetFormKind::Parallel, - LetData { - loc: letdata.loc.clone(), - kw: letdata.kw.clone(), + Box::new(LetData { bindings: new_bindings, body: Rc::new(locally_renamed_body), - }, - ) + ..*letdata.clone() + }), + ); + Ok(new_form) + } + + BodyForm::Let(LetFormKind::Assign, letdata) => { + let (new_compiled_body, new_bindings) = + rename_assign_bindings(&letdata.loc, &letdata.bindings, letdata.body.clone())?; + Ok(BodyForm::Let( + LetFormKind::Assign, + Box::new(LetData { + body: Rc::new(new_compiled_body), + bindings: new_bindings, + ..*letdata.clone() + }), + )) } - BodyForm::Quoted(e) => BodyForm::Quoted(e.clone()), - BodyForm::Value(v) => BodyForm::Value(v.clone()), + BodyForm::Quoted(e) => Ok(BodyForm::Quoted(e.clone())), + BodyForm::Value(v) => Ok(BodyForm::Value(v.clone())), BodyForm::Call(l, vs, tail) => { - let new_vs = vs - .iter() - .map(|a| Rc::new(rename_args_bodyform(a))) - .collect(); - let new_tail = tail - .as_ref() - .map(|t| Rc::new(rename_args_bodyform(t.borrow()))); - BodyForm::Call(l.clone(), new_vs, new_tail) + let new_vs = map_m( + &|a: &Rc| -> Result, CompileErr> { + Ok(Rc::new(rename_args_bodyform(a)?)) + }, + vs, + )?; + let new_tail = if let Some(t) = tail.as_ref() { + Some(Rc::new(rename_args_bodyform(t.borrow())?)) + } else { + None + }; + Ok(BodyForm::Call(l.clone(), new_vs, new_tail)) } - BodyForm::Mod(l, program) => BodyForm::Mod(l.clone(), program.clone()), + BodyForm::Mod(l, program) => Ok(BodyForm::Mod(l.clone(), program.clone())), } } -fn rename_in_helperform(namemap: &HashMap, Vec>, h: &HelperForm) -> HelperForm { +fn rename_in_helperform( + namemap: &HashMap, Vec>, + h: &HelperForm, +) -> Result { match h { - HelperForm::Defconstant(defc) => HelperForm::Defconstant(DefconstData { + HelperForm::Defconstant(defc) => Ok(HelperForm::Defconstant(DefconstData { loc: defc.loc.clone(), kind: defc.kind.clone(), name: defc.name.to_vec(), nl: defc.nl.clone(), kw: defc.kw.clone(), - body: Rc::new(rename_in_bodyform(namemap, defc.body.clone())), - }), - HelperForm::Defmacro(mac) => HelperForm::Defmacro(DefmacData { + body: Rc::new(rename_in_bodyform(namemap, defc.body.clone())?), + })), + HelperForm::Defmacro(mac) => Ok(HelperForm::Defmacro(DefmacData { loc: mac.loc.clone(), kw: mac.kw.clone(), nl: mac.nl.clone(), name: mac.name.to_vec(), args: mac.args.clone(), - program: Rc::new(rename_in_compileform(namemap, mac.program.clone())), - }), - HelperForm::Defun(inline, defun) => HelperForm::Defun( + program: Rc::new(rename_in_compileform(namemap, mac.program.clone())?), + })), + HelperForm::Defun(inline, defun) => Ok(HelperForm::Defun( *inline, DefunData { loc: defun.loc.clone(), @@ -293,22 +395,22 @@ fn rename_in_helperform(namemap: &HashMap, Vec>, h: &HelperForm) -> name: defun.name.to_vec(), orig_args: defun.orig_args.clone(), args: defun.args.clone(), - body: Rc::new(rename_in_bodyform(namemap, defun.body.clone())), + body: Rc::new(rename_in_bodyform(namemap, defun.body.clone())?), }, - ), + )), } } -fn rename_args_helperform(h: &HelperForm) -> HelperForm { +fn rename_args_helperform(h: &HelperForm) -> Result { match h { - HelperForm::Defconstant(defc) => HelperForm::Defconstant(DefconstData { + HelperForm::Defconstant(defc) => Ok(HelperForm::Defconstant(DefconstData { loc: defc.loc.clone(), kind: defc.kind.clone(), nl: defc.nl.clone(), kw: defc.kw.clone(), name: defc.name.clone(), - body: Rc::new(rename_args_bodyform(defc.body.borrow())), - }), + body: Rc::new(rename_args_bodyform(defc.body.borrow())?), + })), HelperForm::Defmacro(mac) => { let mut new_names: HashMap, Vec> = HashMap::new(); for x in invent_new_names_sexp(mac.args.clone()).iter() { @@ -318,9 +420,9 @@ fn rename_args_helperform(h: &HelperForm) -> HelperForm { for x in new_names.iter() { local_namemap.insert(x.0.to_vec(), x.1.to_vec()); } - let local_renamed_arg = rename_in_cons(&local_namemap, mac.args.clone()); - let local_renamed_body = rename_args_compileform(mac.program.borrow()); - HelperForm::Defmacro(DefmacData { + let local_renamed_arg = rename_in_cons(&local_namemap, mac.args.clone(), true); + let local_renamed_body = rename_args_compileform(mac.program.borrow())?; + Ok(HelperForm::Defmacro(DefmacData { loc: mac.loc.clone(), kw: mac.kw.clone(), nl: mac.nl.clone(), @@ -329,8 +431,8 @@ fn rename_args_helperform(h: &HelperForm) -> HelperForm { program: Rc::new(rename_in_compileform( &local_namemap, Rc::new(local_renamed_body), - )), - }) + )?), + })) } HelperForm::Defun(inline, defun) => { let new_names = invent_new_names_sexp(defun.args.clone()); @@ -338,9 +440,9 @@ fn rename_args_helperform(h: &HelperForm) -> HelperForm { for x in new_names.iter() { local_namemap.insert(x.0.clone(), x.1.clone()); } - let local_renamed_arg = rename_in_cons(&local_namemap, defun.args.clone()); - let local_renamed_body = rename_args_bodyform(defun.body.borrow()); - HelperForm::Defun( + let local_renamed_arg = rename_in_cons(&local_namemap, defun.args.clone(), true); + let local_renamed_body = rename_args_bodyform(defun.body.borrow())?; + Ok(HelperForm::Defun( *inline, DefunData { loc: defun.loc.clone(), @@ -352,60 +454,58 @@ fn rename_args_helperform(h: &HelperForm) -> HelperForm { body: Rc::new(rename_in_bodyform( &local_namemap, Rc::new(local_renamed_body), - )), + )?), }, - ) + )) } } } -fn rename_in_compileform(namemap: &HashMap, Vec>, c: Rc) -> CompileForm { - CompileForm { +fn rename_in_compileform( + namemap: &HashMap, Vec>, + c: Rc, +) -> Result { + Ok(CompileForm { loc: c.loc.clone(), args: c.args.clone(), include_forms: c.include_forms.clone(), - helpers: c - .helpers - .iter() - .map(|x| rename_in_helperform(namemap, x)) - .collect(), - exp: Rc::new(rename_in_bodyform(namemap, c.exp.clone())), - } + helpers: map_m(|x| rename_in_helperform(namemap, x), &c.helpers)?, + exp: Rc::new(rename_in_bodyform(namemap, c.exp.clone())?), + }) } -pub fn rename_children_compileform(c: &CompileForm) -> CompileForm { - let local_renamed_helpers = c.helpers.iter().map(rename_args_helperform).collect(); - let local_renamed_body = rename_args_bodyform(c.exp.borrow()); - CompileForm { +pub fn rename_children_compileform(c: &CompileForm) -> Result { + let local_renamed_helpers = map_m(&rename_args_helperform, &c.helpers)?; + let local_renamed_body = rename_args_bodyform(c.exp.borrow())?; + Ok(CompileForm { loc: c.loc.clone(), args: c.args.clone(), include_forms: c.include_forms.clone(), helpers: local_renamed_helpers, exp: Rc::new(local_renamed_body), - } + }) } -pub fn rename_args_compileform(c: &CompileForm) -> CompileForm { +pub fn rename_args_compileform(c: &CompileForm) -> Result { let new_names = invent_new_names_sexp(c.args.clone()); let mut local_namemap = HashMap::new(); for x in new_names.iter() { local_namemap.insert(x.0.clone(), x.1.clone()); } - let local_renamed_arg = rename_in_cons(&local_namemap, c.args.clone()); - let local_renamed_helpers: Vec = - c.helpers.iter().map(rename_args_helperform).collect(); - let local_renamed_body = rename_args_bodyform(c.exp.borrow()); - CompileForm { + let local_renamed_arg = rename_in_cons(&local_namemap, c.args.clone(), true); + let local_renamed_helpers: Vec = map_m(&rename_args_helperform, &c.helpers)?; + let local_renamed_body = rename_args_bodyform(c.exp.borrow())?; + Ok(CompileForm { loc: c.loc(), args: local_renamed_arg, include_forms: c.include_forms.clone(), - helpers: local_renamed_helpers - .iter() - .map(|x| rename_in_helperform(&local_namemap, x)) - .collect(), + helpers: map_m( + |x| rename_in_helperform(&local_namemap, x), + &local_renamed_helpers, + )?, exp: Rc::new(rename_in_bodyform( &local_namemap, Rc::new(local_renamed_body), - )), - } + )?), + }) } diff --git a/src/compiler/repl.rs b/src/compiler/repl.rs index ce8101f2c..e8c228311 100644 --- a/src/compiler/repl.rs +++ b/src/compiler/repl.rs @@ -92,7 +92,7 @@ impl Repl { let loc = Srcloc::start(&opts.filename()); let mut toplevel_forms = HashSet::new(); - for w in vec!["defun", "defun-inline", "defconstant", "defmacro"].iter() { + for w in ["defun", "defun-inline", "defconstant", "defmacro"].iter() { toplevel_forms.insert(w.to_string()); } diff --git a/src/compiler/sexp.rs b/src/compiler/sexp.rs index e9bd666b5..58d01ad5b 100644 --- a/src/compiler/sexp.rs +++ b/src/compiler/sexp.rs @@ -16,6 +16,8 @@ use num_traits::{zero, Num}; use serde::Serialize; +#[cfg(test)] +use crate::classic::clvm::__type_compatibility__::bi_one; use crate::classic::clvm::__type_compatibility__::{bi_zero, Bytes, BytesFromType}; use crate::classic::clvm::casts::{bigint_from_bytes, bigint_to_bytes_clvm, TConvertOption}; use crate::compiler::prims::prims; @@ -191,35 +193,30 @@ impl Hash for SExp { } fn make_cons(a: Rc, b: Rc) -> SExp { - SExp::Cons(a.loc().ext(&b.loc()), a.clone(), b.clone()) + SExp::Cons(a.loc().ext(&b.loc()), Rc::clone(&a), Rc::clone(&b)) } #[derive(Debug, PartialEq, Eq)] -enum TermListCommentState { - InComment, - Empty, -} - -#[derive(Debug)] enum SExpParseState { + // The types of state that the Rust pre-forms can take Empty, - CommentText(Srcloc, Vec), - Bareword(Srcloc, Vec), + CommentText, + Bareword(Srcloc, Vec), //srcloc contains the file, line, column and length for the captured form QuotedText(Srcloc, u8, Vec), QuotedEscaped(Srcloc, u8, Vec), OpenList(Srcloc), ParsingList(Srcloc, Rc, Vec>), TermList( Srcloc, - TermListCommentState, - Option>, - Rc, - Vec>, + Option>, // this is the second value in the dot expression + Rc, // used for inner parsing + Vec>, // list content ), } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] enum SExpParseResult { + // the result of a call to parse an SExp Resume(SExpParseState), Emit(Rc, SExpParseState), Error(Srcloc, String), @@ -311,21 +308,23 @@ pub fn enlist(l: Srcloc, v: &[Rc]) -> SExp { let mut result = SExp::Nil(l); for i_reverse in 0..v.len() { let i = v.len() - i_reverse - 1; - result = make_cons(v[i].clone(), Rc::new(result)); + result = make_cons(Rc::clone(&v[i]), Rc::new(result)); } result } -fn emit(a: Rc, p: SExpParseState) -> SExpParseResult { - SExpParseResult::Emit(a, p) +// this function takes a ParseState and returns an Emit ParseResult which contains the ParseState +fn emit(a: Rc, current_state: SExpParseState) -> SExpParseResult { + SExpParseResult::Emit(a, current_state) } fn error(l: Srcloc, t: &str) -> SExpParseResult { SExpParseResult::Error(l, t.to_string()) } -fn resume(p: SExpParseState) -> SExpParseResult { - SExpParseResult::Resume(p) +// this function takes a ParseState and returns a Resume ParseResult which contains the ParseState +fn resume(current_state: SExpParseState) -> SExpParseResult { + SExpParseResult::Resume(current_state) } fn escape_quote(q: u8, s: &[u8]) -> String { @@ -417,14 +416,14 @@ impl SExp { pub fn cons_fst(&self) -> Rc { match self { - SExp::Cons(_, a, _) => a.clone(), + SExp::Cons(_, a, _) => Rc::clone(a), _ => Rc::new(SExp::Nil(self.loc())), } } pub fn cons_snd(&self) -> Rc { match self { - SExp::Cons(_, _, b) => b.clone(), + SExp::Cons(_, _, b) => Rc::clone(b), _ => Rc::new(SExp::Nil(self.loc())), } } @@ -536,191 +535,206 @@ impl SExp { } } -fn parse_sexp_step(loc: Srcloc, p: &SExpParseState, this_char: u8) -> SExpParseResult { - match p { +fn parse_sexp_step(loc: Srcloc, current_state: &SExpParseState, this_char: u8) -> SExpParseResult { + // switch on our state + match current_state { SExpParseState::Empty => match this_char as char { - '(' => resume(SExpParseState::OpenList(loc)), - '\n' => resume(SExpParseState::Empty), - ';' => resume(SExpParseState::CommentText(loc, Vec::new())), + // we are not currently in a list + '(' => resume(SExpParseState::OpenList(loc)), // move to OpenList state + '\n' => resume(SExpParseState::Empty), // new line, same state + ';' => resume(SExpParseState::CommentText), ')' => error(loc, "Too many close parens"), - '"' => resume(SExpParseState::QuotedText(loc, b'"', Vec::new())), - '\'' => resume(SExpParseState::QuotedText(loc, b'\'', Vec::new())), + '"' => resume(SExpParseState::QuotedText(loc, b'"', Vec::new())), // match on " + '\'' => resume(SExpParseState::QuotedText(loc, b'\'', Vec::new())), // match on ' ch => { if char::is_whitespace(ch) { resume(SExpParseState::Empty) } else { - resume(SExpParseState::Bareword(loc, vec![this_char])) + resume(SExpParseState::Bareword(loc, vec![this_char])) // start of a word - could be an atom or a keyword - the compiler will decide } } }, - SExpParseState::CommentText(pl, t) => match this_char as char { - '\r' => resume(SExpParseState::CommentText(pl.clone(), t.to_vec())), + // t is a Vec of the previous characters in this comment string + SExpParseState::CommentText => match this_char as char { '\n' => resume(SExpParseState::Empty), - _ => { - let mut tcopy = t.to_vec(); - tcopy.push(this_char); - resume(SExpParseState::CommentText(pl.ext(&loc), tcopy)) - } + _ => resume(SExpParseState::CommentText), }, - SExpParseState::Bareword(pl, a) => { + // we currently processing a new word + SExpParseState::Bareword(srcloc, word_so_far) => { if char::is_whitespace(this_char as char) { + // we've found a space, so it's the end of a word emit( - Rc::new(make_atom(pl.clone(), a.to_vec())), + Rc::new(make_atom(srcloc.clone(), word_so_far.to_vec())), SExpParseState::Empty, ) } else { - let mut acopy = a.to_vec(); - acopy.push(this_char); - resume(SExpParseState::Bareword(pl.ext(&loc), acopy)) + // otherwise add letter to word + let mut word_copy = word_so_far.to_vec(); + word_copy.push(this_char); + resume(SExpParseState::Bareword(srcloc.ext(&loc), word_copy)) } } - SExpParseState::QuotedText(pl, term, t) => { + SExpParseState::QuotedText(srcloc, term, t) => { if this_char == b'\\' { - resume(SExpParseState::QuotedEscaped(pl.clone(), *term, t.to_vec())) + // if we have a character escape then copy the character directly + resume(SExpParseState::QuotedEscaped( + srcloc.clone(), + *term, + t.to_vec(), + )) } else if this_char == *term { + // otherwise check if it's the terminating character (either ' or ") emit( - Rc::new(SExp::QuotedString(pl.ext(&loc), *term, t.to_vec())), + Rc::new(SExp::QuotedString(srcloc.ext(&loc), *term, t.to_vec())), // add quoted string to parent list SExpParseState::Empty, ) } else { + // otherwise copy the character let mut tcopy = t.to_vec(); tcopy.push(this_char); - resume(SExpParseState::QuotedText(pl.clone(), *term, tcopy)) + resume(SExpParseState::QuotedText(srcloc.clone(), *term, tcopy)) } } - SExpParseState::QuotedEscaped(pl, term, t) => { + // copy the character the quoted text because we have put the escape character first + SExpParseState::QuotedEscaped(srcloc, term, t) => { let mut tcopy = t.to_vec(); tcopy.push(this_char); - resume(SExpParseState::QuotedText(pl.clone(), *term, tcopy)) + resume(SExpParseState::QuotedText(srcloc.clone(), *term, tcopy)) } - SExpParseState::OpenList(pl) => match this_char as char { - ')' => emit(Rc::new(SExp::Nil(pl.ext(&loc))), SExpParseState::Empty), + SExpParseState::OpenList(srcloc) => match this_char as char { + // we are beginning a new list + ')' => emit(Rc::new(SExp::Nil(srcloc.ext(&loc))), SExpParseState::Empty), // create a Nil object '.' => error(loc, "Dot can't appear directly after begin paren"), _ => match parse_sexp_step(loc.clone(), &SExpParseState::Empty, this_char) { - SExpParseResult::Emit(o, p) => resume(SExpParseState::ParsingList( - pl.ext(&loc), - Rc::new(p), + // fetch result of parsing as if we were in empty state + SExpParseResult::Emit(o, current_state) => resume(SExpParseState::ParsingList( + // we found an object, resume processing + srcloc.ext(&loc), + Rc::new(current_state), // captured state from our pretend empty state vec![o], )), - SExpParseResult::Resume(p) => resume(SExpParseState::ParsingList( - pl.ext(&loc), - Rc::new(p), + SExpParseResult::Resume(current_state) => resume(SExpParseState::ParsingList( + // we're still reading the object, resume processing + srcloc.ext(&loc), + Rc::new(current_state), // captured state from our pretend empty state Vec::new(), )), - SExpParseResult::Error(l, e) => SExpParseResult::Error(l, e), + SExpParseResult::Error(l, e) => SExpParseResult::Error(l, e), // propagate error }, }, - SExpParseState::ParsingList(pl, pp, list_content) => { + // We are in the middle of a list currently + SExpParseState::ParsingList(srcloc, pp, list_content) => { + // pp is the captured inside-list state we received from OpenList match (this_char as char, pp.borrow()) { ('.', SExpParseState::Empty) => resume(SExpParseState::TermList( - pl.ext(&loc), - TermListCommentState::Empty, + // dot notation showing cons cell + srcloc.ext(&loc), None, - Rc::new(SExpParseState::Empty), + Rc::new(SExpParseState::Empty), // nested state is empty list_content.to_vec(), )), (')', SExpParseState::Empty) => emit( - Rc::new(enlist(pl.clone(), list_content)), + // close list and emit it upwards as a complete entity + Rc::new(enlist(srcloc.clone(), list_content)), SExpParseState::Empty, ), (')', SExpParseState::Bareword(l, t)) => { + // you've reached the end of the word AND the end of the list, close list and emit upwards let parsed_atom = make_atom(l.clone(), t.to_vec()); let mut updated_list = list_content.to_vec(); updated_list.push(Rc::new(parsed_atom)); emit( - Rc::new(enlist(pl.clone(), &updated_list)), + Rc::new(enlist(srcloc.clone(), &updated_list)), SExpParseState::Empty, ) } + // analyze this character using the mock "inner state" stored in pp (_, _) => match parse_sexp_step(loc.clone(), pp.borrow(), this_char) { - SExpParseResult::Emit(o, p) => { + // + SExpParseResult::Emit(o, current_state) => { + // add result of parse_sexp_step to our list let mut list_copy = list_content.clone(); list_copy.push(o); - let result = - SExpParseState::ParsingList(pl.ext(&loc), Rc::new(p), list_copy); + let result = SExpParseState::ParsingList( + srcloc.ext(&loc), + Rc::new(current_state), + list_copy, + ); resume(result) } SExpParseResult::Resume(rp) => resume(SExpParseState::ParsingList( - pl.ext(&loc), - Rc::new(rp), + // we aren't finished reading in our nested state + srcloc.ext(&loc), + Rc::new(rp), // store the returned state from parse_sexp_step in pp list_content.to_vec(), )), - SExpParseResult::Error(l, e) => SExpParseResult::Error(l, e), + SExpParseResult::Error(l, e) => SExpParseResult::Error(l, e), // propagate error upwards }, } } - SExpParseState::TermList(pl, TermListCommentState::InComment, parsed, pp, list_content) => { - let end_comment = if this_char as char == '\n' || this_char as char == '\r' { - TermListCommentState::Empty - } else { - TermListCommentState::InComment - }; - resume(SExpParseState::TermList( - pl.clone(), - end_comment, - parsed.clone(), - pp.clone(), - list_content.clone(), - )) - } - SExpParseState::TermList( - pl, - TermListCommentState::Empty, - Some(parsed), - pp, - list_content, - ) => { - if this_char.is_ascii_whitespace() { - resume(SExpParseState::TermList( - pl.ext(&loc), - TermListCommentState::Empty, - Some(parsed.clone()), - pp.clone(), - list_content.to_vec(), - )) - } else if this_char == b')' { - let mut list_copy = list_content.to_vec(); - match list_copy.pop() { - Some(v) => { - let new_tail = make_cons(v, parsed.clone()); - if list_copy.is_empty() { - emit(Rc::new(new_tail), SExpParseState::Empty) - } else { - let mut result_list = new_tail; - for item in list_copy.iter().rev() { - result_list = make_cons(item.clone(), Rc::new(result_list)); + + // if we're not in a comment and have already found a parsed second word for this dot expression + SExpParseState::TermList(srcloc, Some(parsed), pp, list_content) => { + match (this_char as char, pp.borrow()) { + (')', SExpParseState::Empty) => { + // if we see a `)` then we're ready to close this list + let mut list_copy = list_content.to_vec(); + match list_copy.pop() { + Some(v) => { + let new_tail = make_cons(v, Rc::clone(parsed)); + if list_copy.is_empty() { + emit(Rc::new(new_tail), SExpParseState::Empty) + } else { + let mut result_list = new_tail; + for item in list_copy.iter().rev() { + result_list = make_cons(Rc::clone(item), Rc::new(result_list)); + } + emit(Rc::new(result_list), SExpParseState::Empty) + // emit the resultant list } - emit(Rc::new(result_list), SExpParseState::Empty) } + None => error(loc, "Dot as first element of list?"), } - None => error(loc, "Dot as first element of list?"), } - } else if this_char == b';' { - resume(SExpParseState::TermList( - pl.clone(), - TermListCommentState::InComment, - Some(parsed.clone()), - pp.clone(), - list_content.clone(), - )) - } else { - error( - pl.clone(), - &format!("unexpected character {}", this_char as char), - ) + _ => match parse_sexp_step(loc.clone(), pp.borrow(), this_char) { + // nothing should be emitted as we're a term list with an object found + SExpParseResult::Emit(_, _current_state) => { + error(loc, "found object during termlist") + } + // resume means it didn't finish parsing yet, so store inner state and keep going + SExpParseResult::Resume(current_state) => { + match current_state { + SExpParseState::Empty | SExpParseState::CommentText => { + resume(SExpParseState::TermList( + srcloc.ext(&loc), + Some(parsed.clone()), + Rc::new(current_state), // store our partial inner parsestate in pp + list_content.to_vec(), + )) + } + _ => error(loc, "Illegal state during term list."), + } + } + SExpParseResult::Error(l, e) => SExpParseResult::Error(l, e), + }, } } - SExpParseState::TermList(pl, TermListCommentState::Empty, None, pp, list_content) => { + // we are passing a dot-expression (x . y) and not in a comment and don't have an object already discovered + SExpParseState::TermList(srcloc, None, pp, list_content) => { + // pp is the inner parsestate inside the dot-expressions match (this_char as char, pp.borrow()) { + //match based on current character and inner state ('.', SExpParseState::Empty) => { + // if we aren't in a word and we see another dot that's illegal error(loc, "Multiple dots in list notation are illegal") } (')', SExpParseState::Empty) => { + // attempt to close the list if list_content.len() == 1 { - emit(list_content[0].clone(), SExpParseState::Empty) + emit(Rc::clone(&list_content[0]), SExpParseState::Empty) } else { emit( - Rc::new(enlist(pl.ext(&loc), list_content)), + Rc::new(enlist(srcloc.ext(&loc), list_content)), SExpParseState::Empty, ) } @@ -736,7 +750,7 @@ fn parse_sexp_step(loc: Srcloc, p: &SExpParseState, this_char: u8) -> SExpParseR } else { let mut result_list = new_tail; for item in list_copy.iter().rev() { - result_list = make_cons(item.clone(), Rc::new(result_list)); + result_list = make_cons(Rc::clone(item), Rc::new(result_list)); } emit(Rc::new(result_list), SExpParseState::Empty) } @@ -744,19 +758,21 @@ fn parse_sexp_step(loc: Srcloc, p: &SExpParseState, this_char: u8) -> SExpParseR None => error(loc, "Dot as first element of list?"), } } + // if we see anything other than ')' or '.' parse it as if we were in empty state (_, _) => match parse_sexp_step(loc.clone(), pp.borrow(), this_char) { - SExpParseResult::Emit(o, _p) => resume(SExpParseState::TermList( - loc, - TermListCommentState::Empty, - Some(o), - pp.clone(), - list_content.clone(), - )), - SExpParseResult::Resume(p) => resume(SExpParseState::TermList( - pl.ext(&loc), - TermListCommentState::Empty, + SExpParseResult::Emit(parsed_object, _current_state) => { + resume(SExpParseState::TermList( + loc, + Some(parsed_object), // assert parsed_object is not None and then store it in parsed_list + Rc::new(SExpParseState::Empty), + list_content.clone(), + )) + } + // resume means it didn't finish parsing yet, so store inner state and keep going + SExpParseResult::Resume(current_state) => resume(SExpParseState::TermList( + srcloc.ext(&loc), None, - Rc::new(p), + Rc::new(current_state), // store our partial inner parsestate in pp list_content.to_vec(), )), SExpParseResult::Error(l, e) => SExpParseResult::Error(l, e), @@ -766,51 +782,85 @@ fn parse_sexp_step(loc: Srcloc, p: &SExpParseState, this_char: u8) -> SExpParseR } } -fn parse_sexp_inner( - mut start: Srcloc, - mut parse_state: SExpParseState, - s: I, -) -> Result>, (Srcloc, String)> -where - I: Iterator, -{ - let mut res = Vec::new(); +#[derive(Debug, PartialEq, Eq)] +pub struct ParsePartialResult { + // we support compiling multiple things at once, keep these in a Vec + // at the moment this will almost certainly only return 1 thing + res: Vec>, + srcloc: Srcloc, + parse_state: SExpParseState, +} - for this_char in s { - let next_location = start.clone().advance(this_char); +impl ParsePartialResult { + pub fn new(srcloc: Srcloc) -> Self { + ParsePartialResult { + res: Default::default(), + srcloc, + parse_state: SExpParseState::Empty, + } + } + pub fn push(&mut self, this_char: u8) -> Result<(), (Srcloc, String)> { + let next_location = self.srcloc.clone().advance(this_char); - match parse_sexp_step(start.clone(), &parse_state, this_char) { + // call parse_sexp_step for current character + // it will return a ParseResult which contains the new ParseState + match parse_sexp_step(self.srcloc.clone(), &self.parse_state, this_char) { + // catch error and propagate itupwards SExpParseResult::Error(l, e) => { return Err((l, e)); } + // Keep parsing SExpParseResult::Resume(new_parse_state) => { - start = next_location; - parse_state = new_parse_state; + self.srcloc = next_location; + self.parse_state = new_parse_state; } + // End of list (top level compile object), but not necessarily end of file SExpParseResult::Emit(o, new_parse_state) => { - start = next_location; - parse_state = new_parse_state; - res.push(o); + self.srcloc = next_location; + self.parse_state = new_parse_state; + self.res.push(o); } } + + Ok(()) } - match parse_state { - SExpParseState::Empty => Ok(res), - SExpParseState::Bareword(l, t) => Ok(vec![Rc::new(make_atom(l, t))]), - SExpParseState::CommentText(_, _) => Ok(res), - SExpParseState::QuotedText(l, _, _) => Err((l, "unterminated quoted string".to_string())), - SExpParseState::QuotedEscaped(l, _, _) => { - Err((l, "unterminated quoted string with escape".to_string())) + pub fn finalize(self) -> Result>, (Srcloc, String)> { + // depending on the state when we finished return Ok or Err enums + match self.parse_state { + SExpParseState::Empty => Ok(self.res), + SExpParseState::Bareword(l, t) => Ok(vec![Rc::new(make_atom(l, t))]), + SExpParseState::CommentText => Ok(self.res), + SExpParseState::QuotedText(l, _, _) => { + Err((l, "unterminated quoted string".to_string())) + } + SExpParseState::QuotedEscaped(l, _, _) => { + Err((l, "unterminated quoted string with escape".to_string())) + } + SExpParseState::OpenList(l) => Err((l, "Unterminated list (empty)".to_string())), + SExpParseState::ParsingList(l, _, _) => Err((l, "Unterminated mid list".to_string())), + SExpParseState::TermList(l, _, _, _) => Err((l, "Unterminated tail list".to_string())), } - SExpParseState::OpenList(l) => Err((l, "Unterminated list (empty)".to_string())), - SExpParseState::ParsingList(l, _, _) => Err((l, "Unterminated mid list".to_string())), - SExpParseState::TermList(l, _, _, _, _) => Err((l, "Unterminated tail list".to_string())), } } +fn parse_sexp_inner(start: Srcloc, s: I) -> Result>, (Srcloc, String)> +where + I: Iterator, +{ + let mut partial_result = ParsePartialResult::new(start); + + // Loop through all the characters + for this_char in s { + partial_result.push(this_char)?; + } + + partial_result.finalize() +} + /// /// Entrypoint for parsing chialisp input. +/// Called from compiler.rs /// /// This produces Rc, where SExp is described above. /// @@ -818,5 +868,115 @@ pub fn parse_sexp(start: Srcloc, input: I) -> Result>, (Srcloc, where I: Iterator, { - parse_sexp_inner(start, SExpParseState::Empty, input) + parse_sexp_inner(start, input) +} + +#[cfg(test)] +fn check_parser_for_intermediate_result( + parser: &mut ParsePartialResult, + s: &str, + desired: SExpParseState, +) { + for this_char in s.bytes() { + parser.push(this_char).unwrap(); + } + assert_eq!(parser.parse_state, desired); +} + +#[cfg(test)] +fn srcloc_range(name: &Rc, start: usize, end: usize) -> Srcloc { + Srcloc::new(name.clone(), 1, start).ext(&Srcloc::new(name.clone(), 1, end)) +} + +#[test] +fn test_tricky_parser_tail_01() { + let testname = Rc::new("*test*".to_string()); + let loc = Srcloc::start(&testname); + let mut parser = ParsePartialResult::new(loc.clone()); + check_parser_for_intermediate_result( + &mut parser, + "(1 . x", + SExpParseState::TermList( + srcloc_range(&testname, 1, 6), + None, + Rc::new(SExpParseState::Bareword( + srcloc_range(&testname, 6, 6), + vec![b'x'], + )), + vec![Rc::new(SExp::Integer( + srcloc_range(&testname, 2, 2), + bi_one(), + ))], + ), + ); + + parser.push(b')').expect("should complete"); + assert_eq!( + parser.finalize(), + Ok(vec![Rc::new(SExp::Cons( + srcloc_range(&testname, 1, 7), + Rc::new(SExp::Integer(srcloc_range(&testname, 2, 2), bi_one())), + Rc::new(SExp::Atom(srcloc_range(&testname, 6, 7), b"x".to_vec())) + ))]) + ); +} + +#[test] +fn test_tricky_parser_tail_02() { + let testname = Rc::new("*test*".to_string()); + let loc = Srcloc::start(&testname); + let mut parser = ParsePartialResult::new(loc.clone()); + check_parser_for_intermediate_result( + &mut parser, + "(1 . ()", + SExpParseState::TermList( + srcloc_range(&testname, 7, 7), + Some(Rc::new(SExp::Nil(srcloc_range(&testname, 6, 7)))), + Rc::new(SExpParseState::Empty), + vec![Rc::new(SExp::Integer( + srcloc_range(&testname, 2, 2), + bi_one(), + ))], + ), + ); + + parser.push(b')').expect("should complete"); + assert_eq!( + parser.finalize(), + Ok(vec![Rc::new(SExp::Cons( + srcloc_range(&testname, 1, 7), + Rc::new(SExp::Integer(srcloc_range(&testname, 2, 2), bi_one())), + Rc::new(SExp::Nil(srcloc_range(&testname, 6, 7))) + ))]) + ); +} + +#[test] +fn test_tricky_parser_tail_03() { + let testname = Rc::new("*test*".to_string()); + let loc = Srcloc::start(&testname); + let mut parser = ParsePartialResult::new(loc.clone()); + check_parser_for_intermediate_result( + &mut parser, + "(1 . () ;; Test\n", + SExpParseState::TermList( + srcloc_range(&testname, 7, 16), + Some(Rc::new(SExp::Nil(srcloc_range(&testname, 6, 7)))), + Rc::new(SExpParseState::Empty), + vec![Rc::new(SExp::Integer( + srcloc_range(&testname, 2, 2), + bi_one(), + ))], + ), + ); + + parser.push(b')').expect("should complete"); + assert_eq!( + parser.finalize(), + Ok(vec![Rc::new(SExp::Cons( + srcloc_range(&testname, 1, 7), + Rc::new(SExp::Integer(srcloc_range(&testname, 2, 2), bi_one())), + Rc::new(SExp::Nil(srcloc_range(&testname, 6, 7))) + ))]) + ); } diff --git a/src/tests/classic/bls.rs b/src/tests/classic/bls.rs new file mode 100644 index 000000000..e5ffffcc3 --- /dev/null +++ b/src/tests/classic/bls.rs @@ -0,0 +1,123 @@ +use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve}; +use bls12_381::{G1Affine, G1Projective}; + +use crate::classic::clvm::__type_compatibility__::{Bytes, BytesFromType}; +use crate::tests::classic::run::{do_basic_brun, do_basic_run}; + +const MSG1: &[u8] = &[ + 0x97, 0x90, 0x63, 0x5d, 0xe8, 0x74, 0x0e, 0x9a, 0x6a, 0x6b, 0x15, 0xfb, 0x6b, 0x72, 0xf3, 0xa1, + 0x6a, 0xfa, 0x09, 0x73, 0xd9, 0x71, 0x97, 0x9b, 0x6b, 0xa5, 0x47, 0x61, 0xd6, 0xe2, 0x50, 0x2c, + 0x50, 0xdb, 0x76, 0xf4, 0xd2, 0x61, 0x43, 0xf0, 0x54, 0x59, 0xa4, 0x2c, 0xfd, 0x52, 0x0d, 0x44, +]; + +fn bls_map_to_g1(msg: &[u8]) -> Vec { + let dst: &[u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_AUG_"; + let point = >>::hash_to_curve(msg, dst); + let expected_output: [u8; 48] = G1Affine::from(point).to_compressed(); + expected_output.to_vec() +} + +#[test] +fn test_using_bls_operators_0() { + let expected_output = bls_map_to_g1(&MSG1); + // Run a program which uses the map_to_g1 operator and check the output. + let result = do_basic_run(&vec![ + "run".to_string(), + "resources/tests/bls/classic-bls-op-test-1.clsp".to_string(), + ]) + .trim() + .to_string(); + let hex = Bytes::new(Some(BytesFromType::Raw(expected_output.to_vec()))).hex(); + assert_eq!(result, format!("(q . 0x{hex})")); +} + +#[test] +fn test_using_bls_verify_signature_good_msg_classic() { + let right_msg = "(0x0102030405)"; + + let prog = do_basic_run(&vec![ + "run".to_string(), + "resources/tests/bls/classic-bls-verify-signature.clsp".to_string(), + ]) + .trim() + .to_string(); + let result = do_basic_brun(&vec!["brun".to_string(), prog, right_msg.to_string()]) + .trim() + .to_string(); + assert_eq!(result, format!("()")); +} + +#[test] +fn test_using_bls_verify_signature_bad_msg_classic() { + let wrong_msg = "(0x0102030415)"; + + let prog = do_basic_run(&vec![ + "run".to_string(), + "resources/tests/bls/classic-bls-verify-signature.clsp".to_string(), + ]) + .trim() + .to_string(); + let result = do_basic_brun(&vec!["brun".to_string(), prog, wrong_msg.to_string()]) + .trim() + .to_string(); + assert!(result.starts_with("FAIL")); +} +#[test] +fn test_using_bls_verify_signature_good_msg() { + let right_msg = "(0x0102030405)"; + + let prog = do_basic_run(&vec![ + "run".to_string(), + "resources/tests/bls/modern-bls-verify-signature.clsp".to_string(), + ]) + .trim() + .to_string(); + let result = do_basic_brun(&vec!["brun".to_string(), prog, right_msg.to_string()]) + .trim() + .to_string(); + assert_eq!(result, format!("()")); +} + +#[test] +fn test_using_bls_verify_signature_bad_msg() { + let wrong_msg = "(0x0102030415)"; + + let prog = do_basic_run(&vec![ + "run".to_string(), + "resources/tests/bls/modern-bls-verify-signature.clsp".to_string(), + ]) + .trim() + .to_string(); + let result = do_basic_brun(&vec!["brun".to_string(), prog, wrong_msg.to_string()]) + .trim() + .to_string(); + assert!(result.starts_with("FAIL")); +} + +#[test] +fn test_coinid_in_softfork_bad() { + let prog = do_basic_run(&vec![ + "run".to_string(), + "resources/tests/bls/coinid-fail.clsp".to_string(), + ]) + .trim() + .to_string(); + let result = do_basic_brun(&vec!["brun".to_string(), prog, "()".to_string()]) + .trim() + .to_string(); + assert!(result.starts_with("FAIL")); +} + +#[test] +fn test_coinid_in_softfork_good() { + let prog = do_basic_run(&vec![ + "run".to_string(), + "resources/tests/bls/coinid-good.clsp".to_string(), + ]) + .trim() + .to_string(); + let result = do_basic_brun(&vec!["brun".to_string(), prog, "()".to_string()]) + .trim() + .to_string(); + assert!(result.starts_with("()")); +} diff --git a/src/tests/classic/mod.rs b/src/tests/classic/mod.rs index 1a83a2eda..3f13d1767 100644 --- a/src/tests/classic/mod.rs +++ b/src/tests/classic/mod.rs @@ -1,3 +1,4 @@ +mod bls; mod clvmc; mod embed; mod optimize; diff --git a/src/tests/classic/optimize.rs b/src/tests/classic/optimize.rs index 48608956e..b424ce1db 100644 --- a/src/tests/classic/optimize.rs +++ b/src/tests/classic/optimize.rs @@ -19,7 +19,7 @@ fn test_cons_q_a(src: String) -> String { let assembled = assemble_from_ir(&mut allocator, Rc::new(input_ir)).unwrap(); let runner = run_program_for_search_paths("*test*", &vec![".".to_string()], false); let optimized = cons_q_a_optimizer(&mut allocator, &memo, assembled, runner.clone()).unwrap(); - disassemble(&mut allocator, optimized) + disassemble(&mut allocator, optimized, Some(0)) } fn test_children_optimizer(src: String) -> String { @@ -29,7 +29,7 @@ fn test_children_optimizer(src: String) -> String { let assembled = assemble_from_ir(&mut allocator, Rc::new(input_ir)).unwrap(); let runner = run_program_for_search_paths("*test*", &vec![".".to_string()], false); let optimized = children_optimizer(&mut allocator, &memo, assembled, runner.clone()).unwrap(); - disassemble(&mut allocator, optimized) + disassemble(&mut allocator, optimized, Some(0)) } fn test_constant_optimizer(src: String) -> String { @@ -40,7 +40,7 @@ fn test_constant_optimizer(src: String) -> String { let runner = run_program_for_search_paths("*test*", &vec![".".to_string()], false); let optimized = constant_optimizer(&mut allocator, &memo, assembled, 0, runner.clone()).unwrap(); - disassemble(&mut allocator, optimized) + disassemble(&mut allocator, optimized, Some(0)) } fn test_optimizer(src: String) -> String { @@ -49,7 +49,7 @@ fn test_optimizer(src: String) -> String { let assembled = assemble_from_ir(&mut allocator, Rc::new(input_ir)).unwrap(); let runner = run_program_for_search_paths("*test*", &vec![".".to_string()], false); let optimized = optimize_sexp(&mut allocator, assembled, runner.clone()).unwrap(); - disassemble(&mut allocator, optimized) + disassemble(&mut allocator, optimized, Some(0)) } fn test_sub_args(src: String) -> String { @@ -59,7 +59,7 @@ fn test_sub_args(src: String) -> String { match allocator.sexp(assembled) { SExp::Pair(a, b) => { let optimized = sub_args(&mut allocator, a, b).unwrap(); - disassemble(&mut allocator, optimized) + disassemble(&mut allocator, optimized, Some(0)) } _ => { panic!("assembled a list got an atom"); diff --git a/src/tests/classic/run.rs b/src/tests/classic/run.rs index 0255e19fd..ec2af2fa9 100644 --- a/src/tests/classic/run.rs +++ b/src/tests/classic/run.rs @@ -758,7 +758,7 @@ fn compute_hash_of_program(disk_file: &str) -> String { let hexed = OpcConversion {} .invoke(&mut allocator, &want_program_repr) .unwrap(); - let sexp = OpdConversion {} + let sexp = OpdConversion { op_version: None } .invoke(&mut allocator, &hexed.rest()) .unwrap(); format!("0x{}", sha256tree(&mut allocator, *sexp.first()).hex()) @@ -1026,7 +1026,7 @@ fn test_check_tricky_arg_path_random() { Rc::new(sexp::SExp::Atom(random_tree.loc(), k.clone())), ) .unwrap(); - let disassembled = disassemble(&mut allocator, converted); + let disassembled = disassemble(&mut allocator, converted, Some(0)); assert_eq!(disassembled, res); } } @@ -1132,6 +1132,60 @@ fn test_modern_sets_source_file_in_symbols() { ); } +#[test] +fn test_assign_lambda_code_generation() { + let tname = "test_assign_lambda_code_generation.sym".to_string(); + do_basic_run(&vec![ + "run".to_string(), + "--extra-syms".to_string(), + "--symbol-output-file".to_string(), + tname.clone(), + "(mod (A) (include *standard-cl-21*) (defun F (X) (+ X 1)) (assign-lambda X (F A) X))" + .to_string(), + ]); + let read_in_file = fs::read_to_string(&tname).expect("should have dropped symbols"); + fs::remove_file(&tname).expect("should have existed"); + let decoded_symbol_file: HashMap = + serde_json::from_str(&read_in_file).expect("should decode"); + let found_wanted_symbols: Vec = decoded_symbol_file + .iter() + .filter(|(_, v)| *v == "F" || v.starts_with("letbinding")) + .map(|(k, _)| k.clone()) + .collect(); + assert_eq!(found_wanted_symbols.len(), 2); + // We should have these two functions. + assert!(found_wanted_symbols + .contains(&"ccd5be506752cebf01f9930b4c108fe18058c65e1ab57a72ca0a00d9788c7ca6".to_string())); + assert!(found_wanted_symbols + .contains(&"0a5af5ae61fae2e53cb309d4d9c2c64baf0261824823008b9cf2b21b09221e44".to_string())); +} + +#[test] +fn test_assign_lambda_code_generation_normally_inlines() { + let tname = "test_assign_inline_code_generation.sym".to_string(); + do_basic_run(&vec![ + "run".to_string(), + "--extra-syms".to_string(), + "--symbol-output-file".to_string(), + tname.clone(), + "(mod (A) (include *standard-cl-21*) (defun F (X) (+ X 1)) (assign-inline X (F A) X))" + .to_string(), + ]); + let read_in_file = fs::read_to_string(&tname).expect("should have dropped symbols"); + fs::remove_file(&tname).expect("should have existed"); + let decoded_symbol_file: HashMap = + serde_json::from_str(&read_in_file).expect("should decode"); + let found_wanted_symbols: Vec = decoded_symbol_file + .iter() + .filter(|(_, v)| *v == "F" || v.starts_with("letbinding")) + .map(|(k, _)| k.clone()) + .collect(); + assert_eq!(found_wanted_symbols.len(), 1); + // We should have these two functions. + assert!(found_wanted_symbols + .contains(&"ccd5be506752cebf01f9930b4c108fe18058c65e1ab57a72ca0a00d9788c7ca6".to_string())); +} + #[test] fn test_cost_reporting_0() { let program = "(2 (1 2 6 (4 2 (4 (1 . 1) ()))) (4 (1 (2 (1 2 (3 (7 5) (1 2 (1 11 (1 . 2) (2 4 (4 2 (4 (5 5) ()))) (2 4 (4 2 (4 (6 5) ())))) 1) (1 2 (1 11 (1 . 1) 5) 1)) 1) 1) 2 (1 16 5 (1 . 50565442356047746631413349885570059132562040184787699607120092457326103992436)) 1) 1))"; @@ -1148,3 +1202,269 @@ fn test_cost_reporting_0() { "cost = 1978\n0x6fcb06b1fe29d132bb37f3a21b86d7cf03d636bf6230aa206486bef5e68f9875" ); } + +#[test] +fn test_assign_fancy_final_dot_rest() { + let result_prog = do_basic_run(&vec![ + "run".to_string(), + "-i".to_string(), + "resources/tests/chia-gaming".to_string(), + "resources/tests/chia-gaming/test-last.clsp".to_string(), + ]); + let result = do_basic_brun(&vec!["brun".to_string(), result_prog, "()".to_string()]) + .trim() + .to_string(); + assert_eq!(result, "101"); +} + +#[test] +fn test_g1_map_op_modern() { + let program = "(mod (S) (include *standard-cl-21*) (g1_map S \"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_AUG_\"))"; + let compiled = do_basic_run(&vec!["run".to_string(), program.to_string()]); + let output = do_basic_brun(&vec![ + "brun".to_string(), + compiled, + "(abcdef0123456789)".to_string(), + ]) + .trim() + .to_string(); + assert_eq!( + output, + "0x88e7302bf1fa8fcdecfb96f6b81475c3564d3bcaf552ccb338b1c48b9ba18ab7195c5067fe94fb216478188c0a3bef4a" + ); +} + +#[test] +fn test_g1_map_op_classic() { + let program = "(mod (S) (g1_map S \"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_AUG_\"))"; + let compiled = do_basic_run(&vec!["run".to_string(), program.to_string()]); + let output = do_basic_brun(&vec![ + "brun".to_string(), + compiled, + "(abcdef0123456789)".to_string(), + ]) + .trim() + .to_string(); + assert_eq!( + output, + "0x88e7302bf1fa8fcdecfb96f6b81475c3564d3bcaf552ccb338b1c48b9ba18ab7195c5067fe94fb216478188c0a3bef4a" + ); +} + +#[test] +fn test_g2_map_op_modern() { + let program = "(mod (S) (include *standard-cl-21*) (g2_map S \"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_AUG_\"))"; + let compiled = do_basic_run(&vec!["run".to_string(), program.to_string()]); + let output = do_basic_brun(&vec![ + "brun".to_string(), + compiled, + "(0x21473dab7ad0136f7488128d44247b04fa58a9c6b4fab6ef4d)".to_string(), + ]) + .trim() + .to_string(); + assert_eq!( + output, + "0x879584f6c205b4492abca2be331fc2875596b08c7fbd958fb8d5e725a479d1794b85add1266fb5d410de5c416ce12305166b1c3e2e5d5ae2720a058169b057520d8f2a315f6097c774f659ce5619a070e1cbc8212fb460758e459498d0e598d6" + ); +} + +#[test] +fn test_g2_map_op_classic() { + let program = "(mod (S) (g2_map S \"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_AUG_\"))"; + let compiled = do_basic_run(&vec!["run".to_string(), program.to_string()]); + let output = do_basic_brun(&vec![ + "brun".to_string(), + compiled, + "(0x21473dab7ad0136f7488128d44247b04fa58a9c6b4fab6ef4d)".to_string(), + ]) + .trim() + .to_string(); + assert_eq!( + output, + "0x879584f6c205b4492abca2be331fc2875596b08c7fbd958fb8d5e725a479d1794b85add1266fb5d410de5c416ce12305166b1c3e2e5d5ae2720a058169b057520d8f2a315f6097c774f659ce5619a070e1cbc8212fb460758e459498d0e598d6" + ); +} + +#[test] +fn test_secp256k1_verify_modern_succeed() { + let program = "(mod (S) (include *standard-cl-21*) (secp256k1_verify S 0x85932e4d075615be881398cc765f9f78204033f0ef5f832ac37e732f5f0cbda2 0x481477e62a1d02268127ae89cc58929e09ad5d30229721965ae35965d098a5f630205a7e69f4cb8084f16c7407ed7312994ffbf87ba5eb1aee16682dd324943e))"; + let compiled = do_basic_run(&vec!["run".to_string(), program.to_string()]); + let output = do_basic_brun(&vec![ + "brun".to_string(), + compiled, + "(0x02390b19842e100324163334b16947f66125b76d4fa4a11b9ccdde9b7398e64076)".to_string(), + ]) + .trim() + .to_string(); + assert_eq!(output, "()"); +} + +#[test] +fn test_secp256k1_verify_modern_fail() { + let program = "(mod (S) (include *standard-cl-21*) (secp256k1_verify S 0x935d863e2d28d8e5d399ea8af7393ef11fdffc7d862dcc6b5217a8ef15fb5442 0xbbf0712cc0a283a842011c19682629a5381c5f7ead576defcf12a9a19378e23b087cd0be730dbe78722dcfc81543fca17a30e41070ca2e5b3ae77ccec2cca935))"; + let compiled = do_basic_run(&vec!["run".to_string(), program.to_string()]); + let output = do_basic_brun(&vec![ + "brun".to_string(), + compiled, + "(0x0215043e969dcf616fabe8e8d6b61ddcf6e274c5b04fce957b086dbeb7e899ac63)".to_string(), + ]) + .trim() + .to_string(); + assert!(output.starts_with("FAIL: secp256k1_verify failed")); +} + +#[test] +fn test_secp256k1_verify_classic_succeed() { + let program = "(mod (S) (secp256k1_verify S 0x85932e4d075615be881398cc765f9f78204033f0ef5f832ac37e732f5f0cbda2 0x481477e62a1d02268127ae89cc58929e09ad5d30229721965ae35965d098a5f630205a7e69f4cb8084f16c7407ed7312994ffbf87ba5eb1aee16682dd324943e))"; + let compiled = do_basic_run(&vec!["run".to_string(), program.to_string()]); + let output = do_basic_brun(&vec![ + "brun".to_string(), + compiled, + "(0x02390b19842e100324163334b16947f66125b76d4fa4a11b9ccdde9b7398e64076)".to_string(), + ]) + .trim() + .to_string(); + assert_eq!(output, "()"); +} + +#[test] +fn test_secp256k1_verify_classic_fail() { + let program = "(mod (S) (secp256k1_verify S 0x935d863e2d28d8e5d399ea8af7393ef11fdffc7d862dcc6b5217a8ef15fb5442 0xbbf0712cc0a283a842011c19682629a5381c5f7ead576defcf12a9a19378e23b087cd0be730dbe78722dcfc81543fca17a30e41070ca2e5b3ae77ccec2cca935))"; + let compiled = do_basic_run(&vec!["run".to_string(), program.to_string()]); + let output = do_basic_brun(&vec![ + "brun".to_string(), + compiled, + "(0x0215043e969dcf616fabe8e8d6b61ddcf6e274c5b04fce957b086dbeb7e899ac63)".to_string(), + ]) + .trim() + .to_string(); + assert!(output.starts_with("FAIL: secp256k1_verify failed")); +} + +#[test] +fn test_secp256k1_verify_modern_int_succeed() { + // Ensure that even if translated to integer (for example via classic unhygenic macro invocation), this works. + let program = "(mod (S) (if S (332799744 S 0x85932e4d075615be881398cc765f9f78204033f0ef5f832ac37e732f5f0cbda2 0x481477e62a1d02268127ae89cc58929e09ad5d30229721965ae35965d098a5f630205a7e69f4cb8084f16c7407ed7312994ffbf87ba5eb1aee16682dd324943e) \"empty-secp\"))"; + let compiled = do_basic_run(&vec!["run".to_string(), program.to_string()]); + let output = do_basic_brun(&vec![ + "brun".to_string(), + compiled, + "(0x02390b19842e100324163334b16947f66125b76d4fa4a11b9ccdde9b7398e64076)".to_string(), + ]) + .trim() + .to_string(); + assert_eq!(output, "()"); +} + +#[test] +fn test_secp256k1_verify_modern_int_fail() { + let program = "(mod (S) (include *standard-cl-21*) (if S (332799744 S 0x935d863e2d28d8e5d399ea8af7393ef11fdffc7d862dcc6b5217a8ef15fb5442 0xbbf0712cc0a283a842011c19682629a5381c5f7ead576defcf12a9a19378e23b087cd0be730dbe78722dcfc81543fca17a30e41070ca2e5b3ae77ccec2cca935) \"empty-secp\"))"; + let compiled = do_basic_run(&vec!["run".to_string(), program.to_string()]); + let output = do_basic_brun(&vec![ + "brun".to_string(), + compiled, + "(0x0215043e969dcf616fabe8e8d6b61ddcf6e274c5b04fce957b086dbeb7e899ac63)".to_string(), + ]) + .trim() + .to_string(); + assert!(output.starts_with("FAIL: secp256k1_verify failed")); +} + +#[test] +fn test_secp256r1_verify_modern_succeed() { + let program = "(mod (S) (include *standard-cl-21*) (secp256r1_verify S 0x85932e4d075615be881398cc765f9f78204033f0ef5f832ac37e732f5f0cbda2 0xeae2f488080919bd0a7069c24cdd9c6ce2db423861b0c9d4236cdadbd0005f6d8f3709e6eb19249fd9c8bea664aba35218e67ea4b0f2239488dc3147f336e1e6))"; + let compiled = do_basic_run(&vec!["run".to_string(), program.to_string()]); + let output = do_basic_brun(&vec![ + "brun".to_string(), + compiled, + "(0x033e1a1b2ccbc35883c60fdfc3f4a02175096ade6271fe85517ca5772594bbd0dc)".to_string(), + ]) + .trim() + .to_string(); + assert_eq!(output, "()"); +} + +#[test] +fn test_secp256r1_verify_modern_fail() { + let program = "(mod (S) (include *standard-cl-21*) (secp256r1_verify S 0x935d863e2d28d8e5d399ea8af7393ef11fdffc7d862dcc6b5217a8ef15fb5442 0xecef274a7408e6cb0196eac64d2ae32fc54c2537f8a9efd5b75a4e8a53b0b156c64564306f38bade4adceac1073d464e4db3d0332141a7203dfd113ad36e393d))"; + let compiled = do_basic_run(&vec!["run".to_string(), program.to_string()]); + let output = do_basic_brun(&vec![ + "brun".to_string(), + compiled, + "(0x025195db74b1902d53758a62ccd9dd01837aa5ae755e878eb0aeccbe8fe477c543)".to_string(), + ]) + .trim() + .to_string(); + assert!(output.starts_with("FAIL: secp256r1_verify failed")); +} + +#[test] +fn test_secp256r1_verify_classic_succeed() { + let program = "(mod (S) (secp256r1_verify S 0x85932e4d075615be881398cc765f9f78204033f0ef5f832ac37e732f5f0cbda2 0xeae2f488080919bd0a7069c24cdd9c6ce2db423861b0c9d4236cdadbd0005f6d8f3709e6eb19249fd9c8bea664aba35218e67ea4b0f2239488dc3147f336e1e6))"; + let compiled = do_basic_run(&vec!["run".to_string(), program.to_string()]); + let output = do_basic_brun(&vec![ + "brun".to_string(), + compiled, + "(0x033e1a1b2ccbc35883c60fdfc3f4a02175096ade6271fe85517ca5772594bbd0dc)".to_string(), + ]) + .trim() + .to_string(); + assert_eq!(output, "()"); +} + +#[test] +fn test_secp256r1_verify_classic_fail() { + let program = "(mod (S) (secp256r1_verify S 0x935d863e2d28d8e5d399ea8af7393ef11fdffc7d862dcc6b5217a8ef15fb5442 0xecef274a7408e6cb0196eac64d2ae32fc54c2537f8a9efd5b75a4e8a53b0b156c64564306f38bade4adceac1073d464e4db3d0332141a7203dfd113ad36e393d))"; + let compiled = do_basic_run(&vec!["run".to_string(), program.to_string()]); + let output = do_basic_brun(&vec![ + "brun".to_string(), + compiled, + "(0x025195db74b1902d53758a62ccd9dd01837aa5ae755e878eb0aeccbe8fe477c543)".to_string(), + ]) + .trim() + .to_string(); + assert!(output.starts_with("FAIL: secp256r1_verify failed")); +} + +#[test] +fn test_classic_obeys_operator_choice_at_compile_time_no_version() { + let compiled = do_basic_run(&vec![ + "run".to_string(), + "(mod () (coinid (sha256 99) (sha256 99) 1))".to_string(), + ]) + .trim() + .to_string(); + assert_eq!( + compiled, + "(q . 0x97c3f14ced4dfc280611fd8d9b158163e8981b3bce4d1bb6dd0bcc679a2e2455)" + ); +} + +#[test] +fn test_classic_obeys_operator_choice_at_compile_time_version_1() { + let compiled = do_basic_run(&vec![ + "run".to_string(), + "--operators-version".to_string(), + "1".to_string(), + "(mod () (coinid (sha256 99) (sha256 99) 1))".to_string(), + ]) + .trim() + .to_string(); + assert_eq!( + compiled, + "(q . 0x97c3f14ced4dfc280611fd8d9b158163e8981b3bce4d1bb6dd0bcc679a2e2455)" + ); +} + +#[test] +fn test_classic_obeys_operator_choice_at_compile_time_version_0() { + let compiled = do_basic_run(&vec![ + "run".to_string(), + "--operators-version".to_string(), + "0".to_string(), + "(mod () (coinid (sha256 99) (sha256 99) 1))".to_string(), + ]) + .trim() + .to_string(); + assert_eq!(compiled, "FAIL: unimplemented operator 48"); +} diff --git a/src/tests/classic/smoke.rs b/src/tests/classic/smoke.rs index bb4b2e680..98e5187cd 100644 --- a/src/tests/classic/smoke.rs +++ b/src/tests/classic/smoke.rs @@ -48,10 +48,16 @@ fn large_odd_sized_neg_opc() { assert_eq!(result.rest(), "ff8afde1e61f36454dc0000180"); } +fn opd_conversion() -> OpdConversion { + OpdConversion { + op_version: Some(0), + } +} + #[test] fn large_odd_sized_neg_opd() { let mut allocator = Allocator::new(); - let result = OpdConversion {} + let result = opd_conversion() .invoke(&mut allocator, &"ff8afde1e61f36454dc0000180".to_string()) .unwrap(); assert_eq!(result.rest(), "(0xfde1e61f36454dc00001)"); @@ -60,7 +66,7 @@ fn large_odd_sized_neg_opd() { #[test] fn mid_negative_value_opd_m1() { let mut allocator = Allocator::new(); - let result = OpdConversion {} + let result = opd_conversion() .invoke(&mut allocator, &"81ff".to_string()) .unwrap(); assert_eq!(result.rest(), "-1"); @@ -69,7 +75,7 @@ fn mid_negative_value_opd_m1() { #[test] fn mid_negative_value_opd_m2() { let mut allocator = Allocator::new(); - let result = OpdConversion {} + let result = opd_conversion() .invoke(&mut allocator, &"81fe".to_string()) .unwrap(); assert_eq!(result.rest(), "-2"); @@ -78,7 +84,7 @@ fn mid_negative_value_opd_m2() { #[test] fn mid_negative_value_opd_two_bytes() { let mut allocator = Allocator::new(); - let result = OpdConversion {} + let result = opd_conversion() .invoke(&mut allocator, &"82ffff".to_string()) .unwrap(); assert_eq!(result.rest(), "0xffff"); @@ -87,7 +93,7 @@ fn mid_negative_value_opd_two_bytes() { #[test] fn mid_negative_value_opd_three_bytes() { let mut allocator = Allocator::new(); - let result = OpdConversion {} + let result = opd_conversion() .invoke(&mut allocator, &"83ffffff".to_string()) .unwrap(); assert_eq!(result.rest(), "0xffffff"); @@ -96,7 +102,7 @@ fn mid_negative_value_opd_three_bytes() { #[test] fn mid_negative_value_opd_tricky_negative_2() { let mut allocator = Allocator::new(); - let result = OpdConversion {} + let result = opd_conversion() .invoke(&mut allocator, &"82ff00".to_string()) .unwrap(); assert_eq!(result.rest(), "-256"); @@ -105,7 +111,7 @@ fn mid_negative_value_opd_tricky_negative_2() { #[test] fn mid_negative_value_opd_tricky_positive_2() { let mut allocator = Allocator::new(); - let result = OpdConversion {} + let result = opd_conversion() .invoke(&mut allocator, &"8200ff".to_string()) .unwrap(); assert_eq!(result.rest(), "255"); @@ -114,7 +120,7 @@ fn mid_negative_value_opd_tricky_positive_2() { #[test] fn mid_negative_value_opd_tricky_negative_3() { let mut allocator = Allocator::new(); - let result = OpdConversion {} + let result = opd_conversion() .invoke(&mut allocator, &"83ff0000".to_string()) .unwrap(); assert_eq!(result.rest(), "0xff0000"); @@ -123,7 +129,7 @@ fn mid_negative_value_opd_tricky_negative_3() { #[test] fn mid_negative_value_opd_tricky_positive_3() { let mut allocator = Allocator::new(); - let result = OpdConversion {} + let result = opd_conversion() .invoke(&mut allocator, &"8300ffff".to_string()) .unwrap(); assert_eq!(result.rest(), "0x00ffff"); @@ -142,7 +148,7 @@ fn mid_negative_value_bin() { Box::new(SimpleCreateCLVMObject {}), ) .expect("should be able to make nodeptr"); - if let SExp::Atom() = allocator.sexp(atom.1) { + if let SExp::Atom = allocator.sexp(atom.1) { let res_bytes = allocator.atom(atom.1); assert_eq!(res_bytes, &[0xff, 0xff]); } else { @@ -156,7 +162,7 @@ fn mid_negative_value_disassemble() { let nodeptr = allocator .new_atom(&[0xff, 0xff]) .expect("should be able to make an atom"); - assert_eq!(disassemble(&mut allocator, nodeptr), "0xffff"); + assert_eq!(disassemble(&mut allocator, nodeptr, Some(0)), "0xffff"); } #[test] @@ -180,7 +186,7 @@ fn small_test_opc() { #[test] fn large_odd_sized_pos_opd() { let mut allocator = Allocator::new(); - let result = OpdConversion {} + let result = opd_conversion() .invoke(&mut allocator, &"ff8700ffffffffffff80".to_string()) .unwrap(); assert_eq!(result.rest(), "(0x00ffffffffffff)"); @@ -189,7 +195,7 @@ fn large_odd_sized_pos_opd() { #[test] fn basic_opd() { let mut allocator = Allocator::new(); - let result = OpdConversion {} + let result = opd_conversion() .invoke(&mut allocator, &"80".to_string()) .unwrap(); assert_eq!(result.rest(), "()"); @@ -198,7 +204,7 @@ fn basic_opd() { #[test] fn nil_in_list_opd() { let mut allocator = Allocator::new(); - let result = OpdConversion {} + let result = opd_conversion() .invoke(&mut allocator, &"ff8080".to_string()) .unwrap(); assert_eq!(result.rest(), "(())"); @@ -220,7 +226,7 @@ fn big_decode_opd() { }) .unwrap(); - let result = OpdConversion {} + let result = opd_conversion() .invoke(&mut allocator, &expected.first()) .unwrap(); assert_eq!(expected.rest(), result.rest()); @@ -249,7 +255,7 @@ fn compile_program<'a>( let input_sexp = allocator.new_pair(input_program, allocator.null()).unwrap(); let res = runner.run_program(allocator, run_script, input_sexp, None); - return res.map(|x| disassemble(allocator, x.1)); + return res.map(|x| disassemble(allocator, x.1, Some(0))); } #[test] @@ -270,7 +276,7 @@ fn can_run_from_source_nil() { let mut allocator = Allocator::new(); let res = run_from_source(&mut allocator, "()".to_string()); match allocator.sexp(res) { - SExp::Atom() => { + SExp::Atom => { let res_bytes = allocator.atom(res); assert_eq!(res_bytes.len(), 0); } @@ -285,7 +291,7 @@ fn can_echo_quoted_nil() { let mut allocator = Allocator::new(); let res = run_from_source(&mut allocator, "(1)".to_string()); match allocator.sexp(res) { - SExp::Atom() => { + SExp::Atom => { let res_bytes = allocator.atom(res); assert_eq!(res_bytes.len(), 0); } @@ -316,7 +322,7 @@ fn can_echo_quoted_atom() { let mut allocator = Allocator::new(); let res = run_from_source(&mut allocator, "(1 . 3)".to_string()); match allocator.sexp(res) { - SExp::Atom() => { + SExp::Atom => { let res_bytes = allocator.atom(res); assert_eq!(res_bytes.len(), 1); assert_eq!(res_bytes[0], 3); @@ -332,7 +338,7 @@ fn can_do_operations() { let mut allocator = Allocator::new(); let res = run_from_source(&mut allocator, "(16 (1 . 3) (1 . 5))".to_string()); match allocator.sexp(res) { - SExp::Atom() => { + SExp::Atom => { let res_bytes = allocator.atom(res); assert_eq!(res_bytes.len(), 1); assert_eq!(res_bytes[0], 8); @@ -348,7 +354,7 @@ fn can_do_operations_kw() { let mut allocator = Allocator::new(); let res = run_from_source(&mut allocator, "(+ (q . 3) (q . 5))".to_string()); match allocator.sexp(res) { - SExp::Atom() => { + SExp::Atom => { let res_bytes = allocator.atom(res); assert_eq!(res_bytes.len(), 1); assert_eq!(res_bytes[0], 8); @@ -398,7 +404,7 @@ fn basic_opc_quoted_1() { #[test] fn test_simple_opd_conversion() { let mut allocator = Allocator::new(); - let result = OpdConversion {} + let result = opd_conversion() .invoke(&mut allocator, &"ff0183666f6f".to_string()) .unwrap(); assert_eq!(result.rest(), "(q . \"foo\")"); @@ -628,6 +634,8 @@ fn pool_member_innerpuz() { &mut s, &vec![ "run".to_string(), + "--operators-version".to_string(), + "0".to_string(), "-i".to_string(), testpath.into_os_string().into_string().unwrap(), program.to_string(), @@ -705,20 +713,20 @@ fn test_fancy_destructuring_type_language() { // Use first let First::Here(kw) = assert_node_not_error(First::Here(ThisNode::Here).select_nodes(&mut allocator, code)); - assert_eq!(disassemble(&mut allocator, kw), "\"defconst\""); + assert_eq!(disassemble(&mut allocator, kw, Some(0)), "\"defconst\""); // Use second of list let Rest::Here(First::Here(name)) = assert_node_not_error( Rest::Here(First::Here(ThisNode::Here)).select_nodes(&mut allocator, code), ); - assert_eq!(disassemble(&mut allocator, name), "88"); + assert_eq!(disassemble(&mut allocator, name, Some(0)), "88"); let NodeSel::Cons((), NodeSel::Cons(name_by_cons, rest)) = assert_node_not_error( NodeSel::Cons((), NodeSel::Cons(ThisNode::Here, ThisNode::Here)) .select_nodes(&mut allocator, code), ); - assert_eq!(disassemble(&mut allocator, name_by_cons), "88"); - assert_eq!(disassemble(&mut allocator, rest), "((+ 3 1))"); + assert_eq!(disassemble(&mut allocator, name_by_cons, Some(0)), "88"); + assert_eq!(disassemble(&mut allocator, rest, Some(0)), "((+ 3 1))"); } #[test] @@ -803,7 +811,7 @@ fn test_sub_args() { let new_args = assemble(&mut allocator, "(test1 test2)").expect("should assemble"); let result = sub_args(&mut allocator, expr_sexp, new_args).expect("should run"); assert_eq!( - disassemble(&mut allocator, result), + disassemble(&mut allocator, result, None), "(\"body\" (f (\"test1\" \"test2\")) (f (r (\"test1\" \"test2\"))))" ); } diff --git a/src/tests/classic/stage_2.rs b/src/tests/classic/stage_2.rs index 78a019cd6..8bf43064f 100644 --- a/src/tests/classic/stage_2.rs +++ b/src/tests/classic/stage_2.rs @@ -18,6 +18,7 @@ use crate::classic::clvm_tools::stages::stage_2::operators::run_program_for_sear use crate::classic::clvm_tools::stages::stage_2::reader::{process_embed_file, read_file}; use crate::compiler::comptypes::{CompileErr, CompilerOpts, PrimaryCodegen}; +use crate::compiler::dialect::AcceptedDialect; use crate::compiler::sexp::{decode_string, SExp}; use crate::compiler::srcloc::Srcloc; @@ -44,7 +45,7 @@ fn test_expand_macro( symbols_source, ) .unwrap(); - disassemble(allocator, exp_res.1) + disassemble(allocator, exp_res.1, Some(0)) } fn test_inner_expansion( @@ -57,7 +58,7 @@ fn test_inner_expansion( let prog_ir = read_ir(&prog_rest).unwrap(); let prog_source = assemble_from_ir(allocator, Rc::new(prog_ir)).unwrap(); let exp_res = brun(allocator, macro_source, prog_source).unwrap(); - disassemble(allocator, exp_res) + disassemble(allocator, exp_res, Some(0)) } fn test_do_com_prog( @@ -74,7 +75,7 @@ fn test_do_com_prog( let sym_ir = read_ir(&symbol_table_src).unwrap(); let symbol_table = assemble_from_ir(allocator, Rc::new(sym_ir)).unwrap(); let result = do_com_prog(allocator, 849, program, macro_lookup, symbol_table, runner).unwrap(); - disassemble(allocator, result.1) + disassemble(allocator, result.1, Some(0)) } #[test] @@ -151,7 +152,7 @@ fn test_stage_2_quote() { let mut allocator = Allocator::new(); let assembled = assemble(&mut allocator, "(1 2 3)").unwrap(); let quoted = quote(&mut allocator, assembled).unwrap(); - assert_eq!(disassemble(&mut allocator, quoted), "(q 1 2 3)"); + assert_eq!(disassemble(&mut allocator, quoted, Some(0)), "(q 1 2 3)"); } #[test] @@ -161,7 +162,7 @@ fn test_stage_2_evaluate() { let args = assemble(&mut allocator, "(q 9 15)").unwrap(); let to_eval = evaluate(&mut allocator, prog, args).unwrap(); assert_eq!( - disassemble(&mut allocator, to_eval), + disassemble(&mut allocator, to_eval, Some(0)), "(a (q 16 2 3) (q 9 15))" ); } @@ -173,7 +174,7 @@ fn test_stage_2_run() { let macro_lookup_throw = assemble(&mut allocator, "(q 9)").unwrap(); let to_eval = run(&mut allocator, prog, macro_lookup_throw).unwrap(); assert_eq!( - disassemble(&mut allocator, to_eval), + disassemble(&mut allocator, to_eval, Some(0)), "(a (\"com\" (q 16 2 3) (q 1 9)) 1)" ); } @@ -217,8 +218,8 @@ fn test_process_embed_file_as_sexp() { let (name, content) = process_embed_file(&mut allocator, runner, declaration_sexp).expect("should work"); assert_eq!( - disassemble(&mut allocator, want_exp), - disassemble(&mut allocator, content) + disassemble(&mut allocator, want_exp, Some(0)), + disassemble(&mut allocator, content, Some(0)) ); assert_eq!(name, b"test-embed"); } @@ -293,7 +294,7 @@ fn test_process_embed_file_as_hex() { ) .expect("should work"); assert_eq!( - disassemble(&mut allocator, matching_part_of_decl), + disassemble(&mut allocator, matching_part_of_decl, Some(0)), decode_string(outstream.get_value().data()) ); assert_eq!(name, b"test-embed-from-hex"); @@ -319,6 +320,9 @@ impl CompilerOpts for TestCompilerOptsPresentsOwnFiles { fn code_generator(&self) -> Option { None } + fn dialect(&self) -> AcceptedDialect { + AcceptedDialect::default() + } fn in_defun(&self) -> bool { false } @@ -337,12 +341,18 @@ impl CompilerOpts for TestCompilerOptsPresentsOwnFiles { fn start_env(&self) -> Option> { None } + fn disassembly_ver(&self) -> Option { + None + } fn prim_map(&self) -> Rc, Rc>> { Rc::new(HashMap::new()) } fn get_search_paths(&self) -> Vec { vec![".".to_string()] } + fn set_dialect(&self, _dialect: AcceptedDialect) -> Rc { + Rc::new(self.clone()) + } fn set_search_paths(&self, _dirs: &[String]) -> Rc { Rc::new(self.clone()) } @@ -367,6 +377,9 @@ impl CompilerOpts for TestCompilerOptsPresentsOwnFiles { fn set_start_env(&self, _start_env: Option>) -> Rc { Rc::new(self.clone()) } + fn set_disassembly_ver(&self, _ver: Option) -> Rc { + Rc::new(self.clone()) + } fn read_new_file( &self, inc_from: String, @@ -426,7 +439,7 @@ fn test_classic_compiler_with_compiler_opts() { ) .expect("should compile and find the content"); assert_eq!( - disassemble(&mut allocator, result), + disassemble(&mut allocator, result, Some(0)), "(a (q 2 2 (c 2 (c 5 ()))) (c (q 16 5 (q . 1)) 1))" ); // Verify lack of injection diff --git a/src/tests/compiler/assign.rs b/src/tests/compiler/assign.rs new file mode 100644 index 000000000..1f071c9f8 --- /dev/null +++ b/src/tests/compiler/assign.rs @@ -0,0 +1,368 @@ +use crate::tests::classic::run::{do_basic_brun, do_basic_run}; +use std::rc::Rc; + +trait TestCompute { + fn compute(&self, x: i64, y: i64) -> (i64, i64); +} + +#[derive(Default, Clone)] +struct ComposedComputation { + to_run: Vec>, +} +impl TestCompute for ComposedComputation { + fn compute(&self, mut x: i64, mut y: i64) -> (i64, i64) { + for entry in self.to_run.iter() { + let (new_x, new_y) = entry.compute(x, y); + x = new_x; + y = new_y; + } + (x, y) + } +} + +struct XPlus1 {} +impl TestCompute for XPlus1 { + fn compute(&self, x: i64, y: i64) -> (i64, i64) { + (x + 1, y) + } +} + +struct YPlus1 {} +impl TestCompute for YPlus1 { + fn compute(&self, x: i64, y: i64) -> (i64, i64) { + (x, y + 1) + } +} + +struct FFunc {} +impl TestCompute for FFunc { + fn compute(&self, x: i64, y: i64) -> (i64, i64) { + let divxy = x / y; + let modxy = x % y; + (divxy, modxy) + } +} + +struct GFunc {} +impl TestCompute for GFunc { + fn compute(&self, x: i64, _y: i64) -> (i64, i64) { + let v = 1 + (3 * x); + (v, v) + } +} + +struct HFunc {} +impl TestCompute for HFunc { + fn compute(&self, x: i64, y: i64) -> (i64, i64) { + let v = x * y; + (v, v) + } +} + +struct IFunc {} +impl TestCompute for IFunc { + fn compute(&self, x: i64, _y: i64) -> (i64, i64) { + (x - 1, x * 2) + } +} + +struct UseTestFunction { + name: String, + args: usize, + outputs: usize, + text: String, + compute: Rc, +} + +struct AssignTestMatrix { + functions: Vec, +} + +#[test] +#[ignore] +fn test_assign_matrix() { + let inline_kwds = vec!["assign", "assign-lambda", "assign-inline"]; + let matrix = AssignTestMatrix { + functions: vec![ + UseTestFunction { + name: "F".to_string(), + args: 2, + outputs: 2, + text: "(defun F (X Y) (divmod X Y))".to_string(), + compute: Rc::new(FFunc {}), + }, + UseTestFunction { + name: "F-inline".to_string(), + args: 2, + outputs: 2, + text: "(defun-inline F-inline (X Y) (divmod X Y))".to_string(), + compute: Rc::new(FFunc {}), + }, + UseTestFunction { + name: "G".to_string(), + args: 1, + outputs: 1, + text: "(defun G (X) (+ 1 (* 3 X)))".to_string(), + compute: Rc::new(GFunc {}), + }, + UseTestFunction { + name: "G-inline".to_string(), + args: 1, + outputs: 1, + text: "(defun-inline G-inline (X) (+ 1 (* 3 X)))".to_string(), + compute: Rc::new(GFunc {}), + }, + UseTestFunction { + name: "H".to_string(), + args: 2, + outputs: 1, + text: "(defun H (X Y) (* X Y))".to_string(), + compute: Rc::new(HFunc {}), + }, + UseTestFunction { + name: "H-inline".to_string(), + args: 2, + outputs: 1, + text: "(defun-inline H-inline (X Y) (* X Y))".to_string(), + compute: Rc::new(HFunc {}), + }, + UseTestFunction { + name: "I".to_string(), + args: 1, + outputs: 2, + text: "(defun I (X) (c (- X 1) (* X 2)))".to_string(), + compute: Rc::new(IFunc {}), + }, + UseTestFunction { + name: "I-inline".to_string(), + args: 1, + outputs: 2, + text: "(defun-inline I-inline (X) (c (- X 1) (* X 2)))".to_string(), + compute: Rc::new(IFunc {}), + }, + ], + }; + + // The program will take X, Y + + let mut starter_program = vec!["(mod (X Y) (include *standard-cl-21*) ".to_string()]; + for func in matrix.functions.iter() { + starter_program.push(func.text.clone()); + } + + let assert_program_worked = |program: &[String], to_compute: &ComposedComputation| { + let joined = program.join("\n").to_string(); + let compiled = do_basic_run(&vec!["run".to_string(), joined]); + let executed = do_basic_brun(&vec![ + "brun".to_string(), + "-n".to_string(), + compiled, + "(13 19)".to_string(), + ]); + let (ex, ey) = to_compute.compute(13, 19); + let expected = do_basic_brun(&vec![ + "brun".to_string(), + "-n".to_string(), + format!("(1 . ({ex} . {ey}))"), + ]); + assert_eq!(expected, executed); + }; + + let finish_program = |program: &mut Vec, main_or_function: usize, assign_expr: &str| { + if main_or_function == 0 { + program.push(assign_expr.to_string()); + } else { + if main_or_function == 1 { + program.push(format!("(defun Q (X Y) {})", assign_expr)); + } else { + program.push(format!("(defun-inline Q (X Y) {})", assign_expr)); + } + program.push("(Q X Y)".to_string()); + } + program.push(")".to_string()); + }; + + let test_triple_nesting = + |to_compute: &ComposedComputation, main_or_function: usize, assign_expr_list: &[String]| { + let assign_expr = assign_expr_list.join("\n").to_string(); + let mut program = starter_program.clone(); + finish_program(&mut program, main_or_function, &assign_expr); + assert_program_worked(&program, &to_compute); + }; + + let test_third_level_nestings = |to_compute_x: &ComposedComputation, + fourth_var: &str, + y: &UseTestFunction, + main_or_function: usize, + assign_expr: &str, + second_assign: &str, + end_parens: &str| { + // Put on a third level on the stack. + for z in matrix.functions.iter() { + let assign_call_z = if z.args == 1 { + format!("({} V2)", z.name) + } else { + format!("({} V2 (+ 1 {fourth_var}))", z.name) + }; + + let (third_assign, sixth_var) = if z.outputs == 1 { + (format!("V4 {assign_call_z}"), "V4") + } else { + (format!("(V4 . V5) {assign_call_z}"), "V5") + }; + + let final_expr = format!("(c V4 {sixth_var}))"); + let mut to_compute = to_compute_x.clone(); + to_compute.to_run.push(y.compute.clone()); + to_compute.to_run.push(Rc::new(YPlus1 {})); + to_compute.to_run.push(z.compute.clone()); + + // Try with z in the same assign. + test_triple_nesting( + &to_compute, + main_or_function, + &[ + assign_expr.to_string(), + second_assign.to_string(), + third_assign.clone(), + final_expr.clone(), + end_parens.to_string(), + ], + ); + + // Try with z nested. + test_triple_nesting( + &to_compute, + main_or_function, + &[ + assign_expr.to_string(), + second_assign.to_string(), + "(assign".to_string(), + third_assign, + final_expr, + ")".to_string(), + end_parens.to_string(), + ], + ); + } + }; + + for x in matrix.functions.iter() { + let main_expr = if x.args == 1 { + format!("({} X)", x.name) + } else { + format!("({} X Y)", x.name) + }; + + // Depth 1. + for inline_choice in inline_kwds.iter() { + let mut to_compute_x = ComposedComputation::default(); + to_compute_x.to_run.push(x.compute.clone()); + for main_or_function in 0..=2 { + let (assign_expr, second_var) = if x.outputs == 1 { + (format!("({inline_choice} V0 {main_expr}"), "V0") + } else { + (format!("({inline_choice} (V0 . V1) {main_expr}"), "V1") + }; + + { + let mut program = starter_program.clone(); + let finished_assign_expr = + vec![assign_expr.clone(), format!("(c V0 {second_var}))")] + .join("\n") + .to_string(); + finish_program(&mut program, main_or_function, &finished_assign_expr); + assert_program_worked(&program, &to_compute_x); + } + + // Second set of assignments. + for y in matrix.functions.iter() { + // Use both arguments in one more function call. + let second_var = if x.outputs == 1 { "V0" } else { "V1" }; + let assign_call_y = if y.args == 1 { + format!("({} V0)", y.name) + } else { + format!("({} V0 {second_var})", y.name) + }; + let (second_assign, fourth_var) = if y.outputs == 1 { + (format!("V2 {assign_call_y}"), "V2") + } else { + (format!("(V2 . V3) {assign_call_y}"), "V3") + }; + + { + let assign_expr = vec![ + assign_expr.clone(), + second_assign.clone(), + format!("(c V2 {fourth_var}))"), + ] + .join("\n") + .to_string(); + let mut program = starter_program.clone(); + let mut to_compute = to_compute_x.clone(); + to_compute.to_run.push(y.compute.clone()); + finish_program(&mut program, main_or_function, &assign_expr); + assert_program_worked(&program, &to_compute); + } + + test_third_level_nestings( + &to_compute_x, + fourth_var, + y, + main_or_function, + &assign_expr, + &second_assign, + "", + ); + } + + // Nested + for y in matrix.functions.iter() { + for inline_choice_y in inline_kwds.iter() { + // Use both arguments in one more function call. + let second_var = if x.outputs == 1 { "V0" } else { "V1" }; + let assign_call_y = if y.args == 1 { + format!("({} V0)", y.name) + } else { + format!("({} V0 {second_var})", y.name) + }; + let (second_assign, fourth_var) = if y.outputs == 1 { + (format!("({inline_choice_y} V2 {assign_call_y}"), "V2") + } else { + ( + format!("({inline_choice_y} (V2 . V3) {assign_call_y}"), + "V3", + ) + }; + + { + let assign_expr = vec![ + assign_expr.clone(), + second_assign.clone(), + format!("(c V2 {fourth_var})))"), + ] + .join("\n") + .to_string(); + + let mut program = starter_program.clone(); + let mut to_compute = to_compute_x.clone(); + to_compute.to_run.push(y.compute.clone()); + finish_program(&mut program, main_or_function, &assign_expr); + assert_program_worked(&program, &to_compute); + } + + test_third_level_nestings( + &to_compute_x, + fourth_var, + y, + main_or_function, + &assign_expr, + &second_assign, + ")", + ); + } + } + } + } + } +} diff --git a/src/tests/compiler/cldb.rs b/src/tests/compiler/cldb.rs index 90c3da3ec..47da4a32a 100644 --- a/src/tests/compiler/cldb.rs +++ b/src/tests/compiler/cldb.rs @@ -369,3 +369,25 @@ fn test_cldb_explicit_throw() { assert!(watcher.correct_result()); } + +#[test] +fn test_clvm_operator_with_weird_tail() { + let filename = "test-weird-tail.clvm"; + let loc = Srcloc::start(filename); + let program = "(+ (q . 3) (q . 5) . \"\")"; + let parsed = parse_sexp(loc.clone(), program.as_bytes().iter().copied()).expect("should parse"); + let args = Rc::new(SExp::Nil(loc)); + let program_lines = Rc::new(vec![program.to_string()]); + + assert_eq!( + run_clvm_in_cldb( + filename, + program_lines, + parsed[0].clone(), + HashMap::new(), + args, + &mut DoesntWatchCldb {}, + ), + Some("8".to_string()) + ); +} diff --git a/src/tests/compiler/compiler.rs b/src/tests/compiler/compiler.rs index 25a41784c..ea916eb48 100644 --- a/src/tests/compiler/compiler.rs +++ b/src/tests/compiler/compiler.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use std::rc::Rc; use clvm_rs::allocator::Allocator; @@ -7,8 +7,10 @@ use crate::classic::clvm_tools::stages::stage_0::DefaultProgramRunner; use crate::compiler::clvm::run; use crate::compiler::compiler::{compile_file, DefaultCompilerOpts}; use crate::compiler::comptypes::{CompileErr, CompilerOpts}; +use crate::compiler::frontend::{collect_used_names_sexp, frontend}; +use crate::compiler::rename::rename_in_cons; use crate::compiler::runtypes::RunFailure; -use crate::compiler::sexp::{parse_sexp, SExp}; +use crate::compiler::sexp::{decode_string, parse_sexp, SExp}; use crate::compiler::srcloc::Srcloc; const TEST_TIMEOUT: usize = 1000000; @@ -62,6 +64,38 @@ pub fn run_string(content: &String, args: &String) -> Result, CompileEr run_string_maybe_opt(content, args, false) } +// Given some renaming that leaves behind gensym style names with _$_ in them, +// order them and use a locally predictable renaming scheme to give them a final +// test checkable value. +pub fn squash_name_differences(in_sexp: Rc) -> Result, String> { + let found_names_set: BTreeSet<_> = collect_used_names_sexp(in_sexp.clone()) + .into_iter() + .filter(|n| n.contains(&b'$')) + .collect(); + let mut found_names_progression = b'A'; + let mut replacement_map = HashMap::new(); + for found_name in found_names_set.iter() { + if let Some(located_dollar_part) = found_name.iter().position(|x| *x == b'$') { + let mut new_name: Vec = found_name + .iter() + .take(located_dollar_part + 2) + .copied() + .collect(); + new_name.push(found_names_progression); + found_names_progression += 1; + replacement_map.insert(found_name.clone(), new_name); + } else { + return Err(decode_string(&found_name)); + } + } + if replacement_map.len() != found_names_set.len() { + return Err(format!( + "mismatched lengths {replacement_map:?} vs {found_names_set:?}" + )); + } + Ok(rename_in_cons(&replacement_map, in_sexp, false)) +} + /* // Upcoming support for extra optimization (WIP) fn run_string_opt(content: &String, args: &String) -> Result, CompileErr> { run_string_maybe_opt(content, args, true) @@ -1129,8 +1163,7 @@ fn arg_destructure_test_1() { (include *standard-cl-21*) delegated_puzzle_hash -)" - } +)"} .to_string(); let res = run_string(&prog, &"(1 2 3 . 4)".to_string()).unwrap(); assert_eq!(res.to_string(), "4"); @@ -1149,6 +1182,251 @@ fn test_defconstant_tree() { assert_eq!(res.to_string(), "((0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a . 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2) 0x02a12871fee210fb8619291eaea194581cbd2531e4b23759d225f6806923f63222 . 0x02a8d5dd63fba471ebcb1f3e8f7c1e1879b7152a6e7298a91ce119a63400ade7c5)"); } +#[test] +fn test_assign_form_0() { + let prog = indoc! {" +(mod (X) + (assign + X1 (+ X 1) ;; X1 is 14 if X is 13. + X1 + ) + )"} + .to_string(); + let res = run_string(&prog, &"(13)".to_string()).unwrap(); + assert_eq!(res.to_string(), "14"); +} + +#[test] +fn test_assign_form_1() { + let prog = indoc! {" +(mod (X) + (assign + ;; A lot of forms in order. + X1 (+ X 1) ;; 14 + X2 (+ X1 1) ;; 15 + X3 (+ X2 1) ;; 16 + Y0 (+ X3 1) ;; 17 + X4 (+ X3 1) ;; 17 + X5 (+ Y0 1) ;; 18 + Y1 (+ X5 Y0) ;; 35 + Y1 + ) + )"} + .to_string(); + let res = run_string(&prog, &"(13)".to_string()).unwrap(); + assert_eq!(res.to_string(), "35"); +} + +#[test] +fn test_assign_form_cplx_1() { + let prog = indoc! {" +(mod (X) + (defun-inline tup (X Y) (c X Y)) + (assign + (X1 . X2) (tup (+ X 1) (+ X 2)) ;; 14 + X3 (+ X1 1) ;; 15 + X4 (+ X2 1) ;; 16 + (Y0 . X5) (tup (+ X4 1) (+ X4 1)) ;; 17 + X6 (+ Y0 1) ;; 18 + Y1 (+ X6 Y0) ;; 35 + Y1 + ) + )"} + .to_string(); + let res = run_string(&prog, &"(13)".to_string()).unwrap(); + assert_eq!(res.to_string(), "35"); +} + +#[test] +fn test_assign_form_in_let_binding() { + let prog = indoc! {" +(mod (X) + (defun-inline tup (X Y) (c X Y)) + (let + ((FOO + (assign + (X1 . X2) (tup (+ X 1) (+ X 2)) ;; 14 + X3 (+ X1 1) ;; 15 + X4 (+ X2 1) ;; 16 + (Y0 . X5) (tup (+ X4 1) (+ X4 1)) ;; 17 + X6 (+ Y0 1) ;; 18 + Y1 (+ X6 Y0) ;; 35 + Y1 + ))) + FOO + ) + )"} + .to_string(); + let res = run_string(&prog, &"(13)".to_string()).unwrap(); + assert_eq!(res.to_string(), "35"); +} + +#[test] +fn test_assign_form_in_function_argument() { + let prog = indoc! {" +(mod (X) + (defun-inline tup (X Y) (c X Y)) + (defun F (A B) (+ A B)) + (F + (assign + (X1 . X2) (tup (+ X 1) (+ X 2)) ;; 14 + X3 (+ X1 1) ;; 15 + X4 (+ X2 1) ;; 16 + (Y0 . X5) (tup (+ X4 1) (+ X4 1)) ;; 17 + X6 (+ Y0 1) ;; 18 + Y1 (+ X6 Y0) ;; 35 + Y1 + ) + 101 + ) + )"} + .to_string(); + let res = run_string(&prog, &"(13)".to_string()).unwrap(); + assert_eq!(res.to_string(), "136"); +} + +#[test] +fn test_assign_form_in_inline_argument() { + let prog = indoc! {" +(mod (X) + (defun-inline tup (X Y) (c X Y)) + (defun-inline F (A B) (+ A B)) + (F + (assign + (X1 . X2) (tup (+ X 1) (+ X 2)) ;; 14 + X3 (+ X1 1) ;; 15 + X4 (+ X2 1) ;; 16 + (Y0 . X5) (tup (+ X4 1) (+ X4 1)) ;; 17 + X6 (+ Y0 1) ;; 18 + Y1 (+ X6 Y0) ;; 35 + Y1 + ) + 101 + ) + )"} + .to_string(); + let res = run_string(&prog, &"(13)".to_string()).unwrap(); + assert_eq!(res.to_string(), "136"); +} + +#[test] +fn test_assign_in_if() { + let prog = indoc! {" +(mod (X) + (defun-inline tup (X Y) (c X Y)) + (defun-inline F (A B) (+ A B)) + (if X + (assign + (X1 . X2) (tup (+ X 1) (+ X 2)) ;; 14 + X3 (+ X1 1) ;; 15 + X4 (+ X3 1) ;; 16 + (Y0 . X5) (tup (+ X4 1) (+ X4 1)) ;; 17 + X6 (+ Y0 1) ;; 18 + Y1 (+ X6 Y0) ;; 35 + Y1 + ) + 101 + ) + )"} + .to_string(); + let res = run_string(&prog, &"(13)".to_string()).unwrap(); + assert_eq!(res.to_string(), "35"); +} + +#[test] +fn test_assign_fun_cplx_2() { + let prog = indoc! {" +(mod (X) + (defun-inline tup (X Y) (c X Y)) + (defun-inline F (A B) (+ A B)) + (if X + (let* + ((Z + (assign + (X1 . X2) (tup (+ X 1) (+ X 2)) ;; 14 + X3 (+ X1 1) ;; 15 + X4 (+ X2 1) ;; 16 + (Y0 . X5) (tup (+ X4 1) (+ X4 1)) ;; 17 + X6 (+ Y0 1) ;; 18 + Y1 (+ X6 Y0) ;; 35 + Y1 + )) + (Q (assign R (+ 3 2) (* R Z))) + ) + Q + ) + 101 + ) + )"} + .to_string(); + let res = run_string(&prog, &"(13)".to_string()).unwrap(); + assert_eq!(res.to_string(), "175"); +} + +#[test] +fn test_assign_simple_with_reodering() { + let prog = indoc! {" +(mod (A) ;; 11 + (include *standard-cl-21*) + (defun tup (a b) (c a b)) + (assign + ;; This exercises reordering in assign. + ;; Each set os grouped with a tier that will be taken together. + ;; The tiers are numbered in order, but given out of order. + + ;; Tier 1 + (X0 . X1) (tup (+ A 1) (- A 1)) ;; 12 10 + + ;; Tier 4 + finish (+ x2_gtr_x3 (- X3 x2_minus_x3)) ;; 1 + (70 - 50) + + ;; Tier 3 + x2_gtr_x3 (> X2 X3) ;; 1 + x2_minus_x3 (- X2 X3) ;; 50 + + ;; Tier 2 + X2 (* X0 10) ;; 120 + X3 (* X1 7) ;; 70 + + finish + ))"} + .to_string(); + let res = run_string(&prog, &"(11)".to_string()).unwrap(); + assert_eq!(res.to_string(), "21"); +} + +#[test] +fn test_assign_detect_multiple_definition() { + let prog = indoc! {" +(mod (A) ;; 11 + (include *standard-cl-21*) + (defun tup (a b) (c a b)) + (assign + ;; Tier 1 + (X0 . X1) (tup (+ A 1) (- A 1)) ;; 12 10 + + ;; Tier 4 + finish (+ x2_gtr_x3 (- X3 x2_minus_x3)) ;; 1 + (70 - 50) + + ;; Tier 3 + x2_gtr_x3 (> X2 X3) ;; 1 + x2_minus_x3 (- X2 X3) ;; 50 + + ;; Tier 2 + X2 (* X0 10) ;; 120 + X2 (* X1 7) ;; 70 + + finish + ))"} + .to_string(); + if let Err(CompileErr(l, e)) = run_string(&prog, &"(11)".to_string()) { + assert_eq!(l.line, 17); + assert!(e.starts_with("Duplicate")); + } else { + assert!(false); + } +} + #[test] fn test_assign_dont_detect_unrelated_inlines_as_recursive() { let prog = indoc! {" @@ -1247,3 +1525,92 @@ fn test_simple_rest_call_inline() { let res = run_string(&prog, &"(13 99 144)".to_string()).expect("should compile and run"); assert_eq!(res.to_string(), "768"); } + +#[test] +fn test_let_in_rest_0() { + let prog = indoc! {" +(mod (Z X) + (include *standard-cl-21*) + + (defun F (X) (+ X 3)) + + (F &rest (list (let ((Q (* X Z))) (+ Q 99)))) + )"} + .to_string(); + let res = run_string(&prog, &"(3 2)".to_string()).expect("should compile and run"); + assert_eq!(res.to_string(), "108"); +} + +#[test] +fn test_let_in_rest_1() { + let prog = indoc! {" +(mod (Z X) + (include *standard-cl-21*) + + (defun F (X) (+ X 3)) + + (F &rest (let ((Q (* X Z))) (list (+ Q 99)))) + )"} + .to_string(); + let res = run_string(&prog, &"(3 2)".to_string()).expect("should compile and run"); + assert_eq!(res.to_string(), "108"); +} + +#[test] +fn test_rename_in_compileform_run() { + let prog = indoc! {" +(mod (X) + (include *standard-cl-21*) + + (defun F overridden + (let + ((overridden (* 3 (f overridden))) ;; overridden = 33 + (y (f (r overridden))) ;; y = 13 + (z (f (r (r overridden))))) ;; z = 17 + (+ overridden z y) ;; 33 + 13 + 17 = 63 + ) + ) + + (F X 13 17) + )"} + .to_string(); + + let res = run_string(&prog, &"(11)".to_string()).expect("should compile and run"); + assert_eq!(res.to_string(), "63"); +} + +#[test] +fn test_rename_in_compileform_simple() { + let prog = indoc! {" +(mod (X) + (include *standard-cl-21*) + + (defun F overridden + (let + ((overridden (* 3 (f overridden))) ;; overridden = 33 + (y (f (r overridden))) ;; y = 11 + (z (f (r (r overridden))))) ;; z = 17 + (+ overridden z y) ;; 33 11 17 + ) + ) + + (F X 13 17) + )"} + .to_string(); + // Note: renames use gensym so they're unique but not spot predictable. + // + // We'll rename them in detection order to a specific set of names and should + // get for F: + // + let desired_outcome = "(defun F overridden_$_A (let ((overridden_$_B (* 3 (f overridden_$_A))) (y_$_C (f (r overridden_$_A))) (z_$_D (f (r (r overridden_$_A))))) (+ overridden_$_B z_$_D y_$_C)))"; + let parsed = parse_sexp(Srcloc::start("*test*"), prog.bytes()).expect("should parse"); + let opts: Rc = Rc::new(DefaultCompilerOpts::new(&"*test*".to_string())); + let compiled = frontend(opts, &parsed).expect("should compile"); + let helper_f: Vec<_> = compiled + .helpers + .iter() + .filter(|f| f.name() == b"F") + .collect(); + let renamed_helperform = squash_name_differences(helper_f[0].to_sexp()).expect("should rename"); + assert_eq!(renamed_helperform.to_string(), desired_outcome); +} diff --git a/src/tests/compiler/evaluate.rs b/src/tests/compiler/evaluate.rs index 393342ddd..a76033657 100644 --- a/src/tests/compiler/evaluate.rs +++ b/src/tests/compiler/evaluate.rs @@ -118,3 +118,27 @@ fn test_simple_fe_opt_compile_1() { "(2 (1 1 . 99) (4 (1) 1))".to_string() ); } + +#[test] +fn test_assign_simple_form_0() { + assert_eq!( + shrink_expr_from_string("(assign A (* Z 3) X 3 Y 4 Z (+ X Y) A)".to_string()).unwrap(), + "(q . 21)" + ); +} + +#[test] +fn test_assign_simple_form_1() { + assert_eq!( + shrink_expr_from_string("(assign A (* Z 3) Z 2 A)".to_string()).unwrap(), + "(q . 6)" + ); +} + +#[test] +fn test_assign_simple_form_2() { + assert_eq!( + shrink_expr_from_string("(assign Z 2 A (* Z 3) A)".to_string()).unwrap(), + "(q . 6)" + ); +} diff --git a/src/tests/compiler/mod.rs b/src/tests/compiler/mod.rs index b0ea66ffb..74cb6aa23 100644 --- a/src/tests/compiler/mod.rs +++ b/src/tests/compiler/mod.rs @@ -4,6 +4,7 @@ use std::rc::Rc; use crate::compiler::sexp::{parse_sexp, SExp}; use crate::compiler::srcloc::{Srcloc, Until}; +mod assign; mod cldb; mod clvm; mod compiler; diff --git a/src/tests/compiler/repl.rs b/src/tests/compiler/repl.rs index d0fd84e9f..a392c77b8 100644 --- a/src/tests/compiler/repl.rs +++ b/src/tests/compiler/repl.rs @@ -301,3 +301,17 @@ fn test_eval_list_partially_evaluated_xyz() { "(c x (c y (c z (q))))" ); } + +#[test] +fn test_eval_new_bls_operator() { + assert_eq!( + test_repl_outcome_with_stack_limit(vec![indoc!{ + "(softfork + (q . 196005) + (q . 0) + (q #g1_map (1 . 0x9790635de8740e9a6a6b15fb6b72f3a16afa0973d971979b6ba54761d6e2502c50db76f4d26143f05459a42cfd520d44)) () + )"}.to_string() + ], None).unwrap().unwrap(), + "(q)" + ); +} diff --git a/src/tests/compiler/restargs.rs b/src/tests/compiler/restargs.rs index 0bd66c842..15ea7e930 100644 --- a/src/tests/compiler/restargs.rs +++ b/src/tests/compiler/restargs.rs @@ -936,6 +936,42 @@ fn test_compiler_tail_let_ni() { assert_eq!(res.to_string(), "(5 7 8)"); } +#[test] +fn test_compiler_tail_assign_ni() { + let prog = indoc! {" +(mod (X Y) + (include *standard-cl-21*) + + (defun F (A B C) (list A B C)) + + (defun G (X Y) (F X &rest (assign Q (+ Y 1) (list Y Q)))) + + (G X Y) + )"} + .to_string(); + + let res = run_string(&prog, &"(5 7)".to_string()).expect("should compile and run"); + assert_eq!(res.to_string(), "(5 7 8)"); +} + +#[test] +fn test_compiler_tail_assign_inline() { + let prog = indoc! {" +(mod (X Y) + (include *standard-cl-21*) + + (defun F (A B C) (list A B C)) + + (defun-inline G (X Y) (F X &rest (assign Q (+ Y 1) (list Y Q)))) + + (G X Y) + )"} + .to_string(); + + let res = run_string(&prog, &"(5 7)".to_string()).expect("should compile and run"); + assert_eq!(res.to_string(), "(5 7 8)"); +} + #[test] fn test_repl_tail_let() { assert_eq!( diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 032619a9d..ed0d311e9 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,2 +1,3 @@ mod classic; mod compiler; +mod util; diff --git a/src/tests/util.rs b/src/tests/util.rs new file mode 100644 index 000000000..5585a2ba0 --- /dev/null +++ b/src/tests/util.rs @@ -0,0 +1,96 @@ +use crate::util::toposort; +use std::collections::HashSet; + +#[derive(Debug, Clone)] +struct TopoSortCheckItem { + needs: HashSet, + has: HashSet, +} + +impl TopoSortCheckItem { + pub fn new(needs_s: &[&str], has_s: &[&str]) -> Self { + let mut needs: HashSet = HashSet::new(); + let mut has: HashSet = HashSet::new(); + for n in needs_s.iter() { + needs.insert(n.to_string()); + } + for h in has_s.iter() { + has.insert(h.to_string()); + } + TopoSortCheckItem { needs, has } + } +} + +// Given +// +// A B -> X +// X Y -> Z +// A W -> Y +// [] -> A +// [] -> B +// B -> W +// +// We should get something like this: +// +// [] -> A +// [] -> B +// B -> W +// A B -> X +// A W -> Y +// X Y -> Z +// +#[test] +fn test_topo_sort_0() { + let t = |n, h| TopoSortCheckItem::new(n, h); + let items = vec![ + t(&["A", "B"], &["X"]), + t(&["X", "Y"], &["Z"]), + t(&["A", "W"], &["Y"]), + t(&[], &["A"]), + t(&[], &["B"]), + t(&["B"], &["W"]), + ]; + let result = toposort( + &items, + true, + |_p, n: &TopoSortCheckItem| Ok(n.needs.clone()), + |h| h.has.clone(), + ) + .expect("no deadlocks in this data"); + + for (i, item) in result.iter().enumerate() { + let have_item = &items[item.index]; + for j in 0..i { + let item_to_check = &result[j]; + let item_to_check_for_dependencies_on_have = &items[item_to_check.index]; + // item_to_check_for_dependencies is an item occurring prior to + // have_item in the sorted output. + // If its 'needs' has anything in have_item's 'has', then we failed. + let mut intersection = item_to_check_for_dependencies_on_have + .needs + .intersection(&have_item.has); + assert!(intersection.next().is_none()); + } + } +} + +#[test] +fn test_topo_sort_1() { + let t = |n, h| TopoSortCheckItem::new(n, h); + let items = vec![ + t(&["A", "B"], &["X"]), + t(&["X", "Y"], &["Z"]), + t(&["A", "W"], &["Y"]), + t(&[], &["A"]), + t(&["Z"], &["B"]), + t(&["B"], &["W"]), + ]; + let result = toposort( + &items, + true, + |_p, n: &TopoSortCheckItem| Ok(n.needs.clone()), + |h| h.has.clone(), + ); + + assert!(result.is_err()); +} diff --git a/src/util/mod.rs b/src/util/mod.rs index 279ecb0d6..51943649f 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,4 +1,6 @@ use num_bigint::BigInt; +use std::collections::HashSet; +use std::mem::swap; use unicode_segmentation::UnicodeSegmentation; pub type Number = BigInt; @@ -44,6 +46,93 @@ pub fn collapse(r: Result) -> A { } } +#[derive(Debug, Clone)] +pub struct TopoSortItem { + pub index: usize, + pub needs: HashSet, + pub has: HashSet, +} + +// F: tells whether t2 includes t1. +pub fn toposort( + list: &[T], + deadlock: E, + needs: Needs, + has: Has, +) -> Result>, E> +where + Needs: Fn(&HashSet, &T) -> Result, E>, + Has: Fn(&T) -> HashSet, + K: std::cmp::Eq, + K: std::hash::Hash, + K: Clone, +{ + let mut possible = HashSet::new(); + let mut done = HashSet::new(); + let mut items: Vec> = list + .iter() + .enumerate() + .map(|(i, item)| TopoSortItem { + index: i, + needs: HashSet::new(), + has: has(item), + }) + .collect(); + let mut finished_idx = 0; + + // Determine what's defined in these bindings. + for (_, item) in items.iter().enumerate() { + for new_item in item.has.iter() { + possible.insert(new_item.clone()); + } + } + + // Set needs based on possible. We may fail. + for i in 0..items.len() { + items[i].needs = needs(&possible, &list[i])?; + } + + while finished_idx < items.len() { + // Find things with no new dependencies. + let move_to_front: Vec<(usize, TopoSortItem)> = items + .iter() + .enumerate() + .skip(finished_idx) + .filter(|(_, item)| item.needs.is_subset(&done)) + .map(|(i, item)| (i, item.clone())) + .collect(); + + if move_to_front.is_empty() { + // Circular dependency somewhere. + return Err(deadlock); + } + + // Swap items into place earlier in the list. + for (idx, _tomove) in move_to_front.iter() { + if *idx != finished_idx { + let mut tmp = items[*idx].clone(); + swap(&mut tmp, &mut items[finished_idx]); + items[*idx] = tmp; + } + + // Add new 'has' items to done. + let mut tmp = HashSet::new(); + for u in done.union(&items[finished_idx].has) { + tmp.insert(u.clone()); + } + let intersection = tmp.intersection(&possible); + done.clear(); + for i in intersection { + done.insert(i.clone()); + } + + finished_idx += 1; + } + } + + Ok(items) +} + pub trait ErrInto { fn err_into(self) -> D; } diff --git a/support/install_deps.sh b/support/install_deps.sh new file mode 100755 index 000000000..ebc1a11a0 --- /dev/null +++ b/support/install_deps.sh @@ -0,0 +1,24 @@ +#!/bin/bash -x + +# This script is called from $GIT_ROOT/.github/workflows/build-test.yml +# This script is called while in $GIT_ROOT/chia-blockchain of clvm_tools_rs + +. ./venv/bin/activate + +python -m pip install --upgrade pip +python -m pip uninstall clvm clvm_rs clvm_tools clvm_tools_rs + +git clone https://github.com/Chia-Network/clvm.git --branch=main --single-branch +python -m pip install ./clvm + +echo "installing clvm_rs via pip" +pip install clvm_rs + +echo "installing clvm_tools for clvm tests" + +# Ensure clvm_tools is installed from its own repo. +git clone https://github.com/Chia-Network/clvm_tools.git --branch=main --single-branch +python -m pip install ./clvm_tools + +# Install clvm_tools_rs from the directory above. +python -m pip install .. diff --git a/support/recompile_check.py.use-deployed-hash-list.diff b/support/recompile_check.py.use-deployed-hash-list.diff new file mode 100644 index 000000000..752262468 --- /dev/null +++ b/support/recompile_check.py.use-deployed-hash-list.diff @@ -0,0 +1,15 @@ +diff --git a/support/recompile_check.py b/support/recompile_check.py +index c991198e..999896b6 100644 +--- a/support/recompile_check.py ++++ b/support/recompile_check.py +@@ -66,6 +66,10 @@ recompile_list = [ + gentest('test_multiple_generator_input_arguments.clsp') + ] + ++here = Path(__file__).parent.resolve() ++root = here.parent ++hashes_path = root.joinpath("chia/wallet/puzzles/deployed_puzzle_hashes.json") ++ + for recompile_entry in recompile_list: + if 'dirname' in recompile_entry and 'fname' in recompile_entry: + dirname = recompile_entry['dirname'] diff --git a/support/verify_compiler_version.sh b/support/verify_compiler_version.sh new file mode 100755 index 000000000..7efbbdb75 --- /dev/null +++ b/support/verify_compiler_version.sh @@ -0,0 +1,22 @@ +#!/bin/bash -x + +if [[ $# -ne 1 ]]; then + echo "Illegal number of parameters" >&2 + echo "Usage: $0 " >&2 + exit 2 +fi + +pip_installed_version=$(pip list | grep clvm_tools_rs | awk '{print $2}') +python_import_version=$(python -c 'import clvm_tools_rs; print(clvm_tools_rs.get_version())') + +expected_version="$1" + +if [ "$expected_version" == "$pip_installed_version" ] && [ "$expected_version" == "$python_import_version" ]; then + exit 0 +else + echo "clvm_tools_rs VERSIONS does not match expected version" + echo "PIP INSTALLED VERSION: $pip_installed_version" + echo "PYTHON IMPORTED VERSION: $python_import_version" + echo "EXPECTED VERSION: $expected_version" + exit 1 +fi diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index e6087b96a..c64baa9f3 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -117,7 +117,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clvm_tools_rs" -version = "0.1.34" +version = "0.1.35" dependencies = [ "binascii", "bls12_381", @@ -148,7 +148,7 @@ dependencies = [ [[package]] name = "clvm_tools_wasm" -version = "0.1.34" +version = "0.1.35" dependencies = [ "clvm_tools_rs", "clvmr", diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 4388b2dc0..25eca57c8 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clvm_tools_wasm" -version = "0.1.34" +version = "0.1.35" edition = "2018" authors = ["Art Yerkes "] description = "tools for working with chialisp language; compiler, repl, python and wasm bindings" @@ -18,7 +18,7 @@ path = "src/mod.rs" [dependencies] clvm_tools_rs = { path= "..", features = [] } -clvmr = { version = "0.2.5", features = ["pre-eval"] } +clvmr = { version = "0.3.0", features = ["pre-eval"] } wasm-bindgen = "=0.2.83" wasm-bindgen-test = "=0.3.25" js-sys = "0.3.60"