Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/toptobes/typefuck-haskell
Typelevel Brainfuck; Brainfuck implemented solely in Haskell's type system (no TemplateHaskell)
https://github.com/toptobes/typefuck-haskell
brainfuck haskell typefuck
Last synced: about 6 hours ago
JSON representation
Typelevel Brainfuck; Brainfuck implemented solely in Haskell's type system (no TemplateHaskell)
- Host: GitHub
- URL: https://github.com/toptobes/typefuck-haskell
- Owner: toptobes
- License: mit
- Created: 2023-12-29T00:49:28.000Z (about 1 year ago)
- Default Branch: mistress
- Last Pushed: 2023-12-30T17:34:12.000Z (about 1 year ago)
- Last Synced: 2024-11-11T21:42:52.823Z (about 2 months ago)
- Topics: brainfuck, haskell, typefuck
- Language: Haskell
- Homepage:
- Size: 21.5 KB
- Stars: 17
- Watchers: 2
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Typefuck (Haskell edition)
(inspired by [susisu's Typescript-edition typefuck](https://github.com/susisu/typefuck))
This Typefuck also runs solely in Haskell's type system, though (at least I think, as I haven't
looked deeply into their code) it operates a bit differently than susisu's.## (Roughly) How it works
The state of the program is managed by three main types:
```hs
{- | An efficient way to move forwards and backwards through some list
(N.B. index == length preceeding) -}
type Cursor a =
( Nat -- ^ The index of the currently selected thing
, [a] -- ^ The preceeding things
, a -- ^ The thing the cursor is currently pointing at
, [a] -- ^ The succeeding things
){- | An association list to hold the index of each open bracket and its closing counterpart -}
type BracketLUT = [(Nat, Nat)]{- | The state of the brainfuck engine -}
type TFState =
( Cursor Char -- ^ The cursor for moving through the code
, Cursor Nat -- ^ The cursor for the memory tape
, Symbol -- ^ The input to the program
, Symbol -- ^ The output for the program
, BracketLUT -- ^ The aforementioned LUT for the []s
)
```and the rest is basically mutually-recursive type-family spam, which you can read
through as you please.The tape is (hack-ily) generated through an external haskell script which creates
the file which contains the tape (more on that later).I also had to manually implement versions of + and - that would overflow/underflow
to emulate ubyte-sized cells:```hs
type family (a :: Nat) -~ (b :: Nat) :: Nat where
a -~ b = UnderflowingSub' (CmpNat a b) a btype family UnderflowingSub' (ord :: Ordering) (a :: Nat) (b :: Nat) where
UnderflowingSub' 'LT a b = 256 - (b - a)
UnderflowingSub' _ a b = a - btype family (a :: Nat) +~ (b :: Nat) :: Nat where
a +~ b = Mod (a + b) 256
```## Using it for yourself
To run this, you need to have at the very least `cabal` installed, but it'd be in your
favor to also have `make` + `bash` (or a compatable shell) up and running. `node` or
(what I use) `bun` are also used for tests.### Setting up the tape
The tape should already be pregenerated with 10 0s; however, you can generate
a tape with the command `make tape TAPE=""` where `` is some
expression that generates a haskell list. Examples include
- `TAPE="[1, 2, 3]"`
- `TAPE="replicate 10 0"`
- `TAPE="[2 * x | x <- [1..10], mod x 2 == 0]"`and it generates a file like so:
```hs
module TF.Generated.Tape where
type Tape = '[0,0,0,0,0,0,0,0,0,0]
```If, for some reason, you can't use the command line utility, you can just manually generate
the list yourself (make sure you don't forget to use the `'`!)Disclaimer: you're free to generate long tapes, but if you go too long, you'd risk being overtaken
by the heat-death of the universe lol### Actually running Typefuck
My recommended way you use typefuck (if you're just type-fucking around) is to use `make repl` (`cabal repl app`),
`import TF.Core`, and `:k! RunTF "" ""`:
```hs
me@t:~/projects/typefuck-haskell$ make repl
<...>
λ> import TF.Core
λ> :k! RunTF ",." "a"
RunTF ",." "a" :: ghc-prim:GHC.Types.Symbol
= "a"
λ> :k! RunTF "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+." ""
<...>
= "Hello World!"
λ>
```You can also use `reifySF` from `Utils` to create a concrete string, but that takes
*much* longer to generate any non-trivial programs```hs
λ> symbolVal $ Proxy @(RunTF ",." "a")
"a"
λ> reifyTF @",." @"a"
"a"
```### "Open" vs "Closed" inputs
In the event that your input-requiring code isn't working when it should be (e.g. printing empty input), there's a
chance it's because you're using loops but your input isn't `NUL`-terminated (i.e. it doesn't read in a closing `'\0'`
input to stop the loop). In that case, try changing `"Hello!"` to `"Hello!\0"`.By default, when TF runs out of input, it just prints what input it has already (to emulate an interactive interpreter
just pausing, waiting for input).### Running the tests
You can run the tests with `make test` w/ bun/node (or manually executing `spec/run-tests.mjs`).
You can add your own tests to `spec/tests.json` as you please.## Disclaimer
I basically wrote this in a night or two so there may be some minor bugs and things that could be more efficient,
but I'm looking to fix them as I get time and motivation. Feel free to contribute!