commit baeea3eee6dae29bb1210bd67105ff485d665f61 Author: Florian Stinglmayr Date: Tue Feb 13 19:27:38 2018 +0000 first version of libdice diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..b901324 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,3 @@ +Here is a list of authors that have worked on this software: + +2018- Florian Stinglmayr diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..0a04128 --- /dev/null +++ b/COPYING @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..8077840 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,14 @@ +AM_YFLAGS = -d +BUILT_SOURCES = lib/dice_parse.h + +lib_LTLIBRARIES = libdice.la +libdice_la_SOURCES = lib/dice.h \ + lib/dice.c \ + lib/dice_parse.y \ + lib/dice_lexer.l + +AM_CFLAGS = -Ilib + +libdice_la_LIBADD = ${BSD_LIBS} + +SUBDIRS = . tests diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..04db2a9 --- /dev/null +++ b/README @@ -0,0 +1,44 @@ +libdice is a small library used for parsing dice expression and rolling dice. +It is mainly suitable for small projects that wish to support pen and paper +style dice syntax. + +Features: + + o Written in clean C + o Easy API as well as advanced API + o Solid and reliable RNG + o Unit tests + +Dice syntax support: + + o Variable sides + o Variable amount + +Requirements: + + o arc4random_uniform (or libbsd on non-BSDs) + o cmocka (for unit tests) + o bison + o flex + +Compilation + + $ ./configure --prefix=/usr + $ make + $ doas make install + +Usage: + + The functions ``dice_simple`` and ``dice_parse`` create a new ``dice_t`` + object which can be used in conjunction with ``dice_roll`` and + ``dice_evaluate`` to generate random numbers. ``dice_free`` must be used + to cleanup a created ``dice_t`` object. + + + dice_t d20 = dice_simple(1, 20); + int64_t res = dice_roll(d20); + dice_free(d20); + +Authors: + + Florian Stinglmayr diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..f3f2988 --- /dev/null +++ b/configure.ac @@ -0,0 +1,21 @@ +AC_PREREQ([2.69]) +AC_INIT([libdice], [0.1], [florian@n0la.org]) +AM_INIT_AUTOMAKE([subdir-objects]) +LT_INIT() +AC_CONFIG_HEADERS([config.h]) + +AC_PROG_CC + +AC_PROG_LEX +AC_PROG_YACC + +PKG_CHECK_MODULES([CMOCKA], [cmocka]) + +AC_CHECK_HEADER_STDBOOL() + +AC_CHECK_FUNCS([arc4random arc4random_uniform strlcat strlcpy],, + [PKG_CHECK_MODULES([BSD], [libbsd])]) + +AC_CONFIG_FILES([Makefile tests/Makefile]) + +AC_OUTPUT diff --git a/lib/dice.c b/lib/dice.c new file mode 100644 index 0000000..5d98399 --- /dev/null +++ b/lib/dice.c @@ -0,0 +1,178 @@ +#include "dice.h" +#include "dice_parse.h" + +#include +#include + +#ifndef HAVE_ARC4RANDOM_UNIFORM +#include +#endif + +extern void *yy_scan_string(char const *s); +extern void yy_delete_buffer(void *b); + +struct dice_ +{ + uint32_t amount; + uint32_t sides; + + char *errmsg; +}; + +static dice_t dice_new(void) +{ + dice_t tmp = calloc(1, sizeof(struct dice_)); + + if (tmp == NULL) { + return NULL; + } + + return tmp; +} + +void dice_free(dice_t t) +{ + if (t == NULL) { + return; + } + + free(t); +} + +void dice_result_free(dice_result_t *r) +{ + if (r == NULL) { + return; + } + + free(r); +} + +void dice_result_freev(dice_result_t *r, size_t len) +{ + if (r == NULL || len == 0) { + return; + } + + free(r); +} + +dice_t dice_simple(uint32_t amount, uint32_t sides) +{ + dice_t tmp = dice_new(); + + if (amount == 0 || sides == 0) { + return NULL; + } + + if (tmp == NULL) { + return NULL; + } + + tmp->amount = amount; + tmp->sides = sides; + + return tmp; +} + +dice_t dice_parse(char const *s) +{ + dice_t d = NULL; + void *buffer = NULL; + int ret = 0; + + d = dice_new(); + if (d == NULL) { + return NULL; + } + + buffer = yy_scan_string(s); + ret = yyparse(d); + yy_delete_buffer(buffer); + + if (ret) { + dice_free(d); + return NULL; + } + + return d; +} + +bool dice_set(dice_t d, dice_option_t opt, ...) +{ + va_list lst; + + va_start(lst, opt); + switch (opt) { + case DICEOPTION_AMOUNT: d->amount = va_arg(lst, uint32_t); break; + case DICEOPTION_SIDES: d->sides = va_arg(lst, uint32_t); break; + + default: return false; + } + va_end(lst); + + return true; +} + +bool dice_get(dice_t d, dice_option_t opt, ...) +{ + va_list lst; + + va_start(lst, opt); + switch (opt) { + case DICEOPTION_AMOUNT: + { + uint32_t *ptr = va_arg(lst, uint32_t*); + *ptr = d->amount; + } break; + + case DICEOPTION_SIDES: + { + uint32_t *ptr = va_arg(lst, uint32_t*); + *ptr = d->sides; + } break; + + default: return false; + } + va_end(lst); + + return true; +} + +int64_t dice_roll(dice_t d) +{ + int64_t result = 0; + uint32_t i = 0; + + for (i = 0; i < d->amount; i++) { + result += arc4random_uniform(d->sides) + 1; + } + + return result; +} + +bool dice_evaluate(dice_t d, dice_result_t **res, size_t *reslen) +{ + size_t len = 0; + dice_result_t *r = NULL; + uint32_t i = 0; + + if (d == NULL || d->amount <= 0 || d->sides <= 0) { + return false; + } + + len = d->amount; + r = calloc(len, sizeof(dice_result_t)); + if (r == NULL) { + return NULL; + } + + for (i = 0; i < d->amount; i++) { + r[i].result = arc4random_uniform(d->sides) + 1; + } + + *reslen = len; + *res = r; + + return true; +} diff --git a/lib/dice.h b/lib/dice.h new file mode 100644 index 0000000..b576347 --- /dev/null +++ b/lib/dice.h @@ -0,0 +1,34 @@ +#ifndef LIBDICE_DICE_H +#define LIBDICE_DICE_H + +#include +#include +#include +#include + +struct dice_; +typedef struct dice_ * dice_t; + +typedef struct { + int64_t result; +} dice_result_t; + +typedef enum { + DICEOPTION_AMOUNT = 0, + DICEOPTION_SIDES, +} dice_option_t; + +void dice_result_free(dice_result_t *r); +void dice_result_freev(dice_result_t *r, size_t len); + +void dice_free(dice_t t); +dice_t dice_simple(uint32_t amount, uint32_t sides); +dice_t dice_parse(char const *s); + +bool dice_set(dice_t d, dice_option_t opt, ...); +bool dice_get(dice_t d, dice_option_t opt, ...); + +int64_t dice_roll(dice_t d); +bool dice_evaluate(dice_t d, dice_result_t **res, size_t *reslen); + +#endif diff --git a/lib/dice_lexer.l b/lib/dice_lexer.l new file mode 100644 index 0000000..847ce53 --- /dev/null +++ b/lib/dice_lexer.l @@ -0,0 +1,9 @@ +%{ +#include "dice.h" +#include "dice_parse.h" +%} + +%% +[0-9]+ { yylval.integer = atoi(yytext); return TOK_INTEGER; } +[d] return TOK_DICESEP; +%% diff --git a/lib/dice_parse.y b/lib/dice_parse.y new file mode 100644 index 0000000..014a05b --- /dev/null +++ b/lib/dice_parse.y @@ -0,0 +1,39 @@ +%parse-param {dice_t dice} + +%{ +#include "dice.h" + +extern int yylex(void); + +void yyerror(dice_t dice, char const *err) +{ +} + +int yywrap(void) +{ + return 1; +} +%} + +%union { + char *str; + int integer; + double number; +} + +%token TOK_DICESEP +%token TOK_INTEGER + +%% + +dice: TOK_INTEGER TOK_DICESEP TOK_INTEGER + { + dice_set(dice, DICEOPTION_AMOUNT, $1); + dice_set(dice, DICEOPTION_SIDES, $3); + } + | TOK_DICESEP TOK_INTEGER + { + dice_set(dice, DICEOPTION_AMOUNT, 1); + dice_set(dice, DICEOPTION_SIDES, $2); + } + ; diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..456ff28 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,8 @@ +bin_PROGRAMS = test_dice_simple_roll \ + test_dice_parse \ + test_dice_evaluate + +AM_CFLAGS = -I../lib ${CMOCKA_CFLAGS} +AM_LDFLAGS = ${CMOCKA_LIBS} ../libdice.la + +TESTS = $(bin_PROGRAMS) diff --git a/tests/test_dice_evaluate.c b/tests/test_dice_evaluate.c new file mode 100644 index 0000000..b9cb4a6 --- /dev/null +++ b/tests/test_dice_evaluate.c @@ -0,0 +1,72 @@ +#include + +#include +#include +#include +#include +#include + +static void test_dice_evaluate_null(void **data) +{ + dice_t d = NULL; + dice_result_t *r = NULL; + size_t rlen = 0; + + assert_false(dice_evaluate(d, &r, &rlen)); +} + +static void test_dice_evaluate_one(void **data) +{ + dice_t d = NULL; + dice_result_t *r = NULL; + size_t rlen = 0; + + d = dice_simple(1, 10); + + assert_non_null(d); + assert_true(dice_evaluate(d, &r, &rlen)); + assert_non_null(r); + + assert_int_equal(rlen, 1); + assert_true(r[0].result >= 1 && r[0].result <= 10); + + dice_free(d); + dice_result_freev(r, rlen); +} + +static void test_dice_evaluate_many(void **data) +{ + dice_t d = NULL; + dice_result_t *r = NULL; + size_t rlen = 0; + const int amount = 150; + int i = 0; + + /* your average evening playing Shadowrun... + */ + d = dice_simple(amount, 6); + + assert_non_null(d); + assert_true(dice_evaluate(d, &r, &rlen)); + assert_non_null(r); + + assert_int_equal(rlen, amount); + + for (i = 0; i < amount; i++) { + assert_true(r[i].result >= 1 && r[i].result <= 6); + } + + dice_free(d); + dice_result_freev(r, rlen); +} + +int main(int ac, char **av) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_dice_evaluate_null), + cmocka_unit_test(test_dice_evaluate_one), + cmocka_unit_test(test_dice_evaluate_many), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/tests/test_dice_parse.c b/tests/test_dice_parse.c new file mode 100644 index 0000000..31c8e07 --- /dev/null +++ b/tests/test_dice_parse.c @@ -0,0 +1,60 @@ +#include + +#include +#include +#include +#include +#include + +static void test_dice_parse_none(void **data) +{ + dice_t d = dice_parse(""); + assert_null(d); +} + +static void test_dice_parse_amount(void **data) +{ + dice_t d = dice_parse("3d"); + assert_null(d); +} + +static void test_dice_parse_amount_sides(void **data) +{ + dice_t d = dice_parse("5d10"); + int i = 0; + + assert_non_null(d); + + assert_true(dice_get(d, DICEOPTION_AMOUNT, &i)); + assert_int_equal(i, 5); + + assert_true(dice_get(d, DICEOPTION_SIDES, &i)); + assert_int_equal(i, 10); + + dice_free(d); +} + +static void test_dice_parse_sides(void **data) +{ + dice_t d = dice_parse("d12"); + int i = 0; + + assert_non_null(d); + + assert_true(dice_get(d, DICEOPTION_SIDES, &i)); + assert_int_equal(i, 12); + + dice_free(d); +} + +int main(int ac, char **av) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_dice_parse_none), + cmocka_unit_test(test_dice_parse_amount), + cmocka_unit_test(test_dice_parse_amount_sides), + cmocka_unit_test(test_dice_parse_sides), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/tests/test_dice_simple_roll.c b/tests/test_dice_simple_roll.c new file mode 100644 index 0000000..f206ee2 --- /dev/null +++ b/tests/test_dice_simple_roll.c @@ -0,0 +1,113 @@ +#include + +#include +#include +#include +#include +#include + +static void test_dice_simple_null(void **data) +{ + dice_t d = dice_simple(0, 0); + assert_null(d); +} + +static void test_dice_simple_one(void **data) +{ + dice_t d = dice_simple(1, 20); + assert_non_null(d); + dice_free(d); +} + +static void test_dice_simple_roll(void **data) +{ + dice_t d = dice_simple(1, 20); + int i = 0; + + assert_non_null(d); + + for (i = 0; i < 100000; i++) { + int64_t res = dice_roll(d); + assert_true(res >= 1 && res <= 20); + } + + dice_free(d); +} + +static void test_dice_simple_multi(void **data) +{ + dice_t d = dice_simple(3, 6); + int i = 0; + + assert_non_null(d); + + for (i = 0; i < 100000; i++) { + int64_t res = dice_roll(d); + assert_true(res >= 3 && res <= 18); + } + + dice_free(d); +} + +static void test_dice_simple_get(void **data) +{ + dice_t d = dice_simple(8, 10); + int i = 0; + + assert_non_null(d); + + assert_true(dice_get(d, DICEOPTION_AMOUNT, &i)); + assert_int_equal(i, 8); + + assert_true(dice_get(d, DICEOPTION_SIDES, &i)); + assert_int_equal(i, 10); + + dice_free(d); +} + +static void test_dice_simple_set(void **data) +{ + dice_t d = dice_simple(1, 20); + int i = 0; + + assert_non_null(d); + + assert_true(dice_set(d, DICEOPTION_AMOUNT, 20)); + assert_true(dice_set(d, DICEOPTION_SIDES, 4)); + + assert_true(dice_get(d, DICEOPTION_AMOUNT, &i)); + assert_int_equal(i, 20); + + assert_true(dice_get(d, DICEOPTION_SIDES, &i)); + assert_int_equal(i, 4); + + dice_free(d); +} + +static void test_dice_simple_invalid_getset(void **data) +{ + dice_t d = dice_simple(1, 20); + int i = 0; + + assert_non_null(d); + + assert_false(dice_set(d, 9001, 20)); + assert_false(dice_get(d, 9001, &i)); + + dice_free(d); +} + +int main(int ac, char **av) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_dice_simple_null), + cmocka_unit_test(test_dice_simple_one), + cmocka_unit_test(test_dice_simple_roll), + cmocka_unit_test(test_dice_simple_multi), + cmocka_unit_test(test_dice_simple_set), + cmocka_unit_test(test_dice_simple_get), + cmocka_unit_test(test_dice_simple_invalid_getset), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +}