diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f727812..5e0f1a2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,13 +11,13 @@ jobs: steps: - uses: actions/checkout@v4 - # Python, Node.js, Perl, GCC/G++ 14, and the .NET SDK (F#) come - # preinstalled on the runner. + # Python, Node.js, Perl, GCC/G++ 14, Swift, Gradle (Groovy), and the + # .NET SDK (F#) come preinstalled on the runner. - name: Install toolchains not preinstalled on the runner run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ - racket ruby lua5.4 ghc ocaml elixir + racket ruby lua5.4 ghc ocaml elixir r-base-core # Clojure's deps.edn CLI (clj) is not in apt; use the official installer. - name: Install Clojure CLI @@ -28,5 +28,9 @@ jobs: - uses: oven-sh/setup-bun@v2 + # v1.8.1 omitted dart-analyzer.json from its published bundle; pin the + # signed v1.7.2 release until that regression is fixed upstream. + - uses: dart-lang/setup-dart@65eb853c7ba17dde3be364c3d2858773e7144260 # v1.7.2 + - name: Run all language tests run: ./run-tests.sh diff --git a/README.md b/README.md index 86c5130..b6f6de5 100644 --- a/README.md +++ b/README.md @@ -14,22 +14,23 @@ This project is inspired by the classic "hello-world" repo idea, but takes thing 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) +- [ArkScript](languages/a/arkscript), [Assembly](languages/a/assembly), + [Bash](languages/b/bash), and [bruijn](languages/b/bruijn) +- [C](languages/c/c), [C++](languages/c/c++), + [Clojure](languages/c/clojure), and [Dart](languages/d/dart) - [Elixir](languages/e/elixir), [FatScript](languages/f/fatscript), and [F#](languages/f/fsharp) -- [Go](languages/g/go), [Haskell](languages/h/haskell), +- [Go](languages/g/go), [Groovy](languages/g/groovy), + [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) +- [R](languages/r/r), [Racket](languages/r/racket), [Ruby](languages/r/ruby), + [Rust](languages/r/rust), [SQL](languages/s/sql), + [Swift](languages/s/swift), 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) @@ -38,7 +39,10 @@ against the implementations already present. GitHub's 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. +The second August 2026 expansion continued down the same Stack Overflow +ranking and added Assembly, Dart, Swift, R, and Groovy. + +Still wanted: Scala, Zig, Gleam, Prolog, Erlang, APL, and Idris. --- diff --git a/ROADMAP.md b/ROADMAP.md index 9304450..d23ce0a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -76,13 +76,36 @@ Octoverse independently places PHP and Shell in its top ten, while the August reports 11 passed, 0 failed, 10 skipped for unavailable local toolchains, and the pre-existing Java baseline still pending. +### Phase 2 — next five languages by 2025 usage + +Selection method: continue down the Stack Overflow 2025 "have used" language +ranking after removing repository implementations and C#, whose implementation +already exists in active PR #37. The next five absent languages are Assembly +(7.1%), Dart (5.9%), Swift (5.4%), R (4.9%), and Groovy (4.8%). + +- [x] **Step 1 — verify the selection and starting state.** None of + `languages/a/assembly`, `languages/d/dart`, `languages/s/swift`, + `languages/r/r`, or `languages/g/groovy` exists on main. This phase + creates five new implementations; it does not modify an existing + language implementation. +- [x] **Step 2 — implement all five cores.** Add Church booleans, Church + numerals, native output observers, and self-checking examples for + Assembly, Dart, Swift, R, and Groovy. Assembly uses an explicitly + documented x86-64 System V closure ABI because GNU assembler has no + first-class closure type. +- [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.** Use the runner's preinstalled GCC, + Swift, and Gradle toolchains; install only the R and Dart runtimes; list + the new implementations in the README; run every focused check and the + repository harness. GitHub CI reports 25 passed, 0 failed, 0 skipped, + and the pre-existing Java baseline still pending (PR #39). + ### Later major-language phases - [ ] C# -- [ ] Swift - [ ] Scala - [ ] Zig -- [ ] Dart - [ ] Gleam (reopens the slot from closed PR #28) Keep a "wanted" list in the README for languages left open to contributors diff --git a/languages/a/assembly/README.md b/languages/a/assembly/README.md new file mode 100644 index 0000000..098eaf8 --- /dev/null +++ b/languages/a/assembly/README.md @@ -0,0 +1,38 @@ +# Assembly Lambda Core + +This implementation expresses the project's Church booleans and Church +numerals in x86-64 GNU assembler. It includes `TRUE`, `FALSE`, `NOT`, `AND`, +`OR`, `ZERO`, `SUCC`, `PRED`, and `ONE`, then checks every printed example. + +## Requirements + +- An x86-64 System V environment, such as 64-bit Linux +- GCC and the system C library +- No additional packages or libraries + +GCC and GNU binutils are preinstalled on GitHub's `ubuntu-latest` runner. + +## Run and verify + +From this directory, run: + +```sh +sh test.sh +``` + +The test script compiles a position-independent executable into a temporary +directory, runs it, and removes the build artifact. The repository-level +`run-tests.sh` compares its standard output with `expected-output.txt`. + +## How the representation works + +GNU assembler has no first-class closure type, so each term uses an explicit +three-word closure: a code pointer followed by two captured-value slots. Every +code pointer follows the x86-64 System V calling convention and receives its +own closure plus one argument. The core encodings do not store native booleans +or integers; only the output observers create those values. + +The syntax is documented in the +[GNU assembler manual](https://sourceware.org/binutils/docs/as/Manual.html), +and the calling convention comes from the +[x86-64 System V ABI](https://gitlab.com/x86-psABIs/x86-64-ABI). diff --git a/languages/a/assembly/expected-output.txt b/languages/a/assembly/expected-output.txt new file mode 100644 index 0000000..b109aba --- /dev/null +++ b/languages/a/assembly/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/a/assembly/lambda-core.S b/languages/a/assembly/lambda-core.S new file mode 100644 index 0000000..d4aa60e --- /dev/null +++ b/languages/a/assembly/lambda-core.S @@ -0,0 +1,619 @@ +# x86-64 System V / GNU assembler implementation of the Lambda Core. +# +# A term is a three-word closure: +# 0: code pointer with signature Term *(Term *self, Term *argument) +# 8: first captured term/value +# 16: second captured term/value + + .text + + .type make_term, @function +make_term: + pushq %r12 + pushq %r13 + pushq %r14 + movq %rdi, %r12 + movq %rsi, %r13 + movq %rdx, %r14 + movl $24, %edi + call malloc@PLT + testq %rax, %rax + je .Lallocation_failed + movq %r12, 0(%rax) + movq %r13, 8(%rax) + movq %r14, 16(%rax) + popq %r14 + popq %r13 + popq %r12 + ret +.Lallocation_failed: + call abort@PLT + ud2 + + .type apply, @function +apply: + movq 0(%rdi), %rax + jmp *%rax + +# TRUE = lambda x. lambda y. x + .type true_first, @function +true_first: + movq %rsi, %r8 + leaq true_second(%rip), %rdi + movq %r8, %rsi + xorl %edx, %edx + jmp make_term + + .type true_second, @function +true_second: + movq 8(%rdi), %rax + ret + +# FALSE = lambda x. lambda y. y + .type false_first, @function +false_first: + leaq false_second(%rip), %rdi + xorl %esi, %esi + xorl %edx, %edx + jmp make_term + + .type false_second, @function +false_second: + movq %rsi, %rax + ret + +# NOT = lambda b. b FALSE TRUE + .type not_term_code, @function +not_term_code: + pushq %r12 + movq %rsi, %r12 + movq %r12, %rdi + leaq FALSE_TERM(%rip), %rsi + call apply + movq %rax, %rdi + leaq TRUE_TERM(%rip), %rsi + call apply + popq %r12 + ret + +# AND = lambda p. lambda q. p q FALSE + .type and_first, @function +and_first: + movq %rsi, %r8 + leaq and_second(%rip), %rdi + movq %r8, %rsi + xorl %edx, %edx + jmp make_term + + .type and_second, @function +and_second: + subq $8, %rsp + movq 8(%rdi), %rdi + call apply + movq %rax, %rdi + leaq FALSE_TERM(%rip), %rsi + call apply + addq $8, %rsp + ret + +# OR = lambda p. lambda q. p TRUE q + .type or_first, @function +or_first: + movq %rsi, %r8 + leaq or_second(%rip), %rdi + movq %r8, %rsi + xorl %edx, %edx + jmp make_term + + .type or_second, @function +or_second: + pushq %r12 + movq %rsi, %r12 + movq 8(%rdi), %rdi + leaq TRUE_TERM(%rip), %rsi + call apply + movq %rax, %rdi + movq %r12, %rsi + call apply + popq %r12 + ret + +# ZERO = lambda f. lambda x. x + .type zero_first, @function +zero_first: + leaq zero_second(%rip), %rdi + xorl %esi, %esi + xorl %edx, %edx + jmp make_term + + .type zero_second, @function +zero_second: + movq %rsi, %rax + ret + +# SUCC = lambda n. lambda f. lambda x. f (n f x) + .type successor_first, @function +successor_first: + movq %rsi, %r8 + leaq successor_second(%rip), %rdi + movq %r8, %rsi + xorl %edx, %edx + jmp make_term + + .type successor_second, @function +successor_second: + movq 8(%rdi), %r8 + movq %rsi, %r9 + leaq successor_third(%rip), %rdi + movq %r8, %rsi + movq %r9, %rdx + jmp make_term + + .type successor_third, @function +successor_third: + pushq %r12 + pushq %r13 + subq $8, %rsp + movq 16(%rdi), %r12 + movq %rsi, %r13 + movq 8(%rdi), %rdi + movq %r12, %rsi + call apply + movq %rax, %rdi + movq %r13, %rsi + call apply + movq %r12, %rdi + movq %rax, %rsi + call apply + addq $8, %rsp + popq %r13 + popq %r12 + ret + +# PRED = lambda n. lambda f. lambda x. +# n (lambda g. lambda h. h (g f)) (lambda u. x) (lambda a. a) + .type predecessor_first, @function +predecessor_first: + movq %rsi, %r8 + leaq predecessor_second(%rip), %rdi + movq %r8, %rsi + xorl %edx, %edx + jmp make_term + + .type predecessor_second, @function +predecessor_second: + movq 8(%rdi), %r8 + movq %rsi, %r9 + leaq predecessor_third(%rip), %rdi + movq %r8, %rsi + movq %r9, %rdx + jmp make_term + + .type predecessor_third, @function +predecessor_third: + pushq %r12 + pushq %r13 + pushq %r14 + pushq %r15 + subq $8, %rsp + movq 8(%rdi), %r12 + movq 16(%rdi), %r13 + movq %rsi, %r14 + + leaq predecessor_step_first(%rip), %rdi + movq %r13, %rsi + xorl %edx, %edx + call make_term + movq %rax, %r15 + + leaq predecessor_seed(%rip), %rdi + movq %r14, %rsi + xorl %edx, %edx + call make_term + movq %rax, %r14 + + movq %r12, %rdi + movq %r15, %rsi + call apply + movq %rax, %rdi + movq %r14, %rsi + call apply + movq %rax, %rdi + leaq IDENTITY_TERM(%rip), %rsi + call apply + + addq $8, %rsp + popq %r15 + popq %r14 + popq %r13 + popq %r12 + ret + + .type predecessor_step_first, @function +predecessor_step_first: + movq %rsi, %r8 + movq 8(%rdi), %r9 + leaq predecessor_step_second(%rip), %rdi + movq %r8, %rsi + movq %r9, %rdx + jmp make_term + + .type predecessor_step_second, @function +predecessor_step_second: + pushq %r12 + movq %rsi, %r12 + movq 8(%rdi), %rax + movq 16(%rdi), %rsi + movq %rax, %rdi + call apply + movq %r12, %rdi + movq %rax, %rsi + call apply + popq %r12 + ret + + .type predecessor_seed, @function +predecessor_seed: + movq 8(%rdi), %rax + ret + + .type identity_code, @function +identity_code: + movq %rsi, %rax + ret + + .type one_term, @function +one_term: + subq $8, %rsp + leaq SUCCESSOR_TERM(%rip), %rdi + leaq ZERO_TERM(%rip), %rsi + call apply + addq $8, %rsp + ret + + .type two_term, @function +two_term: + subq $8, %rsp + call one_term + leaq SUCCESSOR_TERM(%rip), %rdi + movq %rax, %rsi + call apply + addq $8, %rsp + ret + + .type three_term, @function +three_term: + subq $8, %rsp + call two_term + leaq SUCCESSOR_TERM(%rip), %rdi + movq %rax, %rsi + call apply + addq $8, %rsp + ret + + .type apply_binary, @function +apply_binary: + pushq %r12 + movq %rdx, %r12 + call apply + movq %rax, %rdi + movq %r12, %rsi + call apply + popq %r12 + ret + + .type predecessor_of, @function +predecessor_of: + movq %rdi, %rsi + leaq PREDECESSOR_TERM(%rip), %rdi + jmp apply + +# Observers below this point are the only code that converts terms to native +# booleans or integers. + .type church_to_bool, @function +church_to_bool: + subq $8, %rsp + leaq TRUE_MARKER(%rip), %rsi + call apply + movq %rax, %rdi + leaq FALSE_MARKER(%rip), %rsi + call apply + leaq TRUE_MARKER(%rip), %rcx + cmpq %rcx, %rax + je .Lobserved_true + leaq FALSE_MARKER(%rip), %rcx + cmpq %rcx, %rax + je .Lobserved_false + movl $-1, %eax + jmp .Lobserved_boolean_done +.Lobserved_true: + movl $1, %eax + jmp .Lobserved_boolean_done +.Lobserved_false: + xorl %eax, %eax +.Lobserved_boolean_done: + addq $8, %rsp + ret + + .type counter_code, @function +counter_code: + movq 8(%rdi), %rax + addq $1, 0(%rax) + movq %rsi, %rax + ret + + .type church_to_int, @function +church_to_int: + subq $40, %rsp + movq %rdi, 32(%rsp) + movq $0, 0(%rsp) + leaq counter_code(%rip), %rax + movq %rax, 8(%rsp) + leaq 0(%rsp), %rax + movq %rax, 16(%rsp) + movq $0, 24(%rsp) + movq 32(%rsp), %rdi + leaq 8(%rsp), %rsi + call apply + movq %rax, %rdi + leaq IDENTITY_TERM(%rip), %rsi + call apply + movl 0(%rsp), %eax + addq $40, %rsp + ret + + .type check_boolean, @function +check_boolean: + pushq %r12 + pushq %r13 + subq $8, %rsp + movq %rdi, %r12 + movl %edx, %r13d + movq %rsi, %rdi + call church_to_bool + cmpl %r13d, %eax + jne .Lcheck_failed + leaq false_text(%rip), %rdx + testl %eax, %eax + je .Lprint_boolean + leaq true_text(%rip), %rdx +.Lprint_boolean: + leaq boolean_format(%rip), %rdi + movq %r12, %rsi + xorl %eax, %eax + call printf@PLT + addq $8, %rsp + popq %r13 + popq %r12 + ret + + .type check_numeral, @function +check_numeral: + pushq %r12 + pushq %r13 + subq $8, %rsp + movq %rdi, %r12 + movl %edx, %r13d + movq %rsi, %rdi + call church_to_int + cmpl %r13d, %eax + jne .Lcheck_failed + leaq numeral_format(%rip), %rdi + movq %r12, %rsi + movl %eax, %edx + xorl %eax, %eax + call printf@PLT + addq $8, %rsp + popq %r13 + popq %r12 + ret + +.Lcheck_failed: + call abort@PLT + ud2 + + .globl main + .type main, @function +main: + subq $8, %rsp + leaq booleans_heading(%rip), %rdi + call puts@PLT + + leaq label_true(%rip), %rdi + leaq TRUE_TERM(%rip), %rsi + movl $1, %edx + call check_boolean + leaq label_false(%rip), %rdi + leaq FALSE_TERM(%rip), %rsi + xorl %edx, %edx + call check_boolean + + leaq NOT_TERM(%rip), %rdi + leaq TRUE_TERM(%rip), %rsi + call apply + leaq label_not_true(%rip), %rdi + movq %rax, %rsi + xorl %edx, %edx + call check_boolean + leaq NOT_TERM(%rip), %rdi + leaq FALSE_TERM(%rip), %rsi + call apply + leaq label_not_false(%rip), %rdi + movq %rax, %rsi + movl $1, %edx + call check_boolean + + leaq AND_TERM(%rip), %rdi + leaq FALSE_TERM(%rip), %rsi + leaq FALSE_TERM(%rip), %rdx + call apply_binary + leaq label_false_and_false(%rip), %rdi + movq %rax, %rsi + xorl %edx, %edx + call check_boolean + leaq AND_TERM(%rip), %rdi + leaq FALSE_TERM(%rip), %rsi + leaq TRUE_TERM(%rip), %rdx + call apply_binary + leaq label_false_and_true(%rip), %rdi + movq %rax, %rsi + xorl %edx, %edx + call check_boolean + leaq AND_TERM(%rip), %rdi + leaq TRUE_TERM(%rip), %rsi + leaq FALSE_TERM(%rip), %rdx + call apply_binary + leaq label_true_and_false(%rip), %rdi + movq %rax, %rsi + xorl %edx, %edx + call check_boolean + leaq AND_TERM(%rip), %rdi + leaq TRUE_TERM(%rip), %rsi + leaq TRUE_TERM(%rip), %rdx + call apply_binary + leaq label_true_and_true(%rip), %rdi + movq %rax, %rsi + movl $1, %edx + call check_boolean + + leaq OR_TERM(%rip), %rdi + leaq FALSE_TERM(%rip), %rsi + leaq FALSE_TERM(%rip), %rdx + call apply_binary + leaq label_false_or_false(%rip), %rdi + movq %rax, %rsi + xorl %edx, %edx + call check_boolean + leaq OR_TERM(%rip), %rdi + leaq FALSE_TERM(%rip), %rsi + leaq TRUE_TERM(%rip), %rdx + call apply_binary + leaq label_false_or_true(%rip), %rdi + movq %rax, %rsi + movl $1, %edx + call check_boolean + leaq OR_TERM(%rip), %rdi + leaq TRUE_TERM(%rip), %rsi + leaq FALSE_TERM(%rip), %rdx + call apply_binary + leaq label_true_or_false(%rip), %rdi + movq %rax, %rsi + movl $1, %edx + call check_boolean + leaq OR_TERM(%rip), %rdi + leaq TRUE_TERM(%rip), %rsi + leaq TRUE_TERM(%rip), %rdx + call apply_binary + leaq label_true_or_true(%rip), %rdi + movq %rax, %rsi + movl $1, %edx + call check_boolean + + leaq numerals_heading(%rip), %rdi + call puts@PLT + leaq label_zero(%rip), %rdi + leaq ZERO_TERM(%rip), %rsi + xorl %edx, %edx + call check_numeral + call one_term + leaq label_one(%rip), %rdi + movq %rax, %rsi + movl $1, %edx + call check_numeral + call two_term + leaq label_two(%rip), %rdi + movq %rax, %rsi + movl $2, %edx + call check_numeral + call three_term + leaq label_three(%rip), %rdi + movq %rax, %rsi + movl $3, %edx + call check_numeral + + leaq ZERO_TERM(%rip), %rdi + call predecessor_of + leaq label_pred_zero(%rip), %rdi + movq %rax, %rsi + xorl %edx, %edx + call check_numeral + call one_term + movq %rax, %rdi + call predecessor_of + leaq label_pred_one(%rip), %rdi + movq %rax, %rsi + xorl %edx, %edx + call check_numeral + call two_term + movq %rax, %rdi + call predecessor_of + leaq label_pred_two(%rip), %rdi + movq %rax, %rsi + movl $1, %edx + call check_numeral + call three_term + movq %rax, %rdi + call predecessor_of + leaq label_pred_three(%rip), %rdi + movq %rax, %rsi + movl $2, %edx + call check_numeral + + xorl %eax, %eax + addq $8, %rsp + ret + + .section .data.rel.ro.local,"aw" + .p2align 3 +TRUE_TERM: + .quad true_first, 0, 0 +FALSE_TERM: + .quad false_first, 0, 0 +NOT_TERM: + .quad not_term_code, 0, 0 +AND_TERM: + .quad and_first, 0, 0 +OR_TERM: + .quad or_first, 0, 0 +ZERO_TERM: + .quad zero_first, 0, 0 +SUCCESSOR_TERM: + .quad successor_first, 0, 0 +PREDECESSOR_TERM: + .quad predecessor_first, 0, 0 +IDENTITY_TERM: + .quad identity_code, 0, 0 +TRUE_MARKER: + .quad identity_code, 0, 0 +FALSE_MARKER: + .quad identity_code, 0, 0 + + .section .rodata +booleans_heading: .asciz "CHURCH BOOLEANS" +numerals_heading: .asciz "CHURCH NUMERALS" +boolean_format: .asciz "%s: %s\n" +numeral_format: .asciz "%s: %d\n" +true_text: .asciz "true" +false_text: .asciz "false" +label_true: .asciz "TRUE" +label_false: .asciz "FALSE" +label_not_true: .asciz "NOT TRUE" +label_not_false: .asciz "NOT FALSE" +label_false_and_false: .asciz "FALSE AND FALSE" +label_false_and_true: .asciz "FALSE AND TRUE" +label_true_and_false: .asciz "TRUE AND FALSE" +label_true_and_true: .asciz "TRUE AND TRUE" +label_false_or_false: .asciz "FALSE OR FALSE" +label_false_or_true: .asciz "FALSE OR TRUE" +label_true_or_false: .asciz "TRUE OR FALSE" +label_true_or_true: .asciz "TRUE OR TRUE" +label_zero: .asciz "ZERO" +label_one: .asciz "ONE" +label_two: .asciz "SUCC ONE" +label_three: .asciz "SUCC (SUCC ONE)" +label_pred_zero: .asciz "PRED ZERO" +label_pred_one: .asciz "PRED ONE" +label_pred_two: .asciz "PRED (SUCC ONE)" +label_pred_three: .asciz "PRED (SUCC (SUCC ONE))" + + .section .note.GNU-stack,"",@progbits diff --git a/languages/a/assembly/test.sh b/languages/a/assembly/test.sh new file mode 100644 index 0000000..655cac2 --- /dev/null +++ b/languages/a/assembly/test.sh @@ -0,0 +1,25 @@ +#!/bin/sh +set -eu + +command -v gcc >/dev/null 2>&1 || exit 42 + +case $(uname -m) in + x86_64|amd64) ;; + *) exit 42 ;; +esac + +directory=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +build_directory=$(mktemp -d) +trap 'rm -rf "$build_directory"' EXIT HUP INT TERM + +gcc \ + -Wall \ + -Wextra \ + -Werror \ + -fPIE \ + -pie \ + "$directory/lambda-core.S" \ + -Wl,-z,noexecstack \ + -o "$build_directory/lambda-core" + +"$build_directory/lambda-core" diff --git a/languages/d/dart/README.md b/languages/d/dart/README.md new file mode 100644 index 0000000..2990ef4 --- /dev/null +++ b/languages/d/dart/README.md @@ -0,0 +1,34 @@ +# Dart Lambda Core + +This implementation expresses the project's Church booleans and Church +numerals as callable Dart values. It includes `TRUE`, `FALSE`, `NOT`, `AND`, +`OR`, `ZERO`, `SUCC`, `PRED`, and `ONE`, then checks every printed example. + +## Requirements + +- A current stable Dart SDK +- No packages or other external dependencies + +The CI workflow installs Dart with Dart's official setup action. + +## Run and verify + +From this directory, run: + +```sh +sh test.sh +``` + +The repository-level `run-tests.sh` compares the program's standard output +with `expected-output.txt`. + +## How the representation works + +Dart cannot directly name the infinitely recursive type `Term = Term -> Term`. +The small `Term` wrapper therefore stores a `Term Function(Term)` and exposes +function-call syntax. Each core definition remains a direct translation of its +lambda-calculus form. The encodings do not store Dart booleans or integers; +only the output observers produce those native values. + +Dart's first-class functions and lexical closure behavior are documented in +the official [functions guide](https://dart.dev/language/functions). diff --git a/languages/d/dart/expected-output.txt b/languages/d/dart/expected-output.txt new file mode 100644 index 0000000..b109aba --- /dev/null +++ b/languages/d/dart/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/d/dart/lambda-core.dart b/languages/d/dart/lambda-core.dart new file mode 100644 index 0000000..35f28c2 --- /dev/null +++ b/languages/d/dart/lambda-core.dart @@ -0,0 +1,139 @@ +final class Term { + const Term(this._function); + + final Term Function(Term) _function; + + Term call(Term argument) => _function(argument); +} + +Term lambda(Term Function(Term) function) => Term(function); + +// TRUE = lambda x. lambda y. x +Term churchTrue() => lambda((x) => lambda((_) => x)); + +// FALSE = lambda x. lambda y. y +Term churchFalse() => lambda((_) => lambda((y) => y)); + +// NOT = lambda b. b FALSE TRUE +Term churchNot() => lambda((boolean) => boolean(churchFalse())(churchTrue())); + +// AND = lambda p. lambda q. p q FALSE +Term churchAnd() => + lambda((left) => lambda((right) => left(right)(churchFalse()))); + +// OR = lambda p. lambda q. p TRUE q +Term churchOr() => + lambda((left) => lambda((right) => left(churchTrue())(right))); + +// ZERO = lambda f. lambda x. x +Term zero() => lambda((_) => lambda((value) => value)); + +// SUCC = lambda n. lambda f. lambda x. f (n f x) +Term successor() => lambda( + (numeral) => lambda( + (function) => lambda((value) => function(numeral(function)(value))), + ), +); + +// PRED = lambda n. lambda f. lambda x. +// n (lambda g. lambda h. h (g f)) (lambda u. x) (lambda a. a) +Term predecessor() => lambda( + (numeral) => lambda( + (function) => lambda((value) { + final step = lambda((g) => lambda((h) => h(g(function)))); + final seed = lambda((_) => value); + final identity = lambda((term) => term); + return numeral(step)(seed)(identity); + }), + ), +); + +Term one() => successor()(zero()); + +// These observers are the only code that converts terms to native values. +bool churchToBool(Term boolean) { + final whenTrue = lambda((term) => term); + final whenFalse = lambda((term) => term); + final selected = boolean(whenTrue)(whenFalse); + + if (identical(selected, whenTrue)) { + return true; + } + if (identical(selected, whenFalse)) { + return false; + } + throw StateError('a Church boolean did not select either argument'); +} + +int churchToInt(Term numeral) { + var applications = 0; + final increment = lambda((term) { + applications += 1; + return term; + }); + final seed = lambda((term) => term); + numeral(increment)(seed); + return applications; +} + +void checkBoolean(String label, Term boolean, bool expected) { + final actual = churchToBool(boolean); + if (actual != expected) { + throw StateError('$label: expected $expected, got $actual'); + } + print('$label: $actual'); +} + +void checkNumeral(String label, Term numeral, int expected) { + final actual = churchToInt(numeral); + if (actual != expected) { + throw StateError('$label: expected $expected, got $actual'); + } + print('$label: $actual'); +} + +void main() { + print('CHURCH BOOLEANS'); + 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); + + print('CHURCH NUMERALS'); + 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/d/dart/test.sh b/languages/d/dart/test.sh new file mode 100644 index 0000000..f371473 --- /dev/null +++ b/languages/d/dart/test.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu + +command -v dart >/dev/null 2>&1 || exit 42 + +directory=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +dart run "$directory/lambda-core.dart" diff --git a/languages/g/groovy/README.md b/languages/g/groovy/README.md new file mode 100644 index 0000000..0bff1a9 --- /dev/null +++ b/languages/g/groovy/README.md @@ -0,0 +1,35 @@ +# Groovy Lambda Core + +This implementation expresses the project's Church booleans and Church +numerals as Groovy closures. It includes `TRUE`, `FALSE`, `NOT`, `AND`, `OR`, +`ZERO`, `SUCC`, `PRED`, and `ONE`, then checks every printed example. + +## Requirements + +- A current Groovy runtime, or a current Gradle installation +- No downloaded modules or other external dependencies + +The test script prefers the `groovy` command. When it is unavailable, it uses +the Groovy runtime embedded in Gradle; Gradle is preinstalled on GitHub's +current `ubuntu-latest` runner. The fallback runs offline and resolves no +packages. + +## Run and verify + +From this directory, run: + +```sh +sh test.sh +``` + +The repository-level `run-tests.sh` compares the program's standard output +with `expected-output.txt`. + +## How the representation works + +Groovy closures are first-class values with lexical capture, so the core terms +map directly to nested, curried closures. The encodings do not store Groovy +booleans or integers; only the output observers produce those native values. + +Groovy's closure semantics are documented in the official +[closures guide](https://docs.groovy-lang.org/latest/html/documentation/core-closures.html). diff --git a/languages/g/groovy/build.gradle b/languages/g/groovy/build.gradle new file mode 100644 index 0000000..b62efa9 --- /dev/null +++ b/languages/g/groovy/build.gradle @@ -0,0 +1,7 @@ +apply from: 'lambda-core.groovy' + +if (project.hasProperty('lambdaCoreBuildDirectory')) { + layout.buildDirectory = file(project.property('lambdaCoreBuildDirectory')) +} + +tasks.register('lambdaCore') diff --git a/languages/g/groovy/expected-output.txt b/languages/g/groovy/expected-output.txt new file mode 100644 index 0000000..b109aba --- /dev/null +++ b/languages/g/groovy/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/g/groovy/lambda-core.groovy b/languages/g/groovy/lambda-core.groovy new file mode 100644 index 0000000..714bfc5 --- /dev/null +++ b/languages/g/groovy/lambda-core.groovy @@ -0,0 +1,111 @@ +// TRUE = lambda x. lambda y. x +def churchTrue = { x -> { ignored -> x } } + +// FALSE = lambda x. lambda y. y +def churchFalse = { ignored -> { y -> y } } + +// NOT = lambda b. b FALSE TRUE +def churchNot = { encodedBoolean -> + encodedBoolean.call(churchFalse).call(churchTrue) +} + +// AND = lambda p. lambda q. p q FALSE +def churchAnd = { left -> { right -> left.call(right).call(churchFalse) } } + +// OR = lambda p. lambda q. p TRUE q +def churchOr = { left -> { right -> left.call(churchTrue).call(right) } } + +// ZERO = lambda f. lambda x. x +def zero = { ignored -> { value -> value } } + +// SUCC = lambda n. lambda f. lambda x. f (n f x) +def successor = { numeral -> + { function -> + { value -> function.call(numeral.call(function).call(value)) } + } +} + +// PRED = lambda n. lambda f. lambda x. +// n (lambda g. lambda h. h (g f)) (lambda u. x) (lambda a. a) +def predecessor = { numeral -> + { function -> + { value -> + def step = { g -> { h -> h.call(g.call(function)) } } + def seed = { ignored -> value } + def identity = { term -> term } + numeral.call(step).call(seed).call(identity) + } + } +} + +def one = successor.call(zero) + +// These observers are the only code that converts terms to native values. +def churchToBool = { encodedBoolean -> + def whenTrue = new Object() + def whenFalse = new Object() + def selected = encodedBoolean.call(whenTrue).call(whenFalse) + + if (selected.is(whenTrue)) { + return true + } + if (selected.is(whenFalse)) { + return false + } + throw new IllegalStateException('a Church boolean did not select either argument') +} + +def churchToInt = { numeral -> + int applications = 0 + def increment = { value -> + applications += 1 + value + } + def seed = new Object() + numeral.call(increment).call(seed) + applications +} + +def checkBoolean = { String label, encodedBoolean, boolean expected -> + boolean actual = churchToBool.call(encodedBoolean) + if (actual != expected) { + throw new IllegalStateException("${label}: expected ${expected}, got ${actual}") + } + println "${label}: ${actual}" +} + +def checkNumeral = { String label, numeral, int expected -> + int actual = churchToInt.call(numeral) + if (actual != expected) { + throw new IllegalStateException("${label}: expected ${expected}, got ${actual}") + } + println "${label}: ${actual}" +} + +println 'CHURCH BOOLEANS' +checkBoolean.call('TRUE', churchTrue, true) +checkBoolean.call('FALSE', churchFalse, false) +checkBoolean.call('NOT TRUE', churchNot.call(churchTrue), false) +checkBoolean.call('NOT FALSE', churchNot.call(churchFalse), true) +checkBoolean.call('FALSE AND FALSE', churchAnd.call(churchFalse).call(churchFalse), false) +checkBoolean.call('FALSE AND TRUE', churchAnd.call(churchFalse).call(churchTrue), false) +checkBoolean.call('TRUE AND FALSE', churchAnd.call(churchTrue).call(churchFalse), false) +checkBoolean.call('TRUE AND TRUE', churchAnd.call(churchTrue).call(churchTrue), true) +checkBoolean.call('FALSE OR FALSE', churchOr.call(churchFalse).call(churchFalse), false) +checkBoolean.call('FALSE OR TRUE', churchOr.call(churchFalse).call(churchTrue), true) +checkBoolean.call('TRUE OR FALSE', churchOr.call(churchTrue).call(churchFalse), true) +checkBoolean.call('TRUE OR TRUE', churchOr.call(churchTrue).call(churchTrue), true) + +println 'CHURCH NUMERALS' +checkNumeral.call('ZERO', zero, 0) +checkNumeral.call('ONE', one, 1) +checkNumeral.call('SUCC ONE', successor.call(one), 2) +checkNumeral.call('SUCC (SUCC ONE)', successor.call(successor.call(one)), 3) +checkNumeral.call('PRED ZERO', predecessor.call(zero), 0) +checkNumeral.call('PRED ONE', predecessor.call(one), 0) +checkNumeral.call('PRED (SUCC ONE)', predecessor.call(successor.call(one)), 1) +checkNumeral.call( + 'PRED (SUCC (SUCC ONE))', + predecessor.call(successor.call(successor.call(one))), + 2 +) diff --git a/languages/g/groovy/test.sh b/languages/g/groovy/test.sh new file mode 100644 index 0000000..f9b258c --- /dev/null +++ b/languages/g/groovy/test.sh @@ -0,0 +1,26 @@ +#!/bin/sh +set -eu + +directory=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) + +if command -v groovy >/dev/null 2>&1; then + exec groovy "$directory/lambda-core.groovy" +fi + +if command -v gradle >/dev/null 2>&1; then + cache_directory=$(mktemp -d) + build_directory=$(mktemp -d) + trap 'rm -rf "$cache_directory" "$build_directory"' EXIT HUP INT TERM + gradle \ + --no-daemon \ + --quiet \ + --console=plain \ + --offline \ + -PlambdaCoreBuildDirectory="$build_directory" \ + --project-cache-dir "$cache_directory" \ + --project-dir "$directory" \ + lambdaCore + exit 0 +fi + +exit 42 diff --git a/languages/r/r/README.md b/languages/r/r/README.md new file mode 100644 index 0000000..6f59789 --- /dev/null +++ b/languages/r/r/README.md @@ -0,0 +1,33 @@ +# R Lambda Core + +This implementation expresses the project's Church booleans and Church +numerals as R closures. It includes `TRUE`, `FALSE`, `NOT`, `AND`, `OR`, +`ZERO`, `SUCC`, `PRED`, and `ONE`, then checks every printed example. + +## Requirements + +- A current R runtime with `Rscript` +- No add-on packages or other external dependencies + +The CI workflow installs the minimal `r-base-core` runtime package. + +## Run and verify + +From this directory, run: + +```sh +sh test.sh +``` + +The repository-level `run-tests.sh` compares the program's standard output +with `expected-output.txt`. + +## How the representation works + +R functions are lexical closures, so the core terms map directly to nested, +curried functions. The encodings do not store R logicals or integers; only the +output observers produce those native values. Distinct environments act as +identity markers when observing booleans. + +The language mechanism is documented in R's official +[`function` reference](https://stat.ethz.ch/R-manual/R-devel/library/base/html/function.html). diff --git a/languages/r/r/expected-output.txt b/languages/r/r/expected-output.txt new file mode 100644 index 0000000..b109aba --- /dev/null +++ b/languages/r/r/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/r/lambda-core.R b/languages/r/r/lambda-core.R new file mode 100644 index 0000000..d5ec6dd --- /dev/null +++ b/languages/r/r/lambda-core.R @@ -0,0 +1,125 @@ +# TRUE = lambda x. lambda y. x +church_true <- function(x) { + force(x) + function(y) x +} + +# FALSE = lambda x. lambda y. y +church_false <- function(x) function(y) y + +# NOT = lambda b. b FALSE TRUE +church_not <- function(boolean) boolean(church_false)(church_true) + +# AND = lambda p. lambda q. p q FALSE +church_and <- function(left) { + force(left) + function(right) left(right)(church_false) +} + +# OR = lambda p. lambda q. p TRUE q +church_or <- function(left) { + force(left) + function(right) left(church_true)(right) +} + +# ZERO = lambda f. lambda x. x +zero <- function(f) function(value) value + +# SUCC = lambda n. lambda f. lambda x. f (n f x) +successor <- function(numeral) { + force(numeral) + function(f) { + force(f) + function(value) f(numeral(f)(value)) + } +} + +# PRED = lambda n. lambda f. lambda x. +# n (lambda g. lambda h. h (g f)) (lambda u. x) (lambda a. a) +predecessor <- function(numeral) { + force(numeral) + function(f) { + force(f) + function(value) { + step <- function(g) { + force(g) + function(h) h(g(f)) + } + seed <- function(u) value + identity <- function(a) a + numeral(step)(seed)(identity) + } + } +} + +one <- successor(zero) + +# These observers are the only code that converts terms to native values. +church_to_bool <- function(boolean) { + when_true <- new.env(parent = emptyenv()) + when_false <- new.env(parent = emptyenv()) + selected <- boolean(when_true)(when_false) + + if (identical(selected, when_true)) { + return(TRUE) + } + if (identical(selected, when_false)) { + return(FALSE) + } + stop("a Church boolean did not select either argument", call. = FALSE) +} + +church_to_int <- function(numeral) { + applications <- 0L + increment <- function(value) { + applications <<- applications + 1L + value + } + seed <- new.env(parent = emptyenv()) + numeral(increment)(seed) + applications +} + +check_boolean <- function(label, boolean, expected) { + actual <- church_to_bool(boolean) + if (!identical(actual, expected)) { + stop(sprintf("%s: expected %s, got %s", label, expected, actual), call. = FALSE) + } + cat(sprintf("%s: %s\n", label, tolower(as.character(actual)))) +} + +check_numeral <- function(label, numeral, expected) { + actual <- church_to_int(numeral) + if (!identical(actual, as.integer(expected))) { + stop(sprintf("%s: expected %d, got %d", label, expected, actual), call. = FALSE) + } + cat(sprintf("%s: %d\n", label, actual)) +} + +cat("CHURCH BOOLEANS\n") +check_boolean("TRUE", church_true, TRUE) +check_boolean("FALSE", church_false, FALSE) +check_boolean("NOT TRUE", church_not(church_true), FALSE) +check_boolean("NOT FALSE", church_not(church_false), TRUE) +check_boolean("FALSE AND FALSE", church_and(church_false)(church_false), FALSE) +check_boolean("FALSE AND TRUE", church_and(church_false)(church_true), FALSE) +check_boolean("TRUE AND FALSE", church_and(church_true)(church_false), FALSE) +check_boolean("TRUE AND TRUE", church_and(church_true)(church_true), TRUE) +check_boolean("FALSE OR FALSE", church_or(church_false)(church_false), FALSE) +check_boolean("FALSE OR TRUE", church_or(church_false)(church_true), TRUE) +check_boolean("TRUE OR FALSE", church_or(church_true)(church_false), TRUE) +check_boolean("TRUE OR TRUE", church_or(church_true)(church_true), TRUE) + +cat("CHURCH NUMERALS\n") +check_numeral("ZERO", zero, 0L) +check_numeral("ONE", one, 1L) +check_numeral("SUCC ONE", successor(one), 2L) +check_numeral("SUCC (SUCC ONE)", successor(successor(one)), 3L) +check_numeral("PRED ZERO", predecessor(zero), 0L) +check_numeral("PRED ONE", predecessor(one), 0L) +check_numeral("PRED (SUCC ONE)", predecessor(successor(one)), 1L) +check_numeral( + "PRED (SUCC (SUCC ONE))", + predecessor(successor(successor(one))), + 2L +) diff --git a/languages/r/r/test.sh b/languages/r/r/test.sh new file mode 100644 index 0000000..3d478c7 --- /dev/null +++ b/languages/r/r/test.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu + +command -v Rscript >/dev/null 2>&1 || exit 42 + +directory=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +Rscript --vanilla "$directory/lambda-core.R" diff --git a/languages/s/swift/README.md b/languages/s/swift/README.md new file mode 100644 index 0000000..4e6da17 --- /dev/null +++ b/languages/s/swift/README.md @@ -0,0 +1,36 @@ +# Swift Lambda Core + +This implementation expresses the project's Church booleans and Church +numerals as callable Swift values. It includes `TRUE`, `FALSE`, `NOT`, `AND`, +`OR`, `ZERO`, `SUCC`, `PRED`, and `ONE`, then checks every printed example. + +## Requirements + +- A current stable Swift compiler +- No packages or other external dependencies + +Swift is preinstalled on GitHub's current `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 test script compiles with warnings promoted to errors in a temporary +directory, runs the binary, and removes the build artifact. The repository +test harness compares its standard output with `expected-output.txt`. + +## How the representation works + +Swift cannot directly name the infinitely recursive type `Term = Term -> +Term`. The small `Term` reference type therefore stores a `(Term) -> Term` +closure and implements `callAsFunction`. Each core definition remains a direct +translation of its lambda-calculus form. The encodings do not store Swift +booleans or integers; only the output observers produce those native values. + +Swift's lexical closure behavior is documented in the official +[closures chapter](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/). diff --git a/languages/s/swift/expected-output.txt b/languages/s/swift/expected-output.txt new file mode 100644 index 0000000..b109aba --- /dev/null +++ b/languages/s/swift/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/swift/lambda-core.swift b/languages/s/swift/lambda-core.swift new file mode 100644 index 0000000..a3de49b --- /dev/null +++ b/languages/s/swift/lambda-core.swift @@ -0,0 +1,145 @@ +final class Term { + private let function: (Term) -> Term + + init(_ function: @escaping (Term) -> Term) { + self.function = function + } + + func callAsFunction(_ argument: Term) -> Term { + function(argument) + } +} + +func lambda(_ function: @escaping (Term) -> Term) -> Term { + Term(function) +} + +// TRUE = lambda x. lambda y. x +func churchTrue() -> Term { + lambda { x in lambda { _ in x } } +} + +// FALSE = lambda x. lambda y. y +func churchFalse() -> Term { + lambda { _ in lambda { y in y } } +} + +// NOT = lambda b. b FALSE TRUE +func churchNot() -> Term { + lambda { boolean in boolean(churchFalse())(churchTrue()) } +} + +// AND = lambda p. lambda q. p q FALSE +func churchAnd() -> Term { + lambda { left in + lambda { right in left(right)(churchFalse()) } + } +} + +// OR = lambda p. lambda q. p TRUE q +func churchOr() -> Term { + lambda { left in + lambda { right in left(churchTrue())(right) } + } +} + +// ZERO = lambda f. lambda x. x +func zero() -> Term { + lambda { _ in lambda { value in value } } +} + +// SUCC = lambda n. lambda f. lambda x. f (n f x) +func successor() -> Term { + lambda { numeral in + lambda { function in + lambda { value in function(numeral(function)(value)) } + } + } +} + +// PRED = lambda n. lambda f. lambda x. +// n (lambda g. lambda h. h (g f)) (lambda u. x) (lambda a. a) +func predecessor() -> Term { + lambda { numeral in + lambda { function in + lambda { value in + let step = lambda { g in + lambda { h in h(g(function)) } + } + let seed = lambda { _ in value } + let identity = lambda { term in term } + return numeral(step)(seed)(identity) + } + } + } +} + +func one() -> Term { + successor()(zero()) +} + +// These observers are the only code that converts terms to native values. +func churchToBool(_ boolean: Term) -> Bool { + let whenTrue = lambda { term in term } + let whenFalse = lambda { term in term } + let selected = boolean(whenTrue)(whenFalse) + + if selected === whenTrue { + return true + } + if selected === whenFalse { + return false + } + preconditionFailure("a Church boolean did not select either argument") +} + +func churchToInt(_ numeral: Term) -> Int { + var applications = 0 + let increment = lambda { term in + applications += 1 + return term + } + let seed = lambda { term in term } + _ = numeral(increment)(seed) + return applications +} + +func checkBoolean(_ label: String, _ boolean: Term, _ expected: Bool) { + let actual = churchToBool(boolean) + precondition(actual == expected, "\(label): expected \(expected), got \(actual)") + print("\(label): \(actual ? "true" : "false")") +} + +func checkNumeral(_ label: String, _ numeral: Term, _ expected: Int) { + let actual = churchToInt(numeral) + precondition(actual == expected, "\(label): expected \(expected), got \(actual)") + print("\(label): \(actual)") +} + +print("CHURCH BOOLEANS") +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) + +print("CHURCH NUMERALS") +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/s/swift/test.sh b/languages/s/swift/test.sh new file mode 100644 index 0000000..d9b5568 --- /dev/null +++ b/languages/s/swift/test.sh @@ -0,0 +1,15 @@ +#!/bin/sh +set -eu + +command -v swiftc >/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 + +swiftc \ + -warnings-as-errors \ + "$directory/lambda-core.swift" \ + -o "$build_directory/lambda-core" + +"$build_directory/lambda-core"