guile-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Guile C preprocessor


From: Rob Browning
Subject: Re: Guile C preprocessor
Date: 19 Dec 2000 20:56:56 -0600
User-agent: Gnus/5.0807 (Gnus v5.8.7) Emacs/20.7

Neil Jerram <address@hidden> writes:

> Good point.  But perhaps these conditions apply to a minority of
> cases, and we could still use something like Keisuke's idea to
> autogenerate code for arguments that _do_ always have to be of a
> specific type.  For arguments where your considerations apply, a
> type name like `mixed' or `nonautogen' could be used to indicate
> that the body of the function will take care of the type checking
> for that argument.

(This is long, and might be interesting to some, but many will want to
 skip it ... I'm sure.  I'll also state right up front that g-wrap is
 to some extent an experimental work.  It seems useful, but it's
 certainly not elegant, at least not yet, and maybe not ever, and it's
 hard for me to say whether that's a statement about the problem, or
 the current solution.)

For what it's worth, the latest g-wrap can do much of what you're
talking about, though as pointed out, there will still be cases where
you'll want to handle arg type checking, etc. by hand for complexity
or performance issues, and if you were to try to use g-wrap for
something like what you're discussing, (a) g-wrap would probably need
a little augmentation, and (b) you'd be putting all your C code inside
scheme strings, or you'd be having the wrapper code separate from the
"body function" -- either might be too ugly to be feasible.

Right now G-wrap can, given a appropriate scheme code, generate
wrapper code for C code (whether it be in-line C code or a call to a
given C function).

In consultation with others, I have re-worked g-wrap to have a much
more generic underlying infrastructure.  You have very elaborate
control over what code gets generated in the wrapper for arguments and
return values (both initialization and cleanup), for global
initialization, and for global declarations.  It'll also spit out a
fully functional, dynamically-loadable module if you like (so
technically, (use-modules (g-wrapped ncurses)) is possible now, and
(with a minor tweak to re-enable some code) it will generate html docs
for the wrapped functions.  On top of that underlying infrastructure
we've built more specialized types to handle "non-native objects"
(types with no scheme-side representation -- described below) and
enumerations which, among other things, automatically get the right
C-side values at runtime.  Further, g-wrap now has the first pass at
an implementation that allows you to define types in a module that
other wrapped modules can use, so you could have a glib module that
you imported the "wrapper defs" from so that your wrapped functions
would be able to use glib types in their specifications (for args and
results).

g-wrap itself has also been completely rewritten to be a guile module
itself (but not a dlopened one -- no C code), and it doesn't use
globals anymore.  This means that you you can wrap functions for
multiple modules on the fly from any invocation of guile.

Unfortunately, ATM, there is nearly no documentation for the new
stuff.  We needed something immediately for gnucash, and I haven't had
time to go back and update the docs yet.

As an example, for standard types (ones that have a guile-side native
representation), you can define as indicated in the code below.  For
this example a Timespec is a struct containing seconds and nanoseconds
on the C side, and is represented as a pair of integers on the guile
side.  Also, note that although there are a bunch of ccodegens you can
define for a type, many are optional.

Given the following definition, g-wrap will handle defining the
relevant wrapper code for functions using items of type 'time-pair as
arguments or return values, along with argument checking code, etc.,
and if you don't want to use the most common argument checking method
-- specifying the code that'll validate a value, there's also a way to
do something more tailored.

  (let ((wt (gw:wrap-type mod 'time-pair "Timespec" "const Timespec")))
    
    (gw:type-set-scm-arg-type-test-ccodegen!
     wt
     (lambda (param)
       (list "gnc_timepair_p(" (gw:param-get-scm-name param) ")")))
    
    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (let* ((scm-name (gw:param-get-scm-name param))
              (c-name (gw:param-get-c-name param))
              (old-func
               (lambda (x)
                 (list "gnc_timepair2timespec(" x ")"))))
         (list c-name
               " = "
               (old-func scm-name)
               ";\n"))))
    (gw:type-set-call-ccodegen! wt standard-c-call-gen)
    
    (add-standard-result-handlers!
     wt
     (lambda (scm-name c-name)
       (let ((old-func
              (lambda (x)
                (list "gnc_timespec2timepair(" x ")"))))
         (list scm-name
               " = "
               (old-func c-name)
               ";\n")))))

Note that though the code here is C-code, you can also in-line scheme
code in many cases with a special construct...

As mentioned, on top of the generic types, g-wrap has also constructed
a specialization for non-native types -- types that need a scheme side
representation (i.e need to be carried via a smob).

To do this, g-wrap has the concept of gw:wct's and gw:wcp's (or
wrapped-c-types and wrapped-c-pointers).  The idea is that if you want
to wrap a function that takes as an argument, or returns as a value a
Foo*, then you'll need something on the scheme side to hold the
pointer for a given argument/result.  In g-wrap, instances of type
Foo* on the scheme side will all be gw:wcp's which are just smobs of
type wcp, and there's a corresponding smob (just one) of type wct
representing the entire Foo* type.  Internally, a Foo* wcp
(i.e. scheme side wrapped Foo pointer) has a pointer to the Foo* type
wct.  To make this more concrete, a particular Foo* value would print
as:

  <gw:wcp Foo* 0x43DEAD>

and the smob representing the Foo* type would print as:

  <gw:wct-Foo*>

Internally the former smob has a pointer to the latter, and the type
smob (the wct) will also be bound to a global variable of the same
name.  This is important for calling functions like (gw:wcp-coerce obj
type).  So you could say (gw:wcp-coerce some-void*-obj <gw:wct-Foo*>)
and get a corresponding Foo*.

This is all very ugly, and some of it unsafe, but it's necessary if
you want to have the low-level primitives to be able to wrap existing
C APIs.

For non-native types, g-wrap also gives you, if you want it, the
ability to set code for equal_p, mark, etc., but with some reasonably
convenient defaults.

Here's how you'd wrap a C pointer type that you just wanted the
default semantics for (i.e. no special
marking/equality/etc. behavior):

  (gw:wrap-non-native-type mod 'GList* "GList*" "const GList*")

After this, at runtime we can have functions that take glists as args
and return them as values.  A given glist object would be represented
by a gw:wcp (wrapped-c-pointer) smob.  This smob would know
(internally) that it was of type gw:wct (wrapped-c-type) GList*.  The
type itself is represented by a guile-side smob type set aside for all
wrapped-c-types, and will be bound to a global named <gw:wct-GList*>.
This binding is needed by operations like (gw:wcp-coerce obj type).

Given the GList* definition, you can use GList*'s as args/return
values, and so we've wrapped:

  (gnc:glist->list glist item-type)
  (gnc:list->glist list-of-wcps)
  (gnc:glist-map item-type thunk glist)
  (gnc:glist-for-each item-type thunk glist)

As a more complex example of a native type, showing how you can use
global and initialization-time code, here's how g-wrap wraps "int" for
the very anal, with full range checking using scheme "constants"
cached in statics for efficiency:

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; int
  (let ((wt (gw:wrap-type m 'int "int" "const int")))

    (gw:type-set-init-ccodegen!
     wt
     (lambda (type client-only?)
       (list
        "gw__module_gw_runtime_scm_intmin = gh_long2scm(INT_MIN);\n"
        "scm_protect_object(gw__module_gw_runtime_scm_intmin);\n"
        "gw__module_gw_runtime_scm_intmax = gh_long2scm(INT_MAX);\n"
        "scm_protect_object(gw__module_gw_runtime_scm_intmax);\n"))) 

    (gw:type-set-global-ccodegen!
     wt
     (lambda (type client-only?)
       (list 
        "static SCM gw__module_gw_runtime_scm_intmin;\n"
        "static SCM gw__module_gw_runtime_scm_intmax;\n")))
    
    (gw:type-set-scm-arg-type-test-ccodegen!
     wt
     (lambda (param)
       ;; I don't know if it's more efficient to work on the C side or
       ;; the scheme side...
       (let ((x (gw:param-get-scm-name param)))
         (list "((scm_integer_p(" x ") == SCM_BOOL_T) &&"
               " (scm_geq_p(" x ", gw__module_gw_runtime_scm_intmin) == 
SCM_BOOL_T) &&"
               " (scm_leq_p(" x ", gw__module_gw_runtime_scm_intmax) == 
SCM_BOOL_T))"))))
    
    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (let* ((scm-name (gw:param-get-scm-name param))
              (c-name (gw:param-get-c-name param)))
         (list c-name "= gh_scm2long(" scm-name ");\n"))))
    
    (gw:type-set-call-ccodegen! wt standard-c-call-gen)
    
    (add-standard-result-handlers!
     wt
     (lambda (scm-name c-name)
       (list scm-name " = gh_long2scm(" c-name ");\n"))))


As mentioned, g-wrap also has enumeration support.  So you can say
things like this:

  (let ((we (gw:wrap-enumeration mod
                                 'GNCAccountType
                                 "GNCAccountType" "const GNCAccountType")))
    ;; From Account.h
    (gw:enum-add-value! we "BAD_TYPE" 'bad-type)
    (gw:enum-add-value! we "NO_TYPE" 'no-type)
    (gw:enum-add-value! we "BANK" 'bank)
    (gw:enum-add-value! we "CASH" 'cash)
    (gw:enum-add-value! we "CREDIT" 'credit)
    (gw:enum-add-value! we "ASSET" 'asset)
    (gw:enum-add-value! we "LIABILITY" 'liability)
    (gw:enum-add-value! we "STOCK" 'stock)
    (gw:enum-add-value! we "MUTUAL" 'mutual-fund)
    (gw:enum-add-value! we "CURRENCY" 'currency)
    (gw:enum-add-value! we "INCOME" 'income)
    (gw:enum-add-value! we "EXPENSE" 'expense)
    (gw:enum-add-value! we "EQUITY" 'equity)
    (gw:enum-add-value! we "NUM_ACCOUNT_TYPES" 'num-account-types)
    (gw:enum-add-value! we "CHECKING" 'checking)
    (gw:enum-add-value! we "SAVINGS" 'savings)
    (gw:enum-add-value! we "MONEYMRKT" 'money-market)
    (gw:enum-add-value! we "CREDITLINE" 'credit-line)
    #t)

And then use this type when defining a function.  Enumeration values
as arguments can either be integers or symbols, and a function defined
as returning an enumeration will *always* return an integer.  Defining
an enumeration type also creates two conversion functions at runtime.
In the case above gw:enum-GNCAccountType-val->sym and
gw:enum-GNCAccountType-val->num.  These can be used to convert back
and forth between integers and symbols as appropriate, or just to
check the validity of a given integer or symbol for the given
enumeration.  Further, when converting from an integer to a symbol
with val->sym, you can specify with a boolean arg whether or not you
want "a matching symbol" or "all matching symbols".  Important in
cases where an enumeration specifies the same integer value for
several TOKENS.

Given the standard type, and the specializations non-native-type and
enumeration, you can then wrap functions like this:

  (gw:wrap-function
   mod
   'gnc:glist-map
   'scm "gnc_glist_scm_map" '((gw:wct wct) (scm thunk) (GList* glist))
   "Call thunk on every element of glist after conversion to wcp of type wct, "
   "and return a list of the results.")

  (gw:wrap-function
   mod
   'gnc:numeric-create
   'gnc-numeric
   "gnc_numeric_create"
   '((gint64 num) (gint64 denom))
   "Create a new gnc_numeric object")

Here are the things you can define for standard types (though you don't
use these names, exactly):

  init-ccodegen (type client-only?)
  global-ccodegen (type client-only?)

  pre-type-test-arg-ccodegen (param)
  scm-arg-type-test-ccodegen (param)
  pre-call-result-ccodegen (result)
  pre-call-arg-ccodegen (param)
  call-ccodegen (result func-call-code)
  post-call-arg-ccodegen (param)
  post-call-result-ccodegen (result)

By creating a "standard-type" like this and then specifying the right
functions for these various ccodegens, you can create many other, more
specialized types.  In fact, that's how the non-native and enumeration
types were created.

and for non-native types:

  global-ccodegen
  init-ccodegen
  scm-rep-type-test-ccodegen
  print-ccodegen
  equal?-ccodegen
  gc-mark-ccodegen
  cleanup-ccodegen

Don't know if all this is interesting to anyone, but I thought it was
probably at least worth a mention.

-- 
Rob Browning <address@hidden> PGP=E80E0D04F521A094 532B97F5D64E3930



reply via email to

[Prev in Thread] Current Thread [Next in Thread]