Idris to JavaScript: Playing with the FFI

Posted on January 21, 2013 by raichoo

Until now the FFI for the JavaScript backend for Idris is quite primitive and somehow I’d like to keep it that way since I don’t want to make invasive changes to the language just because of a different target. Therefore a started a little experiment to see how far one can get with the current FFI. In JavaScript can have fields of objects, methods, functions, arrays etc etc. and we need to cover all these cases. Let’s start with a little case study. We want to manipulate the DOM. Here is a little HTML snippet to begin with:

$snippet(“2013-01-21-playing-snippet1.html”)

Now we want to replace the text of the Node with the id test

module Main

data HTMLElement : Type where
  Elem : Ptr -> HTMLElement

data NodeList : Type where
  Nodes : Ptr -> NodeList

query : String -> IO NodeList
query q = do
  e <- mkForeign (FFun "document.querySelectorAll" [FString] FPtr) q
  return (Nodes e)

item : NodeList -> Int -> IO HTMLElement
item (Nodes p) i = do
  i <- mkForeign (FFun ".item" [FPtr, FInt] FPtr) p i
  return (Elem i)

getId : HTMLElement -> IO String
getId (Elem p) = mkForeign (FFun ".id" [FPtr] FString) p

setText : HTMLElement -> String -> IO ()
setText (Elem p) s =
  mkForeign (FFun ".textContent=" [FPtr, FString] FUnit) p s

main : IO ()
main = do
  e <- query "#test"
  i <- item e 0
  s <- getId i
  setText i (s ++ ": SUPERFOO!!!")

In this example I’m using the Ptr type for raw JavaScript values and wrap them in ADTs. The interesting part is in the definition of the foreign functions. Functions starting with “.” are methods or fields, that means that the first argument of the function is handled as and object and the function call gets translated into a method or field name. Functions ending with “=” are turned into assignments

Another thing we have to consider is that sometimes we want to refer to a method with no arguments, therefore we have to distinguish them from reading a field. In our example we read the value of the field id. If we wanted to turn that into a method call we need to declare it like this:

mkForeign (FFun ".id" [FPtr, FUnit] FString) p ()

We simply add an argument of type FUnit to the argument list and apply (). Operations on arrays are declared like this:

mkForeign (FFun ".id[]" [FPtr, FInt] FString)
mkForeign (FFun ".id[]=" [FPtr, FInt, FString] FString)

The second argument is treated as an index Working with the FFI is still dangerous, I’m currently unaware of a different way to do this without changing Idris’ FFI which is something I don’t want to. Another thing I don’t want to do is making the FFI overly complex, it should be very low level and provide the basic building blocks for interacting with JavaScript. Anyway, patches are welcome ^^

comments powered by Disqus