Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fc5be0d03 |
0
.dockerignore
Normal file → Executable file
0
.dockerignore
Normal file → Executable file
@@ -1,2 +0,0 @@
|
||||
# Resolved all lints and formatted the codebase
|
||||
9444e62df9820d6bfd96dbd8849e177bc5cecc2e
|
||||
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1,4 +0,0 @@
|
||||
*.png binary
|
||||
*.pl text eol=lf
|
||||
*.rs text eol=lf diff=rust
|
||||
*.md text eol=lf diff=markdown
|
||||
63
.github/actions/setup-rust/action.yml
vendored
63
.github/actions/setup-rust/action.yml
vendored
@@ -1,63 +0,0 @@
|
||||
name: 'Setup Rust'
|
||||
description: |
|
||||
Setup the rust toolchain and environment for the selected toolchain
|
||||
|
||||
inputs:
|
||||
rust-version:
|
||||
required: true
|
||||
type: string
|
||||
targets:
|
||||
required: true
|
||||
type: string
|
||||
components:
|
||||
required: false
|
||||
default:
|
||||
cache-context:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # master
|
||||
id: toolchain
|
||||
with:
|
||||
toolchain: ${{ inputs.rust-version }}
|
||||
targets: ${{ inputs.targets }}
|
||||
components: ${{ inputs.components }}
|
||||
|
||||
- name: Install i686 dependencies
|
||||
if: "contains(inputs.targets,'i686')"
|
||||
shell: bash
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install libssl-dev:i386 gcc-multilib clang -y
|
||||
echo "CC=clang" >> $GITHUB_ENV
|
||||
echo "PKG_CONFIG_SYSROOT_DIR=/" >> $GITHUB_ENV
|
||||
|
||||
- name: Install s390x cross-compilation toolchain
|
||||
if: ${{ matrix.target == 's390x-unknown-linux-gnu' }}
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-s390x-linux-gnu
|
||||
echo "CARGO_TARGET_S390X_UNKNOWN_LINUX_GNU_LINKER=s390x-linux-gnu-gcc" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ inputs.cache-context }}_${{ inputs.targets }}_rustc-${{ steps.toolchain.outputs.cachekey }}_cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
# Remove build artifacts for the current crate, since it will be rebuilt every
|
||||
# run anyway, but keep dependency artifacts to cache them.
|
||||
# Must be placed after actions/cache so its post step runs first.
|
||||
- uses: pyTooling/Actions/with-post-step@41aed3e559cbf69bfdb46abb25d7c535900ed5b6 # v7.7.0
|
||||
with:
|
||||
main: bash ./.github/actions/setup-rust/cleanup.sh
|
||||
post: bash ./.github/actions/setup-rust/cleanup.sh
|
||||
13
.github/actions/setup-rust/cleanup.sh
vendored
13
.github/actions/setup-rust/cleanup.sh
vendored
@@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
echo Cleanup workspace build artifacts and extra target output
|
||||
|
||||
# clean just the direct members of the current workspace, use cargo metadata to generalize to all rust projects
|
||||
cargo clean --workspace
|
||||
|
||||
# remove directories in /target/ that are not named `debug` or `release`
|
||||
before=`du -s target | awk '{print $1}'`
|
||||
find ./target -maxdepth 1 -type d ! -name debug ! -name release ! -name target -exec rm -r {} \;
|
||||
after=`du -s target | awk '{print $1}'`
|
||||
echo Deleted $(($before - $after)) bytes from target directory
|
||||
52
.github/dependabot.yml
vendored
52
.github/dependabot.yml
vendored
@@ -1,52 +0,0 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
# Enable version updates for cargo
|
||||
- package-ecosystem: "cargo"
|
||||
# Look for `Cargo.toml` and `Cargo.lock` files in the `root` directory
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
cooldown:
|
||||
default-days: "7"
|
||||
groups:
|
||||
cargo-incompatible:
|
||||
applies-to: version-updates
|
||||
# TODO: use incompatible update-type once available
|
||||
# see issue https://github.com/dependabot/dependabot-core/issues/9681
|
||||
update-types:
|
||||
- "major"
|
||||
- "minor" # pre-1.0 dependencies
|
||||
cargo-compatible:
|
||||
applies-to: version-updates
|
||||
# TODO: use compatible update-type once available
|
||||
# see issue https://github.com/dependabot/dependabot-core/issues/9681
|
||||
update-types:
|
||||
- "patch"
|
||||
ignore:
|
||||
# ignore all cargo updates for now while dependabot does not respect msrv/rust-version
|
||||
# see issue https://github.com/dependabot/dependabot-core/issues/5423
|
||||
- dependency-name: "*"
|
||||
|
||||
# Enable version updates for Docker
|
||||
- package-ecosystem: "docker"
|
||||
# Look for a `Dockerfile` in the `root` directory
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
cooldown:
|
||||
default-days: "7"
|
||||
|
||||
# Enable version updates for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
# Workflow files stored in the default location of `.github/workflows`
|
||||
# You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
cooldown:
|
||||
default-days: "7"
|
||||
ignore:
|
||||
# these actions doesn't have proper version tags
|
||||
- dependency-name: "dtolnay/rust-toolchain"
|
||||
- dependency-name: "logtalk-actions/setup-logtalk"
|
||||
12
.github/workflows/check_features.sh
vendored
12
.github/workflows/check_features.sh
vendored
@@ -1,12 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
echo "Checking all feature at once"
|
||||
cargo check -q --all-targets --all-features "$@"
|
||||
|
||||
features=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name = "scryer-prolog") | .features | keys | join(" ")')
|
||||
|
||||
for feature in ${features} ; do
|
||||
echo "Checking feature ${feature} in isolation"
|
||||
cargo check -q --all-targets --no-default-features --features=${feature} "$@"
|
||||
done
|
||||
248
.github/workflows/ci.yml
vendored
248
.github/workflows/ci.yml
vendored
@@ -1,248 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- rebis-dev
|
||||
tags:
|
||||
- "v**"
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 0 * * 3' # At 12:00 AM, only on Wednesday
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
style:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
rust-version: stable
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
components: clippy, rustfmt
|
||||
cache-context: style
|
||||
|
||||
- name: Check formatting
|
||||
run: cargo fmt --check
|
||||
- name: Check clippy
|
||||
run: cargo clippy --no-deps --all-targets
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
read-msrv:
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
msrv: ${{ steps.read-declared-msrv.outputs.msrv }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- id: read-declared-msrv
|
||||
name: Read msrv from Cargo.toml rust_version field
|
||||
run: echo "msrv=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name = "scryer-prolog") | ."rust_version"')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
|
||||
build-test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [read-msrv]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# operating systems
|
||||
- { os: windows-latest, rust-version: stable, target: 'x86_64-pc-windows-msvc', publish: true, check-features: true }
|
||||
- { os: macos-latest, rust-version: stable, target: 'x86_64-apple-darwin', publish: true, check-features: true }
|
||||
# architectures
|
||||
- { os: ubuntu-22.04, rust-version: stable, target: 'x86_64-unknown-linux-gnu', publish: true, check-features: true }
|
||||
- { os: ubuntu-22.04, rust-version: stable, target: 'i686-unknown-linux-gnu', publish: true }
|
||||
# FIXME(issue #2138): run wasm tests, failing to run since https://github.com/mthom/scryer-prolog/pull/2137 removed wasm-pack
|
||||
- { os: ubuntu-22.04, rust-version: nightly, target: 'wasm32-unknown-unknown', publish: true, args: '--no-default-features' , test-args: '--no-run --no-default-features', use_swap: true }
|
||||
# Cargo.toml rust-version
|
||||
- { os: ubuntu-22.04, rust-version: "${{ needs.read-msrv.outputs.msrv }}" , target: 'x86_64-unknown-linux-gnu' }
|
||||
- { os: ubuntu-22.04, rust-version: beta, target: 'x86_64-unknown-linux-gnu' }
|
||||
- { os: ubuntu-22.04, rust-version: nightly, target: 'x86_64-unknown-linux-gnu', miri: true, components: "miri" }
|
||||
# run miri for a big-endian target, with all features that are simple to get cross-compiled
|
||||
- { os: ubuntu-22.04, rust-version: nightly, target: 's390x-unknown-linux-gnu', miri: true, components: "miri", args: '--no-default-features --features=all-simple-cross', test-args: '--no-run --no-default-features --features=all-simple-cross' }
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: actionhippie/swap-space@0cffa893f224708cfb6b011690d8ba819d69c10f # v1.1.0
|
||||
if: matrix.use_swap
|
||||
with:
|
||||
size: 10G
|
||||
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
rust-version: ${{ matrix.rust-version }}
|
||||
targets: ${{ matrix.target }}
|
||||
cache-context: ${{ matrix.os }}
|
||||
components: ${{ matrix.components }}
|
||||
|
||||
# Build and test.
|
||||
- name: Build library
|
||||
run: cargo build --all-targets --target ${{ matrix.target }} ${{ matrix.args }} --verbose
|
||||
|
||||
- name: Test
|
||||
run: cargo test --target ${{ matrix.target }} ${{ matrix.test-args }} --all
|
||||
|
||||
- name: Check features
|
||||
if: matrix.check-features
|
||||
run: bash ./.github/workflows/check_features.sh --target ${{ matrix.target }}
|
||||
|
||||
- name: Check miri
|
||||
if: matrix.miri
|
||||
run: cargo miri test --target ${{ matrix.target }} ${{ matrix.args }}
|
||||
|
||||
# On stable rust builds, build a binary and publish as a github actions
|
||||
# artifact. These binaries could be useful for testing the pipeline but
|
||||
# are only retained by github for 90 days.
|
||||
- name: Build release binary
|
||||
if: matrix.publish
|
||||
run: |
|
||||
cargo rustc --target ${{ matrix.target }} ${{ matrix.args }} --verbose --bin scryer-prolog --release
|
||||
echo "$PWD/target/release" >> $GITHUB_PATH
|
||||
|
||||
- name: Install cargo-deb for creating debian packages
|
||||
if: ${{ matrix.publish && contains(matrix.target, 'linux') }}
|
||||
run: cargo install cargo-deb --force --locked
|
||||
|
||||
- name: Build release debian packages
|
||||
if: ${{ matrix.publish && contains(matrix.target, 'linux') }}
|
||||
run: cargo deb --target ${{ matrix.target }}
|
||||
|
||||
- name: Publish release binary artifact
|
||||
if: matrix.publish
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
path: |
|
||||
target/${{ matrix.target }}/release/scryer-prolog*
|
||||
target/${{ matrix.target }}/debian/scryer-prolog*.deb
|
||||
name: scryer-prolog_${{ matrix.os }}_${{ matrix.target }}
|
||||
|
||||
logtalk-test:
|
||||
# if: false # uncomment to disable job
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [build-test]
|
||||
steps:
|
||||
# Download prebuilt ubuntu binary from build-test job, setup logtalk
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
|
||||
with:
|
||||
name: scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu
|
||||
- run: |
|
||||
chmod +x release/scryer-prolog
|
||||
echo "$PWD/release" >> "$GITHUB_PATH"
|
||||
- name: Install Logtalk
|
||||
uses: logtalk-actions/setup-logtalk@4ea002fe3037199afcf1c6c91bf1f57de0f995e6 # master
|
||||
with:
|
||||
logtalk-version: "3.70.0"
|
||||
logtalk-tool-dependencies: false
|
||||
|
||||
# Run logtalk tests.
|
||||
- name: Run Logtalk's prolog compliance test suite
|
||||
working-directory: ${{ env.LOGTALKUSER }}/tests/prolog/
|
||||
run: |
|
||||
pwd
|
||||
scryerlgt -g '{ack(tester)},halt.'
|
||||
logtalk_tester -p scryer -g "set_logtalk_flag(clean,off)" -w -t 360 \
|
||||
-f xunit \
|
||||
-s "$LOGTALKUSER/tests/prolog" \
|
||||
|| echo "::warning ::logtalk compliance suite failed"
|
||||
# -u "https://github.com/LogtalkDotOrg/logtalk3/tree/$LOGTALK_GIT_HASH/tests/prolog/" \
|
||||
- name: Publish Logtalk test logs
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: logtalk-test-logs
|
||||
path: '${{ env.LOGTALKUSER }}/tests/prolog/logtalk_tester_logs'
|
||||
- name: Publish Logtalk test results artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: logtalk-test-results
|
||||
path: '${{ env.LOGTALKUSER }}/tests/prolog/**/*.xml'
|
||||
- name: Publish Logtalk test summary
|
||||
uses: EnricoMi/publish-unit-test-result-action/composite@c950f6fb443cb5af20a377fd0dfaa78838901040 # v2.23.0
|
||||
with:
|
||||
check_name: Logtalk test summary
|
||||
files: '${{ env.LOGTALKUSER }}/tests/prolog/**/*.xml'
|
||||
fail_on: nothing
|
||||
comment_mode: off
|
||||
|
||||
report:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
rust-version: stable
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
cache-context: report
|
||||
- name: Install CLI tools
|
||||
run: |
|
||||
cargo install cargo2junit --force
|
||||
version=`yq -ptoml -oj -r '.package[] | select(.name == "iai-callgrind") | .version' Cargo.lock`
|
||||
echo installing iai-callgrind "$version"
|
||||
cargo install iai-callgrind-runner --force --version "$version"
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install valgrind -y
|
||||
|
||||
- name: Test and report
|
||||
run: |
|
||||
RUSTC_BOOTSTRAP=1 cargo test --all -- -Z unstable-options --format json --report-time | cargo2junit > cargo_test_results.xml
|
||||
- name: Publish cargo test results artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: cargo-test-results
|
||||
path: cargo_test_results.xml
|
||||
- name: Publish cargo test summary
|
||||
uses: EnricoMi/publish-unit-test-result-action/composite@c950f6fb443cb5af20a377fd0dfaa78838901040 # v2.23.0
|
||||
with:
|
||||
check_name: Cargo test summary
|
||||
files: cargo_test_results.xml
|
||||
fail_on: nothing
|
||||
comment_mode: off
|
||||
|
||||
- run: cargo build --all-targets --release
|
||||
- run: cargo test --bench setup --release
|
||||
- run: cargo bench --bench run_iai -- --save-summary=json
|
||||
- run: cargo bench --bench run_criterion
|
||||
- run: cargo bench --bench run_criterion -- --profile-time 60
|
||||
|
||||
- name: Publish benchmark results
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: benchmark-results
|
||||
path: |
|
||||
target/criterion/*
|
||||
target/iai/*
|
||||
target/benchmark_inference_counts.json
|
||||
|
||||
# Publish binaries when building for a tag
|
||||
release:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [build-test]
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
|
||||
- name: Zip binaries for release
|
||||
run: |
|
||||
zip scryer-prolog_macos-latest.zip ./scryer-prolog_macos-latest_x86_64-apple-darwin/scryer-prolog
|
||||
zip scryer-prolog_ubuntu-22.04_i686.zip ./scryer-prolog_ubuntu-22.04_i686-unknown-linux-gnu/release/scryer-prolog ./scryer-prolog_ubuntu-22.04_i686-unknown-linux-gnu/debian/scryer-prolog*.deb
|
||||
zip scryer-prolog_ubuntu-22.04_x86_64.zip ./scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu/release/scryer-prolog ./scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu/debian/scryer-prolog*.deb
|
||||
zip scryer-prolog_windows-latest.zip ./scryer-prolog_windows-latest_x86_64-pc-windows-msvc/scryer-prolog.exe
|
||||
zip scryer-prolog_wasm32.zip ./scryer-prolog_ubuntu-22.04_wasm32-unknown-unknown/scryer-prolog.wasm
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
|
||||
with:
|
||||
files: |
|
||||
scryer-prolog_macos-latest.zip
|
||||
scryer-prolog_ubuntu-22.04_i686.zip
|
||||
scryer-prolog_ubuntu-22.04_x86_64.zip
|
||||
scryer-prolog_windows-latest.zip
|
||||
scryer-prolog_wasm32.zip
|
||||
61
.github/workflows/docker-publish.yml
vendored
61
.github/workflows/docker-publish.yml
vendored
@@ -1,61 +0,0 @@
|
||||
name: Docker Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
# Workaround: https://github.com/docker/build-push-action/issues/461
|
||||
- name: Setup Docker buildx
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
# Login against Docker registry
|
||||
- name: Log into registry
|
||||
# https://github.com/docker/login-action
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# Extract Docker image tag from git tag. E.g. if git tag is "v0.19.1" then use
|
||||
# Docker image tag "0.19.1". The "latest" tag reflects the most recent build on
|
||||
# master.
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
# https://github.com/docker/metadata-action
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
with:
|
||||
images: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/scryer-prolog
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
# type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
|
||||
|
||||
# Build and push Docker image with Buildx
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
# https://github.com/docker/build-push-action
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
# v4 adds SLSA Provenance attestation which is
|
||||
# - unsupported by AWS Lambda
|
||||
# - limited support by Google Cloud Run
|
||||
# > If deploying a multi-architecture image, the manifest list must include linux/amd64.
|
||||
# see https://github.com/docker/build-push-action/releases/tag/v4.0.0
|
||||
# we might want to disable this if its a problem for someone
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,7 +1,3 @@
|
||||
src/static_atoms.rs
|
||||
target/
|
||||
.direnv/
|
||||
|
||||
|
||||
__pycache__
|
||||
*.pyc
|
||||
31
.travis.yml
Normal file
31
.travis.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
language: rust
|
||||
cache: cargo
|
||||
os: linux
|
||||
dist: xenial
|
||||
|
||||
before_script:
|
||||
- cargo fetch
|
||||
|
||||
jobs:
|
||||
allow_failures:
|
||||
env:
|
||||
- CAN_FAIL=true
|
||||
include:
|
||||
- stage: "Stable: Build"
|
||||
rust: stable
|
||||
script: cargo rustc --verbose -- -D warnings
|
||||
name: "Build Stable"
|
||||
- stage: "Stable: Tests"
|
||||
rust: stable
|
||||
script: cargo test --verbose --all
|
||||
name: "Tests Stable"
|
||||
- stage: "Features"
|
||||
rust: stable
|
||||
script: cargo test --verbose --all --no-default-features --features num
|
||||
name: "num Tests"
|
||||
env: CAN_FAIL=true
|
||||
- stage: "Beta: Build"
|
||||
# - #
|
||||
rust: beta
|
||||
script: cargo rustc --verbose -- -D warnings
|
||||
name: "Build Beta"
|
||||
4352
Cargo.lock
generated
4352
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
201
Cargo.toml
201
Cargo.toml
@@ -1,179 +1,50 @@
|
||||
[package]
|
||||
name = "scryer-prolog"
|
||||
version = "0.10.0"
|
||||
version = "0.8.127"
|
||||
authors = ["Mark Thom <markjordanthom@gmail.com>"]
|
||||
edition = "2024"
|
||||
edition = "2018"
|
||||
description = "A modern Prolog implementation written mostly in Rust."
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/mthom/scryer-prolog"
|
||||
license = "BSD-3-Clause"
|
||||
keywords = ["prolog", "prolog-interpreter", "prolog-system"]
|
||||
categories = ["command-line-utilities"]
|
||||
build = "build/main.rs"
|
||||
# Remember to check CI
|
||||
rust-version = "1.93.1"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = ["all-simple-cross", "tls", "http"]
|
||||
# activates all features that depend on no non pure-rust dependencies
|
||||
#
|
||||
# currently does not include
|
||||
# ffi due to libffi
|
||||
# tls, http due to openssl
|
||||
# crypto-full due to ring
|
||||
all-pure = ["repl", "hostname"]
|
||||
# enables all features that are simple to get working for cross-compliation
|
||||
# currently all but tls, http as those depend on openssl
|
||||
all-simple-cross = ["all-pure", "ffi", "crypto-full"]
|
||||
ffi = ["dep:libffi"]
|
||||
repl = ["dep:crossterm", "dep:ctrlc", "dep:rustyline"]
|
||||
hostname = ["dep:hostname"]
|
||||
tls = ["dep:native-tls"]
|
||||
http = ["dep:warp", "dep:reqwest"]
|
||||
# crypto function that require non pure-rust dependencies
|
||||
crypto-full = ["dep:ring"]
|
||||
|
||||
[lints.clippy]
|
||||
collapsible_match = "allow"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = "deny"
|
||||
function_casts_as_integer = "deny"
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
proc-macro2 = "1.0.86"
|
||||
quote = "1.0.36"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
syn = { version = "2.0.72", features = ['full', 'visit', 'extra-traits'] }
|
||||
to-syn-value = "0.1.1"
|
||||
to-syn-value_derive = "0.1.1"
|
||||
walkdir = "2"
|
||||
indexmap = "1.0.2"
|
||||
|
||||
[features]
|
||||
default = ["rug", "prolog_parser/rug"]
|
||||
num = ["num-rug-adapter", "prolog_parser/num"]
|
||||
|
||||
[dependencies]
|
||||
arcu = { version = "0.1.2", features = ["thread_local_counter"] }
|
||||
base64 = "0.22.1"
|
||||
bit-set = "0.8.0"
|
||||
bitvec = "1"
|
||||
blake2 = "0.10.6"
|
||||
bytes = "1"
|
||||
chrono = "0.4.38"
|
||||
cpu-time = "1.0.0"
|
||||
crrl = "0.9.0"
|
||||
dashu = { version = "0.4.2", features = ["rand"] }
|
||||
derive_more = "0.99.18"
|
||||
dirs-next = "2.0.0"
|
||||
divrem = "1.0.0"
|
||||
futures = "0.3"
|
||||
fxhash = "0.2.1"
|
||||
git-version = "0.3.9"
|
||||
indexmap = "2.3.0"
|
||||
lexical = "7.0.4"
|
||||
libc = "0.2.155"
|
||||
libloading = "0.8"
|
||||
modular-bitfield = "0.13.1"
|
||||
num-order = { version = "1.2.0" }
|
||||
ordered-float = "5.0.0"
|
||||
phf = { version = "0.11", features = ["macros"] }
|
||||
puruspe = "0.4.1"
|
||||
rand = "0.8.5"
|
||||
ring = { version = "0.17.8", features = [
|
||||
"wasm32_unknown_unknown_js",
|
||||
], optional = true }
|
||||
ripemd = "0.1.3"
|
||||
roxmltree = "0.20.0"
|
||||
ryu = "1.0.18"
|
||||
sha3 = "0.10.8"
|
||||
smallvec = "1.13.2"
|
||||
static_assertions = "1.1.0"
|
||||
|
||||
scraper = { version = "0.23.1", default-features = false, features = [
|
||||
"errors",
|
||||
] }
|
||||
ego-tree = "0.10.0"
|
||||
|
||||
|
||||
serde_json = "1.0.122"
|
||||
serde = "1.0.204"
|
||||
parking_lot = "0.12.4"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
crossterm = { version = "0.28.1", optional = true }
|
||||
ctrlc = { version = "3.4.4", optional = true }
|
||||
hostname = { version = "0.4.0", optional = true }
|
||||
libffi = { version = "5.1.0", optional = true }
|
||||
native-tls = { version = "0.2.12", optional = true }
|
||||
# the version requirement of reqwest is kept low for compatibility with old deno versions
|
||||
# that pin reqwest to 0.11.20
|
||||
reqwest = { version = "0.11.0", optional = true }
|
||||
rustyline = { version = "18.0.0", optional = true }
|
||||
tokio = { version = "1.39.2", features = ["full"] }
|
||||
warp = { version = "0.3.7", features = ["tls"], optional = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2.15", features = ["js"] }
|
||||
tokio = { version = "1.39.2", features = [
|
||||
"sync",
|
||||
"macros",
|
||||
"io-util",
|
||||
"rt",
|
||||
"time",
|
||||
] }
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
wasm-bindgen = "0.2.92"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
serde-wasm-bindgen = "0.6"
|
||||
web-sys = { version = "0.3", features = [
|
||||
"Document",
|
||||
"Window",
|
||||
"Element",
|
||||
"Performance",
|
||||
] }
|
||||
js-sys = "0.3"
|
||||
ouroboros = "0.18"
|
||||
|
||||
[dev-dependencies]
|
||||
current_platform = "0.2.0"
|
||||
maplit = "1.0.2"
|
||||
serial_test = "3.1.1"
|
||||
|
||||
[target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dev-dependencies]
|
||||
criterion = "0.5.1"
|
||||
iai-callgrind = "0.12.1"
|
||||
trycmd = "0.15.6"
|
||||
|
||||
[target.'cfg(not(any(target_os = "windows", all(target_arch = "wasm32", target_os = "unknown"))))'.dev-dependencies]
|
||||
pprof = { version = "0.13.0", features = ["criterion", "flamegraph"] }
|
||||
|
||||
[profile.bench]
|
||||
lto = true
|
||||
opt-level = 3
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 3
|
||||
debug = 2
|
||||
|
||||
[profile.wasm-dev]
|
||||
inherits = "dev"
|
||||
opt-level = 1
|
||||
lto = "off"
|
||||
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
lto = "off"
|
||||
panic = "abort"
|
||||
codegen-units = 256
|
||||
|
||||
[[bench]]
|
||||
name = "run_criterion"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "run_iai"
|
||||
harness = false
|
||||
crossterm = "0.16.0"
|
||||
dirs = "2.0.2"
|
||||
divrem = "0.1.0"
|
||||
downcast = "0.10.0"
|
||||
git-version = "0.3.4"
|
||||
hostname = "0.3.1"
|
||||
indexmap = "1.0.2"
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.62"
|
||||
nix = "0.15.0"
|
||||
num-rug-adapter = { optional = true, version = "0.1.3" }
|
||||
ordered-float = "0.5.0"
|
||||
prolog_parser = { version = "0.8.65", default-features = false }
|
||||
ref_thread_local = "0.0.0"
|
||||
rug = { version = "1.4.0", optional = true }
|
||||
rustyline = "6.0.0"
|
||||
unicode_reader = "1.0.0"
|
||||
ring = "0.16.13"
|
||||
ripemd160 = "0.8.0"
|
||||
sha3 = "0.8.2"
|
||||
blake2 = "0.8.1"
|
||||
openssl = { version = "0.10.29", features = ["vendored"] }
|
||||
native-tls = "0.2.4"
|
||||
chrono = "0.4.11"
|
||||
select = "0.4.3"
|
||||
roxmltree = "0.11.0"
|
||||
base64 = "0.12.3"
|
||||
sodiumoxide = "0.2.6"
|
||||
|
||||
64
Dockerfile
Normal file → Executable file
64
Dockerfile
Normal file → Executable file
@@ -1,34 +1,30 @@
|
||||
# See https://github.com/LukeMathWalker/cargo-chef
|
||||
ARG DEBIAN_RELEASE=bookworm
|
||||
ARG RUST_VERSION=1-${DEBIAN_RELEASE}
|
||||
FROM rust:${RUST_VERSION} AS planner
|
||||
WORKDIR /scryer-prolog
|
||||
RUN cargo install cargo-chef
|
||||
COPY . .
|
||||
RUN cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
FROM rust:${RUST_VERSION} AS cacher
|
||||
WORKDIR /scryer-prolog
|
||||
RUN cargo install cargo-chef
|
||||
COPY --from=planner /scryer-prolog/recipe.json recipe.json
|
||||
RUN cargo chef cook --release --recipe-path recipe.json
|
||||
|
||||
FROM rust:${RUST_VERSION} AS builder
|
||||
WORKDIR /scryer-prolog
|
||||
COPY . .
|
||||
# Copy over the cached dependencies
|
||||
COPY --from=cacher /scryer-prolog/target target
|
||||
COPY --from=cacher $CARGO_HOME $CARGO_HOME
|
||||
RUN cargo build --release --bin scryer-prolog
|
||||
|
||||
FROM debian:${DEBIAN_RELEASE}-slim
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends openssl \
|
||||
&& apt-get clean -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=builder /scryer-prolog/target/release/scryer-prolog /usr/local/bin
|
||||
ENV RUST_BACKTRACE=1
|
||||
# Sanity check the binary: if it can't be executed (e.g. if there are missing libraries)
|
||||
# then fail the build
|
||||
RUN scryer-prolog --version
|
||||
ENTRYPOINT ["/usr/local/bin/scryer-prolog"]
|
||||
# Based on https://hub.docker.com/_/rust?tab=description and https://blog.sedrik.se/posts/my-docker-setup-for-rust/
|
||||
|
||||
# The first container is for build purposes only.
|
||||
FROM rust as builder
|
||||
|
||||
WORKDIR /usr/src/scryer-prolog
|
||||
|
||||
# Using a dummy build.rs and src/main.rs with your Cargo.toml lets Docker cache your Rust dependencies and not rebuild
|
||||
# them every time.
|
||||
COPY Cargo.toml .
|
||||
COPY Cargo.lock .
|
||||
RUN mkdir -p src
|
||||
RUN echo "fn main() {}" > src/main.rs
|
||||
RUN echo "fn main() {}" > build.rs
|
||||
RUN cargo build --release
|
||||
|
||||
# We need to touch our real main.rs and build.rs files or else
|
||||
# docker will use the cached ones.
|
||||
COPY . .
|
||||
RUN touch src/main.rs
|
||||
RUN touch build.rs
|
||||
|
||||
RUN cargo build --release
|
||||
|
||||
RUN ls ./target/release
|
||||
|
||||
# Finally, copy the scryer-prolog executable to a slimmer container.
|
||||
FROM debian:buster-slim
|
||||
COPY --from=builder /usr/src/scryer-prolog/target/release/scryer-prolog /usr/local/bin/scryer-prolog
|
||||
CMD ["scryer-prolog"]
|
||||
|
||||
79
INDEX.dj
79
INDEX.dj
@@ -1,79 +0,0 @@
|
||||
# Scryer Prolog
|
||||
|
||||
```
|
||||
?- append("Hello, ", X, "Hello, Scryer Prolog!").
|
||||
X = "Scryer Prolog!".
|
||||
```
|
||||
|
||||
{width=128 style=float:right;} [Scryer Prolog](https://github.com/mthom/scryer-prolog) is a free software ISO Prolog system intended to be an industrial
|
||||
strength production environment *and* a testbed for bleeding edge research in
|
||||
logic and constraint programming.
|
||||
|
||||
Some of the Scryer Prolog features are:
|
||||
|
||||
* ISO standard compliant
|
||||
* Integrated constraint programming libraries: [clp(B)](/clpb.html), [clp(Z)](/clpz.html).
|
||||
* [Definite Clause Grammars](/dcgs.html)
|
||||
* Coroutining support ([`dif/2`](/dif.html), [`freeze/2`](/freeze.html), ...)
|
||||
* [Tabling and SLG resolution](/tabling.html)
|
||||
* Compact string representation
|
||||
* Network libraries ([TCP sockets](/sockets.html), [HTTP server](/http/http_server.html), [HTTP client](/http/http_open.html), ...)
|
||||
* [Cryptographical predicates](/crypto.html)
|
||||
* [Foreign Function Interface](/ffi.html)
|
||||
* WebAssembly support
|
||||
* Usable as a library
|
||||
* WAM based engine, cross-platform made in Rust
|
||||
* _and more..._
|
||||
|
||||
Try Scryer Prolog without any installation! Use [Scryer Playground](https://play.scryer.pl), which uses the WASM version of Scryer Prolog.
|
||||
|
||||
## What is Prolog?
|
||||
|
||||
Prolog is a logic programming language created by [Alain Colmerauer](https://en.wikipedia.org/wiki/Alain_Colmerauer) and [Robert Kowalski](https://en.wikipedia.org/wiki/Robert_Kowalski) in 1972.
|
||||
The idea behind Prolog is try to express a task in language similar to First Order Logic.
|
||||
Prolog systems include _unification_ and _non-determinism_ as key concepts upon which we build programs.
|
||||
|
||||
A Prolog program is made up of predicates which define a relation between its arguments. A predicate
|
||||
is made from clauses. A clause can be either a fact or a rule. There's also a toplevel, which we
|
||||
can use to ask and reason about our task.
|
||||
|
||||
It's still to this day one of the best examples and one of the most popular languages in the field
|
||||
of logic programming. That's because Prolog allows us to elegantly solve many tasks with short and
|
||||
general programs.
|
||||
|
||||
If you want a more detailed description of Prolog, check [A Tour of Prolog](https://www.youtube.com/watch?v=8XUutFBbUrg).
|
||||
|
||||
If you want to learn more about Prolog history, check the videos [l'Aventure Prolog](https://www.youtube.com/watch?v=74Ig_QKndvE) and [50 years of Prolog and beyond](https://prologyear.logicprogramming.org/videos/PrologDay_Session_1_talk.mp4).
|
||||
|
||||
## Where can I learn Prolog?
|
||||
|
||||
There are a lot of classical Prolog books. Those books can teach you the basics of Prolog. Some
|
||||
examples are: _The Art of Prolog (Shapiro)_, _Programming in Prolog (Clocksin, Mellish)_ and _The Craft
|
||||
of Prolog (O'Keefe)_. However, most of them are not updated to _modern_ Prolog.
|
||||
We recommend _[The Power of Prolog (Markus Triska)](https://www.metalevel.at/prolog)_ for modern Prolog. For reference about
|
||||
the builtin Prolog modules and libraries in Scryer, check the documentation site. It's this!
|
||||
|
||||
## Downloads
|
||||
|
||||
The latest version of Scryer Prolog is *0.10.0*. And it's already useful for lots of tasks.
|
||||
|
||||
| Windows (64 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-0100/scryer-prolog_windows-latest_x86_64-pc-windows-msvc.zip) |
|
||||
| macOS (Intel) | [Download](https://scryerprologrelease.blob.core.windows.net/release-0100/scryer-prolog_macos-11_x86_64-apple-darwin.zip) |
|
||||
| macOS (ARM) | [Download](https://scryerprologrelease.blob.core.windows.net/release-0100/scryer-prolog--0.10.0.arm64_tahoe.bottle.tar.gz) |
|
||||
| Linux (Ubuntu 22.04, 64 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-0100/scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu.zip) |
|
||||
| Linux (Ubuntu 22.04, 32 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-0100/scryer-prolog_ubuntu-22.04_i686-unknown-linux-gnu.zip) |
|
||||
|
||||
Scryer Prolog can also be compiled from source, instructions are on the [GitHub README](https://github.com/mthom/scryer-prolog). It runs on Linux, macOS and Windows. Other operating systems may work but they're not regularly tested.
|
||||
|
||||
If you're in Linux, maybe your distribution already has an Scryer Prolog package.
|
||||
|
||||
There's also a [Docker image](https://github.com/mthom/scryer-prolog#docker-install) available.
|
||||
|
||||
## Support and discussions
|
||||
|
||||
If Scryer Prolog crashes or yields unexpected errors, consider filing
|
||||
an [issue](https://github.com/mthom/scryer-prolog/issues).
|
||||
|
||||
To get in touch with the Scryer Prolog community, participate in
|
||||
[discussions](https://github.com/mthom/scryer-prolog/discussions)
|
||||
or visit our #scryer IRC channel on [Libera](https://libera.chat)!
|
||||
511
README.md
511
README.md
@@ -5,20 +5,14 @@ source industrial strength production environment that is also a
|
||||
testbed for bleeding edge research in logic and constraint
|
||||
programming, which is itself written in a high-level language.
|
||||
|
||||
**Scryer Prolog passes all tests** of
|
||||
[syntactic conformity](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/conformity_testing),
|
||||
[`variable_names/1`](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/variable_names) and
|
||||
[`dif/2`](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/dif).
|
||||
|
||||
The homepage of the project is: [**https://www.scryer.pl**](https://www.scryer.pl)
|
||||
|
||||

|
||||
|
||||
## Phase 1
|
||||
|
||||
Produce an implementation of the Warren Abstract Machine in Rust, done
|
||||
according to the progression of languages in [Warren's Abstract
|
||||
Machine: A Tutorial Reconstruction](https://github.com/mthom/scryer-prolog/blob/master/wambook/wambook.pdf).
|
||||
Machine: A Tutorial
|
||||
Reconstruction](http://wambook.sourceforge.net/wambook.pdf).
|
||||
|
||||
Phase 1 has been completed in that Scryer Prolog implements in some form
|
||||
all of the WAM book, including lists, cuts, Debray allocation, first
|
||||
@@ -49,7 +43,7 @@ Extend Scryer Prolog to include the following, among other features:
|
||||
- [x] Support for `attribute_goals/2` and `project_attributes/2`
|
||||
- [x] `call_residue_vars/2`
|
||||
- [x] `if_/3` and related predicates, following the developments of the
|
||||
paper "[Indexing `dif/2`](https://arxiv.org/abs/1607.01590)".
|
||||
paper "Indexing `dif/2`".
|
||||
- [x] All-solutions predicates (`findall/{3,4}`, `bagof/3`, `setof/3`, `forall/2`).
|
||||
- [x] Clause creation and destruction (`asserta/1`, `assertz/1`,
|
||||
`retract/1`, `abolish/1`) with logical update semantics.
|
||||
@@ -57,26 +51,19 @@ Extend Scryer Prolog to include the following, among other features:
|
||||
`bb_put/2` (non-backtrackable) and `bb_b_put/2`
|
||||
(backtrackable).
|
||||
- [x] Delimited continuations based on reset/3, shift/1 (documented in
|
||||
"[Delimited Continuations for Prolog](https://biblio.ugent.be/publication/5646080/file/5646081)").
|
||||
"Delimited Continuations for Prolog").
|
||||
- [x] Tabling library based on delimited continuations
|
||||
(documented in "[Tabling as a Library with Delimited Control](https://biblio.ugent.be/publication/6880648/file/6885145.pdf)").
|
||||
(documented in "Tabling as a Library with Delimited Control").
|
||||
- [x] A _redone_ representation of strings as difference lists of
|
||||
characters, using a packed internal representation.
|
||||
- [x] clp(B) and clp(ℤ) as builtin libraries.
|
||||
- [x] Streams and predicates for stream control.
|
||||
- [x] A simple sockets library representing TCP connections as streams.
|
||||
- [x] Incremental compilation and loading process, newly written,
|
||||
primarily in Prolog.
|
||||
- [ ] Improvements to the WAM compiler and heap representation:
|
||||
- [ ] Replacing choice points pivoting on inlined semi-deterministic predicates
|
||||
(`atom`, `var`, etc) with if/else ladders. (_in progress_)
|
||||
- [ ] Inlining all built-ins and system call instructions.
|
||||
- [x] Greatly reducing the number of instructions used to compile disjunctives.
|
||||
- [x] Storing short atoms to heap cells without writing them to the atom table.
|
||||
- [ ] Configurable JIT/on-demand indexing over all arguments
|
||||
(documented in "[Demand-Driven Indexing of Prolog Clauses](https://user.it.uu.se/~kostis/Papers/iclp07.pdf)"). (_in progress_)
|
||||
- [ ] A compacting garbage collector satisfying the five properties of
|
||||
"[Precise Garbage Collection in Prolog](https://www.complang.tuwien.ac.at/ulrich/papers/PDF/2008-ciclops.pdf)." (_in progress_)
|
||||
- [x] A simple sockets library representing TCP connections as streams.
|
||||
- [ ] Incremental compilation and loading process, newly written,
|
||||
primarily in Prolog. (_in progress_)
|
||||
- [ ] A compacting garbage collector satisfying the five
|
||||
properties of "Precise Garbage Collection in Prolog."
|
||||
- [ ] Mode declarations.
|
||||
|
||||
## Phase 3
|
||||
|
||||
@@ -94,29 +81,24 @@ nice to have in the future. They'd make a good project for anyone wanting
|
||||
to contribute code to Scryer Prolog.
|
||||
|
||||
1. Implement the global analysis techniques described in Peter van
|
||||
Roy's thesis, "[Can Logic Programming Execute as Fast as Imperative
|
||||
Programming?](https://www.info.ucl.ac.be/~pvr/Peter.thesis/Peter.thesis.html)"
|
||||
Roy's thesis, "Can Logic Programming Execute as Fast as Imperative
|
||||
Programming?"
|
||||
|
||||
2. Add unum representation and arithmetic, using either an existing
|
||||
unum implementation or an ad hoc one. Unums are described in
|
||||
Gustafson's book "[The End of Error](http://www.johngustafson.net/unums.html)."
|
||||
Gustafson's book "The End of Error."
|
||||
|
||||
3. Add concurrent tables to manage shared references to atoms and
|
||||
strings.
|
||||
|
||||
4. Add some form of JIT predicate indexing.
|
||||
|
||||
## Installing Scryer Prolog
|
||||
|
||||
### Binaries
|
||||
|
||||
Precompiled binaries for several platforms are available for download
|
||||
at:
|
||||
|
||||
**https://github.com/mthom/scryer-prolog/releases/latest**
|
||||
|
||||
### Native Compilation
|
||||
### Native Install (Unix Only)
|
||||
|
||||
First, install the latest stable version of
|
||||
[Rust](https://www.rust-lang.org/tools/install) using your
|
||||
[Rust](https://www.rust-lang.org/en-US/install.html) using your
|
||||
preferred method. Scryer tends to use features from newer Rust
|
||||
releases, whereas Rust packages in Linux distributions, Macports,
|
||||
etc. tend to lag behind. [rustup](http://rustup.rs) will keep your
|
||||
@@ -124,175 +106,31 @@ Rust updated to the latest stable release; any existing Rust
|
||||
distribution should be uninstalled from your system before rustup is
|
||||
used.
|
||||
|
||||
Scryer Prolog can be installed with cargo, like so:
|
||||
|
||||
> [!NOTE]
|
||||
> The minimum rust toolchain version required can be found in the [Cargo.toml](https://github.com/mthom/scryer-prolog/blob/master/Cargo.toml#L13)
|
||||
under the `package.rust-version` key.
|
||||
> The accuracy of this value is validated in CI
|
||||
```
|
||||
$> cargo install scryer-prolog
|
||||
```
|
||||
|
||||
### From a local git checkout
|
||||
cargo will download and install the libraries Scryer Prolog uses
|
||||
automatically from crates.io. You can find the `scryer-prolog`
|
||||
executable in `~/.cargo/bin`.
|
||||
|
||||
Publishing Rust crates to crates.io and pushing to git are entirely
|
||||
distinct, independent processes, so to be sure you have the latest
|
||||
commit, it is recommended to clone directly from this git repository,
|
||||
which can be done as follows:
|
||||
|
||||
```
|
||||
$> git clone https://github.com/mthom/scryer-prolog
|
||||
$> cd scryer-prolog
|
||||
$> cargo build --release
|
||||
$> cargo run [--release]
|
||||
```
|
||||
|
||||
The `--release` flag performs various optimizations, producing a
|
||||
faster executable.
|
||||
The optional `--release` flag will perform various optimizations,
|
||||
producing a faster executable.
|
||||
|
||||
After compilation, the executable `scryer-prolog` is available in the
|
||||
directory `target/release` and can be invoked to run the system.
|
||||
|
||||
### Via `cargo install`
|
||||
|
||||
#### From git
|
||||
|
||||
```
|
||||
cargo install --locked --git https://github.com/mthom/scryer-prolog.git
|
||||
```
|
||||
|
||||
Afterwards the `scryer-prolog` binary will be in the `$HOME/.cargo/bin` directory which is usually added to your PATH
|
||||
during the installation of the rust toolchain.
|
||||
|
||||
#### From Crates.io [](https://crates.io/crates/scryer-prolog) 
|
||||
|
||||
> [!NOTE]
|
||||
> The latest crates.io release can be significantly behind the version available in the git repository
|
||||
> The crates.io badge in this sections title is a link to the crates.io page.
|
||||
> The msrv badge in the section title references the minimum rust toolchain version required to compile the latest crates.io release
|
||||
|
||||
`scryer-prolog` is also release on crates.io and can be installed with
|
||||
|
||||
```
|
||||
cargo install --locked scryer-prolog
|
||||
```
|
||||
|
||||
### Caveats for Windows
|
||||
|
||||
On Windows, Scryer Prolog is easier to build inside a [MSYS2](https://www.msys2.org/)
|
||||
environment as some crates may require native C compilation. However,
|
||||
the resulting binary does not need MSYS2 to run. When executing Scryer in a shell, it is recommended to use a more advanced shell than mintty (the default MSYS2 shell). The [Windows Terminal](https://github.com/microsoft/terminal) works correctly.
|
||||
|
||||
To build a Windows Installer, you'll need first Scryer Prolog compiled in release mode, then, with WiX Toolset installed, execute:
|
||||
```
|
||||
candle.exe scryer-prolog.wxs
|
||||
light.exe scryer-prolog.wixobj
|
||||
```
|
||||
It will generate a very basic MSI file which installs the main executable and a shortcut in the Start Menu. It can be installed with a double-click. To uninstall, go to the Control Panel and uninstall as usual.
|
||||
|
||||
### Building WebAssembly
|
||||
|
||||
Scryer Prolog has basic WebAssembly support. You can follow `wasm-pack`'s [official instructions](https://rustwasm.github.io/docs/wasm-pack/quickstart.html) to install `wasm-pack` and build it in any way you like.
|
||||
|
||||
However, none of the [default features](https://doc.rust-lang.org/cargo/reference/features.html#the-default-feature) are currently supported. The preferred way of disabling them is passing [extra options](https://rustwasm.github.io/wasm-pack/book/commands/build.html#extra-options) to `wasm-pack`.
|
||||
|
||||
For example, if you want a minimal working package without using any bundler like `webpack`, you can do this:
|
||||
```
|
||||
wasm-pack build --target web -- --no-default-features
|
||||
```
|
||||
Then a `pkg` directory will be created, containing everything you need for a webapp. You can test whether the package is successfully built by creating an html file, adapted from `wasm-bindgen`'s [official example](https://rustwasm.github.io/wasm-bindgen/examples/without-a-bundler.html) like this:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Scryer Prolog - Sudoku Solver Example</title>
|
||||
<script type="module">
|
||||
import initScryer, { MachineBuilder } from "./pkg/scryer_prolog.js";
|
||||
|
||||
// Initialize Scryer Prolog with WASM
|
||||
const wasm = await fetch("./pkg/scryer_prolog_bg.wasm");
|
||||
const module = await WebAssembly.compile(await wasm.arrayBuffer());
|
||||
await initScryer(module);
|
||||
|
||||
// Set up the Prolog machine
|
||||
const machine = new MachineBuilder().build();
|
||||
|
||||
// Knowledge base: Sudoku rules and problem definition
|
||||
const kb = `
|
||||
:- use_module(library(format)).
|
||||
:- use_module(library(clpz)).
|
||||
:- use_module(library(lists)).
|
||||
|
||||
sudoku(Rows) :-
|
||||
length(Rows, 9), maplist(same_length(Rows), Rows),
|
||||
append(Rows, Vs), Vs ins 1..9,
|
||||
maplist(all_distinct, Rows),
|
||||
transpose(Rows, Columns),
|
||||
maplist(all_distinct, Columns),
|
||||
Rows = [A,B,C,D,E,F,G,H,I],
|
||||
blocks(A, B, C),
|
||||
blocks(D, E, F),
|
||||
blocks(G, H, I).
|
||||
|
||||
blocks([], [], []).
|
||||
blocks([A,B,C|T1], [D,E,F|T2], [G,H,I|T3]) :-
|
||||
all_distinct([A,B,C,D,E,F,G,H,I]),
|
||||
blocks(T1, T2, T3).
|
||||
|
||||
problem(1, [[_,_,_,_,_,_,_,_,_],
|
||||
[_,_,_,_,_,3,_,8,5],
|
||||
[_,_,1,_,2,_,_,_,_],
|
||||
[_,_,_,5,_,7,_,_,_],
|
||||
[_,_,4,_,_,_,1,_,_],
|
||||
[_,9,_,_,_,_,_,_,_],
|
||||
[5,_,_,_,_,_,_,7,3],
|
||||
[_,_,2,_,1,_,_,_,_],
|
||||
[_,_,_,_,4,_,_,_,9]]).
|
||||
`;
|
||||
|
||||
machine.consultModuleString("user", kb);
|
||||
|
||||
// Run the query
|
||||
const query = "problem(1, Rows), sudoku(Rows), maplist(portray_clause, Rows).";
|
||||
const answers = machine.runQuery(query);
|
||||
|
||||
const formattedSolutions = [];
|
||||
|
||||
// Format the answers
|
||||
for (const solution of answers) {
|
||||
const rows = solution.bindings["Rows"].list;
|
||||
|
||||
const grid = rows.map(row =>
|
||||
row.list.map(cell => cell.integer)
|
||||
);
|
||||
|
||||
const formatted = grid.map(row => `[${row.join(", ")}]`).join("\n");
|
||||
formattedSolutions.push(formatted);
|
||||
}
|
||||
|
||||
// Output results
|
||||
const solutionDiv = document.querySelector("#soduku-solution");
|
||||
for (const solution of formattedSolutions) {
|
||||
const newPre = document.createElement("pre");
|
||||
newPre.textContent = solution;
|
||||
solutionDiv.appendChild(newPre);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>Sudoku solver returns:</p>
|
||||
<div id="soduku-solution">
|
||||
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
Then you can serve it with your favorite http server like `python -m http.server` or `npx serve`, and access the page with your browser.
|
||||
|
||||
### Docker Install
|
||||
|
||||
Pre-built [Docker images are available on Docker Hub](https://hub.docker.com/r/mjt128/scryer-prolog/tags).
|
||||
The `latest` tag reflects the state on `master`, which might be unstable.
|
||||
There are also tags for Scryer releases 0.9.2 and up.
|
||||
Note though, that the base images are not kept up to date at the moment,
|
||||
so be wary of security vulnerabilities (see [#2646](https://github.com/mthom/scryer-prolog/issues/2646)).
|
||||
### Docker Install (All Platforms)
|
||||
|
||||
First, install [Docker](https://docs.docker.com/get-docker/) on Linux,
|
||||
Windows, or Mac.
|
||||
@@ -365,16 +203,9 @@ predicates it defines. For example, with the program shown above:
|
||||
; What = pure_world.
|
||||
```
|
||||
|
||||
Press `SPACE` to show further answers, if any exist. Press `RETURN`
|
||||
or `.` to abort the search and return to the
|
||||
toplevel prompt. Press `f` to see up to the next multiple of
|
||||
5 answers, and `a` to see all answers. Press `h` to show a help
|
||||
message.
|
||||
|
||||
Use `TAB` to complete atoms and predicate names in queries. For
|
||||
instance, after consulting the program above, typing `decl` followed
|
||||
by `TAB` yields `declarative_world`. Press `TAB` repeatedly
|
||||
to cycle through alternative completions.
|
||||
Press `SPACE` to show further answers, if any exist. Press `RETURN` or
|
||||
`.` to abort the search and return to the toplevel prompt.
|
||||
Press `h` to show a help message.
|
||||
|
||||
To quit Scryer Prolog, use the standard predicate `halt/0`:
|
||||
|
||||
@@ -382,35 +213,6 @@ To quit Scryer Prolog, use the standard predicate `halt/0`:
|
||||
?- halt.
|
||||
```
|
||||
|
||||
### Starting Scryer Prolog
|
||||
|
||||
Scryer Prolog can be started from the command line by specifying
|
||||
options, files and additional arguments. All components are optional:
|
||||
|
||||
<pre>
|
||||
scryer-prolog [OPTIONS] [FILES] [-- ARGUMENTS]
|
||||
</pre>
|
||||
|
||||
The supported options are:
|
||||
|
||||
```
|
||||
-h, --help Display help message
|
||||
-v, --version Print version information and exit
|
||||
-g, --goal GOAL Run the query GOAL after consulting files
|
||||
-f Fast startup. Do not load initialization file (~/.scryerrc)
|
||||
--no-add-history Prevent adding input to history file (~/.scryer_history)
|
||||
```
|
||||
|
||||
All specified Prolog files are consulted.
|
||||
|
||||
After Prolog files, application-specific arguments can be specified on
|
||||
the command line. These arguments can be accessed from within Prolog
|
||||
applications with the predicate `argv/1`, which yields the list
|
||||
of arguments represented as strings.
|
||||
|
||||
Prolog files can also be turned into *shell scripts* as explained in
|
||||
https://github.com/mthom/scryer-prolog/issues/2170#issuecomment-1821713993.
|
||||
|
||||
### Dynamic operators
|
||||
|
||||
Scryer supports dynamic operators. Using the built-in
|
||||
@@ -424,62 +226,11 @@ arithmetic operators with the usual precedences,
|
||||
|
||||
New operators can be defined using the `op` declaration.
|
||||
|
||||
### First instantiated argument indexing
|
||||
|
||||
Scryer Prolog indexes on the leftmost argument that is not a variable
|
||||
in all clauses of a predicate's definition. We call this strategy
|
||||
first *instantiated* argument indexing.
|
||||
|
||||
A key motivation for first instantiated argument indexing is to enable
|
||||
indexing for meta-predicates such as `maplist/N` and `foldl/N`, whose
|
||||
first argument is a partial goal that is a variable in the definition
|
||||
of these predicates and therefore cannot be used for indexing.
|
||||
|
||||
For example, a natural definition of `maplist/2` reads:
|
||||
|
||||
```
|
||||
maplist(_, []).
|
||||
maplist(Goal_1, [L|Ls]) :-
|
||||
call(Goal_1, L),
|
||||
maplist(Goal_1, Ls).
|
||||
```
|
||||
|
||||
In this case, first instantiated argument indexing automatically uses
|
||||
the *second* argument for indexing, and thus prevents choicepoints for
|
||||
calls with lists of fixed lengths (and deterministic goals).
|
||||
Conveniently, no auxiliary predicates with reordered arguments are
|
||||
needed to benefit from indexing in such cases.
|
||||
|
||||
Conventional first argument indexing naturally arises as a
|
||||
special case of this strategy, if the first argument is instantiated
|
||||
in any clause of a predicate's definition.
|
||||
|
||||
### Strings and partial strings
|
||||
|
||||
A very compact internal representation of *strings* is one of the key
|
||||
innovations of Scryer Prolog. This means that terms which appear as
|
||||
lists of characters to Prolog programs are stored in packed
|
||||
UTF-8 encoding by the engine.
|
||||
|
||||
Without this innovation, storing a list of characters in memory would
|
||||
use one WAM memory cell per character, one cell per list
|
||||
constructor, and one cell for each tail that occurs in the list. Since
|
||||
one cell takes 8 bytes in the WAM as implemented by
|
||||
Scryer Prolog, the packed representation yields an up to
|
||||
**24-fold reduction** of memory usage, and corresponding
|
||||
reduction of memory accesses when creating and processing
|
||||
strings.
|
||||
|
||||
Scryer Prolog's compact internal string representation makes it
|
||||
ideally suited for the use case Prolog was originally developed for:
|
||||
efficient and convenient text processing, especially with definite
|
||||
clause grammars (DCGs) as provided by
|
||||
[`library(dcgs)`](src/lib/dcgs.pl) and
|
||||
[`library(pio)`](src/lib/pio.pl) to transparently apply DCGs to files.
|
||||
|
||||
In Scryer Prolog, the default value of the Prolog flag `double_quotes`
|
||||
is `chars`, which is also the recommended setting. This means that
|
||||
lists of characters can be written as double-quoted strings, in the
|
||||
double-quoted strings are interpreted as lists of *characters*, in the
|
||||
tradition of Marseille Prolog.
|
||||
|
||||
For example, the following query succeeds:
|
||||
@@ -489,9 +240,15 @@ For example, the following query succeeds:
|
||||
true.
|
||||
```
|
||||
|
||||
This shows that the string `"abc"`, which is represented as a sequence
|
||||
of 3 bytes internally, appears to Prolog programs as a list of
|
||||
characters.
|
||||
Internally, strings are represented very compactly in packed
|
||||
UTF-8 encoding. A naive representation of strings as lists of
|
||||
characters would use one memory cell per character, one
|
||||
memory cell per list constructor, and one memory cell for
|
||||
each tail that occurs in the list. Since one memory cell takes
|
||||
8 bytes on 64-bit machines, the packed representation used by
|
||||
Scryer Prolog yields an up to **24-fold reduction** of
|
||||
memory usage, and corresponding reduction of memory accesses when
|
||||
creating and processing strings.
|
||||
|
||||
Scryer Prolog uses the same efficient encoding for *partial* strings,
|
||||
which appear to Prolog code as partial lists of characters. The
|
||||
@@ -514,58 +271,13 @@ the above example, posting <tt>Ls0 = [a,b,c|Ls]</tt> yields
|
||||
the exact same internal representation, and has the advantage that
|
||||
only the standard predicate `(=)/2` is used.
|
||||
|
||||
The efficient internal representation of strings and partial strings
|
||||
was first proposed and explained by Ulrich Neumerkel in
|
||||
issues [#24](https://github.com/mthom/scryer-prolog/issues/24)
|
||||
and [#95](https://github.com/mthom/scryer-prolog/issues/95), and
|
||||
Scryer Prolog is the first Prolog system that implements it.
|
||||
Definite clause grammars as provided by
|
||||
[`library(dcgs)`](src/lib/lists.pl), and the predicates from
|
||||
[`library(lists)`](src/lib/lists.pl), are ideally suited for reasoning
|
||||
about strings.
|
||||
|
||||
### Occurs check and cyclic terms
|
||||
|
||||
The *occurs check* is an element of algorithms that perform
|
||||
syntactic unification, causing the unification to fail if a variable
|
||||
is unified with a term that contains that variable as a proper
|
||||
subterm. For efficiency, the *occurs check* is omitted by default
|
||||
in Scryer Prolog and many other Prolog systems.
|
||||
|
||||
In Scryer Prolog, unifications which succeed only if the
|
||||
*occurs check* is omitted yield *cyclic terms*, also called
|
||||
*rational trees*. For example:
|
||||
|
||||
```
|
||||
?- X = f(X), Y = g(X,Y).
|
||||
X = f(X), Y = g(f(X),Y).
|
||||
```
|
||||
|
||||
The creation of cyclic terms often indicates a programming mistake in
|
||||
the formulation of Prolog predicates, and to obtain logically sound
|
||||
results it is desirable to either perform all unifications with
|
||||
*occurs check* enabled, or let Prolog throw an error if enabling
|
||||
the *occurs check* is necessary to prevent a unification.
|
||||
|
||||
Scryer Prolog supports this via the Prolog flag `occurs_check`. It can
|
||||
be set to one of the following values to obtain the desired behaviour:
|
||||
|
||||
- `false`
|
||||
Do not perform the *occurs check*. This is the default.
|
||||
- `true`
|
||||
Perform all unifications with the *occurs check* enabled.
|
||||
- `error`
|
||||
Yield an error if a unification is performed that the
|
||||
*occurs check* would have prevented.
|
||||
|
||||
Especially when starting with Prolog, we recommend to add the
|
||||
following directive to the `~/.scryerrc` configuration file so that
|
||||
programming mistakes in predicates that lead to the creation of cyclic
|
||||
terms are indicated by errors:
|
||||
|
||||
```
|
||||
:- set_prolog_flag(occurs_check, error).
|
||||
```
|
||||
|
||||
Scryer Prolog implements specialized reasoning to make unifications
|
||||
fast in many frequently occurring situations also if the
|
||||
*occurs check* is enabled.
|
||||
Partial strings were first proposed by Ulrich Neumerkel in issue
|
||||
[#95](https://github.com/mthom/scryer-prolog/issues/95).
|
||||
|
||||
### Tabling (SLG resolution)
|
||||
|
||||
@@ -675,10 +387,6 @@ The modules that ship with Scryer Prolog are also called
|
||||
file, reading lazily only as much as is needed. Due to the compact
|
||||
internal string representation, also extremely large files can be
|
||||
efficiently processed with Scryer Prolog in this way.
|
||||
`phrase_to_file/2` and `phrase_to_stream/2` write lists of
|
||||
characters described by DCGs to files and streams, respectively.
|
||||
* [`lambda`](src/lib/lambda.pl)
|
||||
Lambda expressions to simplify higher order programming.
|
||||
* [`charsio`](src/lib/charsio.pl) Various predicates that are useful
|
||||
for parsing and reasoning about characters, notably `char_type/2` to
|
||||
classify characters according to their type, and conversion
|
||||
@@ -722,7 +430,6 @@ The modules that ship with Scryer Prolog are also called
|
||||
Probabilistic predicates and random number generators.
|
||||
* [`http/http_open`](src/lib/http/http_open.pl) Open a stream to
|
||||
read answers from web servers. HTTPS is also supported.
|
||||
* [`http/http_server`](src/lib/http/http_server.pl) Runs a HTTP/1.1 and HTTP/2.0 web server. Uses [Warp](https://github.com/seanmonstar/warp) as a backend. Supports some query and form handling.
|
||||
* [`sgml`](src/lib/sgml.pl)
|
||||
`load_html/3` and `load_xml/3` represent HTML and XML documents
|
||||
as Prolog terms for convenient and efficient reasoning. Use
|
||||
@@ -731,28 +438,19 @@ The modules that ship with Scryer Prolog are also called
|
||||
* [`csv`](src/lib/csv.pl)
|
||||
`parse_csv//1` and `parse_csv//2` can be used with [`phrase_from_file/2`](src/lib/pio.pl)
|
||||
or [`phrase/2`](src/lib/dcgs.pl) to parse csv
|
||||
* [`serialization/abnf`](src/lib/serialization/abnf.pl)
|
||||
DCGs describing the
|
||||
[ABNF grammar core (RFC 5234)](https://tools.ietf.org/html/rfc5234#appendix-B.1),
|
||||
which is used to describe many [IETF](https://www.ietf.org/standards/rfcs/)
|
||||
syntaxes, such as [HTTP v1.1](https://www.rfc-editor.org/rfc/rfc7230.html#page-82),
|
||||
[SMTP](https://www.rfc-editor.org/rfc/rfc5321.html),
|
||||
[iCalendar](https://www.rfc-editor.org/rfc/rfc5545.html), and more.
|
||||
* [`serialization/json`](src/lib/serialization/json.pl)
|
||||
`json_chars//1` can be used with [`phrase_from_file/2`](src/lib/pio.pl)
|
||||
or [`phrase/2`](src/lib/dcgs.pl) to parse and generate
|
||||
[JSON](https://www.json.org/json-en.html).
|
||||
* [`xpath`](src/lib/xpath.pl)
|
||||
The predicate `xpath/3` is used for convenient reasoning about HTML
|
||||
and XML documents, inspired by the XPath language. This library
|
||||
is often used together with [`library(sgml)`](src/lib/sgml.pl).
|
||||
* [`sockets`](src/lib/sockets.pl)
|
||||
Predicates for opening and accepting TCP connections as streams.
|
||||
TLS negotiation is performed via the option `tls(true)` in
|
||||
`socket_client_open/3`, yielding secure encrypted connections.
|
||||
* [`os`](src/lib/os.pl)
|
||||
Predicates for reasoning about environment variables.
|
||||
* [`iso_ext`](src/lib/iso_ext.pl)
|
||||
Conforming extensions to and candidates for inclusion in the Prolog
|
||||
ISO standard, such as `setup_call_cleanup/3`, `call_nth/2` and
|
||||
ISO standard, such as `setup_call_cleanup/3` and
|
||||
`call_with_inference_limit/3`.
|
||||
* [`crypto`](src/lib/crypto.pl)
|
||||
Cryptographically secure random numbers and hashes, HMAC-based key
|
||||
@@ -760,17 +458,6 @@ The modules that ship with Scryer Prolog are also called
|
||||
public key signatures and signature verification with Ed25519,
|
||||
ECDH key exchange over Curve25519 (X25519), authenticated symmetric
|
||||
encryption with ChaCha20-Poly1305, and reasoning about elliptic curves.
|
||||
* [`process`](src/lib/process.pl)
|
||||
Create and manage parallel processes.
|
||||
* [`uuid`](src/lib/uuid.pl) UUIDv4 generation and hex representation
|
||||
* [`tls`](src/lib/tls.pl)
|
||||
Predicates for negotiating TLS connections explicitly.
|
||||
* [`numerics/special_functions`](src/lib/numerics/special_functions.pl)
|
||||
Predicates for erf, gamma, beta, and related special functions.
|
||||
* [`ugraphs`](src/lib/ugraphs.pl) Graph manipulation library
|
||||
* [`simplex`](src/lib/simplex.pl) Providing `assignment/2`,
|
||||
`transportation/4` and other predicates for solving linear
|
||||
programming problems.
|
||||
|
||||
To use predicates provided by the `lists` library, write:
|
||||
|
||||
@@ -829,89 +516,3 @@ For example, a sensible starting point for `~/.scryerrc` is:
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(reif)).
|
||||
```
|
||||
|
||||
### Development environment
|
||||
|
||||
To write and edit Prolog programs, we recommend
|
||||
[GNU Emacs](https://www.gnu.org/software/emacs/) with the
|
||||
[Prolog mode](https://bruda.ca/emacs/prolog_mode_for_emacs)
|
||||
maintained by Stefan Bruda.
|
||||
|
||||
Use [ediprolog](https://www.metalevel.at/ediprolog/) to consult
|
||||
Prolog code and evaluate Prolog queries in arbitrary
|
||||
Emacs buffers.
|
||||
|
||||
Emacs definitions that show Prolog terms as trees are available
|
||||
in [tools](tools).
|
||||
|
||||
To *debug* Prolog code, we recommend the predicates from
|
||||
[**`library(debug)`**](src/lib/debug.pl), most notably:
|
||||
|
||||
- `(*)/1` to *"generalize away"* a Prolog goal. Use it to debug
|
||||
unexpected failures by generalizing your definitions until they
|
||||
succeed. Simply place `*` in front of a goal to generalize it away.
|
||||
- `($)/1` to emit a *trace* of the execution, showing when a goal
|
||||
is invoked, and when it has succeeded. Place `$` in front of a goal
|
||||
to emit this information for that goal.
|
||||
|
||||
This way of debugging Prolog code has several major benefits, such as:
|
||||
It stays close to the actual Prolog code under consideration, it does
|
||||
not need additional tools and formalisms for its application, and
|
||||
further, it encourages declarative reasoning that can in principle
|
||||
also be performed automatically.
|
||||
|
||||
## Applications
|
||||
|
||||
Scryer Prolog's strong commitment to the Prolog ISO standard makes it
|
||||
ideally suited for use in corporations and government agencies
|
||||
that are subject to strict regulations pertaining to interoperability,
|
||||
standards compliance and warranty.
|
||||
|
||||
Successful existing applications of Scryer Prolog include:
|
||||
|
||||
- [DocLog](https://github.com/aarroyoc/doclog) which generates
|
||||
Scryer's own documentation and homepage
|
||||
- [Grants4Companies](https://arxiv.org/abs/2406.15293): reasoning
|
||||
about business grants in the Austrian public administration
|
||||
- parts of the [precautionary](https://github.com/dcnorris/precautionary/tree/main/exec/prolog)
|
||||
package for the analysis of dose-escalation trials in the
|
||||
safety-critical and highly regulated domain of oncology
|
||||
trial design, described in [*An Executable Specification of
|
||||
Oncology Dose-Escalation Protocols with Prolog*](https://arxiv.org/abs/2402.08334)
|
||||
and culminating in [**DEDUCTION**](https://codeberg.org/dcnorris/DEDUCTION)
|
||||
- the core tax engine for VAT reasoning and compliance of the
|
||||
Belgian company [VATmiraal](https://vatmiraal.be/)
|
||||
- semantic reasoning and queries in [AD4M](https://github.com/coasys/ad4m),
|
||||
an agent-centric distributed application meta-ontology.
|
||||
|
||||
Scryer Prolog is also very well suited for teaching and learning
|
||||
Prolog, and for testing syntactic conformance and hence portability of
|
||||
existing Prolog programs.
|
||||
|
||||
## Scryer Prolog Meetups
|
||||
|
||||
Scryer Prolog Meetups are an excellent opportunity to present and get
|
||||
to know the latest developments in Scryer Prolog and its
|
||||
applications, to exchange ideas about current plans and
|
||||
future directions, and to discuss projects and visions
|
||||
in person.
|
||||
|
||||
- [Scryer Prolog Meetup 2023](https://hsd-pbsa.de/veranstaltung/scryer-prolog-meetup-2023/)
|
||||
in Düsseldorf, Germany. Its [announcement](https://github.com/mthom/scryer-prolog/discussions/1813)
|
||||
and [discussion](https://github.com/mthom/scryer-prolog/discussions/2160).
|
||||
- [Scryer Prolog Meetup 2024](https://www.digitalaustria.gv.at/wissenswertes/events/scryerprologmeetup2024)
|
||||
in Vienna, Austria. Its [announcement and discussion](https://github.com/mthom/scryer-prolog/discussions/2377).
|
||||
- [Scryer Prolog Meetup 2025](https://hsd-pbsa.de/veranstaltung/scryer-prolog-meetup-2025/)
|
||||
in Düsseldorf, Germany. Its [announcement and discussion](https://github.com/mthom/scryer-prolog/discussions/2948).
|
||||
- **Attend, present and represent:** The [Scryer Prolog Meetup 2026](https://www.digitalaustria.gv.at/wissenswertes/events/scryerprologmeetup2026.html)
|
||||
will take place on Oct. 24th and 25th 2026 in Vienna, Austria.
|
||||
Its [announcement](https://github.com/mthom/scryer-prolog/discussions/3327).
|
||||
|
||||
## Support and discussions
|
||||
|
||||
If Scryer Prolog crashes or yields unexpected errors, consider filing
|
||||
an [issue](https://github.com/mthom/scryer-prolog/issues).
|
||||
|
||||
To get in touch with the Scryer Prolog community, participate in
|
||||
[discussions](https://github.com/mthom/scryer-prolog/discussions)
|
||||
or visit our #scryer IRC channel on [Libera](https://libera.chat)!
|
||||
|
||||
35
_typos.toml
35
_typos.toml
@@ -1,35 +0,0 @@
|
||||
# config for https://github.com/crate-ci/typos
|
||||
|
||||
[default]
|
||||
# example from https://github.com/crate-ci/typos/blob/master/docs/reference.md#example-configurations
|
||||
extend-ignore-re = [
|
||||
"(#|//)\\s*spellchecker:ignore-next-line\\n.*"
|
||||
]
|
||||
|
||||
# correct word key to value
|
||||
# can be used to ignore a typo by adding an entry <typo> = "<typo>"
|
||||
[default.extend-words]
|
||||
|
||||
# correct identifier key to value
|
||||
# can be used to ignore a typo by adding an entry <typo> = "<typo>"
|
||||
[default.extend-identifiers]
|
||||
interm = "interm"
|
||||
IntermReg = "IntermReg"
|
||||
|
||||
[type.rust]
|
||||
extend-glob = [ "*.rs" ]
|
||||
[type.rust.extend-identifiers]
|
||||
consts = "consts" # std::{f32,f64}::consts
|
||||
|
||||
|
||||
[type.prolog]
|
||||
extend-glob = ["*.pl"]
|
||||
check-file = false
|
||||
|
||||
[type.stdout]
|
||||
extend-glob = ["*.stdout"]
|
||||
check-file = false
|
||||
|
||||
|
||||
[files]
|
||||
extend-exclude = ["lib_integration_test_commands.txt"]
|
||||
@@ -1,95 +0,0 @@
|
||||
# About benches
|
||||
|
||||
The `benches` directory contains benchmarks that test scryer-prolog performance.
|
||||
|
||||
Benchmarks are run via two harnesses:
|
||||
|
||||
* `criterion` - criterion performs statistical analysis of benchmark runs and is
|
||||
great for benchmarking locally.
|
||||
* `iai-callgrind` - this runs the benchmark with callgrind, which is able to
|
||||
precisely track the number of instructions executed during the run. This is
|
||||
especially helpful in a public CI runner context where neighboring VMs can
|
||||
cause a very high wall time variance. While instructions executed is only
|
||||
correlated with the desired metric (wall time), this is a good tradeoff for CI
|
||||
where that metric is unreliable.
|
||||
|
||||
Run benchmarks with the following commands:
|
||||
|
||||
```
|
||||
cargo bench --bench run_criterion
|
||||
|
||||
# run a particular criterion benchmark
|
||||
cargo bench --bench run_criterion -- <benchmark_name>
|
||||
|
||||
# run in profiling mode which outputs flamegraphs. Set profile time in seconds:
|
||||
cargo bench --bench run_criterion -- --profile-time <time>
|
||||
|
||||
# to run iai, you need valgrind installed and to install iai-callgrind-runner
|
||||
# at the same version as is in Cargo.toml:
|
||||
cargo install iai-callgrind-runner --version 0.7.3
|
||||
|
||||
cargo bench --bench run_iai
|
||||
```
|
||||
|
||||
For consistency, both runners -- `run_iai.rs` and `run_criterion.rs` -- import
|
||||
the same setup code from `setup.rs`.
|
||||
|
||||
## Setup
|
||||
|
||||
`setup.rs` contains the setup code to run benchmarks. `fn prolog_benches()` at
|
||||
the top of the file is where the benchmarks are defined.
|
||||
|
||||
Benchmarks are organized around running queries against a prolog module file.
|
||||
Before a benchmark starts, `benchmark.setup()` is called which reads the module
|
||||
file and initializes a new `scryer_prolog::machine::Machine`.
|
||||
|
||||
Each benchmark measurement is done by running a query against the machine. In
|
||||
the case of criterion each query is run many times, in the case of iai it's run
|
||||
once.
|
||||
|
||||
## Adding benchmarks
|
||||
|
||||
This design is meant to support defining lots of benchmarks.
|
||||
|
||||
To add a new benchmark:
|
||||
|
||||
* Add a new file `benches/[module].pl` that contains setup prolog code. Import
|
||||
libraries, define predicates, etc.
|
||||
* Add a new section in `setup.rs::prolog_benchmarks()` that refers to to the
|
||||
file and write a query to be benchmarked.
|
||||
* If the query mutates the machine, then use `Strategy::Fresh` so the criterion
|
||||
benchmark will recreate a new machine for each benchmark run, otherwise use
|
||||
`Strategy::Reuse` which has lower overhead. (This is not used by the iai
|
||||
benchmark because it only runs once anyway.)
|
||||
|
||||
Some tips:
|
||||
|
||||
* The goal of benchmarking is to know if a library or engine change improved
|
||||
performance or not.
|
||||
* Once a benchmark is defined and named, avoid changing it's definition. If a
|
||||
benchmark needs to change to be more useful, give the new definition a new
|
||||
name instead. This will prevent charts from showing wild changes in
|
||||
performance just because the definition changed (see previous).
|
||||
* Aim for queries to execute in less than 0.5s realtime. Longer runtimes make it
|
||||
easier for humans to see big differences, but benchmarks either run 10x slower
|
||||
(iai) or execute repeatedly to attain statistical significance (criterion) and
|
||||
in both cases benchmarking queries that take longer than about 0.5s are
|
||||
cumbersome to run.
|
||||
* Consider that the library runtime actually parses the text output of the top
|
||||
level. So don't use custom outputs or it will fail to parse. Also keep the
|
||||
output small so it doesn't just benchmark the output parsing code.
|
||||
* DO test the output of the benchmark run, we don't want to count broken
|
||||
benchmarks.
|
||||
|
||||
## CI
|
||||
|
||||
Both benchmark harnesses are run in `.github/workflows/ci.yaml` in the `report`
|
||||
job, and the results are published as build artifacts.
|
||||
|
||||
## Todo
|
||||
|
||||
- [ ] Currently, the execution time to load a module is not benchmarked. It
|
||||
would be nice to have at least one benchmark for loading a module (probably a
|
||||
big one).
|
||||
- [ ] Write a new action that downloads the test and benchmark results
|
||||
artifacts, plots them over time, and publishes a report to github pages.
|
||||
File diff suppressed because one or more lines are too long
130
benches/edges.pl
130
benches/edges.pl
@@ -1,130 +0,0 @@
|
||||
:- use_module(library(clpb)).
|
||||
:- use_module(library(assoc)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(pairs)).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Contiguous United States and DC as they appear in SGB:
|
||||
http://www-cs-faculty.stanford.edu/~uno/sgb.html
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
edge(al, fl).
|
||||
edge(al, ga).
|
||||
edge(al, ms).
|
||||
edge(al, tn).
|
||||
edge(ar, la).
|
||||
edge(ar, mo).
|
||||
edge(ar, ms).
|
||||
edge(ar, ok).
|
||||
edge(ar, tn).
|
||||
edge(ar, tx).
|
||||
edge(az, ca).
|
||||
edge(az, nm).
|
||||
edge(az, nv).
|
||||
edge(az, ut).
|
||||
edge(ca, nv).
|
||||
edge(ca, or).
|
||||
edge(co, ks).
|
||||
edge(co, ne).
|
||||
edge(co, nm).
|
||||
edge(co, ok).
|
||||
edge(co, ut).
|
||||
edge(co, wy).
|
||||
edge(ct, ma).
|
||||
edge(ct, ny).
|
||||
edge(ct, ri).
|
||||
edge(dc, md).
|
||||
edge(dc, va).
|
||||
edge(de, md).
|
||||
edge(de, nj).
|
||||
edge(de, pa).
|
||||
edge(fl, ga).
|
||||
edge(ga, nc).
|
||||
edge(ga, sc).
|
||||
edge(ga, tn).
|
||||
edge(ia, il).
|
||||
edge(ia, mn).
|
||||
edge(ia, mo).
|
||||
edge(ia, ne).
|
||||
edge(ia, sd).
|
||||
edge(ia, wi).
|
||||
edge(id, mt).
|
||||
edge(id, nv).
|
||||
edge(id, or).
|
||||
edge(id, ut).
|
||||
edge(id, wa).
|
||||
edge(id, wy).
|
||||
edge(il, in).
|
||||
edge(il, ky).
|
||||
edge(il, mo).
|
||||
edge(il, wi).
|
||||
edge(in, ky).
|
||||
edge(in, mi).
|
||||
edge(in, oh).
|
||||
edge(ks, mo).
|
||||
edge(ks, ne).
|
||||
edge(ks, ok).
|
||||
edge(ky, mo).
|
||||
edge(ky, oh).
|
||||
edge(ky, tn).
|
||||
edge(ky, va).
|
||||
edge(ky, wv).
|
||||
edge(la, ms).
|
||||
edge(la, tx).
|
||||
edge(ma, nh).
|
||||
edge(ma, ny).
|
||||
edge(ma, ri).
|
||||
edge(ma, vt).
|
||||
edge(md, pa).
|
||||
edge(md, va).
|
||||
edge(md, wv).
|
||||
edge(me, nh).
|
||||
edge(mi, oh).
|
||||
edge(mi, wi).
|
||||
edge(mn, nd).
|
||||
edge(mn, sd).
|
||||
edge(mn, wi).
|
||||
edge(mo, ne).
|
||||
edge(mo, ok).
|
||||
edge(mo, tn).
|
||||
edge(ms, tn).
|
||||
edge(mt, nd).
|
||||
edge(mt, sd).
|
||||
edge(mt, wy).
|
||||
edge(nc, sc).
|
||||
edge(nc, tn).
|
||||
edge(nc, va).
|
||||
edge(nd, sd).
|
||||
edge(ne, sd).
|
||||
edge(ne, wy).
|
||||
edge(nh, vt).
|
||||
edge(nj, ny).
|
||||
edge(nj, pa).
|
||||
edge(nm, ok).
|
||||
edge(nm, tx).
|
||||
edge(nv, or).
|
||||
edge(nv, ut).
|
||||
edge(ny, pa).
|
||||
edge(ny, vt).
|
||||
edge(oh, pa).
|
||||
edge(oh, wv).
|
||||
edge(ok, tx).
|
||||
edge(or, wa).
|
||||
edge(pa, wv).
|
||||
edge(sd, wy).
|
||||
edge(tn, va).
|
||||
edge(ut, wy).
|
||||
edge(va, wv).
|
||||
|
||||
independent_set(G, *(NBs)) :-
|
||||
findall(U-V, (edge(U, V),G@<U), Edges),
|
||||
setof(U, V^(member(U-V, Edges);member(V-U, Edges)), Nodes),
|
||||
pairs_keys_values(Pairs, Nodes, _),
|
||||
list_to_assoc(Pairs, Assoc),
|
||||
maplist(not_both(Assoc), Edges, NBs).
|
||||
|
||||
not_both(Assoc, U-V, ~BU + ~BV) :-
|
||||
get_assoc(U, Assoc, BU),
|
||||
get_assoc(V, Assoc, BV).
|
||||
|
||||
independent_set_count(G, Count) :- independent_set(G, Sat), sat_count(Sat, Count).
|
||||
@@ -1,2 +0,0 @@
|
||||
:- use_module(library(between)).
|
||||
run_numlist(Upper, Head) :- numlist(1, Upper, L), L = [Head|_].
|
||||
@@ -1,46 +0,0 @@
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use pprof::criterion::{Output, PProfProfiler};
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
mod setup;
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
fn bench_criterion(c: &mut Criterion) {
|
||||
for (&name, bench) in setup::prolog_benches().iter() {
|
||||
match bench.strategy {
|
||||
setup::Strategy::Fresh => c.bench_function(name, |b| {
|
||||
b.iter_batched(|| bench.setup(), |mut r| r(), BatchSize::LargeInput)
|
||||
}),
|
||||
setup::Strategy::Reuse => c.bench_function(name, |b| b.iter(bench.setup())),
|
||||
};
|
||||
}
|
||||
}
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn config() -> Criterion {
|
||||
Criterion::default()
|
||||
.sample_size(20)
|
||||
.with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn config() -> Criterion {
|
||||
Criterion::default().sample_size(20)
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
criterion_group!(
|
||||
name = benches;
|
||||
config = config();
|
||||
targets = bench_criterion
|
||||
);
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
criterion_main!(benches);
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
|
||||
fn main() {}
|
||||
@@ -1,38 +0,0 @@
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
mod setup;
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
mod iai {
|
||||
use iai_callgrind::{library_benchmark, library_benchmark_group, main};
|
||||
|
||||
use scryer_prolog::LeafAnswer;
|
||||
|
||||
use super::setup;
|
||||
|
||||
#[library_benchmark]
|
||||
#[bench::count_edges(setup::prolog_benches()["count_edges"].setup())]
|
||||
#[bench::numlist(setup::prolog_benches()["numlist"].setup())]
|
||||
#[bench::csv_codename(setup::prolog_benches()["csv_codename"].setup())]
|
||||
fn bench(mut run: impl FnMut() -> Vec<LeafAnswer>) -> Vec<LeafAnswer> {
|
||||
run()
|
||||
}
|
||||
|
||||
library_benchmark_group!(
|
||||
name = benches;
|
||||
benchmarks = bench
|
||||
);
|
||||
|
||||
main!(library_benchmark_groups = benches);
|
||||
|
||||
pub fn call_main() {
|
||||
main()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
fn main() {
|
||||
iai::call_main();
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
|
||||
fn main() {}
|
||||
140
benches/setup.rs
140
benches/setup.rs
@@ -1,140 +0,0 @@
|
||||
use std::{collections::BTreeMap, fs, path::Path};
|
||||
|
||||
use maplit::btreemap;
|
||||
use scryer_prolog::{LeafAnswer, Machine, MachineBuilder, Term};
|
||||
|
||||
pub fn prolog_benches() -> BTreeMap<&'static str, PrologBenchmark> {
|
||||
[
|
||||
(
|
||||
"count_edges", // name of the benchmark
|
||||
"benches/edges.pl", // name of the prolog module file to load. use the same file in multiple benchmarks
|
||||
"independent_set_count(ky, Count).", // query to benchmark in the context of the loaded module. consider making the query adjustable to tune the run time to ~0.1s
|
||||
Strategy::Reuse,
|
||||
btreemap! { "Count" => Term::integer(2869176) },
|
||||
),
|
||||
(
|
||||
"numlist",
|
||||
"benches/numlist.pl",
|
||||
"run_numlist(1000000, Head).",
|
||||
Strategy::Reuse,
|
||||
btreemap! { "Head" => Term::integer(1) },
|
||||
),
|
||||
(
|
||||
"csv_codename",
|
||||
"benches/csv.pl",
|
||||
"get_codename(\"0020\",Name).",
|
||||
Strategy::Reuse,
|
||||
btreemap! { "Name" => Term::string("SPACE") },
|
||||
),
|
||||
]
|
||||
.map(|b| {
|
||||
(
|
||||
b.0,
|
||||
PrologBenchmark {
|
||||
name: b.0,
|
||||
filename: b.1,
|
||||
query: b.2,
|
||||
strategy: b.3,
|
||||
bindings: b.4,
|
||||
},
|
||||
)
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
pub enum Strategy {
|
||||
#[allow(dead_code)]
|
||||
Fresh,
|
||||
Reuse,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct PrologBenchmark {
|
||||
pub name: &'static str,
|
||||
pub filename: &'static str,
|
||||
pub query: &'static str,
|
||||
pub strategy: Strategy,
|
||||
pub bindings: BTreeMap<&'static str, Term>,
|
||||
}
|
||||
|
||||
impl PrologBenchmark {
|
||||
pub fn make_machine(&self) -> Machine {
|
||||
let program = fs::read_to_string(self.filename).unwrap();
|
||||
let module_name = Path::new(self.filename)
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap();
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
machine.load_module_string(module_name, program);
|
||||
machine
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
pub fn setup(&self) -> impl FnMut() -> Vec<LeafAnswer> + use<> {
|
||||
let mut machine = self.make_machine();
|
||||
let query = self.query;
|
||||
move || {
|
||||
use criterion::black_box;
|
||||
black_box(
|
||||
machine
|
||||
.run_query(black_box(query))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
#[test]
|
||||
fn validate_benchmarks() {
|
||||
use super::prolog_benches;
|
||||
use scryer_prolog::LeafAnswer;
|
||||
use std::{fmt::Write, fs};
|
||||
|
||||
struct BenchResult {
|
||||
pub name: &'static str,
|
||||
pub setup_inference_count: u64,
|
||||
pub query_inference_count: u64,
|
||||
}
|
||||
|
||||
let mut results: Vec<BenchResult> = vec![];
|
||||
|
||||
for (_, r) in prolog_benches() {
|
||||
let mut machine = r.make_machine();
|
||||
let setup_inference_count = machine.get_inference_count();
|
||||
|
||||
let result: Vec<_> = machine
|
||||
.run_query(r.query)
|
||||
.collect::<Result<_, _>>()
|
||||
.unwrap();
|
||||
let query_inference_count = machine.get_inference_count() - setup_inference_count;
|
||||
|
||||
let expected = [LeafAnswer::from_bindings(r.bindings.clone())];
|
||||
assert_eq!(result, expected, "validating benchmark {}", r.name);
|
||||
|
||||
results.push(BenchResult {
|
||||
name: r.name,
|
||||
setup_inference_count,
|
||||
query_inference_count,
|
||||
})
|
||||
}
|
||||
|
||||
let mut json: String = Default::default();
|
||||
json.push('[');
|
||||
for r in results {
|
||||
json.push('\n');
|
||||
write!(
|
||||
json,
|
||||
r#"{{"name":"{}","setup_inference_count":{},"query_inference_count":{}}},"#,
|
||||
r.name, r.setup_inference_count, r.query_inference_count
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
json.pop(); // trailing comma
|
||||
json.push_str("\n]");
|
||||
fs::write("target/benchmark_inference_counts.json", json).expect("Unable to write file");
|
||||
}
|
||||
}
|
||||
55
build.rs
Normal file
55
build.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
extern crate indexmap;
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
fn find_prolog_files(libraries: &mut File, prefix: &str, current_dir: &Path) {
|
||||
let entries = match current_dir.read_dir() {
|
||||
Ok(entries) => entries,
|
||||
Err(_) => return,
|
||||
};
|
||||
for entry in entries.filter_map(Result::ok).map(|e| e.path()) {
|
||||
if entry.is_dir() {
|
||||
if let Some(file_name) = entry.file_name() {
|
||||
let new_prefix =
|
||||
prefix.to_owned() + file_name.to_str().unwrap() + "/";
|
||||
find_prolog_files(libraries, &new_prefix, &entry);
|
||||
}
|
||||
} else if entry.is_file() {
|
||||
let ext = std::ffi::OsStr::new("pl");
|
||||
if entry.extension() == Some(ext) {
|
||||
let contain =
|
||||
String::from_utf8(fs::read(&entry).unwrap()).unwrap();
|
||||
let name = entry.file_stem().unwrap().to_str().unwrap();
|
||||
let line = format!(
|
||||
" m.insert(\"{}\",\n{:?});\n",
|
||||
prefix.to_owned() + name,
|
||||
contain
|
||||
);
|
||||
|
||||
libraries.write_all(line.as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let dest_path = Path::new(&out_dir).join("libraries.rs");
|
||||
|
||||
let mut libraries = File::create(&dest_path).unwrap();
|
||||
let lib_path = Path::new("src/lib");
|
||||
|
||||
libraries
|
||||
.write_all(
|
||||
b"ref_thread_local! {
|
||||
pub static managed LIBRARIES: IndexMap<&'static str, &'static str> = {
|
||||
let mut m = IndexMap::new();\n",
|
||||
)
|
||||
.unwrap();
|
||||
find_prolog_files(&mut libraries, "", &lib_path);
|
||||
libraries.write_all(b"\n m\n };\n}\n").unwrap();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
121
build/main.rs
121
build/main.rs
@@ -1,121 +0,0 @@
|
||||
mod instructions_template;
|
||||
mod static_string_indexing;
|
||||
|
||||
use instructions_template::generate_instructions_rs;
|
||||
use static_string_indexing::index_static_strings;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::MAIN_SEPARATOR_STR;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
fn find_prolog_files(path_prefix: &str, current_dir: &Path) -> Vec<(String, PathBuf)> {
|
||||
// use a BTreeMap to get a stable order independent of fs enumeration order
|
||||
let mut libraries = BTreeMap::new();
|
||||
|
||||
let entries = match current_dir.read_dir() {
|
||||
Ok(entries) => entries,
|
||||
Err(_) => return vec![],
|
||||
};
|
||||
|
||||
for entry in entries.filter_map(Result::ok).map(|e| e.path()) {
|
||||
if entry.is_dir() {
|
||||
if let Some(file_name) = entry.file_name() {
|
||||
let file_name = file_name.to_str().unwrap();
|
||||
let new_path_prefix = format!("{path_prefix}{file_name}/");
|
||||
let new_libs = find_prolog_files(&new_path_prefix, &entry);
|
||||
libraries.extend(new_libs);
|
||||
}
|
||||
} else if entry.is_file() {
|
||||
let ext = std::ffi::OsStr::new("pl");
|
||||
if entry.extension() == Some(ext) {
|
||||
let name = entry.file_stem().unwrap().to_str().unwrap();
|
||||
let lib_name = format!("{path_prefix}{name}");
|
||||
|
||||
libraries.insert(lib_name, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
libraries.into_iter().collect()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let has_rustfmt = Command::new("rustfmt")
|
||||
.arg("--version")
|
||||
.stdin(Stdio::inherit())
|
||||
.status()
|
||||
.is_ok();
|
||||
|
||||
if !has_rustfmt {
|
||||
println!("Failed to run rustfmt, will skip formatting generated files.")
|
||||
}
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let dest_path = Path::new(&out_dir).join("libraries.rs");
|
||||
|
||||
let mut libraries = File::create(dest_path).unwrap();
|
||||
let lib_path = Path::new("src").join("lib");
|
||||
|
||||
let constants = find_prolog_files("", &lib_path);
|
||||
|
||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
||||
|
||||
writeln!(libraries, "{{").unwrap();
|
||||
for (name, lib_path) in constants {
|
||||
let path = format!("{}{}", MAIN_SEPARATOR_STR, lib_path.display());
|
||||
writeln!(
|
||||
libraries,
|
||||
"m.insert(\"{name}\", include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), {path:?})));"
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
writeln!(libraries, "}}").unwrap();
|
||||
|
||||
let instructions_path = Path::new(&out_dir).join("instructions.rs");
|
||||
let mut instructions_file = File::create(&instructions_path).unwrap();
|
||||
|
||||
let quoted_output = generate_instructions_rs();
|
||||
|
||||
instructions_file
|
||||
.write_all(quoted_output.to_string().as_bytes())
|
||||
.unwrap();
|
||||
|
||||
if has_rustfmt {
|
||||
format_generated_file(instructions_path.as_path());
|
||||
}
|
||||
|
||||
let static_atoms_path = Path::new(&out_dir).join("static_atoms.rs");
|
||||
let mut static_atoms_file = File::create(&static_atoms_path).unwrap();
|
||||
|
||||
let quoted_output = index_static_strings(&instructions_path);
|
||||
|
||||
static_atoms_file
|
||||
.write_all(quoted_output.to_string().as_bytes())
|
||||
.unwrap();
|
||||
|
||||
if has_rustfmt {
|
||||
format_generated_file(static_atoms_path.as_path());
|
||||
}
|
||||
|
||||
println!("cargo:rerun-if-changed=src/");
|
||||
}
|
||||
|
||||
fn format_generated_file(path: &Path) {
|
||||
Command::new("rustfmt")
|
||||
.arg(path.as_os_str())
|
||||
.spawn()
|
||||
.unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"{}: rustfmt was detected as available, but failed to format generated file '{}'",
|
||||
err,
|
||||
path.display()
|
||||
);
|
||||
})
|
||||
.wait()
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use syn::parse::*;
|
||||
use syn::visit::*;
|
||||
use syn::*;
|
||||
|
||||
struct StaticStrVisitor {
|
||||
static_strs: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl StaticStrVisitor {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
static_strs: BTreeSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MacroFnArgs {
|
||||
args: Vec<Expr>,
|
||||
}
|
||||
|
||||
struct ReadHeapCellExprAndArms {
|
||||
expr: Expr,
|
||||
arms: Vec<Arm>,
|
||||
}
|
||||
|
||||
impl Parse for ReadHeapCellExprAndArms {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut arms = vec![];
|
||||
let expr = input.parse()?;
|
||||
|
||||
input.parse::<Token![,]>()?;
|
||||
arms.push(input.parse()?);
|
||||
|
||||
while !input.is_empty() {
|
||||
let _ = input.parse::<Token![,]>();
|
||||
arms.push(input.parse()?);
|
||||
}
|
||||
|
||||
Ok(ReadHeapCellExprAndArms { expr, arms })
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for MacroFnArgs {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut args = vec![];
|
||||
|
||||
if !input.is_empty() {
|
||||
args.push(input.parse()?);
|
||||
}
|
||||
|
||||
while !input.is_empty() {
|
||||
let _ = input.parse::<Token![,]>();
|
||||
args.push(input.parse()?);
|
||||
}
|
||||
|
||||
Ok(MacroFnArgs { args })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ast> Visit<'ast> for StaticStrVisitor {
|
||||
fn visit_macro(&mut self, m: &'ast Macro) {
|
||||
let Macro { path, .. } = m;
|
||||
|
||||
if path.is_ident("atom") {
|
||||
if let Ok(Lit::Str(string)) = m.parse_body::<Lit>() {
|
||||
self.static_strs.insert(string.value());
|
||||
}
|
||||
} else if path.is_ident("read_heap_cell") || path.is_ident("match_untyped_arena_ptr") {
|
||||
if let Ok(m) = m.parse_body::<ReadHeapCellExprAndArms>() {
|
||||
self.visit_expr(&m.expr);
|
||||
|
||||
for e in m.arms {
|
||||
self.visit_arm(&e);
|
||||
}
|
||||
}
|
||||
} else if let Ok(m) = m.parse_body::<MacroFnArgs>() {
|
||||
for e in m.args {
|
||||
self.visit_expr(&e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const INLINED_ATOM_MAX_LEN: usize = 6;
|
||||
|
||||
fn static_string_index(string: &str, index: usize) -> u64 {
|
||||
if !string.is_empty() && string.len() <= INLINED_ATOM_MAX_LEN && !string.contains('\u{0}') {
|
||||
let mut string_buf: [u8; 8] = [0u8; 8];
|
||||
string_buf[..string.len()].copy_from_slice(string.as_bytes());
|
||||
(u64::from_le_bytes(string_buf) << 1) | 1
|
||||
} else {
|
||||
(index << 1) as u64
|
||||
}
|
||||
}
|
||||
|
||||
pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStream {
|
||||
use quote::*;
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
use walkdir::WalkDir;
|
||||
|
||||
fn filter_rust_files(e: &walkdir::DirEntry) -> bool {
|
||||
if e.path().is_dir() {
|
||||
return true;
|
||||
}
|
||||
|
||||
e.path().extension().and_then(OsStr::to_str) == Some("rs")
|
||||
}
|
||||
|
||||
let mut visitor = StaticStrVisitor::new();
|
||||
|
||||
fn process_filepath(path: &std::path::Path) -> std::result::Result<syn::File, ()> {
|
||||
let mut src = String::new();
|
||||
|
||||
let mut file = match File::open(path) {
|
||||
Ok(file) => file,
|
||||
Err(_) => return Err(()),
|
||||
};
|
||||
|
||||
match file.read_to_string(&mut src) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
panic!("error reading file: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
let syntax = match syn::parse_file(&src) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
println!("cargo::warning=parse error: {e} in file {path:?}");
|
||||
syn::File {
|
||||
shebang: None,
|
||||
attrs: vec![],
|
||||
items: vec![],
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(syntax)
|
||||
}
|
||||
|
||||
for entry in WalkDir::new("src/")
|
||||
.into_iter()
|
||||
.filter_entry(filter_rust_files)
|
||||
{
|
||||
let entry = entry.unwrap();
|
||||
|
||||
if entry.path().is_dir() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let syntax = match process_filepath(entry.path()) {
|
||||
Ok(syntax) => syntax,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
visitor.visit_file(&syntax);
|
||||
}
|
||||
|
||||
if let Ok(syntax) = process_filepath(instruction_rs_path) {
|
||||
visitor.visit_file(&syntax)
|
||||
}
|
||||
|
||||
let static_str_keys: Vec<_> = visitor.static_strs.iter().collect();
|
||||
let mut static_strs = Vec::with_capacity(static_str_keys.len());
|
||||
let mut static_str_indices = Vec::with_capacity(static_str_keys.len());
|
||||
|
||||
let indices: Vec<u64> = visitor
|
||||
.static_strs
|
||||
.iter()
|
||||
.map(|string| {
|
||||
let index = static_string_index(string, static_strs.len());
|
||||
|
||||
if index & 1 == 1 {
|
||||
index
|
||||
} else {
|
||||
static_str_indices.push(index);
|
||||
static_strs.push(string);
|
||||
index
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let static_strs_len = static_strs.len();
|
||||
|
||||
quote! {
|
||||
static STRINGS: [&str; #static_strs_len] = [
|
||||
#(
|
||||
#static_strs,
|
||||
)*
|
||||
];
|
||||
|
||||
macro_rules! atom {
|
||||
#((#static_str_keys) => { Atom { index: #indices } };)*
|
||||
($name:literal) => {compile_error!(concat!("unknown static atom ", $name))};
|
||||
}
|
||||
|
||||
pub static STATIC_ATOMS_MAP: phf::Map<&'static str, Atom> = phf::phf_map! {
|
||||
#(#static_strs => { Atom { index: #static_str_indices } },)*
|
||||
};
|
||||
}
|
||||
}
|
||||
26
clippy.toml
26
clippy.toml
@@ -1,26 +0,0 @@
|
||||
disallowed-macros = [
|
||||
# https://rust-lang.github.io/rust-clippy/master/#disallowed_macros
|
||||
|
||||
# list of macros that may panic on allocation failure e.g.
|
||||
|
||||
# "std::vec",
|
||||
]
|
||||
|
||||
disallowed-methods = [
|
||||
# https://rust-lang.github.io/rust-clippy/master/#disallowed_method
|
||||
|
||||
# list of methods that may panic on allocation failure
|
||||
# though not including things that can be used correctly by reversing ahead of time (i.e. std::vec::Vec::try_reserve + std::iter::Extend::extend ).
|
||||
|
||||
# "std::iter::Iter::collect",
|
||||
# { path = "std::vec::Vec::with_capacity", replacement = "std::vec::Vec::new + std::vec::Vec::try_reserve" },
|
||||
# { path = "std::string::String::with_capacity", replacement = "std::string::String::new + std::string::String::try_reserve" },
|
||||
]
|
||||
|
||||
disallowed-types = [
|
||||
# https://rust-lang.github.io/rust-clippy/master/#disallowed_types
|
||||
|
||||
# list of types that can't be used without risking a panic due to allocation failure
|
||||
|
||||
# { path = "std::collections::BTreeMap", reason = "unlike Vec and HashMap BTreeMap cannot reserve capacity ahead of time (i.e. try_reserve) making it unusable without risk of oom panic"},
|
||||
]
|
||||
@@ -1,13 +0,0 @@
|
||||
project_name("Scryer Prolog").
|
||||
readme_file("INDEX.dj").
|
||||
source_lib_folder("src/lib").
|
||||
websource("https://github.com/mthom/scryer-prolog/tree/master/src/lib").
|
||||
omit(["ops_and_meta_predicates.pl", "tabling"]).
|
||||
learn_pages_source_folder("learn").
|
||||
learn_pages_categories(["First steps", "Tutorials"]).
|
||||
learn_pages([
|
||||
page("Let's play Brisca", "Tutorials", "lets-play-brisca.dj")
|
||||
]).
|
||||
copy_file("logo/scryer.png", "scryer.png").
|
||||
copy_file("learn/Spanish_deck_Fournier.jpg", "learn/Spanish_deck_Fournier.jpg").
|
||||
copy_file("learn/brisca-interactive.png", "learn/brisca-interactive.png").
|
||||
82
flake.lock
generated
82
flake.lock
generated
@@ -1,82 +0,0 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1744868846,
|
||||
"narHash": "sha256-5RJTdUHDmj12Qsv7XOhuospjAjATNiTMElplWnJE9Hs=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ebe4301cbd8f81c4f8d3244b3632338bbeb6d49c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1745289264,
|
||||
"narHash": "sha256-7nt+UJ7qaIUe2J7BdnEEph9n2eKEwxUwKS/QIr091uA=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "3b7171858c20d5293360042936058fb0c4cb93a9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
85
flake.nix
85
flake.nix
@@ -1,85 +0,0 @@
|
||||
{
|
||||
description = "A modern Prolog implementation written mostly in Rust";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
rust-overlay = {
|
||||
url = "github:oxalica/rust-overlay";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { nixpkgs, flake-utils, rust-overlay, ... }:
|
||||
let
|
||||
meta = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package;
|
||||
inherit (meta) name version;
|
||||
overlays = [
|
||||
(import rust-overlay)
|
||||
(self: super: {
|
||||
rustToolchainDev = super.rust-bin.stable.latest.default.override {
|
||||
extensions = [ "rust-src" "rust-analyzer" ];
|
||||
};
|
||||
rustToolchainDevWasm = super.rust-bin.stable.latest.default.override {
|
||||
extensions = [ "rust-src" "rust-analyzer" ];
|
||||
targets = [ "wasm32-unknown-unknown" ];
|
||||
};
|
||||
rustToolchainNightly = super.rust-bin.selectLatestNightlyWith (toolchain:
|
||||
toolchain.default.override {
|
||||
extensions = [ "rust-src" "rust-analyzer" "miri" ];
|
||||
}
|
||||
);
|
||||
})
|
||||
];
|
||||
in flake-utils.lib.eachDefaultSystem(system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system overlays; };
|
||||
nativeBuildInputs = with pkgs; [ pkg-config ];
|
||||
buildInputs = with pkgs; [ openssl ] ++
|
||||
lib.optionals pkgs.stdenv.isDarwin [
|
||||
pkgs.darwin.apple_sdk.frameworks.SystemConfiguration
|
||||
];
|
||||
in
|
||||
{
|
||||
devShells = {
|
||||
default = pkgs.mkShell.override { stdenv = pkgs.clangMultiStdenv; } {
|
||||
nativeBuildInputs = nativeBuildInputs;
|
||||
buildInputs = buildInputs ++ (with pkgs; [
|
||||
rustToolchainDev
|
||||
]);
|
||||
};
|
||||
wasm-js = pkgs.mkShell.override { stdenv = pkgs.clangMultiStdenv; } {
|
||||
nativeBuildInputs = nativeBuildInputs;
|
||||
buildInputs = buildInputs ++ (with pkgs; [
|
||||
wasm-pack
|
||||
rustToolchainDevWasm
|
||||
]);
|
||||
TARGET_CC = "${pkgs.clangMultiStdenv.cc}/bin/clang";
|
||||
hardeningDisable = [ "all" ];
|
||||
};
|
||||
# For use with Miri and stuff like it
|
||||
nightly = pkgs.mkShell {
|
||||
nativeBuildInputs = nativeBuildInputs;
|
||||
buildInputs = buildInputs ++ (with pkgs; [
|
||||
rustToolchainNightly
|
||||
]);
|
||||
};
|
||||
};
|
||||
|
||||
packages = rec {
|
||||
default = scryer-prolog;
|
||||
scryer-prolog = pkgs.rustPlatform.buildRustPackage {
|
||||
pname = name;
|
||||
inherit version;
|
||||
src = ./.;
|
||||
nativeBuildInputs = nativeBuildInputs;
|
||||
buildInputs = buildInputs;
|
||||
cargoLock = {
|
||||
lockFile = ./Cargo.lock;
|
||||
};
|
||||
release = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 629 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 100 KiB |
@@ -1,457 +0,0 @@
|
||||
# Let's play Brisca
|
||||
|
||||
In this article, we'll see how to use modern Prolog to model the game of [Brisca](https://en.wikipedia.org/wiki/Brisca). First things first, what is Brisca?
|
||||
|
||||
## Rules of the game
|
||||
|
||||
Brisca is a traditional card game from Spain, although similar games both in name and in rules are spread across the Mediterranean. It's also similar to Tute. It is played using the traditional Spanish deck, usually with 40 cards in 4 suites (oros, espadas, bastos, copas). Here you have the famous Castillian pattern by Heraclio Fournier, the one I play in my family with my grandparents.
|
||||
|
||||

|
||||
|
||||
How do you play it? Basically, players play in rounds. Every round each player on the table (usually four), selects one of his own three cards to leave one in the middle, and they do that in order. There are some rules, which I will explain later, and one person wins the round, taking all the cards with him. Players take new cards from the stock. Some cards get you points, some others don't. After there are no more cards in player's hands, we count the points, the players with more points, wins!
|
||||
|
||||
The rules for knowing which players takes the round are the following:
|
||||
- Every game there's a trump suite, which was selected as a random card from the stock at the same time when the players got their three initial cards.
|
||||
- The first player in a round (the winner of the previous round) can choose whatever card he wants.
|
||||
- The rest of the players, in order to win the round, they need to improve the card of the first player. They can put a higher card of the same suite as the first player.
|
||||
- Or they can put a card from the trump suite, which is always better than a card from a non-trump suite.
|
||||
- But between cards in the trump suite, there's still an order.
|
||||
- However players are not required to put cards that improve the game if they don't wish to do that, they can just lose the round to discard themselves a bad card.
|
||||
- The order is the following: As (ace, number 1) -> 3 -> Rey (king, number 12) -> Caballo (horse, but usually it's a knight, number 11) -> Sota (a page, number 10) -> 7 -> 6 -> 5 -> 4 -> 2.
|
||||
|
||||
## Modeling cards
|
||||
|
||||
First, we need to decide a representation of our cards. Coming from another languages we can think that a good representation might be a class or a struct, with two fields, one for the number and the other for the suite, but Prolog doesn't have objects. We can use a list with two elements. But lists are better when we're dealing with variable length data. We could also use a compound term. This is the right choice if our fields are fixed.
|
||||
|
||||
A compound term is defined by an atom, followed by the data itself enclosed by parenthesis and separated by comma. Like this: `card(oros, 4)`. Yes, very similar to predicates. In fact the only difference is how we use them, because they're the same. If we pass a compound term in the first level of a query, or inside a call/N, Prolog will treat it as code instead of data. This is one the the examples of Prolog being a homoiconic language.
|
||||
|
||||
We can go further, Prolog is very flexible and we can define custom operators easily if we want. Those are also compound terms, but with a different syntax. There's an operator already defined that is very useful for us: the dash. We can just join two pieces of data with a dash, and they'll be together in the same structure. This is usually called "pair".
|
||||
|
||||
So, using pairs we can model cards like this: `oros-4`, `espadas-7`.
|
||||
|
||||
We can code a predicate that defines valid cards:
|
||||
|
||||
```
|
||||
card(Suite-Number) :-
|
||||
member(Suite, [oros, espadas, bastos, copas]),
|
||||
member(Number, [rey, caballo, sota, 7, 6, 5, 4, 3, 2, as]).
|
||||
```
|
||||
|
||||
## Counting points
|
||||
|
||||
We are going to implement a counting predicate. The suite doesn't matter, only the number. First, we define a `card_score/2` predicate which relates a card to a score and viceversa.
|
||||
|
||||
```
|
||||
card_score(_-as, 11).
|
||||
card_score(_-3, 10).
|
||||
card_score(_-rey, 4).
|
||||
card_score(_-caballo, 3).
|
||||
card_score(_-sota, 2).
|
||||
card_score(_-X, 0) :- member(X, [7, 6, 5, 4, 2]).
|
||||
```
|
||||
|
||||
Now, if we want to know how many points gives an specific card, we can ask:
|
||||
|
||||
```
|
||||
?- card_score(oros-rey, X).
|
||||
X = 4
|
||||
; false.
|
||||
```
|
||||
|
||||
(Remember, to load a file named brisca.pl in Scryer Prolog, we can run `scryer-prolog brisca.pl` in your favourite shell.)
|
||||
|
||||
But we can also ask what cards can give you that value:
|
||||
|
||||
```
|
||||
?- card(X), card_score(X, 11).
|
||||
X = oros-as
|
||||
; X = espadas-as
|
||||
; X = bastos-as
|
||||
; X = copas-as.
|
||||
```
|
||||
|
||||
You have probably noticed that I included the card predicate in the toplevel (which kind of acts like a type restriction), not in the `card_score/2`. However, does it make sense for card_score to work on something that is not a also a card? Both approaches are valid if applied correctly. Some people prefer to set domain restrictions like this externally, as it allows you to write more concise code and it usually performs better. However, having a card predicate in `card_score/2` is more correct since it's not possible to use that predicate in something that is not a card. We can choose either option thanks to Prolog being a dynamic language.
|
||||
|
||||
In my case, my final version is going to be a predicate which includes the card restriction and uses a wrapper predicate to have the short and concise code:
|
||||
|
||||
```
|
||||
card_score(X, N) :-
|
||||
card(X),
|
||||
card_score_(X, N).
|
||||
card_score_(_-as, 11).
|
||||
card_score_(_-3, 10).
|
||||
card_score_(_-rey, 4).
|
||||
card_score_(_-caballo, 3).
|
||||
card_score_(_-sota, 2).
|
||||
card_score_(_-X, 0) :- member(X, [7, 6, 5, 4, 2]).
|
||||
```
|
||||
|
||||
Now we just need to work with a list of cards instead of a single card. This is a place where we can introduce DCGs. DCGs are the short name for [Definite Clause Grammars](https://www.metalevel.at/prolog/dcg), which is a shorthand notation used to describe sequences. We can use them to parse, generate, complete and check sequences in the form of lists.
|
||||
|
||||
We can define a base case. If there are no more elements in the sequence, the score should be zero. If there's a Card, we can calculate its score and sum it together with the rest:
|
||||
|
||||
```
|
||||
cards_score_(0) --> [].
|
||||
cards_score_(X) -->
|
||||
[Card],
|
||||
{ card_score(Card, X0), #X #= #X0 + #X1 },
|
||||
cards_score_(X1).
|
||||
```
|
||||
|
||||
In DCGs, to match an item of the sequence, we use brackets. We use braces to introduce normal Prolog code. Calling other DCGs (in this case, the same, as it's a recursive one), it's just calling it again. Notice in this code that we are doing the addition of X0 and X1 when we still don't know the value of X1. This would be an error in the traditional arithmetic system of Prolog, but it's valid with clpz. clpz allows us to have a more declarative arithmetic, at least with integers.
|
||||
|
||||
Now we can try this code using `phrase/2` which is needed to jump to a DCG.
|
||||
|
||||
```
|
||||
?- phrase(cards_score_(X), [oros-as, oros-rey, oros-7]).
|
||||
X = 15
|
||||
; false.
|
||||
```
|
||||
|
||||
It seems to work. However there's a small problem. if we try to do the reverse, generating a sequence of cards that give you a specific amount of points, we'll find trouble. The program won't end. This is basically because be default we are not fair enumerating. This means that it will go deep into the recursion, trying to create an infinite length sequence. We need a way so that it does iterative deepening (starts with sequences of length 1, then length 2, ...). Luckily, if we pass our list first through the `length/2` predicate, it will do exactly that. This is usually done at the outside.
|
||||
|
||||
```
|
||||
cards_score(Cards, Score) :-
|
||||
phrase(cards_score_(Score), Cards).
|
||||
|
||||
cards_score_(0) --> [].
|
||||
cards_score_(X) -->
|
||||
[Card],
|
||||
{ card_score(Card, X0), #X #= #X0 + #X1 },
|
||||
cards_score_(X1).
|
||||
```
|
||||
|
||||
So now:
|
||||
|
||||
```
|
||||
?- length(X, _), cards_score(X, 15).
|
||||
X = [oros-rey,oros-as]
|
||||
; X = [oros-rey,espadas-as]
|
||||
; X = [oros-rey,bastos-as]
|
||||
; X = [oros-rey,copas-as]
|
||||
; X = [oros-as,oros-rey]
|
||||
; X = [oros-as,espadas-rey]
|
||||
; X = [oros-as,bastos-rey]
|
||||
; ...
|
||||
```
|
||||
|
||||
```
|
||||
?- cards_score(X, Y).
|
||||
X = [], Y = 0
|
||||
; X = [oros-rey], Y = 4
|
||||
; X = [oros-caballo], Y = 3
|
||||
; X = [oros-sota], Y = 2
|
||||
; X = [oros-7], Y = 0
|
||||
; X = [oros-6], Y = 0
|
||||
; X = [oros-5], Y = 0
|
||||
; X = [oros-4], Y = 0
|
||||
; X = [oros-3], Y = 10
|
||||
; X = [oros-2], Y = 0
|
||||
; ...
|
||||
```
|
||||
|
||||
## Winner of a round
|
||||
|
||||
Now, let's try to model the winner of a round in the game. First, we can model the pseudo-numerical order of the cards in a suite. We can start with a simple idea, if we have a list with the right order, we can take an element and see if the other card is in the higher section of the rest of the list.
|
||||
|
||||
```
|
||||
card_higher_n(N0, N1) :-
|
||||
Order = [as, 3, rey, caballo, sota, 7, 6, 5, 4, 2],
|
||||
append(Highers, [N0|_], Order),
|
||||
member(N1, Highers).
|
||||
```
|
||||
|
||||
This works well, and it can show all of the 'Y is higher than X' relationships:
|
||||
|
||||
```
|
||||
?- card_higher_n(X, Y).
|
||||
X = 3, Y = as
|
||||
; X = rey, Y = as
|
||||
; X = rey, Y = 3
|
||||
; X = caballo, Y = as
|
||||
; X = caballo, Y = 3
|
||||
; ...
|
||||
```
|
||||
|
||||
For some people this might be enough. However, since we're in Scryer Prolog, we can also use reificated predicates. Using this technique, we can show not just the 'Y is higher than X' but also, the complete set of solutions of 'Y is NOT higher than X'. In order to do that we need to add a third argument to our predicate, which will be true or false (the predicate is true for the given X and Y or it's false). And we need to change `member/2` to a reified version of it: `memberd_t/3`.
|
||||
|
||||
```
|
||||
card_higher_n(N0, N1, T) :-
|
||||
Order = [as, 3, rey, caballo, sota, 7, 6, 5, 4, 2],
|
||||
append(Highers, [N0|_], Order),
|
||||
memberd_t(N1, Highers, T).
|
||||
```
|
||||
|
||||
Now, we get for every combination a T truth value and also, in the case of false queries, we get the constraints that need to be held to get that result:
|
||||
|
||||
```
|
||||
?- card_higher_n(X, Y, T).
|
||||
X = as, T = false
|
||||
; X = 3, Y = as, T = true
|
||||
; X = 3, T = false, dif:dif(as,Y)
|
||||
; X = rey, Y = as, T = true
|
||||
; X = rey, Y = 3, T = true
|
||||
; X = rey, T = false, dif:dif(3,Y), dif:dif(as,Y)
|
||||
; X = caballo, Y = as, T = true
|
||||
; X = caballo, Y = 3, T = true
|
||||
; X = caballo, Y = rey, T = true
|
||||
; X = caballo, T = false, dif:dif(3,Y), dif:dif(as,Y), dif:dif(rey,Y)
|
||||
```
|
||||
|
||||
For example, if X = 3 and Y = as, then Y is higher than X. But if Y is different from as, then it's false.
|
||||
|
||||
Using reified predicates can be wise if we want to raise ourselves to a more declarative way of working with Prolog, but it's newer and not applicable form some problems. In this case, it's fine, so we'll use them.
|
||||
|
||||
Now, to model the relatioships of whole cards, we need to take into account the suites too. We're going to compare the suites, if they're the same, we just apply the pseudonumerical order. If they're different we check if the suite of the supposedly higher card is the trump suite. We can continue to use reified predicates from `library(reif)` like `if_/3`, which takes a reified predicate as a condition and `=/3` which does reified unification. We also expose our T truth argument in this predicate:
|
||||
|
||||
```
|
||||
card_higher(Trump, Card, Higher, T) :-
|
||||
card(Card),
|
||||
card(Higher),
|
||||
Card = S0-N0,
|
||||
Higher = S1-N1,
|
||||
if_(S0 = S1,
|
||||
card_higher_n(N0, N1, T),
|
||||
=(S1, Trump, T)
|
||||
).
|
||||
```
|
||||
|
||||
The code allows us to generate every card combination with every trump value and their truth value:
|
||||
|
||||
```
|
||||
?- card_higher(Trump, X, Y, T).
|
||||
X = oros-rey, Y = oros-rey, T = false
|
||||
; X = oros-rey, Y = oros-caballo, T = false
|
||||
; X = oros-rey, Y = oros-sota, T = false
|
||||
; X = oros-rey, Y = oros-7, T = false
|
||||
; X = oros-rey, Y = oros-6, T = false
|
||||
; X = oros-rey, Y = oros-5, T = false
|
||||
; X = oros-rey, Y = oros-4, T = false
|
||||
; X = oros-rey, Y = oros-3, T = true
|
||||
; X = oros-rey, Y = oros-2, T = false
|
||||
; X = oros-rey, Y = oros-as, T = true
|
||||
; Trump = espadas, X = oros-rey, Y = espadas-rey, T = true
|
||||
; X = oros-rey, Y = espadas-rey, T = false, dif:dif(espadas,Trump)
|
||||
; Trump = espadas, X = oros-rey, Y = espadas-caballo, T = true
|
||||
; X = oros-rey, Y = espadas-caballo, T = false, dif:dif(espadas,Trump)
|
||||
; Trump = espadas, X = oros-rey, Y = espadas-sota, T = true
|
||||
; X = oros-rey, Y = espadas-sota, T = false, dif:dif(espadas,Trump)
|
||||
```
|
||||
|
||||
For example a espadas-caballo is higher than oros-rey if Trump = espadas, otherwise, it isn't.
|
||||
|
||||
Now, let's check for the winner. For every card we need to check if the current one is higher value than the current highest. It's very easy, we just need to to do a fold! Just like in functional programming, we have a fold _predicate_. Actually, it's a fold-left, but for this case we need the right order, so we will need to reverse the list first. The, for our foldl, we're going to use a lambda predicate from `library(lambda)`.
|
||||
|
||||
This library, allows us to write unnamed predicates that are very useful in metapredicates like `foldl/4`. The syntax might be a bit confusing if you're coming from other languages as it's a bit unique. You can find the complete description of the syntax in the [documentation page](https://www.scryer.pl/lambda.html).
|
||||
|
||||
The code looks like this:
|
||||
|
||||
```
|
||||
round_winner(Cards, Trump, WinnerCard) :-
|
||||
reverse(Cards, [FirstCard|RestCards]),
|
||||
foldl(Trump+\X^Y^Z^if_(card_higher(Trump, X, Y), Y = Z, X = Z), RestCards, FirstCard, WinnerCard).
|
||||
```
|
||||
|
||||
```
|
||||
?- length(Cards, 4), round_winner(Cards, Trump, Winner).
|
||||
Cards = [oros-rey,oros-rey,oros-rey,oros-rey], Winner = oros-rey
|
||||
; Cards = [oros-caballo,oros-rey,oros-rey,oros-rey], Winner = oros-rey
|
||||
; Cards = [oros-sota,oros-rey,oros-rey,oros-rey], Winner = oros-rey
|
||||
; Cards = [oros-7,oros-rey,oros-rey,oros-rey], Winner = oros-rey
|
||||
; Cards = [oros-6,oros-rey,oros-rey,oros-rey], Winner = oros-rey
|
||||
; Cards = [oros-5,oros-rey,oros-rey,oros-rey], Winner = oros-rey
|
||||
; Cards = [oros-4,oros-rey,oros-rey,oros-rey], Winner = oros-rey
|
||||
; Cards = [oros-3,oros-rey,oros-rey,oros-rey], Winner = oros-3
|
||||
; Cards = [oros-2,oros-rey,oros-rey,oros-rey], Winner = oros-rey
|
||||
; Cards = [oros-as,oros-rey,oros-rey,oros-rey], Winner = oros-as
|
||||
; Cards = [espadas-rey,oros-rey,oros-rey,oros-rey], Trump = oros, Winner = oros-rey
|
||||
; Cards = [espadas-rey,oros-rey,oros-rey,oros-rey], Winner = espadas-rey, dif:dif(oros,Trump)
|
||||
```
|
||||
|
||||
# A complete game
|
||||
|
||||
Now we have the basic pieces of the game. But we don't have a complete game. A game starts with someone dealing the first three cards to each player, a trump suite is selected, then we start the rounds: players put one card each, we choose a winner, the winner takes the cards and players get a new card from the stock. This all seems very procedural, but we're using Prolog. What can we do about it?
|
||||
|
||||
Let's ask ourselves what is a procedure. It's a sequence. And we have already seen a way to describe sequences in Prolog! The DCGs.
|
||||
|
||||
The basic idea however is having _explicit_ states. The predicates that we're going to write will take an state (a view of the world at a certain point) and will give us the next state.
|
||||
|
||||
Let's define the state first. In a game of Brisca we have players. Each player has the three cards he can choose to put (less if we're running out of cards) and the cards he has got from winning rounds. Additionally we have a stock, a trump suite and the order to play, which is usually from the player who won the last round and going to the right. We could store the data in a list, with different compound terms:
|
||||
|
||||
```
|
||||
[players([player(Name, PlayableCards, WonCards), player(Name, PlayableCards, WonCards), ...]), stock(Cards), trump(Trump)]
|
||||
```
|
||||
|
||||
In order to express states using DCGs, we can use [semicontext notation](https://www.metalevel.at/prolog/dcg#semicontext):
|
||||
|
||||
```
|
||||
state(S), [S] --> [S].
|
||||
state(S0, S), [S] --> [S0].
|
||||
```
|
||||
|
||||
So, for example our predicate to reset the game, and leave all the different cards in the stock would like this:
|
||||
|
||||
```
|
||||
reset -->
|
||||
state(_, [stock(Cards)]),
|
||||
{
|
||||
setof(Card, card(Card), Cards)
|
||||
}.
|
||||
```
|
||||
|
||||
To shuffle them:
|
||||
|
||||
```
|
||||
shuffle_cards -->
|
||||
state(S0, S),
|
||||
{
|
||||
select(stock(Cards), S0, S1),
|
||||
shuffle(Cards, ShuffledCards),
|
||||
S = [stock(ShuffledCards)|S1]
|
||||
}.
|
||||
```
|
||||
|
||||
Where `shuffle/2` is a predicate that implements the Fisher-Yates algorithm in Prolog:
|
||||
|
||||
```
|
||||
shuffle([], []).
|
||||
shuffle(Xs0, [Y|Ys]) :-
|
||||
length(Xs0, N),
|
||||
random_integer(0, N, R),
|
||||
nth0(R, Xs0, Y, Xs),
|
||||
shuffle(Xs, Ys).
|
||||
```
|
||||
|
||||
We can also code more specific state accessors, which could be useful to know which part of the state each predicate can modify:
|
||||
|
||||
```
|
||||
players(P0, P), [S] -->
|
||||
[S0],
|
||||
{ select(players(P0), S0, S1), S = [players(P)|S1] }.
|
||||
```
|
||||
|
||||
The whole state management topic in DCGs could be the whole topic for another article.
|
||||
|
||||
The final DCG code for an interactive version of Brisca could be as follows:
|
||||
|
||||
```
|
||||
brisca :-
|
||||
phrase(brisca, [_], [_]).
|
||||
|
||||
brisca -->
|
||||
reset,
|
||||
shuffle_cards,
|
||||
set_trump,
|
||||
create_players([aarroyoc, xijinping, donalddtrump, vonderleyen]),
|
||||
play_rounds,
|
||||
show_scores.
|
||||
|
||||
reset -->
|
||||
state(_, [stock(Cards)]),
|
||||
{
|
||||
setof(Card, card(Card), Cards)
|
||||
}.
|
||||
shuffle_cards -->
|
||||
state(S0, S),
|
||||
{
|
||||
select(stock(Cards), S0, S1),
|
||||
shuffle(Cards, ShuffledCards),
|
||||
S = [stock(ShuffledCards)|S1]
|
||||
}.
|
||||
set_trump -->
|
||||
state(S0, S),
|
||||
{
|
||||
member(stock(Cards), S0),
|
||||
length(Cards, N),
|
||||
nth1(N, Cards, LastCard),
|
||||
LastCard = Trump-_,
|
||||
S = [trump(Trump)|S0]
|
||||
}.
|
||||
|
||||
create_players(Names) -->
|
||||
state(S0, S),
|
||||
{
|
||||
same_length(Players, Names),
|
||||
maplist(\N^X^(X=player(N, [], [])), Names, Players),
|
||||
S = [players(Players)|S0]
|
||||
},
|
||||
deal_one_card_per_player,
|
||||
deal_one_card_per_player,
|
||||
deal_one_card_per_player.
|
||||
|
||||
deal_one_card_per_player -->
|
||||
state(S0, S),
|
||||
{
|
||||
select(players(Players), S0, S1),
|
||||
select(stock(Cards), S1, S2),
|
||||
deal_one_card_per_player(Players, Players1, Cards, Cards1),
|
||||
S = [players(Players1), stock(Cards1)|S2]
|
||||
}.
|
||||
|
||||
deal_one_card_per_player([], [], Cs, Cs).
|
||||
deal_one_card_per_player(Ps, Ps, [], []).
|
||||
deal_one_card_per_player([P|Ps], [P1|Ps1], [C|Cs], Cs1) :-
|
||||
P = player(N, A0, B0),
|
||||
P1 = player(N, [C|A0], B0),
|
||||
deal_one_card_per_player(Ps, Ps1, Cs, Cs1).
|
||||
|
||||
play_rounds -->
|
||||
players(P, P),
|
||||
{ P = [player(_, X, _)|_], length(X, 0) }.
|
||||
play_rounds -->
|
||||
players(P, P),
|
||||
{ P = [player(_, X, _)|_], length(X, N), N > 0 },
|
||||
play_round,
|
||||
play_rounds.
|
||||
|
||||
play_round -->
|
||||
state(S),
|
||||
players(P0, P2),
|
||||
{
|
||||
member(trump(Trump), S),
|
||||
format("Brisca round~nTrump is: ~a~n~n", [Trump])
|
||||
},
|
||||
play_players(P0, Cards),
|
||||
{
|
||||
round_winner(Cards, Trump, WinnerCard),
|
||||
maplist(remove_card, P0, Cards, P1),
|
||||
nth0(N, Cards, WinnerCard),
|
||||
nth0(N, P1, WinnerPlayer0),
|
||||
append(PBefore, [WinnerPlayer0|PAfter], P1),
|
||||
WinnerPlayer0 = player(WinnerName, C, W0),
|
||||
format("Winner card is ~w from ~w~n", [WinnerCard, WinnerName]),
|
||||
append(W0, Cards, W1),
|
||||
append([player(WinnerName, C, W1)|PAfter], PBefore, P2)
|
||||
},
|
||||
deal_one_card_per_player.
|
||||
|
||||
remove_card(P0, C, P) :-
|
||||
P0 = player(N, C0, W),
|
||||
select(C, C0, C1),
|
||||
P = player(N, C1, W).
|
||||
|
||||
play_players([], []) --> [].
|
||||
play_players([P|Ps], [C|Cs]) -->
|
||||
{
|
||||
P = player(Name, SelectableCards, _),
|
||||
format("It's ~a's turn!~n", [Name]),
|
||||
format("Selectable cards: ~w~n", [SelectableCards]),
|
||||
read(C),
|
||||
member(C, SelectableCards)
|
||||
},
|
||||
play_players(Ps, Cs).
|
||||
play_players(Ps, Cs) --> play_players(Ps, Cs).
|
||||
|
||||
show_scores -->
|
||||
players(P, P),
|
||||
{
|
||||
maplist(\X^(
|
||||
X=player(N,_,Z),
|
||||
cards_score(Z, Y),
|
||||
format("Score of ~w is ~d~n", [N, Y])), P)
|
||||
}.
|
||||
```
|
||||
|
||||
Now, we can play! Of course, if you substitute interactive choosing (via `read/1`) with another method, you could simulate games, try AI strategies, etc
|
||||
|
||||

|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product Name="Scryer Prolog" Manufacturer="Scryer Prolog contributors" Id="*" UpgradeCode="cfb2dee4-5dd5-4d7d-b426-cd7340810559" Language="1033" Codepage="1252" Version="0.9.0">
|
||||
<Package Description="An open source industrial strength production environment for ISO Prolog that is also a testbed for bleeding edge research in logic and constraint programming, which is itself written in a high-level language." Platform="x64" Keywords="prolog" Id="*" Compressed="yes" InstallScope="perMachine" InstallerVersion="300" Languages="1033" SummaryCodepage="1252" Manufacturer="Scryer Prolog contributors"/>
|
||||
<Property Id="APPHELPLINK" Value="https://github.com/mthom/scryer-prolog"/>
|
||||
<Media Id="1" Cabinet="scryer.cab" EmbedCab="yes" />
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="ProgramFilesFolder" Name="PFiles">
|
||||
<Directory Id="INSTALLDIR" Name="Scryer Prolog">
|
||||
<Component Id="MainExecutable" Guid="1b41ceda-ba18-47f9-911b-ee41b4f20921">
|
||||
<File Id="ScryerPrologEXE" Name="scryer-prolog.exe" DiskId="1" Source="target/release/scryer-prolog.exe" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
<Directory Id="ProgramMenuFolder">
|
||||
<Component Id="ApplicationShortcut" Guid="8c9b14a3-e7b1-4d30-a892-61d7371dcae2">
|
||||
<Shortcut Id="ApplicationStarMenuShortcut" Name="Scryer Prolog" Description="Launch Scryer Prolog" Target="[#ScryerPrologEXE]" WorkingDirectory="INSTALLDIR"/>
|
||||
<RemoveFolder Id="ApplicationShortcut" On="uninstall"/>
|
||||
<RegistryValue Root="HKCU" Key="Software\Microsoft\ScryerProlog" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
<Feature Id="Complete" Level="1" Display="expand" ConfigurableDirectory="INSTALLDIR">
|
||||
<ComponentRef Id="MainExecutable"/>
|
||||
<ComponentRef Id="ApplicationShortcut"/>
|
||||
</Feature>
|
||||
</Product>
|
||||
</Wix>
|
||||
119
src/allocator.rs
119
src/allocator.rs
@@ -1,57 +1,90 @@
|
||||
use crate::parser::ast::*;
|
||||
use crate::prolog_parser::ast::*;
|
||||
|
||||
use crate::fixtures::*;
|
||||
use crate::forms::*;
|
||||
use crate::instructions::*;
|
||||
use crate::machine::machine_indices::*;
|
||||
use crate::targets::*;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub(crate) trait Allocator {
|
||||
pub trait Allocator<'a> {
|
||||
fn new() -> Self;
|
||||
|
||||
fn mark_anon_var<'a, Target: CompilationTarget<'a>>(
|
||||
fn mark_anon_var<Target>(&mut self, _: Level, _: GenContext, _: &mut Vec<Target>)
|
||||
where
|
||||
Target: CompilationTarget<'a>;
|
||||
fn mark_non_var<Target>(&mut self, _: Level, _: GenContext, _: &'a Cell<RegType>, _: &mut Vec<Target>)
|
||||
where
|
||||
Target: CompilationTarget<'a>;
|
||||
fn mark_reserved_var<Target>(
|
||||
&mut self,
|
||||
lvl: Level,
|
||||
context: GenContext,
|
||||
code: &mut CodeDeque,
|
||||
) -> RegType;
|
||||
|
||||
fn mark_non_var<'a, Target: CompilationTarget<'a>>(
|
||||
&mut self,
|
||||
lvl: Level,
|
||||
context: GenContext,
|
||||
cell: &'a Cell<RegType>,
|
||||
code: &mut CodeDeque,
|
||||
);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn mark_reserved_var<'a, Target: CompilationTarget<'a>>(
|
||||
&mut self,
|
||||
var_num: usize,
|
||||
lvl: Level,
|
||||
cell: &Cell<VarReg>,
|
||||
term_loc: GenContext,
|
||||
code: &mut CodeDeque,
|
||||
r: RegType,
|
||||
is_new_var: bool,
|
||||
);
|
||||
|
||||
fn mark_cut_var(&mut self, var_num: usize, chunk_num: usize) -> RegType;
|
||||
|
||||
fn mark_var<'a, Target: CompilationTarget<'a>>(
|
||||
&mut self,
|
||||
var_num: usize,
|
||||
lvl: Level,
|
||||
cell: &Cell<VarReg>,
|
||||
context: GenContext,
|
||||
code: &mut CodeDeque,
|
||||
);
|
||||
_: Rc<Var>,
|
||||
_: Level,
|
||||
_: &'a Cell<VarReg>,
|
||||
_: GenContext,
|
||||
_: &mut Vec<Target>,
|
||||
_: RegType,
|
||||
_: bool,
|
||||
) where
|
||||
Target: CompilationTarget<'a>;
|
||||
fn mark_var<Target>(&mut self, _: Rc<Var>, _: Level, _: &'a Cell<VarReg>, _: GenContext, _: &mut Vec<Target>)
|
||||
where
|
||||
Target: CompilationTarget<'a>;
|
||||
|
||||
fn reset(&mut self);
|
||||
fn reset_arg(&mut self, arg_num: usize);
|
||||
fn reset_at_head(&mut self, args: &[Term]);
|
||||
fn reset_contents(&mut self);
|
||||
fn reset_contents(&mut self) {}
|
||||
fn reset_arg(&mut self, _: usize);
|
||||
fn reset_at_head(&mut self, _: &Vec<Box<Term>>);
|
||||
|
||||
fn advance_arg(&mut self);
|
||||
fn max_reg_allocated(&self) -> usize;
|
||||
|
||||
fn bindings(&self) -> &AllocVarDict;
|
||||
fn bindings_mut(&mut self) -> &mut AllocVarDict;
|
||||
|
||||
fn take_bindings(self) -> AllocVarDict;
|
||||
|
||||
fn drain_var_data(
|
||||
&mut self,
|
||||
vs: VariableFixtures<'a>,
|
||||
num_of_chunks: usize
|
||||
) -> VariableFixtures<'a> {
|
||||
let mut perm_vs = VariableFixtures::new();
|
||||
|
||||
for (var, (var_status, cells)) in vs.into_iter() {
|
||||
match var_status {
|
||||
VarStatus::Temp(chunk_num, tvd) => {
|
||||
self.bindings_mut()
|
||||
.insert(var.clone(), VarData::Temp(chunk_num, 0, tvd));
|
||||
|
||||
if chunk_num + 1 == num_of_chunks {
|
||||
perm_vs.insert_last_chunk_temp_var(var);
|
||||
}
|
||||
}
|
||||
VarStatus::Perm(_) => {
|
||||
self.bindings_mut().insert(var.clone(), VarData::Perm(0));
|
||||
perm_vs.insert(var, (var_status, cells));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
perm_vs
|
||||
}
|
||||
|
||||
fn get(&self, var: Rc<Var>) -> RegType {
|
||||
self.bindings()
|
||||
.get(&var)
|
||||
.map_or(temp_v!(0), |v| v.as_reg_type())
|
||||
}
|
||||
|
||||
fn is_unbound(&self, var: Rc<Var>) -> bool {
|
||||
self.get(var).reg_num() == 0
|
||||
}
|
||||
|
||||
fn record_register(&mut self, var: Rc<Var>, r: RegType) {
|
||||
match self.bindings_mut().get_mut(&var).unwrap() {
|
||||
&mut VarData::Temp(_, ref mut s, _) => *s = r.reg_num(),
|
||||
&mut VarData::Perm(ref mut s) => *s = r.reg_num(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
897
src/arena.rs
897
src/arena.rs
@@ -1,897 +0,0 @@
|
||||
#[cfg(feature = "http")]
|
||||
use crate::http::{HttpListener, HttpResponse};
|
||||
use crate::machine::heap::AllocError;
|
||||
use crate::machine::loader::LiveLoadState;
|
||||
use crate::machine::streams::*;
|
||||
use crate::offset_table::*;
|
||||
use crate::read::*;
|
||||
use crate::types::UntypedArenaPtr;
|
||||
|
||||
use crate::parser::dashu::{Integer, Rational};
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::PipeReader;
|
||||
use std::io::PipeWriter;
|
||||
use std::mem;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::net::TcpListener;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::process::Child;
|
||||
use std::ptr;
|
||||
use std::ptr::NonNull;
|
||||
use std::ptr::addr_of_mut;
|
||||
|
||||
macro_rules! arena_alloc {
|
||||
($e:expr, $arena:expr) => {{
|
||||
let result = $e;
|
||||
$crate::arena::AllocateInArena::arena_allocate(result, $arena)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! float_alloc {
|
||||
($e:expr, $arena:expr) => {{ $arena.f64_tbl.build_with(OrderedFloat($e)) }};
|
||||
}
|
||||
|
||||
pub fn header_offset_from_payload<T: ?Sized + ArenaAllocated>() -> usize
|
||||
where
|
||||
T::Payload: Sized,
|
||||
{
|
||||
let payload_offset = mem::offset_of!(TypedAllocSlab<T>, payload);
|
||||
let slab_offset = mem::offset_of!(TypedAllocSlab<T>, slab);
|
||||
let header_offset = slab_offset + mem::offset_of!(AllocSlab, header);
|
||||
|
||||
debug_assert!(payload_offset > header_offset);
|
||||
payload_offset - header_offset
|
||||
}
|
||||
|
||||
#[derive(Specifier, Copy, Clone, Debug, PartialEq)]
|
||||
#[bits = 7]
|
||||
pub enum ArenaHeaderTag {
|
||||
Integer = 0b10,
|
||||
Rational = 0b11,
|
||||
LiveLoadState = 0b0001000,
|
||||
InactiveLoadState = 0b1011000,
|
||||
InputFileStream = 0b10000,
|
||||
OutputFileStream = 0b10100,
|
||||
NamedTcpStream = 0b011100,
|
||||
NamedTlsStream = 0b100000,
|
||||
HttpReadStream = 0b100001,
|
||||
HttpWriteStream = 0b100010,
|
||||
ReadlineStream = 0b110000,
|
||||
StaticStringStream = 0b110100,
|
||||
ByteStream = 0b111000,
|
||||
CallbackStream = 0b111001,
|
||||
InputChannelStream = 0b111010,
|
||||
StandardOutputStream = 0b1100,
|
||||
StandardErrorStream = 0b11000,
|
||||
NullStream = 0b111100,
|
||||
TcpListener = 0b1000000,
|
||||
HttpListener = 0b1000001,
|
||||
HttpResponse = 0b1000010,
|
||||
PipeWriter = 0b1000011,
|
||||
Dropped = 0b1000100,
|
||||
PipeReader = 0b1001001,
|
||||
ChildProcess = 0b1001010,
|
||||
}
|
||||
|
||||
#[bitfield]
|
||||
#[repr(align(8))]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ArenaHeader {
|
||||
#[allow(dead_code)]
|
||||
size: B56,
|
||||
m: bool,
|
||||
tag: ArenaHeaderTag,
|
||||
}
|
||||
|
||||
const_assert!(mem::size_of::<ArenaHeader>() == 8);
|
||||
|
||||
impl ArenaHeader {
|
||||
#[inline]
|
||||
pub fn build_with(size: u64, tag: ArenaHeaderTag) -> Self {
|
||||
ArenaHeader::new()
|
||||
.with_size(size)
|
||||
.with_tag(tag)
|
||||
.with_m(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_tag(self) -> ArenaHeaderTag {
|
||||
self.tag()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TypedArenaPtr<T: ?Sized + ArenaAllocated>(ptr::NonNull<T::Payload>);
|
||||
|
||||
impl<T: ?Sized + ArenaAllocated> PartialOrd for TypedArenaPtr<T>
|
||||
where
|
||||
T::Payload: PartialOrd,
|
||||
{
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
(**self).partial_cmp(&**other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ArenaAllocated> PartialEq for TypedArenaPtr<T>
|
||||
where
|
||||
T::Payload: PartialEq,
|
||||
{
|
||||
fn eq(&self, other: &TypedArenaPtr<T>) -> bool {
|
||||
std::ptr::addr_eq(self.0.as_ptr(), other.0.as_ptr()) || **self == **other
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ArenaAllocated> Eq for TypedArenaPtr<T> where T::Payload: Eq {}
|
||||
|
||||
impl<T: ?Sized + ArenaAllocated> Ord for TypedArenaPtr<T>
|
||||
where
|
||||
T::Payload: Ord,
|
||||
{
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
(**self).cmp(&**other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ArenaAllocated> Hash for TypedArenaPtr<T>
|
||||
where
|
||||
T::Payload: Hash,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn hash<H: Hasher>(&self, hasher: &mut H) {
|
||||
(self as &T::Payload).hash(hasher)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ArenaAllocated> Clone for TypedArenaPtr<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ArenaAllocated> Copy for TypedArenaPtr<T> {}
|
||||
|
||||
impl<T: ?Sized + ArenaAllocated> Deref for TypedArenaPtr<T> {
|
||||
type Target = T::Payload;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { self.0.as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ArenaAllocated> DerefMut for TypedArenaPtr<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
unsafe { self.0.as_mut() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ArenaAllocated> fmt::Display for TypedArenaPtr<T>
|
||||
where
|
||||
T::Payload: fmt::Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", (self as &T::Payload))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ArenaAllocated> TypedArenaPtr<T> {
|
||||
#[inline]
|
||||
pub fn as_ptr(&self) -> *mut T::Payload {
|
||||
self.0.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T: ?Sized + ArenaAllocated<Payload = ManuallyDrop<P>>> TypedArenaPtr<T> {
|
||||
pub fn drop_payload(&mut self) {
|
||||
if self.get_tag() != ArenaHeaderTag::Dropped {
|
||||
self.set_tag(ArenaHeaderTag::Dropped);
|
||||
unsafe { ManuallyDrop::drop(&mut *self.as_ptr()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ArenaAllocated> TypedArenaPtr<T>
|
||||
where
|
||||
T::Payload: Sized,
|
||||
{
|
||||
#[inline]
|
||||
pub fn header_ptr(&self) -> *const ArenaHeader {
|
||||
unsafe { self.as_ptr().byte_sub(T::header_offset_from_payload()) as *const _ }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn header_ptr_mut(&mut self) -> *mut ArenaHeader {
|
||||
unsafe { self.as_ptr().byte_sub(T::header_offset_from_payload()) as *mut _ }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_mark_bit(&self) -> bool {
|
||||
unsafe { (*self.header_ptr()).m() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_tag(&mut self, tag: ArenaHeaderTag) {
|
||||
unsafe {
|
||||
(*self.header_ptr_mut()).set_tag(tag);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_tag(&self) -> ArenaHeaderTag {
|
||||
unsafe { (*self.header_ptr()).get_tag() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mark(&mut self) {
|
||||
unsafe {
|
||||
(*self.header_ptr_mut()).set_m(true);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unmark(&mut self) {
|
||||
unsafe {
|
||||
(*self.header_ptr_mut()).set_m(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AllocateInArena<AllocFor>
|
||||
where
|
||||
AllocFor: ArenaAllocated,
|
||||
{
|
||||
fn arena_allocate(self, arena: &mut Arena) -> TypedArenaPtr<AllocFor>;
|
||||
}
|
||||
|
||||
impl<P, T: ArenaAllocated<Payload = P>> AllocateInArena<T> for P {
|
||||
fn arena_allocate(self, arena: &mut Arena) -> TypedArenaPtr<T> {
|
||||
T::alloc(arena, self)
|
||||
}
|
||||
}
|
||||
|
||||
/* this isn't allowed due to https://github.com/rust-lang/rust/issues/20400 I think,
|
||||
though P == ManuallyDrop<P> might also be a problem event though that shouldn't be possible
|
||||
impl<P, T: ArenaAllocated<Payload = ManuallyDrop<P>>> AllocateInArena<T> for P {
|
||||
fn arena_allocate(self, arena: &mut Arena) -> TypedArenaPtr<T> {
|
||||
T::alloc(arena, ManuallyDrop::new(self))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub trait ArenaAllocated {
|
||||
type Payload: ?Sized;
|
||||
|
||||
fn tag() -> ArenaHeaderTag;
|
||||
|
||||
fn header_offset_from_payload() -> usize
|
||||
where
|
||||
Self::Payload: Sized,
|
||||
{
|
||||
header_offset_from_payload::<Self>()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// - the caller must guarantee that the pointee type of UntypedArenaPtr is Self
|
||||
/// - the pointer must be non-null
|
||||
unsafe fn typed_ptr(ptr: UntypedArenaPtr) -> TypedArenaPtr<Self>
|
||||
where
|
||||
Self::Payload: Sized,
|
||||
{
|
||||
unsafe {
|
||||
TypedArenaPtr(NonNull::new_unchecked(
|
||||
ptr.get_ptr()
|
||||
.byte_add(Self::header_offset_from_payload())
|
||||
.cast_mut()
|
||||
.cast::<Self::Payload>(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_safety_doc)]
|
||||
fn alloc(arena: &mut Arena, value: Self::Payload) -> TypedArenaPtr<Self>
|
||||
where
|
||||
Self::Payload: Sized,
|
||||
{
|
||||
let size = mem::size_of::<TypedAllocSlab<Self>>();
|
||||
let slab = Box::new(TypedAllocSlab {
|
||||
slab: AllocSlab {
|
||||
next: arena.base.take(),
|
||||
header: ArenaHeader::build_with(size as u64, Self::tag()),
|
||||
},
|
||||
payload: value,
|
||||
});
|
||||
|
||||
let (allocated_ptr, untyped_slab) = slab.to_untyped();
|
||||
|
||||
arena.base = Some(untyped_slab);
|
||||
|
||||
allocated_ptr
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// - ptr points to an allocated slab of the correct kind
|
||||
unsafe fn dealloc(ptr: NonNull<TypedAllocSlab<Self>>) {
|
||||
drop(unsafe { Box::from_raw(ptr.as_ptr()) });
|
||||
}
|
||||
}
|
||||
|
||||
impl ArenaAllocated for Integer {
|
||||
type Payload = Self;
|
||||
#[inline]
|
||||
fn tag() -> ArenaHeaderTag {
|
||||
ArenaHeaderTag::Integer
|
||||
}
|
||||
}
|
||||
|
||||
impl ArenaAllocated for Rational {
|
||||
type Payload = Self;
|
||||
#[inline]
|
||||
fn tag() -> ArenaHeaderTag {
|
||||
ArenaHeaderTag::Rational
|
||||
}
|
||||
}
|
||||
|
||||
impl AllocateInArena<LiveLoadState> for LiveLoadState {
|
||||
fn arena_allocate(self, arena: &mut Arena) -> TypedArenaPtr<LiveLoadState> {
|
||||
LiveLoadState::alloc(arena, ManuallyDrop::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArenaAllocated for LiveLoadState {
|
||||
type Payload = ManuallyDrop<Self>;
|
||||
#[inline]
|
||||
fn tag() -> ArenaHeaderTag {
|
||||
ArenaHeaderTag::LiveLoadState
|
||||
}
|
||||
|
||||
unsafe fn dealloc(ptr: NonNull<TypedAllocSlab<Self>>) {
|
||||
let mut slab = unsafe { Box::from_raw(ptr.as_ptr()) };
|
||||
|
||||
match slab.tag() {
|
||||
ArenaHeaderTag::LiveLoadState | ArenaHeaderTag::InactiveLoadState => {
|
||||
unsafe { ManuallyDrop::drop(&mut slab.payload) };
|
||||
}
|
||||
ArenaHeaderTag::Dropped => {}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
drop(slab);
|
||||
}
|
||||
}
|
||||
|
||||
impl AllocateInArena<TcpListener> for TcpListener {
|
||||
fn arena_allocate(self, arena: &mut Arena) -> TypedArenaPtr<TcpListener> {
|
||||
TcpListener::alloc(arena, ManuallyDrop::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArenaAllocated for TcpListener {
|
||||
type Payload = ManuallyDrop<Self>;
|
||||
#[inline]
|
||||
fn tag() -> ArenaHeaderTag {
|
||||
ArenaHeaderTag::TcpListener
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
impl ArenaAllocated for HttpListener {
|
||||
type Payload = Self;
|
||||
#[inline]
|
||||
fn tag() -> ArenaHeaderTag {
|
||||
ArenaHeaderTag::HttpListener
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
impl ArenaAllocated for HttpResponse {
|
||||
type Payload = Self;
|
||||
#[inline]
|
||||
fn tag() -> ArenaHeaderTag {
|
||||
ArenaHeaderTag::HttpResponse
|
||||
}
|
||||
}
|
||||
|
||||
impl ArenaAllocated for Child {
|
||||
type Payload = ManuallyDrop<Self>;
|
||||
#[inline]
|
||||
fn tag() -> ArenaHeaderTag {
|
||||
ArenaHeaderTag::ChildProcess
|
||||
}
|
||||
}
|
||||
impl AllocateInArena<Child> for Child {
|
||||
fn arena_allocate(self, arena: &mut Arena) -> TypedArenaPtr<Child> {
|
||||
Child::alloc(arena, ManuallyDrop::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct AllocSlab {
|
||||
next: Option<UntypedArenaSlab>,
|
||||
header: ArenaHeader,
|
||||
}
|
||||
|
||||
const _: () = {
|
||||
if std::mem::align_of::<AllocSlab>() < std::mem::align_of::<*const ()>() {
|
||||
panic!("alignment of AllocSlab is too low");
|
||||
}
|
||||
|
||||
if std::mem::offset_of!(AllocSlab, header) % std::mem::align_of::<*const ()>() != 0 {
|
||||
panic!("alignment of header not a multiple of pointers alignment");
|
||||
}
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct TypedAllocSlab<T: ?Sized + ArenaAllocated> {
|
||||
slab: AllocSlab,
|
||||
payload: T::Payload,
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ArenaAllocated> TypedAllocSlab<T> {
|
||||
pub fn tag(&self) -> ArenaHeaderTag {
|
||||
self.slab.header.tag()
|
||||
}
|
||||
|
||||
pub fn payload(&mut self) -> &mut T::Payload {
|
||||
&mut self.payload
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_untyped(self: Box<Self>) -> (TypedArenaPtr<T>, UntypedArenaSlab) {
|
||||
let raw_box = Box::into_raw(self);
|
||||
|
||||
// safety: the pointer from Box::into_raw fulfills addr_of_mut's safety requirements
|
||||
let payload_ptr = unsafe { addr_of_mut!((*raw_box).payload) };
|
||||
|
||||
(
|
||||
TypedArenaPtr(unsafe {
|
||||
// safety: the pointer points into a valid allocation so it is non null
|
||||
ptr::NonNull::new_unchecked(payload_ptr)
|
||||
}),
|
||||
UntypedArenaSlab {
|
||||
// safety: pointer from Box::into_raw is never null
|
||||
slab: unsafe { NonNull::new_unchecked(raw_box.cast::<AllocSlab>()) },
|
||||
tag: T::tag(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UntypedArenaSlab {
|
||||
slab: NonNull<AllocSlab>,
|
||||
tag: ArenaHeaderTag,
|
||||
}
|
||||
|
||||
impl Drop for UntypedArenaSlab {
|
||||
fn drop(&mut self) {
|
||||
unsafe { drop_slab_in_place(self.slab, self.tag) };
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Arena {
|
||||
base: Option<UntypedArenaSlab>,
|
||||
pub f64_tbl: F64Table,
|
||||
pub code_index_tbl: CodeIndexTable,
|
||||
}
|
||||
|
||||
unsafe impl Send for Arena {}
|
||||
unsafe impl Sync for Arena {}
|
||||
|
||||
#[allow(clippy::new_without_default)]
|
||||
impl Arena {
|
||||
#[inline]
|
||||
pub fn new() -> Result<Self, AllocError> {
|
||||
Ok(Arena {
|
||||
base: None,
|
||||
f64_tbl: F64Table::new()?,
|
||||
code_index_tbl: CodeIndexTable::new()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn drop_slab_in_place(value: NonNull<AllocSlab>, tag: ArenaHeaderTag) {
|
||||
macro_rules! drop_typed_slab_in_place {
|
||||
($payload: ty, $value: expr) => {
|
||||
<$payload as ArenaAllocated>::dealloc($value.cast::<TypedAllocSlab<$payload>>())
|
||||
};
|
||||
}
|
||||
unsafe {
|
||||
match tag {
|
||||
ArenaHeaderTag::Integer => {
|
||||
drop_typed_slab_in_place!(Integer, value);
|
||||
}
|
||||
ArenaHeaderTag::Rational => {
|
||||
drop_typed_slab_in_place!(Rational, value);
|
||||
}
|
||||
ArenaHeaderTag::InputFileStream => {
|
||||
drop_typed_slab_in_place!(InputFileStream, value);
|
||||
}
|
||||
ArenaHeaderTag::OutputFileStream => {
|
||||
drop_typed_slab_in_place!(OutputFileStream, value);
|
||||
}
|
||||
ArenaHeaderTag::NamedTcpStream => {
|
||||
drop_typed_slab_in_place!(NamedTcpStream, value);
|
||||
}
|
||||
ArenaHeaderTag::NamedTlsStream => {
|
||||
#[cfg(feature = "tls")]
|
||||
drop_typed_slab_in_place!(NamedTlsStream, value);
|
||||
}
|
||||
ArenaHeaderTag::HttpReadStream => {
|
||||
#[cfg(feature = "http")]
|
||||
drop_typed_slab_in_place!(HttpReadStream, value);
|
||||
}
|
||||
ArenaHeaderTag::HttpWriteStream => {
|
||||
#[cfg(feature = "http")]
|
||||
drop_typed_slab_in_place!(HttpWriteStream, value);
|
||||
}
|
||||
ArenaHeaderTag::ReadlineStream => {
|
||||
drop_typed_slab_in_place!(ReadlineStream, value);
|
||||
}
|
||||
ArenaHeaderTag::StaticStringStream => {
|
||||
drop_typed_slab_in_place!(StaticStringStream, value);
|
||||
}
|
||||
ArenaHeaderTag::ByteStream => {
|
||||
drop_typed_slab_in_place!(ByteStream, value);
|
||||
}
|
||||
ArenaHeaderTag::CallbackStream => {
|
||||
drop_typed_slab_in_place!(CallbackStream, value);
|
||||
}
|
||||
ArenaHeaderTag::InputChannelStream => {
|
||||
drop_typed_slab_in_place!(InputChannelStream, value);
|
||||
}
|
||||
ArenaHeaderTag::LiveLoadState | ArenaHeaderTag::InactiveLoadState => {
|
||||
drop_typed_slab_in_place!(LiveLoadState, value);
|
||||
}
|
||||
ArenaHeaderTag::Dropped => {}
|
||||
ArenaHeaderTag::TcpListener => {
|
||||
drop_typed_slab_in_place!(TcpListener, value);
|
||||
}
|
||||
ArenaHeaderTag::HttpListener => {
|
||||
#[cfg(feature = "http")]
|
||||
drop_typed_slab_in_place!(HttpListener, value);
|
||||
}
|
||||
ArenaHeaderTag::HttpResponse => {
|
||||
#[cfg(feature = "http")]
|
||||
drop_typed_slab_in_place!(HttpResponse, value);
|
||||
}
|
||||
ArenaHeaderTag::StandardOutputStream => {
|
||||
drop_typed_slab_in_place!(StandardOutputStream, value);
|
||||
}
|
||||
ArenaHeaderTag::StandardErrorStream => {
|
||||
drop_typed_slab_in_place!(StandardErrorStream, value);
|
||||
}
|
||||
ArenaHeaderTag::PipeReader => {
|
||||
drop_typed_slab_in_place!(PipeReader, value);
|
||||
}
|
||||
ArenaHeaderTag::PipeWriter => {
|
||||
drop_typed_slab_in_place!(PipeWriter, value);
|
||||
}
|
||||
ArenaHeaderTag::ChildProcess => {
|
||||
drop_typed_slab_in_place!(Child, value);
|
||||
}
|
||||
ArenaHeaderTag::NullStream => {
|
||||
unreachable!("NullStream is never arena allocated!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Arena {
|
||||
fn drop(&mut self) {
|
||||
// we un-nest UntypedArenaSlab to prevent stackoverflow due to the recursive drop
|
||||
|
||||
let mut ptr = self.base.take();
|
||||
|
||||
while let Some(mut slab) = ptr {
|
||||
ptr = unsafe { slab.slab.as_mut() }.next.take();
|
||||
drop(slab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const_assert!(mem::size_of::<AllocSlab>() <= 24);
|
||||
const_assert!(mem::size_of::<OrderedFloat<f64>>() == 8);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::arena::*;
|
||||
use crate::atom_table::*;
|
||||
use crate::machine::mock_wam::*;
|
||||
use crate::types::*;
|
||||
|
||||
use crate::parser::dashu::{Integer, Rational};
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
#[test]
|
||||
fn float_ptr_cast() {
|
||||
let mut wam = MockWAM::new();
|
||||
|
||||
let f = 0f64;
|
||||
let fp = float_alloc!(f, wam.machine_st.arena);
|
||||
let mut cell = HeapCellValue::from(fp);
|
||||
|
||||
assert_eq!(cell.get_tag(), HeapCellValueTag::F64Offset);
|
||||
assert!(!cell.get_mark_bit());
|
||||
assert_eq!(wam.machine_st.arena.f64_tbl.get_entry(fp), OrderedFloat(f));
|
||||
|
||||
cell.set_mark_bit(true);
|
||||
|
||||
assert!(cell.get_mark_bit());
|
||||
|
||||
read_heap_cell!(cell,
|
||||
(HeapCellValueTag::F64Offset, offset) => {
|
||||
let fp = wam.machine_st.arena.f64_tbl.get_entry(offset);
|
||||
assert_eq!(fp, OrderedFloat(0f64))
|
||||
}
|
||||
_ => { unreachable!() }
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heap_cell_value_const_cast() {
|
||||
let mut wam = MockWAM::new();
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
assert_eq!(ConsPtr::NICHE_SHIFT, 0);
|
||||
|
||||
#[cfg(not(target_pointer_width = "32"))]
|
||||
assert_eq!(ConsPtr::NICHE_SHIFT, 3);
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
let dummy_ptr: *const ArenaHeader = std::ptr::without_provenance(0x0000_0438);
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
let dummy_ptr: *const ArenaHeader = std::ptr::without_provenance(0x0000_5555_ff00_0438);
|
||||
|
||||
assert!(dummy_ptr.is_aligned());
|
||||
|
||||
let const_value = HeapCellValue::from_arena_header_ptr(dummy_ptr);
|
||||
|
||||
match const_value.to_untyped_arena_ptr() {
|
||||
Some(arena_ptr) => {
|
||||
assert_eq!(
|
||||
arena_ptr.into_bytes(),
|
||||
const_value.to_untyped_arena_ptr_bytes()
|
||||
);
|
||||
}
|
||||
None => {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
let stream = Stream::from_static_string("test", &mut wam.machine_st.arena);
|
||||
let stream_cell = HeapCellValue::from_arena_header_ptr(stream.as_ptr());
|
||||
|
||||
match stream_cell.to_untyped_arena_ptr() {
|
||||
Some(arena_ptr) => {
|
||||
assert_eq!(
|
||||
arena_ptr.into_bytes(),
|
||||
stream_cell.to_untyped_arena_ptr_bytes()
|
||||
);
|
||||
}
|
||||
None => {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heap_put_literal_tests() {
|
||||
let mut wam = MockWAM::new();
|
||||
|
||||
// integer
|
||||
|
||||
let big_int: Integer = 2 * Integer::from(1u64 << 63);
|
||||
let big_int_ptr: TypedArenaPtr<Integer> = arena_alloc!(big_int, &mut wam.machine_st.arena);
|
||||
|
||||
assert!(!big_int_ptr.as_ptr().is_null());
|
||||
|
||||
let cell = HeapCellValue::from(big_int_ptr);
|
||||
assert_eq!(cell.get_tag(), HeapCellValueTag::Cons);
|
||||
|
||||
let untyped_arena_ptr = match cell.to_untyped_arena_ptr() {
|
||||
Some(ptr) => ptr,
|
||||
None => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
match_untyped_arena_ptr!(untyped_arena_ptr,
|
||||
(ArenaHeaderTag::Integer, n) => {
|
||||
assert_eq!(&*n, &(2 * Integer::from(1u64 << 63)))
|
||||
}
|
||||
_ => unreachable!()
|
||||
);
|
||||
|
||||
read_heap_cell!(cell,
|
||||
(HeapCellValueTag::Cons, cons_ptr) => {
|
||||
match_untyped_arena_ptr!(cons_ptr,
|
||||
(ArenaHeaderTag::Integer, n) => {
|
||||
assert_eq!(&*n, &(2 * Integer::from(1u64 << 63)))
|
||||
}
|
||||
_ => { unreachable!() }
|
||||
)
|
||||
}
|
||||
_ => { unreachable!() }
|
||||
);
|
||||
|
||||
// rational
|
||||
|
||||
let big_rat = Rational::from(2) * Rational::from(1u64 << 63);
|
||||
let big_rat_ptr: TypedArenaPtr<Rational> = arena_alloc!(big_rat, &mut wam.machine_st.arena);
|
||||
|
||||
assert!(!big_rat_ptr.as_ptr().is_null());
|
||||
|
||||
let rat_cell = typed_arena_ptr_as_cell!(big_rat_ptr);
|
||||
assert_eq!(cell.get_tag(), HeapCellValueTag::Cons);
|
||||
|
||||
match rat_cell.to_untyped_arena_ptr() {
|
||||
Some(untyped_arena_ptr) => {
|
||||
assert_eq!(
|
||||
Some(big_rat_ptr.header_ptr()),
|
||||
Some(untyped_arena_ptr.get_ptr()),
|
||||
);
|
||||
}
|
||||
None => {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
// assert_eq!(wam.machine_st.heap[1usize].get_tag(), HeapCellValueTag::Cons);
|
||||
|
||||
read_heap_cell!(rat_cell,
|
||||
(HeapCellValueTag::Cons, cons_ptr) => {
|
||||
match_untyped_arena_ptr!(cons_ptr,
|
||||
(ArenaHeaderTag::Rational, n) => {
|
||||
assert_eq!(&*n, &(Rational::from(2) * Rational::from(1u64 << 63)));
|
||||
}
|
||||
_ => unreachable!()
|
||||
)
|
||||
}
|
||||
_ => { unreachable!() }
|
||||
);
|
||||
|
||||
// atom
|
||||
|
||||
let f_atom = atom!("f");
|
||||
let g_atom = atom!("g");
|
||||
|
||||
assert_eq!(&*f_atom.as_str(), "f");
|
||||
assert_eq!(&*g_atom.as_str(), "g");
|
||||
|
||||
let f_atom_cell = atom_as_cell!(f_atom);
|
||||
let g_atom_cell = atom_as_cell!(g_atom);
|
||||
|
||||
assert_eq!(f_atom_cell.get_tag(), HeapCellValueTag::Atom);
|
||||
|
||||
match f_atom_cell.to_atom() {
|
||||
Some(atom) => {
|
||||
assert_eq!(f_atom, atom);
|
||||
assert_eq!(&*atom.as_str(), "f");
|
||||
}
|
||||
None => {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
read_heap_cell!(f_atom_cell,
|
||||
(HeapCellValueTag::Atom, (atom, arity)) => {
|
||||
assert_eq!(f_atom, atom);
|
||||
assert_eq!(arity, 0);
|
||||
assert_eq!(&*atom.as_str(), "f");
|
||||
}
|
||||
_ => { unreachable!() }
|
||||
);
|
||||
|
||||
read_heap_cell!(g_atom_cell,
|
||||
(HeapCellValueTag::Atom, (atom, arity)) => {
|
||||
assert_eq!(g_atom, atom);
|
||||
assert_eq!(arity, 0);
|
||||
assert_eq!(&*atom.as_str(), "g");
|
||||
}
|
||||
_ => { unreachable!() }
|
||||
);
|
||||
|
||||
// fixnum
|
||||
|
||||
let fixnum_cell = fixnum_as_cell!(Fixnum::build_with(3));
|
||||
|
||||
assert_eq!(fixnum_cell.get_tag(), HeapCellValueTag::Fixnum);
|
||||
|
||||
match fixnum_cell.to_fixnum() {
|
||||
Some(n) => assert_eq!(n.get_num(), 3),
|
||||
None => unreachable!(),
|
||||
}
|
||||
|
||||
read_heap_cell!(fixnum_cell,
|
||||
(HeapCellValueTag::Fixnum, n) => {
|
||||
assert_eq!(n.get_num(), 3);
|
||||
}
|
||||
_ => { unreachable!() }
|
||||
);
|
||||
|
||||
let fixnum_b_cell = fixnum_as_cell!(
|
||||
Fixnum::build_with_checked(1i64 << 54).expect("1 << 54 fits in Fixnum")
|
||||
);
|
||||
|
||||
assert_eq!(fixnum_b_cell.get_tag(), HeapCellValueTag::Fixnum);
|
||||
|
||||
match fixnum_b_cell.to_fixnum() {
|
||||
Some(n) => assert_eq!(n.get_num(), 1 << 54),
|
||||
None => unreachable!(),
|
||||
}
|
||||
|
||||
Fixnum::build_with_checked(1i64 << 56).expect_err("1 << 56 is too large for fixnum");
|
||||
|
||||
Fixnum::build_with_checked(i64::MAX).expect_err("i64::MAX is too large for Fixnum");
|
||||
Fixnum::build_with_checked(i64::MIN).expect_err("i64::MIN is too small for Fixnum");
|
||||
assert_eq!(
|
||||
Fixnum::build_with_checked(-1i64)
|
||||
.expect("-1 fits in fixnum")
|
||||
.get_num(),
|
||||
-1
|
||||
);
|
||||
|
||||
Fixnum::build_with_checked((1i64 << 55) - 1)
|
||||
.expect("(1 << 55) - 1 is the largest value that fits in Fixnum");
|
||||
|
||||
Fixnum::build_with_checked(-(1i64 << 55))
|
||||
.expect("-(1 << 55) is the smallest value that fits in fixnum");
|
||||
Fixnum::build_with_checked(-(1i64 << 55) - 1)
|
||||
.expect_err("-(1<<55) - 1 is too small for Fixnum");
|
||||
|
||||
assert_eq!(
|
||||
-Fixnum::build_with_checked(-1i64).expect("-1 fits in Fixnum"),
|
||||
Fixnum::build_with(1)
|
||||
);
|
||||
|
||||
// float
|
||||
|
||||
let float = std::f64::consts::PI;
|
||||
let float_ptr = float_alloc!(float, wam.machine_st.arena);
|
||||
let cell = HeapCellValue::from(float_ptr);
|
||||
|
||||
assert_eq!(cell.get_tag(), HeapCellValueTag::F64Offset);
|
||||
|
||||
// char
|
||||
|
||||
let c = 'c';
|
||||
let char_cell = char_as_cell!(c);
|
||||
|
||||
read_heap_cell!(char_cell,
|
||||
(HeapCellValueTag::Atom, (c, _arity)) => {
|
||||
assert_eq!(&*c.as_str(), "c");
|
||||
}
|
||||
_ => { unreachable!() }
|
||||
);
|
||||
|
||||
let c = 'Ћ';
|
||||
let cyrillic_char_cell = char_as_cell!(c);
|
||||
|
||||
read_heap_cell!(cyrillic_char_cell,
|
||||
(HeapCellValueTag::Atom, (c, _arity)) => {
|
||||
assert_eq!(&*c.as_str(), "Ћ");
|
||||
}
|
||||
_ => { unreachable!() }
|
||||
);
|
||||
|
||||
// empty list
|
||||
|
||||
let cell = empty_list_as_cell!();
|
||||
|
||||
read_heap_cell!(cell,
|
||||
(HeapCellValueTag::Atom, (el, _arity)) => {
|
||||
assert_eq!(el.flat_index(), empty_list_as_cell!().get_value());
|
||||
assert_eq!(&*el.as_str(), "[]");
|
||||
}
|
||||
_ => { unreachable!() }
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,548 +0,0 @@
|
||||
use crate::machine::heap::AllocError;
|
||||
use crate::parser::ast::MAX_ARITY;
|
||||
use crate::raw_block::*;
|
||||
use crate::types::*;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::mem;
|
||||
use std::ops::Deref;
|
||||
use std::ptr;
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::RwLock;
|
||||
use std::sync::Weak;
|
||||
|
||||
use arcu::Rcu;
|
||||
use arcu::atomic::Arcu;
|
||||
use arcu::epoch_counters::GlobalEpochCounterPool;
|
||||
use arcu::rcu_ref::RcuRef;
|
||||
use indexmap::IndexSet;
|
||||
|
||||
use modular_bitfield::prelude::*;
|
||||
|
||||
#[bitfield]
|
||||
#[repr(u64)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct AtomCell {
|
||||
name: B48,
|
||||
arity: B8,
|
||||
#[allow(unused)]
|
||||
f: bool,
|
||||
#[allow(unused)]
|
||||
m: bool,
|
||||
#[allow(unused)]
|
||||
is_inlined: bool,
|
||||
#[allow(unused)]
|
||||
tag: B5,
|
||||
}
|
||||
|
||||
const INLINED_ATOM_MAX_LEN: usize = 6;
|
||||
|
||||
const_assert!(INLINED_ATOM_MAX_LEN < mem::size_of::<AtomCell>());
|
||||
const_assert!(mem::size_of::<AtomCell>() == 8);
|
||||
|
||||
const_assert!(INLINED_ATOM_MAX_LEN < mem::size_of::<Atom>());
|
||||
const_assert!(mem::size_of::<Atom>() == 8);
|
||||
|
||||
impl AtomCell {
|
||||
#[inline]
|
||||
fn new_static(index: u64) -> Self {
|
||||
// upper 23 bits of index must be 0
|
||||
debug_assert_eq!(index & !((1 << 49) - 1), 0);
|
||||
AtomCell::new()
|
||||
.with_name(index)
|
||||
.with_arity(0u8)
|
||||
.with_m(false)
|
||||
.with_f(false)
|
||||
.with_is_inlined(false)
|
||||
.with_tag(HeapCellValueTag::Atom as u8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_inlined(string: &str, arity: u8) -> Self {
|
||||
debug_assert!(string.len() <= INLINED_ATOM_MAX_LEN);
|
||||
|
||||
let mut string_buf: [u8; 8] = [0u8; 8];
|
||||
string_buf[..string.len()].copy_from_slice(string.as_bytes());
|
||||
let encoding = u64::from_le_bytes(string_buf);
|
||||
|
||||
AtomCell::new()
|
||||
.with_name(encoding)
|
||||
.with_arity(arity)
|
||||
.with_m(false)
|
||||
.with_f(false)
|
||||
.with_is_inlined(true)
|
||||
.with_tag(HeapCellValueTag::Atom as u8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_char_inlined(c: char) -> Self {
|
||||
if c == '\u{0}' {
|
||||
return Self::new_static(NULL_ATOM.flat_index());
|
||||
}
|
||||
|
||||
let mut char_buf = [0u8; 8];
|
||||
c.encode_utf8(&mut char_buf);
|
||||
|
||||
let encoding = u64::from_le_bytes(char_buf);
|
||||
|
||||
AtomCell::new()
|
||||
.with_name(encoding)
|
||||
.with_arity(0u8)
|
||||
.with_m(false)
|
||||
.with_f(false)
|
||||
.with_is_inlined(true)
|
||||
.with_tag(HeapCellValueTag::Atom as u8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn build_with(atom_index: u64, arity: u8) -> Self {
|
||||
debug_assert!((arity as usize) <= MAX_ARITY);
|
||||
|
||||
AtomCell::new()
|
||||
.with_name(atom_index >> 1)
|
||||
.with_arity(arity)
|
||||
.with_f(false)
|
||||
.with_m(false)
|
||||
.with_is_inlined(atom_index & 1 == 1)
|
||||
.with_tag(HeapCellValueTag::Atom as u8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_name(self) -> Atom {
|
||||
Atom {
|
||||
index: (self.name() << 1) | self.is_inlined() as u64,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_arity(self) -> usize {
|
||||
self.arity() as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_name_and_arity(self) -> (Atom, usize) {
|
||||
(self.get_name(), self.get_arity())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Atom {
|
||||
pub index: u64,
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/static_atoms.rs"));
|
||||
|
||||
// populate these in STRINGS so they can be used from build_functor
|
||||
const _: Atom = atom!(".");
|
||||
const _: Atom = atom!("[]");
|
||||
const NULL_ATOM: Atom = atom!("\0");
|
||||
|
||||
impl<'a> From<&'a Atom> for Atom {
|
||||
#[inline]
|
||||
fn from(atom: &'a Atom) -> Self {
|
||||
*atom
|
||||
}
|
||||
}
|
||||
|
||||
impl indexmap::Equivalent<Atom> for str {
|
||||
fn equivalent(&self, key: &Atom) -> bool {
|
||||
&*key.as_str() == self
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for Atom {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.as_str().deref() == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for Atom {
|
||||
fn eq(&self, &other: &&str) -> bool {
|
||||
self.as_str().deref() == other
|
||||
}
|
||||
}
|
||||
|
||||
const ATOM_TABLE_INIT_SIZE: usize = 1 << 16;
|
||||
const ATOM_TABLE_ALIGN: usize = 8;
|
||||
|
||||
#[inline(always)]
|
||||
fn global_atom_table() -> &'static RwLock<Weak<AtomTable>> {
|
||||
static GLOBAL_ATOM_TABLE: RwLock<Weak<AtomTable>> = RwLock::new(Weak::new());
|
||||
&GLOBAL_ATOM_TABLE
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn arc_atom_table() -> Option<Arc<AtomTable>> {
|
||||
global_atom_table().read().unwrap().upgrade()
|
||||
}
|
||||
|
||||
impl RawBlockTraits for AtomTable {
|
||||
#[inline]
|
||||
fn init_size() -> usize {
|
||||
ATOM_TABLE_INIT_SIZE
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn align() -> usize {
|
||||
ATOM_TABLE_ALIGN
|
||||
}
|
||||
}
|
||||
|
||||
#[bitfield]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct AtomHeader {
|
||||
#[allow(unused)]
|
||||
m: bool,
|
||||
len: B50,
|
||||
#[allow(unused)]
|
||||
padding: B13,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct AtomData {
|
||||
header: AtomHeader,
|
||||
data: str,
|
||||
}
|
||||
|
||||
impl AtomHeader {
|
||||
fn build_with(len: u64) -> Self {
|
||||
AtomHeader::new().with_len(len).with_m(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Atom {
|
||||
#[inline]
|
||||
fn hash<H: Hasher>(&self, hasher: &mut H) {
|
||||
self.as_str().hash(hasher)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AtomString<'a> {
|
||||
Static(&'a str),
|
||||
Inlined([u8; 8]),
|
||||
Dynamic(AtomTableRef<str>),
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn inlined_to_str(bytes: &[u8; 8]) -> &str {
|
||||
let slice_len = bytes
|
||||
.iter()
|
||||
.position(|&b| b == 0u8)
|
||||
.unwrap_or(INLINED_ATOM_MAX_LEN);
|
||||
|
||||
unsafe { str::from_utf8_unchecked(&bytes[..slice_len]) }
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AtomString<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
std::fmt::Debug::fmt(self.deref(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AtomString<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self.deref(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for AtomString<'_> {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Self::Static(reference) => reference,
|
||||
Self::Inlined(inlined) => inlined_to_str(inlined),
|
||||
Self::Dynamic(guard) => guard.deref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "repl")]
|
||||
impl rustyline::completion::Candidate for AtomString<'_> {
|
||||
fn display(&self) -> &str {
|
||||
self.deref()
|
||||
}
|
||||
|
||||
fn replacement(&self) -> &str {
|
||||
self.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Atom {
|
||||
#[inline]
|
||||
fn new_inlined(string: &str) -> Self {
|
||||
AtomCell::new_inlined(string, 0).get_name()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_static(self) -> bool {
|
||||
if self.is_inlined() {
|
||||
true
|
||||
} else {
|
||||
(self.flat_index() as usize) < STRINGS.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn flat_index(self) -> u64 {
|
||||
self.index >> 1
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn is_inlined(self) -> bool {
|
||||
self.index & 1 == 1
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn as_ptr(self) -> Option<AtomTableRef<AtomData>> {
|
||||
if self.is_static() {
|
||||
None
|
||||
} else {
|
||||
let atom_table =
|
||||
arc_atom_table().expect("We should only have an Atom while there is an AtomTable");
|
||||
|
||||
AtomTableRef::try_map(atom_table.inner.read(), |buf| unsafe {
|
||||
let ptr = buf
|
||||
.block
|
||||
.get_unchecked(self.flat_index() as usize - STRINGS.len());
|
||||
// TODO use std::ptr::from_raw_parts instead when feature ptr_metadata is stable rust-lang/rust#81513
|
||||
let atom_data = &*(std::ptr::slice_from_raw_parts(ptr, 0) as *const AtomData);
|
||||
let len = atom_data.header.len();
|
||||
Some(&*(std::ptr::slice_from_raw_parts(ptr, len as usize) as *const AtomData))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn from(index: u64) -> Self {
|
||||
Self { index }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn len(self) -> usize {
|
||||
if let Some(s) = self.inlined_str() {
|
||||
s.len()
|
||||
} else if self.is_static() {
|
||||
let index = self.flat_index();
|
||||
STRINGS[index as usize].len()
|
||||
} else {
|
||||
let len: u64 = self.as_ptr().unwrap().header.len();
|
||||
len as usize
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn as_char(self) -> Option<char> {
|
||||
let s = self.as_str();
|
||||
let mut it = s.chars();
|
||||
|
||||
let c1 = it.next();
|
||||
let c2 = it.next();
|
||||
|
||||
if c2.is_none() { c1 } else { None }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inlined_str<'a>(&self) -> Option<AtomString<'a>> {
|
||||
if self.is_inlined() {
|
||||
Some(AtomString::Inlined(self.flat_index().to_le_bytes()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> AtomString<'static> {
|
||||
if let Some(s) = self.inlined_str() {
|
||||
s
|
||||
} else if self.is_static() {
|
||||
let index = self.flat_index() as usize;
|
||||
AtomString::Static(STRINGS[index])
|
||||
} else if let Some(ptr) = self.as_ptr() {
|
||||
AtomString::Dynamic(AtomTableRef::map(ptr, |ptr| &ptr.data))
|
||||
} else {
|
||||
AtomString::Static(STRINGS[(self.index >> 1) as usize])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn defrock_brackets(&self, atom_tbl: &AtomTable) -> Self {
|
||||
let s = self.as_str();
|
||||
|
||||
let sub_str = if s.starts_with('(') && s.ends_with(')') {
|
||||
&s['('.len_utf8()..s.len() - ')'.len_utf8()]
|
||||
} else {
|
||||
return *self;
|
||||
};
|
||||
|
||||
AtomTable::build_with(atom_tbl, sub_str)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn write_to_ptr(string: &str, ptr: *mut u8) {
|
||||
unsafe {
|
||||
ptr::write(ptr as *mut _, AtomHeader::build_with(string.len() as u64));
|
||||
let str_ptr = ptr.add(mem::size_of::<AtomHeader>());
|
||||
ptr::copy_nonoverlapping(string.as_ptr(), str_ptr, string.len());
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Atom {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &Atom) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Atom {
|
||||
#[inline]
|
||||
fn cmp(&self, other: &Atom) -> Ordering {
|
||||
self.as_str().cmp(&*other.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InnerAtomTable {
|
||||
block: RawBlock<AtomTable>,
|
||||
pub table: Arcu<IndexSet<Atom>, GlobalEpochCounterPool>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AtomTable {
|
||||
inner: Arcu<InnerAtomTable, GlobalEpochCounterPool>,
|
||||
// this lock is taking during resizing
|
||||
update: Mutex<()>,
|
||||
}
|
||||
|
||||
pub type AtomTableRef<M> = arcu::rcu_ref::RcuRef<InnerAtomTable, M>;
|
||||
|
||||
impl InnerAtomTable {
|
||||
#[inline(always)]
|
||||
fn lookup_str(self: &InnerAtomTable, string: &str) -> Option<Atom> {
|
||||
STATIC_ATOMS_MAP
|
||||
.get(string)
|
||||
.cloned()
|
||||
.or_else(|| self.table.read().get(string).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
impl AtomTable {
|
||||
#[inline]
|
||||
pub fn new() -> Result<Arc<Self>, AllocError> {
|
||||
let upgraded = global_atom_table().read().unwrap().upgrade();
|
||||
// don't inline upgraded, otherwise temporary will be dropped too late in case of None
|
||||
if let Some(atom_table) = upgraded {
|
||||
Ok(atom_table)
|
||||
} else {
|
||||
let mut guard = global_atom_table().write().unwrap();
|
||||
// try to upgrade again in case we lost the race on the write lock
|
||||
if let Some(atom_table) = guard.upgrade() {
|
||||
Ok(atom_table)
|
||||
} else {
|
||||
let atom_table = Arc::new(Self {
|
||||
inner: Arcu::new(
|
||||
InnerAtomTable {
|
||||
block: RawBlock::new()?,
|
||||
table: Arcu::new(IndexSet::new(), GlobalEpochCounterPool),
|
||||
},
|
||||
GlobalEpochCounterPool,
|
||||
),
|
||||
update: Mutex::new(()),
|
||||
});
|
||||
*guard = Arc::downgrade(&atom_table);
|
||||
Ok(atom_table)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn retrieve() -> Arc<Self> {
|
||||
global_atom_table().read().unwrap().upgrade().unwrap()
|
||||
}
|
||||
|
||||
pub fn active_table(&self) -> RcuRef<IndexSet<Atom>, IndexSet<Atom>> {
|
||||
self.inner.read().table.read()
|
||||
}
|
||||
|
||||
pub fn build_with(atom_table: &AtomTable, string: &str) -> Atom {
|
||||
if !string.is_empty() && string.len() <= INLINED_ATOM_MAX_LEN && !string.contains('\u{0}') {
|
||||
return Atom::new_inlined(string);
|
||||
}
|
||||
|
||||
loop {
|
||||
let mut block_epoch = atom_table.inner.read();
|
||||
let mut table_epoch = block_epoch.table.read();
|
||||
|
||||
if let Some(atom) = block_epoch.lookup_str(string) {
|
||||
return atom;
|
||||
}
|
||||
|
||||
// take a lock to prevent concurrent updates
|
||||
let update_guard = atom_table.update.lock().unwrap();
|
||||
|
||||
let is_same_allocation = RcuRef::same_epoch(&block_epoch, &atom_table.inner.read());
|
||||
let is_same_atom_list = RcuRef::same_epoch(&table_epoch, &block_epoch.table.read());
|
||||
|
||||
if !(is_same_allocation && is_same_atom_list) {
|
||||
// some other thread raced us between our lookup and
|
||||
// us aquring the update lock,
|
||||
// try again
|
||||
continue;
|
||||
}
|
||||
|
||||
let size = mem::size_of::<AtomHeader>() + string.len();
|
||||
let size = size.next_multiple_of(AtomTable::align());
|
||||
|
||||
unsafe {
|
||||
let len_ptr = loop {
|
||||
let ptr = block_epoch.block.alloc(size);
|
||||
|
||||
if ptr.is_null() {
|
||||
// garbage collection would go here
|
||||
let new_block = block_epoch.block.grow_new().unwrap();
|
||||
let new_table = Arcu::new(table_epoch.clone(), GlobalEpochCounterPool);
|
||||
let new_alloc = InnerAtomTable {
|
||||
block: new_block,
|
||||
table: new_table,
|
||||
};
|
||||
atom_table.inner.replace(new_alloc);
|
||||
block_epoch = atom_table.inner.read();
|
||||
table_epoch = block_epoch.table.read();
|
||||
} else {
|
||||
break ptr;
|
||||
}
|
||||
};
|
||||
|
||||
// SAFETY: `len_ptr` was obtained from `block_epoch.block.alloc()`
|
||||
let len_offset = block_epoch.block.get_offset(len_ptr);
|
||||
|
||||
write_to_ptr(string, len_ptr);
|
||||
|
||||
let atom = AtomCell::new()
|
||||
.with_name((STRINGS.len() + len_offset) as u64)
|
||||
.with_arity(0)
|
||||
.with_f(false)
|
||||
.with_m(false)
|
||||
.with_is_inlined(false)
|
||||
.with_tag(HeapCellValueTag::Atom as u8)
|
||||
.get_name();
|
||||
|
||||
let mut table = table_epoch.clone();
|
||||
table.insert(atom);
|
||||
block_epoch.table.replace(table);
|
||||
|
||||
// explicit drop to ensure we don't accidentally drop it early
|
||||
drop(update_guard);
|
||||
|
||||
return atom;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for AtomTable {}
|
||||
unsafe impl Sync for AtomTable {}
|
||||
@@ -1,7 +0,0 @@
|
||||
fn main() -> std::process::ExitCode {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
return std::process::ExitCode::SUCCESS;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
return scryer_prolog::run_binary();
|
||||
}
|
||||
851
src/clause_types.rs
Normal file
851
src/clause_types.rs
Normal file
@@ -0,0 +1,851 @@
|
||||
use crate::prolog_parser::ast::*;
|
||||
|
||||
use crate::forms::Number;
|
||||
use crate::machine::machine_indices::*;
|
||||
use crate::rug::rand::RandState;
|
||||
|
||||
use crate::ref_thread_local::RefThreadLocal;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum CompareNumberQT {
|
||||
GreaterThan,
|
||||
LessThan,
|
||||
GreaterThanOrEqual,
|
||||
LessThanOrEqual,
|
||||
NotEqual,
|
||||
Equal,
|
||||
}
|
||||
|
||||
impl CompareNumberQT {
|
||||
fn name(self) -> &'static str {
|
||||
match self {
|
||||
CompareNumberQT::GreaterThan => ">",
|
||||
CompareNumberQT::LessThan => "<",
|
||||
CompareNumberQT::GreaterThanOrEqual => ">=",
|
||||
CompareNumberQT::LessThanOrEqual => "=<",
|
||||
CompareNumberQT::NotEqual => "=\\=",
|
||||
CompareNumberQT::Equal => "=:=",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CompareTermQT {
|
||||
LessThan,
|
||||
LessThanOrEqual,
|
||||
GreaterThanOrEqual,
|
||||
GreaterThan,
|
||||
}
|
||||
|
||||
impl CompareTermQT {
|
||||
fn name<'a>(self) -> &'a str {
|
||||
match self {
|
||||
CompareTermQT::GreaterThan => "@>",
|
||||
CompareTermQT::LessThan => "@<",
|
||||
CompareTermQT::GreaterThanOrEqual => "@>=",
|
||||
CompareTermQT::LessThanOrEqual => "@=<",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ArithmeticTerm {
|
||||
Reg(RegType),
|
||||
Interm(usize),
|
||||
Number(Number),
|
||||
}
|
||||
|
||||
impl ArithmeticTerm {
|
||||
pub fn interm_or(&self, interm: usize) -> usize {
|
||||
if let &ArithmeticTerm::Interm(interm) = self {
|
||||
interm
|
||||
} else {
|
||||
interm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum InlinedClauseType {
|
||||
CompareNumber(CompareNumberQT, ArithmeticTerm, ArithmeticTerm),
|
||||
IsAtom(RegType),
|
||||
IsAtomic(RegType),
|
||||
IsCompound(RegType),
|
||||
IsInteger(RegType),
|
||||
IsRational(RegType),
|
||||
IsFloat(RegType),
|
||||
IsNonVar(RegType),
|
||||
IsVar(RegType),
|
||||
}
|
||||
|
||||
ref_thread_local! {
|
||||
pub static managed RANDOM_STATE: RandState<'static> = RandState::new();
|
||||
}
|
||||
|
||||
ref_thread_local! {
|
||||
pub static managed CLAUSE_TYPE_FORMS: BTreeMap<(&'static str, usize), ClauseType> = {
|
||||
let mut m = BTreeMap::new();
|
||||
|
||||
let r1 = temp_v!(1);
|
||||
let r2 = temp_v!(2);
|
||||
|
||||
m.insert((">", 2),
|
||||
ClauseType::Inlined(InlinedClauseType::CompareNumber(CompareNumberQT::GreaterThan, ar_reg!(r1), ar_reg!(r2))));
|
||||
m.insert(("<", 2),
|
||||
ClauseType::Inlined(InlinedClauseType::CompareNumber(CompareNumberQT::LessThan, ar_reg!(r1), ar_reg!(r2))));
|
||||
m.insert((">=", 2), ClauseType::Inlined(InlinedClauseType::CompareNumber(CompareNumberQT::GreaterThanOrEqual, ar_reg!(r1), ar_reg!(r2))));
|
||||
m.insert(("=<", 2), ClauseType::Inlined(InlinedClauseType::CompareNumber(CompareNumberQT::LessThanOrEqual, ar_reg!(r1), ar_reg!(r2))));
|
||||
m.insert(("=:=", 2), ClauseType::Inlined(InlinedClauseType::CompareNumber(CompareNumberQT::Equal, ar_reg!(r1), ar_reg!(r2))));
|
||||
m.insert(("=\\=", 2), ClauseType::Inlined(InlinedClauseType::CompareNumber(CompareNumberQT::NotEqual, ar_reg!(r1), ar_reg!(r2))));
|
||||
m.insert(("atom", 1), ClauseType::Inlined(InlinedClauseType::IsAtom(r1)));
|
||||
m.insert(("atomic", 1), ClauseType::Inlined(InlinedClauseType::IsAtomic(r1)));
|
||||
m.insert(("compound", 1), ClauseType::Inlined(InlinedClauseType::IsCompound(r1)));
|
||||
m.insert(("integer", 1), ClauseType::Inlined(InlinedClauseType::IsInteger(r1)));
|
||||
m.insert(("rational", 1), ClauseType::Inlined(InlinedClauseType::IsRational(r1)));
|
||||
m.insert(("float", 1), ClauseType::Inlined(InlinedClauseType::IsFloat(r1)));
|
||||
m.insert(("nonvar", 1), ClauseType::Inlined(InlinedClauseType::IsNonVar(r1)));
|
||||
m.insert(("var", 1), ClauseType::Inlined(InlinedClauseType::IsVar(r1)));
|
||||
m.insert(("acyclic_term", 1), ClauseType::BuiltIn(BuiltInClauseType::AcyclicTerm));
|
||||
m.insert(("arg", 3), ClauseType::BuiltIn(BuiltInClauseType::Arg));
|
||||
m.insert(("compare", 3), ClauseType::BuiltIn(BuiltInClauseType::Compare));
|
||||
m.insert(("@>", 2), ClauseType::BuiltIn(BuiltInClauseType::CompareTerm(CompareTermQT::GreaterThan)));
|
||||
m.insert(("@<", 2), ClauseType::BuiltIn(BuiltInClauseType::CompareTerm(CompareTermQT::LessThan)));
|
||||
m.insert(("@>=", 2), ClauseType::BuiltIn(BuiltInClauseType::CompareTerm(CompareTermQT::GreaterThanOrEqual)));
|
||||
m.insert(("@=<", 2), ClauseType::BuiltIn(BuiltInClauseType::CompareTerm(CompareTermQT::LessThanOrEqual)));
|
||||
m.insert(("copy_term", 2), ClauseType::BuiltIn(BuiltInClauseType::CopyTerm));
|
||||
m.insert(("==", 2), ClauseType::BuiltIn(BuiltInClauseType::Eq));
|
||||
m.insert(("functor", 3), ClauseType::BuiltIn(BuiltInClauseType::Functor));
|
||||
m.insert(("ground", 1), ClauseType::BuiltIn(BuiltInClauseType::Ground));
|
||||
m.insert(("is", 2), ClauseType::BuiltIn(BuiltInClauseType::Is(r1, ar_reg!(r2))));
|
||||
m.insert(("keysort", 2), ClauseType::BuiltIn(BuiltInClauseType::KeySort));
|
||||
m.insert(("nl", 0), ClauseType::BuiltIn(BuiltInClauseType::Nl));
|
||||
m.insert(("\\==", 2), ClauseType::BuiltIn(BuiltInClauseType::NotEq));
|
||||
m.insert(("read", 1), ClauseType::BuiltIn(BuiltInClauseType::Read));
|
||||
m.insert(("sort", 2), ClauseType::BuiltIn(BuiltInClauseType::Sort));
|
||||
|
||||
m
|
||||
};
|
||||
}
|
||||
|
||||
impl InlinedClauseType {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
&InlinedClauseType::CompareNumber(qt, ..) => qt.name(),
|
||||
&InlinedClauseType::IsAtom(..) => "atom",
|
||||
&InlinedClauseType::IsAtomic(..) => "atomic",
|
||||
&InlinedClauseType::IsCompound(..) => "compound",
|
||||
&InlinedClauseType::IsInteger(..) => "integer",
|
||||
&InlinedClauseType::IsRational(..) => "rational",
|
||||
&InlinedClauseType::IsFloat(..) => "float",
|
||||
&InlinedClauseType::IsNonVar(..) => "nonvar",
|
||||
&InlinedClauseType::IsVar(..) => "var",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum SystemClauseType {
|
||||
AbolishClause,
|
||||
AbolishModuleClause,
|
||||
AssertDynamicPredicateToBack,
|
||||
AssertDynamicPredicateToFront,
|
||||
AtEndOfExpansion,
|
||||
AtomChars,
|
||||
AtomCodes,
|
||||
AtomLength,
|
||||
BindFromRegister,
|
||||
CallContinuation,
|
||||
CharCode,
|
||||
CharType,
|
||||
CharsToNumber,
|
||||
ClearAttributeGoals,
|
||||
CloneAttributeGoals,
|
||||
CodesToNumber,
|
||||
CopyTermWithoutAttrVars,
|
||||
CheckCutPoint,
|
||||
Close,
|
||||
CopyToLiftedHeap,
|
||||
CreatePartialString,
|
||||
CurrentHostname,
|
||||
CurrentInput,
|
||||
CurrentOutput,
|
||||
DirectoryFiles,
|
||||
FileSize,
|
||||
FileExists,
|
||||
DirectoryExists,
|
||||
DirectorySeparator,
|
||||
MakeDirectory,
|
||||
DeleteFile,
|
||||
WorkingDirectory,
|
||||
PathCanonical,
|
||||
FileTime,
|
||||
DeleteAttribute,
|
||||
DeleteHeadAttribute,
|
||||
DynamicModuleResolution(usize),
|
||||
EnqueueAttributeGoal,
|
||||
EnqueueAttributedVar,
|
||||
ExpandGoal,
|
||||
ExpandTerm,
|
||||
FetchGlobalVar,
|
||||
FetchGlobalVarWithOffset,
|
||||
FirstStream,
|
||||
FlushOutput,
|
||||
GetByte,
|
||||
GetChar,
|
||||
GetNChars,
|
||||
GetCode,
|
||||
GetSingleChar,
|
||||
ResetAttrVarState,
|
||||
TruncateIfNoLiftedHeapGrowthDiff,
|
||||
TruncateIfNoLiftedHeapGrowth,
|
||||
GetAttributedVariableList,
|
||||
GetAttrVarQueueDelimiter,
|
||||
GetAttrVarQueueBeyond,
|
||||
GetBValue,
|
||||
GetClause,
|
||||
GetContinuationChunk,
|
||||
GetModuleClause,
|
||||
GetNextDBRef,
|
||||
GetNextOpDBRef,
|
||||
IsPartialString,
|
||||
LookupDBRef,
|
||||
LookupOpDBRef,
|
||||
Halt,
|
||||
ModuleHeadIsDynamic,
|
||||
GetLiftedHeapFromOffset,
|
||||
GetLiftedHeapFromOffsetDiff,
|
||||
GetSCCCleaner,
|
||||
HeadIsDynamic,
|
||||
InstallSCCCleaner,
|
||||
InstallInferenceCounter,
|
||||
LiftedHeapLength,
|
||||
ModuleAssertDynamicPredicateToFront,
|
||||
ModuleAssertDynamicPredicateToBack,
|
||||
ModuleExists,
|
||||
ModuleOf,
|
||||
ModuleRetractClause,
|
||||
NextEP,
|
||||
NoSuchPredicate,
|
||||
NumberToChars,
|
||||
NumberToCodes,
|
||||
OpDeclaration,
|
||||
Open,
|
||||
NextStream,
|
||||
PartialStringTail,
|
||||
PeekByte,
|
||||
PeekChar,
|
||||
PeekCode,
|
||||
PointsToContinuationResetMarker,
|
||||
PutByte,
|
||||
PutChar,
|
||||
PutChars,
|
||||
PutCode,
|
||||
REPL(REPLCodePtr),
|
||||
ReadQueryTerm,
|
||||
ReadTerm,
|
||||
RedoAttrVarBinding,
|
||||
RemoveCallPolicyCheck,
|
||||
RemoveInferenceCounter,
|
||||
ResetContinuationMarker,
|
||||
ResetGlobalVarAtKey,
|
||||
ResetGlobalVarAtOffset,
|
||||
RetractClause,
|
||||
RestoreCutPolicy,
|
||||
SetCutPoint(RegType),
|
||||
SetInput,
|
||||
SetOutput,
|
||||
StoreGlobalVar,
|
||||
StoreGlobalVarWithOffset,
|
||||
StreamProperty,
|
||||
SetStreamPosition,
|
||||
InferenceLevel,
|
||||
CleanUpBlock,
|
||||
EraseBall,
|
||||
Fail,
|
||||
GetBall,
|
||||
GetCurrentBlock,
|
||||
GetCutPoint,
|
||||
GetDoubleQuotes,
|
||||
InstallNewBlock,
|
||||
Maybe,
|
||||
CpuNow,
|
||||
CurrentTime,
|
||||
QuotedToken,
|
||||
ReadTermFromChars,
|
||||
ResetBlock,
|
||||
ReturnFromVerifyAttr,
|
||||
SetBall,
|
||||
SetCutPointByDefault(RegType),
|
||||
SetDoubleQuotes,
|
||||
SetSeed,
|
||||
SkipMaxList,
|
||||
Sleep,
|
||||
SocketClientOpen,
|
||||
SocketServerOpen,
|
||||
SocketServerAccept,
|
||||
SocketServerClose,
|
||||
Succeed,
|
||||
TermAttributedVariables,
|
||||
TermVariables,
|
||||
TruncateLiftedHeapTo,
|
||||
UnifyWithOccursCheck,
|
||||
UnwindEnvironments,
|
||||
UnwindStack,
|
||||
Variant,
|
||||
WAMInstructions,
|
||||
WriteTerm,
|
||||
WriteTermToChars,
|
||||
ScryerPrologVersion,
|
||||
CryptoRandomByte,
|
||||
CryptoDataHash,
|
||||
CryptoDataHKDF,
|
||||
CryptoPasswordHash,
|
||||
CryptoDataEncrypt,
|
||||
CryptoDataDecrypt,
|
||||
CryptoCurveScalarMult,
|
||||
Ed25519Sign,
|
||||
Ed25519Verify,
|
||||
Ed25519NewKeyPair,
|
||||
Ed25519KeyPairPublicKey,
|
||||
Curve25519ScalarMult,
|
||||
LoadHTML,
|
||||
LoadXML,
|
||||
GetEnv,
|
||||
SetEnv,
|
||||
UnsetEnv,
|
||||
CharsBase64,
|
||||
}
|
||||
|
||||
impl SystemClauseType {
|
||||
pub fn name(&self) -> ClauseName {
|
||||
match self {
|
||||
&SystemClauseType::AbolishClause => clause_name!("$abolish_clause"),
|
||||
&SystemClauseType::AbolishModuleClause => clause_name!("$abolish_module_clause"),
|
||||
&SystemClauseType::AssertDynamicPredicateToBack => clause_name!("$assertz"),
|
||||
&SystemClauseType::AssertDynamicPredicateToFront => clause_name!("$asserta"),
|
||||
&SystemClauseType::AtEndOfExpansion => clause_name!("$at_end_of_expansion"),
|
||||
&SystemClauseType::AtomChars => clause_name!("$atom_chars"),
|
||||
&SystemClauseType::AtomCodes => clause_name!("$atom_codes"),
|
||||
&SystemClauseType::AtomLength => clause_name!("$atom_length"),
|
||||
&SystemClauseType::BindFromRegister => clause_name!("$bind_from_register"),
|
||||
&SystemClauseType::CallContinuation => clause_name!("$call_continuation"),
|
||||
&SystemClauseType::CharCode => clause_name!("$char_code"),
|
||||
&SystemClauseType::CharType => clause_name!("$char_type"),
|
||||
&SystemClauseType::CharsToNumber => clause_name!("$chars_to_number"),
|
||||
&SystemClauseType::CheckCutPoint => clause_name!("$check_cp"),
|
||||
&SystemClauseType::ClearAttributeGoals => clause_name!("$clear_attribute_goals"),
|
||||
&SystemClauseType::CloneAttributeGoals => clause_name!("$clone_attribute_goals"),
|
||||
&SystemClauseType::CodesToNumber => clause_name!("$codes_to_number"),
|
||||
&SystemClauseType::CopyTermWithoutAttrVars => clause_name!("$copy_term_without_attr_vars"),
|
||||
&SystemClauseType::CreatePartialString => clause_name!("$create_partial_string"),
|
||||
&SystemClauseType::CurrentInput => clause_name!("$current_input"),
|
||||
&SystemClauseType::CurrentHostname => clause_name!("$current_hostname"),
|
||||
&SystemClauseType::CurrentOutput => clause_name!("$current_output"),
|
||||
&SystemClauseType::DirectoryFiles => clause_name!("$directory_files"),
|
||||
&SystemClauseType::FileSize => clause_name!("$file_size"),
|
||||
&SystemClauseType::FileExists => clause_name!("$file_exists"),
|
||||
&SystemClauseType::DirectoryExists => clause_name!("$directory_exists"),
|
||||
&SystemClauseType::DirectorySeparator => clause_name!("$directory_separator"),
|
||||
&SystemClauseType::MakeDirectory => clause_name!("$make_directory"),
|
||||
&SystemClauseType::DeleteFile => clause_name!("$delete_file"),
|
||||
&SystemClauseType::WorkingDirectory => clause_name!("$working_directory"),
|
||||
&SystemClauseType::PathCanonical => clause_name!("$path_canonical"),
|
||||
&SystemClauseType::FileTime => clause_name!("$file_time"),
|
||||
&SystemClauseType::REPL(REPLCodePtr::CompileBatch) => clause_name!("$compile_batch"),
|
||||
&SystemClauseType::REPL(REPLCodePtr::UseModule) => clause_name!("$use_module"),
|
||||
&SystemClauseType::REPL(REPLCodePtr::UseQualifiedModule) => {
|
||||
clause_name!("$use_qualified_module")
|
||||
}
|
||||
&SystemClauseType::REPL(REPLCodePtr::UseModuleFromFile) => {
|
||||
clause_name!("$use_module_from_file")
|
||||
}
|
||||
&SystemClauseType::REPL(REPLCodePtr::UseQualifiedModuleFromFile) => {
|
||||
clause_name!("$use_qualified_module_from_file")
|
||||
}
|
||||
&SystemClauseType::Close => clause_name!("$close"),
|
||||
&SystemClauseType::CopyToLiftedHeap => clause_name!("$copy_to_lh"),
|
||||
&SystemClauseType::DeleteAttribute => clause_name!("$del_attr_non_head"),
|
||||
&SystemClauseType::DeleteHeadAttribute => clause_name!("$del_attr_head"),
|
||||
&SystemClauseType::DynamicModuleResolution(_) => clause_name!("$module_call"),
|
||||
&SystemClauseType::EnqueueAttributeGoal => clause_name!("$enqueue_attribute_goal"),
|
||||
&SystemClauseType::EnqueueAttributedVar => clause_name!("$enqueue_attr_var"),
|
||||
&SystemClauseType::ExpandTerm => clause_name!("$expand_term"),
|
||||
&SystemClauseType::ExpandGoal => clause_name!("$expand_goal"),
|
||||
&SystemClauseType::FetchGlobalVar => clause_name!("$fetch_global_var"),
|
||||
&SystemClauseType::FetchGlobalVarWithOffset => {
|
||||
clause_name!("$fetch_global_var_with_offset")
|
||||
}
|
||||
&SystemClauseType::FirstStream => clause_name!("$first_stream"),
|
||||
&SystemClauseType::FlushOutput => clause_name!("$flush_output"),
|
||||
&SystemClauseType::GetByte => clause_name!("$get_byte"),
|
||||
&SystemClauseType::GetChar => clause_name!("$get_char"),
|
||||
&SystemClauseType::GetNChars => clause_name!("$get_n_chars"),
|
||||
&SystemClauseType::GetCode => clause_name!("$get_code"),
|
||||
&SystemClauseType::GetSingleChar => clause_name!("$get_single_char"),
|
||||
&SystemClauseType::ResetAttrVarState => clause_name!("$reset_attr_var_state"),
|
||||
&SystemClauseType::TruncateIfNoLiftedHeapGrowth => {
|
||||
clause_name!("$truncate_if_no_lh_growth")
|
||||
}
|
||||
&SystemClauseType::TruncateIfNoLiftedHeapGrowthDiff => {
|
||||
clause_name!("$truncate_if_no_lh_growth_diff")
|
||||
}
|
||||
&SystemClauseType::GetAttributedVariableList => clause_name!("$get_attr_list"),
|
||||
&SystemClauseType::GetAttrVarQueueDelimiter => {
|
||||
clause_name!("$get_attr_var_queue_delim")
|
||||
}
|
||||
&SystemClauseType::GetAttrVarQueueBeyond => clause_name!("$get_attr_var_queue_beyond"),
|
||||
&SystemClauseType::GetContinuationChunk => clause_name!("$get_cont_chunk"),
|
||||
&SystemClauseType::GetLiftedHeapFromOffset => clause_name!("$get_lh_from_offset"),
|
||||
&SystemClauseType::GetLiftedHeapFromOffsetDiff => {
|
||||
clause_name!("$get_lh_from_offset_diff")
|
||||
}
|
||||
&SystemClauseType::GetBValue => clause_name!("$get_b_value"),
|
||||
&SystemClauseType::GetClause => clause_name!("$get_clause"),
|
||||
&SystemClauseType::GetNextDBRef => clause_name!("$get_next_db_ref"),
|
||||
&SystemClauseType::GetNextOpDBRef => clause_name!("$get_next_op_db_ref"),
|
||||
&SystemClauseType::LookupDBRef => clause_name!("$lookup_db_ref"),
|
||||
&SystemClauseType::LookupOpDBRef => clause_name!("$lookup_op_db_ref"),
|
||||
&SystemClauseType::GetDoubleQuotes => clause_name!("$get_double_quotes"),
|
||||
&SystemClauseType::GetModuleClause => clause_name!("$get_module_clause"),
|
||||
&SystemClauseType::GetSCCCleaner => clause_name!("$get_scc_cleaner"),
|
||||
&SystemClauseType::Halt => clause_name!("$halt"),
|
||||
&SystemClauseType::HeadIsDynamic => clause_name!("$head_is_dynamic"),
|
||||
&SystemClauseType::Open => clause_name!("$open"),
|
||||
&SystemClauseType::OpDeclaration => clause_name!("$op"),
|
||||
&SystemClauseType::InstallSCCCleaner => clause_name!("$install_scc_cleaner"),
|
||||
&SystemClauseType::InstallInferenceCounter => {
|
||||
clause_name!("$install_inference_counter")
|
||||
}
|
||||
&SystemClauseType::IsPartialString => clause_name!("$is_partial_string"),
|
||||
&SystemClauseType::PartialStringTail => clause_name!("$partial_string_tail"),
|
||||
&SystemClauseType::PeekByte => clause_name!("$peek_byte"),
|
||||
&SystemClauseType::PeekChar => clause_name!("$peek_char"),
|
||||
&SystemClauseType::PeekCode => clause_name!("$peek_code"),
|
||||
&SystemClauseType::LiftedHeapLength => clause_name!("$lh_length"),
|
||||
&SystemClauseType::Maybe => clause_name!("maybe"),
|
||||
&SystemClauseType::CpuNow => clause_name!("$cpu_now"),
|
||||
&SystemClauseType::CurrentTime => clause_name!("$current_time"),
|
||||
&SystemClauseType::ModuleAssertDynamicPredicateToFront => {
|
||||
clause_name!("$module_asserta")
|
||||
}
|
||||
&SystemClauseType::ModuleAssertDynamicPredicateToBack => {
|
||||
clause_name!("$module_assertz")
|
||||
}
|
||||
&SystemClauseType::ModuleHeadIsDynamic => clause_name!("$module_head_is_dynamic"),
|
||||
&SystemClauseType::ModuleExists => clause_name!("$module_exists"),
|
||||
&SystemClauseType::ModuleOf => clause_name!("$module_of"),
|
||||
&SystemClauseType::NextStream => clause_name!("$next_stream"),
|
||||
&SystemClauseType::NoSuchPredicate => clause_name!("$no_such_predicate"),
|
||||
&SystemClauseType::NumberToChars => clause_name!("$number_to_chars"),
|
||||
&SystemClauseType::NumberToCodes => clause_name!("$number_to_codes"),
|
||||
&SystemClauseType::PointsToContinuationResetMarker => {
|
||||
clause_name!("$points_to_cont_reset_marker")
|
||||
}
|
||||
&SystemClauseType::PutByte => {
|
||||
clause_name!("$put_byte")
|
||||
}
|
||||
&SystemClauseType::PutChar => {
|
||||
clause_name!("$put_char")
|
||||
}
|
||||
&SystemClauseType::PutChars => {
|
||||
clause_name!("$put_chars")
|
||||
}
|
||||
&SystemClauseType::PutCode => {
|
||||
clause_name!("$put_code")
|
||||
}
|
||||
&SystemClauseType::QuotedToken => {
|
||||
clause_name!("$quoted_token")
|
||||
}
|
||||
&SystemClauseType::RedoAttrVarBinding => clause_name!("$redo_attr_var_binding"),
|
||||
&SystemClauseType::RemoveCallPolicyCheck => clause_name!("$remove_call_policy_check"),
|
||||
&SystemClauseType::RemoveInferenceCounter => clause_name!("$remove_inference_counter"),
|
||||
&SystemClauseType::RestoreCutPolicy => clause_name!("$restore_cut_policy"),
|
||||
&SystemClauseType::SetCutPoint(_) => clause_name!("$set_cp"),
|
||||
&SystemClauseType::SetInput => clause_name!("$set_input"),
|
||||
&SystemClauseType::SetOutput => clause_name!("$set_output"),
|
||||
&SystemClauseType::SetSeed => clause_name!("$set_seed"),
|
||||
&SystemClauseType::StreamProperty => clause_name!("$stream_property"),
|
||||
&SystemClauseType::SetStreamPosition => clause_name!("$set_stream_position"),
|
||||
&SystemClauseType::StoreGlobalVar => clause_name!("$store_global_var"),
|
||||
&SystemClauseType::StoreGlobalVarWithOffset => {
|
||||
clause_name!("$store_global_var_with_offset")
|
||||
}
|
||||
&SystemClauseType::InferenceLevel => clause_name!("$inference_level"),
|
||||
&SystemClauseType::CleanUpBlock => clause_name!("$clean_up_block"),
|
||||
&SystemClauseType::EraseBall => clause_name!("$erase_ball"),
|
||||
&SystemClauseType::Fail => clause_name!("$fail"),
|
||||
&SystemClauseType::GetBall => clause_name!("$get_ball"),
|
||||
&SystemClauseType::GetCutPoint => clause_name!("$get_cp"),
|
||||
&SystemClauseType::GetCurrentBlock => clause_name!("$get_current_block"),
|
||||
&SystemClauseType::InstallNewBlock => clause_name!("$install_new_block"),
|
||||
&SystemClauseType::ModuleRetractClause => clause_name!("$module_retract_clause"),
|
||||
&SystemClauseType::NextEP => clause_name!("$nextEP"),
|
||||
&SystemClauseType::ReadQueryTerm => clause_name!("$read_query_term"),
|
||||
&SystemClauseType::ReadTerm => clause_name!("$read_term"),
|
||||
&SystemClauseType::ReadTermFromChars => clause_name!("$read_term_from_chars"),
|
||||
&SystemClauseType::ResetGlobalVarAtKey => clause_name!("$reset_global_var_at_key"),
|
||||
&SystemClauseType::ResetGlobalVarAtOffset => clause_name!("$reset_global_var_at_offset"),
|
||||
&SystemClauseType::RetractClause => clause_name!("$retract_clause"),
|
||||
&SystemClauseType::ResetBlock => clause_name!("$reset_block"),
|
||||
&SystemClauseType::ResetContinuationMarker => clause_name!("$reset_cont_marker"),
|
||||
&SystemClauseType::ReturnFromVerifyAttr => clause_name!("$return_from_verify_attr"),
|
||||
&SystemClauseType::SetBall => clause_name!("$set_ball"),
|
||||
&SystemClauseType::SetCutPointByDefault(_) => clause_name!("$set_cp_by_default"),
|
||||
&SystemClauseType::SetDoubleQuotes => clause_name!("$set_double_quotes"),
|
||||
&SystemClauseType::SkipMaxList => clause_name!("$skip_max_list"),
|
||||
&SystemClauseType::Sleep => clause_name!("$sleep"),
|
||||
&SystemClauseType::SocketClientOpen => clause_name!("$socket_client_open"),
|
||||
&SystemClauseType::SocketServerOpen => clause_name!("$socket_server_open"),
|
||||
&SystemClauseType::SocketServerAccept => clause_name!("$socket_server_accept"),
|
||||
&SystemClauseType::SocketServerClose => clause_name!("$socket_server_close"),
|
||||
&SystemClauseType::Succeed => clause_name!("$succeed"),
|
||||
&SystemClauseType::TermAttributedVariables => clause_name!("$term_attributed_variables"),
|
||||
&SystemClauseType::TermVariables => clause_name!("$term_variables"),
|
||||
&SystemClauseType::TruncateLiftedHeapTo => clause_name!("$truncate_lh_to"),
|
||||
&SystemClauseType::UnifyWithOccursCheck => clause_name!("$unify_with_occurs_check"),
|
||||
&SystemClauseType::UnwindEnvironments => clause_name!("$unwind_environments"),
|
||||
&SystemClauseType::UnwindStack => clause_name!("$unwind_stack"),
|
||||
&SystemClauseType::Variant => clause_name!("$variant"),
|
||||
&SystemClauseType::WAMInstructions => clause_name!("$wam_instructions"),
|
||||
&SystemClauseType::WriteTerm => clause_name!("$write_term"),
|
||||
&SystemClauseType::WriteTermToChars => clause_name!("$write_term_to_chars"),
|
||||
&SystemClauseType::ScryerPrologVersion => clause_name!("$scryer_prolog_version"),
|
||||
&SystemClauseType::CryptoRandomByte => clause_name!("$crypto_random_byte"),
|
||||
&SystemClauseType::CryptoDataHash => clause_name!("$crypto_data_hash"),
|
||||
&SystemClauseType::CryptoDataHKDF => clause_name!("$crypto_data_hkdf"),
|
||||
&SystemClauseType::CryptoPasswordHash => clause_name!("$crypto_password_hash"),
|
||||
&SystemClauseType::CryptoDataEncrypt => clause_name!("$crypto_data_encrypt"),
|
||||
&SystemClauseType::CryptoDataDecrypt => clause_name!("$crypto_data_decrypt"),
|
||||
&SystemClauseType::CryptoCurveScalarMult => clause_name!("$crypto_curve_scalar_mult"),
|
||||
&SystemClauseType::Ed25519Sign => clause_name!("$ed25519_sign"),
|
||||
&SystemClauseType::Ed25519Verify => clause_name!("$ed25519_verify"),
|
||||
&SystemClauseType::Ed25519NewKeyPair => clause_name!("$ed25519_new_keypair"),
|
||||
&SystemClauseType::Ed25519KeyPairPublicKey => clause_name!("$ed25519_keypair_public_key"),
|
||||
&SystemClauseType::Curve25519ScalarMult => clause_name!("$curve25519_scalar_mult"),
|
||||
&SystemClauseType::LoadHTML => clause_name!("$load_html"),
|
||||
&SystemClauseType::LoadXML => clause_name!("$load_xml"),
|
||||
&SystemClauseType::GetEnv => clause_name!("$getenv"),
|
||||
&SystemClauseType::SetEnv => clause_name!("$setenv"),
|
||||
&SystemClauseType::UnsetEnv => clause_name!("$unsetenv"),
|
||||
&SystemClauseType::CharsBase64 => clause_name!("$chars_base64"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from(name: &str, arity: usize) -> Option<SystemClauseType> {
|
||||
match (name, arity) {
|
||||
("$abolish_clause", 2) => Some(SystemClauseType::AbolishClause),
|
||||
("$at_end_of_expansion", 0) => Some(SystemClauseType::AtEndOfExpansion),
|
||||
("$atom_chars", 2) => Some(SystemClauseType::AtomChars),
|
||||
("$atom_codes", 2) => Some(SystemClauseType::AtomCodes),
|
||||
("$atom_length", 2) => Some(SystemClauseType::AtomLength),
|
||||
("$abolish_module_clause", 3) => Some(SystemClauseType::AbolishModuleClause),
|
||||
("$bind_from_register", 2) => Some(SystemClauseType::BindFromRegister),
|
||||
("$module_asserta", 5) => Some(SystemClauseType::ModuleAssertDynamicPredicateToFront),
|
||||
("$module_assertz", 5) => Some(SystemClauseType::ModuleAssertDynamicPredicateToBack),
|
||||
("$asserta", 4) => Some(SystemClauseType::AssertDynamicPredicateToFront),
|
||||
("$assertz", 4) => Some(SystemClauseType::AssertDynamicPredicateToBack),
|
||||
("$call_continuation", 1) => Some(SystemClauseType::CallContinuation),
|
||||
("$char_code", 2) => Some(SystemClauseType::CharCode),
|
||||
("$char_type", 2) => Some(SystemClauseType::CharType),
|
||||
("$chars_to_number", 2) => Some(SystemClauseType::CharsToNumber),
|
||||
("$clear_attribute_goals", 0) => Some(SystemClauseType::ClearAttributeGoals),
|
||||
("$clone_attribute_goals", 1) => Some(SystemClauseType::CloneAttributeGoals),
|
||||
("$codes_to_number", 2) => Some(SystemClauseType::CodesToNumber),
|
||||
("$copy_term_without_attr_vars", 2) => Some(SystemClauseType::CopyTermWithoutAttrVars),
|
||||
("$create_partial_string", 3) => Some(SystemClauseType::CreatePartialString),
|
||||
("$check_cp", 1) => Some(SystemClauseType::CheckCutPoint),
|
||||
("$compile_batch", 0) => Some(SystemClauseType::REPL(REPLCodePtr::CompileBatch)),
|
||||
("$copy_to_lh", 2) => Some(SystemClauseType::CopyToLiftedHeap),
|
||||
("$close", 2) => Some(SystemClauseType::Close),
|
||||
("$current_hostname", 1) => Some(SystemClauseType::CurrentHostname),
|
||||
("$current_input", 1) => Some(SystemClauseType::CurrentInput),
|
||||
("$current_output", 1) => Some(SystemClauseType::CurrentOutput),
|
||||
("$first_stream", 1) => Some(SystemClauseType::FirstStream),
|
||||
("$next_stream", 2) => Some(SystemClauseType::NextStream),
|
||||
("$flush_output", 1) => Some(SystemClauseType::FlushOutput),
|
||||
("$del_attr_non_head", 1) => Some(SystemClauseType::DeleteAttribute),
|
||||
("$del_attr_head", 1) => Some(SystemClauseType::DeleteHeadAttribute),
|
||||
("$get_next_db_ref", 2) => Some(SystemClauseType::GetNextDBRef),
|
||||
("$get_next_op_db_ref", 2) => Some(SystemClauseType::GetNextOpDBRef),
|
||||
("$lookup_db_ref", 3) => Some(SystemClauseType::LookupDBRef),
|
||||
("$lookup_op_db_ref", 4) => Some(SystemClauseType::LookupOpDBRef),
|
||||
("$module_call", _) => Some(SystemClauseType::DynamicModuleResolution(arity - 2)),
|
||||
("$enqueue_attribute_goal", 1) => Some(SystemClauseType::EnqueueAttributeGoal),
|
||||
("$enqueue_attr_var", 1) => Some(SystemClauseType::EnqueueAttributedVar),
|
||||
("$partial_string_tail", 2) => Some(SystemClauseType::PartialStringTail),
|
||||
("$peek_byte", 2) => Some(SystemClauseType::PeekByte),
|
||||
("$peek_char", 2) => Some(SystemClauseType::PeekChar),
|
||||
("$peek_code", 2) => Some(SystemClauseType::PeekCode),
|
||||
("$is_partial_string", 1) => Some(SystemClauseType::IsPartialString),
|
||||
("$expand_term", 2) => Some(SystemClauseType::ExpandTerm),
|
||||
("$expand_goal", 2) => Some(SystemClauseType::ExpandGoal),
|
||||
("$fetch_global_var", 2) => Some(SystemClauseType::FetchGlobalVar),
|
||||
("$fetch_global_var_with_offset", 3) => Some(SystemClauseType::FetchGlobalVarWithOffset),
|
||||
("$get_byte", 2) => Some(SystemClauseType::GetByte),
|
||||
("$get_char", 2) => Some(SystemClauseType::GetChar),
|
||||
("$get_n_chars", 3) => Some(SystemClauseType::GetNChars),
|
||||
("$get_code", 2) => Some(SystemClauseType::GetCode),
|
||||
("$get_single_char", 1) => Some(SystemClauseType::GetSingleChar),
|
||||
("$points_to_cont_reset_marker", 1) => {
|
||||
Some(SystemClauseType::PointsToContinuationResetMarker)
|
||||
}
|
||||
("$put_byte", 2) => {
|
||||
Some(SystemClauseType::PutByte)
|
||||
}
|
||||
("$put_char", 2) => {
|
||||
Some(SystemClauseType::PutChar)
|
||||
}
|
||||
("$put_chars", 2) => {
|
||||
Some(SystemClauseType::PutChars)
|
||||
}
|
||||
("$put_code", 2) => {
|
||||
Some(SystemClauseType::PutCode)
|
||||
}
|
||||
("$reset_attr_var_state", 0) => Some(SystemClauseType::ResetAttrVarState),
|
||||
("$truncate_if_no_lh_growth", 1) => {
|
||||
Some(SystemClauseType::TruncateIfNoLiftedHeapGrowth)
|
||||
}
|
||||
("$truncate_if_no_lh_growth_diff", 2) => {
|
||||
Some(SystemClauseType::TruncateIfNoLiftedHeapGrowthDiff)
|
||||
}
|
||||
("$get_attr_list", 2) => Some(SystemClauseType::GetAttributedVariableList),
|
||||
("$get_b_value", 1) => Some(SystemClauseType::GetBValue),
|
||||
("$get_clause", 2) => Some(SystemClauseType::GetClause),
|
||||
("$get_module_clause", 3) => Some(SystemClauseType::GetModuleClause),
|
||||
("$get_lh_from_offset", 2) => Some(SystemClauseType::GetLiftedHeapFromOffset),
|
||||
("$get_lh_from_offset_diff", 3) => Some(SystemClauseType::GetLiftedHeapFromOffsetDiff),
|
||||
("$get_double_quotes", 1) => Some(SystemClauseType::GetDoubleQuotes),
|
||||
("$get_scc_cleaner", 1) => Some(SystemClauseType::GetSCCCleaner),
|
||||
("$halt", 1) => Some(SystemClauseType::Halt),
|
||||
("$head_is_dynamic", 1) => Some(SystemClauseType::HeadIsDynamic),
|
||||
("$install_scc_cleaner", 2) => Some(SystemClauseType::InstallSCCCleaner),
|
||||
("$install_inference_counter", 3) => Some(SystemClauseType::InstallInferenceCounter),
|
||||
("$lh_length", 1) => Some(SystemClauseType::LiftedHeapLength),
|
||||
("$maybe", 0) => Some(SystemClauseType::Maybe),
|
||||
("$cpu_now", 1) => Some(SystemClauseType::CpuNow),
|
||||
("$current_time", 1) => Some(SystemClauseType::CurrentTime),
|
||||
("$module_exists", 1) => Some(SystemClauseType::ModuleExists),
|
||||
("$module_of", 2) => Some(SystemClauseType::ModuleOf),
|
||||
("$module_retract_clause", 5) => Some(SystemClauseType::ModuleRetractClause),
|
||||
("$module_head_is_dynamic", 2) => Some(SystemClauseType::ModuleHeadIsDynamic),
|
||||
("$no_such_predicate", 1) => Some(SystemClauseType::NoSuchPredicate),
|
||||
("$number_to_chars", 2) => Some(SystemClauseType::NumberToChars),
|
||||
("$number_to_codes", 2) => Some(SystemClauseType::NumberToCodes),
|
||||
("$op", 3) => Some(SystemClauseType::OpDeclaration),
|
||||
("$open", 7) => Some(SystemClauseType::Open),
|
||||
("$redo_attr_var_binding", 2) => Some(SystemClauseType::RedoAttrVarBinding),
|
||||
("$remove_call_policy_check", 1) => Some(SystemClauseType::RemoveCallPolicyCheck),
|
||||
("$remove_inference_counter", 2) => Some(SystemClauseType::RemoveInferenceCounter),
|
||||
("$restore_cut_policy", 0) => Some(SystemClauseType::RestoreCutPolicy),
|
||||
("$set_cp", 1) => Some(SystemClauseType::SetCutPoint(temp_v!(1))),
|
||||
("$set_input", 1) => Some(SystemClauseType::SetInput),
|
||||
("$set_output", 1) => Some(SystemClauseType::SetOutput),
|
||||
("$stream_property", 3) => Some(SystemClauseType::StreamProperty),
|
||||
("$set_stream_position", 2) => Some(SystemClauseType::SetStreamPosition),
|
||||
("$inference_level", 2) => Some(SystemClauseType::InferenceLevel),
|
||||
("$clean_up_block", 1) => Some(SystemClauseType::CleanUpBlock),
|
||||
("$erase_ball", 0) => Some(SystemClauseType::EraseBall),
|
||||
("$fail", 0) => Some(SystemClauseType::Fail),
|
||||
("$get_attr_var_queue_beyond", 2) => Some(SystemClauseType::GetAttrVarQueueBeyond),
|
||||
("$get_attr_var_queue_delim", 1) => Some(SystemClauseType::GetAttrVarQueueDelimiter),
|
||||
("$get_ball", 1) => Some(SystemClauseType::GetBall),
|
||||
("$get_cont_chunk", 3) => Some(SystemClauseType::GetContinuationChunk),
|
||||
("$get_current_block", 1) => Some(SystemClauseType::GetCurrentBlock),
|
||||
("$get_cp", 1) => Some(SystemClauseType::GetCutPoint),
|
||||
("$install_new_block", 1) => Some(SystemClauseType::InstallNewBlock),
|
||||
("$quoted_token", 1) => Some(SystemClauseType::QuotedToken),
|
||||
("$nextEP", 3) => Some(SystemClauseType::NextEP),
|
||||
("$read_query_term", 5) => Some(SystemClauseType::ReadQueryTerm),
|
||||
("$read_term", 5) => Some(SystemClauseType::ReadTerm),
|
||||
("$read_term_from_chars", 2) => Some(SystemClauseType::ReadTermFromChars),
|
||||
("$reset_block", 1) => Some(SystemClauseType::ResetBlock),
|
||||
("$reset_cont_marker", 0) => Some(SystemClauseType::ResetContinuationMarker),
|
||||
("$reset_global_var_at_key", 1) => Some(SystemClauseType::ResetGlobalVarAtKey),
|
||||
("$reset_global_var_at_offset", 3) => Some(SystemClauseType::ResetGlobalVarAtOffset),
|
||||
("$retract_clause", 4) => Some(SystemClauseType::RetractClause),
|
||||
("$return_from_verify_attr", 0) => Some(SystemClauseType::ReturnFromVerifyAttr),
|
||||
("$set_ball", 1) => Some(SystemClauseType::SetBall),
|
||||
("$set_cp_by_default", 1) => Some(SystemClauseType::SetCutPointByDefault(temp_v!(1))),
|
||||
("$set_double_quotes", 1) => Some(SystemClauseType::SetDoubleQuotes),
|
||||
("$set_seed", 1) => Some(SystemClauseType::SetSeed),
|
||||
("$skip_max_list", 4) => Some(SystemClauseType::SkipMaxList),
|
||||
("$sleep", 1) => Some(SystemClauseType::Sleep),
|
||||
("$socket_client_open", 8) => Some(SystemClauseType::SocketClientOpen),
|
||||
("$socket_server_open", 3) => Some(SystemClauseType::SocketServerOpen),
|
||||
("$socket_server_accept", 7) => Some(SystemClauseType::SocketServerAccept),
|
||||
("$socket_server_close", 1) => Some(SystemClauseType::SocketServerClose),
|
||||
("$store_global_var", 2) => Some(SystemClauseType::StoreGlobalVar),
|
||||
("$store_global_var_with_offset", 2) => Some(SystemClauseType::StoreGlobalVarWithOffset),
|
||||
("$term_attributed_variables", 2) => Some(SystemClauseType::TermAttributedVariables),
|
||||
("$term_variables", 2) => Some(SystemClauseType::TermVariables),
|
||||
("$truncate_lh_to", 1) => Some(SystemClauseType::TruncateLiftedHeapTo),
|
||||
("$unwind_environments", 0) => Some(SystemClauseType::UnwindEnvironments),
|
||||
("$unwind_stack", 0) => Some(SystemClauseType::UnwindStack),
|
||||
("$unify_with_occurs_check", 2) => Some(SystemClauseType::UnifyWithOccursCheck),
|
||||
("$directory_files", 2) => Some(SystemClauseType::DirectoryFiles),
|
||||
("$file_size", 2) => Some(SystemClauseType::FileSize),
|
||||
("$file_exists", 1) => Some(SystemClauseType::FileExists),
|
||||
("$directory_exists", 1) => Some(SystemClauseType::DirectoryExists),
|
||||
("$directory_separator", 1) => Some(SystemClauseType::DirectorySeparator),
|
||||
("$make_directory", 1) => Some(SystemClauseType::MakeDirectory),
|
||||
("$delete_file", 1) => Some(SystemClauseType::DeleteFile),
|
||||
("$working_directory", 2) => Some(SystemClauseType::WorkingDirectory),
|
||||
("$path_canonical", 2) => Some(SystemClauseType::PathCanonical),
|
||||
("$file_time", 3) => Some(SystemClauseType::FileTime),
|
||||
("$use_module", 1) => Some(SystemClauseType::REPL(REPLCodePtr::UseModule)),
|
||||
("$use_module_from_file", 1) =>
|
||||
Some(SystemClauseType::REPL(REPLCodePtr::UseModuleFromFile)),
|
||||
("$use_qualified_module", 2) =>
|
||||
Some(SystemClauseType::REPL(REPLCodePtr::UseQualifiedModule)),
|
||||
("$use_qualified_module_from_file", 2) =>
|
||||
Some(SystemClauseType::REPL(REPLCodePtr::UseQualifiedModuleFromFile)),
|
||||
("$variant", 2) => Some(SystemClauseType::Variant),
|
||||
("$wam_instructions", 3) => Some(SystemClauseType::WAMInstructions),
|
||||
("$write_term", 7) => Some(SystemClauseType::WriteTerm),
|
||||
("$write_term_to_chars", 7) => Some(SystemClauseType::WriteTermToChars),
|
||||
("$scryer_prolog_version", 1) => Some(SystemClauseType::ScryerPrologVersion),
|
||||
("$crypto_random_byte", 1) => Some(SystemClauseType::CryptoRandomByte),
|
||||
("$crypto_data_hash", 4) => Some(SystemClauseType::CryptoDataHash),
|
||||
("$crypto_data_hkdf", 7) => Some(SystemClauseType::CryptoDataHKDF),
|
||||
("$crypto_password_hash", 4) => Some(SystemClauseType::CryptoPasswordHash),
|
||||
("$crypto_data_encrypt", 6) => Some(SystemClauseType::CryptoDataEncrypt),
|
||||
("$crypto_data_decrypt", 6) => Some(SystemClauseType::CryptoDataDecrypt),
|
||||
("$crypto_curve_scalar_mult", 5) => Some(SystemClauseType::CryptoCurveScalarMult),
|
||||
("$ed25519_sign", 5) => Some(SystemClauseType::Ed25519Sign),
|
||||
("$ed25519_verify", 5) => Some(SystemClauseType::Ed25519Verify),
|
||||
("$ed25519_new_keypair", 1) => Some(SystemClauseType::Ed25519NewKeyPair),
|
||||
("$ed25519_keypair_public_key", 3) => Some(SystemClauseType::Ed25519KeyPairPublicKey),
|
||||
("$curve25519_scalar_mult", 3) => Some(SystemClauseType::Curve25519ScalarMult),
|
||||
("$load_html", 3) => Some(SystemClauseType::LoadHTML),
|
||||
("$load_xml", 3) => Some(SystemClauseType::LoadXML),
|
||||
("$getenv", 2) => Some(SystemClauseType::GetEnv),
|
||||
("$setenv", 2) => Some(SystemClauseType::SetEnv),
|
||||
("$unsetenv", 1) => Some(SystemClauseType::UnsetEnv),
|
||||
("$chars_base64", 4) => Some(SystemClauseType::CharsBase64),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum BuiltInClauseType {
|
||||
AcyclicTerm,
|
||||
Arg,
|
||||
Compare,
|
||||
CompareTerm(CompareTermQT),
|
||||
CopyTerm,
|
||||
Eq,
|
||||
Functor,
|
||||
Ground,
|
||||
Is(RegType, ArithmeticTerm),
|
||||
KeySort,
|
||||
Nl,
|
||||
NotEq,
|
||||
Read,
|
||||
Sort,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ClauseType {
|
||||
BuiltIn(BuiltInClauseType),
|
||||
CallN,
|
||||
Hook(CompileTimeHook),
|
||||
Inlined(InlinedClauseType),
|
||||
Named(ClauseName, usize, CodeIndex), // name, arity, index.
|
||||
Op(ClauseName, SharedOpDesc, CodeIndex),
|
||||
System(SystemClauseType),
|
||||
}
|
||||
|
||||
impl BuiltInClauseType {
|
||||
pub fn name(&self) -> ClauseName {
|
||||
match self {
|
||||
&BuiltInClauseType::AcyclicTerm => clause_name!("acyclic_term"),
|
||||
&BuiltInClauseType::Arg => clause_name!("arg"),
|
||||
&BuiltInClauseType::Compare => clause_name!("compare"),
|
||||
&BuiltInClauseType::CompareTerm(qt) => clause_name!(qt.name()),
|
||||
&BuiltInClauseType::CopyTerm => clause_name!("copy_term"),
|
||||
&BuiltInClauseType::Eq => clause_name!("=="),
|
||||
&BuiltInClauseType::Functor => clause_name!("functor"),
|
||||
&BuiltInClauseType::Ground => clause_name!("ground"),
|
||||
&BuiltInClauseType::Is(..) => clause_name!("is"),
|
||||
&BuiltInClauseType::KeySort => clause_name!("keysort"),
|
||||
&BuiltInClauseType::Nl => clause_name!("nl"),
|
||||
&BuiltInClauseType::NotEq => clause_name!("\\=="),
|
||||
&BuiltInClauseType::Read => clause_name!("read"),
|
||||
&BuiltInClauseType::Sort => clause_name!("sort"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arity(&self) -> usize {
|
||||
match self {
|
||||
&BuiltInClauseType::AcyclicTerm => 1,
|
||||
&BuiltInClauseType::Arg => 3,
|
||||
&BuiltInClauseType::Compare => 2,
|
||||
&BuiltInClauseType::CompareTerm(_) => 2,
|
||||
&BuiltInClauseType::CopyTerm => 2,
|
||||
&BuiltInClauseType::Eq => 2,
|
||||
&BuiltInClauseType::Functor => 3,
|
||||
&BuiltInClauseType::Ground => 1,
|
||||
&BuiltInClauseType::Is(..) => 2,
|
||||
&BuiltInClauseType::KeySort => 2,
|
||||
&BuiltInClauseType::NotEq => 2,
|
||||
&BuiltInClauseType::Nl => 0,
|
||||
&BuiltInClauseType::Read => 1,
|
||||
&BuiltInClauseType::Sort => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClauseType {
|
||||
pub fn spec(&self) -> Option<SharedOpDesc> {
|
||||
match self {
|
||||
&ClauseType::Op(_, ref spec, _) => Some(spec.clone()),
|
||||
&ClauseType::Inlined(InlinedClauseType::CompareNumber(..))
|
||||
| &ClauseType::BuiltIn(BuiltInClauseType::Is(..))
|
||||
| &ClauseType::BuiltIn(BuiltInClauseType::CompareTerm(_))
|
||||
| &ClauseType::BuiltIn(BuiltInClauseType::NotEq)
|
||||
| &ClauseType::BuiltIn(BuiltInClauseType::Eq) => Some(SharedOpDesc::new(700, XFX)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> ClauseName {
|
||||
match self {
|
||||
&ClauseType::BuiltIn(ref built_in) => built_in.name(),
|
||||
&ClauseType::CallN => clause_name!("call"),
|
||||
&ClauseType::Hook(ref hook) => hook.name(),
|
||||
&ClauseType::Inlined(ref inlined) => clause_name!(inlined.name()),
|
||||
&ClauseType::Op(ref name, ..) => name.clone(),
|
||||
&ClauseType::Named(ref name, ..) => name.clone(),
|
||||
&ClauseType::System(ref system) => system.name(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from(name: ClauseName, arity: usize, spec: Option<SharedOpDesc>) -> Self {
|
||||
CLAUSE_TYPE_FORMS
|
||||
.borrow()
|
||||
.get(&(name.as_str(), arity))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| {
|
||||
SystemClauseType::from(name.as_str(), arity)
|
||||
.map(ClauseType::System)
|
||||
.unwrap_or_else(|| {
|
||||
if let Some(spec) = spec {
|
||||
ClauseType::Op(name, spec, CodeIndex::default())
|
||||
} else if name.as_str() == "call" {
|
||||
ClauseType::CallN
|
||||
} else {
|
||||
ClauseType::Named(name, arity, CodeIndex::default())
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InlinedClauseType> for ClauseType {
|
||||
fn from(inlined_ct: InlinedClauseType) -> Self {
|
||||
ClauseType::Inlined(inlined_ct)
|
||||
}
|
||||
}
|
||||
1777
src/codegen.rs
1777
src/codegen.rs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
:- module(bimetatrans_tests, [test_bimetatrans/0]).
|
||||
:- module(bimetatran_tests, [test_bimetatrans/0]).
|
||||
|
||||
:- use_module(bimetatrans).
|
||||
:- use_module('bimetatrans').
|
||||
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(iso_ext)).
|
||||
@@ -21,6 +21,28 @@
|
||||
* order of ascending N.
|
||||
*/
|
||||
|
||||
term_expansion(Term0, Term) :-
|
||||
nonvar(Term0),
|
||||
Term0 = test(N, Assert, Query, XML),
|
||||
integer(N),
|
||||
list_si(Assert),
|
||||
list_si(Query),
|
||||
partial_string(XML),
|
||||
number_chars(N, NChars),
|
||||
atom_chars(NAtom, NChars),
|
||||
atom_concat(prolog2ruleml_, NAtom, Prolog2RuleML),
|
||||
atom_concat(ruleml2prolog_, NAtom, RuleML2Prolog),
|
||||
atom_concat(test_, NAtom, TestN),
|
||||
strip_indentation(XML, XML1),
|
||||
Term = [(Prolog2RuleML :- parse_ruleml(Assert, Query, XML0),
|
||||
XML0 = XML1),
|
||||
(RuleML2Prolog :- parse_ruleml(Assert0, Query0, XML),
|
||||
Assert0 = Assert,
|
||||
Query0 = Query),
|
||||
(TestN :- write(test(N)), nl, Prolog2RuleML, RuleML2Prolog, !),
|
||||
(TestN :- throw(error(test_failure, TestN)))].
|
||||
|
||||
|
||||
until_non_space_or_end([C|Cs], Cs1) :-
|
||||
( C == (' ') ->
|
||||
until_non_space_or_end(Cs, Cs1)
|
||||
@@ -44,28 +66,6 @@ strip_indentation_([C|Cs], Cs0) :-
|
||||
strip_indentation_([], []).
|
||||
|
||||
|
||||
user:term_expansion(Term0, Term) :-
|
||||
nonvar(Term0),
|
||||
Term0 = test(N, Assert, Query, XML),
|
||||
integer(N),
|
||||
list_si(Assert),
|
||||
list_si(Query),
|
||||
partial_string(XML),
|
||||
number_chars(N, NChars),
|
||||
atom_chars(NAtom, NChars),
|
||||
atom_concat(prolog2ruleml_, NAtom, Prolog2RuleML),
|
||||
atom_concat(ruleml2prolog_, NAtom, RuleML2Prolog),
|
||||
atom_concat(test_, NAtom, TestN),
|
||||
strip_indentation(XML, XML1),
|
||||
Term = [(Prolog2RuleML :- parse_ruleml(Assert, Query, XML0),
|
||||
XML0 = XML1),
|
||||
(RuleML2Prolog :- parse_ruleml(Assert0, Query0, XML),
|
||||
Assert0 = Assert,
|
||||
Query0 = Query),
|
||||
(TestN :- write(test(N)), nl, Prolog2RuleML, RuleML2Prolog, !),
|
||||
(TestN :- throw(error(test_failure, TestN)))].
|
||||
|
||||
|
||||
test(1,
|
||||
[people('Alex',male),people('Alex',female),people('Siri',female)],
|
||||
[],
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
*/
|
||||
|
||||
:- module(least_time, [find_min_time/2,
|
||||
write_time_nl/1]).
|
||||
write_time_nl/1]).
|
||||
|
||||
|
||||
:- use_module(library(dcgs)).
|
||||
@@ -20,6 +20,12 @@
|
||||
:- use_module(library(reif)).
|
||||
|
||||
|
||||
permutation([], []).
|
||||
permutation([X|Xs], Ys) :-
|
||||
permutation(Xs, Yss),
|
||||
select(X, Ys, Yss).
|
||||
|
||||
|
||||
valid_time([H1,H2,M1,M2], T) :-
|
||||
memberd_t(H1, [0,1,2], TH1),
|
||||
memberd_t(H2, [0,1,2,3,4,5,6,7,8,9], TH2),
|
||||
@@ -27,10 +33,10 @@ valid_time([H1,H2,M1,M2], T) :-
|
||||
memberd_t(M2, [0,1,2,3,4,5,6,7,8,9], TM2),
|
||||
( maplist(=(true), [TH1, TH2, TM1, TM2]) ->
|
||||
( H1 =:= 2 ->
|
||||
( H2 =< 3 ->
|
||||
T = true
|
||||
; T = false
|
||||
)
|
||||
( H2 =< 3 ->
|
||||
T = true
|
||||
; T = false
|
||||
)
|
||||
; T = true
|
||||
)
|
||||
; T = false
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
:- use_module(library(charsio)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(pio)).
|
||||
:- use_module(library(dcgs)).
|
||||
|
||||
:- initialization(unit_test).
|
||||
|
||||
@@ -12,12 +10,22 @@ unit_test :-
|
||||
Cs = "a£\x2124\".
|
||||
|
||||
write_f :-
|
||||
open('x.txt', write, Stream, [type(binary)]),
|
||||
F = put_byte(Stream),
|
||||
chars_utf8bytes("£\x2124\\x2764\\x1F496\\n", Bs),
|
||||
maplist(char_code, Cs, Bs),
|
||||
phrase_to_file(Cs, "x.txt", [type(binary)]).
|
||||
maplist(F, Bs),
|
||||
close(Stream).
|
||||
|
||||
get_bytes(Stream, Res) :- get_bytes(Stream, [], Res).
|
||||
get_bytes(Stream, Acc, Res) :-
|
||||
get_byte(Stream, B),
|
||||
(B =:= -1 ->
|
||||
reverse(Acc, Res)
|
||||
; get_bytes(Stream, [B|Acc], Res)).
|
||||
|
||||
read_f :-
|
||||
phrase_from_file(seq(Cs), "x.txt", [type(binary)]),
|
||||
maplist(char_code, Cs, Bs),
|
||||
chars_utf8bytes(Chars, Bs),
|
||||
write(Chars).
|
||||
open('x.txt', read, Stream, [type(binary)]),
|
||||
get_bytes(Stream, Bs),
|
||||
chars_utf8bytes(Cs, Bs),
|
||||
write(Cs),
|
||||
close(Stream).
|
||||
|
||||
1004
src/ffi.rs
1004
src/ffi.rs
File diff suppressed because it is too large
Load Diff
323
src/fixtures.rs
Normal file
323
src/fixtures.rs
Normal file
@@ -0,0 +1,323 @@
|
||||
use crate::prolog_parser::ast::*;
|
||||
|
||||
use crate::forms::*;
|
||||
use crate::instructions::*;
|
||||
use crate::iterators::*;
|
||||
|
||||
use crate::indexmap::{IndexMap, IndexSet};
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::BTreeSet;
|
||||
use std::mem::swap;
|
||||
use std::rc::Rc;
|
||||
use std::vec::Vec;
|
||||
|
||||
// labeled with chunk numbers.
|
||||
#[derive(Debug)]
|
||||
pub enum VarStatus {
|
||||
Perm(usize),
|
||||
Temp(usize, TempVarData), // Perm(chunk_num) | Temp(chunk_num, _)
|
||||
}
|
||||
|
||||
pub type OccurrenceSet = BTreeSet<(GenContext, usize)>;
|
||||
|
||||
// Perm: 0 initially, a stack register once processed.
|
||||
// Temp: labeled with chunk_num and temp offset (unassigned if 0).
|
||||
#[derive(Debug)]
|
||||
pub enum VarData {
|
||||
Perm(usize),
|
||||
Temp(usize, usize, TempVarData),
|
||||
}
|
||||
|
||||
impl VarData {
|
||||
pub fn as_reg_type(&self) -> RegType {
|
||||
match self {
|
||||
&VarData::Temp(_, r, _) => RegType::Temp(r),
|
||||
&VarData::Perm(r) => RegType::Perm(r),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TempVarData {
|
||||
pub last_term_arity: usize,
|
||||
pub use_set: OccurrenceSet,
|
||||
pub no_use_set: BTreeSet<usize>,
|
||||
pub conflict_set: BTreeSet<usize>,
|
||||
}
|
||||
|
||||
impl TempVarData {
|
||||
pub fn new(last_term_arity: usize) -> Self {
|
||||
TempVarData {
|
||||
last_term_arity: last_term_arity,
|
||||
use_set: BTreeSet::new(),
|
||||
no_use_set: BTreeSet::new(),
|
||||
conflict_set: BTreeSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uses_reg(&self, reg: usize) -> bool {
|
||||
for &(_, nreg) in self.use_set.iter() {
|
||||
if reg == nreg {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn populate_conflict_set(&mut self) {
|
||||
if self.last_term_arity > 0 {
|
||||
let arity = self.last_term_arity;
|
||||
let mut conflict_set: BTreeSet<usize> = (1..arity).collect();
|
||||
|
||||
for &(_, reg) in self.use_set.iter() {
|
||||
conflict_set.remove(®);
|
||||
}
|
||||
|
||||
self.conflict_set = conflict_set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type VariableFixture<'a> = (VarStatus, Vec<&'a Cell<VarReg>>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VariableFixtures<'a>{
|
||||
perm_vars: IndexMap<Rc<Var>, VariableFixture<'a>>,
|
||||
last_chunk_temp_vars: IndexSet<Rc<Var>>
|
||||
}
|
||||
|
||||
impl<'a> VariableFixtures<'a> {
|
||||
pub fn new() -> Self {
|
||||
VariableFixtures {
|
||||
perm_vars: IndexMap::new(),
|
||||
last_chunk_temp_vars: IndexSet::new()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, var: Rc<Var>, vs: VariableFixture<'a>) {
|
||||
self.perm_vars.insert(var, vs);
|
||||
}
|
||||
|
||||
pub fn insert_last_chunk_temp_var(&mut self, var: Rc<Var>) {
|
||||
self.last_chunk_temp_vars.insert(var);
|
||||
}
|
||||
|
||||
// computes no_use and conflict sets for all temp vars.
|
||||
pub fn populate_restricting_sets(&mut self) {
|
||||
// three stages:
|
||||
// 1. move the use sets of each variable to a local IndexMap, use_set
|
||||
// (iterate mutably, swap mutable refs).
|
||||
// 2. drain use_set. For each use set of U, add into the
|
||||
// no-use sets of appropriate variables T =/= U.
|
||||
// 3. Move the use sets back to their original locations in the fixture.
|
||||
// Compute the conflict set of u.
|
||||
|
||||
// 1.
|
||||
let mut use_sets: IndexMap<Rc<Var>, OccurrenceSet> = IndexMap::new();
|
||||
|
||||
for (var, &mut (ref mut var_status, _)) in self.iter_mut() {
|
||||
if let &mut VarStatus::Temp(_, ref mut var_data) = var_status {
|
||||
let mut use_set = OccurrenceSet::new();
|
||||
|
||||
swap(&mut var_data.use_set, &mut use_set);
|
||||
use_sets.insert((*var).clone(), use_set);
|
||||
}
|
||||
}
|
||||
|
||||
for (u, use_set) in use_sets.drain(..) {
|
||||
// 2.
|
||||
for &(term_loc, reg) in use_set.iter() {
|
||||
if let GenContext::Last(cn_u) = term_loc {
|
||||
for (ref t, &mut (ref mut var_status, _)) in self.iter_mut() {
|
||||
if let &mut VarStatus::Temp(cn_t, ref mut t_data) = var_status {
|
||||
if cn_u == cn_t && *u != ***t {
|
||||
if !t_data.uses_reg(reg) {
|
||||
t_data.no_use_set.insert(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3.
|
||||
match self.get_mut(u).unwrap() {
|
||||
&mut (VarStatus::Temp(_, ref mut u_data), _) => {
|
||||
u_data.use_set = use_set;
|
||||
u_data.populate_conflict_set();
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, u: Rc<Var>) -> Option<&mut VariableFixture<'a>> {
|
||||
self.perm_vars.get_mut(&u)
|
||||
}
|
||||
|
||||
fn iter_mut(&mut self) -> indexmap::map::IterMut<Rc<Var>, VariableFixture<'a>> {
|
||||
self.perm_vars.iter_mut()
|
||||
}
|
||||
|
||||
fn record_temp_info(&mut self, tvd: &mut TempVarData, arg_c: usize, term_loc: GenContext) {
|
||||
match term_loc {
|
||||
GenContext::Head | GenContext::Last(_) => {
|
||||
tvd.use_set.insert((term_loc, arg_c));
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn vars_above_threshold(&self, index: usize) -> usize {
|
||||
let mut var_count = 0;
|
||||
|
||||
for &(ref var_status, _) in self.values() {
|
||||
if let &VarStatus::Perm(i) = var_status {
|
||||
if i > index {
|
||||
var_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var_count
|
||||
}
|
||||
|
||||
pub fn mark_vars_in_chunk<I>(&mut self, iter: I, lt_arity: usize, term_loc: GenContext)
|
||||
where
|
||||
I: Iterator<Item = TermRef<'a>>,
|
||||
{
|
||||
let chunk_num = term_loc.chunk_num();
|
||||
let mut arg_c = 1;
|
||||
|
||||
for term_ref in iter {
|
||||
if let &TermRef::Var(lvl, cell, ref var) = &term_ref {
|
||||
let mut status = self.perm_vars.swap_remove(var).unwrap_or((
|
||||
VarStatus::Temp(chunk_num, TempVarData::new(lt_arity)),
|
||||
Vec::new(),
|
||||
));
|
||||
|
||||
status.1.push(cell);
|
||||
|
||||
match status.0 {
|
||||
VarStatus::Temp(cn, ref mut tvd) if cn == chunk_num => {
|
||||
if let Level::Shallow = lvl {
|
||||
self.record_temp_info(tvd, arg_c, term_loc);
|
||||
}
|
||||
}
|
||||
_ => status.0 = VarStatus::Perm(chunk_num),
|
||||
};
|
||||
|
||||
self.perm_vars.insert(var.clone(), status);
|
||||
}
|
||||
|
||||
if let Level::Shallow = term_ref.level() {
|
||||
arg_c += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_iter(self) -> indexmap::map::IntoIter<Rc<Var>, VariableFixture<'a>> {
|
||||
self.perm_vars.into_iter()
|
||||
}
|
||||
|
||||
fn values(&self) -> indexmap::map::Values<Rc<Var>, VariableFixture<'a>> {
|
||||
self.perm_vars.values()
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.perm_vars.len()
|
||||
}
|
||||
|
||||
pub fn set_perm_vals(&self, has_deep_cuts: bool) {
|
||||
let mut values_vec: Vec<_> = self
|
||||
.values()
|
||||
.filter_map(|ref v| match &v.0 {
|
||||
&VarStatus::Perm(i) => Some((i, &v.1)),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
values_vec.sort_by_key(|ref v| v.0);
|
||||
|
||||
let offset = has_deep_cuts as usize;
|
||||
|
||||
for (i, (_, cells)) in values_vec.into_iter().rev().enumerate() {
|
||||
for cell in cells {
|
||||
cell.set(VarReg::Norm(RegType::Perm(i + 1 + offset)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnsafeVarMarker {
|
||||
pub unsafe_vars: IndexMap<RegType, usize>,
|
||||
pub safe_vars: IndexSet<RegType>,
|
||||
}
|
||||
|
||||
impl UnsafeVarMarker {
|
||||
pub fn new() -> Self {
|
||||
UnsafeVarMarker {
|
||||
unsafe_vars: IndexMap::new(),
|
||||
safe_vars: IndexSet::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_safe_vars(safe_vars: IndexSet<RegType>) -> Self {
|
||||
UnsafeVarMarker {
|
||||
unsafe_vars: IndexMap::new(),
|
||||
safe_vars
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark_safe_vars(&mut self, query_instr: &QueryInstruction) -> bool {
|
||||
match query_instr {
|
||||
&QueryInstruction::PutVariable(r @ RegType::Temp(_), _)
|
||||
| &QueryInstruction::SetVariable(r) => {
|
||||
self.safe_vars.insert(r);
|
||||
true
|
||||
}
|
||||
_ => {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark_phase(&mut self, query_instr: &QueryInstruction, phase: usize) {
|
||||
match query_instr {
|
||||
&QueryInstruction::PutValue(r @ RegType::Perm(_), _)
|
||||
| &QueryInstruction::SetValue(r) => {
|
||||
let p = self.unsafe_vars.entry(r).or_insert(0);
|
||||
*p = phase;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark_unsafe_vars(&mut self, query_instr: &mut QueryInstruction, phase: usize) {
|
||||
match query_instr {
|
||||
&mut QueryInstruction::PutValue(RegType::Perm(i), arg) => {
|
||||
if let Some(p) = self.unsafe_vars.swap_remove(&RegType::Perm(i)) {
|
||||
if p == phase {
|
||||
*query_instr = QueryInstruction::PutUnsafeValue(i, arg);
|
||||
self.safe_vars.insert(RegType::Perm(i));
|
||||
} else {
|
||||
self.unsafe_vars.insert(RegType::Perm(i), p);
|
||||
}
|
||||
}
|
||||
}
|
||||
&mut QueryInstruction::SetValue(r) => {
|
||||
if !self.safe_vars.contains(&r) {
|
||||
*query_instr = QueryInstruction::SetLocalValue(r);
|
||||
|
||||
self.safe_vars.insert(r);
|
||||
self.unsafe_vars.remove(&r);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
1352
src/forms.rs
1352
src/forms.rs
File diff suppressed because it is too large
Load Diff
@@ -1,798 +0,0 @@
|
||||
//! A macro to construct functor terms ready to be written to the WAM
|
||||
//! heap.
|
||||
|
||||
use crate::atom_table::*;
|
||||
use crate::instructions::IndexingCodePtr;
|
||||
use crate::machine::heap::Heap;
|
||||
use crate::parser::ast::Fixnum;
|
||||
use crate::types::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum FunctorElement {
|
||||
AbsoluteCell(HeapCellValue),
|
||||
Cell(HeapCellValue),
|
||||
InnerFunctor(u64, Vec<FunctorElement>),
|
||||
String(u64, String),
|
||||
}
|
||||
|
||||
// helper macros
|
||||
macro_rules! count {
|
||||
() => (0);
|
||||
( $x:tt $($xs:tt)* ) => (1 + count!($($xs)*));
|
||||
}
|
||||
|
||||
// core macros
|
||||
|
||||
/*
|
||||
* functor! is more declarative now, with fewer effects and more
|
||||
* work done at compile time using const functions. With these
|
||||
* advantages come new quirks: expressions must generally be wrapped
|
||||
* in round parentheses for rustc to parse them. See the tests module
|
||||
* below for examples, especially those involving atom!
|
||||
* subexpressions.
|
||||
*/
|
||||
|
||||
macro_rules! functor {
|
||||
($name:expr) => ({
|
||||
vec![FunctorElement::Cell(atom_as_cell!($name))]
|
||||
});
|
||||
($name:expr, [$($dt:ident($($value:tt),*)),+]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[FunctorElement::Cell(atom_as_cell!($name, count!($($dt) *)))],
|
||||
1,
|
||||
[])
|
||||
});
|
||||
}
|
||||
|
||||
macro_rules! inner_functor {
|
||||
($name:expr, $res_len:expr, [$($dt:ident($($value:tt),*)),+]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[FunctorElement::Cell(atom_as_cell!($name, count!($($dt) *)))],
|
||||
1 + $res_len,
|
||||
[])
|
||||
});
|
||||
}
|
||||
|
||||
macro_rules! build_functor {
|
||||
([], [$($res:expr),*], $res_len:expr, [$($subfunctor:expr),*]) => ({
|
||||
vec![$($res,)* $($subfunctor),*]
|
||||
});
|
||||
([indexing_code_ptr($e:expr) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
let (inner_functor, cell_size) = indexing_code_ptr($e);
|
||||
let referent = if cell_size == 1 {
|
||||
heap_loc_as_cell!(1u64 + count!($($dt)*) + $res_len)
|
||||
} else {
|
||||
str_loc_as_cell!(1u64 + count!($($dt)*) + $res_len)
|
||||
};
|
||||
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(referent)],
|
||||
1 + cell_size + $res_len,
|
||||
[$($subfunctor, )* FunctorElement::InnerFunctor(cell_size, inner_functor)])
|
||||
});
|
||||
([fixnum($e:expr) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(fixnum_as_cell!(/*FIXME this is not safe*/ unsafe{Fixnum::build_with_unchecked($e as i64)}))],
|
||||
1 + $res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([cell($e:expr) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::AbsoluteCell($e)],
|
||||
1 + $res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([literal($e:expr) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::AbsoluteCell(HeapCellValue::from($e))],
|
||||
1 + $res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([number($n:expr, $arena:expr) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
let number_cell = HeapCellValue::arena_from($n, $arena);
|
||||
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(number_cell)],
|
||||
1 + $res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([list([]) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(empty_list_as_cell!())],
|
||||
1 + $res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([list([$id:ident($($id_value:tt),*) $(, $in_dt:ident($($in_value:tt),*))*]) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
build_functor!([functor((atom!(".")), [$id($($id_value),*), list([$($in_dt($($in_value),*)),*])])
|
||||
$(, $dt($($value),*))*],
|
||||
[$($res),*],
|
||||
$res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([string($s:expr) $(, $dt:ident($($value:tt),*))*], [$($res:expr),*], $res_len:expr, [$($subfunctor:expr),*]) => ({
|
||||
#[allow(unused_parens)]
|
||||
let string = $s;
|
||||
let pstr_len = cell_index!(Heap::compute_pstr_size(&string)) as u64;
|
||||
let result_len = 1 + count!($($dt)*) + $res_len;
|
||||
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(pstr_loc_as_cell!(heap_index!(result_len as usize) as u64))],
|
||||
1 + $res_len + pstr_len,
|
||||
[$($subfunctor, )* FunctorElement::String(pstr_len, string)])
|
||||
});
|
||||
([atom_as_cell($n:expr) $(, $dt:ident($($value:tt),*))*], [$($res:expr),*], $res_len:expr, [$($subfunctor:expr),*]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(atom_as_cell!($n))],
|
||||
1 + $res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([functor($stub:expr) $(, $dt:ident($($value:tt),*))*], [$($res:expr),*], $res_len:expr, [$($subfunctor:expr),*]) => ({
|
||||
let result_len = 1u64 + count!($($dt)*) + $res_len;
|
||||
let inner_functor_size = cell_index!(Heap::compute_functor_byte_size(&$stub)) as u64;
|
||||
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(str_loc_as_cell!(result_len))],
|
||||
1 + $res_len + inner_functor_size,
|
||||
[$($subfunctor, )*
|
||||
FunctorElement::InnerFunctor(inner_functor_size, $stub)])
|
||||
});
|
||||
([$id:ident($n:expr) $(, $dt:ident($($value:tt),*))*], [$($res:expr),*], $res_len:expr, [$($subfunctor:expr),*]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell($id!($n))],
|
||||
1 + $res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([functor($name:expr, [$($in_dt:ident($($in_value:tt),*)),+]) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
let result_len = 1u64 + count!($($dt)*) + $res_len;
|
||||
let inner_functor = inner_functor!($name, 0, [$($in_dt($($in_value),*)),*]);
|
||||
let inner_functor_size = cell_index!(Heap::compute_functor_byte_size(&inner_functor)) as u64;
|
||||
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(str_loc_as_cell!(result_len))],
|
||||
1 + $res_len + inner_functor_size,
|
||||
[$($subfunctor, )*
|
||||
FunctorElement::InnerFunctor(inner_functor_size, inner_functor)])
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn indexing_code_ptr(code_ptr: IndexingCodePtr) -> (Vec<FunctorElement>, u64) {
|
||||
match code_ptr {
|
||||
IndexingCodePtr::DynamicExternal(o) => {
|
||||
(functor!(atom!("dynamic_external"), [fixnum(o)]), 2)
|
||||
}
|
||||
IndexingCodePtr::External(o) => (functor!(atom!("external"), [fixnum(o)]), 2),
|
||||
IndexingCodePtr::Internal(o) => (functor!(atom!("internal"), [fixnum(o)]), 2),
|
||||
IndexingCodePtr::Fail => (vec![FunctorElement::Cell(atom_as_cell!(atom!("fail")))], 1),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn variadic_functor(
|
||||
name: Atom,
|
||||
arity: usize,
|
||||
iter: impl Iterator<Item = Vec<FunctorElement>>,
|
||||
) -> Vec<FunctorElement> {
|
||||
let mut arg_vec = vec![
|
||||
FunctorElement::Cell(atom_as_cell!(name, arity)),
|
||||
FunctorElement::Cell(list_loc_as_cell!(2)),
|
||||
];
|
||||
|
||||
let key_value_pairs: Vec<_> = iter.collect();
|
||||
let num_items = key_value_pairs.len();
|
||||
let mut functor_offset = 0;
|
||||
|
||||
for (idx, kv_func) in key_value_pairs.iter().enumerate() {
|
||||
let functor_size = cell_index!(Heap::compute_functor_byte_size(kv_func));
|
||||
|
||||
arg_vec.push(FunctorElement::Cell(str_loc_as_cell!(
|
||||
2 + num_items * 2 + functor_offset
|
||||
)));
|
||||
arg_vec.push(FunctorElement::Cell(list_loc_as_cell!(4 + 2 * idx)));
|
||||
|
||||
functor_offset += functor_size;
|
||||
}
|
||||
|
||||
arg_vec.pop();
|
||||
arg_vec.push(FunctorElement::Cell(empty_list_as_cell!()));
|
||||
|
||||
arg_vec.extend(key_value_pairs.into_iter().map(|kv_func| {
|
||||
let inner_functor_size = cell_index!(Heap::compute_functor_byte_size(&kv_func));
|
||||
FunctorElement::InnerFunctor(inner_functor_size as u64, kv_func)
|
||||
}));
|
||||
|
||||
arg_vec
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(unused_parens)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use FunctorElement::*;
|
||||
use indexmap::indexmap;
|
||||
use std::string::String;
|
||||
|
||||
#[test]
|
||||
fn basic_terms() {
|
||||
let functor = functor!(
|
||||
atom!("first"),
|
||||
[atom_as_cell((atom!("a"))), char_as_cell('c')]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 3);
|
||||
|
||||
assert_eq!(functor[0], Cell(atom_as_cell!(atom!("first"), 2)));
|
||||
assert_eq!(functor[1], Cell(atom_as_cell!(atom!("a"))));
|
||||
assert_eq!(functor[2], Cell(char_as_cell!('c')));
|
||||
|
||||
let functor = functor!(
|
||||
atom!("second"),
|
||||
[
|
||||
atom_as_cell((atom!("a"))),
|
||||
functor((atom!("b")), [fixnum(1), fixnum(2)]),
|
||||
char_as_cell('c')
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 5);
|
||||
|
||||
assert_eq!(functor[0], Cell(atom_as_cell!(atom!("second"), 3)));
|
||||
assert_eq!(functor[1], Cell(atom_as_cell!(atom!("a"))));
|
||||
assert_eq!(functor[2], Cell(str_loc_as_cell!(4)));
|
||||
assert_eq!(functor[3], Cell(char_as_cell!('c')));
|
||||
assert_eq!(
|
||||
functor[4],
|
||||
InnerFunctor(3, functor!(atom!("b"), [fixnum(1), fixnum(2)]))
|
||||
);
|
||||
|
||||
let functor = functor!(
|
||||
atom!("third"),
|
||||
[
|
||||
atom_as_cell((atom!("a"))),
|
||||
functor((atom!("b")), [fixnum(1), fixnum(2)]),
|
||||
functor((atom!("c")), [fixnum(1), fixnum(2)]),
|
||||
char_as_cell('c')
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 7);
|
||||
|
||||
assert_eq!(functor[0], Cell(atom_as_cell!(atom!("third"), 4)));
|
||||
assert_eq!(functor[1], Cell(atom_as_cell!(atom!("a"))));
|
||||
assert_eq!(functor[2], Cell(str_loc_as_cell!(5)));
|
||||
assert_eq!(functor[3], Cell(str_loc_as_cell!(8)));
|
||||
assert_eq!(functor[4], Cell(char_as_cell!('c')));
|
||||
assert_eq!(
|
||||
functor[5],
|
||||
InnerFunctor(3, functor!(atom!("b"), [fixnum(1), fixnum(2)]))
|
||||
);
|
||||
assert_eq!(
|
||||
functor[6],
|
||||
InnerFunctor(3, functor!(atom!("c"), [fixnum(1), fixnum(2)]))
|
||||
);
|
||||
|
||||
let functor = functor!(
|
||||
atom!("fourth"),
|
||||
[
|
||||
atom_as_cell((atom!("a"))),
|
||||
functor((atom!("b")), [fixnum(1), fixnum(2)]),
|
||||
functor((atom!("c")), [fixnum(1)]),
|
||||
functor((atom!("d")), [fixnum(453), fixnum(2)]),
|
||||
char_as_cell('c')
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 9);
|
||||
|
||||
assert_eq!(functor[0], Cell(atom_as_cell!(atom!("fourth"), 5)));
|
||||
assert_eq!(functor[1], Cell(atom_as_cell!(atom!("a"))));
|
||||
assert_eq!(functor[2], Cell(str_loc_as_cell!(6)));
|
||||
assert_eq!(functor[3], Cell(str_loc_as_cell!(9)));
|
||||
assert_eq!(functor[4], Cell(str_loc_as_cell!(11)));
|
||||
assert_eq!(functor[5], Cell(char_as_cell!('c')));
|
||||
assert_eq!(
|
||||
functor[6],
|
||||
InnerFunctor(3, functor!(atom!("b"), [fixnum(1), fixnum(2)]))
|
||||
);
|
||||
assert_eq!(
|
||||
functor[7],
|
||||
InnerFunctor(2, functor!(atom!("c"), [fixnum(1)]))
|
||||
);
|
||||
assert_eq!(
|
||||
functor[8],
|
||||
InnerFunctor(3, functor!(atom!("d"), [fixnum(453), fixnum(2)]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_terms_in_heap() {
|
||||
let functor = functor!(
|
||||
atom!("first"),
|
||||
[atom_as_cell((atom!("a"))), char_as_cell('b')]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 3);
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
let loc = functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(loc, str_loc_as_cell!(0));
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("first"), 2));
|
||||
assert_eq!(heap[1], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[2], char_as_cell!('b'));
|
||||
|
||||
heap.truncate(2);
|
||||
|
||||
let functor = functor!(
|
||||
atom!("second"),
|
||||
[
|
||||
atom_as_cell((atom!("a"))),
|
||||
functor((atom!("b")), [fixnum(1), fixnum(2)]),
|
||||
functor((atom!("c")), [fixnum(1), fixnum(2)]),
|
||||
char_as_cell('b')
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 7);
|
||||
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
let loc = functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(loc, str_loc_as_cell!(2));
|
||||
|
||||
assert_eq!(heap[2], atom_as_cell!(atom!("second"), 4));
|
||||
assert_eq!(heap[3], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[4], str_loc_as_cell!(7));
|
||||
assert_eq!(heap[5], str_loc_as_cell!(10));
|
||||
assert_eq!(heap[6], char_as_cell!('b'));
|
||||
assert_eq!(heap[7], atom_as_cell!(atom!("b"), 2));
|
||||
assert_eq!(heap[8], fixnum_as_cell!(Fixnum::build_with(1)));
|
||||
assert_eq!(heap[9], fixnum_as_cell!(Fixnum::build_with(2)));
|
||||
assert_eq!(heap[10], atom_as_cell!(atom!("c"), 2));
|
||||
assert_eq!(heap[11], fixnum_as_cell!(Fixnum::build_with(1)));
|
||||
assert_eq!(heap[12], fixnum_as_cell!(Fixnum::build_with(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_functors() {
|
||||
let functor = functor!(
|
||||
atom!("first"),
|
||||
[
|
||||
atom_as_cell((atom!("a"))),
|
||||
functor(
|
||||
(atom!("d")),
|
||||
[
|
||||
fixnum(1),
|
||||
functor(
|
||||
(atom!("b")),
|
||||
[atom_as_cell((atom!("c"))), char_as_cell('c')]
|
||||
)
|
||||
]
|
||||
),
|
||||
functor((atom!("e")), [fixnum(453), fixnum(2)]),
|
||||
char_as_cell('b')
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 7);
|
||||
|
||||
assert_eq!(functor[0], Cell(atom_as_cell!(atom!("first"), 4)));
|
||||
assert_eq!(functor[1], Cell(atom_as_cell!(atom!("a"))));
|
||||
assert_eq!(functor[2], Cell(str_loc_as_cell!(5)));
|
||||
assert_eq!(functor[3], Cell(str_loc_as_cell!(11)));
|
||||
assert_eq!(functor[4], Cell(char_as_cell!('b')));
|
||||
assert_eq!(
|
||||
functor[5],
|
||||
InnerFunctor(
|
||||
6,
|
||||
vec![
|
||||
Cell(atom_as_cell!(atom!("d"), 2)),
|
||||
Cell(fixnum_as_cell!(Fixnum::build_with(1))),
|
||||
Cell(str_loc_as_cell!(3)),
|
||||
InnerFunctor(
|
||||
3,
|
||||
functor!(atom!("b"), [atom_as_cell((atom!("c"))), char_as_cell('c')])
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
functor[6],
|
||||
InnerFunctor(3, functor!(atom!("e"), [fixnum(453), fixnum(2)]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_functors_in_heap() {
|
||||
let functor = functor!(
|
||||
atom!("first"),
|
||||
[
|
||||
atom_as_cell((atom!("a"))),
|
||||
functor(
|
||||
(atom!("second")),
|
||||
[
|
||||
fixnum(1),
|
||||
functor(
|
||||
(atom!("third")),
|
||||
[atom_as_cell((atom!("b"))), char_as_cell('c')]
|
||||
)
|
||||
]
|
||||
),
|
||||
functor((atom!("fourth")), [fixnum(453), fixnum(2)]),
|
||||
char_as_cell('b')
|
||||
]
|
||||
);
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
let loc = functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(loc, str_loc_as_cell!(0));
|
||||
|
||||
assert_eq!(heap.cell_len(), 14);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("first"), 4));
|
||||
assert_eq!(heap[1], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[2], str_loc_as_cell!(5));
|
||||
assert_eq!(heap[3], str_loc_as_cell!(11));
|
||||
assert_eq!(heap[4], char_as_cell!('b'));
|
||||
assert_eq!(heap[5], atom_as_cell!(atom!("second"), 2));
|
||||
assert_eq!(heap[6], fixnum_as_cell!(Fixnum::build_with(1)));
|
||||
assert_eq!(heap[7], str_loc_as_cell!(8));
|
||||
assert_eq!(heap[8], atom_as_cell!(atom!("third"), 2));
|
||||
assert_eq!(heap[9], atom_as_cell!(atom!("b")));
|
||||
assert_eq!(heap[10], char_as_cell!('c'));
|
||||
assert_eq!(heap[11], atom_as_cell!(atom!("fourth"), 2));
|
||||
assert_eq!(heap[12], fixnum_as_cell!(Fixnum::build_with(453)));
|
||||
assert_eq!(heap[13], fixnum_as_cell!(Fixnum::build_with(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn functors_with_strings_in_heap() {
|
||||
let functor = functor!(atom!("first"), [string((String::from("a string")))]);
|
||||
|
||||
assert_eq!(functor.len(), 3);
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
let loc = functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(loc, str_loc_as_cell!(0));
|
||||
assert_eq!(heap.cell_len(), 5);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("first"), 1));
|
||||
assert_eq!(heap[1], pstr_loc_as_cell!(heap_index!(2)));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(2), "a string".len()),
|
||||
"a string"
|
||||
);
|
||||
assert_eq!(heap[4], empty_list_as_cell!());
|
||||
|
||||
heap.truncate(0);
|
||||
|
||||
let functor = functor!(
|
||||
atom!("second"),
|
||||
[string((String::from("a stuttered\0 string")))]
|
||||
);
|
||||
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap.cell_len(), 10);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("second"), 1));
|
||||
assert_eq!(heap[1], pstr_loc_as_cell!(heap_index!(2)));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(2), "a stuttered".len()),
|
||||
"a stuttered"
|
||||
);
|
||||
assert_eq!(heap[4], list_loc_as_cell!(5));
|
||||
assert_eq!(heap[5], char_as_cell!('\u{0}'));
|
||||
assert_eq!(heap[6], pstr_loc_as_cell!(heap_index!(7)));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(7), " string".len()),
|
||||
" string"
|
||||
);
|
||||
assert_eq!(heap[9], empty_list_as_cell!());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn functors_with_lists_in_heap() {
|
||||
let functor = functor!(
|
||||
atom!("first"),
|
||||
[list([fixnum(1), atom_as_cell((atom!("a"))), fixnum(2)])]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 3);
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap.cell_len(), 11);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("first"), 1));
|
||||
assert_eq!(heap[1], str_loc_as_cell!(2));
|
||||
assert_eq!(heap[2], atom_as_cell!(atom!("."), 2));
|
||||
assert_eq!(heap[3], fixnum_as_cell!(Fixnum::build_with(1)));
|
||||
assert_eq!(heap[4], str_loc_as_cell!(5));
|
||||
assert_eq!(heap[5], atom_as_cell!(atom!("."), 2));
|
||||
assert_eq!(heap[6], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[7], str_loc_as_cell!(8));
|
||||
assert_eq!(heap[8], atom_as_cell!(atom!("."), 2));
|
||||
assert_eq!(heap[9], fixnum_as_cell!(Fixnum::build_with(2)));
|
||||
assert_eq!(heap[10], empty_list_as_cell!());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inlined_atoms() {
|
||||
let atom_table = AtomTable::new().unwrap();
|
||||
let inlined = AtomTable::build_with(&atom_table, "inline");
|
||||
|
||||
assert!(inlined.is_inlined());
|
||||
assert_eq!(&*inlined.as_str(), "inline");
|
||||
|
||||
let non_inlined = AtomTable::build_with(&atom_table, "longer non-inlined atom");
|
||||
|
||||
assert!(!non_inlined.is_inlined());
|
||||
assert_eq!(&*non_inlined.as_str(), "longer non-inlined atom");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn functors_with_indexing_code_ptr() {
|
||||
let code_ptr = IndexingCodePtr::Internal(0);
|
||||
let functor = functor!(
|
||||
atom!("first"),
|
||||
[
|
||||
string((String::from("a string"))),
|
||||
indexing_code_ptr(code_ptr)
|
||||
]
|
||||
);
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap.cell_len(), 8);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("first"), 2));
|
||||
assert_eq!(heap[1], pstr_loc_as_cell!(heap_index!(3)));
|
||||
assert_eq!(heap[2], str_loc_as_cell!(6));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(3), "a string".len()),
|
||||
"a string"
|
||||
);
|
||||
assert_eq!(heap[5], empty_list_as_cell!());
|
||||
assert_eq!(heap[6], atom_as_cell!(atom!("internal"), 1));
|
||||
assert_eq!(heap[7], fixnum_as_cell!(Fixnum::build_with(0)));
|
||||
|
||||
heap.truncate(0);
|
||||
|
||||
let functor = functor!(
|
||||
atom!("second"),
|
||||
[
|
||||
string((String::from("a string"))),
|
||||
functor(
|
||||
(atom!("third")),
|
||||
[
|
||||
atom_as_cell((atom!("a"))),
|
||||
string((String::from("another string"))),
|
||||
indexing_code_ptr(code_ptr)
|
||||
]
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap.cell_len(), 15);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("second"), 2));
|
||||
assert_eq!(heap[1], pstr_loc_as_cell!(heap_index!(3)));
|
||||
assert_eq!(heap[2], str_loc_as_cell!(6));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(3), "a string".len()),
|
||||
"a string"
|
||||
);
|
||||
assert_eq!(heap[5], empty_list_as_cell!());
|
||||
assert_eq!(heap[6], atom_as_cell!(atom!("third"), 3));
|
||||
assert_eq!(heap[7], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[8], pstr_loc_as_cell!(heap_index!(10)));
|
||||
assert_eq!(heap[9], str_loc_as_cell!(13));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(10), "another string".len()),
|
||||
"another string"
|
||||
);
|
||||
assert_eq!(heap[12], empty_list_as_cell!());
|
||||
assert_eq!(heap[13], atom_as_cell!(atom!("internal"), 1));
|
||||
assert_eq!(heap[14], fixnum_as_cell!(Fixnum::build_with(0)));
|
||||
|
||||
let functor = functor!(
|
||||
atom!("fourth"),
|
||||
[
|
||||
string((String::from("a string"))),
|
||||
functor(
|
||||
(atom!("a")),
|
||||
[
|
||||
functor(
|
||||
(atom!("fifth")),
|
||||
[
|
||||
fixnum(5),
|
||||
string((String::from("another string"))),
|
||||
indexing_code_ptr(code_ptr)
|
||||
]
|
||||
),
|
||||
string((String::from("and another")))
|
||||
]
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
heap.truncate(0);
|
||||
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap.cell_len(), 21);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("fourth"), 2));
|
||||
assert_eq!(heap[1], pstr_loc_as_cell!(heap_index!(3)));
|
||||
assert_eq!(heap[2], str_loc_as_cell!(6));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(3), "a string".len()),
|
||||
"a string"
|
||||
);
|
||||
assert_eq!(heap[5], empty_list_as_cell!());
|
||||
assert_eq!(heap[6], atom_as_cell!(atom!("a"), 2));
|
||||
assert_eq!(heap[7], str_loc_as_cell!(9));
|
||||
assert_eq!(heap[8], pstr_loc_as_cell!(heap_index!(18))); // <-- wrong!
|
||||
assert_eq!(heap[9], atom_as_cell!(atom!("fifth"), 3));
|
||||
assert_eq!(heap[10], fixnum_as_cell!(Fixnum::build_with(5)));
|
||||
assert_eq!(heap[11], pstr_loc_as_cell!(heap_index!(13)));
|
||||
assert_eq!(heap[12], str_loc_as_cell!(16));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(13), "another string".len()),
|
||||
"another string"
|
||||
);
|
||||
assert_eq!(heap[15], empty_list_as_cell!());
|
||||
assert_eq!(heap[16], atom_as_cell!(atom!("internal"), 1));
|
||||
assert_eq!(heap[17], fixnum_as_cell!(Fixnum::build_with(0)));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(18), "and another".len()),
|
||||
"and another"
|
||||
);
|
||||
assert_eq!(heap[20], empty_list_as_cell!());
|
||||
|
||||
let constants = indexmap![
|
||||
atom_as_cell!(atom!("a")) => IndexingCodePtr::External(2),
|
||||
atom_as_cell!(atom!("d")) => IndexingCodePtr::External(7),
|
||||
];
|
||||
|
||||
let functor = variadic_functor(
|
||||
atom!("switch_on_constants"),
|
||||
1,
|
||||
constants
|
||||
.iter()
|
||||
.map(|(c, ptr)| functor!(atom!(":"), [cell((*c)), indexing_code_ptr((*ptr))])),
|
||||
);
|
||||
|
||||
heap.truncate(0);
|
||||
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("switch_on_constants"), 1));
|
||||
assert_eq!(heap[1], list_loc_as_cell!(2));
|
||||
assert_eq!(heap[2], str_loc_as_cell!(6));
|
||||
assert_eq!(heap[3], list_loc_as_cell!(4));
|
||||
assert_eq!(heap[4], str_loc_as_cell!(11));
|
||||
assert_eq!(heap[5], empty_list_as_cell!());
|
||||
assert_eq!(heap[6], atom_as_cell!(atom!(":"), 2));
|
||||
assert_eq!(heap[7], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[8], str_loc_as_cell!(9));
|
||||
assert_eq!(heap[9], atom_as_cell!(atom!("external"), 1));
|
||||
assert_eq!(heap[10], fixnum_as_cell!(Fixnum::build_with(2)));
|
||||
assert_eq!(heap[11], atom_as_cell!(atom!(":"), 2));
|
||||
assert_eq!(heap[12], atom_as_cell!(atom!("d")));
|
||||
assert_eq!(heap[13], str_loc_as_cell!(14));
|
||||
assert_eq!(heap[14], atom_as_cell!(atom!("external"), 1));
|
||||
assert_eq!(heap[15], fixnum_as_cell!(Fixnum::build_with(7)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undefined_procedure_functor() {
|
||||
// existence_error
|
||||
let culprit = functor!(atom!("/"), [atom_as_cell((atom!("a"))), fixnum(1)]);
|
||||
|
||||
let stub = functor!(
|
||||
atom!("existence_error"),
|
||||
[
|
||||
atom_as_cell((atom!("procedure"))),
|
||||
functor((culprit.clone()))
|
||||
]
|
||||
);
|
||||
|
||||
println!("{stub:?}");
|
||||
|
||||
// now the error form
|
||||
let lineless_error_form = functor!(atom!("error"), [functor(stub), functor(culprit)]);
|
||||
|
||||
println!("{lineless_error_form:?}");
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(lineless_error_form);
|
||||
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("error"), 2));
|
||||
assert_eq!(heap[1], str_loc_as_cell!(3));
|
||||
assert_eq!(heap[2], str_loc_as_cell!(9));
|
||||
assert_eq!(heap[3], atom_as_cell!(atom!("existence_error"), 2));
|
||||
assert_eq!(heap[4], atom_as_cell!(atom!("procedure")));
|
||||
assert_eq!(heap[5], str_loc_as_cell!(6)); // is str_loc_as_cell!(3)
|
||||
assert_eq!(heap[6], atom_as_cell!(atom!("/"), 2));
|
||||
assert_eq!(heap[7], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[8], fixnum_as_cell!(Fixnum::build_with(1)));
|
||||
assert_eq!(heap[9], atom_as_cell!(atom!("/"), 2));
|
||||
assert_eq!(heap[10], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[11], fixnum_as_cell!(Fixnum::build_with(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn argless_functor() {
|
||||
let name = functor!(atom!("[]"));
|
||||
|
||||
assert_eq!(name.len(), 1);
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(name);
|
||||
let loc = functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(loc, heap_loc_as_cell!(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predefined_subfunctors() {
|
||||
let stub = functor!(atom!("sub"), [atom_as_cell((atom!("[]")))]);
|
||||
let name = functor!(atom!("super"), [functor(stub)]);
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(name);
|
||||
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap.cell_len(), 4);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("super"), 1));
|
||||
assert_eq!(heap[1], str_loc_as_cell!(2));
|
||||
assert_eq!(heap[2], atom_as_cell!(atom!("sub"), 1));
|
||||
assert_eq!(heap[3], empty_list_as_cell!());
|
||||
}
|
||||
}
|
||||
3653
src/heap_iter.rs
3653
src/heap_iter.rs
File diff suppressed because it is too large
Load Diff
2635
src/heap_print.rs
2635
src/heap_print.rs
File diff suppressed because it is too large
Load Diff
25
src/http.rs
25
src/http.rs
@@ -1,25 +0,0 @@
|
||||
use bytes::{Bytes, buf::Reader};
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
use tokio::sync::Notify;
|
||||
|
||||
use warp::http;
|
||||
|
||||
pub struct HttpListener {
|
||||
pub incoming: std::sync::mpsc::Receiver<HttpRequest>,
|
||||
pub warp_shutdown: Arc<Notify>,
|
||||
}
|
||||
|
||||
pub struct HttpRequest {
|
||||
pub request_data: HttpRequestData,
|
||||
pub response: HttpResponse,
|
||||
}
|
||||
|
||||
pub type HttpResponse = Arc<(Mutex<bool>, Mutex<Option<warp::reply::Response>>, Condvar)>;
|
||||
|
||||
pub struct HttpRequestData {
|
||||
pub method: http::Method,
|
||||
pub headers: http::HeaderMap,
|
||||
pub path: String,
|
||||
pub query: String,
|
||||
pub body: Reader<Bytes>,
|
||||
}
|
||||
1797
src/indexing.rs
1797
src/indexing.rs
File diff suppressed because it is too large
Load Diff
1951
src/instructions.rs
1951
src/instructions.rs
File diff suppressed because it is too large
Load Diff
685
src/iterators.rs
685
src/iterators.rs
@@ -1,65 +1,137 @@
|
||||
use crate::atom_table::*;
|
||||
use crate::prolog_parser::ast::*;
|
||||
|
||||
use crate::clause_types::*;
|
||||
use crate::forms::*;
|
||||
use crate::instructions::*;
|
||||
use crate::parser::ast::*;
|
||||
use crate::machine::machine_indices::*;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::iter::*;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::vec::Vec;
|
||||
|
||||
#[allow(clippy::borrowed_box)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum TermRef<'a> {
|
||||
pub enum TermRef<'a> {
|
||||
AnonVar(Level),
|
||||
Cons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
|
||||
Literal(Level, &'a Cell<RegType>, &'a Literal),
|
||||
Clause(Level, &'a Cell<RegType>, Atom, &'a Vec<Term>),
|
||||
PartialString(Level, &'a Cell<RegType>, Rc<String>, &'a Box<Term>),
|
||||
CompleteString(Level, &'a Cell<RegType>, Rc<String>),
|
||||
Var(Level, &'a Cell<VarReg>, VarPtr),
|
||||
Constant(Level, &'a Cell<RegType>, &'a Constant),
|
||||
Clause(Level, &'a Cell<RegType>, ClauseType, &'a Vec<Box<Term>>),
|
||||
PartialString(Level, &'a Cell<RegType>, String, Option<&'a Term>),
|
||||
Var(Level, &'a Cell<VarReg>, Rc<Var>),
|
||||
}
|
||||
|
||||
#[allow(clippy::borrowed_box)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum TermIterState<'a> {
|
||||
AnonVar(Level),
|
||||
Clause(Level, usize, &'a Cell<RegType>, Atom, &'a Vec<Term>),
|
||||
Literal(Level, &'a Cell<RegType>, &'a Literal),
|
||||
InitialCons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
|
||||
FinalCons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
|
||||
InitialPartialString(Level, &'a Cell<RegType>, Rc<String>, &'a Box<Term>),
|
||||
FinalPartialString(Level, &'a Cell<RegType>, Rc<String>, &'a Box<Term>),
|
||||
CompleteString(Level, &'a Cell<RegType>, Rc<String>),
|
||||
Var(Level, &'a Cell<VarReg>, VarPtr),
|
||||
}
|
||||
|
||||
impl<'a> TermIterState<'a> {
|
||||
pub(crate) fn subterm_to_state(lvl: Level, term: &'a Term) -> TermIterState<'a> {
|
||||
match term {
|
||||
Term::AnonVar => TermIterState::AnonVar(lvl),
|
||||
Term::Clause(cell, name, subterms) => {
|
||||
TermIterState::Clause(lvl, 0, cell, *name, subterms)
|
||||
}
|
||||
Term::Cons(cell, head, tail) => {
|
||||
TermIterState::InitialCons(lvl, cell, head.as_ref(), tail.as_ref())
|
||||
}
|
||||
Term::Literal(cell, constant) => TermIterState::Literal(lvl, cell, constant),
|
||||
Term::PartialString(cell, string_buf, tail) => {
|
||||
TermIterState::InitialPartialString(lvl, cell, string_buf.clone(), tail)
|
||||
}
|
||||
Term::CompleteString(cell, string) => {
|
||||
TermIterState::CompleteString(lvl, cell, string.clone())
|
||||
}
|
||||
Term::Var(cell, var_ptr) => TermIterState::Var(lvl, cell, var_ptr.clone()),
|
||||
impl<'a> TermRef<'a> {
|
||||
pub fn level(self) -> Level {
|
||||
match self {
|
||||
TermRef::AnonVar(lvl)
|
||||
| TermRef::Cons(lvl, ..)
|
||||
| TermRef::Constant(lvl, ..)
|
||||
| TermRef::Var(lvl, ..)
|
||||
| TermRef::Clause(lvl, ..) => lvl,
|
||||
| TermRef::PartialString(lvl, ..) => lvl,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct QueryIterator<'a> {
|
||||
pub enum TermIterState<'a> {
|
||||
AnonVar(Level),
|
||||
Constant(Level, &'a Cell<RegType>, &'a Constant),
|
||||
Clause(
|
||||
Level,
|
||||
usize,
|
||||
&'a Cell<RegType>,
|
||||
ClauseType,
|
||||
&'a Vec<Box<Term>>,
|
||||
),
|
||||
InitialCons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
|
||||
FinalCons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
|
||||
PartialString(Level, &'a Cell<RegType>, String, Option<&'a Term>),
|
||||
Var(Level, &'a Cell<VarReg>, Rc<Var>),
|
||||
}
|
||||
|
||||
fn is_partial_string<'a>(
|
||||
head: &'a Term,
|
||||
mut tail: &'a Term,
|
||||
) -> Option<(String, Option<&'a Term>)>
|
||||
{
|
||||
let mut string =
|
||||
match head {
|
||||
&Term::Constant(_, Constant::Atom(ref atom, _)) if atom.is_char() => {
|
||||
atom.as_str().chars().next().unwrap().to_string()
|
||||
}
|
||||
&Term::Constant(_, Constant::Char(c)) => {
|
||||
c.to_string()
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
while let Term::Cons(_, ref head, ref succ) = tail {
|
||||
match head.as_ref() {
|
||||
&Term::Constant(_, Constant::Atom(ref atom, _)) if atom.is_char() => {
|
||||
string.push(atom.as_str().chars().next().unwrap());
|
||||
}
|
||||
&Term::Constant(_, Constant::Char(c)) => {
|
||||
string.push(c);
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
tail = succ.as_ref();
|
||||
}
|
||||
|
||||
match tail {
|
||||
Term::AnonVar | Term::Var(..) => {
|
||||
return Some((string, Some(tail)));
|
||||
}
|
||||
Term::Constant(_, Constant::EmptyList) => {
|
||||
return Some((string, None));
|
||||
}
|
||||
Term::Constant(_, Constant::String(tail)) => {
|
||||
string += &tail;
|
||||
return Some((string, None));
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TermIterState<'a> {
|
||||
pub fn subterm_to_state(lvl: Level, term: &'a Term) -> TermIterState<'a> {
|
||||
match term {
|
||||
&Term::AnonVar => {
|
||||
TermIterState::AnonVar(lvl)
|
||||
}
|
||||
&Term::Clause(ref cell, ref name, ref subterms, ref spec) => {
|
||||
let ct = if let Some(spec) = spec {
|
||||
ClauseType::Op(name.clone(), spec.clone(), CodeIndex::default())
|
||||
} else {
|
||||
ClauseType::Named(name.clone(), subterms.len(), CodeIndex::default())
|
||||
};
|
||||
|
||||
TermIterState::Clause(lvl, 0, cell, ct, subterms)
|
||||
}
|
||||
&Term::Cons(ref cell, ref head, ref tail) => {
|
||||
TermIterState::InitialCons(lvl, cell, head.as_ref(), tail.as_ref())
|
||||
}
|
||||
&Term::Constant(ref cell, ref constant) => {
|
||||
TermIterState::Constant(lvl, cell, constant)
|
||||
}
|
||||
&Term::Var(ref cell, ref var) => {
|
||||
TermIterState::Var(lvl, cell, var.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QueryIterator<'a> {
|
||||
state_stack: Vec<TermIterState<'a>>,
|
||||
}
|
||||
|
||||
@@ -69,31 +141,42 @@ impl<'a> QueryIterator<'a> {
|
||||
.push(TermIterState::subterm_to_state(lvl, term));
|
||||
}
|
||||
|
||||
/*
|
||||
fn from_rule_head_clause(terms: &'a Vec<Term>) -> Self {
|
||||
fn from_rule_head_clause(terms: &'a Vec<Box<Term>>) -> Self {
|
||||
let state_stack = terms
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|bt| TermIterState::subterm_to_state(Level::Shallow, bt))
|
||||
.map(|bt| TermIterState::subterm_to_state(Level::Shallow, bt.as_ref()))
|
||||
.collect();
|
||||
|
||||
QueryIterator { state_stack }
|
||||
}
|
||||
*/
|
||||
|
||||
fn from_term(term: &'a Term) -> Self {
|
||||
let state = match term {
|
||||
Term::AnonVar
|
||||
| Term::Cons(..)
|
||||
| Term::Literal(..)
|
||||
| Term::PartialString(..)
|
||||
| Term::CompleteString(..) => {
|
||||
&Term::AnonVar => {
|
||||
return QueryIterator {
|
||||
state_stack: vec![],
|
||||
};
|
||||
}
|
||||
}
|
||||
Term::Clause(r, name, terms) => TermIterState::Clause(Level::Root, 0, r, *name, terms),
|
||||
Term::Var(cell, var_ptr) => TermIterState::Var(Level::Root, cell, var_ptr.clone()),
|
||||
&Term::Clause(ref r, ref name, ref terms, ref fixity) => TermIterState::Clause(
|
||||
Level::Root,
|
||||
0,
|
||||
r,
|
||||
ClauseType::from(name.clone(), terms.len(), fixity.clone()),
|
||||
terms,
|
||||
),
|
||||
&Term::Cons(..) => {
|
||||
return QueryIterator {
|
||||
state_stack: vec![],
|
||||
}
|
||||
}
|
||||
&Term::Constant(_, _) => {
|
||||
return QueryIterator {
|
||||
state_stack: vec![],
|
||||
}
|
||||
}
|
||||
&Term::Var(ref cell, ref var) =>
|
||||
TermIterState::Var(Level::Root, cell, (*var).clone()),
|
||||
};
|
||||
|
||||
QueryIterator {
|
||||
@@ -101,26 +184,45 @@ impl<'a> QueryIterator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn extend_state(&mut self, lvl: Level, term: &'a QueryTerm) {
|
||||
fn new(term: &'a QueryTerm) -> Self {
|
||||
match term {
|
||||
QueryTerm::Clause(cell, ClauseType::CallN(_), terms, _) => {
|
||||
self.state_stack
|
||||
.push(TermIterState::Clause(lvl, 1, cell, atom!("$call"), terms));
|
||||
&QueryTerm::Clause(ref cell, ClauseType::CallN, ref terms, _) => {
|
||||
let state = TermIterState::Clause(Level::Root, 1, cell, ClauseType::CallN, terms);
|
||||
QueryIterator {
|
||||
state_stack: vec![state],
|
||||
}
|
||||
}
|
||||
QueryTerm::Clause(cell, ct, terms, _) => {
|
||||
self.state_stack
|
||||
.push(TermIterState::Clause(lvl, 0, cell, ct.name(), terms));
|
||||
&QueryTerm::Clause(ref cell, ref ct, ref terms, _) => {
|
||||
let state = TermIterState::Clause(Level::Root, 0, cell, ct.clone(), terms);
|
||||
QueryIterator {
|
||||
state_stack: vec![state],
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
&QueryTerm::UnblockedCut(ref cell) => {
|
||||
let state = TermIterState::Var(Level::Root, cell, rc_atom!("!"));
|
||||
QueryIterator {
|
||||
state_stack: vec![state],
|
||||
}
|
||||
}
|
||||
&QueryTerm::GetLevelAndUnify(ref cell, ref var) => {
|
||||
let state = TermIterState::Var(Level::Root, cell, var.clone());
|
||||
QueryIterator {
|
||||
state_stack: vec![state],
|
||||
}
|
||||
}
|
||||
&QueryTerm::Jump(ref vars) => {
|
||||
let state_stack = vars
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|t| TermIterState::subterm_to_state(Level::Shallow, t))
|
||||
.collect();
|
||||
|
||||
pub fn new(term: &'a QueryTerm) -> Self {
|
||||
let mut iter = QueryIterator {
|
||||
state_stack: vec![],
|
||||
};
|
||||
iter.extend_state(Level::Root, term);
|
||||
iter
|
||||
QueryIterator { state_stack }
|
||||
}
|
||||
&QueryTerm::BlockedCut => QueryIterator {
|
||||
state_stack: vec![],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,17 +235,20 @@ impl<'a> Iterator for QueryIterator<'a> {
|
||||
TermIterState::AnonVar(lvl) => {
|
||||
return Some(TermRef::AnonVar(lvl));
|
||||
}
|
||||
TermIterState::Clause(lvl, child_num, cell, name, child_terms) => {
|
||||
TermIterState::Clause(lvl, child_num, cell, ct, child_terms) => {
|
||||
if child_num == child_terms.len() {
|
||||
match name {
|
||||
atom!("$call") if lvl == Level::Root => {
|
||||
self.push_subterm(Level::Shallow, &child_terms[0]);
|
||||
match ct {
|
||||
ClauseType::CallN => {
|
||||
self.push_subterm(Level::Shallow, child_terms[0].as_ref())
|
||||
}
|
||||
_ => {
|
||||
ClauseType::Named(..) | ClauseType::Op(..) => {
|
||||
return match lvl {
|
||||
Level::Root => None,
|
||||
lvl => Some(TermRef::Clause(lvl, cell, name, child_terms)),
|
||||
};
|
||||
lvl => Some(TermRef::Clause(lvl, cell, ct, child_terms)),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
@@ -151,39 +256,43 @@ impl<'a> Iterator for QueryIterator<'a> {
|
||||
lvl,
|
||||
child_num + 1,
|
||||
cell,
|
||||
name,
|
||||
ct,
|
||||
child_terms,
|
||||
));
|
||||
|
||||
self.push_subterm(lvl.child_level(), &child_terms[child_num]);
|
||||
self.push_subterm(lvl.child_level(), child_terms[child_num].as_ref());
|
||||
}
|
||||
}
|
||||
TermIterState::InitialCons(lvl, cell, head, tail) => {
|
||||
self.state_stack
|
||||
.push(TermIterState::FinalCons(lvl, cell, head, tail));
|
||||
if let Some((string, tail)) = is_partial_string(head, tail) {
|
||||
self.state_stack.push(TermIterState::PartialString(
|
||||
lvl,
|
||||
cell,
|
||||
string,
|
||||
tail,
|
||||
));
|
||||
|
||||
self.push_subterm(lvl.child_level(), tail);
|
||||
self.push_subterm(lvl.child_level(), head);
|
||||
if let Some(tail) = tail {
|
||||
self.push_subterm(lvl.child_level(), tail);
|
||||
}
|
||||
} else {
|
||||
self.state_stack.push(TermIterState::FinalCons(lvl, cell, head, tail));
|
||||
|
||||
self.push_subterm(lvl.child_level(), tail);
|
||||
self.push_subterm(lvl.child_level(), head);
|
||||
}
|
||||
}
|
||||
TermIterState::InitialPartialString(lvl, cell, string, tail) => {
|
||||
self.state_stack
|
||||
.push(TermIterState::FinalPartialString(lvl, cell, string, tail));
|
||||
self.push_subterm(lvl.child_level(), tail);
|
||||
}
|
||||
TermIterState::FinalPartialString(lvl, cell, string, tail) => {
|
||||
TermIterState::PartialString(lvl, cell, string, tail) => {
|
||||
return Some(TermRef::PartialString(lvl, cell, string, tail));
|
||||
}
|
||||
TermIterState::CompleteString(lvl, cell, string) => {
|
||||
return Some(TermRef::CompleteString(lvl, cell, string));
|
||||
}
|
||||
TermIterState::FinalCons(lvl, cell, head, tail) => {
|
||||
return Some(TermRef::Cons(lvl, cell, head, tail));
|
||||
}
|
||||
TermIterState::Literal(lvl, cell, constant) => {
|
||||
return Some(TermRef::Literal(lvl, cell, constant));
|
||||
TermIterState::Constant(lvl, cell, constant) => {
|
||||
return Some(TermRef::Constant(lvl, cell, constant));
|
||||
}
|
||||
TermIterState::Var(lvl, cell, var_ptr) => {
|
||||
return Some(TermRef::Var(lvl, cell, var_ptr));
|
||||
TermIterState::Var(lvl, cell, var) => {
|
||||
return Some(TermRef::Var(lvl, cell, var));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -193,63 +302,48 @@ impl<'a> Iterator for QueryIterator<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FactIterator<'a> {
|
||||
pub struct FactIterator<'a> {
|
||||
state_queue: VecDeque<TermIterState<'a>>,
|
||||
iterable_root: RootIterationPolicy,
|
||||
iterable_root: bool,
|
||||
}
|
||||
|
||||
impl<'a> FactIterator<'a> {
|
||||
fn push_subterm(&mut self, lvl: Level, term: &'a Term) {
|
||||
self.state_queue
|
||||
.push_back(TermIterState::subterm_to_state(lvl, term));
|
||||
self.state_queue.push_back(TermIterState::subterm_to_state(lvl, term));
|
||||
}
|
||||
|
||||
pub(crate) fn from_rule_head_clause(terms: &'a [Term]) -> Self {
|
||||
pub fn from_rule_head_clause(terms: &'a Vec<Box<Term>>) -> Self {
|
||||
let state_queue = terms
|
||||
.iter()
|
||||
.map(|bt| TermIterState::subterm_to_state(Level::Shallow, bt))
|
||||
.map(|bt| TermIterState::subterm_to_state(Level::Shallow, bt.as_ref()))
|
||||
.collect();
|
||||
|
||||
FactIterator {
|
||||
state_queue,
|
||||
iterable_root: RootIterationPolicy::NotIterated,
|
||||
iterable_root: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn new(term: &'a Term, iterable_root: RootIterationPolicy) -> Self {
|
||||
fn new(term: &'a Term, iterable_root: bool) -> Self {
|
||||
let states = match term {
|
||||
Term::AnonVar => {
|
||||
&Term::AnonVar => {
|
||||
vec![TermIterState::AnonVar(Level::Root)]
|
||||
}
|
||||
Term::Clause(cell, name, terms) => {
|
||||
vec![TermIterState::Clause(Level::Root, 0, cell, *name, terms)]
|
||||
&Term::Clause(ref cell, ref name, ref terms, ref fixity) => {
|
||||
let ct = ClauseType::from(name.clone(), terms.len(), fixity.clone());
|
||||
vec![TermIterState::Clause(Level::Root, 0, cell, ct, terms)]
|
||||
}
|
||||
Term::Cons(cell, head, tail) => vec![TermIterState::InitialCons(
|
||||
&Term::Cons(ref cell, ref head, ref tail) => vec![TermIterState::InitialCons(
|
||||
Level::Root,
|
||||
cell,
|
||||
head.as_ref(),
|
||||
tail.as_ref(),
|
||||
)],
|
||||
Term::PartialString(cell, string, tail) => {
|
||||
vec![TermIterState::InitialPartialString(
|
||||
Level::Root,
|
||||
cell,
|
||||
string.clone(),
|
||||
tail,
|
||||
)]
|
||||
&Term::Constant(ref cell, ref constant) => {
|
||||
vec![TermIterState::Constant(Level::Root, cell, constant)]
|
||||
}
|
||||
Term::CompleteString(cell, string) => {
|
||||
vec![TermIterState::CompleteString(
|
||||
Level::Root,
|
||||
cell,
|
||||
string.clone(),
|
||||
)]
|
||||
}
|
||||
Term::Literal(cell, constant) => {
|
||||
vec![TermIterState::Literal(Level::Root, cell, constant)]
|
||||
}
|
||||
Term::Var(cell, var_ptr) => {
|
||||
vec![TermIterState::Var(Level::Root, cell, var_ptr.clone())]
|
||||
&Term::Var(ref cell, ref var) => {
|
||||
vec![TermIterState::Var(Level::Root, cell, var.clone())]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -269,36 +363,38 @@ impl<'a> Iterator for FactIterator<'a> {
|
||||
TermIterState::AnonVar(lvl) => {
|
||||
return Some(TermRef::AnonVar(lvl));
|
||||
}
|
||||
TermIterState::Clause(lvl, _, cell, name, child_terms) => {
|
||||
TermIterState::Clause(lvl, _, cell, ct, child_terms) => {
|
||||
for child_term in child_terms {
|
||||
self.push_subterm(lvl.child_level(), child_term);
|
||||
}
|
||||
|
||||
match lvl {
|
||||
Level::Root if !self.iterable_root.iterable() => continue,
|
||||
_ => return Some(TermRef::Clause(lvl, cell, name, child_terms)),
|
||||
Level::Root if !self.iterable_root => continue,
|
||||
_ => return Some(TermRef::Clause(lvl, cell, ct, child_terms)),
|
||||
};
|
||||
}
|
||||
TermIterState::InitialCons(lvl, cell, head, tail) => {
|
||||
self.push_subterm(Level::Deep, head);
|
||||
self.push_subterm(Level::Deep, tail);
|
||||
if let Some((string, tail)) = is_partial_string(head, tail) {
|
||||
if let Some(tail) = tail {
|
||||
self.push_subterm(Level::Deep, tail);
|
||||
}
|
||||
|
||||
return Some(TermRef::Cons(lvl, cell, head, tail));
|
||||
return Some(TermRef::PartialString(lvl, cell, string, tail));
|
||||
} else {
|
||||
self.push_subterm(Level::Deep, head);
|
||||
self.push_subterm(Level::Deep, tail);
|
||||
|
||||
return Some(TermRef::Cons(lvl, cell, head, tail));
|
||||
}
|
||||
}
|
||||
TermIterState::InitialPartialString(lvl, cell, string_buf, tail) => {
|
||||
self.push_subterm(Level::Deep, tail);
|
||||
return Some(TermRef::PartialString(lvl, cell, string_buf, tail));
|
||||
TermIterState::Constant(lvl, cell, constant) => {
|
||||
return Some(TermRef::Constant(lvl, cell, constant))
|
||||
}
|
||||
TermIterState::CompleteString(lvl, cell, atom) => {
|
||||
return Some(TermRef::CompleteString(lvl, cell, atom));
|
||||
TermIterState::Var(lvl, cell, var) => {
|
||||
return Some(TermRef::Var(lvl, cell, var));
|
||||
}
|
||||
TermIterState::Literal(lvl, cell, constant) => {
|
||||
return Some(TermRef::Literal(lvl, cell, constant));
|
||||
_ => {
|
||||
}
|
||||
TermIterState::Var(lvl, cell, var_ptr) => {
|
||||
return Some(TermRef::Var(lvl, cell, var_ptr));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,164 +402,203 @@ impl<'a> Iterator for FactIterator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn post_order_iter(term: &Term) -> QueryIterator<'_> {
|
||||
pub fn post_order_iter(term: &Term) -> QueryIterator {
|
||||
QueryIterator::from_term(term)
|
||||
}
|
||||
|
||||
pub(crate) fn breadth_first_iter(
|
||||
term: &Term,
|
||||
iterable_root: RootIterationPolicy,
|
||||
) -> FactIterator<'_> {
|
||||
pub fn breadth_first_iter(term: &Term, iterable_root: bool) -> FactIterator {
|
||||
FactIterator::new(term, iterable_root)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum ClauseIteratorState<'a> {
|
||||
RemainingChunks(&'a VecDeque<ChunkedTerms>, usize),
|
||||
RemainingBranches(
|
||||
&'a Vec<Arc<BranchNumber>>,
|
||||
&'a Vec<VecDeque<ChunkedTerms>>,
|
||||
usize,
|
||||
),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum ClauseItem<'a> {
|
||||
FirstBranch {
|
||||
branch_num: &'a Arc<BranchNumber>,
|
||||
num_branches: usize,
|
||||
},
|
||||
NextBranch {
|
||||
branch_num: &'a Arc<BranchNumber>,
|
||||
},
|
||||
BranchEnd {
|
||||
depth: usize,
|
||||
},
|
||||
Chunk {
|
||||
terms: &'a VecDeque<QueryTerm>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ClauseIterator<'a> {
|
||||
state_stack: Vec<ClauseIteratorState<'a>>,
|
||||
remaining_chunks_on_stack: usize,
|
||||
pub enum ChunkedTerm<'a> {
|
||||
HeadClause(ClauseName, &'a Vec<Box<Term>>),
|
||||
BodyTerm(&'a QueryTerm),
|
||||
}
|
||||
|
||||
fn state_from_chunked_terms(chunk_vec: &VecDeque<ChunkedTerms>) -> ClauseIteratorState<'_> {
|
||||
if chunk_vec.len() == 1 {
|
||||
if let Some(ChunkedTerms::Branch { branch_nums, arms }) = chunk_vec.front() {
|
||||
return ClauseIteratorState::RemainingBranches(branch_nums, arms, 0);
|
||||
pub fn query_term_post_order_iter<'a>(query_term: &'a QueryTerm) -> QueryIterator<'a> {
|
||||
QueryIterator::new(query_term)
|
||||
}
|
||||
|
||||
impl<'a> ChunkedTerm<'a> {
|
||||
pub fn post_order_iter(&self) -> QueryIterator<'a> {
|
||||
match self {
|
||||
&ChunkedTerm::BodyTerm(ref qt) => QueryIterator::new(qt),
|
||||
&ChunkedTerm::HeadClause(_, terms) => QueryIterator::from_rule_head_clause(terms),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_cut_var<'a, Iter: Iterator<Item = &'a Term>>(terms: Iter) -> bool {
|
||||
for term in terms {
|
||||
if let &Term::Var(_, ref var) = term {
|
||||
if var.as_str() == "!" {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClauseIteratorState::RemainingChunks(chunk_vec, 0)
|
||||
false
|
||||
}
|
||||
|
||||
impl<'a> ClauseIterator<'a> {
|
||||
pub fn new(clauses: &'a ChunkedTermVec) -> Self {
|
||||
match state_from_chunked_terms(&clauses.chunk_vec) {
|
||||
state @ ClauseIteratorState::RemainingBranches(..) => Self {
|
||||
state_stack: vec![state],
|
||||
remaining_chunks_on_stack: 0,
|
||||
},
|
||||
state @ ClauseIteratorState::RemainingChunks(..) => Self {
|
||||
state_stack: vec![state],
|
||||
remaining_chunks_on_stack: 1,
|
||||
},
|
||||
pub struct ChunkedIterator<'a> {
|
||||
pub chunk_num: usize,
|
||||
iter: Box<dyn Iterator<Item = ChunkedTerm<'a>> + 'a>,
|
||||
deep_cut_encountered: bool,
|
||||
cut_var_in_head: bool,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for ChunkedIterator<'a> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("ChunkedIterator")
|
||||
.field("chunk_num", &self.chunk_num)
|
||||
// Hacky solution.
|
||||
.field("iter", &"Box<dyn Iterator<Item = ChunkedTerm<'a>> + 'a>")
|
||||
.field("deep_cut_encountered", &self.deep_cut_encountered)
|
||||
.field("cut_var_in_head", &self.cut_var_in_head)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
type ChunkedIteratorItem<'a> = (usize, usize, Vec<ChunkedTerm<'a>>);
|
||||
type RuleBodyIteratorItem<'a> = (usize, usize, Vec<&'a QueryTerm>);
|
||||
|
||||
impl<'a> ChunkedIterator<'a> {
|
||||
pub fn rule_body_iter(self) -> Box<dyn Iterator<Item = RuleBodyIteratorItem<'a>> + 'a> {
|
||||
Box::new(self.filter_map(|(cn, lt_arity, terms)| {
|
||||
let filtered_terms: Vec<_> = terms
|
||||
.into_iter()
|
||||
.filter_map(|ct| match ct {
|
||||
ChunkedTerm::BodyTerm(qt) => Some(qt),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if filtered_terms.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((cn, lt_arity, filtered_terms))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn from_term_sequence(terms: &'a [QueryTerm]) -> Self {
|
||||
ChunkedIterator {
|
||||
chunk_num: 0,
|
||||
iter: Box::new(terms.iter().map(|t| ChunkedTerm::BodyTerm(t))),
|
||||
deep_cut_encountered: false,
|
||||
cut_var_in_head: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn in_tail_position(&self) -> bool {
|
||||
self.remaining_chunks_on_stack == 0
|
||||
pub fn from_rule_body(p1: &'a QueryTerm, clauses: &'a Vec<QueryTerm>) -> Self {
|
||||
let inner_iter = Box::new(once(ChunkedTerm::BodyTerm(p1)));
|
||||
let iter = inner_iter.chain(clauses.iter().map(|t| ChunkedTerm::BodyTerm(t)));
|
||||
|
||||
ChunkedIterator {
|
||||
chunk_num: 0,
|
||||
iter: Box::new(iter),
|
||||
deep_cut_encountered: false,
|
||||
cut_var_in_head: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn branch_end_depth(&mut self) -> usize {
|
||||
let mut depth = 1;
|
||||
pub fn from_rule(rule: &'a Rule) -> Self {
|
||||
let &Rule {
|
||||
head: (ref name, ref args, ref p1),
|
||||
ref clauses,
|
||||
} = rule;
|
||||
|
||||
while let Some(state) = self.state_stack.pop() {
|
||||
match state {
|
||||
ClauseIteratorState::RemainingBranches(_branch_nums, terms, focus)
|
||||
if terms.len() == focus =>
|
||||
{
|
||||
depth += 1;
|
||||
let iter = once(ChunkedTerm::HeadClause(name.clone(), args));
|
||||
let inner_iter = Box::new(once(ChunkedTerm::BodyTerm(p1)));
|
||||
let iter = iter.chain(inner_iter.chain(clauses.iter().map(|t| ChunkedTerm::BodyTerm(t))));
|
||||
|
||||
ChunkedIterator {
|
||||
chunk_num: 0,
|
||||
iter: Box::new(iter),
|
||||
deep_cut_encountered: false,
|
||||
cut_var_in_head: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encountered_deep_cut(&self) -> bool {
|
||||
self.deep_cut_encountered
|
||||
}
|
||||
|
||||
fn take_chunk(&mut self, term: ChunkedTerm<'a>) -> (usize, usize, Vec<ChunkedTerm<'a>>) {
|
||||
let mut arity = 0;
|
||||
let mut item = Some(term);
|
||||
let mut result = Vec::new();
|
||||
|
||||
while let Some(term) = item {
|
||||
match term {
|
||||
ChunkedTerm::HeadClause(_, terms) => {
|
||||
if contains_cut_var(terms.iter().map(|t| t.as_ref())) {
|
||||
self.cut_var_in_head = true;
|
||||
}
|
||||
|
||||
result.push(term);
|
||||
}
|
||||
_ => {
|
||||
self.state_stack.push(state);
|
||||
ChunkedTerm::BodyTerm(&QueryTerm::Jump(ref vars)) => {
|
||||
result.push(term);
|
||||
arity = vars.len();
|
||||
|
||||
if contains_cut_var(vars.iter()) && !self.cut_var_in_head {
|
||||
self.deep_cut_encountered = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
ChunkedTerm::BodyTerm(&QueryTerm::BlockedCut) => {
|
||||
result.push(term);
|
||||
|
||||
if self.chunk_num > 0 {
|
||||
self.deep_cut_encountered = true;
|
||||
}
|
||||
}
|
||||
ChunkedTerm::BodyTerm(&QueryTerm::GetLevelAndUnify(..)) => {
|
||||
self.deep_cut_encountered = true;
|
||||
|
||||
result.push(term);
|
||||
arity = 1;
|
||||
break;
|
||||
}
|
||||
ChunkedTerm::BodyTerm(&QueryTerm::UnblockedCut(..)) => result.push(term),
|
||||
ChunkedTerm::BodyTerm(&QueryTerm::Clause(_, ClauseType::Inlined(_), ..)) => {
|
||||
result.push(term)
|
||||
}
|
||||
ChunkedTerm::BodyTerm(&QueryTerm::Clause(
|
||||
_,
|
||||
ClauseType::CallN,
|
||||
ref subterms,
|
||||
_,
|
||||
)) => {
|
||||
result.push(term);
|
||||
arity = subterms.len() + 1;
|
||||
break;
|
||||
}
|
||||
ChunkedTerm::BodyTerm(qt) => {
|
||||
result.push(term);
|
||||
arity = qt.arity();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
item = self.iter.next();
|
||||
}
|
||||
|
||||
depth
|
||||
let chunk_num = self.chunk_num;
|
||||
self.chunk_num += 1;
|
||||
|
||||
(chunk_num, arity, result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ClauseIterator<'a> {
|
||||
type Item = ClauseItem<'a>;
|
||||
impl<'a> Iterator for ChunkedIterator<'a> {
|
||||
// the chunk number, last term arity, and vector of references.
|
||||
type Item = ChunkedIteratorItem<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(state) = self.state_stack.pop() {
|
||||
match state {
|
||||
ClauseIteratorState::RemainingChunks(chunks, focus) if focus < chunks.len() => {
|
||||
if focus + 1 < chunks.len() {
|
||||
self.state_stack
|
||||
.push(ClauseIteratorState::RemainingChunks(chunks, focus + 1));
|
||||
} else {
|
||||
self.remaining_chunks_on_stack -= 1;
|
||||
}
|
||||
|
||||
match &chunks[focus] {
|
||||
ChunkedTerms::Branch { branch_nums, arms } => {
|
||||
self.state_stack
|
||||
.push(ClauseIteratorState::RemainingBranches(branch_nums, arms, 0));
|
||||
}
|
||||
ChunkedTerms::Chunk { terms } => {
|
||||
return Some(ClauseItem::Chunk { terms });
|
||||
}
|
||||
}
|
||||
}
|
||||
ClauseIteratorState::RemainingChunks(chunks, focus) => {
|
||||
debug_assert_eq!(chunks.len(), focus);
|
||||
}
|
||||
ClauseIteratorState::RemainingBranches(branch_nums, branches, focus)
|
||||
if focus < branches.len() =>
|
||||
{
|
||||
self.state_stack
|
||||
.push(ClauseIteratorState::RemainingBranches(
|
||||
branch_nums,
|
||||
branches,
|
||||
focus + 1,
|
||||
));
|
||||
let state = state_from_chunked_terms(&branches[focus]);
|
||||
|
||||
if let ClauseIteratorState::RemainingChunks(..) = &state {
|
||||
self.remaining_chunks_on_stack += 1;
|
||||
}
|
||||
|
||||
self.state_stack.push(state);
|
||||
|
||||
return if focus == 0 {
|
||||
Some(ClauseItem::FirstBranch {
|
||||
branch_num: &branch_nums[0],
|
||||
num_branches: branches.len(),
|
||||
})
|
||||
} else {
|
||||
Some(ClauseItem::NextBranch {
|
||||
branch_num: &branch_nums[focus],
|
||||
})
|
||||
};
|
||||
}
|
||||
ClauseIteratorState::RemainingBranches(_branch_nums, branches, focus) => {
|
||||
debug_assert_eq!(branches.len(), focus);
|
||||
return Some(ClauseItem::BranchEnd {
|
||||
depth: self.branch_end_depth(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
self.iter.next().map(|term| self.take_chunk(term))
|
||||
}
|
||||
}
|
||||
|
||||
85
src/lib.rs
85
src/lib.rs
@@ -1,85 +0,0 @@
|
||||
//! A free software ISO Prolog system.
|
||||
#![recursion_limit = "4112"]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate static_assertions;
|
||||
|
||||
#[macro_use]
|
||||
pub(crate) mod macros;
|
||||
#[macro_use]
|
||||
pub(crate) mod atom_table;
|
||||
#[macro_use]
|
||||
pub(crate) mod arena;
|
||||
pub(crate) mod offset_table;
|
||||
#[macro_use]
|
||||
pub(crate) mod parser;
|
||||
#[macro_use]
|
||||
pub(crate) mod functor_macro;
|
||||
mod allocator;
|
||||
mod arithmetic;
|
||||
pub(crate) mod codegen;
|
||||
mod debray_allocator;
|
||||
#[cfg(feature = "ffi")]
|
||||
mod ffi;
|
||||
mod forms;
|
||||
mod heap_iter;
|
||||
pub(crate) mod heap_print;
|
||||
#[cfg(feature = "http")]
|
||||
mod http;
|
||||
mod indexing;
|
||||
mod variable_records;
|
||||
|
||||
#[macro_use]
|
||||
pub(crate) mod instructions;
|
||||
|
||||
mod iterators;
|
||||
pub(crate) mod machine;
|
||||
mod raw_block;
|
||||
pub(crate) mod read;
|
||||
#[cfg(feature = "repl")]
|
||||
mod repl_helper;
|
||||
mod targets;
|
||||
pub(crate) mod types;
|
||||
|
||||
// Re-exports
|
||||
pub use machine::Machine;
|
||||
pub use machine::config::*;
|
||||
pub use machine::lib_machine::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod wasm;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
/// The entry point for the Scryer Prolog CLI.
|
||||
pub fn run_binary() -> std::process::ExitCode {
|
||||
use crate::atom_table::Atom;
|
||||
|
||||
#[cfg(feature = "repl")]
|
||||
use crate::machine::INTERRUPT;
|
||||
|
||||
#[cfg(feature = "repl")]
|
||||
ctrlc::set_handler(move || {
|
||||
INTERRUPT.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
runtime.block_on(async move {
|
||||
let mut wam = MachineBuilder::default()
|
||||
.with_streams(StreamConfig::stdio())
|
||||
.build();
|
||||
wam.run_module_predicate(atom!("$toplevel"), (atom!("$repl"), 0))
|
||||
})
|
||||
}
|
||||
@@ -1,20 +1,11 @@
|
||||
/** Arithmetic predicates
|
||||
|
||||
These predicates are additions to standard the arithmetic functions provided by `is/2`.
|
||||
*/
|
||||
|
||||
:- module(arithmetic, [expmod/4, lcm/3, lsb/2, msb/2, number_to_rational/2,
|
||||
number_to_rational/3, popcount/2,
|
||||
:- module(arithmetic, [expmod/4, lsb/2, msb/2, number_to_rational/2,
|
||||
number_to_rational/3,
|
||||
rational_numerator_denominator/3]).
|
||||
|
||||
:- use_module(library(charsio), [write_term_to_chars/3]).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(lists), [append/3, member/2]).
|
||||
|
||||
|
||||
%% expmod(+Base, +Expo, +Mod, -R).
|
||||
%
|
||||
% Modular exponentiation. Base, Expo and Mod must be integers.
|
||||
expmod(Base, Expo, Mod, R) :-
|
||||
( member(N, [Base, Expo, Mod]), var(N) -> instantiation_error(expmod/4)
|
||||
; member(N, [Base, Expo, Mod]), \+ integer(N) ->
|
||||
@@ -37,25 +28,6 @@ expmod_(Base0, Expo0, Mod, C, R) :-
|
||||
Base is (Base0 * Base0) mod Mod,
|
||||
expmod_(Base, Expo, Mod, C, R).
|
||||
|
||||
%% lcm(+A, +B, -Lcm) is det.
|
||||
%
|
||||
% Calculates the Least common multiple for A and B: the smallest positive integer
|
||||
% that is divisible by both A and B.
|
||||
%
|
||||
% A and B need to be integers.
|
||||
lcm(A, B, X) :-
|
||||
builtins:must_be_number(A, lcm/2),
|
||||
builtins:must_be_number(B, lcm/2),
|
||||
( \+ integer(A) -> type_error(integer, A, lcm/2)
|
||||
; \+ integer(B) -> type_error(integer, B, lcm/2)
|
||||
; (A = 0, B = 0) -> X = 0
|
||||
; builtins:can_be_number(X, lcm/2),
|
||||
X is abs(B) // gcd(A,B) * abs(A)
|
||||
).
|
||||
|
||||
%% lsb(+X, -N).
|
||||
%
|
||||
% True iff N is the least significat bit of integer X
|
||||
lsb(X, N) :-
|
||||
builtins:must_be_number(X, lsb/2),
|
||||
( \+ integer(X) -> type_error(integer, X, lsb/2)
|
||||
@@ -65,9 +37,6 @@ lsb(X, N) :-
|
||||
msb_(X1, -1, N)
|
||||
).
|
||||
|
||||
%% msb(+X, -N).
|
||||
%
|
||||
% True iff N is the most significant bit of integer X
|
||||
msb(X, N) :-
|
||||
builtins:must_be_number(X, msb/2),
|
||||
( \+ integer(X) -> type_error(integer, X, msb/2)
|
||||
@@ -83,9 +52,6 @@ msb_(X, M, N) :-
|
||||
M1 is M + 1,
|
||||
msb_(X1, M1, N).
|
||||
|
||||
%% number_to_rational(+Real, -Fraction).
|
||||
%
|
||||
% True iff given a number Real, Fraction is the same number represented as a fraction.
|
||||
number_to_rational(Real, Fraction) :-
|
||||
( var(Real) -> instantiation_error(number_to_rational/2)
|
||||
; integer(Real) -> Fraction is Real rdiv 1
|
||||
@@ -128,6 +94,12 @@ number_to_rational(Eps0, Real0, Fraction) :-
|
||||
),
|
||||
!.
|
||||
|
||||
number(X) :-
|
||||
( integer(X)
|
||||
; float(X)
|
||||
; rational(X)
|
||||
).
|
||||
|
||||
stern_brocot_(Qnn/Qnd, Qpn/Qpd, A/B, C/D, Fraction) :-
|
||||
Fn1 is A + C,
|
||||
Fd1 is B + D,
|
||||
@@ -144,20 +116,8 @@ simplify_fraction(A0/B0, A/B) :-
|
||||
A is A0 // G,
|
||||
B is B0 // G.
|
||||
|
||||
%% rational_numerator_denominator(+Fraction, -Numerator, -Denominator).
|
||||
%
|
||||
% True iff given a fraction Fraction, Numerator is the numerator of that fraction
|
||||
% and Denominator the denominator.
|
||||
rational_numerator_denominator(R, N, D) :-
|
||||
write_term_to_chars(R, [], Cs),
|
||||
append(Ns, [' ', r, d, i, v, ' '|Ds], Cs),
|
||||
number_chars(N, Ns),
|
||||
number_chars(D, Ds).
|
||||
|
||||
%% popcount(+Number, -Bits1).
|
||||
%
|
||||
% True iff given an integer Number, Bits1 is the amount of 1 bits the binary representation
|
||||
% of that number has.
|
||||
popcount(X, N) :-
|
||||
must_be(integer, X),
|
||||
'$popcount'(X, N).
|
||||
|
||||
132
src/lib/assoc.pl
132
src/lib/assoc.pl
@@ -54,27 +54,31 @@
|
||||
|
||||
:- use_module(library(lists)).
|
||||
|
||||
/** Binary associations
|
||||
/** <module> Binary associations
|
||||
|
||||
Assocs are Key-Value associations implemented as a balanced binary tree
|
||||
(AVL tree).
|
||||
|
||||
Authors: R.A.O'Keefe, L.Damas, V.S.Costa and Jan Wielemaker
|
||||
@see library(pairs), library(rbtrees)
|
||||
@author R.A.O'Keefe, L.Damas, V.S.Costa and Jan Wielemaker
|
||||
*/
|
||||
|
||||
:- meta_predicate(map_assoc(1, ?)).
|
||||
:- meta_predicate(map_assoc(2, ?, ?)).
|
||||
/*
|
||||
:- meta_predicate
|
||||
map_assoc(1, ?),
|
||||
map_assoc(2, ?, ?).
|
||||
*/
|
||||
|
||||
%% empty_assoc(?Assoc) is semidet.
|
||||
%! empty_assoc(?Assoc) is semidet.
|
||||
%
|
||||
% Is true if Assoc is the empty association list.
|
||||
% Is true if Assoc is the empty association list.
|
||||
|
||||
empty_assoc(t).
|
||||
|
||||
%% assoc_to_list(+Assoc, -Pairs) is det.
|
||||
%! assoc_to_list(+Assoc, -Pairs) is det.
|
||||
%
|
||||
% Translate Assoc to a list Pairs of Key-Value pairs. The keys
|
||||
% in Pairs are sorted in ascending order.
|
||||
% Translate Assoc to a list Pairs of Key-Value pairs. The keys
|
||||
% in Pairs are sorted in ascending order.
|
||||
|
||||
assoc_to_list(Assoc, List) :-
|
||||
assoc_to_list(Assoc, List, []).
|
||||
@@ -85,10 +89,10 @@ assoc_to_list(t(Key,Val,_,L,R), List, Rest) :-
|
||||
assoc_to_list(t, List, List).
|
||||
|
||||
|
||||
%% assoc_to_keys(+Assoc, -Keys) is det.
|
||||
%! assoc_to_keys(+Assoc, -Keys) is det.
|
||||
%
|
||||
% True if Keys is the list of keys in Assoc. The keys are sorted
|
||||
% in ascending order.
|
||||
% True if Keys is the list of keys in Assoc. The keys are sorted
|
||||
% in ascending order.
|
||||
|
||||
assoc_to_keys(Assoc, List) :-
|
||||
assoc_to_keys(Assoc, List, []).
|
||||
@@ -99,11 +103,11 @@ assoc_to_keys(t(Key,_,_,L,R), List, Rest) :-
|
||||
assoc_to_keys(t, List, List).
|
||||
|
||||
|
||||
%% assoc_to_values(+Assoc, -Values) is det.
|
||||
%! assoc_to_values(+Assoc, -Values) is det.
|
||||
%
|
||||
% True if Values is the list of values in Assoc. Values are
|
||||
% ordered in ascending order of the key to which they were
|
||||
% associated. Values may contain duplicates.
|
||||
% True if Values is the list of values in Assoc. Values are
|
||||
% ordered in ascending order of the key to which they were
|
||||
% associated. Values may contain duplicates.
|
||||
|
||||
assoc_to_values(Assoc, List) :-
|
||||
assoc_to_values(Assoc, List, []).
|
||||
@@ -113,12 +117,12 @@ assoc_to_values(t(_,Value,_,L,R), List, Rest) :-
|
||||
assoc_to_values(R, More, Rest).
|
||||
assoc_to_values(t, List, List).
|
||||
|
||||
%% is_assoc(+Assoc) is semidet.
|
||||
%! is_assoc(+Assoc) is semidet.
|
||||
%
|
||||
% True if Assoc is an association list. This predicate checks
|
||||
% that the structure is valid, elements are in order, and tree
|
||||
% is balanced to the extent guaranteed by AVL trees. I.e.,
|
||||
% branches of each subtree differ in depth by at most 1.
|
||||
% True if Assoc is an association list. This predicate checks
|
||||
% that the structure is valid, elements are in order, and tree
|
||||
% is balanced to the extent guaranteed by AVL trees. I.e.,
|
||||
% branches of each subtree differ in depth by at most 1.
|
||||
|
||||
is_assoc(Assoc) :-
|
||||
is_assoc(Assoc, _Min, _Max, _Depth).
|
||||
@@ -150,10 +154,12 @@ balance(=,-).
|
||||
balance(<,<).
|
||||
balance(>,>).
|
||||
|
||||
%% gen_assoc(?Key, +Assoc, ?Value) is nondet.
|
||||
%! gen_assoc(?Key, +Assoc, ?Value) is nondet.
|
||||
%
|
||||
% True if Key-Value is an association in Assoc. Enumerates keys in
|
||||
% ascending order on backtracking.
|
||||
% True if Key-Value is an association in Assoc. Enumerates keys in
|
||||
% ascending order on backtracking.
|
||||
%
|
||||
% @see get_assoc/3.
|
||||
|
||||
gen_assoc(Key, Assoc, Value) :-
|
||||
( ground(Key)
|
||||
@@ -168,11 +174,11 @@ gen_assoc_(Key, t(_,_,_,_,R), Val) :-
|
||||
gen_assoc_(Key, R, Val).
|
||||
|
||||
|
||||
%% get_assoc(+Key, +Assoc, -Value) is semidet.
|
||||
%! get_assoc(+Key, +Assoc, -Value) is semidet.
|
||||
%
|
||||
% True if Key-Value is an association in Assoc.
|
||||
% True if Key-Value is an association in Assoc.
|
||||
%
|
||||
% Throws error: `type_error(assoc, Assoc)` if Assoc is not an association list.
|
||||
% @error type_error(assoc, Assoc) if Assoc is not an association list.
|
||||
|
||||
get_assoc(Key, Assoc, Val) :-
|
||||
must_be(assoc, Assoc),
|
||||
@@ -198,9 +204,9 @@ get_assoc(>, Key, _, _, Tree, Val) :-
|
||||
% :- endif.
|
||||
|
||||
|
||||
%% get_assoc(+Key, +Assoc0, ?Val0, ?Assoc, ?Val) is semidet.
|
||||
%! get_assoc(+Key, +Assoc0, ?Val0, ?Assoc, ?Val) is semidet.
|
||||
%
|
||||
% True if Key-Val0 is in Assoc0 and Key-Val is in Assoc.
|
||||
% True if Key-Val0 is in Assoc0 and Key-Val is in Assoc.
|
||||
|
||||
get_assoc(Key, t(K,V,B,L,R), Val, t(K,NV,B,NL,NR), NVal) :-
|
||||
compare(Rel, Key, K),
|
||||
@@ -213,12 +219,12 @@ get_assoc(>, Key, V, L, R, Val, V, L, NR, NVal) :-
|
||||
get_assoc(Key, R, Val, NR, NVal).
|
||||
|
||||
|
||||
%% list_to_assoc(+Pairs, -Assoc) is det.
|
||||
%! list_to_assoc(+Pairs, -Assoc) is det.
|
||||
%
|
||||
% Create an association from a list Pairs of Key-Value pairs. List
|
||||
% must not contain duplicate keys.
|
||||
% Create an association from a list Pairs of Key-Value pairs. List
|
||||
% must not contain duplicate keys.
|
||||
%
|
||||
% Throws error: `domain_error(unique_key_pairs, List)` if List contains duplicate keys
|
||||
% @error domain_error(unique_key_pairs, List) if List contains duplicate keys
|
||||
|
||||
list_to_assoc(List, Assoc) :-
|
||||
( List = [] -> Assoc = t
|
||||
@@ -243,13 +249,13 @@ list_to_assoc(N, List, More, Depth, t(K,V,Balance,L,R)) :-
|
||||
compare(B, RDepth, LDepth),
|
||||
balance(B, Balance).
|
||||
|
||||
%% ord_list_to_assoc(+Pairs, -Assoc) is det.
|
||||
%! ord_list_to_assoc(+Pairs, -Assoc) is det.
|
||||
%
|
||||
% Assoc is created from an ordered list Pairs of Key-Value
|
||||
% pairs. The pairs must occur in strictly ascending order of
|
||||
% their keys.
|
||||
% Assoc is created from an ordered list Pairs of Key-Value
|
||||
% pairs. The pairs must occur in strictly ascending order of
|
||||
% their keys.
|
||||
%
|
||||
% Throws error: `domain_error(key_ordered_pairs, List)` if pairs are not ordered.
|
||||
% @error domain_error(key_ordered_pairs, List) if pairs are not ordered.
|
||||
|
||||
ord_list_to_assoc(Sorted, Assoc) :-
|
||||
( Sorted = [] -> Assoc = t
|
||||
@@ -260,9 +266,9 @@ ord_list_to_assoc(Sorted, Assoc) :-
|
||||
)
|
||||
).
|
||||
|
||||
%% ord_pairs(+Pairs) is semidet
|
||||
%! ord_pairs(+Pairs) is semidet
|
||||
%
|
||||
% True if Pairs is a list of Key-Val pairs strictly ordered by key.
|
||||
% True if Pairs is a list of Key-Val pairs strictly ordered by key.
|
||||
|
||||
ord_pairs([K-_V|Rest]) :-
|
||||
ord_pairs(Rest, K).
|
||||
@@ -271,9 +277,9 @@ ord_pairs([K-_V|Rest], K0) :-
|
||||
K0 @< K,
|
||||
ord_pairs(Rest, K).
|
||||
|
||||
%% map_assoc(:Pred, +Assoc) is semidet.
|
||||
%! map_assoc(:Pred, +Assoc) is semidet.
|
||||
%
|
||||
% True if Pred(Value) is true for all values in Assoc.
|
||||
% True if Pred(Value) is true for all values in Assoc.
|
||||
|
||||
map_assoc(Pred, T) :-
|
||||
map_assoc_(T, Pred).
|
||||
@@ -284,10 +290,10 @@ map_assoc_(t(_,Val,_,L,R), Pred) :-
|
||||
call(Pred, Val),
|
||||
map_assoc_(R, Pred).
|
||||
|
||||
%% map_assoc(:Pred, +Assoc0, ?Assoc) is semidet.
|
||||
%! map_assoc(:Pred, +Assoc0, ?Assoc) is semidet.
|
||||
%
|
||||
% Map corresponding values. True if Assoc is Assoc0 with Pred
|
||||
% applied to all corresponding pairs of of values.
|
||||
% Map corresponding values. True if Assoc is Assoc0 with Pred
|
||||
% applied to all corresponding pairs of of values.
|
||||
|
||||
map_assoc(Pred, T0, T) :-
|
||||
map_assoc_(T0, Pred, T).
|
||||
@@ -299,9 +305,9 @@ map_assoc_(t(Key,Val,B,L0,R0), Pred, t(Key,Ans,B,L1,R1)) :-
|
||||
map_assoc_(R0, Pred, R1).
|
||||
|
||||
|
||||
%% max_assoc(+Assoc, -Key, -Value) is semidet.
|
||||
%! max_assoc(+Assoc, -Key, -Value) is semidet.
|
||||
%
|
||||
% True if Key-Value is in Assoc and Key is the largest key.
|
||||
% True if Key-Value is in Assoc and Key is the largest key.
|
||||
|
||||
max_assoc(t(K,V,_,_,R), Key, Val) :-
|
||||
max_assoc(R, K, V, Key, Val).
|
||||
@@ -311,9 +317,9 @@ max_assoc(t(K,V,_,_,R), _, _, Key, Val) :-
|
||||
max_assoc(R, K, V, Key, Val).
|
||||
|
||||
|
||||
%% min_assoc(+Assoc, -Key, -Value) is semidet.
|
||||
%! min_assoc(+Assoc, -Key, -Value) is semidet.
|
||||
%
|
||||
% True if Key-Value is in assoc and Key is the smallest key.
|
||||
% True if Key-Value is in assoc and Key is the smallest key.
|
||||
|
||||
min_assoc(t(K,V,_,L,_), Key, Val) :-
|
||||
min_assoc(L, K, V, Key, Val).
|
||||
@@ -323,10 +329,10 @@ min_assoc(t(K,V,_,L,_), _, _, Key, Val) :-
|
||||
min_assoc(L, K, V, Key, Val).
|
||||
|
||||
|
||||
%% put_assoc(+Key, +Assoc0, +Value, -Assoc) is det.
|
||||
%! put_assoc(+Key, +Assoc0, +Value, -Assoc) is det.
|
||||
%
|
||||
% Assoc is Assoc0, except that Key is associated with
|
||||
% Value. This can be used to insert and change associations.
|
||||
% Assoc is Assoc0, except that Key is associated with
|
||||
% Value. This can be used to insert and change associations.
|
||||
|
||||
put_assoc(Key, A0, Value, A) :-
|
||||
insert(A0, Key, Value, A, _).
|
||||
@@ -358,11 +364,11 @@ table(< , right , - , no , no ) :- !.
|
||||
table(> , left , - , no , no ) :- !.
|
||||
table(> , right , - , no , yes ) :- !.
|
||||
|
||||
%% del_min_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
|
||||
%! del_min_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
|
||||
%
|
||||
% True if Key-Value is in Assoc0 and Key is the smallest key.
|
||||
% Assoc is Assoc0 with Key-Value removed. Warning: This will
|
||||
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
|
||||
% True if Key-Value is in Assoc0 and Key is the smallest key.
|
||||
% Assoc is Assoc0 with Key-Value removed. Warning: This will
|
||||
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
|
||||
|
||||
del_min_assoc(Tree, Key, Val, NewTree) :-
|
||||
del_min_assoc(Tree, Key, Val, NewTree, _DepthChanged).
|
||||
@@ -372,11 +378,11 @@ del_min_assoc(t(K,V,B,L,R), Key, Val, NewTree, Changed) :-
|
||||
del_min_assoc(L, Key, Val, NewL, LeftChanged),
|
||||
deladjust(LeftChanged, t(K,V,B,NewL,R), left, NewTree, Changed).
|
||||
|
||||
%% del_max_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
|
||||
%! del_max_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
|
||||
%
|
||||
% True if Key-Value is in Assoc0 and Key is the greatest key.
|
||||
% Assoc is Assoc0 with Key-Value removed. Warning: This will
|
||||
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
|
||||
% True if Key-Value is in Assoc0 and Key is the greatest key.
|
||||
% Assoc is Assoc0 with Key-Value removed. Warning: This will
|
||||
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
|
||||
|
||||
del_max_assoc(Tree, Key, Val, NewTree) :-
|
||||
del_max_assoc(Tree, Key, Val, NewTree, _DepthChanged).
|
||||
@@ -386,10 +392,10 @@ del_max_assoc(t(K,V,B,L,R), Key, Val, NewTree, Changed) :-
|
||||
del_max_assoc(R, Key, Val, NewR, RightChanged),
|
||||
deladjust(RightChanged, t(K,V,B,L,NewR), right, NewTree, Changed).
|
||||
|
||||
%% del_assoc(+Key, +Assoc0, ?Value, -Assoc) is semidet.
|
||||
%! del_assoc(+Key, +Assoc0, ?Value, -Assoc) is semidet.
|
||||
%
|
||||
% True if Key-Value is in Assoc0. Assoc is Assoc0 with
|
||||
% Key-Value removed.
|
||||
% True if Key-Value is in Assoc0. Assoc is Assoc0 with
|
||||
% Key-Value removed.
|
||||
|
||||
del_assoc(Key, A0, Value, A) :-
|
||||
delete(A0, Key, Value, A, _).
|
||||
|
||||
187
src/lib/atts.pl
187
src/lib/atts.pl
@@ -1,8 +1,12 @@
|
||||
:- module(atts, [op(1199, fx, attribute),
|
||||
term_attributed_variables/2]).
|
||||
:- module(atts, [op(1199, fx, attribute), call_residue_vars/2,
|
||||
term_attributed_variables/2,
|
||||
'$absent_attr'/2, '$copy_attr_list'/2, '$get_attr'/2,
|
||||
'$put_attr'/2, '$absent_from_list'/2,
|
||||
'$get_from_list'/3, '$add_to_list'/3, '$del_attr'/3,
|
||||
'$del_attr_step'/3, '$del_attr_buried'/4,
|
||||
'$default_attr_list'/4]).
|
||||
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(terms)).
|
||||
|
||||
/* represent the list of attributes belonging to a variable,
|
||||
@@ -15,93 +19,135 @@
|
||||
).
|
||||
|
||||
'$default_attr_list'([PG | PGs], Module, AttrVar) -->
|
||||
[Module:put_atts(AttrVar, PG)],
|
||||
( { '$module_of'(Module, PG) } -> [Module:put_atts(AttrVar, PG)]
|
||||
; { true }
|
||||
),
|
||||
'$default_attr_list'(PGs, Module, AttrVar).
|
||||
'$default_attr_list'([], _, _) --> [].
|
||||
|
||||
'$absent_attr'(V, Module, Attr) :-
|
||||
( '$get_from_attr_list'(V, Module, Attr) ->
|
||||
false
|
||||
; true
|
||||
'$absent_attr'(V, Attr) :-
|
||||
'$get_attr_list'(V, Ls),
|
||||
'$absent_from_list'(Ls, Attr).
|
||||
|
||||
'$absent_from_list'(X, Attr) :-
|
||||
( var(X) -> true
|
||||
; X = [L|Ls], L \= Attr -> '$absent_from_list'(Ls, Attr)
|
||||
).
|
||||
|
||||
'$copy_attr_list'(L, _Module, []) :- var(L), !.
|
||||
'$copy_attr_list'([Module0:Att|Atts], Module, CopiedAtts) :-
|
||||
( Module0 == Module ->
|
||||
CopiedAtts = [Att|CopiedAtts0],
|
||||
'$copy_attr_list'(Atts, Module, CopiedAtts0)
|
||||
; '$copy_attr_list'(Atts, Module, CopiedAtts)
|
||||
'$get_attr'(V, Attr) :-
|
||||
'$get_attr_list'(V, Ls), nonvar(Ls), '$get_from_list'(Ls, V, Attr).
|
||||
|
||||
'$get_from_list'([L|Ls], V, Attr) :-
|
||||
nonvar(L),
|
||||
( L \= Attr -> nonvar(Ls), '$get_from_list'(Ls, V, Attr)
|
||||
; L = Attr, '$enqueue_attr_var'(V)
|
||||
).
|
||||
|
||||
'$put_attr'(V, Attr) :-
|
||||
'$get_attr_list'(V, Ls), '$add_to_list'(Ls, V, Attr).
|
||||
|
||||
'$add_to_list'(Ls, V, Attr) :-
|
||||
( var(Ls) ->
|
||||
Ls = [Attr | _], '$enqueue_attr_var'(V)
|
||||
; Ls = [_ | Ls0], '$add_to_list'(Ls0, V, Attr)
|
||||
).
|
||||
|
||||
'$del_attr'(Ls0, _, _) :-
|
||||
var(Ls0), !.
|
||||
'$del_attr'(Ls0, V, Attr) :-
|
||||
Ls0 = [Att | Ls1],
|
||||
nonvar(Att),
|
||||
( Att \= Attr ->
|
||||
'$del_attr_buried'(Ls0, Ls1, V, Attr)
|
||||
; '$enqueue_attr_var'(V),
|
||||
'$del_attr_head'(V),
|
||||
'$del_attr'(Ls1, V, Attr)
|
||||
).
|
||||
|
||||
'$del_attr_step'(Ls1, V, Attr) :-
|
||||
( nonvar(Ls1) -> Ls1 = [_ | Ls2], '$del_attr_buried'(Ls1, Ls2, V, Attr)
|
||||
; true ).
|
||||
|
||||
%% assumptions: Ls0 is a list, Ls1 is its tail;
|
||||
%% the head of Ls0 can be ignored.
|
||||
'$del_attr_buried'(Ls0, Ls1, V, Attr) :-
|
||||
( var(Ls1) -> true
|
||||
; Ls1 = [Att | Ls2] ->
|
||||
( Att \= Attr -> '$del_attr_buried'(Ls1, Ls2, V, Attr)
|
||||
; '$enqueue_attr_var'(V),
|
||||
'$del_attr_non_head'(Ls0), %% set tail of Ls0 = tail of Ls1. can be undone by backtracking.
|
||||
'$del_attr_step'(Ls1, V, Attr)
|
||||
)
|
||||
).
|
||||
|
||||
'$copy_attr_list'(L, []) :- var(L), !.
|
||||
'$copy_attr_list'([Att|Atts], [Att|CopiedAtts]) :-
|
||||
'$copy_attr_list'(Atts, CopiedAtts).
|
||||
|
||||
user:term_expansion(Term0, Terms) :-
|
||||
nonvar(Term0),
|
||||
Term0 = (:- attribute Atts),
|
||||
nonvar(Atts),
|
||||
prolog_load_context(module, Module),
|
||||
phrase(expand_terms(Atts, Module), Terms).
|
||||
phrase(expand_terms(Atts), Terms).
|
||||
|
||||
expand_terms(Atts, Module) -->
|
||||
expand_terms(Atts) -->
|
||||
put_attrs_var_check,
|
||||
put_attrs(Atts, Module),
|
||||
get_attrs_var_check(Module),
|
||||
get_attrs(Atts, Module).
|
||||
put_attrs(Atts),
|
||||
get_attrs_var_check,
|
||||
get_attrs(Atts).
|
||||
|
||||
put_attrs_var_check -->
|
||||
[(put_atts(Var, Attr) :- nonvar(Var),
|
||||
throw(error(uninstantiation_error(Var), put_atts/2))),
|
||||
(put_atts(Var, Attr) :- var(Attr),
|
||||
throw(error(instantiation_error, put_atts/2)))].
|
||||
{ numbervars([Var, Attr], 0, _) },
|
||||
[(put_atts(Var, Attr) :- nonvar(Var), throw(error(type_error(variable, Var), put_atts/2))),
|
||||
(put_atts(Var, Attr) :- var(Attr), throw(error(instantiation_error, put_atts/2)))].
|
||||
|
||||
get_attrs_var_check(Module) -->
|
||||
[(get_atts(Var, Attr) :- nonvar(Var),
|
||||
throw(error(uninstantiation_error(Var), get_atts/2))),
|
||||
(get_atts(Var, Attr) :- var(Attr),
|
||||
!,
|
||||
'$get_attr_list'(Var, Ls),
|
||||
nonvar(Ls),
|
||||
atts:'$copy_attr_list'(Ls, Module, Attr))].
|
||||
get_attrs_var_check -->
|
||||
{ numbervars([Var, Ls, Attr], 0, _) },
|
||||
[(get_atts(Var, Attr) :- nonvar(Var), throw(error(type_error(variable, Var), get_atts/2))),
|
||||
(get_atts(Var, Attr) :- var(Attr), !, '$get_attr_list'(Var, Ls), nonvar(Ls),
|
||||
'$copy_attr_list'(Ls, Attr))].
|
||||
|
||||
put_attrs(Name/Arity, Module) -->
|
||||
put_attr(Name, Arity, Module),
|
||||
[(put_atts(Var, Attr) :- lists:maplist(Module:put_atts(Var), Attr), !)].
|
||||
put_attrs((Name/Arity, Atts), Module) -->
|
||||
put_attrs(Name/Arity) -->
|
||||
put_attr(Name, Arity),
|
||||
{ numbervars([Var, Attr], 0, _) },
|
||||
[(put_atts(Var, Attr) :- lists:maplist(put_atts(Var), Attr), !)].
|
||||
put_attrs((Name/Arity, Atts)) -->
|
||||
{ nonvar(Atts) },
|
||||
put_attr(Name, Arity, Module),
|
||||
put_attrs(Atts, Module).
|
||||
put_attr(Name, Arity),
|
||||
put_attrs(Atts).
|
||||
|
||||
get_attrs(Name/Arity, Module) -->
|
||||
get_attr(Name, Arity, Module).
|
||||
get_attrs((Name/Arity, Atts), Module) -->
|
||||
get_attrs(Name/Arity) -->
|
||||
get_attr(Name, Arity).
|
||||
get_attrs((Name/Arity, Atts)) -->
|
||||
{ nonvar(Atts) },
|
||||
get_attr(Name, Arity, Module),
|
||||
get_attrs(Atts, Module).
|
||||
get_attr(Name, Arity),
|
||||
get_attrs(Atts).
|
||||
|
||||
put_attr(Name, Arity, Module) -->
|
||||
{ functor(Attr, Name, Arity) },
|
||||
[(put_atts(V, +Attr) :-
|
||||
!,
|
||||
'$put_to_attr_list'(V, Module, Attr)),
|
||||
(put_atts(V, Attr) :-
|
||||
!,
|
||||
'$put_to_attr_list'(V, Module, Attr)),
|
||||
(put_atts(V, -Attr) :-
|
||||
!,
|
||||
'$del_from_attr_list'(V, Module, Attr))].
|
||||
put_attr(Name, Arity) -->
|
||||
{ functor(Attr, Name, Arity),
|
||||
numbervars(Attr, 0, Arity),
|
||||
V = '$VAR'(Arity) },
|
||||
[(put_atts(V, +Attr) :- !, functor(Attr, Head, Arity),
|
||||
functor(AttrForm, Head, Arity),
|
||||
'$get_attr_list'(V, Ls),
|
||||
'$del_attr'(Ls, V, AttrForm),
|
||||
'$put_attr'(V, Attr)),
|
||||
(put_atts(V, Attr) :- !, functor(Attr, Head, Arity),
|
||||
functor(AttrForm, Head, Arity),
|
||||
'$get_attr_list'(V, Ls),
|
||||
'$del_attr'(Ls, V, AttrForm),
|
||||
'$put_attr'(V, Attr)),
|
||||
(put_atts(V, -Attr) :- !, functor(Attr, _, _),
|
||||
'$get_attr_list'(V, Ls),
|
||||
'$del_attr'(Ls, V, Attr))].
|
||||
|
||||
get_attr(Name, Arity, Module) -->
|
||||
{ functor(Attr, Name, Arity) },
|
||||
[(get_atts(V, +Attr) :-
|
||||
!,
|
||||
functor(Attr, _, _),
|
||||
atts:'$get_from_attr_list'(V, Module, Attr)),
|
||||
(get_atts(V, Attr) :-
|
||||
!,
|
||||
functor(Attr, _, _),
|
||||
atts:'$get_from_attr_list'(V, Module, Attr)),
|
||||
(get_atts(V, -Attr) :-
|
||||
!,
|
||||
functor(Attr, _, _),
|
||||
atts:'$absent_attr'(V, Module, Attr))].
|
||||
get_attr(Name, Arity) -->
|
||||
{ functor(Attr, Name, Arity),
|
||||
numbervars(Attr, 0, Arity),
|
||||
V = '$VAR'(Arity) },
|
||||
[(get_atts(V, +Attr) :- !, functor(Attr, _, _), '$get_attr'(V, Attr)),
|
||||
(get_atts(V, Attr) :- !, functor(Attr, _, _), '$get_attr'(V, Attr)),
|
||||
(get_atts(V, -Attr) :- !, functor(Attr, _, _), '$absent_attr'(V, Attr))].
|
||||
|
||||
user:goal_expansion(Term, M:put_atts(Var, Attr)) :-
|
||||
nonvar(Term),
|
||||
@@ -110,5 +156,10 @@ user:goal_expansion(Term, M:get_atts(Var, Attr)) :-
|
||||
nonvar(Term),
|
||||
Term = get_atts(Var, M, Attr).
|
||||
|
||||
call_residue_vars(Goal, Vars) :-
|
||||
'$get_attr_var_queue_delim'(B),
|
||||
call(Goal),
|
||||
'$get_attr_var_queue_beyond'(B, Vars).
|
||||
|
||||
term_attributed_variables(Term, Vars) :-
|
||||
'$term_attributed_variables'(Term, Vars).
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/** Predicates that generate integers
|
||||
|
||||
These predicates can be used to reason about integers in a reduced domain that
|
||||
follow some property. `library(clpz)` provides another way of reasoning about
|
||||
integers that may also be interesting.
|
||||
*/
|
||||
|
||||
:- module(between, [between/3, gen_int/1, gen_nat/1, numlist/2, numlist/3, repeat/1]).
|
||||
|
||||
%% TODO: numlist/5.
|
||||
@@ -12,24 +5,6 @@ integers that may also be interesting.
|
||||
:- use_module(library(lists), [length/2]).
|
||||
:- use_module(library(error)).
|
||||
|
||||
%% between(+Lower, +Upper, ?X).
|
||||
%
|
||||
% Given Lower and Upper are both integer numbers, true iff X is an integer so that _Lower =< X =< Upper_.
|
||||
% Can be used both to check if X is between Lower and Upper or to generate an integer between
|
||||
% Lower and Upper.
|
||||
%
|
||||
% Examples:
|
||||
%
|
||||
% ```
|
||||
% ?- between(10, 20, 15).
|
||||
% true.
|
||||
% ?- between(10, 20, 25).
|
||||
% false.
|
||||
% ?- between(3, 5, X).
|
||||
% X = 3
|
||||
% ; X = 4
|
||||
% ; X = 5.
|
||||
% ```
|
||||
between(Lower, Upper, X) :-
|
||||
must_be(integer, Lower),
|
||||
must_be(integer, Upper),
|
||||
@@ -37,27 +12,21 @@ between(Lower, Upper, X) :-
|
||||
( nonvar(X) ->
|
||||
Lower =< X,
|
||||
X =< Upper
|
||||
; Lower =< Upper,
|
||||
between_(Lower, Upper, X)
|
||||
; between_(Lower, Upper, X)
|
||||
).
|
||||
|
||||
between_(Lower, Upper, Lower1) :-
|
||||
Lower < Upper,
|
||||
!,
|
||||
( Lower1 = Lower
|
||||
; Lower0 is Lower + 1,
|
||||
between_(Lower0, Upper, Lower1)
|
||||
).
|
||||
between_(Lower, Lower, Lower).
|
||||
between_(Lower, Upper, Lower) :-
|
||||
Lower =< Upper.
|
||||
between_(Lower1, Upper, X) :-
|
||||
Lower1 < Upper,
|
||||
Lower2 is Lower1 + 1,
|
||||
between_(Lower2, Upper, X).
|
||||
|
||||
enumerate_nats(I, I).
|
||||
enumerate_nats(I0, N) :-
|
||||
I1 is I0 + 1,
|
||||
enumerate_nats(I1, N).
|
||||
|
||||
%% gen_nat(?N)
|
||||
%
|
||||
% True iff N is a natural number.
|
||||
gen_nat(N) :-
|
||||
can_be(integer, N),
|
||||
( var(N) -> enumerate_nats(0, N)
|
||||
@@ -72,9 +41,6 @@ enumerate_ints(I0, N) :-
|
||||
I1 is I0 + 1,
|
||||
enumerate_ints(I1, N).
|
||||
|
||||
%% gen_int(?N)
|
||||
%
|
||||
% True iff N is an integer.
|
||||
gen_int(N) :-
|
||||
can_be(integer, N),
|
||||
( var(N) -> enumerate_ints(0, N)
|
||||
@@ -86,24 +52,9 @@ repeat_integer(N) :-
|
||||
repeat_integer(N0) :-
|
||||
N0 > 0, N1 is N0 - 1, repeat_integer(N1).
|
||||
|
||||
%% repeat(+N)
|
||||
%
|
||||
% Succeeds N times. This predicate is only included for compatibility and *should not be used*
|
||||
% because it lacks a declarative interpretation.
|
||||
repeat(N) :-
|
||||
must_be(integer, N), repeat_integer(N).
|
||||
|
||||
%% numlist(?Upper, ?List)
|
||||
%
|
||||
% True iff List is the list of integers _[1, ..., Upper]_. Example:
|
||||
%
|
||||
% ```
|
||||
% ?- numlist(X, Y).
|
||||
% X = 1, Y = [1],
|
||||
% ; X = 2, Y = [1,2]
|
||||
% ; X = 3, Y = [1,2,3]
|
||||
% ; ... .
|
||||
% ```
|
||||
numlist(Upper, List) :-
|
||||
( integer(Upper) -> findall(X, between(1, Upper, X), List)
|
||||
; List = [_|_], length(List, Upper), findall(X, between(1, Upper, X), List)
|
||||
@@ -152,14 +103,5 @@ gen_ints(L, U) :-
|
||||
),
|
||||
L =< U.
|
||||
|
||||
%% numlist(?Lower, ?Upper, ?List).
|
||||
%
|
||||
% True iff List is a list of the form _[Lower, ..., Upper]_.
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- numlist(5, 10, X).
|
||||
% X = [5,6,7,8,9,10].
|
||||
% ```
|
||||
numlist(Lower, Upper, List) :-
|
||||
gen_ints(Lower, Upper), findall(X, between(Lower, Upper, X), List).
|
||||
|
||||
2214
src/lib/builtins.pl
2214
src/lib/builtins.pl
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,8 @@
|
||||
/** High-level predicates to work with chars and strings
|
||||
|
||||
This module contains predicates that relates strings of chars
|
||||
to other representations, as well as high-level predicates to
|
||||
read and write chars.
|
||||
|
||||
*/
|
||||
|
||||
:- module(charsio, [char_type/2,
|
||||
chars_utf8bytes/2,
|
||||
get_single_char/1,
|
||||
get_n_chars/3,
|
||||
get_line_to_chars/3,
|
||||
read_from_chars/2,
|
||||
read_term_from_chars/3,
|
||||
read_line_to_chars/3,
|
||||
read_term_from_chars/2,
|
||||
write_term_to_chars/3,
|
||||
chars_base64/3]).
|
||||
|
||||
@@ -20,8 +10,6 @@ read and write chars.
|
||||
:- use_module(library(iso_ext)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(between)).
|
||||
:- use_module(library(iso_ext), [partial_string/1,partial_string/3]).
|
||||
|
||||
fabricate_var_name(VarType, VarName, N) :-
|
||||
char_code('A', AC),
|
||||
@@ -65,7 +53,7 @@ extend_var_list(Vars, VarList, NewVarList, VarType) :-
|
||||
extend_var_list_(Vars, 0, VarList, NewVarList0, VarType),
|
||||
append(VarList, NewVarList0, NewVarList).
|
||||
|
||||
extend_var_list_([], _, _, [], _).
|
||||
extend_var_list_([], _, VarList, [], _).
|
||||
extend_var_list_([V|Vs], N, VarList, NewVarList, VarType) :-
|
||||
( var_list_contains_variable(VarList, V) ->
|
||||
extend_var_list_(Vs, N, VarList, NewVarList, VarType)
|
||||
@@ -75,90 +63,24 @@ extend_var_list_([V|Vs], N, VarList, NewVarList, VarType) :-
|
||||
).
|
||||
|
||||
|
||||
%% char_type(?Char, ?Type).
|
||||
%
|
||||
% Type is one of the categories that Char fits in.
|
||||
% At least one of the arguments must be ground.
|
||||
% Possible categories are:
|
||||
%
|
||||
% - `alnum`
|
||||
% - `alpha`
|
||||
% - `alphabetic`
|
||||
% - `alphanumeric`
|
||||
% - `ascii`
|
||||
% - `ascii_graphic`
|
||||
% - `ascii_punctuation`
|
||||
% - `binary_digit`
|
||||
% - `control`
|
||||
% - `decimal_digit`
|
||||
% - `exponent`
|
||||
% - `graphic`
|
||||
% - `graphic_token`
|
||||
% - `hexadecimal_digit`
|
||||
% - `layout`
|
||||
% - `lower`
|
||||
% - `meta`
|
||||
% - `numeric`
|
||||
% - `octal_digit`
|
||||
% - `octet`
|
||||
% - `prolog`
|
||||
% - `sign`
|
||||
% - `solo`
|
||||
% - `symbolic_control`
|
||||
% - `symbolic_hexadecimal`
|
||||
% - `upper`
|
||||
% - `lower(Lower)`
|
||||
% - `upper(Upper)`
|
||||
% - `whitespace`
|
||||
%
|
||||
% An example:
|
||||
%
|
||||
% ```
|
||||
% ?- char_type(a, Type).
|
||||
% Type = alnum
|
||||
% ; Type = alpha
|
||||
% ; Type = alphabetic
|
||||
% ; Type = alphanumeric
|
||||
% ; Type = ascii
|
||||
% ; Type = ascii_graphic
|
||||
% ; Type = hexadecimal_digit
|
||||
% ; Type = lower
|
||||
% ; Type = octet
|
||||
% ; Type = prolog
|
||||
% ; Type = symbolic_control
|
||||
% ; Type = lower("a")
|
||||
% ; Type = upper("A")
|
||||
% ; false.
|
||||
% ```
|
||||
%
|
||||
% Note that uppercase and lowercase transformations use a string. This is because
|
||||
% some characters do not map 1:1 between lowercase and uppercase.
|
||||
char_type(Char, Type) :-
|
||||
can_be(character, Char),
|
||||
( \+ ctype(Type) ->
|
||||
domain_error(char_type, Type, char_type/2)
|
||||
; true
|
||||
),
|
||||
( ground(Char) ->
|
||||
ctype(Type),
|
||||
( var(Char) -> instantiation_error(char_type/2)
|
||||
; atom_length(Char, 1) ->
|
||||
( ground(Type) ->
|
||||
( ctype(Type) ->
|
||||
'$char_type'(Char, Type)
|
||||
; domain_error(char_type, Type, char_type/2)
|
||||
)
|
||||
; ctype(Type),
|
||||
'$char_type'(Char, Type)
|
||||
; ground(Type) ->
|
||||
ccode(Code),
|
||||
char_code(Char, Code),
|
||||
'$char_type'(Char, Type)
|
||||
; must_be(character, Char)
|
||||
).
|
||||
)
|
||||
; type_error(in_character, Char, char_type/2)
|
||||
).
|
||||
|
||||
|
||||
% 0xD800 to 0xDFFF are surrogate code points used by UTF-16.
|
||||
|
||||
ccode(Code) :- between(0, 0xD7FF, Code).
|
||||
ccode(Code) :- between(0xE000, 0x10FFFF, Code).
|
||||
|
||||
ctype(alnum).
|
||||
ctype(alpha).
|
||||
ctype(alphabetic).
|
||||
ctype(alphanumeric).
|
||||
ctype(ascii).
|
||||
ctype(ascii_graphic).
|
||||
ctype(ascii_punctuation).
|
||||
@@ -167,81 +89,48 @@ ctype(control).
|
||||
ctype(decimal_digit).
|
||||
ctype(exponent).
|
||||
ctype(graphic).
|
||||
ctype(graphic_token).
|
||||
ctype(hexadecimal_digit).
|
||||
ctype(layout).
|
||||
ctype(lower).
|
||||
ctype(meta).
|
||||
ctype(numeric).
|
||||
ctype(octal_digit).
|
||||
ctype(octet).
|
||||
ctype(prolog).
|
||||
ctype(sign).
|
||||
ctype(solo).
|
||||
ctype(symbolic_control).
|
||||
ctype(symbolic_hexadecimal).
|
||||
ctype(lower(_)).
|
||||
ctype(upper(_)).
|
||||
ctype(upper).
|
||||
ctype(whitespace).
|
||||
|
||||
|
||||
%% get_single_char(-Char).
|
||||
%
|
||||
% Gets a single char from the current input stream.
|
||||
get_single_char(C) :-
|
||||
( var(C) -> '$get_single_char'(C)
|
||||
; atom_length(C, 1) -> '$get_single_char'(C)
|
||||
; type_error(in_character, C, get_single_char/1)
|
||||
).
|
||||
|
||||
%% read_from_chars(+Chars, ?Term).
|
||||
%
|
||||
% Given a string made of chars which contains a representation of
|
||||
% a Prolog term, Term is the Prolog term represented. Example:
|
||||
%
|
||||
% ```
|
||||
% ?- read_from_chars("f(x,y).", X).
|
||||
% X = f(x,y).
|
||||
% ```
|
||||
read_from_chars(Chars, Term) :-
|
||||
must_be(chars, Chars),
|
||||
'$read_from_chars'(Chars, Term0),
|
||||
Term = Term0.
|
||||
|
||||
%% read_term_from_chars(+Chars, ?Term, +Options).
|
||||
%
|
||||
% Like `read_from_chars`, except the reader is configured according to
|
||||
% `Options` which are those of `read_term`.
|
||||
%
|
||||
% ```
|
||||
% ?- read_term_from_chars("f(X,y).", T, [variable_names(['X'=X])]).
|
||||
% T = f(X,y).
|
||||
% ```
|
||||
read_term_from_chars(Chars, Term, Options) :-
|
||||
must_be(chars, Chars),
|
||||
builtins:parse_read_term_options(Options, [Singletons, VariableNames, Variables], read_term_from_chars/3),
|
||||
'$read_term_from_chars'(Chars, Term0, Singletons, Variables, VariableNames),
|
||||
Term = Term0.
|
||||
read_term_from_chars(Chars, Term) :-
|
||||
( var(Chars) ->
|
||||
instantiation_error(read_term_from_chars/2)
|
||||
; nonvar(Term) ->
|
||||
throw(error(uninstantiation_error(Term), read_term_from_chars/2))
|
||||
; '$skip_max_list'(_, -1, Chars, Chars0),
|
||||
Chars0 == [],
|
||||
partial_string(Chars) ->
|
||||
true
|
||||
;
|
||||
type_error(complete_string, Chars, read_term_from_chars/2)
|
||||
),
|
||||
'$read_term_from_chars'(Chars, Term).
|
||||
|
||||
|
||||
%% write_term_to_chars(+Term, +Options, -Chars).
|
||||
%
|
||||
% Given a Term which is a Prolog term and a set of options, Chars is
|
||||
% string representation of that term. Options available are:
|
||||
%
|
||||
% * `ignore_ops(+Boolean)` if `true`, the generic term representation is used everywhere. In `false`
|
||||
% (default), operators do not use that generic term representation.
|
||||
% * `max_depth(+N)` if the term is nested deeper than N, print the reminder as ellipses.
|
||||
% If N = 0 (default), there's no limit.
|
||||
% * `numbervars(+Boolean)` if true, replaces `$VAR(N)` variables with letters, in order. Default is false.
|
||||
% * `quoted(+Boolean)` if true, strings and atoms that need quotes to be valid Prolog syntax, are quoted. Default is false.
|
||||
% * `variable_names(+List)` assign names to variables in term. List should be a list of terms of format `Name=Var`.
|
||||
% * `double_quotes(+Boolean)` if true, strings are printed in double quotes rather than with list notation. Default is false.
|
||||
write_term_to_chars(_, Options, _) :-
|
||||
var(Options), instantiation_error(write_term_to_chars/3).
|
||||
write_term_to_chars(Term, Options, Chars) :-
|
||||
builtins:parse_write_options(Options,
|
||||
[DoubleQuotes, IgnoreOps, MaxDepth, NumberVars, Quoted, VNNames],
|
||||
[IgnoreOps, MaxDepth, NumberVars, Quoted, VNNames],
|
||||
write_term_to_chars/3),
|
||||
( nonvar(Chars) ->
|
||||
throw(error(uninstantiation_error(Chars), write_term_to_chars/3))
|
||||
@@ -250,7 +139,7 @@ write_term_to_chars(Term, Options, Chars) :-
|
||||
),
|
||||
term_variables(Term, Vars),
|
||||
extend_var_list(Vars, VNNames, NewVarNames, numbervars),
|
||||
'$write_term_to_chars'(Chars, Term, IgnoreOps, NumberVars, Quoted, NewVarNames, MaxDepth, DoubleQuotes).
|
||||
'$write_term_to_chars'(Chars, Term, IgnoreOps, NumberVars, Quoted, NewVarNames, MaxDepth).
|
||||
|
||||
% Encodes Ch character to list of Bytes.
|
||||
char_utf8bytes(Ch, Bytes) :-
|
||||
@@ -270,17 +159,6 @@ encode(Code, Prefix, Nb) -->
|
||||
% Maps characters and UTF-8 bytes.
|
||||
% If Cs is a variable, parses Bs as a list of UTF-8 bytes.
|
||||
% Otherwise, transform the list of characters Cs to UTF-8 bytes.
|
||||
|
||||
%% chars_utf8bytes(?Chars, ?Bytes).
|
||||
%
|
||||
% Maps a string made of chars with a list of UTF-8 bytes. Some examples:
|
||||
%
|
||||
% ```
|
||||
% ?- chars_utf8bytes("Prolog", X).
|
||||
% X = [80,114,111,108,111,103].
|
||||
% ?- chars_utf8bytes(X, [226, 136, 145]).
|
||||
% X = "∑".
|
||||
% ```
|
||||
chars_utf8bytes(Cs, Bs) :-
|
||||
var(Cs), must_be(list, Bs) ->
|
||||
once(phrase(decode_utf8(Cs), Bs))
|
||||
@@ -307,66 +185,36 @@ continuation(Code, Chars, Nb) --> [Byte],
|
||||
% each remaining continuation byte (if any) will raise 0xFFFD too
|
||||
continuation(_, ['\xFFFD\'|T], _) --> [_], decode_utf8(T).
|
||||
|
||||
%% get_line_to_chars(+Stream, -Chars, +InitialChars).
|
||||
%
|
||||
% Reads chars from stream Stream until it finds a `\n` character.
|
||||
% InitialChars will be appended at the end of Chars
|
||||
get_line_to_chars(Stream, Cs0, Cs) :-
|
||||
|
||||
read_line_to_chars(Stream, Cs0, Cs) :-
|
||||
'$get_n_chars'(Stream, 1, Char), % this also works for binary streams
|
||||
( Char == [] -> Cs0 = Cs
|
||||
; Char = [C],
|
||||
Cs0 = [C|Rest],
|
||||
( C == '\n' -> Rest = Cs
|
||||
; get_line_to_chars(Stream, Rest, Cs)
|
||||
; read_line_to_chars(Stream, Rest, Cs)
|
||||
)
|
||||
).
|
||||
|
||||
%% get_n_chars(+Stream, ?N, -Chars).
|
||||
%
|
||||
% Read N chars from stream Stream. N can be an integer, in that case
|
||||
% only N chars are read, or a variable, unifying N with the number of chars
|
||||
% read until it found EOF.
|
||||
get_n_chars(Stream, N, Cs) :-
|
||||
can_be(integer, N),
|
||||
( var(N) ->
|
||||
get_to_eof(Stream, Cs),
|
||||
length(Cs, N)
|
||||
; N >= 0,
|
||||
'$get_n_chars'(Stream, N, Cs)
|
||||
).
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Relation between a list of characters Cs and its Base64 encoding Bs,
|
||||
also a list of characters.
|
||||
|
||||
get_n_chars_wrapper(Stream, N, Cs) :-
|
||||
'$get_n_chars'(Stream, N, Cs).
|
||||
At least one of the arguments must be instantiated.
|
||||
|
||||
get_to_eof(Stream, Cs) :-
|
||||
catch(get_n_chars_wrapper(Stream, 512, Cs0),
|
||||
error(syntax_error(unexpected_end_of_file), _),
|
||||
Cs0 = []),
|
||||
( Cs0 == [] -> Cs = []
|
||||
; partial_string(Cs0, Cs, Rest),
|
||||
get_to_eof(Stream, Rest)
|
||||
).
|
||||
Options are:
|
||||
|
||||
%% chars_base64(?Chars, ?Base64, +Options).
|
||||
%
|
||||
% Relation between a list of characters Cs and its Base64 encoding Bs,
|
||||
% also a list of characters.
|
||||
%
|
||||
% At least one of the arguments must be instantiated.
|
||||
%
|
||||
% Options are:
|
||||
%
|
||||
% - `padding(Boolean)`
|
||||
% Whether to use padding: true (the default) or false.
|
||||
% - `charset(C)`
|
||||
% Either 'standard' (RFC 4648 §4, the default) or 'url' (RFC 4648 §5).
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- chars_base64("hello", Bs, []).
|
||||
% Bs = "aGVsbG8=".
|
||||
% ```
|
||||
- padding(Boolean)
|
||||
Whether to use padding: true (the default) or false.
|
||||
- charset(C)
|
||||
Either 'standard' (RFC 4648 §4, the default) or 'url' (RFC 4648 §5).
|
||||
|
||||
Example:
|
||||
|
||||
?- chars_base64("hello", Bs, []).
|
||||
Bs = "aGVsbG8="
|
||||
; false.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
chars_base64(Cs, Bs, Options) :-
|
||||
must_be(list, Options),
|
||||
@@ -385,11 +233,10 @@ chars_base64(Cs, Bs, Options) :-
|
||||
; domain_error(charset, Charset, chars_base64/3)
|
||||
),
|
||||
( var(Cs) ->
|
||||
must_be(chars, Bs),
|
||||
must_be(list, Bs),
|
||||
maplist(must_be(character), Bs),
|
||||
'$chars_base64'(Cs, Bs, Padding, Charset)
|
||||
; must_be(list, Cs),
|
||||
maplist(must_be(character), Cs),
|
||||
'$chars_base64'(Cs, Bs, Padding, Charset)
|
||||
; must_be(chars, Cs),
|
||||
( '$first_non_octet'(Cs, N) ->
|
||||
domain_error(octet_character, N, chars_base64/3)
|
||||
; '$chars_base64'(Cs, Bs, Padding, Charset)
|
||||
)
|
||||
).
|
||||
|
||||
476
src/lib/clpb.pl
476
src/lib/clpb.pl
@@ -1,29 +1,10 @@
|
||||
/* CLP(B): Constraint Logic Programming over Boolean Variables
|
||||
|
||||
Author: Markus Triska
|
||||
Copyright (C): 2019 Markus Triska
|
||||
All rights reserved.
|
||||
|
||||
E-mail: triska@metalevel.at
|
||||
WWW: https://www.metalevel.at
|
||||
Copyright (C): 2019-2025 Markus Triska
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
WWW: http://www.metalevel.at
|
||||
|
||||
*/
|
||||
|
||||
@@ -36,8 +17,8 @@
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
:- module(clpb, [op(300, fy, ~),
|
||||
op(500, yfx, #),
|
||||
sat/1,
|
||||
op(500, yfx, #),
|
||||
sat/1,
|
||||
taut/2,
|
||||
labeling/1,
|
||||
sat_count/2,
|
||||
@@ -68,6 +49,17 @@
|
||||
Compatibility predicates.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
group_pairs_by_key([], []).
|
||||
group_pairs_by_key([M-N|T0], [M-[N|TN]|T]) :-
|
||||
same_key(M, T0, TN, T1),
|
||||
group_pairs_by_key(T1, T).
|
||||
|
||||
same_key(M0, [M-N|T0], [N|TN], T) :-
|
||||
M0 == M,
|
||||
!,
|
||||
same_key(M, T0, TN, T).
|
||||
same_key(_, L, [], L).
|
||||
|
||||
must_be(What, Term) :- must_be(What, unknown(Term)-1, Term).
|
||||
|
||||
must_be(acyclic, Where, Term) :- !,
|
||||
@@ -110,46 +102,6 @@ domain_error(Expectation, Term) :-
|
||||
type_error(Expectation, Term) :-
|
||||
type_error(Expectation, Term, unknown(Term)-1).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Compatibility predicates.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
:- meta_predicate(include(1, ?, ?)).
|
||||
|
||||
include(_, [], []).
|
||||
include(Goal, [L|Ls0], Ls) :-
|
||||
( call(Goal, L) ->
|
||||
Ls = [L|Rest]
|
||||
; Ls = Rest
|
||||
),
|
||||
include(Goal, Ls0, Rest).
|
||||
|
||||
:- meta_predicate(exclude(1, ?, ?)).
|
||||
|
||||
exclude(_, [], []).
|
||||
exclude(Goal, [L|Ls0], Ls) :-
|
||||
( call(Goal, L) ->
|
||||
Ls = Rest
|
||||
; Ls = [L|Rest]
|
||||
),
|
||||
exclude(Goal, Ls0, Rest).
|
||||
|
||||
:- meta_predicate(partition(2,?,?,?,?)).
|
||||
|
||||
partition(_, [], [], [], []).
|
||||
partition(Pred, [H|T], L, E, G) :-
|
||||
call(Pred, H, Diff),
|
||||
partition_(Diff, H, Pred, T, L, E, G).
|
||||
|
||||
partition_(<, H, Pred, T, [H|Rest], E, G) :-
|
||||
partition(Pred, T, Rest, E, G).
|
||||
partition_(=, H, Pred, T, L, [H|Rest], G) :-
|
||||
partition(Pred, T, L, Rest, G).
|
||||
partition_(>, H, Pred, T, L, E, [H|Rest]) :-
|
||||
partition(Pred, T, L, E, Rest).
|
||||
|
||||
:- meta_predicate(partition(1,?,?,?)).
|
||||
|
||||
partition(Pred, Ls0, As, Bs) :-
|
||||
include(Pred, Ls0, As),
|
||||
exclude(Pred, Ls0, Bs).
|
||||
@@ -164,262 +116,6 @@ goal_expansion(del_attr(Var, Module), (var(Var) -> put_atts(Var, -Access);true))
|
||||
Access =.. [Module,_].
|
||||
|
||||
|
||||
/** Constraint Logic Programming over Boolean variables
|
||||
|
||||
## Introduction
|
||||
|
||||
This library provides CLP(B), Constraint Logic Programming over
|
||||
Boolean variables. It can be used to model and solve combinatorial
|
||||
problems such as verification, allocation and covering tasks.
|
||||
|
||||
CLP(B) is an instance of the general CLP(_X_) scheme,
|
||||
extending logic programming with reasoning over specialised domains.
|
||||
|
||||
The implementation is based on reduced and ordered Binary Decision
|
||||
Diagrams (BDDs).
|
||||
|
||||
Benchmarks and usage examples of this library are available from:
|
||||
[*https://www.metalevel.at/clpb/*](https://www.metalevel.at/clpb/)
|
||||
|
||||
## Boolean expressions
|
||||
|
||||
A _Boolean expression_ is one of:
|
||||
|
||||
| `0` | false |
|
||||
| `1` | true |
|
||||
| _variable_ | unknown truth value |
|
||||
| _atom_ | universally quantified variable |
|
||||
| `~` _Expr_ | logical NOT |
|
||||
| _Expr_ `+` _Expr_ | logical OR |
|
||||
| _Expr_ `*` _Expr_ | logical AND |
|
||||
| _Expr_ `#` _Expr_ | exclusive OR |
|
||||
| _Var_ `^` _Expr_ | existential quantification |
|
||||
| _Expr_ `=:=` _Expr_ | equality |
|
||||
| _Expr_ `=\=` _Expr_ | disequality (same as #) |
|
||||
| _Expr_ `=<` _Expr_ | less or equal (implication) |
|
||||
| _Expr_ `>=` _Expr_ | greater or equal |
|
||||
| _Expr_ `<` _Expr_ | less than |
|
||||
| _Expr_ `>` _Expr_ | greater than |
|
||||
| `card(Is,Exprs)` | cardinality constraint (_see below_) |
|
||||
| `+(Exprs)` | n-fold disjunction (_see below_) |
|
||||
| `*(Exprs)` | n-fold conjunction (_see below_) |
|
||||
|
||||
where _Expr_ again denotes a Boolean expression.
|
||||
|
||||
The Boolean expression `card(Is,Exprs)` is true iff the number of true
|
||||
expressions in the list `Exprs` is a member of the list `Is` of
|
||||
integers and integer ranges of the form `From-To`. For example, to
|
||||
state that precisely two of the three variables `X`, `Y` and `Z` are
|
||||
`true`, you can use `sat(card([2],[X,Y,Z]))`.
|
||||
|
||||
`+(Exprs)` and `*(Exprs)` denote, respectively, the disjunction and
|
||||
conjunction of all elements in the list `Exprs` of Boolean
|
||||
expressions.
|
||||
|
||||
Atoms denote parametric values that are universally quantified. All
|
||||
universal quantifiers appear implicitly in front of the entire
|
||||
expression. In residual goals, universally quantified variables always
|
||||
appear on the right-hand side of equations. Therefore, they can be
|
||||
used to express functional dependencies on input variables.
|
||||
|
||||
## Interface predicates
|
||||
|
||||
The most frequently used CLP(B) predicates are:
|
||||
|
||||
* `sat(+Expr)`
|
||||
True iff the Boolean expression Expr is satisfiable.
|
||||
|
||||
* `taut(+Expr, -T)`
|
||||
If Expr is a tautology with respect to the posted constraints, succeeds
|
||||
with *T = 1*. If Expr cannot be satisfied, succeeds with *T = 0*.
|
||||
Otherwise, it fails.
|
||||
|
||||
* `labeling(+Vs)`
|
||||
Assigns truth values to the variables Vs such that all constraints
|
||||
are satisfied.
|
||||
|
||||
The unification of a CLP(B) variable _X_ with a term _T_ is equivalent
|
||||
to posting the constraint sat(X=:=T).
|
||||
|
||||
## Examples
|
||||
|
||||
Here is an example session with a few queries and their answers:
|
||||
|
||||
```
|
||||
?- use_module(library(clpb)).
|
||||
true.
|
||||
|
||||
?- sat(X*Y).
|
||||
X = 1, Y = 1.
|
||||
|
||||
?- sat(X * ~X).
|
||||
false.
|
||||
|
||||
?- taut(X * ~X, T).
|
||||
T = 0, clpb:sat(X=:=X).
|
||||
|
||||
?- sat(X^Y^(X+Y)).
|
||||
clpb:sat(X=:=X), clpb:sat(Y=:=Y).
|
||||
|
||||
?- sat(X*Y + X*Z), labeling([X,Y,Z]).
|
||||
X = 1, Y = 0, Z = 1
|
||||
; X = 1, Y = 1, Z = 0
|
||||
; X = 1, Y = 1, Z = 1.
|
||||
|
||||
?- sat(X =< Y), sat(Y =< Z), taut(X =< Z, T).
|
||||
T = 1, clpb:sat(X=:=X*Y), clpb:sat(Y=:=Y*Z).
|
||||
|
||||
?- sat(1#X#a#b).
|
||||
clpb:sat(X=:=a#b).
|
||||
```
|
||||
|
||||
The pending residual goals constrain remaining variables to Boolean
|
||||
expressions and are declaratively equivalent to the original query.
|
||||
The last example illustrates that when applicable, remaining variables
|
||||
are expressed as functions of universally quantified variables.
|
||||
|
||||
## Obtaining BDDs
|
||||
|
||||
By default, CLP(B) residual goals appear in (approximately) algebraic
|
||||
normal form (ANF). This projection is often computationally expensive.
|
||||
We can assert `clpb:clpb_residuals(bdd)` to see the BDD representation
|
||||
of all constraints. This results in faster projection to residual
|
||||
goals, and is also useful for learning more about BDDs. For example:
|
||||
|
||||
```
|
||||
?- asserta(clpb:clpb_residuals(bdd)).
|
||||
true.
|
||||
|
||||
?- sat(X#Y).
|
||||
node(3)- (v(X, 0)->node(2);node(1)),
|
||||
node(1)- (v(Y, 1)->true;false),
|
||||
node(2)- (v(Y, 1)->false;true).
|
||||
```
|
||||
|
||||
Note that this representation cannot be pasted back on the toplevel,
|
||||
and its details are subject to change. Use copy_term/3 to obtain
|
||||
such answers as Prolog terms.
|
||||
|
||||
The variable order of the BDD is determined by the order in which the
|
||||
variables first appear in constraints. To obtain different orders,
|
||||
we can for example use:
|
||||
|
||||
```
|
||||
?- sat(+[1,Y,X]), sat(X#Y).
|
||||
node(3)- (v(Y, 0)->node(2);node(1)),
|
||||
node(1)- (v(X, 1)->true;false),
|
||||
node(2)- (v(X, 1)->false;true).
|
||||
```
|
||||
|
||||
## Enabling monotonic CLP(B)
|
||||
|
||||
In the default execution mode, CLP(B) constraints are _not_ monotonic.
|
||||
This means that _adding_ constraints can yield new solutions. For
|
||||
example:
|
||||
|
||||
```
|
||||
?- sat(X=:=1), X = 1+0.
|
||||
false.
|
||||
|
||||
?- X = 1+0, sat(X=:=1), X = 1+0.
|
||||
X = 1+0.
|
||||
```
|
||||
|
||||
This behaviour is highly problematic from a logical point of view, and
|
||||
it may render [*declarative
|
||||
debugging*](https://www.metalevel.at/prolog/debugging)
|
||||
techniques inapplicable.
|
||||
|
||||
Assert `clpb:monotonic` to make CLP(B) *monotonic*. If this mode is
|
||||
enabled, then you must wrap CLP(B) variables with the functor
|
||||
`v/1`. For example:
|
||||
|
||||
```
|
||||
?- asserta(clpb:monotonic).
|
||||
true.
|
||||
|
||||
?- sat(v(X)=:=1#1).
|
||||
X = 0.
|
||||
```
|
||||
|
||||
## Example: Pigeons
|
||||
|
||||
In this example, we are attempting to place _I_ pigeons into _J_ holes
|
||||
in such a way that each hole contains at most one pigeon. One
|
||||
interesting property of this task is that it can be formulated using
|
||||
only _cardinality constraints_ (`card/2`). Another interesting aspect
|
||||
is that this task has no short resolution refutations in general.
|
||||
|
||||
In the following, we use [*Prolog DCG
|
||||
notation*](https://www.metalevel.at/prolog/dcg) to describe a
|
||||
list `Cs` of CLP(B) constraints that must all be satisfied.
|
||||
|
||||
```
|
||||
:- use_module(library(clpb)).
|
||||
:- use_module(library(clpz)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(dcgs)).
|
||||
|
||||
pigeon(I, J, Rows, Cs) :-
|
||||
length(Rows, I), length(Row, J),
|
||||
maplist(same_length(Row), Rows),
|
||||
transpose(Rows, TRows),
|
||||
phrase((all_cards(Rows,[1]),all_cards(TRows,[0,1])), Cs).
|
||||
|
||||
all_cards([], _) --> [].
|
||||
all_cards([Ls|Lss], Cs) --> [card(Cs,Ls)], all_cards(Lss, Cs).
|
||||
```
|
||||
|
||||
Example queries:
|
||||
|
||||
```
|
||||
?- pigeon(9, 8, Rows, Cs), sat(*(Cs)).
|
||||
false.
|
||||
|
||||
?- pigeon(2, 3, Rows, Cs), sat(*(Cs)),
|
||||
append(Rows, Vs), labeling(Vs),
|
||||
maplist(portray_clause, Rows).
|
||||
[0,0,1].
|
||||
[0,1,0].
|
||||
etc.
|
||||
```
|
||||
|
||||
## Example: Boolean circuit
|
||||
|
||||
Consider a Boolean circuit that express the Boolean function =|XOR|=
|
||||
with 4 =|NAND|= gates. We can model such a circuit with CLP(B)
|
||||
constraints as follows:
|
||||
|
||||
```
|
||||
:- use_module(library(clpb)).
|
||||
|
||||
nand_gate(X, Y, Z) :- sat(Z =:= ~(X*Y)).
|
||||
|
||||
xor(X, Y, Z) :-
|
||||
nand_gate(X, Y, T1),
|
||||
nand_gate(X, T1, T2),
|
||||
nand_gate(Y, T1, T3),
|
||||
nand_gate(T2, T3, Z).
|
||||
```
|
||||
|
||||
Using universally quantified variables, we can show that the circuit
|
||||
does compute =|XOR|= as intended:
|
||||
|
||||
```
|
||||
?- xor(x, y, Z).
|
||||
clpb:sat(Z=:=x#y).
|
||||
```
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
The interface predicates of this library follow the example of
|
||||
[*SICStus Prolog*](https://sicstus.sics.se).
|
||||
|
||||
Use SICStus Prolog for higher performance in many cases.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Each CLP(B) variable belongs to exactly one BDD. Each CLP(B)
|
||||
variable gets an attribute (in module "clpb") of the form:
|
||||
@@ -506,10 +202,6 @@ non_monotonic(X) :-
|
||||
; true
|
||||
).
|
||||
|
||||
:- meta_predicate(bdd_nodes(1, ?, ?)).
|
||||
:- meta_predicate(bdd_nodes_(1, ?, ?, ?)).
|
||||
:- meta_predicate(with_aux(1, ?)).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Rewriting to canonical expressions.
|
||||
Atoms are converted to variables with a special attribute.
|
||||
@@ -1252,7 +944,7 @@ bdd_restriction_(Node, VI, Value, Res) -->
|
||||
node_id(Node, ID) },
|
||||
( { I0 =:= VI } ->
|
||||
( { Value =:= 0 } -> { Res = Low }
|
||||
; { Res = High }
|
||||
; { Value =:= 1 } -> { Res = High }
|
||||
)
|
||||
; { I0 > VI } -> { Res = Node }
|
||||
; state(G0), { get_assoc(ID, G0, Res) } -> []
|
||||
@@ -1428,19 +1120,19 @@ indomain(1).
|
||||
%
|
||||
% Examples:
|
||||
%
|
||||
% ```
|
||||
% ==
|
||||
% ?- sat(A =< B), Vs = [A,B], sat_count(+[1|Vs], Count).
|
||||
% Vs = [A,B], Count = 3, clpb:sat(A=:=A*B).
|
||||
% Vs = [A, B],
|
||||
% Count = 3,
|
||||
% sat(A=:=A*B).
|
||||
%
|
||||
% ?- length(Vs, 120),
|
||||
% sat_count(+Vs, CountOr),
|
||||
% sat_count(*(Vs), CountAnd).
|
||||
% Vs = [...],
|
||||
% CountOr = 1329227995784915872903807060280344575,
|
||||
% CountAnd = 1.
|
||||
% ```
|
||||
|
||||
|
||||
% Vs = [...],
|
||||
% CountOr = 1329227995784915872903807060280344575,
|
||||
% CountAnd = 1.
|
||||
% ==
|
||||
|
||||
sat_count(Sat0, N) :-
|
||||
catch((parse_sat(Sat0, Sat),
|
||||
@@ -1551,15 +1243,11 @@ random_bindings(VNum, Node) -->
|
||||
{ node_var_low_high(Node, Var, Low, High),
|
||||
bdd_count(Node, VNum, Total),
|
||||
bdd_count(Low, VNum, LCount) },
|
||||
( { weighted_maybe(LCount, Total) } ->
|
||||
( { maybe(LCount, Total) } ->
|
||||
[Var=0], random_bindings(VNum, Low)
|
||||
; [Var=1], random_bindings(VNum, High)
|
||||
).
|
||||
|
||||
weighted_maybe(K, N) :-
|
||||
random_integer(0, N, X),
|
||||
X < K.
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Find solutions with maximum weight.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
@@ -1570,7 +1258,7 @@ weighted_maybe(K, N) :-
|
||||
% linear objective function over Boolean variables Vs with integer
|
||||
% coefficients Weights. This predicate assigns 0 and 1 to the
|
||||
% variables in Vs such that all stated constraints are satisfied, and
|
||||
% Maximum is the maximum of `sum(Weight_i*V_i)` over all admissible
|
||||
% Maximum is the maximum of sum(Weight_i*V_i) over all admissible
|
||||
% assignments. On backtracking, all admissible assignments that
|
||||
% attain the optimum are generated.
|
||||
%
|
||||
@@ -1579,10 +1267,10 @@ weighted_maybe(K, N) :-
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ==
|
||||
% ?- sat(A#B), weighted_maximum([1,2,1], [A,B,C], Maximum).
|
||||
% A = 0, B = 1, C = 1, Maximum = 3.
|
||||
% ```
|
||||
% A = 0, B = 1, C = 1, Maximum = 3.
|
||||
% ==
|
||||
|
||||
weighted_maximum(Ws, Vars, Max) :-
|
||||
must_be(list(integer), Ws),
|
||||
@@ -1600,18 +1288,13 @@ weighted_maximum(Ws, Vars, Max) :-
|
||||
maplist(var_with_index, Vars, IVs),
|
||||
pairs_keys_values(Pairs0, IVs, Ws),
|
||||
keysort(Pairs0, Pairs1),
|
||||
% sum linear combinations of repeated variables
|
||||
group_pairs_by_key(Pairs1, Groups),
|
||||
maplist(group_sumweights_pair, Groups, Pairs2),
|
||||
pairs_keys_values(Pairs2, IVs1, WeightsIndexOrder),
|
||||
pairs_keys_values(Pairs1, IVs1, WeightsIndexOrder),
|
||||
pairs_values(IVs1, VarsIndexOrder),
|
||||
% Pairs is a list of Var-Weight terms, in index order of Vars
|
||||
pairs_keys_values(Pairs, VarsIndexOrder, WeightsIndexOrder),
|
||||
bdd_maximum(BDD, Pairs, Max),
|
||||
max_labeling(BDD, Pairs).
|
||||
|
||||
group_sumweights_pair((I-V)-Ws, (I-V)-W) :- sum_list(Ws, W).
|
||||
|
||||
max_labeling(1, Pairs) :- max_upto(Pairs, _, _).
|
||||
max_labeling(node(_,Var,Low,High,Aux), Pairs0) :-
|
||||
max_upto(Pairs0, Var, Pairs),
|
||||
@@ -1695,14 +1378,14 @@ skip_to_var_(Var, Weight, [Var0-Weight0|VWs0], VWs) -->
|
||||
|
||||
attribute_goals(Var) -->
|
||||
{ var_index_root(Var, _, Root) },
|
||||
!,
|
||||
( { root_get_formula_bdd(Root, Formula, BDD) } ->
|
||||
{ del_bdd(Root) },
|
||||
( { clpb_residuals(bdd) } ->
|
||||
{ bdd_nodes(BDD, Nodes),
|
||||
phrase(nodes(Nodes), Ns) },
|
||||
[clpb:'$clpb_bdd'(Ns)]
|
||||
; { phrase(sat_ands(Formula), Ands0),
|
||||
; { prepare_global_variables(BDD),
|
||||
phrase(sat_ands(Formula), Ands0),
|
||||
ands_fusion(Ands0, Ands),
|
||||
maplist(formula_anf, Ands, ANFs0),
|
||||
sort(ANFs0, ANFs1),
|
||||
@@ -1722,24 +1405,39 @@ attribute_goals(Var) -->
|
||||
booleans(RestVs)
|
||||
; boolean(Var) % the variable may have occurred only in taut/2
|
||||
).
|
||||
attribute_goals(Var) -->
|
||||
{ get_atts(Var, clpb_max(_)),
|
||||
!,
|
||||
put_atts(Var, -clpb_max(_)) }.
|
||||
attribute_goals(Var) -->
|
||||
{ get_atts(Var, clpb_bdd(BDD)),
|
||||
ground(BDD),
|
||||
put_atts(Var, -clpb_bdd(_)) }.
|
||||
|
||||
del_clpb(Var) :-
|
||||
del_attr(Var, clpb),
|
||||
del_attr(Var, clpb_hash),
|
||||
del_attr(Var, clpb_atom).
|
||||
del_attr(Var, clpb_hash).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
To make residual projection work with recorded constraints, the
|
||||
global counters must be adjusted so that new variables and nodes
|
||||
also get new IDs. Also, clpb_next_id/2 is used to actually create
|
||||
these counters, because creating them with b_setval/2 would make
|
||||
them [] on backtracking, which is quite unfortunate in itself.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
b_setval(K, T) :- bb_b_put(K, T).
|
||||
nb_setval(K, T) :- bb_put(K, T).
|
||||
b_getval(K, T) :- bb_get(K, T).
|
||||
|
||||
prepare_global_variables(BDD) :-
|
||||
clpb_next_id('$clpb_next_var', V0),
|
||||
clpb_next_id('$clpb_next_node', N0),
|
||||
bdd_nodes(BDD, Nodes),
|
||||
foldl(max_variable_node, Nodes, V0-N0, MaxV0-MaxN0),
|
||||
MaxV is MaxV0 + 1,
|
||||
MaxN is MaxN0 + 1,
|
||||
b_setval('$clpb_next_var', MaxV),
|
||||
b_setval('$clpb_next_node', MaxN).
|
||||
|
||||
max_variable_node(Node, V0-N0, V-N) :-
|
||||
node_id(Node, N1),
|
||||
node_varindex(Node, V1),
|
||||
N is max(N0,N1),
|
||||
V is max(V0,V1).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Fuse formulas that share the same variables into single conjunctions.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
@@ -1831,8 +1529,8 @@ pairs_([], _) --> [].
|
||||
pairs_([B|Bs], A) --> [A-B], pairs_(Bs, A).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Assert clpb:clpb_residuals(bdd) to obtain the BDD nodes as
|
||||
residuals. Note that they cannot be used as regular goals.
|
||||
Set the Prolog flag clpb_residuals to bdd to obtain the BDD nodes
|
||||
as residuals. Note that they cannot be used as regular goals.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
nodes([]) --> [].
|
||||
@@ -1860,12 +1558,10 @@ sats([]) --> [].
|
||||
sats([A|As]) --> [clpb:sat(A)], sats(As).
|
||||
|
||||
booleans([]) --> [].
|
||||
booleans([B|Bs]) --> boolean(B), booleans(Bs).
|
||||
booleans([B|Bs]) --> boolean(B), { del_clpb(B) }, booleans(Bs).
|
||||
|
||||
boolean(Var) -->
|
||||
{ del_clpb(Var) },
|
||||
( { get_attr(Var, clpb_omit_boolean, true) } ->
|
||||
{ put_atts(Var, -clpb_omit_boolean(_)) }
|
||||
( { get_attr(Var, clpb_omit_boolean, true) } -> []
|
||||
; [clpb:sat(Var =:= Var)]
|
||||
).
|
||||
|
||||
@@ -1967,3 +1663,49 @@ clpb_atom_var(Atom, Var) :-
|
||||
put_assoc(Atom, A0, Var, A),
|
||||
b_setval('$clpb_atoms', A)
|
||||
).
|
||||
|
||||
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Compatibility predicates.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
|
||||
include(Goal, List, Is) :-
|
||||
include_(List, Goal, Is).
|
||||
|
||||
include_([], _, []).
|
||||
include_([X1|Xs1], P, Is) :-
|
||||
( call(P, X1)
|
||||
-> Is = [X1|Is1]
|
||||
; Is = Is1
|
||||
),
|
||||
include_(Xs1, P, Is1).
|
||||
|
||||
|
||||
exclude(Goal, List, Is) :-
|
||||
exclude_(List, Goal, Is).
|
||||
|
||||
exclude_([], _, []).
|
||||
exclude_([X1|Xs1], P, Is) :-
|
||||
( call(P, X1)
|
||||
-> Is = Is1
|
||||
; Is = [X1|Is1]
|
||||
),
|
||||
exclude_(Xs1, P, Is1).
|
||||
|
||||
|
||||
partition(Pred, List, Less, Equal, Greater) :-
|
||||
partition_(List, Pred, Less, Equal, Greater).
|
||||
|
||||
partition_([], _, [], [], []).
|
||||
partition_([H|T], Pred, L, E, G) :-
|
||||
call(Pred, H, Diff),
|
||||
partition_(Diff, H, Pred, T, L, E, G).
|
||||
|
||||
partition_(<, H, Pred, T, [H|Rest], E, G) :-
|
||||
partition_(T, Pred, Rest, E, G).
|
||||
partition_(=, H, Pred, T, L, [H|Rest], G) :-
|
||||
partition_(T, Pred, L, Rest, G).
|
||||
partition_(>, H, Pred, T, L, E, [H|Rest]) :-
|
||||
partition_(T, Pred, L, E, Rest).
|
||||
|
||||
2381
src/lib/clpz.pl
2381
src/lib/clpz.pl
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,5 @@
|
||||
:- module(cont, [reset/3, shift/1]).
|
||||
|
||||
:- meta_predicate(reset(0, ?, ?)).
|
||||
|
||||
reset(Goal, Ball, Cont) :-
|
||||
call(Goal),
|
||||
'$reset_cont_marker',
|
||||
@@ -13,7 +11,7 @@ shift(Ball) :-
|
||||
get_chunks(E, P, L),
|
||||
( L == [] ->
|
||||
Cont = cont(true)
|
||||
; Cont = cont(cont:call_continuation(L))
|
||||
; Cont = cont(call_continuation(L))
|
||||
),
|
||||
'$write_cont_and_term'(_, _, Cont, Ball),
|
||||
'$unwind_environments'.
|
||||
|
||||
1076
src/lib/crypto.pl
1076
src/lib/crypto.pl
File diff suppressed because it is too large
Load Diff
@@ -1,67 +1,54 @@
|
||||
/** Predicates for parsing CSV data
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Predicates for parsing CSV data
|
||||
|
||||
## Read CSV files.
|
||||
|
||||
Only two options with default values:
|
||||
Read csv files
|
||||
|
||||
- `token_separator(',')`
|
||||
- `with_header(true)`
|
||||
Only two options with default values :
|
||||
- token_separator(',')
|
||||
- with_header(true)
|
||||
|
||||
### Examples:
|
||||
Examples
|
||||
|
||||
Parsing a CSV string:
|
||||
* parsing a csv string:
|
||||
|
||||
```
|
||||
?- use_module(library(csv)).
|
||||
?- use_module(library(dcgs)).
|
||||
?- phrase(parse_csv(Data), "col1,col2,col3,col4\none,2,,three").
|
||||
Data = frame(["col1","col2","col3","col4"],[["one",2,[],"three"]]).
|
||||
```
|
||||
?- use_module(library(csv)).
|
||||
?- use_module(library(dcgs)).
|
||||
?- phrase(parse_csv(Data), "col1,col2,col3,col4\none,2,,three").
|
||||
Data = frame(["col1","col2","col3","col4"],[["one",2,[],"three"]]).
|
||||
|
||||
With some options:
|
||||
* with some options:
|
||||
|
||||
```
|
||||
?- phrase(parse_csv(Data, [with_header(false), token_separator(';')]), "one;2;;three").
|
||||
Data = frame([],[["one",2,[],"three"]]).
|
||||
```
|
||||
?- phrase(parse_csv(Data, [with_header(false), token_separator(';')]), "one;2;;three").
|
||||
Data = frame([],[["one",2,[],"three"]]).
|
||||
|
||||
Parsing a CSV file:
|
||||
* parsing a csv file:
|
||||
|
||||
```
|
||||
?- use_module(library(csv)).
|
||||
?- use_module(library(pio)).
|
||||
?- phrase_from_file(parse_csv(frame(Header, Rows)), './test.csv').
|
||||
```
|
||||
?- use_module(library(csv)).
|
||||
?- use_module(library(pio)).
|
||||
?- phrase_from_file(parse_csv(frame(Header, Rows)), './test.csv').
|
||||
|
||||
## Write CSV files
|
||||
|
||||
Four options with default values :
|
||||
Write csv files
|
||||
|
||||
- `line_separator('\n')`
|
||||
- `token_separator(',')`
|
||||
- `with_header(true)`
|
||||
- `null_value(empty)`
|
||||
Four options with default values :
|
||||
- line_separator('\n')
|
||||
- token_separator(',')
|
||||
- with_header(true)
|
||||
- null_value(empty)
|
||||
|
||||
### Examples
|
||||
Examples
|
||||
|
||||
Writing a CSV file:
|
||||
* writing a csv file:
|
||||
|
||||
```
|
||||
?- use_module(library(csv)).
|
||||
?- write_csv('./test.csv', frame(["col1","col2","col3","col4"], [["one",2,[],"three"]])).
|
||||
```
|
||||
?- use_module(library(csv)).
|
||||
?- write_csv('./test.csv', frame(["col1","col2","col3","col4"], [["one",2,[],"three"]])).
|
||||
|
||||
With some options
|
||||
* with some options
|
||||
|
||||
```
|
||||
?- use_module(library(csv)).
|
||||
?- write_csv('./test.csv', frame(
|
||||
["col1","col2","col3","col4"],
|
||||
[["one",2,[],"three"]]
|
||||
),
|
||||
[with_header(false), line_separator('\r\n'), token_separator(';'), null_value('\\N')]).
|
||||
```
|
||||
*/
|
||||
?- use_module(library(csv)).
|
||||
?- write_csv('./test.csv', frame(["col1","col2","col3","col4"], [["one",2,[],"three"]]), [with_header(false), line_separator('\r\n'), token_separator(';'), null_value('\\N')]).
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
:- module(csv, [
|
||||
parse_csv//1,
|
||||
@@ -221,7 +208,7 @@ row([X | Y], Opt) -->
|
||||
!,
|
||||
( separator(Opt) ->
|
||||
row(Y, Opt)
|
||||
; end_token,
|
||||
; end_token ->
|
||||
{ Y = [] }).
|
||||
|
||||
|
||||
|
||||
229
src/lib/dcgs.pl
229
src/lib/dcgs.pl
@@ -1,118 +1,45 @@
|
||||
/** Support for Definite Clause Grammars.
|
||||
|
||||
A Prolog definite clause grammar (DCG) describes a sequence. Operationally, DCGs
|
||||
can be used to parse, generate, complete and check sequences manifested as lists.
|
||||
|
||||
Check [The Power of Prolog chapter on DCGs](https://www.metalevel.at/prolog/dcg)
|
||||
to learn more about them.
|
||||
*/
|
||||
|
||||
|
||||
:- module(dcgs,
|
||||
[op(1105, xfy, '|'),
|
||||
phrase/2,
|
||||
phrase/3,
|
||||
phrase//2,
|
||||
phrase//3,
|
||||
seq//1,
|
||||
seqq//1,
|
||||
... //0,
|
||||
(-->)/2
|
||||
]).
|
||||
phrase/2,
|
||||
phrase/3]).
|
||||
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(iso_ext)).
|
||||
:- use_module(library(lists), [append/3, member/2]).
|
||||
:- use_module(library(loader), [strip_module/3]).
|
||||
|
||||
:- meta_predicate(phrase(2, ?)).
|
||||
|
||||
:- meta_predicate(phrase(2, ?, ?)).
|
||||
|
||||
:- meta_predicate(phrase(3, ?, ?, ?)).
|
||||
|
||||
:- meta_predicate(phrase(4, ?, ?, ?, ?)).
|
||||
|
||||
:- meta_predicate(','(2, 2, ?, ?)).
|
||||
|
||||
:- meta_predicate(;(2, 2, ?, ?)).
|
||||
|
||||
%% phrase(+Body, ?Ls).
|
||||
%
|
||||
% True iff Body describes the list Ls. Body must be a DCG body.
|
||||
% It is equivalent to `phrase(Body, Ls, [])`.
|
||||
%
|
||||
% Examples:
|
||||
%
|
||||
% ```
|
||||
% as --> [].
|
||||
% as --> [a], as.
|
||||
%
|
||||
% ?- phrase(as, Ls).
|
||||
% Ls = []
|
||||
% ; Ls = "a"
|
||||
% ; Ls = "aa"
|
||||
% ; Ls = "aaa"
|
||||
% ; ... .
|
||||
%
|
||||
% ?- phrase(as, "aaa").
|
||||
% true.
|
||||
% ```
|
||||
:- use_module(library(lists), [append/3]).
|
||||
|
||||
phrase(GRBody, S0) :-
|
||||
phrase(GRBody, S0, []).
|
||||
|
||||
%% phrase(+Body, ?Ls, ?Ls0).
|
||||
%
|
||||
% True iff Body describes part of the list Ls and the rest of Ls is Ls0.
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- phrase(seq(X), "aaa", Y).
|
||||
% X = [], Y = "aaa"
|
||||
% ; X = "a", Y = "aa"
|
||||
% ; X = "aa", Y = "a"
|
||||
% ; X = "aaa", Y = [].
|
||||
% ```
|
||||
phrase(GRBody, S0, S) :-
|
||||
strip_module(GRBody, M, GRBody1),
|
||||
( var(GRBody) ->
|
||||
instantiation_error(phrase/3)
|
||||
; nonvar(GRBody1),
|
||||
dcg_constr(GRBody1),
|
||||
dcg_body(GRBody1, S0, S, GRBody2) ->
|
||||
call(M:GRBody2)
|
||||
; call(M:GRBody1, S0, S)
|
||||
( var(GRBody) -> throw(error(instantiation_error, phrase/3))
|
||||
; dcg_constr(GRBody) -> phrase_(GRBody, S0, S)
|
||||
; functor(GRBody, _, _) -> call(GRBody, S0, S)
|
||||
; throw(error(type_error(callable, GRBody), phrase/3))
|
||||
).
|
||||
|
||||
phrase(GRBody, Arg, S0, S) :-
|
||||
strip_module(GRBody, M, GRBody1),
|
||||
( var(GRBody) ->
|
||||
instantiation_error(phrase/4)
|
||||
; nonvar(GRBody1),
|
||||
GRBody1 =.. GRBodys1,
|
||||
append(GRBodys1, [Arg], GRBodys2),
|
||||
GRBody2 =.. GRBodys2,
|
||||
dcg_constr(GRBody2),
|
||||
dcg_body(GRBody2, S0, S, GRBody3) ->
|
||||
call(M:GRBody3)
|
||||
; call(M:GRBody1, Arg, S0, S)
|
||||
).
|
||||
|
||||
phrase(GRBody, Arg1, Arg2, S0, S) :-
|
||||
strip_module(GRBody, M, GRBody1),
|
||||
( var(GRBody) ->
|
||||
instantiation_error(phrase/5)
|
||||
; nonvar(GRBody1),
|
||||
GRBody1 =.. GRBodys1,
|
||||
append(GRBodys1, [Arg1,Arg2], GRBodys2),
|
||||
GRBody2 =.. GRBodys2,
|
||||
dcg_constr(GRBody2),
|
||||
dcg_body(GRBody2, S0, S, GRBody3) ->
|
||||
call(M:GRBody3)
|
||||
; call(M:GRBody1, Arg1, Arg2, S0, S)
|
||||
phrase_([], S, S).
|
||||
phrase_(!, S, S).
|
||||
phrase_((A, B), S0, S) :-
|
||||
phrase(A, S0, S1), phrase(B, S1, S).
|
||||
phrase_((A -> B ; C), S0, S) :-
|
||||
!,
|
||||
( phrase(A, S0, S1) ->
|
||||
phrase(B, S1, S)
|
||||
; phrase(C, S0, S)
|
||||
).
|
||||
phrase_((A ; B), S0, S) :-
|
||||
( phrase(A, S0, S) ; phrase(B, S0, S) ).
|
||||
phrase_((A | B), S0, S) :-
|
||||
( phrase(A, S0, S) ; phrase(B, S0, S) ).
|
||||
phrase_({G}, S0, S) :-
|
||||
( call(G), S0 = S ).
|
||||
phrase_(call(G), S0, S) :-
|
||||
call(G, S0, S).
|
||||
phrase_((A -> B), S0, S) :-
|
||||
phrase((A -> B ; fail), S0, S).
|
||||
phrase_(phrase(NonTerminal), S0, S) :-
|
||||
phrase(NonTerminal, S0, S).
|
||||
phrase_([T|Ts], S0, S) :-
|
||||
append([T|Ts], S, S0).
|
||||
|
||||
% The same version of the below two dcg_rule clauses, but with module scoping.
|
||||
dcg_rule(( M:NonTerminal, Terminals --> GRBody ), ( M:Head :- Body )) :-
|
||||
@@ -140,10 +67,7 @@ dcg_rule(( NonTerminal --> GRBody ), ( Head :- Body )) :-
|
||||
dcg_non_terminal(NonTerminal, S0, S, Goal) :-
|
||||
NonTerminal =.. NonTerminalUniv,
|
||||
append(NonTerminalUniv, [S0, S], GoalUniv),
|
||||
( callable(NonTerminal) ->
|
||||
Goal =.. GoalUniv
|
||||
; Goal = NonTerminal % let call/N throw an error instead of throwing one here.
|
||||
).
|
||||
Goal =.. GoalUniv.
|
||||
|
||||
dcg_terminals(Terminals, S0, S, S0 = List) :-
|
||||
append(Terminals, S, List).
|
||||
@@ -155,15 +79,12 @@ dcg_body(GRBody, S0, S, Body) :-
|
||||
nonvar(GRBody),
|
||||
dcg_constr(GRBody),
|
||||
dcg_cbody(GRBody, S0, S, Body).
|
||||
dcg_body(NonTerminal, S0, S, Goal1) :-
|
||||
dcg_body(NonTerminal, S0, S, Goal) :-
|
||||
nonvar(NonTerminal),
|
||||
\+ dcg_constr(NonTerminal),
|
||||
loader:strip_module(NonTerminal, M, NonTerminal0),
|
||||
dcg_non_terminal(NonTerminal0, S0, S, Goal0),
|
||||
( functor(NonTerminal, (:), 2) ->
|
||||
Goal1 = M:Goal0
|
||||
; Goal1 = Goal0
|
||||
).
|
||||
NonTerminal \= ( _ -> _ ),
|
||||
NonTerminal \= ( \+ _ ),
|
||||
dcg_non_terminal(NonTerminal, S0, S, Goal).
|
||||
|
||||
% The following constructs in a grammar rule body
|
||||
% are defined in the corresponding subclauses.
|
||||
@@ -175,13 +96,9 @@ dcg_constr(( _'|'_ )). % 7.14.6 - alternative
|
||||
dcg_constr({_}). % 7.14.7
|
||||
dcg_constr(call(_)). % 7.14.8
|
||||
dcg_constr(phrase(_)). % 7.14.9
|
||||
dcg_constr(phrase(_,_)). % extension of 7.14.9
|
||||
dcg_constr(phrase(_,_,_)). % extension of 7.14.9
|
||||
dcg_constr(!). % 7.14.10
|
||||
dcg_constr(\+ G_0) :- % 7.14.11 - not (existence implementation def.)
|
||||
throw(error(representation_error(dcg_body), [culprit- (\+ G_0)])).
|
||||
dcg_constr((If->Then)) :- % 7.14.12 - if-then (existence implementation def.)
|
||||
throw(error(representation_error(dcg_body), [culprit- (If->Then)])).
|
||||
%% dcg_constr(\+ _). % 7.14.11 - not (existence implementation dep.)
|
||||
dcg_constr((_->_)). % 7.14.12 - if-then (existence implementation dep.)
|
||||
|
||||
% The principal functor of the first argument indicates
|
||||
% the construct to be expanded.
|
||||
@@ -206,79 +123,13 @@ dcg_cbody(( GREither '|' GROr ), S0, S, ( Either ; Or )) :-
|
||||
dcg_cbody({Goal}, S0, S, ( Goal, S0 = S )).
|
||||
dcg_cbody(call(Cont), S0, S, call(Cont, S0, S)).
|
||||
dcg_cbody(phrase(Body), S0, S, phrase(Body, S0, S)).
|
||||
dcg_cbody(phrase(Body, Arg), S0, S, phrase(Body, Arg, S0, S)).
|
||||
dcg_cbody(phrase(Body, Arg1, Arg2), S0, S, phrase(Body, Arg1, Arg2, S0, S)).
|
||||
dcg_cbody(!, S0, S, ( !, S0 = S )).
|
||||
% dcg_cbody(\+ GRBody, S0, S, ( \+ phrase(GRBody,S0,_), S0 = S )).
|
||||
dcg_cbody(\+ GRBody, S0, S, ( \+ phrase(GRBody,S0,_), S0 = S )).
|
||||
dcg_cbody(( GRIf -> GRThen ), S0, S, ( If -> Then )) :-
|
||||
dcg_body(GRIf, S0, S1, If),
|
||||
dcg_body(GRThen, S1, S, Then).
|
||||
|
||||
|
||||
% When DCG expansion throws an exception – remove offending term and rethrow.
|
||||
user:term_expansion(throw_dcg_expansion_error(E), _) :-
|
||||
throw(E).
|
||||
user:term_expansion(Term0, Term) :-
|
||||
nonvar(Term0),
|
||||
catch(dcg_rule(Term0, Term), E, Term = throw_dcg_expansion_error(E)).
|
||||
|
||||
%% seq(Seq)//
|
||||
%
|
||||
% Describes a sequence
|
||||
seq(Xs, Cs0,Cs) :-
|
||||
var(Xs),
|
||||
Cs0 == [],
|
||||
!,
|
||||
Xs = [],
|
||||
Cs0 = Cs.
|
||||
seq([]) --> [].
|
||||
seq([E|Es]) --> [E], seq(Es).
|
||||
|
||||
%% seqq(SeqOfSeqs)//
|
||||
%
|
||||
% Describes a sequence of sequences
|
||||
seqq([]) --> [].
|
||||
seqq([Es|Ess]) --> seq(Es), seqq(Ess).
|
||||
|
||||
%% ...//
|
||||
%
|
||||
% Describes an arbitrary number of elements
|
||||
...(Cs0,Cs) :-
|
||||
Cs0 == [],
|
||||
!,
|
||||
Cs0 = Cs.
|
||||
... --> [] | [_], ... .
|
||||
|
||||
% defer instantiation errors until runtime. instantiations may be made
|
||||
% then.
|
||||
error_goal(error(instantiation_error, _Context), _).
|
||||
error_goal(error(E, must_be/2), error(E, must_be/2)).
|
||||
error_goal(error(E, (=..)/2), error(E, (=..)/2)).
|
||||
error_goal(error(representation_error(dcg_body), Context),
|
||||
error(representation_error(dcg_body), Context)).
|
||||
error_goal(E, _) :- throw(E).
|
||||
|
||||
user:goal_expansion(phrase(GRBody, S, S0), GRBody2) :-
|
||||
loader:strip_module(GRBody, M, GRBody0),
|
||||
nonvar(GRBody0),
|
||||
catch(dcgs:dcg_body(GRBody0, S, S0, GRBody1),
|
||||
E,
|
||||
dcgs:error_goal(E, GRBody1)
|
||||
),
|
||||
( E = error(instantiation_error, _),
|
||||
GRBody0 = [T|Ts] ->
|
||||
GRBody2 = (error:must_be(list, [T|Ts]),
|
||||
lists:append([T|Ts], S0, S))
|
||||
; GRBody = (_:_) ->
|
||||
GRBody2 = M:GRBody1
|
||||
; GRBody2 = GRBody1
|
||||
).
|
||||
|
||||
user:goal_expansion(phrase(GRBody, S), phrase(GRBody, S, [])).
|
||||
|
||||
|
||||
% (-->)/2 behaves as if it didn't exist. We export (and define) it
|
||||
% only so that clauses for (-->)/2 cannot be asserted when
|
||||
% library(dcgs) is loaded.
|
||||
|
||||
(_-->_) :- throw(error(existence_error(procedure,(-->)/2),(-->)/2)).
|
||||
dcg_rule(Term0, (Head :- Body)),
|
||||
Term = (Head :- Body).
|
||||
|
||||
@@ -1,22 +1,4 @@
|
||||
/** Declarative debugging.
|
||||
|
||||
This library provides three predicates with associated operators.
|
||||
The operators can be placed in front of goals to debug Prolog
|
||||
programs.
|
||||
|
||||
Of these predicates, the most frequently used is `(*)/1`, with
|
||||
associated prefix operator `*` (star). Placing `*` in front of a
|
||||
goal means to _generalize away_ the goal. `* Goal` acts as if `Goal`
|
||||
did not appear at all in the source code. It is declaratively
|
||||
equivalent to _commenting out_ the goal, and easier to write,
|
||||
because `*` can also be placed in front of the last goal in a clause
|
||||
without any additional changes.
|
||||
|
||||
Source: [https://stackoverflow.com/a/30791637](https://stackoverflow.com/a/30791637)
|
||||
|
||||
*/
|
||||
|
||||
|
||||
% Source: https://stackoverflow.com/a/30791637
|
||||
|
||||
:- module(debug, [
|
||||
op(900, fx, $),
|
||||
@@ -29,28 +11,12 @@
|
||||
|
||||
:- use_module(library(format), [portray_clause/1]).
|
||||
|
||||
:- meta_predicate(*(0)).
|
||||
:- meta_predicate($(0)).
|
||||
:- meta_predicate($-(0)).
|
||||
|
||||
%% $-(Goal)
|
||||
%
|
||||
% Portray exceptions thrown by Goal.
|
||||
|
||||
$-(G_0) :-
|
||||
catch(G_0, Ex, ( portray_clause(exception:Ex:G_0), throw(Ex) ) ).
|
||||
|
||||
%% $(Goal)
|
||||
%
|
||||
% Provide a _trace_ for calls of Goal.
|
||||
|
||||
$(G_0) :-
|
||||
portray_clause(call:G_0),
|
||||
$-G_0,
|
||||
portray_clause(exit:G_0).
|
||||
|
||||
%% *(Goal)
|
||||
%
|
||||
% Generalize away Goal.
|
||||
|
||||
*(_).
|
||||
|
||||
185
src/lib/diag.pl
185
src/lib/diag.pl
@@ -1,187 +1,14 @@
|
||||
:- module(diag, [wam_instructions/2, inlined_instructions/2]).
|
||||
|
||||
/** Diagnostics library
|
||||
|
||||
The predicate `wam_instructions/2` _decompiles_ a predicate so that
|
||||
we can inspect its Warren Abstract Machine (WAM) instructions.
|
||||
In this way, we can verify and reason about compiled programs,
|
||||
and detect opportunities for optimization.
|
||||
|
||||
For example, we have:
|
||||
|
||||
```
|
||||
?- use_module(library(lists)).
|
||||
true.
|
||||
?- use_module(library(diag)).
|
||||
true.
|
||||
?- use_module(library(format)).
|
||||
true.
|
||||
?- wam_instructions(append/3, Is),
|
||||
maplist(portray_clause, Is).
|
||||
switch_on_term(1,external(1),external(2),external(6),fail).
|
||||
try_me_else(4).
|
||||
get_constant(level(shallow),[],x(1)).
|
||||
get_value(x(2),3).
|
||||
proceed.
|
||||
trust_me(0).
|
||||
get_list(level(shallow),x(1)).
|
||||
unify_variable(x(4)).
|
||||
unify_variable(x(1)).
|
||||
get_list(level(shallow),x(3)).
|
||||
unify_value(x(4)).
|
||||
unify_variable(x(3)).
|
||||
execute(append,3).
|
||||
Is = [switch_on_term(1,external(1),external(2),external(6),fail)|...].
|
||||
```
|
||||
|
||||
`inlined_instructions/2` decompiles predicates at the code offset in
|
||||
its first argument.
|
||||
|
||||
For example, given the program
|
||||
|
||||
```
|
||||
?- [user].
|
||||
:- use_module(library(clpz)).
|
||||
|
||||
all_eq(Vs, E) :- maplist(#=(E), Vs).
|
||||
|
||||
```
|
||||
|
||||
we inspect the code of `all_eqs/2` using `wam_instructions/2`,
|
||||
revealing:
|
||||
|
||||
```
|
||||
?- wam_instructions(all_eq/2, Is),
|
||||
maplist(portray_clause, Is).
|
||||
put_structure('$aux',2,x(3)).
|
||||
set_local_value(x(2)).
|
||||
set_void(1).
|
||||
set_constant('$index_ptr'(115334)).
|
||||
get_variable(x(4),1).
|
||||
put_structure(:,2,x(1)).
|
||||
set_constant(user).
|
||||
set_local_value(x(3)).
|
||||
get_variable(x(5),2).
|
||||
put_value(x(4),2).
|
||||
execute(maplist,2).
|
||||
Is = [put_structure('$aux',2,x(3)),set_local_value(x(2)),set_void(1),set_constant('$index_ptr'(115334)),get_variable(x(4),1),put_structure(:,2,x(1)),set_constant(user),set_local_value(x(3)),get_variable(x(5),2),put_value(x(4),2),execute(maplist,2)].
|
||||
```
|
||||
|
||||
The `'$index_ptr(115334)` functor gives a code offset to an inlined
|
||||
predicate compiled for the use of maplist/2. `inlined_instructions/2`
|
||||
can be used to decompile its source code:
|
||||
|
||||
```
|
||||
?- inlined_instructions(115334, Is),
|
||||
maplist(portray_clause, Is).
|
||||
allocate(1).
|
||||
get_level(y(1)).
|
||||
get_variable(x(5),2).
|
||||
put_value(x(3),2).
|
||||
get_variable(x(6),3).
|
||||
put_value(x(5),3).
|
||||
put_unsafe_value(1,4).
|
||||
deallocate.
|
||||
jmp_by_execute(1).
|
||||
try_me_else(8).
|
||||
call(integer,1).
|
||||
neck_cut.
|
||||
get_variable(x(5),1).
|
||||
put_value(x(2),1).
|
||||
get_variable(x(6),2).
|
||||
put_value(x(5),2).
|
||||
jmp_by_execute(7).
|
||||
try_me_else(12).
|
||||
allocate(3).
|
||||
get_level(y(1)).
|
||||
get_variable(y(3),1).
|
||||
get_variable(y(2),2).
|
||||
call_default(true,0).
|
||||
call(var,1).
|
||||
cut(y(1)).
|
||||
put_unsafe_value(3,1).
|
||||
put_unsafe_value(2,2).
|
||||
deallocate.
|
||||
execute_default(is,2).
|
||||
default_retry_me_else(4).
|
||||
call(integer,1).
|
||||
neck_cut.
|
||||
execute(=:=,2).
|
||||
default_trust_me(0).
|
||||
allocate(2).
|
||||
get_variable(y(1),1).
|
||||
get_variable(y(2),3).
|
||||
put_value(y(2),1).
|
||||
call_default(is,2).
|
||||
put_unsafe_value(2,1).
|
||||
put_unsafe_value(1,2).
|
||||
deallocate.
|
||||
execute_default(clpz_equal,2).
|
||||
default_retry_me_else(4).
|
||||
call(integer,1).
|
||||
neck_cut.
|
||||
jmp_by_execute(29).
|
||||
try_me_else(12).
|
||||
allocate(3).
|
||||
get_level(y(1)).
|
||||
get_variable(y(3),1).
|
||||
get_variable(y(2),2).
|
||||
call_default(true,0).
|
||||
call(var,1).
|
||||
cut(y(1)).
|
||||
put_unsafe_value(3,1).
|
||||
put_unsafe_value(2,2).
|
||||
deallocate.
|
||||
execute_default(is,2).
|
||||
default_trust_me(0).
|
||||
allocate(2).
|
||||
get_variable(y(2),1).
|
||||
get_variable(y(1),3).
|
||||
put_value(y(1),1).
|
||||
call_default(is,2).
|
||||
put_unsafe_value(2,1).
|
||||
put_unsafe_value(1,2).
|
||||
deallocate.
|
||||
execute_default(clpz_equal,2).
|
||||
default_trust_me(0).
|
||||
execute_default(clpz_equal,2).
|
||||
Is = [allocate(1),get_level(y(1)),get_variable(x(5),2),put_value(x(3),2),get_variable(x(6),3),put_value(x(5),3),put_unsafe_value(1,4),deallocate,jmp_by_execute(1),try_me_else(8),call(integer,1),neck_cut,get_variable(x(5),1),put_value(x(2),1),get_variable(x(6),2),put_value(x(5),2),jmp_by_execute(7),try_me_else(12),allocate(3),get_level(...),...].
|
||||
```
|
||||
*/
|
||||
|
||||
:- module(diag, [wam_instructions/2]).
|
||||
|
||||
:- use_module(library(error)).
|
||||
|
||||
%% wam_instructions(+PI, -Instrs)
|
||||
%
|
||||
% _Instrs_ are the WAM instructions corresponding to predicate indicator _PI_.
|
||||
|
||||
wam_instructions(Clause, Listing) :-
|
||||
( nonvar(Clause) ->
|
||||
( Clause = Name / Arity ->
|
||||
fetch_instructions(user, Name, Arity, Listing)
|
||||
; Clause = Module : (Name / Arity) ->
|
||||
fetch_instructions(Module, Name, Arity, Listing)
|
||||
Clause = Name / Arity,
|
||||
must_be(atom, Name),
|
||||
must_be(integer, Arity),
|
||||
( Arity >= 0 -> '$wam_instructions'(Name, Arity, Listing)
|
||||
; throw(error(domain_error(not_less_than_zero, Arity), wam_instructions/2))
|
||||
)
|
||||
; throw(error(instantiation_error, wam_instructions/2))
|
||||
).
|
||||
|
||||
%% inlined_instructions(+IndexPtr, -Instrs)
|
||||
%
|
||||
% _Instrs_ are the WAM instructions corresponding to code offset _IndexPtr_.
|
||||
|
||||
inlined_instructions(IndexPtr, Listing) :-
|
||||
must_be(integer, IndexPtr),
|
||||
( IndexPtr >= 0 ->
|
||||
'$inlined_instructions'(IndexPtr, Listing)
|
||||
; throw(error(domain_error(not_less_than_zero, IndexPtr), inlined_instructions/2))
|
||||
).
|
||||
|
||||
fetch_instructions(Module, Name, Arity, Listing) :-
|
||||
must_be(atom, Module),
|
||||
must_be(atom, Name),
|
||||
must_be(integer, Arity),
|
||||
( Arity >= 0 ->
|
||||
'$wam_instructions'(Module, Name, Arity, Listing)
|
||||
; throw(error(domain_error(not_less_than_zero, Arity), wam_instructions/2))
|
||||
).
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
/**
|
||||
Provides predicate `dif/2`. `dif/2` is a constraint that is true only if both of its
|
||||
arguments are different terms.
|
||||
*/
|
||||
|
||||
:- module(dif, [dif/2]).
|
||||
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(lists), [append/3, maplist/3]).
|
||||
:- use_module(library(lists), [append/3]).
|
||||
|
||||
:- attribute dif/1.
|
||||
|
||||
put_dif_att(Var, X, Y) :-
|
||||
( get_atts(Var, +dif(Z)) ->
|
||||
sort([X \== Y | Z], NewZ),
|
||||
put_atts(Var, +dif(NewZ))
|
||||
sort([X \== Y | Z], NewZ),
|
||||
put_atts(Var, +dif(NewZ))
|
||||
; put_atts(Var, +dif([X \== Y]))
|
||||
).
|
||||
|
||||
@@ -23,85 +18,38 @@ dif_set_variables([Var|Vars], X, Y) :-
|
||||
put_dif_att(Var, X, Y),
|
||||
dif_set_variables(Vars, X, Y).
|
||||
|
||||
remove_goal([], _, []).
|
||||
remove_goal([G0|G0s], Goal0, Goals) :-
|
||||
( G0 == Goal0 ->
|
||||
remove_goal(G0s, Goal0, Goals)
|
||||
; Goals = [G0|Goals1],
|
||||
remove_goal(G0s, Goal0, Goals1)
|
||||
).
|
||||
|
||||
vars_remove_goal([], _).
|
||||
vars_remove_goal([Var|Vars], Goal0) :-
|
||||
( get_atts(Var, +dif(Goals0)) ->
|
||||
remove_goal(Goals0, Goal0, Goals),
|
||||
( Goals = [] ->
|
||||
put_atts(Var, -dif(_))
|
||||
; put_atts(Var, +dif(Goals))
|
||||
)
|
||||
; true
|
||||
),
|
||||
vars_remove_goal(Vars, Goal0).
|
||||
|
||||
reinforce_goal(Goal0, Goal) :-
|
||||
Goal = (
|
||||
term_variables(Goal0, Vars),
|
||||
dif:vars_remove_goal(Vars, Goal0),
|
||||
Goal0 = (L \== R),
|
||||
dif:dif(L, R)
|
||||
).
|
||||
|
||||
append_goals([], _).
|
||||
append_goals([Var|Vars], Goals) :-
|
||||
( get_atts(Var, +dif(VarGoals)) ->
|
||||
append(Goals, VarGoals, NewGoals0),
|
||||
sort(NewGoals0, NewGoals)
|
||||
append(Goals, VarGoals, NewGoals0),
|
||||
sort(NewGoals0, NewGoals)
|
||||
; NewGoals = Goals
|
||||
),
|
||||
put_atts(Var, +dif(NewGoals)),
|
||||
append_goals(Vars, Goals).
|
||||
|
||||
verify_attributes(Var, Value, Goals) :-
|
||||
( get_atts(Var, +dif(Goals0)) ->
|
||||
term_variables(Value, ValueVars),
|
||||
append_goals(ValueVars, Goals0),
|
||||
maplist(reinforce_goal, Goals0, Goals)
|
||||
( get_atts(Var, +dif(Goals)) ->
|
||||
term_variables(Value, ValueVars),
|
||||
append_goals(ValueVars, Goals)
|
||||
; Goals = []
|
||||
).
|
||||
|
||||
%% dif(?X, ?Y).
|
||||
%
|
||||
% True iff X and Y are different terms. Unlike `\=/2`, `dif/2` is more declarative because if X and Y can
|
||||
% unify but they're not yet equal, the decision is delayed, and prevents X and Y to become equal later.
|
||||
% Examples:
|
||||
%
|
||||
% ```
|
||||
% ?- dif(a, a).
|
||||
% false.
|
||||
% ?- dif(a, b).
|
||||
% true.
|
||||
% ?- dif(X, b).
|
||||
% dif:dif(X,b).
|
||||
% ?- dif(X, b), X = b.
|
||||
% false.
|
||||
% ```
|
||||
dif(X, Y) :-
|
||||
X \== Y,
|
||||
( X \= Y -> true
|
||||
; term_variables(dif(X,Y), Vars),
|
||||
dif_set_variables(Vars, X, Y)
|
||||
).
|
||||
% Probably the world's worst dif/2 implementation. I'm open to
|
||||
% suggestions for improvement.
|
||||
|
||||
gather_dif_goals(_, []) --> [].
|
||||
gather_dif_goals(V, [(X \== Y) | Goals]) -->
|
||||
( { term_variables(X-Y, [V0 | _]),
|
||||
V == V0 } ->
|
||||
[dif:dif(X, Y)]
|
||||
; []
|
||||
),
|
||||
gather_dif_goals(V, Goals).
|
||||
dif(X, Y) :- X \== Y,
|
||||
( term_variables(X, XVars), term_variables(Y, YVars),
|
||||
dif_set_variables(XVars, X, Y),
|
||||
dif_set_variables(YVars, X, Y)
|
||||
).
|
||||
|
||||
gather_dif_goals([]) --> [].
|
||||
gather_dif_goals([(X \== Y) | Goals]) -->
|
||||
[dif(X, Y)],
|
||||
gather_dif_goals(Goals).
|
||||
|
||||
attribute_goals(X) -->
|
||||
{ get_atts(X, +dif(Goals)) },
|
||||
gather_dif_goals(X, Goals),
|
||||
gather_dif_goals(Goals),
|
||||
{ put_atts(X, -dif(_)) }.
|
||||
|
||||
252
src/lib/error.pl
252
src/lib/error.pl
@@ -1,49 +1,35 @@
|
||||
:- module(error, [must_be/2,
|
||||
can_be/2,
|
||||
instantiation_error/1,
|
||||
domain_error/3,
|
||||
type_error/3
|
||||
]).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2018-2025 by Markus Triska (triska@metalevel.at)
|
||||
Written September 2018 by Markus Triska (triska@metalevel.at)
|
||||
I place this code in the public domain. Use it in any way you want.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
:- module(error, [must_be/2,
|
||||
can_be/2,
|
||||
instantiation_error/0,
|
||||
domain_error/2,
|
||||
type_error/2,
|
||||
representation_error/1,
|
||||
resource_error/1,
|
||||
instantiation_error/1,
|
||||
domain_error/3,
|
||||
type_error/3,
|
||||
call_with_error_context/2
|
||||
]).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
must_be(Type, Term)
|
||||
|
||||
:- meta_predicate(check_(1, ?, ?)).
|
||||
This predicate is intended for type-checks of built-in predicates.
|
||||
|
||||
It asserts that Term is:
|
||||
|
||||
%% must_be(Type, Term)
|
||||
%
|
||||
% This predicate is intended for type-checks of built-in predicates.
|
||||
%
|
||||
% It asserts that Term is:
|
||||
%
|
||||
% 1) instantiated *and*
|
||||
% 2) instantiated to an instance of the given Type.
|
||||
%
|
||||
% It corresponds to usage mode +Term.
|
||||
%
|
||||
% Currently, the following types are supported:
|
||||
%
|
||||
% - atom
|
||||
% - boolean
|
||||
% - character
|
||||
% - chars
|
||||
% - in_character
|
||||
% - integer
|
||||
% - list
|
||||
% - octet_character
|
||||
% - octet_chars
|
||||
% - pair
|
||||
% - term
|
||||
1) instantiated *and*
|
||||
2) instantiated to an instance of the given Type.
|
||||
|
||||
It corresponds to usage mode +Term.
|
||||
|
||||
Currently, the following types are supported:
|
||||
|
||||
- integer
|
||||
- atom
|
||||
- list
|
||||
- boolean
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
must_be(Type, Term) :-
|
||||
must_be_(type, Type),
|
||||
@@ -54,57 +40,14 @@ must_be_(Type, _) :-
|
||||
instantiation_error(must_be/2).
|
||||
must_be_(var, Term) :-
|
||||
( var(Term) -> true
|
||||
; throw(error(uninstantiation_error(Term), must_be/2))
|
||||
; throw(error(uninstantiation_error, must_be/2))
|
||||
).
|
||||
must_be_(integer, Term) :- check_(integer, integer, Term).
|
||||
must_be_(not_less_than_zero, N) :-
|
||||
must_be(integer, N),
|
||||
( N >= 0 -> true
|
||||
; domain_error(not_less_than_zero, N, must_be/2)
|
||||
).
|
||||
must_be_(atom, Term) :- check_(atom, atom, Term).
|
||||
must_be_(character, T) :- check_(error:character, character, T).
|
||||
must_be_(in_character, T) :- check_(error:in_character, in_character, T).
|
||||
must_be_(chars, Ls) :-
|
||||
can_be(chars, Ls), % prioritize type errors over instantiation errors
|
||||
must_be(list, Ls),
|
||||
( '$is_partial_string'(Ls) ->
|
||||
% The expected case (success) uses a very fast test.
|
||||
% We cannot use partial_string/1 from library(iso_ext),
|
||||
% because that library itself imports library(error).
|
||||
true
|
||||
; all_characters(Ls)
|
||||
).
|
||||
must_be_(octet_character, C) :-
|
||||
must_be(character, C),
|
||||
( octet_character(C) -> true
|
||||
; domain_error(octet_character, C, must_be/2)
|
||||
).
|
||||
must_be_(octet_chars, Cs) :-
|
||||
must_be(chars, Cs),
|
||||
( '$first_non_octet'(Cs, C) ->
|
||||
domain_error(octet_character, C, must_be/2)
|
||||
; true
|
||||
).
|
||||
must_be_(list, Term) :- check_(error:ilist, list, Term).
|
||||
must_be_(type, Term) :- check_(error:type, type, Term).
|
||||
must_be_(boolean, Term) :- check_(error:boolean, boolean, Term).
|
||||
must_be_(pair, Term) :- check_(error:pair, pair, Term).
|
||||
must_be_(term, Term) :-
|
||||
( acyclic_term(Term) ->
|
||||
( ground(Term) -> true
|
||||
; instantiation_error(must_be/2)
|
||||
)
|
||||
; type_error(term, Term, must_be/2)
|
||||
).
|
||||
|
||||
% We cannot use maplist(must_be(character), Cs), because library(lists)
|
||||
% uses library(error), so importing it would create a cyclic dependency.
|
||||
|
||||
all_characters([]).
|
||||
all_characters([C|Cs]) :-
|
||||
must_be(character, C),
|
||||
all_characters(Cs).
|
||||
must_be_(character, T) :- check_(character, character, T).
|
||||
must_be_(list, Term) :- check_(ilist, list, Term).
|
||||
must_be_(type, Term) :- check_(type, type, Term).
|
||||
must_be_(boolean, Term) :- check_(boolean, boolean, Term).
|
||||
|
||||
check_(Pred, Type, Term) :-
|
||||
( var(Term) -> instantiation_error(must_be/2)
|
||||
@@ -112,56 +55,37 @@ check_(Pred, Type, Term) :-
|
||||
; type_error(Type, Term, must_be/2)
|
||||
).
|
||||
|
||||
|
||||
pair(_-_).
|
||||
|
||||
boolean(B) :- ( B == true ; B == false ).
|
||||
|
||||
character(C) :-
|
||||
atom(C),
|
||||
atom_length(C, 1).
|
||||
|
||||
octet_character(C) :-
|
||||
char_code(C, Code),
|
||||
0 =< Code, Code =< 0xff.
|
||||
|
||||
in_character(C) :-
|
||||
( character(C)
|
||||
; C == end_of_file
|
||||
).
|
||||
|
||||
ilist(Ls) :-
|
||||
'$skip_max_list'(_, _, Ls, Rs),
|
||||
( var(Rs) ->
|
||||
instantiation_error(must_be/2)
|
||||
; Rs == []
|
||||
).
|
||||
ilist(V) :- var(V), instantiation_error(must_be/2).
|
||||
ilist([]).
|
||||
ilist([_|Ls]) :- ilist(Ls).
|
||||
|
||||
type(type).
|
||||
type(integer).
|
||||
type(atom).
|
||||
type(character).
|
||||
type(in_character).
|
||||
type(octet_character).
|
||||
type(octet_chars).
|
||||
type(chars).
|
||||
type(list).
|
||||
type(var).
|
||||
type(boolean).
|
||||
type(term).
|
||||
type(not_less_than_zero).
|
||||
type(pair).
|
||||
|
||||
%% can_be(Type, Term)
|
||||
%
|
||||
% This predicate is intended for type-checks of built-in predicates.
|
||||
%
|
||||
% It asserts that there is a substitution which, if applied to Term,
|
||||
% makes it an instance of Type.
|
||||
%
|
||||
% It corresponds to usage mode ?Term.
|
||||
%
|
||||
% It supports the same types as must_be/2.
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
can_be(Type, Term)
|
||||
|
||||
This predicate is intended for type-checks of built-in predicates.
|
||||
|
||||
It asserts that there is a substitution which, if applied to Term,
|
||||
makes it an instance of Type.
|
||||
|
||||
It corresponds to usage mode ?Term.
|
||||
|
||||
It supports the same types as must_be/2.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
|
||||
can_be(Type, Term) :-
|
||||
must_be(type, Type),
|
||||
@@ -171,79 +95,18 @@ can_be(Type, Term) :-
|
||||
).
|
||||
|
||||
can_(integer, Term) :- integer(Term).
|
||||
can_(not_less_than_zero, N) :-
|
||||
( integer(N) ->
|
||||
( N >= 0 -> true
|
||||
; domain_error(not_less_than_zero, N, can_be/2)
|
||||
)
|
||||
; type_error(integer, N, can_be/2)
|
||||
).
|
||||
can_(atom, Term) :- atom(Term).
|
||||
can_(character, T) :- character(T).
|
||||
can_(in_character, T) :- in_character(T).
|
||||
can_(chars, Ls) :-
|
||||
( '$is_partial_string'(Ls) -> true
|
||||
; can_be(list, Ls),
|
||||
can_be_chars(Ls)
|
||||
).
|
||||
can_(octet_character, C) :-
|
||||
( octet_character(C) -> true
|
||||
; domain_error(octet_character, C, can_be/2)
|
||||
).
|
||||
can_(octet_chars, Cs) :-
|
||||
can_be(chars, Cs),
|
||||
( '$skip_max_list'(_, _, Cs, []), % temporarily turn Cs into a list
|
||||
'$first_non_octet'(Cs, C) ->
|
||||
domain_error(octet_character, C, can_be/2)
|
||||
; true
|
||||
).
|
||||
can_(list, Term) :- list_or_partial_list(Term).
|
||||
can_(boolean, Term) :- boolean(Term).
|
||||
can_(pair, Term) :- pair(Term).
|
||||
can_(term, Term) :-
|
||||
( acyclic_term(Term) ->
|
||||
true
|
||||
; type_error(term, Term, can_be/2)
|
||||
).
|
||||
|
||||
can_be_chars(Var) :- var(Var), !.
|
||||
can_be_chars([]).
|
||||
can_be_chars([X|Xs]) :-
|
||||
can_be(character, X),
|
||||
can_be_chars(Xs).
|
||||
|
||||
list_or_partial_list(Ls) :-
|
||||
'$skip_max_list'(_, _, Ls, Rs),
|
||||
( var(Rs) -> true
|
||||
; Rs == []
|
||||
).
|
||||
list_or_partial_list(Var) :- var(Var).
|
||||
list_or_partial_list([]).
|
||||
list_or_partial_list([_|Ls]) :-
|
||||
list_or_partial_list(Ls).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Shorthands for throwing ISO errors.
|
||||
|
||||
The variants without context promote the use of
|
||||
call_with_error_context/2.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
instantiation_error :-
|
||||
throw(error(instantiation_error, [])).
|
||||
|
||||
domain_error(Type, Term) :-
|
||||
throw(error(domain_error(Type, Term), [])).
|
||||
|
||||
type_error(Type, Term) :-
|
||||
throw(error(type_error(Type, Term), [])).
|
||||
|
||||
representation_error(Flag) :-
|
||||
throw(error(representation_error(Flag), [])).
|
||||
|
||||
resource_error(Resource) :-
|
||||
throw(error(resource_error(Resource), [])).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
The variants *with* context would not have been needed if
|
||||
call_with_error_context/2 had been found earlier. In the future,
|
||||
we may be able to remove them.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
instantiation_error(Context) :-
|
||||
@@ -254,20 +117,3 @@ domain_error(Type, Term, Context) :-
|
||||
|
||||
type_error(Type, Term, Context) :-
|
||||
throw(error(type_error(Type, Term), Context)).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
call_with_error_context/2
|
||||
|
||||
See https://github.com/mthom/scryer-prolog/discussions/2839 .
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% call_with_error_context(+Goal, +Pair)
|
||||
%
|
||||
% Call _Goal_ with error context _Pair_.
|
||||
%
|
||||
% Examples of error contexts: `predicate-PI`, `file-Filename` etc.
|
||||
|
||||
:- meta_predicate(call_with_error_context(0,+)).
|
||||
call_with_error_context(G_0, Pair) :-
|
||||
must_be(pair, Pair),
|
||||
catch(G_0, error(E,Pairs), throw(error(E,[Pair|Pairs]))).
|
||||
|
||||
259
src/lib/ffi.pl
259
src/lib/ffi.pl
@@ -1,259 +0,0 @@
|
||||
:- module(ffi, [use_foreign_module/2, foreign_struct/2, with_locals/2, allocate/4, deallocate/3, read_ptr/3, array_type/3]).
|
||||
|
||||
/** Foreign Function Interface
|
||||
|
||||
This module contains predicates used to call native code (exposed by the C ABI).
|
||||
It uses [libffi](https://sourceware.org/libffi/) under the hood. The bridge is very simple
|
||||
and is very unsafe and should be used with care. FFI isn't the only way to communicate with
|
||||
the outside world in Prolog: sockets, pipes and HTTP may be good enough for your use case.
|
||||
|
||||
The main predicate is `use_foreign_module/2`. It takes a library name (which depending on the
|
||||
operating system could be a `.so`, `.dylib` or `.dll` file). and a list of functions. Each
|
||||
function is defined by its name, a list of the type of the arguments, and the return argument.
|
||||
|
||||
For each function in the list a predicate of the same name is generated in the ffi module which
|
||||
can then be used to call the native code.
|
||||
The predicates arguments are the input arguments of the foreign function and depending on the return type an extra argument for the return value.
|
||||
Functions with return type `void` or `bool` don't have this extra argument.
|
||||
Predicates for functions with return type `void` always succeed.
|
||||
Predicates for functions with retun type `bool` succeed iff the return value is 1.
|
||||
|
||||
```
|
||||
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN, -ReturnArg). % for all return types except void and bool
|
||||
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN). % for void and bool
|
||||
```
|
||||
|
||||
## Available types are
|
||||
|
||||
### Basic C Types
|
||||
|
||||
[C Types Reference](https://en.cppreference.com/w/c/language/types.html)
|
||||
|
||||
- `void`,
|
||||
- `char`, `uchar`, `schar`
|
||||
- `short`, `ushort`
|
||||
- `int`, `uint`
|
||||
- `long`, `ulong`,
|
||||
- `longlong`, `ulonglong`,
|
||||
- `float`, `double`
|
||||
|
||||
### Fixed Width Integer Types
|
||||
|
||||
[C Fixed With Integer Types Reference](https://en.cppreference.com/w/c/types/integer.html)
|
||||
|
||||
- `sint8`/`i8`, `uint8`/`u8`,
|
||||
- `sint16`/`i16`, `uint16`/`u16`,
|
||||
- `sint32`/`i32`, `uint32`/`u32`,
|
||||
- `sint64`/`i64`, `uint64`/`u64`,
|
||||
|
||||
### Fixed Width Floating-Point Types
|
||||
|
||||
[C++ Fixed Width Floating-Point Types Reference](https://en.cppreference.com/w/cpp/types/floating-point.html)
|
||||
|
||||
- `f32`, `f64`
|
||||
|
||||
### Other Types
|
||||
|
||||
- `cstr`,
|
||||
- `ptr`,
|
||||
- `bool` and,
|
||||
- custom structs, which can be defined with `foreign_struct/2`.
|
||||
|
||||
### Notes regarding bool
|
||||
|
||||
- Not necessarily compatible with the fundamental C type bool.
|
||||
- Same as `i8` but only values 0 and 1 are valid values.
|
||||
|
||||
### Notes regarding cstr
|
||||
|
||||
- When using `cstr` as an argument type the string will be deallocated once the function returns.
|
||||
- When using `cstr` as a return type the string will be copied and won't be deallocated.
|
||||
- When an ffi function returns bytes that are not a valid utf8-string the bytes will be turned into a list of `codes` (integers)
|
||||
instead of a string (list of `chars`). Note: passing a list of `codes` is not accepted in argument position.
|
||||
- In argument position you can also pass a pointer directly instead of a string,
|
||||
e.g. to pass a null-pointer one can provide the integer 0 as the argument.
|
||||
- In return position a null-pointer will be returned as the integer 0
|
||||
|
||||
## Example
|
||||
|
||||
For example, let's see how to define a function from the [raylib](https://www.raylib.com/) library.
|
||||
|
||||
```
|
||||
?- use_foreign_module("./libraylib.so", ['InitWindow'([int, int, cstr], void)]).
|
||||
```
|
||||
|
||||
This creates a `'InitWindow'` predicate under the ffi module. Now, we can call it:
|
||||
|
||||
```
|
||||
?- ffi:'InitWindow'(800, 600, "Scryer Prolog + Raylib").
|
||||
```
|
||||
|
||||
And a new window should pop up!
|
||||
*/
|
||||
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(format)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(iso_ext)).
|
||||
|
||||
%% foreign_struct(+Name, +Elements).
|
||||
%
|
||||
% Defines a new struct type with name Name, composed of the elements Elements, which is a list
|
||||
% of other types.
|
||||
%
|
||||
% The name of the types doesn't matter, but the order of Elements must match the ones in the
|
||||
% native code.
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- foreign_struct(color, [uint8, uint8, uint8, uint8]).
|
||||
% ```
|
||||
foreign_struct(Name, Elements) :-
|
||||
'$define_foreign_struct'(Name, Elements).
|
||||
|
||||
|
||||
%% use_foreign_module(+LibName, +Predicates)
|
||||
%
|
||||
% - LibName the path to the shared library to load/bind
|
||||
% - Predicates list of function definitions
|
||||
%
|
||||
% Each function definition is a functor of arity 2.
|
||||
% The functor name is the name of the function to bind,
|
||||
% the first argument is the list of arguments of the function,
|
||||
% the second argument is the return type of the function.
|
||||
%
|
||||
% This will define a predicate in the ffi module with the defined name,
|
||||
% for void and bool return type functions the arity will match the length of the arguments list,
|
||||
% for other return types there will be an additional out parameter.
|
||||
%
|
||||
use_foreign_module(LibName, Predicates) :-
|
||||
'$load_foreign_lib'(LibName, Predicates),
|
||||
maplist(assert_predicate, Predicates).
|
||||
|
||||
assert_predicate(PredicateDefinition) :-
|
||||
PredicateDefinition =.. [Name, Inputs, void],
|
||||
length(Inputs, NumInputs),
|
||||
functor(Head, Name, NumInputs),
|
||||
term_variables(Head, TermList),
|
||||
Body = (
|
||||
'$foreign_call'(Name, TermList, _),!
|
||||
),
|
||||
Predicate = (Head:-Body),
|
||||
assertz(ffi:Predicate).
|
||||
|
||||
assert_predicate(PredicateDefinition) :-
|
||||
PredicateDefinition =.. [Name, Inputs, bool],
|
||||
length(Inputs, NumInputs),
|
||||
functor(Head, Name, NumInputs),
|
||||
term_variables(Head, TermList),
|
||||
Body = (
|
||||
'$foreign_call'(Name, TermList, 1),!
|
||||
),
|
||||
Predicate = (Head:-Body),
|
||||
assertz(ffi:Predicate).
|
||||
|
||||
assert_predicate(PredicateDefinition) :-
|
||||
PredicateDefinition =.. [Name, Inputs, Return],
|
||||
\+ member(Return, [void, bool]),
|
||||
length(Inputs, NumInputs),
|
||||
NumArgs is NumInputs + 1,
|
||||
functor(Head, Name, NumArgs),
|
||||
term_variables(Head, TermList),
|
||||
Body = (
|
||||
lists:append(TermListInputs, [TermListReturn], TermList),
|
||||
'$foreign_call'(Name, TermListInputs, TermListReturn),!
|
||||
),
|
||||
Predicate = (Head:-Body),
|
||||
assertz(ffi:Predicate).
|
||||
|
||||
%% allocate(+Allocator, +Type, +Args, -Ptr)
|
||||
%
|
||||
% Using the Allocator allocate Type initialized with Args and
|
||||
% unify Ptr with a pointer to that allocation.
|
||||
%
|
||||
allocate(Allocator, Type, Args, Ptr) :-
|
||||
must_be(var, Ptr),
|
||||
must_be(atom, Type),
|
||||
must_be(atom, Allocator),
|
||||
'$ffi_allocate'(Allocator, Type, Args, Ptr).
|
||||
|
||||
|
||||
%% read_ptr(+Type, +Ptr, -Value)
|
||||
%
|
||||
% Read a value of Type from the pointer Ptr and unify the read value with Value
|
||||
%
|
||||
% For type cstr read a nul-terminated utf-8 string starting at Ptr.
|
||||
%
|
||||
read_ptr(Type, Ptr, Value) :-
|
||||
must_be(atom, Type),
|
||||
must_be(integer, Ptr),
|
||||
'$ffi_read_ptr'(Type, Ptr, Value).
|
||||
|
||||
%% deallocate(+Allocator, +Type, +Ptr)
|
||||
%
|
||||
% Deallocate the allocation at Ptr of Type allocated with Allocator
|
||||
%
|
||||
deallocate(Allocator, Type, Ptr) :-
|
||||
must_be(atom, Allocator),
|
||||
must_be(integer, Ptr),
|
||||
'$ffi_deallocate'(Allocator, Type, Ptr).
|
||||
|
||||
:- dynamic(is_array_type_defined/1).
|
||||
|
||||
%% array_type(+ElemType, +Len, -ArrayType)
|
||||
%
|
||||
% unify the ffi type for an array of length Len with element type ElemType with ArrayType
|
||||
%
|
||||
array_type(ElemType, Len, ArrayType) :-
|
||||
(Len =< 0 -> domain_error(greater_than_zero, Len, array_type/3); true),
|
||||
phrase(format_("$[~a;~d]", [ElemType, Len]), ArrayTypeName),
|
||||
atom_chars(ArrayType, ArrayTypeName),
|
||||
(is_array_type_defined(ArrayType) -> true
|
||||
; length(Fields, Len),
|
||||
maplist(=(ElemType), Fields),
|
||||
foreign_struct(ArrayType, Fields),
|
||||
assertz(is_array_type_defined(ArrayType))
|
||||
).
|
||||
|
||||
:- meta_predicate(with_locals(?, 0)).
|
||||
|
||||
%% with_locals(+Locals, :Goal)
|
||||
%
|
||||
% Allocate the Locals, evaluate the Goal and deallocate the Locals.
|
||||
% The Locals will also be cleandup when Goal fails or throws an error.
|
||||
%
|
||||
% Locals is a list of local variable definitions `let(-Ptr, +Type, +Args)`.
|
||||
% Ptr will be unified with the pointer to the local of Type initialized with Args.
|
||||
%
|
||||
with_locals(Locals, Goal) :-
|
||||
verify_locals(Locals),
|
||||
setup_call_cleanup(
|
||||
allocate_locals(Locals),
|
||||
Goal,
|
||||
deallocate_locals(Locals)
|
||||
).
|
||||
|
||||
verify_locals(Locals) :-
|
||||
must_be(list, Locals),
|
||||
( maplist(verify_local, Locals) -> true
|
||||
; domain_error(locals_decl_list, Locals, [verify_locals/1])
|
||||
).
|
||||
|
||||
verify_local(let(Var, Type, Init)) :-
|
||||
must_be(var, Var),
|
||||
must_be(atom, Type),
|
||||
ground(Init).
|
||||
|
||||
allocate_locals([]).
|
||||
allocate_locals([let(Var, Type, Init) | Ls]) :-
|
||||
allocate(rust, Type, Init , Var),
|
||||
(catch(allocate_locals(Ls), E, (deallocate_locals([let(Var, Type, Init)]), throw(E))) -> true
|
||||
; deallocate_locals([let(Var, Type, Init)]), false
|
||||
).
|
||||
|
||||
deallocate_locals([]).
|
||||
deallocate_locals([let(Var, Type, _) | Ls]) :-
|
||||
deallocate(rust, Type, Var),
|
||||
deallocate_locals(Ls).
|
||||
237
src/lib/files.pl
237
src/lib/files.pl
@@ -1,24 +1,5 @@
|
||||
/** Predicates for reasoning about files and directories.
|
||||
|
||||
In this library, directories and files are represented as
|
||||
_lists of characters_. This is an ideal representation:
|
||||
|
||||
* Lists of characters can be conveniently reasoned about with DCGs
|
||||
and built-in Prolog predicates from `library(lists)`. This alone
|
||||
is already a very compelling argument to use them.
|
||||
* Other Scryer libraries such as `library(http/http_open)` also already
|
||||
use lists of characters to represent paths.
|
||||
* File names are mostly ephemeral, so it is good for efficiency
|
||||
that they can quickly allocated transiently on the heap, leaving the
|
||||
atom table mostly unaffected. Indexing is almost never needed
|
||||
for file names. If needed, it should be added to the engine.
|
||||
* The previous point is also good for security, since the system
|
||||
leaves little trace of which files were even accessed.
|
||||
* Scryer Prolog represents lists of characters extremely compactly.
|
||||
*/
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2020-2025 by Markus Triska (triska@metalevel.at)
|
||||
Written June 2020 by Markus Triska (triska@metalevel.at)
|
||||
Part of Scryer Prolog.
|
||||
|
||||
Predicates for reasoning about files and directories.
|
||||
@@ -70,11 +51,7 @@ _lists of characters_. This is an ideal representation:
|
||||
file_exists/1,
|
||||
directory_exists/1,
|
||||
delete_file/1,
|
||||
rename_file/2,
|
||||
file_copy/2,
|
||||
delete_directory/1,
|
||||
make_directory/1,
|
||||
make_directory_path/1,
|
||||
working_directory/2,
|
||||
path_canonical/2,
|
||||
path_segments/2,
|
||||
@@ -85,130 +62,66 @@ _lists of characters_. This is an ideal representation:
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(charsio)).
|
||||
:- use_module(library(dcgs)).
|
||||
|
||||
%% directory_files(+Directory, -Files).
|
||||
%
|
||||
% True if `Files` are the files *and* directories available at a specific
|
||||
% `Directory` in the current system.
|
||||
list_of_chars(Cs) :-
|
||||
must_be(list, Cs),
|
||||
maplist(must_be(character), Cs).
|
||||
|
||||
directory_files(Directory, Files) :-
|
||||
must_be(chars, Directory),
|
||||
list_of_chars(Directory),
|
||||
can_be(list, Files),
|
||||
'$directory_files'(Directory, Files).
|
||||
|
||||
%% file_size(+File, ?Size).
|
||||
%
|
||||
% True iff `Size` is the size (in bytes) of `File`. The file must exist.
|
||||
|
||||
file_size(File, Size) :-
|
||||
file_must_exist(File, file_size/2),
|
||||
list_of_chars(File),
|
||||
can_be(integer, Size),
|
||||
'$file_size'(File, Size).
|
||||
|
||||
%% file_exists(+File).
|
||||
%
|
||||
% True iff `File` is a file that exists in the current system.
|
||||
|
||||
file_exists(File) :-
|
||||
must_be(chars, File),
|
||||
list_of_chars(File),
|
||||
'$file_exists'(File).
|
||||
|
||||
%% directory_exists(+Directory).
|
||||
%
|
||||
% True iff `Directory` is a directory that exists in the current system.
|
||||
|
||||
directory_exists(Directory) :-
|
||||
must_be(chars, Directory),
|
||||
list_of_chars(Directory),
|
||||
'$directory_exists'(Directory).
|
||||
|
||||
%% make_directory(+Directory).
|
||||
%
|
||||
% Creates a new directory named `Directory`.
|
||||
% If you want to create a nested directory, use `make_directory_path/1`.
|
||||
|
||||
make_directory(Directory) :-
|
||||
must_be(chars, Directory),
|
||||
list_of_chars(Directory),
|
||||
'$make_directory'(Directory).
|
||||
|
||||
%% make_directory_path(+Directory).
|
||||
%
|
||||
% Similar to `make_directory/1` but recursively creates directories if they're missing.
|
||||
% Equivalent to mkdir -p in Unix.
|
||||
make_directory_path(Directory) :-
|
||||
must_be(chars, Directory),
|
||||
'$make_directory_path'(Directory).
|
||||
|
||||
%% delete_file(+File).
|
||||
%
|
||||
% Succeeds if deletes File from the current system.
|
||||
delete_file(File) :-
|
||||
file_must_exist(File, delete_file/1),
|
||||
list_of_chars(File),
|
||||
'$delete_file'(File).
|
||||
|
||||
%% rename_file(+File, +Renamed).
|
||||
%
|
||||
% Succeeds if File is renamed to Renamed
|
||||
rename_file(File, Renamed) :-
|
||||
file_must_exist(File, rename_file/2),
|
||||
must_be(chars, Renamed),
|
||||
'$rename_file'(File, Renamed).
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Dir0 is the current working directory, and the working directory
|
||||
is changed to Dir.
|
||||
|
||||
%% file_copy(+File, +Copied).
|
||||
%
|
||||
% Succeeds if File is copied to Copied
|
||||
file_copy(File, Copied) :-
|
||||
file_must_exist(File, file_copy/2),
|
||||
must_be(chars, Copied),
|
||||
'$file_copy'(File, Copied).
|
||||
|
||||
%% delete_directory(+Directory).
|
||||
%
|
||||
% Succeeds if Directory is deleted from the current system.
|
||||
% Directory must be empty.
|
||||
delete_directory(Directory) :-
|
||||
directory_must_exist(Directory, delete_directory/1),
|
||||
must_be(chars, Directory),
|
||||
'$delete_directory'(Directory).
|
||||
|
||||
file_must_exist(File, Context) :-
|
||||
( file_exists(File) -> true
|
||||
; throw(error(existence_error(file, File), Context))
|
||||
).
|
||||
|
||||
directory_must_exist(Directory, Context) :-
|
||||
( directory_exists(Directory) -> true
|
||||
; throw(error(existence_error(directory, Directory), Context))
|
||||
).
|
||||
|
||||
%% working_directory(Dir0, Dir).
|
||||
%
|
||||
% Dir0 is the current working directory, and the working directory
|
||||
% is changed to Dir.
|
||||
%
|
||||
% Use `working_directory/2` to determine the current working directory,
|
||||
% and leave it as is.
|
||||
Use working_directory(Ds, Ds) to determine the current working directory,
|
||||
and leave it as is.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
working_directory(Dir0, Dir) :-
|
||||
can_be(list, Dir0),
|
||||
can_be(list, Dir),
|
||||
'$working_directory'(Dir0, Dir).
|
||||
|
||||
%% path_canonical(Ps, Cs).
|
||||
%
|
||||
% True iff Cs is the canonical, absolute path of Ps.
|
||||
%
|
||||
% All intermediate components are normalized, and all symbolic links
|
||||
% are resolved.
|
||||
%
|
||||
% The predicate fails in the following situations, though not
|
||||
% necessarily *only* in these cases:
|
||||
%
|
||||
% 1. Ps is a path that does not exist.
|
||||
% 2. A non-final component in Ps is not a directory.
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
True iff Cs is the canonical, absolute path of Ps.
|
||||
|
||||
All intermediate components are normalized, and all symbolic links
|
||||
are resolved.
|
||||
|
||||
The predicate fails in the following situations, though not
|
||||
necessarily *only* in these cases:
|
||||
|
||||
1. Ps is a path that does not exist.
|
||||
2. A non-final component in Ps is not a directory.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
path_canonical(Ps, Cs) :-
|
||||
must_be(chars, Ps),
|
||||
must_be(list, Ps),
|
||||
maplist(must_be(character), Ps),
|
||||
can_be(list, Cs),
|
||||
'$path_canonical'(Ps, Cs).
|
||||
|
||||
@@ -219,81 +132,65 @@ path_canonical(Ps, Cs) :-
|
||||
For two time stamps A and B, if A precedes B, then A @< B holds.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% file_modification_time(+File, ?T).
|
||||
%
|
||||
% For a file `File` that must exist, `T` is the modification time.
|
||||
%
|
||||
% T is a time stamp compatible with `library(time)`.
|
||||
file_modification_time(File, T) :-
|
||||
file_time_(File, modification, T).
|
||||
|
||||
%% file_access_time(+File, ?T).
|
||||
%
|
||||
% For a file `File` that must exist, `T` is the access time.
|
||||
%
|
||||
% T is a time stamp compatible with `library(time)`.
|
||||
|
||||
file_access_time(File, T) :-
|
||||
file_time_(File, access, T).
|
||||
|
||||
%% file_creation_time(+File, ?T).
|
||||
%
|
||||
% For a file `File` that must exist, `T` is the creation time.
|
||||
%
|
||||
% T is a time stamp compatible with `library(time)`.
|
||||
file_creation_time(File, T) :-
|
||||
file_time_(File, creation, T).
|
||||
|
||||
file_time_(File, Which, T) :-
|
||||
file_must_exist(File, file_time_/3),
|
||||
'$file_time'(File, Which, T0),
|
||||
read_from_chars(T0, T).
|
||||
read_term_from_chars(T0, T).
|
||||
|
||||
|
||||
%% path_segments(?Ps, ?Segments).
|
||||
%
|
||||
% True iff Segments are the segments of Ps.
|
||||
%
|
||||
% Segments is the list of components of the path Ps that are
|
||||
% separated by the platform-specific directory separator. Each
|
||||
% segment is a list of characters.
|
||||
%
|
||||
% At least one of the arguments must be instantiated.
|
||||
%
|
||||
% Examples:
|
||||
%
|
||||
% ```
|
||||
% ?- path_segments("/hello/there", Segments).
|
||||
% Segments = [[],"hello","there"].
|
||||
% ?- path_segments(Path, ["hello","there"]).
|
||||
% Path = "hello/there".
|
||||
% ```
|
||||
%
|
||||
% To obtain the platform-specific directory separator, you can use:
|
||||
%
|
||||
% ```
|
||||
% ?- path_segments(Separator, ["",""]).
|
||||
% Separator = "/".
|
||||
% ```
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
path_segments(Ps, Segments): True iff Segments are the segments of Ps.
|
||||
|
||||
Segments is the list of components of the path Ps that are
|
||||
separated by the platform-specific directory separator. Each
|
||||
segment is a list of characters.
|
||||
|
||||
At least one of the arguments must be instantiated.
|
||||
|
||||
Examples:
|
||||
|
||||
?- path_segments("/hello/there", Segments).
|
||||
Segments = [[],"hello","there"]
|
||||
; false.
|
||||
|
||||
?- path_segments(Path, ["hello","there"]).
|
||||
Path = "hello/there"
|
||||
; false.
|
||||
|
||||
|
||||
To obtain the platform-specific directory separator, you can use:
|
||||
|
||||
?- path_segments(Separator, ["",""]).
|
||||
Separator = "/"
|
||||
; false.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
path_segments(Path, Segments) :-
|
||||
'$directory_separator'(Sep),
|
||||
( var(Path) ->
|
||||
must_be(list, Segments),
|
||||
maplist(must_be(chars), Segments),
|
||||
phrase(append_with_separator(Segments, Sep), Path)
|
||||
; must_be(chars, Path),
|
||||
maplist(list_of_chars, Segments),
|
||||
append_with_separator(Segments, Sep, Path)
|
||||
; list_of_chars(Path),
|
||||
path_to_segments(Path, Sep, Segments)
|
||||
).
|
||||
|
||||
append_with_separator([], _) --> [].
|
||||
append_with_separator([Segment|Segments], Sep) -->
|
||||
append_with_separator_(Segments, Segment, Sep).
|
||||
append_with_separator([], _, []).
|
||||
append_with_separator([Segment|Segments], Sep, Path) :-
|
||||
append_with_separator_(Segments, Segment, Sep, Path).
|
||||
|
||||
append_with_separator_([], Segment, _) --> seq(Segment).
|
||||
append_with_separator_([Segment|Segments], Prev, Sep) -->
|
||||
seq(Prev), [Sep],
|
||||
append_with_separator_(Segments, Segment, Sep).
|
||||
append_with_separator_([], Segment, _, Segment).
|
||||
append_with_separator_([Segment|Segments], Prev, Sep, Path) :-
|
||||
append(Prev, [Sep|Rest], Path),
|
||||
append_with_separator_(Segments, Segment, Sep, Rest).
|
||||
|
||||
path_to_segments(Path, Sep, Segments) :-
|
||||
( append(Front, [Sep|Ps], Path) ->
|
||||
|
||||
@@ -1,21 +1,78 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2020-2025 by Markus Triska (triska@metalevel.at)
|
||||
Written March 2020 by Markus Triska (triska@metalevel.at)
|
||||
Part of Scryer Prolog.
|
||||
|
||||
This library provides the nonterminal format_//2 to describe
|
||||
formatted strings. format/[2,3] are provided for impure output.
|
||||
|
||||
Usage:
|
||||
======
|
||||
|
||||
phrase(format_(FormatString, Arguments), Ls)
|
||||
|
||||
format_//2 describes a list of characters Ls that are formatted
|
||||
according to FormatString. FormatString is a string (i.e.,
|
||||
a list of characters) that specifies the layout of Ls.
|
||||
The characters in FormatString are used literally, except
|
||||
for the following tokens with special meaning:
|
||||
|
||||
~w use the next available argument from Arguments here
|
||||
~q use the next argument here, formatted as by writeq/1
|
||||
~a use the next argument here, which must be an atom
|
||||
~s use the next argument here, which must be a string
|
||||
~d use the next argument here, which must be an integer
|
||||
~f use the next argument here, a floating point number
|
||||
~Nf where N is an integer: format the float argument
|
||||
using N digits after the decimal point
|
||||
~Nd like ~d, placing the last N digits after a decimal point;
|
||||
if N is 0 or omitted, no decimal point is used.
|
||||
~ND like ~Nd, separating digits to the left of the decimal point
|
||||
in groups of three, using the character "," (comma)
|
||||
~Nr where N is an integer between 2 and 36: format the
|
||||
next argument, which must be an integer, in radix N.
|
||||
The characters "a" to "z" are used for radices 10 to 36.
|
||||
~NR like ~Nr, except that "A" to "Z" are used for radices > 9
|
||||
~| place a tab stop at this position
|
||||
~N| where N is an integer: place a tab stop at text column N
|
||||
~N+ where N is an integer: place a tab stop N characters
|
||||
after the previous tab stop (or start of line)
|
||||
~t distribute spaces evenly between the two closest tab stops
|
||||
~`Ct like ~t, use character C instead of spaces to fill the space
|
||||
~n newline
|
||||
~Nn N newlines
|
||||
~i ignore the next argument
|
||||
~~ the literal ~
|
||||
|
||||
Instead of ~N, you can write ~* to use the next argument from Arguments
|
||||
as the numeric argument.
|
||||
|
||||
The predicate format/2 is like format_//2, except that it outputs
|
||||
the text on the terminal instead of describing it declaratively.
|
||||
|
||||
format/3, used as format(Stream, FormatString, Arguments), outputs
|
||||
the described string to the given Stream. If Stream is a binary
|
||||
stream, then the code of each emitted character must be in 0..255.
|
||||
|
||||
If at all possible, format_//2 should be used, to stress pure parts
|
||||
that enable easy testing etc. If necessary, you can emit the list Ls
|
||||
with maplist(write, Ls).
|
||||
|
||||
The entire library only works if the Prolog flag double_quotes
|
||||
is set to chars, the default value in Scryer Prolog. This should
|
||||
also stay that way, to encourage a sensible environment.
|
||||
|
||||
Example:
|
||||
|
||||
?- phrase(format_("~s~n~`.t~w!~12|", ["hello",there]), Cs).
|
||||
%@ Cs = "hello\n......there!"
|
||||
%@ ; false.
|
||||
|
||||
I place this code in the public domain. Use it in any way you want.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/** This library provides the nonterminal `format_//2` to describe
|
||||
formatted strings. `format/[2,3]` are provided for _impure_ output.
|
||||
|
||||
The entire library only works if the Prolog flag `double_quotes`
|
||||
is set to `chars`, the default value in Scryer Prolog. This should
|
||||
also stay that way, to encourage a sensible environment.
|
||||
*/
|
||||
|
||||
:- module(format, [format_//2,
|
||||
format/2,
|
||||
format/3,
|
||||
portray_clause_//1,
|
||||
portray_clause/1,
|
||||
portray_clause/2,
|
||||
listing/1
|
||||
@@ -26,124 +83,13 @@
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(charsio)).
|
||||
:- use_module(library(between)).
|
||||
:- use_module(library(pio)).
|
||||
|
||||
%% format_(+FormatString, +Arguments)//
|
||||
%
|
||||
% Usage:
|
||||
%
|
||||
% ```
|
||||
% phrase(format_(FormatString, Arguments), Ls)
|
||||
% ```
|
||||
%
|
||||
% `format_//2` describes a list of characters Ls that are formatted
|
||||
% according to FormatString. FormatString is a string (i.e., a list of
|
||||
% characters) that specifies the layout of Ls. The characters in
|
||||
% FormatString are used literally, except for the following tokens
|
||||
% with special meaning:
|
||||
%
|
||||
% | `~q` | use the next argument here, formatted as by `writeq/1` |
|
||||
% | `~a` | use the next argument here, which must be an atom |
|
||||
% | `~s` | use the next argument here, which must be a string |
|
||||
% | `~d` | use the next argument here, which must be an integer |
|
||||
% | `~f` | use the next argument here, a floating point number |
|
||||
% | `~Nf` | where N is an integer: format the float argument |
|
||||
% | | using N digits after the decimal point |
|
||||
% | `~Nd` | like ~d, placing the last N digits after a decimal point; |
|
||||
% | | if N is 0 or omitted, no decimal point is used. |
|
||||
% | `~ND` | like ~Nd, separating digits to the left of the decimal point |
|
||||
% | | in groups of three, using the character "," (comma) |
|
||||
% | `~NU` | like ~ND, using "_" (underscore) to separate groups of digits |
|
||||
% | `~NL` | format an integer so that at most N digits appear on a line. |
|
||||
% | | If N is 0 or omitted, it defaults to 72. |
|
||||
% | `~Nr` | where N is an integer between 2 and 36: format the |
|
||||
% | | next argument, which must be an integer, in radix N. |
|
||||
% | | The characters "a" to "z" are used for radices 10 to 36. |
|
||||
% | | If N is omitted, it defaults to 8 (octal). |
|
||||
% | `~NR` | like ~Nr, except that "A" to "Z" are used for radices > 9 |
|
||||
% | `~|` | place a tab stop at this position |
|
||||
% | `~N|` | where N is an integer: place a tab stop at text column N |
|
||||
% | `~N+` | where N is an integer: place a tab stop N characters |
|
||||
% | | after the previous tab stop (or start of line) |
|
||||
% | `~t` | distribute spaces evenly between the two closest tab stops |
|
||||
% | ``~`Ct`` | like ~t, use character C instead of spaces to fill the space |
|
||||
% | `~n` | newline |
|
||||
% | `~Nn` | N newlines |
|
||||
% | `~i` | ignore the next argument |
|
||||
% | `~~` | the literal ~ |
|
||||
% | `~w` | format like `write/1` would; consider using `~q`, `~d`, etc. |
|
||||
%
|
||||
% Instead of `~N`, you can write `~*` to use the next argument from
|
||||
% Arguments as the numeric argument.
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- phrase(format_("~s~n~`.t~w!~12|", ["hello",there]), Cs).
|
||||
% Cs = "hello\n......there!".
|
||||
% ```
|
||||
|
||||
format_(Fs, Args) -->
|
||||
{ format_args_cells(Fs, Args, Cells) },
|
||||
{ must_be(list, Fs),
|
||||
must_be(list, Args),
|
||||
phrase(cells(Fs,Args,0,[]), Cells) },
|
||||
format_cells(Cells).
|
||||
|
||||
format_args_cells(Fs, Args, Cells) :-
|
||||
must_be(chars, Fs),
|
||||
must_be(list, Args),
|
||||
unique_variable_names(fabricated, Args, VNs),
|
||||
phrase(cells(Fs,Args,0,[],VNs), Cells).
|
||||
|
||||
unique_variable_names(Type, Term, VNs) :-
|
||||
term_variables(Term, Vs),
|
||||
foldl(var_name(Type), Vs, VNs, 0, _).
|
||||
|
||||
var_name(Type, V, Name=V, Num0, Num) :-
|
||||
charsio:fabricate_var_name(Type, Name, Num0),
|
||||
Num is Num0 + 1.
|
||||
|
||||
user:goal_expansion(format_(Fs,Args,Cs0,Cs),
|
||||
format:format_cells(Cells, Cs0, Cs)) :-
|
||||
catch(format_args_cells(Fs,Args,Cells),
|
||||
E,
|
||||
% no partial evaluation for uses of format_//2 that
|
||||
% cannot be compiled statically, for example those where
|
||||
% the argument list is a variable, or where ~*n occurs
|
||||
% in the format string, or a domain error occurs
|
||||
( ( E = error(instantiation_error,_)
|
||||
; E = error(domain_error(_,_), _)
|
||||
) ->
|
||||
false
|
||||
; throw(E)
|
||||
)).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Partial evaluation of goals involving conditions that can be
|
||||
checked at compilation time. This is especially useful for the
|
||||
common case of conditions that test a numeric argument against 0.
|
||||
It is currently used for the goals of ~d.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
goal_pe(G0, G) :- var(G0), !, G = G0.
|
||||
goal_pe((A0,B0), (A,B)) :- !, goal_pe(A0, A), goal_pe(B0, B).
|
||||
goal_pe((Body0 ; Else0), Body) :-
|
||||
nonvar(Body0),
|
||||
Body0 = ( If -> Then0 ),
|
||||
!,
|
||||
( ground(If) ->
|
||||
( If ->
|
||||
goal_pe(Then0, Body)
|
||||
; goal_pe(Else0, Body)
|
||||
)
|
||||
; goal_pe(Then0, Then),
|
||||
goal_pe(Else0, Else),
|
||||
Body = ( If -> Then ; Else )
|
||||
).
|
||||
goal_pe(Goal, Goal).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
format_cells//1 is an interpreter for cells, describing a string.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
format_cells([]) --> [].
|
||||
format_cells([Cell|Cells]) -->
|
||||
format_cell(Cell),
|
||||
@@ -174,12 +120,14 @@ format_elements([E|Es]) -->
|
||||
format_element(E),
|
||||
format_elements(Es).
|
||||
|
||||
format_element(chars(Cs)) --> seq(Cs).
|
||||
format_element(chars(Cs)) --> list(Cs).
|
||||
format_element(glue(Fill,Num)) -->
|
||||
{ length(Ls, Num),
|
||||
maplist(=(Fill), Ls) },
|
||||
seq(Ls).
|
||||
format_element(goal(_)) --> [].
|
||||
list(Ls).
|
||||
|
||||
list([]) --> [].
|
||||
list([L|Ls]) --> [L], list(Ls).
|
||||
|
||||
elements_gluevars([], N, N) --> [].
|
||||
elements_gluevars([E|Es], N0, N) -->
|
||||
@@ -187,211 +135,173 @@ elements_gluevars([E|Es], N0, N) -->
|
||||
elements_gluevars(Es, N1, N).
|
||||
|
||||
element_gluevar(chars(Cs), N0, N) -->
|
||||
{ must_be(chars, Cs),
|
||||
length(Cs, L),
|
||||
{ length(Cs, L),
|
||||
N is N0 + L }.
|
||||
element_gluevar(glue(_,V), N, N) --> [V].
|
||||
element_gluevar(goal(G), N, N) --> { G }.
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Our key datastructure is a list of cells and newlines.
|
||||
A cell has the shape cell(From,To,Elements), where
|
||||
A cell has the shape from_to(From,To,Elements), where
|
||||
From and To denote the positions of surrounding tab stops.
|
||||
|
||||
Elements is a list of elements that occur in a cell,
|
||||
namely terms of the form chars(Cs), glue(Char, Var)
|
||||
and goal(G).
|
||||
|
||||
namely terms of the form chars(Cs) and glue(Char, Var).
|
||||
"glue" elements (TeX terminology) are evenly stretched
|
||||
to fill the remaining whitespace in the cell. For each
|
||||
glue element, the character Char is used for filling,
|
||||
and Var is a free variable that is used when the
|
||||
available space is distributed. Goals are dynamically
|
||||
executed to obtain characters. In this way, format strings
|
||||
can be parsed and compiled statically when possible.
|
||||
available space is distributed.
|
||||
|
||||
newline is used if ~n occurs in a format string.
|
||||
It is used because a newline character does not
|
||||
It is is used because a newline character does not
|
||||
consume whitespace in the sense of format strings.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
cells([], Args, Tab, Es, _) --> !,
|
||||
cells([], Args, Tab, Es) -->
|
||||
( { Args == [] } -> cell(Tab, Tab, Es)
|
||||
; { domain_error(empty_list, Args, format_//2) }
|
||||
).
|
||||
cells([~,~|Fs], Args, Tab, Es, VNs) --> !,
|
||||
cells(Fs, Args, Tab, [chars("~")|Es], VNs).
|
||||
cells([~,w|Fs], [Arg|Args], Tab, Es, VNs) --> !,
|
||||
{ G = write_term_to_chars(Arg, [numbervars(true),variable_names(VNs)], Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars),goal(G)|Es], VNs).
|
||||
cells([~,q|Fs], [Arg|Args], Tab, Es, VNs) --> !,
|
||||
{ G = write_term_to_chars(Arg, [quoted(true),numbervars(true),variable_names(VNs)], Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars),goal(G)|Es], VNs).
|
||||
cells([~,a|Fs], [Arg|Args], Tab, Es, VNs) --> !,
|
||||
{ G = atom_chars(Arg, Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars),goal(G)|Es], VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
cells([~,~|Fs], Args, Tab, Es) --> !,
|
||||
cells(Fs, Args, Tab, [chars("~")|Es]).
|
||||
cells([~,w|Fs], [Arg|Args], Tab, Es) --> !,
|
||||
{ write_term_to_chars(Arg, [], Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars)|Es]).
|
||||
cells([~,q|Fs], [Arg|Args], Tab, Es) --> !,
|
||||
{ write_term_to_chars(Arg, [quoted(true)], Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars)|Es]).
|
||||
cells([~,a|Fs], [Arg|Args], Tab, Es) --> !,
|
||||
{ atom_chars(Arg, Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars)|Es]).
|
||||
cells([~|Fs0], Args0, Tab, Es) -->
|
||||
{ numeric_argument(Fs0, Num, [d|Fs], Args0, [Arg0|Args]) },
|
||||
!,
|
||||
{ G0 = ( Arg is Arg0, % evaluate compound expression
|
||||
must_be(integer, Arg),
|
||||
number_chars(Arg, Cs0),
|
||||
( Num =:= 0 -> Cs = Cs0
|
||||
; length(Cs0, L),
|
||||
( L =< Num ->
|
||||
Delta is Num - L,
|
||||
length(Zs, Delta),
|
||||
maplist(=('0'), Zs),
|
||||
phrase(("0.",seq(Zs),seq(Cs0)), Cs)
|
||||
; BeforeComma is L - Num,
|
||||
length(Bs, BeforeComma),
|
||||
append(Bs, Ds, Cs0),
|
||||
phrase((seq(Bs),".",seq(Ds)), Cs)
|
||||
)
|
||||
)),
|
||||
goal_pe(G0, G) },
|
||||
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
{ Arg is Arg0, % evaluate compound expression
|
||||
must_be(integer, Arg),
|
||||
number_chars(Arg, Cs0) },
|
||||
( { Num =:= 0 } -> { Cs = Cs0 }
|
||||
; { length(Cs0, L),
|
||||
( L =< Num ->
|
||||
Delta is Num - L,
|
||||
length(Zs, Delta),
|
||||
maplist(=('0'), Zs),
|
||||
phrase(("0.",list(Zs),list(Cs0)), Cs)
|
||||
; BeforeComma is L - Num,
|
||||
length(Bs, BeforeComma),
|
||||
append(Bs, Ds, Cs0),
|
||||
phrase((list(Bs),".",list(Ds)), Cs)
|
||||
) }
|
||||
),
|
||||
cells(Fs, Args, Tab, [chars(Cs)|Es]).
|
||||
cells([~|Fs0], Args0, Tab, Es) -->
|
||||
{ numeric_argument(Fs0, Num, ['D'|Fs], Args0, [Arg|Args]) },
|
||||
!,
|
||||
{ G = separate_digits_fractional(Arg, ',', Num, Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
{ numeric_argument(Fs0, Num, ['U'|Fs], Args0, [Arg|Args]) },
|
||||
!,
|
||||
{ G = separate_digits_fractional(Arg, '_', Num, Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
{ numeric_argument(Fs0, Num0, ['L'|Fs], Args0, [Arg|Args]) },
|
||||
!,
|
||||
{ G = (( Num0 =:= 0 ->
|
||||
Num = 72
|
||||
; Num = Num0
|
||||
),
|
||||
phrase(format_("~d", [Arg]), Cs0),
|
||||
phrase(split_lines_width(Cs0, Num), Cs) ) },
|
||||
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
|
||||
cells([~,i|Fs], [_|Args], Tab, Es, VNs) --> !,
|
||||
cells(Fs, Args, Tab, Es, VNs).
|
||||
cells([~,n|Fs], Args, Tab, Es, VNs) --> !,
|
||||
{ number_chars(Num, NCs),
|
||||
phrase(("~",list(NCs),"d"), FStr),
|
||||
phrase(format_(FStr, [Arg]), Cs0),
|
||||
phrase(upto_what(Bs0, .), Cs0, Ds),
|
||||
reverse(Bs0, Bs1),
|
||||
phrase(groups_of_three(Bs1), Bs2),
|
||||
reverse(Bs2, Bs),
|
||||
append(Bs, Ds, Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs)|Es]).
|
||||
cells([~,i|Fs], [_|Args], Tab, Es) --> !,
|
||||
cells(Fs, Args, Tab, Es).
|
||||
cells([~,n|Fs], Args, Tab, Es) --> !,
|
||||
cell(Tab, Tab, Es),
|
||||
n_newlines(1),
|
||||
cells(Fs, Args, 0, [], VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
cells(Fs, Args, 0, []).
|
||||
cells([~|Fs0], Args0, Tab, Es) -->
|
||||
{ numeric_argument(Fs0, Num, [n|Fs], Args0, Args) },
|
||||
!,
|
||||
cell(Tab, Tab, Es),
|
||||
n_newlines(Num),
|
||||
cells(Fs, Args, 0, [], VNs).
|
||||
cells([~,s|Fs], [Arg|Args], Tab, Es, VNs) --> !,
|
||||
cells(Fs, Args, Tab, [chars(Arg)|Es], VNs).
|
||||
cells([~,f|Fs], Args, Tab, Es, VNs) --> !,
|
||||
cells([~,'6',f|Fs], Args, Tab, Es, VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
cells(Fs, Args, 0, []).
|
||||
cells([~,s|Fs], [Arg|Args], Tab, Es) --> !,
|
||||
cells(Fs, Args, Tab, [chars(Arg)|Es]).
|
||||
cells([~,f|Fs], [Arg|Args], Tab, Es) --> !,
|
||||
{ format_number_chars(Arg, Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars)|Es]).
|
||||
cells([~|Fs0], Args0, Tab, Es) -->
|
||||
{ numeric_argument(Fs0, Num, [f|Fs], Args0, [Arg|Args]) },
|
||||
!,
|
||||
{ G = phrase(float_with_n_decimal_digits(Arg, Num), Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars),goal(G)|Es], VNs).
|
||||
cells([~,r|Fs], Args, Tab, Es, VNs) --> !,
|
||||
cells([~,'8',r|Fs], Args, Tab, Es, VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
{ format_number_chars(Arg, Cs0),
|
||||
phrase(upto_what(Bs, .), Cs0, Cs),
|
||||
( Num =:= 0 -> Chars = Bs
|
||||
; ( Cs = ['.'|Rest] ->
|
||||
length(Rest, L),
|
||||
( Num < L ->
|
||||
length(Ds, Num),
|
||||
append(Ds, _, Rest)
|
||||
; Num =:= L ->
|
||||
Ds = Rest
|
||||
; Num > L,
|
||||
Delta is Num - L,
|
||||
% we should look into the float with
|
||||
% greater accuracy here, and use the
|
||||
% actual digits instead of 0.
|
||||
length(Zs, Delta),
|
||||
maplist(=('0'), Zs),
|
||||
append(Rest, Zs, Ds)
|
||||
)
|
||||
; length(Ds, Num),
|
||||
maplist(=('0'), Ds)
|
||||
),
|
||||
append(Bs, ['.'|Ds], Chars)
|
||||
) },
|
||||
cells(Fs, Args, Tab, [chars(Chars)|Es]).
|
||||
cells([~|Fs0], Args0, Tab, Es) -->
|
||||
{ numeric_argument(Fs0, Num, [r|Fs], Args0, [Arg|Args]) },
|
||||
!,
|
||||
{ G = integer_to_radix(Arg, Num, lowercase, Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
|
||||
cells([~,'R'|Fs], Args, Tab, Es, VNs) --> !,
|
||||
cells([~,'8','R'|Fs], Args, Tab, Es, VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
{ integer_to_radix(Arg, Num, lowercase, Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs)|Es]).
|
||||
cells([~|Fs0], Args0, Tab, Es) -->
|
||||
{ numeric_argument(Fs0, Num, ['R'|Fs], Args0, [Arg|Args]) },
|
||||
!,
|
||||
{ G = integer_to_radix(Arg, Num, uppercase, Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
|
||||
cells([~,'`',Char,t|Fs], Args, Tab, Es, VNs) --> !,
|
||||
cells(Fs, Args, Tab, [glue(Char,_)|Es], VNs).
|
||||
cells([~,t|Fs], Args, Tab, Es, VNs) --> !,
|
||||
cells(Fs, Args, Tab, [glue(' ',_)|Es], VNs).
|
||||
cells([~,'|'|Fs], Args, Tab0, Es, VNs) --> !,
|
||||
( { ground(Tab0), Es = [chars(Cs)], ground(Cs) } ->
|
||||
{ length(Cs, Width),
|
||||
Tab is Tab0 + Width },
|
||||
cell(Tab0, Tab, Es)
|
||||
; { G = (phrase(elements_gluevars(Es, 0, Width), _),
|
||||
Tab is Tab0 + Width) },
|
||||
cell(Tab0, Tab, [goal(G)|Es])
|
||||
),
|
||||
cells(Fs, Args, Tab, [], VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
{ integer_to_radix(Arg, Num, uppercase, Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs)|Es]).
|
||||
cells([~,'`',Char,t|Fs], Args, Tab, Es) --> !,
|
||||
cells(Fs, Args, Tab, [glue(Char,_)|Es]).
|
||||
cells([~,t|Fs], Args, Tab, Es) --> !,
|
||||
cells(Fs, Args, Tab, [glue(' ',_)|Es]).
|
||||
cells([~|Fs0], Args0, Tab, Es) -->
|
||||
{ numeric_argument(Fs0, Num, ['|'|Fs], Args0, Args) },
|
||||
!,
|
||||
cell(Tab, Num, Es),
|
||||
cells(Fs, Args, Num, [], VNs).
|
||||
cells([~|Fs0], Args0, Tab0, Es, VNs) -->
|
||||
cells(Fs, Args, Num, []).
|
||||
cells([~|Fs0], Args0, Tab0, Es) -->
|
||||
{ numeric_argument(Fs0, Num, [+|Fs], Args0, Args) },
|
||||
!,
|
||||
( { ground(Tab0+Num) } ->
|
||||
{ Tab is Tab0 + Num },
|
||||
cell(Tab0, Tab, Es)
|
||||
; { G = (Tab is Tab0 + Num) },
|
||||
cell(Tab0, Tab, [goal(G)|Es])
|
||||
),
|
||||
cells(Fs, Args, Tab, [], VNs).
|
||||
cells([~|Cs], Args, _, _, _) -->
|
||||
( { Args == [] } ->
|
||||
{ domain_error(non_empty_list, [], format_//2) }
|
||||
; { domain_error(format_string, [~|Cs], format_//2) }
|
||||
).
|
||||
cells(Fs0, Args, Tab, Es, VNs) -->
|
||||
{ Tab is Tab0 + Num },
|
||||
cell(Tab0, Tab, Es),
|
||||
cells(Fs, Args, Tab, []).
|
||||
cells([~,C|_], _, _, _) -->
|
||||
{ atom_chars(A, [~,C]),
|
||||
domain_error(format_string, A, format_//2) }.
|
||||
cells(Fs0, Args, Tab, Es) -->
|
||||
{ phrase(upto_what(Fs1, ~), Fs0, Fs),
|
||||
Fs1 = [_|_] },
|
||||
cells(Fs, Args, Tab, [chars(Fs1)|Es], VNs).
|
||||
cells(Fs, Args, Tab, [chars(Fs1)|Es]).
|
||||
|
||||
float_with_n_decimal_digits(F, N) -->
|
||||
{ Fr is abs(float_fractional_part(F)),
|
||||
FrR0 is round(Fr*10^N),
|
||||
I0 is truncate(F),
|
||||
( FrR0 >= 10^N
|
||||
-> I is I0+truncate(sign(F)), FrR = FrR0
|
||||
; I = I0, FrR is FrR0+10^N
|
||||
)
|
||||
},
|
||||
( { I=0, F<0, FrR>10^N } -> "-0" ; { number_chars(I, Is) }, seq(Is) ),
|
||||
".",
|
||||
( { FrR = 1 } -> "0" ; { number_chars(FrR, ['1'|FrRs]) }, seq(FrRs) ).
|
||||
format_number_chars(N0, Chars) :-
|
||||
N is N0, % evaluate compound expression
|
||||
number_chars(N, Chars).
|
||||
|
||||
n_newlines(0) --> !.
|
||||
n_newlines(N0) --> { N0 > 0, N is N0 - 1 }, [newline], n_newlines(N).
|
||||
n_newlines(0) --> [].
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
?- phrase(format:upto_what(Cs, ~), "abc~test", Rest).
|
||||
Cs = "abc", Rest = "~test".
|
||||
?- phrase(format:upto_what(Cs, ~), "abc", Rest).
|
||||
Cs = "abc", Rest = [].
|
||||
?- phrase(upto_what(Cs, ~), "abc~test", Rest).
|
||||
Cs = [a,b,c], Rest = [~,t,e,s,t].
|
||||
?- phrase(upto_what(Cs, ~), "abc", Rest).
|
||||
Cs = [a,b,c], Rest = [].
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
separate_digits_fractional(Arg, Sep, Num, Cs) :-
|
||||
number_chars(Num, NCs),
|
||||
phrase(("~",seq(NCs),"d"), FStr),
|
||||
phrase(format_(FStr, [Arg]), Cs0),
|
||||
phrase(upto_what(Bs0, .), Cs0, Ds),
|
||||
reverse(Bs0, Bs1),
|
||||
phrase(groups_of_three(Bs1,Sep), Bs2),
|
||||
reverse(Bs2, Bs),
|
||||
append(Bs, Ds, Cs).
|
||||
|
||||
upto_what([], W), [W] --> [W], !.
|
||||
upto_what([C|Cs], W) --> [C], !, upto_what(Cs, W).
|
||||
upto_what([], _) --> [].
|
||||
|
||||
groups_of_three([A,B,C,D|Rs], Sep) --> !, [A,B,C,Sep], groups_of_three([D|Rs], Sep).
|
||||
groups_of_three(Ls, _) --> seq(Ls).
|
||||
|
||||
split_lines_width(Cs, Num) -->
|
||||
( { length(Prefix, Num),
|
||||
append(Prefix, [R|Rs], Cs) } ->
|
||||
seq(Prefix), "_\n",
|
||||
split_lines_width([R|Rs], Num)
|
||||
; seq(Cs)
|
||||
).
|
||||
groups_of_three([A,B,C,D|Rs]) --> !, [A,B,C], ",", groups_of_three([D|Rs]).
|
||||
groups_of_three(Ls) --> list(Ls).
|
||||
|
||||
cell(From, To, Es0) -->
|
||||
( { Es0 == [] } -> []
|
||||
@@ -399,39 +309,37 @@ cell(From, To, Es0) -->
|
||||
[cell(From,To,Es)]
|
||||
).
|
||||
|
||||
%?- format:numeric_argument("2f", Num, [f|Fs], Args0, Args).
|
||||
%?- numeric_argument("2f", Num, ['f'|Fs], Args0, Args).
|
||||
|
||||
%?- format:numeric_argument("100b", Num, Rs, Args0, Args).
|
||||
%?- numeric_argument("100b", Num, Rs, Args0, Args).
|
||||
|
||||
numeric_argument(Ds, Num, Rest, Args0, Args) :-
|
||||
( Ds = [*|Rest] ->
|
||||
Args0 = [Num|Args]
|
||||
; phrase(numeric_argument_(Ds, Rest), Ns),
|
||||
foldl(plus_times10, Ns, 0, Num),
|
||||
; numeric_argument_(Ds, [], Ns, Rest),
|
||||
foldl(pow10, Ns, 0-0, Num-_),
|
||||
Args0 = Args
|
||||
).
|
||||
|
||||
numeric_argument_([D|Ds], Rest) -->
|
||||
( { member(D, "0123456789") } ->
|
||||
{ number_chars(N, [D]) },
|
||||
[N],
|
||||
numeric_argument_(Ds, Rest)
|
||||
; { Rest = [D|Ds] }
|
||||
numeric_argument_([D|Ds], Ns0, Ns, Rest) :-
|
||||
( member(D, "0123456789") ->
|
||||
number_chars(N, [D]),
|
||||
numeric_argument_(Ds, [N|Ns0], Ns, Rest)
|
||||
; Ns = Ns0,
|
||||
Rest = [D|Ds]
|
||||
).
|
||||
|
||||
|
||||
plus_times10(D, N0, N) :- N is D + N0*10.
|
||||
|
||||
radix_error(lowercase, R) --> format_("~~~dr", [R]).
|
||||
radix_error(uppercase, R) --> format_("~~~dR", [R]).
|
||||
pow10(D, N0-Pow0, N-Pow) :-
|
||||
N is N0 + D*10^Pow0,
|
||||
Pow is Pow0 + 1.
|
||||
|
||||
integer_to_radix(I0, R, Which, Cs) :-
|
||||
I is I0, % evaluate compound expression
|
||||
must_be(integer, I),
|
||||
must_be(integer, R),
|
||||
( \+ between(2, 36, R) ->
|
||||
phrase(radix_error(Which,R), Es),
|
||||
domain_error(format_string, Es, format_//2)
|
||||
domain_error(radix, R, format_//2)
|
||||
; true
|
||||
),
|
||||
digits(Which, Ds),
|
||||
@@ -447,7 +355,8 @@ integer_to_radix_(0, _, _) --> !.
|
||||
integer_to_radix_(I0, R, Ds) -->
|
||||
{ M is I0 mod R,
|
||||
nth0(M, Ds, D),
|
||||
I is I0 // R },
|
||||
I is I0 // R
|
||||
},
|
||||
[D],
|
||||
integer_to_radix_(I, R, Ds).
|
||||
|
||||
@@ -459,85 +368,67 @@ digits(uppercase, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").
|
||||
Impure I/O, implemented as a small wrapper over format_//2.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% format(+Fs, +Args)
|
||||
%
|
||||
% The predicate `format/2` is like `format_//2`, except that it
|
||||
% outputs the text on the terminal instead of describing it
|
||||
% declaratively as a list of characters.
|
||||
%
|
||||
% If at all possible, `format_//2` should be used, to stress pure
|
||||
% parts that enable easy testing etc. If necessary, you can emit the
|
||||
% described list of characters `Ls` with `maplist(put_char, Ls)` or,
|
||||
% much faster, with `format("~s", [Ls])`. Ideally, however, you use
|
||||
% `phrase_to_file/[2,3]` or `phrase_to_stream/2` from `library(pio)`
|
||||
% to write the described list directly to a file or stream,
|
||||
% respectively: `phrase_to_stream(format_(..., [...]), S)`. The
|
||||
% advantage of this is that an ideal implementation writes the
|
||||
% characters as they become known, without manifesting the list.
|
||||
format(Fs, Args) :-
|
||||
current_output(Stream),
|
||||
format(Stream, Fs, Args).
|
||||
|
||||
format(_, _) :- not_used.
|
||||
|
||||
user:goal_expansion(format(Fs, Args),
|
||||
( current_output(Stream),
|
||||
format(Stream, Fs, Args))).
|
||||
|
||||
%% format(Stream, FormatString, Arguments)
|
||||
%
|
||||
% Output the described string to the given Stream. If Stream is a
|
||||
% binary stream, then the code of each emitted character must be in
|
||||
% 0..255.
|
||||
|
||||
format(_, _, _) :- not_used.
|
||||
|
||||
user:goal_expansion(format(Stream, Fs, Args),
|
||||
( pio:phrase_to_stream(format:format_(Fs, Args), Stream),
|
||||
flush_output(Stream))).
|
||||
format(Stream, Fs, Args) :-
|
||||
phrase(format_(Fs, Args), Cs),
|
||||
% we use a specialised internal predicate that uses only a
|
||||
% single "write" operation for efficiency. It is equivalent to
|
||||
% maplist(put_char(Stream), Cs). It also works for binary streams.
|
||||
'$put_chars'(Stream, Cs),
|
||||
flush_output(Stream).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
?- phrase(format:cells("hello", [], 0, [], []), Cs).
|
||||
?- phrase(cells("hello", [], 0, []), Cs).
|
||||
|
||||
?- phrase(format:cells("hello~10|", [], 0, [], []), Cs).
|
||||
?- phrase(format:cells("~ta~t~10|", [], 0, [], []), Cs).
|
||||
?- phrase(cells("hello~10|", [], 0, []), Cs).
|
||||
?- phrase(cells("~ta~t~10|", [], 0, []), Cs).
|
||||
|
||||
?- phrase(format_("~`at~50|", []), Ls).
|
||||
|
||||
?- phrase(format:cells("~`at~50|", [], 0, [], []), Cs),
|
||||
phrase(format:format_cells(Cs), Ls).
|
||||
?- phrase(format:cells("~ta~t~tb~tc~21|", [], 0, [], []), Cs).
|
||||
Cs = [cell(0,21,[glue(' ',_A),chars("a"),glue(' ',_B),glue(' ',_C),chars("b"),glue(' ',_D),chars("c")])].
|
||||
?- phrase(format:cells("~ta~t~4|", [], 0, [], []), Cs).
|
||||
Cs = [cell(0,4,[glue(' ',_A),chars("a"),glue(' ',_B)])].
|
||||
?- phrase(cells("~`at~50|", [], 0, []), Cs),
|
||||
phrase(format_cells(Cs), Ls).
|
||||
?- phrase(cells("~ta~t~tb~tc~21|", [], 0, []), Cs).
|
||||
Cs = [cell(0,21,[glue(' ',_38),chars([a]),glue(' ',_62),glue(' ',_67),chars([b]),glue(' ',_91),chars([c])])].
|
||||
?- phrase(cells("~ta~t~4|", [], 0, []), Cs).
|
||||
Cs = [cell(0,4,[glue(' ',_38),chars([a]),glue(' ',_62)])].
|
||||
|
||||
?- phrase(format:format_cell(cell(0,1,[glue(a,_94)])), Ls).
|
||||
?- phrase(format_cell(cell(0,1,[glue(a,_94)])), Ls).
|
||||
|
||||
?- phrase(format:format_cell(cell(0,50,[chars("hello")])), Ls).
|
||||
?- phrase(format_cell(cell(0,50,[chars("hello")])), Ls).
|
||||
|
||||
?- phrase(format_("~`at~50|~n", []), Ls).
|
||||
?- phrase(format_("hello~n~tthere~6|", []), Ls).
|
||||
|
||||
?- format("~ta~t~4|", []).
|
||||
a true.
|
||||
a true
|
||||
; false.
|
||||
|
||||
?- format("~ta~tb~tc~10|", []).
|
||||
a b c true.
|
||||
a b c true
|
||||
; false.
|
||||
|
||||
?- format("~tabc~3|", []).
|
||||
|
||||
?- format("~ta~t~4|", []).
|
||||
|
||||
?- format("~ta~t~tb~tc~20|", []).
|
||||
a b c true.
|
||||
a b c true
|
||||
; false.
|
||||
|
||||
?- format("~2f~n", [3]).
|
||||
3.00
|
||||
true.
|
||||
true
|
||||
|
||||
?- format("~20f", [0.1]).
|
||||
0.10000000000000000000 true.
|
||||
0.10000000000000000000 true % this should use higher accuracy!
|
||||
; false.
|
||||
|
||||
?- X is atan(2), format("~7f~n", [X]).
|
||||
1.1071487
|
||||
X = 1.1071487177940906.
|
||||
X = 1.1071487177940906
|
||||
|
||||
?- format("~`at~50|~n", []).
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
@@ -546,10 +437,10 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
?- format("~t~N", []).
|
||||
|
||||
?- format("~q", [.]).
|
||||
'.' true.
|
||||
'.' true
|
||||
|
||||
?- format("~12r", [300]).
|
||||
210 true.
|
||||
210 true
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
@@ -557,46 +448,31 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
|
||||
In the eventual library organization, portray_clause/1 and
|
||||
related predicates may be placed in their own dedicated library.
|
||||
|
||||
portray_clause/1 is useful for printing solutions in such a way
|
||||
that they can be read back with read/1.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
|
||||
%% portray_clause(+Term)
|
||||
%
|
||||
% `portray_clause/1` is useful for printing solutions in such a way
|
||||
% that they can be read back with `read/1`.
|
||||
|
||||
portray_clause(Term) :-
|
||||
current_output(Out),
|
||||
portray_clause(Out, Term).
|
||||
|
||||
portray_clause(Stream, Term) :-
|
||||
phrase_to_stream(portray_clause_(Term), Stream),
|
||||
flush_output(Stream).
|
||||
phrase(portray_clause_(Term), Ls),
|
||||
format(Stream, "~s", [Ls]).
|
||||
|
||||
portray_clause_(Term) -->
|
||||
{ unique_variable_names(numbervars, Term, VNs) },
|
||||
{ term_variables(Term, Vs),
|
||||
foldl(var_name, Vs, VNs, 0, _) },
|
||||
portray_(Term, VNs), ".\n".
|
||||
|
||||
literal(Lit, VNs) -->
|
||||
{ write_term_to_chars(Lit, [quoted(true),variable_names(VNs),double_quotes(true)], Ls) },
|
||||
( { nonvar(Lit),
|
||||
\+ number(Lit),
|
||||
functor(Lit, F, A),
|
||||
current_op(Pri, _, F),
|
||||
( A =:= 0
|
||||
; Pri >= 1000
|
||||
) } ->
|
||||
"(", seq(Ls), ")"
|
||||
; seq(Ls)
|
||||
).
|
||||
var_name(V, Name=V, Num0, Num) :-
|
||||
charsio:fabricate_var_name(numbervars, Name, Num0),
|
||||
Num is Num0 + 1.
|
||||
|
||||
literal_(Lit, VNs) -->
|
||||
{ phrase(literal(Lit, VNs), Ls) },
|
||||
seq(Ls),
|
||||
( { phrase((...,[Last]), Ls), char_type(Last, graphic_token) } ->
|
||||
" "
|
||||
; ""
|
||||
).
|
||||
literal(Lit, VNs) -->
|
||||
{ write_term_to_chars(Lit, [quoted(true),variable_names(VNs)], Ls) },
|
||||
list(Ls).
|
||||
|
||||
portray_(Var, VNs) --> { var(Var) }, !, literal(Var, VNs).
|
||||
portray_((Head :- Body), VNs) --> !,
|
||||
@@ -605,7 +481,7 @@ portray_((Head :- Body), VNs) --> !,
|
||||
portray_((Head --> Body), VNs) --> !,
|
||||
literal(Head, VNs), " -->\n",
|
||||
body_(Body, 0, 3, VNs).
|
||||
portray_(Any, VNs) --> literal_(Any, VNs).
|
||||
portray_(Any, VNs) --> literal(Any, VNs).
|
||||
|
||||
|
||||
body_(Var, C, I, VNs) --> { var(Var) }, !,
|
||||
@@ -614,50 +490,35 @@ body_(Var, C, I, VNs) --> { var(Var) }, !,
|
||||
body_((A,B), C, I, VNs) --> !,
|
||||
body_(A, C, I, VNs), ",\n",
|
||||
body_(B, 0, I, VNs).
|
||||
body_(Body, C, I, VNs) -->
|
||||
{ body_if_then_else(Body, If, Then, Else) },
|
||||
body_((A ; Else), C, I, VNs) --> % ( If -> Then ; Else )
|
||||
{ nonvar(A), A = (If -> Then) },
|
||||
!,
|
||||
indent_to(C, I),
|
||||
"( ",
|
||||
{ C1 is I + 3 },
|
||||
body_(If, C1, C1, VNs), " ->\n",
|
||||
body_(Then, 0, C1, VNs), "\n",
|
||||
else_branch(Else, I, VNs).
|
||||
else_branch(Else, C1, I, VNs).
|
||||
body_((A;B), C, I, VNs) --> !,
|
||||
indent_to(C, I),
|
||||
"( ",
|
||||
{ C1 is I + 3 },
|
||||
body_(A, C1, C1, VNs), "\n",
|
||||
else_branch(B, I, VNs).
|
||||
else_branch(B, C1, I, VNs).
|
||||
body_(Goal, C, I, VNs) -->
|
||||
indent_to(C, I), literal_(Goal, VNs).
|
||||
indent_to(C, I), literal(Goal, VNs).
|
||||
|
||||
|
||||
% True iff Body has the shape ( If -> Then ; Else ).
|
||||
body_if_then_else(Body, If, Then, Else) :-
|
||||
nonvar(Body),
|
||||
Body = (A ; Else),
|
||||
nonvar(A),
|
||||
A = (If -> Then).
|
||||
|
||||
else_branch(Else, I, VNs) -->
|
||||
else_branch(Else, C, I, VNs) -->
|
||||
indent_to(0, I),
|
||||
"; ",
|
||||
{ C is I + 3 },
|
||||
( { body_if_then_else(Else, If, Then, NextElse) } ->
|
||||
body_(If, C, C, VNs), " ->\n",
|
||||
body_(Then, 0, C, VNs), "\n",
|
||||
else_branch(NextElse, I, VNs)
|
||||
; { nonvar(Else), Else = ( A ; B ) } ->
|
||||
body_(A, C, C, VNs), "\n",
|
||||
else_branch(B, I, VNs)
|
||||
; body_(Else, C, C, VNs), "\n",
|
||||
indent_to(0, I),
|
||||
")"
|
||||
).
|
||||
body_(Else, C, C, VNs), "\n",
|
||||
indent_to(0, I),
|
||||
")".
|
||||
|
||||
indent_to(CurrentColumn, Indent) -->
|
||||
format_("~t~*|", [Indent-CurrentColumn]).
|
||||
{ Delta is Indent - CurrentColumn },
|
||||
format_("~t~*|", [Delta]).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
?- portray_clause(a).
|
||||
@@ -672,7 +533,7 @@ a :-
|
||||
b,
|
||||
c,
|
||||
d.
|
||||
true.
|
||||
true
|
||||
|
||||
|
||||
?- portray_clause([a,b,c,d]).
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
:- module(freeze, [freeze/2]).
|
||||
|
||||
/** Provides the constraint `freeze/2`.
|
||||
*/
|
||||
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(dcgs)).
|
||||
|
||||
:- meta_predicate(freeze(-, 0)).
|
||||
|
||||
:- attribute frozen/1.
|
||||
|
||||
verify_attributes(Var, Other, Goals) :-
|
||||
@@ -22,15 +17,6 @@ verify_attributes(Var, Other, Goals) :-
|
||||
).
|
||||
verify_attributes(_, _, []).
|
||||
|
||||
%% freeze(Var, Goal)
|
||||
%
|
||||
% Schedules Goal to be executed when Var is instantiated. This can
|
||||
% be useful to observe the exact moment a variable becomes bound to a
|
||||
% more concrete term, for example when creating animations of search
|
||||
% processes. Higher-level constructs such as `phrase_from_file/2` can
|
||||
% also be implemented with `freeze/2`, by scheduling a goal that
|
||||
% reads additional data from a file as soon as it is needed.
|
||||
|
||||
freeze(X, Goal) :-
|
||||
put_atts(Fresh, frozen(Goal)),
|
||||
Fresh = X.
|
||||
@@ -38,5 +24,5 @@ freeze(X, Goal) :-
|
||||
attribute_goals(Var) -->
|
||||
{ get_atts(Var, frozen(Goals)),
|
||||
put_atts(Var, -frozen(_)) },
|
||||
[freeze:freeze(Var, Goals)].
|
||||
[freeze(Var, Goals)].
|
||||
|
||||
|
||||
@@ -19,14 +19,14 @@ gensym(Base, Unique) :-
|
||||
must_be(var, Unique),
|
||||
atom_si(Base),
|
||||
gensym_key(Base, BaseKey),
|
||||
( bb_get(BaseKey, UniqueID0) -> true
|
||||
; UniqueID0 = 0
|
||||
),
|
||||
UniqueID is UniqueID0 + 1,
|
||||
append_id(Base, UniqueID, Unique),
|
||||
bb_put(BaseKey, UniqueID).
|
||||
( bb_get(BaseKey, UniqueID0) ->
|
||||
UniqueID is UniqueID0 + 1,
|
||||
bb_put(BaseKey, UniqueID),
|
||||
append_id(Base, UniqueID, Unique)
|
||||
; bb_put(BaseKey, 1),
|
||||
append_id(Base, 1, Unique)
|
||||
).
|
||||
|
||||
reset_gensym(Base) :-
|
||||
atom_si(Base),
|
||||
gensym_key(Base, BaseKey),
|
||||
bb_put(BaseKey, 0).
|
||||
bb_put(Base, 0).
|
||||
|
||||
@@ -1,73 +1,84 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2022 by Adrián Arroyo Calle (adrian.arroyocalle@gmail.com)
|
||||
Written June 2020 by Markus Triska (triska@metalevel.at)
|
||||
Part of Scryer Prolog.
|
||||
*/
|
||||
|
||||
/** Make HTTP requests.
|
||||
http_open(+Address, -Stream, +Options)
|
||||
======================================
|
||||
|
||||
This library contains the predicate `http_open/3` which allows you to perform HTTP(S) calls.
|
||||
Useful for making API calls, or parsing websites. It uses Hyper underneath.
|
||||
*/
|
||||
Yields Stream to read the body of an HTTP reply from Address.
|
||||
Address is a list of characters, and includes the method. Both HTTP
|
||||
and HTTPS are supported. Redirects are followed.
|
||||
|
||||
Currently, Options must be the empty list. Options may be
|
||||
added in the future to give more control over the connection.
|
||||
|
||||
We use HTTP/1.0 until we can read chunked transfer-encoding.
|
||||
|
||||
Example:
|
||||
|
||||
?- http_open("https://github.com/mthom/scryer-prolog", S, []).
|
||||
%@ S = '$stream'(0x7f86f94a6cd0)
|
||||
%@ ; false.
|
||||
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
:- module(http_open, [http_open/3]).
|
||||
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(sockets)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(format)).
|
||||
:- use_module(library(charsio)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(lists), [member/2]).
|
||||
|
||||
%% http_open(+Address, -Stream, +Options).
|
||||
%
|
||||
% Yields Stream to read the body of an HTTP reply from Address.
|
||||
% Address is a list of characters, and includes the method. Both HTTP
|
||||
% and HTTPS are supported.
|
||||
%
|
||||
% Options supported:
|
||||
%
|
||||
% * `method(+Method)`: Sets the HTTP method of the call. Method can be `get` (default), `head`, `delete`, `post`, `put` or `patch`.
|
||||
% * `data(+Data)`: Data to be sent in the request. Useful for POST, PUT and PATCH operations.
|
||||
% * `size(-Size)`: Unifies with the value of the Content-Length header
|
||||
% * `request_headers(+RequestHeaders)`: Headers to be used in the request
|
||||
% * `headers(-ListHeaders)`: Unifies with a list with all headers returned in the response
|
||||
% * `status_code(-Code)`: Unifies with the status code of the request (200, 201, 404, ...)
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- http_open("https://www.example.com", S, []), get_n_chars(S, N, HTML).
|
||||
% S = '$stream'(0x7fb548001be8), N = 1256, HTML = "<!doctype html>\n<ht ...".
|
||||
% ```
|
||||
http_open(Address, Response, Options) :-
|
||||
parse_http_options(Options, OptionValues),
|
||||
( member(method(Method), OptionValues) -> true; Method = get),
|
||||
( member(data(Data), OptionValues) -> true; Data = []),
|
||||
( member(request_headers(RequestHeaders), OptionValues) -> true; RequestHeaders = ['user-agent'("Scryer Prolog")]),
|
||||
( member(status_code(Code), OptionValues) -> true; true),
|
||||
( member(headers(Headers), OptionValues) -> true; true),
|
||||
( member(size(Size), OptionValues) -> member('content-length'(Size), Headers); true),
|
||||
'$http_open'(Address, Response, Method, Code, Data, Headers, RequestHeaders).
|
||||
http_open(Address, Stream, Options) :-
|
||||
must_be(list, Options),
|
||||
must_be(list, Address),
|
||||
once(phrase((list(SchemeCs), "://", list(Rest)), Address)),
|
||||
atom_chars(Scheme, SchemeCs),
|
||||
chars_host_url(Rest, Host, URL),
|
||||
connect(Scheme, Host, Stream0),
|
||||
format(Stream0, "\
|
||||
GET ~s HTTP/1.0\r\n\
|
||||
Host: ~w\r\n\
|
||||
User-Agent: Scryer Prolog\r\n\
|
||||
Connection: close\r\n\r\n\
|
||||
", [URL,Host]),
|
||||
read_line_to_chars(Stream0, StatusLine, []),
|
||||
once(phrase(("HTTP/1.",(['0']|['1'])," ",[D1]), StatusLine, _)),
|
||||
read_header_lines(Stream0, HeaderLines),
|
||||
handle_response(D1, HeaderLines, Stream0, Stream).
|
||||
|
||||
parse_http_options(Options, OptionValues) :-
|
||||
maplist(parse_http_options_, Options, OptionValues).
|
||||
list([]) --> [].
|
||||
list([L|Ls]) --> [L], list(Ls).
|
||||
|
||||
parse_http_options_(method(Method), method(Method)) :-
|
||||
( var(Method) ->
|
||||
throw(error(instantiation_error, http_open/3))
|
||||
;
|
||||
member(Method, [get, post, put, delete, patch, head]) -> true
|
||||
;
|
||||
throw(error(domain_error(http_option, method(Method)), _))
|
||||
).
|
||||
handle_response('2', _, Stream, Stream). % ok
|
||||
handle_response('3', HeaderLines, Stream0, Stream) :- % redirect
|
||||
close(Stream0),
|
||||
once((member(Line, HeaderLines),
|
||||
phrase(("Location: ",list(Location),"\r\n"), Line))),
|
||||
http_open(Location, Stream, []).
|
||||
|
||||
parse_http_options_(data(Data), data(Data)) :-
|
||||
( var(Data) ->
|
||||
throw(error(instantiation_error, http_open/3))
|
||||
; true
|
||||
).
|
||||
% Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
|
||||
|
||||
parse_http_options_(request_headers(Headers), request_headers(Headers)) :-
|
||||
( var(Headers) ->
|
||||
throw(error(instantiation_error, http_open/3))
|
||||
; true
|
||||
).
|
||||
read_header_lines(Stream, Hs) :-
|
||||
read_line_to_chars(Stream, Cs, []),
|
||||
( Cs == "" -> Hs = []
|
||||
; Cs == "\r\n" -> Hs = []
|
||||
; Hs = [Cs|Rest],
|
||||
read_header_lines(Stream, Rest)
|
||||
).
|
||||
|
||||
chars_host_url(Cs, Host, [/|Us]) :-
|
||||
( phrase((list(Hs),"/",list(Us)), Cs) ->
|
||||
true
|
||||
; Hs = Cs,
|
||||
Us = []
|
||||
),
|
||||
atom_chars(Host, Hs).
|
||||
|
||||
connect(https, Host, Stream) :-
|
||||
socket_client_open(Host:443, Stream, [tls(true)]).
|
||||
connect(http, Host, Stream) :-
|
||||
socket_client_open(Host:80, Stream, []).
|
||||
|
||||
parse_http_options_(size(Size), size(Size)).
|
||||
parse_http_options_(status_code(Code), status_code(Code)).
|
||||
parse_http_options_(headers(Headers), headers(Headers)).
|
||||
|
||||
@@ -1,476 +0,0 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written in December 2020 by Adrián Arroyo (adrian.arroyocalle@gmail.com)
|
||||
Updated in March 2022 by Adrián Arroyo to use the Hyper backend
|
||||
Part of Scryer Prolog.
|
||||
I place this code in the public domain. Use it in any way you want.
|
||||
*/
|
||||
|
||||
/** This library provides an starting point to build HTTP server based applications.
|
||||
It is based on [Warp](https://github.com/seanmonstar/warp), which allows for HTTP/1.0, HTTP/1.1 and HTTP/2. However,
|
||||
some advanced features that Warp provides are still not accesible.
|
||||
|
||||
## Usage
|
||||
|
||||
The main predicate of the library is `http_listen/2`, which needs a port number
|
||||
(usually 80) and a list of handlers. A handler is a compound term with the functor
|
||||
as one HTTP method (in lowercase) and followed by a Route Match and a predicate
|
||||
which will handle the call.
|
||||
|
||||
```
|
||||
:- use_module(library(http/http_server)).
|
||||
|
||||
text_handler(Request, Response) :-
|
||||
http_status_code(Response, 200),
|
||||
http_body(Response, text("Welcome to Scryer Prolog!")).
|
||||
|
||||
parameter_handler(User, Request, Response) :-
|
||||
http_body(Response, text(User)).
|
||||
|
||||
run:-
|
||||
http_listen(7890, [
|
||||
get(echo, text_handler), % GET /echo
|
||||
post(user/User, parameter_handler(User)) % POST /user/<User>
|
||||
]).
|
||||
```
|
||||
|
||||
Every handler predicate will have at least 2-arity, with Request and Response.
|
||||
Although you can work directly with `http_request` and `http_response` terms, it is
|
||||
recommeded to use the helper predicates, which are easier to understand and cleaner:
|
||||
|
||||
- `http_headers(Response/Request, Headers)`
|
||||
- `http_status_code(Responde, StatusCode)`
|
||||
- `http_body(Response/Request, text(Body))`
|
||||
- `http_body(Response/Request, binary(Body))`
|
||||
- `http_body(Request, form(Form))`
|
||||
- `http_body(Response, file(Filename))`
|
||||
- `http_redirect(Response, Url)`
|
||||
- `http_query(Request, QueryName, QueryValue)`
|
||||
|
||||
Some things that are still missing:
|
||||
|
||||
- Read forms in multipart format
|
||||
- Session handling via cookies
|
||||
- HTML Templating (but you can use [Teruel](https://github.com/aarroyoc/teruel/), [Marquete](https://github.com/aarroyoc/marquete/) or [Djota](https://github.com/aarroyoc/djota) for that)
|
||||
*/
|
||||
|
||||
|
||||
:- module(http_server, [
|
||||
http_listen/2,
|
||||
http_listen/3,
|
||||
http_headers/2,
|
||||
http_status_code/2,
|
||||
http_body/2,
|
||||
http_redirect/2,
|
||||
http_query/3,
|
||||
http_basic_auth/4
|
||||
]).
|
||||
|
||||
:- meta_predicate(http_listen(?, :)).
|
||||
:- meta_predicate(http_listen(?, :, ?)).
|
||||
|
||||
:- meta_predicate(http_basic_auth(:, :, ?, ?)).
|
||||
|
||||
:- use_module(library(charsio)).
|
||||
:- use_module(library(crypto)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(format)).
|
||||
:- use_module(library(iso_ext)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(pio)).
|
||||
:- use_module(library(time)).
|
||||
|
||||
%% http_listen(+Port, +Handlers).
|
||||
%
|
||||
% Equivalent to `http_listen(Port, Handlers, [])`.
|
||||
http_listen(Port, Module:Handlers0) :-
|
||||
must_be(integer, Port),
|
||||
must_be(list, Handlers0),
|
||||
maplist(module_qualification(Module), Handlers0, Handlers),
|
||||
http_listen_(Port, Handlers, []).
|
||||
|
||||
%% http_listen(+Port, +Handlers, +Options).
|
||||
%
|
||||
% Listens for HTTP connections on port Port. Each handler on the list Handlers should be of the form: `HttpVerb(PathUnification, Predicate)`.
|
||||
% For example: `get(user/User, get_info(User))` will match an HTTP request that is a GET, the path unifies with /user/User (where User is a variable)
|
||||
% and it will call `get_info` with three arguments: an `http_request` term, an `http_response` term and User.
|
||||
%
|
||||
% The following options are supported:
|
||||
%
|
||||
% - `tls_key(+Key)` - a TLS key for HTTPS (string)
|
||||
% - `tls_cert(+Cert)` - a TLS cert for HTTPS (string)
|
||||
% - `content_length_limit(+Limit)` - maximum length (in bytes) for the incoming bodies. By default, 32KB.
|
||||
%
|
||||
% In order to have a HTTPS server (instead of plain HTTP), both `tls_key` and `tls_cert` options must be provided.
|
||||
http_listen(Port, Module:Handlers0, Options) :-
|
||||
must_be(integer, Port),
|
||||
must_be(list, Handlers0),
|
||||
must_be(list, Options),
|
||||
maplist(module_qualification(Module), Handlers0, Handlers),
|
||||
http_listen_(Port, Handlers, Options).
|
||||
|
||||
module_qualification(M, H0, H) :-
|
||||
H0 =.. [Method, Path, Goal],
|
||||
H =.. [Method, Path, M:Goal].
|
||||
|
||||
http_listen__(Addr, HttpListener, TLSKey, TLSCert, ContentLengthLimit) :-
|
||||
'$http_listen'(Addr, HttpListener, TLSKey, TLSCert, ContentLengthLimit).
|
||||
|
||||
http_listen_stop_(HttpListener) :-
|
||||
'$http_listen_stop'(HttpListener).
|
||||
|
||||
http_accept_(HttpListener, RequestMethod, RequestPath, RequestHeaders, RequestQuery, RequestStream, ResponseHandle) :-
|
||||
'$http_accept'(HttpListener, RequestMethod, RequestPath, RequestHeaders, RequestQuery, RequestStream, ResponseHandle).
|
||||
|
||||
http_answer_(ResponseHandle, Code, Headers, ResponseStream) :-
|
||||
'$http_answer'(ResponseHandle, Code, Headers, ResponseStream).
|
||||
|
||||
http_listen_(Port, Handlers, Options) :-
|
||||
parse_options(Options, TLSKey, TLSCert, ContentLengthLimit),
|
||||
phrase(format_("0.0.0.0:~d", [Port]), Addr),
|
||||
setup_call_cleanup(
|
||||
(
|
||||
http_listen__(Addr, HttpListener, TLSKey, TLSCert, ContentLengthLimit),
|
||||
format("Listening at http://~s\n", [Addr])
|
||||
),
|
||||
http_loop(HttpListener, Handlers),
|
||||
http_listen_stop_(HttpListener)
|
||||
).
|
||||
|
||||
parse_options(Options, TLSKey, TLSCert, ContentLengthLimit) :-
|
||||
member_option_default(tls_key, Options, "", TLSKey),
|
||||
member_option_default(tls_cert, Options, "", TLSCert),
|
||||
member_option_default(content_length_limit, Options, 32768, ContentLengthLimit),
|
||||
must_be(integer, ContentLengthLimit).
|
||||
|
||||
member_option_default(Key, List, _Default, Value) :-
|
||||
X =.. [Key, Value],
|
||||
member(X, List).
|
||||
member_option_default(Key, List, Default, Default) :-
|
||||
X =.. [Key, _],
|
||||
\+ member(X, List).
|
||||
|
||||
http_loop(HttpListener, Handlers) :-
|
||||
time((
|
||||
http_accept_(HttpListener, RequestMethod, RequestPath, RequestHeaders, RequestQuery, RequestStream, ResponseHandle),
|
||||
current_time(Time),
|
||||
phrase(format_time("%Y-%m-%d (%H:%M:%S)", Time), TimeString),
|
||||
format("~s ~w ~s", [TimeString, RequestMethod, RequestPath]),
|
||||
maplist(map_header_kv, RequestHeaders, RequestHeadersKV),
|
||||
phrase(parse_queries(RequestQueries), RequestQuery),
|
||||
(
|
||||
match_handler(Handlers, RequestMethod, RequestPath, Handler) ->
|
||||
(
|
||||
HttpRequest = http_request(RequestHeadersKV, stream(RequestStream), RequestQueries),
|
||||
HttpResponse = http_response(_, _, _),
|
||||
catch(
|
||||
(call(Handler, HttpRequest, HttpResponse) ->
|
||||
send_response(ResponseHandle, HttpResponse)
|
||||
;
|
||||
setup_call_cleanup(
|
||||
http_answer_(ResponseHandle, 500, [], ResponseStream),
|
||||
format(ResponseStream, "Internal Server Error", []),
|
||||
close(ResponseStream)
|
||||
),
|
||||
throw(handler_not_available(Handler, RequestMethod, RequestPath, RequestQuery, RequestHeaders))
|
||||
),
|
||||
HandlerError,
|
||||
(
|
||||
setup_call_cleanup(
|
||||
http_answer_(ResponseHandle, 500, [], ResponseStream),
|
||||
format(ResponseStream, "Internal Server Error", []),
|
||||
close(ResponseStream)
|
||||
),
|
||||
throw(HandlerError)
|
||||
)
|
||||
)
|
||||
)
|
||||
;
|
||||
setup_call_cleanup(
|
||||
http_answer_(ResponseHandle, 404, [], ResponseStream),
|
||||
format(ResponseStream, "Not Found", []),
|
||||
close(ResponseStream)
|
||||
)
|
||||
)
|
||||
)),
|
||||
http_loop(HttpListener, Handlers).
|
||||
|
||||
send_response(ResponseHandle, http_response(StatusCode0, text(ResponseText), ResponseHeaders0)) :-
|
||||
default(StatusCode0, 200, StatusCode),
|
||||
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
|
||||
http_answer_(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream0),
|
||||
open(stream(ResponseStream0), write, ResponseStream, [type(text)]),
|
||||
catch(
|
||||
call_cleanup(format(ResponseStream, "~s", [ResponseText]),close(ResponseStream)),
|
||||
error(existence_error(stream, _), _),
|
||||
true
|
||||
).
|
||||
|
||||
send_response(ResponseHandle, http_response(StatusCode0, bytes(ResponseBytes), ResponseHeaders0)) :-
|
||||
default(StatusCode0, 200, StatusCode),
|
||||
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
|
||||
http_answer_(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
|
||||
catch(
|
||||
call_cleanup(format(ResponseStream, "~s", [ResponseBytes]),close(ResponseStream)),
|
||||
error(existence_error(stream, _), _),
|
||||
true
|
||||
).
|
||||
|
||||
send_response(ResponseHandle, http_response(StatusCode0, file(Filename), ResponseHeaders0)) :-
|
||||
default(StatusCode0, 200, StatusCode),
|
||||
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
|
||||
http_answer_(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
|
||||
catch(
|
||||
call_cleanup(
|
||||
setup_call_cleanup(
|
||||
open(Filename, read, FileStream, [type(binary)]),
|
||||
(
|
||||
get_n_chars(FileStream, _, FileCs),
|
||||
format(ResponseStream, "~s", [FileCs])
|
||||
),
|
||||
close(FileStream)
|
||||
),
|
||||
close(ResponseStream)
|
||||
),
|
||||
error(existence_error(stream, _), _),
|
||||
true
|
||||
).
|
||||
|
||||
|
||||
default(Var, Default, Out) :-
|
||||
(var(Var) -> Out = Default
|
||||
; Var = Out
|
||||
).
|
||||
|
||||
map_header_kv(T, K-V) :-
|
||||
T =.. [K0, V],
|
||||
atom_chars(K0, K).
|
||||
|
||||
map_header_kv_2(T, K-V) :-
|
||||
atom_chars(K0, K),
|
||||
T =.. [K0, V].
|
||||
|
||||
match_handler(Handlers, Method, "/", Handler) :-
|
||||
member(H, Handlers),
|
||||
H =.. [Method, /, Handler].
|
||||
match_handler(Handlers, Method, Path, Handler) :-
|
||||
member(H, Handlers),
|
||||
copy_term(H, H1),
|
||||
H1 =.. [Method, Pattern, Handler],
|
||||
\+ var(Pattern),
|
||||
phrase(path(Pattern), Path).
|
||||
match_handler(Handlers, Method, Path, Handler) :-
|
||||
member(H, Handlers),
|
||||
copy_term(H, H1),
|
||||
H1 =.. [Method, Var, Handler],
|
||||
var(Var),
|
||||
Var = Path.
|
||||
|
||||
path(Pattern) -->
|
||||
{
|
||||
Pattern =.. Parts,
|
||||
length(Parts, 3),
|
||||
nth0(1, Parts, Pattern0),
|
||||
nth0(2, Parts, PartAtom),
|
||||
(var(PartAtom) -> Part = PartAtom; atom_chars(PartAtom, Part))
|
||||
},
|
||||
path(Pattern0),
|
||||
"/",
|
||||
string_without("/", Part).
|
||||
|
||||
path(Pattern) -->
|
||||
{
|
||||
Pattern =.. Parts,
|
||||
Parts = [PartAtom],
|
||||
(var(PartAtom) -> Part = PartAtom; atom_chars(PartAtom, Part))
|
||||
},
|
||||
"/",
|
||||
string_without("/", Part).
|
||||
|
||||
path([]) --> [].
|
||||
|
||||
string_without(Not, [Char|String]) -->
|
||||
[Char],
|
||||
{
|
||||
\+ member(Char, Not)
|
||||
},
|
||||
string_without(Not, String).
|
||||
|
||||
string_without(_, []) -->
|
||||
[].
|
||||
|
||||
%% http_headers(?Request_Response, ?Headers).
|
||||
%
|
||||
% True iff `Request_Response` is a request or response with headers Headers. Can be used both to get headers (usually in from a request)
|
||||
% and to add headers (usually in a response).
|
||||
http_headers(http_request(Headers, _, _), Headers).
|
||||
http_headers(http_response(_, _, Headers), Headers).
|
||||
|
||||
%% http_body(?Request_Response, ?Body).
|
||||
%
|
||||
% True iff Body is the body of the request or response. A body can be of the following types:
|
||||
%
|
||||
% * `bytes(Bytes)` for both requests and responses, interprets the body as bytes
|
||||
% * `text(Bytes)` for both requests and responses, interprets the body as text
|
||||
% * `form(Form)` only for requests, interprets the body as an `application/x-www-form-urlencoded` form.
|
||||
% * `file(File)` only for responses, interprets the body as the content of a file (useful to send static files).
|
||||
http_body(http_request(_, stream(StreamBody), _), bytes(BytesBody)) :- get_n_chars(StreamBody, _, BytesBody).
|
||||
http_body(http_request(_, stream(StreamBody), _), text(TextBody)) :- get_n_chars(StreamBody, _, TextBody).
|
||||
http_body(http_request(Headers, stream(StreamBody), _), form(FormBody)) :-
|
||||
member("content-type"-"application/x-www-form-urlencoded", Headers),
|
||||
get_n_chars(StreamBody, _, TextBody),
|
||||
phrase(parse_queries(FormBody), TextBody).
|
||||
http_body(http_request(_, Body, _), Body).
|
||||
http_body(http_response(_, Body, _), Body).
|
||||
|
||||
%% http_status_code(?Response, ?StatusCode).
|
||||
%
|
||||
% True iff the status code of the response Response unifies with StatusCode.
|
||||
http_status_code(http_response(StatusCode, _, _), StatusCode).
|
||||
|
||||
%% http_redirect(-Response, +Uri).
|
||||
%
|
||||
% True iff Response is a response that redirects the user to the uri Uri.
|
||||
http_redirect(http_response(307, text("Moved Temporarily"), ["Location"-Uri]), Uri).
|
||||
|
||||
%% http_query(+Request, ?Key, ?Value).
|
||||
%
|
||||
% True iff there's a query in request Request with key Key and value Value.
|
||||
http_query(http_request(_, _, Queries), Key, Value) :- member(Key-Value, Queries).
|
||||
|
||||
parse_queries([Key-Value|Queries]) -->
|
||||
string_without("=", Key0),
|
||||
"=",
|
||||
string_without("&", Value0),
|
||||
"&",
|
||||
parse_queries(Queries),
|
||||
{
|
||||
phrase(url_decode(Key), Key0),
|
||||
phrase(url_decode(Value), Value0)
|
||||
}.
|
||||
|
||||
parse_queries([Key-Value]) -->
|
||||
string_without("=", Key0),
|
||||
"=",
|
||||
string_without(" ", Value0),
|
||||
{
|
||||
phrase(url_decode(Key), Key0),
|
||||
phrase(url_decode(Value), Value0)
|
||||
}.
|
||||
|
||||
parse_queries([]) -->
|
||||
[].
|
||||
|
||||
% Decodes a UTF-8 URL Encoded string: RFC-1738
|
||||
url_decode([Char|Chars]) -->
|
||||
[Char],
|
||||
{
|
||||
Char \= '%',
|
||||
Char \= (+)
|
||||
},
|
||||
url_decode(Chars).
|
||||
url_decode([' '|Chars]) -->
|
||||
"+",
|
||||
url_decode(Chars).
|
||||
url_decode([Char|Chars]) -->
|
||||
"%",
|
||||
[A],
|
||||
[B],
|
||||
{
|
||||
hex_bytes([A,B], Bytes),
|
||||
Bytes = [FirstByte|_],
|
||||
FirstByte < 128,
|
||||
chars_utf8bytes(Chars0, Bytes),
|
||||
Chars0 = [Char]
|
||||
},
|
||||
url_decode(Chars).
|
||||
url_decode([Char|Chars]) -->
|
||||
"%",
|
||||
[A, B],
|
||||
"%",
|
||||
[C, D],
|
||||
{
|
||||
hex_bytes([A,B,C,D], Bytes),
|
||||
Bytes = [FirstByte|_],
|
||||
FirstByte < 224,
|
||||
chars_utf8bytes(Chars0, Bytes),
|
||||
Chars0 = [Char]
|
||||
},
|
||||
url_decode(Chars).
|
||||
url_decode([Char|Chars]) -->
|
||||
"%",
|
||||
[A, B],
|
||||
"%",
|
||||
[C, D],
|
||||
"%",
|
||||
[E, F],
|
||||
{
|
||||
hex_bytes([A,B,C,D,E,F], Bytes),
|
||||
Bytes = [FirstByte|_],
|
||||
FirstByte < 240,
|
||||
chars_utf8bytes(Chars0, Bytes),
|
||||
Chars0 = [Char]
|
||||
},
|
||||
url_decode(Chars).
|
||||
url_decode([Char|Chars]) -->
|
||||
"%",
|
||||
[A, B],
|
||||
"%",
|
||||
[C, D],
|
||||
"%",
|
||||
[E, F],
|
||||
"%",
|
||||
[H, I],
|
||||
{
|
||||
hex_bytes([A,B,C,D,E,F,H,I], Bytes),
|
||||
chars_utf8bytes(Chars0, Bytes),
|
||||
Chars0 = [Char]
|
||||
},
|
||||
url_decode(Chars).
|
||||
|
||||
url_decode([]) --> [].
|
||||
|
||||
%% http_basic_auth(+LoginPredicate, +Handler, +Request, -Response)
|
||||
%
|
||||
% Metapredicate that wraps an existing Handler with an HTTP Basic Auth flow.
|
||||
% Checks if a given user + password is authorized to execute that handler, returning 401
|
||||
% if it's not satisfied.
|
||||
%
|
||||
% `LoginPredicate` must be a predicate of arity 2 that takes a User and a Password.
|
||||
% `Handler` will have, in addition to the Request and Response arguments, a User argument
|
||||
% containing the User given in the authentication.
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% main :-
|
||||
% http_listen(8800,[get('/', http_basic_auth(login, inside_handler("data")))]).
|
||||
%
|
||||
% login(User, Pass) :-
|
||||
% User = "aarroyoc",
|
||||
% Pass = "123456".
|
||||
%
|
||||
% inside_handler(Data, User, Request, Response) :-
|
||||
% http_body(Response, text(User)).
|
||||
% ```
|
||||
http_basic_auth(LoginPredicate, Handler, Request, Response) :-
|
||||
http_headers(Request, Headers),
|
||||
member("authorization"-AuthorizationStr, Headers),
|
||||
append("Basic ", Coded, AuthorizationStr),
|
||||
chars_base64(UserPass, Coded, []),
|
||||
append(User, [':'|Password], UserPass),
|
||||
(
|
||||
call(LoginPredicate, User, Password) ->
|
||||
call(Handler, User, Request, Response)
|
||||
; http_basic_auth_unauthorized_response(Response)
|
||||
).
|
||||
|
||||
http_basic_auth(_LoginPredicate, _Handler, Request, Response) :-
|
||||
http_headers(Request, Headers),
|
||||
\+ member("authorization"-_, Headers),
|
||||
http_basic_auth_unauthorized_response(Response).
|
||||
|
||||
http_basic_auth_unauthorized_response(Response) :-
|
||||
http_status_code(Response, 401),
|
||||
http_headers(Response, ["www-authenticate"-"Basic realm=\"Scryer Prolog\", charset=\"UTF-8\""]),
|
||||
http_body(Response, text("Unauthorized")).
|
||||
|
||||
@@ -1,408 +1,163 @@
|
||||
/** Useful general predicates that are not ISO standard yet
|
||||
%% for builtins that are not part of the ISO standard.
|
||||
%% must be loaded at the REPL with
|
||||
|
||||
Predicates available here are similar to the ones defined in builtin.pl,
|
||||
but they're not part of the ISO Prolog standard at the moment.
|
||||
*/
|
||||
%% ?- use_module(library(iso_ext)).
|
||||
|
||||
:- module(iso_ext, [bb_b_put/2,
|
||||
bb_get/2,
|
||||
bb_put/2,
|
||||
call_cleanup/2,
|
||||
call_with_inference_limit/3,
|
||||
call_residue_vars/2,
|
||||
forall/2,
|
||||
partial_string/1,
|
||||
partial_string/3,
|
||||
partial_string_tail/2,
|
||||
setup_call_cleanup/3,
|
||||
succ/2,
|
||||
call_nth/2,
|
||||
countall/2,
|
||||
copy_term_nat/2,
|
||||
copy_term/3]).
|
||||
:- module(iso_ext, [bb_b_put/2, bb_get/2, bb_put/2, call_cleanup/2,
|
||||
call_with_inference_limit/3, forall/2,
|
||||
partial_string/1, partial_string/3,
|
||||
partial_string_tail/2, setup_call_cleanup/3,
|
||||
variant/2]).
|
||||
|
||||
:- use_module(library(error), [can_be/2,
|
||||
must_be/2,
|
||||
domain_error/3,
|
||||
instantiation_error/1,
|
||||
type_error/3]).
|
||||
|
||||
:- use_module(library(lists), [maplist/3]).
|
||||
|
||||
:- use_module(library('$project_atts')).
|
||||
|
||||
:- meta_predicate(forall(0, 0)).
|
||||
|
||||
%% forall(Generate, Test).
|
||||
%
|
||||
% For all bindings possible by Generate, Test must be true.
|
||||
%
|
||||
% In this example, it checks that all numbers are even:
|
||||
%
|
||||
% ```
|
||||
% ?- Ns = [2,4,6], forall(member(N, Ns), 0 is N mod 2).
|
||||
% Ns = [2,4,6].
|
||||
% ```
|
||||
forall(Generate, Test) :-
|
||||
\+ (Generate, \+ Test).
|
||||
|
||||
% (non-)backtrackable global variables.
|
||||
%% (non-)backtrackable global variables.
|
||||
|
||||
%% bb_put(+Key, +Value).
|
||||
%
|
||||
% Sets a global variable named Key (must be an atom) with value Value.
|
||||
% The global variable isn't backtrackable. Check `bb_b_put/2` for the
|
||||
% backtrackable version.
|
||||
%
|
||||
% ```
|
||||
% ?- bb_put(city, "Valladolid").
|
||||
% true.
|
||||
% ?- bb_get(city, X).
|
||||
% X = "Valladolid".
|
||||
% ```
|
||||
%
|
||||
% In this example one can understand the difference between `bb_put/2` and
|
||||
% `bb_b_put/2`:
|
||||
%
|
||||
% ```
|
||||
% ?- bb_put(city, "Valladolid"), (bb_put(city, "Salamanca"), false);(bb_get(city, X)).
|
||||
% X = "Salamanca".
|
||||
% ?- bb_put(city, "Valladolid"), (bb_b_put(city, "Salamanca"), false);(bb_get(city, X)).
|
||||
% X = "Valladolid".
|
||||
% ```
|
||||
bb_put(Key, Value) :-
|
||||
( atom(Key) ->
|
||||
'$store_global_var'(Key, Value)
|
||||
; type_error(atom, Key, bb_put/2)
|
||||
bb_put(Key, Value) :- atom(Key), !, '$store_global_var'(Key, Value).
|
||||
bb_put(Key, _) :- throw(error(type_error(atom, Key), bb_put/2)).
|
||||
|
||||
%% backtrackable global variables.
|
||||
|
||||
bb_b_put(Key, NewValue) :-
|
||||
( '$bb_get_with_offset'(Key, OldValue, OldOffset) ->
|
||||
call_cleanup((store_global_var_with_offset(Key, NewValue) ; false),
|
||||
reset_global_var_at_offset(Key, OldValue, OldOffset))
|
||||
; call_cleanup((store_global_var_with_offset(Key, NewValue) ; false),
|
||||
reset_global_var_at_key(Key))
|
||||
).
|
||||
|
||||
% backtrackable global variables.
|
||||
store_global_var_with_offset(Key, Value) :- '$store_global_var_with_offset'(Key, Value).
|
||||
|
||||
%% bb_b_put(+Key, +Value).
|
||||
%
|
||||
% Sets a global variable named Key (must be an atom) with value Value.
|
||||
% The global variable is backtrackable. Check `bb_put/2` for the
|
||||
% non-backtrackable version.
|
||||
%
|
||||
% ```
|
||||
% ?- bb_b_put(city, "Valladolid").
|
||||
% true.
|
||||
% ?- bb_get(city, X).
|
||||
% X = "Valladolid".
|
||||
% ```
|
||||
%
|
||||
% In this example one can understand the difference between `bb_put/2` and
|
||||
% `bb_b_put/2`:
|
||||
%
|
||||
% ```
|
||||
% ?- bb_put(city, "Valladolid"), (bb_put(city, "Salamanca"), false);(bb_get(city, X)).
|
||||
% X = "Salamanca".
|
||||
% ?- bb_put(city, "Valladolid"), (bb_b_put(city, "Salamanca"), false);(bb_get(city, X)).
|
||||
% X = "Valladolid".
|
||||
% ```
|
||||
bb_b_put(Key, Value) :-
|
||||
( atom(Key) ->
|
||||
'$store_backtrackable_global_var'(Key, Value)
|
||||
; type_error(atom, Key, bb_b_put/2)
|
||||
).
|
||||
store_global_var(Key, Value) :- '$store_global_var'(Key, Value).
|
||||
|
||||
%% bb_get(+Key, -Value).
|
||||
%
|
||||
% Gets the value Value of a global variable named Key (must be an atom)
|
||||
bb_get(Key, Value) :-
|
||||
( atom(Key) ->
|
||||
'$fetch_global_var'(Key, Value)
|
||||
; type_error(atom, Key, bb_get/2)
|
||||
).
|
||||
reset_global_var_at_key(Key) :- '$reset_global_var_at_key'(Key).
|
||||
|
||||
reset_global_var_at_offset(Key, Value, Offset) :- '$reset_global_var_at_offset'(Key, Value, Offset).
|
||||
|
||||
%% succ(?I, ?S).
|
||||
%
|
||||
% True iff S is the successor of the non-negative integer I.
|
||||
% At least one of the arguments must be instantiated.
|
||||
'$bb_get_with_offset'(Key, OldValue, Offset) :-
|
||||
atom(Key), !, '$fetch_global_var_with_offset'(Key, OldValue, Offset).
|
||||
'$bb_get_with_offset'(Key, _, _) :-
|
||||
throw(error(type_error(atom, Key), bb_b_put/2)).
|
||||
|
||||
succ(I, S) :-
|
||||
can_be(not_less_than_zero, I),
|
||||
can_be(not_less_than_zero, S),
|
||||
( integer(S) ->
|
||||
S > 0,
|
||||
I is S-1
|
||||
; integer(I) ->
|
||||
S is I+1
|
||||
; instantiation_error(succ/2)
|
||||
).
|
||||
bb_get(Key, Value) :- atom(Key), !, '$fetch_global_var'(Key, Value).
|
||||
bb_get(Key, _) :- throw(error(type_error(atom, Key), bb_get/2)).
|
||||
|
||||
call_cleanup(G, C) :- setup_call_cleanup(true, G, C).
|
||||
|
||||
|
||||
% setup_call_cleanup.
|
||||
|
||||
:- meta_predicate(call_cleanup(0, 0)).
|
||||
|
||||
%% call_cleanup(Goal, Cleanup).
|
||||
%
|
||||
% Executes Goal and then, either on success or failure, executes Cleanup.
|
||||
% The success or failure of Cleanup is ignored and choice points created inside are destroyed.
|
||||
call_cleanup(G, C) :- setup_call_cleanup(true, G, C).
|
||||
|
||||
:- meta_predicate(setup_call_cleanup(0, 0, 0)).
|
||||
|
||||
:- non_counted_backtracking setup_call_cleanup/3.
|
||||
|
||||
%% setup_call_cleanup(Setup, Goal, Cleanup).
|
||||
%
|
||||
% If Setup succeeds, Cleanup will be called after the execution of Goal. Goal itself can succeed or not.
|
||||
%
|
||||
% In this example, we use the predicate to always close an open file:
|
||||
%
|
||||
% ```
|
||||
% ?- setup_call_cleanup(open(File, read, Stream), do_something_with_stream(Stream), close(Stream)).
|
||||
% ```
|
||||
setup_call_cleanup(S, G, C) :-
|
||||
'$get_b_value'(B),
|
||||
'$call_with_inference_counting'(call(S)),
|
||||
call(S),
|
||||
'$set_cp_by_default'(B),
|
||||
'$get_current_scc_block'(Bb),
|
||||
( C = _:CC,
|
||||
var(CC) ->
|
||||
instantiation_error(setup_call_cleanup/3)
|
||||
; scc_helper(C, G, Bb)
|
||||
'$get_current_block'(Bb),
|
||||
( '$call_with_default_policy'(var(C)) ->
|
||||
throw(error(instantiation_error, setup_call_cleanup/3))
|
||||
; '$call_with_default_policy'(scc_helper(C, G, Bb))
|
||||
).
|
||||
|
||||
:- meta_predicate(scc_helper(?,0,?)).
|
||||
|
||||
:- non_counted_backtracking scc_helper/3.
|
||||
|
||||
scc_helper(C, G, Bb) :-
|
||||
'$get_cp'(Cp),
|
||||
'$install_scc_cleaner'(C),
|
||||
'$call_with_inference_counting'(call(G)),
|
||||
( '$check_cp'(Cp) ->
|
||||
'$reset_scc_block'(Bb),
|
||||
run_cleaners_without_handling(Cp)
|
||||
; true
|
||||
; '$fail'
|
||||
).
|
||||
'$get_cp'(Cp), '$install_scc_cleaner'(C, NBb), call(G),
|
||||
( '$check_cp'(Cp) ->
|
||||
'$reset_block'(Bb),
|
||||
'$call_with_default_policy'(run_cleaners_without_handling(Cp))
|
||||
; '$call_with_default_policy'(true)
|
||||
; '$reset_block'(NBb),
|
||||
'$fail').
|
||||
scc_helper(_, _, Bb) :-
|
||||
'$reset_scc_block'(Bb),
|
||||
'$push_ball_stack',
|
||||
run_cleaners_with_handling,
|
||||
'$pop_from_ball_stack',
|
||||
'$unwind_stack'.
|
||||
'$reset_block'(Bb),
|
||||
'$get_ball'(Ball),
|
||||
'$call_with_default_policy'(run_cleaners_with_handling),
|
||||
'$erase_ball',
|
||||
'$call_with_default_policy'(throw(Ball)).
|
||||
scc_helper(_, _, _) :-
|
||||
'$get_cp'(Cp),
|
||||
run_cleaners_without_handling(Cp),
|
||||
'$call_with_default_policy'(run_cleaners_without_handling(Cp)),
|
||||
'$fail'.
|
||||
|
||||
:- non_counted_backtracking run_cleaners_with_handling/0.
|
||||
|
||||
run_cleaners_with_handling :-
|
||||
'$get_scc_cleaner'(C),
|
||||
'$get_cp'(B),
|
||||
catch(C, _, true),
|
||||
'$get_scc_cleaner'(C), '$get_level'(B),
|
||||
'$call_with_default_policy'(catch(C, _, true)),
|
||||
'$set_cp_by_default'(B),
|
||||
run_cleaners_with_handling.
|
||||
'$call_with_default_policy'(run_cleaners_with_handling).
|
||||
run_cleaners_with_handling :-
|
||||
'$restore_cut_policy'.
|
||||
|
||||
:- non_counted_backtracking run_cleaners_without_handling/1.
|
||||
|
||||
run_cleaners_without_handling(Cp) :-
|
||||
'$get_scc_cleaner'(C),
|
||||
'$get_cp'(B),
|
||||
'$get_level'(B),
|
||||
call(C),
|
||||
'$set_cp_by_default'(B),
|
||||
run_cleaners_without_handling(Cp).
|
||||
'$call_with_default_policy'(run_cleaners_without_handling(Cp)).
|
||||
run_cleaners_without_handling(Cp) :-
|
||||
'$set_cp_by_default'(Cp),
|
||||
'$restore_cut_policy'.
|
||||
|
||||
% call_with_inference_limit
|
||||
|
||||
:- meta_predicate(call_with_inference_limit(0, ?, ?)).
|
||||
:- non_counted_backtracking end_block/4.
|
||||
end_block(_, Bb, NBb, L) :-
|
||||
'$clean_up_block'(NBb),
|
||||
'$reset_block'(Bb).
|
||||
end_block(B, Bb, NBb, L) :-
|
||||
'$install_inference_counter'(B, L, _),
|
||||
'$reset_block'(NBb),
|
||||
'$fail'.
|
||||
|
||||
:- non_counted_backtracking call_with_inference_limit/3.
|
||||
:- non_counted_backtracking handle_ile/3.
|
||||
handle_ile(B, inference_limit_exceeded(B), inference_limit_exceeded) :- !.
|
||||
handle_ile(B, E, _) :-
|
||||
'$remove_call_policy_check'(B),
|
||||
'$call_with_default_policy'(throw(E)).
|
||||
|
||||
%% call_with_inference_limit(Goal, Limit, Result).
|
||||
%
|
||||
% Similar to `call(Goal)` but it limits the number of inferences for each solution of Goal.
|
||||
% Calls to it may be nested, but only the last limit will be in power.
|
||||
call_with_inference_limit(G, L, R) :-
|
||||
( integer(L) ->
|
||||
( L < 0 ->
|
||||
domain_error(not_less_than_zero, L, call_with_inference_limit/3)
|
||||
; true
|
||||
)
|
||||
; var(L) ->
|
||||
instantiation_error(call_with_inference_limit/3)
|
||||
; type_error(integer, L, call_with_inference_limit/3)
|
||||
),
|
||||
'$get_current_block'(Bb),
|
||||
'$get_b_value'(B),
|
||||
call_with_inference_limit(G, L, R, Bb, B),
|
||||
'$call_with_default_policy'(call_with_inference_limit(G, L, R, Bb, B)),
|
||||
'$remove_call_policy_check'(B).
|
||||
|
||||
|
||||
:- meta_predicate(call_with_inference_limit(0,?,?,?,?)).
|
||||
|
||||
:- non_counted_backtracking call_with_inference_limit/5.
|
||||
|
||||
call_with_inference_limit(G, L, R, Bb, B) :-
|
||||
'$install_new_block'(NBb),
|
||||
'$install_inference_counter'(NBb, L, Count0),
|
||||
'$call_with_inference_counting'(call(G)),
|
||||
'$install_inference_counter'(B, L, Count0),
|
||||
call(G),
|
||||
'$inference_level'(R, B),
|
||||
'$remove_inference_counter'(NBb, Count1),
|
||||
Diff is L - (Count1 - Count0),
|
||||
( '$clean_up_block'(NBb),
|
||||
'$reset_block'(Bb)
|
||||
; '$install_inference_counter'(NBb, Diff, _),
|
||||
'$reset_block'(NBb),
|
||||
'$fail'
|
||||
).
|
||||
'$remove_inference_counter'(B, Count1),
|
||||
'$call_with_default_policy'(is(Diff, L - (Count1 - Count0))),
|
||||
'$call_with_default_policy'(end_block(B, Bb, NBb, Diff)).
|
||||
call_with_inference_limit(_, _, R, Bb, B) :-
|
||||
( '$inference_limit_exceeded' ->
|
||||
R = inference_limit_exceeded
|
||||
; true
|
||||
),
|
||||
'$get_current_block'(NBb),
|
||||
'$remove_inference_counter'(NBb, _),
|
||||
'$reset_block'(Bb),
|
||||
'$remove_call_policy_check'(B),
|
||||
( '$get_ball'(_),
|
||||
'$push_ball_stack',
|
||||
'$get_cp'(Cp),
|
||||
'$set_cp_by_default'(Cp),
|
||||
'$pop_from_ball_stack',
|
||||
'$unwind_stack'
|
||||
; nonvar(R)
|
||||
).
|
||||
'$remove_inference_counter'(B, _),
|
||||
( '$get_ball'(Ball),
|
||||
'$get_level'(Cp),
|
||||
'$set_cp_by_default'(Cp)
|
||||
; '$remove_call_policy_check'(B),
|
||||
'$fail'
|
||||
),
|
||||
'$erase_ball',
|
||||
'$call_with_default_policy'(handle_ile(B, Ball, R)).
|
||||
|
||||
%% partial_string(String, Ls0, Ls)
|
||||
%
|
||||
% Explicitly construct a partial string "manually". It can be used as an optimized append/3.
|
||||
% It's not recommended to use this predicate in application code.
|
||||
partial_string(String, Ls0, Ls) :-
|
||||
must_be(chars, String),
|
||||
variant(X, Y) :- '$variant'(X, Y).
|
||||
|
||||
partial_string(String, L, L0) :-
|
||||
( String == [] ->
|
||||
Ls0 = Ls
|
||||
; '$create_partial_string'(String, Ls0, Ls)
|
||||
L = L0
|
||||
; catch(atom_chars(Atom, String),
|
||||
error(E, _),
|
||||
throw(error(E, partial_string/3))),
|
||||
'$create_partial_string'(Atom, L, L0)
|
||||
).
|
||||
|
||||
%% partial_string(+String)
|
||||
%
|
||||
% Succeeds if String is a _partial string_. A partial string is a string composed of several smaller
|
||||
% strings, even just one. That means all strings in Scryer are partial strings.
|
||||
partial_string(String) :-
|
||||
'$is_partial_string'(String).
|
||||
|
||||
%% partial_string_tail(+String, -Tail).
|
||||
%
|
||||
% Unifies Tail with the last section of the partial string.
|
||||
% It's not recommended to use this predicate in application code.
|
||||
partial_string_tail(String, Tail) :-
|
||||
( partial_string(String) ->
|
||||
'$partial_string_tail'(String, Tail)
|
||||
; throw(error(type_error(partial_string, String), partial_string_tail/2))
|
||||
).
|
||||
|
||||
:- dynamic(i_call_nth_nesting/2).
|
||||
:- dynamic(i_call_nth_counter/1).
|
||||
|
||||
:- meta_predicate(call_nth(0, ?)).
|
||||
|
||||
%% call_nth(Goal, N).
|
||||
%
|
||||
% Succeeds when Goal succeeded for the Nth time (there are at least N solutions)
|
||||
call_nth(Goal, N) :-
|
||||
can_be(integer, N),
|
||||
( integer(N) ->
|
||||
( N < 0 ->
|
||||
domain_error(not_less_than_zero, N, call_nth/2)
|
||||
; N > 0
|
||||
)
|
||||
; true
|
||||
),
|
||||
setup_call_cleanup(call_nth_nesting(C, ID),
|
||||
( Goal,
|
||||
bb_get(ID, N0),
|
||||
N1 is N0 + 1,
|
||||
bb_put(ID, N1),
|
||||
( integer(N) ->
|
||||
N = N1,
|
||||
!
|
||||
; N = N1
|
||||
)
|
||||
),
|
||||
( bb_get(i_call_nth_counter, C) ->
|
||||
C1 is C - 1,
|
||||
bb_put(i_call_nth_counter, C1)
|
||||
; true
|
||||
)).
|
||||
|
||||
call_nth_nesting(C, ID) :-
|
||||
( bb_get(i_call_nth_counter, C0) ->
|
||||
C is C0 + 1
|
||||
; C = 0
|
||||
),
|
||||
number_chars(C, Cs),
|
||||
atom_chars(Atom, Cs),
|
||||
atom_concat(i_call_nth_nesting_, Atom, ID),
|
||||
bb_put(ID, 0),
|
||||
bb_put(i_call_nth_counter, C).
|
||||
|
||||
%% countall(G_0, N).
|
||||
%
|
||||
% countall(G_0, N) is true iff N unifies with the total number of
|
||||
% answers of call(G_0).
|
||||
|
||||
:- meta_predicate(countall(0, ?)).
|
||||
|
||||
countall(Goal, N) :-
|
||||
can_be(integer, N),
|
||||
( integer(N) ->
|
||||
( N < 0 ->
|
||||
domain_error(not_less_than_zero, N, countall/2)
|
||||
; true
|
||||
)
|
||||
; true
|
||||
),
|
||||
setup_call_cleanup(call_nth_nesting(C, ID),
|
||||
( ( Goal,
|
||||
bb_get(ID, N0),
|
||||
N1 is N0 + 1,
|
||||
bb_put(ID, N1),
|
||||
false
|
||||
; bb_get(ID, N)
|
||||
)
|
||||
),
|
||||
( bb_get(i_call_nth_counter, C) ->
|
||||
C1 is C - 1,
|
||||
bb_put(i_call_nth_counter, C1)
|
||||
; true
|
||||
)).
|
||||
|
||||
%% copy_term_nat(Source, Dest)
|
||||
%
|
||||
% Similar to `copy_term/2` but without attribute variables
|
||||
copy_term_nat(Source, Dest) :-
|
||||
'$copy_term_without_attr_vars'(Source, Dest).
|
||||
|
||||
%% copy_term(+Term, -Copy, -Gs).
|
||||
%
|
||||
% Produce a deep copy of Term and unify it to Copy, without attributes.
|
||||
% Unify Gs with a list of goals that represent the attributes of Term.
|
||||
% Similar to `copy_term/2` but splitting the attributes.
|
||||
copy_term(Term, Copy, Gs) :-
|
||||
can_be(list, Gs),
|
||||
findall(Term-Rs, '$project_atts':term_residual_goals(Term,Rs), [Copy-Gs]),
|
||||
( var(Gs) ->
|
||||
Gs = []
|
||||
; true
|
||||
).
|
||||
|
||||
:- meta_predicate(call_residue_vars(0, ?)).
|
||||
|
||||
call_residue_vars(Goal, Vars) :-
|
||||
can_be(list, Vars),
|
||||
'$get_attr_var_queue_delim'(B),
|
||||
call(Goal),
|
||||
'$get_attr_var_queue_beyond'(B, Vars).
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
/*
|
||||
Author: Ulrich Neumerkel
|
||||
E-mail: ulrich@complang.tuwien.ac.at
|
||||
Copyright (C): 2009 Ulrich Neumerkel. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY Ulrich Neumerkel ``AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Ulrich Neumerkel OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation
|
||||
are those of the authors and should not be interpreted as representing
|
||||
official policies, either expressed or implied, of Ulrich Neumerkel.
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
:- module(lambda, [
|
||||
(^)/3, (^)/4, (^)/5, (^)/6, (^)/7, (^)/8, (^)/9, (^)/10,
|
||||
(\)/1, (\)/2, (\)/3, (\)/4, (\)/5, (\)/6, (\)/7, (\)/8,
|
||||
(+\)/2, (+\)/3, (+\)/4, (+\)/5, (+\)/6, (+\)/7, (+\)/8,
|
||||
(+\)/9, op(201,xfx,+\)]).
|
||||
|
||||
:- use_module(library(iso_ext)).
|
||||
|
||||
/** <module> Lambda expressions
|
||||
|
||||
This library provides lambda expressions to simplify higher order
|
||||
programming based on call/N.
|
||||
|
||||
Lambda expressions are represented by ordinary Prolog terms.
|
||||
There are two kinds of lambda expressions:
|
||||
|
||||
```
|
||||
Free+\X1^X2^ ..^XN^Goal
|
||||
|
||||
\X1^X2^ ..^XN^Goal
|
||||
```
|
||||
|
||||
The second is a shorthand for `t+\X1^X2^..^XN^Goal`.
|
||||
|
||||
Xi are the parameters.
|
||||
|
||||
Goal is a goal or continuation. Syntax note: Operators within Goal
|
||||
require parentheses due to the low precedence of the ^ operator.
|
||||
|
||||
Free contains variables that are valid outside the scope of the lambda
|
||||
expression. They are thus free variables within.
|
||||
|
||||
All other variables of Goal are considered local variables. They must
|
||||
not appear outside the lambda expression. This restriction is
|
||||
currently not checked. Violations may lead to unexpected bindings.
|
||||
|
||||
In the following example the parentheses around X>3 are necessary.
|
||||
|
||||
```
|
||||
?- use_module(library(lambda)).
|
||||
?- use_module(library(lists)).
|
||||
|
||||
?- maplist(\X^(X>3),[4,5,9]).
|
||||
true.
|
||||
```
|
||||
|
||||
In the following X is a variable that is shared by both instances of
|
||||
the lambda expression. The second query illustrates the cooperation of
|
||||
continuations and lambdas. The lambda expression is in this case a
|
||||
continuation expecting a further argument.
|
||||
|
||||
```
|
||||
?- use_module(library(dif)).
|
||||
true.
|
||||
|
||||
?- Xs = [A,B], maplist(X+\Y^dif(X,Y), Xs).
|
||||
Xs = [A,B], dif:dif(X,A), dif:dif(X,B).
|
||||
|
||||
?- Xs = [A,B], maplist(X+\dif(X), Xs).
|
||||
Xs = [A,B], dif:dif(X,A), dif:dif(X,B).
|
||||
```
|
||||
|
||||
The following queries are all equivalent. To see this, use
|
||||
the fact `f(x,y)`.
|
||||
|
||||
```
|
||||
?- call(f,A1,A2).
|
||||
?- call(\X^f(X),A1,A2).
|
||||
?- call(\X^Y^f(X,Y), A1,A2).
|
||||
?- call(\X^(X+\Y^f(X,Y)), A1,A2).
|
||||
?- call(call(f, A1),A2).
|
||||
?- call(f(A1),A2).
|
||||
?- f(A1,A2).
|
||||
A1 = x, A2 = y.
|
||||
```
|
||||
|
||||
Further discussions
|
||||
[http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord](http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord)
|
||||
|
||||
@tbd Static expansion similar to apply_macros.
|
||||
@author Ulrich Neumerkel
|
||||
*/
|
||||
|
||||
:- meta_predicate(^(?,0,?)).
|
||||
:- meta_predicate(^(?,1,?,?)).
|
||||
:- meta_predicate(^(?,2,?,?,?)).
|
||||
:- meta_predicate(^(?,3,?,?,?,?)).
|
||||
:- meta_predicate(^(?,4,?,?,?,?,?)).
|
||||
:- meta_predicate(^(?,5,?,?,?,?,?,?)).
|
||||
:- meta_predicate(^(?,6,?,?,?,?,?,?,?)).
|
||||
:- meta_predicate(^(?,7,?,?,?,?,?,?,?,?)).
|
||||
:- meta_predicate(\(0)).
|
||||
:- meta_predicate(\(1,?)).
|
||||
:- meta_predicate(\(2,?,?)).
|
||||
:- meta_predicate(\(3,?,?,?)).
|
||||
:- meta_predicate(\(4,?,?,?,?)).
|
||||
:- meta_predicate(\(5,?,?,?,?,?)).
|
||||
:- meta_predicate(\(6,?,?,?,?,?,?)).
|
||||
:- meta_predicate(\(7,?,?,?,?,?,?,?)).
|
||||
:- meta_predicate(+\(?,0)).
|
||||
:- meta_predicate(+\(?,1,?)).
|
||||
:- meta_predicate(+\(?,2,?,?)).
|
||||
:- meta_predicate(+\(?,3,?,?,?)).
|
||||
:- meta_predicate(+\(?,4,?,?,?,?)).
|
||||
:- meta_predicate(+\(?,5,?,?,?,?,?)).
|
||||
:- meta_predicate(+\(?,6,?,?,?,?,?,?)).
|
||||
:- meta_predicate(+\(?,7,?,?,?,?,?,?,?)).
|
||||
|
||||
:- meta_predicate(no_hat_call(0)).
|
||||
|
||||
^(V1,C_0,V1) :-
|
||||
no_hat_call(C_0).
|
||||
^(V1,C_1,V1,V2) :-
|
||||
call(C_1,V2).
|
||||
^(V1,C_2,V1,V2,V3) :-
|
||||
call(C_2,V2,V3).
|
||||
^(V1,C_3,V1,V2,V3,V4) :-
|
||||
call(C_3,V2,V3,V4).
|
||||
^(V1,C_4,V1,V2,V3,V4,V5) :-
|
||||
call(C_4,V2,V3,V4,V5).
|
||||
^(V1,C_5,V1,V2,V3,V4,V5,V6) :-
|
||||
call(C_5,V2,V3,V4,V5,V6).
|
||||
^(V1,C_6,V1,V2,V3,V4,V5,V6,V7) :-
|
||||
call(C_6,V2,V3,V4,V5,V6,V7).
|
||||
^(V1,C_7,V1,V2,V3,V4,V5,V6,V7,V8) :-
|
||||
call(C_7,V2,V3,V4,V5,V6,V7,V8).
|
||||
|
||||
\(FC_0) :-
|
||||
copy_term_nat(FC_0,C_0),
|
||||
no_hat_call(C_0).
|
||||
\(FC_1,V1) :-
|
||||
copy_term_nat(FC_1,C_1),
|
||||
call(C_1,V1).
|
||||
\(FC_2,V1,V2) :-
|
||||
copy_term_nat(FC_2,C_2),
|
||||
call(C_2,V1,V2).
|
||||
\(FC_3,V1,V2,V3) :-
|
||||
copy_term_nat(FC_3,C_3),
|
||||
call(C_3,V1,V2,V3).
|
||||
\(FC_4,V1,V2,V3,V4) :-
|
||||
copy_term_nat(FC_4,C_4),
|
||||
call(C_4,V1,V2,V3,V4).
|
||||
\(FC_5,V1,V2,V3,V4,V5) :-
|
||||
copy_term_nat(FC_5,C_5),
|
||||
call(C_5,V1,V2,V3,V4,V5).
|
||||
\(FC_6,V1,V2,V3,V4,V5,V6) :-
|
||||
copy_term_nat(FC_6,C_6),
|
||||
call(C_6,V1,V2,V3,V4,V5,V6).
|
||||
\(FC_7,V1,V2,V3,V4,V5,V6,V7) :-
|
||||
copy_term_nat(FC_7,C_7),
|
||||
call(C_7,V1,V2,V3,V4,V5,V6,V7).
|
||||
|
||||
|
||||
+\(GV,FC_0) :-
|
||||
copy_term_nat(GV+FC_0,GV+C_0),
|
||||
no_hat_call(C_0).
|
||||
+\(GV,FC_1,V1) :-
|
||||
copy_term_nat(GV+FC_1,GV+C_1),
|
||||
call(C_1,V1).
|
||||
+\(GV,FC_2,V1,V2) :-
|
||||
copy_term_nat(GV+FC_2,GV+C_2),
|
||||
call(C_2,V1,V2).
|
||||
+\(GV,FC_3,V1,V2,V3) :-
|
||||
copy_term_nat(GV+FC_3,GV+C_3),
|
||||
call(C_3,V1,V2,V3).
|
||||
+\(GV,FC_4,V1,V2,V3,V4) :-
|
||||
copy_term_nat(GV+FC_4,GV+C_4),
|
||||
call(C_4,V1,V2,V3,V4).
|
||||
+\(GV,FC_5,V1,V2,V3,V4,V5) :-
|
||||
copy_term_nat(GV+FC_5,GV+C_5),
|
||||
call(C_5,V1,V2,V3,V4,V5).
|
||||
+\(GV,FC_6,V1,V2,V3,V4,V5,V6) :-
|
||||
copy_term_nat(GV+FC_6,GV+C_6),
|
||||
call(C_6,V1,V2,V3,V4,V5,V6).
|
||||
+\(GV,FC_7,V1,V2,V3,V4,V5,V6,V7) :-
|
||||
copy_term_nat(GV+FC_7,GV+C_7),
|
||||
call(C_7,V1,V2,V3,V4,V5,V6,V7).
|
||||
|
||||
|
||||
%% no_hat_call(:Goal_0)
|
||||
%
|
||||
% Like call, but issues an error for a goal (^)/2. Such goals are
|
||||
% likely the result of an insufficient number of arguments.
|
||||
|
||||
no_hat_call(MGoal_0) :-
|
||||
strip_module(MGoal_0, _, Goal_0),
|
||||
( nonvar(Goal_0),
|
||||
Goal_0 = (_^_)
|
||||
-> throw(
|
||||
error(
|
||||
existence_error(lambda_parameter,MGoal_0),
|
||||
_))
|
||||
; call(MGoal_0)
|
||||
).
|
||||
|
||||
% I would like to replace this by:
|
||||
% V1^Goal :- throw(error(existence_error(lambda_parameter,V1^Goal),_)).
|
||||
488
src/lib/lists.pl
488
src/lib/lists.pl
@@ -1,183 +1,62 @@
|
||||
/**
|
||||
List manipulation predicates
|
||||
*/
|
||||
|
||||
:- module(lists, [member/2, select/3, append/2, append/3, foldl/4, foldl/5, foldl/6,
|
||||
memberchk/2, reverse/2, length/2, maplist/2,
|
||||
maplist/3, maplist/4, maplist/5, maplist/6,
|
||||
maplist/7, maplist/8, maplist/9, same_length/2, nth0/3, nth0/4, nth1/3, nth1/4,
|
||||
sum_list/2, transpose/2, list_to_set/2, list_max/2,
|
||||
list_min/2, permutation/2]).
|
||||
|
||||
/* Author: Mark Thom, Jan Wielemaker, and Richard O'Keefe
|
||||
Copyright (c) 2018-2021, Mark Thom
|
||||
Copyright (c) 2002-2020, University of Amsterdam
|
||||
VU University Amsterdam
|
||||
SWI-Prolog Solutions b.v.
|
||||
All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
:- module(lists, [member/2, select/3, append/2, append/3, foldl/4, foldl/5,
|
||||
memberchk/2, reverse/2, length/2, maplist/2,
|
||||
maplist/3, maplist/4, maplist/5, maplist/6,
|
||||
maplist/7, maplist/8, maplist/9, same_length/2, nth0/3,
|
||||
sum_list/2, transpose/2, list_to_set/2]).
|
||||
|
||||
|
||||
:- use_module(library(error)).
|
||||
|
||||
|
||||
:- meta_predicate(maplist(1, ?)).
|
||||
:- meta_predicate(maplist(2, ?, ?)).
|
||||
:- meta_predicate(maplist(3, ?, ?, ?)).
|
||||
:- meta_predicate(maplist(4, ?, ?, ?, ?)).
|
||||
:- meta_predicate(maplist(5, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(maplist(6, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(maplist(7, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(maplist(8, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
|
||||
:- meta_predicate(foldl(3, ?, ?, ?)).
|
||||
:- meta_predicate(foldl(4, ?, ?, ?, ?)).
|
||||
|
||||
:- use_module(library(error)).
|
||||
|
||||
:- meta_predicate(resource_error(+,:)).
|
||||
|
||||
resource_error(Resource, Context) :-
|
||||
throw(error(resource_error(Resource), Context)).
|
||||
|
||||
%% length(?Xs, ?N).
|
||||
%
|
||||
% Relates a list to its length (number of elements). It can be used to count the elements of a current list or
|
||||
% to create a list full of free variables with N length.
|
||||
%
|
||||
% ```
|
||||
% ?- length("abc", 3).
|
||||
% true.
|
||||
% ?- length("abc", N).
|
||||
% N = 3.
|
||||
% ?- length(Xs, 3).
|
||||
% Xs = [_A,_B,_C].
|
||||
% ```
|
||||
|
||||
length(Xs0, N) :-
|
||||
'$skip_max_list'(M, N, Xs0,Xs),
|
||||
!,
|
||||
( Xs == [] -> N = M
|
||||
; nonvar(Xs) -> var(N), Xs = [_|_], resource_error(finite_memory,length/2)
|
||||
; nonvar(N) -> R is N-M, length_rundown(Xs, R)
|
||||
; N == Xs -> failingvarskip(Xs), resource_error(finite_memory,length/2)
|
||||
; length_addendum(Xs, N, M)
|
||||
).
|
||||
length(Xs, N) :-
|
||||
var(N), !,
|
||||
'$skip_max_list'(M, -1, Xs, Xs0),
|
||||
( Xs0 == [] -> N = M
|
||||
; var(Xs0) -> length_addendum(Xs0, N, M)).
|
||||
length(Xs, N) :-
|
||||
integer(N),
|
||||
N >= 0, !,
|
||||
'$skip_max_list'(M, N, Xs, Xs0),
|
||||
( Xs0 == [] -> N = M
|
||||
; var(Xs0) -> R is N-M, length_rundown(Xs0, R)).
|
||||
length(_, N) :-
|
||||
integer(N), !,
|
||||
domain_error(not_less_than_zero, N, length/2).
|
||||
integer(N), !,
|
||||
domain_error(not_less_than_zero, N, length/2).
|
||||
length(_, N) :-
|
||||
type_error(integer, N, length/2).
|
||||
|
||||
length_rundown(Xs, 0) :- !, Xs = [].
|
||||
length_rundown(Vs, N) :-
|
||||
'$unattributed_var'(Vs), % unconstrained
|
||||
!,
|
||||
'$det_length_rundown'(Vs, N).
|
||||
length_rundown([_|Xs], N) :- % force unification
|
||||
N1 is N-1,
|
||||
length(Xs, N1). % maybe some new info on Xs
|
||||
|
||||
failingvarskip(Xs) :-
|
||||
'$unattributed_var'(Xs), % unconstrained
|
||||
!.
|
||||
failingvarskip([_|Xs0]) :- % force unification
|
||||
'$skip_max_list'(_, _, Xs0,Xs),
|
||||
( nonvar(Xs) -> Xs = [_|_]
|
||||
; failingvarskip(Xs)
|
||||
).
|
||||
type_error(integer, N, length/2).
|
||||
|
||||
length_addendum([], N, N).
|
||||
length_addendum([_|Xs], N, M) :-
|
||||
M1 is M + 1,
|
||||
length_addendum(Xs, N, M1).
|
||||
|
||||
%% member(?X, ?Xs).
|
||||
%
|
||||
% Succeeds when X unifies with an item of the list Xs, which can be at any position.
|
||||
%
|
||||
% ```
|
||||
% ?- member(X, "hello world").
|
||||
% X = h
|
||||
% ; ... .
|
||||
% ```
|
||||
length_rundown(Xs, 0) :- !, Xs = [].
|
||||
length_rundown([_|Xs], N) :-
|
||||
N1 is N-1,
|
||||
length_rundown(Xs, N1).
|
||||
|
||||
member(X, [L|Ls]) :-
|
||||
member_(Ls, L, X).
|
||||
|
||||
member_(_, X, X).
|
||||
member_([L|Ls], _, X) :-
|
||||
member_(Ls, L, X).
|
||||
member(X, [X|_]).
|
||||
member(X, [_|Xs]) :- member(X, Xs).
|
||||
|
||||
|
||||
%% select(X, Xs0, Xs1).
|
||||
%
|
||||
% Succeeds when the list Xs1 is the list Xs0 without the item X
|
||||
%
|
||||
% ```
|
||||
% ?- select(c, "abcd", X).
|
||||
% X = "abd"
|
||||
% ; false.
|
||||
% ```
|
||||
select(X, [X|Xs], Xs).
|
||||
select(X, [Y|Xs], [Y|Ys]) :- select(X, Xs, Ys).
|
||||
|
||||
%% append(+XsXs, ?Xs).
|
||||
%
|
||||
% Concatenates a list of lists
|
||||
%
|
||||
% ```
|
||||
% ?- append([[1, 2], [3]], Xs).
|
||||
% Xs = [1,2,3].
|
||||
% ```
|
||||
|
||||
append([], []).
|
||||
append([L0|Ls0], Ls) :-
|
||||
append(L0, Rest, Ls),
|
||||
append(Ls0, Rest).
|
||||
|
||||
%% append(Xs0, Xs1, Xs).
|
||||
%
|
||||
% List Xs is the concatenation of Xs0 and Xs1
|
||||
%
|
||||
% ```
|
||||
% ?- append([1,2,3], [4,5,6], Xs).
|
||||
% Xs = [1,2,3,4,5,6].
|
||||
% ```
|
||||
|
||||
append([], R, R).
|
||||
append([X|L], R, [X|S]) :- append(L, R, S).
|
||||
|
||||
%% memberchk(?X, +Xs).
|
||||
%
|
||||
% This predicate is similar to `member/2`, but it only provides a single answer
|
||||
|
||||
memberchk(X, Xs) :- member(X, Xs), !.
|
||||
|
||||
%% reverse(?Xs, ?Ys).
|
||||
%
|
||||
% Xs is the Ys list in reverse order
|
||||
%
|
||||
% ?- reverse([1,2,3], [3,2,1]).
|
||||
% true.
|
||||
%
|
||||
|
||||
reverse(Xs, Ys) :-
|
||||
( nonvar(Xs) -> reverse(Xs, Ys, [], Xs)
|
||||
; reverse(Ys, Xs, [], Ys)
|
||||
@@ -187,174 +66,93 @@ reverse([], [], YsRev, YsRev).
|
||||
reverse([_|Xs], [Y1|Ys], YsPreludeRev, Xss) :-
|
||||
reverse(Xs, Ys, [Y1|YsPreludeRev], Xss).
|
||||
|
||||
%% maplist(+Predicate, ?Xs0).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the list Xs0
|
||||
%
|
||||
% ```
|
||||
% ?- maplist(write, [1,2,3]).
|
||||
% 123 true.
|
||||
% ```
|
||||
|
||||
maplist(_, []).
|
||||
maplist(Cont1, [E1|E1s]) :-
|
||||
call(Cont1, E1),
|
||||
maplist(Cont1, E1s).
|
||||
|
||||
%% maplist(+Predicate, ?Xs0, ?Xs1).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the lists Xs0 and Xs1.
|
||||
%
|
||||
% ```
|
||||
% ?- maplist(length, ["hello", "prolog", "marseille"], Xs1).
|
||||
% Xs1 = [5,6,9].
|
||||
% ```
|
||||
maplist(_, [], []).
|
||||
maplist(Cont2, [E1|E1s], [E2|E2s]) :-
|
||||
call(Cont2, E1, E2),
|
||||
maplist(Cont2, E1s, E2s).
|
||||
|
||||
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1 and Xs2.
|
||||
maplist(_, [], [], []).
|
||||
maplist(Cont3, [E1|E1s], [E2|E2s], [E3|E3s]) :-
|
||||
call(Cont3, E1, E2, E3),
|
||||
maplist(Cont3, E1s, E2s, E3s).
|
||||
|
||||
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2 and Xs3.
|
||||
maplist(_, [], [], [], []).
|
||||
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s]) :-
|
||||
call(Cont, E1, E2, E3, E4),
|
||||
maplist(Cont, E1s, E2s, E3s, E4s).
|
||||
|
||||
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3 and Xs4.
|
||||
maplist(_, [], [], [], [], []).
|
||||
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s]) :-
|
||||
call(Cont, E1, E2, E3, E4, E5),
|
||||
maplist(Cont, E1s, E2s, E3s, E4s, E5s).
|
||||
|
||||
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4, ?Xs5).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3, Xs4 and Xs5.
|
||||
maplist(_, [], [], [], [], [], []).
|
||||
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s]) :-
|
||||
call(Cont, E1, E2, E3, E4, E5, E6),
|
||||
maplist(Cont, E1s, E2s, E3s, E4s, E5s, E6s).
|
||||
|
||||
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4, ?Xs5, ?Xs6).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3, Xs4, Xs5 and Xs6.
|
||||
maplist(_, [], [], [], [], [], [], []).
|
||||
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s], [E7|E7s]) :-
|
||||
call(Cont, E1, E2, E3, E4, E5, E6, E7),
|
||||
maplist(Cont, E1s, E2s, E3s, E4s, E5s, E6s, E7s).
|
||||
|
||||
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4, ?Xs5, ?Xs6, ?Xs7).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3, Xs4, Xs5, Xs6 and Xs7.
|
||||
maplist(_, [], [], [], [], [], [], [], []).
|
||||
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s], [E7|E7s], [E8|E8s]) :-
|
||||
call(Cont, E1, E2, E3, E4, E5, E6, E7, E8),
|
||||
maplist(Cont, E1s, E2s, E3s, E4s, E5s, E6s, E7s, E8s).
|
||||
|
||||
%% sum_list(+Xs, -Sum).
|
||||
%
|
||||
% Takes a lists of numbers and unifies Sum with the result of summing all the elements of the list.
|
||||
%
|
||||
% ```
|
||||
% ?- sum_list([2,2,2], 6).
|
||||
% true.
|
||||
% ```
|
||||
|
||||
sum_list(Ls, S) :-
|
||||
foldl(lists:sum_, Ls, 0, S).
|
||||
foldl(sum_, Ls, 0, S).
|
||||
|
||||
sum_(L, S0, S) :- S is S0 + L.
|
||||
|
||||
|
||||
%% same_length(?Xs, ?Ys).
|
||||
%
|
||||
% Succeeds if Xs and Ys are lists of the same length
|
||||
|
||||
same_length([], []).
|
||||
same_length([_|As], [_|Bs]) :-
|
||||
same_length(As, Bs).
|
||||
|
||||
%% foldl(+Predicate, ?Ls, +A0, ?A).
|
||||
%
|
||||
% foldl, sometimes called reduce, is a metapredicate that takes a predicate, a list of items
|
||||
% and a starting value, and outputs a single value. The predicate _Predicate_ must be able to take the current
|
||||
% element of the list, the previous value of the computation and the next value of the computation.
|
||||
%
|
||||
% For example, if we define sum_ as:
|
||||
%
|
||||
% ```
|
||||
% sum_(L, S0, S) :- S is S0 + L.
|
||||
% ```
|
||||
%
|
||||
% Then we can define `sum_list/2` as the following:
|
||||
%
|
||||
% ```
|
||||
% sum_list(Ls, S) :- foldl(sum_, Ls, 0, S).
|
||||
% ```
|
||||
|
||||
foldl(_, [], A, A).
|
||||
foldl(G_3, [L|Ls], A0, A) :-
|
||||
foldl(Goal_3, Ls, A0, A) :-
|
||||
foldl_(Ls, Goal_3, A0, A).
|
||||
|
||||
foldl_([], _, A, A).
|
||||
foldl_([L|Ls], G_3, A0, A) :-
|
||||
call(G_3, L, A0, A1),
|
||||
foldl(G_3, Ls, A1, A).
|
||||
foldl_(Ls, G_3, A1, A).
|
||||
|
||||
%% foldl(+Predicate, ?Ls0, ?Ls1, +A0, ?A).
|
||||
%
|
||||
% Same as `foldl/4` but with an extra list
|
||||
|
||||
foldl(_, [], [], A, A).
|
||||
foldl(G_4, [X|Xs], [Y|Ys], A0, A) :-
|
||||
foldl(Goal_4, Xs, Ys, A0, A) :-
|
||||
foldl_(Xs, Ys, Goal_4, A0, A).
|
||||
|
||||
foldl_([], [], _, A, A).
|
||||
foldl_([X|Xs], [Y|Ys], G_4, A0, A) :-
|
||||
call(G_4, X, Y, A0, A1),
|
||||
foldl(G_4, Xs, Ys, A1, A).
|
||||
foldl_(Xs, Ys, G_4, A1, A).
|
||||
|
||||
|
||||
%% foldl(+Goal, ?Ls0, ?Ls1, ?Ls2, +A0, ?A).
|
||||
%
|
||||
% Like `foldl/4`, with 2 additional lists.
|
||||
|
||||
foldl(_, [], [], [], A, A).
|
||||
foldl(G_5, [X|Xs], [Y|Ys], [Z|Zs], A0, A) :-
|
||||
call(G_5, X, Y, Z, A0, A1),
|
||||
foldl(G_5, Xs, Ys, Zs, A1, A).
|
||||
|
||||
%% transpose(?Ls, ?Ts).
|
||||
%
|
||||
% If Ls is a list of lists, Ts contains the transposition
|
||||
%
|
||||
% ```
|
||||
% ?- transpose([[1,1],[2,2]], Ts).
|
||||
% Ts = [[1,2],[1,2]].
|
||||
% ```
|
||||
transpose(Ls, Ts) :-
|
||||
lists_transpose(Ls, Ts).
|
||||
|
||||
lists_transpose([], []).
|
||||
lists_transpose([L|Ls], Ts) :-
|
||||
maplist(lists:same_length(L), Ls),
|
||||
foldl(lists:transpose_, L, Ts, [L|Ls], _).
|
||||
maplist(same_length(L), Ls),
|
||||
foldl(transpose_, L, Ts, [L|Ls], _).
|
||||
|
||||
transpose_(_, Fs, Lists0, Lists) :-
|
||||
maplist(lists:list_first_rest, Lists0, Fs, Lists).
|
||||
maplist(list_first_rest, Lists0, Fs, Lists).
|
||||
|
||||
list_first_rest([L|Ls], L, Ls).
|
||||
|
||||
%% list_to_set(+Ls0, -Set).
|
||||
%
|
||||
% Takes a list Ls0 and returns a list Set that doesn't contain any repeated element
|
||||
%
|
||||
% ```
|
||||
% ?- list_to_set([2,3,4,4,1,2], Set).
|
||||
% Set = [2,3,4,1].
|
||||
% ```
|
||||
|
||||
list_to_set(Ls0, Ls) :-
|
||||
maplist(lists:with_var, Ls0, LVs0),
|
||||
maplist(with_var, Ls0, LVs0),
|
||||
keysort(LVs0, LVs),
|
||||
same_elements(LVs),
|
||||
pick_firsts(LVs0, Ls).
|
||||
@@ -372,7 +170,7 @@ with_var(E, E-_).
|
||||
|
||||
same_elements([]).
|
||||
same_elements([EV|EVs]) :-
|
||||
foldl(lists:unify_same, EVs, EV, _).
|
||||
foldl(unify_same, EVs, EV, _).
|
||||
|
||||
unify_same(E-V, Prev-Var, E-V) :-
|
||||
( Prev == E ->
|
||||
@@ -380,171 +178,25 @@ unify_same(E-V, Prev-Var, E-V) :-
|
||||
; true
|
||||
).
|
||||
|
||||
%% nth0(?N, ?Ls, ?E).
|
||||
%
|
||||
% Succeeds if in the N position of the list Ls, we found the element E. The elements start counting from zero.
|
||||
%
|
||||
% ```
|
||||
% ?- nth0(2, [1,2,3,4], 3).
|
||||
% true.
|
||||
% ```
|
||||
nth0(N, Es0, E) :-
|
||||
nonvar(N),
|
||||
'$skip_max_list'(Skip, N, Es0,Es1),
|
||||
!,
|
||||
( Skip == N
|
||||
-> Es1 = [E|_]
|
||||
; ( var(Es1) ; Es1 = [_|_] ) % a partial or infinite list
|
||||
-> R is N-Skip,
|
||||
skipn(R,Es1,Es2),
|
||||
Es2 = [E|_]
|
||||
).
|
||||
nth0(N, Es0, E) :-
|
||||
can_be(not_less_than_zero, N),
|
||||
Es0 = [E0|Es1],
|
||||
nth0_el(0,N, E0,E, Es1).
|
||||
|
||||
skipn(N0, Es0,Es) :-
|
||||
N0>0,
|
||||
N1 is N0-1,
|
||||
Es0 = [_|Es1],
|
||||
skipn(N1, Es1,Es).
|
||||
skipn(0, Es,Es).
|
||||
nth0(N, Es, E) :-
|
||||
can_be(integer, N),
|
||||
can_be(list, Es),
|
||||
( integer(N) ->
|
||||
nth0_index(N, Es, E)
|
||||
; nth0_search(N, Es, E)
|
||||
).
|
||||
|
||||
nth0_el(N0,N, E0,E, Es0) :-
|
||||
Es0 == [],
|
||||
!, % indexing
|
||||
N0 = N,
|
||||
E0 = E.
|
||||
nth0_el(N,N, E,E, _).
|
||||
nth0_el(N0,N, _,E, [E0|Es0]) :-
|
||||
N1 is N0+1,
|
||||
nth0_el(N1,N, E0,E, Es0).
|
||||
nth0_index(0, [E|_], E) :- !.
|
||||
nth0_index(N, [_|Es], E) :-
|
||||
N > 0,
|
||||
N1 is N - 1,
|
||||
nth0_index(N1, Es, E).
|
||||
|
||||
%% nth1(?N, ?Ls, ?E).
|
||||
%
|
||||
% Succeeds if in the N position of the list Ls, we found the element E. The elements start counting from one.
|
||||
%
|
||||
% ```
|
||||
% ?- nth1(2, [1,2,3,4], 2).
|
||||
% true.
|
||||
% ```
|
||||
nth1(N, Es0, E) :-
|
||||
N \== 0,
|
||||
nth0(N, [_|Es0], E),
|
||||
N \== 0.
|
||||
nth0_search(N, Es, E) :-
|
||||
nth0_search(0, N, Es, E).
|
||||
|
||||
skipn(N0, Es0,Es, Xs0,Xs) :-
|
||||
N0>0,
|
||||
N1 is N0-1,
|
||||
Es0 = [E|Es1],
|
||||
Xs0 = [E|Xs1],
|
||||
skipn(N1, Es1,Es, Xs1,Xs).
|
||||
skipn(0, Es,Es, Xs,Xs).
|
||||
|
||||
%% nth0(?N, ?Ls, ?E, ?Rs).
|
||||
%
|
||||
% Succeeds if in the N position of the list Ls, we found the element E and the rest of the list is Rs. The elements start counting from zero.
|
||||
%
|
||||
% ```
|
||||
% ?- nth0(2, [1,2,3,4], 3, [1,2,4]).
|
||||
% true.
|
||||
% ```
|
||||
nth0(N, Es0, E, Es) :-
|
||||
integer(N),
|
||||
N >= 0,
|
||||
!,
|
||||
skipn(N, Es0,Es1, Es,Es2),
|
||||
Es1 = [E|Es2].
|
||||
nth0(N, Es0, E, Es) :-
|
||||
can_be(not_less_than_zero, N),
|
||||
Es0 = [E0|Es1],
|
||||
nth0_elx(0,N, E0,E, Es1, Es).
|
||||
|
||||
nth0_elx(N0,N, E0,E, Es0, Es) :-
|
||||
Es0 == [],
|
||||
!,
|
||||
N0 = N,
|
||||
E0 = E,
|
||||
Es0 = Es.
|
||||
nth0_elx(N,N, E,E, Es, Es).
|
||||
nth0_elx(N0,N, E0,E, [E1|Es0], [E0|Es]) :-
|
||||
N1 is N0+1,
|
||||
nth0_elx(N1,N, E1,E, Es0, Es).
|
||||
|
||||
% p.p.8.5
|
||||
|
||||
%% nth1(?N, ?Ls, ?E, ?Rs).
|
||||
%
|
||||
% Succeeds if in the N position of the list Ls, we found the element E and the rest of the list is Rs. The elements start counting from one.
|
||||
%
|
||||
% ```
|
||||
% ?- nth1(2, [1,2,3,4], 2, [1,3,4]).
|
||||
% true.
|
||||
% ```
|
||||
nth1(N, Es0, E, Es) :-
|
||||
N \== 0,
|
||||
nth0(N, [_|Es0], E, [_|Es]),
|
||||
N \== 0.
|
||||
|
||||
%% list_max(+Xs, -Max).
|
||||
%
|
||||
% Takes a list Xs and unifies with the maximum value of the list
|
||||
list_max([N|Ns], Max) :-
|
||||
foldl(lists:list_max_, Ns, N, Max).
|
||||
|
||||
list_max_(N, Max0, Max) :-
|
||||
Max is max(N, Max0).
|
||||
|
||||
%% list_min(+Xs, -Min).
|
||||
%
|
||||
% Takes a list Xs and unifies with the minimum value of the list
|
||||
list_min([N|Ns], Min) :-
|
||||
foldl(lists:list_min_, Ns, N, Min).
|
||||
|
||||
list_min_(N, Min0, Min) :-
|
||||
Min is min(N, Min0).
|
||||
|
||||
%% permutation(?Xs, ?Ys) is nondet.
|
||||
%
|
||||
% True when Xs is a permutation of Ys. This can solve for Ys given
|
||||
% Xs or Xs given Ys, or even enumerate Xs and Ys together. The
|
||||
% predicate `permutation/2` is primarily intended to generate
|
||||
% permutations. Note that a list of length N has N! permutations,
|
||||
% and unbounded permutation generation becomes prohibitively
|
||||
% expensive, even for rather short lists (10! = 3,628,800).
|
||||
%
|
||||
% The example below illustrates that Xs and Ys being proper lists
|
||||
% is not a sufficient condition to use the above replacement.
|
||||
%
|
||||
% ```
|
||||
% ?- permutation([1,2], [X,Y]).
|
||||
% X = 1, Y = 2
|
||||
% ; X = 2, Y = 1
|
||||
% ; false.
|
||||
% ```
|
||||
%
|
||||
% Throws `type_error(list, Arg)` if either argument is not a proper
|
||||
% or partial list.
|
||||
|
||||
permutation(Xs, Ys) :-
|
||||
'$skip_max_list'(Xlen, _, Xs, XTail),
|
||||
'$skip_max_list'(Ylen, _, Ys, YTail),
|
||||
( XTail == [], YTail == [] % both proper lists
|
||||
-> Xlen == Ylen
|
||||
; var(XTail), YTail == [] % partial, proper
|
||||
-> length(Xs, Ylen)
|
||||
; XTail == [], var(YTail) % proper, partial
|
||||
-> length(Ys, Xlen)
|
||||
; var(XTail), var(YTail) % partial, partial
|
||||
-> length(Xs, Len),
|
||||
length(Ys, Len)
|
||||
; must_be(list, Xs), % either is not a list
|
||||
must_be(list, Ys)
|
||||
),
|
||||
perm(Xs, Ys).
|
||||
|
||||
perm([], []).
|
||||
perm(List, [First|Perm]) :-
|
||||
select(First, List, Rest),
|
||||
perm(Rest, Perm).
|
||||
nth0_search(N, N, [E|_], E).
|
||||
nth0_search(N0, N, [_|Es], E) :-
|
||||
N1 is N0 + 1,
|
||||
nth0_search(N1, N, Es, E).
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
% Efforts toward literate tests with quads
|
||||
|
||||
:- module(quadtests, [check_module_quads/2]).
|
||||
|
||||
:- use_module(library(iso_ext)).
|
||||
:- use_module(library(pio)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(format)).
|
||||
:- use_module(library(reif)).
|
||||
:- use_module(library(debug)).
|
||||
:- use_module(library(lambda)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(time)).
|
||||
:- use_module(library('numerics/testutils')).
|
||||
:- use_module(library('numerics/special_functions')).
|
||||
|
||||
portray_term(Stream) :-
|
||||
read_term(Stream, Term, []),
|
||||
portray_clause(Term).
|
||||
|
||||
?- check_module_quads(special_functions, _).
|
||||
% Checking 11 quads ..
|
||||
% CHECKING.. (?-A=0.6174468790806071,erf(A,A),B is-A,erf(B,B)).
|
||||
% CHECKING.. (?-try_falsify(odd_t(erf,real(A)))).
|
||||
% CHECKING.. (?-witness(odd_t(erf,real(A),false))).
|
||||
% CHECKING.. (?-witness((real(A),erf(A,B),erf(-A,C),abs(B+C)>0))).
|
||||
% CHECKING.. (?-length(A,B)).
|
||||
% CHECKING.. (?-real(A),erf(A,B),erfc(A,C),abs(B+C-1)>epsilon).
|
||||
% CHECKING.. (?-try_falsify(δ_inverses_t(40*epsilon,erf,inverf,interval(-2,2,A)))).
|
||||
% CHECKING.. (?-try_falsify(δ_inverses_t(40*epsilon,erfc,inverfc,interval(-2,2,A)))).
|
||||
% CHECKING.. (?-A=10,B is A+1,gamma(B,C),int_realfact(A,D)).
|
||||
% CHECKING.. (?-gamma_P_Q(1.2,2.3,A,B),abs(A+B-1)<epsilon).
|
||||
% CHECKING.. (?-A=1.5,B=0.7,invgammp(A,B,C),gamma_P_Q(A,C,D,E),abs(B-D)<epsilon).
|
||||
true.
|
||||
|
||||
check_module_quads(Module, Quads) :-
|
||||
use_module(Module),
|
||||
read_quads(Module, Quads),
|
||||
zip(Qs, ADs, Quads),
|
||||
length(Qs, NQ),
|
||||
format("% Checking ~d quads ..~n", [NQ]),
|
||||
maplist(check_qu_ad(Module), Qs, ADs).
|
||||
|
||||
read_quads(Module, Quads) :-
|
||||
module_terms(Module, Terms),
|
||||
terms_quads(Terms, Quads).
|
||||
|
||||
module_terms(Module, Terms) :-
|
||||
module_file(Module, File),
|
||||
setup_call_cleanup(
|
||||
open(File, read, Stream, [type(text)]),
|
||||
read_terms(Stream, Terms),
|
||||
close(Stream)
|
||||
).
|
||||
|
||||
module_file(Module, File) :- atom_concat(Module, '.pl', File).
|
||||
|
||||
% Given a list of terms, filter out the predicate clauses.
|
||||
% TODO: Arg 1 is really a list of Term-VarNames _pairs_;
|
||||
% it would be very nice to find a less unsightly
|
||||
% name than 'TermVN' for these!
|
||||
terms_quads([Term|Terms], Quads) :-
|
||||
( term_type(Term, clause) -> terms_quads(Terms, Quads)
|
||||
; Quads = [Term|Quads_],
|
||||
terms_quads(Terms, Quads_)
|
||||
).
|
||||
terms_quads([], []).
|
||||
|
||||
term_type(Term-_, Type) :-
|
||||
( Term = (?- _) -> Type = query
|
||||
; Term = (_,_) -> Type = answer_description
|
||||
; Term = (_;_) -> Type = answer_description
|
||||
; Term = (_ = _) -> Type = answer_description
|
||||
; Term == true -> Type = answer_description
|
||||
; Term == false -> Type = answer_description
|
||||
; Type = clause
|
||||
).
|
||||
|
||||
?- term_type(test("erf is odd",try_falsify(odd_t(erf,real(_L))))-_, Type).
|
||||
Type = clause.
|
||||
|
||||
read_terms(Stream, Terms) :-
|
||||
read_terms_(Stream, [], Terms).
|
||||
read_terms_(Stream, Terms0, Terms) :-
|
||||
Options = [variable_names(VarNames)],
|
||||
read_term(Stream, Term, Options),
|
||||
( Term = end_of_file -> reverse(Terms0, Terms)
|
||||
; read_terms_(Stream, [Term-VarNames|Terms0], Terms)
|
||||
).
|
||||
|
||||
%% zip(+Xs, +Ys, ?XYs)
|
||||
%% zip(?Xs, ?Ys, +XYs)
|
||||
%
|
||||
% List XYs interleaves same-length lists Xs and Ys.
|
||||
zip([X|Xs], [Y|Ys], [X,Y|XYs]) :-
|
||||
zip(Xs, Ys, XYs).
|
||||
zip([], [], []).
|
||||
|
||||
?- zip(Xs, Ys, XYs). % MGQ does not terminate
|
||||
error('$interrupt_thrown',repl/0).
|
||||
|
||||
% The following suggested by Ulrich via Quad Works chat
|
||||
?- zip(Xs, Ys, XYs), false. % loops
|
||||
|
||||
?- zip(X, [4,5,6], [1,4,2,5,3,6]).
|
||||
X = [1,2,3].
|
||||
|
||||
?- zip([1,2,3], Y, [1,4,2,5,3,6]).
|
||||
Y = [4,5,6].
|
||||
|
||||
?- zip([1,2,3], [4,5,6], Z).
|
||||
Z = [1,4,2,5,3,6].
|
||||
|
||||
?- zip(Xs, Ys, [1,4,2,5,3,6]).
|
||||
Xs = [1,2,3], Ys = [4,5,6].
|
||||
|
||||
% 3. Demonstrate checking 1 quad, the top two elements of a QAs list.
|
||||
check_qu_ad(Module, Q-QVN, A-AVN) :-
|
||||
Q = ?-(G),
|
||||
phrase(portray_clause_(Q), LitQ), % NB: LitQ terminates w/ newline
|
||||
format("% CHECKING.. ",[]),
|
||||
( A == true -> call(Module:G)
|
||||
; A == false -> ( call(Module:G) -> false
|
||||
; true
|
||||
)
|
||||
; phrase(unconj(A), As) ->
|
||||
( length(As, N),
|
||||
n_answers(N, A, AVN, ADs),
|
||||
n_answers(N, Module:G, QVN, Answers),
|
||||
maplist(contains, ADs, Answers)
|
||||
)
|
||||
; % Otherwise, we have the ',' case of a solitary answer
|
||||
call(Module:G),
|
||||
call(A),
|
||||
QVN == AVN
|
||||
),
|
||||
format("~s", [LitQ]).
|
||||
|
||||
% Answer-description AD (qua set-of-bindings) contains Answer.
|
||||
contains(AD, Answer) :- append(Answer, _, AD).
|
||||
|
||||
?- contains(['Xs'=[C],'L'=1,'_A'=C,'_B'=D], ['Xs'=[A],'L'=1]).
|
||||
C = A.
|
||||
|
||||
?- check_qu_ad(quadtests, (?-length(_F,_G))-['Xs'=_F,'L'=_G],(_H=[],_I=0;_H=[_J],_I=1;_H=[_J,_K],_I=2;...)-['Xs'=_H,'L'=_I,'_A'=_J,'_B'=_K]).
|
||||
% CHECKING.. (?-length(A,B)).
|
||||
_F = [_A,_B], _G = 2, _H = [_J,_K], _I = 2.
|
||||
|
||||
% Unravel the nested (;)/1 applications of multiple-AD structures.
|
||||
unconj(Conj) --> { Conj = (Elt;Conj_) },
|
||||
[Elt],
|
||||
unconj(Conj_).
|
||||
unconj(...) --> [].
|
||||
|
||||
?- phrase(unconj((_H=[],_I=0;_H=[_J],_I=1;_H=[_J,_K],_I=2;...)), List).
|
||||
List = [(_H=[],_I=0),(_H=[_J],_I=1),(_H=[_J,_K],_I=2)].
|
||||
|
||||
empty_anstack :-
|
||||
( retract('$anstack'(_)), fail
|
||||
; asserta('$anstack'([]))
|
||||
).
|
||||
|
||||
push(VN) :-
|
||||
retract('$anstack'(As)),
|
||||
asserta('$anstack'([VN|As])).
|
||||
|
||||
backtrack(N) :-
|
||||
( '$anstack'(Ans),
|
||||
length(Ans, N) -> true
|
||||
; fail
|
||||
).
|
||||
|
||||
n_answers(N, G, VN, ADs) :-
|
||||
must_be(integer, N),
|
||||
( N > 0 -> n_answers_(N, G, VN, ADs)
|
||||
; domain_error(not_less_than_zero, N, n_answers/4)
|
||||
).
|
||||
|
||||
n_answers_(N, G, VN, ADs) :-
|
||||
empty_anstack,
|
||||
call(G), push(VN),
|
||||
backtrack(N),
|
||||
!,
|
||||
retract('$anstack'(As)),
|
||||
reverse(As, ADs).
|
||||
|
||||
?- n_answers(3, length(Xs, L), ('Xs'=Xs,'Len'=L), ADs).
|
||||
Xs = [_A,_B], L = 2, ADs = [('Xs'=[],'Len'=0),('Xs'=[_C],'Len'=1),('Xs'=[_D,_E],'Len'=2)].
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2025 by David C. Norris (david@precisionmethods.guru)
|
||||
As with all things floating-point, use at your own risk.
|
||||
Part of Scryer Prolog.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/** Special math functions in the Error, Gamma and Beta families
|
||||
|
||||
The underlying Rust implementations come from the
|
||||
[puruspe](https://docs.rs/puruspe/latest/puruspe/) crate.
|
||||
*/
|
||||
|
||||
:- module(special_functions, [
|
||||
erf/2
|
||||
,erfc/2
|
||||
,inverf/2
|
||||
,inverfc/2
|
||||
,gamma/2
|
||||
,gamma/3
|
||||
,gamma_P_Q/4
|
||||
,invgammp/3
|
||||
,log_gamma/2
|
||||
,beta/3
|
||||
,betai/4
|
||||
,invbetai/4
|
||||
,test/2
|
||||
,test_special_functions/0
|
||||
,try_falsify/1
|
||||
,witness/1
|
||||
]).
|
||||
|
||||
:- use_module(library(numerics/testutils)).
|
||||
|
||||
%% erf(+Xexpr, -Erf)
|
||||
%
|
||||
% X is Xexpr ∈ ℝ, Erf is erf(X).
|
||||
%
|
||||
% [DLMF §7.2.1](https://dlmf.nist.gov/7.2#E1),
|
||||
% [`puruspe::error::erf`](https://docs.rs/puruspe/latest/puruspe/error/fn.erf.html)
|
||||
erf(Xexpr, Erf) :-
|
||||
X is Xexpr,
|
||||
builtins:must_be_number(X, erf/2),
|
||||
'$erf'(X, Erf).
|
||||
|
||||
% Demonstrate the roots of x - erf(x))
|
||||
?- X0 = 0.6174468790806071, erf(X0, X0), _X0 is -X0, erf(_X0, _X0).
|
||||
X0 = 0.6174468790806071, _X0 = -0.6174468790806071.
|
||||
|
||||
% erf is an odd function:
|
||||
?- try_falsify(odd_t(erf, real(_))).
|
||||
false.
|
||||
|
||||
% Another way to say the same thing..
|
||||
?- witness(odd_t(erf, real(_), false)).
|
||||
false.
|
||||
|
||||
% ..and yet one more:
|
||||
?- witness((real(X), erf(X,Erf), erf(-X,_Erf), abs(Erf+_Erf) > 0)).
|
||||
false.
|
||||
|
||||
%% erfc(+X, -Erfc)
|
||||
%
|
||||
% Erfc is erfc(X) for X ∈ ℝ.
|
||||
%
|
||||
% [DLMF §7.2.2](https://dlmf.nist.gov/7.2#E2),
|
||||
% [`puruspe::error::erfc`](https://docs.rs/puruspe/latest/puruspe/error/fn.erfc.html)
|
||||
erfc(X, Erfc) :-
|
||||
builtins:must_be_number(X, erfc/2),
|
||||
'$erfc'(X, Erfc).
|
||||
|
||||
?- real(X), erf(X, Erf), erfc(X, Erfc), abs(Erf+Erfc-1) > epsilon.
|
||||
false.
|
||||
|
||||
%% inverf(+ErfX, -X)
|
||||
%
|
||||
% X is erf⁻¹(ErfX) for ErfX ∈ (-1,1).
|
||||
inverf(ErfX, X) :-
|
||||
builtins:must_be_number(ErfX, inverf/2),
|
||||
'$inverf'(ErfX, X).
|
||||
|
||||
?- try_falsify(δ_inverses_t(40*epsilon, erf, inverf, interval(-2,2,_))).
|
||||
false.
|
||||
|
||||
%% inverfc(+ErfcX, -X)
|
||||
%
|
||||
% X is erfc⁻¹(ErfcX) for ErfcX ∈ (0,2).
|
||||
inverfc(ErfcX, X) :-
|
||||
builtins:must_be_number(ErfcX, inverfc/2),
|
||||
'$inverfc'(ErfcX, X).
|
||||
|
||||
?- try_falsify(δ_inverses_t(40*epsilon, erfc, inverfc, interval(-2,2,_))).
|
||||
false.
|
||||
|
||||
%% gamma(+X, -Gamma)
|
||||
%
|
||||
% Gamma is Γ(X), the [ordinary] gamma function evaluated at X ∈ ℝ.
|
||||
%
|
||||
% [DLMF §5.2.1](https://dlmf.nist.gov/5.2#E1)
|
||||
% [`puruspe::gamma::gamma`](https://docs.rs/puruspe/latest/puruspe/gamma/fn.gamma.html)
|
||||
gamma(X, Gamma) :-
|
||||
builtins:must_be_number(X, gamma/2),
|
||||
'$gamma'(X, Gamma).
|
||||
|
||||
% Γ(n+1) ≡ n!
|
||||
?- N = 10, N1 is N+1, gamma(N1, ΓN1), int_realfact(N, Γ11).
|
||||
N = 10, N1 = 11, ΓN1 = 3628800.0, Γ11 = 3628800.0.
|
||||
|
||||
|
||||
%% gamma(+A, +X, -Gamma)
|
||||
%
|
||||
% Gamma is Γ(A,X), the upper incomplete gamma function, where A > 0
|
||||
% is the shape parameter and X ≥ 0 is the lower limit of integration.
|
||||
%
|
||||
% [DLMF §8.2.2](https://dlmf.nist.gov/8.2#E2),
|
||||
gamma(A, X, Gamma) :-
|
||||
builtins:must_be_number(A, gammq/3),
|
||||
builtins:must_be_number(X, gammq/3),
|
||||
'$gammq'(A, X, Q),
|
||||
gamma(A, GammaA),
|
||||
Gamma is Q*GammaA.
|
||||
|
||||
%% gamma_P_Q(+A, +X, -P, -Q)
|
||||
%
|
||||
% For shape parameter A > 0 and lower limit of integration X ≥ 0,
|
||||
%
|
||||
% * P is γ(A,X)/Γ(X), the regularized _lower_ incomplete gamma function, and
|
||||
%
|
||||
% * Q is Γ(A,X)/Γ(X), the regularized _upper_ incomplete gamma function.
|
||||
%
|
||||
% [DLMF §8.2.4](https://dlmf.nist.gov/8.2#E4),
|
||||
% [`puruspe::gammp::gammp`](https://docs.rs/puruspe/latest/puruspe/gamma/fn.gammp.html),
|
||||
% [`puruspe::gammp::gammq`](https://docs.rs/puruspe/latest/puruspe/gamma/fn.gammq.html)
|
||||
gamma_P_Q(A, X, P, Q) :-
|
||||
builtins:must_be_number(A, gamma_P_Q/4),
|
||||
builtins:must_be_number(X, gamma_P_Q/4),
|
||||
'$gammp'(A, X, P),
|
||||
'$gammq'(A, X, Q).
|
||||
|
||||
% P + Q ≈ 1
|
||||
?- gamma_P_Q(1.2, 2.3, P, Q), abs(P + Q - 1) < epsilon.
|
||||
P = 0.8621845438106976, Q = 0.1378154561893024.
|
||||
|
||||
%% invgammp(+A, +P, -X)
|
||||
%
|
||||
% Given shape parameter A > 0 and probability P ∈ [0,1),
|
||||
%
|
||||
% X is the unique solution of P = P(A,X), where P(-,-) is the
|
||||
% regularized lower incomplete gamma function.
|
||||
%
|
||||
% [`puruspe::gamma::invgammp`](https://docs.rs/puruspe/latest/puruspe/gamma/fn.invgammp.html)
|
||||
invgammp(A, P, X) :-
|
||||
builtins:must_be_number(A, invgammp/3),
|
||||
builtins:must_be_number(P, invgammp/3),
|
||||
'$invgammp'(P, A, X).
|
||||
|
||||
?- A = 1.5, P = 0.7, invgammp(A, P, X), gamma_P_Q(A, X, P_, _), abs(P-P_) < epsilon.
|
||||
A = 1.5, P = 0.7, X = 1.8324353915624363, P_ = 0.7000000000000001.
|
||||
|
||||
%% log_gamma(+X, -LogGamma)
|
||||
%
|
||||
% LogGamma is ln(Γ(X)), the natural logarithm of Γ(X).
|
||||
%
|
||||
% [`puruspe::gamma::ln_gamma`](https://docs.rs/puruspe/latest/puruspe/gamma/fn.ln_gamma.html)
|
||||
log_gamma(X, LnGamma) :-
|
||||
builtins:must_be_number(X, log_gamma/2),
|
||||
'$ln_gamma'(X, LnGamma).
|
||||
|
||||
%% beta(+X, +Y, -B)
|
||||
%
|
||||
% B is B(X,Y) ≡ Γ(X)*Γ(Y)/Γ(X+Y)
|
||||
%
|
||||
% [DLMF §5.12.1](https://dlmf.nist.gov/5.12#E1)
|
||||
% [`puruspe::beta::beta`](https://docs.rs/puruspe/latest/puruspe/beta/fn.beta.html)
|
||||
beta(X, Y, B) :-
|
||||
builtins:must_be_number(X, beta/3),
|
||||
builtins:must_be_number(Y, beta/3),
|
||||
'$beta'(X, Y, B).
|
||||
|
||||
%% betai(+A, +B, +X, -Ix)
|
||||
%
|
||||
% Given:
|
||||
%
|
||||
% * shape parameters A > 0 and B > 0,
|
||||
% * upper limit of integration X ∈ [0,1],
|
||||
%
|
||||
% Ix is Iₓ(A,B) ≡ B(X;A,B)/B(A,B), the regularized incomplete beta function;
|
||||
%
|
||||
% [DLMF §8.17.2](https://dlmf.nist.gov/8.17#E2),
|
||||
% [`puruspe::beta::betai`](https://docs.rs/puruspe/latest/puruspe/beta/fn.betai.html)
|
||||
betai(A, B, X, Ix) :-
|
||||
builtins:must_be_number(A, betai/4),
|
||||
builtins:must_be_number(B, betai/4),
|
||||
builtins:must_be_number(X, betai/4),
|
||||
'$betai'(A, B, X, Ix).
|
||||
|
||||
%% invbetai(+A, +B, +P, -X)
|
||||
%
|
||||
% Given:
|
||||
%
|
||||
% * shape parameters A > 0 and B > 0,
|
||||
% * probability P ∈ [0,1],
|
||||
%
|
||||
% X ∈ [0,1] is the unique solution of P = Iₓ(A,B) ≡ B(X;A,B)/B(A,B).
|
||||
%
|
||||
% [`puruspe::beta::invbetai`](https://docs.rs/puruspe/latest/puruspe/beta/fn.invbetai.html)
|
||||
invbetai(A, B, P, X) :-
|
||||
builtins:must_be_number(A, invbetai/4),
|
||||
builtins:must_be_number(B, invbetai/4),
|
||||
builtins:must_be_number(P, invbetai/4),
|
||||
'$invbetai'(P, A, B, X).
|
||||
|
||||
|
||||
% ============================== TESTS ==============================
|
||||
|
||||
%% test_special_functions
|
||||
%
|
||||
% Run all tests defined in this module. (These tests _succeed_ when
|
||||
% they find counterexamples, so the 'desirable' result is `false`.)
|
||||
test_special_functions :-
|
||||
format("Seeking counterexamples to assertions:~n", []),
|
||||
test(T, G), format("% ~s ~n", [T]),
|
||||
call(G).
|
||||
|
||||
% We default to 1M falsification attempts per assertion, and -- more
|
||||
% importantly -- use the (unexported) testutils:try_falsify_/2, to
|
||||
% avoid testutils:reproducibly/0 fixing an RNG seed. Thus we obtain
|
||||
% truly pseudorandom tests untainted by seed-hacking impropriety.
|
||||
:- meta_predicate(try_falsify(1)).
|
||||
try_falsify(G) :- testutils:try_falsify_(10^6, G).
|
||||
|
||||
% A unary witness/1 predicate similarly renders queries more concise.
|
||||
:- meta_predicate(witness(0)).
|
||||
witness(G) :- testutils:witness(10^6, G).
|
||||
|
||||
:- discontiguous(test/2).
|
||||
|
||||
%% test(+Name, ?Goal)
|
||||
%
|
||||
% Tests have the signature established by @bakaq's test_framework,
|
||||
% each with a user-facing string Name, and a Goal which serves as an
|
||||
% _assertion_ by succeeding iff the Name'd desirable property holds.
|
||||
test("erf is odd", try_falsify(odd_t(erf, real(_)))).
|
||||
|
||||
test("pos root of erf(x)-x", \+ (X0 = 0.6174468790806071, erf(X0, X0))).
|
||||
|
||||
test("erfc ≈ 1 - erf", try_falsify(erf_plus_erfc_unity_t(real(_)))).
|
||||
|
||||
erf_plus_erfc_unity_t(Any, T) :-
|
||||
call_free(Any, X), erf(X, Erf), erfc(X, Erfc),
|
||||
( abs(Erf + Erfc - 1) < epsilon -> T = true
|
||||
; T = false
|
||||
).
|
||||
|
||||
test("inverf ≈ erf⁻¹",
|
||||
try_falsify(δ_inverses_t(40*epsilon, erf, inverf, interval(-2,2,_)))).
|
||||
|
||||
test("('false' is good)", false).
|
||||
@@ -1,147 +0,0 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2025 by David C. Norris (david@precisionmethods.guru)
|
||||
As with all things floating-point, use at your own risk.
|
||||
Part of Scryer Prolog.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/** Utility predicates for testing numerics
|
||||
|
||||
*/
|
||||
|
||||
:- module(testutils, [
|
||||
try_falsify/2
|
||||
,witness/2
|
||||
,real/1
|
||||
,posreal/1
|
||||
,interval/3
|
||||
,call_free/2
|
||||
,odd_t/3
|
||||
,δ_inverses_t/5
|
||||
,int_realfact/2
|
||||
]).
|
||||
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(random)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(format)).
|
||||
|
||||
%% real(-X)
|
||||
%
|
||||
% X = tan(U) for uniform U ~ U[-π,π).
|
||||
real(X) :-
|
||||
random(U),
|
||||
X is tan(pi*(U - 0.5)).
|
||||
|
||||
%% posreal(-X)
|
||||
%
|
||||
% X = 1/U for uniform U ~ U[0,1).
|
||||
posreal(X) :-
|
||||
random(U),
|
||||
X is 1/U - 1.
|
||||
|
||||
%% interval(+A, +B, -X)
|
||||
%
|
||||
% X ~ U[A,B).
|
||||
interval(A, B, X) :-
|
||||
random(U),
|
||||
X is A + (B-A)*U.
|
||||
|
||||
:- meta_predicate(try_falsify(+, 1)).
|
||||
|
||||
% NOTE: NON-reproducible tests would in fact be MORE STRINGENT,
|
||||
% creating opportunities to detect rare error cases over time.
|
||||
reproducibly :- set_random(seed(2025)).
|
||||
|
||||
%% try_falsify(+IntExpr, ?G_1)
|
||||
%
|
||||
% Make (N is IntExpr) attempts to falsify the partial goal G/1,
|
||||
% reporting the first counterexample found.
|
||||
try_falsify(N, G_1) :- reproducibly -> try_falsify_(N, G_1).
|
||||
try_falsify_(IntExpr, G_1) :- N is IntExpr, must_be(integer, N), N > 0,
|
||||
( call(G_1, false) -> counterexample(G_1)
|
||||
; N_ is N - 1,
|
||||
try_falsify_(N_, G_1)
|
||||
).
|
||||
|
||||
%% call_free(?G, -V)
|
||||
%
|
||||
% Call goal G, the single free variable of which is bound to V.
|
||||
call_free(G, V) :- term_variables(G, [V]), call(G).
|
||||
|
||||
:- meta_predicate(odd_t(2, 0, ?)).
|
||||
|
||||
:- meta_predicate(witness(+, 0)).
|
||||
|
||||
%% witness(+N, ?OhNo)
|
||||
%
|
||||
% Make N attempts to satisfy the goal OhNo, reporting the first
|
||||
% counterexample found.
|
||||
witness(N, OhNo) :- N > 0,
|
||||
( call(OhNo) -> counterexample(OhNo)
|
||||
; N_ is N - 1,
|
||||
witness(N_, OhNo)
|
||||
).
|
||||
|
||||
% The goal below is obviously _designed_ to succeed 10% of the time,
|
||||
% giving us plenty of chances to see 'counterexamples':
|
||||
?- witness(10, (random(X), format("% X = ~f~n", [X]), X > 0.9)).
|
||||
% X = 0.8084510175379878
|
||||
% X = 0.3173976152322231
|
||||
% X = 0.6016479707924276
|
||||
% X = 0.2782828276608216
|
||||
% X = 0.5737059731916494
|
||||
% X = 0.4300520177737992
|
||||
% X = 0.5411038305190763
|
||||
% X = 0.6901983097300066
|
||||
% X = 0.05013429563996663
|
||||
% X = 0.7321078902421636
|
||||
false.
|
||||
% X = 0.6401599325865659
|
||||
% X = 0.7024957773981413
|
||||
% X = 0.5798797162672757
|
||||
% X = 0.3107013295892864
|
||||
% X = 0.3792925200660049
|
||||
% X = 0.3424593598278811
|
||||
% X = 0.0589899862019303
|
||||
% X = 0.9692529829158145
|
||||
% COUNTEREXAMPLE: user:random(0.9692529829158145),(current_output(user_output),pio:phrase_to_stream(format:format_([%, ,X, ,=, ,~,f,~,n],[0.9692529829158145]),user_output),flush_output(user_output)),0.9692529829158145>0.9
|
||||
X = 0.9692529829158145.
|
||||
|
||||
%% odd_t(+F_2, +Any, ?T)
|
||||
%
|
||||
% T is the truth-value from testing that function F_2 is
|
||||
% [odd](https://en.wikipedia.org/wiki/Even_and_odd_functions) at a
|
||||
% value X obtained via call_free(Any, X).
|
||||
odd_t(F, Any, T) :-
|
||||
( call_free(Any, X),
|
||||
_X is -X,
|
||||
call(F, X, Fx),
|
||||
call(F, _X, _Fx),
|
||||
_Fx is -Fx -> T = true
|
||||
; T = false
|
||||
).
|
||||
|
||||
:- meta_predicate(δ_inverses_t(?, 2, 2, 0, ?)).
|
||||
|
||||
%% δ_inverses_t(+Δ, +F_2, +Finv_2, +Any, ?T)
|
||||
%
|
||||
% For given functions F_2 and Finv_2, T is the truth-value from
|
||||
% testing that Finv_2 ∘ F_2 is within Δ of the identity at a value X
|
||||
% obtained via call_free(Any, X).
|
||||
δ_inverses_t(Δ, F, Finv, Any, T) :-
|
||||
( call_free(Any, X),
|
||||
call(F, X, Fx),
|
||||
call(Finv, Fx, X_),
|
||||
( abs(X - X_) < Δ -> T = true
|
||||
; T = false
|
||||
)
|
||||
).
|
||||
|
||||
%% int_realfact(+N, -FactN)
|
||||
%
|
||||
% FactN is floating-point N!
|
||||
int_realfact(N, FactN) :-
|
||||
N > 0, N_ is N - 1, int_realfact(N_, FactN_), FactN is N*FactN_.
|
||||
int_realfact(0, 1.0).
|
||||
|
||||
counterexample(G) :- format("% COUNTEREXAMPLE: ~w~n", [G]).
|
||||
@@ -1,133 +0,0 @@
|
||||
:- op(400, yfx, /).
|
||||
|
||||
% module resolution operator.
|
||||
:- op(600, xfy, :).
|
||||
|
||||
% Implementation specific declarative operator used to implement
|
||||
% call_with_inference_limit/3 and setup_call_cleanup/3. Compiler switches
|
||||
% to the default trust_me, retry_me_else and some other instructions for all
|
||||
% predicates that are marked with it. Indexing choice instructions are unchanged.
|
||||
%
|
||||
% Implementation details:
|
||||
% Default instructions are not subject to inference counting, so their
|
||||
% execution will not be considered if they happen to be called by
|
||||
% call_with_inference_limit/3.
|
||||
%
|
||||
:- op(700, fx, non_counted_backtracking).
|
||||
|
||||
% arithmetic operators.
|
||||
:- op(700, xfx, is).
|
||||
:- op(500, yfx, +).
|
||||
:- op(500, yfx, -).
|
||||
:- op(400, yfx, *).
|
||||
:- op(200, xfx, **).
|
||||
:- op(200, xfy, ^).
|
||||
:- op(500, yfx, /\).
|
||||
:- op(500, yfx, \/).
|
||||
:- op(400, yfx, div).
|
||||
:- op(400, yfx, //).
|
||||
:- op(400, yfx, rdiv).
|
||||
:- op(400, yfx, <<).
|
||||
:- op(400, yfx, >>).
|
||||
:- op(400, yfx, mod).
|
||||
:- op(400, yfx, rem).
|
||||
:- op(200, fy, +).
|
||||
:- op(200, fy, -).
|
||||
:- op(200, fy, \).
|
||||
|
||||
% arithmetic comparison operators.
|
||||
:- op(700, xfx, >).
|
||||
:- op(700, xfx, <).
|
||||
:- op(700, xfx, =\=).
|
||||
:- op(700, xfx, =:=).
|
||||
:- op(700, xfx, >=).
|
||||
:- op(700, xfx, =<).
|
||||
|
||||
% term comparison.
|
||||
:- op(700, xfx, ==).
|
||||
:- op(700, xfx, \==).
|
||||
:- op(700, xfx, @=<).
|
||||
:- op(700, xfx, @>=).
|
||||
:- op(700, xfx, @<).
|
||||
:- op(700, xfx, @>).
|
||||
|
||||
% conditional operators.
|
||||
:- op(1050, xfy, ->).
|
||||
:- op(1100, xfy, ;).
|
||||
|
||||
% control.
|
||||
:- op(700, xfx, =).
|
||||
:- op(700, xfx, =..).
|
||||
:- op(700, xfx, \=).
|
||||
:- op(900, fy, \+).
|
||||
|
||||
:- op(1200, xfx, -->).
|
||||
|
||||
% meta_predicate declarations for call/{1, 66}.
|
||||
:- meta_predicate(call(0)).
|
||||
:- meta_predicate(call(1, ?)).
|
||||
:- meta_predicate(call(2, ?, ?)).
|
||||
:- meta_predicate(call(3, ?, ?, ?)).
|
||||
:- meta_predicate(call(4, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(5, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(6, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(7, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(8, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(9, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(10, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(11, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(12, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(13, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(14, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(15, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(16, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(17, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(18, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(19, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(20, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(21, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(22, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(23, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(24, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(25, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(26, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(27, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(28, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(29, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(30, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(31, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(32, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(33, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(34, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(35, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(36, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(37, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(38, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(39, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(40, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(41, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(42, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(43, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(44, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(45, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(46, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(47, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(48, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(49, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(50, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(51, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(52, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(53, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(54, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(55, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(56, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(57, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(58, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(59, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(60, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(60, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(61, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(62, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(63, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(64, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(65, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
@@ -54,41 +54,42 @@
|
||||
|
||||
:- use_module(library(lists)).
|
||||
|
||||
/** Ordered set manipulation
|
||||
|
||||
/** <module> Ordered set manipulation
|
||||
Ordered sets are lists with unique elements sorted to the standard order
|
||||
of terms (see `sort/2`). Exploiting ordering, many of the set operations
|
||||
of terms (see sort/2). Exploiting ordering, many of the set operations
|
||||
can be expressed in order N rather than N^2 when dealing with unordered
|
||||
sets that may contain duplicates. The library(ordsets) is available in a
|
||||
number of Prolog implementations. Our predicates are designed to be
|
||||
compatible with common practice in the Prolog community.
|
||||
compatible with common practice in the Prolog community. The
|
||||
implementation is incomplete and relies partly on library(oset), an
|
||||
older ordered set library distributed with SWI-Prolog. New applications
|
||||
are advised to use library(ordsets).
|
||||
Some of these predicates match directly to corresponding list
|
||||
operations. It is advised to use the versions from this library to make
|
||||
clear you are operating on ordered sets. An exception is `member/2`. See
|
||||
`ord_memberchk/2`.
|
||||
|
||||
clear you are operating on ordered sets. An exception is member/2. See
|
||||
ord_memberchk/2.
|
||||
The ordsets library is based on the standard order of terms. This
|
||||
implies it can handle all Prolog terms, including variables. Note
|
||||
however, that the ordering is not stable if a term inside the set is
|
||||
further instantiated. Also note that variable ordering changes if
|
||||
variables in the set are unified with each other or a variable in the
|
||||
set is unified with a variable that is _older_ than the newest variable
|
||||
set is unified with a variable that is `older' than the newest variable
|
||||
in the set. In practice, this implies that it is allowed to use
|
||||
member(X, OrdSet) on an ordered set that holds variables only if X is a
|
||||
fresh variable. In other cases one should cease using it as an ordset
|
||||
because the order it relies on may have been changed.
|
||||
*/
|
||||
|
||||
%% is_ordset(@Term) is semidet.
|
||||
%! is_ordset(@Term) is semidet.
|
||||
%
|
||||
% True if Term is an ordered set. All predicates in this library
|
||||
% expect ordered sets as input arguments. Failing to fullfil this
|
||||
% assumption results in undefined behaviour. Typically, ordered
|
||||
% sets are created by predicates from this library, `sort/2` or
|
||||
% `setof/3`.
|
||||
% True if Term is an ordered set. All predicates in this library
|
||||
% expect ordered sets as input arguments. Failing to fullfil this
|
||||
% assumption results in undefined behaviour. Typically, ordered
|
||||
% sets are created by predicates from this library, sort/2 or
|
||||
% setof/3.
|
||||
|
||||
is_ordset(Term) :-
|
||||
'$skip_max_list'(_, _, Term, Tail), Tail == [], %% is_list(Term),
|
||||
'$skip_max_list'(_, -1, Term, Tail), Tail == [], %% is_list(Term),
|
||||
is_ordset2(Term).
|
||||
|
||||
is_ordset2([]).
|
||||
@@ -101,35 +102,37 @@ is_ordset3([H2|T], H) :-
|
||||
is_ordset3(T, H2).
|
||||
|
||||
|
||||
%% ord_empty(?List) is semidet.
|
||||
%! ord_empty(?List) is semidet.
|
||||
%
|
||||
% True when List is the empty ordered set. Simply unifies list
|
||||
% with the empty list. Not part of Quintus.
|
||||
% True when List is the empty ordered set. Simply unifies list
|
||||
% with the empty list. Not part of Quintus.
|
||||
|
||||
ord_empty([]).
|
||||
|
||||
|
||||
%% ord_seteq(+Set1, +Set2) is semidet.
|
||||
%! ord_seteq(+Set1, +Set2) is semidet.
|
||||
%
|
||||
% True if Set1 and Set2 have the same elements. As both are
|
||||
% canonical sorted lists, this is the same as `==/2`.
|
||||
% True if Set1 and Set2 have the same elements. As both are
|
||||
% canonical sorted lists, this is the same as ==/2.
|
||||
%
|
||||
% @compat sicstus
|
||||
|
||||
ord_seteq(Set1, Set2) :-
|
||||
Set1 == Set2.
|
||||
|
||||
|
||||
%% list_to_ord_set(+List, -OrdSet) is det.
|
||||
%! list_to_ord_set(+List, -OrdSet) is det.
|
||||
%
|
||||
% Transform a list into an ordered set. This is the same as
|
||||
% sorting the list.
|
||||
% Transform a list into an ordered set. This is the same as
|
||||
% sorting the list.
|
||||
|
||||
list_to_ord_set(List, Set) :-
|
||||
sort(List, Set).
|
||||
|
||||
|
||||
%% ord_intersect(+Set1, +Set2) is semidet.
|
||||
%! ord_intersect(+Set1, +Set2) is semidet.
|
||||
%
|
||||
% True if both ordered sets have a non-empty intersection.
|
||||
% True if both ordered sets have a non-empty intersection.
|
||||
|
||||
ord_intersect([H1|T1], L2) :-
|
||||
ord_intersect_(L2, H1, T1).
|
||||
@@ -145,29 +148,31 @@ ord_intersect__(>, H1, T1, _H2, T2) :-
|
||||
ord_intersect_(T2, H1, T1).
|
||||
|
||||
|
||||
%% ord_disjoint(+Set1, +Set2) is semidet.
|
||||
%! ord_disjoint(+Set1, +Set2) is semidet.
|
||||
%
|
||||
% True if Set1 and Set2 have no common elements. This is the
|
||||
% negation of `ord_intersect/2`.
|
||||
% True if Set1 and Set2 have no common elements. This is the
|
||||
% negation of ord_intersect/2.
|
||||
|
||||
ord_disjoint(Set1, Set2) :-
|
||||
\+ ord_intersect(Set1, Set2).
|
||||
|
||||
|
||||
%% ord_intersect(+Set1, +Set2, -Intersection)
|
||||
%! ord_intersect(+Set1, +Set2, -Intersection)
|
||||
%
|
||||
% Intersection holds the common elements of Set1 and Set2.
|
||||
% Intersection holds the common elements of Set1 and Set2.
|
||||
%
|
||||
% This predicate is *deprecated*. Use `ord_intersection/3`
|
||||
% @deprecated Use ord_intersection/3
|
||||
|
||||
ord_intersect(Set1, Set2, Intersection) :-
|
||||
oset_int(Set1, Set2, Intersection).
|
||||
|
||||
|
||||
%% ord_intersection(+PowerSet, -Intersection)
|
||||
%! ord_intersection(+PowerSet, -Intersection)
|
||||
%
|
||||
% Intersection of a powerset. True when Intersection is an ordered
|
||||
% set holding all elements common to all sets in PowerSet.
|
||||
% Intersection of a powerset. True when Intersection is an ordered
|
||||
% set holding all elements common to all sets in PowerSet.
|
||||
%
|
||||
% @compat sicstus
|
||||
|
||||
ord_intersection(PowerSet, Intersection) :-
|
||||
key_by_length(PowerSet, Pairs),
|
||||
@@ -185,10 +190,10 @@ l_int([_-H|T], S0, S) :-
|
||||
l_int(T, S1, S).
|
||||
|
||||
|
||||
%% ord_intersection(+Set1, +Set2, -Intersection) is det.
|
||||
%! ord_intersection(+Set1, +Set2, -Intersection) is det.
|
||||
%
|
||||
% Intersection holds the common elements of Set1 and Set2. Uses
|
||||
% `ord_disjoint/2` if Intersection is bound to `[]` on entry.
|
||||
% Intersection holds the common elements of Set1 and Set2. Uses
|
||||
% ord_disjoint/2 if Intersection is bound to `[]` on entry.
|
||||
|
||||
ord_intersection(Set1, Set2, Intersection) :-
|
||||
( Intersection == []
|
||||
@@ -197,11 +202,13 @@ ord_intersection(Set1, Set2, Intersection) :-
|
||||
).
|
||||
|
||||
|
||||
%% ord_intersection(+Set1, +Set2, ?Intersection, ?Difference) is det.
|
||||
%! ord_intersection(+Set1, +Set2, ?Intersection, ?Difference) is det.
|
||||
%
|
||||
% Intersection and difference between two ordered sets.
|
||||
% Intersection is the intersection between Set1 and Set2, while
|
||||
% Difference is defined by `ord_subtract(Set2, Set1, Difference)`.
|
||||
% Intersection and difference between two ordered sets.
|
||||
% Intersection is the intersection between Set1 and Set2, while
|
||||
% Difference is defined by ord_subtract(Set2, Set1, Difference).
|
||||
%
|
||||
% @see ord_intersection/3 and ord_subtract/3.
|
||||
|
||||
ord_intersection([], L, [], L) :- !.
|
||||
ord_intersection([_|_], [], [], []) :- !.
|
||||
@@ -217,35 +224,35 @@ ord_intersection2(>, H1, T1, H2, T2, Intersection, [H2|HDiff]) :-
|
||||
ord_intersection([H1|T1], T2, Intersection, HDiff).
|
||||
|
||||
|
||||
%% ord_add_element(+Set1, +Element, ?Set2) is det.
|
||||
%! ord_add_element(+Set1, +Element, ?Set2) is det.
|
||||
%
|
||||
% Insert an element into the set. This is the same as
|
||||
% `ord_union(Set1, [Element], Set2)`.
|
||||
% Insert an element into the set. This is the same as
|
||||
% ord_union(Set1, [Element], Set2).
|
||||
|
||||
ord_add_element(Set1, Element, Set2) :-
|
||||
oset_addel(Set1, Element, Set2).
|
||||
|
||||
|
||||
%% ord_del_element(+Set, +Element, -NewSet) is det.
|
||||
%! ord_del_element(+Set, +Element, -NewSet) is det.
|
||||
%
|
||||
% Delete an element from an ordered set. This is the same as
|
||||
% `ord_subtract(Set, [Element], NewSet)`.
|
||||
% Delete an element from an ordered set. This is the same as
|
||||
% ord_subtract(Set, [Element], NewSet).
|
||||
|
||||
ord_del_element(Set, Element, NewSet) :-
|
||||
oset_delel(Set, Element, NewSet).
|
||||
|
||||
|
||||
%% ord_selectchk(+Item, ?Set1, ?Set2) is semidet.
|
||||
%! ord_selectchk(+Item, ?Set1, ?Set2) is semidet.
|
||||
%
|
||||
% `selectchk/3`, specialised for ordered sets. Is true when
|
||||
% select(Item, Set1, Set2) and Set1, Set2 are both sorted lists
|
||||
% without duplicates. This implementation is only expected to work
|
||||
% for Item ground and either Set1 or Set2 ground. The "chk" suffix
|
||||
% is meant to remind you of `memberchk/2`, which also expects its
|
||||
% first argument to be ground. `ord_selectchk(X, S, T) =>
|
||||
% ord_memberchk(X, S) & \+ ord_memberchk(X, T).`
|
||||
% Selectchk/3, specialised for ordered sets. Is true when
|
||||
% select(Item, Set1, Set2) and Set1, Set2 are both sorted lists
|
||||
% without duplicates. This implementation is only expected to work
|
||||
% for Item ground and either Set1 or Set2 ground. The "chk" suffix
|
||||
% is meant to remind you of memberchk/2, which also expects its
|
||||
% first argument to be ground. ord_selectchk(X, S, T) =>
|
||||
% ord_memberchk(X, S) & \+ ord_memberchk(X, T).
|
||||
%
|
||||
% Author: Richard O'Keefe
|
||||
% @author Richard O'Keefe
|
||||
|
||||
ord_selectchk(Item, [X|Set1], [X|Set2]) :-
|
||||
X @< Item,
|
||||
@@ -259,19 +266,19 @@ ord_selectchk(Item, [Item|Set1], Set1) :-
|
||||
).
|
||||
|
||||
|
||||
%% ord_memberchk(+Element, +OrdSet) is semidet.
|
||||
%! ord_memberchk(+Element, +OrdSet) is semidet.
|
||||
%
|
||||
% True if Element is a member of OrdSet, compared using ==. Note
|
||||
% that _enumerating_ elements of an ordered set can be done using
|
||||
% `member/2`.
|
||||
% True if Element is a member of OrdSet, compared using ==. Note
|
||||
% that _enumerating_ elements of an ordered set can be done using
|
||||
% member/2.
|
||||
%
|
||||
% Some Prolog implementations also provide `ord_member/2`, with the
|
||||
% same semantics as `ord_memberchk/2`. We believe that having a
|
||||
% semidet `ord_member/2` is unacceptably inconsistent with the \*\_chk
|
||||
% convention. Portable code should use `ord_memberchk/2` or
|
||||
% `member/2`.
|
||||
% Some Prolog implementations also provide ord_member/2, with the
|
||||
% same semantics as ord_memberchk/2. We believe that having a
|
||||
% semidet ord_member/2 is unacceptably inconsistent with the *_chk
|
||||
% convention. Portable code should use ord_memberchk/2 or
|
||||
% member/2.
|
||||
%
|
||||
% Author: Richard O'Keefe
|
||||
% @author Richard O'Keefe
|
||||
|
||||
ord_memberchk(Item, [X1,X2,X3,X4|Xs]) :-
|
||||
!,
|
||||
@@ -296,9 +303,9 @@ ord_memberchk(Item, [X1]) :-
|
||||
Item == X1.
|
||||
|
||||
|
||||
%% ord_subset(+Sub, +Super) is semidet.
|
||||
%! ord_subset(+Sub, +Super) is semidet.
|
||||
%
|
||||
% Is true if all elements of Sub are in Super
|
||||
% Is true if all elements of Sub are in Super
|
||||
|
||||
ord_subset([], _).
|
||||
ord_subset([H1|T1], [H2|T2]) :-
|
||||
@@ -312,20 +319,22 @@ ord_subset_(=, _, T1, T2) :-
|
||||
ord_subset(T1, T2).
|
||||
|
||||
|
||||
%% ord_subtract(+InOSet, +NotInOSet, -Diff) is det.
|
||||
%! ord_subtract(+InOSet, +NotInOSet, -Diff) is det.
|
||||
%
|
||||
% Diff is the set holding all elements of InOSet that are not in
|
||||
% NotInOSet.
|
||||
% Diff is the set holding all elements of InOSet that are not in
|
||||
% NotInOSet.
|
||||
|
||||
ord_subtract(InOSet, NotInOSet, Diff) :-
|
||||
oset_diff(InOSet, NotInOSet, Diff).
|
||||
|
||||
|
||||
%% ord_union(+SetOfSets, -Union) is det.
|
||||
%! ord_union(+SetOfSets, -Union) is det.
|
||||
%
|
||||
% True if Union is the union of all elements in the superset
|
||||
% SetOfSets. Each member of SetOfSets must be an ordered set, the
|
||||
% sets need not be ordered in any way.
|
||||
% True if Union is the union of all elements in the superset
|
||||
% SetOfSets. Each member of SetOfSets must be an ordered set, the
|
||||
% sets need not be ordered in any way.
|
||||
%
|
||||
% @author Copied from YAP, probably originally by Richard O'Keefe.
|
||||
|
||||
ord_union([], []).
|
||||
ord_union([Set|Sets], Union) :-
|
||||
@@ -346,18 +355,18 @@ ord_union_all(N, Sets0, Union, Sets) :-
|
||||
).
|
||||
|
||||
|
||||
%% ord_union(+Set1, +Set2, ?Union) is det.
|
||||
%! ord_union(+Set1, +Set2, ?Union) is det.
|
||||
%
|
||||
% Union is the union of Set1 and Set2
|
||||
% Union is the union of Set1 and Set2
|
||||
|
||||
ord_union(Set1, Set2, Union) :-
|
||||
oset_union(Set1, Set2, Union).
|
||||
|
||||
|
||||
%% ord_union(+Set1, +Set2, -Union, -New) is det.
|
||||
%! ord_union(+Set1, +Set2, -Union, -New) is det.
|
||||
%
|
||||
% True iff `ord_union(Set1, Set2, Union)` and
|
||||
% `ord_subtract(Set2, Set1, New)`.
|
||||
% True iff ord_union(Set1, Set2, Union) and
|
||||
% ord_subtract(Set2, Set1, New).
|
||||
|
||||
ord_union([], Set2, Set2, Set2).
|
||||
ord_union([H|T], Set2, Union, New) :-
|
||||
@@ -381,26 +390,26 @@ ord_union_2([H|T], H2, T2, Union, New) :-
|
||||
ord_union(Order, H, T, H2, T2, Union, New).
|
||||
|
||||
|
||||
%% ord_symdiff(+Set1, +Set2, ?Difference) is det.
|
||||
%! ord_symdiff(+Set1, +Set2, ?Difference) is det.
|
||||
%
|
||||
% Is true when Difference is the symmetric difference of Set1 and
|
||||
% Set2. I.e., Difference contains all elements that are not in the
|
||||
% intersection of Set1 and Set2. The semantics is the same as the
|
||||
% sequence below (but the actual implementation requires only a
|
||||
% single scan).
|
||||
% Is true when Difference is the symmetric difference of Set1 and
|
||||
% Set2. I.e., Difference contains all elements that are not in the
|
||||
% intersection of Set1 and Set2. The semantics is the same as the
|
||||
% sequence below (but the actual implementation requires only a
|
||||
% single scan).
|
||||
%
|
||||
% ```
|
||||
% ord_union(Set1, Set2, Union),
|
||||
% ord_intersection(Set1, Set2, Intersection),
|
||||
% ord_subtract(Union, Intersection, Difference).
|
||||
% ```
|
||||
% ==
|
||||
% ord_union(Set1, Set2, Union),
|
||||
% ord_intersection(Set1, Set2, Intersection),
|
||||
% ord_subtract(Union, Intersection, Difference).
|
||||
% ==
|
||||
%
|
||||
% For example:
|
||||
% For example:
|
||||
%
|
||||
% ```
|
||||
% ?- ord_symdiff([1,2], [2,3], X).
|
||||
% X = [1,3].
|
||||
% ```
|
||||
% ==
|
||||
% ?- ord_symdiff([1,2], [2,3], X).
|
||||
% X = [1,3].
|
||||
% ==
|
||||
|
||||
ord_symdiff([], Set2, Set2).
|
||||
ord_symdiff([H1|T1], Set2, Difference) :-
|
||||
@@ -448,7 +457,7 @@ ord_symdiff(>, H1, T1, H2, Set2, [H2|Difference]) :-
|
||||
*/
|
||||
|
||||
|
||||
/* Ordered set manipulation
|
||||
/** <module> Ordered set manipulation
|
||||
|
||||
This library defines set operations on sets represented as ordered
|
||||
lists.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Predicates for reasoning about the operating system (OS) environment.
|
||||
Written July 2020 by Markus Triska (triska@metalevel.at).
|
||||
|
||||
@@ -7,89 +7,33 @@
|
||||
Example:
|
||||
|
||||
?- getenv("LANG", Ls).
|
||||
Ls = "en_US.UTF-8".
|
||||
Ls = "en_US.UTF-8"
|
||||
; false.
|
||||
|
||||
Public domain code.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/** Predicates for reasoning about the operating system (OS) environment.
|
||||
|
||||
This includes predicates about environment variables, calls to shell and
|
||||
finding out the PID of the running system.
|
||||
*/
|
||||
|
||||
:- module(os, [getenv/2,
|
||||
setenv/2,
|
||||
unsetenv/1,
|
||||
shell/1,
|
||||
shell/2,
|
||||
pid/1,
|
||||
raw_argv/1,
|
||||
argv/1]).
|
||||
unsetenv/1]).
|
||||
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(charsio)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(si)).
|
||||
|
||||
%% getenv(+Key, -Value).
|
||||
%
|
||||
% True iff Value contains the value of the environment variable Key.
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- getenv("LANG", Ls).
|
||||
% Ls = "en_US.UTF-8".
|
||||
% ```
|
||||
getenv(Key, Value) :-
|
||||
must_be_env_var(Key),
|
||||
'$getenv'(Key, Value).
|
||||
|
||||
%% setenv(+Key, +Value).
|
||||
%
|
||||
% Sets the environment variable Key to Value
|
||||
setenv(Key, Value) :-
|
||||
must_be_env_var(Key),
|
||||
must_be_chars(Value),
|
||||
'$setenv'(Key, Value).
|
||||
|
||||
%% unsetenv(+Key).
|
||||
%
|
||||
% Unsets the environment variable Key
|
||||
unsetenv(Key) :-
|
||||
must_be_env_var(Key),
|
||||
'$unsetenv'(Key).
|
||||
|
||||
%% shell(+Command)
|
||||
%
|
||||
% Equivalent to `shell(Command, 0)`.
|
||||
shell(Command) :- shell(Command, 0).
|
||||
|
||||
%% shell(+Command, -Status).
|
||||
%
|
||||
% True iff executes Command in a shell of the operating system and the exit code is Status.
|
||||
% Keep in mind the shell syntax is dependant on the operating system, so it should be
|
||||
% used very carefully.
|
||||
%
|
||||
% Example (using Linux and fish shell):
|
||||
%
|
||||
% ```
|
||||
% ?- shell("echo $SHELL", Status).
|
||||
% /bin/fish
|
||||
% Status = 0.
|
||||
% ```
|
||||
shell(Command, Status) :-
|
||||
must_be_chars(Command),
|
||||
can_be(integer, Status),
|
||||
'$shell'(Command, Status).
|
||||
|
||||
%% pid(-PID).
|
||||
%
|
||||
% True iff PID is the process identification number of current Scryer Prolog instance.
|
||||
pid(PID) :-
|
||||
can_be(integer, PID),
|
||||
'$pid'(PID).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
For now, we only support a restricted subset of variable names.
|
||||
|
||||
@@ -112,34 +56,3 @@ permitted('_').
|
||||
must_be_chars(Cs) :-
|
||||
must_be(list, Cs),
|
||||
maplist(must_be(character), Cs).
|
||||
|
||||
%% raw_argv(-Argv)
|
||||
%
|
||||
% True iff Argv is the list of arguments that this program was started with (usually passed via command line).
|
||||
% In contrast to `argv/1`, this version includes every argument, without any postprocessing, just as the operating
|
||||
% system reports it to the system. This includes-flags of Scryer itself, which are not needed in general.
|
||||
raw_argv(Argv) :-
|
||||
can_be(list, Argv),
|
||||
'$argv'(Argv).
|
||||
|
||||
%% argv(-Argv)
|
||||
%
|
||||
% True if Argv is the list of arguments that this program was started with (usually passed via command line).
|
||||
% In this version, only arguments specific to the program are passed. To differentiate between the system
|
||||
% arguments and the program arguments, we use `--` as a separator.
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% % Call with scryer-prolog -f -- -t hello
|
||||
% ?- argv(X).
|
||||
% X = ["-t", "hello"].
|
||||
% ```
|
||||
argv(Argv) :-
|
||||
can_be(list, Argv),
|
||||
'$argv'(Argv0),
|
||||
( append(_, ["--"|Argv1], Argv0) ->
|
||||
Argv = Argv1
|
||||
;
|
||||
Argv = []
|
||||
).
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/** Reasoning about pairs.
|
||||
|
||||
Pairs are Prolog terms with principal functor `(-)/2`. A pair
|
||||
often has the form `Key-Value`. The predicates of this library
|
||||
relate pairs to keys and values.
|
||||
*/
|
||||
|
||||
:- module(pairs, [pairs_keys_values/3,
|
||||
pairs_keys/2,
|
||||
pairs_values/2,
|
||||
@@ -12,27 +5,12 @@
|
||||
map_list_to_pairs/3]).
|
||||
|
||||
|
||||
:- meta_predicate(map_list_to_pairs(2, ?, ?)).
|
||||
|
||||
%% pairs_keys_values(?Pairs, ?Keys, ?Values)
|
||||
%
|
||||
% The first argument is a list of Pairs, the second the corresponding
|
||||
% Keys, and the third argument the corresponding values.
|
||||
|
||||
pairs_keys_values([], [], []).
|
||||
pairs_keys_values([A-B|ABs], [A|As], [B|Bs]) :-
|
||||
pairs_keys_values(ABs, As, Bs).
|
||||
|
||||
%% pairs_keys(?Pairs, ?Keys)
|
||||
%
|
||||
% Same as `pairs_keys_values(Pairs, Keys, _)`.
|
||||
|
||||
pairs_keys(Ps, Ks) :- pairs_keys_values(Ps, Ks, _).
|
||||
|
||||
%% pairs_values(?Pairs, ?Values)
|
||||
%
|
||||
% Same as `pairs_keys_values(Pairs, _, Values)`.
|
||||
|
||||
pairs_values(Ps, Vs) :- pairs_keys_values(Ps, _, Vs).
|
||||
|
||||
map_list_to_pairs(Pred, Ls, Ps) :-
|
||||
|
||||
237
src/lib/pio.pl
237
src/lib/pio.pl
@@ -1,62 +1,19 @@
|
||||
/** Pure I/O.
|
||||
|
||||
Our goal is to encourage the use of definite clause grammars (DCGs)
|
||||
for describing strings. The predicates `phrase_from_file/[2,3]`,
|
||||
`phrase_to_file/[2,3]` and `phrase_to_stream/2` let us apply DCGs
|
||||
transparently to files and streams, and therefore decouple side-effects
|
||||
from declarative descriptions.
|
||||
*/
|
||||
|
||||
:- module(pio, [phrase_from_file/2,
|
||||
phrase_from_file/3,
|
||||
phrase_from_stream/2,
|
||||
phrase_to_file/2,
|
||||
phrase_to_file/3,
|
||||
phrase_to_stream/2
|
||||
]).
|
||||
phrase_from_file/3]).
|
||||
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(freeze)).
|
||||
:- use_module(library(gensym)).
|
||||
:- use_module(library(iso_ext), [
|
||||
bb_get/2, bb_put/2, setup_call_cleanup/3, partial_string/3, partial_string_tail/2
|
||||
]).
|
||||
:- use_module(library(lists), [append/3, length/2, member/2, maplist/2]).
|
||||
:- use_module(library(charsio), [get_n_chars/3]).
|
||||
|
||||
:- meta_predicate(phrase_from_file(2, ?)).
|
||||
:- meta_predicate(phrase_from_file(2, ?, ?)).
|
||||
:- meta_predicate(phrase_from_stream(2, ?)).
|
||||
:- meta_predicate(phrase_to_file(2, ?)).
|
||||
:- meta_predicate(phrase_to_file(2, ?, ?)).
|
||||
:- meta_predicate(phrase_to_stream(2, ?)).
|
||||
|
||||
|
||||
%% phrase_from_stream(+GRBody, +Stream)
|
||||
%
|
||||
% True if grammar rule body GRBody covers the contents of the stream,
|
||||
% represented as a list of characters.
|
||||
|
||||
phrase_from_stream(GRBody, Stream) :-
|
||||
stream_property(Stream, reposition(Reposition)),
|
||||
stream_to_lazy_list(Reposition, Stream, Ls),
|
||||
phrase(GRBody, Ls).
|
||||
|
||||
%% phrase_from_file(+GRBody, +File)
|
||||
%
|
||||
% True if grammar rule body GRBody covers the contents of File,
|
||||
% represented as a list of characters.
|
||||
:- use_module(library(iso_ext), [setup_call_cleanup/3, partial_string/3]).
|
||||
:- use_module(library(lists), [member/2]).
|
||||
|
||||
phrase_from_file(NT, File) :-
|
||||
phrase_from_file(NT, File, []).
|
||||
|
||||
%% phrase_from_file(+GRBody, +File, +Options)
|
||||
%
|
||||
% Like `phrase_from_file/2`, using Options to open the file.
|
||||
|
||||
phrase_from_file(NT, File, Options) :-
|
||||
( var(File) -> instantiation_error(phrase_from_file/3)
|
||||
; (\+ atom(File) ; File = []) ->
|
||||
domain_error(source_sink, File, phrase_from_file/3)
|
||||
; must_be(list, Options),
|
||||
( member(Var, Options), var(Var) -> instantiation_error(phrase_from_file/3)
|
||||
; member(type(Type), Options) ->
|
||||
@@ -64,174 +21,22 @@ phrase_from_file(NT, File, Options) :-
|
||||
member(Type, [text,binary])
|
||||
; Type = text
|
||||
),
|
||||
setup_call_cleanup(
|
||||
open(File, read, Stream, [reposition(true)|Options]),
|
||||
phrase_from_stream(NT, Stream),
|
||||
close(Stream)
|
||||
)
|
||||
).
|
||||
|
||||
% How many chars to read from stream and buffer in each step
|
||||
chars_to_read(4096).
|
||||
|
||||
stream_to_lazy_list(Reposition, Stream, Ls) :-
|
||||
get_stream_buffer_position(Reposition, Stream, Pos),
|
||||
freeze(Ls, render_step(Reposition, Stream, Pos, Ls)).
|
||||
|
||||
render_step(Reposition, Stream, Pos, Ls) :-
|
||||
set_stream_buffer_position(Reposition, Stream, Pos),
|
||||
( buffer_at_end_of_stream(Reposition, Stream) ->
|
||||
Ls = []
|
||||
; chars_to_read(CharsToRead),
|
||||
buffer_get_n_chars(Reposition, Stream, CharsToRead, Chars),
|
||||
partial_string(Chars, Ls, Ls0),
|
||||
stream_to_lazy_list(Reposition, Stream, Ls0)
|
||||
).
|
||||
|
||||
buffer_at_end_of_stream(true, Stream) :- at_end_of_stream(Stream).
|
||||
buffer_at_end_of_stream(false, Stream) :-
|
||||
stream_bufferids(Stream, _, BufferPosId, _),
|
||||
bb_get(BufferPosId, Pos),
|
||||
Pos = eof.
|
||||
|
||||
get_stream_buffer_position(true, Stream, Pos) :-
|
||||
stream_property(Stream, position(Pos)).
|
||||
get_stream_buffer_position(false, Stream, Pos) :-
|
||||
stream_bufferids(Stream, _, BufferPosId, _),
|
||||
bb_get(BufferPosId, Pos).
|
||||
|
||||
set_stream_buffer_position(true, Stream, Pos) :-
|
||||
set_stream_position(Stream, Pos).
|
||||
set_stream_buffer_position(false, Stream, Pos) :-
|
||||
stream_bufferids(Stream, _, BufferPosId, _),
|
||||
bb_put(BufferPosId, Pos).
|
||||
|
||||
buffer_get_n_chars(true, Stream, N, Chars) :-
|
||||
get_n_chars(Stream, N, Chars).
|
||||
buffer_get_n_chars(false, Stream, N, Chars) :-
|
||||
stream_bufferids(Stream, BufferId, BufferPosId, BufferLenId),
|
||||
buffer_prepare_for_n(Stream, BufferId, BufferPosId, BufferLenId, N),
|
||||
bb_get(BufferId, Buffer),
|
||||
bb_get(BufferPosId, BufferPos),
|
||||
( BufferPos = eof ->
|
||||
Chars = []
|
||||
; string_get_n_chars(Buffer, BufferPos, N, Chars),
|
||||
length(Chars, NChars),
|
||||
( NChars = 0 ->
|
||||
BufferPos1 = eof
|
||||
; BufferPos1 is BufferPos + NChars
|
||||
),
|
||||
bb_put(BufferPosId, BufferPos1)
|
||||
).
|
||||
|
||||
buffer_prepare_for_n(Stream, BufferId, BufferPosId, BufferLenId, N) :-
|
||||
bb_get(BufferPosId, BufferPos),
|
||||
bb_get(BufferLenId, BufferLen),
|
||||
( BufferLen < BufferPos + N ->
|
||||
bb_get(BufferId, Buffer),
|
||||
(
|
||||
( var(Buffer) ->
|
||||
BufferTail = Buffer
|
||||
; partial_string_last_tail(Buffer, BufferTail)
|
||||
) ->
|
||||
( at_end_of_stream(Stream) ->
|
||||
BufferTail = [],
|
||||
bb_put(BufferId, Buffer)
|
||||
; chars_to_read(CharsToRead),
|
||||
get_n_chars(Stream, CharsToRead, Chars),
|
||||
length(Chars, NChars),
|
||||
partial_string(Chars, BufferTail, _),
|
||||
bb_put(BufferId, Buffer),
|
||||
BufferLen1 is BufferLen + NChars,
|
||||
bb_put(BufferLenId, BufferLen1),
|
||||
buffer_prepare_for_n(Stream, BufferId, BufferPosId, BufferLenId, N)
|
||||
)
|
||||
; true
|
||||
)
|
||||
; true
|
||||
).
|
||||
|
||||
partial_string_last_tail(PartialString, PartialStringTail) :-
|
||||
partial_string_tail(PartialString, PartialStringTail0),
|
||||
( var(PartialStringTail0) ->
|
||||
PartialStringTail = PartialStringTail0
|
||||
; partial_string_last_tail(PartialStringTail0, PartialStringTail)
|
||||
).
|
||||
|
||||
string_get_n_chars(String, Pos, N, Chars) :-
|
||||
'$skip_max_list'(_, Pos, String, String1),
|
||||
'$skip_max_list'(N1, N, String1, _),
|
||||
length(Chars, N1),
|
||||
append(Chars, _, String1).
|
||||
|
||||
stream_bufferids(Stream, BufferId, BufferPosId, BufferLenId) :-
|
||||
( bb_get(streams_buffers, _) ->
|
||||
true
|
||||
; bb_put(streams_buffers, [])
|
||||
),
|
||||
bb_get(streams_buffers, StreamsBuffers),
|
||||
( member(
|
||||
stream_buffer(Stream, BufferId, BufferPosId, BufferLenId),
|
||||
StreamsBuffers
|
||||
) ->
|
||||
true
|
||||
; gensym(buffer, BufferId),
|
||||
gensym(buffer_pos, BufferPosId),
|
||||
gensym(buffer_len, BufferLenId),
|
||||
bb_put(
|
||||
streams_buffers,
|
||||
[stream_buffer(Stream, BufferId, BufferPosId, BufferLenId)|StreamsBuffers]
|
||||
),
|
||||
bb_put(BufferId, _),
|
||||
bb_put(BufferPosId, 0),
|
||||
bb_put(BufferLenId, 0)
|
||||
).
|
||||
|
||||
%% phrase_to_stream(+GRBody, +Stream)
|
||||
%
|
||||
% Emit the list of characters described by the grammar rule body
|
||||
% GRBody to Stream.
|
||||
%
|
||||
% An ideal implementation of `phrase_to_stream/2` writes each
|
||||
% character as soon as it becomes known and no choice-points remain,
|
||||
% and thus avoids the manifestation of the entire string in memory.
|
||||
% See [#691](https://github.com/mthom/scryer-prolog/issues/691) for
|
||||
% more information.
|
||||
%
|
||||
% The current preliminary implementation is provided so that Prolog
|
||||
% programmers can already get used to describing output with DCGs,
|
||||
% and then writing it to a file when necessary. This simple
|
||||
% implementation suffices as long as the entire contents can be
|
||||
% represented in memory, and thus covers a large number of use cases.
|
||||
|
||||
phrase_to_stream(GRBody, Stream) :-
|
||||
phrase(GRBody, Cs),
|
||||
must_be(chars, Cs),
|
||||
( stream_property(Stream, type(binary)) ->
|
||||
( '$first_non_octet'(Cs, N) ->
|
||||
domain_error(octet_character, N, phrase_to_stream/2)
|
||||
; true
|
||||
)
|
||||
; true
|
||||
),
|
||||
% we use a specialised internal predicate that uses only a
|
||||
% single "write" operation for efficiency. It is equivalent to
|
||||
% maplist(put_char(Stream), Cs). It also works for binary streams.
|
||||
'$put_chars'(Stream, Cs).
|
||||
|
||||
%% phrase_to_file(+GRBody, +File)
|
||||
%
|
||||
% Write the string described by GRBody to File.
|
||||
|
||||
phrase_to_file(GRBody, File) :-
|
||||
phrase_to_file(GRBody, File, []).
|
||||
setup_call_cleanup(open(File, read, Stream, [reposition(true)|Options]),
|
||||
( stream_to_lazy_list(Stream, Xs),
|
||||
phrase(NT, Xs) ),
|
||||
close(Stream))
|
||||
).
|
||||
|
||||
|
||||
%% phrase_to_file(+GRBody, +File, +Options)
|
||||
%
|
||||
% Like `phrase_to_file/2`, using Options to open the file.
|
||||
stream_to_lazy_list(Stream, Xs) :-
|
||||
stream_property(Stream, position(Pos)),
|
||||
freeze(Xs, reader_step(Stream, Pos, Xs)).
|
||||
|
||||
phrase_to_file(GRBody, File, Options) :-
|
||||
setup_call_cleanup(open(File, write, Stream, Options),
|
||||
phrase_to_stream(GRBody, Stream),
|
||||
close(Stream)).
|
||||
reader_step(Stream, Pos, Xs0) :-
|
||||
set_stream_position(Stream, Pos),
|
||||
( at_end_of_stream(Stream)
|
||||
-> Xs0 = []
|
||||
; '$get_n_chars'(Stream, 4096, Cs),
|
||||
partial_string(Cs, Xs0, Xs),
|
||||
stream_to_lazy_list(Stream, Xs)
|
||||
).
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
:- module(process, [
|
||||
process_create/3,
|
||||
process_id/2,
|
||||
process_release/1,
|
||||
process_wait/2,
|
||||
process_wait/3,
|
||||
process_kill/1
|
||||
]).
|
||||
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(iso_ext)).
|
||||
:- use_module(library(lists), [member/2, maplist/2, maplist/3, append/2]).
|
||||
:- use_module(library(reif), [tfilter/3, memberd_t/3]).
|
||||
|
||||
|
||||
%% process_create(+Exe, +Args:list, +Options).
|
||||
%
|
||||
% Create a new process by executing the executable Exe and passing it the Arguments Args.
|
||||
%
|
||||
% Note: On windows please take note of [windows argument splitting](https://doc.rust-lang.org/std/process/index.html#windows-argument-splitting).
|
||||
%
|
||||
% Options is a list consisting of the following options:
|
||||
%
|
||||
% * `cwd(+Path)` Set the processes working directory to `Path`
|
||||
% * `process(-Process)` `Process` will be assigned a process handle for the spawned process
|
||||
% * `env(+List)` Don't inherit environment variables and set the variables defined in `List`
|
||||
% * `environment(+List)` Inherit environment variables and set/override the variables defined in `List`
|
||||
% * `stdin(Spec)`, `stdout(Spec)` or `stderr(Spec)` defines how to redirect the spawned processes io streams
|
||||
%
|
||||
% The elements of `List` in `env(List)`/`environment(List)` List must be string pairs using `=/2`.
|
||||
% `env/1` and `environment/1` may not be both specified.
|
||||
%
|
||||
% The following stdio `Spec` are available:
|
||||
%
|
||||
% * `std` inherit the current processes original stdio streams (does currently not account for stdio being changed by `set_input` or `set_output`)
|
||||
% * `file(+Path)` attach the strea to the file at `Path`
|
||||
% * `null` discards writes and behaves as eof for read. Equivalent to using `file(/dev/null)`
|
||||
% * `pipe(-Steam)` create a new pipe and assigne one end to the created process and the other end to `Stream`
|
||||
%
|
||||
% Specifying an option multiple times is an error, when an option is not specified the following defaults apply:
|
||||
%
|
||||
% - `cwd(".")`
|
||||
% - `environment([])`
|
||||
% - `stdin(std)`, `stdout(std)`, `stderr(std)`
|
||||
%
|
||||
process_create(Exe, Args, Options) :- call_with_error_context(process_create_(Exe, Args, Options), predicate-process_create/3).
|
||||
|
||||
process_create_(Exe, Args, Options) :-
|
||||
must_be(chars, Exe),
|
||||
must_be(list, Args),
|
||||
maplist(must_be(chars), Args),
|
||||
must_be(list, Options),
|
||||
check_options(
|
||||
[
|
||||
option([stdin], valid_stdio, stdin(std), stdin(Stdin)),
|
||||
option([stdout], valid_stdio, stdout(std), stdout(Stdout)),
|
||||
option([stderr], valid_stdio, stderr(std), stderr(Stderr)),
|
||||
option([env, environment], valid_env, environment([]), Env),
|
||||
option([process], valid_uninit_process, process(_), process(Process)),
|
||||
option([cwd], valid_cwd, cwd("."), cwd(Cwd))
|
||||
],
|
||||
Options,
|
||||
process_create_option
|
||||
),
|
||||
Stdin =.. Stdin1,
|
||||
Stdout =.. Stdout1,
|
||||
Stderr =.. Stderr1,
|
||||
simplify_env(Env, Env1),
|
||||
'$process_create'(Exe, Args, Stdin1, Stdout1, Stderr1, Env1, Cwd, Process).
|
||||
|
||||
%% process_id(+Process, -Pid).
|
||||
%
|
||||
process_id(Process, Pid) :- call_with_error_context(process_id_(Process, Pid), predicate-process_id/2).
|
||||
|
||||
process_id_(Process, Pid) :-
|
||||
valid_process(Process),
|
||||
must_be(var, Pid),
|
||||
'$process_id'(Process, Pid).
|
||||
|
||||
%% process_wait(+Process, Status).
|
||||
%
|
||||
% See `process_create/3` with `Options = []`
|
||||
%
|
||||
process_wait(Process, Status) :- call_with_error_context(process_wait(Process, Status, []), predicate-process_wait/2).
|
||||
|
||||
|
||||
%% process_wait(+Process, Status, Options).
|
||||
%
|
||||
% Wait for the process behind the process handle `Process` to exit.
|
||||
%
|
||||
% When the process exits regulary `Status` will be unified with `exit(Exit)` where `Exit` is the processes exit code.
|
||||
% When the process exits was killed `Status` will be unified with `killed(Signal)` where `Signal` is the signal number that killed the process.
|
||||
% When the process doesn't exit before the timeout `Status` will be unified with `timeout`.
|
||||
%
|
||||
% `Options` is a a list of the following options
|
||||
%
|
||||
% * timeout(Timeout) supported values for `Timeout` are 0 or `infinite`
|
||||
% * release(Bool) supported values for `Bool` are `true` or `false`
|
||||
%
|
||||
% Each options may be specified at most once, when an option is not specified the following defaults apply:
|
||||
%
|
||||
% - timeout(infinite)
|
||||
% - release(true)
|
||||
%
|
||||
process_wait(Process, Status, Options) :- call_with_error_context(process_wait_(Process, Status, Options), predicate-process_wait/3).
|
||||
|
||||
process_wait_(Process, Status, Options) :-
|
||||
valid_process(Process),
|
||||
check_options(
|
||||
[
|
||||
option([timeout], valid_timeout, timeout(infinite), timeout(Timeout)),
|
||||
option([release], valid_release, release(true), release(Release))
|
||||
],
|
||||
Options,
|
||||
process_wait_option
|
||||
),
|
||||
'$process_wait'(Process, Exit, Timeout),
|
||||
((true = Release) -> '$process_release'(Process) ; true),
|
||||
Exit = Status.
|
||||
|
||||
valid_timeout(timeout(infinite)).
|
||||
valid_timeout(timeout(0)).
|
||||
|
||||
valid_release(release(Arg)) :-
|
||||
( var(Arg) -> instantiation_error([])
|
||||
; valid_bool(Arg) -> true
|
||||
; domain_error(boolean, Arg, [])
|
||||
).
|
||||
|
||||
|
||||
valid_bool(true).
|
||||
valid_bool(false).
|
||||
|
||||
|
||||
%% process_kill(+Process).
|
||||
%
|
||||
% Kill the process using the process handle `Process`.
|
||||
% On Unix this sends SIGKILL.
|
||||
%
|
||||
% Only works for processes spawned with `process_create/3` that have not yet been release with `process_release/1`
|
||||
%
|
||||
process_kill(Process) :- call_with_error_context(process_kill_(Process), predicate-process_kill/1).
|
||||
|
||||
process_kill_(Process) :-
|
||||
valid_process(Process),
|
||||
'$process_kill'(Process).
|
||||
|
||||
%% process_release(+Process)
|
||||
%
|
||||
% wait for the process to exit (if not already) and release process handle `Process`
|
||||
%
|
||||
% It's an error if `Process` is not a valid process handle
|
||||
%
|
||||
process_release(Process) :- call_with_error_context(process_release_(Process), predicate-process_release/1).
|
||||
|
||||
process_release_(Process) :-
|
||||
valid_process(Process),
|
||||
process_wait(Process, _).
|
||||
|
||||
|
||||
must_be_known_options(Valid, Options, Domain) :- call_with_error_context(must_be_known_options_(Valid, [], Options, Domain),predicate-must_be_known_options/3).
|
||||
|
||||
must_be_known_options_(_, _, [], _).
|
||||
must_be_known_options_(Valid, Found, [X|XS], Domain) :-
|
||||
( functor(X, Option, 1) -> true
|
||||
; domain_error(Domain, X, [])
|
||||
) ,
|
||||
( member(Option, Found) -> domain_error(non_duplicate_options, Option , [])
|
||||
; member(Option, Valid) -> true
|
||||
; domain_error(Domain, Option, [])
|
||||
),
|
||||
must_be_known_options_(Valid, [Option | Found], XS, Domain).
|
||||
|
||||
check_options(KnownOptions, Options, Domain) :- call_with_error_context(check_options_(KnownOptions, Options, Domain), predicate-check_options/3).
|
||||
|
||||
check_options_(KnownOptions, Options, Domain) :-
|
||||
maplist(option_names, KnownOptions, Namess),
|
||||
append(Namess, Names),
|
||||
must_be_known_options(Names, Options, Domain),
|
||||
extract_options(KnownOptions, Options).
|
||||
|
||||
option_names(option(Names,_,_,_), Names).
|
||||
|
||||
extract_options(KnownOptions, Options) :- call_with_error_context(extract_options_(KnownOptions, Options), predicate-extract_options/2).
|
||||
|
||||
extract_options_([], _).
|
||||
extract_options_([X | XS], Options) :-
|
||||
option(Kinds, Pred, Default, Choice) = X,
|
||||
tfilter(find_option(Kinds), Options, Solutions),
|
||||
( Solutions = [] -> Choice = Default
|
||||
; Solutions = [Provided] -> functor(Pred, Name, Arity), ArityP1 is Arity+1, call_with_error_context(call(Pred, Provided),predicate-Name/ArityP1), Choice = Provided
|
||||
; domain_error(non_conflicting_options, Solutions, [])
|
||||
),
|
||||
extract_options_(XS, Options).
|
||||
|
||||
find_option(Names, Found, T) :-
|
||||
functor(Found, Name, 1),
|
||||
memberd_t(Name, Names, T).
|
||||
|
||||
valid_stdio(IO) :- arg(1, IO, Arg),
|
||||
( var(Arg) -> instantiation_error([])
|
||||
; valid_stdio_(Arg) -> true
|
||||
; domain_error(stdio_spec, Arg, [])
|
||||
).
|
||||
|
||||
valid_stdio_(std).
|
||||
valid_stdio_(null).
|
||||
valid_stdio_(pipe(Stream)) :- must_be(var, Stream).
|
||||
valid_stdio_(file(Path)) :- must_be(chars, Path).
|
||||
|
||||
valid_env(env(E)) :-
|
||||
must_be(list, E),
|
||||
( valid_env_(E) -> true
|
||||
; domain_error(process_create_option, env(E), [])
|
||||
).
|
||||
valid_env(environment(E)) :-
|
||||
must_be(list, E),
|
||||
( valid_env_(E) -> true
|
||||
; domain_error(process_create_option, environment(E), [])
|
||||
).
|
||||
|
||||
valid_env_([]).
|
||||
valid_env_([N=V|ES]) :-
|
||||
must_be(chars, N),
|
||||
must_be(chars, V),
|
||||
valid_env_(ES).
|
||||
|
||||
valid_uninit_process(process(Process)) :- must_be(var, Process).
|
||||
|
||||
valid_process(Process) :- var(Process) -> instantiation_error([]) ; true.
|
||||
|
||||
valid_cwd(cwd(Cwd)) :- must_be(chars, Cwd).
|
||||
|
||||
simplify_env(E, [Kind, Envs1]) :- E =.. [Kind, Envs], simplify_env_(Envs, Envs1).
|
||||
|
||||
simplify_env_([],[]).
|
||||
simplify_env_([N=V|E],[[N, V]|E1]) :- simplify_env_(E, E1).
|
||||
@@ -1,52 +1,51 @@
|
||||
/**
|
||||
This library provides probabilistic predicates and random number generators.
|
||||
|
||||
To retain desirable declarative properties, predicates that internally
|
||||
use random numbers should be equipped with an argument that specifies
|
||||
the random seed. This makes everything completely reproducible.
|
||||
*/
|
||||
|
||||
:- module(random, [maybe/0, random/1, random_integer/3, set_random/1]).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
To retain desirable declarative properties, predicates that internally
|
||||
use random numbers should be equipped with an argument that specifies
|
||||
the random seed. This makes everything completely reproducible.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
:- use_module(library(error)).
|
||||
|
||||
%% maybe.
|
||||
%
|
||||
% Succeeds with probability 0.5.
|
||||
% succeeds with probability 0.5.
|
||||
maybe :- '$maybe'.
|
||||
|
||||
%% random(-R).
|
||||
%
|
||||
% Generates a random floating number between 0 (inclusive) and 1 (exclusive).
|
||||
% The higher the precision, the slower it gets.
|
||||
random_number_precision(64).
|
||||
|
||||
random(R) :-
|
||||
var(R),
|
||||
N is 2^50,
|
||||
'$random_integer'(0, N, K),
|
||||
R is K/N.
|
||||
random_number_precision(N),
|
||||
rnd(N, R).
|
||||
|
||||
%% random_integer(+Lower, +Upper, -R).
|
||||
%
|
||||
% Generates a random integer number between Lower (inclusive) and Upper (exclusive).
|
||||
%
|
||||
% Throws `instantiation_error` if Lower or Upper are variables.
|
||||
%
|
||||
% Throws `type_error` if Lower or Upper aren't integers.
|
||||
random_integer(Lower, Upper, R) :-
|
||||
var(R),
|
||||
( (var(Lower) ; var(Upper)) ->
|
||||
instantiation_error(random_integer/3)
|
||||
instantiation_error(random_integer/3)
|
||||
; \+ integer(Lower) ->
|
||||
type_error(integer, Lower, random_integer/3)
|
||||
domain_error(integer, Lower, random_integer/3)
|
||||
; \+ integer(Upper) ->
|
||||
type_error(integer, Upper, random_integer/3)
|
||||
; Lower < Upper,
|
||||
'$random_integer'(Lower, Upper, R)
|
||||
domain_error(integer, Upper, random_integer/3)
|
||||
; Upper > Lower,
|
||||
random(R0),
|
||||
R is floor((Upper - Lower) * R0 + Lower)
|
||||
).
|
||||
|
||||
%% set_random(+Seed).
|
||||
%
|
||||
% Sets a seed that will be used for subsequent random generations in this library.
|
||||
% It's necessary to set a seed to provide reproducible executions using this library.
|
||||
rnd(N, R) :-
|
||||
rnd_(N, 0, R).
|
||||
|
||||
rnd_(0, R, R) :- !.
|
||||
rnd_(N, R0, R) :-
|
||||
maybe,
|
||||
!,
|
||||
N1 is N - 1,
|
||||
rnd_(N1, R0, R).
|
||||
rnd_(N, R0, R) :-
|
||||
N1 is N - 1,
|
||||
R1 is R0 + 1.0 / 2.0 ^ N,
|
||||
rnd_(N1, R1, R).
|
||||
|
||||
set_random(Seed) :-
|
||||
( nonvar(Seed) ->
|
||||
( Seed = seed(S) ->
|
||||
|
||||
@@ -1,24 +1,9 @@
|
||||
/** Predicates from [*Indexing dif/2*](https://arxiv.org/abs/1607.01590).
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
?- tfilter(=(a), [X,Y], Es).
|
||||
X = a, Y = a, Es = "aa"
|
||||
; X = a, Es = "a", dif:dif(a,Y)
|
||||
; Y = a, Es = "a", dif:dif(a,X)
|
||||
; Es = [], dif:dif(a,X), dif:dif(a,Y).
|
||||
```
|
||||
*/
|
||||
|
||||
:- module(reif, [if_/3, (=)/3, (',')/3, (;)/3, cond_t/3, dif/3,
|
||||
memberd_t/3, tfilter/3, tmember/2, tmember_t/3,
|
||||
tpartition/4]).
|
||||
memberd_t/3, tfilter/3, tmember/2, tmember_t/3,
|
||||
tpartition/4]).
|
||||
|
||||
:- use_module(library(dif)).
|
||||
|
||||
:- meta_predicate(if_(1, 0, 0)).
|
||||
|
||||
if_(If_1, Then_0, Else_0) :-
|
||||
call(If_1, T),
|
||||
( T == true -> call(Then_0)
|
||||
@@ -41,14 +26,13 @@ dif(X, Y, T) :-
|
||||
non(true, false).
|
||||
non(false, true).
|
||||
|
||||
:- meta_predicate(tfilter(2, ?, ?)).
|
||||
tfilter(C_2, Es, Fs) :-
|
||||
i_tfilter(Es, C_2, Fs).
|
||||
|
||||
tfilter(_, [], []).
|
||||
tfilter(C_2, [E|Es], Fs0) :-
|
||||
i_tfilter([], _, []).
|
||||
i_tfilter([E|Es], C_2, Fs0) :-
|
||||
if_(call(C_2, E), Fs0 = [E|Fs], Fs0 = Fs),
|
||||
tfilter(C_2, Es, Fs).
|
||||
|
||||
:- meta_predicate(tpartition(2, ?, ?, ?)).
|
||||
i_tfilter(Es, C_2, Fs).
|
||||
|
||||
tpartition(P_2, Xs, Ts, Fs) :-
|
||||
i_tpartition(Xs, P_2, Ts, Fs).
|
||||
@@ -60,18 +44,12 @@ i_tpartition([X|Xs], P_2, Ts0, Fs0) :-
|
||||
, ( Fs0 = [X|Fs], Ts0 = Ts ) ),
|
||||
i_tpartition(Xs, P_2, Ts, Fs).
|
||||
|
||||
:- meta_predicate(','(1, 1, ?)).
|
||||
|
||||
','(A_1, B_1, T) :-
|
||||
if_(A_1, call(B_1, T), T = false).
|
||||
|
||||
:- meta_predicate(';'(1, 1, ?)).
|
||||
|
||||
';'(A_1, B_1, T) :-
|
||||
if_(A_1, T = true, call(B_1, T)).
|
||||
|
||||
:- meta_predicate(cond_t(1, 0, ?)).
|
||||
|
||||
cond_t(If_1, Then_0, T) :-
|
||||
if_(If_1, ( Then_0, T = true ), T = false ).
|
||||
|
||||
@@ -82,13 +60,8 @@ i_memberd_t([], _, false).
|
||||
i_memberd_t([X|Xs], E, T) :-
|
||||
if_( X = E, T = true, i_memberd_t(Xs, E, T) ).
|
||||
|
||||
:- meta_predicate(tmember(2, ?)).
|
||||
|
||||
tmember(P_2, [X|Xs]) :-
|
||||
if_( call(P_2, X), true, tmember(P_2, Xs) ).
|
||||
|
||||
:- meta_predicate(tmember_t(2, ?, ?)).
|
||||
|
||||
tmember_t(_P_2, [], false).
|
||||
tmember_t(P_2, [X|Xs], T) :-
|
||||
if_( call(P_2, X), T = true, tmember_t(P_2, Xs, T) ).
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written Apr 2021 by Aram Panasenco (panasenco@ucla.edu)
|
||||
Part of Scryer Prolog.
|
||||
|
||||
[Core Rules](https://tools.ietf.org/html/rfc5234#appendix-B.1) of the
|
||||
Augmented Backus-Naur Form specification (ABNF - RFC 5234). ABNF commonly
|
||||
serves as the definition language for IETF communication protocols, so
|
||||
having these DCGs can be extremely useful for reasoning about most IETF
|
||||
syntaxes. The DCGs are presented in the order they appear in the RFC.
|
||||
While some DCGs below use `char_type/2`, the most common ones are defined
|
||||
manually in order to take advantage of Prolog's first-argument indexing.
|
||||
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2021, Aram Panasenco
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
:- module(abnf, [abnf_alpha//1,
|
||||
abnf_bit//1,
|
||||
abnf_char//1,
|
||||
abnf_cr//0,
|
||||
abnf_crlf//0,
|
||||
abnf_ctl//1,
|
||||
abnf_digit//1,
|
||||
abnf_dquote//0,
|
||||
abnf_hexdig//1,
|
||||
abnf_htab//0,
|
||||
abnf_lf//0,
|
||||
abnf_lwsp//0,
|
||||
abnf_octet//1,
|
||||
abnf_sp//0,
|
||||
abnf_vchar//1,
|
||||
abnf_wsp//0 ]).
|
||||
|
||||
:- use_module(library(charsio)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(dif)).
|
||||
:- use_module(library(lists)).
|
||||
|
||||
abnf_alpha('a') --> "a".
|
||||
abnf_alpha('b') --> "b".
|
||||
abnf_alpha('c') --> "c".
|
||||
abnf_alpha('d') --> "d".
|
||||
abnf_alpha('e') --> "e".
|
||||
abnf_alpha('f') --> "f".
|
||||
abnf_alpha('g') --> "g".
|
||||
abnf_alpha('h') --> "h".
|
||||
abnf_alpha('i') --> "i".
|
||||
abnf_alpha('j') --> "j".
|
||||
abnf_alpha('k') --> "k".
|
||||
abnf_alpha('l') --> "l".
|
||||
abnf_alpha('m') --> "m".
|
||||
abnf_alpha('n') --> "n".
|
||||
abnf_alpha('o') --> "o".
|
||||
abnf_alpha('p') --> "p".
|
||||
abnf_alpha('q') --> "q".
|
||||
abnf_alpha('r') --> "r".
|
||||
abnf_alpha('s') --> "s".
|
||||
abnf_alpha('t') --> "t".
|
||||
abnf_alpha('u') --> "u".
|
||||
abnf_alpha('v') --> "v".
|
||||
abnf_alpha('w') --> "w".
|
||||
abnf_alpha('x') --> "x".
|
||||
abnf_alpha('y') --> "y".
|
||||
abnf_alpha('z') --> "z".
|
||||
abnf_alpha('A') --> "A".
|
||||
abnf_alpha('B') --> "B".
|
||||
abnf_alpha('C') --> "C".
|
||||
abnf_alpha('D') --> "D".
|
||||
abnf_alpha('E') --> "E".
|
||||
abnf_alpha('F') --> "F".
|
||||
abnf_alpha('G') --> "G".
|
||||
abnf_alpha('H') --> "H".
|
||||
abnf_alpha('I') --> "I".
|
||||
abnf_alpha('J') --> "J".
|
||||
abnf_alpha('K') --> "K".
|
||||
abnf_alpha('L') --> "L".
|
||||
abnf_alpha('M') --> "M".
|
||||
abnf_alpha('N') --> "N".
|
||||
abnf_alpha('O') --> "O".
|
||||
abnf_alpha('P') --> "P".
|
||||
abnf_alpha('Q') --> "Q".
|
||||
abnf_alpha('R') --> "R".
|
||||
abnf_alpha('S') --> "S".
|
||||
abnf_alpha('T') --> "T".
|
||||
abnf_alpha('U') --> "U".
|
||||
abnf_alpha('V') --> "V".
|
||||
abnf_alpha('W') --> "W".
|
||||
abnf_alpha('X') --> "X".
|
||||
abnf_alpha('Y') --> "Y".
|
||||
abnf_alpha('Z') --> "Z".
|
||||
|
||||
abnf_bit('0') --> "0".
|
||||
abnf_bit('1') --> "1".
|
||||
|
||||
abnf_char(Char) --> [Char], { dif(Char, '\x0000\'), char_type(Char, ascii) }. %'
|
||||
|
||||
abnf_cr --> "\r".
|
||||
|
||||
abnf_crlf --> "\r\n".
|
||||
|
||||
abnf_ctl(Char) --> [Char], { char_type(Char, ascii), char_type(Char, control) }.
|
||||
|
||||
abnf_digit('0') --> "0".
|
||||
abnf_digit('1') --> "1".
|
||||
abnf_digit('2') --> "2".
|
||||
abnf_digit('3') --> "3".
|
||||
abnf_digit('4') --> "4".
|
||||
abnf_digit('5') --> "5".
|
||||
abnf_digit('6') --> "6".
|
||||
abnf_digit('7') --> "7".
|
||||
abnf_digit('8') --> "8".
|
||||
abnf_digit('9') --> "9".
|
||||
|
||||
abnf_dquote --> "\"".
|
||||
|
||||
abnf_hexdig(Char) --> abnf_digit(Char).
|
||||
abnf_hexdig('A') --> "A".
|
||||
abnf_hexdig('B') --> "B".
|
||||
abnf_hexdig('C') --> "C".
|
||||
abnf_hexdig('D') --> "D".
|
||||
abnf_hexdig('E') --> "E".
|
||||
abnf_hexdig('F') --> "F".
|
||||
|
||||
abnf_htab --> "\t".
|
||||
|
||||
abnf_lf --> "\n".
|
||||
|
||||
abnf_lwsp --> "".
|
||||
abnf_lwsp --> abnf_wsp, abnf_lwsp.
|
||||
abnf_lwsp --> abnf_crlf, abnf_wsp, abnf_lwsp.
|
||||
|
||||
abnf_octet(Char) --> [Char], char_type(Char, octet).
|
||||
|
||||
abnf_sp --> " ".
|
||||
|
||||
abnf_vchar(Char) --> [Char], char_type(Char, ascii_graphic).
|
||||
|
||||
abnf_wsp --> abnf_sp.
|
||||
abnf_wsp --> abnf_htab.
|
||||
@@ -1,273 +0,0 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written Apr 2021 by Aram Panasenco (panasenco@ucla.edu)
|
||||
Part of Scryer Prolog.
|
||||
|
||||
`json_chars//1` can be used with [`phrase_from_file/2`](src/lib/pio.pl)
|
||||
or [`phrase/2`](src/lib/dcgs.pl) to parse and generate [JSON](https://www.json.org/json-en.html).
|
||||
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2021, Aram Panasenco
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
:- module(json, [
|
||||
json_chars//1
|
||||
]).
|
||||
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(dif)).
|
||||
:- use_module(library(lists)).
|
||||
|
||||
/* The DCGs are written to match the McKeeman form presented on the right side of https://www.json.org/json-en.html
|
||||
as closely as possible. Note that the names in the McKeeman form conflict with the pictures on the site. */
|
||||
json_chars(Internal) --> json_element(Internal).
|
||||
|
||||
/* Because it's impossible to distinguish between an empty array [] and an empty string "", we distinguish between
|
||||
different types of values based on their principal functor. The principal functors match the types defined in
|
||||
the JSON Schema spec here: https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.1.1
|
||||
EXCEPT we don't yet support the integer type. There are plans for more JSON Schema support in the near future. */
|
||||
json_value(pairs(Pairs)) --> json_object(Pairs).
|
||||
json_value(list(List)) --> json_array(List).
|
||||
json_value(string(Chars)) --> json_string(Chars).
|
||||
json_value(number(Number)) --> json_number(Number).
|
||||
json_value(boolean(Bool)) --> json_boolean(Bool).
|
||||
json_value(null) --> "null".
|
||||
|
||||
/* We pull json_boolean out into its own predicate in order to take advantage of first argument indexing and not leave
|
||||
choice points. For more details, watch this video on decomposing arguments: https://youtu.be/FZLofckPu4A?t=1648 */
|
||||
json_boolean(true) --> "true".
|
||||
json_boolean(false) --> "false".
|
||||
|
||||
json_object([]) --> "{", json_ws, "}".
|
||||
json_object([Pair|Pairs]) -->
|
||||
"{",
|
||||
json_members(Pairs, Pair),
|
||||
"}".
|
||||
|
||||
/* `json_members//2` below is implemented with a lagged argument to take advantage of first argument indexing.
|
||||
This is a pure performance-driven decision that doesn't affect the logic. The predicate could equivalently be
|
||||
implementes as `json_members//1` below:
|
||||
```
|
||||
json_members([Key-Value, Pair2 | Pairs]) --> json_member(Key, Value), ",", json_members([Pair2 | Pairs]).
|
||||
```
|
||||
That's a logically equivalent and equally clean representation to the lagged argument. However, it leaves
|
||||
choice points, while using the lagged argument doesn't. For more info, watch: https://youtu.be/FZLofckPu4A?t=1737
|
||||
*/
|
||||
json_members([], Key-Value) --> json_member(Key, Value).
|
||||
json_members([NextPair|Pairs], Key-Value) -->
|
||||
json_member(Key, Value),
|
||||
",",
|
||||
json_members(Pairs, NextPair).
|
||||
|
||||
json_member(string(Key), Value) --> json_ws, json_string(Key), json_ws, ":", json_element(Value).
|
||||
|
||||
json_array([]) --> "[", json_ws, "]".
|
||||
json_array([Value|Values]) --> "[", json_elements(Values, Value), "]".
|
||||
|
||||
/* Also using a lagged argument with `json_elements//2` to take advantage of first-argument indexing */
|
||||
json_elements([], Value) --> json_element(Value).
|
||||
json_elements([NextValue|Values], Value) -->
|
||||
json_element(Value),
|
||||
",",
|
||||
json_elements(Values, NextValue).
|
||||
|
||||
json_element(Value) --> json_ws, json_value(Value), json_ws.
|
||||
|
||||
json_string(Chars) --> "\"", json_characters(Chars), "\"".
|
||||
|
||||
json_characters("") --> "".
|
||||
json_characters([Char|Chars]) --> json_character(Char), json_characters(Chars).
|
||||
|
||||
/* Note on variable instantiation checks (`var/1` and `nonvar/1`) used below and in Prolog in general.
|
||||
Instantiation checks should never be used to change the logic of your program. Instead, they are one of
|
||||
many tools to adjust the 'control' or 'search strategy' used by Prolog to execute the logic of your program.
|
||||
For a general overview of the idea, read Bob Kowalski's "Algorithm = Logic + Control":
|
||||
https://www.doc.ic.ac.uk/~rak/papers/algorithm%20=%20logic%20+%20control.pdf
|
||||
For an introduction to search strategies in Prolog, read: https://www.metalevel.at/prolog/sorting#searching
|
||||
It's tempting to use instantiation checks to be more strict while generating and more relaxed while parsing.
|
||||
In fact, the early version of this library aimed to return exactly one result when generating. However, doing that
|
||||
is **wrong** and leads to difficult-to-catch bugs. Instead, adjust the search strategy to return the most ideal
|
||||
and strictest answer FIRST and then return less ideal answers on backtracking.
|
||||
As an example, consider a string containing just the forward slash. The JSON standard recommends the forward slash
|
||||
be escaped with a backslash, but allows it to not be escaped. Attempting to force stricter behavior with
|
||||
instantiation checks can lead to this confusing mess:
|
||||
```
|
||||
phrase(json:json_characters("/"), External).
|
||||
External = "\\/".
|
||||
?- phrase(json:json_characters(Internal), "/").
|
||||
Internal = "/"
|
||||
; false.
|
||||
?- phrase(json:json_characters("/"), "/").
|
||||
false.
|
||||
```
|
||||
To avoid such bugs, never use instantiation checks to reduce the number of right answers, but rather to adjust
|
||||
the *path* used to traverse those answers. */
|
||||
|
||||
escape_char('"', '"').
|
||||
escape_char('\\', '\\').
|
||||
escape_char('/', '/').
|
||||
escape_char('\b', 'b').
|
||||
escape_char('\f', 'f').
|
||||
escape_char('\n', 'n').
|
||||
escape_char('\r', 'r').
|
||||
escape_char('\t', 't').
|
||||
|
||||
json_character(EscapeChar) -->
|
||||
{ escape_char(EscapeChar, PrintChar) },
|
||||
"\\",
|
||||
[PrintChar].
|
||||
json_character(PrintChar) -->
|
||||
[PrintChar],
|
||||
{ dif(PrintChar, '\\'),
|
||||
dif(PrintChar, '"'),
|
||||
char_code(PrintChar, PrintCharCode),
|
||||
PrintCharCode >= 32 }.
|
||||
json_character(EscapeChar) -->
|
||||
"\\u",
|
||||
json_hex(H1),
|
||||
json_hex(H2),
|
||||
json_hex(H3),
|
||||
json_hex(H4),
|
||||
{ ( nonvar(H1) ->
|
||||
EscapeCharCode is H1 * 16^3 + H2 * 16^2 + H3 * 16 + H4,
|
||||
char_code(EscapeChar, EscapeCharCode)
|
||||
; char_code(EscapeChar, EscapeCharCode),
|
||||
H1 is (EscapeCharCode // 16^3) mod 16,
|
||||
H2 is (EscapeCharCode // 16^2) mod 16,
|
||||
H3 is (EscapeCharCode // 16^1) mod 16,
|
||||
H4 is (EscapeCharCode // 16^0) mod 16
|
||||
) }.
|
||||
|
||||
json_hex(Digit) --> json_digit(Digit).
|
||||
json_hex(10) --> "a".
|
||||
json_hex(11) --> "b".
|
||||
json_hex(12) --> "c".
|
||||
json_hex(13) --> "d".
|
||||
json_hex(14) --> "e".
|
||||
json_hex(15) --> "f".
|
||||
json_hex(10) --> "A".
|
||||
json_hex(11) --> "B".
|
||||
json_hex(12) --> "C".
|
||||
json_hex(13) --> "D".
|
||||
json_hex(14) --> "E".
|
||||
json_hex(15) --> "F".
|
||||
|
||||
/* I can't think of any alternatives to using `number_chars/2` when generating, though this leads
|
||||
to under-reporting of correct solutions. At least matching solutions unify when both are instantiated...
|
||||
```
|
||||
?- phrase(json:json_number(N), "123E2").
|
||||
N = 12300
|
||||
; false.
|
||||
?- phrase(json:json_number(12300), Cs).
|
||||
Cs = "12300".
|
||||
?- phrase(json:json_number(12300), "123E2").
|
||||
true
|
||||
; false.
|
||||
```
|
||||
*/
|
||||
parsing, [C] --> [C], { nonvar(C) }.
|
||||
|
||||
json_number(Number) -->
|
||||
( parsing ->
|
||||
json_sign_noplus(Sign),
|
||||
json_integer(Integer),
|
||||
json_fraction(Fraction),
|
||||
json_exponent(Exponent),
|
||||
{ ( Exponent >= 0 ->
|
||||
Base = 10
|
||||
; Base = 10.0
|
||||
),
|
||||
Number is Sign * (Integer + Fraction) * Base ^ Exponent }
|
||||
; { number_chars(Number, NumberChars) },
|
||||
NumberChars
|
||||
).
|
||||
|
||||
json_integer(Digit) --> json_digit(Digit).
|
||||
json_integer(TotalValue) -->
|
||||
json_onenine(FirstDigit),
|
||||
json_digits(RemainingValue, Power),
|
||||
{ TotalValue is FirstDigit * 10 ^ (Power + 1) + RemainingValue }.
|
||||
|
||||
json_digits(Digit, 0) --> json_digit(Digit).
|
||||
json_digits(Value, Power) -->
|
||||
json_digit(FirstDigit),
|
||||
json_digits(RemainingValue, NextPower),
|
||||
{ Power is NextPower + 1,
|
||||
Value is FirstDigit * 10^Power + RemainingValue }.
|
||||
|
||||
json_digit(0) --> "0".
|
||||
json_digit(Digit) --> json_onenine(Digit).
|
||||
|
||||
json_onenine(1) --> "1".
|
||||
json_onenine(2) --> "2".
|
||||
json_onenine(3) --> "3".
|
||||
json_onenine(4) --> "4".
|
||||
json_onenine(5) --> "5".
|
||||
json_onenine(6) --> "6".
|
||||
json_onenine(7) --> "7".
|
||||
json_onenine(8) --> "8".
|
||||
json_onenine(9) --> "9".
|
||||
|
||||
json_fraction(0) --> "".
|
||||
json_fraction(Fraction) -->
|
||||
".",
|
||||
json_digits(Value, Power),
|
||||
{ Fraction is Value / 10.0 ^ (Power + 1) }.
|
||||
|
||||
json_exponent(0) --> "".
|
||||
json_exponent(Exponent) -->
|
||||
json_exponent_signifier,
|
||||
json_sign(Sign),
|
||||
json_digits(Value, _),
|
||||
{ Exponent is Sign * Value }.
|
||||
|
||||
json_exponent_signifier --> "E".
|
||||
json_exponent_signifier --> "e".
|
||||
|
||||
json_sign_noplus(1) --> "".
|
||||
json_sign_noplus(-1) --> "-".
|
||||
|
||||
json_sign(Sign) --> json_sign_noplus(Sign).
|
||||
json_sign(1) --> "+".
|
||||
|
||||
/* Make `json_ws/0` greedy when parsing, lazy when generating */
|
||||
json_ws_empty --> "".
|
||||
json_ws_nonempty --> " ".
|
||||
json_ws_nonempty --> "\n".
|
||||
json_ws_nonempty --> "\r".
|
||||
json_ws_nonempty --> "\t".
|
||||
json_ws_greedy --> json_ws_nonempty, json_ws_greedy.
|
||||
json_ws_greedy --> json_ws_empty.
|
||||
json_ws_lazy --> json_ws_empty.
|
||||
json_ws_lazy --> json_ws_nonempty, json_ws_lazy.
|
||||
json_ws -->
|
||||
( parsing ->
|
||||
json_ws_greedy
|
||||
; json_ws_lazy
|
||||
).
|
||||
155
src/lib/sgml.pl
155
src/lib/sgml.pl
@@ -1,71 +1,56 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Predicates for parsing HTML and XML documents.
|
||||
Written 2020-2022 by Markus Triska (triska@metalevel.at)
|
||||
Written June 2020 by Markus Triska (triska@metalevel.at)
|
||||
Part of Scryer Prolog.
|
||||
|
||||
Currently, two predicates are provided:
|
||||
|
||||
- load_html(+Source, -Es, +Options)
|
||||
- load_xml(+Source, -Es, +Options)
|
||||
|
||||
These predicates parse HTML and XML documents, respectively.
|
||||
|
||||
Source must be a stream, specified as stream(S), or a file,
|
||||
specified as file(Name), where Name is a list of characters, or a
|
||||
list of characters with the document contents.
|
||||
|
||||
Es is unified with the abstract syntax tree of the parsed document,
|
||||
represented as a list of elements where each is of the form:
|
||||
|
||||
* a list of characters, representing text
|
||||
* element(Name, Attrs, Children)
|
||||
- Name is the name of the tag
|
||||
- Attrs is a list of Key=Value pairs:
|
||||
Key is an atom, and Value is a list of characters
|
||||
- Children is a list of elements as specified here.
|
||||
|
||||
Currently, Options are ignored. In the future, more options may be
|
||||
provided to control parsing.
|
||||
|
||||
Example:
|
||||
|
||||
?- load_html("<html><head><title>Hello!</title></head></html>", Es, []).
|
||||
|
||||
Yielding:
|
||||
|
||||
Es = [element(html,[],
|
||||
[element(head,[],
|
||||
[element(title,[],
|
||||
["Hello!"])]),
|
||||
element(body,[],[])])].
|
||||
|
||||
library(xpath) provides convenient reasoning about parsed documents.
|
||||
For example, to fetch the title of the document above, we can use:
|
||||
|
||||
?- load_html("<html><head><title>Hello!</title></head></html>", Es, []),
|
||||
xpath(Es, //title(text), T).
|
||||
|
||||
Yielding T = "Hello!".
|
||||
|
||||
Use http_open/3 from library(http/http_open) to read answers from
|
||||
web servers via streams.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/** Predicates for parsing HTML and XML documents.
|
||||
|
||||
Currently, two predicates are provided:
|
||||
|
||||
- `load_html(+Source, -Es, +Options)`
|
||||
- `load_xml(+Source, -Es, +Options)`
|
||||
|
||||
These predicates parse HTML and XML documents, respectively.
|
||||
|
||||
Source must be one of:
|
||||
|
||||
- a list of characters with the document contents
|
||||
- `stream(S)`, specifying a stream S from which to read the content
|
||||
- `file(Name)`, where Name is a list of characters specifying a file name.
|
||||
|
||||
Es is unified with the abstract syntax tree of the parsed document,
|
||||
represented as a list of elements where each is of the form:
|
||||
|
||||
* a list of characters, representing text
|
||||
|
||||
* `element(Name, Attrs, Children)`
|
||||
|
||||
- `Name`, an atom, is the name of the tag
|
||||
|
||||
- `Attrs` is a list of `Key=Value` pairs:
|
||||
`Key` is an atom, and `Value` is a list of characters
|
||||
|
||||
- `Children` is a list of elements as specified here.
|
||||
|
||||
Currently, Options are ignored. In the future, more options may be
|
||||
provided to control parsing.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
?- load_html("<html><head><title>Hello!</title></head></html>", Es, []).
|
||||
```
|
||||
|
||||
Yielding:
|
||||
|
||||
```
|
||||
Es = [element(html,[],
|
||||
[element(head,[],
|
||||
[element(title,[],
|
||||
["Hello!"])]),
|
||||
element(body,[],[])])].
|
||||
```
|
||||
|
||||
`library(xpath)` provides convenient reasoning about parsed documents.
|
||||
For example, to fetch the title of the document above, we can use:
|
||||
|
||||
```
|
||||
?- load_html("<html><head><title>Hello!</title></head></html>", Es, []),
|
||||
xpath(Es, //title(text), T).
|
||||
```
|
||||
|
||||
Yielding `T = "Hello!"`.
|
||||
|
||||
Use `http_open/3` from `library(http/http_open)` to read answers from
|
||||
web servers via streams.
|
||||
*/
|
||||
|
||||
:- module(sgml, [load_html/3,
|
||||
load_xml/3]).
|
||||
|
||||
@@ -73,37 +58,35 @@ web servers via streams.
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(pio)).
|
||||
:- use_module(library(charsio)).
|
||||
|
||||
load_html(Source, Es, Options) :-
|
||||
must_be_source(Source, load_html/3),
|
||||
must_be(list, Options),
|
||||
load_structure_(Source, Es, Options, html).
|
||||
load_xml(Source, Es, Options) :-
|
||||
must_be_source(Source, load_xml/3),
|
||||
must_be(list, Options),
|
||||
load_structure_(Source, Es, Options, xml).
|
||||
|
||||
must_be_source(Source, Context) :-
|
||||
( var(Source) -> instantiation_error(Context)
|
||||
; is_sgml_source(Source) -> true
|
||||
; domain_error(sgml_source, Source, Context)
|
||||
).
|
||||
|
||||
is_sgml_source(file(Fs)) :- must_be(chars, Fs).
|
||||
is_sgml_source(stream(_)).
|
||||
is_sgml_source([]).
|
||||
is_sgml_source([C|Cs]) :- must_be(chars, [C|Cs]).
|
||||
list([]) --> [].
|
||||
list([L|Ls]) --> [L], list(Ls).
|
||||
|
||||
load_structure_([], [], _, _).
|
||||
load_structure_([C|Cs], [E|Es], Options, What) :-
|
||||
load_(What, [C|Cs], [E|Es], Options).
|
||||
load_structure_(file(Fs), [E|Es], Options, What) :-
|
||||
once(phrase_from_file(seq(Cs), Fs)),
|
||||
load_(What, Cs, [E|Es], Options).
|
||||
load_structure_(stream(Stream), [E|Es], Options, What) :-
|
||||
get_n_chars(Stream, _, Cs),
|
||||
load_(What, Cs, [E|Es], Options).
|
||||
load_structure_([C|Cs], [E], Options, What) :-
|
||||
load_(What, [C|Cs], E, Options).
|
||||
load_structure_(file(Fs), [E], Options, What) :-
|
||||
must_be(list, Options),
|
||||
must_be(list, Fs),
|
||||
atom_chars(File, Fs),
|
||||
once(phrase_from_file(list(Cs), File)),
|
||||
load_(What, Cs, E, Options).
|
||||
load_structure_(stream(Stream), [E], Options, What) :-
|
||||
must_be(list, Options),
|
||||
read_to_end(Stream, Cs),
|
||||
load_(What, Cs, E, Options).
|
||||
|
||||
load_(html, Cs, E, Options) :- '$load_html'(Cs, E, Options).
|
||||
load_(xml, Cs, E, Options) :- '$load_xml'(Cs, E, Options).
|
||||
|
||||
read_to_end(Stream, Cs) :-
|
||||
'$get_n_chars'(Stream, 4096, Cs0),
|
||||
( Cs0 = [] -> Cs = []
|
||||
; partial_string(Cs0, Cs, Rest),
|
||||
read_to_end(Stream, Rest)
|
||||
).
|
||||
|
||||
130
src/lib/si.pl
130
src/lib/si.pl
@@ -1,47 +1,33 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
/** Safe type tests.
|
||||
Safe type tests
|
||||
===============
|
||||
|
||||
"si" stands for "sufficiently instantiated". It can also be read as
|
||||
"safe inference", so possibly also other predicates are candidates
|
||||
for this library.
|
||||
"si" stands for "sufficiently instantiated".
|
||||
|
||||
A safe type test:
|
||||
These predicates:
|
||||
|
||||
- throws an *instantiation error* if the argument is
|
||||
- throw instantiation errors if the argument is
|
||||
not sufficiently instantiated to make a sound decision
|
||||
- *succeeds* if the argument is of the specified type
|
||||
- *fails* otherwise.
|
||||
- succeed if the argument is of the specified type
|
||||
- fail otherwise.
|
||||
|
||||
For instance, `atom_si(A)` yields an *instantiation error* if `A` is a
|
||||
For instance, atom_si(A) yields an *instantiation error* if A is a
|
||||
variable. This is logically sound, since in that case the argument
|
||||
is not sufficiently instantiated to make any decision.
|
||||
|
||||
The definitions are taken from [Safer type tests in Prolog](https://stackoverflow.com/questions/27306453/safer-type-tests-in-prolog).
|
||||
The definitions are taken from:
|
||||
|
||||
Examples:
|
||||
https://stackoverflow.com/questions/27306453/safer-type-tests-in-prolog
|
||||
|
||||
```
|
||||
?- chars_si(Cs).
|
||||
error(instantiation_error,list_si/1).
|
||||
?- chars_si([h|Cs]).
|
||||
error(instantiation_error,list_si/1).
|
||||
?- chars_si("hello").
|
||||
true.
|
||||
?- chars_si(hello).
|
||||
false.
|
||||
```
|
||||
*/
|
||||
"si" can also be read as "safe inference", so possibly also other
|
||||
predicates are candidates for this library.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
:- module(si, [atom_si/1,
|
||||
integer_si/1,
|
||||
atomic_si/1,
|
||||
list_si/1,
|
||||
character_si/1,
|
||||
term_si/1,
|
||||
chars_si/1,
|
||||
dif_si/2,
|
||||
not_si/1,
|
||||
when_si/2]).
|
||||
list_si/1]).
|
||||
|
||||
:- use_module(library(lists)).
|
||||
|
||||
@@ -56,86 +42,6 @@ integer_si(I) :-
|
||||
atomic_si(AC) :-
|
||||
functor(AC,_,0).
|
||||
|
||||
% list_si(L) :-
|
||||
% \+ \+ length(L, _),
|
||||
% sort(L, _).
|
||||
|
||||
list_si(L0) :-
|
||||
'$skip_max_list'(_,_, L0,L),
|
||||
( nonvar(L) -> L = []
|
||||
; throw(error(instantiation_error, list_si/1))
|
||||
).
|
||||
|
||||
character_si(Ch) :-
|
||||
functor(Ch,Ch,0),
|
||||
atom(Ch),
|
||||
atom_length(Ch,1).
|
||||
|
||||
term_si(Term) :-
|
||||
( ground(Term) -> acyclic_term(Term)
|
||||
; throw(error(instantiation_error, term_si/1))
|
||||
).
|
||||
|
||||
chars_si(Chs0) :-
|
||||
'$skip_max_list'(_,_, Chs0,Chs),
|
||||
( nonvar(Chs) -> Chs == [] ; true ), % fails for infinite lists too
|
||||
failnochars(Chs0, Uninstantiated),
|
||||
( nonvar(Uninstantiated)
|
||||
-> throw(error(instantiation_error, chars_si/1))
|
||||
; true
|
||||
).
|
||||
|
||||
failnochars(Chs0, U) :-
|
||||
( var(Chs0) -> U = true
|
||||
; Chs0 == [] -> true
|
||||
; Chs0 = [Ch|Chs1],
|
||||
( nonvar(Ch) -> atom(Ch), atom_length(Ch,1)
|
||||
; U = true
|
||||
),
|
||||
failnochars(Chs1, U)
|
||||
).
|
||||
|
||||
dif_si(X, Y) :-
|
||||
X \== Y,
|
||||
( X \= Y -> true
|
||||
; throw(error(instantiation_error,dif_si/2))
|
||||
).
|
||||
|
||||
%% not_si(+Goal).
|
||||
%
|
||||
% True if Goal is not provable. Instantiation error if Goal is not
|
||||
% ground.
|
||||
|
||||
:- meta_predicate(not_si(0)).
|
||||
|
||||
not_si(Goal) :-
|
||||
term_si(Goal),
|
||||
\+ Goal.
|
||||
|
||||
:- meta_predicate(when_si(+, 0)).
|
||||
|
||||
%% when_si(Condition, Goal).
|
||||
%
|
||||
% Executes Goal when Condition becomes true. Throws an instantiation error if
|
||||
% it can't decide.
|
||||
when_si(Condition, Goal) :-
|
||||
% Taken from https://stackoverflow.com/a/40449516
|
||||
( when_condition_si(Condition) ->
|
||||
( Condition ->
|
||||
Goal
|
||||
; throw(error(instantiation_error,when_si/2))
|
||||
)
|
||||
; throw(error(domain_error(when_condition_si, Condition),_))
|
||||
).
|
||||
|
||||
when_condition_si(Cond) :-
|
||||
var(Cond), !, throw(error(instantiation_error,when_condition_si/2)).
|
||||
when_condition_si(ground(_)).
|
||||
when_condition_si(nonvar(_)).
|
||||
when_condition_si((A, B)) :-
|
||||
when_condition_si(A),
|
||||
when_condition_si(B).
|
||||
when_condition_si((A ; B)) :-
|
||||
when_condition_si(A),
|
||||
when_condition_si(B).
|
||||
|
||||
list_si(L) :-
|
||||
\+ \+ length(L, _),
|
||||
sort(L, _).
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user