Current section

Files

Jump to
fast_yaml c_src fast_yaml.c
Raw

c_src/fast_yaml.c

/*
* Copyright (C) 2002-2023 ProcessOne, SARL. All Rights Reserved.
*
* 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 <yaml.h>
#include <erl_nif.h>
#include <stdlib.h>
#include <assert.h>
#define OK 0
#define ERR_MEMORY_FAIL 1
#define PLAIN_AS_ATOM 1
#define SANE_SCALARS 2
#define MAPS 4
typedef struct events_t {
yaml_event_t *event;
struct events_t *next;
} events_t;
static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
{
return 0;
}
static int reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
{
return load(env, priv, info);
}
static int upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info)
{
return load(env, priv, info);
}
static void unload(ErlNifEnv* env, void* priv)
{
enif_free(priv);
}
static ERL_NIF_TERM make_binary_size(ErlNifEnv* env,
const unsigned char *str,
size_t size)
{
ErlNifBinary b;
enif_alloc_binary(size, &b);
if (str) memcpy(b.data, str, size);
return enif_make_binary(env, &b);
}
static ERL_NIF_TERM make_binary(ErlNifEnv* env, const unsigned char *str)
{
size_t size = 0;
if (str) size = strlen((char *) str);
return make_binary_size(env, str, size);
}
static int make_num(ErlNifEnv* env, const unsigned char *value, size_t size, ERL_NIF_TERM *result)
{
long int i;
double d;
*result = 0;
if (size>0) {
char *buf = enif_alloc(size + 1);
if (buf) {
memcpy(buf, value, size);
buf[size] = '\0';
char *check;
i = strtol(buf, &check, 10);
if (*check == '\0')
*result = enif_make_long(env, i);
else if (*check == '.') {
d = strtod(buf, &check);
if (*check == '\0')
*result = enif_make_double(env, d);
}
enif_free(buf);
if (*result) {
return 0;
}
}
}
return 1;
}
static int make_atom(ErlNifEnv* env, yaml_event_t *event)
{
size_t length = event->data.scalar.length;
yaml_char_t *value = event->data.scalar.value;
ERL_NIF_TERM err;
if (length > 255) {
char *problem = "atom value must not exceed 255 octets in length";
err = enif_make_tuple4(env,
enif_make_atom(env, "parser_error"),
make_binary(env, (unsigned char *) problem),
enif_make_uint(env, event->start_mark.line),
enif_make_uint(env, event->start_mark.column));
return enif_raise_exception(env, err);
} else {
return enif_make_atom_len(env, (char *) value, length);
}
}
static ERL_NIF_TERM make_scalar(ErlNifEnv* env, yaml_event_t *event, int flags, int is_map_value)
{
int as_atom = PLAIN_AS_ATOM & flags;
int sane_scalars = SANE_SCALARS & flags;
yaml_scalar_style_t style = event->data.scalar.style;
ERL_NIF_TERM rterm;
if (sane_scalars) {
if (is_map_value && style == YAML_PLAIN_SCALAR_STYLE) {
if ((!make_num(env, event->data.scalar.value, event->data.scalar.length, &rterm))) {
// rterm filled in make_num
}
else if (!strcmp((char *)event->data.scalar.value, "true")) {
rterm = enif_make_atom(env, "true");
}
else if (!strcmp((char *)event->data.scalar.value, "false")) {
rterm = enif_make_atom(env, "false");
}
else if (!event->data.scalar.length || !strcmp((char *)event->data.scalar.value, "null") || !strcmp((char *)event->data.scalar.value, "~")) {
rterm = enif_make_atom(env, "undefined");
} else {
rterm = make_binary_size(env, event->data.scalar.value, event->data.scalar.length);
}
} else {
rterm = make_binary_size(env, event->data.scalar.value, event->data.scalar.length);
}
} else if (as_atom && style == YAML_SINGLE_QUOTED_SCALAR_STYLE) {
rterm = make_atom(env, event);
} else if (style == YAML_DOUBLE_QUOTED_SCALAR_STYLE) {
rterm = make_binary_size(env, event->data.scalar.value,
event->data.scalar.length);
} else if ((!make_num(env, event->data.scalar.value, event->data.scalar.length, &rterm))) {
// rterm filled in make_num
} else if (as_atom && style == YAML_PLAIN_SCALAR_STYLE && event->data.scalar.length) {
rterm = make_atom(env, event);
} else {
rterm = make_binary_size(env, event->data.scalar.value,
event->data.scalar.length);
}
return rterm;
}
static ERL_NIF_TERM make_alias(ErlNifEnv* env, yaml_event_t *event)
{
return make_binary(env, event->data.alias.anchor);
}
static ERL_NIF_TERM zip(ErlNifEnv* env, ERL_NIF_TERM list)
{
ERL_NIF_TERM key, val, tmp1, tmp2;
if (enif_get_list_cell(env, list, &key, &tmp1)) {
if (enif_get_list_cell(env, tmp1, &val, &tmp2)) {
return enif_make_list_cell(env,
enif_make_tuple2(env, key, val),
zip(env, tmp2));
} else {
return enif_make_list_cell(env, key, enif_make_list(env, 0));
}
} else
return list;
}
static ERL_NIF_TERM map(ErlNifEnv* env, ERL_NIF_TERM pairs)
{
ERL_NIF_TERM ret;
ERL_NIF_TERM key;
ERL_NIF_TERM val;
ret = enif_make_new_map(env);
while(enif_get_list_cell(env, pairs, &val, &pairs)) {
if (!enif_get_list_cell(env, pairs, &key, &pairs)) {
assert(0 == 1 && "Unbalanced object pairs.");
}
if (!enif_make_map_put(env, ret, key, val, &ret)) {
return 0;
}
}
return ret;
}
static ERL_NIF_TERM make_error(ErlNifEnv* env, yaml_parser_t *parser)
{
ERL_NIF_TERM err;
switch (parser->error) {
case YAML_MEMORY_ERROR:
err = enif_make_atom(env, "memory_error");
break;
case YAML_PARSER_ERROR:
err = enif_make_tuple4(env,
enif_make_atom(env, "parser_error"),
make_binary(env, (const unsigned char*) parser->problem),
enif_make_uint(env, parser->problem_mark.line),
enif_make_uint(env, parser->problem_mark.column));
break;
case YAML_SCANNER_ERROR:
err = enif_make_tuple4(env,
enif_make_atom(env, "scanner_error"),
make_binary(env, (const unsigned char*) parser->problem),
enif_make_uint(env, parser->problem_mark.line),
enif_make_uint(env, parser->problem_mark.column));
break;
default:
err = enif_make_atom(env, "unexpected_error");
break;
}
return enif_make_tuple2(env, enif_make_atom(env, "error"), err);
}
static yaml_event_t *next(events_t **events)
{
yaml_event_t *event = NULL;
events_t *tmp;
if (*events) {
event = (*events)->event;
tmp = *events;
*events = (*events)->next;
enif_free(tmp);
}
return event;
}
static void free_events(events_t **events)
{
yaml_event_t *event = NULL;
if (events) {
while (*events) {
event = next(events);
if (event) {
yaml_event_delete(event);
enif_free(event);
}
}
}
}
static ERL_NIF_TERM process_events(ErlNifEnv* env, events_t **events,
yaml_parser_t *parser, int flags, int is_map)
{
ERL_NIF_TERM els, el;
yaml_event_t *event;
els = enif_make_list(env, 0);
int mapping_node = 0;
if (events) {
while (*events) {
event = next(events);
if (event) {
switch (event->type) {
case YAML_SEQUENCE_START_EVENT:
el = process_events(env, events, parser, flags, 0);
els = enif_make_list_cell(env, el, els);
mapping_node = 0;
break;
case YAML_SEQUENCE_END_EVENT:
yaml_event_delete(event);
enif_free(event);
enif_make_reverse_list(env, els, &els);
return els;
case YAML_MAPPING_START_EVENT:
el = process_events(env, events, parser, flags, 1);
els = enif_make_list_cell(env, el, els);
mapping_node = 0;
break;
case YAML_MAPPING_END_EVENT:
yaml_event_delete(event);
enif_free(event);
if (MAPS & flags) {
return map(env, els);
}
enif_make_reverse_list(env, els, &els);
return zip(env, els);
case YAML_SCALAR_EVENT:
el = make_scalar(env, event, flags, is_map ? (mapping_node++) % 2 : 1);
els = enif_make_list_cell(env, el, els);
break;
case YAML_ALIAS_EVENT:
el = make_alias(env, event);
els = enif_make_list_cell(env, el, els);
break;
default:
break;
}
yaml_event_delete(event);
enif_free(event);
} else {
break;
}
}
}
return els;
}
static ERL_NIF_TERM parse(ErlNifEnv* env, yaml_parser_t *parser,
int flags, unsigned char *data, int size)
{
int result = 0, done = 0;
yaml_event_t *event = NULL;
events_t *first_events = NULL;
events_t *prev_events = NULL;
events_t *events = NULL;
ERL_NIF_TERM rterm;
yaml_parser_set_input_string(parser, data, size);
do {
event = enif_alloc(sizeof(yaml_event_t));
result = yaml_parser_parse(parser, event);
if (result) {
events = enif_alloc(sizeof(events_t));
events->event = event;
events->next = NULL;
if (!first_events) {
first_events = events;
}
if (prev_events) {
prev_events->next = events;
}
prev_events = events;
done = (event->type == YAML_STREAM_END_EVENT);
} else {
enif_free(event);
done = 1;
}
} while (!done);
if (result) {
enif_make_reverse_list(env, process_events(env, &first_events, parser, flags, 0), &rterm);
rterm = enif_make_tuple2(env, enif_make_atom(env, "ok"), rterm);
} else {
rterm = make_error(env, parser);
}
free_events(&first_events);
return rterm;
}
static ERL_NIF_TERM decode(ErlNifEnv* env, int argc,
const ERL_NIF_TERM argv[])
{
ErlNifBinary input;
ERL_NIF_TERM result;
unsigned int flags;
yaml_parser_t parser;
if (argc == 2) {
if (enif_inspect_iolist_as_binary(env, argv[0], &input) &&
enif_get_uint(env, argv[1], &flags)) {
yaml_parser_initialize(&parser);
result = parse(env, &parser, flags, input.data, input.size);
yaml_parser_delete(&parser);
return result;
}
}
return enif_make_badarg(env);
}
static ErlNifFunc nif_funcs[] =
{
{"nif_decode", 2, decode}
};
ERL_NIF_INIT(fast_yaml, nif_funcs, load, reload, upgrade, unload)