1 Commits

409 changed files with 32425 additions and 103769 deletions

0
.dockerignore Normal file → Executable file
View File

1
.envrc
View File

@@ -1 +0,0 @@
use flake

View File

@@ -1,2 +0,0 @@
# Resolved all lints and formatted the codebase
9444e62df9820d6bfd96dbd8849e177bc5cecc2e

4
.gitattributes vendored
View File

@@ -1,4 +0,0 @@
*.png binary
*.pl text eol=lf
*.rs text eol=lf diff=rust
*.md text eol=lf diff=markdown

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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
View File

@@ -1,7 +1,3 @@
src/static_atoms.rs
target/
.direnv/
__pycache__
*.pyc

31
.travis.yml Normal file
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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
View 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"]

View File

@@ -1,79 +0,0 @@
# Scryer Prolog
```
?- append("Hello, ", X, "Hello, Scryer Prolog!").
X = "Scryer Prolog!".
```
![scryer](scryer.png){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
View File

@@ -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&nbsp;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)
![Scryer Logo: Cryer](logo/scryer.png)
## 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&nbsp;`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 [![Crates.io Version](https://img.shields.io/crates/v/scryer-prolog)](https://crates.io/crates/scryer-prolog) ![Crates.io MSRV](https://img.shields.io/crates/msrv/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&nbsp;`.` to abort the search and return to the
toplevel&nbsp;prompt. Press&nbsp;`f` to see up to the next multiple of
5 answers, and `a` to see all answers. Press&nbsp;`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&nbsp;`TAB` yields `declarative_world`. Press&nbsp;`TAB` repeatedly
to cycle through alternative completions.
Press `SPACE` to show further answers, if any exist. Press `RETURN` or
&nbsp;`.` to abort the search and return to the toplevel&nbsp;prompt.
Press&nbsp;`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&nbsp;`argv/1`, which yields the list
of arguments represented as strings.
Prolog files can also be turned into *shell&nbsp;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&nbsp;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&nbsp;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&nbsp;encoding by the engine.
Without this innovation, storing a list of characters in memory would
use one WAM memory&nbsp;cell per character, one cell per list
constructor, and one cell for each tail that occurs in the list. Since
one cell takes 8&nbsp;bytes in the WAM as implemented by
Scryer&nbsp;Prolog, the packed representation yields an up&nbsp;to
**24-fold&nbsp;reduction** of memory usage, and corresponding
reduction of memory&nbsp;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&nbsp;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&nbsp;bytes internally, appears to Prolog programs as a list of
characters.
Internally, strings are represented very compactly in packed
UTF-8&nbsp;encoding. A naive representation of strings as lists of
characters would use one memory&nbsp;cell per character, one
memory&nbsp;cell per list constructor, and one memory&nbsp;cell for
each tail that occurs in the list. Since one memory&nbsp;cell takes
8&nbsp;bytes on 64-bit machines, the packed representation used by
Scryer&nbsp;Prolog yields an up&nbsp;to **24-fold&nbsp;reduction** of
memory usage, and corresponding reduction of memory&nbsp;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&nbsp;=&nbsp;[a,b,c|Ls]</tt> yields
the exact same internal representation, and has the advantage that
only the standard predicate&nbsp;`(=)/2` is used.
The efficient internal representation of strings and partial strings
was first proposed and explained by Ulrich Neumerkel in
issues&nbsp;[#24](https://github.com/mthom/scryer-prolog/issues/24)
and&nbsp;[#95](https://github.com/mthom/scryer-prolog/issues/95), and
Scryer&nbsp;Prolog is the first Prolog&nbsp;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&nbsp;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&nbsp;check* is omitted by default
in Scryer&nbsp;Prolog and many other Prolog systems.
In Scryer Prolog, unifications which succeed only if the
*occurs&nbsp;check* is omitted yield *cyclic&nbsp;terms*, also called
*rational&nbsp;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&nbsp;check* enabled, or let Prolog throw an error if enabling
the *occurs&nbsp;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&nbsp;check*. This is the default.
- `true`
Perform all unifications with the *occurs&nbsp;check* enabled.
- `error`
Yield an error if a unification is performed that the
*occurs&nbsp;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&nbsp;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&nbsp;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&nbsp;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&nbsp;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&nbsp;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&nbsp;documents
as Prolog&nbsp;terms for convenient and efficient reasoning. Use
@@ -731,28 +438,19 @@ The modules that ship with Scryer&nbsp;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&nbsp;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&nbsp;variables.
* [`iso_ext`](src/lib/iso_ext.pl)
Conforming extensions to and candidates for inclusion in the Prolog
ISO&nbsp;standard, such as `setup_call_cleanup/3`, `call_nth/2` and
ISO&nbsp;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&nbsp;Prolog are also called
public key signatures and signature verification with&nbsp;Ed25519,
ECDH key&nbsp;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&nbsp;Emacs](https://www.gnu.org/software/emacs/) with the
[Prolog&nbsp;mode](https://bruda.ca/emacs/prolog_mode_for_emacs)
maintained by Stefan Bruda.
Use [ediprolog](https://www.metalevel.at/ediprolog/) to consult
Prolog&nbsp;code and evaluate Prolog queries in arbitrary
Emacs&nbsp;buffers.
Emacs definitions that show Prolog terms as trees are available
in&nbsp;[tools](tools).
To *debug* Prolog code, we recommend the predicates from
[**`library(debug)`**](src/lib/debug.pl), most notably:
- `(*)/1` to *"generalize&nbsp;away"* a Prolog goal. Use it to debug
unexpected failures by generalizing your definitions until they
succeed. Simply place&nbsp;`*` 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&nbsp;`$` 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&nbsp;agencies
that are subject to strict regulations pertaining to interoperability,
standards&nbsp;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&nbsp;grants in the Austrian public&nbsp;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&nbsp;design, described in [*An Executable Specification of
Oncology Dose-Escalation Protocols with&nbsp;Prolog*](https://arxiv.org/abs/2402.08334)
and culminating in&nbsp;[**DEDUCTION**](https://codeberg.org/dcnorris/DEDUCTION)
- the core tax engine for VAT reasoning and compliance of the
Belgian&nbsp;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&nbsp;programs.
## Scryer Prolog Meetups
Scryer Prolog Meetups are an excellent opportunity to present and get
to know the latest developments in Scryer&nbsp;Prolog and its
applications, to exchange ideas about current&nbsp;plans and
future&nbsp;directions, and to discuss projects and visions
in&nbsp;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.&nbsp;24th and 25th&nbsp;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&nbsp;[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)!

View File

@@ -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"]

View File

@@ -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

View File

@@ -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).

View File

@@ -1,2 +0,0 @@
:- use_module(library(between)).
run_numlist(Upper, Head) :- numlist(1, Upper, L), L = [Head|_].

View File

@@ -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() {}

View File

@@ -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() {}

View File

@@ -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
View 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

View File

@@ -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();
}

View File

@@ -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 } },)*
};
}
}

View File

@@ -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"},
]

View File

@@ -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
View File

@@ -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
}

View File

@@ -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

View File

@@ -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.
![Heraclio Fournier Spanish cards](Spanish_deck_Fournier.jpg)
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
![Playing a game of Brisca](brisca-interactive.png)

View File

@@ -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>

View File

@@ -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(),
}
}
}

View File

@@ -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

View File

@@ -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 {}

View File

@@ -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
View 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)
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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)],
[],

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

323
src/fixtures.rs Normal file
View 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(&reg);
}
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);
}
}
_ => {}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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!());
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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>,
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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))
}
}

View File

@@ -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))
})
}

View File

@@ -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).

View File

@@ -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, _).

View File

@@ -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).

View File

@@ -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).

File diff suppressed because it is too large Load Diff

View File

@@ -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)
)
).

View File

@@ -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).

File diff suppressed because it is too large Load Diff

View File

@@ -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'.

File diff suppressed because it is too large Load Diff

View File

@@ -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 = [] }).

View File

@@ -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).

View File

@@ -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.
*(_).

View File

@@ -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))
).

View File

@@ -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(_)) }.

View File

@@ -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]))).

View File

@@ -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).

View File

@@ -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) ->

View File

@@ -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]).

View File

@@ -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)].

View File

@@ -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).

View File

@@ -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)).

View File

@@ -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")).

View File

@@ -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).

View File

@@ -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),_)).

View File

@@ -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).

View File

@@ -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)].

View File

@@ -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).

View File

@@ -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]).

View File

@@ -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, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).

View File

@@ -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.

View File

@@ -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 = []
).

View File

@@ -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) :-

View File

@@ -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)
).

View File

@@ -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).

View File

@@ -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) ->

View File

@@ -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) ).

View File

@@ -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.

View File

@@ -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
).

View File

@@ -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)
).

View File

@@ -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