├── editors ├── vim │ ├── indent │ │ └── bruijn.vim │ ├── ftdetect │ │ └── bruijn.vim │ └── README.md ├── kate │ ├── README.md │ └── bruijn.xml └── README.md ├── samples ├── aoc │ ├── 2015 │ │ ├── 01 │ │ │ ├── input │ │ │ ├── output.check │ │ │ └── solve.bruijn │ │ └── 02 │ │ │ ├── input │ │ │ ├── output.check │ │ │ └── solve.bruijn │ ├── 2016 │ │ └── 01 │ │ │ ├── output.check │ │ │ ├── input │ │ │ └── solve.bruijn │ ├── 2017 │ │ └── 01 │ │ │ ├── input │ │ │ ├── output.check │ │ │ └── solve.bruijn │ ├── 2018 │ │ └── 01 │ │ │ ├── input │ │ │ ├── output.check │ │ │ └── solve.bruijn │ ├── 2019 │ │ └── 01 │ │ │ ├── input │ │ │ ├── output.check │ │ │ └── solve.bruijn │ ├── 2020 │ │ └── 01 │ │ │ ├── input │ │ │ ├── output.check │ │ │ └── solve.bruijn │ ├── 2021 │ │ └── 01 │ │ │ ├── output.check │ │ │ ├── input │ │ │ └── solve.bruijn │ ├── 2022 │ │ ├── 02 │ │ │ ├── input │ │ │ ├── output.check │ │ │ └── solve.bruijn │ │ └── 01 │ │ │ ├── output.check │ │ │ ├── input │ │ │ └── solve.bruijn │ └── 2023 │ │ ├── 01 │ │ ├── output.check │ │ ├── input │ │ └── solve.bruijn │ │ └── 02 │ │ ├── output.check │ │ ├── input │ │ └── solve.bruijn ├── rosetta │ ├── empty_program.bruijn │ ├── comments.bruijn │ ├── loops_infinite.bruijn │ ├── halt_and_catch_fire.bruijn │ ├── higher_order_functions.bruijn │ ├── test_a_function.bruijn │ ├── palindrome_detection.bruijn │ ├── sum_and_product_of_an_array.bruijn │ ├── a+b.bruijn │ ├── least_common_multiple.bruijn │ ├── Sierpinski_triangle.bruijn │ ├── greatest_common_divisor.bruijn │ ├── prime_decomposition.bruijn │ ├── fizzBuzz.bruijn │ ├── sorting_quicksort.bruijn │ ├── towers_of_hanoi.bruijn │ ├── 99_bottles_of_beer.bruijn │ ├── levenshtein_distance.bruijn │ ├── y_combinator.bruijn │ ├── compare_a_list_of_strings.bruijn │ ├── de_bruijn_sequence.bruijn │ ├── determine_sentence_type.bruijn │ ├── luhn_test_of_credit_card_numbers.bruijn │ ├── mutual_recursion.bruijn │ ├── ackermann_function.bruijn │ ├── harmonic_series.bruijn │ ├── function_composition.bruijn │ ├── fibonacci_sequence.bruijn │ ├── binary_search.bruijn │ ├── hailstone_sequence.bruijn │ ├── hamming_numbers.bruijn │ ├── validate_international_securities_identification_number.bruijn │ ├── special_factorials.bruijn │ ├── Universal_Lambda_Machine.bruijn │ ├── ternary_logic.bruijn │ ├── variadic_fixed-point_combinator.bruijn │ └── balanced_ternary.bruijn ├── euler │ ├── 005.bruijn │ ├── 063.bruijn │ ├── 003.bruijn │ ├── 016.bruijn │ ├── 048.bruijn │ ├── 007.bruijn │ ├── 001.bruijn │ ├── 006.bruijn │ ├── 013.bruijn │ ├── 002.bruijn │ ├── 012.bruijn │ ├── 014.bruijn │ ├── 009.bruijn │ └── 004.bruijn ├── fun │ ├── quine.bruijn │ ├── graham.bruijn │ ├── IO.bruijn │ ├── gen.bruijn │ ├── thue-morse.bruijn │ ├── halting_problem.bruijn │ ├── goldbach.bruijn │ ├── huge.bruijn │ ├── deepthought_state.bruijn │ ├── rng-state.bruijn │ ├── jottary.bruijn │ ├── wilsons_primes.bruijn │ ├── pi-rational.bruijn │ ├── mutrec.bruijn │ ├── encode.bruijn │ ├── interpreter.bruijn │ ├── recursion-schemes.bruijn │ ├── minibruijn.bruijn │ └── collatz.bruijn ├── readme.md └── test_all.sh ├── docs ├── .gitignore ├── res │ ├── logo.png │ ├── qlogo.png │ └── iosevka.woff2 ├── readme.md ├── gen.sh ├── wiki_src │ ├── introduction │ │ ├── philosophy.md │ │ ├── animations.md │ │ ├── installation.md │ │ ├── setup.md │ │ └── lambda-calculus.md │ ├── index.md │ ├── coding │ │ ├── uniform-function-call-syntax.md │ │ ├── currying.md │ │ ├── prefix.md │ │ ├── IO.md │ │ ├── compilation.md │ │ ├── mixfix.md │ │ ├── laziness.md │ │ ├── test-driven-development.md │ │ ├── style.md │ │ ├── REPL.md │ │ ├── combinators.md │ │ ├── recursion.md │ │ ├── examples.md │ │ └── meta-programming.md │ ├── technical │ │ ├── performance.md │ │ └── reduction.md │ └── custom.css ├── content.css ├── index.css ├── content.template ├── std.template ├── code.css ├── samples.template ├── genstd.sh ├── gensamples.sh ├── mkdocs.yml └── style.css ├── .gitattributes ├── Setup.hs ├── benchmarks ├── by-n │ ├── .gitignore │ ├── fac.bruijn │ ├── omega.bruijn │ ├── metafac.bruijn │ ├── prime.bruijn │ ├── run.sh │ └── plot.py └── standalone │ └── optimization.bruijn ├── .gitignore ├── std ├── List.bruijn ├── Logic.bruijn ├── Monad.bruijn ├── Imperative.bruijn ├── Number │ ├── Dezani.bruijn │ ├── Wadsworth.bruijn │ ├── SK.bruijn │ ├── Scott.bruijn │ ├── Pairing.bruijn │ ├── Tuple.bruijn │ ├── Bruijn.bruijn │ ├── Conversion.bruijn │ └── Parigot.bruijn ├── IO.bruijn ├── Set.bruijn ├── test_all.sh ├── Set │ ├── Number.bruijn │ └── String.bruijn ├── Number.bruijn ├── Generic │ ├── Monad.bruijn │ ├── Schemes.bruijn │ └── Number.bruijn ├── Monad │ ├── State.bruijn │ ├── List.bruijn │ └── Parser.bruijn ├── List │ ├── Parigot.bruijn │ └── ChurchFold.bruijn ├── Map.bruijn ├── Box.bruijn ├── Logic │ ├── Ternary.bruijn │ ├── Linear.bruijn │ └── Binary.bruijn ├── generate_map.py ├── Pair.bruijn ├── Tree │ └── Rose.bruijn ├── Option.bruijn ├── AIT │ └── Beavers.bruijn ├── Char.bruijn ├── Result.bruijn ├── Math │ └── Complex.bruijn └── Tuples.bruijn ├── config ├── src ├── Reducer.hs ├── Config.hs ├── Reducer │ └── HigherOrder.hs ├── Binary.hs ├── Humanification.hs ├── Target.hs └── Error.hs ├── stack.yaml.lock ├── .github └── workflows │ ├── ci.yml │ └── gen.yml ├── license ├── broogle.sh ├── package.yaml ├── app └── Main.hs ├── stack.yaml └── readme.md /editors/vim/indent/bruijn.vim: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/aoc/2015/01/input: -------------------------------------------------------------------------------- 1 | ()()) 2 | -------------------------------------------------------------------------------- /samples/aoc/2017/01/input: -------------------------------------------------------------------------------- 1 | 91212129 2 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | std/ 2 | wiki/ 3 | samples/ 4 | -------------------------------------------------------------------------------- /samples/aoc/2015/02/input: -------------------------------------------------------------------------------- 1 | 2x3x4 2 | 1x1x10 3 | -------------------------------------------------------------------------------- /samples/aoc/2016/01/output.check: -------------------------------------------------------------------------------- 1 | ?> 252t 2 | -------------------------------------------------------------------------------- /samples/aoc/2022/02/input: -------------------------------------------------------------------------------- 1 | A Y 2 | B X 3 | C Z -------------------------------------------------------------------------------- /samples/aoc/2023/01/output.check: -------------------------------------------------------------------------------- 1 | ?> 142t 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.bruijn linguist-language=Bruijn 2 | -------------------------------------------------------------------------------- /samples/rosetta/empty_program.bruijn: -------------------------------------------------------------------------------- 1 | main [0] 2 | -------------------------------------------------------------------------------- /samples/rosetta/comments.bruijn: -------------------------------------------------------------------------------- 1 | # This is a comment 2 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /benchmarks/by-n/.gitignore: -------------------------------------------------------------------------------- 1 | temp.bruijn 2 | *.json 3 | *.png 4 | -------------------------------------------------------------------------------- /samples/aoc/2018/01/input: -------------------------------------------------------------------------------- 1 | +7 2 | +7 3 | -2 4 | -7 5 | -4 6 | -------------------------------------------------------------------------------- /samples/aoc/2019/01/input: -------------------------------------------------------------------------------- 1 | 12 2 | 14 3 | 1969 4 | 100756 5 | -------------------------------------------------------------------------------- /samples/aoc/2015/01/output.check: -------------------------------------------------------------------------------- 1 | ?> -1/6 (approx. -0.16666667) 2 | -------------------------------------------------------------------------------- /samples/aoc/2015/02/output.check: -------------------------------------------------------------------------------- 1 | ?> 101/49 (approx. 2.06122449) 2 | -------------------------------------------------------------------------------- /samples/aoc/2017/01/output.check: -------------------------------------------------------------------------------- 1 | ?> 9/7 (approx. 1.28571429) 2 | -------------------------------------------------------------------------------- /samples/aoc/2018/01/output.check: -------------------------------------------------------------------------------- 1 | ?> 1/15 (approx. 0.06666667) 2 | -------------------------------------------------------------------------------- /samples/aoc/2021/01/output.check: -------------------------------------------------------------------------------- 1 | ?> 7/6 (approx. 1.16666667) 2 | -------------------------------------------------------------------------------- /samples/aoc/2022/02/output.check: -------------------------------------------------------------------------------- 1 | ?> 15/13 (approx. 1.15384615) 2 | -------------------------------------------------------------------------------- /samples/aoc/2023/02/output.check: -------------------------------------------------------------------------------- 1 | ?> 8/2287 (approx. 0.00349803) 2 | -------------------------------------------------------------------------------- /samples/aoc/2019/01/output.check: -------------------------------------------------------------------------------- 1 | ?> 34241/51317 (approx. 0.66724477) 2 | -------------------------------------------------------------------------------- /samples/aoc/2022/01/output.check: -------------------------------------------------------------------------------- 1 | ?> 24000/45001 (approx. 0.53332148) 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | *~ 3 | std/All.bruijn 4 | samples/All.bruijn 5 | -------------------------------------------------------------------------------- /samples/aoc/2020/01/input: -------------------------------------------------------------------------------- 1 | 1721 2 | 979 3 | 366 4 | 299 5 | 675 6 | 1456 7 | -------------------------------------------------------------------------------- /samples/aoc/2020/01/output.check: -------------------------------------------------------------------------------- 1 | ?> 514579/241861951 (approx. 0.00212757) 2 | -------------------------------------------------------------------------------- /samples/aoc/2023/01/input: -------------------------------------------------------------------------------- 1 | 1abc2 2 | pqr3stu8vwx 3 | a1b2c3d4e5f 4 | treb7uchet -------------------------------------------------------------------------------- /editors/vim/ftdetect/bruijn.vim: -------------------------------------------------------------------------------- 1 | au BufRead,BufNewFile *.bruijn set filetype=bruijn 2 | -------------------------------------------------------------------------------- /docs/res/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marvinborner/bruijn/HEAD/docs/res/logo.png -------------------------------------------------------------------------------- /docs/res/qlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marvinborner/bruijn/HEAD/docs/res/qlogo.png -------------------------------------------------------------------------------- /benchmarks/by-n/fac.bruijn: -------------------------------------------------------------------------------- 1 | # 0 10 40 2 | 3 | :import std/Math . 4 | 5 | main [fac (+${BENCH_N})] 6 | -------------------------------------------------------------------------------- /docs/res/iosevka.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marvinborner/bruijn/HEAD/docs/res/iosevka.woff2 -------------------------------------------------------------------------------- /benchmarks/by-n/omega.bruijn: -------------------------------------------------------------------------------- 1 | # 0 7 2 | 3 | :import std/Combinator . 4 | 5 | main [ω (+${BENCH_N}u)] 6 | -------------------------------------------------------------------------------- /benchmarks/standalone/optimization.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Math . 2 | 3 | main [(fac (+100)) + (fac (+100))] 4 | -------------------------------------------------------------------------------- /samples/aoc/2021/01/input: -------------------------------------------------------------------------------- 1 | 199 2 | 200 3 | 208 4 | 210 5 | 200 6 | 207 7 | 240 8 | 269 9 | 260 10 | 263 -------------------------------------------------------------------------------- /samples/rosetta/loops_infinite.bruijn: -------------------------------------------------------------------------------- 1 | :import std/String . 2 | 3 | main [spam spam] 4 | spam ["SPAM\n" ++ (0 0)] 5 | -------------------------------------------------------------------------------- /benchmarks/by-n/metafac.bruijn: -------------------------------------------------------------------------------- 1 | # 0 5 20 2 | 3 | :import std/Math . 4 | :import std/Meta . 5 | 6 | main [!`(fac (+${BENCH_N}))] 7 | -------------------------------------------------------------------------------- /benchmarks/by-n/prime.bruijn: -------------------------------------------------------------------------------- 1 | # 0 40 200 2 | 3 | :import std/Math . 4 | :import std/List . 5 | 6 | main [primes !! (+${BENCH_N})] 7 | -------------------------------------------------------------------------------- /samples/rosetta/halt_and_catch_fire.bruijn: -------------------------------------------------------------------------------- 1 | # invalid test 2 | # :test ([[0]]) ([[1]]) 3 | 4 | # or infinite loop 5 | main [[0 0] [0 0]] 6 | -------------------------------------------------------------------------------- /samples/aoc/2022/01/input: -------------------------------------------------------------------------------- 1 | 1000 2 | 2000 3 | 3000 4 | 5 | 4000 6 | 7 | 5000 8 | 6000 9 | 10 | 7000 11 | 8000 12 | 9000 13 | 14 | 10000 15 | -------------------------------------------------------------------------------- /samples/rosetta/higher_order_functions.bruijn: -------------------------------------------------------------------------------- 1 | first [0 [[0]]] 2 | 3 | second [first [[1]]] 4 | 5 | :test (second) ([[[[0]]]]) 6 | 7 | main [[0]] 8 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # Docs 2 | 3 | These files get translated to HTML. Visit the compiled 4 | [docs](https://bruijn.marvinborner.de) for the full experience. 5 | -------------------------------------------------------------------------------- /samples/rosetta/test_a_function.bruijn: -------------------------------------------------------------------------------- 1 | :import std/String . 2 | 3 | main [<~>0 =? 0] 4 | 5 | :test (main "tacocat") ([[1]]) 6 | :test (main "bruijn") ([[0]]) 7 | -------------------------------------------------------------------------------- /std/List.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | # this is just a reference to the Church implementation 3 | 4 | :input std/List/Church 5 | -------------------------------------------------------------------------------- /std/Logic.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | # this is just a reference to the boolean implementation 3 | 4 | :input std/Logic/Binary 5 | -------------------------------------------------------------------------------- /std/Monad.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | # just a reference to the list monad (default interface) 3 | 4 | :input std/Monad/List 5 | -------------------------------------------------------------------------------- /samples/rosetta/palindrome_detection.bruijn: -------------------------------------------------------------------------------- 1 | :import std/String . 2 | 3 | main [<~>0 =? 0] 4 | 5 | :test (main "tacocat") ([[1]]) 6 | :test (main "bruijn") ([[0]]) 7 | -------------------------------------------------------------------------------- /samples/rosetta/sum_and_product_of_an_array.bruijn: -------------------------------------------------------------------------------- 1 | :import std/List . 2 | :import std/Math . 3 | 4 | arr (+1) : ((+2) : ((+3) : {}(+4))) 5 | 6 | main [∑arr : ∏arr] 7 | -------------------------------------------------------------------------------- /docs/gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env bash 2 | 3 | ./genstd.sh 4 | echo "std done" 5 | 6 | ./gensamples.sh 7 | echo "samples done" 8 | 9 | mkdocs build 10 | echo "wiki done" 11 | -------------------------------------------------------------------------------- /samples/euler/005.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Math . 2 | :import std/List . 3 | 4 | solve [foldr1 lcm ({ (+1) → 0 })] 5 | 6 | :test ((solve (+10)) =? (+2520)) ([[1]]) 7 | 8 | main [solve (+20)] 9 | -------------------------------------------------------------------------------- /samples/euler/063.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Math . 3 | :import std/List . 4 | 5 | main [∑([y [[0 =? ++(log (+10) (2 ** 0)) (1 ++0) --0]] (+1)] <$> ({ (+1) → (+9) }))] 6 | -------------------------------------------------------------------------------- /samples/rosetta/a+b.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/String . 3 | :import std/Number . 4 | :import std/Char C 5 | 6 | main (split-by (C.eq? ' ')) → &(add ⋔ string→number) 7 | -------------------------------------------------------------------------------- /samples/euler/003.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Math . 3 | 4 | solve factors → lmax 5 | 6 | :test ((solve (+13195)) =? (+29)) ([[1]]) 7 | 8 | main [solve (+600851475143)] 9 | -------------------------------------------------------------------------------- /samples/fun/quine.bruijn: -------------------------------------------------------------------------------- 1 | # "bruijn -b quine.bruijn >quine" 2 | # "cat quine | bruijn -e quine" 3 | # compare with "od -t u1 quine" 4 | # duplicates input, John Tromp 5 | 6 | main [0 ([0 0] [[[[[0 3 (2 (4 4) 1)]]]]]) 0] 7 | -------------------------------------------------------------------------------- /samples/aoc/2023/01/solve.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/String . 3 | :import std/Math . 4 | :import std/Char . 5 | 6 | part1 (filter numeric?) → [^0 : {}(_0)] → string→number 7 | 8 | main [∑(part1 <$> (lines 0))] 9 | -------------------------------------------------------------------------------- /samples/fun/graham.bruijn: -------------------------------------------------------------------------------- 1 | # bruijn graham.bruijn 2 | # beats Graham's number in less than 8 byte blc 3 | # https://codegolf.stackexchange.com/a/219734 4 | # (probably won't finish :D) 5 | 6 | main [[0 (0 [[[0 2 1 0]]] 0) 0 0] (+2u)] 7 | -------------------------------------------------------------------------------- /samples/rosetta/least_common_multiple.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Math . 2 | 3 | lcm [[=?1 1 (=?0 0 |(1 / (gcd 1 0) ⋅ 0))]] 4 | 5 | :test ((lcm (+12) (+18)) =? (+36)) ([[1]]) 6 | :test ((lcm (+42) (+25)) =? (+1050)) ([[1]]) 7 | 8 | main [[0]] 9 | -------------------------------------------------------------------------------- /samples/euler/016.bruijn: -------------------------------------------------------------------------------- 1 | # TODO: digit-sum/quot-rem is still the bottleneck 2 | 3 | :import std/Combinator . 4 | :import std/Math . 5 | 6 | solve (pow (+2)) → digit-sum 7 | 8 | :test ((solve (+15)) =? (+26)) ([[1]]) 9 | 10 | main [solve (+1000)] 11 | -------------------------------------------------------------------------------- /samples/euler/048.bruijn: -------------------------------------------------------------------------------- 1 | # TODO: also very slow 2 | 3 | :import std/Combinator . 4 | :import std/Math . 5 | 6 | solve [∑ (+1) → 0 | [pow-mod 0 0 (+10000000000)]] 7 | 8 | :test ((solve (+10)) =? (+405071317)) ([[1]]) 9 | 10 | main [solve (+1000)] 11 | -------------------------------------------------------------------------------- /samples/rosetta/Sierpinski_triangle.bruijn: -------------------------------------------------------------------------------- 1 | y [[1 (0 0)] [1 (0 0)]] 2 | 3 | # infinite depth 4 | triangle [y [[0 1 [[0]] 1 1]]] 5 | 6 | :import std/Number . 7 | 8 | # limited depth 9 | triangle-n [y [[[[1 0 [[0]] 0 0] (=?1 [[1]] (2 --1))]]] (+7)] 10 | -------------------------------------------------------------------------------- /samples/euler/007.bruijn: -------------------------------------------------------------------------------- 1 | # TODO: Write faster primes function 2 | 3 | :import std/Combinator . 4 | :import std/Math . 5 | :import std/List . 6 | 7 | solve dec → (index primes) 8 | 9 | :test ((solve (+6)) =? (+13)) ([[1]]) 10 | 11 | main [solve (+10001)] 12 | -------------------------------------------------------------------------------- /samples/fun/IO.bruijn: -------------------------------------------------------------------------------- 1 | # read two lines using monadic I/O 2 | # printf "i love\nmonads\n" | bruijn IO.bruijn 3 | 4 | :import std/IO . 5 | 6 | main get-line >>= [get-line >>= [return [0 2 1]]] 7 | 8 | :test (main "i love\nmonads\n") ([0 [0 "i love" "monads"] [[0]]]) 9 | -------------------------------------------------------------------------------- /samples/aoc/2022/01/solve.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Math . 3 | :import std/String . 4 | 5 | main blocks → (map (map string→number)) → (map sum) → sort-desc → res 6 | res [^0 : (^0 + ^(~0) + ^(~(~0)))] 7 | blocks [split-list-by (eq? "\n") (lines 0)] 8 | -------------------------------------------------------------------------------- /samples/euler/001.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/List . 3 | :import std/Math . 4 | 5 | solve [∑(crit <#> ({ (+0) → --0 }))] 6 | crit [ψ m ((mod 0) → zero?) (+3) (+5)] 7 | 8 | :test ((solve (+10)) =? (+23)) ([[1]]) 9 | 10 | main [solve (+1000)] 11 | -------------------------------------------------------------------------------- /samples/rosetta/greatest_common_divisor.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Number . 3 | 4 | gcd y [[[=?0 1 (2 0 (1 % 0))]]] 5 | 6 | :test ((gcd (+2) (+4)) =? (+2)) ([[1]]) 7 | :test ((gcd (+10) (+5)) =? (+5)) ([[1]]) 8 | :test ((gcd (+3) (+8)) =? (+1)) ([[1]]) 9 | -------------------------------------------------------------------------------- /std/Imperative.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2025 Marvin Borner 2 | # helpful functions for programming in a more imperative style 3 | 4 | :input std/Monad/State 5 | 6 | when [[1 0 (pure empty)]] 7 | empty [[0]] 8 | 9 | while y [[[1 >>= [when 0 (1 >> (3 2 1))]]]] 10 | -------------------------------------------------------------------------------- /samples/aoc/2015/01/solve.bruijn: -------------------------------------------------------------------------------- 1 | :import std/List . 2 | :import std/Number . 3 | :import std/Char C 4 | 5 | walk scanl floor (+0) 6 | floor [[(C.eq? 0 '(') ++1 --1]] 7 | 8 | main [parts (walk instructions)] 9 | instructions init 0 10 | parts [(last 0) : (find-index >= [(C.eq? 0 '\n') (pure [[0]]) (1 >>= [pure [0 2 1]])]] 13 | -------------------------------------------------------------------------------- /samples/fun/halting_problem.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Logic . 2 | 3 | # hypothetical halting decider (shall not loop) 4 | # assuming (Program Arg) reduces to boolean 5 | halting [[halts (1 0)]] ⧗ Program → Arg → Boolean 6 | halts [0 true false] 7 | 8 | # basically y combinator 9 | # paradox! 10 | e halting [¬(halting 0 0)] [¬(halting 0 0)] 11 | -------------------------------------------------------------------------------- /samples/euler/002.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/List . 3 | :import std/Math . 4 | 5 | # could be arbitrary >34, chosen by manual min search 6 | lim (+34) 7 | 8 | solve [∑(crit <#> (take lim fibs))] 9 | crit φ [[0 1 0]] even? (\lt? 0) 10 | 11 | :test ((solve (+4000)) =? (+3382)) ([[1]]) 12 | 13 | main [solve (+4000000)] 14 | -------------------------------------------------------------------------------- /samples/aoc/2023/02/input: -------------------------------------------------------------------------------- 1 | Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green 2 | Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue 3 | Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red 4 | Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red 5 | Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green -------------------------------------------------------------------------------- /docs/wiki_src/introduction/philosophy.md: -------------------------------------------------------------------------------- 1 | # Philosophy 2 | 3 | - minimal, proven core 4 | - abstraction from hardware architecture 5 | - permaculture 6 | - small, expressive functions 7 | - style consistency 8 | - thoroughly tested library (700+ tests!) 9 | - API consistency 10 | - implicit parallelisation 11 | - shared evaluation 12 | -------------------------------------------------------------------------------- /docs/content.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Iosevka"; 3 | src: url("iosevka.woff2") format("woff2"); 4 | } 5 | 6 | body { 7 | font-family: "Iosevka", monospace; 8 | background-color: #222222; 9 | color: #cccccc; 10 | font-size: 1.3em; 11 | } 12 | 13 | a { 14 | color: #cccccc; 15 | } 16 | 17 | pre { 18 | margin: 0; 19 | padding: .5em; 20 | } 21 | -------------------------------------------------------------------------------- /samples/euler/012.bruijn: -------------------------------------------------------------------------------- 1 | # TODO: Find faster algorithm 2 | 3 | :import std/Combinator . 4 | :import std/Math . 5 | 6 | factors [[y [[[(1 ? 3 1 (2 (1 + 0) ++0)]]] (+0) (+0)] 9 | 10 | :test ((solve (+5)) =? (+28)) ([[1]]) 11 | 12 | main [solve (+500)] 13 | -------------------------------------------------------------------------------- /samples/rosetta/prime_decomposition.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/List . 3 | :import std/Math . 4 | 5 | factors \divs primes 6 | divs y [[&[[&[[3 ⋅ 3 >? 4 case-1 (=?0 case-2 case-3)]] (quot-rem 2 1)]]]] 7 | case-1 4 >? (+1) {}4 empty 8 | case-2 3 : (5 1 (3 : 2)) 9 | case-3 5 4 2 10 | 11 | main [factors <$> ({ (+42) → (+50) })] 12 | -------------------------------------------------------------------------------- /samples/rosetta/fizzBuzz.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/String . 3 | :import std/Number . 4 | 5 | main [y [[0 =? (+101) case-end case-rec]] (+1)] 6 | case-rec str ++ "\n" ++ (1 ++0) 7 | str fizzbuzz "FizzBuzz" (fizz "Fizz" (buzz "Buzz" (number→string 0))) 8 | fizz =?(0 % (+3)) 9 | buzz =?(0 % (+5)) 10 | fizzbuzz fizz buzz fizz 11 | case-end empty 12 | -------------------------------------------------------------------------------- /samples/rosetta/sorting_quicksort.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Number . 3 | :import std/List . 4 | 5 | sort y [[0 [[[case-sort]]] case-end]] 6 | case-sort (4 lesser) ++ (2 : (4 greater)) 7 | lesser (\lt? 2) <#> 1 8 | greater (\ge? 2) <#> 1 9 | case-end empty 10 | 11 | :test (sort ((+3) : ((+2) : {}(+1)))) ((+1) : ((+2) : {}(+3))) 12 | 13 | main [[0]] 14 | -------------------------------------------------------------------------------- /samples/rosetta/towers_of_hanoi.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Number . 3 | :import std/String . 4 | 5 | hanoi y [[[[=?2 empty go]]]] 6 | go [(4 --3 2 0) ++ str ++ (4 --3 0 1)] ((+6) - 1 - 0) 7 | str "Move " ++ disk ++ " from " ++ source ++ " to " ++ destination ++ "\n" 8 | disk number→string 3 9 | source number→string 2 10 | destination number→string 1 11 | -------------------------------------------------------------------------------- /samples/aoc/2017/01/solve.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/String . 3 | :import std/Number . 4 | :import std/Char C 5 | :import std/Pair P 6 | 7 | self [[(zip 0) ∘ (drop 1) ∘ cycle 0]] 8 | 9 | main [φ (cons ⋔ solve) part1 part2 (C.char→number <$> (trim 0))] 10 | solve (filter (P.uncurry eq?)) → (foldl \(g add head) (+0)) 11 | part1 self (+1) 12 | part2 self /²(length 0) 13 | -------------------------------------------------------------------------------- /samples/rosetta/99_bottles_of_beer.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Number . 3 | :import std/String . 4 | 5 | main [y [[=?0 case-end case-rec]] (+99)] 6 | case-rec n ++ t1 ++ n ++ t2 ++ t3 ++ n ++ t1 ++ "\n" ++ (1 --0) 7 | n number→string 0 8 | t1 " bottles of beer on the wall\n" 9 | t2 " bottles of beer\n" 10 | t3 "Take one down, pass it around\n" 11 | case-end empty 12 | -------------------------------------------------------------------------------- /docs/wiki_src/index.md: -------------------------------------------------------------------------------- 1 | # Bruijn wiki 2 | 3 | Bruijn is basically pure lambda calculus with a bit of syntactic sugar. 4 | It has no side effects, primitive functions or datatypes. 5 | 6 | Browse this wiki by using the search or by clicking *next* or any of the 7 | links. If you prefer free-flowing text, read the blog post [The bruijn 8 | programming language](https://text.marvinborner.de/2023-04-06-01.html). 9 | -------------------------------------------------------------------------------- /samples/aoc/2020/01/solve.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/String . 3 | :import std/Math . 4 | 5 | solve (find (sum → (eq? (+2020)))) → product 6 | 7 | main trim → lines → (map string→number) → (φ (cons ⋔ solve) part1 part2) 8 | part1 [[[~1 : {}0] <$> (drop ^0 1)] <++> (enumerate 0)] 9 | part2 [[[[~2 : (~1 : {}0)] <$> (drop ^0 2)] <++> (enumerate (drop ^0 1))] <++> (enumerate 0)] 10 | -------------------------------------------------------------------------------- /samples/aoc/2018/01/solve.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/String . 3 | :import std/Math . 4 | :import std/Set S 5 | 6 | part1 ∑‣ ⧗ (List FrequencyDiff) → Frequency 7 | 8 | part2 z [[[[rec]]]] S.empty (+0) 9 | rec S.has? 1 2 1 go 10 | go 3 (S.add 1 2) (1 + ^0) ~0 11 | 12 | main [parts nums] 13 | nums string→signed-number <$> (init (lines 0)) 14 | parts [(part1 0) : (part2 (cycle 0))] 15 | -------------------------------------------------------------------------------- /samples/rosetta/levenshtein_distance.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Char C 3 | :import std/List . 4 | :import std/Math . 5 | 6 | levenshtein y [[[∅?1 ∀0 (∅?0 ∀1 (0 (1 [[[[go]]]])))]]] 7 | go (C.eq? 3 1) (6 2 0) ++(lmin ((6 2 0) : ((6 5 0) : {}(6 2 4)))) 8 | 9 | :test ((levenshtein "rosettacode" "raisethysword") =? (+8)) ([[1]]) 10 | :test ((levenshtein "kitten" "sitting") =? (+3)) ([[1]]) 11 | -------------------------------------------------------------------------------- /samples/rosetta/y_combinator.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Number . 2 | 3 | # sage bird combinator 4 | y [[1 (0 0)] [1 (0 0)]] 5 | 6 | # factorial using y 7 | factorial y [[=?0 (+1) (0 ⋅ (1 --0))]] 8 | 9 | :test ((factorial (+6)) =? (+720)) ([[1]]) 10 | 11 | # (very slow) fibonacci using y 12 | fibonacci y [[0 (lines 0) 10 | parts [∑(part1 0) : ∑(part2 0)] 11 | 12 | # equivalent alternative using std/Pair P 13 | # part1 [length (filter (P.uncurry … (C.?nub 1)) 11 | case-end drop 0 1 12 | 13 | :test (de-bruijn "abcd" (+2)) ("dccaadbbacbdabcd") 14 | 15 | main [[0]] 16 | -------------------------------------------------------------------------------- /samples/fun/goldbach.bruijn: -------------------------------------------------------------------------------- 1 | # Tromp's version, will reduce to [0] iff Goldbach conjecture is false 2 | 3 | :import std/Combinator . 4 | 5 | zero [[1]] 6 | 7 | one [[0]] 8 | 9 | sieve y [[[0 one (2 sn1 f)]]] 10 | f y [sn2 0] 11 | sn2 [[0 (0 4 1)] [[[[0 2 (1 3)]]]]] 12 | sn1 [[0 (0 3 1)] [[[[0 2 (1 3)]]]]] 13 | 14 | zeroS [[[[0 zero (1 3)]]]] 15 | 16 | primes sieve zeroS 17 | 18 | check y [[[[[primes 0 (1 (4 0))] testp1]]]] 19 | testp1 [0 0 2 [0 4] 0] 20 | 21 | main [primes (check [[[[0]]]])] 22 | -------------------------------------------------------------------------------- /samples/rosetta/determine_sentence_type.bruijn: -------------------------------------------------------------------------------- 1 | :import std/List . 2 | :import std/Char . 3 | 4 | determine [∅?0 '?' ([(0 =? '?' 'Q' (0 =? '.' 'S' (0 =? '!' 'E' 'N')))] _0)] 5 | 6 | :test (determine empty) ('?') 7 | :test (determine "hi there, how are you today?") ('Q') 8 | :test (determine "I'd like to present to you the washing machine 9001.") ('S') 9 | :test (determine "You have been nominated to win one of these!") ('E') 10 | :test (determine "Just make sure you don't break it") ('N') 11 | 12 | main [[0]] 13 | -------------------------------------------------------------------------------- /samples/rosetta/luhn_test_of_credit_card_numbers.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Math . 3 | :import std/List . 4 | 5 | luhn number→list → reverse → check → (\mod (+10)) → zero? 6 | check y [[[[0 [[[6 \5 (4 + (5 odd even)) 1]]] 1]]]] k (+0) 7 | odd 2 8 | even digit-sum (2 ⋅ (+2)) 9 | 10 | :test (luhn (+61789372994)) ([[1]]) 11 | :test (luhn (+49927398716)) ([[1]]) 12 | :test (luhn (+49927398717)) ([[0]]) 13 | :test (luhn (+1234567812345678)) ([[0]]) 14 | :test (luhn (+1234567812345670)) ([[1]]) 15 | -------------------------------------------------------------------------------- /samples/rosetta/mutual_recursion.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Number . 3 | :import std/List . 4 | 5 | f' [[[=?0 (+1) (0 - (1 (2 --0)))]]] 6 | 7 | m' [[[=?0 (+0) (0 - (2 (1 --0)))]]] 8 | 9 | f ^(y* (f' : {}m')) 10 | 11 | m _(y* (f' : {}m')) 12 | 13 | :test ((f (+0)) =? (+1)) ([[1]]) 14 | :test ((m (+0)) =? (+0)) ([[1]]) 15 | :test ((f (+4)) =? (+3)) ([[1]]) 16 | :test ((m (+4)) =? (+2)) ([[1]]) 17 | :test ((f (+15)) =? (+9)) ([[1]]) 18 | :test ((m (+15)) =? (+9)) ([[1]]) 19 | 20 | main [[0]] 21 | -------------------------------------------------------------------------------- /docs/index.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Iosevka"; 3 | src: url("iosevka.woff2") format("woff2"); 4 | } 5 | 6 | body { 7 | font-family: "Iosevka", monospace; 8 | background-color: #222222; 9 | color: #cccccc; 10 | font-size: 1.2em; 11 | } 12 | 13 | a { 14 | color: #cccccc; 15 | } 16 | 17 | ol { 18 | max-width: 500px; 19 | } 20 | 21 | .stats { 22 | float: right; 23 | } 24 | 25 | .com { 26 | color: #ff64bd; 27 | } 28 | 29 | @media (max-width: 800px) { 30 | .stats { 31 | display: none; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/fun/huge.bruijn: -------------------------------------------------------------------------------- 1 | # "bruijn huge.bruijn" 2 | # some fun fast growing functions per blc length 3 | 4 | # 55 bits inflating to 65536 bits, John Tromp 5 | huge-55 [[0 0 0 0 [[0 [[0]] 1]]] (+2u)] 6 | 7 | # 64 bits inflating to ??? bits, John Tromp 8 | huge-64 [0 0 h 0 0 0] (+2u) 9 | h [[[0 2 1 0]]] 10 | 11 | # 79 bits inflating to ??? bits, John Tromp and Bertram Felgenhauer 12 | huge-79 [0 0 e 0] (+2u) 13 | e [0 g [[0]] d0 0] 14 | g [[1 (d 0) (0 [0])]] 15 | d [[[2 [2 0 1]]]] 16 | d0 [[1 0 0]] 17 | 18 | main huge-64 19 | -------------------------------------------------------------------------------- /samples/readme.md: -------------------------------------------------------------------------------- 1 | # Samples 2 | 3 | Most of the examples are copies of our solutions on [Rosetta 4 | Code](https://rosettacode.org/wiki/Category:Bruijn). All examples are 5 | written by bruijn's contributors and may be used according to the GNU 6 | FDL license (see `license.md`). 7 | 8 | While solutions to the Project Euler puzzles should not be published in 9 | general, we may provide bruijn's solutions to the first 100 puzzles for 10 | introductory purposes. This is [tolerated by the 11 | creators](https://projecteuler.net/about). 12 | -------------------------------------------------------------------------------- /samples/aoc/2016/01/input: -------------------------------------------------------------------------------- 1 | L3,R1,L4,L1,L2,R4,L3,L3,R2,R3,L5,R1,R3,L4,L1,L2,R2,R1,L4,L4,R2,L5,R3,R2,R1,L1,L2,R2,R2,L1,L1,R2,R1,L3,L5,R4,L3,R3,R3,L5,L190,L4,R4,R51,L4,R5,R5,R2,L1,L3,R1,R4,L3,R1,R3,L5,L4,R2,R5,R2,L1,L5,L1,L1,R78,L3,R2,L3,R5,L2,R2,R4,L1,L4,R1,R185,R3,L4,L1,L1,L3,R4,L4,L1,R5,L5,L1,R5,L1,R2,L5,L2,R4,R3,L2,R3,R1,L3,L5,L4,R3,L2,L4,L5,L4,R1,L1,R5,L2,R4,R2,R3,L1,L1,L4,L3,R4,L3,L5,R2,L5,L1,L1,R2,R3,L5,L3,L2,L1,L4,R4,R4,L2,R3,R1,L2,R1,L2,L2,R3,R3,L1,R4,L5,L3,R4,R4,R1,L2,L5,L3,R1,R4,L2,R5,R4,R2,L5,L3,R4,R1,L1,R5,L3,R1,R5,L2,R1,L5,L2,R2,L2,L3,R3,R3,R1 2 | -------------------------------------------------------------------------------- /samples/aoc/2015/02/solve.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/String . 3 | :import std/Math . 4 | :import std/Char C 5 | 6 | parse (split-list-by (C.eq? 'x')) → (map string→number) 7 | 8 | main trim → lines → (map parse) → (φ cons part1 part2) 9 | part1 foldl \(g add [φ add surface slack (^0 ⋅ ^(~0) : (^(~0) ⋅ ^(~(~0)) : {}(^(~(~0)) ⋅ ^0)))]) (+0) 10 | surface foldl \(g add (mul (+2))) (+0) 11 | slack lmin 12 | part2 foldl \(g add (φ add ribbon bow)) (+0) 13 | ribbon sort-asc → (take (+2)) → sum → (mul (+2)) 14 | bow product 15 | -------------------------------------------------------------------------------- /docs/content.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | bruijn std/NAME 9 | 10 | 11 |

NAME

INFO 12 |
13 | 
14 | CONTENT
15 | 
16 | 
17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /benchmarks/by-n/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env sh 2 | 3 | rm -rf temp.bruijn *.json 4 | for file in $(find . -type f -name "*.bruijn" | sort); do 5 | echo "testing $file" 6 | spec=$(head -n 1 "$file") 7 | range=${spec#??} 8 | nseq=$(seq -s, $range) # requires splitting, no quotes! 9 | reducers=$(find ../../src/Reducer/ -type f -name "*.hs" -exec basename {} .hs \; | paste -sd, -) 10 | hyperfine --export-json "$file.json" --warmup 3 -r 3 -L REDUCER "$reducers" -L N $nseq "BENCH_N={N} envsubst <$file >temp.bruijn; bruijn -y -r {REDUCER} temp.bruijn /dev/null;" 11 | done 12 | -------------------------------------------------------------------------------- /samples/rosetta/ackermann_function.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Number/Unary U 3 | :import std/Math . 4 | 5 | # unary ackermann 6 | ackermann-unary [0 [[U.inc 0 1 (+1u)]] U.inc] 7 | 8 | :test (ackermann-unary (+0u) (+0u)) ((+1u)) 9 | :test (ackermann-unary (+3u) (+4u)) ((+125u)) 10 | 11 | # ternary ackermann (lower space complexity) 12 | ackermann-ternary y [[[=?1 ++0 (=?0 (2 --1 (+1)) (2 --1 (2 1 --0)))]]] 13 | 14 | :test ((ackermann-ternary (+0) (+0)) =? (+1)) ([[1]]) 15 | :test ((ackermann-ternary (+3) (+4)) =? (+125)) ([[1]]) 16 | 17 | main [[0]] 18 | -------------------------------------------------------------------------------- /docs/std.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | bruijn standard library 8 | 9 | 10 |

bruijn standard library

11 | This is the browsable standard library of the bruijn programming language. Follow any of the links below to see the source of the corresponding module. 12 |
    13 | LINKS 14 |
15 |
16 |

STATS

17 | 18 | 19 | -------------------------------------------------------------------------------- /samples/rosetta/harmonic_series.bruijn: -------------------------------------------------------------------------------- 1 | :import std/List . 2 | :import std/Combinator . 3 | :import std/Math/Rational Q 4 | :import std/Number N 5 | 6 | harmonic [0 &[[(Q.add 1 op) : N.++0]] start [[1]]] ⧗ Unary → Rational 7 | op (+1) : N.--0 8 | start (+0.0q) : (+1) 9 | 10 | custom-gt? &[[[N.gt? 2 (N.mul 0 N.++1)]]] ⧗ Rational → Νumber → Boolean 11 | 12 | main [φ cons first-20 first-10-above (harmonic <$> (iterate [[[1 (2 1 0)]]] (+0u)))] 13 | first-20 take (+20) 14 | first-10-above [take (+10) first-above] 15 | first-above [find-index [custom-gt? 0 1] 1] <$> (iterate N.inc (+0)) 16 | -------------------------------------------------------------------------------- /samples/fun/deepthought_state.bruijn: -------------------------------------------------------------------------------- 1 | # state monad example: logger/writer 2 | 3 | :import std/Combinator . 4 | :import std/List . 5 | :import std/Number . 6 | :import std/Monad/State . 7 | 8 | log [[[[0 (1 ++ 2) 3]]]] 9 | 10 | deepthought answer >>= [correct >>= [check >>= [return]]] 11 | answer log (+42) "Finding answer... " 12 | correct log (0 =? (+42)) "Checking answer... " 13 | check 0 (log [[0]] "Is correct!") (log [[0]] "Is false!") 14 | return pure 2 15 | 16 | :test (deepthought [[0]]) ([0 (+42) "Finding answer... Checking answer... Is correct!"]) 17 | 18 | main [deepthought [[0]] [[0]]] 19 | -------------------------------------------------------------------------------- /samples/rosetta/function_composition.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Number . 2 | 3 | # composition, bluebird combinator 4 | …∘… [[[2 (1 0)]]] 5 | 6 | :test (((inc ∘ (mul (+2))) (+3)) =? (+7)) ([[1]]) 7 | 8 | # 2x composition, blackbird combinator 9 | …∘∘… [[[[3 (2 1 0)]]]] 10 | 11 | :test (((inc ∘∘ mul) (+2) (+3)) =? (+7)) ([[1]]) 12 | 13 | # 3x composition, bunting combinator 14 | …∘∘∘… [[[[[4 (3 2 1 0)]]]]] 15 | 16 | :test (((inc ∘∘∘ (add ∘∘ mul)) (+1) (+2) (+4)) =? (+7)) ([[1]]) 17 | 18 | # reverse composition, queer bird combinator 19 | …→… [[[1 (2 0)]]] 20 | 21 | :test ((((mul (+2)) → inc) (+3)) =? (+7)) ([[1]]) 22 | -------------------------------------------------------------------------------- /samples/fun/rng-state.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | # generates three pseudo-random integers using a state monad 3 | # printf | bruijn rng-state.bruijn 4 | 5 | :import std/Combinator . 6 | :import std/String . 7 | :import std/Number . 8 | :import std/Monad/State . 9 | 10 | max (+1000) 11 | 12 | rand [[[0 1 1]] rng] 13 | rng (+1103515245) ⋅ 0 + (+12345) % max 14 | 15 | rand-bool map even? rand 16 | 17 | # normal bind 18 | triple rand >>= [rand >>= [rand >>= [pure [0 1 2 3]]]] 19 | 20 | :test ((triple (+50) [[1]] [[[0]]]) =? (+595)) ([[1]]) 21 | 22 | main string→number → triple → &[[1]] 23 | -------------------------------------------------------------------------------- /samples/rosetta/fibonacci_sequence.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Math . 3 | :import std/List . 4 | 5 | # unary/Church fibonacci (moderately fast but very high space complexity) 6 | fib-unary [0 [[[2 0 [2 (1 0)]]]] k i] 7 | 8 | :test (fib-unary (+6u)) ((+8u)) 9 | 10 | # ternary fibonacci using infinite list iteration (very fast) 11 | fib-list index fibs 12 | fibs head <$> (iterate &[[0 : (1 + 0)]] ((+0) : (+1))) 13 | 14 | :test (fib-list (+6)) ((+8)) 15 | 16 | # recursive fib (very slow) 17 | fib-rec y [[0 lst] 7 | # check (2 + 1 + 0 =? 3) ⋀? ((0 ** (+2)) + (1 ** (+2)) =? (2 ** (+2))) (true : (2 ⋅ 1 ⋅ 0)) {}false 8 | # lst concat-map [concat-map [map [check] ({ (+1) → --0 })] ({ (+1) → --0 })] ({ (+1) → 0 }) 9 | 10 | solve [head <#> lst] → head → tail 11 | check [[[[[2 + 1 + 0 =? 7 (true : (2 ⋅ 1 ⋅ 0)) {}false]]] a b c]] (1 ** (+2)) (0 ** (+2)) 12 | a 1 - 0 13 | b (+2) ⋅ 3 ⋅ 2 14 | c 1 + 0 15 | lst [[check] <$> ({ (+1) → --0 })] <++> ({ (+2) → (sqrt 0) }) 16 | 17 | main [solve (+1000)] 18 | -------------------------------------------------------------------------------- /docs/wiki_src/introduction/animations.md: -------------------------------------------------------------------------------- 1 | # Animations 2 | 3 | ## Id substitution 4 | 5 | 7 | 8 | ## Omega beta reduction 9 | 10 | 12 | -------------------------------------------------------------------------------- /samples/rosetta/binary_search.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/List . 3 | :import std/Math . 4 | :import std/Option . 5 | 6 | binary-search [y [[[[[2 (iterate ++‣ (-50))) 15 | 16 | :test (find (+100)) ("not found") 17 | :test ((head (find (+125))) =? (+55)) ([[1]]) 18 | :test ((head (find (+117649))) =? (+99)) ([[1]]) 19 | 20 | main [[0]] 21 | -------------------------------------------------------------------------------- /samples/euler/004.bruijn: -------------------------------------------------------------------------------- 1 | # TODO: This has serious performance problems 2 | 3 | :import std/Combinator . 4 | :import std/List . 5 | :import std/Math . 6 | 7 | # palindrome? S.number→string → [S.eq? S.<~>0 0] 8 | 9 | palindrome? [y [[[&[[>?2 (4 (3 ⋅ (+10) + 0) 1) (3 =? 5)]] (quot-rem 0 (+10))]]] (+0) 0] 10 | 11 | # combs [[S.concat-map [S.map (mul 0) ({ 0 → 1 })] ({ ((+10) ** --1) → 0 })] --((+10) ** 0)] 12 | 13 | solve [[y [[[0 =? 3 1 rec]]] empty ((+10) ** --1)] ((+10) ** 0)] → lmax 14 | rec 2 (comb ++ 1) ++0 15 | comb y [[[0 =? 6 1 (2 ([palindrome? 0 (0 : 2) 2] (3 ⋅ 0)) ++0)]]] empty 0 16 | 17 | :test ((solve (+2)) =? (+9009)) ([[1]]) 18 | 19 | main [solve (+3)] 20 | -------------------------------------------------------------------------------- /samples/rosetta/hailstone_sequence.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/List . 3 | :import std/Math M 4 | :import std/Number/Binary . 5 | 6 | # hailstone sequence using binary shifts 7 | hailstone y [[(0 =? (+1b)) {}0 go]] 8 | go 0 : (=²?0 (1 /²0) (1 (↑¹0 + 0))) 9 | 10 | # --- tests --- 11 | 12 | seq-27 hailstone (+27b) 13 | 14 | :test (∀seq-27) ((+112)) 15 | :test (take (+4) seq-27) ((+27b) : ((+82b) : ((+41b) : {}(+124b)))) 16 | :test (take (+4) <~>seq-27) ((+1b) : ((+2b) : ((+4b) : {}(+8b)))) 17 | 18 | all-below-100000 [0 : ∀(hailstone 0)] <$> seq 19 | seq take (+99999) (iterate ++‣ (+1b)) 20 | 21 | main [head (max-by (M.compare ⋔ tail) all-below-100000)] 22 | -------------------------------------------------------------------------------- /docs/code.css: -------------------------------------------------------------------------------- 1 | code, pre { 2 | tab-size: 4; 3 | } 4 | 5 | .pp { 6 | color: #ff64bd; 7 | } 8 | 9 | .definition { 10 | color: #13dbee; 11 | } 12 | 13 | .comment { 14 | color: #999999; 15 | } 16 | 17 | .type { 18 | color: #ffa120; 19 | } 20 | 21 | .abstraction { 22 | color: #6b82ff; 23 | } 24 | 25 | .application { 26 | color: #ff8750; 27 | } 28 | 29 | .index { 30 | color: #ff5050; 31 | } 32 | 33 | .number, .float, .complex { 34 | color: #b1ee13; 35 | } 36 | 37 | .string, .char { 38 | color: #bb73f0; 39 | } 40 | 41 | .code-highlight { 42 | filter: saturate(5) brightness(3); 43 | -webkit-text-stroke-width: 0.1ex; 44 | } 45 | 46 | .code-clickable { 47 | cursor: pointer; 48 | } 49 | -------------------------------------------------------------------------------- /std/Set.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2023 Marvin Borner 2 | # Generic set implementation using AVL trees 3 | # some functions require a compare-case argument! 4 | 5 | :import std/Tree/Balanced T 6 | :import std/List . 7 | 8 | # empty set 9 | empty T.empty ⧗ Set 10 | 11 | # adds a number of a set 12 | add T.insert ⧗ Compare → Number → Set → Set 13 | 14 | # returns true if a number is in a set 15 | has? T.has? ⧗ Compare → Number → Set → Boolean 16 | 17 | # count of elements in set 18 | size T.size ⧗ Set → Number 19 | 20 | # converts a set to a list 21 | set→list T.tree→list ⧗ Set → (List Number) 22 | 23 | # converts a list to a set 24 | list→set T.list→tree ⧗ Compare → (List Number) → Set 25 | -------------------------------------------------------------------------------- /editors/vim/README.md: -------------------------------------------------------------------------------- 1 | # Vim syntax highlighting 2 | 3 | ## Install manually 4 | 5 | Copy or symlink this directory to your vim plugins directory, e.g. using 6 | the following command inside this directory: 7 | 8 | mkdir -p $HOME/.vim/pack/plugins/start/ 9 | ln -s $PWD $HOME/.vim/pack/plugins/start/bruijn 10 | 11 | ## Install with a plugin manager 12 | 13 | In this example using vim-plug (others should work similarly): 14 | 15 | 1. Add `Plug 'marvinborner/bruijn', { 'rtp': 'editors/vim' }` to your 16 | `.vimrc` 17 | 2. Run `:PlugInstall` 18 | 19 | # Autocompletion 20 | 21 | This plugin uses vim’s `abbreviation`s for autocompletion. You can 22 | complete them using `space` or `C-]` 23 | -------------------------------------------------------------------------------- /std/test_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "$1" ]; then 4 | echo "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | echo "# useful for running all tests of the standard library" >All.bruijn 9 | echo >>All.bruijn 10 | 11 | FILES="$(find * -type f -name "*.bruijn" ! -name "All.bruijn" ! -path "*Generic*")" 12 | 13 | for f in $FILES; do 14 | echo ":import std/${f%*.bruijn} ." >>All.bruijn 15 | done 16 | 17 | # for ci, just run `bruijn All.bruijn` 18 | echo >>All.bruijn 19 | echo "main [[0]]" >>All.bruijn 20 | 21 | if cat /dev/null | bruijn All.bruijn -r "$1" | tee /dev/fd/2 | grep -q "ERROR"; then 22 | exit 1 23 | fi 24 | 25 | hyperfine --warmup 5 --runs 20 "cat /dev/null | bruijn -r $1 All.bruijn" 26 | -------------------------------------------------------------------------------- /samples/aoc/2022/02/solve.bruijn: -------------------------------------------------------------------------------- 1 | # perfectly normal rock paper scissors :) 2 | 3 | :import std/Combinator . 4 | :import std/String . 5 | :import std/Number . 6 | :import std/Number/Conversion . 7 | 8 | :import std/Char C 9 | :import std/Number/Binary B 10 | 11 | parse (split-by (C.eq? ' ')) → &[[²³(B.sub ^1 'A') : ²³(B.sub ^0 'X')]] 12 | 13 | solve [foldr go (+0)] 14 | go &[[[^3 !! 1 + ((drop 0 ^(~3)) ++ (take 0 ^(~3)) !! 1)] (~(~2) !! 1)]] → add 15 | 16 | main lines → (map parse) → (φ cons part1 part2) 17 | part1 solve (((+1) : ((+2) : {}(+3))) : (((+0) : ((+3) : {}(+6))) : ((+1) : ((+0) : {}(+2))))) 18 | part2 solve (((+0) : ((+3) : {}(+6))) : (((+3) : ((+1) : {}(+2))) : ((+0) : ((+1) : {}(+2))))) 19 | -------------------------------------------------------------------------------- /src/Reducer.hs: -------------------------------------------------------------------------------- 1 | -- MIT License, Copyright (c) 2024 Marvin Borner 2 | module Reducer 3 | ( reduce 4 | , reduceNoIO 5 | ) where 6 | 7 | import Config 8 | import Helper 9 | import qualified Reducer.HigherOrder as HigherOrder 10 | import qualified Reducer.ION as ION 11 | import qualified Reducer.RKNL as RKNL 12 | 13 | reduce :: EvalConf -> Expression -> IO Expression 14 | reduce conf e = case _reducer conf of 15 | "RKNL" -> RKNL.reduce e 16 | "ION" -> pure $ ION.reduce e 17 | "HigherOrder" -> pure $ HigherOrder.reduce e 18 | _ -> error "Invalid reducer" 19 | 20 | reduceNoIO :: Expression -> Expression 21 | reduceNoIO = HigherOrder.reduce 22 | -------------------------------------------------------------------------------- /std/Number/Wadsworth.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2023 Marvin Borner 2 | # from Wadsworth's "some unusual numeral systems" (p224, see refs in README) 3 | 4 | :import std/Combinator . 5 | 6 | zero [0 [k]] ⧗ Wadsworth 7 | 8 | # increment Wadsworth number 9 | inc [[[2 [2 (1 0) 1]]]] ⧗ Wadsworth → Wadsworth 10 | 11 | :test (inc (inc zero)) ([[[2 (1 (0 (k [[1]]))) 1 0]]]) 12 | 13 | # decrement Wadsworth number 14 | dec [[1 [k (1 0)] i]] ⧗ Wadsworth → Wadsworth 15 | 16 | :test (dec (dec (inc (inc zero)))) (zero) 17 | 18 | # returns true if Wadsworth number is zero 19 | zero? [0 i (k (k [[0]]))] ⧗ Wadsworth → Bool 20 | 21 | :test (zero? zero) ([[1]]) 22 | :test (zero? (inc zero)) ([[0]]) 23 | :test (zero? (inc (inc zero))) ([[0]]) 24 | -------------------------------------------------------------------------------- /samples/fun/jottary.bruijn: -------------------------------------------------------------------------------- 1 | # a small Jottary (unary Jot) interpreter 2 | # also serves as example usage of monadic list operations 3 | # run with "printf 1...1 | bruijn jottary.bruijn" 4 | 5 | :import std/Combinator . 6 | :import std/List . 7 | :import std/Monad . 8 | :import std/Number . 9 | 10 | go [eval-r (<~>((concat huh) !! 0) ; i)] 11 | huh (\replicate-m (l : {}r)) <$> (iterate ++‣ (+0)) 12 | l [(0 s) k] 13 | r [s (k 0)] 14 | 15 | :test (go (+0)) (i) 16 | :test (go (+1)) ((i s) k) 17 | :test (go (+2)) (s (k i)) 18 | :test (go (+3)) (i s k s k) 19 | :test (go (+4)) (s (k (i s k))) 20 | :test (go (+5)) (((s (k i)) s) k) 21 | :test (go (+6)) (s (k (s (k i)))) 22 | :test (go (+59)) (k) 23 | :test (go (+503)) (s) 24 | 25 | main go ∘ length 26 | -------------------------------------------------------------------------------- /std/Number/SK.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2025 Marvin Borner 2 | # by https://john-tromp.medium.com/sk-numerals-9ad1b5634b28 3 | # and proposed by Stephen Wolfram in "Combinators: A Centennial View" 4 | 5 | :import std/Combinator . 6 | :import std/Tuples . 7 | 8 | # -4 [[[[3]]]] 9 | # -3 [[[[2]]]] 10 | # -2 [[[2]]] 11 | # -1 [[[1]]] 12 | # 0 [[1]] 13 | # +1 [[0]] 14 | # +2 [[1 0]] 15 | # +3 [[0 (1 0)]] 16 | # +4 [[(1 0) (0 (1 0))]] 17 | 18 | # +n has fib(n) of 1s 19 | 20 | zero k ⧗ SK 21 | 22 | inc s ⧗ SK → SK 23 | 24 | ++‣ inc 25 | 26 | dec [[[2 (k 0) 1]]] ⧗ SK → SK 27 | 28 | --‣ dec 29 | 30 | :test (dec (inc zero)) (zero) 31 | 32 | # only for positive numbers (also see Bruijn.bruijn!) 33 | zero? ki : i : s : k ⧗ SK → Boolean 34 | 35 | =?‣ zero? 36 | -------------------------------------------------------------------------------- /samples/fun/wilsons_primes.bruijn: -------------------------------------------------------------------------------- 1 | # 208-bit golfed characteristic prime sequence using Wilson's theorem 2 | # (Tromp's prime sieve only takes 143 bit) 3 | 4 | # b' not-zero? mod 5 | zmod [[1 [0 [[1]]] (1 [1 [[[0 (2 [[1]]) 1]]] [1] [[0]]] [[[0]]]) [[0]]]] 6 | 7 | zero? [0 [[[0]]] [[1]]] ⧗ Unary → Boolean 8 | 9 | # experiment with repeated subtraction (flipped args, zero? not golfed yet) 10 | # TODO: consider Parigot for trivial predecessor? 11 | zmod' [[0 0] [[[zero? (0 3 1) (2 2 (0 1 3)) (zero? 1)] sub]]] 12 | sub [[0 [[[2 [[0 (1 3)]] [1] [0]]]] 1]] 13 | 14 | # y [[[(zmod 1 0) : (2 (1 ⋅ (0 ⋅ 0)) ++0)]]] (+1u) (+1u) 15 | loop [0 0] [[[[0 (2 [0 [[1]]] (2 [2 [[[0 (2 [[1]]) 1]]] [1] [[0]]] [[[0]]]) [[0]]) (3 3 [3 (2 (2 0))] [[1 (3 1 0)]])]]]] (+1u) (+1u) 16 | 17 | main [loop] 18 | -------------------------------------------------------------------------------- /stack.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: 7 | - completed: 8 | hackage: bitstring-0.0.0@sha256:7638c1c515d728a84507e33c830854cfd141a5bcabec6963e68f52baf27979e9,1248 9 | pantry-tree: 10 | sha256: 2006295f9a9943177952b9b0bfee7de96d5f86d8f989de7dd154159705fa2572 11 | size: 284 12 | original: 13 | hackage: bitstring-0.0.0 14 | snapshots: 15 | - completed: 16 | sha256: e2c529ccfb21501f98f639e056cbde50470b86256d9849d7a82d414ca23e4276 17 | size: 712898 18 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/12.yaml 19 | original: lts-22.12 20 | -------------------------------------------------------------------------------- /samples/aoc/2019/01/solve.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Logic . 3 | :import std/String . 4 | :import std/Math . 5 | 6 | fuel [/³*0 - (+2)] ⧗ Mass → Fuel 7 | 8 | :test ((fuel (+12)) =? (+2)) (true) 9 | :test ((fuel (+14)) =? (+2)) (true) 10 | :test ((fuel (+1969)) =? (+654)) (true) 11 | :test ((fuel (+100756)) =? (+33583)) (true) 12 | 13 | fuelfuel z [[rec]] ⧗ Mass → Fuel 14 | rec go (fuel 0) 15 | go [>?0 (0 + (2 0)) (+0)] 16 | 17 | :test ((fuelfuel (+14)) =? (+2)) (true) 18 | :test ((fuelfuel (+1969)) =? (+966)) (true) 19 | :test ((fuelfuel (+100756)) =? (+50346)) (true) 20 | 21 | part1 ∑‣ ∘ (map fuel) ⧗ (List Mass) → Fuel 22 | 23 | part2 ∑‣ ∘ (map fuelfuel) ⧗ (List Mass) → Fuel 24 | 25 | main [parts nums] 26 | nums string→number <$> ~(<~>(lines 0)) 27 | parts [(part1 0) : (part2 0)] 28 | -------------------------------------------------------------------------------- /samples/aoc/2023/02/solve.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/String . 3 | :import std/Math . 4 | :import std/Char C 5 | 6 | valid? &[[\(C.eq? 0 'r' (1 >? (+12)) (C.eq? 0 'g' (1 >? (+13)) (1 >? (+14))))]] 7 | 8 | parse map (game → (map (sets → (map (cubes → (map (tail → cube)))) → concat → maxes)) → head) 9 | game (split-list-by (C.eq? ':')) → tail 10 | sets split-list-by (C.eq? ';') 11 | cubes split-list-by (C.eq? ',') 12 | cube (split-list-by (C.eq? ' ')) → &[[(string→number 1) : ^(^0)]] 13 | maxes [(m 'r') : ((m 'g') : {}(m 'b'))] 14 | m [max-by (compare ⋔ head) (filter [C.eq? (tail 0) 1] 1)] 15 | 16 | main lines → parse → enumerate → (φ cons part1 part2) 17 | part1 (map &[[1 : (all? valid? 0)]]) → (filter tail) → (map (head → inc)) → sum 18 | part2 (map &[(map head) → product]) → sum 19 | -------------------------------------------------------------------------------- /std/Set/Number.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | 3 | :input std/Set 4 | 5 | :import std/Number T 6 | 7 | # adds a number of a set 8 | add T.add ⧗ Number → NumberSet → NumberSet 9 | 10 | # returns true if a number is in a set 11 | has? T.has? ⧗ Number → NumberSet → Boolean 12 | 13 | :test (has? (+5) (add (+5) empty)) ([[1]]) 14 | :test (has? (+5) empty) ([[0]]) 15 | 16 | # converts a list to a set 17 | list→set T.list→set ⧗ (List Number) → NumberSet 18 | 19 | :test (has? (+0) (list→set ((+5) : ((+3) : ((+2) : ((+1) : {}(+0))))))) ([[1]]) 20 | :test (has? (+5) (list→set ((+5) : ((+3) : ((+2) : ((+1) : {}(+0))))))) ([[1]]) 21 | :test (has? (+6) (list→set ((+5) : ((+3) : ((+2) : ((+1) : {}(+0))))))) ([[0]]) 22 | :test (has? (+7) (list→set ((+5) : ((+7) : {}(+1))))) ([[1]]) 23 | -------------------------------------------------------------------------------- /std/Set/String.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | # TODO: hash instead of comparing 3 | 4 | :input std/Set 5 | 6 | :import std/String S 7 | 8 | # adds a number of a set 9 | add S.add ⧗ String → StringSet → StringSet 10 | 11 | # returns true if a number is in a set 12 | has? S.has? ⧗ String → StringSet → Boolean 13 | 14 | :test (has? "abc" (add "abc" empty)) ([[1]]) 15 | :test (has? "abc" empty) ([[0]]) 16 | 17 | # converts a list to a set 18 | list→set S.list→set ⧗ (List String) → StringSet 19 | 20 | :test (has? "0" (list→set ("a" : ("b" : ("d" : ("c" : {}"0")))))) ([[1]]) 21 | :test (has? "a" (list→set ("a" : ("b" : ("d" : ("c" : {}"0")))))) ([[1]]) 22 | :test (has? "e" (list→set ("a" : ("b" : ("d" : ("c" : {}"0")))))) ([[0]]) 23 | :test (has? "c" (list→set ("a" : ("c" : {}"b")))) ([[1]]) 24 | -------------------------------------------------------------------------------- /samples/fun/pi-rational.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Pair . 2 | :import std/Combinator . 3 | :import std/Number/Unary . 4 | 5 | qadd &[[&[[p : q]]]] ⧗ Rational → Rational → Rational 6 | p (3 ⋅ ++0) + (1 ⋅ ++2) 7 | q (2 ⋅ 0) + (2 + 0) 8 | 9 | fac [[1 [[0 (1 [[2 1 (1 0)]])]] [1] i]] ⧗ Unary → Unary 10 | 11 | # challenge: smallest, yet fastest converging, π ratio calculation 12 | 13 | # naive 14 | v1-pi/2 [0 &[[op]] start [[1]]] 15 | start ((+0u) : (+0u)) : (+0u) 16 | op (qadd 1 (enum : --denom)) : ++0 17 | enum (0 (+2u)) ⋅ ((+2u) (fac 0)) 18 | denom fac ++((+2u) ⋅ 0) 19 | 20 | dfac [[1 [[0 (1 [[2 1 (1 (1 0))]])]] [1] i]] ⧗ Unary → Unary 21 | 22 | # double factorial 23 | # dfac and fac could be shared! 24 | v2-pi/2 [0 &[[op]] start [[1]]] 25 | start ((+0u) : (+0u)) : (+0u) 26 | op (qadd 1 (enum : --denom)) : ++0 27 | enum fac 0 28 | denom dfac ++0 29 | -------------------------------------------------------------------------------- /docs/wiki_src/coding/currying.md: -------------------------------------------------------------------------------- 1 | # Currying 2 | 3 | Lambda calculus naturally supports currying -- that is, only *partially* 4 | applying a function. In fact *any* function can be applied with *any* 5 | amount of arguments! 6 | 7 | In bruijn, currying is a great way to make functions even more elegant. 8 | 9 | For example, take the negation function: 10 | 11 | ``` bruijn 12 | # subtracts argument from zero 13 | -‣ [(+0) - 0] ⧗ Number → Number 14 | 15 | # equivalent curried version 16 | -‣ sub (+0) 17 | ``` 18 | 19 | Currying is also very useful for higher-order functions. 20 | 21 | Multiplying values in a list by partially applying the `mul`{.bruijn} 22 | function: 23 | 24 | ``` bruijn 25 | # doubles numbers in a list 26 | double-list [0 <$> (mul (+2))] ⧗ (List Number) → (List Number) 27 | 28 | :test (double-list ((+1) : {}(+2))) ((+2) : {}(+4)) 29 | ``` 30 | -------------------------------------------------------------------------------- /std/Number.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2023 Marvin Borner 2 | # this is just a reference to the ternary implementation 3 | # read the wiki for the reasoning of using balanced ternary by default 4 | 5 | :import std/List . 6 | 7 | :input std/Number/Ternary 8 | 9 | # the following functions are only here because of recursive imports of list/ternary 10 | 11 | # converts number to list of its digits 12 | number→list [=?0 {}(+0) (z [[[rec]]] empty 0)] ⧗ Number → (List Number) 13 | rec =?0 case-end case-rec 14 | case-rec &[[4 (0 : 3) 1]] (quot-rem 0 (+10)) 15 | case-end 1 16 | 17 | :test (number→list (+0)) ({}(+0)) 18 | 19 | # converts a list of digits into a balanced ternary number 20 | list→number foldl [[(+10) ⋅ 1 + 0]] (+0) ⧗ (List Number) → Number 21 | 22 | :test (list→number ((+4) : ((+2) : {}(+0)))) ((+420)) 23 | :test (list→number empty) ((+0)) 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Test standard library 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: freckle/stack-action@v5 17 | - name: Install hyperfine 18 | run: sudo apt-get install hyperfine 19 | - name: Install bruijn 20 | run: stack install 21 | - name: Run HigherOrder tests and measure time 22 | run: cd std/ && ./test_all.sh HigherOrder 23 | - name: Run RKNL tests and measure time 24 | run: cd std/ && ./test_all.sh RKNL 25 | # - name: Run ION tests and measure time 26 | # run: cd std/ && ./test_all.sh ION 27 | - name: Run HigherOrder sample tests 28 | run: cd samples/ && ./test_all.sh HigherOrder 29 | -------------------------------------------------------------------------------- /docs/wiki_src/coding/prefix.md: -------------------------------------------------------------------------------- 1 | # Prefix 2 | 3 | Prefix functions are symbols written directly in front of another term 4 | (without space). The term gets applied as an argument to the prefix 5 | function. Use [mixfix functions](mixfix.md) if the function has more 6 | than one argument. 7 | 8 | They are defined by the `‣`{.bruijn} suffix. 9 | 10 | Example: 11 | 12 | ``` bruijn 13 | # defines a negation prefix function called '-' 14 | -‣ [(+0) - 0] ⧗ Number → Number 15 | 16 | # returns 0 - 10 = -10 17 | :test (-(+10)) ((-10)) 18 | ``` 19 | 20 | You can use them as normal functions by writing the identifier 21 | literally: 22 | 23 | ``` bruijn 24 | :test (-‣ (+10)) ((-10)) 25 | ``` 26 | 27 | ## Allowed characters 28 | 29 | Prefix functions can use any characters of `!?*@:;+-_#$%^&<>/\|{}~=` as 30 | well as mathematical unicode operators and arrows. They must be at least 31 | 1 character long. 32 | -------------------------------------------------------------------------------- /std/Number/Scott.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2023 Marvin Borner 2 | 3 | :import std/Combinator . 4 | :import std/Logic . 5 | 6 | zero [[1]] ⧗ Scott 7 | 8 | inc [[[0 2]]] ⧗ Scott → Scott 9 | 10 | :test (inc zero) ([[0 zero]]) 11 | :test (inc (inc zero)) ([[0 [[0 zero]]]]) 12 | :test (inc (inc (inc zero))) ([[0 [[0 [[0 zero]]]]]]) 13 | 14 | dec [0 zero [0]] ⧗ Scott → Scott 15 | 16 | :test (dec zero) (zero) 17 | :test (dec (inc zero)) (zero) 18 | :test (dec (inc (inc zero))) (inc zero) 19 | 20 | zero? [0 true [false]] ⧗ Scott → Boolean 21 | 22 | :test (zero? zero) (true) 23 | :test (zero? (inc zero)) (false) 24 | :test (zero? (inc (inc zero))) (false) 25 | 26 | add z [[[1 0 [inc (3 0 1)]]]] ⧗ Scott → Scott → Scott 27 | 28 | :test (add zero zero) (zero) 29 | :test (add zero (inc zero)) (inc zero) 30 | :test (add (inc zero) zero) (inc zero) 31 | :test (add (inc zero) (inc zero)) (inc (inc zero)) 32 | -------------------------------------------------------------------------------- /docs/wiki_src/introduction/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | You first need to install Haskell's 4 | [Stack](https://docs.haskellstack.org/en/stable/) using their official 5 | instructions for your operating system. After that, clone the bruijn 6 | repository using 7 | 8 | ``` bash 9 | $ git clone https://github.com/marvinborner/bruijn 10 | $ cd bruijn 11 | ``` 12 | 13 | Then, run bruijn using `stack run`{.bash}. This starts bruijn's 14 | [REPL](../coding/REPL.md). You can now play around with bruijn. 15 | 16 | Passing normal arguments to `bruijn`{.bash} with `stack`{.bash} can be 17 | done with `stack run -- [args]`. If you enjoy it, you can install 18 | `bruijn` into your stack path using `stack install`. 19 | 20 | ------------------------------------------------------------------------ 21 | 22 | Please create an issue on 23 | [GitHub](https://github.com/marvinborner/bruijn/issues/new) if you 24 | encounter any issues. 25 | -------------------------------------------------------------------------------- /samples/aoc/2016/01/solve.bruijn: -------------------------------------------------------------------------------- 1 | # vim first: :s/ //g 2 | 3 | :import std/Combinator . 4 | :import std/Char C 5 | :import std/String S 6 | :import std/Pair P 7 | :import std/Number . 8 | :import std/List . 9 | 10 | direction! [^0 : (S.string→number ~0)] ⧗ (List Char) → Direction 11 | 12 | :test (direction! "R42") ('R' : (+42)) 13 | 14 | # north=0, east=1, south=2, west=3 15 | # rotation : (x : y) 16 | start ((+0) : ((+0) : (+0))) ⧗ State 17 | 18 | move [[go (C.eq? ^0 'R' ++‣ --‣ ^1)]] ⧗ State → Direction → State 19 | go [0 : (even? 0 y x)] 20 | x ^(~2) + (p ~1) : ~(~2) 21 | p 0 % (+4) =? (+3) -‣ [0] 22 | y ^(~2) : (~(~2) + (p ~1)) 23 | p 0 % (+4) =? (+2) -‣ [0] 24 | 25 | :test (move start (direction! "R42")) ((+1) : ((+42) : (+0))) 26 | 27 | part1 (P.uncurry …+…) ∘ ~‣ ∘ (foldl move start) ⧗ (List Direction) → State 28 | 29 | main [parts coords] 30 | coords direction! <$> (split-list-by (C.eq? ',') (init 0)) 31 | parts [part1 0] 32 | -------------------------------------------------------------------------------- /samples/fun/mutrec.bruijn: -------------------------------------------------------------------------------- 1 | # some example usage of the variadic fixed point combinator 2 | 3 | :import std/List . 4 | :import std/Logic . 5 | :import std/Number . 6 | 7 | # generates odd? and even? function as list 8 | odd-even? y* (even? : {}odd?) 9 | even? [[[=?0 true (1 --0)]]] 10 | odd? [[[=?0 false (2 --0)]]] 11 | 12 | :test (^odd-even? (+5)) (false) 13 | :test (_odd-even? (+5)) (true) 14 | 15 | # calculates n % 3 16 | mod3 ^(y* (a : (b : {}c))) 17 | a [[[[=?0 (+0) (2 --0)]]]] 18 | b [[[[=?0 (+1) (1 --0)]]]] 19 | c [[[[=?0 (+2) (3 --0)]]]] 20 | 21 | :test ((mod3 (+0)) =? (+0)) (true) 22 | :test ((mod3 (+1)) =? (+1)) (true) 23 | :test ((mod3 (+2)) =? (+2)) (true) 24 | :test ((mod3 (+3)) =? (+0)) (true) 25 | :test ((mod3 (+4)) =? (+1)) (true) 26 | :test ((mod3 (+5)) =? (+2)) (true) 27 | :test ((mod3 (+6)) =? (+0)) (true) 28 | :test ((mod3 (+7)) =? (+1)) (true) 29 | :test ((mod3 (+8)) =? (+2)) (true) 30 | :test ((mod3 (+9)) =? (+0)) (true) 31 | 32 | main [[0]] 33 | -------------------------------------------------------------------------------- /samples/rosetta/hamming_numbers.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Number . 3 | :import std/List . 4 | 5 | merge y [[[∅?1 0 (1 [[2 [[go]]]])]]] 6 | go 3 ((+1) : 0))]]] 11 | 12 | :test ((hammings-classic !! (+42)) =? (+162)) ([[1]]) 13 | 14 | # enumeration by a chain of folded merges (faster) 15 | hammings-folded ([(0 ∘ a) ∘ (0 ∘ b)] (foldr merge1 empty)) $ c 16 | merge1 [[1 [[1 : (merge 0 2)]]]] 17 | a iterate (map (mul (+5))) 18 | b iterate (map (mul (+3))) 19 | c iterate (mul (+2)) (+1) 20 | 21 | :test ((hammings-folded !! (+42)) =? (+162)) ([[1]]) 22 | 23 | # --- output --- 24 | 25 | main [first-twenty : (n1691 : {}n1000000)] 26 | first-twenty take (+20) hammings-folded 27 | n1691 hammings-folded !! (+1690) 28 | n1000000 hammings-folded !! (+999999) 29 | -------------------------------------------------------------------------------- /std/Generic/Monad.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | # for now, monads, functors, applicative, etc are all in here.. 3 | 4 | # TODO: more monad interface 5 | 6 | :import std/List L 7 | 8 | liftA map ⧗ (a → b) → (M a) → (M b) 9 | 10 | liftA2 ap ∘∘ map ⧗ (a → b → c) → (M a) → (M b) → (M c) 11 | 12 | liftA3 [[[[(liftA2 3 2 1) <*> 0]]]] ⧗ (a → b → c → d) → (M a) → (M b) → (M c) → (M d) 13 | 14 | …<$… map ∘ k ⧗ a → (M b) → (M a) 15 | 16 | …*>… [[(i <$ 1) <*> 0]] ⧗ (M a) → (M b) → (M b) 17 | 18 | …<*… liftA2 k ⧗ (M a) → (M b) → (M a) 19 | 20 | …=<<… \bind 21 | 22 | …>>… [[1 >>= [1]]] ⧗ (M a) → (M b) → (M b) 23 | 24 | # TODO: traverse, sequence 25 | # in parser: string = traverse char! 26 | 27 | some [y [liftA2 L.cons 1 many]] ⧗ (M a) → (M (List a)) 28 | many 0 <|> (pure L.empty) 29 | 30 | many [y [some <|> (pure L.empty)]] ⧗ (M a) → (M (List a)) 31 | some liftA2 L.cons 1 0 32 | 33 | between [[[2 *> 0 <* 1]]] ⧗ (M a) → (M a) → (M a) → (M a) 34 | -------------------------------------------------------------------------------- /std/Monad/State.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | # see samples/fun/rng-state for example usage 3 | 4 | :import std/Tuples . 5 | :import std/Combinator . 6 | 7 | # scala: s0 ⇒ { (s1, a) = run(s0); (s1, f(a)) } 8 | map [[[1 0 [[1 : (4 0)]]]]] ⧗ (a → b) → (State s a) → (State s b) 9 | 10 | …<$>… map 11 | 12 | # monadic bind/flat-map operator 13 | # scala: s0 ⇒ { (s1, a) = run(s0); f(a).run(s1) } 14 | bind [[[2 0 [[3 0 1]]]]] ⧗ (State s a) → (a → (State s b)) → (State s b) 15 | 16 | …>>=… bind 17 | 18 | pure tuple ⧗ a → (State s a) 19 | 20 | :test ((w' ∘ c) >>= [(w' ∘ c) >>= [pure 0]] [[0]]) (w' [[0]]) 21 | :test ((w' ∘ c) >>= [(w' ∘ c) >>= [pure 0]] [[1]]) (w' [[1]]) 22 | 23 | get [0 : 0] 24 | 25 | gets [[0 : (1 0)]] 26 | 27 | put [[1 : Ω]] 28 | 29 | puts [[(1 0) : Ω]] 30 | 31 | :test (bind get [bind (put ((+2u) 0)) [gets [(+2u) 0]]] (+2u)) ((+4u) : (+16u)) 32 | 33 | # TODO 34 | ap [0] 35 | 36 | # TODO 37 | …<|>… [0] 38 | 39 | :input std/Generic/Monad 40 | -------------------------------------------------------------------------------- /benchmarks/by-n/plot.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python3 2 | 3 | import matplotlib.pyplot as plt 4 | import json 5 | import os, glob 6 | 7 | 8 | def render(file): 9 | data = json.load(open(file, "r"))["results"] 10 | basename = os.path.basename(file).split(".")[0] 11 | reducers = list({p["parameters"]["REDUCER"] for p in data}) 12 | for reducer in reducers: 13 | labels = [ 14 | p["parameters"]["N"] 15 | for p in data 16 | if p["parameters"]["REDUCER"] == reducer 17 | ] 18 | times = [ 19 | p["median"] for p in data if p["parameters"]["REDUCER"] == reducer 20 | ] 21 | plt.plot(times) 22 | plt.xticks(range(len(labels)), labels) 23 | plt.legend(reducers) 24 | plt.title(f"reducer comparison for {basename}") 25 | plt.xlabel("N") 26 | plt.ylabel("Time (s)") 27 | plt.savefig(f"{basename}.png") 28 | plt.close() 29 | 30 | 31 | for file in glob.glob("*.json"): 32 | render(file) 33 | -------------------------------------------------------------------------------- /std/Number/Pairing.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | # pairing functions for writing two integers as one, bijectionally 3 | 4 | :import std/Logic . 5 | :import std/Pair . 6 | :import std/Number . 7 | :import std/Math . 8 | 9 | # strong Rosenberg pairing function 10 | pair-ternary [[[0 ⋅ 0 + 0 + 2 - 1] (max 1 0)]] ⧗ Number → Number → Number 11 | 12 | :test ((pair-ternary (+0) (+0)) =? (+0)) (true) 13 | :test ((pair-ternary (+0) (+1)) =? (+1)) (true) 14 | :test ((pair-ternary (+1) (+0)) =? (+3)) (true) 15 | :test ((pair-ternary (+2) (+1)) =? (+7)) (true) 16 | 17 | # strong Rosenberg unpairing function 18 | unpair-ternary [[[go] (1 - (0 ⋅ 0))] (sqrt 0)] ⧗ Number → (Pair Number Number) 19 | go (0 (unpair-ternary (+0))) ((+0) : (+0)) 22 | :test (strip <$> (unpair-ternary (+1))) ((+0) : (+1)) 23 | :test (strip <$> (unpair-ternary (+3))) ((+1) : (+0)) 24 | :test (strip <$> (unpair-ternary (+7))) ((+2) : (+1)) 25 | -------------------------------------------------------------------------------- /docs/samples.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | bruijn samples 8 | 9 | 10 |

bruijn samples

11 |

These are some code samples written in the bruijn programming language. Most of the examples are copies of our solutions on Rosetta Code. All examples are written by bruijn's contributors and may be used according to the GNU FDL license.

12 |

While solutions to the Project Euler puzzles should not be published in general, we may provide bruijn's solutions to the first 100 puzzles for introductory purposes. This is tolerated by the creators.

13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/test_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "$1" ]; then 4 | echo "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | # ================= 9 | # Euler/Fun/Rosetta 10 | # ================= 11 | 12 | echo "# useful for run the tests of all samples at once" >All.bruijn 13 | echo >>All.bruijn 14 | 15 | FILES="$(find euler fun rosetta -type f -name "*.bruijn")" 16 | 17 | for f in $FILES; do 18 | echo ":import ${f%*.bruijn} ." >>All.bruijn 19 | done 20 | 21 | # for ci, just run `bruijn All.bruijn` 22 | echo >>All.bruijn 23 | echo "main [[0]]" >>All.bruijn 24 | 25 | if cat /dev/null | bruijn All.bruijn -r "$1" | tee /dev/fd/2 | grep -q "ERROR"; then 26 | exit 1 27 | fi 28 | 29 | # === 30 | # AOC 31 | # === 32 | 33 | FILES="$(find aoc -type f -name "*.bruijn")" 34 | 35 | for f in $FILES; do 36 | dir="$(dirname "$f")" 37 | cat "$dir/input" | bruijn -r "$1" "$f" | tail -n1 >temp.out 38 | cmp temp.out "$dir/output.check" || ( 39 | echo "AOC check $f failed" 40 | exit 1 41 | ) 42 | done 43 | 44 | rm -f temp.out 45 | -------------------------------------------------------------------------------- /samples/fun/encode.bruijn: -------------------------------------------------------------------------------- 1 | # encode lambda terms as numbers 2 | # from https://hbr.github.io/Lambda-Calculus/computability/text.html 3 | # see also std/Meta for unary variant 4 | 5 | :import std/Combinator . 6 | :import std/Logic . 7 | :import std/Math . 8 | 9 | # compute pairing function [n,m] 10 | compress [[--((pow (+2) 1) ⋅ ++((+2) ⋅ 0))]] ⧗ Number → Number → Number 11 | 12 | :test ((compress (+0) (+0)) =? (+0)) (true) 13 | :test ((compress (+1) (+0)) =? (+1)) (true) 14 | :test ((compress (+0) (+1)) =? (+2)) (true) 15 | :test ((compress (+2) (+1)) =? (+11)) (true) 16 | 17 | idx compress (+0) ⧗ Number → Number 18 | 19 | app (compress (+1)) ∘∘ compress ⧗ Number → Number → Number 20 | 21 | abs (compress (+2)) ∘∘ compress ⧗ Number → Number → Number 22 | 23 | enc_zero abs (+1) (abs (+0) (idx (+0))) ⧗ Number 24 | 25 | enc_inc abs (+2) (abs (+1) (abs (+0) enc)) ⧗ Number 26 | enc app (idx (+1)) (app (app (idx (+2)) (idx (+1))) (idx (+0))) 27 | 28 | wrap [0 (app enc_inc) enc_zero] ⧗ Number → Number 29 | 30 | main [[0]] 31 | -------------------------------------------------------------------------------- /docs/wiki_src/technical/performance.md: -------------------------------------------------------------------------------- 1 | # Performance 2 | 3 | In general, the reduction of practical programs encoded in lambda 4 | calculus is not very efficient when compared to traditional programming 5 | languages. We do, however, work a lot on making the performance as 6 | comparable as possible: 7 | 8 | - We have different reducers and constantly benchmark and improve them 9 | in order to find the most efficient method of reduction. Read more 10 | about our [reducer choices](reduction.md). 11 | - Bruijn uses efficient data structures by default. For example, for 12 | nary numbers we use results of Torben Mogensens investigations (as 13 | described in [number/byte encodings](../coding/data-structures.md)). 14 | - Bruijn has a `-O` flag that enables abstraction of duplicated terms 15 | - The lambda calculus optimizers 16 | [BLoC](https://github.com/marvinborner/bloc) and 17 | [BLoCade](https://github.com/marvinborner/blocade) are directly 18 | integrated into bruijn and can be enabled optionally (see 19 | [compilation](../coding/compilation.md)) 20 | -------------------------------------------------------------------------------- /std/Number/Tuple.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2025 Marvin Borner 2 | # by Tromp @ https://github.com/tromp/AIT/blob/master/numerals/tuple_numerals.lam 3 | 4 | :import std/Tuples . 5 | :import std/Combinator . 6 | :import std/Logic/Binary . 7 | 8 | zero i 9 | 10 | inc [[1 (0 :)]] 11 | 12 | ++‣ inc 13 | 14 | :test (++zero) (t) 15 | 16 | dec [[1 0 [0]]] 17 | 18 | --‣ dec 19 | 20 | :test (--(++zero)) (zero) 21 | :test (--(++(++zero))) (t) 22 | 23 | lt? [(k false) : (0 (k true))] 24 | 25 | …0] 27 | ``` 28 | 29 | ``` bash 30 | $ printf "tacocat" | bruijn reverse.bruijn 31 | tacocat 32 | ``` 33 | 34 | [^1]: [Tromp, John. "Functional Bits: Lambda Calculus based Algorithmic 35 | Information Theory." (2023).](https://tromp.github.io/cl/LC.pdf) 36 | -------------------------------------------------------------------------------- /std/List/Parigot.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | # see "on the representation of data in lambda-calculus" (Parigot 1989) 3 | # constant append/snoc 4 | 5 | # empty list element 6 | empty [0] ⧗ (Parigot a) 7 | 8 | # prepends an element to a list 9 | cons [[[[0 3 (2 1)]]]] ⧗ a → (Parigot a) → (Parigot a) 10 | 11 | …:… cons 12 | 13 | # returns the head of a list or k 14 | head [0 [0] [[1]]] ⧗ (Parigot a) → a 15 | 16 | :test (head ('a' : ('b' : ('c' : empty)))) ('a') 17 | :test (head empty) ([[1]]) 18 | 19 | # returns the tail of a list or &ki 20 | tail [[1 0 [[0]]]] ⧗ (Parigot a) → (Parigot a) 21 | 22 | :test (tail ('a' : ('b' : ('c' : empty)))) ('b' : ('c' : empty)) 23 | :test (tail empty) ([0 [[0]]]) 24 | 25 | # appends two lists 26 | append [[[2 (1 0)]]] ⧗ (Parigot a) → (Parigot a) → (Parigot a) 27 | 28 | …++… append 29 | 30 | :test (append ('a' : ('b' : empty)) ('c' : ('d' : empty))) ('a' : ('b' : ('c' : ('d' : empty)))) 31 | 32 | # appends an element to a list 33 | snoc [[[2 [0 2 1]]]] ⧗ (Parigot a) → a → (Parigot a) 34 | 35 | …;… snoc 36 | 37 | :test (snoc ('a' : ('b' : empty)) 'c') ('a' : ('b' : ('c' : empty))) 38 | 39 | iter [[[0 ι ρ ρ]]] 40 | ρ [[3 (1 0 0)]] 41 | ι [[4]] 42 | -------------------------------------------------------------------------------- /std/Map.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | # Generic map implementation using AVL trees 3 | # the key-value pair is stored in the tree as a Church pair 4 | # some functions require a hash function! 5 | # TODO: what about hash collisions?? 6 | 7 | :import std/Tree/Balanced T 8 | :import std/Option O 9 | :import std/Number N 10 | :import std/Combinator . 11 | :import std/List . 12 | 13 | ‣ &[[[[[N.compare-case 4 3 2 ^1 ^0]]]]] ⧗ (Compare Number) 14 | 15 | # key to element (for searching) 16 | ↑‣ [0 : i] ⧗ k → (Pair k v) 17 | 18 | # empty map 19 | empty T.empty ⧗ (Map k v) 20 | 21 | # returns true if a value is in a map 22 | has? [[T.has? ↑(1 0)]] ⧗ (k → Number) → k → (Map k v) → Boolean 23 | 24 | # counts the key-value pairs in a map 25 | size T.size ⧗ (Map k v) → Number 26 | 27 | # returns the value of a key (or none) 28 | lookup (O.map &ki) ∘∘∘ [[T.find ↑(1 0)]] ⧗ (k → Number) → k → (Map k v) → (Option v) 29 | 30 | # inserts (or replaces) a key with a value in a map 31 | insert [[[T.insert ((2 1) : 0)]]] ⧗ (k → Number) → k → v → (Map k v) → (Map k v) 32 | 33 | :test (has? i (+2) (insert i (+2) "two" empty)) ([[1]]) 34 | :test (lookup i (+2) (insert i (+2) "two" empty)) (O.some "two") 35 | -------------------------------------------------------------------------------- /std/Box.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2023 Marvin Borner 2 | # a box can store a single item very efficiently 3 | # similar structure to std/Option 4 | 5 | :import std/Logic . 6 | :import std/Combinator . 7 | 8 | # a empty box 9 | empty true ⧗ (Box a) 10 | 11 | # returns true if the box is empty 12 | empty? [0 true [false]] ⧗ (Box a) → Boolean 13 | 14 | ∅?‣ empty? 15 | 16 | :test (∅?empty) (true) 17 | 18 | # builds a box out of a value 19 | box [[[0 2]]] ⧗ a → (Box a) 20 | 21 | <>‣ box 22 | 23 | # returns true if the box is set 24 | set? [0 false [true]] ⧗ (Box a) → Boolean 25 | 26 | :test (set? <>[0]) (true) 27 | :test (set? empty) (false) 28 | 29 | # sets the value of a empty box, ignores argument if already set 30 | store! [[∅?1 <>0 1]] ⧗ (Box a) → a → (Box a) 31 | 32 | :test (store! <>[[0]] [[1]]) (<>[[0]]) 33 | :test (store! empty [[1]]) (<>[[1]]) 34 | 35 | # sets/overrides the value of a box 36 | set! [[<>0]] ⧗ (Box a) → a → (Box a) 37 | 38 | :test (set! <>[[0]] [[1]]) (<>[[1]]) 39 | :test (set! empty [[1]]) (<>[[1]]) 40 | 41 | # extracts value from a box or returns first argument if none 42 | get [[0 1 i]] ⧗ a → (Box b) → c 43 | 44 | :test (get [[0]] <>[[1]]) ([[1]]) 45 | :test (get [[0]] empty) ([[0]]) 46 | -------------------------------------------------------------------------------- /std/Number/Bruijn.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2023 Marvin Borner 2 | # de Bruijn numeral system (named by me) 3 | # proof that this numeral system does not support zero/eq/sub/etc. is in 4 | # Wadsworth, Christopher. "Some unusual λ-calculus numeral systems." 5 | # very sad indeed 6 | 7 | zero (+0d) 8 | 9 | # increments de Bruijn numeral 10 | inc [[[2 1]]] ⧗ Bruijn → Bruijn 11 | 12 | ++‣ inc 13 | 14 | :test (++(+0d)) ((+1d)) 15 | :test (++(+5d)) ((+6d)) 16 | 17 | # decrements de Bruijn numeral 18 | dec [[1 0 0]] ⧗ Bruijn → Bruijn 19 | 20 | --‣ dec 21 | 22 | :test (--(+1d)) ((+0d)) 23 | :test (--(+5d)) ((+4d)) 24 | 25 | # adds de Bruijn numerals 26 | add [[[2 (1 0)]]] ⧗ Bruijn → Bruijn → Bruijn 27 | 28 | …+… add 29 | 30 | :test ((+5d) + (+0d)) ((+5d)) 31 | :test ((+0d) + (+5d)) ((+5d)) 32 | :test ((+5d) + (+2d)) ((+7d)) 33 | 34 | # multiplies de Bruijn numeral with unary number 35 | mul [[1 0]] ⧗ Unary → Bruijn → Bruijn 36 | 37 | …⋅… mul 38 | 39 | :test ((+5u) ⋅ (+5d)) ((+25d)) 40 | :test ((+0u) ⋅ (+5d)) ((+0d)) 41 | :test ((+5u) ⋅ (+0d)) ((+0d)) 42 | 43 | # factorial function from unary number 44 | fac [0 [[0 (1 [[2 1 (1 0)]])]] [(+1d)] [0]] ⧗ Unary → Bruijn 45 | 46 | # A000217 47 | triangular [[1 [[0 (1 ([[[2 1]]] 0))]] [1] [0]]] ⧗ Unary → Bruijn 48 | -------------------------------------------------------------------------------- /.github/workflows/gen.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | concurrency: 14 | group: "pages" 15 | cancel-in-progress: false 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: actions/setup-python@v4 23 | - name: Setup Pages 24 | id: pages 25 | uses: actions/configure-pages@v3 26 | - name: Install dependencies 27 | run: pip3 install mkdocs-terminal 28 | - name: Generate wiki 29 | run: cd docs/ && mkdocs build 30 | - name: Generate std 31 | run: cd docs/ && ./gen.sh 32 | - name: Generate std map 33 | run: | 34 | sudo apt install python3 35 | cd std/ && ./generate_map.py >../docs/std_map.json 36 | - name: Upload artifact 37 | uses: actions/upload-pages-artifact@v3 38 | with: 39 | path: docs/ 40 | 41 | deploy: 42 | environment: 43 | name: github-pages 44 | url: ${{ steps.deployment.outputs.page_url }} 45 | runs-on: ubuntu-latest 46 | needs: build 47 | steps: 48 | - name: Deploy to GitHub Pages 49 | id: deployment 50 | uses: actions/deploy-pages@v4 51 | -------------------------------------------------------------------------------- /docs/wiki_src/custom.css: -------------------------------------------------------------------------------- 1 | pre { 2 | width: 100%; 3 | white-space: pre; 4 | overflow: auto !important; 5 | display: grid; 6 | } 7 | 8 | code { 9 | overflow: auto !important; 10 | } 11 | 12 | /* to match std */ 13 | pre, pre > code, code.bruijn { 14 | background-color: #222222; 15 | } 16 | 17 | pre a { 18 | color: var(--font-color); 19 | } 20 | 21 | .headerlink { 22 | display: none; 23 | } 24 | 25 | h2,h3,h4,h5,h6 { 26 | line-height: var(--global-line-height); 27 | margin-bottom: 0.5em; 28 | } 29 | 30 | .terminal h1 { 31 | font-size: 2.33em; 32 | } 33 | 34 | .terminal h1 > * { 35 | font-size: inherit; 36 | } 37 | 38 | .terminal h2 { 39 | font-size: 1.83em; 40 | } 41 | 42 | .terminal h2 > * { 43 | font-size: inherit; 44 | } 45 | 46 | .terminal h3{ 47 | font-size: 1.50em; 48 | } 49 | 50 | .terminal h3 > * { 51 | font-size: inherit; 52 | } 53 | 54 | .terminal h4 { 55 | font-size: 1.33em; 56 | } 57 | 58 | .terminal h4 > * { 59 | font-size: inherit; 60 | } 61 | 62 | .terminal h5 { 63 | font-size: 1.16em; 64 | } 65 | 66 | .terminal h5 > * { 67 | font-size: inherit; 68 | } 69 | 70 | .terminal h6 { 71 | font-size: 1em; 72 | } 73 | 74 | .terminal h6 > * { 75 | font-size: inherit; 76 | } 77 | 78 | pre:has(.language-code-showcase), .language-code-showcase { 79 | background-color: transparent; 80 | border: 0; 81 | margin: 0 auto; 82 | width: fit-content; 83 | max-width: 100%; 84 | white-space: pre; 85 | } 86 | -------------------------------------------------------------------------------- /std/Monad/List.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2023 Marvin Borner 2 | # monadic interface for anything based on lists (e.g. IO, strings) 3 | # afaik originally proposed by John Tromp and inspired by Haskell 4 | 5 | :import std/List . 6 | :import std/Combinator . 7 | 8 | read [0] ⧗ a → (M a) 9 | 10 | return [[1 : 0]] ⧗ a → (M a) 11 | 12 | pure return ⧗ a → (M a) 13 | 14 | # monadic bind operator 15 | bind [[[2 0 1]]] ⧗ (M a) → (a → (M b)) → (M a) 16 | 17 | …>>=… bind 18 | 19 | :test ((read >>= return) "woa") ("woa") 20 | 21 | # monadic reverse bind operator 22 | …=<<… \…>>=… ⧗ (a → (M b)) → (M a) → (M b) 23 | 24 | :test ((return =<< read) "woa") ("woa") 25 | 26 | # monadic compose operator 27 | …>>… [[1 >>= [1]]] ⧗ (M a) → (M b) → (M b) 28 | 29 | :test ((read >> (return 'a')) "hah") ("aah") 30 | 31 | # monadifies a list 32 | lift-m map ⧗ (a → b) → (M a) → (M b) 33 | 34 | # monadifies a list with two monadic arguments 35 | lift-m2 [[[concat ([[4 1 0] <$> 1] <$> 1)]]] ⧗ (a → b → c) → (M a) → (M b) → (M c) 36 | 37 | # evaluates monadic actions 38 | sequence foldr (lift-m2 cons) {}empty ⧗ (List (M a)) → (M (List a)) 39 | 40 | >‣ [sequence ∘∘ 0] 41 | 42 | # traverses list based on modifier 43 | traverse sequence ∘∘ map ⧗ (a → (M b)) → (N a) → (M (N b)) 44 | 45 | # performs action n times 46 | replicate-m >replicate ⧗ Number → (M a) → (M (List a)) 47 | 48 | # maps elements to a monadic action 49 | map-m >map ⧗ (a → (M b)) → (List a) → (M (List b)) 50 | -------------------------------------------------------------------------------- /broogle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env bash 2 | # MIT License, Copyright (c) 2023 Marvin Borner 3 | # TODO: jq is quite slow tbh hmm 4 | 5 | # TODO: Custom path and absolute library path 6 | path="std/" 7 | 8 | pretty() { 9 | jq -c 'select(.type == "match") | .data' <<<"$1" | 10 | while IFS=$"\n" read -r c; do 11 | read file line name type < <(echo $(jq -r '.path.text, .line_number, (.lines.text | split(" ")[0]), (.lines.text | split("⧗")[1] | gsub("^\\s+|\\s+$";""))' <<<"$c" 2>/dev/null)) 12 | desc=$(sed -n "$((line > 10 ? line - 10 : 1)),${line}p" "$file" | awk -v RS= 'END{print}' | awk '/^# /') 13 | alia=$(sed -n "$line,$ p" "$file" | sed -n "s/^\\(.*\\) $name$/\\1/p" | xargs) 14 | [ -z "$type" ] && type="Any" 15 | 16 | echo -e "\e[101m\e[30mFOUND\e[0m \e[95m$name\e[0m ⧗ \e[33m$type\e[0m" 17 | [ -z "$alia" ] || echo -e "\e[106m\e[30malso\e[0m \e[95m$alia\e[0m" 18 | echo -e "\e[104m\e[30min\e[0m $file:$line" 19 | [ -z "$desc" ] || echo -e "\e[3;2m$desc\e[0m" 20 | echo 21 | done 22 | } 23 | 24 | search_type() { 25 | json=$(rg --json "^.* .* ⧗ $1$" "$path") 26 | pretty "$json" 27 | } 28 | 29 | search_name() { 30 | json=$(rg --json "^$1 .*$" "$path") 31 | pretty "$json" 32 | } 33 | 34 | # TODO: Add search by comment 35 | 36 | case $1 in 37 | -t) 38 | shift 39 | search_type "${@//->/→}" 40 | ;; 41 | -f) 42 | shift 43 | search_name "$@" 44 | ;; 45 | -c) 46 | echo "sorry, not supported right now" 47 | ;; 48 | *) 49 | echo "unknown command $1, please use -t/f/c" 50 | ;; 51 | esac 52 | -------------------------------------------------------------------------------- /samples/rosetta/special_factorials.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/List . 3 | :import std/Math . 4 | 5 | factorial [∏ (+1) → 0 | [0]] 6 | 7 | superfactorial [∏ (+1) → 0 | factorial] 8 | 9 | hyperfactorial [∏ (+1) → 0 | [0 ** 0]] 10 | 11 | alternating-factorial y [[=?0 0 ((factorial 0) - (1 --0))]] 12 | 13 | exponential-factorial y [[=?0 0 (0 ** (1 --0))]] 14 | 15 | :test ((factorial (+4)) =? (+24)) ([[1]]) 16 | :test ((superfactorial (+4)) =? (+288)) ([[1]]) 17 | :test ((hyperfactorial (+4)) =? (+27648)) ([[1]]) 18 | :test ((alternating-factorial (+3)) =? (+5)) ([[1]]) 19 | :test ((exponential-factorial (+4)) =? (+262144)) ([[1]]) 20 | 21 | invfac y [[[compare-case 1 (2 ++1 0) (-1) 0 (∏ (+0) → --1 | ++‣)]]] (+0) 22 | 23 | :test ((invfac (+1)) =? (+0)) ([[1]]) 24 | :test ((invfac (+2)) =? (+2)) ([[1]]) 25 | :test ((invfac (+6)) =? (+3)) ([[1]]) 26 | :test ((invfac (+24)) =? (+4)) ([[1]]) 27 | :test ((invfac (+120)) =? (+5)) ([[1]]) 28 | :test ((invfac (+720)) =? (+6)) ([[1]]) 29 | :test ((invfac (+5040)) =? (+7)) ([[1]]) 30 | :test ((invfac (+40320)) =? (+8)) ([[1]]) 31 | :test ((invfac (+362880)) =? (+9)) ([[1]]) 32 | :test ((invfac (+3628800)) =? (+10)) ([[1]]) 33 | :test ((invfac (+119)) =? (-1)) ([[1]]) 34 | 35 | seq-a [((superfactorial 0) : ((hyperfactorial 0) : {}(alternating-factorial 0)))] <$> (iterate ++‣ (+0)) 36 | 37 | seq-b exponential-factorial <$> (iterate ++‣ (+0)) 38 | 39 | # return first 10/4 elements of both sequences 40 | main [(take (+10) seq-a) : {}(take (+5) seq-b)] 41 | -------------------------------------------------------------------------------- /docs/genstd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env bash 2 | 3 | set -e 4 | 5 | rm -rf std/ && mkdir -p std/ 6 | 7 | files=$(find ../std/ -type f -name "*.bruijn" -printf '%h\0%d\0%p\n' | sort -t '\0' -n | awk -F '\0' '{print $3}') 8 | links="" 9 | 10 | tot_ntests=0 11 | tot_ndefs=0 12 | prefix="seeelefant" 13 | for file in $files; do 14 | name=$(cut -c8- <<<"$file") 15 | _prefix=$(cut -c8- <<<"$file" | sed -rn 's@^(.*)/.*$@\1@p') 16 | if ! [ "$prefix" = "$_prefix" ]; then 17 | prefix=$_prefix 18 | links="$links\n

std/$prefix

    " 19 | fi 20 | filename=$(sed s@/@_@g <<<"$name") 21 | ndefs=$(grep -cP "^[^:# \t]" "$file" || true) 22 | ntests=$(grep -cP "^:test" "$file" || true) 23 | if [ "$ndefs" = 0 ]; then 24 | stats="(alias)" 25 | else 26 | stats="($ndefs definitions, $ntests tests)" 27 | fi 28 | links="$links\n
  1. :import $(basename "$name" .bruijn) $stats
  2. " 29 | awk 'NR==FNR { gsub("<", "\\<", $0); gsub(">", "\\>", $0); a[n++]=$0; next } /CONTENT/ { for (i=0;i"std/$filename.html" 30 | sed -i -e "s@NAME@$name@g" -e "s@INFO@@g" "std/$filename.html" 31 | 32 | tot_ndefs=$((tot_ndefs + ndefs)) 33 | tot_ntests=$((tot_ntests + ntests)) 34 | done 35 | 36 | sed -e "s@STATS@Total: $tot_ndefs definitions, $tot_ntests tests@g" -e "s@LINKS@$links@g" std.template >std/index.html 37 | 38 | cp res/* code.js content.css index.css code.css std/ 39 | -------------------------------------------------------------------------------- /docs/gensamples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env bash 2 | 3 | set -e 4 | 5 | rm -rf samples/ && mkdir -p samples/ 6 | 7 | files=$(find ../samples/ -type f -name "*.bruijn" -printf '%h\0%d\0%p\n' | sort -t '\0' -n | awk -F '\0' '{print $3}') 8 | links="" 9 | 10 | prefix="" 11 | for file in $files; do 12 | name=$(cut -c12- <<<"$file") 13 | _prefix=$(cut -d/ -f1 <<<"$name") 14 | if ! [ "$prefix" = "$_prefix" ]; then 15 | prefix=$_prefix 16 | links="$links\n

    $prefix

      " 17 | fi 18 | filename=$(sed s@/@_@g <<<"$name") 19 | links="$links\n
    • $name
    • " 20 | 21 | if [ "$prefix" = "euler" ]; then 22 | info="Problem description" 23 | elif [ "$prefix" = "aoc" ]; then 24 | year=$(cut -c5-8 <<<"$name") 25 | day=$(cut -c10-11 <<<"$name" | sed 's/^0*//') 26 | info="Problem description" 27 | elif [ "$prefix" = "rosetta" ]; then 28 | info="Problem description" 29 | else 30 | info="" 31 | fi 32 | 33 | awk 'NR==FNR { gsub("<", "\\<", $0); gsub(">", "\\>", $0); a[n++]=$0; next } /CONTENT/ { for (i=0;i"samples/$filename.html" 34 | sed -i -e "s@NAME@$name@g" -e "s@INFO@$info@g" "samples/$filename.html" 35 | done 36 | 37 | sed -e "s@LINKS@$links@g" samples.template >samples/index.html 38 | 39 | cp res/* code.js content.css index.css code.css samples/ 40 | -------------------------------------------------------------------------------- /docs/wiki_src/coding/compilation.md: -------------------------------------------------------------------------------- 1 | # Compilation 2 | 3 | Bruijn can be compiled to John Tromp's binary lambda calculus (BLC). 4 | 5 | BLC uses the following encoding: 6 | 7 | | term | lambda | bruijn | BLC | 8 | |:-------------|:-------|:--------|:-----------------| 9 | | abstraction | λM | `[M]` | 00M | 10 | | application | (MN) | `(M N)` | 01MN | 11 | | bruijn index | i | `i` | 1i+10 | 12 | 13 | There are two modes of compilation: 14 | 15 | - **Bitwise** compiles to BLC and encodes every bit as 1 bit and pads 16 | the last remaining byte: `bruijn -b path` 17 | - **ASCII** compiles to BLC and encodes every bit as 1 ASCII character 18 | (`'0'`/`'1'`): `bruijn -B path` 19 | 20 | ## Compilation overhead 21 | 22 | By default, bruijn's compilation to BLC results in much redundant code, 23 | since every used function gets substituted and translated separately. In 24 | `((+3) + (+4) + (+3))`{.bruijn}, for example, `add`{.bruijn} gets 25 | compiled to BLC two times, resulting in a redundant overhead of around 26 | 3500 bits. 27 | 28 | If you want smaller (and more efficient) files, install 29 | [BLoC](https://github.com/marvinborner/BLoC) and 30 | [BLoCade](https://github.com/marvinborner/BLoCade). The combination of 31 | these tools results in the abstraction of shared terms and translation 32 | to a specified target. 33 | 34 | With the bruijn CLI, BLoCade can be executed directly using the flag 35 | `-t TARGET`, where `TARGET` is one of the supported targets. 36 | -------------------------------------------------------------------------------- /docs/wiki_src/coding/mixfix.md: -------------------------------------------------------------------------------- 1 | # Mixfix 2 | 3 | Mixfix functions allow arbitrary infix operations based on "substitution 4 | holes" by using the `…` symbol in definitions. The symbols and terms 5 | always need to be delimited by a space character, otherwise they get 6 | interpreted as a [prefix](prefix.md). 7 | 8 | Example: 9 | 10 | ``` bruijn 11 | …+… add 12 | 13 | # the "holes" get applied in normal order 14 | :test ((+4) + (+3)) (add (+4) (+3)) 15 | ``` 16 | 17 | You can define as many holes as you like. Make sure to place parenthesis 18 | for applications inside substitution holes. 19 | 20 | ``` bruijn 21 | {…<$>…|… [[[2 - 1 + 0]]] 22 | 23 | # evaluated as (5 - 2) + 1 = 4 24 | :test ({ (+5) <$> (+2) | (+1)) ((+4)) 25 | :test ({ ((+3) + (+2)) <$> (+2) | (+1)) ((+4)) 26 | ``` 27 | 28 | You can use them as normal functions by writing the identifier 29 | literally: 30 | 31 | ``` bruijn 32 | :test (…+… (+4) (+3)) (add (+4) (+3)) 33 | ``` 34 | 35 | ## Associativity 36 | 37 | If you write several mixfix operations without parenthesis, they will be 38 | reduced in left-associative order. Just make sure that the longer mixfix 39 | chain is not actually overwritten by *another* mixfix chain. 40 | 41 | ``` bruijn 42 | :test ((+8) + (-4) ⋅ (-2)) ((-8)) 43 | 44 | …+…⋅… [[[2 + (1 ⋅ 0)]]] 45 | 46 | :test ((+8) + (-4) ⋅ (-2)) ((+16)) 47 | ``` 48 | 49 | ## Allowed characters 50 | 51 | Mixfix functions can use any characters of `!?*@:;+-_#$%^&<>/\|{}~=` as 52 | well as mathematical unicode operators and arrows. Each part must be at 53 | least 1 character long. 54 | -------------------------------------------------------------------------------- /docs/wiki_src/coding/laziness.md: -------------------------------------------------------------------------------- 1 | # Laziness 2 | 3 | Due to the call-by-need reduction order of bruijn, several operations 4 | are lazily evaluated (automagically!). 5 | 6 | ## Infinite lists 7 | 8 | You can use infinite list generators like `repeat`{.bruijn} or 9 | `iterate`{.bruijn} to lazily interact with list elements. 10 | 11 | ``` bruijn 12 | :import std/List . 13 | 14 | :test (length (take (+3) (repeat (+4)))) ((+3)) 15 | :test (take (+5) (iterate ++‣ (+0))) (((+0) : ((+1) : ((+2) : ((+3) : {}(+4)))))) 16 | ``` 17 | 18 | ## Math 19 | 20 | ``` bruijn 21 | # power function 22 | :test ((iterate (…⋅… (+2)) (+1)) !! (+3)) ((+8)) 23 | 24 | # prime numbers 25 | primes nub ((…≠?… (+1)) ∘∘ gcd) (iterate ++‣ (+2)) ⧗ (List Number) 26 | 27 | :test (take (+4) primes) ((+2) : ((+3) : ((+5) : {}(+7)))) 28 | 29 | # fibonacci 30 | fibs head <$> (iterate [~0 : (^0 + ~0)] ((+0) : (+1))) ⧗ (List Number) 31 | 32 | :test (take (+4) primes) ((+0) : ((+1) : ((+1) : {}(+2)))) 33 | ``` 34 | 35 | ## Optimization 36 | 37 | Laziness can (in some cases) produce huge performance boosts. For 38 | example: 39 | 40 | ``` bruijn 41 | # 10 seconds 42 | :time (+10) ** (+1000) 43 | 44 | # 0.02 seconds 45 | :time ((+10) ** (+1000)) =? (+42) 46 | ``` 47 | 48 | This works because a ternary number is just a concatenation of trits 49 | which (in this case) gets recursively generated by the `pow`{.bruijn} 50 | function. The `eq?`{.bruijn} function just throws away the first 51 | argument if it's already clear that the numbers can't be equal (in this 52 | case after the first argument got bigger than `(+400)`{.bruijn}). 53 | -------------------------------------------------------------------------------- /docs/wiki_src/coding/test-driven-development.md: -------------------------------------------------------------------------------- 1 | # Test driven development (TDD) 2 | 3 | The suggested technique for bruijn development is the TDD method. When 4 | creating functions, we suggest the following procedure: 5 | 6 | - Write a comment, a type signature, and the head of the function 7 | 8 | ``` bruijn 9 | # returns the item at index in a list, starting from 0 10 | index y [[[rec]]] ⧗ (List a) → Number → a 11 | ``` 12 | 13 | - Write several tests including edge cases 14 | 15 | ``` bruijn 16 | # returns the item at index in a list, starting from 0 17 | index y [[[rec]]] ⧗ (List a) → Number → a 18 | 19 | :test (empty !! (+0)) (empty) 20 | :test ({}(+1) !! (+0)) ((+1)) 21 | :test (((+1) : ((+2) : {}(+3))) !! (+0)) ((+1)) 22 | :test (((+1) : ((+2) : {}(+3))) !! (+2)) ((+3)) 23 | :test (((+1) : ((+2) : {}(+3))) !! (-1)) (empty) 24 | :test (((+1) : ((+2) : {}(+3))) !! (+3)) (empty) 25 | ``` 26 | 27 | - Finish the implementation until all tests pass (e.g. using the 28 | [`:watch`{.bruijn} repl command](REPL.md#watch)) 29 | - Refactor and clean up the definition 30 | 31 | ``` bruijn 32 | # returns the item at index in a list, starting from 0 33 | index y [[[rec]]] ⧗ (List a) → Number → a 34 | rec 0 [[[case-index]]] case-end 35 | case-index =?4 2 (5 --4 1) 36 | case-end empty 37 | 38 | :test (empty !! (+0)) (empty) 39 | :test ({}(+1) !! (+0)) ((+1)) 40 | :test (((+1) : ((+2) : {}(+3))) !! (+0)) ((+1)) 41 | :test (((+1) : ((+2) : {}(+3))) !! (+2)) ((+3)) 42 | :test (((+1) : ((+2) : {}(+3))) !! (-1)) (empty) 43 | :test (((+1) : ((+2) : {}(+3))) !! (+3)) (empty) 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Bruijn Wiki 2 | docs_dir: wiki_src/ 3 | site_dir: wiki/ 4 | 5 | extra_css: [custom.css, ../code.css] 6 | extra_javascript: [../code.js] 7 | 8 | theme: 9 | name: terminal 10 | palette: dark 11 | features: 12 | - navigation.top.cursor_animation.hide 13 | - footer.prev_next 14 | 15 | nav: 16 | - Index: index.md 17 | - Introduction: 18 | - introduction/installation.md 19 | - introduction/setup.md 20 | - introduction/lambda-calculus.md 21 | - introduction/syntax.md 22 | - introduction/animations.md 23 | - Coding: 24 | - coding/REPL.md 25 | - coding/mixfix.md 26 | - coding/prefix.md 27 | - coding/recursion.md 28 | - coding/data-structures.md 29 | - coding/combinators.md 30 | - coding/currying.md 31 | - coding/laziness.md 32 | - coding/IO.md 33 | - coding/meta-programming.md 34 | - UFCS: coding/uniform-function-call-syntax.md 35 | - coding/style.md 36 | - TDD: coding/test-driven-development.md 37 | - coding/compilation.md 38 | - Technical: 39 | - technical/performance.md 40 | - technical/reduction.md 41 | - Examples: coding/examples.md 42 | - Landing page: https://bruijn.marvinborner.de 43 | - GitHub: https://github.com/marvinborner/bruijn 44 | 45 | markdown_extensions: 46 | - attr_list 47 | - def_list 48 | - footnotes 49 | - md_in_html 50 | - meta 51 | - toc: 52 | permalink: "#" 53 | permalink_title: Anchor link to this section for reference 54 | - pymdownx.caret 55 | - pymdownx.mark 56 | - pymdownx.tilde 57 | - pymdownx.snippets: 58 | base_path: 59 | - wiki_src 60 | -------------------------------------------------------------------------------- /src/Config.hs: -------------------------------------------------------------------------------- 1 | -- MIT License, Copyright (c) 2022 Marvin Borner 2 | module Config where 3 | 4 | import Data.Maybe ( fromMaybe 5 | , isNothing 6 | ) 7 | 8 | data ArgMode = ArgEval | ArgEvalBblc | ArgEvalBlc | ArgDumpBblc | ArgDumpBlc 9 | 10 | data Args = Args 11 | { _argMode :: ArgMode 12 | , _argNoTests :: Bool 13 | , _argVerbose :: Bool 14 | , _argOptimize :: Bool 15 | , _argToTarget :: String 16 | , _argReducer :: String 17 | , _argPath :: Maybe String 18 | } 19 | 20 | data EvalConf = EvalConf 21 | { _isRepl :: Bool 22 | , _isVerbose :: Bool 23 | , _evalTests :: Bool 24 | , _optimize :: Bool 25 | , _nicePath :: String 26 | , _path :: String 27 | , _evalPaths :: [String] 28 | , _toTarget :: String 29 | , _reducer :: String 30 | , _hasArg :: Bool 31 | } 32 | 33 | argsToConf :: Args -> EvalConf 34 | argsToConf args = EvalConf { _isRepl = isNothing $ _argPath args 35 | , _isVerbose = _argVerbose args 36 | , _evalTests = not $ _argNoTests args 37 | , _optimize = _argOptimize args 38 | , _path = path 39 | , _nicePath = path 40 | , _evalPaths = [] 41 | , _toTarget = _argToTarget args 42 | , _reducer = _argReducer args 43 | , _hasArg = False 44 | } 45 | where path = fromMaybe "" (_argPath args) 46 | -------------------------------------------------------------------------------- /samples/fun/interpreter.bruijn: -------------------------------------------------------------------------------- 1 | # originally written in obfuscated form by John Tromp for IOCCC 2012 2 | # run with `printf 000010 | bruijn interpreter.bruijn` 3 | # note that Tromp's IO uses an additional abstraction at the beginning ("00" ++ ...) 4 | 5 | :import std/Combinator . 6 | :import std/Number/Binary . 7 | :import std/List . 8 | 9 | uni [0 0] int [0 Ω] ⧗ (List Bit) → Term 10 | int [[[0 go (2 2) 1]]] 11 | go [[[[2 match]]]] 12 | match [4 case-0 case-1] 13 | case-0 2 exp 14 | exp [1 case-00 case-01] 15 | case-00 2 abs 16 | abs [[2 [0 1 2]]] 17 | case-01 3 [3 app] 18 | app [2 0 (1 0)] 19 | case-1 0 case-10 case-11 20 | case-10 1 cont 21 | cont [0 1] 22 | case-11 [3 [3 var] 4] 23 | var [1 (0 3)] 24 | 25 | str→blc map (c ∘ lsb) 26 | 27 | :test (str→blc "0010") ([[1]] : ([[1]] : ([[0]] : {}[[1]]))) 28 | 29 | exec uni ∘ str→blc 30 | 31 | :test (exec "0011") ([0]) 32 | :test (exec "001111") ([0]) 33 | :test (exec "000010") ([0]) 34 | :test (exec "00000001110011100111010") ((+3u)) 35 | :test (exec "0001010000000001011111011001011110110100000011100111001110100000011100111001110011100111010") ((+8u)) 36 | :test (exec "00010000010101110000001100111000000101111011001110100011000100000011100111001110011100111010") ((+120u)) 37 | :test (exec "000101010100010001110000101110110100001110000101110110100000000001011000000001010111111110111111001011111110111110111011011001000000010111101011000000001011011101100000100001011000000001110011001100110011001110011100110111000010110000000011001110011001100110011100111001101110000101100000000111001110011001100110011100111001101110000010") ("cba") 38 | 39 | main exec 40 | -------------------------------------------------------------------------------- /std/List/ChurchFold.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2025 Marvin Borner 2 | # from Wikipedia, right-folded Church encoding 3 | 4 | :import std/Logic . 5 | :import std/Number/Ternary . 6 | 7 | # empty list element 8 | empty [[0]] 9 | 10 | # returns true if a list is empty 11 | empty? [0 [[false]] true] 12 | 13 | # creates list with single element 14 | singleton [[[1 2 0]]] 15 | 16 | {}‣ singleton 17 | 18 | :test (empty? ({}(+2))) (false) 19 | 20 | # prepends an element to a list 21 | cons [[[[1 3 (2 1 0)]]]] 22 | 23 | …:… cons 24 | 25 | # returns the head of a list or empty 26 | head [0 [[1]] false] 27 | 28 | ^‣ head 29 | 30 | :test (^((+1) : {}(+2))) ((+1)) 31 | 32 | # returns the tail of a list or empty 33 | tail [[[2 [[[0 2 (1 4)]]] [1] [[0]]]]] 34 | 35 | ~‣ tail 36 | 37 | :test (~((+1) : {}(+2))) ({}(+2)) 38 | 39 | # returns the length of a list in balanced ternary 40 | length [0 [add (+1)] (+0)] 41 | 42 | ∀‣ length 43 | 44 | :test (∀((+1) : {}(+2))) ((+2)) 45 | :test (∀empty) ((+0)) 46 | 47 | # appends two lists 48 | append [[[[3 1 (2 1 0)]]]] 49 | 50 | …++… append 51 | 52 | :test ({}(+1) ++ empty) ({}(+1)) 53 | :test (empty ++ {}(+1)) ({}(+1)) 54 | :test (empty ++ empty) (empty) 55 | :test (((+1) : ((+2) : {}(+3))) ++ {}(+4)) ((+1) : ((+2) : ((+3) : {}(+4)))) 56 | 57 | # maps each element to a function 58 | map [[[[2 [[3 (5 1) 0]] 0]]]] 59 | 60 | …<$>… map 61 | 62 | :test (++‣ <$> ((+1) : ((+2) : {}(+3)))) ((+2) : ((+3) : {}(+4))) 63 | 64 | # applies a right fold on a list 65 | foldr [[[0 2 1]]] 66 | 67 | :test ((foldr …+… (+0) ((+1) : ((+2) : {}(+3)))) =? (+6)) (true) 68 | :test ((foldr …-… (+2) ((+1) : ((+2) : {}(+3)))) =? (+0)) (true) 69 | -------------------------------------------------------------------------------- /std/Logic/Ternary.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | 3 | # true 4 | true [[[0]]] 5 | 6 | # maybe (true-ish) 7 | maybe [[[1]]] 8 | 9 | # false 10 | false [[[2]]] 11 | 12 | # inverts trit value 13 | ¬‣ [0 true maybe false] ⧗ Trit → Trit 14 | 15 | :test (¬true) (false) 16 | :test (¬maybe) (maybe) 17 | :test (¬false) (true) 18 | 19 | # true if both args are true-ish 20 | and? [[1 (0 1 1 1) (0 0 0 1) (0 0 0 0)]] ⧗ Trit → Trit → Trit 21 | 22 | …⋀?… and? 23 | 24 | :test (true ⋀? true) (true) 25 | :test (true ⋀? maybe) (maybe) 26 | :test (true ⋀? false) (false) 27 | :test (maybe ⋀? true) (maybe) 28 | :test (maybe ⋀? maybe) (maybe) 29 | :test (maybe ⋀? false) (false) 30 | :test (false ⋀? true) (false) 31 | :test (false ⋀? maybe) (false) 32 | :test (false ⋀? false) (false) 33 | 34 | # true if one of the args is true-ish 35 | or? [[1 (0 0 0 0) (0 1 0 0) (0 1 1 1)]] ⧗ Trit → Trit → Trit 36 | 37 | …⋁?… or? 38 | 39 | :test (true ⋁? true) (true) 40 | :test (true ⋁? maybe) (true) 41 | :test (true ⋁? false) (true) 42 | :test (maybe ⋁? true) (true) 43 | :test (maybe ⋁? maybe) (maybe) 44 | :test (maybe ⋁? false) (maybe) 45 | :test (false ⋁? true) (true) 46 | :test (false ⋁? maybe) (maybe) 47 | :test (false ⋁? false) (false) 48 | 49 | # mathematical iff (if and only if) definition 50 | iff [[1 (0 true 0 1) (0 1 1 1) (0 0 0 0)]] ⧗ Trit → Trit → Trit 51 | 52 | …⇔?… iff 53 | 54 | :test (true ⇔? true) (true) 55 | :test (true ⇔? maybe) (maybe) 56 | :test (true ⇔? false) (false) 57 | :test (maybe ⇔? true) (maybe) 58 | :test (maybe ⇔? maybe) (maybe) 59 | :test (maybe ⇔? false) (maybe) 60 | :test (false ⇔? true) (false) 61 | :test (false ⇔? maybe) (maybe) 62 | :test (false ⇔? false) (true) 63 | -------------------------------------------------------------------------------- /std/generate_map.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python 2 | 3 | from pathlib import Path 4 | import json 5 | 6 | 7 | def list_defs(path, kind, prefix): 8 | res = [] 9 | for line in open(path).readlines(): 10 | if line.startswith(":input"): 11 | parts = line.split(" ") 12 | input_path = parts[1].strip().split("std/")[1] + ".bruijn" 13 | # not using kind="input" is important, "import"s should be inherited 14 | res = res + list_defs(input_path, kind, prefix) 15 | elif line.startswith(":import") and kind == "input": 16 | parts = line.split(" ") 17 | import_path = parts[1].strip().split("std/")[1] + ".bruijn" 18 | new_prefix = ( 19 | "" if parts[2].strip() == "." else parts[2].strip() + "." 20 | ) 21 | new_prefix = prefix + new_prefix if prefix else new_prefix 22 | res = res + list_defs(import_path, "import", new_prefix) 23 | elif ( 24 | line.startswith(":") 25 | or line.startswith("# ") 26 | or line.strip() == "" 27 | or line[0].isspace() 28 | ): 29 | continue 30 | else: 31 | parts = line.strip().split(" ") 32 | res.append( 33 | { 34 | "name": prefix + parts[0], 35 | "source": path, 36 | "kind": kind, 37 | } 38 | ) 39 | return res 40 | 41 | 42 | res = {} 43 | files = Path(".").glob("**/*.bruijn") 44 | for file in files: 45 | path = str(file) 46 | if path != "All.bruijn": 47 | res[path] = list_defs(path, "input", "") 48 | print(json.dumps(res)) 49 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | name: bruijn 2 | version: 0.1.0.0 3 | github: "githubuser/bruijn" 4 | license: MIT 5 | author: "Marvin Borner" 6 | maintainer: "develop@marvinborner.de" 7 | copyright: "2022 Marvin Borner" 8 | 9 | extra-source-files: 10 | - readme.md 11 | 12 | data-files: 13 | - config 14 | - std/* 15 | - std/**/* 16 | 17 | # Metadata used when publishing your package 18 | # synopsis: Short description of your package 19 | # category: Web 20 | 21 | # To avoid duplicated efforts in documentation and dealing with the 22 | # complications of embedding Haddock markup inside cabal files, it is 23 | # common to point users to the README.md file. 24 | description: Please see the README on GitHub at 25 | 26 | default-extensions: 27 | - LambdaCase 28 | 29 | dependencies: 30 | - array 31 | - base >= 4.7 && < 5 32 | - binary 33 | - bitstring 34 | - bytestring 35 | - clock 36 | - containers 37 | - deepseq 38 | - directory 39 | - filepath 40 | - haskeline 41 | - megaparsec 42 | - mtl 43 | - optparse-applicative 44 | - process 45 | - random 46 | - time 47 | 48 | library: 49 | source-dirs: src 50 | ghc-options: 51 | - -O3 52 | - -optc-O3 53 | - -funfolding-use-threshold=16 54 | - -Wall 55 | - -Wextra 56 | - -Wincomplete-uni-patterns 57 | - -Wincomplete-record-updates 58 | - -Widentities 59 | - -Wredundant-constraints 60 | 61 | executables: 62 | bruijn: 63 | main: Main.hs 64 | source-dirs: app 65 | ghc-options: 66 | - -O3 67 | - -optc-O3 68 | - -threaded 69 | - -rtsopts 70 | - -with-rtsopts=-N 71 | dependencies: 72 | - bruijn 73 | -------------------------------------------------------------------------------- /std/Logic/Linear.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | # binary logic but every abstraction is referred to *exactly once* (linearity) 3 | # originally by "Harry G. Mairson" in "Linear lambda calculus and PTIME-completeness" 4 | # could potentially be used for better visuals/graphs or more efficient reduction 5 | 6 | # garbage collection hack 7 | gc [0 [0] [0] [0]] ⧗ LinearBoolean → (a → a) 8 | 9 | # true 10 | true [[[0 2 1]]] ⧗ LinearBoolean 11 | 12 | # false 13 | false [[[0 1 2]]] ⧗ LinearBoolean 14 | 15 | # inverts boolean value 16 | not! [[[2 0 1]]] ⧗ LinearBoolean → LinearBoolean 17 | 18 | ¬‣ not! 19 | 20 | :test (¬true) (false) 21 | :test (¬false) (true) 22 | 23 | # true if both args are true 24 | and? [[1 0 false [[gc 0 1]]]] ⧗ LinearBoolean → LinearBoolean → LinearBoolean 25 | 26 | …⋀?… and? 27 | 28 | :test (true ⋀? true) (true) 29 | :test (true ⋀? false) (false) 30 | :test (false ⋀? true) (false) 31 | :test (false ⋀? false) (false) 32 | 33 | # true if not both args are true 34 | nand? [[not! (and? 1 0)]] ⧗ LinearBoolean → LinearBoolean → LinearBoolean 35 | 36 | :test (nand? true true) (false) 37 | :test (nand? true false) (true) 38 | :test (nand? false true) (true) 39 | :test (nand? false false) (true) 40 | 41 | # true if one of the args is true 42 | or? [[1 true 0 [[gc 0 1]]]] ⧗ LinearBoolean → LinearBoolean → LinearBoolean 43 | 44 | …⋁?… or? 45 | 46 | :test (true ⋁? true) (true) 47 | :test (true ⋁? false) (true) 48 | :test (false ⋁? true) (true) 49 | :test (false ⋁? false) (false) 50 | 51 | # true if both args are false 52 | nor? [[not! (or? 1 0)]] ⧗ LinearBoolean → LinearBoolean → LinearBoolean 53 | 54 | :test (nor? true true) (false) 55 | :test (nor? true false) (false) 56 | :test (nor? false true) (false) 57 | :test (nor? false false) (true) 58 | -------------------------------------------------------------------------------- /std/Pair.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2022 Marvin Borner 2 | 3 | :import std/Combinator . 4 | 5 | :input std/Tuples 6 | 7 | pair tuple ⧗ a → b → (Pair a b) 8 | 9 | # extracts first expression from pair 10 | fst &k ⧗ (Pair a b) → a 11 | 12 | ^‣ fst 13 | 14 | :test (^([[0]] : [[1]])) ([[0]]) 15 | 16 | # extracts second expression from pair 17 | snd &ki ⧗ (Pair a b) → b 18 | 19 | ~‣ snd 20 | 21 | :test (~([[0]] : [[1]])) ([[1]]) 22 | 23 | # maps both elements to a function 24 | map [[(1 ^0) : (1 ~0)]] ⧗ (a → b) → (Pair a a) → (Pair b b) 25 | 26 | …<$>… map 27 | 28 | :test ([[1]] <$> ([[0]] : [[1]])) ([[[0]]] : [[[1]]]) 29 | 30 | # maps the first and second element to different functions 31 | bimap [[[(2 ^0) : (1 ~0)]]] ⧗ (a → b) → (c → d) → (Pair a c) → (Pair b d) 32 | 33 | :test (bimap [[1]] [[0]] ([[0]] : [[1]])) ([[[0]]] : [0]) 34 | 35 | # applies both elements of a pair to a function 36 | uncurry [[1 ^0 ~0]] ⧗ (a → b → c) → (Pair a b) → c 37 | 38 | :test (uncurry w ([[0]] : [[1]])) ([[1]]) 39 | 40 | # applies a function to the pair of two values 41 | curry [[[2 (1 : 0)]]] ⧗ ((Pair a b) → c) → a → b → c 42 | 43 | :test (curry fst [[0]] [[1]]) ([[0]]) 44 | 45 | # zips two pairs (basically rotating the elements) 46 | zip [[(^1 : ^0) : (~1 : ~0)]] ⧗ (Pair a b) → (Pair c d) → (Pair (Pair a c) (Pair b d)) 47 | 48 | :test (zip ([[0]] : [[[0]]]) ([[1]] : [[[1]]])) (([[0]] : [[1]]) : ([[[0]]] : [[[1]]])) 49 | 50 | # applies pairs of two pairs as arguments to a function 51 | zip-with [[[(2 ^1 ^0) : (2 ~1 ~0)]]] ⧗ (a → b → c) → (Pair a b) → (Pair a b) → (Pair c c) 52 | 53 | :test (zip-with w ([[0]] : [[[0]]]) ([[1]] : [[[1]]])) ([[1]] : [0]) 54 | 55 | # swaps the elements of a pair 56 | swap [~0 : ^0] ⧗ (Pair a b) → (Pair b a) 57 | 58 | :test (swap ([[0]] : [[1]])) ([[1]] : [[0]]) 59 | -------------------------------------------------------------------------------- /std/Tree/Rose.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2023 Marvin Borner 2 | # Rose trees based on std/List 3 | 4 | :import std/Combinator . 5 | :import std/List L 6 | :import std/Logic . 7 | :import std/Math . 8 | :import std/Pair . 9 | 10 | # a tree node has a label as its head and subtrees as its tail 11 | 12 | # constructs a tree with a label and no branches 13 | leaf [0 : L.empty] ⧗ a → (RoseTree a) 14 | 15 | {:}‣ leaf 16 | 17 | # constructs a node with subnodes 18 | node [[1 : 0]] ⧗ a → (List (RoseTree a)) → (RoseTree a) 19 | 20 | {…:…} node 21 | 22 | # returns the root label of a tree 23 | label ^‣ ⧗ (RoseTree a) → a 24 | 25 | ^‣ label 26 | 27 | # returns the branches of a tree 28 | branches ~‣ ⧗ (RoseTree a) → (List (RoseTree a)) 29 | 30 | ~‣ branches 31 | 32 | # returns true if a tree is empty 33 | empty? [L.empty? ~0] ⧗ (RoseTree a) → Boolean 34 | 35 | ∅?‣ empty? 36 | 37 | :test (∅?({ 'a' : ({:}'b' : L.empty) })) (false) 38 | :test (∅?({:}'a')) (true) 39 | 40 | # applies a function to leaf and the leafs of all branches 41 | map z [[[rec]]] ⧗ (a → b) → (RoseTree a) → (RoseTree b) 42 | rec { (1 ^0) : (L.map (2 1) ~0) } 43 | 44 | …<$>… map 45 | 46 | :test (map ^‣ ({ "woo" : ({:}"oof" : (({ "aah" : ({:}"huh" : L.empty) }) : L.empty)) })) ({ 'w' : ({:}'o' : (({ 'a' : ({:}'h' : L.empty) }) : L.empty)) }) 47 | 48 | # maps a function returning list of trees and concatenates 49 | concat-map L.concat ∘∘ map ⧗ ((RoseTree a) → (List (RoseTree b))) → (List (RoseTree a)) → (List (RoseTree b)) 50 | 51 | # folds a tree 52 | # TODO: fix 53 | fold [[[z [[rec]] 0]]] ⧗ (a → (List b) → b) → (RoseTree a) → b 54 | rec ∅?0 case-end case-fold 55 | case-fold 4 ^0 (1 ~0) 56 | case-end 3 57 | 58 | # :test (fold [[∅?0 (+1) (sum 0)]] ({ 'w' : ({:}'o' : (({ 'a' : ({:}'h' : L.empty) }) : L.empty)) })) ((+4)) 59 | -------------------------------------------------------------------------------- /app/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Options.Applicative 4 | 5 | import Config ( ArgMode(..) 6 | , Args(..) 7 | ) 8 | import Eval 9 | 10 | mode :: Parser ArgMode 11 | mode = 12 | flag' ArgEvalBblc 13 | (long "eval-bblc" <> short 'e' <> help "Evaluate file with BLC bits") 14 | <|> flag' 15 | ArgEvalBlc 16 | (long "eval-blc" <> short 'E' <> help "Evaluate file with ASCII BLC") 17 | <|> flag' ArgDumpBblc 18 | (long "dump-bblc" <> short 'b' <> help "Dump file as BLC bits") 19 | <|> flag' ArgDumpBlc 20 | (long "dump-blc" <> short 'B' <> help "Dump file as ASCII BLC") 21 | 22 | args :: Parser Args 23 | args = 24 | Args 25 | <$> (mode <|> pure ArgEval) 26 | <*> switch (long "yolo" <> short 'y' <> help "Don't run tests") 27 | <*> switch (long "verbose" <> short 'v' <> help "Increase verbosity") 28 | <*> switch 29 | (long "optimize" <> short 'O' <> help 30 | "Optimize program (abstraction of duplicated terms)" 31 | ) 32 | <*> strOption 33 | (long "target" <> short 't' <> metavar "TARGET" <> value "" <> help 34 | "Compile to target using BLoC and BLoCade" 35 | ) 36 | <*> strOption 37 | ( long "reducer" 38 | <> short 'r' 39 | <> metavar "REDUCER" 40 | <> value "HigherOrder" 41 | <> help "Reducer (currently RKNL, ION, or HigherOrder)" 42 | ) 43 | <*> optional (argument str (metavar "PATH" <> help "Path to file")) 44 | 45 | main :: IO () 46 | main = evalMain =<< execParser opts 47 | where 48 | opts = 49 | info (args <**> helper) (fullDesc <> header "bruijn programming language") 50 | -------------------------------------------------------------------------------- /std/Option.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2022 Marvin Borner 2 | # TODO: move to monad? 3 | 4 | :import std/Combinator . 5 | :import std/Logic . 6 | 7 | # empty option 8 | none true ⧗ (Option a) 9 | 10 | # encapsulates value in option 11 | some [[[0 2]]] ⧗ a → (Option a) 12 | 13 | # checks whether option is none 14 | none? [0 true [false]] ⧗ (Option a) → Boolean 15 | 16 | :test (none? none) (true) 17 | :test (none? (some [[0]])) (false) 18 | 19 | # checks whether option is some 20 | some? [0 false [true]] ⧗ (Option a) → Boolean 21 | 22 | :test (some? none) (false) 23 | :test (some? (some [[0]])) (true) 24 | 25 | # applies a function to the value in option 26 | map [[0 none [some (2 0)]]] ⧗ (a → b) → (Option a) → (Option b) 27 | 28 | :test (map [[1]] (some [[0]])) (some [[[0]]]) 29 | :test (map [[1]] none) (none) 30 | 31 | # applies a function to the value in option or returns first arg if none 32 | map-or v ⧗ b → (a → b) → (Option a) → b 33 | 34 | :test (map-or [[[2]]] [[1]] (some [[0]])) ([[[0]]]) 35 | :test (map-or [[[2]]] [[1]] none) ([[[2]]]) 36 | 37 | # extracts value from option or returns first argument if none 38 | unwrap-or [[0 1 i]] ⧗ a → (Option b) → c 39 | 40 | :test (unwrap-or false (some true)) (true) 41 | :test (unwrap-or false none) (false) 42 | 43 | # applies encapsulated value to given function (or return none) 44 | apply [[1 none 0]] ⧗ (Option a) → (a → b) → b 45 | 46 | :test (apply none [some ([[1]] 0)]) (none) 47 | :test (apply (some [[0]]) [some ([[1]] 0)]) (some [[[0]]]) 48 | 49 | result-or [[0 [[0 3]] [[[1 2]]]]] 50 | 51 | :test (result-or "fail" none) ([[0 "fail"]]) 52 | :test (result-or "fail" (some "ok")) ([[1 "ok"]]) 53 | 54 | pure some ⧗ a → (Option a) 55 | 56 | bind [[1 1 0]] ⧗ (Option a) → (a → (Option b)) → (Option a) 57 | 58 | …>>=… bind 59 | 60 | :test (none >>= (pure "idk")) (none) 61 | :test ((some 'a') >>= [pure [1]]) (some ['a']) 62 | -------------------------------------------------------------------------------- /docs/wiki_src/introduction/setup.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | In theory you can use any common editor and operating system. Please 4 | create an [issue on 5 | GitHub](https://github.com/marvinborner/bruijn/issues/new) if you have 6 | problems setting up bruijn. 7 | 8 | ## Recommended setup 9 | 10 | The recommended setup is to use [Vim](https://www.vim.org/) and its 11 | bruijn plugin. 12 | 13 | The Vim plugin adds syntax highlighting for bruijn files as well as a 14 | custom keymap for typing commonly used unicode symbols. 15 | 16 | ### Installation 17 | 18 | 1. Use Vim and [vim-plug](https://github.com/junegunn/vim-plug) (or 19 | comparable plugin managers) 20 | 2. Add `"Plug 'marvinborner/bruijn', { 'rtp': 'editors/vim' }"` to your 21 | `.vimrc` 22 | 3. Run `:PlugInstall` 23 | 24 | ### Unicode abbreviations 25 | 26 | You can find all abbreviations in 27 | [`editors/vim/syntax/bruijn.vim`](https://github.com/marvinborner/bruijn/blob/main/editors/vim/syntax/bruijn.vim). 28 | Abbreviations get replaced after pressing space or `C-]`. Feel free to 29 | suggest improvements or create your own abbreviations. 30 | 31 | ## Alternatives 32 | 33 | We have a Kate XML syntax highlighting file in 34 | [`editors/kate/bruijn.xml`](https://github.com/marvinborner/bruijn/blob/main/editors/kate/bruijn.xml), 35 | although it has *not* actually been tested with Kate (only as a syntax 36 | highlighting file for `pandoc`). 37 | 38 | ## Broogle 39 | 40 | Broogle is a tool for searching standard library functions by name, type 41 | signatures, or comment. It's highly inspired by Haskell's 42 | [hoogle](https://hoogle.haskell.org/). 43 | 44 | You can use it after cloning [bruijn's 45 | repository](https://github.com/marvinborner/bruijn) and installing `rg` 46 | (`ripgrep`), `jq`, `sed`, and `awk`. 47 | 48 | ``` bash 49 | ./broogle.sh -t "a -> a" 50 | ./broogle.sh -f "i" 51 | ./broogle.sh -c "idiot combinator" 52 | ``` 53 | -------------------------------------------------------------------------------- /std/AIT/Beavers.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2023 Marvin Borner 2 | # all currently known binary busy beavers 3 | # originally proposed and calculated by John Tromp 4 | 5 | # reduction results in OEIS sequence A333479 6 | 7 | invalid [0 0] [0 0] 8 | 9 | # not possible 10 | b1 invalid 11 | 12 | # not possible 13 | b2 invalid 14 | 15 | # not possible 16 | b3 invalid 17 | 18 | # 4 19 | b4 [0] 20 | 21 | # not possible 22 | b5 invalid 23 | 24 | # 6 25 | b6 [[0]] 26 | 27 | # 7 28 | b7 [[1]] 29 | 30 | # 8 31 | b8 [[[0]]] 32 | 33 | # 9 34 | b9 [[[1]]] 35 | 36 | # 10 37 | b10 [[[[0]]]] 38 | 39 | # 11 40 | b11 [[[[1]]]] 41 | 42 | # 12 43 | b12 [[[[[0]]]]] 44 | 45 | # 13 46 | b13 [[[[[1]]]]] 47 | 48 | # 14 49 | b14 [[[[[[0]]]]]] 50 | 51 | # 15 52 | b15 [[[[[[1]]]]]] 53 | 54 | # 16 55 | b16 [[[[[[[0]]]]]]] 56 | 57 | # 17 58 | b17 [[[[[[[1]]]]]]] 59 | 60 | # 18 61 | b18 [[[[[[[[0]]]]]]]] 62 | 63 | # 19 64 | b19 [[[[[[[[1]]]]]]]] 65 | 66 | # 20 67 | b20 [[[[[[[[[0]]]]]]]]] 68 | 69 | # 22 70 | b21 [[0 0] (0 [1])] 71 | 72 | # 24 73 | b22 [[0 0 0] (0 0)] 74 | 75 | # 26 76 | b23 [[0 0] (0 [[1]])] 77 | 78 | # 30 79 | b24 [[0 0 0] (0 [0])] 80 | 81 | # 42 82 | b25 [[0 0] [0 (1 0)]] 83 | 84 | # 52 85 | b26 [0 0] [[1 (0 1)]] 86 | 87 | # 44 88 | b27 [[[0 0] [0 (1 0)]]] 89 | 90 | # 58 91 | b28 [[0 0] [0 (1 [1])]] 92 | 93 | # 223 94 | b29 [[0 0] [0 (0 (1 0))]] 95 | 96 | # 160 97 | b30 [0 0 0] [[1 (0 1)]] 98 | 99 | # 267 100 | b31 [0 0] [[1 (1 (0 1))]] 101 | 102 | # 298 103 | b32 [[0 0] [0 (0 (1 [1]))]] 104 | 105 | # 1812 106 | b33 [[0 0] [0 (0 (0 (1 0)))]] 107 | 108 | # 327686 109 | b34 [0 0 0 0] [[1 (1 0)]] 110 | 111 | # 38127987424941 112 | b35 [0 0 0] [[1 (1 (1 0))]] 113 | 114 | # 578960446186580977117854925043439539266349923328202820197287920039565648199686 115 | b36 [0 0] [0 (0 [[1 (1 0)]])] 116 | -------------------------------------------------------------------------------- /samples/rosetta/Universal_Lambda_Machine.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Number/Binary . 3 | :import std/Meta M 4 | :import std/List . 5 | 6 | # converts string to list of bits 7 | str→blc map (c ∘ lsb) 8 | 9 | :test (str→blc "0010") ([[1]] : ([[1]] : ([[0]] : {}[[1]]))) 10 | 11 | # converts list of bits to string 12 | blc→str map [0 '0' '1'] 13 | 14 | :test (blc→str ([[1]] : ([[1]] : ([[0]] : {}[[1]])))) ("0010") 15 | 16 | # evaluates BLC string 17 | main str→blc → M.blc→meta+rest → &M.eval → blc→str 18 | 19 | # --- tests --- 20 | 21 | id "0010" 22 | 23 | # 342 bit IO example 24 | io "010100011010000000011000010110011110000010010111110111100001010110000000011000011111000000101111110110010111111011001011110100111010111100010000001011100101010001101000000000010110000101011111101111100000010101111011111011111100001011000000101111111010110111000000111111000010110111101110011110100000010110000011011000100000101111000111001110" 25 | 26 | # quine example 27 | quine "000101100100011010000000000001011011110010111100111111011111011010000101100100011010000000000001011011110010111100111111011111011010" 28 | 29 | # 100 doors example 30 | doors "0001000100010101000110100000010110000011001110110010100011010000000000101111111000000101111101011001011001000110100001111100110100101111101111000000001011111111110110011001111111011100000000101111110000001011111010110011011100101011000000101111011001011110011110011110110100000000001011011100111011110000000001000000111001110100000000101101110110" 31 | 32 | # sieve of Eratosthenes example 33 | primes "00010001100110010100011010000000010110000010010001010111110111101001000110100001110011010000000000101101110011100111111101111000000001111100110111000000101100000110110" 34 | 35 | :test (main id) (empty) 36 | :test (main io) ("11010") 37 | :test (main quine) (quine) 38 | :test (take (+20) (main doors)) ("10010000100000010000") 39 | :test (take (+20) (main primes)) ("00110101000101000101") 40 | -------------------------------------------------------------------------------- /src/Reducer/HigherOrder.hs: -------------------------------------------------------------------------------- 1 | -- MIT License, Copyright (c) 2024 Marvin Borner 2 | -- Slightly modified version of Tromp's AIT/Lambda.lhs reducer implementation 3 | module Reducer.HigherOrder 4 | ( reduce 5 | ) where 6 | 7 | import Helper 8 | 9 | data HigherOrder = HigherOrderBruijn Int | HigherOrderAbstraction (HigherOrder -> HigherOrder) | HigherOrderApplication HigherOrder HigherOrder 10 | data NamedTerm = NamedVariable Int | NamedAbstraction Int NamedTerm | NamedApplication NamedTerm NamedTerm 11 | 12 | (!?) :: [a] -> Int -> Maybe a 13 | (!?) [] _ = Nothing 14 | (!?) (x : _ ) 0 = Just x 15 | (!?) (_ : xs) i = xs !? (i - 1) 16 | 17 | app :: HigherOrder -> HigherOrder -> HigherOrder 18 | app (HigherOrderAbstraction f) = f 19 | app f = HigherOrderApplication f 20 | 21 | eval :: Expression -> HigherOrder 22 | eval = go [] 23 | where 24 | go env (Bruijn x) = case env !? x of 25 | Just v -> v 26 | _ -> HigherOrderBruijn x 27 | go env (Abstraction e ) = HigherOrderAbstraction $ \x -> go (x : env) e 28 | go env (Application e1 e2) = app (go env e1) (go env e2) 29 | go _ _ = invalidProgramState 30 | 31 | toNamedTerm :: HigherOrder -> NamedTerm 32 | toNamedTerm = go 0 33 | where 34 | go _ (HigherOrderBruijn i) = NamedVariable i 35 | go d (HigherOrderAbstraction f) = 36 | NamedAbstraction d $ go (d + 1) (f (HigherOrderBruijn d)) 37 | go d (HigherOrderApplication e1 e2) = NamedApplication (go d e1) (go d e2) 38 | 39 | resolveExpression :: NamedTerm -> Expression 40 | resolveExpression = resolve [] 41 | where 42 | resolve vs (NamedVariable i) = Bruijn $ case vs !? i of 43 | Just v -> v 44 | _ -> i 45 | resolve vs (NamedAbstraction v t) = Abstraction $ resolve (v : vs) t 46 | resolve vs (NamedApplication l r) = Application (resolve vs l) (resolve vs r) 47 | 48 | reduce :: Expression -> Expression 49 | reduce = resolveExpression . toNamedTerm . eval 50 | -------------------------------------------------------------------------------- /std/Generic/Schemes.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2025 Marvin Borner 2 | # recursion schemes 3 | # depends on fmap, fix, unfix 4 | # WARNING: in general, fmap != map; most probably, fmap = bimap [0] 5 | # fix/unfix must match fmap (so, fix=unfix=[0] sometimes is ok) 6 | 7 | :import std/Combinator . 8 | :import std/Pair P 9 | :import std/Result R 10 | 11 | # ======= # 12 | # Folding # 13 | # ======= # 14 | 15 | # f (Fix f) --- fmap (cata alg) ----> f a 16 | # | | 17 | # | | 18 | # | | 19 | # | | 20 | # Fix alg 21 | # | | 22 | # | | 23 | # | | 24 | # v v 25 | # Fix f ------- cata alg -------> a 26 | 27 | cata y [[unfix → (fmap (1 0)) → 0]] ⧗ ((f a) → a) → (Fix f) → a 28 | 29 | para y [[unfix → (fmap [P.pair 0 (2 1 0)]) → 0]] ⧗ ((f (Pair (Fix f) a)) → a) → (Fix f) → a 30 | 31 | # ========= # 32 | # Unfolding # 33 | # ========= # 34 | 35 | # f (Cofix f) <-- fmap (ana coalg) ---- f a 36 | # ^ ^ 37 | # | | 38 | # | | 39 | # | | 40 | # unfix coalg 41 | # | | 42 | # | | 43 | # | | 44 | # | | 45 | # Cofix f <------ ana coalg ------- a 46 | 47 | ana y [[0 → (fmap (1 0)) → fix]] ⧗ (a → (f a)) → a → (Fix f) 48 | 49 | apo y [[0 → (fmap (R.either (1 0) [0])) → fix]] ⧗ (a → (Result a (Fix f))) → a → (Fix f) 50 | 51 | # ========= # 52 | # Refolding # 53 | # ========= # 54 | 55 | # [[(ana 1) → (cata 0)]] 56 | hylo y [[[0 → (fmap (2 1 0)) → 1]]] 57 | -------------------------------------------------------------------------------- /src/Binary.hs: -------------------------------------------------------------------------------- 1 | -- MIT License, Copyright (c) 2022 Marvin Borner 2 | module Binary 3 | ( toBinary 4 | , fromBinary 5 | , toBitString 6 | , fromBitString 7 | , fromJot 8 | ) where 9 | 10 | import qualified Data.BitString as Bit 11 | 12 | import Helper 13 | 14 | toBinary :: Expression -> String 15 | toBinary (Bruijn x ) = replicate (x + 1) '1' ++ "0" 16 | toBinary (Abstraction e ) = "00" ++ toBinary e 17 | toBinary (Application exp1 exp2) = "01" ++ toBinary exp1 ++ toBinary exp2 18 | toBinary _ = invalidProgramState 19 | 20 | fromBinary' :: String -> (Expression, String) 21 | fromBinary' inp = case inp of 22 | '0' : '0' : rst -> let (e, es) = fromBinary' rst in (Abstraction e, es) 23 | '0' : '1' : rst -> 24 | let (exp1, rst1) = fromBinary' rst 25 | (exp2, rst2) = fromBinary' rst1 26 | in (Application exp1 exp2, rst2) 27 | '1' : _ : rst -> binaryBruijn rst 28 | _ -> invalidProgramState 29 | where 30 | binaryBruijn rst = 31 | let idx = length (takeWhile (== '1') inp) - 1 32 | in case rst of 33 | "" -> (Bruijn idx, "") 34 | _ -> (Bruijn idx, drop idx rst) 35 | 36 | fromBinary :: String -> Expression 37 | fromBinary = fst . fromBinary' 38 | 39 | toBitString :: String -> Bit.BitString 40 | toBitString = Bit.fromList . map 41 | (\case 42 | '0' -> False 43 | '1' -> True 44 | _ -> invalidProgramState 45 | ) 46 | 47 | fromBitString :: Bit.BitString -> String 48 | fromBitString = 49 | map 50 | (\case 51 | False -> '0' 52 | True -> '1' 53 | ) 54 | . Bit.toList 55 | 56 | --- 57 | 58 | fromJot :: String -> Expression 59 | fromJot = worker . reverse 60 | where 61 | s = Function $ NormalFunction "s" 62 | k = Function $ NormalFunction "k" 63 | worker ('0' : xs) = Application (Application (worker xs) s) k 64 | worker ('1' : xs) = Application s (Application k (worker xs)) 65 | worker _ = Abstraction (Bruijn 0) 66 | -------------------------------------------------------------------------------- /samples/rosetta/ternary_logic.bruijn: -------------------------------------------------------------------------------- 1 | true [[[0]]] 2 | 3 | maybe [[[1]]] 4 | 5 | false [[[2]]] 6 | 7 | ¬‣ [0 true maybe false] 8 | 9 | :test (¬true) (false) 10 | :test (¬maybe) (maybe) 11 | :test (¬false) (true) 12 | 13 | …⋀… [[1 (0 1 1 1) (0 0 0 1) (0 0 0 0)]] 14 | 15 | :test (true ⋀ true) (true) 16 | :test (true ⋀ maybe) (maybe) 17 | :test (true ⋀ false) (false) 18 | :test (maybe ⋀ true) (maybe) 19 | :test (maybe ⋀ maybe) (maybe) 20 | :test (maybe ⋀ false) (false) 21 | :test (false ⋀ true) (false) 22 | :test (false ⋀ maybe) (false) 23 | :test (false ⋀ false) (false) 24 | 25 | …⋁… [[1 (0 0 0 0) (0 1 0 0) (0 1 1 1)]] 26 | 27 | :test (true ⋁ true) (true) 28 | :test (true ⋁ maybe) (true) 29 | :test (true ⋁ false) (true) 30 | :test (maybe ⋁ true) (true) 31 | :test (maybe ⋁ maybe) (maybe) 32 | :test (maybe ⋁ false) (maybe) 33 | :test (false ⋁ true) (true) 34 | :test (false ⋁ maybe) (maybe) 35 | :test (false ⋁ false) (false) 36 | 37 | …⊃… [[1 (0 true 0 1) (0 true 1 1) (0 1 1 1)]] 38 | 39 | :test (true ⊃ true) (true) 40 | :test (true ⊃ maybe) (true) 41 | :test (true ⊃ false) (true) 42 | :test (maybe ⊃ true) (maybe) 43 | :test (maybe ⊃ maybe) (maybe) 44 | :test (maybe ⊃ false) (true) 45 | :test (false ⊃ true) (false) 46 | :test (false ⊃ maybe) (maybe) 47 | :test (false ⊃ false) (true) 48 | 49 | …≡… [[1 (0 true 0 1) (0 1 1 1) (0 0 0 0)]] 50 | 51 | :test (true ≡ true) (true) 52 | :test (true ≡ maybe) (maybe) 53 | :test (true ≡ false) (false) 54 | :test (maybe ≡ true) (maybe) 55 | :test (maybe ≡ maybe) (maybe) 56 | :test (maybe ≡ false) (maybe) 57 | :test (false ≡ true) (false) 58 | :test (false ≡ maybe) (maybe) 59 | :test (false ≡ false) (true) 60 | 61 | # --- result samples --- 62 | 63 | :import std/List . 64 | 65 | main [[inp <> "=" <> !res ++ "\n"] <++> (cross3 ops trits trits)] 66 | !‣ [0 "false" "maybe" "true"] 67 | …<>… [[1 ++ " " ++ 0]] 68 | inp 0 [[~1 <> (0 [[!1 <> (0 [[!1]])]])]] 69 | res ^(^0) ^(~0) ^(~(~0)) 70 | ops (…⋀… : "and") : ((…⋁… : "or") : ((…⊃… : "if") : {}(…≡… : "equiv"))) 71 | trits true : (maybe : {}false) 72 | -------------------------------------------------------------------------------- /docs/wiki_src/technical/reduction.md: -------------------------------------------------------------------------------- 1 | # Reduction 2 | 3 | Bruijn supports several reducers that can be chosen using its 4 | `--reducer` flag. 5 | 6 | ## HigherOrder [`(source)`](https://github.com/marvinborner/bruijn/blob/main/src/Reducer/HigherOrder.hs) 7 | 8 | HigherOrder reduction is one of the simplest reducers. By translating 9 | the entire expression to a higher-order representation, we can abuse 10 | Haskell's internal reduction implementation in our favour. Aside from 11 | conversion from/to the higher-order encoding, this reducer does 12 | basically nothing special. 13 | 14 | ## RKNL [`(source)`](https://github.com/marvinborner/bruijn/blob/main/src/Reducer/RKNL.hs) 15 | 16 | RKNL[^1] is an abstract machine for reducing lambda calculus. It uses 17 | the call-by-need reduction strategy, similar to Haskell and other 18 | functional programming languages. For you this means that you have 19 | efficient support for [laziness](../coding/laziness.md) with generally 20 | less redundant reductions. 21 | 22 | ## ION [`(source)`](https://github.com/marvinborner/bruijn/blob/main/src/Reducer/ION.hs) 23 | 24 | [The ION machine](https://crypto.stanford.edu/~blynn/compiler/ION.html) 25 | was created by Benn Lynn as a reducer for a hypothetical functional 26 | stack machine computer. We convert the lambda calculus term to 27 | combinatory logic using [Kiselyov 28 | translation](https://crypto.stanford.edu/~blynn/lambda/kiselyov.html), 29 | set up a "virtual" machine with the combinators, and let it run until 30 | the stack has reached its end. 31 | 32 | Most of the work was done by John Tromp in his 33 | [nf.c](https://github.com/tromp/AIT/commits/master/nf.c). The 34 | translation to Haskell and its integration into bruijn was mainly done 35 | as an experiment on performance. 36 | 37 | [^1]: [Biernacka, Małgorzata, Witold Charatonik, and Tomasz Drab. "A 38 | simple and efficient implementation of strong call by need by an 39 | abstract machine." Proceedings of the ACM on Programming Languages 40 | 6.ICFP (2022): 109-136.](https://doi.org/10.5281/zenodo.6786796) 41 | -------------------------------------------------------------------------------- /std/Char.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2023 Marvin Borner 2 | 3 | :input std/Number/Binary 4 | 5 | :import std/Number/Conversion . 6 | 7 | # prefix for comparing functions 8 | ?‣ &eq? 9 | 10 | # converts a char to a balanced ternary number 11 | char→number (\sub '0') → binary→ternary ⧗ Char → Number 12 | 13 | :test (char→number '0') ((+0)) 14 | 15 | # converts a balanced ternary number to a char 16 | number→char ternary→binary → (add '0') ⧗ Number → Char 17 | 18 | :test (number→char (+0)) ('0') 19 | :test (number→char (+9)) ('9') 20 | 21 | # returns true if char is in A-Z 22 | uppercase? φ and? (\ge? 'A') (\le? 'Z') ⧗ Char → Boolean 23 | 24 | :test (uppercase? 'a') (false) 25 | :test (uppercase? 'z') (false) 26 | :test (uppercase? 'A') (true) 27 | :test (uppercase? 'Z') (true) 28 | :test (uppercase? '0') (false) 29 | 30 | # returns true if char is in a-z 31 | lowercase? φ and? (\ge? 'a') (\le? 'z') ⧗ Char → Boolean 32 | 33 | :test (lowercase? 'a') (true) 34 | :test (lowercase? 'z') (true) 35 | :test (lowercase? 'A') (false) 36 | :test (lowercase? 'Z') (false) 37 | :test (lowercase? '0') (false) 38 | 39 | # returns true if char is in a-zA-Z 40 | alpha? φ or? lowercase? uppercase? ⧗ Char → Boolean 41 | 42 | :test (alpha? 'a') (true) 43 | :test (alpha? 'z') (true) 44 | :test (alpha? 'A') (true) 45 | :test (alpha? 'Z') (true) 46 | :test (alpha? '0') (false) 47 | 48 | # returns true if char is in 0-9 49 | numeric? φ and? (\ge? '0') (\le? '9') ⧗ Char → Boolean 50 | 51 | :test (numeric? '0') (true) 52 | :test (numeric? '9') (true) 53 | :test (numeric? 'a') (false) 54 | 55 | # returns true if char is in a-zA-Z0-9 56 | alpha-numeric? φ or? numeric? alpha? ⧗ Char → Boolean 57 | 58 | :test (alpha-numeric? 'a') (true) 59 | :test (alpha-numeric? 'z') (true) 60 | :test (alpha-numeric? 'A') (true) 61 | :test (alpha-numeric? 'Z') (true) 62 | :test (alpha-numeric? '0') (true) 63 | :test (alpha-numeric? '9') (true) 64 | :test (alpha-numeric? '$') (false) 65 | 66 | # returns true if char is space 67 | space? eq? ' ' 68 | 69 | :test (space? ' ') (true) 70 | :test (space? '2') (false) 71 | -------------------------------------------------------------------------------- /src/Humanification.hs: -------------------------------------------------------------------------------- 1 | -- MIT License, Copyright (c) 2022 Marvin Borner 2 | module Humanification where 3 | 4 | import Control.Applicative ( (<|>) ) 5 | import Data.List ( intercalate ) 6 | import Data.Maybe ( fromMaybe ) 7 | 8 | import Conversion 9 | import Helper 10 | 11 | -- TODO: Show list as pair if not ending with empty 12 | maybeHumanifyExpression :: Expression -> Maybe String 13 | maybeHumanifyExpression e = 14 | unaryToDecimal e 15 | <|> binaryToChar e 16 | <|> binaryToString e 17 | <|> ternaryToString e 18 | <|> rationalToString e 19 | <|> realToString e 20 | <|> complexToString e 21 | <|> humanifyString e 22 | <|> humanifyList e 23 | <|> humanifyPair e 24 | <|> humanifyMeta e 25 | 26 | humanifyExpression :: Expression -> String 27 | humanifyExpression e = fromMaybe "" (maybeHumanifyExpression e) 28 | 29 | humanifyMeta :: Expression -> Maybe String 30 | humanifyMeta e = ("`" <>) <$> go e 31 | where 32 | go (Abstraction (Abstraction (Abstraction (Application (Bruijn 0) t)))) = 33 | go t >>= (\a -> pure $ "[" <> a <> "]") 34 | go (Abstraction (Abstraction (Abstraction (Application (Application (Bruijn 1) a) b)))) 35 | = go a >>= \l -> go b >>= \r -> pure $ "(" <> l <> " " <> r <> ")" 36 | go (Abstraction (Abstraction (Abstraction (Application (Bruijn 2) n)))) = 37 | fmap show (unaryToDecimal' n) 38 | go _ = Nothing 39 | 40 | humanifyList :: Expression -> Maybe String 41 | humanifyList e = do 42 | es <- unlistify e 43 | let conv x = fromMaybe (show x) (maybeHumanifyExpression x) 44 | m = map conv es 45 | pure $ "{" <> intercalate ", " m <> "}" 46 | 47 | humanifyString :: Expression -> Maybe String 48 | humanifyString e = do 49 | es <- unlistify e 50 | str <- mapM binaryToChar' es 51 | pure $ "\"" <> str <> "\"" 52 | 53 | humanifyPair :: Expression -> Maybe String 54 | humanifyPair e = do 55 | es <- unpairify e 56 | let conv x = fromMaybe (show x) (maybeHumanifyExpression x) 57 | m = map conv es 58 | pure $ "<" <> intercalate " : " m <> ">" 59 | -------------------------------------------------------------------------------- /samples/fun/recursion-schemes.bruijn: -------------------------------------------------------------------------------- 1 | # uses Generic/Schemes.bruijn for ana/cata/para/etc. 2 | 3 | :import std/Number . 4 | :import std/Combinator . 5 | :import std/Tuples . 6 | 7 | # ===== # 8 | # lists # 9 | # ===== # 10 | 11 | :import std/List/Church . 12 | :import std/Monad/List . 13 | 14 | # example list 15 | list (+1) : ((+2) : {}(+3)) 16 | 17 | sum cata ([[[2 + 1]]] : (+0)) 18 | 19 | :test (list.sum =? (+6)) ([[1]]) 20 | 21 | length cata ([[[++1]]] : (+0)) 22 | 23 | :test (list.length =? (+3)) ([[1]]) 24 | 25 | tails para ([[[1 [[(4 : 1) : 0]]]]] : {}empty) 26 | 27 | :test (list.tails) (list : (~list : (~(~list) : {}empty))) 28 | 29 | splits para (lst : nil) 30 | lst [&[[[(empty : (3 : 2)) : (&[[(5 : 1) : 0]] <$> 1)]]]] 31 | nil {}(empty : empty) 32 | 33 | …>>=… y [[[1 [[[(3 2) ++ (5 1 3)]]] empty]]] 34 | 35 | perms foldr [[0 >>= splits >>= &[[{}(1 ++ {}3 ++ 0)]]]] {}empty 36 | 37 | nats ana [0 : ++0] (+0) ⧗ (List Number) 38 | 39 | :test (nats [[1]]) ((+0)) 40 | 41 | fac hylo alg coalg 42 | coalg [=?0 [[0]] (0 : --0)] 43 | alg [[[2 ⋅ 1]]] : (+1) 44 | 45 | :test ((fac (+5)) =? (+120)) ([[1]]) 46 | 47 | # ======= # 48 | # numbers # 49 | # ======= # 50 | 51 | :import std/Number/Ternary . 52 | 53 | fac para ((+1) : &[[0 ⋅ ++1]]) ⧗ Number → Number 54 | 55 | # where 56 | # coalg 0 = Nil 57 | # coalg n = Cons n (n - 1) 58 | # alg Nil = 1 59 | # alg (Cons n acc) = n * acc 60 | 61 | # ==== # 62 | # meta # 63 | # ==== # 64 | 65 | :import std/Meta . 66 | :import std/Number/Conversion . 67 | 68 | length cata (case-idx : case-app : case-abs) 69 | case-idx (add (+2)) ∘ unary→ternary 70 | case-app (add (+2)) ∘∘ add 71 | case-abs add (+2) 72 | 73 | :test (length `[0]) ((+4)) 74 | :test (length `[[1 1]]) ((+12)) 75 | 76 | main [[0]] 77 | 78 | :import std/List . 79 | :import std/Number . 80 | 81 | fac ^(list-y* (end : rest)) 82 | end [[0 =? (+0) (+1) (0 ⋅ (^(~1) 0))]] 83 | rest y [[[[0 =? 2 (^1 --2) (1 !! ++2 0)]] : (1 ++0)]] (+1) 84 | 85 | :import std/Imperative . 86 | 87 | …;… …→… 88 | 89 | # ([0 : (+1)] → (while (gets &[[not-eq? 1 (+0)]]) (get >>= &[[put (--1 : (mul 1 0))]])) → &[[0 [[0]]]]) 90 | fac i=1 ; (while (i≠0) n*=i;i-=1) ; return-n 91 | i=1 [0 : (+1)] 92 | i≠0 gets &[[1 ≠? (+0)]] 93 | n*=i;i-=1 get [[put (--1 : (1 ⋅ 0))]] 94 | return-n &[&[[0]]] 95 | -------------------------------------------------------------------------------- /samples/rosetta/variadic_fixed-point_combinator.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Number . 3 | :import std/List . 4 | 5 | # --------------- 6 | # explicit Church 7 | # --------------- 8 | 9 | # passes all functions explicitly 10 | explicit-y* [[[0 1] <$> 0] ([[1 0)]] <$> 0)] 11 | 12 | # even x = if x == 0 then true else odd? (x-1) 13 | g [[[=?0 [[1]] (1 --0)]]] 14 | 15 | # odd x = if x == 0 then false else even? (x-1) 16 | h [[[=?0 [[0]] (2 --0)]]] 17 | 18 | # merged even/odd 19 | rec explicit-y* (g : {}h) 20 | 21 | :test (^rec (+5)) ([[0]]) 22 | :test (_rec (+5)) ([[1]]) 23 | 24 | # n % 3 25 | mod3 ^(explicit-y* (zero : (one : {}two))) 26 | zero [[[[=?0 (+0) (2 --0)]]]] 27 | one [[[[=?0 (+1) (1 --0)]]]] 28 | two [[[[=?0 (+2) (3 --0)]]]] 29 | 30 | :test ((mod3 (+5)) =? (+2)) ([[1]]) 31 | 32 | # ---------------- 33 | # explicit tupling 34 | # ---------------- 35 | 36 | # passes all functions explicitly 37 | # requires a tuple mapping function first 38 | # or, minified: [[0 0] [[2 (1 1 0) 0]]] (38 bit!) 39 | tupled-y* [y [[2 (1 0) 0]]] 40 | 41 | # merged even odd 42 | rec tupled-y* map [0 g h] 43 | map [&[[[0 (3 2) (3 1)]]]] 44 | 45 | # [[1]] / [[0]] are tuple selectors: 46 | 47 | :test (rec [[1]] (+5)) ([[0]]) 48 | :test (rec [[0]] (+5)) ([[1]]) 49 | 50 | # n % 3, [[[2]]] selects first tuple element 51 | mod3 tupled-y* map [0 zero one two] [[[2]]] 52 | map [&[[[[0 (4 3) (4 2) (4 1)]]]]] 53 | zero [[[[=?0 (+0) (2 --0)]]]] 54 | one [[[[=?0 (+1) (1 --0)]]]] 55 | two [[[[=?0 (+2) (3 --0)]]]] 56 | 57 | :test ((mod3 (+5)) =? (+2)) ([[1]]) 58 | 59 | # NOTE: You can merge the mapping argument directly into the list 60 | # like [[0 (1 A) (1 B) (1 C) ...]]. Then y*=y. 61 | 62 | # --------------- 63 | # implicit Church 64 | # --------------- 65 | 66 | # passes all functions in a single list 67 | implicit-y* y [[&(1 0) <$> 0]] 68 | 69 | # even x = if x == 0 then true else odd? (x-1) 70 | g [[=?0 [[1]] (_1 --0)]] 71 | 72 | # odd x = if x == 0 then false else even? (x-1) 73 | h [[=?0 [[0]] (^1 --0)]] 74 | 75 | # merged even/odd 76 | rec implicit-y* (g : {}h) 77 | 78 | :test (^rec (+5)) ([[0]]) 79 | :test (_rec (+5)) ([[1]]) 80 | 81 | # n % 3 82 | mod3 ^(implicit-y* (zero : (one : {}two))) 83 | zero [[=?0 (+0) (_1 --0)]] 84 | one [[=?0 (+1) (^(~1) --0)]] 85 | two [[=?0 (+2) (^1 --0)]] 86 | 87 | :test ((mod3 (+5)) =? (+2)) ([[1]]) 88 | 89 | main [[0]] 90 | -------------------------------------------------------------------------------- /std/Result.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2022 Marvin Borner 2 | 3 | :import std/Combinator . 4 | :import std/Logic . 5 | :import std/Option . 6 | 7 | # encapsulates a value in ok 8 | ok [[[1 2]]] ⧗ (Result a b) 9 | 10 | :test (ok [[0]]) ([[1 [[0]]]]) 11 | 12 | # encapsulates a value in err 13 | err [[[0 2]]] ⧗ (Result a b) 14 | 15 | :test (err [[0]]) ([[0 [[0]]]]) 16 | 17 | # checks whether result is ok 18 | ok? [0 [true] [false]] ⧗ (Result a b) → Boolean 19 | 20 | :test (ok? (ok [[0]])) (true) 21 | :test (ok? (err [[0]])) (false) 22 | 23 | # checks whether result is not ok 24 | err? [0 [false] [true]] ⧗ (Result a b) → Boolean 25 | 26 | :test (err? (ok [[0]])) (false) 27 | :test (err? (err [[0]])) (true) 28 | 29 | # encapsulates result ok value in a option 30 | option-ok [0 some [none]] ⧗ (Result a b) → (Option a) 31 | 32 | :test (option-ok (ok [[0]])) (some [[0]]) 33 | :test (option-ok (err [[0]])) (none) 34 | 35 | # encapsulate result err value in a option 36 | option-err [0 [none] some] ⧗ (Result a b) → (Option b) 37 | 38 | :test (option-err (ok [[0]])) (none) 39 | :test (option-err (err [[0]])) (some [[0]]) 40 | 41 | # extracts value from result or returns first arg 42 | unwrap-or [[0 i [2]]] ⧗ a → (Result a b) → a 43 | 44 | :test (unwrap-or [[1]] (ok [[0]])) ([[0]]) 45 | :test (unwrap-or [[1]] (err [[0]])) ([[1]]) 46 | 47 | # applies a function to the value in ok result 48 | map [[0 [ok (2 0)] err]] ⧗ (a → c) → (Result a b) → (Result c b) 49 | 50 | :test (map [[1]] (ok [[0]])) (ok [[[0]]]) 51 | :test (map [[1]] (err [[0]])) (err [[0]]) 52 | 53 | # applies a function to the value in err result 54 | map-err [[0 ok [err (2 0)]]] ⧗ (b → c) → (Result a b) → (Result a c) 55 | 56 | :test (map-err [[1]] (ok [[0]])) ((ok [[0]])) 57 | :test (map-err [[1]] (err [[0]])) ((err [[[0]]])) 58 | 59 | # applies functions to either sides of the result, yielding the mapped result 60 | bimap [[[0 [ok (3 0)] [err (2 0)]]]] ⧗ (a → b) → (c → d) → (Result a c) → (Result b d) 61 | 62 | # applies functions to either sides of the result, yielding only the value 63 | either [[[0 2 1]]] ⧗ (a → c) → (b → c) → (Result a b) → c 64 | 65 | pure ok ⧗ a → (Result a b) 66 | 67 | # applies encapsulated value to given function (if ok) 68 | bind [[1 0 err]] ⧗ (Result a b) → (a → (Result c b)) → (Result c b) 69 | 70 | …>>=… bind 71 | 72 | :test ((err 'a') >>= (pure "idk")) (err 'a') 73 | :test ((ok 'a') >>= [pure [1]]) (ok ['a']) 74 | -------------------------------------------------------------------------------- /std/Math/Complex.bruijn: -------------------------------------------------------------------------------- 1 | # ideas by u/DaVinci103 2 | # MIT License, Copyright (c) 2024 Marvin Borner 3 | 4 | :import std/Combinator . 5 | :import std/Pair . 6 | :import std/Number N 7 | :import std/Math/Rational Q 8 | :import std/Math/Real R 9 | 10 | i (+0.0+1.0i) 11 | 12 | # converts a balanced ternary number to a complex number 13 | number→complex [[(R.number→real 1 0) : ((+0.0r) 0)]] ⧗ Number → Complex 14 | 15 | :test (number→complex (+5)) ((+5.0+0.0i)) 16 | 17 | # returns real part of a complex number 18 | real [[1 0 [[1]]]] ⧗ Complex → Real 19 | 20 | :test (real (+5.0+2.0i)) ((+5.0r)) 21 | 22 | # returns imaginary part of a complex number 23 | imag [[1 0 [[0]]]] ⧗ Complex → Real 24 | 25 | :test (imag (+5.0+2.0i)) ((+2.0r)) 26 | 27 | # adds two complex numbers 28 | add φ &[[&[[(Q.add 3 1) : (Q.add 2 0)]]]] ⧗ Complex → Complex → Complex 29 | 30 | …+… add 31 | 32 | # subtracts two complex numbers 33 | sub φ &[[&[[(Q.sub 3 1) : (Q.sub 2 0)]]]] ⧗ Complex → Complex → Complex 34 | 35 | …-… sub 36 | 37 | # multiplies two complex numbers 38 | mul φ &[[&[[p : q]]]] ⧗ Complex → Complex → Complex 39 | p Q.sub (Q.mul 3 1) (Q.mul 2 0) 40 | q Q.add (Q.mul 3 0) (Q.mul 2 1) 41 | 42 | …⋅… mul 43 | 44 | # divides two complex numbers 45 | div φ &[[&[[p : q]]]] ⧗ Complex → Complex → Complex 46 | p Q.div (Q.add (Q.mul 3 1) (Q.mul 2 0)) (Q.add (Q.mul 1 1) (Q.mul 0 0)) 47 | q Q.div (Q.sub (Q.mul 2 1) (Q.mul 3 0)) (Q.add (Q.mul 1 1) (Q.mul 0 0)) 48 | 49 | …/… div 50 | 51 | # negates a complex number 52 | negate b &[[(Q.negate 1) : (Q.negate 0)]] ⧗ Complex → Complex 53 | 54 | -‣ negate 55 | 56 | # inverts a complex number 57 | invert b &[[p : q]] ⧗ Complex → Complex 58 | p Q.div 1 (Q.add (Q.mul 1 1) (Q.mul 0 0)) 59 | q Q.div 0 (Q.add (Q.mul 1 1) (Q.mul 0 0)) 60 | 61 | ~‣ invert 62 | 63 | # --- 64 | 65 | :import std/List L 66 | 67 | # power function: complex^number 68 | pow-n [L.nth-iterate (mul 0) (+1.0+0.0i)] ⧗ Complex → Number → Complex 69 | 70 | exp [[L.nth-iterate &[[[[op]]]] start 0 [[[[1]]]] 0]] ⧗ Complex → Complex 71 | start [0 (+1.0+0.0i) (+1.0+0.0i) (+1.0+0.0i) (+1)] 72 | op [[[2 1 0 (4 + (1 / 0)) N.++3]] pow fac] 73 | pow 6 ⋅ 4 74 | fac 3 ⋅ (number→complex 1) 75 | 76 | ln [[[p : q] (1 0)]] ⧗ Complex → Complex 77 | p R.ln (&[[R.hypot [2] [1]]] 0) 1 78 | q &[[\R.atan2 [2] [1]]] 0 1 79 | 80 | # complex power function: complex^complex 81 | pow [[exp (0 ⋅ (ln 1))]] ⧗ Complex → Complex → Complex 82 | 83 | …**… pow 84 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # https://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # 15 | # The location of a snapshot can be provided as a file or url. Stack assumes 16 | # a snapshot provided as a file might change, whereas a url resource does not. 17 | # 18 | # resolver: ./custom-snapshot.yaml 19 | # resolver: https://example.com/snapshots/2018-01-01.yaml 20 | resolver: lts-22.12 21 | 22 | # User packages to be built. 23 | # Various formats can be used as shown in the example below. 24 | # 25 | # packages: 26 | # - some-directory 27 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 28 | # subdirs: 29 | # - auto-update 30 | # - wai 31 | packages: 32 | - . 33 | # Dependency packages to be pulled from upstream that are not in the resolver. 34 | # These entries can reference officially published versions as well as 35 | # forks / in-progress versions pinned to a git hash. For example: 36 | # 37 | # extra-deps: 38 | # - acme-missiles-0.3 39 | # - git: https://github.com/commercialhaskell/stack.git 40 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 41 | # 42 | # extra-deps: [] 43 | extra-deps: 44 | - bitstring-0.0.0 45 | 46 | # Override default flag values for local packages and extra-deps 47 | # flags: {} 48 | 49 | # Extra package databases containing global packages 50 | # extra-package-dbs: [] 51 | 52 | # Control whether we use the GHC we find on the path 53 | # system-ghc: true 54 | # 55 | # Require a specific version of stack, using version ranges 56 | # require-stack-version: -any # Default 57 | # require-stack-version: ">=2.7" 58 | # 59 | # Override the architecture used by stack, especially useful on Windows 60 | # arch: i386 61 | # arch: x86_64 62 | # 63 | # Extra directories used by stack for building 64 | # extra-include-dirs: [/path/to/dir] 65 | # extra-lib-dirs: [/path/to/dir] 66 | # 67 | # Allow a newer minor version of GHC than the snapshot specifies 68 | # compiler-check: newer-minor 69 | -------------------------------------------------------------------------------- /src/Target.hs: -------------------------------------------------------------------------------- 1 | -- MIT License, Copyright (c) 2024 Marvin Borner 2 | -- BLoC format is used in combination with BLoCade the BLoC-aid. 3 | module Target 4 | ( toTarget 5 | ) where 6 | 7 | import Control.Exception 8 | import qualified Data.BitString as Bit 9 | import qualified Data.ByteString.Lazy.Char8 as Byte 10 | import System.IO 11 | import System.Process 12 | 13 | import Binary 14 | import Config 15 | import Error 16 | import Helper 17 | 18 | tryIO :: IO a -> IO (Either IOException a) 19 | tryIO = try 20 | 21 | compile :: Expression -> String -> IO (Failable String) 22 | compile e target = do 23 | res <- tryIO $ createProcess 24 | (shell $ "bloc -v -i - --from-blc | blocade -v -i - -t " <> target) 25 | { std_in = CreatePipe 26 | , std_out = CreatePipe 27 | } 28 | case res of 29 | Right (Just inH, Just outH, _, _) -> do 30 | let binary = toBinary e 31 | hSetBuffering inH NoBuffering 32 | hSetBuffering outH NoBuffering 33 | hSetBinaryMode outH True 34 | hIsOpen inH >>= print 35 | hIsWritable inH >>= print 36 | putStrLn "sending binary" 37 | hPutStrLn inH binary 38 | hPutStr inH "\0" 39 | hFlush inH 40 | content <- hGetContents' outH 41 | hClose outH 42 | hClose inH 43 | return $ case content of 44 | "" -> Left $ OptimizerError "blocade returned empty string" 45 | _ -> Right content 46 | _ -> return $ Left $ OptimizerError "can't read from blocade" 47 | 48 | -- TODO: add more targets (including other PL compilation) 49 | toExpression :: String -> String -> Expression 50 | toExpression "blc" = fromBinary 51 | toExpression "unblc" = fromBinary 52 | toExpression "bblc" = 53 | fromBinary . fromBitString . Bit.bitStringLazy . Byte.pack 54 | toExpression "unbblc" = 55 | fromBinary . fromBitString . Bit.bitStringLazy . Byte.pack 56 | toExpression _ = invalidProgramState 57 | 58 | toTarget :: EvalConf -> Expression -> IO Expression 59 | toTarget conf e = do 60 | let target = _toTarget conf 61 | case target of 62 | "" -> return e -- No target, fallback to unoptimized expression 63 | _ -> do 64 | maybeRes <- compile e target 65 | case maybeRes of 66 | Left err -> do 67 | print err 68 | return e -- Fallback to unoptimized expression 69 | Right res -> return $ toExpression target res 70 | -------------------------------------------------------------------------------- /std/Generic/Number.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | # generic number template, meant to be :input-ted 3 | # assumes: zero, zero?, even?, gt?, zero?, eq?, add, inc, dec, compare-case 4 | 5 | # TODO: also include std/Number more std/Math etc.? Requires solution for recursive imports 6 | # then: give numeral systems unary id, match with index-unary 7 | # or: have unified way of translating to ternary (then add hash function) 8 | 9 | :import std/Combinator . 10 | :import std/Logic . 11 | 12 | # returns true if number is not zero 13 | not-zero? not! ∘ zero? ⧗ Generic → Boolean 14 | 15 | ≠?‣ not-zero? 16 | 17 | # returns true if two numbers are not equal 18 | not-eq? not! ∘∘ eq? ⧗ Generic → Generic → Boolean 19 | 20 | …≠?… not-eq? 21 | 22 | # prefix for comparing functions 23 | ?‣ &eq? 24 | 25 | # returns true if number is less than other number 26 | lt? \gt? ⧗ Generic → Generic → Boolean 27 | 28 | …b, -1 if a… compare 45 | 46 | <=>‣ &compare 47 | 48 | # returns true if comparison result is equal (EQ) 49 | c-eq? eq? (+0) ⧗ Generic → Generic 50 | 51 | # returns true if comparison result is less than (LT) 52 | c-lt? eq? (-1) ⧗ Generic → Generic 53 | 54 | # returns true if comparison result is greater than (GT) 55 | c-gt? eq? (+1) ⧗ Generic → Generic 56 | 57 | # returns max number of two 58 | max [[(1 ≤? 0) 0 1]] ⧗ Generic → Generic → Generic 59 | 60 | # returns min number of two 61 | min [[(1 ≤? 0) 1 0]] ⧗ Generic → Generic → Generic 62 | 63 | # clamps a number between two numbers 64 | clamp [[[min 1 (max 0 2)]]] ⧗ Generic → Generic → Generic 65 | 66 | # returns true if the number is odd (remainder mod 2 == 1) 67 | odd? ¬‣ ∘ even? ⧗ Generic → Boolean 68 | 69 | # ================= # 70 | # recursion schemes # 71 | # ================= # 72 | 73 | :import std/Option O 74 | 75 | fmap [[0 O.none (O.some ∘ 1)]] ⧗ (a → b) → (GenericF a) → (GenericF b) 76 | 77 | fix O.map-or zero inc ⧗ (GenericF a) → (Fix (GenericF a)) 78 | 79 | unfix [=?0 O.none (O.some --0)] ⧗ (Fix (GenericF a)) → (GenericF a) 80 | 81 | :input std/Generic/Schemes 82 | -------------------------------------------------------------------------------- /docs/wiki_src/coding/style.md: -------------------------------------------------------------------------------- 1 | # Coding style 2 | 3 | ## Program 4 | 5 | - Every function has to be delimited by one empty line. 6 | - Every (non-scoped) function must have a comment directly above it. 7 | - Tests must appear in a single block (no empty lines) one line under 8 | the definition. 9 | 10 | See the [standard library](/std/) for inspiration. 11 | 12 | ## Function naming 13 | 14 | De Bruijn indices can be seen as a disadvantage to readability. It's 15 | therefore much more important to name the functions appropriately. 16 | 17 | For functions that return a boolean, we suggest using the suffix `?`. If 18 | your function has different cases it's recommended to use the `case-` 19 | prefix in scoped sub-terms. 20 | 21 | ``` bruijn 22 | # from std/Ternary 23 | zero? [0 case-end case-neg case-pos i] ⧗ Number → Boolean 24 | case-end true 25 | case-neg [false] 26 | case-pos [false] 27 | ``` 28 | 29 | Appropriate [type signatures](../introduction/syntax.md#types) are also 30 | encouraged. 31 | 32 | ## If/else 33 | 34 | Since booleans are just lambda terms either returning its first or 35 | second argument, the use of if/else procedures is generally redundant. 36 | See [bit/boolean data 37 | structure](data-structures.md#booleansbits-stdlogic). 38 | 39 | ``` bruijn 40 | :test (true 'a' 'b') ('a') 41 | :test (false 'a' 'b') ('b') 42 | ``` 43 | 44 | ## Head/tail 45 | 46 | The internal structure of the list encoding means that when a list is 47 | applied to a function, the function is called with the head and tail of 48 | the list. 49 | 50 | ``` bruijn 51 | :test ("abc" [[1]]) ('a') 52 | :test ("abc" [[0]]) ("bc") 53 | ``` 54 | 55 | Therefore the recommended style for coding with lists is to use 56 | `head`{.bruijn}/`tail`{.bruijn} only when truly needed. 57 | 58 | ## Recursion 59 | 60 | [Recursion](recursion.md) should almost always be achieved with the 61 | `y`{.bruijn} or `z`{.bruijn} combinators. 62 | 63 | A common coding style in bruijn's standard library is to use the scoped 64 | `rec` function to indicate recursion. You would then use `n+1` 65 | abstraction around `rec` to indicate `n` arguments and the additionally 66 | induced recursive call. 67 | 68 | Example of the `length`{.bruijn} function for lists: 69 | 70 | ``` bruijn 71 | # 3 abstractions => two arguments 72 | # 2 is recursive call 73 | # 1 is accumulator (+0) 74 | # 0 is argument (list) 75 | length z [[[rec]]] (+0) ⧗ (List a) → Number 76 | rec 0 [[[case-inc]]] case-end 77 | case-inc 5 ++4 1 78 | case-end 1 79 | ``` 80 | -------------------------------------------------------------------------------- /std/Number/Conversion.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | # convert bases to other bases 3 | 4 | :import std/Combinator . 5 | :import std/Tuples . 6 | :import std/Number/Unary U 7 | :import std/Number/Binary B 8 | :import std/Number/Ternary T 9 | :import std/Number/Parigot P 10 | 11 | # converts unary numbers to ternary 12 | unary→ternary [0 T.inc (+0t)] ⧗ Unary → Ternary 13 | 14 | ¹³‣ unary→ternary 15 | 16 | :test (¹³(+0u)) ((+0t)) 17 | :test (¹³(+2u)) ((+2t)) 18 | 19 | # converts unary numbers to bruijn 20 | unary→bruijn &k ⧗ Unary → Bruijn 21 | 22 | ¹ᵇ‣ unary→bruijn 23 | 24 | :test (¹ᵇ(+0u)) ((+0d)) 25 | :test (¹ᵇ(+2u)) ((+2d)) 26 | 27 | # converts Parigot numbers to unary 28 | parigot→unary [[[P.iter 0 1 2]]] ⧗ Parigot → Unary 29 | 30 | ᵖ¹‣ parigot→unary 31 | 32 | :test (ᵖ¹P.zero) ((+0u)) 33 | :test (ᵖ¹(P.inc (P.inc P.zero))) ((+2u)) 34 | 35 | # converts binary numbers to unary 36 | binary→unary [[0 (+0u) [U.inc (2 0)] 1]] (U.mul (+2u)) ⧗ Binary → Unary 37 | 38 | ²¹‣ binary→unary 39 | 40 | :test (²¹(+0b)) ((+0u)) 41 | :test (²¹(+2b)) ((+2u)) 42 | 43 | # converts binary numbers to ternary 44 | # constructs reversed path of composed functions and applies to ternary 45 | binary→ternary [y [[[rec]]] [0] 0 (+0t)] ⧗ Binary → Ternary 46 | rec B.zero? 0 case-end case-rec 47 | case-rec B.odd? 0 (2 (1 ∘ T.inc) (B.dec 0)) (2 (1 ∘ (T.mul (+2t))) (B.div² 0)) 48 | case-end 1 49 | 50 | ²³‣ binary→ternary 51 | 52 | :test (T.eq? ²³(+0b) (+0t)) ([[1]]) 53 | :test (T.eq? ²³(+42b) (+42t)) ([[1]]) 54 | 55 | # converts ternary numbers to unary 56 | ternary→unary [T.apply 0 U.inc (+0u)] ⧗ Ternary → Unary 57 | 58 | ³¹‣ ternary→unary 59 | 60 | :test (³¹(+0t)) ((+0u)) 61 | :test (³¹(+2t)) ((+2u)) 62 | 63 | # converts numbers to binary 64 | # constructs reversed path of composed functions and applies to ternary 65 | ternary→binary [y [[[rec]]] [0] 0 (+0b)] ⧗ Ternary → Binary 66 | rec T.zero? 0 case-end case-rec 67 | case-rec T.odd? 0 (2 (1 ∘ B.inc) (T.dec 0)) (2 (1 ∘ (B.mul (+2b))) (T.div² 0)) 68 | case-end 1 69 | 70 | ³²‣ ternary→binary 71 | 72 | :test (B.eq? ³²(+0t) (+0b)) ([[1]]) 73 | :test (B.eq? ³²(+42t) (+42b)) ([[1]]) 74 | 75 | # converts ternary numbers to bruijn 76 | ternary→bruijn ternary→unary → unary→bruijn ⧗ Ternary → Bruijn 77 | 78 | :test (ternary→bruijn (+5t)) ((+5d)) 79 | 80 | # converts sk numerals to unary (by Bertram Felgenhauer) 81 | sk→unary [[1 0 (0 0) ki] ([[[2 : U.++1]]] : (+0u))] ⧗ SK → Unary 82 | 83 | # converts tuple numerals to unary (by Tromp) 84 | tuple→unary &t ⧗ TupleN → Unary 85 | -------------------------------------------------------------------------------- /docs/wiki_src/coding/REPL.md: -------------------------------------------------------------------------------- 1 | # REPL 2 | 3 | The REPL is a very helpful tool for functional programming languages 4 | like bruijn. You can use it to continuously test or execute parts of 5 | your code. 6 | 7 | You can start the REPL using `stack run`{.bash} or (if installed) 8 | `bruijn`{.bash}. 9 | 10 | Any valid term will get reduced to normal form after pressing enter. 11 | Common [data structures](data-structures.md) will get resolved in a 12 | seperate line if detected (e.g. numbers, lists or strings). 13 | 14 | ## Definitions 15 | 16 | Since everything you type will get evaluated, definitions (compared to 17 | definitions in files) require an equal sign: 18 | 19 | ``` bruijn 20 | > id = [0] 21 | > id 22 | [0] 23 | ``` 24 | 25 | ## Commands 26 | 27 | ### `:import`{.bruijn}/`:input`{.bruijn} 28 | 29 | Equivalent to the [respective commands in 30 | files](../introduction/syntax.md#imports). 31 | 32 | ``` bruijn 33 | > :import std/Math . 34 | ``` 35 | 36 | ### `:test`{.bruijn} 37 | 38 | Equivalent to the [test command in 39 | files](../introduction/syntax.md#tests). 40 | 41 | ``` bruijn 42 | > :test ([0]) ([[1]]) 43 | ERROR test failed: [0] = [[1]] 44 | reduced to [0] = [[1]] 45 | ``` 46 | 47 | ### `:watch`{.bruijn} 48 | 49 | `:watch`{.bruijn} re-imports the file automatically after every saved 50 | change. It will rerun any test the watched file contains. 51 | 52 | ``` bruijn 53 | > :watch collatz-proof 54 | ``` 55 | 56 | This can be very helpful for [test driven 57 | development](test-driven-development.md). 58 | 59 | ### `:time`{.bruijn} 60 | 61 | Measures the time from start of reduction to its end (normal form) in 62 | seconds. 63 | 64 | ``` bruijn 65 | > :time fac (+30) 66 | 0.15 seconds 67 | ``` 68 | 69 | ### `:blc`{.bruijn} 70 | 71 | Translates both the unreduced and the reduced expression to binary 72 | lambda calculus. Helpful for golfed [compilation](compilation.md). 73 | 74 | ``` bruijn 75 | > :blc [0] [0] 76 | 0100100010 77 | 0010 78 | ``` 79 | 80 | ### `:length`{.bruijn} 81 | 82 | Measures the length of the binary lambda calculus encoding of both the 83 | unreduced and the reduced expression. Helpful for golfed 84 | [compilation](compilation.md). 85 | 86 | ``` bruijn 87 | > :length [0] [0] 88 | 10 89 | 4 90 | ``` 91 | 92 | ### `:free`{.bruijn} 93 | 94 | The `free` command frees the current environment including all defined 95 | identifiers and imported files. 96 | 97 | ``` bruijn 98 | > id = [0] 99 | > :free 100 | > id 101 | ERROR undefined identifier id 102 | ``` 103 | -------------------------------------------------------------------------------- /docs/wiki_src/coding/combinators.md: -------------------------------------------------------------------------------- 1 | # Combinators 2 | 3 | Combinators are short *closed* terms that can be combined with other 4 | terms or combinators. 5 | 6 | ## Common 7 | 8 | All of these combinators (and many more) can be found in 9 | [`std/Combinator`](/std/Combinator.bruijn.html). The names are taken 10 | from Raymond Smullyan's book "To Mock a Mockingbird"[^1]. 11 | 12 | `y`{.bruijn}/`z`{.bruijn}: *Fixed-point* combinators 13 | 14 | : used to achieve [recursion](recursion.md) 15 | 16 | : `(y g)`{.bruijn} = `(g (y g))`{.bruijn} 17 | 18 | `b`{.bruijn}/`b'`{.bruijn}/`b'''`{.bruijn} or `…∘…`{.bruijn}/`…∘∘…`{.bruijn}/`…∘∘∘…`{.bruijn}: *Blackbird* combinators 19 | 20 | : used to compose two functions with 1/2/3 arguments 21 | 22 | : `((f ∘ g) x)`{.bruijn} = `(f (g x))`{.bruijn} 23 | 24 | : `(((f ∘∘ g) x) y)`{.bruijn} = `(f ((g x) y))`{.bruijn} 25 | 26 | : `((((f ∘∘∘ g) x) y) z)`{.bruijn} = `(f (((g x) y) z))`{.bruijn} 27 | 28 | `c`{.bruijn} or `\‣`{.bruijn}: *Cardinal* combinator 29 | 30 | : used to flip arguments (e.g. for higher-order application) 31 | 32 | : `((\f x) y)`{.bruijn} = `((f y) x)`{.bruijn} 33 | 34 | `s`{.bruijn} or `…<*>…`{.bruijn}: *Starling* combinator 35 | 36 | : used to apply one argument to two functions (*substitution*) 37 | 38 | : `((f <*> g) x)`{.bruijn} = `((f x) (g x))`{.bruijn} 39 | 40 | `k`{.bruijn} or `const`{.bruijn}: *Kestrel* combinator 41 | 42 | : used to wrap a term inside an additional abstraction (also for 43 | [boolean logic](data-structures.md#booleansbits-stdlogic)) 44 | 45 | : `(k f)`{.bruijn} = `[f]`{.bruijn} 46 | 47 | `i`{.bruijn} (Haskell's `id`{.haskell}): *Kestrel* combinator 48 | 49 | : used as identity function or to indicate an unused argument 50 | 51 | : `(i x)`{.bruijn} = `x`{.bruijn} 52 | 53 | `ψ`{.bruijn}: *Psi* combinator (Haskell's `on`{.haskell}) 54 | 55 | : used to apply two arguments to one function seperately 56 | 57 | : `((((ψ f) g) x) y)`{.bruijn} = `((f (g x)) (g y))`{.bruijn} 58 | 59 | `ω`{.bruijn}: *Mockingbird*/*omega* combinator 60 | 61 | : used to apply a term to itself 62 | 63 | : `(ω f)`{.bruijn} = `(f f)`{.bruijn} 64 | 65 | : Also: `Ω`{.bruijn} = `(ω ω)`{.bruijn} 66 | 67 | ------------------------------------------------------------------------ 68 | 69 | If you enjoy the use of combinators, you might also enjoy bruijn's 70 | sister language [Birb](https://esolangs.org/wiki/Birb). 71 | 72 | [^1]: Smullyan, Raymond M. To Mock a Mockingbird: and other logic 73 | puzzles including an amazing adventure in combinatory logic. Oxford 74 | University Press, USA, 2000. 75 | -------------------------------------------------------------------------------- /docs/wiki_src/coding/recursion.md: -------------------------------------------------------------------------------- 1 | # Recursion 2 | 3 | Just as normal lambda calculus, bruijn does *not* support typical 4 | [recursion](recursion.md). 5 | 6 | If you want to recursively call a function (or imitate `for`/`while` 7 | loops), you need to use *fixed-point combinators* like `y`{.bruijn} from 8 | [`std/Combinator`](/std/Combinator.bruijn.html). 9 | 10 | Fixed-point combinators have the fascinating property of inducing 11 | recursive behaviour in programming languages without support for 12 | recursion. 13 | 14 | Say we want a function `g`{.bruijn} to be able to call itself. With the 15 | `y`{.bruijn} combinator the following equivalence is obtained: 16 | 17 | (y g) 18 | ⤳ [[1 (0 0)] [1 (0 0)]] g 19 | ⤳ [g (0 0)] [g (0 0)] 20 | ⤳ g ([g (0 0)] [g (0 0)]) 21 | ≡ g (y g) 22 | 23 | With this equivalence, `g`{.bruijn} is able to call itself since its 24 | outer argument is the initial function again. 25 | 26 | Example for using `y`{.bruijn} to find the factorial of 3: 27 | 28 | ``` bruijn 29 | # here, `1` is the induced outer argument `(y g)` 30 | # `0` is the accumulator (the argument of `factorial`) 31 | g [[=?0 (+1) (0 ⋅ (1 --0))]] 32 | 33 | factorial y g ⧗ Number → Number 34 | 35 | :test ((factorial (+3)) =? (+6)) (true) 36 | ``` 37 | 38 | In the wild it might look like this: 39 | 40 | ``` bruijn 41 | # 3 abstractions => two arguments 42 | # 2 is recursive call 43 | # 1 is accumulator (+0) 44 | # 0 is argument (list) 45 | length z [[[rec]]] (+0) ⧗ (List a) → Number 46 | rec 0 [[[case-inc]]] case-end 47 | case-inc 5 ++4 1 48 | case-end 1 49 | ``` 50 | 51 | Read [list data structure](data-structures.md#lists-stdlist) for more 52 | information. Also read [coding style](style.md) for other style 53 | suggestions. 54 | 55 | ## Mutual recurrence relations 56 | 57 | For solving mutual recurrence relations, you can use the *variadic 58 | fixed-point combinator* `y*`{.bruijn} from 59 | [`std/List`](/std/List.bruijn.html). This combinator produces all the 60 | fixed points of given functions as an iterable 61 | [list](data-structures.md). 62 | 63 | Example `even?`{.bruijn}/`odd?`{.bruijn} implementation using 64 | `y*`{.bruijn}: 65 | 66 | ``` bruijn 67 | # the odd? recursive call will be the second argument (1) 68 | g [[[=?0 true (1 --0)]]] 69 | 70 | # the even? recursive call will be the first argument (2) 71 | h [[[=?0 false (2 --0)]]] 72 | 73 | even? head (y* (g : {}h)) ⧗ Number → Boolean 74 | 75 | odd? tail (y* (g : {}h)) ⧗ Number → Boolean 76 | ``` 77 | 78 | Read more about this in the blog post [Variadic fixed-point 79 | combinators](https://text.marvinborner.de/2023-06-18-15.html). 80 | -------------------------------------------------------------------------------- /std/Number/Parigot.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | # see "on the representation of data in lambda-calculus" (Parigot 1989) 3 | # has a "one-step" predecessor *and* addition function 4 | 5 | # Compared to unary/church, they're also faster in many reducers 6 | 7 | :import std/Combinator . 8 | :import std/Logic . 9 | 10 | # zero Parigot number 11 | zero i ⧗ Parigot 12 | 13 | # returns true if Parigot number is zero 14 | zero? [0 [true] [false]] ⧗ Parigot → Boolean 15 | 16 | =?‣ zero? 17 | 18 | :test (=?zero) (true) 19 | :test (=?[[0 1]]) (false) 20 | :test (=?[[0 [0 2]]]) (false) 21 | 22 | # increments Parigot number 23 | inc q''' ⧗ Parigot → Parigot 24 | 25 | ++‣ inc 26 | 27 | :test (++zero) ([[0 1]]) 28 | :test (++[[0 1]]) ([[0 [0 2]]]) 29 | 30 | # decrements Parigot number 31 | dec r i ⧗ Parigot → Parigot 32 | 33 | --‣ dec 34 | 35 | :test (--(++zero)) (zero) 36 | :test (--[[0 [0 2]]]) ([[0 1]]) 37 | 38 | # decrements Parigot number with `dec 0 = 0` 39 | dec' [0 [[[0]]] [[0]] [1 0 [0]]] ⧗ Parigot → Parigot 40 | 41 | # adds two Parigot numbers 42 | add b ⧗ Parigot → Parigot → Parigot 43 | 44 | …+… add 45 | 46 | :test (add (inc (zero)) (inc (inc zero))) (inc (inc (inc zero))) 47 | 48 | # multiplies two Parigot numbers 49 | # [[iter zero (add 1) 0]] 50 | mul [[[1 [[[0]]] 0 0] [[[4 (2 1 1 0)]]]]] ⧗ Parigot → Parigot → Parigot 51 | 52 | …⋅… mul 53 | 54 | :test ((inc zero) ⋅ zero) (zero) 55 | :test ((inc zero) ⋅ (inc (inc zero))) (inc (inc zero)) 56 | :test ((inc (inc (inc zero))) ⋅ (inc (inc zero))) (inc (inc (inc (inc (inc (inc zero)))))) 57 | 58 | # subtracts two Parigot numbers 59 | # will not give correct results when b>a in a-b (use sub' then) 60 | # [[iter 1 dec 0]] 61 | sub [[[1 [[4]] 0 0] [[[2 1 1 0 [0]]]]]] ⧗ Parigot → Parigot → Parigot 62 | 63 | …-… sub 64 | 65 | :test ((inc zero) - zero) (inc zero) 66 | :test ((inc (inc (inc zero))) - (inc zero)) (inc (inc zero)) 67 | 68 | # subtracts two Parigot numbers such that a-b=0 if b>a 69 | sub' [[[0 [[3]] 2 2]]] [[dec' (1 0 0)]] ⧗ Parigot → Parigot → Parigot 70 | 71 | …-*… sub' 72 | 73 | :test ((inc zero) -* zero) (inc zero) 74 | :test ((inc (inc (inc zero))) -* (inc zero)) (inc (inc zero)) 75 | :test ((inc zero) -* (inc (inc zero))) (zero) 76 | 77 | # returns true if Parigot number is greater than other Parigot number 78 | gt? not! ∘∘ (zero? ∘∘ sub') ⧗ Parigot → Parigot → Parigot 79 | 80 | iter [[[0 ι ρ ρ]]] ⧗ a → (a → a) → Parigot → a 81 | ρ [[3 (1 0 0)]] 82 | ι [[4]] 83 | 84 | :test (iter zero inc (inc (inc (inc zero)))) (inc (inc (inc zero))) 85 | 86 | rec [[[0 ι ρ ρ --0]]] ⧗ Parigot → (Parigot → Boolean) → Parigot → Parigot 87 | ρ [[[4 0 (2 1 1)]]] 88 | ι [[[5]]] 89 | -------------------------------------------------------------------------------- /samples/fun/minibruijn.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | 3 | # usage: 4 | # write a file test.bruijn 5 | # ``` 6 | # zero [[0]] 7 | # inc [[[1 (2 1 0)]]] 8 | # two inc (inc zero) 9 | # four two two 10 | # main four four 11 | # ``` 12 | # run `cat test.bruijn | bruijn minibruijn.bruijn` 13 | 14 | # This parser/interpreter works by parsing the input to the meta encoding 15 | # (similar to Mogensen-Scott) and reducing it using its self-interpreter! 16 | # Substituting the definitions is done *while parsing* using hashmaps 17 | 18 | :import std/Char 19 | :import std/Combinator . 20 | :import std/List . 21 | :import std/Meta 22 | :import std/Monad/Parser . 23 | :import std/Number/Conversion 24 | :import std/Map 25 | :import std/Result 26 | :import std/String 27 | :import std/Option 28 | 29 | # meta encoding uses Church numerals instead of binary! 30 | char→number (\Char.sub '0') → Conversion.binary→unary 31 | 32 | # parses [a-z]+ 33 | identifier some (satisfy Char.alpha?) 34 | 35 | # parses * 36 | spaces many (satisfy Char.space?) 37 | 38 | # parses \n+ 39 | newlines some (satisfy (Char.eq? '\n')) 40 | 41 | # parses between parentheses 42 | parens between (char '(') (char ')') 43 | 44 | # parses a single number (as number) 45 | number char→number <$> (satisfy Char.numeric?) 46 | 47 | error-identifier error-custom "identifier not found" 48 | 49 | # T := [T] # Abstraction 50 | # | T..T # Application 51 | # | (T) # Parenthesized 52 | # | 0-9 # de Bruijn index 53 | # identifiers ([a-z]*) just get looked up in the hashmap! 54 | term [y [(foldl1 Meta.app) <$> (some (spaces *> singleton <* spaces))]] 55 | singleton abs <|> idx <|> def <|> (parens 0) 56 | abs Meta.abs <$> (between (char '[') (char ']') 0) 57 | idx Meta.idx <$> number 58 | def identifier >>= [lift-result (Option.result-or error-identifier lookup)] 59 | lookup String.#Map.lookup 0 2 60 | 61 | :test (term Map.empty "()") (Result.err (error-compose (error-unexpected "(") (error-unexpected ")"))) 62 | :test (term Map.empty "[[0 1]]") (Result.ok [0 `[[(0 1)]] empty]) 63 | :test (term (String.#Map.insert "foo" `[[1]] Map.empty) "[foo 0]") (Result.ok [0 `[[[1]] 0] empty]) 64 | 65 | # parses an identifier, a term, and newlines to a hashmap insertion 66 | block [[[String.#Map.insert 1 0 2]] <$> identifier <*> (term 0) <* newlines] 67 | 68 | :test (block Map.empty "main [0]\n") (Result.ok [0 (String.#Map.insert "main" `[0] Map.empty) empty]) 69 | :test (block Map.empty "main ()\n") (Result.err (error-compose (error-unexpected "(") (error-unexpected ")"))) 70 | 71 | # iterates parsing of blocks starting with an empty hashmap until end 72 | program y [[((block 0) >>= 1) <|> (eof *> (pure 0))]] Map.empty 73 | 74 | # evaluates the main function of a program 75 | main Meta.eval <$> ([String.#Map.lookup "main" 0 i i] <$> program) → [0 i i] 76 | -------------------------------------------------------------------------------- /samples/fun/collatz.bruijn: -------------------------------------------------------------------------------- 1 | # bruijn collatz.bruijn 2 | # fun collatz implementation using binary abstract machine 3 | # inspired by David Barina's algorithm 4 | # adapted for LC by fusing certain operations and carefully choosing encodings and bases 5 | 6 | # no dependence on std/Number/... because of golfing plans 7 | 8 | :import std/Combinator . 9 | 10 | # :import std/Number/Binary . 11 | 12 | start (+01189998819991197253b) 13 | 14 | # TODO: could we also strip leading zeroes here? 15 | ≠²!‣ [[[[3 z a¹ a⁰ k]]]] ⧗ Binary → Binary 16 | z [0 3 3] 17 | a¹ &[[[0 (4 1) (4 1)]]] 18 | a⁰ &[[[0 2 (3 1)]]] 19 | 20 | =²?‣ [0 [[1]] [[[0]]] [[[1]]]] ⧗ Binary → Bit 21 | 22 | ≠²?‣ [0 [[0]] [[[1]]] [[[0]]]] ⧗ Binary → Bit 23 | 24 | ↑¹‣ [[[[1 (3 2 1 0)]]]] ⧗ Binary → Binary 25 | 26 | ↑⁰‣ [[[[0 (3 2 1 0)]]]] ⧗ Binary → Binary 27 | 28 | …↑… [[[[[4 1 0 (3 2 1 0)]]]]] ⧗ Bit → Binary → Binary 29 | 30 | ++‣ [0 z a¹ a⁰ ki] ⧗ Binary → Binary 31 | z [0 (+0b) (+1b)] 32 | a¹ &[[[0 ↑¹2 ↑⁰1]]] 33 | a⁰ &[[[0 ↑⁰2 ↑¹2]]] 34 | 35 | /²‣ [0 z a¹ a⁰ [[0]]] ⧗ Binary → Binary 36 | z [0 (+0b) (+0b)] 37 | a¹ &[[[0 ↑¹2 2]]] 38 | a⁰ &[[[0 ↑⁰2 2]]] 39 | 40 | # converts the normal binary representation into abstract 41 | →^‣ [0 z a¹ a⁰] ⧗ Binary → AbstractBinary 42 | z (+0b) 43 | a¹ [[[[1 3]]]] 44 | a⁰ [[[[0 3]]]] 45 | 46 | # converts the abstracted binary representation back to normal 47 | →_‣ y [[0 z a¹ a⁰]] ⧗ AbstractBinary → Binary 48 | z (+0b) 49 | a¹ [↑¹(2 0)] 50 | a⁰ [↑⁰(2 0)] 51 | 52 | # a + b 53 | # TODO: can we do this without abstracting? 54 | …+… [[abs 1 →^0]] ⧗ Binary → Binary → Binary 55 | abs [c (0 z a¹ a⁰)] 56 | c¹ [1 ↑⁰(3 0 [[1]]) ↑¹(3 0 [[0]])] 57 | a¹ [[[1 (c¹ 1) c¹' c¹]]] 58 | c¹' [1 ↑ (3 0 [[1]])] 59 | a⁰ [[[1 (c⁰ 1) c¹ c⁰]]] 60 | c⁰ [1 ↑ (3 0 [[0]])] 61 | z [[0 ++(→_1) →_1]] 62 | c [[1 0 [[0]]]] 63 | 64 | =¹?‣ [0 z a¹ a⁰ [[1]]] ⧗ Binary → Binary 65 | z [0 [[0]] [[1]]] 66 | a¹ &[[[0 1 [[0]]]]] 67 | a⁰ &[[[0 [[0]] [[0]]]]] 68 | 69 | # interestingly, ≠²!(↑¹0 + 0) as a side effect strips leading zeroes, TODO: overhead? 70 | # :test (=¹?(+1b)) ([[1]]) 71 | # :test (=¹?[[[1 (0 2)]]]) ([[1]]) 72 | # :test (=¹?[[[1 (0 (0 2))]]]) ([[1]]) 73 | # :test (=¹?[[[1 (0 (0 (0 2)))]]]) ([[1]]) 74 | 75 | :test (=¹?(+0b)) ([[0]]) 76 | :test (=¹?(+2b)) ([[0]]) 77 | :test (=¹?(+3b)) ([[0]]) 78 | :test (=¹?(+4b)) ([[0]]) 79 | :test (=¹?(+5b)) ([[0]]) 80 | :test (=¹?(+6b)) ([[0]]) 81 | :test (=¹?(+7b)) ([[0]]) 82 | :test (=¹?(+8b)) ([[0]]) 83 | :test (=¹?(+9b)) ([[0]]) 84 | :test (=¹?(+10b)) ([[0]]) 85 | 86 | collatz make-odd → (z [[rec]]) ⧗ Binary → Boolean 87 | make-odd [=²?0 ≠²!(/²0) 0] 88 | rec =¹?0 case-end case-rec 89 | case-rec 1 ≠²!(↑¹0 + 0) 90 | case-end [[1]] 91 | 92 | odd-collatz z [[rec]] ⧗ Binary → Boolean 93 | rec =¹?0 case-end case-rec 94 | case-rec 1 ≠²!(↑¹0 + 0) 95 | case-end [[1]] 96 | 97 | main [collatz start] 98 | -------------------------------------------------------------------------------- /std/Logic/Binary.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2022 Marvin Borner 2 | 3 | :import std/Combinator . 4 | 5 | # true 6 | true k ⧗ Boolean 7 | 8 | # false 9 | false ki ⧗ Boolean 10 | 11 | # inverts boolean value 12 | # equivalent of [0 ⇒ false] 13 | not! c ⧗ Boolean → Boolean 14 | 15 | ¬‣ not! 16 | 17 | :test (¬true) (false) 18 | :test (¬false) (true) 19 | 20 | # true if both args are true 21 | and? [[0 1 0]] ⧗ Boolean → Boolean → Boolean 22 | 23 | …⋀?… and? 24 | 25 | :test (true ⋀? true) (true) 26 | :test (true ⋀? false) (false) 27 | :test (false ⋀? true) (false) 28 | :test (false ⋀? false) (false) 29 | 30 | # true if not both args are true 31 | nand? [[1 0 1 false true]] ⧗ Boolean → Boolean → Boolean 32 | 33 | :test (nand? true true) (false) 34 | :test (nand? true false) (true) 35 | :test (nand? false true) (true) 36 | :test (nand? false false) (true) 37 | 38 | # true if one of the args is true 39 | or? m ⧗ Boolean → Boolean → Boolean 40 | 41 | …⋁?… or? 42 | 43 | :test (true ⋁? true) (true) 44 | :test (true ⋁? false) (true) 45 | :test (false ⋁? true) (true) 46 | :test (false ⋁? false) (false) 47 | 48 | # true if both args are false 49 | nor? [[1 1 0 false true]] ⧗ Boolean → Boolean → Boolean 50 | 51 | :test (nor? true true) (false) 52 | :test (nor? true false) (false) 53 | :test (nor? false true) (false) 54 | :test (nor? false false) (true) 55 | 56 | # true if args are not same bools 57 | xor? [[0 (1 false 0) 1]] ⧗ Boolean → Boolean → Boolean 58 | 59 | …^?… xor? 60 | 61 | :test (xor? true true) (false) 62 | :test (xor? true false) (true) 63 | :test (xor? false true) (true) 64 | :test (xor? false false) (false) 65 | 66 | # true if both args are same bools 67 | xnor? [[0 1 (1 0 true)]] ⧗ Boolean → Boolean → Boolean 68 | 69 | :test (xnor? true true) (true) 70 | :test (xnor? true false) (false) 71 | :test (xnor? false true) (false) 72 | :test (xnor? false false) (true) 73 | 74 | # if first arg is true, exec first exp; else second exp 75 | # this function is generally redundant 76 | # I personally just write (exp? case-T case-F) directly 77 | if [[[2 1 0]]] ⧗ Boolean → a → b → c 78 | 79 | …?…:… if 80 | 81 | :test (if true true false) (true) 82 | :test (true ? true : false) (true) 83 | :test (if false true false) (false) 84 | :test (false ? true : false) (false) 85 | 86 | # mathematical implies definition 87 | implies [[1 0 true]] ⧗ Boolean → Boolean → Boolean 88 | 89 | …⇒?… implies 90 | 91 | :test (true ⇒? true) (true) 92 | :test (true ⇒? false) (false) 93 | :test (false ⇒? true) (true) 94 | :test (false ⇒? false) (true) 95 | 96 | # mathematical iff (if and only if) definition 97 | iff xnor? ⧗ Boolean → Boolean → Boolean 98 | 99 | …⇔?… iff 100 | 101 | :test (true ⇔? true) (true) 102 | :test (true ⇔? false) (false) 103 | :test (false ⇔? true) (false) 104 | :test (false ⇔? false) (true) 105 | -------------------------------------------------------------------------------- /std/Monad/Parser.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2024 Marvin Borner 2 | # see samples/fun/minibruijn for example usage 3 | # TODO: also support line/char offset 4 | 5 | :import std/List . 6 | :import std/Combinator . 7 | :import std/Char C 8 | :import std/Result R 9 | 10 | # TODO: don't just use strings for errors 11 | 12 | error-unexpected ["unexpected symbol " ++ 0] ⧗ Error 13 | 14 | error-end-of-input "end of input" ⧗ Error 15 | 16 | error-expected-end "expected end of input" ⧗ Error 17 | 18 | error-custom [0] ⧗ Error 19 | 20 | error-compose [[C.?eq? 1 0 0 (1 ++ " or " ++ 0)]] ⧗ Error → Error → Error 21 | 22 | satisfy [[0 [[[go]]] end]] ⧗ (a → Boolean) → (Parser a) 23 | go 4 2 (R.ok (2 : 1)) (R.err (error-unexpected {}2)) 24 | end R.err error-end-of-input 25 | 26 | char [satisfy (C.eq? 0)] ⧗ Char → (Parser a) 27 | 28 | :test (char 'a' "abc") (R.ok ('a' : "bc")) 29 | :test (char 'b' "abc") (R.err (error-unexpected "a")) 30 | :test (char 'a' [[0]]) (R.err error-end-of-input) 31 | 32 | map [[[R.map ok (1 0)]]] ⧗ (a → b) → (Parser a) → (Parser b) 33 | ok &[[(4 1) : 0]] 34 | 35 | …<$>… map 36 | 37 | fail [[R.err 1]] ⧗ a → (Parser a) 38 | 39 | pure [[R.ok (1 : 0)]] ⧗ a → (Parser a) 40 | 41 | ap [[[R.bind (2 0) ok]]] ⧗ (Parser (a → b)) → (Parser a) → (Parser b) 42 | ok &[[R.map ok (3 0)]] 43 | ok &[[(3 1) : 0]] 44 | 45 | …<*>… ap 46 | 47 | string y [[0 [[[go]]] (pure [[0]])]] ⧗ String → (Parser a) 48 | go cons <$> (char 2) <*> (4 1) 49 | 50 | :test (string "ac" "abc") (R.err (error-unexpected "b")) 51 | :test (string "ab" "abcd") (R.ok ("ab" : "cd")) 52 | 53 | return pure ⧗ a → (Parser a) 54 | 55 | bind [[[R.bind (2 0) ok]]] ⧗ (Parser a) → (a → (Parser b)) → (Parser a) 56 | ok &[[3 1 0]] 57 | 58 | …>>=… bind 59 | 60 | alt [[[2 0 R.ok err]]] ⧗ (Parser a) → (Parser a) → (Parser a) 61 | err [2 1 R.ok err] 62 | err R.err ∘ (error-compose 0) 63 | 64 | …<|>… alt 65 | 66 | :test ((string "ab") <|> (string "cd") "abc") (R.ok ("ab" : "c")) 67 | :test ((string "ab") <|> (string "cd") "cde") (R.ok ("cd" : "e")) 68 | :test ((string "ab") <|> (string "cd") "acd") (R.err (error-compose (error-unexpected "c") (error-unexpected "a"))) 69 | :test ((string "ab") <|> (string "cd") "cbe") (R.err (error-compose (error-unexpected "c") (error-unexpected "b"))) 70 | 71 | eof [0 [[[go]]] end] ⧗ (Parser a) 72 | go R.err error-expected-end 73 | end R.ok ([[0]] : [[0]]) 74 | 75 | lift-result [0 pure fail] ⧗ (Result a) → (Parser a) 76 | 77 | :test (lift-result (R.ok "ok") "rst") (R.ok ("ok" : "rst")) 78 | :test (lift-result (R.err "oh") "rst") (R.err "oh") 79 | 80 | # =========================================================================== # 81 | # most relevant functions are defined - we can now derive from Generic/Monad! # 82 | # =========================================================================== # 83 | 84 | :input std/Generic/Monad 85 | 86 | :test (k <$ (string "ab") "abc") (R.ok (k : "c")) 87 | :test ((char '{') *> (string "wow") <* (char '}') "{wow}{owo}") (R.ok ("wow" : "{owo}")) 88 | -------------------------------------------------------------------------------- /samples/rosetta/balanced_ternary.bruijn: -------------------------------------------------------------------------------- 1 | :import std/Combinator . 2 | :import std/Logic . 3 | :import std/Pair . 4 | 5 | # negative trit indicating coefficient of (-1) 6 | t⁻ [[[2]]] 7 | 8 | # positive trit indicating coefficient of (+1) 9 | t⁺ [[[1]]] 10 | 11 | # zero trit indicating coefficient of 0 12 | t⁰ [[[0]]] 13 | 14 | # shifts a negative trit into a balanced ternary number 15 | ↑⁻‣ [[[[[2 (4 3 2 1 0)]]]]] 16 | 17 | # shifts a positive trit into a balanced ternary number 18 | ↑⁺‣ [[[[[1 (4 3 2 1 0)]]]]] 19 | 20 | # shifts a zero trit into a balanced ternary number 21 | ↑⁰‣ [[[[[0 (4 3 2 1 0)]]]]] 22 | 23 | # shifts a specified trit into a balanced ternary number 24 | …↑… [[[[[[5 2 1 0 (4 3 2 1 0)]]]]]] 25 | 26 | # negates a balanced ternary number 27 | -‣ [[[[[4 3 1 2 0]]]]] 28 | 29 | # increments a balanced ternary number (can introduce leading 0s) 30 | ++‣ [~(0 z a⁻ a⁺ a⁰)] 31 | z (+0) : (+1) 32 | a⁻ &[[↑⁻1 : ↑⁰1]] 33 | a⁺ &[[↑⁺1 : ↑⁻0]] 34 | a⁰ &[[↑⁰1 : ↑⁺1]] 35 | 36 | # decrements a balanced ternary number (can introduce leading 0s) 37 | --‣ [~(0 z a⁻ a⁺ a⁰)] 38 | z (+0) : (-1) 39 | a⁻ &[[↑⁻1 : ↑⁺0]] 40 | a⁺ &[[↑⁺1 : ↑⁰1]] 41 | a⁰ &[[↑⁰1 : ↑⁻1]] 42 | 43 | # converts the normal balanced ternary representation into abstract form 44 | →^‣ [0 z a⁻ a⁺ a⁰] 45 | z (+0) 46 | a⁻ [[[[[2 4]]]]] 47 | a⁺ [[[[[1 4]]]]] 48 | a⁰ [[[[[0 4]]]]] 49 | 50 | # converts the abstracted balanced ternary representation back to normal 51 | →_‣ y [[0 z a⁻ a⁺ a⁰]] 52 | z (+0) 53 | a⁻ [↑⁻(2 0)] 54 | a⁺ [↑⁺(2 0)] 55 | a⁰ [↑⁰(2 0)] 56 | 57 | # adds two balanced ternary numbers (can introduce leading 0s) 58 | …+… [[[c (0 z a⁻ a⁺ a⁰)] 1 →^0]] 59 | b⁻ [1 ↑⁺(3 0 t⁻) ↑⁰(3 0 t⁰) ↑⁻(3 0 t⁰)] 60 | b⁰ [1 ↑ (3 0 t⁰)] 61 | b⁺ [1 ↑⁰(3 0 t⁰) ↑⁻(3 0 t⁺) ↑⁺(3 0 t⁰)] 62 | a⁻ [[[1 (b⁻ 1) b⁻' b⁰ b⁻]]] 63 | b⁻' [1 ↑⁰(3 0 t⁻) ↑⁻(3 0 t⁰) ↑⁺(3 0 t⁻)] 64 | a⁺ [[[1 (b⁺ 1) b⁰ b⁺' b⁺]]] 65 | b⁺' [1 ↑⁺(3 0 t⁰) ↑⁰(3 0 t⁺) ↑⁻(3 0 t⁺)] 66 | a⁰ [[[1 (b⁰ 1) b⁻ b⁺ b⁰]]] 67 | z [[0 --(→_1) ++(→_1) →_1]] 68 | c [[1 0 t⁰]] 69 | 70 | # subtracts two balanced ternary numbers (can introduce leading 0s) 71 | …-… [[1 + -0]] 72 | 73 | # multiplicates two balanced ternary numbers (can introduce leading 0s) 74 | …⋅… [[1 z a⁻ a⁺ a⁰]] 75 | z (+0) 76 | a⁻ [↑⁰0 - 1] 77 | a⁺ [↑⁰0 + 1] 78 | a⁰ [↑⁰0] 79 | 80 | # true if balanced ternary number is zero 81 | =?‣ [0 true [false] [false] i] 82 | 83 | # true if two balanced ternary numbers are equal 84 | # → ignores leading 0s! 85 | …=?… [[[0 z a⁻ a⁺ a⁰] 1 →^0]] 86 | z [=?(→_0)] 87 | a⁻ [[0 false [2 0] [false] [false]]] 88 | a⁺ [[0 false [false] [2 0] [false]]] 89 | a⁰ [[0 (1 0) [false] [false] [2 0]]] 90 | 91 | main [[0]] 92 | 93 | # --- tests/examples --- 94 | 95 | :test ((-42) + (-1) =? (-43)) (true) 96 | :test ((+1) + (+2) =? (+3)) (true) 97 | :test ((-42) - (-1) =? (-41)) (true) 98 | :test ((+1) - (+2) =? (-1)) (true) 99 | :test ((-1) ⋅ (+42) =? (-42)) (true) 100 | :test ((+3) ⋅ (+11) =? (+33)) (true) 101 | -------------------------------------------------------------------------------- /editors/kate/bruijn.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

      2 | 3 | Bruijn logo 4 | 5 |

      6 | 7 | > A purely functional programming language based on lambda calculus and 8 | > de Bruijn indices written in Haskell. 9 | 10 | Pronunciation: `/bɹaʊn/`. 11 | 12 | Wiki, docs, articles, examples and more: 13 | [website](https://bruijn.marvinborner.de). Also: [Rosetta 14 | Code](https://rosettacode.org/wiki/Category:Bruijn). 15 | 16 | ## Features 17 | 18 | - Substantial **standard library** with 700+ useful functions (see 19 | `std/`) 20 | - **No primitive functions** - every function is implemented in Bruijn 21 | itself 22 | - 1:1 correspondence to lambda calculus (e.g. space-efficient 23 | compilation to **binary lambda calculus (BLC)**) 24 | - **de Bruijn indices** instead of named variables 25 | - Lazy evaluation by default (**call-by-need** reduction) 26 | - **Syntactic sugar** makes writing terms simpler (e.g. numbers, 27 | strings, chars, meta terms) 28 | - **Mixfix** and **prefix** operators 29 | - **Recursion** can be implemented using combinators such as Y, Z or ω 30 | 31 | ## Why 32 | 33 | - By having a very small core (the reducer), bruijn is safe, consistent, 34 | and (potentially) proven to be correct! 35 | - Since it doesn’t have builtin functions, bruijn is independent of 36 | hardware internals and could easily be run on almost any architecture. 37 | - Compiled binary lambda calculus is incredibly expressive and tiny. 38 | Read the articles by [Jart](https://justine.lol/lambda/#why) and 39 | [Tromp](https://tromp.github.io/cl/cl.html). 40 | - Exploring different encodings of data as function abstractions is 41 | really fascinating. 42 | - Naming parameters of functions is annoying. De Bruijn indices are a 43 | universal reference independent of the function and can actually help 44 | readability! 45 | - Really, just for fun. 46 | 47 | ## Wiki 48 | 49 | Learn anything about bruijn in the 50 | [wiki](https://bruijn.marvinborner.de/wiki/) (also found in 51 | `docs/wiki_src/`). 52 | 53 | ## References 54 | 55 | 0. De Bruijn, Nicolaas Govert. “Lambda calculus notation with nameless 56 | dummies, a tool for automatic formula manipulation, with application 57 | to the Church-Rosser theorem.” Indagationes Mathematicae 58 | (Proceedings). Vol. 75. No. 5. North-Holland, 1972. 59 | 1. Mogensen, Torben. “An investigation of compact and efficient number 60 | representations in the pure lambda calculus.” International Andrei 61 | Ershov Memorial Conference on Perspectives of System Informatics. 62 | Springer, Berlin, Heidelberg, 2001. 63 | 2. Wadsworth, Christopher. “Some unusual λ-calculus numeral systems.” 64 | (1980): 215-230. 65 | 3. Tromp, John. “Binary lambda calculus and combinatory logic.” 66 | Randomness and Complexity, from Leibniz to Chaitin. 2007. 237-260. 67 | 4. Tromp, John. “Functional Bits: Lambda Calculus based Algorithmic 68 | Information Theory.” (2022). 69 | 5. Biernacka, M., Charatonik, W., & Drab, T. (2022). A simple and 70 | efficient implementation of strong call by need by an abstract 71 | machine. Proceedings of the ACM on Programming Languages, 6(ICFP), 72 | 109-136. 73 | -------------------------------------------------------------------------------- /docs/wiki_src/introduction/lambda-calculus.md: -------------------------------------------------------------------------------- 1 | # Lambda calculus 2 | 3 | Bruijn is based on de Bruijn indexed lambda calculus. 4 | 5 | ## Traditional lambda calculus 6 | 7 | Lambda calculus basically has three types of terms: 8 | 9 | - *Variable*: `x` binds the named variable `x` to an abstraction. 10 | - *Abstraction*: `λx.E` accepts an argument `x` and binds it to term 11 | `E` respectively. It's helpful to think of abstractions as anonymous 12 | functions. 13 | - *Application*: `(f x)` applies `f` to `x` -- the standard convention 14 | allows repeated left-associative application: `f x y z` is 15 | `(((f x) y) z)`. 16 | 17 | Combining these terms and removing redundant parentheses can result in 18 | terms like `λx.λy.x y`, basically representing a function with two 19 | parameters that uses its second parameter as an argument for its first. 20 | 21 | Evaluating terms is called *reduction*. There's only one rule you need 22 | to know: `(λx.E A)` becomes `E[x := A]` -- that is, calling an 23 | abstraction with an argument substitutes the argument inside the body of 24 | the abstraction ("β-reduction"). There are many different kinds of 25 | reduction techniques, but they basically all come back to this simple 26 | rule -- mainly because of the "Church-Rosser theorem" that states that 27 | the order of reduction doesn't change the eventual result. 28 | 29 | When we talk about reduction in bruijn, we typically mean "reduction 30 | until normal form" -- we reduce until the term can't be reduced any 31 | further (there does not exist any `(λx.E A)`). 32 | 33 | (λx.x λx.x) ⤳ λx.x 34 | (λx.x λx.λy.x) ⤳ λx.λy.x 35 | (λx.λy.x (λx.x) ⤳ λy.λx.x 36 | 37 | ## De Bruijn indices 38 | 39 | Programs written in lambda calculus often have many abstractions and 40 | therefore at least as many variables. I hate naming variables, 41 | especially if you need hundreds of them for small programs. With that 42 | many variables it's also really complicated to compare two expressions, 43 | since you first need to resolve shadowed and conflicting variables 44 | ("α-conversion"). 45 | 46 | De Bruijn indices replace the concept of variables by using numeric 47 | references to the abstraction layer. Let me explain using an example: 48 | The expression `λx.x` becomes `λ0` -- the `0` refers to the first 49 | parameter of the abstraction. Subsequently, the expression `λx.λy.x y` 50 | becomes `λλ1 0`. Basically, if you're reading from left to right 51 | starting at the abstraction you want to bind, you increment on every 52 | occurring `λ` until you arrive at the index. 53 | 54 | While confusing at first, programs written with de Bruijn indices can 55 | actually be way easier to understand than the equivalent program with 56 | named variables. 57 | 58 | ## Bruijn's bracketing 59 | 60 | Bruijn's final syntactic variation from lambda calculus is the use of 61 | square brackets instead of lambda symbols. By putting the entire 62 | abstracted term in brackets, it's much clearer where indices and the 63 | respective terms are bound to. 64 | 65 | Representing `λλ1 0` in bruijn's syntax then becomes `[[1 0]]`{.bruijn}. 66 | The application of `λ0` and `λλ1 0` becomes `[0] [[1 0]]`{.bruijn}. 67 | 68 | ------------------------------------------------------------------------ 69 | 70 | Random example reductions: 71 | 72 | ``` bruijn 73 | a [0] [[1]] ⤳ [[1]] 74 | b [[0]] [[1]] ⤳ [0] 75 | c [[1]] [[1]] ⤳ [[[1]]] 76 | d [[0]] [0] [[1]] ⤳ [[1]] 77 | e [[0 1]] [0] ⤳ [0 [0]] 78 | f [[1 0]] [0] ⤳ [0] 79 | ``` 80 | -------------------------------------------------------------------------------- /std/Tuples.bruijn: -------------------------------------------------------------------------------- 1 | # MIT License, Copyright (c) 2025 Marvin Borner 2 | 3 | :import std/Combinator . 4 | 5 | …: t ⧗ a → (Singleton a) 6 | 7 | tuple [[[0 2 1]]] ⧗ a → b → (Tuple a b) 8 | 9 | …:… tuple 10 | 11 | triple [[[[0 3 2 1]]]] ⧗ a → b → c → (Triple a b c) 12 | 13 | …:…:… triple 14 | 15 | quadruple [[[[[0 4 3 2 1]]]]] ⧗ a → b → c → d → (Quadruple a b c d) 16 | 17 | …:…:…:… quadruple 18 | 19 | quintuple [[[[[[0 5 4 3 2 1]]]]]] ⧗ a → b → c → d → e → (Quintuple a b c d e) 20 | 21 | …:…:…:…:… quintuple 22 | 23 | sextuple [[[[[[[0 6 5 4 3 2 1]]]]]]] ⧗ a → b → c → d → e → f → (Sextuple a b c d e f) 24 | 25 | …:…:…:…:…:… sextuple 26 | 27 | septuple [[[[[[[[0 7 6 5 4 3 2 1]]]]]]]] ⧗ a → b → c → d → e → f → g → (Septuple a b c d e f g) 28 | 29 | …:…:…:…:…:…:… septuple 30 | 31 | octuple [[[[[[[[[0 8 7 6 5 4 3 2 1]]]]]]]]] ⧗ a → b → c → d → e → f → g → h → (Octuple a b c d e f g h) 32 | 33 | …:…:…:…:…:…:…:… octuple 34 | 35 | nonuple [[[[[[[[[[0 9 8 7 6 5 4 3 2 1]]]]]]]]]] ⧗ a → b → c → d → e → f → g → h → i → (Nonuple a b c d e f g h i) 36 | 37 | …:…:…:…:…:…:…:…:… nonuple 38 | 39 | # allocates a tuple of size n with a default element 40 | alloc [[1 (t 0)]] ⧗ Unary → (a → (NTuple a)) 41 | 42 | :test (alloc (+3u) [[1]]) ([[1]] : [[1]] : [[1]]) 43 | 44 | # adds an element to the front of a tuple 45 | push [[[1 (0 2)]]] ⧗ a → (NTuple a) → (NTuple a) 46 | 47 | :test (push (+0d) ((+1d) : (+2d))) ((+0d) : (+1d) : (+2d)) 48 | 49 | # adds an element to the end of a tuple 50 | push-end [[[1 0 2]]] ⧗ a → (NTuple a) → (NTuple a) 51 | 52 | :test (push-end (+0d) ((+1d) : (+2d))) ((+1d) : (+2d) : (+0d)) 53 | 54 | # removes the head of a tuple 55 | pop [[1 [1]]] ⧗ (NTuple a) → (NTuple a) 56 | 57 | :test (pop ((+0u) : (+1u) : (+2u) : (+3u) : (+4u))) ((+1u) : (+2u) : (+3u) : (+4u)) 58 | 59 | # removes the nth element of a tuple 60 | pop-n [[[1 (2 b k 0)]]] ⧗ Unary → (NTuple a) → (NTuple a) 61 | 62 | :test (pop-n (+0u) ((+0u) : (+1u) : (+2u) : (+3u) : (+4u))) ((+1u) : (+2u) : (+3u) : (+4u)) 63 | :test (pop-n (+1u) ((+0u) : (+1u) : (+2u) : (+3u) : (+4u))) ((+0u) : (+2u) : (+3u) : (+4u)) 64 | :test (pop-n (+2u) ((+0u) : (+1u) : (+2u) : (+3u) : (+4u))) ((+0u) : (+1u) : (+3u) : (+4u)) 65 | :test (pop-n (+3u) ((+0u) : (+1u) : (+2u) : (+3u) : (+4u))) ((+0u) : (+1u) : (+2u) : (+4u)) 66 | :test (pop-n (+4u) ((+0u) : (+1u) : (+2u) : (+3u) : (+4u))) ((+0u) : (+1u) : (+2u) : (+3u)) 67 | 68 | # moves the head to the nth position 69 | move [[[1 [3 b [0 1] 1]]]] ⧗ Unary → (NTuple a) → (NTuple a) 70 | 71 | :test (move (+0u) ((+0u) : (+1u) : (+2u) : (+3u) : (+4u))) ((+0u) : (+1u) : (+2u) : (+3u) : (+4u)) 72 | :test (move (+1u) ((+0u) : (+1u) : (+2u) : (+3u) : (+4u))) ((+1u) : (+0u) : (+2u) : (+3u) : (+4u)) 73 | :test (move (+2u) ((+0u) : (+1u) : (+2u) : (+3u) : (+4u))) ((+1u) : (+2u) : (+0u) : (+3u) : (+4u)) 74 | :test (move (+3u) ((+0u) : (+1u) : (+2u) : (+3u) : (+4u))) ((+1u) : (+2u) : (+3u) : (+0u) : (+4u)) 75 | :test (move (+4u) ((+0u) : (+1u) : (+2u) : (+3u) : (+4u))) ((+1u) : (+2u) : (+3u) : (+4u) : (+0u)) 76 | 77 | # shifts the head to the end, requires size of tuple 78 | shift move ⧗ Unary → (NTuple a) → (NTuple a) 79 | 80 | :test (shift (+4u) ((+0u) : (+1u) : (+2u) : (+3u) : (+4u))) ((+1u) : (+2u) : (+3u) : (+4u) : (+0u)) 81 | :test ((+5u) (shift (+4u)) ((+0u) : (+1u) : (+2u) : (+3u) : (+4u))) ((+0u) : (+1u) : (+2u) : (+3u) : (+4u)) 82 | 83 | # constructs selector for kth element of n-tuple 84 | select [[0 (t i) ((1 k) (0 k))]] ⧗ Unary → Unary → ((NTuple a) → a) 85 | 86 | :test (select (+5u) (+0u)) ([[[[[[0]]]]]]) 87 | :test (select (+5u) (+3u)) ([[[[[[3]]]]]]) 88 | -------------------------------------------------------------------------------- /src/Error.hs: -------------------------------------------------------------------------------- 1 | -- MIT License, Copyright (c) 2022 Marvin Borner 2 | {-# LANGUAGE BangPatterns #-} 3 | {-# LANGUAGE RecordWildCards #-} 4 | {-# LANGUAGE ScopedTypeVariables #-} 5 | 6 | module Error where 7 | 8 | import Data.List ( intercalate ) 9 | import Text.Megaparsec 10 | 11 | import Helper 12 | 13 | errPrefix :: String 14 | errPrefix = "\ESC[101m\ESC[30mERROR\ESC[0m " 15 | 16 | okPrefix :: String 17 | okPrefix = "\ESC[102m\ESC[30m OK \ESC[0m " 18 | 19 | data Error = SyntaxError String 20 | | UndefinedIdentifier Identifier 21 | | UnmatchedMixfix [MixfixIdentifierKind] [Mixfix] 22 | | InvalidIndex Int 23 | | FailedTest Expression Expression Expression Expression 24 | | PassedTest Expression Expression 25 | | ContextualError Error Context 26 | | SuggestSolution Error String 27 | | ImportError String 28 | | OptimizerError String 29 | 30 | instance Show Error where 31 | show (ContextualError err ctx) = show err <> "\n" <> printContext ctx 32 | show (SuggestSolution err sol) = 33 | show err <> "\n\ESC[102m\ESC[30msuggestion\ESC[0m Perhaps you meant " <> sol 34 | show (SyntaxError err) = 35 | errPrefix <> "invalid syntax\n\ESC[105m\ESC[30mnear\ESC[0m " <> err 36 | show (UndefinedIdentifier ident) = 37 | errPrefix <> "undefined identifier " <> show ident 38 | show (UnmatchedMixfix ks ms) = 39 | errPrefix 40 | <> "couldn't find matching mixfix for " 41 | <> intercalate "" (map show ks) 42 | <> "\n\ESC[105m\ESC[30mnear\ESC[0m " 43 | <> unwords (map show ms) 44 | show (InvalidIndex err) = errPrefix <> "invalid index " <> show err 45 | show (PassedTest exp1 exp2) = 46 | okPrefix <> "test passed: " <> show exp1 <> " = " <> show exp2 47 | show (FailedTest exp1 exp2 red1 red2) = 48 | errPrefix 49 | <> "test failed: " 50 | <> show exp1 51 | <> " = " 52 | <> show exp2 53 | <> "\n reduced to " 54 | <> show red1 55 | <> " = " 56 | <> show red2 57 | show (ImportError path) = errPrefix <> "invalid import " <> show path 58 | show (OptimizerError msg ) = errPrefix <> "optimizer failed: " <> msg 59 | 60 | type Failable = Either Error 61 | 62 | -- Modified from megaparsec's errorBundlePretty 63 | printBundle 64 | :: forall s e 65 | . (VisualStream s, TraversableStream s, ShowErrorComponent e) 66 | => ParseErrorBundle s e 67 | -> String 68 | printBundle ParseErrorBundle {..} = 69 | let (r, _) = foldl f (id, bundlePosState) bundleErrors in drop 1 (r "") 70 | where 71 | f :: (ShowS, PosState s) -> ParseError s e -> (ShowS, PosState s) 72 | f (o, !pst) e = (o . (outChunk ++), pst') 73 | where 74 | (msline, pst') = reachOffset (errorOffset e) pst 75 | epos = pstateSourcePos pst' 76 | outChunk = "\n\n" <> offendingLine <> init (parseErrorTextPretty e) 77 | offendingLine = case msline of 78 | Nothing -> "" 79 | Just sline -> 80 | let pointer = "^" 81 | rpadding = replicate rpshift ' ' 82 | rpshift = unPos (sourceColumn epos) - 2 83 | lineNumber = (show . unPos . sourceLine) epos 84 | padding = replicate (length lineNumber + 1) ' ' 85 | in padding 86 | <> "|\n" 87 | <> " | " 88 | <> sline 89 | <> "\n" 90 | <> padding 91 | <> "| " 92 | <> rpadding 93 | <> pointer 94 | <> "\n" 95 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Iosevka"; 3 | src: url("res/iosevka.woff2") format("woff2"); 4 | } 5 | 6 | body { 7 | font-family: "Iosevka", monospace; 8 | background-color: #222222; 9 | color: #cccccc; 10 | font-size: 1.3em; 11 | padding: 0; 12 | margin: 0; 13 | } 14 | 15 | a { 16 | color: #cccccc; 17 | } 18 | 19 | .header { 20 | display: flex; 21 | align-items: center; 22 | flex-direction: column; 23 | } 24 | 25 | .header img { 26 | width: auto; 27 | max-height: 20vh; 28 | } 29 | 30 | .header h1 { 31 | margin-top: -0.5em; 32 | font-size: 3em; 33 | } 34 | 35 | .example { 36 | display: flex; 37 | flex-flow: row wrap; 38 | align-items: center; 39 | justify-content: center; 40 | margin: 0 auto; 41 | width: 50%; 42 | } 43 | 44 | .example > * { 45 | display: flex; 46 | flex-basis: 50%; 47 | justify-content: center; 48 | } 49 | 50 | .example .code { 51 | width: fit-content; 52 | } 53 | 54 | .example p { 55 | font-size: 1.2em; 56 | max-width: 80%; 57 | text-align: center; 58 | } 59 | 60 | .instructions { 61 | max-width: 70%; 62 | margin: 0 auto; 63 | } 64 | 65 | @media(max-width: 768px) { 66 | body { 67 | font-size: 1.1em; 68 | } 69 | } 70 | 71 | /* mobile */ 72 | @media(max-width: 768px) { 73 | .example > * { 74 | flex-basis: 100%; 75 | } 76 | 77 | .example * { 78 | max-width: 90% !important; 79 | } 80 | 81 | .example { 82 | font-size: 1.3em; 83 | width: 100% 84 | } 85 | 86 | .instructions { 87 | max-width: 90%; 88 | font-size: 1.1em; 89 | } 90 | 91 | .instructions a { 92 | line-height: 1.5em; 93 | } 94 | 95 | .example :nth-child(8) { order: 7; } 96 | .example :nth-child(7) { order: 8; } 97 | .example :nth-child(6) { order: 6; } 98 | .example :nth-child(5) { order: 5; } 99 | .example :nth-child(4) { order: 3; } 100 | .example :nth-child(3) { order: 4; } 101 | .example :nth-child(2) { order: 2; } 102 | .example :nth-child(1) { order: 1; } 103 | } 104 | 105 | /* small desktop / tablet */ 106 | @media(max-width: 1700px) { 107 | .example { 108 | width: 80%; 109 | } 110 | 111 | .example .code { 112 | font-size: 1em; 113 | } 114 | } 115 | 116 | @media(max-width: 1000px) { 117 | .example { 118 | width: 100%; 119 | } 120 | } 121 | 122 | .bar { 123 | text-align: center; 124 | background-color: #333333; 125 | margin: 30px 0; 126 | } 127 | 128 | .bar.small { 129 | font-size: 1.2em; 130 | line-height: 1.2em; 131 | padding: 15px; 132 | } 133 | 134 | .bar.big { 135 | font-size: 2em; 136 | line-height: 5em; 137 | padding: 20px; 138 | } 139 | 140 | .bar a { 141 | color: #cccccc; 142 | } 143 | 144 | .popup { 145 | position: absolute; 146 | display: block; 147 | border-sizing: border-box; 148 | box-shadow: 0 10px 50px rgba(0,0,0,.6); 149 | background-color: #333333; 150 | border-radius: 10px; 151 | padding: 15px; 152 | max-width: 300px; 153 | } 154 | 155 | .code { 156 | background-color: #333333; 157 | padding: 15px; 158 | font-size: 1.2em; 159 | border-radius: 10px; 160 | overflow: auto; 161 | } 162 | 163 | .code .repl { 164 | color: #13dbee; 165 | } 166 | 167 | .code .def { 168 | color: #13dbee; 169 | } 170 | 171 | .code .left-abs, .code .right-abs { 172 | color: #6b82ff; 173 | } 174 | 175 | .code .left-app, .code .right-app { 176 | color: #ff8750; 177 | } 178 | 179 | .code .com { 180 | color: #ff64bd; 181 | } 182 | 183 | .code .ternary, .code .binary, .code .unary { 184 | color: #b1ee13; 185 | } 186 | 187 | .code .char, .code .string { 188 | color: #b1ee13; 189 | } 190 | 191 | .code .mixfix, .code .prefix { 192 | color: #eee513; 193 | } 194 | 195 | .code .symbol { 196 | color: #f9f9f9; 197 | } 198 | 199 | .code .index { 200 | color: #ff5050; 201 | } 202 | 203 | .code .meta { 204 | color: #ff94ff; 205 | } 206 | -------------------------------------------------------------------------------- /docs/wiki_src/coding/examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | You can find a lot more examples on [Rosetta 4 | code](https://rosettacode.org/wiki/Category:Bruijn) or on the dedicated 5 | interactive [samples](/samples/) page. 6 | 7 | ## Hello world! 8 | 9 | Hello world using [lists](../coding/data-structures.md#lists-stdlist) 10 | and [IO](../coding/IO.md)! 11 | 12 | ``` bruijn 13 | :import std/List . 14 | 15 | main ["Hello " ++ 0 ++ "!\n"] 16 | ``` 17 | 18 | ``` bash 19 | $ printf "world" | bruijn file.bruijn 20 | Hello world! 21 | ``` 22 | 23 | ## Syntax 24 | 25 | Example functions demonstrating the syntax without usage of 26 | [`std/`](/std/). 27 | 28 | ``` bruijn 29 | # this is a comment 30 | # returns ternary 1 (syntactic sugar) 31 | get-one (+1) 32 | 33 | # we can use the function in all functions below its definition 34 | get-one2 get-one 35 | 36 | # tests are similar to assertions in other languages 37 | # they test equality using α-equivalence of reduced expressions 38 | :test (get-one2) ((+1)) 39 | 40 | # indenting acts similarly to Haskell's where statement 41 | get-one3 foo 42 | bar (+1) 43 | foo bar 44 | 45 | # equivalent of λx.x or Haskell's id x = x 46 | id [0] 47 | 48 | # testing equivalent of (λx.x) (λx.λy.x) = λx.λy.x 49 | # the numbers in the abstractions refer to arguments using 50 | # De Bruijn indices 51 | :test (id [[1]]) ([[1]]) 52 | 53 | # prefix function definition 54 | !‣ [[1]] 55 | 56 | # use prefix function '!' 57 | # ![0] becomes ([[1]] [0]) which in turn becomes [[0]] 58 | :test (![0]) ([[0]]) 59 | 60 | # infix function definition: flip and apply arguments 61 | …<>… [[0 1]] 62 | 63 | # use infix function '<>' 64 | # [[0]] <> [[1]] becomes (([[0 1]] [[0]]) [[1]]) 65 | :test ([[0]] <> [[1]]) ([[1]] [[0]]) 66 | 67 | # multiple arguments 68 | number-set set-of-three (+1) (+2) (+3) 69 | set-of-three [[[[0 1 2 3]]]] 70 | 71 | access-first [0 [[[0]]]] 72 | 73 | :test (access-first number-set) ((+1)) 74 | 75 | # ignore stdin and return string 76 | main ["Hello world!\n"] 77 | ``` 78 | 79 | ## Standard library 80 | 81 | ``` bruijn 82 | :import std/Combinator . 83 | :import std/List . 84 | :import std/Logic . 85 | :import std/Number . 86 | :import std/Option . 87 | :import std/Pair . 88 | 89 | # pairs with some values 90 | love pair me you 91 | me [[[1]]] 92 | you [[[2]]] 93 | 94 | :test (fst love) ([[[1]]]) 95 | :test (snd love) ([[[2]]]) 96 | 97 | # you can also write (me : you) instead of (pair me you) 98 | # also ^love and ~love instead of (fst love) and (snd love) 99 | 100 | # numerical operations 101 | # remember that every mixfix chain is left-associative 102 | five --((+8) + (-4) - (-2)) 103 | 104 | not-five? [if (0 =? (+5)) false true] 105 | 106 | # awesome mixfix functions 107 | :test (∑ (+1) → (+3) | [++0]) ((+9)) 108 | :test (∏ (+1) → (+3) | [++0]) ((+24)) 109 | 110 | :test (not-five? five) (false) 111 | 112 | :test ((uncurry mul (pair (+3) (+2))) =? (+6)) (true) 113 | 114 | # lazy evaluation using infinite lists and indexing 115 | pow2 …!!… (iterate (…⋅… (+2)) (+1)) 116 | 117 | :test ((pow2 (+5)) =? (+32)) (true) 118 | 119 | # options 120 | :test (map inc (some (+1))) (some (+2)) 121 | :test (apply (some (+1)) [some ++0]) (some (+2)) 122 | 123 | # boolean 124 | # the main function gets executed automatically 125 | # ignore stdin arguments by not referencing 0 126 | main [¬(false ⋀? true ⋁? true)] 127 | 128 | :test (main [0]) (false) 129 | ``` 130 | 131 | ## More examples 132 | 133 | You can find more example programs in 134 | [`samples/`](https://github.com/marvinborner/bruijn/tree/main/samples) 135 | of our source-code repository. The samples include several solutions to 136 | [Advent of Code](https://adventofcode.com/) problems. 137 | 138 | Reading the source of the [standard library](/std/) can also be helpful. 139 | -------------------------------------------------------------------------------- /docs/wiki_src/coding/meta-programming.md: -------------------------------------------------------------------------------- 1 | # Metaprogramming 2 | 3 | Bruijn has a homoiconic meta encoding inspired by Lisp's quoting 4 | feature. 5 | 6 | Blog post with more details: [Metaprogramming and 7 | self-interpretation](https://text.marvinborner.de/2023-09-03-21.html). 8 | 9 | ## Encoding 10 | 11 | `X ⤳ [[[2 (+Xu)]]] 12 | `(M N) ⤳ [[[1 `M `N]]] 13 | `[M] ⤳ [[[0 `M]]] 14 | 15 | Any quoted term gets converted to this encoding: 16 | 17 | ``` bruijn 18 | # example quotations 19 | :test (`0) ([[[2 (+0u)]]]) 20 | :test (`[0]) ([[[0 [[[2 (+0u)]]]]]]) 21 | :test (`'0') ([[[0 [[[0 [[[0 [[[1 [[[2 (+0u)]]] [[[1 [[[2 (+0u)]]] [[[1 [[[2 (+0u)]]] [[[1 [[[2 (+0u)]]] [[[1 [[[2 (+1u)]]] [[[1 [[[2 (+1u)]]] [[[1 [[[2 (+0u)]]] [[[1 [[[2 (+0u)]]] [[[2 (+2u)]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]) 22 | 23 | # quotes are nestable! 24 | :test (``0) ([[[0 [[[0 [[[0 [[[1 [[[2 (+2u)]]] [[[0 [[[0 [[[2 (+0u)]]]]]]]]]]]]]]]]]]]]]) 25 | :test (`[0 `0]) ([[[0 [[[1 [[[2 (+0u)]]] [[[0 [[[0 [[[0 [[[1 [[[2 (+2u)]]] [[[0 [[[0 [[[2 (+0u)]]]]]]]]]]]]]]]]]]]]]]]]]]]) 26 | ``` 27 | 28 | ## Quasiquotation 29 | 30 | Quoted terms can be escaped (*unquoted*) using the comma symbol. 31 | Unquoted terms will be fully evaluated first before getting quoted 32 | again. 33 | 34 | ``` bruijn 35 | :test (```,[0]) (``[0]) 36 | :test (`,`,[0]) ([0]) 37 | :test (`[0 `,[0]]) (`[0 [0]]) 38 | ``` 39 | 40 | Unquoted de Bruijn indices will get bound to the respective abstraction 41 | outside of its meta encoding. 42 | 43 | ``` bruijn 44 | # adds two using normal quotation 45 | add-two `[0 + (+2u)] 46 | 47 | :test (!add-two (+2u)) ((+4u)) 48 | 49 | # adds two using a reaching de Bruijn index 50 | add-two* [`(,0 + (+2u))] 51 | 52 | :test (!(add-two* `(+2u))) ((+4u)) 53 | ``` 54 | 55 | ## Self-interpretation 56 | 57 | Using a metacircular self-interpreter, bruijn can reduce the meta 58 | encoding to its normal form. A 194 bit interpreter in the form of 59 | bruijn's logo: 60 | 61 | ``` code-showcase 62 | 01010001 00011100 63 | 11010000 ###### 11100110 64 | 10000 ############ 00001 65 | 01011 ##### ##### 00001 66 | 11100 #### #### 00101 67 | 01110 #### ##### 00011 68 | 00000 #### ###### 10100 69 | 00011 #### ### #### 00111 70 | 10000 #### ## #### 11111 71 | 00001 #### ### #### 11110 72 | 00010 ###### #### 11110 73 | 10011 ##### #### 10100 74 | 11110 #### #### 00011 75 | 11000 ##### ##### 00011 76 | 11000 ############ 01011 77 | 01101110 ###### 00011001 78 | 00011010 00011010 79 | ``` 80 | 81 | The code can also be found in the `eval`{.bruijn}/`!‣`{.bruijn} function 82 | of the meta library: 83 | 84 | ``` bruijn 85 | :test (!`"tacocat".reverse) ("tacocat") 86 | ``` 87 | 88 | ## Meta library [`std/Meta`](/std/Meta.bruijn.html) 89 | 90 | The meta library enables simple interaction with the meta encoding. 91 | 92 | Examples: 93 | 94 | ``` bruijn 95 | # testing equivalence 96 | :test (α-eq? `[0 0] `[0 0]) (true) 97 | :test (α-eq? `α-eq? `α-eq?) (true) 98 | 99 | # BLC length of meta term 100 | :test (length `[0]) ((+4u)) 101 | :test (length `[[1 1]]) ((+12u)) 102 | 103 | # self-modification 104 | :test (lhs `(1 0) `0) (`(0 0)) 105 | :test (rhs `(0 1) `0) (`(0 0)) 106 | :test (swap `(1 0)) (`(0 1)) 107 | :test (map inc `0) (`1) 108 | :test (map (map inc) `[0]) (`[1]) 109 | :test (map swap `[0 1]) (`[1 0]) 110 | 111 | # encoding terms as numbers 112 | :test ((encode `(0 0)) =? (+3)) (true) 113 | :test ((encode `[0]) =? (+8)) (true) 114 | 115 | # decoding numbers to terms 116 | :test (decode (+3)) (`(0 0)) 117 | :test (decode (+8)) (`[0]) 118 | ``` 119 | --------------------------------------------------------------------------------