diff --git a/README.md b/README.md index 1b9d6f2..5845d5e 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ tinytemplate_compile(const char *src, size_t len, First you need to compile the template into a program and then you can evaluate it by executing the program. ### Compilation -The compilation function expects a source string of the template through `src` and with length in bytes `len`. The string doesn't need to be zero-terminated. The program will be compilated by writing its instructions in the `program` buffer. The `program` array is assumed to have space for a maximum of `max_instr` instructions. If more space would be required to memorize the program, the compilation fails returning the status code `TINYTEMPLATE_STATUS_EMEMORY`. At that point the caller program can either abort or try again with a bigger buffer. If compilation succeded, then the number of instruction is written to the variable `num_instr`. A status code of the compilation is returned through the main return value. The status `TINYTEMPLATE_STATUS_DONE` means all went well, while all other status codes refer to a specific error that occurred. If the compilation failed, an error message is written to the caller-provided buffer `errmsg` whith size `errmax`. +The compilation function expects a source string of the template through `src` and with length in bytes `len`. The string doesn't need to be zero-terminated. The program will be compilated by writing its instructions in the `prog` buffer. The `prog` array is assumed to have space for a maximum of `max_instr` instructions. If more space would be required to memorize the program, the compilation fails returning the status code `TINYTEMPLATE_STATUS_EMEMORY`. At that point the caller program can either abort or try again with a bigger buffer. If compilation succeded, then the number of instruction is written to the variable `num_instr`. A status code of the compilation is returned through the main return value. The status `TINYTEMPLATE_STATUS_DONE` means all went well, while all other status codes refer to a specific error that occurred. If the compilation failed, an error message is written to the caller-provided buffer `errmsg` whith size `errmax`. Here's an example of how you would use this function: ```c diff --git a/python/pytinytemplate.c b/python/pytinytemplate.c new file mode 100644 index 0000000..48f9a89 --- /dev/null +++ b/python/pytinytemplate.c @@ -0,0 +1,143 @@ +#define PY_SSIZE_T_CLEAN +#include +#include "tinytemplate.h" + +typedef struct { + PyObject_HEAD + PyObject *src; + tinytemplate_instr_t *program; +} Template; + +static void +TinyTemplate_dealloc(CustomObject *self) +{ + Py_XDECREF(self->src); + Py_TYPE(self)->tp_free((PyObject *) self); +} + +static PyObject * +TinyTemplate_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + Template *self; + self = (Template *) type->tp_alloc(type, 0); + if (self != NULL) { + self->src = PyUnicode_FromString(""); + if (self->src == NULL) { + Py_DECREF(self); + return NULL; + } + } + return (PyObject *) self; +} + +static int +TinyTemplate_init(Template *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"src", NULL}; + PyObject *src = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, &src)) + return -1; + + if (src) { + PyObject *tmp = self->src; + Py_INCREF(src); + self->src = src; + Py_XDECREF(tmp); + } + return 0; +} + +static PyObject * +TinyTemplate_eval(Template *self, PyObject *args) +{ + PyObject *src = self->src; + + if (src == NULL) { + PyErr_SetString(PyExc_AttributeError, "src"); + return NULL; + } + if (!PyUnicode_Check(src)) + return NULL; // Raise exception? + + PyObject *bytes = PyUnicode_AsEncodedString(src, "UTF-8", "strict"); // Owned reference + if (bytes == NULL) { + .. + return NULL; + } + + char *cstr = PyBytes_AS_STRING(bytes); + + .. + +} + +static PyMemberDef TinyTemplate_members[] = { + {"src", T_OBJECT_EX, offsetof(Template, src), 0, "source string"}, + {NULL} /* Sentinel */ +}; + +static PyMethodDef TinyTemplate_methods[] = { + {"eval", (PyCFunction) TinyTemplate_eval, METH_NOARGS, "Evaluate the template" }, + {NULL} /* Sentinel */ +}; + +static PyTypeObject TemplateType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "tinytemplate.Template", + .tp_doc = PyDoc_STR("A template"), + .tp_basicsize = sizeof(Template), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_new = TinyTemplate_new, + .tp_init = (initproc) TinyTemplate_init, + .tp_dealloc = (destructor) TinyTemplate_dealloc, + .tp_members = TinyTemplate_members, + .tp_methods = TinyTemplate_methods, +}; + +static PyObject* +compile(PyObject *self, PyObject *args) +{ + PyObject *src; + if (!PyArg_ParseTuple(args, "o", &src)) + return NULL; + + TinyTemplate_instr_t *prog = malloc(); + + return PyObject_Call((PyObject*) &TemplateType, src); +} + +static PyMethodDef methods[] = { + {"compile", compile, METH_VARARGS, "Compile a template"}, + {NULL, NULL, 0, NULL}, /* Sentinel */ +}; + +static struct PyModuleDef module = { + PyModuleDef_HEAD_INIT, + "tinytemplate", /* name of module */ + spam_doc, /* module documentation, may be NULL */ + 0, /* size of per-interpreter state of the module, + or -1 if the module keeps state in global variables. */ + methods, +}; + +PyMODINIT_FUNC +PyInit_tinytemplate(void) +{ + if (PyType_Ready(&TemplateType) < 0) + return NULL; + + PyObject *m = PyModule_Create(&module); + if (m == NULL) + return NULL; + + Py_INCREF(&TemplateType); + if (PyModule_AddObject(m, "Template", (PyObject *) &TemplateType) < 0) { + Py_DECREF(&TemplateType); + Py_DECREF(m); + return NULL; + } + + return m; +} \ No newline at end of file diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000..d227c01 --- /dev/null +++ b/python/setup.py @@ -0,0 +1,3 @@ +from distutils.core import setup, Extension +setup(name="tinytemplate", version="1.0", + ext_modules=[Extension("tinytemplate", ["pytinytemplate.c"])]) diff --git a/src/tinytemplate.c b/src/tinytemplate.c index d7427ef..0ba82f4 100644 --- a/src/tinytemplate.c +++ b/src/tinytemplate.c @@ -178,6 +178,7 @@ append_instr(compile_state_t *state, case OPCODE_ITER: case OPCODE_CHLD: case OPCODE_IDX: + instr->operands[0].as_size = va_arg(operands, size_t); break; case OPCODE_NEXT: @@ -388,12 +389,12 @@ next_token_kword_or_ident(scanner_t *scanner, slice_t *slice, { (void) payload; - assert(follows_alpha(scanner)); + assert(follows_alpha(scanner) || follows_char(scanner, '_')); size_t offset = scanner->cur; do scanner->cur++; - while (follows_alpha(scanner)); + while (follows_alpha(scanner) || follows_char(scanner, '_')); size_t length = scanner->cur - offset; if (slice) { @@ -553,7 +554,7 @@ parse_primary(scanner_t *scanner, // Check if the label matches the iteration label if (slice.length == scope->for_child_label.length && !memcmp(scanner->src + slice.offset, scanner->src + scope->for_child_label.offset, slice.length)) { - append_instr(state, OPCODE_CHLD, slice.offset, slice.length); + append_instr(state, OPCODE_CHLD, j); found = true; break; } @@ -563,7 +564,7 @@ parse_primary(scanner_t *scanner, // its length will be 0 and the expression's // label won't match. if (slice.length == scope->for_index_label.length && !memcmp(scanner->src + slice.offset, scanner->src + scope->for_index_label.offset, slice.length)) { - append_instr(state, OPCODE_IDX, slice.offset, slice.length); + append_instr(state, OPCODE_IDX, j); found = true; break; } @@ -1001,7 +1002,7 @@ tinytemplate_compile(const char *src, size_t len, size_t raw_off = scanner.cur; // Start offset of the raw block while (scanner.cur < scanner.len) { // Look for a "{" or the end - while (follows_char(&scanner, '{')) + while (scanner.cur < scanner.len && !follows_char(&scanner, '{')) scanner.cur++; // If the end wasn't reached (a "{" was found) @@ -1147,7 +1148,7 @@ tinytemplate_eval(const char *src, const instr_t *program, assert(iter_depth > 0); // The CHLD instruction can't // be run outside of an iteration. - + assert(iter_idx < iter_depth); // The index of the iteration must // refer to one of the active ones. diff --git a/src/tinytemplate.h b/src/tinytemplate.h index 98982fe..d0a46ca 100644 --- a/src/tinytemplate.h +++ b/src/tinytemplate.h @@ -42,8 +42,7 @@ typedef struct { } tinytemplate_array_t; typedef struct { - const char *str; - size_t len; + const char *str; size_t len; } tinytemplate_string_t; typedef union { diff --git a/tests/fuzz.c b/tests/fuzz.c new file mode 100644 index 0000000..1edbf8b --- /dev/null +++ b/tests/fuzz.c @@ -0,0 +1,59 @@ +#include +#include +#include "../src/tinytemplate.h" + +/* this lets the source compile without afl-clang-fast/lto */ +#ifndef __AFL_FUZZ_TESTCASE_LEN + +ssize_t fuzz_len; +unsigned char fuzz_buf[1024000]; + +#define __AFL_FUZZ_TESTCASE_LEN fuzz_len +#define __AFL_FUZZ_TESTCASE_BUF fuzz_buf +//#define __AFL_FUZZ_INIT() void sync(void); +//#define __AFL_LOOP(x) ((fuzz_len = read(0, fuzz_buf, sizeof(fuzz_buf))) > 0 ? 1 : 0) +//#define __AFL_INIT() sync() + +#endif + +__AFL_FUZZ_INIT(); + +//#pragma clang optimize off +//#pragma GCC optimize("O0") + +static void callback(void *userp, const char *label, size_t label_len, + const char *str, size_t len) +{ + (void) userp; + (void) label; + (void) label_len; + (void) str; + (void) len; +} + +int main(void) +{ + +#ifdef __AFL_HAVE_MANUAL_CONTROL + __AFL_INIT(); +#endif + + unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF; // must be after __AFL_INIT + // and before __AFL_LOOP! + tinytemplate_instr_t program[1024]; + size_t max_instr = sizeof(program)/sizeof(program[0]); + + while (__AFL_LOOP(10000)) { + + int len = __AFL_FUZZ_TESTCASE_LEN; // don't use the macro directly in a + // call! + + tinytemplate_status_t status; + + status = tinytemplate_compile((char*) buf, len, program, max_instr, NULL, NULL, 0); + if (status == TINYTEMPLATE_STATUS_DONE) + status = tinytemplate_eval((char*) buf, program, NULL, NULL, callback, NULL, 0); + } + + return 0; +} \ No newline at end of file diff --git a/tests/samples/000.tt b/tests/samples/000.tt new file mode 100644 index 0000000..7abe9cc --- /dev/null +++ b/tests/samples/000.tt @@ -0,0 +1 @@ +{{1+2}} \ No newline at end of file diff --git a/tests/samples/001.tt b/tests/samples/001.tt new file mode 100644 index 0000000..1404fb0 --- /dev/null +++ b/tests/samples/001.tt @@ -0,0 +1,3 @@ +{% if 1 + 2 %} +text +{% end %} \ No newline at end of file diff --git a/tests/samples/002.tt b/tests/samples/002.tt new file mode 100644 index 0000000..bca1f9a --- /dev/null +++ b/tests/samples/002.tt @@ -0,0 +1,5 @@ +{% if 1 + 2 %} +text +{% else %} +hoyo +{% end %} \ No newline at end of file diff --git a/tests/samples/003.tt b/tests/samples/003.tt new file mode 100644 index 0000000..75e3dce --- /dev/null +++ b/tests/samples/003.tt @@ -0,0 +1,3 @@ +{% for item in items %} + {{item}} +{% end %} \ No newline at end of file diff --git a/tests/test.c b/tests/test.c new file mode 100644 index 0000000..ca0caa2 --- /dev/null +++ b/tests/test.c @@ -0,0 +1,77 @@ + +struct { + const char *src; + const char *exp; + const char *params; +} cases[] = { + +}; + +typedef enum { + SECTION_INPUT, + SECTION_OUTPUT, + SECTION_PARAMS, +} Section; + +static void runTest(const char *src, size_t len) +{ + size_t params_offset; + size_t params_length; + size_t input_offset; + size_t input_length; + size_t output_offset; + size_t output_length; + + size_t i = 0; + while (1) { + while (i < len && src[i] != '@') + i++; + + if (i < len && isalpha(src[i])) { + + size_t kword_off = i; + do + i++; + while (i < len && isalpha(src[i])); + size_t kword_len = i - kword_off; + + bool is_kword = true; + Section section; + if (kword_len == 6 && !strncmp("params", src + kword_off, 6)) { + section = SECTION_PARAMS; + } else if (kword_len == 5 && !strncmp("input", src + kword_off, 5)) { + section = SECTION_INPUT; + } else if (kword_len == 6 && !strncmp("output", src + kword_off, 6)) { + section = SECTION_OUTPUT; + } else + is_kword = false; + + if (is_kword) { + // Get to the end of the line + while (i < len && (src[i] == ' ' || src[i] == '\t' || src[i] == '\r')) + i++; + if (i < len && src[i] != '\n') { + + } + while (i < len && src[i] != '\n') + i++; + if (i < len) { + assert(src[i] == '\n'); + i++; + } + switch (section) { + case SECTION_INPUT: input_offset = i; break; + case SECTION_OUTPUT: output_offset = i; break; + case SECTION_PARAMS: params_offset = i; break; + } + } + } + + } +} + +int main(void) +{ + + return 0; +}