Browse Source

initial commit

load_floats_from_doubles
Alexandros Theodotou 2 years ago
commit
f8eb024c1a
Signed by: alex GPG Key ID: 022EAE42313D70F3
  1. 85
      .gitignore
  2. 14
      LICENSE
  3. 195
      Makefile
  4. 106
      README.md
  5. 15
      docs/api.doxygen.conf
  6. 15
      docs/devel.doxygen.conf
  7. 155
      docs/guide.md
  8. 7
      examples/numerical/README.md
  9. 8
      examples/numerical/fibonacci.yaml
  10. 107
      examples/numerical/main.c
  11. 8
      examples/planner/README.md
  12. 468
      examples/planner/main.c
  13. 58
      examples/planner/project.yaml
  14. 1616
      include/cyaml/cyaml.h
  15. 10
      libcyaml.pc.in
  16. 59
      meson.build
  17. 142
      src/data.h
  18. 161
      src/free.c
  19. 2508
      src/load.c
  20. 35
      src/mem.c
  21. 103
      src/mem.h
  22. 1470
      src/save.c
  23. 259
      src/utf8.c
  24. 46
      src/utf8.h
  25. 122
      src/util.c
  26. 184
      src/util.h
  27. 19
      test/data/basic.yaml
  28. 5918
      test/units/errs.c
  29. 429
      test/units/file.c
  30. 152
      test/units/free.c
  31. 6530
      test/units/load.c
  32. 4028
      test/units/save.c
  33. 120
      test/units/test.c
  34. 232
      test/units/ttest.h
  35. 271
      test/units/utf8.c
  36. 246
      test/units/util.c

85
.gitignore

@ -0,0 +1,85 @@
# Copyright (C) 2019 Alexandros Theodotou <alex at zrythm dot org>
#
# This file is part of Zrythm
#
# Zrythm is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Zrythm is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Zrythm. If not, see <https://www.gnu.org/licenses/>.
# Build files
build
builddir
_build
# -- C++
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
**/*/*.so*
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
build/*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# -- Vim
# Swap
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
.vimrc
# Session
Session.vim
# Temporary
.netrwhist
*~
*.ui#
**/.deps
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
# Backup files
*.bak
# Valgrind
vgcore.*

14
LICENSE

@ -0,0 +1,14 @@
Copyright (c) 2017-2018, Michael Drake
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

195
Makefile

@ -0,0 +1,195 @@
# SPDX-License-Identifier: ISC
#
# Copyright (C) 2017-2020 Michael Drake <tlsa@netsurf-browser.org>
# CYAML's versioning is <MAJOR>.<MINOR>.<PATCH>[-DEVEL]
# Master branch will always be DEVEL. The release process will be to make
# the release branch, set VESION_DEVEL to 0, and tag the release.
VERSION_MAJOR = 1
VERSION_MINOR = 0
VERSION_PATCH = 0
VERSION_DEVEL = 0
VERSION_STR = $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH)
# Default variant depends on whether it's a development build.
ifeq ($(VERSION_DEVEL), 1)
VARIANT = debug
else
VARIANT = release
endif
# Unfortunately ASan is incompatible with valgrind, so we have a special
# variant for running with sanitisers.
VALID_VARIANTS := release debug san
ifneq ($(filter $(VARIANT),$(VALID_VARIANTS)),)
else
$(error VARIANT must be 'debug' (default), 'san', or 'release')
endif
LIB_NAME = libcyaml
LIB_PKGCON = $(LIB_NAME).pc
LIB_STATIC = $(LIB_NAME).a
LIB_SHARED = $(LIB_NAME).so
LIB_SH_MAJ = $(LIB_SHARED).$(VERSION_MAJOR)
LIB_SH_VER = $(LIB_SHARED).$(VERSION_STR)
.IMPLICIT =
PREFIX ?= /usr/local
LIBDIR ?= lib
INCLUDEDIR ?= include
CC ?= gcc
AR ?= ar
MKDIR = mkdir -p
INSTALL ?= install -c -D
VALGRIND = valgrind --leak-check=full --track-origins=yes
VERSION_FLAGS = -DVERSION_MAJOR=$(VERSION_MAJOR) \
-DVERSION_MINOR=$(VERSION_MINOR) \
-DVERSION_PATCH=$(VERSION_PATCH) \
-DVERSION_DEVEL=$(VERSION_DEVEL)
LIBYAML = yaml-0.1
LIBYAML_CFLAGS := $(if $(PKG_CONFIG),$(shell $(PKG_CONFIG) --cflags $(LIBYAML)),)
LIBYAML_LIBS := $(if $(PKG_CONFIG),$(shell $(PKG_CONFIG) --libs $(LIBYAML)),-lyaml)
INCLUDE = -I include
CFLAGS += $(INCLUDE) $(VERSION_FLAGS) $(LIBYAML_CFLAGS)
CFLAGS += -std=c11 -Wall -Wextra -pedantic -Wconversion
LDFLAGS += $(LIBYAML_LIBS)
LDFLAGS_SHARED += -Wl,-soname=$(LIB_SH_MAJ) -shared
ifeq ($(VARIANT), debug)
CFLAGS += -O0 -g
else ifeq ($(VARIANT), san)
CFLAGS += -O0 -g -fsanitize=address -fsanitize=undefined -fno-sanitize-recover
LDFLAGS += -fsanitize=address -fsanitize=undefined -fno-sanitize-recover
else
CFLAGS += -O2 -DNDEBUG
endif
ifneq ($(filter coverage,$(MAKECMDGOALS)),)
BUILDDIR = build/coverage/$(VARIANT)
CFLAGS_COV = --coverage -DNDEBUG
LDFLAGS_COV = --coverage
else
BUILDDIR = build/$(VARIANT)
CFLAGS_COV =
LDFLAGS_COV =
endif
BUILDDIR_SHARED = $(BUILDDIR)/shared
BUILDDIR_STATIC = $(BUILDDIR)/static
LIB_SRC_FILES = mem.c free.c load.c save.c util.c utf8.c
LIB_SRC := $(addprefix src/,$(LIB_SRC_FILES))
LIB_OBJ = $(patsubst %.c,%.o, $(addprefix $(BUILDDIR)/,$(LIB_SRC)))
LIB_OBJ_SHARED = $(patsubst $(BUILDDIR)%,$(BUILDDIR_SHARED)%,$(LIB_OBJ))
LIB_OBJ_STATIC = $(patsubst $(BUILDDIR)%,$(BUILDDIR_STATIC)%,$(LIB_OBJ))
LIB_PATH = LD_LIBRARY_PATH=$(BUILDDIR)
TEST_SRC_FILES = units/free.c units/load.c units/test.c units/util.c \
units/errs.c units/file.c units/save.c units/utf8.c
TEST_SRC := $(addprefix test/,$(TEST_SRC_FILES))
TEST_OBJ = $(patsubst %.c,%.o, $(addprefix $(BUILDDIR)/,$(TEST_SRC)))
TEST_BINS = \
$(BUILDDIR)/test/units/cyaml-shared \
$(BUILDDIR)/test/units/cyaml-static
all: $(BUILDDIR)/$(LIB_SH_MAJ) $(BUILDDIR)/$(LIB_STATIC) examples
coverage: test-verbose
@$(MKDIR) $(BUILDDIR)
@gcovr -e 'test/.*' -r .
@gcovr -e 'test/.*' -x -o build/coverage.xml -r .
@gcovr -e 'test/.*' --html --html-details -o build/coverage.html -r .
test: $(TEST_BINS)
@for i in $(^); do $(LIB_PATH) $$i || exit; done
test-quiet: $(TEST_BINS)
@for i in $(^); do $(LIB_PATH) $$i -q || exit; done
test-verbose: $(TEST_BINS)
@for i in $(^); do $(LIB_PATH) $$i -v || exit; done
test-debug: $(TEST_BINS)
@for i in $(^); do $(LIB_PATH) $$i -d || exit; done
valgrind: $(TEST_BINS)
@for i in $(^); do $(LIB_PATH) $(VALGRIND) $$i || exit; done
valgrind-quiet: $(TEST_BINS)
@for i in $(^); do $(LIB_PATH) $(VALGRIND) $$i -q || exit; done
valgrind-verbose: $(TEST_BINS)
@for i in $(^); do $(LIB_PATH) $(VALGRIND) $$i -v || exit; done
valgrind-debug: $(TEST_BINS)
@for i in $(^); do $(LIB_PATH) $(VALGRIND) $$i -d || exit; done
$(BUILDDIR)/$(LIB_PKGCON): $(LIB_PKGCON).in
sed \
-e 's#PREFIX#$(PREFIX)#' \
-e 's#LIBDIR#$(LIBDIR)#' \
-e 's#INCLUDEDIR#$(INCLUDEDIR)#' \
-e 's#VERSION#$(VERSION_STR)#' \
$(LIB_PKGCON).in >$(BUILDDIR)/$(LIB_PKGCON)
$(BUILDDIR)/$(LIB_STATIC): $(LIB_OBJ_STATIC)
$(AR) -rcs $@ $^
$(BUILDDIR)/$(LIB_SH_MAJ): $(LIB_OBJ_SHARED)
$(CC) $(LDFLAGS) $(LDFLAGS_COV) $(LDFLAGS_SHARED) -o $@ $^
$(LIB_OBJ_STATIC): $(BUILDDIR_STATIC)/%.o : %.c
@$(MKDIR) $(BUILDDIR_STATIC)/src
$(CC) $(CFLAGS) $(CFLAGS_COV) -c -o $@ $<
$(LIB_OBJ_SHARED): $(BUILDDIR_SHARED)/%.o : %.c
@$(MKDIR) $(BUILDDIR_SHARED)/src
$(CC) $(CFLAGS) -fPIC $(CFLAGS_COV) -c -o $@ $<
docs:
$(MKDIR) build/docs/api
$(MKDIR) build/docs/devel
doxygen docs/api.doxygen.conf
doxygen docs/devel.doxygen.conf
clean:
rm -rf build/
install: $(BUILDDIR)/$(LIB_SH_MAJ) $(BUILDDIR)/$(LIB_STATIC) $(BUILDDIR)/$(LIB_PKGCON)
$(INSTALL) $(BUILDDIR)/$(LIB_SH_MAJ) $(DESTDIR)$(PREFIX)/$(LIBDIR)/$(LIB_SH_VER)
(cd $(DESTDIR)$(PREFIX)/$(LIBDIR) && { ln -s -f $(LIB_SH_VER) $(LIB_SH_MAJ) || { rm -f $(LIB_SH_MAJ) && ln -s $(LIB_SH_VER) $(LIB_SH_MAJ); }; })
(cd $(DESTDIR)$(PREFIX)/$(LIBDIR) && { ln -s -f $(LIB_SH_VER) $(LIB_SHARED) || { rm -f $(LIB_SHARED) && ln -s $(LIB_SH_VER) $(LIB_SHARED); }; })
$(INSTALL) $(BUILDDIR)/$(LIB_STATIC) $(DESTDIR)$(PREFIX)/$(LIBDIR)/$(LIB_STATIC)
chmod 644 $(DESTDIR)$(PREFIX)/$(LIBDIR)/$(LIB_STATIC)
$(INSTALL) -d $(DESTDIR)$(PREFIX)/$(INCLUDEDIR)/cyaml
$(INSTALL) -m 644 include/cyaml/* $(DESTDIR)$(PREFIX)/$(INCLUDEDIR)/cyaml
$(INSTALL) -m 644 $(BUILDDIR)/$(LIB_PKGCON) $(DESTDIR)$(PREFIX)/$(LIBDIR)/pkgconfig/$(LIB_PKGCON)
examples: $(BUILDDIR)/planner $(BUILDDIR)/numerical
$(BUILDDIR)/planner: examples/planner/main.c $(BUILDDIR)/$(LIB_STATIC)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
$(BUILDDIR)/numerical: examples/numerical/main.c $(BUILDDIR)/$(LIB_STATIC)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
.PHONY: all test test-quiet test-verbose test-debug \
valgrind valgrind-quiet valgrind-verbose valgrind-debug \
clean coverage docs install examples
$(BUILDDIR)/test/units/cyaml-static: $(TEST_OBJ) $(BUILDDIR)/$(LIB_STATIC)
$(CC) $(LDFLAGS_COV) -o $@ $^ $(LDFLAGS)
$(BUILDDIR)/test/units/cyaml-shared: $(TEST_OBJ) $(BUILDDIR)/$(LIB_SH_MAJ)
$(CC) $(LDFLAGS_COV) -o $@ $^ $(LDFLAGS)
$(TEST_OBJ): $(BUILDDIR)/%.o : %.c
@$(MKDIR) $(BUILDDIR)/test/units
$(CC) $(CFLAGS) $(CFLAGS_COV) -c -o $@ $<

106
README.md

@ -0,0 +1,106 @@
LibCYAML: Schema-based YAML parsing and serialisation
=====================================================
[![Build Status](https://github.com/tlsa/libcyaml/workflows/CI/badge.svg)](https://github.com/tlsa/libcyaml/actions) [![Code Coverage](https://codecov.io/gh/tlsa/libcyaml/branch/master/graph/badge.svg)](https://codecov.io/gh/tlsa/libcyaml) [![Code Quality](https://img.shields.io/lgtm/grade/cpp/g/tlsa/libcyaml.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/tlsa/libcyaml/alerts/)
LibCYAML is a C library for reading and writing structured YAML documents.
It is written in ISO C11 and licensed under the ISC licence.
Overview
--------
The fundamental idea behind CYAML is to allow applications to construct
schemas which describe both the permissible structure of the YAML documents
to read/write, and the C data structure(s) in which the loaded data is
arranged in memory.
### Goals
* Make it easy to load YAML into client's custom C data structures.
* Good compromise between flexibility and simplicity.
### Features
* Load, Save and Free functions.
* Reads and writes arbitrary client data structures.
* Schema-driven, allowing control over permitted YAML, for example:
- Required / optional mapping fields.
- Allowed / disallowed values.
- Minimum / maximum sequence entry count.
- etc...
* Enumerations and flag words.
* YAML backtraces make it simple for users to fix their YAML to
conform to your schema.
* Uses standard [`libyaml`](https://github.com/yaml/libyaml) library for
low-level YAML read / write.
* Support for YAML aliases and anchors.
Building
--------
To build the library, run:
make
You can control the optimisation and building of asserts by setting
the build variant:
make VARIANT=debug
make VARIANT=release
Another debug build variant which is built with address sanitiser (incompatible
with valgrind) can be built with:
make VARIANT=san
Installation
------------
To install a release version of the library, run:
make install VARIANT=release
It will install to the PREFIX `/usr/local` by default, and it will use
DESTDIR and PREFIX from the environment if set.
Testing
-------
To run the tests, run any of the following, which generate various
levels of output verbosity (optionally setting `VARIANT=release`, or
`VARIANT=san`):
make test
make test-quiet
make test-verbose
make test-debug
To run the tests under `valgrind`, a similar set of targets is available:
make valgrind
make valgrind-quiet
make valgrind-verbose
make valgrind-debug
To generate a test coverage report, `gcovr` is required:
make coverage
Documentation
-------------
To generate both public API documentation, and documentation of CYAML's
internals, `doxygen` is required:
make docs
Alternatively, the read the API documentation directly from the
[cyaml.h](https://github.com/tlsa/libcyaml/blob/master/include/cyaml/cyaml.h)
header file.
There is also a [tutorial](docs/guide.md).
Examples
--------
In addition to the documentation, you can study the [examples](examples/).

15
docs/api.doxygen.conf

@ -0,0 +1,15 @@
# SPDX-License-Identifier: ISC
# This generates documentation for developers using libcyaml.
PROJECT_NAME = "CYAML"
OUTPUT_DIRECTORY = "build/docs/api"
OPTIMIZE_OUTPUT_FOR_C = YES
EXTRACT_STATIC = NO
MARKDOWN_SUPPORT = YES
INPUT = include README.md docs/guide.md
RECURSIVE = YES
USE_MDFILE_AS_MAINPAGE = README.md
GENERATE_HTML = YES
GENERATE_LATEX = NO
QUIET = YES

15
docs/devel.doxygen.conf

@ -0,0 +1,15 @@
# SPDX-License-Identifier: ISC
# This generates documentation for developers developing libcyaml.
PROJECT_NAME = "CYAML Internals"
OUTPUT_DIRECTORY = "build/docs/devel"
OPTIMIZE_OUTPUT_FOR_C = YES
EXTRACT_STATIC = YES
MARKDOWN_SUPPORT = YES
INPUT = include src README.md docs/guide.md
RECURSIVE = YES
USE_MDFILE_AS_MAINPAGE = README.md
GENERATE_HTML = YES
GENERATE_LATEX = NO
QUIET = YES

155
docs/guide.md

@ -0,0 +1,155 @@
LibCYAML: Tutorial
==================
This document is intended for C developers wishing to make use of
[LibCYAML](https://github.com/tlsa/libcyaml).
Overview
--------
If you want to use LibCYAML you'll need to have two things:
1. A consistent structure to the sort of YAML you want to load/save.
2. Some C data structure to load/save to/from.
LibCYAML's aim is to make this as simple as possible for the programmer.
However, LibCYAML knows nothing about either your data structure or the
"shape" of the YAML you want to load. You provide this information by
defining "schemas", and passing them to LibCYAML.
> **Note**: If you need to handle arbitrary "free-form" YAML (e.g. for a tool
> to convert between YAML and JSON), then LibCYAML would not be much help.
> In such a case, I'd recommend using [libyaml](https://github.com/yaml/libyaml)
> directly.
A simple example: loading YAML
------------------------------
Let's say you want to load the following YAML document:
```yaml
name: Fibonacci
data:
- 1
- 1
- 2
- 3
- 5
- 8
```
And you want to load it into the following C data structure:
```c
struct numbers {
char *name;
int *data;
unsigned data_count;
};
```
Then we need to define a CYAML schema to describe these to LibCYAML.
> **Note**: Use the doxygen API documentation, or else the documentation in
> [cyaml.h](https://github.com/tlsa/libcyaml/blob/master/include/cyaml/cyaml.h)
> in conjunction with this guide.
At the top level of the YAML is a mapping with two fields, "name" and
"data". The the first field is just a simple scalar value (it's neither
a mapping nor a sequence). The second field has a sequence value.
We'll start by defining the CYAML schema for the "data" sequence,
since since that's the "deepest" non-scalar type. The reason for
starting here will become clear later.
```c
/* CYAML value schema for entries of the data sequence. */
static const cyaml_schema_value_t data_entry = {
CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int),
};
```
Here we're making a `cyaml_schema_value_t` for the entries in the
sequence. There are various `CYAML_VALUE_{TYPE}` macros to assist with
this. Here we're using `CYAML_VALUE_INT`, because the value is a signed
integer. The parameters passed to the macro are `enum cyaml_flag`, and
the C data type of the value.
Now we can write the schema for the mapping. First we'll construct
an array of `cyaml_schema_field_t` entries that describe each
field in the mapping.
```c
/* CYAML mapping schema fields array for the top level mapping. */
static const cyaml_schema_field_t top_mapping_schema[] = {
CYAML_FIELD_STRING_PTR("name", CYAML_FLAG_POINTER,
struct numbers, name,
0, CYAML_UNLIMITED),
CYAML_FIELD_SEQUENCE("data", CYAML_FLAG_POINTER,
struct numbers, data, &data_entry,
0, CYAML_UNLIMITED),
CYAML_FIELD_END
};
```
There are `CYAML_FIELD_{TYPE}` helper macros to construct the mapping field
entries. The array must be terminated by a `CYAML_FIELD_END` entry.
The helper macro parameters are specific to each `CYAML_FIELD_{TYPE}` macro.
The entry for the name field is of type string pointer. You can consult the
documentation for the `CYAML_FIELD_{TYPE}` macros to see what the parameters
mean.
> **Note**: The field for the sequence takes a pointer to the sequence entry
> data type that we defined earlier as `data_entry`.
Finally we can define the schema for the top level value that gets passed to
the LibCYAML.
```c
/* CYAML value schema for the top level mapping. */
static const cyaml_schema_value_t top_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct numbers, top_mapping_schema),
};
```
In this case our top level value is a mapping type. One of the parameters
needed for mappings is the array of field definitions. In this case we're
passing the `top_mapping_schema` that we defined above.
```c
/* Create our CYAML configuration. */
static const cyaml_config_t config = {
.log_level = CYAML_LOG_WARNING, /* Logging errors and warnings only. */
.log_fn = cyaml_log, /* Use the default logging function. */
.mem_fn = cyaml_mem, /* Use the default memory allocator. */
};
/* Where to store the loaded data */
struct numbers *n;
/* Load the file into p */
cyaml_err_t err = cyaml_load_file(argv[ARG_PATH_IN], &config,
&top_schema, (cyaml_data_t **)&n, NULL);
if (err != CYAML_OK) {
/* Handle error */
}
/* Use the data. */
printf("%s:\n", n->name);
for (unsigned i = 0; i < n->data_count; i++) {
printf(" - %i\n", n->data[i]);
}
/* Free the data */
err = cyaml_free(&config, &top_schema, n, 0);
if (err != CYAML_OK) {
/* Handle error */
}
```
And that's it, the YAML is loaded into the custom C data structure.
You can find the code for this in the "numerical" example in the
[examples](../examples) directory.

7
examples/numerical/README.md

@ -0,0 +1,7 @@
Simple LibCYAML example
=======================
This has a simple YAML document for the Fibonacci sequence, and an example
LibCYAML binding to load/free the data from C.
The [tutorial](../../docs/guide.md) explains how this works.

8
examples/numerical/fibonacci.yaml

@ -0,0 +1,8 @@
name: Fibonacci
data:
- 1
- 1
- 2
- 3
- 5
- 8

107
examples/numerical/main.c

@ -0,0 +1,107 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2018 Michael Drake <tlsa@netsurf-browser.org>
*/
#include <stdlib.h>
#include <stdio.h>
#include <cyaml/cyaml.h>
/******************************************************************************
* C data structure for storing a project plan.
*
* This is what we want to load the YAML into.
******************************************************************************/
/* Structure for storing numerical sequence */
struct numbers {
char *name;
int *data;
unsigned data_count;
};
/******************************************************************************
* CYAML schema to tell libcyaml about both expected YAML and data structure.
*
* (Our CYAML schema is just a bunch of static const data.)
******************************************************************************/
/* CYAML value schema for entries of the data sequence. */
static const cyaml_schema_value_t data_entry = {
CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int),
};
/* CYAML mapping schema fields array for the top level mapping. */
static const cyaml_schema_field_t top_mapping_schema[] = {
CYAML_FIELD_STRING_PTR("name", CYAML_FLAG_POINTER,
struct numbers, name,
0, CYAML_UNLIMITED),
CYAML_FIELD_SEQUENCE("data", CYAML_FLAG_POINTER,
struct numbers, data, &data_entry,
0, CYAML_UNLIMITED),
CYAML_FIELD_END
};
/* CYAML value schema for the top level mapping. */
static const cyaml_schema_value_t top_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct numbers, top_mapping_schema),
};
/******************************************************************************
* Actual code to load and save YAML doc using libcyaml.
******************************************************************************/
/* Our CYAML config.
*
* If you want to change it between calls, don't make it const.
*
* Here we have a very basic config.
*/
static const cyaml_config_t config = {
.log_level = CYAML_LOG_WARNING, /* Logging errors and warnings only. */
.log_fn = cyaml_log, /* Use the default logging function. */
.mem_fn = cyaml_mem, /* Use the default memory allocator. */
};
/* Main entry point from OS. */
int main(int argc, char *argv[])
{
cyaml_err_t err;
struct numbers *n;
enum {
ARG_PROG_NAME,
ARG_PATH_IN,
ARG__COUNT,
};
/* Handle args */
if (argc != ARG__COUNT) {
fprintf(stderr, "Usage:\n");
fprintf(stderr, " %s <INPUT>\n", argv[ARG_PROG_NAME]);
return EXIT_FAILURE;
}
/* Load input file. */
err = cyaml_load_file(argv[ARG_PATH_IN], &config,
&top_schema, (cyaml_data_t **)&n, NULL);
if (err != CYAML_OK) {
fprintf(stderr, "ERROR: %s\n", cyaml_strerror(err));
return EXIT_FAILURE;
}
/* Use the data. */
printf("%s:\n", n->name);
for (unsigned i = 0; i < n->data_count; i++) {
printf(" - %i\n", n->data[i]);
}
/* Free the data */
cyaml_free(&config, &top_schema, n, 0);
return EXIT_SUCCESS;
}

8
examples/planner/README.md

@ -0,0 +1,8 @@
Simple LibCYAML example
=======================
This has a simple YAML document for project planning, and an example
LibCYAML binding to load/modify/save/free the data from C.
The C source code is heavliy documented to help explain how to
write a CYAML schema data structure.

468
examples/planner/main.c

@ -0,0 +1,468 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2018 Michael Drake <tlsa@netsurf-browser.org>
*/
#include <stdlib.h>
#include <stdio.h>
#include <cyaml/cyaml.h>
/******************************************************************************
* C data structure for storing a project plan.
*
* This is what we want to load the YAML into.
******************************************************************************/
/* Enumeration of months of the year */
enum months {
MONTH_JAN = 1,
MONTH_FEB,
MONTH_MAR,
MONTH_APR,
MONTH_MAY,
MONTH_JUN,
MONTH_JUL,
MONTH_AUG,
MONTH_SEP,
MONTH_OCT,
MONTH_NOV,
MONTH_DEC
};
/* Structure for storing dates */
struct date {
uint8_t day;
enum months month;
uint16_t year;
};
/* Structure for storing durations */
struct duration {
uint8_t hours;
unsigned days;
unsigned weeks;
unsigned years;
};
/* Enumeration of task flags */
enum task_flags {
FLAGS_NONE = 0,
FLAGS_IMPORTANT = (1 << 0),
FLAGS_ENGINEERING = (1 << 1),
FLAGS_DOCUMENTATION = (1 << 2),
FLAGS_MANAGEMENT = (1 << 3),
};
/* Structure for storing a task */
struct task {
const char *name;
enum task_flags flags;
struct duration estimate;
const char **depends;
unsigned depends_count;
const char **people;
unsigned n_people;
};
/* Top-level structure for storing a plan */
struct plan {
const char *name;
struct date *start;
const char **people;
unsigned n_people;
struct task *tasks;
uint64_t tasks_count;
};
/******************************************************************************
* CYAML schema to tell libcyaml about both expected YAML and data structure.
*
* (Our CYAML schema is just a bunch of static const data.)
******************************************************************************/
/* Mapping from "task_flags" strings to enum values for schema. */
static const cyaml_strval_t task_flags_strings[] = {
{ "None", FLAGS_NONE },
{ "Important", FLAGS_IMPORTANT },
{ "Engineering", FLAGS_ENGINEERING },
{ "Documentation", FLAGS_DOCUMENTATION },
{ "Management", FLAGS_MANAGEMENT },
};
/* Mapping from "month" strings to flag values for schema. */
static const cyaml_strval_t month_strings[] = {
{ "January", MONTH_JAN },
{ "February", MONTH_FEB },
{ "March", MONTH_MAR },
{ "April", MONTH_APR },
{ "May", MONTH_MAY },
{ "June", MONTH_JUN },
{ "July", MONTH_JUL },
{ "August", MONTH_AUG },
{ "September", MONTH_SEP },
{ "October", MONTH_OCT },
{ "November", MONTH_NOV },
{ "December", MONTH_DEC },
};
/* Schema for string pointer values (used in sequences of strings). */
static const cyaml_schema_value_t string_ptr_schema = {
CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char, 0, CYAML_UNLIMITED),
};
/* The duration mapping's field definitions schema is an array.
*
* All the field entries will refer to their parent mapping's structure,
* in this case, `struct duration`.
*/
static const cyaml_schema_field_t duration_fields_schema[] = {
/* The fields here are all optional unsigned integers.
*
* Note that if an optional field is unset in the YAML, its value
* will be zero in the C data structure.
*
* In all cases here, the YAML mapping key name (first parameter to
* the macros) matches the name of the associated member of the
* `duration` structure (fourth parameter).
*/
CYAML_FIELD_UINT(
"hours", CYAML_FLAG_OPTIONAL,
struct duration, hours),
CYAML_FIELD_UINT(
"days", CYAML_FLAG_OPTIONAL,
struct duration, days),
CYAML_FIELD_UINT(
"weeks", CYAML_FLAG_OPTIONAL,
struct duration, weeks),
CYAML_FIELD_UINT(
"years", CYAML_FLAG_OPTIONAL,
struct duration, years),
/* The field array must be terminated by an entry with a NULL key.
* Here we use the CYAML_FIELD_END macro for that. */
CYAML_FIELD_END
};
/* The date mapping's field definitions schema is an array.
*
* All the field entries will refer to their parent mapping's structure,
* in this case, `struct date`.
*/
static const cyaml_schema_field_t date_fields_schema[] = {
/* The "day" and "year" fields are just normal UNIT CYAML types.
* See the comments for duration_fields_schema for details.
* The only difference is neither of these are optional.
* Note: The order of the fields in this array doesn't matter.
*/
CYAML_FIELD_UINT(
"day", CYAML_FLAG_DEFAULT,
struct date, day),
CYAML_FIELD_UINT(
"year", CYAML_FLAG_DEFAULT,
struct date, year),
/* The month field is an enum.
*
* YAML key: "month".
* C structure member for this key: "month".
* Array mapping strings to values: month_strings
*
* Its CYAML type is ENUM, so an array of cyaml_strval_t must be
* provided to map from string to values.
* Note that we're not setting the strict flag here so both strings and
* numbers are accepted in the YAML. (For example, both "4" and "April"
* would be accepted.)
*/
CYAML_FIELD_ENUM(
"month", CYAML_FLAG_DEFAULT,
struct date, month, month_strings,
CYAML_ARRAY_LEN(month_strings)),
/* The field array must be terminated by an entry with a NULL key.
* Here we use the CYAML_FIELD_END macro for that. */
CYAML_FIELD_END
};
/* The task mapping's field definitions schema is an array.
*
* All the field entries will refer to their parent mapping's structure,
* in this case, `struct task`.
*/
static const cyaml_schema_field_t task_fields_schema[] = {
/* The first field in the mapping is a task name.
*
* YAML key: "name".
* C structure member for this key: "name".
*
* Its CYAML type is string pointer, and we have no minimum or maximum
* string length constraints.
*/
CYAML_FIELD_STRING_PTR(
"name", CYAML_FLAG_POINTER,
struct task, name, 0, CYAML_UNLIMITED),
/* The flags field is a flags value.
*
* YAML key: "flags".
* C structure member for this key: "flags".
* Array mapping strings to values: task_flags_strings
*
* In the YAML a CYAML flags value should be a sequence of scalars.
* The values of each set scalar is looked up the in array of
* string/value mappings, and the values are bitwise ORed together.
*
* Note that we're setting the strict flag here so only strings
* present in task_flags_strings are allowed, and numbers are not.
*
* We make the field optional so when there are no flags set, the field
* can be omitted from the YAML.
*/
CYAML_FIELD_FLAGS(
"flags", CYAML_FLAG_OPTIONAL | CYAML_FLAG_STRICT,
struct task, flags, task_flags_strings,
CYAML_ARRAY_LEN(task_flags_strings)),
/* The next field is the task estimate.
*
* YAML key: "estimate".
* C structure member for this key: "estimate".
*
* Its CYAML type is a mapping.
*
* Since it's a mapping type, the schema for its mapping's fields must
* be provided too. In this case, it's `duration_fields_schema`.
*/
CYAML_FIELD_MAPPING(
"estimate", CYAML_FLAG_DEFAULT,
struct task, estimate, duration_fields_schema),
/* The next field is the tasks that this task depends on.
*
* YAML key: "depends".
* C structure member for this key: "depends".
*
* Its CYAML type is a sequence.
*
* Since it's a sequence type, the value schema for its entries must
* be provided too. In this case, it's string_ptr_schema.
*
* Since it's not a sequence of a fixed-length, we must tell CYAML
* where the sequence entry count is to be stored. In this case, it
* goes in the "depends_count" C structure member in `struct task`.
* Since this is "depends" with the "_count" postfix, we can use
* the following macro, which assumes a postfix of "_count" in the
* struct member name.
*/
CYAML_FIELD_SEQUENCE(
"depends", CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL,
struct task, depends,
&string_ptr_schema, 0, CYAML_UNLIMITED),
/* The next field is the task people.
*
* YAML key: "people".
* C structure member for this key: "people".
*
* Its CYAML type is a sequence.
*
* Since it's a sequence type, the value schema for its entries must
* be provided too. In this case, it's string_ptr_schema.
*
* Since it's not a sequence of a fixed-length, we must tell CYAML
* where the sequence entry count is to be stored. In this case, it
* goes in the "n_people" C structure member in `struct plan`.
*/
CYAML_FIELD_SEQUENCE_COUNT(
"people", CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL,
struct task, people, n_people,
&string_ptr_schema, 0, CYAML_UNLIMITED),
/* The field array must be terminated by an entry with a NULL key.
* Here we use the CYAML_FIELD_END macro for that. */
CYAML_FIELD_END
};
/* The value for a task is a mapping.
*
* Its fields are defined in task_fields_schema.
*/
static const cyaml_schema_value_t task_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT,
struct task, task_fields_schema),
};
/* The plan mapping's field definitions schema is an array.
*
* All the field entries will refer to their parent mapping's structure,
* in this case, `struct plan`.
*/
static const cyaml_schema_field_t plan_fields_schema[] = {
/* The first field in the mapping is a project name.
*
* YAML key: "project".
* C structure member for this key: "name".
*
* Its CYAML type is string pointer, and we have no minimum or maximum
* string length constraints.
*/
CYAML_FIELD_STRING_PTR(
"project", CYAML_FLAG_POINTER,
struct plan, name, 0, CYAML_UNLIMITED),
/* The next field is the project start date.
*
* YAML key: "start".
* C structure member for this key: "start".
*
* Its CYAML type is a mapping pointer.
*
* Since it's a mapping type, the schema for its mapping's fields must
* be provided too. In this case, it's `date_fields_schema`.
*/
CYAML_FIELD_MAPPING_PTR(
"start", CYAML_FLAG_POINTER,
struct plan, start, date_fields_schema),
/* The next field is the project people.
*
* YAML key: "people".
* C structure member for this key: "people".
*
* Its CYAML type is a sequence.
*
* Since it's a sequence type, the value schema for its entries must
* be provided too. In this case, it's string_ptr_schema.
*
* Since it's not a sequence of a fixed-length, we must tell CYAML
* where the sequence entry count is to be stored. In this case, it
* goes in the "n_people" C structure member in `struct plan`.
*/
CYAML_FIELD_SEQUENCE_COUNT(
"people", CYAML_FLAG_POINTER,
struct plan, people, n_people,
&string_ptr_schema, 0, CYAML_UNLIMITED),
/* The next field is the project tasks.
*
* YAML key: "tasks".
* C structure member for this key: "tasks".
*
* Its CYAML type is a sequence.
*
* Since it's a sequence type, the value schema for its entries must
* be provided too. In this case, it's task_schema.
*
* Since it's not a sequence of a fixed-length, we must tell CYAML
* where the sequence entry count is to be stored. In this case, it
* goes in the "tasks_count" C structure member in `struct plan`.
* Since this is "tasks" with the "_count" postfix, we can use
* the following macro, which assumes a postfix of "_count" in the
* struct member name.
*/
CYAML_FIELD_SEQUENCE(
"tasks", CYAML_FLAG_POINTER,
struct plan, tasks,
&task_schema, 0, CYAML_UNLIMITED),
/* If the YAML contains a field that our program is not interested in
* we can ignore it, so the value of the field will not be loaded.
*
* Note that unless the OPTIONAL flag is set, the ignored field must
* be present.
*
* Another way of handling this would be to use the config flag
* to ignore unknown keys. This config is passed to libcyaml
* separately from the schema.
*/
CYAML_FIELD_IGNORE("irrelevant", CYAML_FLAG_OPTIONAL),
/* The field array must be terminated by an entry with a NULL key.
* Here we use the CYAML_FIELD_END macro for that. */
CYAML_FIELD_END
};
/* Top-level schema. The top level value for the plan is a mapping.
*
* Its fields are defined in plan_fields_schema.
*/
static const cyaml_schema_value_t plan_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct plan, plan_fields_schema),
};
/******************************************************************************
* Actual code to load and save YAML doc using libcyaml.
******************************************************************************/
/* Our CYAML config.
*
* If you want to change it between calls, don't make it const.
*
* Here we have a very basic config.
*/
static const cyaml_config_t config = {
.log_level = CYAML_LOG_WARNING, /* Logging errors and warnings only. */
.log_fn = cyaml_log, /* Use the default logging function. */
.mem_fn = cyaml_mem, /* Use the default memory allocator. */
};
/* Main entry point from OS. */
int main(int argc, char *argv[])
{
cyaml_err_t err;
struct plan *plan;
enum {
ARG_PROG_NAME,
ARG_PATH_IN,
ARG_PATH_OUT,
ARG__COUNT,
};
/* Handle args */
if (argc != ARG__COUNT) {
fprintf(stderr, "Usage:\n");
fprintf(stderr, " %s <INPUT> <OUTPUT>\n", argv[ARG_PROG_NAME]);
return EXIT_FAILURE;
}
/* Load input file. */
err = cyaml_load_file(argv[ARG_PATH_IN], &config,
&plan_schema, (void **) &plan, NULL);
if (err != CYAML_OK) {
fprintf(stderr, "ERROR: %s\n", cyaml_strerror(err));
return EXIT_FAILURE;
}
/* Use the data. */
printf("Project: %s\n", plan->name);
for (unsigned i = 0; i < plan->tasks_count; i++) {
printf("%u. %s\n", i + 1, plan->tasks[i].name);
}
/* Modify the data */
plan->tasks[0].estimate.days += 3;
plan->tasks[0].estimate.weeks += 1;
/* Save data to new YAML file. */
err = cyaml_save_file(argv[ARG_PATH_OUT], &config,
&plan_schema, plan, 0);
if (err != CYAML_OK) {
fprintf(stderr, "ERROR: %s\n", cyaml_strerror(err));
cyaml_free(&config, &plan_schema, plan, 0);
return EXIT_FAILURE;
}
/* Free the data */
cyaml_free(&config, &plan_schema, plan, 0);
return EXIT_SUCCESS;
}

58
examples/planner/project.yaml

@ -0,0 +1,58 @@
project: Write new browser layout engine.
start:
day: 1
month: June
year: 2018
people:
- Stephen
- Neil
- Alex
irrelevant:
- details:
- the app doesn't need this stuff
- so it should be able to ignore it
tasks:
- name: Read the HTML and CSS specs.
flags:
- Important
estimate:
weeks: 2
people:
- Stephen
- Neil
- name: Think of name for library.
estimate:
hours: 1
- name: Create project repo.
estimate:
hours: 1
depends:
- Think of name for library.
people:
- Alex
- name: Initial design of library API.
flags:
- Important
- Engineering
depends:
- Read the HTML and CSS specs.
- Create project repo.
estimate:
days: 1
- name: Plan the initial implementation
flags:
- Engineering
- Documentation
- Management
depends:
- Initial design of library API.
estimate:
days: 6
people:
- Stephen
- Alex

1616
include/cyaml/cyaml.h
File diff suppressed because it is too large
View File

10
libcyaml.pc.in

@ -0,0 +1,10 @@
prefix=PREFIX
exec_prefix=${prefix}
libdir=${exec_prefix}/LIBDIR
includedir=${prefix}/INCLUDEDIR
Name: libcyaml
Description: Schema-based YAML parsing and serialisation
Version: VERSION
Libs: -L${libdir} -lcyaml -lyaml
Cflags: -I${includedir}

59
meson.build

@ -0,0 +1,59 @@
# Copyright (C) 2019-2020 Alexandros Theodotou <alex at zrythm dot org>
#
# This file is part of Zrythm
#
# Zrythm is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Zrythm is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Zrythm. If not, see <https://www.gnu.org/licenses/>.
project (
'zrythm-cyaml', ['c'],
version: '1.0.0',
license: 'ISC',
default_options: [
'warning_level=1',
'buildtype=debug',
'c_std=gnu11',
],
)
cc = meson.get_compiler ('c')
cargs = [
'-DVERSION_MAJOR=1',
'-DVERSION_MINOR=0',
'-DVERSION_PATCH=0',
]
yaml_dep = dependency('yaml-0.1')
zrythm_cyaml_inc = [
include_directories ('include'),
]
zrythm_cyaml = static_library (
'cyaml',
sources: [
join_paths ('src', 'mem.c'),
join_paths ('src', 'save.c'),
join_paths ('src', 'free.c'),
join_paths ('src', 'load.c'),
join_paths ('src', 'utf8.c'),
join_paths ('src', 'util.c'),
],
c_args: cargs,
dependencies: yaml_dep,
include_directories: zrythm_cyaml_inc,
)
zrythm_cyaml_dep = declare_dependency (
include_directories: 'include',
link_with: zrythm_cyaml)

142
src/data.h

@ -0,0 +1,142 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2017 Michael Drake <tlsa@netsurf-browser.org>
*/
/**
* \file
* \brief CYAML functions for manipulating client data structures.
*/
#ifndef CYAML_DATA_H
#define CYAML_DATA_H
#include "cyaml/cyaml.h"
#include "util.h"
/**
* Write a value of up to eight bytes to data_target.
*
* \param[in] value The value to write.
* \param[in] entry_size The number of bytes of value to write.
* \param[in] data_tgt The address to write to.
* \return \ref CYAML_OK on success, or appropriate error code otherwise.
*/
static inline cyaml_err_t cyaml_data_write(
uint64_t value,
uint64_t entry_size,
uint8_t *data_tgt)
{
if (entry_size == 0) {
return CYAML_ERR_INVALID_DATA_SIZE;
}
data_tgt += entry_size - 1;
switch (entry_size) {
case 8: *data_tgt-- = (uint8_t)(value >> 56) & 0xff; /* Fall through. */
case 7: *data_tgt-- = (uint8_t)(value >> 48) & 0xff; /* Fall through. */
case 6: *data_tgt-- = (uint8_t)(value >> 40) & 0xff; /* Fall through. */
case 5: *data_tgt-- = (uint8_t)(value >> 32) & 0xff; /* Fall through. */
case 4: *data_tgt-- = (uint8_t)(value >> 24) & 0xff; /* Fall through. */
case 3: *data_tgt-- = (uint8_t)(value >> 16) & 0xff; /* Fall through. */
case 2: *data_tgt-- = (uint8_t)(value >> 8) & 0xff; /* Fall through. */
case 1: *data_tgt-- = (uint8_t)(value >> 0) & 0xff;
break;
default:
return CYAML_ERR_INVALID_DATA_SIZE;
}
return CYAML_OK;
}
/**
* Write a pointer to data.
*
* This is a wrapper for \ref cyaml_data_write that does a compile time
* assertion on the pointer size, so it can never return a runtime error.
*
* \param[in] ptr The pointer address to write.
* \param[in] data_target The address to write to.
*/
static inline void cyaml_data_write_pointer(
const void *ptr,
uint8_t *data_target)
{
/* Refuse to build on platforms where sizeof pointer would
* lead to \ref CYAML_ERR_INVALID_DATA_SIZE. */
cyaml_static_assert(sizeof(char *) > 0);
cyaml_static_assert(sizeof(char *) <= sizeof(uint64_t));
CYAML_UNUSED(cyaml_data_write((uint64_t)ptr, sizeof(ptr), data_target));
return;
}
/**
* Read a value of up to eight bytes from data.
*
* \param[in] entry_size The number of bytes to read.
* \param[in] data The address to read from.
* \param[out] error_out Returns the error code. \ref CYAML_OK on success,
* or appropriate error otherwise.
* \return On success, returns the value read from data.
* On failure, returns 0.
*/
static inline uint64_t cyaml_data_read(
uint64_t entry_size,
const uint8_t *data,
cyaml_err_t *error_out)
{
uint64_t ret = 0;
if (entry_size == 0) {
*error_out = CYAML_ERR_INVALID_DATA_SIZE;
return ret;
}
data += entry_size - 1;
switch (entry_size) {
case 8: ret |= ((uint64_t)(*data-- & 0xff)) << 56; /* Fall through. */
case 7: ret |= ((uint64_t)(*data-- & 0xff)) << 48; /* Fall through. */
case 6: ret |= ((uint64_t)(*data-- & 0xff)) << 40; /* Fall through. */
case 5: ret |= ((uint64_t)(*data-- & 0xff)) << 32; /* Fall through. */
case 4: ret |= ((uint64_t)(*data-- & 0xff)) << 24; /* Fall through. */
case 3: ret |= ((uint64_t)(*data-- & 0xff)) << 16; /* Fall through. */
case 2: ret |= ((uint64_t)(*data-- & 0xff)) << 8; /* Fall through. */
case 1: ret |= ((uint64_t)(*data-- & 0xff)) << 0;
break;
default:
*error_out = CYAML_ERR_INVALID_DATA_SIZE;
return ret;
}
*error_out = CYAML_OK;
return ret;
}
/**
* Read a pointer from data.
*
* This is a wrapper for \ref cyaml_data_read that does a compile time
* assertion on the pointer size, so it can never return a runtime error.
*
* \param[in] data The address to read from.
* \return Returns the value read from data.
*/
static inline uint8_t * cyaml_data_read_pointer(
const uint8_t *data)
{
cyaml_err_t err;
/* Refuse to build on platforms where sizeof pointer would
* lead to \ref CYAML_ERR_INVALID_DATA_SIZE. */
cyaml_static_assert(sizeof(char *) > 0);
cyaml_static_assert(sizeof(char *) <= sizeof(uint64_t));
return (void *)cyaml_data_read(sizeof(char *), data, &err);
}
#endif

161
src/free.c

@ -0,0 +1,161 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2017-2018 Michael Drake <tlsa@netsurf-browser.org>
*/
/**
* \file
* \brief Free data structures created by the CYAML load functions.
*
* As described in the public API for \ref cyaml_free(), it is preferable for
* clients to write their own free routines, tailored for their data structure.
*
* Recursion and stack usage
* -------------------------
*
* This generic CYAML free routine is implemented using recursion, rather
* than iteration with a heap-allocated stack. This is because recursion
* seems less bad than allocating within the free code, and the stack-cost
* of these functions isn't huge. The maximum recursion depth is of course
* bound by the schema, however schemas for recursively nesting data structures
* are unbound, e.g. for a data tree structure.
*/
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include "data.h"
#include "util.h"
#include "mem.h"
/**
* Internal function for freeing a CYAML-parsed data structure.
*
* \param[in] cfg The client's CYAML library config.
* \param[in] schema The schema describing how to free `data`.
* \param[in] data The data structure to be freed.
* \param[in] count If data is of type \ref CYAML_SEQUENCE, this is the
* number of entries in the sequence.
*/
static void cyaml__free_value(
const cyaml_config_t *cfg,
const cyaml_schema_value_t *schema,
uint8_t * data,
uint64_t count);
/**
* Internal function for freeing a CYAML-parsed sequence.
*
* \param[in] cfg The client's CYAML library config.
* \param[in] sequence_schema The schema describing how to free `data`.
* \param[in] data The data structure to be freed.
* \param[in] count The sequence's entry count.
*/
static void cyaml__free_sequence(
const cyaml_config_t *cfg,
const cyaml_schema_value_t *sequence_schema,
uint8_t * const data,
uint64_t count)
{
const cyaml_schema_value_t *schema = sequence_schema->sequence.entry;
uint32_t data_size = schema->data_size;
cyaml__log(cfg, CYAML_LOG_DEBUG,
"Free: Freeing sequence with count: %u\n", count);
if (schema->flags & CYAML_FLAG_POINTER) {
data_size = sizeof(data);
}
for (unsigned i = 0; i < count; i++) {
cyaml__log(cfg, CYAML_LOG_DEBUG,
"Free: Freeing sequence entry: %u\n", i);
cyaml__free_value(cfg, schema, data + data_size * i, 0);
}
}
/**
* Internal function for freeing a CYAML-parsed mapping.
*
* \param[in] cfg The client's CYAML library config.
* \param[in] mapping_schema The schema describing how to free `data`.
* \param[in] data The data structure to be freed.
*/
static void cyaml__free_mapping(
const cyaml_config_t *cfg,
const cyaml_schema_value_t *mapping_schema,
uint8_t * const data)
{
const cyaml_schema_field_t *schema = mapping_schema->mapping.fields;
while (schema->key != NULL) {
uint64_t count = 0;
cyaml__log(cfg, CYAML_LOG_DEBUG,
"Free: Freeing key: %s (at offset: %u)\n",
schema->key, (unsigned)schema->data_offset);
if (schema->value.type == CYAML_SEQUENCE) {
cyaml_err_t err;
count = cyaml_data_read(schema->count_size,
data + schema->count_offset, &err);
if (err != CYAML_OK) {
return;
}
}
cyaml__free_value(cfg, &schema->value,
data + schema->data_offset, count);
schema++;
}
}
/* This function is documented at the forward declaration above. */
static void cyaml__free_value(
const cyaml_config_t *cfg,
const cyaml_schema_value_t *schema,
uint8_t * data,
uint64_t count)
{
if (schema->flags & CYAML_FLAG_POINTER) {
data = cyaml_data_read_pointer(data);
if (data == NULL) {
return;
}
}
if (schema->type == CYAML_MAPPING) {
cyaml__free_mapping(cfg, schema, data);
} else if (schema->type == CYAML_SEQUENCE ||
schema->type == CYAML_SEQUENCE_FIXED) {
if (schema->type == CYAML_SEQUENCE_FIXED) {
count = schema->sequence.max;
}
cyaml__free_sequence(cfg, schema, data, count);
}
if (schema->flags & CYAML_FLAG_POINTER) {
cyaml__log(cfg, CYAML_LOG_DEBUG, "Free: Freeing: %p\n", data);
cyaml__free(cfg, data);
}
}
/* Exported function, documented in include/cyaml/cyaml.h */
cyaml_err_t cyaml_free(
const cyaml_config_t *config,
const cyaml_schema_value_t *schema,
cyaml_data_t *data,
unsigned seq_count)
{
if (config == NULL) {
return CYAML_ERR_BAD_PARAM_NULL_CONFIG;
}
if (config->mem_fn == NULL) {
return CYAML_ERR_BAD_CONFIG_NULL_MEMFN;
}
if (schema == NULL) {
return CYAML_ERR_BAD_PARAM_NULL_SCHEMA;
}
cyaml__log(config, CYAML_LOG_DEBUG, "Free: Top level data: %p\n", data);
cyaml__free_value(config, schema, (void *)&data, seq_count);
return CYAML_OK;
}

2508
src/load.c
File diff suppressed because it is too large
View File

35