Previous: , Up: Guile   [Contents][Index]


5.5.5 Example Module

In this subsection we will show how to build a simple dicod module written in Scheme. The source code of this module, called listdict.scm and a short database for it, numerals-pl.db, are shipped with the distribution in the directory examples.

The database is stored in a disk file in form of a list. The first two elements of this list contain database description and full information strings. Rest of elements are conses, whose car contains the headword, and cdr contains the corresponding dictionary article. Following is an example of such a database:

("Short English-Norwegian numerals dictionary"
 "Short English-Norwegian dictionary of numerals (1 - 7)"
 ("one" . "en")
 ("two" . "to")
 ("three" . "tre")
 ("four" . "fire")
 ("five" . "fem")
 ("six" . "seks")
 ("seven" . "sju"))

We wish to declare such databases in dicod.conf the following way:

database {
        name "numerals";
        handler "guile example.db";
}

Thus, the rest argument to ‘open-db’ callback will be ‘("guile" "example.db")’ (see open-db). Given this, we may write the callback as follows:

(define (open-db name . rest)
  (let ((db (with-input-from-file
                (cadr rest)
              (lambda () (read)))))
    (cond
     ((list? db) (cons name db))
     (else
      (format (current-error-port) "open-module: ~A: invalid format\n"
              (car args))
      #f))))

The list returned by this callback will then be passed as a database handle to another callback functions. To facilitate access to particular elements of this list, it is convenient to define the following syntax:

(define-syntax db:get
  (syntax-rules (info descr name corpus)
    ((db:get dbh name)   ;; Return the name of the database.
     (list-ref dbh 0))
    ((db:get dbh descr)  ;; Return the desctiption.
     (list-ref dbh 1))
    ((db:get dbh info)   ;; Return the info string. 
     (list-ref dbh 2))
    ((db:get dbh corpus) ;; Return the word list.
     (list-tail dbh 3))))

Now, we can write ‘descr’ and ‘info’ callbacks:

(define (descr dbh)
  (db:get dbh descr))

(define (info dbh)
  (db:get dbh info))

The two callbacks ‘define-word’ and ‘match-word’ provide the core module functionality. Their results will be passed to ‘output’ and ‘result-count’ callbacks as a “result handler” argument. In the spirit of Scheme, we make the result a list. Its car is a boolean value: #t, if the result comes from ‘define-word’ callback, and #f if it comes from ‘match-word’. The cdr of this list contains a list of matches. For ‘define-word’, it is a list of conses copied from the database word list, whereas for ‘match-word’, it is a list of headwords.

The ‘define-word’ callback returns all list entries whose cars contain the look up word. It uses mapcan function, which is supposed to be defined elsewhere:

(define (define-word dbh word)
  (let ((res (mapcan (lambda (elt)
                       (and (string-ci=? word (car elt))
                            elt))
                     (db:get dbh corpus))))
    (and res (cons #t res))))

The ‘match-word’ callback (see match-word) takes three arguments: a database handler dbh, a strategy descriptor strat, and a word word to look for. The result handle it returns contains a list of headwords from the database that match word in the sense of strat. Thus, the behavior of ‘match-word’ depends on the strat. To implement this, let’s define a list of directly supported strategies (see below for definitions of particular ‘match-’ functions):

(define strategy-list
  (list (cons "exact"  match-exact)
        (cons "prefix"  match-prefix)
        (cons "suffix"  match-suffix)))

The ‘match-word’ callback will then select an entry from that list and call its cdr, e.g.:

(define (match-word dbh strat key)
  (let ((sp (assoc (dico-strat-name strat) strategy-list)))
    (let ((res (cond
                (sp
                 ((cdr sp) dbh strat (dico-key->word key)))

If the requested strategy is not in that list, the function will use the selector function if it is available, and the default matching function otherwise:

                ((dico-strat-selector? strat)
                 (match-selector dbh strat key))
                (else
                 (match-default dbh strat (dico-key->word key))))))

Notice the use of dico-key->word function to extract the actual lookup word from the key object.

To summarize, the ‘match-word’ callback is:

(define (match-word dbh strat key)
  (let ((sp (assoc (dico-strat-name strat) strategy-list)))
    (let ((res (cond
                (sp
                 ((cdr sp) dbh strat (dico-key->word key)))
                ((dico-strat-selector? strat)
                 (match-selector dbh strat key))
                (else
                 (match-default dbh strat (dico-key->word key))))))
      (if res
          (cons #f res)
          #f))))

Now, let’s create the ‘match-’ functions it uses. The ‘exact’ strategy is easy to implement:

(define (match-exact dbh strat word)
  (mapcan (lambda (elt)
            (and (string-ci=? word (car elt))
                 (car elt)))
          (db:get dbh corpus)))

The ‘prefix’ and ‘suffix’ strategies are implemented using SRFI-13 (see SRFI-13 in The Guile Reference Manual) functions string-prefix-ci? and string-suffix-ci?, e.g.:

(define (match-prefix dbh strat word)
  (mapcan (lambda (elt)
            (and (string-prefix-ci? word (car elt))
                 (car elt)))
          (db:get dbh corpus)))

Notice that whereas the ‘prefix’ strategy is defined by the server itself, the ‘suffix’ strategy is an extension, and should therefore be registered:

(dico-register-strat "suffix" "Match word suffixes")

The match-selector function is pretty similar to its siblings, except that it uses dico-strat-select? (see dico-strat-select?) to select the matching elements. This also leads to this function expecting a key as its third argument, in contrast to the previous matchers, which expect the actual lookup word there:

(define (match-selector dbh strat key)
  (mapcan (lambda (elt)
            (and (dico-strat-select? strat (car elt) key)
                 (car elt)))
          (db:get dbh corpus)))

Finally, the match-default is a variable that refers to the default matching strategy for this module, e.g.:

(define match-default match-prefix)

The two callbacks left to define are ‘result-count’ and ‘output’. The first of them simply returns the number of elements in cdr of the result:

(define (result-count rh)
  (length (cdr rh)))

The behavior of ‘output’ depends on whether the result is produced by ‘define-word’ or by ‘match-word’.

(define (output rh n)
  (if (car rh)
      ;; Result comes from DEFINE command.
      (let ((res (list-ref (cdr rh) n)))
        (display (car res))
        (newline)
        (display (cdr res)))
      ;; Result comes from MATCH command.
      (display (list-ref (cdr rh) n))))

Finally, at the end of the module the callbacks are made known to dicod by the module initialization function:

(define-public (example-init arg)
  (list (cons "open" open-module)
        (cons "descr" descr)
        (cons "info" info)
        (cons "define" define-word)
        (cons "match" match-word)
        (cons "output" output)
        (cons "result-count" result-count)))

Notice, that in this implementation ‘close-db’ callback was not needed.


Previous: Dico Scheme Primitives, Up: Guile   [Contents][Index]