Table of Contents
In the interest of having some recent code samples to show off, I've developed this trivial doubly linked list library over the last few days. My goal is to demonstrate not only my ability to program in C, but also my ability to manage a complete project, including documentation and integration with other languages.
The core library is written in C. I've embedded documentation in the header file, and used Doxygen to extract and format it. The Doxygen output is attached to the end of this document. I've used SCons (an advanced replacement for make) to build everything. I've used SWIG to compile the library as modules for both Perl and Python, and written a trivial demonstration program for each one. I've written this document in DocBook.
Here is the C header file. You'll note the embedded Doxygen documentation:
/**
* \mainpage Doubly Linked List Library
* A completely useless and trivial implementation of a doubly linked list
* library in C. Please do not use for anything serious.
* \author Jeff Laughlin
* \date (c)2008
*/
/**
* \file dll.h
* \brief Definitions for doubly linked list library
*/
/// A node in a doubly linked list
/**
* All node fields must be initialized by the user. All operations not
* specifically embodied in a function are the responsibility of the user.
*/
struct Node {
struct Node *nextNode; ///<Pointer to the next Node in the list
struct Node *prevNode; ///<Pointer to the previous Node in the list
char *payload; ///<Pointer to a string
};
/// Given any node in a list, return the first node in the list
struct Node * getFirstNode(struct Node *node);
/// Given any node in a list, return the last node in the list
struct Node * getLastNode(struct Node *node);
/// Insert newNode in the list before rootNode
void insertBefore(struct Node *rootNode, struct Node *newNode);
/// Insert newNode in the list after rootNode
void insertAfter(struct Node *rootNode, struct Node *newNode);
/// Delete the node from the list
/**
* Removes the given node from the list. This is done by linking it's previous
* and next nodes to each other, and setting the given nodes previous and next
* fields to NULL. Note that the memory used by the Node is not freed.
*/
void deleteNode(struct Node *node);
/// Search the list for a node containing searchString
/**
* Given any node in a list, search the list for a node containing the given
* search string and return a pointer to that node. Searches forwards, then
* backwards from given node.
*/
struct Node * searchList(struct Node *node, char *searchString);
/// Join the strings in a list on a given string
/**
* Given any node in a list, and a string, return the strings of all the nodes
* in the list in list order with joinString inserted inbetween each one.
* EG if the list contains ['foo', 'bar'], and it is joined with 'ack', the
* result will be 'fooackbar'.
*/
char * joinList(struct Node *node, char *joinString);
Here's the actual source code:
// dll.c
// Part of the doubly linked list library
// (c) 2008 Jeff Laughlin
#include <stdlib.h>
#include <error.h>
#include <err.h>
#include <string.h>
#include <dll.h>
#define CHUNK_SIZE 1024*4
struct Node * getFirstNode(struct Node *node) {
while (node->prevNode != NULL)
node = node->prevNode;
return node;
}
struct Node * getLastNode(struct Node *node) {
while (node->nextNode != NULL)
node = node->nextNode;
return node;
}
void insertBefore(struct Node *rootNode, struct Node *newNode) {
if (rootNode->prevNode != NULL) {
rootNode->prevNode->nextNode = newNode;
newNode->prevNode = rootNode->prevNode;
}
else {
newNode->prevNode = NULL;
}
rootNode->prevNode = newNode;
newNode->nextNode = rootNode;
}
void insertAfter(struct Node *rootNode, struct Node *newNode) {
if (rootNode->nextNode != NULL) {
rootNode->nextNode->prevNode = newNode;
newNode->nextNode = rootNode->nextNode;
}
else {
newNode->nextNode = NULL;
}
rootNode->nextNode = newNode;
newNode->prevNode = rootNode;
}
void deleteNode(struct Node *node) {
if (node->prevNode != NULL)
node->prevNode->nextNode = node->nextNode;
if (node->nextNode != NULL)
node->nextNode->prevNode = node->prevNode;
node->prevNode = NULL;
node->nextNode = NULL;
}
struct Node * searchList(struct Node *node, char *searchString) {
struct Node * currentNode;
struct Node sentinalStruct;
struct Node * sentinal = &sentinalStruct;
// search forward
sentinal->prevNode = NULL;
sentinal->nextNode = node;
currentNode = sentinal;
do {
currentNode = currentNode->nextNode;
if (strcmp(currentNode->payload, searchString) == 0)
return currentNode;
}
while (currentNode->nextNode != NULL);
// search backwards
sentinal->nextNode = NULL;
sentinal->prevNode = node;
currentNode = sentinal;
do {
currentNode = currentNode->prevNode;
if (strcmp(currentNode->payload, searchString) == 0)
return currentNode;
}
while (currentNode->prevNode != NULL);
return NULL;
}
char * joinList(struct Node *node, char *joinString) {
char * retString;
size_t retStringSize = CHUNK_SIZE;
size_t payloadLength;
size_t retStringLength;
size_t joinStringLength;
struct Node dummyFirst;
struct Node * ptrDummyFirst = &dummyFirst;
joinStringLength = strlen(joinString);
// move to first node
node = getFirstNode(node);
if (node->nextNode == NULL)
return node->payload;
retString = malloc(retStringSize);
if (retString == NULL)
err(1, "Malloc failed");
retString[0] = (char)NULL;
ptrDummyFirst->nextNode = node;
node = ptrDummyFirst;
// Iterate over nodes
do {
node = node->nextNode;
// is retString big enough? realloc if necessary
payloadLength = strlen(node->payload);
retStringLength = strlen(retString);
while (payloadLength + retStringLength + joinStringLength + 1 > retStringSize) {
// Hypothetically, if the string being joined was many times larger
// than CHUNK_SIZE, it would be faster to just allocate that many
// chunks at once. But that would be one long string!
if (payloadLength + joinStringLength + 1 > CHUNK_SIZE)
retStringSize = retStringSize + (payloadLength + joinStringLength + 1) / CHUNK_SIZE * CHUNK_SIZE;
else
retStringSize += CHUNK_SIZE;
retString = realloc(retString, retStringSize);
if (retString == NULL)
err(1, "Malloc failed");
}
strcat(retString, node->payload);
if (node->nextNode != NULL)
strcat(retString, joinString);
}
while (node->nextNode != NULL);
return retString;
}
I used Mercurial as a revision control mechanism for this project. I've found Mercurial to be highly polished and easy to use, and the distributed functionality is great. Here is my worklog for this project:
laughlin@nibbler:~/tmp/dll$ hg log -v
changeset: 12:04063e6b80bc
tag: tip
user: Jeffrey Laughlin <jlaughlin@ucsd.edu>
date: Thu Mar 27 16:17:28 2008 -0700
files: doc/dll_examples/dll_examples.html doc/dll_examples/dll_examples.xml
description:
Updated report with mention of Mercurial
changeset: 11:557498ddb295
user: Jeffrey Laughlin <jlaughlin@ucsd.edu>
date: Thu Mar 27 15:35:17 2008 -0700
files: dll.dox
description:
added doxygen config file
changeset: 10:9348ce525033
user: Jeffrey Laughlin <jlaughlin@ucsd.edu>
date: Thu Mar 27 15:34:14 2008 -0700
files: SConscript.perl SConscript.python SConstruct dll.c dll.h dll.i \
dll_unit.c doc/dll_examples/Makefile doc/dll_examples/dll_examples.html \
doc/dll_examples/dll_examples.xml doc/dll_examples/style.css \
perl/dll_example.pl python/dll_example.py
description:
Finished up examples, added docbook file for examples, added doxygen doc strings
changeset: 9:b68dba32d60a
user: Jeffrey Laughlin <jlaughlin@ucsd.edu>
date: Thu Mar 27 13:03:43 2008 -0700
files: perl/dll_example.pl
description:
fixed perl example
changeset: 8:096394db27ec
user: Jeffrey Laughlin <jlaughlin@ucsd.edu>
date: Thu Mar 27 11:59:20 2008 -0700
files: dll_example.py perl/dll_example.pl python/dll_example.py
description:
created perl example
changeset: 7:cadcdddd8207
user: Jeffrey Laughlin <jlaughlin@ucsd.edu>
date: Thu Mar 27 11:30:45 2008 -0700
files: SConscript.perl SConscript.python SConstruct
description:
Added perl target to scons. Put perl and python modules in seperate dirs, but its buggy
changeset: 6:79b1d26ef679
user: Jeffrey Laughlin <jlaughlin@ucsd.edu>
date: Thu Mar 27 10:22:50 2008 -0700
files: dll_example.py
description:
created simple python demo program
changeset: 5:6b86d8f58ffd
user: Jeffrey Laughlin <jlaughlin@ucsd.edu>
date: Thu Mar 27 10:13:32 2008 -0700
files: SConstruct dll_unit.c
description:
Finished unit test suite
changeset: 4:1067a33310dc
user: Jeffrey Laughlin <jlaughlin@ucsd.edu>
date: Thu Mar 27 09:44:53 2008 -0700
files: dll_unit.c
description:
Factored list initialization out from suite init
changeset: 3:a655b29150fc
user: Jeffrey Laughlin <jlaughlin@ucsd.edu>
date: Thu Mar 27 09:42:42 2008 -0700
files: SConstruct dll.c dll_unit.c
description:
Building unit tests
changeset: 2:5fe63dfbff97
user: Jeffrey Laughlin <jlaughlin@ucsd.edu>
date: Wed Mar 26 21:40:41 2008 -0700
files: dll.c dll.h dll.i
description:
Added deleteNode function
changeset: 1:eae1127089b2
user: Jeffrey Laughlin <jlaughlin@ucsd.edu>
date: Wed Mar 26 21:34:49 2008 -0700
files: SConstruct dll.c dll.h dll.i
description:
Added search list function, fixed swig interface
changeset: 0:efda71edd302
user: Jeffrey Laughlin <jlaughlin@ucsd.edu>
date: Wed Mar 26 21:19:07 2008 -0700
files: SConstruct dll.c dll.h dll.i
description:
Built basic doubly linked list library, SWIG interface, and SCONS file
I used the SCons build system for this project. It is a highly portable and flexable make replacement.
Here is the main script:
# SConstruct
# Part of the doubly linked list library
# (c)2008 Jeffrey Laughlin
import distutils.sysconfig
import os
pythonEnv = Environment(SWIGFLAGS=['-python'],
CPPPATH=['.', distutils.sysconfig.get_python_inc()],
SHLIBPREFIX="")
Export('pythonEnv')
SConscript('SConscript.python', build_dir='python')
perlPath = os.popen("perl -e 'use Config; print $Config{sitearch};'").read()
perl = Environment(SWIGFLAGS=['-perl'],
CPPPATH=['.', '/usr/lib/perl/5.8.8/CORE', perlPath],
CFLAGS=['-D_GNU_SOURCE'],
SHLIBPREFIX="")
Export('perl')
SConscript('SConscript.perl', build_dir='perl')
env = Environment(
CPPPATH=['.'],
)
sl = env.SharedLibrary('doublylinkedlist', 'dll.c')
unitTest = Environment(
CPPPATH=['.'],
CFLAGS=['-g']
)
dll_unit = unitTest.Program('dll_unit', ['dll.c', 'dll_unit.c'], LIBS='cunit')
Default(sl)
Also there is a supporting script to build DLL as a Perl module:
# SConscript.perl
# Part of the doubly linked list library
# (c)2008 Jeff Laughlin
# Builds the perl module. It works.
Import('perl')
perlModFile = File("doublylinkedlist.so")
perlModule = perl.SharedLibrary(perlModFile, ("dll.c", "dll.i"))
#perlModule = SharedLibrary(perlModFile, ("dll.c", "dll.i"))
And a supporting script to build the Python module:
# SConscript.python
# Part of the doubly linked list library
# (c)2008 Jeff Laughlin
# Builds the python module. It's slightly broken. Interestingly, the perl one
# is almost exactly the same and it works fine.
Import('pythonEnv')
pymodFile = File("_doublylinkedlist.so")
#pythonModule = SharedLibrary(pymodFile, ("dll.c", "dll.i"))
pythonModule = pythonEnv.SharedLibrary(pymodFile, ("dll.c", "dll.i"))
I used SWIG to build both Perl and Python modules from a common C codebase. This is the SWIG interface file I created for this project:
/* dll.i
* SWIG Interface file
* Part of the doubly linked list library
* (c)2008 Jeff Laughlin
*/
%module doublylinkedlist
%{
#include <dll.h>
%}
struct Node {
struct Node *nextNode;
struct Node *prevNode;
char *payload;
};
struct Node * getFirstNode(struct Node *node);
struct Node * getLastNode(struct Node *node);
void insertBefore(struct Node *rootNode, struct Node *newNode);
void insertAfter(struct Node *rootNode, struct Node *newNode);
void deleteNode(struct Node *node);
struct Node * searchList(struct Node *node, char *searchString);
char * joinList(struct Node *node, char *joinString);
This full suite of unit tests tries to cover all of the corner cases.
// dll_unit.c
// (c)2008 Jeffrey Laughlin
// Part of the doubly linked list library
// CUnit based unit tests. Just compile and run.
#include <stdlib.h>
#include <dll.h>
#include <CUnit/Basic.h>
struct Node *threeNodes[3];
struct Node *twoNodes[2];
struct Node *oneNode;
int init_suite1(void) {
threeNodes[0] = malloc(sizeof (struct Node));
threeNodes[1] = malloc(sizeof (struct Node));
threeNodes[2] = malloc(sizeof (struct Node));
twoNodes[0] = malloc(sizeof (struct Node));
twoNodes[1] = malloc(sizeof (struct Node));
oneNode = malloc(sizeof (struct Node));
return 0;
}
void setupNodes(void) {
threeNodes[0]->prevNode = NULL;
threeNodes[0]->nextNode = threeNodes[1];
threeNodes[0]->payload = "Cherokee";
threeNodes[1]->prevNode = threeNodes[0];
threeNodes[1]->nextNode = threeNodes[2];
threeNodes[1]->payload = "Comanchee";
threeNodes[2]->prevNode = threeNodes[1];
threeNodes[2]->nextNode = NULL;
threeNodes[2]->payload = "Chief";
twoNodes[0]->prevNode = NULL;
twoNodes[0]->nextNode = twoNodes[1];
twoNodes[0]->payload = "Wrangler";
twoNodes[1]->prevNode = twoNodes[0];
twoNodes[1]->nextNode = NULL;
twoNodes[1]->payload = "CJ";
oneNode->prevNode = NULL;
oneNode->nextNode = NULL;
oneNode->payload = "Jeepster";
}
int clean_suite1(void) {
free(threeNodes[0]);
free(threeNodes[1]);
free(threeNodes[2]);
free(twoNodes[0]);
free(twoNodes[1]);
free(oneNode);
return 0;
}
void test_getFirstNode(void) {
setupNodes();
CU_ASSERT_PTR_EQUAL(getFirstNode(threeNodes[0]), threeNodes[0]);
CU_ASSERT_PTR_EQUAL(getFirstNode(threeNodes[1]), threeNodes[0]);
CU_ASSERT_PTR_EQUAL(getFirstNode(threeNodes[2]), threeNodes[0]);
CU_ASSERT_PTR_EQUAL(getFirstNode(twoNodes[0]), twoNodes[0]);
CU_ASSERT_PTR_EQUAL(getFirstNode(twoNodes[1]), twoNodes[0]);
CU_ASSERT_PTR_EQUAL(getFirstNode(oneNode), oneNode);
}
void test_getLastNode(void) {
setupNodes();
CU_ASSERT_PTR_EQUAL(getLastNode(threeNodes[0]), threeNodes[2]);
CU_ASSERT_PTR_EQUAL(getLastNode(threeNodes[1]), threeNodes[2]);
CU_ASSERT_PTR_EQUAL(getLastNode(threeNodes[2]), threeNodes[2]);
CU_ASSERT_PTR_EQUAL(getLastNode(twoNodes[0]), twoNodes[1]);
CU_ASSERT_PTR_EQUAL(getLastNode(twoNodes[1]), twoNodes[1]);
CU_ASSERT_PTR_EQUAL(getLastNode(oneNode), oneNode);
}
void test_insertBefore(void) {
setupNodes();
insertBefore(twoNodes[0], oneNode);
CU_ASSERT_PTR_EQUAL(twoNodes[0]->prevNode, oneNode);
CU_ASSERT_PTR_EQUAL(twoNodes[0]->nextNode, twoNodes[1]);
CU_ASSERT_PTR_EQUAL(oneNode->nextNode, twoNodes[0]);
CU_ASSERT_PTR_EQUAL(oneNode->prevNode, NULL);
twoNodes[0]->prevNode = NULL;
insertBefore(twoNodes[1], oneNode);
CU_ASSERT_PTR_EQUAL(twoNodes[1]->prevNode, oneNode);
CU_ASSERT_PTR_EQUAL(twoNodes[1]->nextNode, NULL);
CU_ASSERT_PTR_EQUAL(oneNode->nextNode, twoNodes[1]);
CU_ASSERT_PTR_EQUAL(oneNode->prevNode, twoNodes[0]);
CU_ASSERT_PTR_EQUAL(twoNodes[0]->nextNode, oneNode);
CU_ASSERT_PTR_EQUAL(twoNodes[0]->prevNode, NULL);
}
void test_insertAfter(void) {
setupNodes();
insertAfter(twoNodes[1], oneNode);
CU_ASSERT_PTR_EQUAL(twoNodes[1]->nextNode, oneNode);
CU_ASSERT_PTR_EQUAL(twoNodes[1]->prevNode, twoNodes[0]);
CU_ASSERT_PTR_EQUAL(oneNode->prevNode, twoNodes[1]);
CU_ASSERT_PTR_EQUAL(oneNode->nextNode, NULL);
twoNodes[1]->nextNode = NULL;
insertAfter(twoNodes[0], oneNode);
CU_ASSERT_PTR_EQUAL(twoNodes[1]->prevNode, oneNode);
CU_ASSERT_PTR_EQUAL(twoNodes[1]->nextNode, NULL);
CU_ASSERT_PTR_EQUAL(oneNode->nextNode, twoNodes[1]);
CU_ASSERT_PTR_EQUAL(oneNode->prevNode, twoNodes[0]);
CU_ASSERT_PTR_EQUAL(twoNodes[0]->nextNode, oneNode);
CU_ASSERT_PTR_EQUAL(twoNodes[0]->prevNode, NULL);
}
void test_deleteNode(void) {
setupNodes();
deleteNode(threeNodes[1]);
CU_ASSERT_PTR_EQUAL(threeNodes[1]->prevNode, NULL);
CU_ASSERT_PTR_EQUAL(threeNodes[1]->nextNode, NULL);
CU_ASSERT_PTR_EQUAL(threeNodes[0]->nextNode, threeNodes[2]);
CU_ASSERT_PTR_EQUAL(threeNodes[2]->prevNode, threeNodes[0]);
deleteNode(threeNodes[0]);
CU_ASSERT_PTR_EQUAL(threeNodes[0]->prevNode, NULL);
CU_ASSERT_PTR_EQUAL(threeNodes[0]->nextNode, NULL);
CU_ASSERT_PTR_EQUAL(threeNodes[2]->nextNode, NULL);
CU_ASSERT_PTR_EQUAL(threeNodes[2]->prevNode, NULL);
deleteNode(threeNodes[0]);
CU_ASSERT_PTR_EQUAL(threeNodes[0]->prevNode, NULL);
CU_ASSERT_PTR_EQUAL(threeNodes[0]->nextNode, NULL);
CU_PASS("Didn't freak out on deleteing a one node list");
}
void test_searchList(void) {
setupNodes();
CU_ASSERT_PTR_EQUAL(searchList(threeNodes[0], "Chief"), threeNodes[2]);
CU_ASSERT_PTR_EQUAL(searchList(threeNodes[2], "Chief"), threeNodes[2]);
CU_ASSERT_PTR_EQUAL(searchList(threeNodes[2], "Cherokee"), threeNodes[0]);
CU_ASSERT_PTR_EQUAL(searchList(threeNodes[0], "Cherokee"), threeNodes[0]);
CU_ASSERT_PTR_EQUAL(searchList(oneNode, "Jeepster"), oneNode);
CU_ASSERT_PTR_EQUAL(searchList(threeNodes[2], "foobar"), NULL);
}
void test_joinList(void) {
setupNodes();
CU_ASSERT_STRING_EQUAL(joinList(threeNodes[0], " "),
"Cherokee Comanchee Chief");
CU_ASSERT_STRING_EQUAL(joinList(twoNodes[0], " "),
"Wrangler CJ");
CU_ASSERT_STRING_EQUAL(joinList(threeNodes[2], " and "),
"Cherokee and Comanchee and Chief");
CU_ASSERT_STRING_EQUAL(joinList(oneNode, " "),
"Jeepster");
}
int main() {
CU_pSuite pSuite = NULL;
/* initialize the CUnit test registry */
if (CUE_SUCCESS != CU_initialize_registry())
return CU_get_error();
/* add a suite to the registry */
pSuite = CU_add_suite("Suite_1", init_suite1, clean_suite1);
if (NULL == pSuite) {
CU_cleanup_registry();
return CU_get_error();
}
/* add the tests to the suite */
if ((NULL == CU_add_test(pSuite, "test of getFirstNode", test_getFirstNode))
|| (NULL == CU_add_test(pSuite, "test of getLastNode", test_getLastNode))
|| (NULL == CU_add_test(pSuite, "test of insertBefore", test_insertBefore))
|| (NULL == CU_add_test(pSuite, "test of insertAfter", test_insertAfter))
|| (NULL == CU_add_test(pSuite, "test of searchList", test_searchList))
|| (NULL == CU_add_test(pSuite, "test of joinList", test_joinList))
|| (NULL == CU_add_test(pSuite, "test of deleteNode", test_deleteNode)))
{
CU_cleanup_registry();
return CU_get_error();
}
/* Run all tests using the CUnit Basic interface */
CU_basic_set_mode(CU_BRM_VERBOSE);
CU_basic_run_tests();
CU_cleanup_registry();
return CU_get_error();
}
Here is the output:
CUnit - A Unit testing framework for C - Version 2.1-0
http://cunit.sourceforge.net/
Suite: Suite_1
Test: test of getFirstNode ... passed
Test: test of getLastNode ... passed
Test: test of insertBefore ... passed
Test: test of insertAfter ... passed
Test: test of searchList ... passed
Test: test of joinList ... passed
Test: test of deleteNode ... passed
--Run Summary: Type Total Ran Passed Failed
suites 1 1 n/a 0
tests 7 7 7 0
asserts 53 53 53 0
Here is a trivial program to demonstrate the use of the DLL built as a Python module:
#!/usr/bin/python
# dll_example.py
# Part of the doubly linked list library
# (c)2008 Jeff Laughlin
# Demonstrates basic functionality of the DLL built as a python module with
# SWIG.
from doublylinkedlist import *
node1 = Node()
node2 = Node()
node3 = Node()
node1.prevNode = None
node1.nextNode = node2
node1.payload = "Jeep"
node2.prevNode = node1
node2.nextNode = node3
node2.payload = "Ford"
node3.prevNode = node2
node3.nextNode = None
node3.payload = "Chevy"
print ("Node 1: " + repr(node1))
print ("Node 2: " + repr(node2))
print ("Node 3: " + repr(node3))
print ("Node containing 'Ford': " + repr(searchList(node1, "Ford")))
print ("List joined on ' and ': " + joinList(node1, " and "))
Here is the output produced:
laughlin@nibbler:~/tmp/dll$ python/dll_example.py Node 1: <doublylinkedlist.Node; proxy of <Swig Object of type 'struct Node *' at 0x81c0048> > Node 2: <doublylinkedlist.Node; proxy of <Swig Object of type 'struct Node *' at 0x81c3750> > Node 3: <doublylinkedlist.Node; proxy of <Swig Object of type 'struct Node *' at 0x81c37a0> > Node containing 'Ford': <doublylinkedlist.Node; proxy of <Swig Object of type 'struct Node *' at 0x81c3750> > List joined on ' and ': Jeep and Ford and Chevy
Here is a trivial program to demonstrate the use of the DLL built as a Perl module:
#!/usr/bin/perl
# dll_example.pl
# Part of the doubly linked list library
# (c)2008 Jeff Laughlin
# Demonstrates basic functionality of the DLL built as a perl module with SWIG.
use strict;
use doublylinkedlist;
my $node1 = doublylinkedlist::Node->new();
my $node2 = doublylinkedlist::Node->new();
my $node3 = doublylinkedlist::Node->new();
$node1->{prevNode} = undef;
$node1->{nextNode} = $node2;
$node2->{prevNode} = $node1;
$node2->{nextNode} = $node3;
$node3->{prevNode} = $node2;
$node3->{nextNode} = undef;
$node1->{payload} = "Rice";
$node2->{payload} = "Wheat";
$node3->{payload} = "Corn";
printf( "%s\n", doublylinkedlist::joinList($node1, " is not "));
Here is the output produced:
laughlin@nibbler:~/tmp/dll/perl$ ./dll_example.pl Rice is not Wheat is not Corn