Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added code for nifs version in chapter 12
- Loading branch information
Showing
8 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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); | |||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,4 @@ | |||
edoc-info | |||
stylesheet.css | |||
erlang.png | |||
*.html |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1 @@ | |||
*.beam |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1 @@ | |||
parser |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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 |