blob: 68f1317646d38fb233f0f840b700326e4e44443d [file] [log] [blame]
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "erl_nif.h"
#include "erl_nif_compat.h"
#include "yajl/yajl_parse.h"
#include "yajl/yajl_parser.h"
#include "yajl/yajl_lex.h"
typedef struct {
ERL_NIF_TERM head;
ErlNifEnv* env;
} decode_ctx;
#define ENV(ctxarg) (((decode_ctx*)ctxarg)->env)
#define CONTINUE 1
#define CANCEL 0
static ERL_NIF_TERM
make_error(yajl_handle handle, ErlNifEnv* env)
{
char* yajlError = (char*) yajl_get_error(handle, 0, NULL, 0);
ERL_NIF_TERM errMsg;
if(yajlError != NULL)
{
errMsg = enif_make_string(env, yajlError, ERL_NIF_LATIN1);
yajl_free_error(handle, (unsigned char*) yajlError);
}
else
{
errMsg = enif_make_string(env, "unknown parse error", ERL_NIF_LATIN1);
}
return enif_make_tuple(env, 2,
enif_make_atom(env, "error"),
enif_make_tuple(env, 2,
enif_make_uint(env, handle->bytesConsumed),
errMsg
)
);
}
static void
add_to_head(void* vctx, ERL_NIF_TERM newhead)
{
decode_ctx* ctx = (decode_ctx*)vctx;
ctx->head = enif_make_list_cell(ctx->env, newhead, ctx->head);
}
static int
decode_null(void* ctx)
{
add_to_head(ctx, enif_make_atom(ENV(ctx), "null"));
return CONTINUE;
}
static int
decode_boolean(void* ctx, int val)
{
add_to_head(ctx, enif_make_atom(ENV(ctx), val ? "true" : "false"));
return CONTINUE;
}
static int
decode_number(void * ctx, const char * numberVal, unsigned int numberLen)
{
// scan in the input to see if it's a float or int
int numberType = 0; // 0 means integer, 1 means float
unsigned int i;
ErlNifBinary bin;
int missingDot = 1;
unsigned int expPos;
for(i=0; i<numberLen; i++) {
switch (numberVal[i]) {
case '.':
missingDot = 0;
numberType = 1; // it's a float
goto loopend;
case 'E':
case 'e':
expPos = i;
numberType = 1; // it's a float
goto loopend;
}
}
loopend:
if ((numberType == 1) && missingDot)
{
if(!enif_alloc_binary_compat(ENV(ctx), numberLen + 2, &bin))
{
return CANCEL;
}
memcpy(bin.data, numberVal, expPos);
bin.data[expPos] = '.';
bin.data[expPos + 1] = '0';
memcpy(bin.data + expPos + 2, numberVal + expPos, numberLen - expPos);
}
else
{
if(!enif_alloc_binary_compat(ENV(ctx), numberLen, &bin))
{
return CANCEL;
}
memcpy(bin.data, numberVal, numberLen);
}
add_to_head(ctx, enif_make_tuple(ENV(ctx), 2,
enif_make_int(ENV(ctx), numberType),
enif_make_binary(ENV(ctx), &bin)));
return CONTINUE;
}
static int
decode_string(void* ctx, const unsigned char* data, unsigned int size)
{
ErlNifBinary bin;
if(!enif_alloc_binary_compat(ENV(ctx), size, &bin))
{
return CANCEL;
}
memcpy(bin.data, data, size);
add_to_head(ctx, enif_make_binary(ENV(ctx), &bin));
return CONTINUE;
}
static int
decode_start_array(void* ctx)
{
add_to_head(ctx, enif_make_int(ENV(ctx), 0));
return CONTINUE;
}
static int
decode_end_array(void* ctx)
{
add_to_head(ctx, enif_make_int(ENV(ctx), 1));
return CONTINUE;
}
static int
decode_start_map(void* ctx)
{
add_to_head(ctx, enif_make_int(ENV(ctx), 2));
return CONTINUE;
}
static int
decode_end_map(void* ctx)
{
add_to_head(ctx, enif_make_int(ENV(ctx), 3));
return CONTINUE;
}
static int
decode_map_key(void* ctx, const unsigned char* data, unsigned int size)
{
ErlNifBinary bin;
if(!enif_alloc_binary_compat(ENV(ctx), size, &bin))
{
return CANCEL;
}
memcpy(bin.data, data, size);
add_to_head(ctx, enif_make_tuple(ENV(ctx), 2,
enif_make_int(ENV(ctx), 3),
enif_make_binary(ENV(ctx), &bin)));
return CONTINUE;
}
static yajl_callbacks
decoder_callbacks = {
decode_null,
decode_boolean,
NULL,
NULL,
decode_number,
decode_string,
decode_start_map,
decode_map_key,
decode_end_map,
decode_start_array,
decode_end_array
};
static int
check_rest(unsigned char* data, unsigned int size, unsigned int used)
{
unsigned int i = 0;
for(i = used; i < size; i++)
{
switch(data[i])
{
case ' ':
case '\t':
case '\r':
case '\n':
continue;
default:
return CANCEL;
}
}
return CONTINUE;
}
ERL_NIF_TERM
reverse_tokens(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
decode_ctx ctx;
yajl_parser_config conf = {0, 1}; // No comments, check utf8
yajl_handle handle = yajl_alloc(&decoder_callbacks, &conf, NULL, &ctx);
yajl_status status;
unsigned int used;
ErlNifBinary bin;
ERL_NIF_TERM ret;
ctx.env = env;
ctx.head = enif_make_list_from_array(env, NULL, 0);
if(!enif_inspect_iolist_as_binary(env, argv[0], &bin))
{
ret = enif_make_badarg(env);
goto done;
}
status = yajl_parse(handle, bin.data, bin.size);
used = handle->bytesConsumed;
// Parsing something like "2.0" (without quotes) will
// cause a spurious semi-error. We add the extra size
// check so that "2008-20-10" doesn't pass.
if(status == yajl_status_insufficient_data && used == bin.size)
{
status = yajl_parse_complete(handle);
}
if(status == yajl_status_ok && used != bin.size)
{
if(check_rest(bin.data, bin.size, used) == CANCEL)
{
ret = enif_make_tuple(env, 2,
enif_make_atom(env, "error"),
enif_make_atom(env, "garbage_after_value")
);
goto done;
}
}
switch(status)
{
case yajl_status_ok:
ret = enif_make_tuple(env, 2, enif_make_atom(env, "ok"), ctx.head);
goto done;
case yajl_status_error:
ret = make_error(handle, env);
goto done;
case yajl_status_insufficient_data:
ret = enif_make_tuple(env, 2,
enif_make_atom(env, "error"),
enif_make_atom(env, "insufficient_data")
);
goto done;
case yajl_status_client_canceled:
/* the only time we do this is when we can't allocate a binary. */
ret = enif_make_tuple(env, 2,
enif_make_atom(env, "error"),
enif_make_atom(env, "insufficient_memory")
);
goto done;
default:
ret = enif_make_tuple(env, 2,
enif_make_atom(env, "error"),
enif_make_atom(env, "unknown")
);
goto done;
}
done:
if(handle != NULL) yajl_free(handle);
return ret;
}