Initial commit

This commit is contained in:
Yann Weber 2020-01-24 16:21:43 +01:00
commit a42b237918
27 changed files with 4037 additions and 0 deletions

258
Doxyfile.mk Normal file
View file

@ -0,0 +1,258 @@
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = pyrpn
PROJECT_BRIEF = "RPN expression JIT compilation & evaluation"
OUTPUT_DIRECTORY = doc
CREATE_SUBDIRS = NO
ALLOW_UNICODE_NAMES = NO
OUTPUT_LANGUAGE = English
BRIEF_MEMBER_DESC = YES
REPEAT_BRIEF = YES
ABBREVIATE_BRIEF = "The $name class" \
"The $name widget" \
"The $name file" \
is \
provides \
specifies \
contains \
represents \
a \
an \
the
ALWAYS_DETAILED_SEC = NO
INLINE_INHERITED_MEMB = NO
FULL_PATH_NAMES = YES
SHORT_NAMES = NO
JAVADOC_AUTOBRIEF = NO
QT_AUTOBRIEF = NO
MULTILINE_CPP_IS_BRIEF = NO
INHERIT_DOCS = YES
SEPARATE_MEMBER_PAGES = NO
TAB_SIZE = 4
OPTIMIZE_OUTPUT_FOR_C = YES
OPTIMIZE_OUTPUT_JAVA = NO
OPTIMIZE_FOR_FORTRAN = NO
OPTIMIZE_OUTPUT_VHDL = NO
MARKDOWN_SUPPORT = YES
TOC_INCLUDE_HEADINGS = 0
AUTOLINK_SUPPORT = YES
BUILTIN_STL_SUPPORT = NO
CPP_CLI_SUPPORT = NO
SIP_SUPPORT = NO
IDL_PROPERTY_SUPPORT = YES
DISTRIBUTE_GROUP_DOC = NO
GROUP_NESTED_COMPOUNDS = NO
SUBGROUPING = YES
INLINE_GROUPED_CLASSES = NO
INLINE_SIMPLE_STRUCTS = NO
TYPEDEF_HIDES_STRUCT = NO
LOOKUP_CACHE_SIZE = 0
EXTRACT_ALL = YES
EXTRACT_PRIVATE = NO
EXTRACT_PACKAGE = NO
EXTRACT_STATIC = NO
EXTRACT_LOCAL_CLASSES = YES
EXTRACT_LOCAL_METHODS = NO
EXTRACT_ANON_NSPACES = NO
HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_CLASSES = NO
HIDE_FRIEND_COMPOUNDS = NO
HIDE_IN_BODY_DOCS = NO
INTERNAL_DOCS = NO
CASE_SENSE_NAMES = NO
HIDE_SCOPE_NAMES = YES
HIDE_COMPOUND_REFERENCE= NO
SHOW_INCLUDE_FILES = YES
SHOW_GROUPED_MEMB_INC = NO
FORCE_LOCAL_INCLUDES = NO
INLINE_INFO = YES
SORT_MEMBER_DOCS = YES
SORT_BRIEF_DOCS = NO
SORT_MEMBERS_CTORS_1ST = NO
SORT_GROUP_NAMES = NO
SORT_BY_SCOPE_NAME = NO
STRICT_PROTO_MATCHING = NO
GENERATE_TODOLIST = YES
GENERATE_TESTLIST = YES
GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = YES
SHOW_FILES = YES
SHOW_NAMESPACES = YES
QUIET = NO
WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES
WARN_NO_PARAMDOC = NO
WARN_AS_ERROR = NO
WARN_FORMAT = "$file:$line: $text"
INPUT = ./
INPUT_ENCODING = UTF-8
FILE_VERSION_FILTER = "git log --format='rev:%h' -- "
FILE_PATTERNS = *.c \
*.cc \
*.cxx \
*.cpp \
*.c++ \
*.java \
*.ii \
*.ixx \
*.ipp \
*.i++ \
*.inl \
*.idl \
*.ddl \
*.odl \
*.h \
*.hh \
*.hxx \
*.hpp \
*.h++ \
*.cs \
*.d \
*.php \
*.php4 \
*.php5 \
*.phtml \
*.inc \
*.m \
*.markdown \
*.md \
*.mm \
*.dox \
*.py \
*.pyw \
*.f90 \
*.f95 \
*.f03 \
*.f08 \
*.f \
*.for \
*.tcl \
*.vhd \
*.vhdl \
*.ucf \
*.asm \
*.qsf
RECURSIVE = NO
EXCLUDE = test.c
EXCLUDE_SYMLINKS = NO
EXAMPLE_PATTERNS = *
EXAMPLE_RECURSIVE = NO
FILTER_SOURCE_FILES = NO
SOURCE_BROWSER = YES
INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = YES
REFERENCED_BY_RELATION = YES
REFERENCES_RELATION = YES
REFERENCES_LINK_SOURCE = YES
SOURCE_TOOLTIPS = YES
USE_HTAGS = NO
VERBATIM_HEADERS = YES
CLANG_ASSISTED_PARSING = NO
ALPHABETICAL_INDEX = YES
COLS_IN_ALPHA_INDEX = 5
GENERATE_HTML = YES
HTML_OUTPUT = html
HTML_FILE_EXTENSION = .html
HTML_COLORSTYLE_HUE = 220
HTML_COLORSTYLE_SAT = 100
HTML_COLORSTYLE_GAMMA = 80
HTML_TIMESTAMP = NO
HTML_DYNAMIC_SECTIONS = NO
HTML_INDEX_NUM_ENTRIES = 100
GENERATE_DOCSET = NO
DOCSET_FEEDNAME = "Doxygen generated docs"
DOCSET_BUNDLE_ID = org.doxygen.Project
DOCSET_PUBLISHER_ID = org.doxygen.Publisher
DOCSET_PUBLISHER_NAME = Publisher
GENERATE_HTMLHELP = NO
GENERATE_CHI = NO
BINARY_TOC = NO
TOC_EXPAND = NO
GENERATE_QHP = NO
QHP_NAMESPACE = org.doxygen.Project
QHP_VIRTUAL_FOLDER = doc
GENERATE_ECLIPSEHELP = NO
ECLIPSE_DOC_ID = org.doxygen.Project
DISABLE_INDEX = NO
GENERATE_TREEVIEW = NO
ENUM_VALUES_PER_LINE = 4
TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO
FORMULA_FONTSIZE = 10
FORMULA_TRANSPARENT = YES
USE_MATHJAX = NO
MATHJAX_FORMAT = HTML-CSS
MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
SEARCHENGINE = YES
SERVER_BASED_SEARCH = NO
EXTERNAL_SEARCH = NO
SEARCHDATA_FILE = searchdata.xml
GENERATE_LATEX = NO
LATEX_OUTPUT = latex
LATEX_CMD_NAME = latex
MAKEINDEX_CMD_NAME = makeindex
COMPACT_LATEX = NO
PAPER_TYPE = a4
PDF_HYPERLINKS = YES
USE_PDFLATEX = YES
LATEX_BATCHMODE = NO
LATEX_HIDE_INDICES = NO
LATEX_SOURCE_CODE = NO
LATEX_BIB_STYLE = plain
LATEX_TIMESTAMP = NO
GENERATE_RTF = NO
RTF_OUTPUT = rtf
COMPACT_RTF = NO
RTF_HYPERLINKS = NO
RTF_SOURCE_CODE = NO
GENERATE_MAN = YES
MAN_OUTPUT = man
MAN_EXTENSION = .3
MAN_LINKS = NO
GENERATE_XML = NO
XML_OUTPUT = xml
XML_PROGRAMLISTING = YES
GENERATE_DOCBOOK = NO
DOCBOOK_OUTPUT = docbook
DOCBOOK_PROGRAMLISTING = NO
GENERATE_AUTOGEN_DEF = NO
GENERATE_PERLMOD = NO
PERLMOD_LATEX = NO
PERLMOD_PRETTY = YES
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = YES
EXPAND_ONLY_PREDEF = NO
SEARCH_INCLUDES = YES
SKIP_FUNCTION_MACROS = YES
ALLEXTERNALS = NO
EXTERNAL_GROUPS = YES
EXTERNAL_PAGES = YES
PERL_PATH = /usr/bin/perl
CLASS_DIAGRAMS = YES
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = YES
DOT_NUM_THREADS = 0
DOT_FONTNAME = Helvetica
DOT_FONTSIZE = 10
CLASS_GRAPH = YES
COLLABORATION_GRAPH = YES
GROUP_GRAPHS = YES
UML_LOOK = NO
UML_LIMIT_NUM_FIELDS = 10
TEMPLATE_RELATIONS = NO
INCLUDE_GRAPH = YES
INCLUDED_BY_GRAPH = YES
CALL_GRAPH = YES
CALLER_GRAPH = YES
GRAPHICAL_HIERARCHY = YES
DIRECTORY_GRAPH = YES
DOT_IMAGE_FORMAT = svg
INTERACTIVE_SVG = YES
DOT_GRAPH_MAX_NODES = 50
MAX_DOT_GRAPH_DEPTH = 0
DOT_TRANSPARENT = NO
DOT_MULTI_TARGETS = NO
GENERATE_LEGEND = YES
DOT_CLEANUP = YES

9
LICENCE.txt Normal file
View file

@ -0,0 +1,9 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="https://www.gnu.org/licenses/gpl-3.0.txt">here</a>.</p>
<hr>
<address>Apache/2.4.7 Server at www.gnu.org Port 443</address>
</body></html>

91
Makefile Normal file
View file

@ -0,0 +1,91 @@
CC=gcc
NASM=nasm
LD=ld
ifeq ($(DEBUG), 1)
CFLAGS=-ggdb -fPIC -Wall -DDEBUG
LDFLAGS=-g
NASMCFLAGS=-g -f elf64
#PYTHON=python3dm
PYTHON=python3-dbg
else
CFLAGS=-fPIC -Wall -Werror
LDFLAGS=-s
NASMCFLAGS=-f elf64
PYTHON=python3
endif
PYTHON_CONFIG=$(PYTHON)-config
PYTHON_CFLAGS=`$(PYTHON_CONFIG) --includes` `$(PYTHON_CONFIG) --cflags`
PYTHON_LDFLAGS=-shared -fPIC `$(PYTHON_CONFIG) --libs` `$(PYTHON_CONFIG) --ldflags|cut -d' ' -f1,2`
all: .deps pyrpn.so
pyrpn.so: python_pyrpn.o python_rpnexpr.o rpn_lib.o rpn_jit.o rpn_parse.o rpn_mutation.o rpn_if.o
$(LD) $(LDFLAGS) $(PYTHON_LDFLAGS) -o $@ $^
python_pyrpn.o: python_pyrpn.c python_rpnexpr.h python_rpnexpr.o rpn_jit.o
$(CC) $(PYTHON_CFLAGS) $(CFLAGS) -c $<
python_rpnexpr.o: python_rpnexpr.c python_rpnexpr.h rpn_jit.o
$(CC) $(PYTHON_CFLAGS) $(CFLAGS) -c $<
rpn_jit.o: rpn_jit.c rpn_jit.h rpn_parse.o rpn_lib.o
$(CC) $(CFLAGS) -c $<
rpn_parse.o: rpn_parse.c rpn_parse.h rpn_lib.o
$(CC) $(CFLAGS) -c $<
rpn_mutation.o: rpn_mutation.c rpn_mutation.h rpn_parse.o
$(CC) $(CFLAGS) -c $<
rpn_if.o: rpn_if.c rpn_if.h rpn_jit.o
$(CC) $(CFLAGS) -c $<
rpn_lib.o: rpn_lib.asm rpn_lib.h
$(NASM) $(NASMCFLAGS) -o $@ $<
# Dirty & quick tests
test: test.o rpn_lib.o rpn_jit.o rpn_parse.o
$(CC) $(CFLAGS) -o $@ $^
test.o: test.c
$(CC) $(CFLAGS) -c -o $@ $<
# Doxygen documentation
doc: doc/.doxygen.stamp
Doxyfile: Doxyfile.mk
echo "PROJECT_NUMBER = `cat VERSION`_rev:`git rev-parse --short HEAD`" > $@
cat $< >> $@
doc/.doxygen.stamp: $(wildcard *.c) $(wildcard *.h) Doxyfile
touch doc/.doxygen.stamp
doxygen 1>/dev/null
echo "Documentation in file://`pwd`/doc/html/index.html"
# Dependencies checking
.deps: check_deps.sh
sh check_deps.sh "$(CC)" "$(LD)" "$(NASM)" "$(PYTHON)" "$(PYTHON_CONFIG)" && touch .deps
.PHONY: clean distclean checks runtest unittest benchmark
checks: runtest unittest benchmark
benchmark: pyrpn.so
PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py
unittest: pyrpn.so
PYTHONPATH=`pwd` $(PYTHON) -m unittest
runtest: test
./test
clean:
-rm -fv *.o pyrpn.so test
-rm -fRv doc/.doxygen.stamp doc/* Doxyfile
distclean: clean
-rm -vf .deps
-rm -Rvf tests/__pycache__

45
README Normal file
View file

@ -0,0 +1,45 @@
rpnifs fast IFS using RPN notation :
====================================
Provides :
----------
- C library for parameterized RPN expression JIT compilation and evaluation
- C library for handling IFS (composed of JIT RPN expressions)
- C library for RPN expression random mutation
- Python bindings : pyrpn Python module (pyrpn.so)
More details on Python module by running :
make && python3 -c "import pyrpn; help(pyrpn)"
More details on C API see Doxygen documentation.
Dependencies :
--------------
- gcc
- nasm
- ld
- python3
- python3 headers
Documentation :
- doxygen
- git
Compilation :
-------------
make
Doxygen documentation :
-----------------------
make doc
www-browser doc/html/index.html
Running self tests and benchmark :
----------------------------------
make checks
Debugging :
-----------
make clean
DEBUG=1 make
DEBUG=1 make check

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.0.1-dev

55
check_deps.sh Executable file
View file

@ -0,0 +1,55 @@
#!/bin/sh
if [ "$#" -ne 5 ]
then
echo "Usage : $0 CC LD NASM PYTHON PYTHON-CONFIG" >&2
exit 1
fi
CC=$1
LD=$2
NASM=$3
PYTHON=$4
PYTHON_CONFIG=$5
error=""
for i in `seq $#`
do
deps=$1
shift 1
echo -n "Searching $deps :\t"
which $deps
if [ "r$?" != "r0" ]
then
echo " NOT FOUND"
error="$error\n$deps not found"
fi
done
if which $CC $PYTHON_CONFIG > /dev/null
then
echo -n "Checking C compiler : "
echo '#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h"
int main() { Py_Initialize(); return 0; }' | $CC `$PYTHON_CONFIG --cflags` -xc -c -o /dev/null -
if [ "r$?" != "r0" ]
then
err="Unable to compile a Python program : missing python headers ?"
error="$error\n$err"
echo $err
else
echo "$CC compile with $PYTHON_CONFIG OK"
fi
fi
if [ -n "$error" ]
then
echo -n "\n======== ERRORS ========"
echo "$error"
exit 1
fi
exit 0

22
config.h Normal file
View file

@ -0,0 +1,22 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __RPN_CONFIG__
#define __RPN_CONFIG__
#define _GNU_SOURCE
#endif

129
python_pyrpn.c Normal file
View file

@ -0,0 +1,129 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#include "python_pyrpn.h"
/**@file python_pyrpn.c
* @brief Python module & type definition
* @ingroup python_ext
*
* This file contains pyrpn Python module definition
*/
PyMethodDef rpnmodule_methods[] = {
{"get_ops", (PyCFunction)pyrpn_ops, METH_NOARGS,
"Returns a valid operands dict"},
{"random_expr", (PyCFunction)pyrpn_random, METH_VARARGS | METH_KEYWORDS,
"Return a random RPN expression"},
{NULL} // Sentinel
};
PyModuleDef rpnmodule = {
PyModuleDef_HEAD_INIT,
"pyrpn",
"Python librarie for RPN evaluation",
-1, // module size
rpnmodule_methods,
NULL, // m_slots
NULL, // m_traverse
NULL, // m_clear
NULL // m_free
};
PyMODINIT_FUNC
PyInit_pyrpn(void)
{
PyObject *mod;
// init module & globals
mod = PyModule_Create(&rpnmodule);
if(mod == NULL) { return NULL; }
// Init RPNExpr type
if(PyType_Ready(&RPNExprType) < 0)
{
return NULL;
}
// Add type to module
Py_INCREF(&RPNExprType);
if(PyModule_AddObject(mod, "RPNExpr", (PyObject*)&RPNExprType) < 0)
{
Py_DECREF(&RPNExprType);
Py_DECREF(mod);
return NULL;
}
return mod;
}
PyObject* pyrpn_ops(PyObject* mod, PyObject* noargs)
{
PyObject *ret, *value;
const rpn_op_t *op;
size_t i;
ret = PyDict_New();
if(!ret)
{
return ret;
}
foreach_rpn_ops(i)
{
op = &rpn_ops[i];
value = Py_BuildValue("C", op->chr);
if(PyDict_SetItemString(ret, op->str, value))
{
Py_DECREF(value);
Py_DECREF(ret);
return NULL;
}
Py_DECREF(value);
}
return ret;
}
PyObject* pyrpn_random(PyObject *mod, PyObject *args, PyObject *kwds)
{
long long int args_count, expr_sz;
char *expr, err_str[128];
PyObject *res;
char *names[] = {"args_count", "token_count", NULL};
expr_sz = 10;
if(!PyArg_ParseTupleAndKeywords(args, kwds, "L|L:pyrpn.random_expr", names,
&args_count, &expr_sz))
{
return NULL;
}
expr = rpn_random(expr_sz, args_count);
if(!expr)
{
snprintf(err_str, 128,
"Error generating random expression : %s",
strerror(errno));
PyErr_SetString(PyExc_RuntimeError, err_str);
return NULL;
}
res = Py_BuildValue("s", expr);
//free(expr);
return res;
}

96
python_pyrpn.h Normal file
View file

@ -0,0 +1,96 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _PYTHON_PYRPN_H__
#define _PYTHON_PYRPN_H__
#include "config.h"
#include <errno.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h"
#include "rpn_jit.h"
#include "python_rpnexpr.h"
/**@defgroup python_ext Python API
* @brief Python API definitions
*
* @ref python_pyrpn.c and @ref python_rpnexpr.c should be compiled in a .so file
* in order to expose a pyrpn Python module.
*
* This module contains functions :
* - pyrpn.get_ops() returning a dict with long & short operations
* - pyrpn.random_expr(args_count, token_count=10) generating a random expression
*
* And it contains the pyrpn.RPNExpr class with following methods :
* - pyrpn.RPNExpr.__init__(self, expression, args_count, stack_size=16)
* - pyrpn.RPNExpr.eval(self, ...) expression evaluation
* - pyrpn.RPNExpr.reset_stack(self) to set all stack items to 0
*/
/**@defgroup python_module pyrpn Python module
* @brief Exposed Python module : pyrpn
* @ingroup python_ext
*/
/**@file python_pyrpn.h
* @brief Python module & type headers
* @ingroup python_module
*
* This file is the header of the pyrpn Python module definition
*/
extern PyTypeObject RPNExprType;
/**@brief Python module initialization function
* @ingroup python_module */
PyMODINIT_FUNC PyInit_pyrpn(void);
/**@brief pyrpn module methods list
* @ingroup python_module */
extern PyMethodDef rpnmodule_methods[];
/**@brief Python module specs
* @ingroup python_module */
extern PyModuleDef rpnmodule;
/**@brief Return a dict with valid operations (short keys and long values)
* @param mod pyrpn module object
* @param noargs Dummy argument for METH_NOARG
* @return The a new dict
* @ingroup python_module
*/
PyObject* pyrpn_ops(PyObject* mod, PyObject* noargs);
/**@brief Return a new Python str with a random RPN expression
* @param mod pyrpn module object
* @param args Position arguments in Python list
* @param kwds Keywords arguments in Python dict
* @ingroup python_module
*/
PyObject* pyrpn_random(PyObject *mod, PyObject *args, PyObject *kwds);
/**@mainpage
* Documentation entrypoints :
* - @ref python_ext
* - @ref rpn_lang
* - @ref rpn
*/
#endif

283
python_rpnexpr.c Normal file
View file

@ -0,0 +1,283 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#include "python_rpnexpr.h"
PyMethodDef RPNExpr_methods[] = {
{"eval", (PyCFunction)rpnexpr_eval, METH_FASTCALL, "Evaluate an expression"},
{"reset_stack", (PyCFunction)rpnexpr_reset_stack, METH_NOARGS,
"Reset stack memory storage (set all items to 0)"},
{NULL} //Sentinel
};
PyMemberDef RPNExpr_members[] = {
{NULL}
};
PyTypeObject RPNExprType = {
PyVarObject_HEAD_INIT(NULL, 0)
"rpn.RPNExpr", /* tp_name */
sizeof(PyRPNExpr_t), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)rpnexpr_del, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
rpnexpr_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
rpnexpr_str, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT |
Py_TPFLAGS_BASETYPE, /* tp_flags */
"RPN expression evaluator", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
RPNExpr_methods, /* tp_methods */
RPNExpr_members, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
rpnexpr_init, /* tp_init */
0, /* tp_alloc */
rpnexpr_new, /* tp_new */
};
PyObject* rpnexpr_new(PyTypeObject *subtype, PyObject *args, PyObject* kwds)
{
PyObject *ret, *err;
PyRPNExpr_t *expr;
ret = PyType_GenericNew(subtype, args, kwds);
if((err = PyErr_Occurred()))
{
Py_DECREF(err);
return ret;
}
expr = (PyRPNExpr_t*)ret;
expr->rpn = NULL;
expr->args = NULL;
return ret;
}
int rpnexpr_init(PyObject *self, PyObject *args, PyObject *kwds)
{
PyRPNExpr_t *expr_self;
char *names[] = {"expression", "args_count", "stack_size", NULL};
char err_str[256];
const char *expr;
long long int args_count, stack_size;
expr_self = (PyRPNExpr_t*)self;
stack_size = 16;
expr_self->rpn = NULL;
if(!PyArg_ParseTupleAndKeywords(args, kwds, "sL|L:RPNExpr.__init__", names, &expr,
&args_count, &stack_size))
{
return -1;
}
if(strlen(expr) == 0)
{
PyErr_SetString(PyExc_ValueError,
"RpnExpr.__init__() expect expression argument to be not empty");
return -1;
}
if(args_count < 0 || args_count > 255)
{
snprintf(err_str, 128,
"Argument count should be in [4..255] but %lld given",
args_count);
PyErr_SetString(PyExc_ValueError, err_str);
return -1;
}
if(stack_size < 4 || stack_size > 255)
{
snprintf(err_str, 128,
"Stack size should be in [0..255] but %lld given",
stack_size);
PyErr_SetString(PyExc_ValueError, err_str);
return -1;
}
expr_self->rpn = malloc(sizeof(rpn_expr_t));
if(!expr_self->rpn)
{
snprintf(err_str, 256,
"Expression memory allocation error : %s",
strerror(errno));
}
bzero(expr_self->rpn, sizeof(rpn_expr_t));
if(rpn_expr_init(expr_self->rpn, stack_size, args_count) < 0)
{
snprintf(err_str, 256,
"Expression init error : %s",
expr_self->rpn->err_reason);
PyErr_SetString(PyExc_ValueError, err_str);
return -1;
}
if(!stack_size)
{
expr_self->args = NULL;
return 0;
}
expr_self->args = malloc(sizeof(unsigned long) * args_count);
if(!expr_self->args)
{
snprintf(err_str, 256,
"Error allocating arguments memory : %s",
strerror(errno));
return -1;
}
if(rpn_expr_compile(expr_self->rpn, expr))
{
PyErr_SetString(PyExc_ValueError, expr_self->rpn->err_reason);
return -1;
}
return 0;
}
void rpnexpr_del(PyObject *self)
{
PyRPNExpr_t *expr_self;
expr_self = (PyRPNExpr_t*)self;
if(expr_self->rpn)
{
rpn_expr_close(expr_self->rpn);
free(expr_self->rpn);
expr_self->rpn = NULL;
}
if(expr_self->args)
{
free(expr_self->args);
expr_self->args = NULL;
}
}
PyObject* rpnexpr_eval(PyObject* self, PyObject** argv, Py_ssize_t argc)
{
PyRPNExpr_t *expr_self;
unsigned long res;
char err_str[128];
Py_ssize_t i;
PyObject *cur, *ret;
expr_self = (PyRPNExpr_t*)self;
if((unsigned long)argc != expr_self->rpn->args_count)
{
snprintf(err_str, 128,
"RPNExpr expected %ld arguments but %ld given",
expr_self->rpn->args_count,
argc);
PyErr_SetString(PyExc_ValueError, err_str);
return NULL;
}
for(i=0; i<argc; i++)
{
cur = argv[i];
if(!PyLong_Check(cur))
{
snprintf(err_str, 128,
"RpnExpr.__call__ expect int as arguments but argument %ld is not",
i+1);
PyErr_SetString(PyExc_ValueError, err_str);
return NULL;
}
expr_self->args[i] = PyLong_AsUnsignedLong(argv[i]);
if((ret = PyErr_Occurred()))
{
Py_DECREF(ret);
return NULL;
}
}
res = rpn_expr_eval(expr_self->rpn, expr_self->args);
//dprintf(2, "[RES=%lu]\n", res);
return PyLong_FromUnsignedLong(res);
}
PyObject* rpnexpr_reset_stack(PyObject *self, PyObject *noargs)
{
rpn_expr_reset_stack(((PyRPNExpr_t*)self)->rpn);
Py_RETURN_NONE;
}
PyObject* rpnexpr_str(PyObject *self)
{
PyRPNExpr_t *expr_self;
PyObject *res;
expr_self = (PyRPNExpr_t*)self;
res = Py_BuildValue("s", expr_self->rpn->expr);
return res;
}
PyObject* rpnexpr_repr(PyObject *self)
{
PyRPNExpr_t *expr_self;
PyObject *res;
size_t sz;
char *buff, err_str[128];
expr_self = (PyRPNExpr_t*)self;
sz = snprintf(NULL, 0, "<RPNExpr argc:%ld stck_sz:%d '%s'>",
expr_self->rpn->args_count, expr_self->rpn->stack_sz,
expr_self->rpn->expr);
buff = malloc(sizeof(char) * (sz + 1));
if(!buff)
{
snprintf(err_str, 128,
"Error allocating repr : %s",
strerror(errno));
PyErr_SetString(PyExc_RuntimeError, err_str);
return NULL;
}
snprintf(buff, sz+1, "<RPNExpr argc:%ld stck_sz:%d '%s'>",
expr_self->rpn->args_count, expr_self->rpn->stack_sz,
expr_self->rpn->expr);
res = Py_BuildValue("s", buff);
free(buff);
return res;
}

120
python_rpnexpr.h Normal file
View file

@ -0,0 +1,120 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _PYTHON_PYRPN_H__
#define _PYTHON_PYRPN_H__
#include "config.h"
#include <errno.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h"
#include "rpn_jit.h"
/**@defgroup python_type RPNExpr Python class
* @brief Exposed Python class : RPNExpr
* @ingroup python_ext
*/
/**@file python_rpnexpr.h
* @brief Python RPNExpr type headers
* @ingroup python_type
*
* This file is the header of the RPNExpr Python class
*/
/**@brief RPNExpr Python class methods list
* @ingroup python_type */
extern PyMethodDef RPNExpr_methods[];
/**@brief RPNExpr Python class members list
* @ingroup python_type */
extern PyMemberDef RPNExpr_members[];
/**@brief RPNExpr Python class type definition
* @ingroup python_type */
extern PyTypeObject RPNExprType;
/**@brief Structure holding RPNExpr objects
* @ingroup python_type */
typedef struct
{
PyObject_VAR_HEAD;
/**@brief Pointer on @ref rpn_expr_s */
rpn_expr_t *rpn;
/**@brief Array storing expression argument
* @note As attribute of rpn_expr allowing malloc & free only once */
unsigned long *args;
} PyRPNExpr_t;
/**@brief RpnExpr __new__ method
* @param subtype Type of object being created (pyrpn.RPNExpr)
* @param args positional arguments for subtype
* @param kwargs keyword argumenrs for subtype
* @return The new Python RPNExpr object
*/
PyObject* rpnexpr_new(PyTypeObject *subtype, PyObject *args, PyObject* kwds);
/**@brief RpnExpr constructor
* @param self New RPNExpr instance
* @param args Positional arguments list
* @param kwds Keywords arguments dict
* @return 0 if no error else -1
* @ingroup python_type
*/
int rpnexpr_init(PyObject *self, PyObject *args, PyObject *kwds);
/**@brief RPNExpr __del__ method
* @param self RPNExpr instance
* @ingroup python_type
*/
void rpnexpr_del(PyObject *self);
/**@brief Eval an RPN expression given arguments and return the
* value
* @param self RPNExpr instance
* @param argv Array of PyObject* arguments
* @param argc Number of arguments
* @return The value resulting of evaluation
* @ingroup python_type
*/
PyObject* rpnexpr_eval(PyObject* self, PyObject** argv, Py_ssize_t argc);
/**@brief Set all stack item to zero
* @param self RPNExpr instance
* @param noargs Dummy argument for METH_NOARG
* @return None
* @ingroup python_type
*/
PyObject* rpnexpr_reset_stack(PyObject *self, PyObject *noargs);
/**@brief RPNExpr.__repr__()
* @param self RPNExpr instance
* @ingroup python_type
*/
PyObject* rpnexpr_repr(PyObject *self);
/**@brief RPNExpr.__str__()
* @param self RPNExpr instance
* @ingroup python_type
*/
PyObject* rpnexpr_str(PyObject *self);
#endif

145
rpn_if.c Normal file
View file

@ -0,0 +1,145 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#include "rpn_if.h"
rpn_if_t* rpn_if_new(size_t mem_sz, rpn_if_transfo_t *if2rpn,
rpn_if_transfo_t *rpn2if, rpn_expr_t *rpn)
{
rpn_if_t *res;
if(mem_sz != 0)
{
if((if2rpn->mem_sz != 0 && if2rpn->mem_sz != mem_sz) ||
(rpn2if->mem_sz != 0 && rpn2if->mem_sz != mem_sz))
{
errno = EINVAL;
return NULL;
}
}
else
{
if(if2rpn->mem_sz == 0 && rpn2if->mem_sz == 0)
{
errno = EINVAL;
return NULL;
}
if(if2rpn->mem_sz != rpn2if->mem_sz && if2rpn->mem_sz != 0 &&
rpn2if->mem_sz != 0)
{
errno = EINVAL;
return NULL;
}
mem_sz = if2rpn->mem_sz != 0?if2rpn->mem_sz:rpn2if->mem_sz;
}
if(if2rpn->data_sz != rpn2if->data_sz || if2rpn->data_sz == 0)
{
errno = EINVAL;
return NULL;
}
res = _rpn_if_new(mem_sz, if2rpn->argc, rpn2if->argc, rpn2if->data_sz);
if(!res)
{
return NULL;
}
res->if2rpn = if2rpn->if2rpn;
res->rpn2if = rpn2if->rpn2if;
res->rpn = rpn;
return res;
}
void rpn_if_free(rpn_if_t* rif)
{
size_t i;
for(i=0; i<rif->rpn_sz; i++)
{
rpn_expr_close(&(rif->rpn[i]));
}
free(rif->rpn);
free(rif->rpn_res);
munmap(rif->mem, rif->mem_sz);
free(rif);
}
int rpn_if_rpn_upd(rpn_if_t *rif, rpn_tokenized_t *rpns)
{
return rpn_if_rpn_upd_rng(rif, rpns, rif->rpn_sz, 0);
}
int rpn_if_rpn_upd_rng(rpn_if_t *rif, rpn_tokenized_t *rpns, size_t sz,
size_t offset)
{
size_t i;
for(i=offset; i<offset+sz; i++)
{
/**@todo continue implementation */
}
return 0;
}
rpn_if_t* _rpn_if_new(size_t mem_sz, size_t rpn_argc, size_t rpn_count, size_t
value_sz)
{
rpn_if_t *res;
int err;
res = malloc(sizeof(rpn_if_t));
if(!res)
{
goto error;
}
res->rpn_sz = rpn_count;
res->rpn_argc = rpn_argc;
res->mem_sz = mem_sz;
res->value_sz = value_sz;
res->mem = mmap(NULL, mem_sz, PROT_READ|PROT_WRITE, MAP_ANON, -1, 0);
if(res->mem == (void*)-1)
{
goto mmap_err;
}
res->rpn_res = malloc(sizeof(unsigned long) * (rpn_count +rpn_argc));
if(!res->rpn_res)
{
goto rpn_malloc_err;
}
res->rpn_args = &(res->rpn_res[res->rpn_sz]);
res->rpn = malloc(sizeof(rpn_expr_t) * res->rpn_sz);
if(!res->rpn)
{
goto rpn_expr_err;
}
return res;
rpn_expr_err:
err = errno;
free(res->rpn_res);
rpn_malloc_err:
err = errno;
munmap(res->mem, mem_sz);
mmap_err:
err = errno;
free(res);
error:
err = errno;
errno = err;
return NULL;
}

220
rpn_if.h Normal file
View file

@ -0,0 +1,220 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __rpn_if__h__
#define __rpn_if__h__
#include "config.h"
#include "rpn_jit.h"
/**@defgroup ifs_if Iterated function
* @brief Iterated RPN expression
*
* A single Iterated Function implemented using an RPN expression.
*
* @note The goal is to optimize iteration writing the code in C and providing
* an high level Python API.
*
* For more details about IF see dedicated page : @ref doc_ifs_if
*/
typedef void* rpn_if_arg_t;
typedef unsigned long rpn_arg_t;
typedef struct rpn_if_s rpn_if_t;
typedef struct rpn_if_res_s rpn_if_res_t;
typedef struct rpn_if_state_s rpn_if_state_t;
typedef struct rpn_if_transfo_s rpn_if_transfo_t;
typedef enum rpn_if_transfo_type_e rpn_if_transfo_type_t;
/**@brief IF state to RPN arguments transformation */
typedef void (*if2rpn_f)(rpn_if_t *rif, rpn_if_state_t, rpn_arg_t*);
/**@brief RPN arguments to IF state transformation */
typedef rpn_if_res_t (*rpn2if_f)(rpn_if_t *rif, rpn_arg_t, rpn_if_state_t);
/**@brief IF state
*
* Will always be something like :
* - memory adress/offset/index
* - value
*/
struct rpn_if_state_s
{
/**@brief Data address */
size_t i;
/**@brief Data value(s) */
void *val;
};
/**@brief Indicate function type for @ref rpn_if_transfo_s */
enum rpn_if_transfo_type_e
{
/**@brief No transformation
*
* Default behavior is to copy data in args directly assuming
* argc * sizeof(unsigned long) = data_sz */
RPN_f_null,
/**@brief Transform RPN result into data */
RPN_f_rpn2if,
/**@brief Transform data into RPN result */
RPN_f_if2rpn
};
/**@brief Represent an IF transformation function
*
* Can transform data into arguments or arguments into data, depending
* on function type.
*/
struct rpn_if_transfo_s
{
/**@brief Function pointer type
* @note optionnal, for type checking
*/
rpn_if_transfo_type_t type;
/**@brief Data size (a @ref rpn_if_s::mem item ) in bytes */
size_t data_sz;
/**@brief Memory size in byte */
size_t mem_sz;
/**@brief RPN arg/result size
*
* - if type is RPN_if2rpn argc is the rpn expression argc
* - if type is RPN_rpn2if argc is the rpn expression count (results
* count)
*/
size_t argc;
union {
/**@brief IF state to RPN arguments transformation */
if2rpn_f if2rpn;
/**@brief RPN arguments to IF state transformation */
rpn2if_f rpn2if;
};
};
/**@brief Generic Iterated function implementation */
struct rpn_if_s
{
/**@brief Memory map in wich data are fetch & stored */
void *mem;
/**@brief Memory map size in bytes */
size_t mem_sz;
/**@brief Size of a memory item */
size_t value_sz;
/**@brief IF last position + value buffer */
rpn_if_state_t state;
/**@brief RPN expression(s) result(s) buffer */
unsigned long *rpn_res;
/**@brief Arguments given to RPN expression(s) buffer */
rpn_arg_t *rpn_args;
/**@brief Number of arguments expected by RPN expressions */
size_t rpn_argc;
/**@brief RPN expression(s) pointer(s) */
rpn_expr_t *rpn;
/**@brief RPN expression count */
size_t rpn_sz;
/**@brief IF state to RPN arguments transformation */
if2rpn_f if2rpn;
/**@brief RPN arguments to IF state transformation */
rpn2if_f rpn2if;
};
/**@brief Alloc a new @ref rpn_if_s from two transformation functions
* @param if2rpn informations about data to rpn args transformation
* @param rpn2if informations about rpn args to data transformation
* @param rpn list of RPN expresions ( must be of rpn2if->argc size !!!)
* @return A pointer on an allocated @ref rpn_if_s
*/
rpn_if_t* rpn_if_new(size_t mem_sz, rpn_if_transfo_t *if2rpn,
rpn_if_transfo_t *rpn2if, rpn_expr_t *rpn);
/**@brief Deallocate an @ref rpn_ifs_s and close associated @ref rpn_expr_s
* @param rif The IF to deallocate
*/
void rpn_if_free(rpn_if_t *rif);
/**@brief Update all RPN expressions
* @param rif The concerned IF
* @param rpn A list of tokenized expressions (must be of rif->rpn_sz size)
* @return 0 if no error else -1
* @note Shortcut for @ref rpn_if_rpn_upd_rng(rif, rpns, rif->rpn_sz, 0);
*/
int rpn_if_rpn_upd(rpn_if_t *rif, rpn_tokenized_t *rpns);
/**@brief Update a range of RPN expressions
* @param rif The concerned IF
* @param rpn A list of tokenized expressions
* @param sz Number of rpn expression in rpn argument
* @param offset Start updating expressions from this offset
* @return 0 if no error else -1
*/
int rpn_if_rpn_upd_rng(rpn_if_t *rif, rpn_tokenized_t *rpns, size_t sz,
size_t offset);
/**@brief New @ref rpn_if_s and partial initialisation
* @param mem_sz memory size in bytes
* @param argc number of arguments taken by @ref rpn_expr_s
* @param rpn_count number of @ref rpn_expr_s
*/
rpn_if_t* _rpn_if_new(size_t mem_sz, size_t rpn_argc, size_t rpn_count,
size_t value_sz);
/**@page doc_ifs
*
* Iterated functions system are a composed of Iterated function choosed
* randomly.
*
* @section doc_ifs_if Iterated function general considerations
*
* Iterated functions can be seen as transformations in a given space.
* Functions takes items as argument and set an item as a result.
*
* For the moment, the main goal is to interact with a 2 dimension world with
* 1 to 3 values per items (an image in grayscale or in RGB).
*
* A simple approach can be to use a single expression working with a single
* number later decomposed in multiple composant using bitmasks (basically
* for storage address and stored value).
*
* This can later be decomposed by assigning one (or multiple) expression
* to each composant (one expression for storage address, another one for
* the storage value).
*
* The same consideration can be done on argument number/composition taken
* by the expression.
*
* @subsection doc_ifs_if_io Iterated function generalisation
*
* A generic implementation can be IF as :
* - A generic input transformation system : X arguments transformed in Y
* RPNExpression arguments
* - A generic output system : N results (from N RPNExpr) transformed in X
* results
* - A generic transformation system : N expressions, taking Y arguments
*
* @section doc_ifs_if_api Iterated Function API
*
* Multiple steps are expected in API development :
* - A simple generic API will be defined (something like expecting a
* void* fun(void*) transforming X data in Y result using a blackbox optimized
* behavior associated with a memory map
* - Helper function will be written allowing C and/or Python extensions
*/
#endif

41
rpn_ifs.h Normal file
View file

@ -0,0 +1,41 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __rpn_ifs__h__
#define __rpn_ifs__h__
#include "config.h"
#include "rpn_jit.h"
/**@defgroup ifs Iterated function system
* @brief IFS implementation with RPN expressions
*
* IFS are basically a list of @ref ifs_if associated with a probability
* of evaluation.
*
* This implementation aims to :
* - optimize @ref ifs_if calls
* - optimize random number generation and IF random calls
*
* @note It aims to provide an API close to @ref ifs_if API, in order to
* be able to use both IFS and IF almost tansparently via Python API.
*/
#endif

465
rpn_jit.c Normal file
View file

@ -0,0 +1,465 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#include "rpn_jit.h"
int rpn_expr_init(rpn_expr_t* expr, const unsigned char stack_sz,
const size_t args_count)
{
#ifdef DEBUG
if(!expr)
{
dprintf(2, "Error, NULL ptr given as expression to rpn_expr_init");
errno = EINVAL;
return -1;
}
#endif
bzero(expr, sizeof(rpn_expr_t));
expr->stack_sz = stack_sz;
expr->args_count = args_count;
expr->state = RPN_SOURCE;
memset(expr->err_reason, (int)'\0', 128);
expr->stack = malloc(sizeof(unsigned long) * stack_sz);
if(!expr->stack)
{
snprintf(expr->err_reason, 128,
"Unable to malloc stack : %s", strerror(errno));
expr->state = RPN_ERROR;
return -1;
}
bzero(expr->stack, sizeof(unsigned long) * stack_sz);
if(_rpn_expr_init_map(expr) < 0)
{
snprintf(expr->err_reason, 128,
"Unable to init code map : %s", strerror(errno));
free(expr->expr);
expr->state = RPN_ERROR;
return -1;
}
return 0;
}
int rpn_expr_compile(rpn_expr_t *expr, const char *code)
{
#ifdef DEBUG
if(!expr)
{
dprintf(2, "Error, NULL ptr given as expression to rpn_expr_compile");
errno = EINVAL;
return -1;
}
#endif
expr->expr = strdup(code);
if(!expr->expr)
{
snprintf(expr->err_reason, 128,
"Unable to strdup expression : %s", strerror(errno));
expr->state = RPN_ERROR;
return -1;
}
return _rpn_expr_compile_expr(expr);
}
int rpn_expr_untokenize(rpn_expr_t *expr, rpn_tokenized_t *tokens, char long_op)
{
int err;
size_t i;
errno = 0;
#ifdef DEBUG
if(!expr)
{
dprintf(2, "Error, NULL ptr given as expression to rpn_expr_untokenize");
err = EINVAL;
goto ret_err;
}
if(tokens->argc != expr->args_count)
{
/* even if it should work with tokens->argc < expr->args_count */
snprintf(expr->err_reason, 128,
"Expression argc differ from tokenized version");
err = EINVAL;
goto ret_err;
}
#endif
if(!(expr->expr = rpn_tokenized_expr(tokens, long_op)))
{
err = errno;
snprintf(expr->err_reason, 128,
"Error reading tokenized expression : %s",
strerror(err));
goto ret_err;
}
for(i=0; i<tokens->tokens_sz; i++)
{
if(_rpn_expr_token_copy(expr, &(tokens->tokens[i])) < 0)
{
err = errno;
if(errno == EINVAL)
{
dprintf(2,
"Fatal error, unknown token type : %d.\nMemory corruption ?\n",
tokens->tokens[i].type);
exit(1);
}
snprintf(expr->err_reason, 128,
"Untokenize error : %s",
strerror(err));
goto ret_err;
}
}
return 0;
ret_err:
errno = err;
return -1;
}
char* rpn_random(size_t op_sz, size_t args_count)
{
double step;
size_t i, buff_sz, offset, rnd;
char *buff, *cur;
unsigned char op_n;
unsigned long int seed, rnd_val;
int nchr, err;
buff_sz = offset = 0;
buff = NULL;
step = 1.0 / (rpn_op_sz() + (args_count>0?2:1)); // + args and values
if(getrandom(&seed, sizeof(long int), 0) < 0)
{
err=errno;
perror("Fails to get a random number from kernel");
errno=err;
return NULL;
}
srand48(seed);
for(i=0; i<op_sz; i++)
{
if(buff_sz - offset < 21)
{
buff_sz += 40;
cur = realloc(buff, sizeof(char) * buff_sz);
if(!cur)
{
err=errno;
perror("Error allocating random expression");
errno=err;
return NULL;
}
buff=cur;
}
cur = buff + offset;
*cur = '\0';
op_n = drand48() / step;
if(op_n < rpn_op_sz())
{
cur[0] = rpn_ops[op_n].chr;
cur[1] = ' ';
cur[2] = '\0';
offset += 2;
}
else if(op_n == rpn_op_sz())
{
if(getrandom(&rnd_val, sizeof(long int), 0) < 0)
{
err=errno;
perror("Fails to get a random number for value");
errno=err;
return NULL;
}
// values
if((nchr = sprintf(cur, "0x%lX ", rnd_val)) < 0)
{
err=errno;
perror("Error while sprintf arguments in random generator");
errno=err;
return NULL;
}
offset += nchr;
}
else
{
rnd = drand48() / (1.0 / args_count);
// arguments
if((nchr = sprintf(cur, "A%ld ", rnd)) < 0)
{
err=errno;
perror("Error while sprintf arguments in random generator");
errno=err;
return NULL;
}
offset += nchr;
}
}
buff[offset] = '\0';
return buff;
}
int _rpn_expr_compile_expr(rpn_expr_t* expr)
{
rpn_tokenizer_t tokenizer;
rpn_token_t *token;
if(expr->state == RPN_ERROR)
{
goto err;
}
if(rpn_tokenizer_start(&tokenizer, &(expr->toks), expr->expr,
expr->args_count) < 0)
{
snprintf(expr->err_reason, 128,
"Error starting tokenizer : %s",
tokenizer.err_reason);
goto err;
}
while((token = rpn_tok(&tokenizer)))
{
if(_rpn_expr_token_copy(expr, token) < 0)
{
if(errno == EINVAL)
{
dprintf(2,
"Fatal error, unknown token type : %d chr %ld.\nMemory corruption ?\n",
token->type, tokenizer.chr_no);
exit(1);
}
snprintf(expr->err_reason, 128,
"Compilation error on chr %ld, unable to copy code part : %s",
tokenizer.chr_no, strerror(errno));
goto err;
}
}
if(rpn_tokenizer_error(&tokenizer))
{
snprintf(expr->err_reason, 128,
"Compilation error, chr %ld : %s",
tokenizer.chr_no, tokenizer.err_reason);
goto err;
}
if(_rpn_expr_end_map(expr))
{
snprintf(expr->err_reason, 128,
"Error ending code map : %s",
strerror(errno));
expr->state = RPN_ERROR;
return -1;
}
expr->state = RPN_READY;
return 0;
err:
expr->state = RPN_ERROR;
return -1;
}
int _rpn_expr_compile_tokens(rpn_expr_t* expr)
{
size_t i;
rpn_token_t *token;
for(i=0; i<expr->toks.tokens_sz; i++)
{
token = &(expr->toks.tokens[i]);
if(_rpn_expr_token_copy(expr, token) < 0)
{
if(errno == EINVAL)
{
dprintf(2,
"Fatal error, unknown token type : %d\nMemory corruption ?\n",
token->type);
exit(1);
}
snprintf(expr->err_reason, 128,
"Compilation error, unable to copy code part : %s",
strerror(errno));
expr->state = RPN_ERROR;
return -1;
}
}
if(_rpn_expr_end_map(expr))
{
snprintf(expr->err_reason, 128,
"Error ending code map : %s",
strerror(errno));
expr->state = RPN_ERROR;
return -1;
}
expr->state = RPN_READY;
return 0;
}
unsigned long rpn_expr_eval(rpn_expr_t *expr, unsigned long *args)
{
rpn_run_f expr_run;
unsigned long int res;
if(expr->state == RPN_ERROR)
{
return 0;
}
expr_run = expr->code_map;
res = expr_run(expr->stack_sz, args, expr->stack);
return res;
}
void rpn_expr_close(rpn_expr_t* expr)
{
if(expr->expr)
{
free(expr->expr);
expr->expr = NULL;
}
if(expr->stack)
{
free(expr->stack);
expr->stack = NULL;
}
if(expr->code_map)
{
if(munmap(expr->code_map, expr->code_map_sz))
{
perror("Unable to unmap code_map");
}
expr->code_map = NULL;
}
}
void rpn_expr_reset_stack(rpn_expr_t *expr)
{
bzero(expr->stack, sizeof(unsigned long) * expr->stack_sz);
}
int _rpn_expr_token_copy(rpn_expr_t *expr, rpn_token_t *token)
{
unsigned long int *value;
rpn_op_t local_op;
value = NULL;
switch(token->type)
{
case RPN_op:
local_op = *(token->op);
value = NULL;
break;
case RPN_arg:
local_op.fun = &rpn_arg;
local_op.fun_sz = &(CODE_SZ(rpn_arg));
value = &(token->arg_n);
break;
case RPN_val:
local_op.fun = &rpn_value;
local_op.fun_sz = &(CODE_SZ(rpn_value));
value = &(token->value);
break;
default:
errno = EINVAL;
return -1;
}
if(_rpn_code_part_cpy(expr, local_op.fun, *(local_op.fun_sz),
value))
{
return -1;
}
return 0;
}
int _rpn_code_part_cpy(rpn_expr_t *expr, const void *code_part,
unsigned long code_part_sz, const unsigned long *value)
{
size_t old_sz, code_sz;
void *new_ptr;
//printf("DEBUG _copy : %p %ld %p:%ld\n", code_part, code_part_sz, value, value?*value:0);
code_sz = expr->code_map_ptr - expr->code_map;
if(!expr->code_map_sz)
{
errno = EINVAL;
return -1;
}
if(code_sz + code_part_sz >= expr->code_map_sz)
{
old_sz = expr->code_map_sz;
expr->code_map_sz = (((code_sz + code_part_sz)>>9)+1)<<9;
new_ptr = mremap(expr->code_map, old_sz, expr->code_map_sz,
MREMAP_MAYMOVE);
if(new_ptr == (void*)-1)
{
expr->code_map_sz = 0;
return -1;
}
expr->code_map = new_ptr;
expr->code_map_ptr = expr->code_map + code_sz;
}
memcpy(expr->code_map_ptr, code_part, code_part_sz);
if(value)
{
// set 1st instruction argument
*(unsigned long*)(expr->code_map_ptr + 2) = *value;
}
expr->code_map_ptr += code_part_sz;
return 0;
}
int _rpn_expr_init_map(rpn_expr_t* expr)
{
expr->code_map_sz = RPN_MAP_CHUNK;
expr->code_map = mmap(NULL, expr->code_map_sz, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if(!expr->code_map)
{
return -1;
}
expr->code_map_ptr = expr->code_map;
if(CODE_PART_CPY(expr, rpn_exec))
{
return -1;
}
return 0;
}
int _rpn_expr_end_map(rpn_expr_t *expr)
{
if(CODE_PART_CPY(expr, rpn_exec_ret))
{
return -1;
}
if(mprotect(expr->code_map, expr->code_map_ptr - expr->code_map,
PROT_EXEC))
{
return -1;
}
return 0;
}

251
rpn_jit.h Normal file
View file

@ -0,0 +1,251 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __rpn_jit__h__
#define __rpn_jit__h__
#include "config.h"
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/random.h>
#include "rpn_lib.h"
#include "rpn_parse.h"
/**@defgroup rpn RPN evaluation lib
* @brief High speed RPN expression evaluation librarie
*/
/**@defgroup rpn_compile RPN expression compilation
* @brief How is an RPN expression transformed into a callable function
*
* Compilation is done in two steps :
* - @ref rpn_tokenize
* - @ref rpn_exec
* @ingroup rpn
*/
/**@defgroup rpn_cmap Executable code map creation
* @brief A code map containing compiled code to evaluate the expression
*
* In order to provide a callable memory adress, the compilation process starts
* by creating a new memory map. Then @ref rpn_lib precompiled symbols are
* copyied in it, suffixed by @ref rpn_exec_ret symbol. Finally, the map is set executable using mprotect.
* @ingroup rpn_compile
*/
/**@file rpn_jit.h
* @brief Contains structure & functions for rpn_expr_t manipulation & evaluation
* @ingroup rpn
*/
/**@brief Mremap chunk size */
#define RPN_MAP_CHUNK 512
/**@brief @ref rpn_expr_t error state */
#define RPN_ERROR -1
/**@brief @ref rpn_expr_t initialization state */
#define RPN_SOURCE 0
/**@brief @ref rpn_expr_t ready to eval state */
#define RPN_READY 1
/**@brief Code part copy macro
* @param expr rpn_expr_t* A pointer on @ref rpn_expr_t
* @param NAME code part name
* @see _rpn_code_part_cpy
* @ingroup rpn_cmap
*/
#define CODE_PART_CPY(expr, NAME) _rpn_code_part_cpy(expr, &NAME, CODE_SZ(NAME), NULL)
/**@brief Copy a value push in rpn code ptr
* @param expr rpn_expr_t* A pointer on @ref rpn_expr_t
* @param value unsigned long that will be push
* @see _rpn_code_part_cpy
* @ingroup rpn_cmap
*/
#define CODE_VALUE_CPY(expr, value) _rpn_code_part_cpy(expr, &rpn_value, CODE_SZ(rpn_value), &(value));
/**@brief Add an instruction to push an argument on RPN stack
* @param expr rpn_expr_t* A pointer on @ref rpn_expr_t
* @param value unsigned long The argument number
* @see _rpn_code_part_cpy
* @ingroup rpn_cmap
*/
#define CODE_ARG_CPY(expr, value) _rpn_code_part_cpy(expr, &rpn_arg, CODE_SZ(rpn_arg), &(value));
/**@brief RPN evaluation function type
* @ingroup rpn_cmap
*/
typedef unsigned long (*rpn_run_f)(unsigned long, unsigned long*, void*);
/**@brief RPN expression type
* @ingroup rpn */
typedef struct rpn_expr_s rpn_expr_t;
/**@brief Stores RPN expression informations
* @ingroup rpn
*/
struct rpn_expr_s
{
/**@brief pointer on RPN expression */
char *expr;
/**@brief Tokenized version of expression (set by @ref rpn_expr_compile)*/
rpn_tokenized_t toks;
/**@brief pointer on jit code memory map */
void *code_map;
/**@brief Pointer on next copy addr in code map */
void *code_map_ptr;
/**@brief Code map size */
size_t code_map_sz;
/**@brief rpn expression arguments count */
size_t args_count;
/**@brief Stack values memory */
unsigned long *stack;
/**@brief rpn jit function stack size */
unsigned char stack_sz;
/**@brief Expression status. Takes value in @ref RPN_ERROR,
* @ref RPN_SOURCE, @ref RPN_READY */
short state;
/**@brief Error reason */
char err_reason[128];
};
/**@brief Initialize a new @ref rpn_expr_s
* @param expr Pointer on the expression
* @param stack_sz Expression stack size
* @param args_count Expression argument counter
* @return 0 if no error else -1
* @note Once rpn_expr_init has been called, you have to call
* @ref rpn_expr_close in order to free handled ressources
* @ingroup rpn
*/
int rpn_expr_init(rpn_expr_t* expr, const unsigned char stack_sz,
const size_t args_count);
/**@brief Starts a new initialized expression from an expression string
* @param expr Pointer on an intialized expression ( see @ref rpn_expr_init )
* @param code '\0' terminated string representing the RPN expr
* @return 0 if no error else -1 and @ref rpn_expr_s::err_reason is set
* @note The expression is copied, given buffer can be deallocated after call
* @ingroup rpn
*/
int rpn_expr_compile(rpn_expr_t *expr, const char *code);
/**@brief Starts a new initialized expression from a tokenized form
* @param expr Pointer on an intialized expression ( see @ref rpn_expr_init )
* @param tokens Tokenized form
* @param long_op If true long operation are used, else 1 chr op are used
* @return 0 if no error else -1 and @ref rpn_expr_s::err_reason is set
* @note The @ref rpn_tokenized_s::tokens attribute is "captured" by the
* @ref rpn_expr_s structure and will be deallocated when @ref rpn_expr_close
* is called. It is not encouraged to keep a reference on this attribute after
* @ref rpn_expr_start_untokenize call (but pointed @ref rpn_tokenized_t
* can be reused)
* @ingroup rpn
*/
int rpn_expr_untokenize(rpn_expr_t *expr, rpn_tokenized_t *tokens, char long_op);
/**@brief Generate a new random rpn_expression
* @param op_sz Number of token in generated expression
* @param args_count Number of arguments for generated expression
* @return A pointer on allocated memory storing a valid random RPN expression.
* NULL if error with errno set (produce a message on stderr)
* @ingroup rpn
*/
char* rpn_random(size_t op_sz, size_t args_count);
/**@brief Compile an new RPN expression from string expression
* @param expr Pointer on @ref rpn_expr_s
* @return 0 if no error else -1 and set @ref rpn_expr_s err_reason
* @ingroup rpn_compile
*/
int _rpn_expr_compile_expr(rpn_expr_t* expr);
/**@brief Compile an new RPN expression from string expression
* @param expr Pointer on @ref rpn_expr_s
* @return 0 if no error else -1 and set @ref rpn_expr_s err_reason
* @ingroup rpn_compile
*/
int _rpn_expr_compile_tokens(rpn_expr_t* expr);
/**@brief Evaluate an RPN expression
* @param expr Pointer on @ref rpn_expr_s
* @param args List of arguments
* @return Head of stack after evaluation
* @note If expression not compiled yet compile it before eval
* @ingroup rpn
*/
unsigned long rpn_expr_eval(rpn_expr_t *expr, unsigned long *args);
/**@brief Free ressources handled by given @ref rpn_expr_s
* @param expr Pointer on rpn_expr_t
* @param expr Pointer on @ref rpn_expr_s
* @ingroup rpn
*/
void rpn_expr_close(rpn_expr_t* expr);
/**@brief bzero memory allocated for stack
* @param expr Pointer on rpn_expr_t
* @ingroup rpn
*/
void rpn_expr_reset_stack(rpn_expr_t *expr);
/**@brief Copy precompiled code from @ref rpn_lib.h in expression code map
* @param expr The expression being compiled
* @param token Pointer on token informations
* @return 0 if no error else -1, set error and errno
* @ingroup rpn_cmap
*/
int _rpn_expr_token_copy(rpn_expr_t *expr, rpn_token_t *token);
/**@brief Copy a part of code to the code map
* @warning designed to be use with @ref CODE_PART_CPY and CODE_VALUE_CPY macros
* @param expr Pointer on rpn_expr_t
* @param code_part copy src address
* @param code_part_sz the code part size
* @param value if not NULL set the first code part
* instruction argument to this value
* @return 0 if no error else -1 and set errno
* @ingroup rpn_cmap
*/
int _rpn_code_part_cpy(rpn_expr_t* expr, const void* code_part,
unsigned long code_part_sz, const unsigned long *value);
/**@brief Allocate a memory map for given rpn_expt_t and copy the function
* header
* @param expr Pointer on rpn_expr_t
* @return 0 if no error, else -1 and set errno
* @ingroup rpn_cmap
*/
int _rpn_expr_init_map(rpn_expr_t* expr);
/**@brief Copy the function suffix and change memory map protection
* @param expr Pointer on rpn_expr_t
* @return 0 if no error, else -1 and set errno
* @ingroup rpn_cmap
*/
int _rpn_expr_end_map(rpn_expr_t *expr);
#endif

246
rpn_lib.asm Normal file
View file

@ -0,0 +1,246 @@
; TODO Use memory area given in argument to preserve stack between calls
[bits 64]
; local variables macro
%define stack_size [rbp - 8]
%define stack_base [rbp - 16]
%define brkinit [rbp - 24]
%define args_ptr [rbp - 32]
%define stack_cur r11
; push & pop the RPN stack macros
; check if stack full, then push value
%macro rpn_push 1
inc stack_cur
cmp stack_cur, stack_size
jl %%end
xor stack_cur, stack_cur
%%end:
mov [r10 + stack_cur * 8], %1
%endmacro
; check if stack empty, then pop value
%macro rpn_pop 1
mov %1, [r10 + stack_cur * 8]
cmp stack_cur, 0
jg %%end
mov stack_cur, stack_size
%%end:
dec stack_cur
%endmacro
; define a label with code portion size
; and set global
%macro part_sz 1
%1_sz: dq $ - %1
global %1
global %1_sz
%endmacro
section .data
rpn_exec:
; unsigned long int rpn_exec(unsigned long int stack_size, unsigned long args)
; rdi -> stack size (in values (64 bits))
; rsi -> args pointers
enter 32, 0
mov stack_size, rdi
mov args_ptr, rsi
mov r10, rdx ; r10 is stack base
xor rdx, rdx
mov brkinit, rdx
cmp r10, 0
jne .nobrk
; if stack is NULL allocate using brk
mov rax, 0x0C
xor rdi, rdi ; get brk
syscall
mov brkinit, rax
mov rdi, stack_size
shl rdi, 3 ; mul 8
add rdi, rax
mov rax, 0x0C
syscall ; new brk
cmp rax, -1
jne .cont
leave
ret
.cont:
mov rcx, stack_size
mov rdi, brkinit
xor rax, rax
.bzero:
stosq
loop .bzero
mov r10, brkinit
.nobrk:
xor stack_cur, stack_cur ; r11 is stack item counter
part_sz rpn_exec
rpn_exec_ret:
rpn_pop rax
mov rdi, brkinit
cmp rdi, 0
jne .end
push rax
mov rax, 0x0C
mov rdi, brkinit
syscall
pop rax
.end:
leave
ret
part_sz rpn_exec_ret
rpn_value:
mov rax, strict qword 0x12345678
rpn_push rax
part_sz rpn_value
rpn_arg:
mov rax, strict qword 0x12345678
mov rsi, 8
mul rsi
mov rsi, args_ptr
add rsi, rax
mov rax, [rsi]
rpn_push rax
part_sz rpn_arg
rpn_add:
rpn_pop rax
rpn_pop rbx
add rax, rbx
rpn_push rax
part_sz rpn_add
rpn_sub:
rpn_pop rbx
rpn_pop rax
sub rax, rbx
rpn_push rax
part_sz rpn_sub
rpn_mul:
rpn_pop rbx
rpn_pop rax
mul rbx
rpn_push rax
part_sz rpn_mul
rpn_div:
rpn_pop rbx
rpn_pop rax
xor rdx, rdx
test rbx, rbx
jz .zerodiv
div rbx
jmp .end
.zerodiv: xor rax, rax
.end:
rpn_push rax
part_sz rpn_div
rpn_mod:
rpn_pop rbx
rpn_pop rax
xor rdx, rdx
test rbx, rbx
jz .zerodiv
div rbx
jmp .end
.zerodiv: xor rax, rax
.end
rpn_push rdx
part_sz rpn_mod
rpn_neg:
rpn_pop rax
neg rax
rpn_push rax
part_sz rpn_neg
rpn_not:
rpn_pop rax
not rax
rpn_push rax
part_sz rpn_not
rpn_and:
rpn_pop rbx
rpn_pop rax
and rax, rbx
rpn_push rax
part_sz rpn_and
rpn_or:
rpn_pop rbx
rpn_pop rax
or rax, rbx
rpn_push rax
part_sz rpn_or
rpn_xor:
rpn_pop rbx
rpn_pop rax
xor rax, rbx
rpn_push rax
part_sz rpn_xor
rpn_shl:
rpn_pop rcx
rpn_pop rax
cmp rcx, 64
jge .zero
shl rax, cl
jmp .end
.zero:
xor rax, rax
.end:
rpn_push rax
part_sz rpn_shl
rpn_shr:
rpn_pop rcx
rpn_pop rax
cmp rcx, 64
jge .zero
shr rax, cl
jmp .end
.zero:
xor rax, rax
.end:
rpn_push rax
part_sz rpn_shr
rpn_xchg:
rpn_pop rbx
rpn_pop rax
rpn_push rbx
rpn_push rax
part_sz rpn_xchg
rpn_dup:
rpn_pop rax
rpn_push rax
rpn_push rax
part_sz rpn_dup
rpn_pop_op:
rpn_pop rax
part_sz rpn_pop_op
section .text

122
rpn_lib.h Normal file
View file

@ -0,0 +1,122 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __rpn_lib__h__
#define __rpn_lib__h__
#include "config.h"
#include <sys/mman.h>
/**@defgroup rpn_lib RPNlib
* @brief Precompiled x86_64 assembly operations
* @ingroup rpn_compile
*/
/**@file rpn_lib.h
* @brief "Import" symbols defined in @ref rpn_lib.asm using @ref CODE_PART macro.
*
* For each symbol (piece of compiled assembly code) @ref CODE_PART defines :
* - void *NAME : the pointer on compiled code
* - unsigned long NAME_sz : the size of the code portion
*
* @see rpn_lib.asm
* @ingroup rpn_lib
*/
/**@file rpn_lib.asm
* @brief x86_64 RPN expression operations implementations
*
* Exported symbols are found in @ref rpn_lib.h
* @ingroup rpn_lib
*/
/**@brief Code part size macro */
#define CODE_SZ(NAME) NAME ## _sz
/**@brief macro to declare a code part and associated size */
#define CODE_PART(NAME) const extern void* NAME; extern const unsigned long NAME ## _sz
/**@brief Function heading code
*
* - stack frame creation
* - argument processing
* - etc.
* @ingroup rpn_lib */
CODE_PART(rpn_exec);
/**@brief Function ends and ret code
*
* - pop and return head of stack value
* - stack frame deletion
* @ingroup rpn_lib */
CODE_PART(rpn_exec_ret);
/**@brief Constant value pushing symbol
* @note except a value to be set
* @ingroup rpn_lib */
CODE_PART(rpn_value);
/**@brief Argument pushing symbol
* @note except a value to be set
* @ingroup rpn_lib */
CODE_PART(rpn_arg);
/**@brief Addition symbol
* @ingroup rpn_lib */
CODE_PART(rpn_add);
/**@brief Substraction symbol
* @ingroup rpn_lib */
CODE_PART(rpn_sub);
/**@brief Division symbol
* @ingroup rpn_lib */
CODE_PART(rpn_div);
/**@brief Multiplication symbol
* @ingroup rpn_lib */
CODE_PART(rpn_mul);
/**@brief Modulo symbol
* @ingroup rpn_lib */
CODE_PART(rpn_mod);
/**@brief Left shift symbol
* @ingroup rpn_lib */
CODE_PART(rpn_shl);
/**@brief Right shift symbol
* @ingroup rpn_lib */
CODE_PART(rpn_shr);
/**@brief Value exchange symbol
* @ingroup rpn_lib */
CODE_PART(rpn_xchg);
/**@brief Head of stack duplication
* @ingroup rpn_lib */
CODE_PART(rpn_dup);
/**@brief Arithmetic negation
* @ingroup rpn_lib */
CODE_PART(rpn_neg);
/**@brief Binary not
* @ingroup rpn_lib */
CODE_PART(rpn_not);
/**@brief Binary and
* @ingroup rpn_lib */
CODE_PART(rpn_and);
/**@brief Binary or
* @ingroup rpn_lib */
CODE_PART(rpn_or);
/**@brief Binary xor
* @ingroup rpn_lib */
CODE_PART(rpn_xor);
/**@brief Pop head of stack
* @ingroup rpn_lib */
CODE_PART(rpn_pop_op);
#endif

58
rpn_mutation.c Normal file
View file

@ -0,0 +1,58 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#include "rpn_mutation.h"
/**@file rpn_mutation.c
* @todo continue implementation */
const rpn_mutation_profile_t rpn_default_mutprof = {16,
{ RPN_del, RPN_del,
RPN_add, RPN_add,
RPN_chg, RPN_chg, RPN_chg, RPN_chg, RPN_chg, RPN_chg,
RPN_upd, RPN_upd, RPN_upd, RPN_upd, RPN_upd, RPN_upd}
};
rpn_expr_t* rpn_expr_mutation(rpn_expr_t *src, size_t mutations)
{
return rpn_expr_mutation_p(src, mutations, &rpn_default_mutprof);
}
rpn_expr_t* rpn_expr_mutation_p(rpn_expr_t *src, size_t mutations,
const rpn_mutation_profile_t *prof)
{
unsigned char op;
prof = prof?prof:&rpn_default_mutprof;
op = prof->mods[(int)(drand48() / (1.0 / prof->mods_sz))];
switch(op)
{
case 0: // add a token
break;
case 1: // delete a token
break;
case 2: // update token type
break;
default: // update token, same type
break;
}
return NULL;
}

83
rpn_mutation.h Normal file
View file

@ -0,0 +1,83 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __rpn_mutation__h__
#define __rpn_mutation__h__
#include <stddef.h>
#include "rpn_jit.h"
/**@defgroup mutation RPN expression mutation
* @ingroup rpn
*/
/**@file rpn_mutation.h
* @brief Contains structures and function to mutate RPN epxressions
*/
/**@brief Defines mutation actions types */
enum rpn_mutation_op_e {
/**@brief Mutation action : delete a token */
RPN_del,
/**@brief Mutation action : add a token */
RPN_add,
/**@brief Mutation action : change a token */
RPN_chg,
/**@brief Mutation action : update a token (same type, different value) */
RPN_upd
};
/**@brief Shortcut for struct @ref rpn_mutation_profile_s */
typedef struct rpn_mutation_profile_s rpn_mutation_profile_t;
/**@brief Stores mutation informations
* @ingroup mutation
*/
struct rpn_mutation_profile_s
{
/**@brief Size of @ref rpn_mutation_profile_s::mods attribute */
size_t mods_sz;
/**@brief Modification possibilities
*
* One value is picked up randomly from this list to determine
* the type of mutation : addition, deletion, modification, value change
*/
unsigned char mods[];
};
/**@brief Default mutation profile */
extern const rpn_mutation_profile_t rpn_default_mutprof;
/**@brief Shortcut for @ref rpn_expr_mutation_p with a @ref rpn_default_mutprof
* @ingroup mutation */
rpn_expr_t* rpn_expr_mutation(rpn_expr_t *src, size_t mutations);
/**@brief Generate a new expression by applying mutations to a source
* expression
* @param src Source expression
* @param mutations number of mutations
* @param prof Mutation profile
* @return A new instance of rpn_expr_t ready to be evaluate
* @ingroup mutation
*/
rpn_expr_t* rpn_expr_mutation_p(rpn_expr_t *src, size_t mutations,
const rpn_mutation_profile_t *prof);
#endif

387
rpn_parse.c Normal file
View file

@ -0,0 +1,387 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#include "rpn_parse.h"
/**@brief Macro for @ref rpn_ops member definition
* @param NAME @ref rpn_lib.h symbol
* @param s The short (1 char) symbol code
* @param l The long (char*) symbole code
*/
#define __op(NAME, s, l) {&NAME, &CODE_SZ(NAME), s, l}
const rpn_op_t rpn_ops[] = {\
__op(rpn_add, '+', "add"),\
__op(rpn_sub, '-', "sub"),\
__op(rpn_div, '/', "div"),\
__op(rpn_mul, '*', "mul"),\
__op(rpn_mod, '%', "mod"),\
__op(rpn_neg, '!', "neg"),\
__op(rpn_not, '~', "not"),\
__op(rpn_and, '&', "and"),\
__op(rpn_or, '|', "or"),\
__op(rpn_xor, '^', "xor"),\
__op(rpn_shr, 'r', ">>"),\
__op(rpn_shl, 'l', "<<"),\
__op(rpn_xchg, 'x', "xchg"),\
__op(rpn_dup, 'd', "dup"),\
__op(rpn_pop_op, 'p', "pop"),\
};
#undef __op
int rpn_tokenizer_start(rpn_tokenizer_t *tokenizer, rpn_tokenized_t *dst,
const char* expr, size_t argc)
{
int err;
bzero(tokenizer, sizeof(rpn_tokenizer_t));
tokenizer->orig = expr;
tokenizer->toks = dst;
tokenizer->toks->argc = argc;
tokenizer->toks->tokens_sz = 0;
tokenizer->toks->tokens = NULL;
if(!(tokenizer->buff = strdup(expr)))
{
err = errno;
snprintf(tokenizer->err_reason, 64,
"Error duplicating expression for tokenization : %s",
strerror(err));
errno = err;
return -1;
}
tokenizer->cur = tokenizer->buff;
return 0;
}
rpn_token_t* rpn_tok(rpn_tokenizer_t *tokenizer)
{
char *token;
rpn_token_t ret, *tmp, *res;
int err;
token = tokenizer->cur;
if(!*token) // end of expression
{
rpn_tokenizer_free(tokenizer);
return NULL;
}
while(*(tokenizer->cur))
{
if(*(tokenizer->cur) == ' ' ||
*(tokenizer->cur) == '\n' ||
*(tokenizer->cur) == '\t')
{
break;
}
tokenizer->cur++;
tokenizer->chr_no++;
}
if(*(tokenizer->cur))
{
// not end, go on next chr for further rpn_tok calls
*(tokenizer->cur) = '\0';
tokenizer->cur++;
tokenizer->chr_no++;
}
// we have a clean token '\0' terminated
if(rpn_tokenize(token, &ret, tokenizer->err_reason) < 0)
{
errno = 0;
return NULL;
}
if(ret.type == RPN_arg && ret.arg_n >= tokenizer->toks->argc)
{
// invalid argument number
if(tokenizer->toks->argc)
{
snprintf(tokenizer->err_reason, 64,
"Argument number is too big : should be in [0..%ld] but \"%s\" found",
tokenizer->toks->argc - 1, token);
}
else
{
snprintf(tokenizer->err_reason, 64,
"No argument accepted but \"%s\" found",
token);
}
errno = EINVAL;
return NULL;
}
if(tokenizer->toks->tokens_sz >= tokenizer->allocated_toks)
{
tokenizer->allocated_toks += 8;
tmp = realloc(tokenizer->toks->tokens,
sizeof(rpn_token_t) * tokenizer->allocated_toks);
if(!tmp)
{
err = errno;
snprintf(tokenizer->err_reason, 64,
"Unable to realloc tokens list : %s",
strerror(err));
errno = err;
return NULL;
}
tokenizer->toks->tokens = tmp;
}
res = &(tokenizer->toks->tokens[tokenizer->toks->tokens_sz]);
*res = ret;
tokenizer->toks->tokens_sz++;
return res;
}
int rpn_tokenize(const char *token, rpn_token_t *dst, char error[64])
{
int rep;
unsigned long num;
const char *orig;
if((rep = rpn_match_token_i(token)) >= 0)
{
dst->type = RPN_op;
dst->op_n = rep;
dst->op = &(rpn_ops[rep]);
return 0;
}
orig = token;
if(*token == 'A')
{
if(!token[1])
{
snprintf(error, 64,
"Argument number is missing, lonely \"A \" found");
return -1;
}
dst->type = RPN_arg;
token++;
}
else
{
dst->type = RPN_val;
}
if(rpn_match_number(token, &num) < 0)
{
snprintf(error, 64,
"Invalid %snumber : \"%s\"",
dst->type == RPN_arg?"argument ":"constant ",
orig);
return -1;
}
if(dst->type == RPN_arg)
{
dst->arg_n = num;
}
else
{
dst->value = num;
}
return 0;
}
void rpn_tokenizer_free(rpn_tokenizer_t *tokenizer)
{
if(tokenizer->buff)
{
free(tokenizer->buff);
bzero(tokenizer, sizeof(rpn_tokenizer_t));
}
}
char* rpn_tokenized_expr(rpn_tokenized_t *tokens, char long_op)
{
size_t expr_sz, i;
int err, written;
char *cur, *tmp, *expr;
rpn_token_t *token;
int ALLOC_CHUNK=22; /* 64 bit uint is 20 decimal digit */
#ifdef DEBUG
if(!tokens)
{
dprintf(2, "Error, NULL ptr given to rpn_rokenize_expr");
err = EINVAL;
goto ret_err;
}
#endif
errno = 0;
err = 0;
expr_sz = 0;
expr_sz = 128;
cur = expr = malloc(sizeof(char)*expr_sz);
if(!expr)
{
err=errno;
dprintf(2,"Error allocating memory for expression : %s",
strerror(err));
goto ret_err;
}
for(i=0; i<tokens->tokens_sz; i++)
{
token = &(tokens->tokens[i]);
if(cur - expr >= expr_sz - ALLOC_CHUNK)
{
expr_sz += 128;
tmp = realloc(expr, sizeof(char) * expr_sz);
if(!tmp)
{
err=errno;
dprintf(2,"Error allocating memory for expression : %s",
strerror(err));
goto ret_err;
}
cur = tmp + (cur - expr);
expr = tmp;
}
switch(token->type)
{
case RPN_op:
if(!long_op)
{
*cur = token->op->chr;
cur[1] = ' ';
cur[2] = '\0';
written = 2;
}
else
{
written = snprintf(cur, ALLOC_CHUNK,
"%s ", token->op->str);
written--;
}
break;
case RPN_arg:
written = snprintf(cur, ALLOC_CHUNK,
"A%lu ", token->arg_n);
break;
case RPN_val:
written = snprintf(cur, ALLOC_CHUNK,
"0x%lX ", token->value);
break;
default:
err = EUCLEAN;
goto free_err;
}
#ifdef DEBUG
if(written > ALLOC_CHUNK)
{
dprintf(2, "Expression too long : %s",
token->op->str);
err = EINVAL;
goto free_err;
}
#endif
cur += written;
}
return expr;
free_err:
free(expr);
ret_err:
errno = err;
return NULL;
}
const rpn_op_t* rpn_match_token(const char* token)
{
int rep;
if((rep = rpn_match_token_i(token)) < 0)
{
return NULL;
}
return &(rpn_ops[rep]);
}
int rpn_match_token_i(const char* token)
{
unsigned char i;
if(token[1] == '\0') //strlen(token) == 1
{
foreach_rpn_ops(i)
{
if(rpn_ops[i].chr == token[0])
{
return (int)i;
}
}
return -1;
}
foreach_rpn_ops(i)
{
if(!strncmp(rpn_ops[i].str, token, 8))
{
return (int)i;
}
}
return -1;
}
int rpn_match_number(const char* token, unsigned long *result)
{
char *endptr;
unsigned long long res;
int base;
if(*token == '\0')
{
return -1;
}
base = 10;
if(*token == '0' && token[1] != '\0')
{
token++;
if(*token == 'x')
{
token++;
base = 16;
}
else if(*token == 'o')
{
token++;
base = 8;
}
else if(*token == 'b')
{
token++;
base = 2;
}
}
res = strtoull(token, &endptr, base);
if(*endptr != '\0')
{
return -1;
}
*result = res;
return 0;
}
size_t rpn_op_sz()
{
return sizeof(rpn_ops)/sizeof(rpn_op_t);
}

326
rpn_parse.h Normal file
View file

@ -0,0 +1,326 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __rpn_parse__h__
#define __rpn_parse__h__
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "config.h"
#include "rpn_lib.h"
/**@file rpn_parse.h
* @brief RPN expression parsing headers
* @ingroup rpn_tokenize
*
* Contains headers of @ref rpn_tokenize and @ref rpn_parse .
*/
/**@defgroup rpn_tokenize Expression tokenization
* @brief Parsing an expression into a @ref rpn_tokenized_t
*
* The tokenized form ( see @ref rpn_tokenized_t ) of an expression is usefull
* for @ref mutation.
*
* The tokenizing process is done in a way allowing compilation process to
* fetch tokens while parsing the expression (see @ref rpn_tok).
* @ingroup rpn_compile
*/
/**@defgroup rpn_parse Token parsing functions
* @brief Internal parsing functions
* @ingroup rpn_tokenize
*/
/**@brief Shortcut for loop on all operations list */
#define foreach_rpn_ops(IDX) for(IDX=0; IDX<rpn_op_sz(); IDX++)
/**@brief Check if a tokenizer is in error state
* @param tokenizer Pointer on a @ref rpn_tokenizer_s
* @return false if no error else true
* @note test if first chr of @ref rpn_tokenizer_s::err_reason is "\0"
*/
#define rpn_tokenizer_error(tokenizer) (*((tokenizer)->err_reason))
/**@brief Shortcut for struct @ref rpn_op_s */
typedef struct rpn_op_s rpn_op_t;
/**@brief Shortcut for struct @ref rpn_token_type_e */
typedef enum rpn_token_type_e rpn_token_type_t;
/**@brief Shortcut for struct @ref rpn_token_s */
typedef struct rpn_token_s rpn_token_t;
/**@brief Shortcut for struct @ref rpn_tokenized_s */
typedef struct rpn_tokenized_s rpn_tokenized_t;
/**@brief Shortcut for struct @ref rpn_tokenizer_s */
typedef struct rpn_tokenizer_s rpn_tokenizer_t;
/**@brief Handles operation identification informations storage
* @ingroup rpn_tokenize
*/
struct rpn_op_s
{
/**@brief Pointer on function pointer */
const void **fun;
/**@brief Function code size */
const unsigned long *fun_sz;
/**@brief Caracter representing operation ('\0' if None)*/
char chr;
/**@brief String representing operation */
char *str;
};
/**@brief Defines @ref rpn_token_s types
* @ingroup rpn_tokenize */
enum rpn_token_type_e {
/**@brief The token is an operation */
RPN_op,
/**@brief The token is an argument */
RPN_arg,
/**@brief The token is a value */
RPN_val
};
/**@brief Represent an expression token (value, argument or operation)
* @ingroup rpn_tokenize */
struct rpn_token_s
{
/**@brief Token type */
rpn_token_type_t type;
/**@brief Token data depending on @ref type */
union {
/**@brief Token data for @ref RPN_op tokens */
struct {
/**@brief Indicate the operation index in @ref rpn_ops */
unsigned char op_n;
/**@brief Pointer on operation informations */
const rpn_op_t *op;
};
/**@brief Indicate the argument number */
unsigned long int arg_n;
/**@brief Indicate the constant value */
unsigned long int value;
};
};
//} __attribute__((aligned));
/**@brief Represent a tokenized expression
*
* A list of @ref rpn_token_s and argc
* @ingroup rpn_tokenize */
struct rpn_tokenized_s
{
/**@brief Number of expected arguments */
size_t argc;
/**@brief The number of token in the expression */
size_t tokens_sz;
/**@brief List of tokens */
rpn_token_t *tokens;
};
/**@brief Handles data will tokenizing
*
* Store compilation state, allowing to return new token as soon as they
* become ready.
* @ingroup rpn_tokenize */
struct rpn_tokenizer_s
{
/**@brief Source expression */
const char *orig;
/**@brief Expression work buffer */
char *buff;
/**@brief Current expression buffer */
char *cur;
/**@brief Current chr number (for error generation & debugging) */
size_t chr_no;
/**@brief The tokenized representation of the expression
* @note Should point on @ref rpn_expr_t::toks */
rpn_tokenized_t *toks;
/**@brief The number of allocated rpn_token_t in toks */
size_t allocated_toks;
/**@brief Tokenization error */
char err_reason[64];
};
/**@brief Define all operations
*
* Stores operation identification informations
* @ingroup rpn_tokenize */
extern const rpn_op_t rpn_ops[];
/**@brief Initialize a tokenizer and a tokenized representation
* @param tokenizer Pointer on a new tokenizer
* @param dst Pointer on a tokenized struct to store generated tokens
* @param expr Pointer on the RPN expression to tokenize
* @param argc Number of argument accepted by expression
* @return 0 if no error else -1 and set @ref rpn_tokenizer_s::err_reason
* @warning no NULL checks for the moment...
* @ingroup rpn_tokenize
*/
int rpn_tokenizer_start(rpn_tokenizer_t *tokenizer, rpn_tokenized_t *dst,
const char* expr, size_t argc);
/**@brief Return the next token
* @param tokenizer Pointer on tokenizing task informations
* @return The a pointer on next @ref rpn_token_s in @ref rpn_tokenizer_s::toks
* or NULL if end of expression or error
* @note When NULL is returned all ressources are freed, no need to
* call @ref rpn_tokenizer_free
* @ingroup rpn_tokenize
*/
rpn_token_t* rpn_tok(rpn_tokenizer_t *tokenizer);
/**@brief Free ressources of a tokenizer
* @param tokenizer Pointer on the tokenizer we want to deallocate
* @note This method must be used to abord a tokenizing process with no
* error or end of expression encountered
* @ingroup rpn_tokenize
*/
void rpn_tokenizer_free(rpn_tokenizer_t *tokenizer);
/**@brief Tokenize a '\0' terminated string
* @param token A '\0' terminated string
* @param dst Pointer on information destination
* @param error Pointer on an error reason buffer
* @return 0 if dst set and token recognized else -1 and set error buffer
* @warning assert token is not empty
* @ingroup rpn_tokenize
*/
int rpn_tokenize(const char *token, rpn_token_t *dst, char error[64]);
/**@brief Represented a tokenized expression in a string
* @param tokens Tokenized expression
* @param long_op If true uses @ref rpn_op_s::str else @ref rpn_op_s::chr
* @return A newly allocated char* that should be deallocated using free()
* @ingroup rpn_tokenize
*/
char* rpn_tokenized_expr(rpn_tokenized_t *tokens, char long_op);
/**@brief Returns NULL or a pointer on corresponding operation infos
* @param token The token we want to match
* @return NULL or operation informations
* @ingroup rpn_parse
*/
const rpn_op_t* rpn_match_token(const char* token);
/**@brief Return -1 or an index corresponding to @ref rpn_ops
* @param token The token we want to match
* @return NULL or operation informations
* @ingroup rpn_parse
*/
int rpn_match_token_i(const char* token);
/**@brief Get an integer from a token
* @param token The token to decode
* @param result A pointer on the result
* @return -1 if given token is not a decimal number else 0 is returned
* and result is set
* @ingroup rpn_parse
*/
int rpn_match_number(const char* token, unsigned long *result);
/**@brief Get operations list size
* @return number of operations in @ref rpn_ops
*/
size_t rpn_op_sz();
/**@page rpn_lang RPN expression syntax
* @brief Howto write an expression
*
* \section rpn_lang_syntax General syntax
* An expression is composed of tokens separated by 1 or multiple separation
* characters (space, newline or tabs).
*
* There is 3 types of token (see @ref rpn_token_type_e ) : @ref rpn_lang_op ,
* @ref rpn_lang_arg and @ref rpn_lang_value .
*
* \section rpn_lang_tokens RPN tokens
* \subsection rpn_lang_arg Arguments
* Expression can be parametric : arguments are given at evaluation and
* replaced in expression.
*
* In RPN epxressions arguments are desgined by a number (starting from 0)
* and preffixed by 'A' char.
*
* For example an expression evaluating to the sum of their two arguments
* will be written : "A0 A1 +"
*
* \subsection rpn_lang_value Constant values
* Constant values can be expressed in different bases (the Python syntax) :
* - 42
* - 0x2a or 0x2A
* - 0o52
* - 0b101010
*
* \subsection rpn_lang_op Operations
* Operations have two form : a short (1 character long) and a long (a string).
*
* Each valid operations are declared in @ref rpn_ops variable (see
* @ref rpn_parse.c for details).
*
* The @ref python_module expose a function pyrpn.get_ops() ( @see pyrpn_ops )
* returning a dict with long operations as key and short as value.
* \subsubsection rpn_lan_op_internal Internal mechanism
* Operations are done using a loopstack : operands are poped from stack, and
* the result is pushed onto it.
*
* Operations implementation are wrote in x86_64 linux assembly code ( see
* @ref rpn_lib.asm ). Each operations has a corresponding exported symbol
* (declared in @ref rpn_lib.h ) pointing compiled code. This code will be
* copied in a memory map in order to compile (@ref rpn_cmap ) an evaluation
* function.
*/
/**@page rpn_lang_ext Language extension
* @brief Howto add new operations
*
* @section rpn_lang_ext_op Add an operation
*
* In order to add a new operation you have to do three things :
*
* @subsection rpn_lang_ext_asm Write the operation code
*
* You have to write the operation code in @ref rpn_lib.asm and to expose
* the corresponding symbols (the code label and the code portion size).
*
* The macro part_sz allows to do most of the work by :
* - defining a symbol NAME_sz pointing on the code portion size
* - export the symbols NAME and NAME_sz
*
* @warning The part_sz macro HAS TO be placed at the end of the corresponding
* code_portion
*
* @subsection rpn_lang_ext_head Import the symbols in C headers
*
* The @ref rpn_lib.h header is designed to contain all extern assembly symbols
* (pointer on compiled code and on size).
*
* To add a new operation you have to "import" the symbols defined in
* @ref rpn_lib.asm using the @ref CODE_PART macro
*
* @subsection rpn_lang_ext_code Declare corresponding short and long tokens
*
* The @ref rpn_compile will match short or long operations and corresponding
* pre-compiled code.
*
* The association between short (char) long (char*) and pre-compiled code is
* done in the @ref rpn_ops variable.
* @note The __op macro allow simple operation declaration.
*/
#endif

257
test.c Normal file
View file

@ -0,0 +1,257 @@
/*
* Copyright (C) 2020 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include "rpn_lib.h"
#include "rpn_jit.h"
#include "rpn_parse.h"
int test0()
{
unsigned long res;
rpn_expr_t expr;
rpn_run_f expr_run;
expr.stack_sz = 8;
expr.args_count = 0;
if(_rpn_expr_init_map(&expr) < 0)
{
perror("Error starting map");
return 1;
}
unsigned long val;
/*
val = 2;
CODE_VALUE_CPY(&expr, val);
val = 40;
CODE_VALUE_CPY(&expr, val);
CODE_VALUE_CPY(&expr, val);
CODE_VALUE_CPY(&expr, val);
CODE_VALUE_CPY(&expr, val);
CODE_VALUE_CPY(&expr, val);
CODE_VALUE_CPY(&expr, val);
CODE_VALUE_CPY(&expr, val);
CODE_VALUE_CPY(&expr, val);
CODE_VALUE_CPY(&expr, val);
CODE_PART_CPY(&expr, rpn_add);
CODE_PART_CPY(&expr, rpn_add);
CODE_PART_CPY(&expr, rpn_add);
CODE_PART_CPY(&expr, rpn_add);
CODE_PART_CPY(&expr, rpn_add);
CODE_PART_CPY(&expr, rpn_add);
CODE_PART_CPY(&expr, rpn_add);
CODE_PART_CPY(&expr, rpn_add);
CODE_PART_CPY(&expr, rpn_add);
*/
val = 1;
CODE_ARG_CPY(&expr, val);
val = 0;
CODE_ARG_CPY(&expr, val);
CODE_PART_CPY(&expr, rpn_add);
if(_rpn_expr_end_map(&expr) < 0)
{
perror("Error ending map");
return 2;
}
expr_run = expr.code_map;
long unsigned int args[4] = {1337,42,0,4242};
//printf("Ready to run !\n");
res = expr_run(8, args, NULL);
//printf("Tada : %ld %p\n", res, res);
res++;
return 0;
}
int test_add()
{
unsigned long res;
rpn_expr_t expr;
if(rpn_expr_init(&expr, 8, 0) < 0 ||
rpn_expr_compile(&expr, "16 26 +"))
{
dprintf(2, "Error initializing expr");
return 1;
}
res = rpn_expr_eval(&expr, NULL);
//printf("Result = %ld\n", res);
if(res != 42)
{
dprintf(2, "Error : expected 42 but %ld received\n",
res);
return 2;
}
rpn_expr_close(&expr);
return 0;
}
int test_args()
{
unsigned long res, args[2];
rpn_expr_t expr;
if(rpn_expr_init(&expr, 8, 2))
{
dprintf(2, "Error initializing expr");
return 1;
}
if(rpn_expr_compile(&expr, "A0 A1 +") < 0)
{
dprintf(2, "Compilation error : %s\n",
expr.err_reason);
return 1;
}
args[0] = 16;
args[1] = 26;
res = rpn_expr_eval(&expr, args);
//printf("Result = %ld\n", res);
if(res != 42)
{
dprintf(2, "Error : expected 42 but %ld received\n",
res);
return 2;
}
rpn_expr_close(&expr);
return 0;
}
int test_stack_sz()
{
rpn_expr_t expr;
int stack_sz, i;
unsigned long res, args[2];
for(stack_sz=4; stack_sz<256; stack_sz++)
{
if(rpn_expr_init(&expr, stack_sz, 2))
{
dprintf(2, "Error initializing expr");
return 1;
}
if(rpn_expr_compile(&expr,
"+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ") < 0)
{
dprintf(2, "Compilation error : %s\n",
expr.err_reason);
return 1;
}
args[0] = 16;
args[1] = 26;
for(i=0; i<256; i++)
{
res = rpn_expr_eval(&expr, args);
//dprintf(2, "DEBUG : eval %d:%d res = %ld\n", stack_sz, i, res);
//printf("Result = %ld\n", res);
if(res != 0)
{
dprintf(2, "Error : expected 0 but %ld received\n",
res);
return 2;
}
}
rpn_expr_close(&expr);
}
return 0;
}
int test_tokenization()
{
rpn_expr_t expr;
int stack_sz, argc, i;
char *expr_orig, *expr_untok;
stack_sz = 32;
argc=4;
for(i=0;i<20;i++)
{
if(rpn_expr_init(&expr, stack_sz, argc))
{
dprintf(2, "Error initializing expr");
return 1;
}
expr_orig = rpn_random(20+i, argc);
if(rpn_expr_compile(&expr, expr_orig) < 0)
{
dprintf(2, "Compilation error : %s\n",
expr.err_reason);
return 1;
}
expr_untok = rpn_tokenized_expr(&(expr.toks), 0);
if(strcmp(expr_untok, expr_orig))
{
dprintf(2,
"Untokenized str differ from original : \"%s\" != \"%s\"\n",
expr_orig, expr_untok);
return 1;
}
rpn_expr_close(&expr);
free(expr_untok);
}
return 0;
}
#define RUNTEST(NAME) \
printf("Running %s\n", #NAME); \
printf("%s()\t", #NAME); \
fsync(1); \
if(NAME()) \
{ \
printf("[failed]\n"); \
res++; \
} \
else \
{ \
printf("[OK]\n"); \
}
int main()
{
int res;
res = 0;
//RUNTEST(test0);
RUNTEST(test_add);
RUNTEST(test_args);
RUNTEST(test_stack_sz);
RUNTEST(test_tokenization);
return res;
}

0
tests/__init__.py Normal file
View file

51
tests/benchmark.py Normal file
View file

@ -0,0 +1,51 @@
#!/usr/bin/python3
import sys
import random
import time
try:
import pyrpn
except (ImportError, NameError) as e:
print("Error importing pyrpn. Try to run make.",
file=sys.stderr)
raise e
expr_count = 0x200
max_iter = 0x3000
argc = 2
sz = 0x10
if len(sys.argv) > 1:
expr_count = int(sys.argv[1], 0)
if len(sys.argv) > 2:
max_iter = int(sys.argv[2], 0)
if len(sys.argv) > 3:
argc = int(sys.argv[3], 0)
if len(sys.argv) > 4:
sz = int(sys.argv[4], 0)
tot = 0
time_op = 0
start = time.time()
IMAX = (1<<63)-1
samples = 8
rnd_samples = [[[random.randint(0, IMAX) for _ in range(argc)] for _ in range(max_iter)]
for _ in range(samples)]
for i in range(expr_count):
rnd_expr = pyrpn.random_expr(argc, sz)
all_start = time.time()
expr = pyrpn.RPNExpr(rnd_expr, argc)
start_op = time.time()
rnds = random.choice(rnd_samples)
for rnd in rnds:
expr.eval(*rnd)
time_op += time.time() - start_op
tot += time.time() - all_start
sys.stderr.write(".")
sys.stderr.flush()
sys.stderr.write("\n")
total = time.time() - start
print("Runned %.2fs" % total)
print("%.1fK iter/s %.1fms per expr" % (max_iter*expr_count/time_op/1000, tot/expr_count*1000))

1
tests/pyrpn.so Symbolic link
View file

@ -0,0 +1 @@
../pyrpn.so

275
tests/tests_rpn.py Executable file
View file

@ -0,0 +1,275 @@
#!/usr/bin/python3
# Copyright 2020 Weber Yann
#
# This file is part of geneifs.
#
# geneifs is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# geneifs 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with geneifs. If not, see <http://www.gnu.org/licenses/>.
#
import sys
import random
import math
import unittest
from unittest import skip
IMAX = (1<<64)-1
try:
import pyrpn
except (ImportError, NameError) as e:
print("Error importing pyrpn. Try to run make.",
file=sys.stderr)
raise e
class Test0RpnModule(unittest.TestCase):
def test_init(self):
""" RPNExpr instanciation """
expr = pyrpn.RPNExpr("42", 2)
def test_init_badargs(self):
""" RPNExpr instanciation with bad arguments """
badargs = [('', 2), (), ('ab+',), ('ab+',300), (42, 42), ('ab', '42')]
for badarg in badargs:
with self.assertRaises((ValueError, TypeError)):
expr = pyrpn.RPNExpr(*badarg)
def test_init_loop(self):
""" Testing pyrpn.RPNExpr multiple instanciation """
exprs = []
for i in range(256):
expr = pyrpn.RPNExpr("42", 2)
def test_init_args(self):
""" Testing pyrpn.RPNExpr instanciation with arguments """
for argc in range(0, 256):
with self.subTest("RPNExpr('42', %d)" % argc):
expr = pyrpn.RPNExpr("42", argc)
def test_init_stack_sz(self):
""" Instanciate RPNExpr specifiing a stack_size """
for argc in range(0,256, 8):
for sz in range(4, 256, 2):
with self.subTest("RPNExpr('42', %d,%d)" % (argc, sz)):
expr = pyrpn.RPNExpr("42", argc, sz)
def test_op_dict(self):
""" Testing RPNExpr.get_ops() method """
known_ops = dict([
('add', '+'),
('sub', '-'),
('mul', '*'),
('div', '/'),
('mod', '%'),
('neg', '!'),
('not', '~'),
('and', '&'),
('or', '|'),
('xor', '^'),
('>>', 'r'),
('<<', 'l'),
('xchg', 'x'),
('dup', 'd'),
('pop', 'p'),
])
for long, short in pyrpn.get_ops().items():
self.assertIn(long, known_ops)
self.assertEqual(short, known_ops[long])
def test_rand_expr(self):
""" Testing RPNExpr.random_expr() """
result = {}
counters = {'vals': 0, 'args': 0}
all_count = 0
for sz in range(1,64,3):
for argc in range(1,128):
rnd_expr = pyrpn.random_expr(argc, sz)
#print(repr(rnd_expr))
for tok in rnd_expr.split(' '):
all_count += 1
if len(tok) == 0:
continue
try:
itok = int(tok, 0)
counters['vals'] += 1
continue
except Exception:
pass
if tok[0] == 'A':
counters['args'] += 1
else:
if tok not in counters:
counters[tok] = 0
counters[tok] += 1
all_ops = len(pyrpn.get_ops()) + 2
entropy = 1-sum([(n/all_count)**2
for _, n in counters.items()])
self.assertGreater(entropy, 1-(1/all_ops), "Low entropy !")
class TestRpnCompile(unittest.TestCase):
def test_basic(self):
""" Compile 6 7 * """
expr = pyrpn.RPNExpr("6 7 *", 2)
def test_base_error(self):
""" Compile error a b + """
with self.assertRaises(ValueError):
expr = pyrpn.RPNExpr("a b +", 3)
def test_various_compile(self):
""" Compile various expressions """
exprs = (("42 2 + * /", 0),
("A1 A2 A0 * + /", 3),
)
for expr, argc in exprs:
res = pyrpn.RPNExpr(expr, argc)
def test_long_code(self):
""" Compile long expressions """
for i in range(64,256,2):
for j in range(256,4):
exprs = ' '.join(['+' for _ in range(i)])
expr = pyrpn.RPNExpr(exprs, j)
class TestRpnEval(unittest.TestCase):
def test_arithm(self):
""" Tests arithmetic ops """
ops = [
('+', '+'),
('-', '-'),
('*', '*'),
('/', '//'),
('%', '%'),
('&', '&'),
('|', '|'),
('^', '^'),
#('l', '<<'),
#('r', '>>')
]
for rpn_op, pyop in ops:
with self.subTest('Testing op %s (%s)' % (rpn_op, pyop)):
sys.stderr.write('.')
sys.stderr.flush()
for i in range(0x1000):
op1, op2 = random.randint(0,IMAX), random.randint(1,IMAX)
pyexpr = '%d %s %d' % (op1, pyop, op2)
pyval = eval(pyexpr) % (1<<64)
rpn_expr = "%d %d %s" % (op1, op2, rpn_op)
expr = pyrpn.RPNExpr(rpn_expr, 0)
res = expr.eval()
self.assertEqual(res, pyval,
"TEST#%d %s != %s" % (i, rpn_expr, pyexpr))
def test_not(self):
""" Test unary op """
ops = [('~', '~%d'), ('!', '-%d')]
for rpn_op, pystr in ops:
with self.subTest("Testing op '%sX' (%s)" % (rpn_op, pystr)):
sys.stderr.write('.')
sys.stderr.flush()
for i in range(0x1000):
op1 = random.randint(0, IMAX)
pyexpr = pystr % op1
pyval = eval(pyexpr) % (1<<64)
rpn_expr = '%d %s' % (op1, rpn_op)
expr = pyrpn.RPNExpr(rpn_expr, 0)
res = expr.eval()
self.assertEqual(res, pyval,
"TEST#%d %s != %s" % (i, rpn_expr, pyexpr))
def test_div_zero(self):
""" Test division by zeros """
tests = ['A0 0 /', 'A0 0 %']
for test in tests:
with self.subTest('Testing division by zero using %s' % test):
for i in range(10):
expr = pyrpn.RPNExpr(test, 1)
res = expr.eval(random.randint(0, IMAX))
self.assertEqual(res, 0)
def test_stack_init(self):
""" testing that stack is initialized to 0 """
rpn = ' '.join(['+' for _ in range(15)])
for stack_size in range(4, 128):
with self.subTest('Stack with size %d initialized to 0' % stack_size):
for argc in range(0,256,16):
expr = pyrpn.RPNExpr(rpn, argc, stack_size)
r = expr.eval(*[random.randint(0, IMAX)
for _ in range(argc)])
self.assertEqual(r, 0,
'"+ + + +..." should be 0 but %d returned with %d args' % (r, argc))
def test_lshift_limit(self):
""" 2 << 0x10000 == 0 ? """
expr = pyrpn.RPNExpr("2 %d <<" % 0x100000, 0)
res = expr.eval()
self.assertEqual(res, 0)
def test_rshift_limit(self):
""" (1<<64)-1 >> 0x10000 == 0 ? """
expr = pyrpn.RPNExpr("%d %d >>" % ((1<<64)-1, 0x100000), 0)
res = expr.eval()
self.assertEqual(res, 0)
def test_airthm_extended(self):
""" Extended arithmetic tests """
exprs = (
('A0 A1 +', '{0} + {1}', 2),
('A0 A0 A1 + +', '{0} + {1} + {0}', 2),
('A1 A0 A0 A0 A0 A0 A0 A0 A0 + + + + + + +', '{0} * 8', 2),
('+', '0', 2),
('-', '0', 2),
('A0 A1 -', '{0} - {1}', 2),
('A0 A0 A1 - -', '{0} - ({0} - {1})', 2),
('A0 0 A0 - -', '({0} - (0 - {0})) ', 2),
('*', '0', 2),
('A0 A1 *', '{0} * {1}', 2),
('A0 A0 A1 * *', '{0} * {0} * {1}', 2),
('A0 A0 A0 * *', '{0} * {0} * {0}', 2),
('A0 A1 x /', '{1} // {0}', 2),
('A0 A1 A0 pop /', '{0} // {1}', 2),
('A0 A1 dup +', '{1} + {1}', 2),
)
for rpn, pye, argc in exprs:
expr = pyrpn.RPNExpr(rpn, argc, 8)
for _ in range(0x300):
args = tuple([random.randint(0,255) for _ in range(argc)])
with self.subTest('%s == %s %r' % (rpn, pye, args)):
pyexpr = pye.format(*args)
try:
respy = eval(pyexpr) % (1<<64)
except ZeroDivisionError:
respy = 0
res = expr.eval(*args)
self.assertEqual(res, respy,
'%s%r != %s' % (rpn, args, pyexpr))
class SequentialTestLoader(unittest.TestLoader):
def getTestCaseNames(self, testCaseClass):
test_names = super().getTestCaseNames(testCaseClass)
testcase_methods = list(testCaseClass.__dict__.keys())
test_names.sort(key=testcase_methods.index)
return test_names
if __name__ == '__main__':
unittest.main(testLoader=SequentialTestLoader())