Announcing: Little Haskell Compiler v0.1.0 pre-release - Try it online!

I am pleased to announce the (pre) release of v0.1.0 of the Little Haskell Compiler, or LHC for short.

The Little Haskell Compiler is a lightweight functional virtual machine based on Peter Landin’s SECD machine, which was was used to implement the homoiconic dialect of Pure Lisp. I am using it now to implement a homoiconic dialect of Haskell that has first-class access to low-level memory primitives, making it suitable for browser applications or embedded environments!

Check out the web demo, there are presets and tutorials to show you how it works! I suggest running the quickstart!

  • It is small
  • It can run in just a few kilobytes of memory (the web demo is hardcoded to 1 megabyte)
  • It compiles for native and for wasi-wasm
  • It compiles and runs code in my browser and on my phone
  • It is 100% compliant with the original SECD machine
  • It was designed to make compiling lambda calculus trivial, which it does
  • It can be easily extended with new opcodes, machine procedures, and language features
  • It is designed to be relocatable in memory by using relative addressing instead of pointers

In just over 2600 lines of C, it includes:

  • A full application lifecycle with logging and an event loop
  • An assembler
  • A pretty printer
  • A repl with commands
  • The virtual machine itself

LHC’s assembly syntax is 100% Haskell-compatible syntax (think of it as the SECD monad, which, coming soon…), and will eventually be extended upwards with more language features as I bootstrap things. Simulataneously, the implementation is currently written in pure C, and will be rewritten in 100% Haskell using the memalloc library when I bootstrap things far enough.

A link to the source code is at the bottom of the page (its just a single C file right now, behold my strange C style that everybody disagrees with), and is an AI Disclaimer (tl;dr 100% human, no autonomous AI agents were used)

–

Coming soon:

  • let and letrec syntax
  • DeBruiijn expression syntax
  • tuple syntax
  • Negative integers
  • Escapable character literals
  • EOF symbol
  • Constructor-mnemonics
  • Top-level environment accessor

–

Helpful resources in developing this:


Please report any discovered issues.

Known issues include:

  • Application timer is disabled (causes WASM crashes, no throttling until fixed)
  • WASM Command mode is broken
  • Larger programs can crash the stack on mobile browsers (if that happens, try clicking reset and run again, as the wasm runtime may allocate a larger stack after it crashes with OOM / stack limit a first time)
  • Logging uses recursive functions that can blow the stack
  • Logging dominates CPU time and memory, even when piped to /dev/nul
  • Excessive logging (Hello world can generate up to a half-megabyte of logs)
  • Textfields don’t resize properly on mobile
  • Autogc sometimes gets skipped (is not currently turned on though - use :gc to force garbage collection, or set auto_gc to 1 to run every frame
18 Likes

GitHub - lemmih/lhc: The LLVM LHC Haskell Optimization System · GitHub name conflicts

I don’t understand. Where do I enter the Haskell code?

I tried 1+2, but I could not get it to run.

It not a full Haskell compiler yet, so it only supports an extremely limited set of syntax, basically a glorified functional assembly but it is the same syntax as a top-level do in my SECD monad.

I will be writing a lot of the more familiar syntax constructs as the bootstrapping continues. There is no standard library yet, or even variable names so things like ‘print “foo”’ will not work just quite yet.

But soon I will bootstrap the lambda parser down to secd and this will look a lot more recognizably Haskelly.

It has to start somewhere though, and this assembly layer is a solid foundation. I said “prerelease” and “v0.1.0” for a reason - have patience, this is just the first milestone.

1 Like

Try ‘[ LDC, 1, LDC, 2, ADD ]’ - it is a haskell compatible assembly right now so it does not have infix operators yet.

1 Like

If I have to write assembly code, then it’s not a compiler.

2 Likes

seems more like bytecode than assembly to me. but this bytecode also happens to be a valid userland Haskell program too? iiuc

I did quite specifically say it was an assembler, current status. The project’s name is the Little Haskell Compiler, and I have built the assembler for my compiler, and I figured it might be fun to share because I far prefer writing code to debating nomenclature pedantry.

Assembly is accurate! This functional assembly language was designed in the era of tape machines and punch-cards. The machine is virtual now, and we emulate tape with RAM, so you could call it bytecode, 1) you could build a real machine to run it and 2) its not actually byte- code, more like (cons-)cell-code.

And yes, its a small but genuine subset of userland Haskell! Its because of overloaded lists and rebindable syntax, I can cajole a SECD monad into sharing the same syntax as the assembly. The <0,0> index syntax is the only outlier, which I can hand-wave away by saying “language extension” - and the only reason that that is needed is because constructor-mnemonics are still to-do, which will turn [ LDC, <0,0> ] into LDC 0 0 like a normal haskell constructor (and the same for LDE and LDF and SEL, the other opcodes with operands. Its not a constructor though you see, its a mnemonic, because data constructors havent been written as a feature yet.

2 Likes

I can’t really see where it specifically says that this is an assembler. But it doesn’t matter. You built an assembler and interpreter for the SECD machine. Cool!

4 Likes

This looks like a cool project but I don’t understand how this SECD interpreter will fit into a fully-fledged Haskell implementation.

  • Is this assembly an intermediate representation or is it the surface language? Or is it an intermediate representation that can somehow be used in the surface language via some special construct (the equivalent of asm in C)?
  • What does homoiconic mean in practice? Can Haskell even be homoiconic?
1 Like

Good questions about deep topics!

Shallow answer: My plan is for the SECD assembly to serve as a intermediate layer that you can choose to either run in a VM (optimizing for portability) or be compiled further to eg WASM, X86, ARM, RISCV (optimizing for speed).

Deep answer: It is both, since the SECD syntax is a subset of Haskell syntax it won’t need a special construct, just a SECD monad (for writing in Haskell) and / or a {-# LANGUAGE SECD #-} set of language extensions to turn off any unimplemented Haskell features (for writing Haskell itself)

A self-hosted language means that it is written and implemented in itself. GHC is for the most part a self-hosted language, with the huge glaring exception of its C runtime. If I were to take my memalloc library and rewrite all of the C in GHC to Haskell, it would make it closer to 100% self-hosted, but that would not make it homoiconic.

Homoiconic means that the language itself is a data structure that can be manipulated at runtime - the language itself is first class, and can be interacted with via macros or the equivalent. GHC discards type information when compiling, which makes it not homoiconic, and the result is that parsing a Haskell module at runtime requires embedding the entire GHC compiler into your executable, or writing your own DSL. TH helps fill the gap, but a homoiconic language has the equivalent of TH built-in (ie using the COPY evaluation strategy).

The difference is that self-hosting is usually good enough to get you an eval("[1,2,3]") function like Python or Javascript, but being homoiconic takes it further, and it means you don’t even need that because you can just say [1,2,3] directly.

Lisp’s homoiconicity comes from lists. If I treat Haskell’s do-syntax as a list-ish monad and the opcodes as statements, the SECD assembly naturally fits as a list monad, allowing me to directly pass around lists of opcodes as data and execute them as code - this makes sense because most assembly languages are completely represented as lists of instructions, and a good choice of assembly. My Core AST (not yet published) really isn’t that different from GHC.Core, I just have added cons-cells and opcodes to the mix - this wasn’t intentional, but it wasn’t accidental either, it was confluence of design.

Making Haskell homoiconic is a bit harder than eg to do it with Lisp but it is doable. I’ll have to deal with (being able to) keeping more type information around, and I’ll have to be able to simulate a heap with the cons-yard, just like how right now I’m simulating a cons-yard with a heap. If homoiconic lisp is a snake eating its own tail, homoiconic haskell is two snakes, yard and heap, linear-immutable and random-access-mutable, eating each other. This SECD machine is a first step is getting the C runtime small enough for the Haskell to swallow it completely.

1 Like

:heart: And it runs in a browser locally! It doesn’t pass off the source code to the server for compilation, it does all of the parsing and assembling itself. I am so excited to be able to say that.

If I want Haskell to eg be able to replace Javascript / Typescript, it has to be able to run in the browser end-to-end.

Thanks for your answers!

How far are you planning to go with homoiconicity? Is it limited to the SECD instructions, to Core (i.e. with dictionaries instead of type classes) or to the full surface language?

I think I can kind of mentally visualize homoiconicity up to Core. Full Haskell on the other hand seems rather ambitious, since it has features (type classes) that depend on elaboration. Which sounds like a nightmare to design, implement and use correctly! I’m curious to know how you approached this problem.

1 Like

Starting small is good for homoiconicity, because from there, you can always add more code (language features) to a homoiconic language as data, because that is what homoiconic means - code is data.

This is why making the SECD assembly share syntax with the SECD monad is so important - it closes the loop, and the SECD monad requires proper Haskell with typeclasses and such, meaning I must target full Haskell to some extent.

I have a plan though - my approach is that of a meta-compiler, and I will be creating language extensions for every single language feature - even things that you might normally take for granted. By turning the appropriate extensions on or off, I will target 4 ‘flavors’, pushing syntax sugar as far down as possible for convenience.

An example: DeBruijnLambda would enable using \ 2 -> $1 $0 syntax for the same function that with NamedLambda would be \ a f -> f a, both of which are equivalent to the SECD expression [ NIL, LDE, <0,0>, CONS, LDE, <0,1>, AP, RTN ].

A rough outline:

  • SECD assembly
    • opcodes
    • mnemonic constructors
    • address assignment
    • let
    • letrec
    • list syntax
    • tuple syntax
    • do-syntax
    • literal syntax
  • Debruijn expressions
    • scott constructors
    • variable assignment
    • modules and module-signatures and module-functors
  • Lambda expressions
    • aforementionedly close to GHC.Core
    • data constructors
    • types
    • type dictionaries
  • Haskell
    • all the goodies
    • type classes
    • extensions
    • etc

I’m sort of treating language features as ‘effects’ to make this easier, and you might recognize the Data Types a la Carte influence:

-- Fixed point
newtype Lang f = Lang (f (Lang f))

-- Functor.Sum / Coproduct
data SumF f g a = InL (f a) | InR (g a)
    deriving (Functor)

infixr 6 :+
type (f :+ g) = SumF f g

-- Functor.Prod / Product
data ProdF f g a = ProdF (f a) (g a)
    deriving (Functor)

infixr 7 :*
type (f :* g) = SumF f g

-- Subfunctor / Feature
class (Functor f, Functor g) => Subfunctor f g where
    lift :: f a -> g a

instance (Functor f) => Subfunctor f f where
    lift = id

instance {-# OVERLAPPING #-} (Functor f, Functor g) => Subfunctor f (SumF f g) where
  lift = InL

instance (Functor f, Functor g, Functor h, Subfunctor f g) => Subfunctor f (SumF h g) where
  lift = InR . lift

-- NOTE: Lang becomes embed when using recursion-schemes
liftLang :: (Subfunctor f lang) => f (Lang lang) -> Lang lang
liftLang = Lang . lift

This is a simplification, the real thing uses more type parameters and recursion schemes, but basically:

-- Language feature 'effect' base functors
data Var ast = Var String
    deriving (Functor)

var :: (Subfunctor Var f) => String -> Lang f
var x = liftLang $ Var x

data Lam ast = Lam String ast
    deriving (Functor)

lam :: (Subfunctor Lam f) => String -> Lang f -> Lang f
lam v e = liftLang $ Lam v e

data App ast = App ast ast
    deriving (Functor)

app :: (Subfunctor App f) => Lang f -> Lang f -> Lang f
app f e = liftLang $ App f e

data Lit ast = Lit Int
    deriving (Functor)

lit :: (Subfunctor Lit f) => Int -> Lang f
lit n = liftLang $ Lit n

data LetIn ast = LetIn String ast ast
    deriving (Functor)

letin :: Subfunctor LetIn f => String -> Lang f -> Lang f -> Lang f
letin v x y = liftLang $ LetIn v x y

Then I can define some ASTs that share some features:

type CombinatorCalculus = Var :+ App
type LambdaCalculus = Var :+ Lam :+ App
type RichLambdaCalculus = Var :+ Lam :+ App :+ Lit :+ LetIn

-- Lang LambdaCalculus
lambdaExample ::
    ( Subfunctor App f
    , Subfunctor Lam f
    , Subfunctor Var f
    ) => Lang f
lambdaExample = app (lam "x" (var "x")) (var "y")

-- Lang RichLambdaCalculus
richLambdaExample ::
    ( Subfunctor App f
    , Subfunctor Lam f
    , Subfunctor Var f
    , Subfunctor Lit f
    , Subfunctor LetIn f
    ) => Lang f
richLambdaExample = letin "x" (lit 5) (app (lam "y" (app (app (var "add") (var "x")) (var "y"))) (lit 3))

NOTE: I am working on a Features [Var, App, Lam, etc] ast convenience

And viola! This is what I’ll be using to describe my syntax, and as you can see, it needs full and proper Haskell - but it makes good use of it so it is worth the extra effort that will require.

The real thing is a bit more complicated of course, uses more type parameters, and will allow for you to eg attach parser and language extension instances to your language features and so on. Then I will use recursion schemes to turn compiler passes into nice neat base functor algorithms. I have lots of fun things planned!

4 Likes

Achieving liftoff!

I’ve been working hard on an update (not out yet) that includes 100% more Haskell. If the present C implementation is Haskell only in the most minimal sense, it’s because writing a complex parser in C is pretty hard. It’s a lot easier to write a parser in Haskell, and then parse the parser down to the assembly language. So I’m doing that.

I have written a little parser complete with support for offside blocks and continuation lines, that ramp things up a little and asks, what if we tried to add as much Haskell syntax as possible to the assembly language?

The resulting syntax supports modules, variables, integer / char / string literals, list and tuple syntax, if-then-else expressions, let-expressions, do-expressions with do-bind and do-let, and even top-level modules. I was even able to sneak whitespace function application in, even though functions / closures only take a single argument that must be a list! The syntax isn’t finalized quite yet, but it looks like this:

-- A simple module
module Hello.World where

-- Recursive printLn
printLn = if [ LDE 0 0, NULL ]
    then do
         LDC '\n'
         PUTC
         RTN
    else do
         LDE 0 0
         CAR
         PUTC
         xs <- [ LDE 0 0, CDR ]
         printLn [ xs ]

-- Module main
main =  let msg = "Hello, world!"
        in  println [ msg ]

I’m calling this little haskell-assembly dialect Landin for Peter Landin who invented the SECD machine - I figured it was a nice nod and easier to say than “SECD” all the time.

This is the AST that it gets parsed into. I’ve omitted the SECD opcodes for brevity, they just get parsed as vars for now. I’ve also omitted the actual parsers, they are mostly boring, and based off of parsec.

type LdnName = String

data LdnLit
    = LdnIntegerLit Integer
    | LdnCharLit Char
    | LdnStringLit String
    deriving (Show)

type LdnModlNameComponent = String
type LdnModlName = [LdnModlNameComponent]

type LdnDecl = (LdnName, Landin)

data LdnDoStmt
    = LdnDoStmt Landin
    | LdnDoBind LdnName Landin
    | LdnDoLet [LdnDecl]
    deriving (Show)

data Landin
    = LdnVar LdnName -- Opcodes
    | LdnLit LdnLit
    -- | LdnLam Name f
    | LdnApp Landin Landin
    | LdnList [Landin]
    | LdnTuple [Landin]
    | LdnIfte Landin Landin Landin
    | LdnLet [LdnDecl] Landin
    | LdnDo [LdnDoStmt]
    | LdnModl LdnModlName [LdnDecl] 
    | LdnScript [LdnDoStmt]  -- A headless module
    deriving (Show)

I’ll be integrating this with the fancy AST stuff from the previous comment in due time, but for now I hope you can see where I’m going.

If we run the source through the parser, we get this nice AST:

LdnModl
    [ "Hello"
    , "World"
    ]
    [
        ( "printLn"
        , LdnIfte
            ( LdnList
                [ LdnApp
                    ( LdnApp
                        ( LdnVar "LDE" )
                        ( LdnLit
                            ( LdnIntegerLit 0 )
                        )
                    )
                    ( LdnLit
                        ( LdnIntegerLit 0 )
                    )
                , LdnVar "NULL"
                ]
            )
            ( LdnDo
                [ LdnDoStmt
                    ( LdnApp
                        ( LdnVar "LDC" )
                        ( LdnLit
                            ( LdnCharLit '
                            ' )
                        )
                    )
                , LdnDoStmt
                    ( LdnVar "PUTC" )
                , LdnDoStmt
                    ( LdnVar "RTN" )
                ]
            )
            ( LdnDo
                [ LdnDoStmt
                    ( LdnApp
                        ( LdnApp
                            ( LdnVar "LDE" )
                            ( LdnLit
                                ( LdnIntegerLit 0 )
                            )
                        )
                        ( LdnLit
                            ( LdnIntegerLit 0 )
                        )
                    )
                , LdnDoStmt
                    ( LdnVar "CAR" )
                , LdnDoStmt
                    ( LdnVar "PUTC" )
                , LdnDoBind "xs"
                    ( LdnList
                        [ LdnApp
                            ( LdnApp
                                ( LdnVar "LDE" )
                                ( LdnLit
                                    ( LdnIntegerLit 0 )
                                )
                            )
                            ( LdnLit
                                ( LdnIntegerLit 0 )
                            )
                        , LdnVar "CDR"
                        ]
                    )
                , LdnDoStmt
                    ( LdnApp
                        ( LdnVar "printLn" )
                        ( LdnList
                            [ LdnVar "xs" ]
                        )
                    )
                ]
            )
        )
    ,
        ( "main"
        , LdnLet
            [
                ( "msg"
                , LdnLit
                    ( LdnStringLit "Hello, world!" )
                )
            ]
            ( LdnApp
                ( LdnVar "println" )
                ( LdnList
                    [ LdnVar "msg" ]
                )
            )
        )
    ]

I am quite pleased with the result, and the transformation from this Landin AST to pure SECD opcodes is rather straightforward and mechanical - a module is just a letrec in disguise, which gets converted to DUM / RAP, let is just LDF AP, if-then-else is just SEL, juxtaposition is AP, list and tuple literals are NILs and CONSes, and so on.

Tomorrow, I’ll be able to start on actually compiling this down to SECD. The main impediment to that is that the C VM only understands the minimalist Haskell syntax and not this new stuff, so I’ve got to come up with a simple VM image format that I can export from Haskell that doesn’t require a full parser to load. It’s not hard, just tedious - but once done, I can produce VM images using GHC, and run them in the little C VM. Then, I can start lowering the parser itself into Landin syntax, and get a VM image of the parser. Then the C VM will be able to create new images for itself. And so on!

However, it is well past midnight here, so I am calling it a night. I’ll be back at it tomorrow!

3 Likes

An update is almost here. In the meantime, I want to talk about atoms and the C FFI. Brace yourselves for some C - you can’t avoid it when talking about the C FFI!

Atoms are tagged unions allowing us to pass around any value that fits in a cons-cell.

typedef enum AtomType : uint8_t {
    ATOM_BIT,   // Boolean type
    ATOM_INT8
    // ...
} AtomType;

typedef union AtomData {
    bool bit;
    int8 int8;
    // ...
} AtomData;

typedef struct Atom {
    AtomType type;
    AtomData data;
} Atom;

#define FIELD_NAME(TYPE)    FIELD_ ## TYPE
#define ATOM_NAME(TYPE)     ATOM_ ## TYPE

#define FIELD_BIT   bit
#define FIELD_INT_8 int8

In order to make this compiler more flexible, I have made base data types a choice. An example is the ‘boolean’ data type, which might be backed by a single bit (C really hates me calling it a bool) or if you don’t have a bool, might store it as an int8

// Obviously, we should do this, if we can
#define ATOM_BOOLEAN    ATOM_BIT
#define FIELD_BOOLEAN   FIELD_BIT
#define BOOLEAN_FALSE   false
#define BOOLEAN_TRUE    true
// But if bool was unavailable (WASM32, RISCV) we could do this:
#define ATOM_BOOLEAN    ATOM_INT8
#define FIELD_BOOLEAN   FIELD_INT8
#define BOOLEAN_FALSE   0
#define BOOLEAN_TRUE    1

This was useful for swapping out implementations, but then I realized that I can actually use it to make a C FFI:

#define UNPACK_ATOM(TYPE,ATOM)  ((ATOM).data.FIELD_NAME(TYPE))
#define PACK_ATOM(TYPE,VAL)     atom(ATOM_NAME(TYPE), (AtomData){ .FIELD_NAME(TYPE) = (VAL) })

#define UNPACK_BIT(ATOM)        UNPACK_ATOM(BIT, ATOM)
#define PACK_BIT(VAL)           PACK_ATOM(BIT, VAL)

#define UNPACK_INT8(ATOM)       UNPACK_ATOM(INT8, ATOM)
#define PACK_INT8(VAL)          PACK_ATOM(INT8, VAL)

The PACK_ functions are probably useful just for creating Atom values, but where this really shine is when I define a small wrapper to pop arguments from a stack, and push the result:

#define C_FFI_WRAP_1(RET_TYPE, FN_NAME, ARG0_TYPE) \
void c_ffi_##FN_NAME(Machine * m) { \
    Atom arg0 = cell_atom(yard_peek_pop(m->yard, &(m->reg.stack))); \
    Atom out = PACK_ATOM(RET_TYPE, FN_NAME(UNPACK_ATOM(ARG0_TYPE, arg0))); \
    YardAddr out_addr = yard_atom(m->yard, out); \
    yard_push(m->yard, &(m->reg.stack), out_addr); \
}

Then, given a function

bool complement(bool b) {
    return !b;
}

// generates void c_ffi_complement(Machine * m)
// Assume an FFI index of 0
C_FFI_WRAP_1(BIT, complement, BIT)

We could add a CFFI instruction taking an FFI index:

// State before
// S: [true]
// C: [CFFI, 0], triggers the FFI call
c_ffi_complement(m);
// State after
// S: [false]

And because of the type and field magic, we can use the same function on an int8 fn(int8):

int8 increment(int8 a) {
    return a + 1;
}

// generates void c_ffi_increment(Machine * m)
// Assume an FFI index of 1
C_FFI_WRAP_1(INT8, increment, INT8)

// State before
// S: [3]
// C: [CFFI, 1], triggers the FFI call
c_ffi_increment(m);
// State after
// S: [4]

This means I’ll need to add syntax support to the Landin / Haskell parser for foreign imports, of course, to automatically generate linker code - you know, these statements:

foreign import capi "botan/ffi.h f" some_fn :: CInt -> IO CInt

This would kick off compiler directives to patch in a C_FFI_WRAP_N with the appropriate types, and link it to the generated FFI index so it can be called via the CFFI opcode.

Oh yes, of course the first library I want to bind to is botan - I’ve been planning this for some time! This compiler will support code signing, and memalloc and botan both have big parts to play!

1 Like

VM images and the imminent update

I have been quiet, and anyone that knows me knows that means I have been busy working, because I can’t talk and work at the same time. So, a short update while I get things sorted.

It is a small success as measured by program complexity, but I have just run the first program compiled on the Haskell side, and run on the C side, to test the new VM image format. The actual program is irrelevant - at 176 bytes, it was enough to describe the machine state and a program capable of producing a ‘true’ value - but this will allow me to use Haskell, to compile my Haskell parser-compiler, written in Haskell, down to a VM image. This way, instead of writing a Haskell parser-compiler in C, I can just load the resulting VM image, and use it to parse more source code, and generate more VM images, which I can then run. I really didn’t want to spend any more time writing a parser in C - it was bad enough just getting the basic list syntax of the v0.1.0 assembler working.

Thus, my bootstrapping loop continues to tighten. Hopefully, I am rapidly approaching the point at which I no longer need to write any C, because it is both wonderful and tedious - I love dealing with raw memory even as I miss my higher-level concepts; it just makes me want to fuse them together all the more, abstracting memory.

The next step after loading VM images, is the C FFI. Specifically, I can start subsuming any C memory manipulation, and also I can start binding to foreign libraries. One target is especially prominent in my mind - botan. I need cryptography for code signing, because now that I can produce itty bitty VM images, it would be nice for people to be able to run them without worrying about where they came from. This is actually a priority goal for the compiler - first class security, built in from the beginning.

So, to tide you over until I finish tidying up the actual git repo and makefile and stuff, have my current hacked-together foreign import parser’s test output:

ghci> import LHC.Parser.Landin
ghci> parseDebug (ldnLexer (pure ()) >> ldnForeignImport) "" "-- | Get the name of this hash function\nforeign import capi safe \"botan/ffi.h botan_hash_name\"\n    botan_hash_name\n        :: BotanHash    -- ^ __hash__: the object to read\n        -> Ptr CChar    -- ^ __name__: output buffer\n        -> Ptr CSize    -- ^ __name_len__: on input, the length of buffer, on success the number of bytes written\n        -> IO CInt"
Parsing: "-- | Get the name of this hash function\nforeign import capi safe \"botan/ffi.h botan_hash_name\"\n    botan_hash_name\n        :: BotanHash    -- ^ __hash__: the object to read\n        -> Ptr CChar    -- ^ __name__: output buffer\n        -> Ptr CSize    -- ^ __name_len__: on input, the length of buffer, on success the number of bytes written\n        -> IO CInt"
Result: (CApiCall,Safe,"botan/ffi.h",False,False,LdnVar "botan_hash_name",LdnVar "botan_hash_name",[["BotanHash"],["Ptr","CChar"],["Ptr","CSize"],["IO","CInt"]])
Done parsing.

Yes, why that is in fact parsing a raw chunk of the botan FFI :slight_smile: oh how I’ve missed you - we’ll be needing that later!

3 Likes