Thursday, April 21, 2011

Go & WordNet on OS X

These are quick notes about accessing the Wordnet API from Go on Mac OS X (10.6).  Go is a neat language (Golang http://golang.org/ ) and I found myself wanting to access Wordnet from it.

The Wordnet library is written in C. Luckily, cgo allows one to call C directly from Go program (http://blog.golang.org/2011/03/c-go-cgo.html.)  So our program is to 1) get the Wordnet library ready, 2) create/build a package with cgo to access it and 3) show that it works with a trivial program.

First, download the full Wordnet package  ( WordNet-3.0.tar.gz).  The instructions to build the library will give you a static libWN.a which will not work with cgo.  Still the first step is to build wordnet following the instructions and make sure that it's compiling, installing and running properly:

tar xvzf WordNet-3.0.tar.gz
cd WordNet-3.0
./configure
make
sudo make install

worked out of the box and installs everything under /usr/local/WordNet-3.0.  Next is building a dynamic lib on OS X so it will work with cgo.  The library is in the source under  ~/WordNet-3.0/lib. The Makefile can be quickly tweaked to generate the dyn lib we need:

Locate the line with "libWN_a_CPPFLAGS =  $(INCLUDES)" and replace with "libWN_a_CPPFLAGS =  -fPIC -fno-common $(INCLUDES)".

Then add a build target:


libWN.dyn: $(libWN_a_OBJECTS) $(libWN_a_DEPENDENCIES)
        -rm -f libWN.3.dylib
        gcc -dynamiclib -Wl,-headerpad_max_install_names,-undefined,dynamic_lookup,-compatibility_version,1.0,-current_version,1.0,-install_name,/usr/local/WordNet-3.0/lib/libWN.3.dylib -o libWN.3.dylib $(libWN_a_OBJECTS)

make libWN.dyn should generate the needed file:  libWN.3.dylib 
sudo cp libWN.3.dylib /usr/local/WordNet-3.0/lib  

Note that ./configure will pick the 64 bit architecture for OS X when supported. This means that the amd64 architecture must be used for Go as well (unless you've set GOARCH=amd64, Go on OS X build for the 386 architecture.)

The next step is to create a Go package that cgo can compile. Here we'll just call one of the lib's function, namely "char *findtheinfo(char *searchstr, int dbase, int ptrtyp, int whichsense)" for demonstration purposes. There are 3 files that should all be put in the same directory: wn.go, main.go, Makefile (see below).  The 'printlicense()' and PrintLicenses functions defined in wn.go are there because the two strings license and dblicense defined in wn.h are otherwise not used and the compiler complains about it. 


wn.go:
================

package wn

/*
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include "wn.h"

static void printlicense()
{
    printf("WordNet License %s\n\n%s", dblicense, license);
}
*/
import "C"
import "unsafe"

import (
        "os"
)

func PrintLicenses() {
  C.printlicense()
}

// initialize WordNet
func InitWN() (err os.Error)  {
  status := C.wninit()
  if int(status) != 0 { return os.NewError("WN Fatal: can't open database")}
  return nil
}

// search is the item to search for, e.g. 'house'.
// dbase maps the the 'pos' or Part Of Speech, e.g. Noun, Verb, Adjective or Adverb
// ptrtyp is the search type. Example are ANTPTR, CAUSETO, et..
// whichsense should be set to 0 (ALLSENSES) to get all meanings.

func FindTheInfo(search string, dbase, ptrtyp, whichsense int) string {
  cSearch := C.CString(search)
  defer C.free(unsafe.Pointer(cSearch))

  cDbase := C.int(dbase)
  cPtrtyp := C.int(ptrtyp)
  cWhichsense := C.int(whichsense)
  result := C.findtheinfo(cSearch,cDbase, cPtrtyp, cWhichsense)
  gRes := C.GoString(result)
  return gRes
}

main.go:
================
package main

import (
        "fmt"
        "wn"
)

func main() {
  wn.InitWN()
  word := "go"
  fmt.Printf("Sense Results for '%s': %s\n",word, wn.FindTheInfo(word,1,5,0))
}


Makefile:
================
include $(GOROOT)/src/Make.inc

TARG=wn
CGOFILES=\
        wn.go\


CGO_CFLAGS=-I/usr/local/WordNet-3.0/include
CGO_LDFLAGS= /usr/local/WordNet-3.0/lib/libWN.3.dylib

CLEANFILES+= gown

include $(GOROOT)/src/Make.pkg

gown: install main.go
        $(GC) main.go
        $(LD) -o $@ main.$O


make gown  will create the executable 'gown' which should output:

Sense Results for 'go': 
4 senses of go                                                          

Sense 1
go, spell, tour, turn -- (a time for working (after which you will be relieved by someone else); "it's my go"; "a spell of work")

Sense 2
Adam, ecstasy, XTC, go, disco biscuit, cristal, X, hug drug -- (street names for methylenedioxymethamphetamine)

Sense 3
crack, fling, go, pass, whirl, offer -- (a usually brief attempt; "he took a crack at it"; "I gave it a whirl")

Sense 4
go, go game -- (a board game for two players who place counters on a grid; the object is to surround and so capture the opponent's counters)