-
Notifications
You must be signed in to change notification settings - Fork 0
FFI
In your novika
command, put ffi
as one of the runnables like so: novika ffi examples/sdl.nk
or novika ffi repl
.
-
u8
foruint8_t
orchar
(in the numeric sense). Represented by decimal in Novika. -
u16
foruint16_t
. Represented by decimal in Novika. -
u32
foruint32_t
orunsigned int
. Represented by decimal in Novika. -
u64
foruint64_t
orunsigned long
. Represented by decimal in Novika. -
i8
forint8_t
. Represented by decimal in Novika. -
i16
forint16_t
. Represented by decimal in Novika. -
i32
forint
. Represented by decimal in Novika. -
i64
forint64_t
orlong int
. Represented by decimal in Novika. -
f32
forfloat
. Represented by decimal in Novika. -
f64
fordouble
. Represented by decimal in Novika. -
char
forchar
, but represented as a single-character quote in Novika -
cstr
forchar*
, interpreted a null-termined C string. Represented by quote in Novika. -
pointer
foru64
pointer. Represented by decimal in Novika. -
nothing
forvoid
, can only be used as a function's return type -
&<name>
is a struct reference (equivalent tostruct <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 tostruct <name>
). Values of this type are represented by the corresponding struct view form -- an inline struct view form. -
?<name>
is an inline union (equivalent tounion <name>
). Values of this type are represented by the corresponding struct view form -- a union view form.
To make your .so
library visible to Novika on Linux:
- Put it in global (at user's home) or local (in current folder)
env/
or.novika/
, or - Put it in your distro's usual lookup path, e.g.
/usr/lib
. Novika should be able to pick it up, or - 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
.
'<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"
'<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
[
<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
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
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
.
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
TODO
TODO
TODO
Because I'm lazy and that'd be a bit unsafe.