Updated testing framework
This commit is contained in:
76
README.md
76
README.md
@@ -3,18 +3,24 @@ Micro Parser Combinators
|
||||
|
||||
_mpc_ is a lightweight Parser Combinator library for C.
|
||||
|
||||
The current main alternative is a branch of (https://github.com/wbhart/Cesium3)[Cesium3].
|
||||
|
||||
Features & Advantages
|
||||
---------------------
|
||||
Features
|
||||
--------
|
||||
|
||||
* Full Type Generic Parser Combinator
|
||||
* Error Message Support
|
||||
* Regular Expression Support
|
||||
* Parser Grammar Support
|
||||
* Works for Generic Types
|
||||
* AST Extension
|
||||
* Single source & header files
|
||||
* Packaged with AST generator
|
||||
* Easy to including in source
|
||||
* Written in clean ANSI C
|
||||
|
||||
Alternatives
|
||||
------------
|
||||
|
||||
The current main alternative is a branch of (https://github.com/wbhart/Cesium3)[Cesium3].
|
||||
|
||||
The main advantages of _mpc_ over this are:
|
||||
|
||||
* Works for Generic Types
|
||||
* Doesn't rely on Boehm-Demers-Weiser Garbage Collection
|
||||
* Doesn't use `setjmp` and `longjmp` for errors
|
||||
* Doesn't pollute namespace
|
||||
@@ -24,51 +30,8 @@ Example
|
||||
|
||||
```c
|
||||
|
||||
mpc_val_t* combine_maths(int n, mpc_val_t** xs) {
|
||||
|
||||
int** vs = (int**)xs;
|
||||
|
||||
if (*vs[1] == '*') { *vs[0] *= *vs[2]; }
|
||||
if (*vs[1] == '/') { *vs[0] /= *vs[2]; }
|
||||
if (*vs[1] == '+') { *vs[0] += *vs[2]; }
|
||||
if (*vs[1] == '-') { *vs[0] -= *vs[2]; }
|
||||
|
||||
free(vs[1]);
|
||||
free(vs[2]);
|
||||
|
||||
return vs[0];
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
mpc_parser_t* Expr = mpc_new();
|
||||
mpc_parser_t* Factor = mpc_new();
|
||||
mpc_parser_t* Term = mpc_new();
|
||||
mpc_parser_t* Maths = mpc_new();
|
||||
|
||||
mpc_define(Expr,
|
||||
mpc_pc("cmaths ( fact ['*' | '/'] fact ) | fact",
|
||||
combine_maths, Factor, free, Factor, free, Factor),
|
||||
);
|
||||
|
||||
mpc_define(Factor,
|
||||
mpc_pc("cmaths ( term ['+' | '-'] term ) | term",
|
||||
combine_maths, Term, free, Term, free, Term),
|
||||
);
|
||||
|
||||
mpc_define(Term,
|
||||
mpc_pc("num | snd ('(' expr ')')",
|
||||
mpc_int(), mpcf_asnd_free, Expr, free)
|
||||
);
|
||||
|
||||
mpc_define(Maths, mpc_ends(Expr, free));
|
||||
|
||||
mpc_delete(Expr);
|
||||
mpc_delete(Factor);
|
||||
mpc_delete(Term);
|
||||
mpc_delete(Maths);
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Parsers
|
||||
@@ -90,6 +53,17 @@ Combinator Grammars
|
||||
Abstract Syntax Tree
|
||||
--------------------
|
||||
|
||||
If you want to do all the data processing after the parsing stage _mpc_ comes packaged with a basic AST type which makes the grammar declaration much cleaner as you don't have to pass around destructors and fold functions. All these functions reside under `mpca_*`.
|
||||
|
||||
This also allows for the use of parser grammars that can be declared directly in C strings similarly to regular expressions.
|
||||
|
||||
```c
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
Reference
|
||||
---------
|
||||
|
11
TODO.md
Normal file
11
TODO.md
Normal file
@@ -0,0 +1,11 @@
|
||||
- Special Start of Input
|
||||
- Special End of Input
|
||||
- Integrate ptest
|
||||
- Test All Regex Features
|
||||
- Test Regex Range Feature
|
||||
- Parser Naming
|
||||
- Test Grammar Trees
|
||||
- Change Grammar Many Fold Operator
|
||||
- Find some good demo grammars to show
|
||||
- Add Tutorial Teaching from scratch
|
||||
- Add Reference
|
104
mpc.h
104
mpc.h
@@ -22,6 +22,7 @@ int mpc_err_line(mpc_err_t* x);
|
||||
int mpc_err_column(mpc_err_t* x);
|
||||
char mpc_err_unexpected(mpc_err_t* x);
|
||||
char** mpc_err_expected(mpc_err_t* x, int* num);
|
||||
char* mpc_err_filename(mpc_err_t* x);
|
||||
|
||||
void mpc_err_delete(mpc_err_t* x);
|
||||
void mpc_err_print(mpc_err_t* x);
|
||||
@@ -42,10 +43,21 @@ typedef union {
|
||||
struct mpc_parser_t;
|
||||
typedef struct mpc_parser_t mpc_parser_t;
|
||||
|
||||
bool mpc_parse(const char* s, mpc_parser_t* p, mpc_result_t* r);
|
||||
bool mpc_parse_file(FILE* f, mpc_parser_t* p, mpc_result_t* r);
|
||||
bool mpc_parse(const char* filename, const char* s, mpc_parser_t* p, mpc_result_t* r);
|
||||
bool mpc_parse_file(const char* filename, FILE* f, mpc_parser_t* p, mpc_result_t* r);
|
||||
bool mpc_parse_filename(const char* filename, mpc_parser_t* p, mpc_result_t* r);
|
||||
|
||||
/*
|
||||
** Function Types
|
||||
*/
|
||||
|
||||
typedef void(*mpc_dtor_t)(mpc_val_t*);
|
||||
typedef mpc_val_t*(*mpc_apply_t)(mpc_val_t*);
|
||||
typedef mpc_val_t*(*mpc_apply_to_t)(mpc_val_t*,void*);
|
||||
typedef mpc_val_t*(*mpc_fold_t)(mpc_val_t*,mpc_val_t*);
|
||||
typedef mpc_val_t*(*mpc_afold_t)(int,mpc_val_t**);
|
||||
typedef mpc_val_t*(*mpc_lift_t)(void);
|
||||
|
||||
/*
|
||||
** Building a Parser
|
||||
*/
|
||||
@@ -56,11 +68,11 @@ mpc_parser_t* mpc_new(void);
|
||||
mpc_parser_t* mpc_define(mpc_parser_t* p, mpc_parser_t* a);
|
||||
mpc_parser_t* mpc_undefine(mpc_parser_t* p);
|
||||
|
||||
mpc_parser_t* mpc_expect(mpc_parser_t* a, const char* expected);
|
||||
|
||||
mpc_parser_t* mpc_pass(void);
|
||||
mpc_parser_t* mpc_fail(void);
|
||||
mpc_parser_t* mpc_lift(mpc_val_t* x);
|
||||
mpc_parser_t* mpc_lift(mpc_lift_t f);
|
||||
mpc_parser_t* mpc_lift_val(mpc_val_t* x);
|
||||
mpc_parser_t* mpc_expect(mpc_parser_t* a, const char* expected);
|
||||
|
||||
/*
|
||||
** Basic Parsers
|
||||
@@ -74,31 +86,28 @@ mpc_parser_t* mpc_noneof(const char* s);
|
||||
mpc_parser_t* mpc_satisfy(bool(*f)(char));
|
||||
mpc_parser_t* mpc_string(const char* s);
|
||||
|
||||
/*
|
||||
** Function Types
|
||||
*/
|
||||
|
||||
typedef void (*mpc_dtor_t)(mpc_val_t*);
|
||||
typedef mpc_val_t*(*mpc_apply_t)(mpc_val_t*);
|
||||
typedef mpc_val_t*(*mpc_fold_t)(mpc_val_t*,mpc_val_t*);
|
||||
typedef mpc_val_t*(*mpc_afold_t)(int,mpc_val_t**);
|
||||
|
||||
void mpc_dtor_null(mpc_val_t* x);
|
||||
|
||||
/*
|
||||
** Core Parsers
|
||||
*/
|
||||
|
||||
mpc_parser_t* mpc_apply(mpc_parser_t* a, mpc_apply_t f);
|
||||
mpc_parser_t* mpc_apply_to(mpc_parser_t* a, mpc_apply_to_t f, void* x);
|
||||
mpc_parser_t* mpc_not(mpc_parser_t* a, mpc_dtor_t da);
|
||||
mpc_parser_t* mpc_not_else(mpc_parser_t* a, mpc_dtor_t da, mpc_lift_t lf);
|
||||
mpc_parser_t* mpc_maybe(mpc_parser_t* a);
|
||||
mpc_parser_t* mpc_maybe_else(mpc_parser_t* a, mpc_lift_t lf);
|
||||
mpc_parser_t* mpc_many(mpc_parser_t* a, mpc_fold_t f);
|
||||
mpc_parser_t* mpc_many_else(mpc_parser_t* a, mpc_fold_t f, mpc_lift_t lf);
|
||||
mpc_parser_t* mpc_many1(mpc_parser_t* a, mpc_fold_t f);
|
||||
mpc_parser_t* mpc_count(mpc_parser_t* a, mpc_dtor_t da, mpc_fold_t f, int n);
|
||||
mpc_parser_t* mpc_either(mpc_parser_t* a, mpc_parser_t* b);
|
||||
mpc_parser_t* mpc_count_else(mpc_parser_t* a, mpc_dtor_t da, mpc_fold_t f, int n, mpc_lift_t lf);
|
||||
mpc_parser_t* mpc_else(mpc_parser_t* a, mpc_parser_t* b);
|
||||
mpc_parser_t* mpc_also(mpc_parser_t* a, mpc_parser_t* b, mpc_dtor_t da, mpc_fold_t f);
|
||||
mpc_parser_t* mpc_bind(mpc_parser_t* a, mpc_parser_t* b, mpc_dtor_t da, mpc_fold_t f);
|
||||
mpc_parser_t* mpc_or(int n, ...);
|
||||
mpc_parser_t* mpc_and(int n, mpc_afold_t f, ...);
|
||||
mpc_parser_t* mpc_or_va(int n, va_list va);
|
||||
mpc_parser_t* mpc_and_va(int n, mpc_afold_t f, va_list va);
|
||||
|
||||
/*
|
||||
** Common Parsers
|
||||
@@ -124,12 +133,14 @@ mpc_parser_t* mpc_lower(void);
|
||||
mpc_parser_t* mpc_upper(void);
|
||||
mpc_parser_t* mpc_alpha(void);
|
||||
mpc_parser_t* mpc_underscore(void);
|
||||
mpc_parser_t* mpc_alphanum(void);
|
||||
|
||||
mpc_parser_t* mpc_int(void);
|
||||
mpc_parser_t* mpc_hex(void);
|
||||
mpc_parser_t* mpc_oct(void);
|
||||
mpc_parser_t* mpc_number(void);
|
||||
|
||||
mpc_parser_t* mpc_real(void);
|
||||
mpc_parser_t* mpc_float(void);
|
||||
|
||||
mpc_parser_t* mpc_semi(void);
|
||||
@@ -168,11 +179,17 @@ mpc_parser_t* mpc_re(const char* re);
|
||||
** Common Fold Functions
|
||||
*/
|
||||
|
||||
void mpcf_dtor_null(mpc_val_t* x);
|
||||
mpc_val_t* mpcf_lift_null(void);
|
||||
mpc_val_t* mpcf_lift_emptystr(void);
|
||||
|
||||
mpc_val_t* mpcf_free(mpc_val_t* x);
|
||||
mpc_val_t* mpcf_int(mpc_val_t* x);
|
||||
mpc_val_t* mpcf_hex(mpc_val_t* x);
|
||||
mpc_val_t* mpcf_oct(mpc_val_t* x);
|
||||
mpc_val_t* mpcf_float(mpc_val_t* x);
|
||||
mpc_val_t* mpcf_escape(mpc_val_t* x);
|
||||
mpc_val_t* mpcf_unescape(mpc_val_t* x);
|
||||
|
||||
mpc_val_t* mpcf_fst(mpc_val_t* x, mpc_val_t* y);
|
||||
mpc_val_t* mpcf_snd(mpc_val_t* x, mpc_val_t* y);
|
||||
@@ -183,6 +200,7 @@ mpc_val_t* mpcf_snd_free(mpc_val_t* x, mpc_val_t* y);
|
||||
mpc_val_t* mpcf_freefold(mpc_val_t* t, mpc_val_t* x);
|
||||
mpc_val_t* mpcf_strfold(mpc_val_t* t, mpc_val_t* x);
|
||||
|
||||
mpc_val_t* mpcf_astrfold(int n, mpc_val_t** xs);
|
||||
mpc_val_t* mpcf_between_free(int n, mpc_val_t** xs);
|
||||
mpc_val_t* mpcf_maths(int n, mpc_val_t** xs);
|
||||
|
||||
@@ -193,13 +211,59 @@ mpc_val_t* mpcf_maths(int n, mpc_val_t** xs);
|
||||
|
||||
void mpc_print(mpc_parser_t* p);
|
||||
|
||||
|
||||
/*
|
||||
** Testing
|
||||
*/
|
||||
|
||||
bool mpc_test(mpc_parser_t* p, const char* input, void* data,
|
||||
bool(*tester)(void*, void*),
|
||||
void(*destructor)(void*),
|
||||
bool mpc_unmatch(mpc_parser_t* p, const char* s, void* d,
|
||||
bool(*tester)(void*, void*),
|
||||
mpc_dtor_t destructor,
|
||||
void(*printer)(void*));
|
||||
|
||||
bool mpc_match(mpc_parser_t* p, const char* s, void* d,
|
||||
bool(*tester)(void*, void*),
|
||||
mpc_dtor_t destructor,
|
||||
void(*printer)(void*));
|
||||
|
||||
|
||||
/*
|
||||
** AST
|
||||
*/
|
||||
|
||||
typedef struct mpc_ast_t {
|
||||
int tag;
|
||||
char* contents;
|
||||
int children_num;
|
||||
struct mpc_ast_t** children;
|
||||
} mpc_ast_t;
|
||||
|
||||
void mpc_ast_delete(mpc_ast_t* a);
|
||||
mpc_ast_t* mpc_ast_empty(void);
|
||||
mpc_ast_t* mpc_ast_new(char* contents);
|
||||
|
||||
void mpc_ast_add_child(mpc_ast_t* r, mpc_ast_t* a);
|
||||
mpc_ast_t* mpc_ast_tag(mpc_ast_t* a, int t);
|
||||
void mpc_ast_print(mpc_ast_t* a);
|
||||
|
||||
mpc_val_t* mpcf_fold_ast(mpc_val_t* a, mpc_val_t* b);
|
||||
mpc_val_t* mpcf_afold_ast(int n, mpc_val_t** as);
|
||||
mpc_val_t* mpcf_apply_str_ast(mpc_val_t* c);
|
||||
mpc_val_t* mpcf_lift_ast(void);
|
||||
|
||||
mpc_parser_t* mpc_ast(mpc_parser_t* a);
|
||||
|
||||
mpc_parser_t* mpca_not(mpc_parser_t* a);
|
||||
mpc_parser_t* mpca_maybe(mpc_parser_t* a);
|
||||
mpc_parser_t* mpca_many(mpc_parser_t* a);
|
||||
mpc_parser_t* mpca_many1(mpc_parser_t* a);
|
||||
mpc_parser_t* mpca_count(mpc_parser_t* a, int n);
|
||||
mpc_parser_t* mpca_else(mpc_parser_t* a, mpc_parser_t* b);
|
||||
mpc_parser_t* mpca_also(mpc_parser_t* a, mpc_parser_t* b);
|
||||
mpc_parser_t* mpca_bind(mpc_parser_t* a, mpc_parser_t* b);
|
||||
mpc_parser_t* mpca_or(int n, ...);
|
||||
mpc_parser_t* mpca_and(int n, ...);
|
||||
mpc_parser_t* mpca_ends(mpc_parser_t* a);
|
||||
mpc_parser_t* mpca_grammar(const char* grammar, ...);
|
||||
|
||||
#endif
|
81
tests/core.c
Normal file
81
tests/core.c
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "ptest.h"
|
||||
#include "../mpc.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static bool int_eq(void* x, void* y) { return (*(int*)x == *(int*)y); }
|
||||
static void int_print(void* x) { printf("'%i'", *((int*)x)); }
|
||||
static bool string_eq(void* x, void* y) { return (strcmp(x, y) == 0); }
|
||||
static void string_print(void* x) { printf("'%s'", (char*)x); }
|
||||
|
||||
void test_ident(void) {
|
||||
|
||||
/* ^[a-zA-Z_][a-zA-Z0-9_]*$ */
|
||||
|
||||
mpc_parser_t* Ident = mpc_ends(
|
||||
mpc_also(
|
||||
mpc_else(mpc_alpha(), mpc_underscore()),
|
||||
mpc_many1(mpc_or(3, mpc_alpha(), mpc_underscore(), mpc_digit()), mpcf_strfold),
|
||||
free, mpcf_strfold),
|
||||
free
|
||||
);
|
||||
|
||||
PT_ASSERT(mpc_match(Ident, "test", "test", string_eq, free, string_print));
|
||||
PT_ASSERT(mpc_unmatch(Ident, " blah", "", string_eq, free, string_print));
|
||||
PT_ASSERT(mpc_match(Ident, "anoth21er", "anoth21er", string_eq, free, string_print));
|
||||
PT_ASSERT(mpc_match(Ident, "du__de", "du__de", string_eq, free, string_print));
|
||||
PT_ASSERT(mpc_unmatch(Ident, "some spaces", "", string_eq, free, string_print));
|
||||
PT_ASSERT(mpc_unmatch(Ident, "", "", string_eq, free, string_print));
|
||||
PT_ASSERT(mpc_unmatch(Ident, "18nums", "", string_eq, free, string_print));
|
||||
|
||||
mpc_delete(Ident);
|
||||
|
||||
}
|
||||
|
||||
void test_maths(void) {
|
||||
|
||||
mpc_parser_t* Expr = mpc_new();
|
||||
mpc_parser_t* Factor = mpc_new();
|
||||
mpc_parser_t* Term = mpc_new();
|
||||
mpc_parser_t* Maths = mpc_new();
|
||||
|
||||
mpc_define(Expr, mpc_else(
|
||||
mpc_and(3, mpcf_maths, Factor, mpc_oneof("*/"), Factor, free, free),
|
||||
Factor
|
||||
));
|
||||
|
||||
mpc_define(Factor, mpc_else(
|
||||
mpc_and(3, mpcf_maths, Term, mpc_oneof("+-"), Term, free, free),
|
||||
Term
|
||||
));
|
||||
|
||||
mpc_define(Term, mpc_else(
|
||||
mpc_int(),
|
||||
mpc_parens(Expr, free)
|
||||
));
|
||||
|
||||
mpc_define(Maths, mpc_ends(Expr, free));
|
||||
|
||||
PT_ASSERT(mpc_match(Maths, "1", (int[]){ 1 }, int_eq, free, int_print));
|
||||
PT_ASSERT(mpc_match(Maths, "(5)", (int[]){ 5 }, int_eq, free, int_print));
|
||||
PT_ASSERT(mpc_match(Maths, "(4*2)+5", (int[]){ 13 }, int_eq, free, int_print));
|
||||
PT_ASSERT(mpc_unmatch(Maths, "a", (int[]){ 0 }, int_eq, free, int_print));
|
||||
PT_ASSERT(mpc_unmatch(Maths, "2b+4", (int[]){ 2 }, int_eq, free, int_print));
|
||||
|
||||
mpc_undefine(Expr);
|
||||
mpc_undefine(Factor);
|
||||
mpc_undefine(Term);
|
||||
mpc_undefine(Maths);
|
||||
|
||||
mpc_delete(Expr);
|
||||
mpc_delete(Factor);
|
||||
mpc_delete(Term);
|
||||
mpc_delete(Maths);
|
||||
|
||||
}
|
||||
|
||||
void suite_core(void) {
|
||||
pt_add_test(test_ident, "Test Ident", "Suite Core");
|
||||
pt_add_test(test_maths, "Test Maths", "Suite Core");
|
||||
}
|
56
tests/grammar.c
Normal file
56
tests/grammar.c
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "ptest.h"
|
||||
#include "../mpc.h"
|
||||
|
||||
bool ast_eq(void* x, void* y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void test_grammar(void) {
|
||||
|
||||
mpc_parser_t* Test = mpc_new();
|
||||
|
||||
mpc_define(Test, mpca_grammar("'c'*"));
|
||||
|
||||
mpc_print(Test);
|
||||
|
||||
mpc_undefine(Test);
|
||||
|
||||
mpc_delete(Test);
|
||||
|
||||
mpc_parser_t* Expression = mpc_new();
|
||||
mpc_parser_t* Product = mpc_new();
|
||||
mpc_parser_t* Value = mpc_new();
|
||||
|
||||
mpc_define(Expression, mpca_grammar("<0> (('+' | '-') <0>)*", Product));
|
||||
mpc_define(Product, mpca_grammar("<0> (('*' | '/') <0>)*", Value));
|
||||
mpc_define(Value, mpca_grammar("/[0-9]/ | '(' <0> ')'", Expression));
|
||||
|
||||
mpc_print(Expression);
|
||||
mpc_print(Product);
|
||||
mpc_print(Value);
|
||||
|
||||
mpc_ast_t* empty = mpc_ast_empty();
|
||||
|
||||
/*
|
||||
PT_ASSERT(mpc_match(Expression, "1", empty, ast_eq, (mpc_dtor_t)mpc_ast_delete, (void(*)(void*))mpc_ast_print));
|
||||
PT_ASSERT(mpc_match(Expression, "(5)", empty, ast_eq, (mpc_dtor_t)mpc_ast_delete, (void(*)(void*))mpc_ast_print));
|
||||
PT_ASSERT(mpc_match(Expression, "(4*2)+5", empty, ast_eq, (mpc_dtor_t)mpc_ast_delete, (void(*)(void*))mpc_ast_print));
|
||||
PT_ASSERT(mpc_match(Expression, "a", empty, ast_eq, (mpc_dtor_t)mpc_ast_delete, (void(*)(void*))mpc_ast_print));
|
||||
PT_ASSERT(mpc_match(Expression, "2b+4", empty, ast_eq, (mpc_dtor_t)mpc_ast_delete, (void(*)(void*))mpc_ast_print));
|
||||
*/
|
||||
|
||||
mpc_ast_delete(empty);
|
||||
|
||||
mpc_undefine(Expression);
|
||||
mpc_undefine(Product);
|
||||
mpc_undefine(Value);
|
||||
|
||||
mpc_delete(Expression);
|
||||
mpc_delete(Product);
|
||||
mpc_delete(Value);
|
||||
|
||||
}
|
||||
|
||||
void suite_grammar(void) {
|
||||
pt_add_test(test_grammar, "Test Grammar", "Suite Grammar");
|
||||
}
|
301
tests/ptest.c
Normal file
301
tests/ptest.c
Normal file
@@ -0,0 +1,301 @@
|
||||
#include "ptest.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Globals */
|
||||
|
||||
#define MAX_NAME 512
|
||||
#define MAX_ERROR 2048
|
||||
#define MAX_TESTS 2048
|
||||
|
||||
static bool test_passing = false;
|
||||
static bool suite_passing = false;
|
||||
|
||||
/* Colors */
|
||||
|
||||
enum {
|
||||
BLACK = 0x0,
|
||||
BLUE = 0x1,
|
||||
GREEN = 0x2,
|
||||
AQUA = 0x3,
|
||||
RED = 0x4,
|
||||
PURPLE = 0x5,
|
||||
YELLOW = 0x6,
|
||||
WHITE = 0x7,
|
||||
GRAY = 0x8,
|
||||
LIGHT_BLUE = 0x9,
|
||||
LIGHT_GREEN = 0xA,
|
||||
LIGHT_AQUA = 0xB,
|
||||
LIGHT_RED = 0xC,
|
||||
LIGHT_PURPLE = 0xD,
|
||||
LIGHT_YELLOW = 0xE,
|
||||
LIGHT_WHITE = 0xF,
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
static void pt_color(int color) {
|
||||
HANDLE hCon = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
SetConsoleTextAttribute(hCon, color);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static const char* colors[] = {
|
||||
"\x1B[0m",
|
||||
"\x1B[34m",
|
||||
"\x1B[32m",
|
||||
"\x1B[36m",
|
||||
"\x1B[31m",
|
||||
"\x1B[35m",
|
||||
"\x1B[33m",
|
||||
"\x1B[37m",
|
||||
"",
|
||||
"\x1B[34m",
|
||||
"\x1B[32m",
|
||||
"\x1B[36m",
|
||||
"\x1B[31m",
|
||||
"\x1B[35m",
|
||||
"\x1B[33m",
|
||||
"\x1B[37m"
|
||||
};
|
||||
|
||||
static void pt_color(int color) {
|
||||
|
||||
printf("%s", colors[color]);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* Asserts */
|
||||
|
||||
static int num_asserts = 0;
|
||||
static int num_assert_passes = 0;
|
||||
static int num_assert_fails = 0;
|
||||
|
||||
static char assert_err[MAX_ERROR];
|
||||
static char assert_err_buff[MAX_ERROR];
|
||||
static int assert_err_num = 0;
|
||||
|
||||
void pt_assert_run(bool result, const char* expr, const char* func, const char* file, int line) {
|
||||
|
||||
num_asserts++;
|
||||
test_passing = test_passing && result;
|
||||
|
||||
if (result) {
|
||||
num_assert_passes++;
|
||||
} else {
|
||||
sprintf(assert_err_buff, " %i. Assert [ %s ] (%s:%i)\n", assert_err_num+1, expr, file, line );
|
||||
strcat(assert_err, assert_err_buff);
|
||||
assert_err_num++;
|
||||
num_assert_fails++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void ptest_signal(int sig) {
|
||||
|
||||
test_passing = false;
|
||||
|
||||
switch( sig ) {
|
||||
case SIGFPE: sprintf(assert_err_buff, " %i. Division by Zero\n", assert_err_num+1); break;
|
||||
case SIGILL: sprintf(assert_err_buff, " %i. Illegal Instruction\n", assert_err_num+1); break;
|
||||
case SIGSEGV: sprintf(assert_err_buff, " %i. Segmentation Fault\n", assert_err_num+1); break;
|
||||
}
|
||||
|
||||
assert_err_num++;
|
||||
strcat(assert_err, assert_err_buff);
|
||||
|
||||
pt_color(RED); printf("Failed! \n\n%s\n", assert_err); pt_color(WHITE);
|
||||
|
||||
printf(" | Stopping Execution.\n");
|
||||
fflush(stdout);
|
||||
exit(0);
|
||||
|
||||
}
|
||||
|
||||
/* Tests */
|
||||
|
||||
static void pt_title_case(char* output, const char* input) {
|
||||
|
||||
bool space = true;
|
||||
|
||||
strcpy(output, input);
|
||||
|
||||
unsigned int i;
|
||||
for(i = 0; i < strlen(output); i++) {
|
||||
|
||||
if (output[i] == '_' || output[i] == ' ') {
|
||||
space = true;
|
||||
output[i] = ' ';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (space && output[i] >= 'a' && output[i] <= 'z') {
|
||||
output[i] = output[i] - 32;
|
||||
continue;
|
||||
}
|
||||
|
||||
space = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
void (*func)(void);
|
||||
char name[MAX_NAME];
|
||||
char suite[MAX_NAME];
|
||||
} test_t;
|
||||
|
||||
static test_t tests[MAX_TESTS];
|
||||
|
||||
static int num_tests = 0;
|
||||
static int num_tests_passes = 0;
|
||||
static int num_tests_fails = 0;
|
||||
|
||||
void pt_add_test(void (*func)(void), const char* name, const char* suite) {
|
||||
|
||||
if (num_tests == MAX_TESTS) {
|
||||
printf("ERROR: Exceeded maximum test count of %i!\n", MAX_TESTS); abort();
|
||||
}
|
||||
|
||||
if (strlen(name) >= MAX_NAME) {
|
||||
printf("ERROR: Test name '%s' too long (Maximum is %i characters)\n", name, MAX_NAME); abort();
|
||||
}
|
||||
|
||||
if (strlen(suite) >= MAX_NAME) {
|
||||
printf("ERROR: Test suite '%s' too long (Maximum is %i characters)\n", suite, MAX_NAME); abort();
|
||||
}
|
||||
|
||||
test_t test;
|
||||
test.func = func;
|
||||
pt_title_case(test.name, name);
|
||||
pt_title_case(test.suite, suite);
|
||||
|
||||
tests[num_tests] = test;
|
||||
num_tests++;
|
||||
|
||||
}
|
||||
|
||||
/* Suites */
|
||||
|
||||
static int num_suites = 0;
|
||||
static int num_suites_passes = 0;
|
||||
static int num_suites_fails = 0;
|
||||
|
||||
void pt_add_suite(void (*func)(void)) {
|
||||
num_suites++;
|
||||
func();
|
||||
}
|
||||
|
||||
/* Running */
|
||||
|
||||
static clock_t start, end;
|
||||
static char current_suite[MAX_NAME];
|
||||
|
||||
int pt_run(void) {
|
||||
|
||||
printf(" \n");
|
||||
printf(" +-------------------------------------------+\n");
|
||||
printf(" | ptest MicroTesting Magic for C |\n");
|
||||
printf(" | |\n");
|
||||
printf(" | http://github.com/orangeduck/ptest |\n");
|
||||
printf(" | |\n");
|
||||
printf(" | Daniel Holden (contact@theorangeduck.com) |\n");
|
||||
printf(" +-------------------------------------------+\n");
|
||||
|
||||
signal(SIGFPE, ptest_signal);
|
||||
signal(SIGILL, ptest_signal);
|
||||
signal(SIGSEGV, ptest_signal);
|
||||
|
||||
start = clock();
|
||||
strcpy(current_suite, "");
|
||||
|
||||
unsigned int i;
|
||||
for(i = 0; i < num_tests; i++) {
|
||||
|
||||
test_t test = tests[i];
|
||||
|
||||
/* Check for transition to a new suite */
|
||||
if (strcmp(test.suite, current_suite)) {
|
||||
|
||||
/* Don't increment any counter for first entrance */
|
||||
if (strcmp(current_suite, "")) {
|
||||
if (suite_passing) {
|
||||
num_suites_passes++;
|
||||
} else {
|
||||
num_suites_fails++;
|
||||
}
|
||||
}
|
||||
|
||||
suite_passing = true;
|
||||
strcpy(current_suite, test.suite);
|
||||
printf("\n\n ===== %s =====\n\n", current_suite);
|
||||
}
|
||||
|
||||
/* Run Test */
|
||||
|
||||
test_passing = true;
|
||||
strcpy(assert_err, "");
|
||||
strcpy(assert_err_buff, "");
|
||||
assert_err_num = 0;
|
||||
printf(" | %s ... ", test.name);
|
||||
|
||||
test.func();
|
||||
|
||||
suite_passing = suite_passing && test_passing;
|
||||
|
||||
if (test_passing) {
|
||||
num_tests_passes++;
|
||||
pt_color(GREEN); printf("Passed! \n"); pt_color(WHITE);
|
||||
} else {
|
||||
num_tests_fails++;
|
||||
pt_color(RED); printf("Failed! \n\n%s\n", assert_err); pt_color(WHITE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (suite_passing) {
|
||||
num_suites_passes++;
|
||||
} else {
|
||||
num_suites_fails++;
|
||||
}
|
||||
|
||||
end = clock();
|
||||
|
||||
printf(" \n");
|
||||
printf(" +---------------------------------------------------+\n");
|
||||
printf(" | Summary |\n");
|
||||
printf(" +---------++------------+-------------+-------------+\n");
|
||||
|
||||
printf(" | Suites ||");
|
||||
pt_color(YELLOW); printf(" Total %4d ", num_suites); pt_color(WHITE); printf("|");
|
||||
pt_color(GREEN); printf(" Passed %4d ", num_suites_passes); pt_color(WHITE); printf("|");
|
||||
pt_color(RED); printf(" Failed %4d ", num_suites_fails); pt_color(WHITE); printf("|\n");
|
||||
|
||||
printf(" | Tests ||");
|
||||
pt_color(YELLOW); printf(" Total %4d ", num_tests); pt_color(WHITE); printf("|");
|
||||
pt_color(GREEN); printf(" Passed %4d ", num_tests_passes); pt_color(WHITE); printf("|");
|
||||
pt_color(RED); printf(" Failed %4d ", num_tests_fails); pt_color(WHITE); printf("|\n");
|
||||
|
||||
printf(" | Asserts ||");
|
||||
pt_color(YELLOW); printf(" Total %4d ", num_asserts); pt_color(WHITE); printf("|");
|
||||
pt_color(GREEN); printf(" Passed %4d ", num_assert_passes); pt_color(WHITE); printf("|");
|
||||
pt_color(RED); printf(" Failed %4d ", num_assert_fails); pt_color(WHITE); printf("|\n");
|
||||
|
||||
printf(" +---------++------------+-------------+-------------+\n");
|
||||
printf(" \n");
|
||||
|
||||
double total = (double)(end - start) / CLOCKS_PER_SEC;
|
||||
|
||||
printf(" Total Running Time: %0.3fs\n\n", total);
|
||||
|
||||
if (num_suites_fails > 0) { return 1; } else { return 0; }
|
||||
}
|
19
tests/ptest.h
Normal file
19
tests/ptest.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef ptest_h
|
||||
#define ptest_h
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#define PT_SUITE(name) void name(void)
|
||||
#define PT_TEST(name) auto void name(void); pt_add_test(name, #name, __func__); void name(void)
|
||||
|
||||
#define PT_ASSERT(expr) pt_assert_run((bool)(expr), #expr, __func__, __FILE__, __LINE__)
|
||||
#define PT_ASSERT_STR_EQ(fst, snd) pt_assert_run(strcmp(fst, snd) == 0, "strcmp( " #fst ", " #snd " ) == 0", __func__, __FILE__, __LINE__)
|
||||
|
||||
void pt_assert_run(bool result, const char* expr, const char* func, const char* file, int line);
|
||||
|
||||
void pt_add_test(void (*func)(void), const char* name, const char* suite);
|
||||
void pt_add_suite(void (*func)(void));
|
||||
int pt_run(void);
|
||||
|
||||
#endif
|
@@ -1,27 +1,29 @@
|
||||
#include "ptest.h"
|
||||
#include "../mpc.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/*
|
||||
static bool string_eq(void* x, void* y) { return (strcmp(x, y) == 0); }
|
||||
static void string_print(void* x) { printf("'%s'", (char*)x); }
|
||||
*/
|
||||
|
||||
bool suite_regex(void) {
|
||||
|
||||
void test_regex_basic(void) {
|
||||
|
||||
mpc_parser_t* re0 = mpc_re("abc|bcd");
|
||||
mpc_parser_t* re1 = mpc_re("abc|bcd|e");
|
||||
mpc_parser_t* re2 = mpc_re("abc(ab)*");
|
||||
mpc_parser_t* re2 = mpc_re("ab()c(ab)*");
|
||||
mpc_parser_t* re3 = mpc_re("abc(abdd)?");
|
||||
mpc_parser_t* re4 = mpc_re("ab|c(abdd)?");
|
||||
mpc_parser_t* re5 = mpc_re("abc(ab|dd)+g$");
|
||||
|
||||
mpc_print(re0);
|
||||
mpc_print(re1);
|
||||
mpc_print(re2);
|
||||
mpc_print(re3);
|
||||
mpc_print(re4);
|
||||
mpc_print(re5);
|
||||
PT_ASSERT(mpc_match(re0, "abc", "abc", string_eq, free, string_print));
|
||||
PT_ASSERT(mpc_match(re0, "bcd", "bcd", string_eq, free, string_print));
|
||||
PT_ASSERT(mpc_unmatch(re0, "bc", "bc", string_eq, free, string_print));
|
||||
PT_ASSERT(mpc_unmatch(re0, "ab", "ab", string_eq, free, string_print));
|
||||
PT_ASSERT(mpc_match(re1, "e", "e", string_eq, free, string_print));
|
||||
PT_ASSERT(mpc_match(re2, "abc", "abc", string_eq, free, string_print));
|
||||
PT_ASSERT(mpc_match(re2, "abcabab", "abcabab", string_eq, free, string_print));
|
||||
PT_ASSERT(mpc_match(re2, "abcababd", "abcabab", string_eq, free, string_print));
|
||||
|
||||
mpc_delete(re0);
|
||||
mpc_delete(re1);
|
||||
@@ -29,6 +31,29 @@ bool suite_regex(void) {
|
||||
mpc_delete(re3);
|
||||
mpc_delete(re4);
|
||||
mpc_delete(re5);
|
||||
|
||||
}
|
||||
|
||||
void test_regex_range(void) {
|
||||
|
||||
mpc_parser_t* re0 = mpc_re("abg[abcdef]");
|
||||
mpc_parser_t* re1 = mpc_re("y*[a-z]");
|
||||
mpc_parser_t* re2 = mpc_re("zz(p+)?[A-Z_0\\]123]*");
|
||||
mpc_parser_t* re3 = mpc_re("[^56hy].*$");
|
||||
|
||||
mpc_print(re0);
|
||||
mpc_print(re1);
|
||||
mpc_print(re2);
|
||||
mpc_print(re3);
|
||||
|
||||
return true;
|
||||
mpc_delete(re0);
|
||||
mpc_delete(re1);
|
||||
mpc_delete(re2);
|
||||
mpc_delete(re3);
|
||||
|
||||
}
|
||||
|
||||
void suite_regex(void) {
|
||||
pt_add_test(test_regex_basic, "Test Regex Basic", "Suite Regex");
|
||||
pt_add_test(test_regex_range, "Test Regex Range", "Suite Regex");
|
||||
}
|
19
tests/test.c
19
tests/test.c
@@ -1,15 +1,12 @@
|
||||
#include <stdbool.h>
|
||||
#include "ptest.h"
|
||||
|
||||
bool suite_ident(void);
|
||||
bool suite_math(void);
|
||||
bool suite_regex(void);
|
||||
void suite_core(void);
|
||||
void suite_regex(void);
|
||||
void suite_grammar(void);
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
suite_ident();
|
||||
suite_math();
|
||||
suite_regex();
|
||||
|
||||
return 0;
|
||||
|
||||
pt_add_suite(suite_core);
|
||||
pt_add_suite(suite_regex);
|
||||
pt_add_suite(suite_grammar);
|
||||
return pt_run();
|
||||
}
|
Reference in New Issue
Block a user