Skip to content
Alexey Yurchenko edited this page Jan 7, 2023 · 3 revisions

How to enable FFI?

In your novika command, put ffi as one of the runnables like so: novika ffi examples/sdl.nk or novika ffi repl.

What types are available?

  • u8 for uint8_t or char (in the numeric sense). Represented by decimal in Novika.
  • u16 for uint16_t. Represented by decimal in Novika.
  • u32 for uint32_t or unsigned int. Represented by decimal in Novika.
  • u64 for uint64_t or unsigned long. Represented by decimal in Novika.
  • i8 for int8_t. Represented by decimal in Novika.
  • i16 for int16_t. Represented by decimal in Novika.
  • i32 for int. Represented by decimal in Novika.
  • i64 for int64_t or long int. Represented by decimal in Novika.
  • f32 for float. Represented by decimal in Novika.
  • f64 for double. Represented by decimal in Novika.
  • char for char, but represented as a single-character quote in Novika
  • cstr for char*, interpreted a null-termined C string. Represented by quote in Novika.
  • pointer for u64 pointer. Represented by decimal in Novika.
  • nothing for void, can only be used as a function's return type
  • &<name> is a struct reference (equivalent to struct <name>*) where name is the name of an entry holding a struct layout form (lookup-able in the caller block). Values of this type are represented by the corresponding struct view form -- a struct reference view form.
  • ~<name> is an inline struct (equivalent to struct <name>). Values of this type are represented by the corresponding struct view form -- an inline struct view form.
  • ?<name> is an inline union (equivalent to union <name>). Values of this type are represented by the corresponding struct view form -- a union view form.

How to make my library visible to Novika?

To make your .so library visible to Novika on Linux:

  1. Put it in global (at user's home) or local (in current folder) env/ or .novika/, or
  2. Put it in your distro's usual lookup path, e.g. /usr/lib. Novika should be able to pick it up, or
  3. Provide it as a runnable: novika ffi ... /path/to/shared/library.so ...

On Windows, there are no default lookup paths. (1.) and (3.) still apply. The extension must be .dll.

On MacOS, I just don't know, but (1.) and (3.) should apply too. The extension must be .dylib.

How to get a library handle inside Novika?

'<id-of-your-library>' ffi:getLibrary @: <some-name>

Library identifier is crafted by removing the lib prefix (if any), and removing the extension. The identifier is case-sensitive.

'SDL2' ffi:getLibrary @: _sdl "Will look for libSDL2.so/dll/dylib and SDL2.so/dll/dylib"
'crypto' ffi:getLibrary @: _crypto "Will look for libcrypto.so/dll/dylib and crypto.so/dll/dylib"

How to declare a function?

'<id-of-your-library>' ffi:getLibrary @: <some-name>

[
  [ <name-of-function> <0+ argument types> -- <return type> ]
  [ <name-of-function> <0+ argument types> -- <return type> ]
  [ [ <name-of-function> <alias-in-novika> ] <0+ argument types> -- <return type> ]
  [ <name-of-variadic-function> <0+ argument types> [ <1+ allowed vararg types> ] -- <return type> ]
] <some-name>

Real-world example:

'sdl' ffi:getLibrary @: _sdl

[
  [ [ SDL_Init sdl:init ] u32 -- i32 ]
  [ [ SDL_GetError sdl:error ] -- cstr ]
  [ [ SDL_CreateWindow sdl:createWindow ] cstr i32 i32 i32 i32 u32 -- pointer ]
  [ [ SDL_CreateRenderer sdl:createRenderer ] pointer i32 u32 -- pointer ]
  [ [ SDL_RenderClear sdl:renderClear ] pointer -- i32 ]
  [ [ SDL_SetRenderDrawColor sdl:setRenderDrawColor ] pointer u8 u8 u8 u8 -- i32 ]
  [ [ SDL_RenderFillRect sdl:fillRect ] pointer &sdl:rect -- i32 ]
  [ [ SDL_RenderPresent sdl:renderPresent ] pointer -- nothing ]
  [ [ SDL_PollEvent sdl:pollEvent ] pointer -- i32 ]
  [ [ SDL_WaitEvent sdl:waitEvent ] pointer -- i32 ]
  [ [ SDL_Quit sdl:quit ] -- nothing ]
] _sdl

How to declare a struct layout?

[
  <field name> <field type>
  <field name> <field type>
  <...>
] ffi:createLayout $: <some-name>

Self-references and forward references are allowed:

[
  self-referencing-field     &a "!!! But not ~a or ?a"
  forward-referencing-field  &b "Could also be ~b or ?b"
] ffi:createLayout $: a

[
  above-referencing-field  &a
] ffi:createLayout $: b

Real-world example:

[
  flags u32
  format &sdl:pixelFormat
  w i32
  h i32
  pitch i32
  pixels pointer
  userdata pointer
  locked i32
  list_blitmap pointer
  "Note how we use `pointer` instead of struct type here. We can do
   that when we don't want to define the layout for struct."
  clip_rect pointer
  map pointer
  refcount i32
] ffi:createLayout $: sdl:surface

How to allocate/build/cast to a struct?

To allocate a struct, use the corresponding allocate method: ffi:allocateStruct~ to allocate an inline struct given a struct layout, and ffi:allocateStruct& to allocate a struct reference given a struct layout.

[ x i32 y i32 ] ffi:createLayout $: point

point ffi:allocateStruct~ $: point~
point ffi:allocateStruct& $: point&

You can then pass it to a function that accepts ~ or & struct, correspondingly.

To allocate and fill out the struct from inside Novika, use ffi:buildStruct~ and ffi:buildStruct&. They both take an entry block followed by struct layout. If you're sure, you can use this as the entry block. If you're not, you can use a custom block with obj. You can use any block, really, provided it can answer the definitions for all of the struct layout's fields (in our point example, that'd be x and y). Block friends and block inheritance work. No traps, unfortunately, but that'd be fixed soon, hopefully!

[ x i32 y i32 ] ffi:createLayout $: point

[
  123 $: x
  456 $: y
] obj point ffi:buildStruct& "or ffi:buildStruct~"

Using this:

[ x i32 y i32 ] ffi:createLayout $: point

123 $: x
456 $: y
this point ffi:buildStruct& "or ffi:buildStruct~"

To cast an address to a struct type, use ffi:asStruct& and ffi:asStruct~.

[ x i32 y i32 ] ffi:createLayout $: point

'myCoolLib' ffi:getLibrary @: myCoolLib

[
  [ FuncThatReturnsPoint* -- pointer ]
  [ FuncThatReturnsPoint -- pointer ]
] myCoolLib

FuncThatReturnsPoint* point ffi:asStruct& $: p1
FuncThatReturnsPoint  point ffi:asStruct~ $: p2

How to allocate/build/cast to a union?

You can allocate a union with ffi:allocateUnion. The usage of the word is the same as that of ffi:allocateStruct~ and ffi:allocateStruct&. Real-world example:

...

sdl:event ffi:allocateUnion $: e

while: [ e ffi:addressof sdl:waitEvent 0 > ] [
  e.type SDL_QUIT_EVENT = => break
  e.type SDL_MOUSE_MOTION_EVENT = => [
    e.motion.x =: mouseX
 
...

To build a union, use ffi:buildUnion. It works like the ffi:buildStruct variants, but needs only one of the fields in the struct layout defined in the entry block. If more are defined, the first entry that is found is used to set the corresponding field.

[ chr char
  ord u8
] ffi:createLayout $: quux

[ 'A' $: chr
  this quux ffi:buildUnion
] val $: unionByChr

[ 66 $: ord
  this quux ffi:buildUnion
] val $: unionByOrd

[ 'A' $: chr
  123 $: ord
  this quux ffi:buildUnion
] val $: unionBoth

unionByChr.ord leaves: 65
unionByOrd.chr leaves: 'B'

"'chr' is defined first, therefore, it is used rather
 than 'ord'"
unionBoth.chr leaves: 'A'
unionBoth.ord leaves: 65

To cast a pointer to a union, use ffi:asUnion.

How to set/get/update field in an existing struct/union?

Structs and unions are readable and submittable stores, meaning you can use entry:fetch, entry:fetch?, entry:exists?, entry:isOpenEntry? (always false for unions and structs) to get struct fields, and entry:submit to set/update them. You cannot add new fields to existing structs.

A lot of words are implemented using the above entry: words, therefore, for example, you can use . and -> on structs.

[ x i32 y i32 ] ffi:createLayout $: point

123 $: x
456 $: y
this point ffi:buildStruct& $: pt

pt.x leaves: 123
pt.y leaves: 456

pt -> [ x y ] leaves: [ [ 123 456 ] ]
"... etc"

pt #x 1000 entry:submit
pt.x leaves: 1000

What is ffi:hole?

TODO

What are ffi:box and ffi:unbox?

TODO

Difference between ffi:hole and ffi:box

TODO

Why is serializing FFI forms impossible?

Because I'm lazy and that'd be a bit unsafe.