The Programs of the Week of Tax Day
This Week’s Program: Apr 17 - Apr 21
Who else was blazing some green this week? I’m talking about paying taxes, of course!
It’s been a busy week, but I want to highlight just two commits.
8a15987f8c390b16c59c50db9a4a76dc3d9fa15f
You might recall from last week that one of the few remaining big hurdles in my GObject Introspection library was putting in bindings for GObjects. This commit represents a bunch of thinking I’ve done over the past several weeks about how I’d like to work with GObjects from within Racket.
The gi-object-quasiclass
method
uses quasiquoting to
construct a racket/class
expression. Comments
elided:
`(class object%
(init-field pointer
[base-info ,obj])
(super-new)
,@(for/list ([method (gi-object-methods obj)]
#:when (gi-callable-method? method))
(let ([args (map gi-base-sym (gi-callable-args method))])
`(define/public (,(gi-base-sym method)
,@args)
(,method pointer ,@args)))))
So what on earth is going on here? The gi-object-quasiclass
function
takes an obj
(an introspected GObject “class”). This quasiquoted
form is how to construct classes in Racket. The comma is
for unquote
, and that lets me dip in and out of the
quoted form. An introspected GObject has two fields at initialization:
A pointer to the object in C, and the base info that constructed
it. We iterate over the known methods of the GObject and for each one
declare a public method that will invoke the introspected method.
Then, in the gi-object->class
function, I eval
it, because
eval
is the apotheosis of programming.
gi-object->ctype
is where this gets really good. When I encounter a
C pointer to a GObject, I coerce it to an instance of the
eval
‘d classes:
(define (gi-object->ctype obj)
(let ([name (gi-base-name obj)]
[gobject% (gi-object->class obj)])
(_cpointer/null name _pointer
(curry dynamic-get-field 'pointer)
(lambda (ptr)
(if ptr
(new gobject% [pointer ptr])
#f)))))
Now, in my REPL I can start messing with GStreamer…
> (define src-factory (((introspection 'Gst) 'ElementFactory) 'find "fakesrc"))
> (object? src-factory)
#t
> (send src-factory get-metadata "long-name")
"Fake Source"
> (send src-factory get-metadata "description")
"Push empty (no data) buffers around"
> (send src-factory create)
; create method: arity mismatch;
; the expected number of arguments does not match the given number
; expected: 1
; given: 0
> (send src-factory create "mysrc")
(object ...)
Cool! In that example, I find an ElementFactory
with a factory name
(in this case a “fakesrc” factory), get some metadata about the
factory, and then use the factory to create an Element
of that
factory type. All of this using the idioms and data structures
provided by racket/class
. Hey, you got your OOP in my Scheme!
There’s still a bunch more to implement (like Signals), but I’ve got
just enough now to actually go about building up an API for GStreamer!
a819ea3d08766058a186f0e3b0947774c05ba446
Before jumping into GStreamer, I wanted to close out this week by tidying up my introspection library. One thing I did here was implement introspection for Array types, and I thought I found a neat trick for doing that.
(_array/vector _paramtype
(cond
[(positive? length) length]
[zero-term? (letrec ([deref (lambda (offset)
(if (ptr-ref ptr _paramtype offset)
(deref (add1 offset))
offset))])
(deref 0))]
[else 0]))
_array/vector
is a Racket FFI ctype that will take a C Array and
transform it into a Racket vector. It needs a type and a length. But
for some C arrays, you don’t know the length ahead of time. But you
might know that it’s terminated by a NULL
. This function will check
a passed in length parameter and if the length isn’t positive, it will
then look to see if the array is zero-term?
. If so, it
uses letrec
to set up a recursive binding. Starting at
zero, we recursively try to dereference a pointer at incrementing
offsets until we reach NULL. Once we do, that offset is the length of
the list! Tail-call optimization! Recursion! Scheming!
The result, using the same src-factory
from the above example:
> (send src-factory get-metadata-keys)
'#("long-name" "klass" "description" "author")
The C function, gst_element_factory_get_metadata_keys ()
returns a
gchar **
. When called from Racket, we get back a Vector of strings.
The remaining work in the ffi/unsafe/introspection
library surrounds
documentation. To me, documentation can mean a lot of things. There’s
formal documentation, like that written
in Scribble, Racket’s
documentation tool. That needs to be
written. There’s
unit tests. Those need to be
written. And then there are
Racket
Contracts. I need
to write those, too. Over the next couple of weeks, I plan on writing
and providing these different forms of documentation while also
exercising the library by building up tools for GStreamer.
There’s more to do on introspection
, but it feels like I reached
some kind of milestone this week.
💚 Mark