Initial commit
This commit is contained in:
commit
a42b237918
27 changed files with 4037 additions and 0 deletions
258
Doxyfile.mk
Normal file
258
Doxyfile.mk
Normal 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
9
LICENCE.txt
Normal 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
91
Makefile
Normal 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
45
README
Normal 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
1
VERSION
Normal file
|
|
@ -0,0 +1 @@
|
|||
0.0.1-dev
|
||||
55
check_deps.sh
Executable file
55
check_deps.sh
Executable 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
22
config.h
Normal 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
129
python_pyrpn.c
Normal 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
96
python_pyrpn.h
Normal 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
283
python_rpnexpr.c
Normal 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
120
python_rpnexpr.h
Normal 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
145
rpn_if.c
Normal 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
220
rpn_if.h
Normal 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
41
rpn_ifs.h
Normal 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
465
rpn_jit.c
Normal 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
251
rpn_jit.h
Normal 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
246
rpn_lib.asm
Normal 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
122
rpn_lib.h
Normal 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
58
rpn_mutation.c
Normal 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
83
rpn_mutation.h
Normal 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
387
rpn_parse.c
Normal 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
326
rpn_parse.h
Normal 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
257
test.c
Normal 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
0
tests/__init__.py
Normal file
51
tests/benchmark.py
Normal file
51
tests/benchmark.py
Normal 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
1
tests/pyrpn.so
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../pyrpn.so
|
||||
275
tests/tests_rpn.py
Executable file
275
tests/tests_rpn.py
Executable 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())
|
||||
Loading…
Add table
Add a link
Reference in a new issue