diff --git a/README.md b/README.md index bf64c47..86c5130 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,38 @@ This project is inspired by the classic "hello-world" repo idea, but takes thing --- +## Implemented Languages + +Lambda Core currently has implementations in: + +- [ArkScript](languages/a/arkscript), [Bash](languages/b/bash), and + [bruijn](languages/b/bruijn) +- [C](languages/c/c), [C++](languages/c/c++), and + [Clojure](languages/c/clojure) +- [Elixir](languages/e/elixir), [FatScript](languages/f/fatscript), and + [F#](languages/f/fsharp) +- [Go](languages/g/go), [Haskell](languages/h/haskell), + [Java](languages/j/java), and [JavaScript](languages/j/javascript) +- [Kotlin](languages/k/kotlin), [Language 84](languages/l/language84), and + [Lua](languages/l/lua) +- [OCaml](languages/o/ocaml), [Perl](languages/p/perl), + [PHP](languages/p/php), [PowerShell](languages/p/powershell), and + [Python](languages/p/python) +- [Racket](languages/r/racket), [Ruby](languages/r/ruby), + [Rust](languages/r/rust), [SQL](languages/s/sql), and + [TypeScript](languages/t/typescript) + +The August 2026 expansion added Bash, PHP, PowerShell, Rust, and SQL by +filtering the [2025 Stack Overflow language usage results](https://survey.stackoverflow.co/2025/technology) +against the implementations already present. GitHub's +[2025 Octoverse language ranking](https://github.blog/news-insights/octoverse/what-the-fastest-growing-tools-reveal-about-how-software-is-being-built/) +and the [August 2026 TIOBE index](https://www.tiobe.com/tiobe-index/) were used +as cross-checks. + +Still wanted: Swift, Scala, Zig, Dart, Gleam, Prolog, Erlang, APL, and Idris. + +--- + ## What is the "Lambda Core"? The *Lambda Core* refers to: diff --git a/ROADMAP.md b/ROADMAP.md index d5715ea..9304450 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -47,15 +47,41 @@ Folders without a `test.sh` are skipped until backfilled. ## Milestone 2 — fill out the missing major languages -One language per step, each landing as its own PR with implementation, README, -test.sh, and expected output, CI-green before merge. Order roughly by reach: +Most additions land one language at a time with an implementation, README, +test.sh, and observed expected output. The five-language batch below is one +explicitly requested single-pass phase. + +### Phase 1 — 2025 popularity expansion + +Selection method: filter the Stack Overflow 2025 "have used" language ranking +against the repository's existing implementations, excluding HTML/CSS because +it is markup. The five highest absent entries are SQL (58.6%), Bash/Shell +(48.7%), PowerShell (23.2%), PHP (18.9%), and Rust (14.8%). GitHub's 2025 +Octoverse independently places PHP and Shell in its top ten, while the August +2026 TIOBE index ranks SQL eighth, Rust tenth, and PHP thirteenth. + +- [x] **Step 1 — verify the selection and starting state.** None of the five + language folders existed before this phase; choosing them creates new + implementations rather than fixing existing ones. +- [x] **Step 2 — implement all five cores.** Add Church booleans, Church + numerals, native output adapters, and self-checking examples for Bash, + PHP, PowerShell, Rust, and SQL. SQL may use an explicitly documented + relational emulation because SQLite has no first-class functions. +- [x] **Step 3 — add verification artifacts.** Every folder gets README.md, + test.sh, and expected-output.txt captured from an observed run. +- [x] **Step 4 — integrate and verify.** Verify the five toolchains in the + current GitHub runner inventory (all are preinstalled), list the new + implementations in the README, run focused checks, and run the repository + harness. All five focused baseline checks pass; the full local harness + reports 11 passed, 0 failed, 10 skipped for unavailable local toolchains, + and the pre-existing Java baseline still pending. + +### Later major-language phases -- [ ] Rust - [ ] C# - [ ] Swift - [ ] Scala - [ ] Zig -- [ ] PHP - [ ] Dart - [ ] Gleam (reopens the slot from closed PR #28) diff --git a/languages/b/bash/README.md b/languages/b/bash/README.md new file mode 100644 index 0000000..5ec1903 --- /dev/null +++ b/languages/b/bash/README.md @@ -0,0 +1,38 @@ +# Bash Lambda Core + +Bash has shell functions, but it does not have first-class functions or +lexical closures. This implementation therefore uses a small closure runtime: +each lambda term has an opaque ID, and Bash associative arrays attach a +handler plus captured terms to that ID. The handlers directly implement +`TRUE`, `FALSE`, `NOT`, `AND`, `OR`, `ZERO`, `SUCC`, `PRED`, and `ONE`. + +## Requirements + +- Bash 4 or newer (for associative arrays) +- No external commands or packages at runtime + +Bash is part of the base operating system on GitHub's `ubuntu-latest` runner, +so this folder does not require a workflow installation step. + +## Run and verify + +From this directory, run: + +```sh +sh test.sh +``` + +The repository-level `run-tests.sh` runs the same script and compares its +standard output with `expected-output.txt`. + +## How the representation works + +The term registry is the only emulation layer. Applying a term dispatches its +handler in the current shell process, and nested handlers allocate terms that +capture their arguments. The Church definitions never store native boolean or +numeral values. Output observers use two marker terms for booleans and count +function applications for numerals. + +The GNU Bash manual documents +[shell functions](https://www.gnu.org/software/bash/manual/bash.html#Shell-Functions) +and [associative arrays](https://www.gnu.org/software/bash/manual/html_node/Arrays.html). diff --git a/languages/b/bash/expected-output.txt b/languages/b/bash/expected-output.txt new file mode 100644 index 0000000..b109aba --- /dev/null +++ b/languages/b/bash/expected-output.txt @@ -0,0 +1,22 @@ +CHURCH BOOLEANS +TRUE: true +FALSE: false +NOT TRUE: false +NOT FALSE: true +FALSE AND FALSE: false +FALSE AND TRUE: false +TRUE AND FALSE: false +TRUE AND TRUE: true +FALSE OR FALSE: false +FALSE OR TRUE: true +TRUE OR FALSE: true +TRUE OR TRUE: true +CHURCH NUMERALS +ZERO: 0 +ONE: 1 +SUCC ONE: 2 +SUCC (SUCC ONE): 3 +PRED ZERO: 0 +PRED ONE: 0 +PRED (SUCC ONE): 1 +PRED (SUCC (SUCC ONE)): 2 diff --git a/languages/b/bash/lambda-core.sh b/languages/b/bash/lambda-core.sh new file mode 100644 index 0000000..09d7a8a --- /dev/null +++ b/languages/b/bash/lambda-core.sh @@ -0,0 +1,352 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Bash functions are not first-class values and do not form lexical closures. +# This tiny runtime represents a lambda term with an opaque ID. Associative +# arrays attach a handler and up to two captured terms to each ID, letting the +# handlers below implement curried application without eval or generated code. +declare -A term_handler=() +declare -A term_a=() +declare -A term_b=() + +next_term=0 +result= + +new_term() { + local handler=$1 + local captured_a=${2-} + local captured_b=${3-} + + next_term=$((next_term + 1)) + result="term_$next_term" + term_handler["$result"]=$handler + term_a["$result"]=$captured_a + term_b["$result"]=$captured_b +} + +apply() { + local term=$1 + local argument=$2 + local handler=${term_handler["$term"]-} + + if [[ -z $handler ]]; then + printf 'cannot apply unknown term: %s\n' "$term" >&2 + return 1 + fi + + "$handler" "$term" "$argument" +} + +apply_two() { + local term=$1 + local first=$2 + local second=$3 + + apply "$term" "$first" + local partial=$result + apply "$partial" "$second" +} + +# TRUE = λx.λy.x +make_true() { + new_term true_first +} + +true_first() { + local selected=$2 + new_term constant_second "$selected" +} + +constant_second() { + local self=$1 + result=${term_a["$self"]} +} + +# FALSE = λx.λy.y +make_false() { + new_term false_first +} + +false_first() { + new_term identity +} + +identity() { + result=$2 +} + +# NOT = λb.b FALSE TRUE +make_not() { + local false_term=$1 + local true_term=$2 + new_term not_boolean "$false_term" "$true_term" +} + +not_boolean() { + local self=$1 + local boolean=$2 + + apply_two "$boolean" "${term_a["$self"]}" "${term_b["$self"]}" +} + +# AND = λp.λq.p q FALSE +make_and() { + local false_term=$1 + new_term and_left "$false_term" +} + +and_left() { + local self=$1 + local left=$2 + new_term and_right "$left" "${term_a["$self"]}" +} + +and_right() { + local self=$1 + local right=$2 + apply_two "${term_a["$self"]}" "$right" "${term_b["$self"]}" +} + +# OR = λp.λq.p TRUE q +make_or() { + local true_term=$1 + new_term or_left "$true_term" +} + +or_left() { + local self=$1 + local left=$2 + new_term or_right "$left" "${term_a["$self"]}" +} + +or_right() { + local self=$1 + local right=$2 + apply_two "${term_a["$self"]}" "${term_b["$self"]}" "$right" +} + +# ZERO = λf.λx.x +make_zero() { + new_term zero_function +} + +zero_function() { + new_term identity +} + +# SUCC = λn.λf.λx.f (n f x) +make_successor() { + new_term successor_numeral +} + +successor_numeral() { + local numeral=$2 + new_term successor_function "$numeral" +} + +successor_function() { + local self=$1 + local function=$2 + new_term successor_value "${term_a["$self"]}" "$function" +} + +successor_value() { + local self=$1 + local value=$2 + local numeral=${term_a["$self"]} + local function=${term_b["$self"]} + + apply_two "$numeral" "$function" "$value" + local inner=$result + apply "$function" "$inner" +} + +# PRED = λn.λf.λx.n (λg.λh.h (g f)) (λu.x) (λa.a) +make_predecessor() { + new_term predecessor_numeral +} + +predecessor_numeral() { + local numeral=$2 + new_term predecessor_function "$numeral" +} + +predecessor_function() { + local self=$1 + local function=$2 + new_term predecessor_value "${term_a["$self"]}" "$function" +} + +predecessor_value() { + local self=$1 + local value=$2 + local numeral=${term_a["$self"]} + local function=${term_b["$self"]} + + new_term predecessor_step_g "$function" + local step=$result + new_term constant_second "$value" + local seed=$result + new_term identity + local identity_term=$result + + apply "$numeral" "$step" + local at_step=$result + apply "$at_step" "$seed" + local at_seed=$result + apply "$at_seed" "$identity_term" +} + +predecessor_step_g() { + local self=$1 + local g=$2 + new_term predecessor_step_h "$g" "${term_a["$self"]}" +} + +predecessor_step_h() { + local self=$1 + local h=$2 + + apply "${term_a["$self"]}" "${term_b["$self"]}" + local g_at_function=$result + apply "$h" "$g_at_function" +} + +# Observers use markers and a counter; the core terms above do not use native +# booleans or integer numeral values. +marker() { + printf 'marker terms are not callable\n' >&2 + return 1 +} + +church_to_bool() { + local boolean=$1 + + new_term marker + local true_marker=$result + new_term marker + local false_marker=$result + + apply_two "$boolean" "$true_marker" "$false_marker" + local selected=$result + + if [[ $selected == "$true_marker" ]]; then + result=true + elif [[ $selected == "$false_marker" ]]; then + result=false + else + printf 'a Church boolean did not select either argument\n' >&2 + return 1 + fi +} + +application_count=0 + +increment_counter() { + application_count=$((application_count + 1)) + result=$2 +} + +church_to_int() { + local numeral=$1 + + application_count=0 + new_term increment_counter + local increment=$result + new_term marker + local seed=$result + + apply_two "$numeral" "$increment" "$seed" + result=$application_count +} + +check_boolean() { + local label=$1 + local boolean=$2 + local expected=$3 + + church_to_bool "$boolean" + local actual=$result + if [[ $actual != "$expected" ]]; then + printf '%s: expected %s, got %s\n' "$label" "$expected" "$actual" >&2 + return 1 + fi + + printf '%s: %s\n' "$label" "$actual" +} + +check_numeral() { + local label=$1 + local numeral=$2 + local expected=$3 + + church_to_int "$numeral" + local actual=$result + if [[ $actual != "$expected" ]]; then + printf '%s: expected %s, got %s\n' "$label" "$expected" "$actual" >&2 + return 1 + fi + + printf '%s: %s\n' "$label" "$actual" +} + +make_true +church_true=$result +make_false +church_false=$result +make_not "$church_false" "$church_true" +church_not=$result +make_and "$church_false" +church_and=$result +make_or "$church_true" +church_or=$result +make_zero +zero=$result +make_successor +successor=$result +make_predecessor +predecessor=$result +apply "$successor" "$zero" +one=$result + +printf 'CHURCH BOOLEANS\n' +check_boolean 'TRUE' "$church_true" true +check_boolean 'FALSE' "$church_false" false +apply "$church_not" "$church_true" +check_boolean 'NOT TRUE' "$result" false +apply "$church_not" "$church_false" +check_boolean 'NOT FALSE' "$result" true +apply_two "$church_and" "$church_false" "$church_false" +check_boolean 'FALSE AND FALSE' "$result" false +apply_two "$church_and" "$church_false" "$church_true" +check_boolean 'FALSE AND TRUE' "$result" false +apply_two "$church_and" "$church_true" "$church_false" +check_boolean 'TRUE AND FALSE' "$result" false +apply_two "$church_and" "$church_true" "$church_true" +check_boolean 'TRUE AND TRUE' "$result" true +apply_two "$church_or" "$church_false" "$church_false" +check_boolean 'FALSE OR FALSE' "$result" false +apply_two "$church_or" "$church_false" "$church_true" +check_boolean 'FALSE OR TRUE' "$result" true +apply_two "$church_or" "$church_true" "$church_false" +check_boolean 'TRUE OR FALSE' "$result" true +apply_two "$church_or" "$church_true" "$church_true" +check_boolean 'TRUE OR TRUE' "$result" true + +printf 'CHURCH NUMERALS\n' +check_numeral 'ZERO' "$zero" 0 +check_numeral 'ONE' "$one" 1 +apply "$successor" "$one" +two=$result +check_numeral 'SUCC ONE' "$two" 2 +apply "$successor" "$two" +three=$result +check_numeral 'SUCC (SUCC ONE)' "$three" 3 +apply "$predecessor" "$zero" +check_numeral 'PRED ZERO' "$result" 0 +apply "$predecessor" "$one" +check_numeral 'PRED ONE' "$result" 0 +apply "$predecessor" "$two" +check_numeral 'PRED (SUCC ONE)' "$result" 1 +apply "$predecessor" "$three" +check_numeral 'PRED (SUCC (SUCC ONE))' "$result" 2 diff --git a/languages/b/bash/test.sh b/languages/b/bash/test.sh new file mode 100644 index 0000000..a0eb6f3 --- /dev/null +++ b/languages/b/bash/test.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -eu + +command -v bash >/dev/null 2>&1 || exit 42 +bash -c '(( BASH_VERSINFO[0] >= 4 ))' >/dev/null 2>&1 || exit 42 + +directory=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +exec bash "$directory/lambda-core.sh" diff --git a/languages/p/php/README.md b/languages/p/php/README.md new file mode 100644 index 0000000..d1427d0 --- /dev/null +++ b/languages/p/php/README.md @@ -0,0 +1,38 @@ +# PHP Lambda Core + +This implementation uses PHP closures to express `TRUE`, `FALSE`, `NOT`, +`AND`, `OR`, `ZERO`, `SUCC`, `PRED`, and `ONE` directly as curried callable +values. Native booleans and integers appear only in the output observers and +self-checks. + +## Requirements + +- PHP 8.0 or newer +- No Composer packages or other external dependencies + +PHP is included in GitHub's current +[`ubuntu-latest` runner image](https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md#php-tools), +so this folder does not require a workflow installation step. + +## Run and verify + +From this directory, run: + +```sh +sh test.sh +``` + +The repository-level `run-tests.sh` runs the same script and compares its +standard output with `expected-output.txt`. + +## How the representation works + +PHP arrow functions are closure objects and automatically capture values from +their enclosing scope. That permits a close transcription of the curried +lambda terms; for example, `TRUE` is `fn($x) => fn($_y) => $x`, and `SUCC` +applies a supplied function once more than its input numeral does. + +Only `churchToBool` and `churchToInt` cross into native PHP values. See the +official PHP documentation for +[anonymous functions](https://www.php.net/manual/en/functions.anonymous.php) +and [arrow functions](https://www.php.net/manual/en/functions.arrow.php). diff --git a/languages/p/php/expected-output.txt b/languages/p/php/expected-output.txt new file mode 100644 index 0000000..b109aba --- /dev/null +++ b/languages/p/php/expected-output.txt @@ -0,0 +1,22 @@ +CHURCH BOOLEANS +TRUE: true +FALSE: false +NOT TRUE: false +NOT FALSE: true +FALSE AND FALSE: false +FALSE AND TRUE: false +TRUE AND FALSE: false +TRUE AND TRUE: true +FALSE OR FALSE: false +FALSE OR TRUE: true +TRUE OR FALSE: true +TRUE OR TRUE: true +CHURCH NUMERALS +ZERO: 0 +ONE: 1 +SUCC ONE: 2 +SUCC (SUCC ONE): 3 +PRED ZERO: 0 +PRED ONE: 0 +PRED (SUCC ONE): 1 +PRED (SUCC (SUCC ONE)): 2 diff --git a/languages/p/php/lambda-core.php b/languages/p/php/lambda-core.php new file mode 100644 index 0000000..b96cd1c --- /dev/null +++ b/languages/p/php/lambda-core.php @@ -0,0 +1,100 @@ + fn(mixed $_y): mixed => $x; + +// FALSE = λx.λy.y +$churchFalse = fn(mixed $_x): Closure => fn(mixed $y): mixed => $y; + +// NOT = λb.b FALSE TRUE +$churchNot = fn(Closure $boolean): mixed => + $boolean($churchFalse)($churchTrue); + +// AND = λp.λq.p q FALSE +$churchAnd = fn(Closure $left): Closure => + fn(Closure $right): mixed => $left($right)($churchFalse); + +// OR = λp.λq.p TRUE q +$churchOr = fn(Closure $left): Closure => + fn(Closure $right): mixed => $left($churchTrue)($right); + +// ZERO = λf.λx.x +$zero = fn(Closure $_function): Closure => fn(mixed $value): mixed => $value; + +// SUCC = λn.λf.λx.f (n f x) +$successor = fn(Closure $numeral): Closure => + fn(Closure $function): Closure => + fn(mixed $value): mixed => + $function($numeral($function)($value)); + +// PRED = λn.λf.λx.n (λg.λh.h (g f)) (λu.x) (λa.a) +$predecessor = fn(Closure $numeral): Closure => + fn(Closure $function): Closure => + fn(mixed $value): mixed => + $numeral( + fn(Closure $g): Closure => + fn(Closure $h): mixed => $h($g($function)) + )(fn(mixed $_u): mixed => $value)(fn(mixed $a): mixed => $a); + +$one = $successor($zero); + +// These observers are the only code that converts terms to native values. +function churchToBool(Closure $boolean): bool +{ + return $boolean(true)(false); +} + +function churchToInt(Closure $numeral): int +{ + return $numeral(fn(int $value): int => $value + 1)(0); +} + +function checkBoolean(string $label, Closure $boolean, bool $expected): void +{ + $actual = churchToBool($boolean); + if ($actual !== $expected) { + throw new RuntimeException( + sprintf('%s: expected %s, got %s', $label, $expected ? 'true' : 'false', $actual ? 'true' : 'false') + ); + } + + printf("%s: %s\n", $label, $actual ? 'true' : 'false'); +} + +function checkNumeral(string $label, Closure $numeral, int $expected): void +{ + $actual = churchToInt($numeral); + if ($actual !== $expected) { + throw new RuntimeException( + sprintf('%s: expected %d, got %d', $label, $expected, $actual) + ); + } + + printf("%s: %d\n", $label, $actual); +} + +echo "CHURCH BOOLEANS\n"; +checkBoolean('TRUE', $churchTrue, true); +checkBoolean('FALSE', $churchFalse, false); +checkBoolean('NOT TRUE', $churchNot($churchTrue), false); +checkBoolean('NOT FALSE', $churchNot($churchFalse), true); +checkBoolean('FALSE AND FALSE', $churchAnd($churchFalse)($churchFalse), false); +checkBoolean('FALSE AND TRUE', $churchAnd($churchFalse)($churchTrue), false); +checkBoolean('TRUE AND FALSE', $churchAnd($churchTrue)($churchFalse), false); +checkBoolean('TRUE AND TRUE', $churchAnd($churchTrue)($churchTrue), true); +checkBoolean('FALSE OR FALSE', $churchOr($churchFalse)($churchFalse), false); +checkBoolean('FALSE OR TRUE', $churchOr($churchFalse)($churchTrue), true); +checkBoolean('TRUE OR FALSE', $churchOr($churchTrue)($churchFalse), true); +checkBoolean('TRUE OR TRUE', $churchOr($churchTrue)($churchTrue), true); + +echo "CHURCH NUMERALS\n"; +checkNumeral('ZERO', $zero, 0); +checkNumeral('ONE', $one, 1); +checkNumeral('SUCC ONE', $successor($one), 2); +checkNumeral('SUCC (SUCC ONE)', $successor($successor($one)), 3); +checkNumeral('PRED ZERO', $predecessor($zero), 0); +checkNumeral('PRED ONE', $predecessor($one), 0); +checkNumeral('PRED (SUCC ONE)', $predecessor($successor($one)), 1); +checkNumeral('PRED (SUCC (SUCC ONE))', $predecessor($successor($successor($one))), 2); diff --git a/languages/p/php/test.sh b/languages/p/php/test.sh new file mode 100644 index 0000000..0c9bb37 --- /dev/null +++ b/languages/p/php/test.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu + +command -v php >/dev/null 2>&1 || exit 42 + +directory=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +exec php "$directory/lambda-core.php" diff --git a/languages/p/powershell/README.md b/languages/p/powershell/README.md new file mode 100644 index 0000000..d083421 --- /dev/null +++ b/languages/p/powershell/README.md @@ -0,0 +1,38 @@ +# PowerShell Lambda Core + +This implementation uses PowerShell script blocks as callable values for the +project's Church booleans and Church numerals. It includes `TRUE`, `FALSE`, +`NOT`, `AND`, `OR`, `ZERO`, `SUCC`, `PRED`, and `ONE`, with self-checking +examples for every operation. + +## Requirements + +- PowerShell 7 or newer (`pwsh`) +- No PowerShell modules or other external dependencies + +PowerShell is included in GitHub's current +[`ubuntu-latest` runner image](https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md#powershell-tools), +so this folder does not require a workflow installation step. + +## Run and verify + +From this directory, run: + +```sh +sh test.sh +``` + +The script starts PowerShell without profiles or interactive behavior. The +repository-level `run-tests.sh` compares its standard output with +`expected-output.txt`. + +## How the representation works + +A PowerShell script block is callable, and `GetNewClosure()` captures the +current values needed by a returned, nested script block. That makes the +curried lambda terms expressible without native booleans or integers in the +core definitions. Only the two `ConvertFrom-Church*` observers cross into +native values for output. + +Microsoft documents script-block closure creation in +[`ScriptBlock.GetNewClosure`](https://learn.microsoft.com/dotnet/api/system.management.automation.scriptblock.getnewclosure). diff --git a/languages/p/powershell/expected-output.txt b/languages/p/powershell/expected-output.txt new file mode 100644 index 0000000..b109aba --- /dev/null +++ b/languages/p/powershell/expected-output.txt @@ -0,0 +1,22 @@ +CHURCH BOOLEANS +TRUE: true +FALSE: false +NOT TRUE: false +NOT FALSE: true +FALSE AND FALSE: false +FALSE AND TRUE: false +TRUE AND FALSE: false +TRUE AND TRUE: true +FALSE OR FALSE: false +FALSE OR TRUE: true +TRUE OR FALSE: true +TRUE OR TRUE: true +CHURCH NUMERALS +ZERO: 0 +ONE: 1 +SUCC ONE: 2 +SUCC (SUCC ONE): 3 +PRED ZERO: 0 +PRED ONE: 0 +PRED (SUCC ONE): 1 +PRED (SUCC (SUCC ONE)): 2 diff --git a/languages/p/powershell/lambda-core.ps1 b/languages/p/powershell/lambda-core.ps1 new file mode 100644 index 0000000..e43c996 --- /dev/null +++ b/languages/p/powershell/lambda-core.ps1 @@ -0,0 +1,190 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# TRUE = λx.λy.x +$ChurchTrue = { + param([object] $X) + + $Selected = $X + return { param([object] $_Y) $Selected }.GetNewClosure() +} + +# FALSE = λx.λy.y +$ChurchFalse = { + param([object] $_X) + + return { param([object] $Y) $Y } +} + +# NOT = λb.b FALSE TRUE +$ChurchNot = { + param([scriptblock] $Boolean) + + $WithFalse = & $Boolean $ChurchFalse + return & $WithFalse $ChurchTrue +}.GetNewClosure() + +# AND = λp.λq.p q FALSE +$ChurchAnd = { + param([scriptblock] $Left) + + $CapturedLeft = $Left + $CapturedFalse = $ChurchFalse + return { + param([scriptblock] $Right) + + $Selected = & $CapturedLeft $Right + return & $Selected $CapturedFalse + }.GetNewClosure() +}.GetNewClosure() + +# OR = λp.λq.p TRUE q +$ChurchOr = { + param([scriptblock] $Left) + + $CapturedLeft = $Left + $CapturedTrue = $ChurchTrue + return { + param([scriptblock] $Right) + + $Selected = & $CapturedLeft $CapturedTrue + return & $Selected $Right + }.GetNewClosure() +}.GetNewClosure() + +# ZERO = λf.λx.x +$Zero = { + param([scriptblock] $_Function) + + return { param([object] $Value) $Value } +} + +# SUCC = λn.λf.λx.f (n f x) +$Successor = { + param([scriptblock] $Numeral) + + $CapturedNumeral = $Numeral + return { + param([scriptblock] $Function) + + $NumeralForValue = $CapturedNumeral + $CapturedFunction = $Function + return { + param([object] $Value) + + $NumeralAtFunction = & $NumeralForValue $CapturedFunction + $Inner = & $NumeralAtFunction $Value + return & $CapturedFunction $Inner + }.GetNewClosure() + }.GetNewClosure() +} + +# PRED = λn.λf.λx.n (λg.λh.h (g f)) (λu.x) (λa.a) +$Predecessor = { + param([scriptblock] $Numeral) + + $CapturedNumeral = $Numeral + return { + param([scriptblock] $Function) + + $NumeralForValue = $CapturedNumeral + $CapturedFunction = $Function + return { + param([object] $Value) + + $StepFunction = $CapturedFunction + $Step = { + param([scriptblock] $G) + + $CapturedG = $G + $FunctionForG = $StepFunction + return { + param([scriptblock] $H) + + $GAtFunction = & $CapturedG $FunctionForG + return & $H $GAtFunction + }.GetNewClosure() + }.GetNewClosure() + + $SeedValue = $Value + $Seed = { param([object] $_U) $SeedValue }.GetNewClosure() + $Identity = { param([object] $Term) $Term } + + $AtStep = & $NumeralForValue $Step + $AtSeed = & $AtStep $Seed + return & $AtSeed $Identity + }.GetNewClosure() + }.GetNewClosure() +} + +$One = & $Successor $Zero + +# These observers are the only code that converts terms to native values. +function ConvertFrom-ChurchBoolean { + param([scriptblock] $Boolean) + + $Selected = & $Boolean $true + return [bool] (& $Selected $false) +} + +function ConvertFrom-ChurchNumeral { + param([scriptblock] $Numeral) + + $Increment = { param([int] $Value) $Value + 1 } + $AtIncrement = & $Numeral $Increment + return [int] (& $AtIncrement 0) +} + +function Assert-ChurchBoolean { + param( + [string] $Label, + [scriptblock] $Boolean, + [bool] $Expected + ) + + $Actual = ConvertFrom-ChurchBoolean $Boolean + if ($Actual -ne $Expected) { + throw "$Label`: expected $Expected, got $Actual" + } + + Write-Output ('{0}: {1}' -f $Label, $Actual.ToString().ToLowerInvariant()) +} + +function Assert-ChurchNumeral { + param( + [string] $Label, + [scriptblock] $Numeral, + [int] $Expected + ) + + $Actual = ConvertFrom-ChurchNumeral $Numeral + if ($Actual -ne $Expected) { + throw "$Label`: expected $Expected, got $Actual" + } + + Write-Output ('{0}: {1}' -f $Label, $Actual) +} + +Write-Output 'CHURCH BOOLEANS' +Assert-ChurchBoolean 'TRUE' $ChurchTrue $true +Assert-ChurchBoolean 'FALSE' $ChurchFalse $false +Assert-ChurchBoolean 'NOT TRUE' (& $ChurchNot $ChurchTrue) $false +Assert-ChurchBoolean 'NOT FALSE' (& $ChurchNot $ChurchFalse) $true +Assert-ChurchBoolean 'FALSE AND FALSE' (& (& $ChurchAnd $ChurchFalse) $ChurchFalse) $false +Assert-ChurchBoolean 'FALSE AND TRUE' (& (& $ChurchAnd $ChurchFalse) $ChurchTrue) $false +Assert-ChurchBoolean 'TRUE AND FALSE' (& (& $ChurchAnd $ChurchTrue) $ChurchFalse) $false +Assert-ChurchBoolean 'TRUE AND TRUE' (& (& $ChurchAnd $ChurchTrue) $ChurchTrue) $true +Assert-ChurchBoolean 'FALSE OR FALSE' (& (& $ChurchOr $ChurchFalse) $ChurchFalse) $false +Assert-ChurchBoolean 'FALSE OR TRUE' (& (& $ChurchOr $ChurchFalse) $ChurchTrue) $true +Assert-ChurchBoolean 'TRUE OR FALSE' (& (& $ChurchOr $ChurchTrue) $ChurchFalse) $true +Assert-ChurchBoolean 'TRUE OR TRUE' (& (& $ChurchOr $ChurchTrue) $ChurchTrue) $true + +Write-Output 'CHURCH NUMERALS' +Assert-ChurchNumeral 'ZERO' $Zero 0 +Assert-ChurchNumeral 'ONE' $One 1 +Assert-ChurchNumeral 'SUCC ONE' (& $Successor $One) 2 +Assert-ChurchNumeral 'SUCC (SUCC ONE)' (& $Successor (& $Successor $One)) 3 +Assert-ChurchNumeral 'PRED ZERO' (& $Predecessor $Zero) 0 +Assert-ChurchNumeral 'PRED ONE' (& $Predecessor $One) 0 +Assert-ChurchNumeral 'PRED (SUCC ONE)' (& $Predecessor (& $Successor $One)) 1 +Assert-ChurchNumeral 'PRED (SUCC (SUCC ONE))' (& $Predecessor (& $Successor (& $Successor $One))) 2 diff --git a/languages/p/powershell/test.sh b/languages/p/powershell/test.sh new file mode 100644 index 0000000..b462f83 --- /dev/null +++ b/languages/p/powershell/test.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu + +command -v pwsh >/dev/null 2>&1 || exit 42 + +directory=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +exec pwsh -NoLogo -NoProfile -NonInteractive -File "$directory/lambda-core.ps1" diff --git a/languages/r/rust/README.md b/languages/r/rust/README.md new file mode 100644 index 0000000..5bc8f5f --- /dev/null +++ b/languages/r/rust/README.md @@ -0,0 +1,39 @@ +# Rust Lambda Core + +This implementation expresses the project's Church booleans and Church +numerals as callable Rust values. It includes `TRUE`, `FALSE`, `NOT`, `AND`, +`OR`, `ZERO`, `SUCC`, `PRED`, and `ONE`, then checks every printed example. + +## Requirements + +- A current stable Rust compiler +- No Cargo crates or other external dependencies + +Rust is included in GitHub's current +[`ubuntu-latest` runner image](https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md#rust-tools), +so this folder does not require a workflow installation step. + +## Run and verify + +From this directory, run: + +```sh +sh test.sh +``` + +The test script compiles with warnings denied into a temporary directory, +runs the binary, and removes the build artifact. The repository-level +`run-tests.sh` compares its standard output with `expected-output.txt`. + +## How the representation works + +Rust cannot spell the infinitely recursive type `Term = Term -> Term` +directly. The small `Term` wrapper therefore holds an `Rc +Term>`. `Rc` supplies shared ownership for captured terms, while each core +definition remains a direct translation of its lambda-calculus form. The +encodings do not store Rust booleans or integers; only the output observers +produce those native values. + +The relevant standard-library mechanism is documented under +[`std::rc`](https://doc.rust-lang.org/std/rc/), and the Rust book explains +[closure capture](https://doc.rust-lang.org/book/ch13-01-closures.html). diff --git a/languages/r/rust/expected-output.txt b/languages/r/rust/expected-output.txt new file mode 100644 index 0000000..b109aba --- /dev/null +++ b/languages/r/rust/expected-output.txt @@ -0,0 +1,22 @@ +CHURCH BOOLEANS +TRUE: true +FALSE: false +NOT TRUE: false +NOT FALSE: true +FALSE AND FALSE: false +FALSE AND TRUE: false +TRUE AND FALSE: false +TRUE AND TRUE: true +FALSE OR FALSE: false +FALSE OR TRUE: true +TRUE OR FALSE: true +TRUE OR TRUE: true +CHURCH NUMERALS +ZERO: 0 +ONE: 1 +SUCC ONE: 2 +SUCC (SUCC ONE): 3 +PRED ZERO: 0 +PRED ONE: 0 +PRED (SUCC ONE): 1 +PRED (SUCC (SUCC ONE)): 2 diff --git a/languages/r/rust/lambda-core.rs b/languages/r/rust/lambda-core.rs new file mode 100644 index 0000000..1a1fca1 --- /dev/null +++ b/languages/r/rust/lambda-core.rs @@ -0,0 +1,205 @@ +use std::cell::Cell; +use std::rc::Rc; + +#[derive(Clone)] +struct Term { + function: Rc Term>, +} + +impl Term { + fn apply(&self, argument: Term) -> Term { + (self.function)(argument) + } + + fn same_value_as(&self, other: &Term) -> bool { + Rc::ptr_eq(&self.function, &other.function) + } +} + +fn lambda(function: impl Fn(Term) -> Term + 'static) -> Term { + Term { + function: Rc::new(function), + } +} + +// TRUE = λx.λy.x +fn church_true() -> Term { + lambda(|x| { + let selected = x.clone(); + lambda(move |_| selected.clone()) + }) +} + +// FALSE = λx.λy.y +fn church_false() -> Term { + lambda(|_| lambda(|y| y)) +} + +// NOT = λb.b FALSE TRUE +fn church_not() -> Term { + lambda(|boolean| boolean.apply(church_false()).apply(church_true())) +} + +// AND = λp.λq.p q FALSE +fn church_and() -> Term { + lambda(|left| lambda(move |right| left.apply(right).apply(church_false()))) +} + +// OR = λp.λq.p TRUE q +fn church_or() -> Term { + lambda(|left| lambda(move |right| left.apply(church_true()).apply(right))) +} + +// ZERO = λf.λx.x +fn zero() -> Term { + lambda(|_| lambda(|value| value)) +} + +// SUCC = λn.λf.λx.f (n f x) +fn successor() -> Term { + lambda(|numeral| { + lambda(move |function| { + let captured_numeral = numeral.clone(); + lambda(move |value| { + let inner = captured_numeral.apply(function.clone()).apply(value); + function.apply(inner) + }) + }) + }) +} + +// PRED = λn.λf.λx.n (λg.λh.h (g f)) (λu.x) (λa.a) +fn predecessor() -> Term { + lambda(|numeral| { + lambda(move |function| { + let captured_numeral = numeral.clone(); + lambda(move |value| { + let step_function = function.clone(); + let step = lambda(move |g| { + let captured_function = step_function.clone(); + lambda(move |h| h.apply(g.apply(captured_function.clone()))) + }); + + let seed_value = value.clone(); + let seed = lambda(move |_| seed_value.clone()); + let identity = lambda(|term| term); + + captured_numeral.apply(step).apply(seed).apply(identity) + }) + }) + }) +} + +fn one() -> Term { + successor().apply(zero()) +} + +// These observers are the only code that converts terms to native values. +fn church_to_bool(boolean: Term) -> bool { + let when_true = lambda(|term| term); + let when_false = lambda(|term| term); + let selected = boolean.apply(when_true.clone()).apply(when_false.clone()); + + if selected.same_value_as(&when_true) { + true + } else if selected.same_value_as(&when_false) { + false + } else { + panic!("a Church boolean did not select either argument"); + } +} + +fn church_to_int(numeral: Term) -> i32 { + let applications = Rc::new(Cell::new(0)); + let counter = Rc::clone(&applications); + let increment = lambda(move |term| { + counter.set(counter.get() + 1); + term + }); + let seed = lambda(|term| term); + + numeral.apply(increment).apply(seed); + applications.get() +} + +fn check_boolean(label: &str, boolean: Term, expected: bool) { + let actual = church_to_bool(boolean); + assert_eq!(actual, expected, "{label}"); + println!("{label}: {actual}"); +} + +fn check_numeral(label: &str, numeral: Term, expected: i32) { + let actual = church_to_int(numeral); + assert_eq!(actual, expected, "{label}"); + println!("{label}: {actual}"); +} + +fn main() { + println!("CHURCH BOOLEANS"); + check_boolean("TRUE", church_true(), true); + check_boolean("FALSE", church_false(), false); + check_boolean("NOT TRUE", church_not().apply(church_true()), false); + check_boolean("NOT FALSE", church_not().apply(church_false()), true); + check_boolean( + "FALSE AND FALSE", + church_and().apply(church_false()).apply(church_false()), + false, + ); + check_boolean( + "FALSE AND TRUE", + church_and().apply(church_false()).apply(church_true()), + false, + ); + check_boolean( + "TRUE AND FALSE", + church_and().apply(church_true()).apply(church_false()), + false, + ); + check_boolean( + "TRUE AND TRUE", + church_and().apply(church_true()).apply(church_true()), + true, + ); + check_boolean( + "FALSE OR FALSE", + church_or().apply(church_false()).apply(church_false()), + false, + ); + check_boolean( + "FALSE OR TRUE", + church_or().apply(church_false()).apply(church_true()), + true, + ); + check_boolean( + "TRUE OR FALSE", + church_or().apply(church_true()).apply(church_false()), + true, + ); + check_boolean( + "TRUE OR TRUE", + church_or().apply(church_true()).apply(church_true()), + true, + ); + + println!("CHURCH NUMERALS"); + check_numeral("ZERO", zero(), 0); + check_numeral("ONE", one(), 1); + check_numeral("SUCC ONE", successor().apply(one()), 2); + check_numeral( + "SUCC (SUCC ONE)", + successor().apply(successor().apply(one())), + 3, + ); + check_numeral("PRED ZERO", predecessor().apply(zero()), 0); + check_numeral("PRED ONE", predecessor().apply(one()), 0); + check_numeral( + "PRED (SUCC ONE)", + predecessor().apply(successor().apply(one())), + 1, + ); + check_numeral( + "PRED (SUCC (SUCC ONE))", + predecessor().apply(successor().apply(successor().apply(one()))), + 2, + ); +} diff --git a/languages/r/rust/test.sh b/languages/r/rust/test.sh new file mode 100644 index 0000000..5d368fa --- /dev/null +++ b/languages/r/rust/test.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -eu + +command -v rustc >/dev/null 2>&1 || exit 42 + +directory=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +build_directory=$(mktemp -d) +trap 'rm -rf "$build_directory"' EXIT HUP INT TERM + +rustc \ + --edition=2021 \ + -D warnings \ + "$directory/lambda-core.rs" \ + -o "$build_directory/lambda-core" + +"$build_directory/lambda-core" diff --git a/languages/s/sql/README.md b/languages/s/sql/README.md new file mode 100644 index 0000000..c2af0f9 --- /dev/null +++ b/languages/s/sql/README.md @@ -0,0 +1,41 @@ +# SQL Lambda Core + +This implementation uses SQLite to demonstrate the Lambda Core through a +relational emulation. SQLite does not have first-class functions, so Church +booleans are stored by the argument branch they select, while a Church numeral +is stored as one row per function application. Views implement `NOT`, `AND`, +and `OR`; triggers implement reusable `SUCC` and `PRED` transformations. + +## Requirements + +- SQLite 3 +- No extensions or external packages + +SQLite is included in GitHub's current +[`ubuntu-latest` runner image](https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md#databases), +so this folder does not require a workflow installation step. + +## Run and verify + +From this directory, run: + +```sh +sh test.sh +``` + +The script evaluates `lambda-core.sql` in a fresh in-memory database. Each +example is inserted into a table whose `CHECK` constraint compares actual and +expected values, so an incorrect operation exits nonzero before output. The +repository-level `run-tests.sh` compares the observed output with +`expected-output.txt`. + +## Why this is an emulation + +The project explicitly permits languages to express *or emulate* the core +lambda-calculus concepts. SQL's reusable abstraction is a relation rather +than a closure. The implementation preserves Church behavior—the boolean +selects one branch, and a numeral describes repeated application—without +claiming that SQLite rows are first-class lambda terms. + +The implementation relies only on SQLite's documented +[`CREATE TRIGGER`](https://www.sqlite.org/lang_createtrigger.html) behavior. diff --git a/languages/s/sql/expected-output.txt b/languages/s/sql/expected-output.txt new file mode 100644 index 0000000..b109aba --- /dev/null +++ b/languages/s/sql/expected-output.txt @@ -0,0 +1,22 @@ +CHURCH BOOLEANS +TRUE: true +FALSE: false +NOT TRUE: false +NOT FALSE: true +FALSE AND FALSE: false +FALSE AND TRUE: false +TRUE AND FALSE: false +TRUE AND TRUE: true +FALSE OR FALSE: false +FALSE OR TRUE: true +TRUE OR FALSE: true +TRUE OR TRUE: true +CHURCH NUMERALS +ZERO: 0 +ONE: 1 +SUCC ONE: 2 +SUCC (SUCC ONE): 3 +PRED ZERO: 0 +PRED ONE: 0 +PRED (SUCC ONE): 1 +PRED (SUCC (SUCC ONE)): 2 diff --git a/languages/s/sql/lambda-core.sql b/languages/s/sql/lambda-core.sql new file mode 100644 index 0000000..8dd17c0 --- /dev/null +++ b/languages/s/sql/lambda-core.sql @@ -0,0 +1,235 @@ +.bail on +.headers off +.mode list + +PRAGMA foreign_keys = ON; + +-- SQLite has no first-class functions. A Church boolean is represented by the +-- branch it selects, and the views below implement the same selection rules as +-- the lambda terms. +CREATE TEMP TABLE church_boolean ( + name TEXT PRIMARY KEY, + selected_branch TEXT NOT NULL + CHECK (selected_branch IN ('first', 'second')) +); + +-- TRUE = λx.λy.x; FALSE = λx.λy.y +INSERT INTO church_boolean (name, selected_branch) VALUES + ('TRUE', 'first'), + ('FALSE', 'second'); + +-- NOT = λb.b FALSE TRUE +CREATE TEMP VIEW church_not AS +SELECT + name AS input_name, + CASE selected_branch + WHEN 'first' THEN 'second' + ELSE 'first' + END AS selected_branch +FROM church_boolean; + +-- AND = λp.λq.p q FALSE +CREATE TEMP VIEW church_and AS +SELECT + left_boolean.name AS left_name, + right_boolean.name AS right_name, + CASE left_boolean.selected_branch + WHEN 'first' THEN right_boolean.selected_branch + ELSE 'second' + END AS selected_branch +FROM church_boolean AS left_boolean +CROSS JOIN church_boolean AS right_boolean; + +-- OR = λp.λq.p TRUE q +CREATE TEMP VIEW church_or AS +SELECT + left_boolean.name AS left_name, + right_boolean.name AS right_name, + CASE left_boolean.selected_branch + WHEN 'first' THEN 'first' + ELSE right_boolean.selected_branch + END AS selected_branch +FROM church_boolean AS left_boolean +CROSS JOIN church_boolean AS right_boolean; + +-- The CHECK constraint turns every displayed example into an assertion. +CREATE TEMP TABLE boolean_result ( + label TEXT PRIMARY KEY, + display_order INTEGER NOT NULL UNIQUE, + actual_branch TEXT NOT NULL, + expected_branch TEXT NOT NULL, + CHECK (actual_branch = expected_branch) +); + +INSERT INTO boolean_result +SELECT 'TRUE', 1, selected_branch, 'first' +FROM church_boolean WHERE name = 'TRUE'; + +INSERT INTO boolean_result +SELECT 'FALSE', 2, selected_branch, 'second' +FROM church_boolean WHERE name = 'FALSE'; + +INSERT INTO boolean_result +SELECT 'NOT TRUE', 3, selected_branch, 'second' +FROM church_not WHERE input_name = 'TRUE'; + +INSERT INTO boolean_result +SELECT 'NOT FALSE', 4, selected_branch, 'first' +FROM church_not WHERE input_name = 'FALSE'; + +INSERT INTO boolean_result +SELECT left_name || ' AND ' || right_name, + CASE left_name || ':' || right_name + WHEN 'FALSE:FALSE' THEN 5 + WHEN 'FALSE:TRUE' THEN 6 + WHEN 'TRUE:FALSE' THEN 7 + ELSE 8 + END, + selected_branch, + CASE + WHEN left_name = 'TRUE' AND right_name = 'TRUE' THEN 'first' + ELSE 'second' + END +FROM church_and; + +INSERT INTO boolean_result +SELECT left_name || ' OR ' || right_name, + CASE left_name || ':' || right_name + WHEN 'FALSE:FALSE' THEN 9 + WHEN 'FALSE:TRUE' THEN 10 + WHEN 'TRUE:FALSE' THEN 11 + ELSE 12 + END, + selected_branch, + CASE + WHEN left_name = 'FALSE' AND right_name = 'FALSE' THEN 'second' + ELSE 'first' + END +FROM church_or; + +-- A Church numeral applies its function n times. SQL represents that repeated +-- application as n related rows; ZERO therefore has no application rows. +CREATE TEMP TABLE church_numeral ( + name TEXT PRIMARY KEY +); + +CREATE TEMP TABLE church_application ( + numeral_name TEXT NOT NULL REFERENCES church_numeral(name), + ordinal INTEGER NOT NULL CHECK (ordinal > 0), + PRIMARY KEY (numeral_name, ordinal) +); + +-- SUCC copies an input application chain and appends one application. +CREATE TEMP TABLE successor_request ( + input_name TEXT NOT NULL REFERENCES church_numeral(name), + output_name TEXT NOT NULL UNIQUE +); + +CREATE TEMP TRIGGER apply_successor +AFTER INSERT ON successor_request +BEGIN + INSERT INTO church_numeral (name) VALUES (NEW.output_name); + + INSERT INTO church_application (numeral_name, ordinal) + SELECT NEW.output_name, ordinal + FROM church_application + WHERE numeral_name = NEW.input_name; + + INSERT INTO church_application (numeral_name, ordinal) + SELECT NEW.output_name, COALESCE(MAX(ordinal), 0) + 1 + FROM church_application + WHERE numeral_name = NEW.input_name; +END; + +-- PRED copies every application except the final one. For ZERO, there are no +-- rows to copy, so the result remains ZERO as required. +CREATE TEMP TABLE predecessor_request ( + input_name TEXT NOT NULL REFERENCES church_numeral(name), + output_name TEXT NOT NULL UNIQUE +); + +CREATE TEMP TRIGGER apply_predecessor +AFTER INSERT ON predecessor_request +BEGIN + INSERT INTO church_numeral (name) VALUES (NEW.output_name); + + INSERT INTO church_application (numeral_name, ordinal) + SELECT NEW.output_name, ordinal + FROM church_application + WHERE numeral_name = NEW.input_name + AND ordinal < COALESCE( + (SELECT MAX(last.ordinal) + FROM church_application AS last + WHERE last.numeral_name = NEW.input_name), + 0 + ); +END; + +-- ZERO = λf.λx.x; ONE = SUCC ZERO +INSERT INTO church_numeral (name) VALUES ('ZERO'); +INSERT INTO successor_request VALUES ('ZERO', 'ONE'); +INSERT INTO successor_request VALUES ('ONE', 'TWO'); +INSERT INTO successor_request VALUES ('TWO', 'THREE'); + +INSERT INTO predecessor_request VALUES ('ZERO', 'PRED ZERO'); +INSERT INTO predecessor_request VALUES ('ONE', 'PRED ONE'); +INSERT INTO predecessor_request VALUES ('TWO', 'PRED TWO'); +INSERT INTO predecessor_request VALUES ('THREE', 'PRED THREE'); + +CREATE TEMP VIEW church_to_integer AS +SELECT numeral.name, COUNT(application.ordinal) AS native_value +FROM church_numeral AS numeral +LEFT JOIN church_application AS application + ON application.numeral_name = numeral.name +GROUP BY numeral.name; + +CREATE TEMP TABLE numeral_result ( + label TEXT PRIMARY KEY, + display_order INTEGER NOT NULL UNIQUE, + actual_value INTEGER NOT NULL, + expected_value INTEGER NOT NULL, + CHECK (actual_value = expected_value) +); + +INSERT INTO numeral_result +SELECT 'ZERO', 1, native_value, 0 +FROM church_to_integer WHERE name = 'ZERO'; + +INSERT INTO numeral_result +SELECT 'ONE', 2, native_value, 1 +FROM church_to_integer WHERE name = 'ONE'; + +INSERT INTO numeral_result +SELECT 'SUCC ONE', 3, native_value, 2 +FROM church_to_integer WHERE name = 'TWO'; + +INSERT INTO numeral_result +SELECT 'SUCC (SUCC ONE)', 4, native_value, 3 +FROM church_to_integer WHERE name = 'THREE'; + +INSERT INTO numeral_result +SELECT 'PRED ZERO', 5, native_value, 0 +FROM church_to_integer WHERE name = 'PRED ZERO'; + +INSERT INTO numeral_result +SELECT 'PRED ONE', 6, native_value, 0 +FROM church_to_integer WHERE name = 'PRED ONE'; + +INSERT INTO numeral_result +SELECT 'PRED (SUCC ONE)', 7, native_value, 1 +FROM church_to_integer WHERE name = 'PRED TWO'; + +INSERT INTO numeral_result +SELECT 'PRED (SUCC (SUCC ONE))', 8, native_value, 2 +FROM church_to_integer WHERE name = 'PRED THREE'; + +.print CHURCH BOOLEANS +SELECT label || ': ' || + CASE actual_branch WHEN 'first' THEN 'true' ELSE 'false' END +FROM boolean_result +ORDER BY display_order; + +.print CHURCH NUMERALS +SELECT label || ': ' || actual_value +FROM numeral_result +ORDER BY display_order; diff --git a/languages/s/sql/test.sh b/languages/s/sql/test.sh new file mode 100644 index 0000000..e088817 --- /dev/null +++ b/languages/s/sql/test.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu + +command -v sqlite3 >/dev/null 2>&1 || exit 42 + +directory=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +exec sqlite3 :memory: < "$directory/lambda-core.sql"