Skip to content

Commit

Permalink
added code for nifs version in chapter 12
Browse files Browse the repository at this point in the history
  • Loading branch information
richcarl authored and Eric Merritt committed Aug 10, 2010
1 parent e7c3322 commit dd5d41f
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 0 deletions.
28 changes: 28 additions & 0 deletions chapter_12/nifs/json_parser/README
@@ -0,0 +1,28 @@
To build the Erlang code, run the following command:

erlc -o ./ebin ./src/*.erl

To build the C code using gcc, run the following:

gcc -o ./priv/jp_nifs -fpic -shared -I$(OTPROOT)/erts-5.7.5/include -I$(OTPROOT)/lib/erl_interface-3.6.5/include -I$(YAJLROOT)/include -L$(OTPROOT)/lib/erl_interface-3.6.5/lib -L$(YAJLROOT)/lib ./c_src/jp_nifs.c -lyajl

OTPROOT is typically /usr/lib/erlang. Check what your versions of erts and
erl_interface are and that the include and lib directories exist and contain
the expected header files and library files - not all Erlang distributions
ship these development files in the basic installation package. YAJLROOT is
wherever your YAJL installation is. If you built YAJL but did not do 'make
install', this will be something like lloyd-yajl-1.0.9-0/build/yajl-1.0.9/.

For the YAJL shared library to be loaded at runtime, you may need to set the
environment variable LD_LIBRARY_PATH to ${YAJLROOT}/lib if you did not
install YAJL it in a standard location.

To run the program, first start Erlang like this:

erl -pa ../json_parser/ebin

Then, run the following in the Erlang shell:

1> json_parser:parse_document(<<"[null,true,{\"int\":42,\"float\":3.14}]">>).
{ok,{undefined,true,[{<<"int">>,42},{<<"float">>,3.14}]}}
2>
283 changes: 283 additions & 0 deletions chapter_12/nifs/json_parser/c_src/jp_nifs.c
@@ -0,0 +1,283 @@
/*
Erlang NIF interface to the YAJL JSON parser
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <erl_nif.h>

#include <yajl/yajl_parse.h>

#define BUFSIZE 65536

/* yajl callback prototypes */
static int handle_null(void *ctx);
static int handle_boolean(void *ctx, int boolVal);
static int handle_integer(void *ctx, long integerVal);
static int handle_double(void *ctx, double doubleVal);
static int handle_string(void *ctx, const unsigned char *stringVal,
unsigned int stringLen);
static int handle_map_key(void *ctx, const unsigned char *stringVal,
unsigned int stringLen);
static int handle_start_map(void *ctx);
static int handle_end_map(void *ctx);
static int handle_start_array(void *ctx);
static int handle_end_array(void *ctx);

static yajl_callbacks callbacks = {
handle_null,
handle_boolean,
handle_integer, /* note: only handles long integers, not bignums */
handle_double,
NULL, /* any number - if defined, integer/double are not used */
handle_string,
handle_start_map,
handle_map_key,
handle_end_map,
handle_start_array,
handle_end_array
};


typedef struct container_t {
int count; /* number of elements */
int arraysz; /* size of elements array */
ERL_NIF_TERM *array; /* elements array */
struct container_t *next;
} container_t;

typedef struct {
ErlNifEnv *env; /* NIF environment */
int key; /* true if last term was a key */
container_t *c; /* innermost container */
} state_t;


static void *alloc_func(void *ctx, unsigned int sz)
{
return enif_alloc((ErlNifEnv *)ctx, sz);
}

static void *realloc_func(void *ctx, void *ptr, unsigned int sz)
{
return enif_realloc((ErlNifEnv *)ctx, ptr, sz);
}

static void free_func(void *ctx, void *ptr)
{
enif_free((ErlNifEnv *)ctx, ptr);
}

static yajl_alloc_funcs alloc_funcs = {
alloc_func,
realloc_func,
free_func,
NULL /* must be initialized below */
};


static const char *parse_json(state_t *st, unsigned char *buf, size_t len)
{
yajl_parser_config cfg = {
1, /* allow comments */
0 /* don't check UTF-8 */
};
yajl_handle yh;
yajl_status ys;
const char *err=NULL;
char errmsg[256];

alloc_funcs.ctx = st->env; /* will be passed to alloc funcs */
yh = yajl_alloc(&callbacks, &cfg, &alloc_funcs, st);
ys = yajl_parse(yh, buf, len);
if (ys == yajl_status_insufficient_data) {
ys = yajl_parse_complete(yh);
}
if (ys == yajl_status_insufficient_data) {
err = "unexpected end of document";
} else if (ys != yajl_status_ok) {
unsigned char *msg = yajl_get_error(yh, 0, NULL, 0);
strncpy(errmsg, (char *)msg, sizeof(errmsg)-1);
yajl_free_error(yh, msg);
errmsg[sizeof(errmsg)] = 0;
err = errmsg;
}
yajl_free(yh);
return err;
}


static ERL_NIF_TERM parse_document_1(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[])
{
/* initialize the state, with a dummy top level container */
state_t st;
st.env = env; /* keep NIF environment in state */
st.key = 0;
ERL_NIF_TERM term;
container_t c = { 0, 1, &term, NULL };
st.c = &c;

ErlNifBinary bin;
if (argc != 1 || !enif_is_binary(env, argv[0]))
return enif_make_badarg(env);
if (!enif_inspect_binary(env, argv[0], &bin))
return enif_make_badarg(env);

const char *err;
if ((err = parse_json(&st, bin.data, bin.size)) != NULL) {
return enif_make_tuple2(env, enif_make_atom(env, "error"),
enif_make_string(env, err, ERL_NIF_LATIN1));
}
return enif_make_tuple2(env, enif_make_atom(env, "ok"), term);
}


static ErlNifFunc json_parser_NIFs[] = {
{"parse_document", 1, &parse_document_1}
};

ERL_NIF_INIT(json_parser, json_parser_NIFs, NULL, NULL, NULL, NULL);


/*
* JSON Erlang json() representation
* ---- ----------------------------
* true 'true'
* false 'false'
* null 'undefined'
* number (integers and floats) number()
* string: "..." binary()
* array/list: [ value, ... ] {json()}
* map: { label: value, ... } [{binary(), json()}]
*/

static void add_element(state_t *st, ERL_NIF_TERM t)
{
container_t *c = st->c;
if (c != NULL) {
if (c->count >= c->arraysz) {
c->arraysz *= 2;
c->array = enif_realloc(st->env, c->array, c->arraysz);
}
if (st->key) {
/* the previous stored element was a key, so replace the entry
with a key/value pair, and don't count it twice */
c->array[c->count-1] = enif_make_tuple2(st->env,
c->array[c->count-1],
t);
st->key = 0;
} else {
c->array[c->count] = t;
++(c->count);
}
}
}

static int handle_null(void *ctx)
{
state_t *st = (state_t *)ctx;
add_element(st, enif_make_atom(st->env, "undefined"));
return 1;
}

static int handle_boolean(void *ctx, int boolVal)
{
state_t *st = (state_t *)ctx;
if (boolVal) {
add_element(st, enif_make_atom(st->env, "true"));
} else {
add_element(st, enif_make_atom(st->env, "false"));
}
return 1;
}

static int handle_integer(void *ctx, long integerVal)
{
state_t *st = (state_t *)ctx;
add_element(st, enif_make_long(st->env, integerVal));
return 1;
}

static int handle_double(void *ctx, double doubleVal)
{
state_t *st = (state_t *)ctx;
add_element(st, enif_make_double(st->env, doubleVal));
return 1;
}

static int handle_string(void *ctx, const unsigned char *stringVal,
unsigned int stringLen)
{
state_t *st = (state_t *)ctx;
ErlNifBinary bin;
enif_alloc_binary(st->env, stringLen, &bin);
strncpy((char *)bin.data, (char *)stringVal, stringLen);
add_element(st, enif_make_binary(st->env, &bin));
return 1;
}

static int handle_map_key(void *ctx, const unsigned char *stringVal,
unsigned int stringLen)
{
state_t *st = (state_t *)ctx;
ErlNifBinary bin;
enif_alloc_binary(st->env, stringLen, &bin);
strncpy((char *)bin.data, (char *)stringVal, stringLen);
add_element(st, enif_make_binary(st->env, &bin));
st->key = 1; /* note that the next term will be the value */
return 1;
}

static int handle_start(void *ctx, int array)
{
state_t *st = (state_t *)ctx;
container_t *c = enif_alloc(st->env, sizeof(container_t));
/* link and initialize container struct */
c->next = st->c;
st->c = c;
c->count = 0;
c->arraysz = 32; /* initial term buffer size */
c->array = enif_alloc(st->env, c->arraysz);
return 1;
}

static int handle_start_map(void *ctx)
{
return handle_start(ctx, 0);
}

static int handle_start_array(void *ctx)
{
return handle_start(ctx, 1);
}

static int handle_end(void *ctx, int array)
{
state_t *st = (state_t *)ctx;
container_t *c = st->c;
/* unlink container struct from state */
st->c = c->next;
/* create and add container term */
if (array) {
add_element(st, enif_make_tuple_from_array(st->env, c->array,
c->count));
} else {
add_element(st, enif_make_list_from_array(st->env, c->array,
c->count));
}
/* decallocate used container struct */
enif_free(st->env, c);
return 1;
}

static int handle_end_map(void *ctx)
{
return handle_end(ctx, 0);
}

static int handle_end_array(void *ctx)
{
return handle_end(ctx, 1);
}
4 changes: 4 additions & 0 deletions chapter_12/nifs/json_parser/doc/.gitignore
@@ -0,0 +1,4 @@
edoc-info
stylesheet.css
erlang.png
*.html
1 change: 1 addition & 0 deletions chapter_12/nifs/json_parser/ebin/.gitignore
@@ -0,0 +1 @@
*.beam
8 changes: 8 additions & 0 deletions chapter_12/nifs/json_parser/ebin/json_parser.app
@@ -0,0 +1,8 @@
%% -*- mode: Erlang; fill-column: 75; comment-column: 50; -*-

{application, json_parser,
[{description, "JSON parser (using plain ports)"},
{vsn, "0.1.0"},
{modules, [json_parser]},
{applications, [kernel, stdlib]}
]}.
Empty file.
1 change: 1 addition & 0 deletions chapter_12/nifs/json_parser/priv/.gitignore
@@ -0,0 +1 @@
parser
25 changes: 25 additions & 0 deletions chapter_12/nifs/json_parser/src/json_parser.erl
@@ -0,0 +1,25 @@
%%%-------------------------------------------------------------------
%%% @doc JSON parser user API.
%%% @end
%%%-------------------------------------------------------------------

-module(json_parser).

-export([init/0, parse_document/1]).

-on_load(init/0).

-define(APPNAME, json_parser).

init() ->
case code:priv_dir(?APPNAME) of
{error, _} ->
error_logger:format("~w priv dir not found~n", [?APPNAME]),
exit(error);
PrivDir ->
erlang:load_nif(filename:join([PrivDir, "jp_nifs"]), 0)
end.

%% @doc Parses a document given as a binary
parse_document(Data) ->
erlang:error(nif_not_loaded). % help Dialyzer and friends

0 comments on commit dd5d41f

Please sign in to comment.