diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f8253ca --- /dev/null +++ b/.clang-format @@ -0,0 +1,12 @@ +--- +AlignTrailingComments: false +AllowShortFunctionsOnASingleLine: None +BreakBeforeTernaryOperators: false +ColumnLimit: 80 +ContinuationIndentWidth: 2 +IndentCaseLabels: true +IndentWrappedFunctionNames: true +ReflowComments: false +SortIncludes: false +SpaceAfterCStyleCast: true +... diff --git a/.cmake-format b/.cmake-format new file mode 100644 index 0000000..54f4495 --- /dev/null +++ b/.cmake-format @@ -0,0 +1,10 @@ +# How wide to allow formatted cmake files. +line_width = 80 +# How many spaces to tab for indent. +tab_size = 2 +# What style line endings to use in the output. +line_ending = 'unix' +# Enable comment markup parsing and reflow. +enable_markup = False +# Specify the encoding of the output file. +output_encoding = 'utf-8' diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0f17867 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.gitignore b/.gitignore index 648721f..77eda33 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,8 @@ expr_test *.gcda *.gcno *.gcov +*.profraw +*.profdata # Debug files *.dSYM/ @@ -40,3 +42,9 @@ expr_test # Makefile local config config.mk + +# Build directory +build/ + +# VSCode directory +.vscode diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..41eb98c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,61 @@ +cmake_minimum_required(VERSION 3.0.2) + +project(expr C) + +set(CMAKE_C_STANDARD 99) + +option(EXPR_PICKY_COMPILER "Enable picky compiler options" ON) + +if(NOT MSVC) + option(EXPR_PVS_STUDIO "Enable PVS-Studio analysis" OFF) +endif() + +#TODO: coverage + +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) + +if(EXPR_PICKY_COMPILER) + if(NOT MSVC) + set(CMAKE_C_FLAGS + "${CMAKE_C_FLAGS} -Wall -Werror -Wno-unused-function -Wpedantic -Wdeclaration-after-statement -Wstrict-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline" + ) + endif() +endif() + +include_directories(include) + +if(NOT MSVC) + list(APPEND _libs m) +endif() + +add_executable(example1 examples/example1.c) +target_link_libraries(example1 ${_libs}) + +add_executable(example2 examples/example2.c) +target_link_libraries(example2 ${_libs}) + +add_executable(example3 examples/example3.c) +target_link_libraries(example3 ${_libs}) + +add_executable(test expr_test.c) +target_link_libraries(test ${_libs}) + +if(EXPR_PVS_STUDIO) + include(PVS-Studio) + if(NOT CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + endif() + pvs_studio_add_target( + TARGET + pvs_studio_analysis + ALL + FORMAT + fullhtml + ANALYZE + example1 + example2 + example3 + test + LOG + pvs_studio_fullhtml) +endif() diff --git a/Makefile b/Makefile index 7a2e8e5..21cb805 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,13 @@ -CFLAGS ?= -std=c99 -g -O0 -pedantic -Wall -Wextra +CFLAGS ?= -std=c99 -g -O0 -pedantic -Wall -Werror -Wextra -Wno-unused-function -Wpedantic -Wdeclaration-after-statement -Wstrict-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -Iinclude LDFLAGS ?= -lm -O0 -g +INC_DIR := include TESTBIN := expr_test all: - @echo make test - run tests - @echo make llvm-cov - report test coverage using LLVM (set LLVM_VER if needed) - @echo make gcov - report test coverage (set GCC_VER if needed) + @echo make test - run tests + @echo make llvm-cov - report test coverage using LLVM "(set LLVM_VER if needed)" + @echo make gcov - report test coverage "(set GCC_VER if needed)" test: $(TESTBIN) ./$(TESTBIN) @@ -14,7 +15,7 @@ test: $(TESTBIN) $(TESTBIN): expr_test.o $(CC) $^ $(LDFLAGS) -o $@ -expr_test.o: expr_test.c expr.h expr_debug.h +expr_test.o: expr_test.c $(INC_DIR)/expr.h $(INC_DIR)/expr_debug.h llvm-cov: CC := clang$(LLVM_VER) llvm-cov: CFLAGS += -fprofile-instr-generate -fcoverage-mapping diff --git a/README.md b/README.md index d2967aa..ee33595 100644 --- a/README.md +++ b/README.md @@ -7,81 +7,88 @@ input and returns floating-point number as a result. ## Features -* Supports arithmetic, bitwise and logical operators -* Supports variables -* Can be extended with custom functions -* Simple evaluation takes ~50 nanoseconds on an average PC -* Low memory usage makes it suitable for embedded systems -* Pure C99 with no external dependencies -* Good test coverage -* Easy to understand (~600 LOC in a single header file) +- Supports arithmetic, bitwise and logical operators +- Supports variables +- Supports macros +- Can be extended with custom functions +- Error handling with error kind and position +- Simple evaluation takes ~50 nanoseconds on an average PC +- Low memory usage makes it suitable for embedded systems +- Pure C99 with no external dependencies +- Good test coverage +- Easy to understand (~600 LOC in a single header file) ## Example ```c #include "expr.h" -// Custom function that returns the sum of its two arguments -static float add(struct expr_func *f, vec_expr_t *args, void *c) { - float a = expr_eval(&vec_nth(args, 0)); - float b = expr_eval(&vec_nth(args, 1)); - return a + b; +int main(void) { + printf("result: %f\n", expr_calc("2 + 3")); + return 0; } +``` -static struct expr_func user_funcs[] = { - {"add", add, NULL, 0}, - {NULL, NULL, NULL, 0}, -}; - -int main() { - const char *s = "x = 5, add(2, x)"; - struct expr_var_list vars = {0}; - struct expr *e = expr_create(s, strlen(s), &vars, user_funcs); - if (e == NULL) { - printf("Syntax error"); - return 1; - } +Output: `result: 5.000000` - float result = expr_eval(e); - printf("result: %f\n", result); +## API - expr_destroy(e, &vars); - return 0; -} +```c +static struct expr *expr_create2(const char *s, size_t len, + struct expr_var_list *vars, + struct expr_func *funcs, int *near, + int *error); + +static struct expr *expr_create(const char *s, size_t len, + struct expr_var_list *vars, + struct expr_func *funcs); ``` -Output: `result: 7.000000` +Returns compiled expression from the given string. If expression uses +variables - they are bound to `vars`, so you can modify values before evaluation +or check the results after the evaluation. The `near` and `error` arguments are +used for error handling. -## API +```c +static void expr_destroy(struct expr *e, struct expr_var_list *vars); +``` -`struct expr *expr_create(const char *s, size_t len, struct expr_var_list -*vars, struct expr_func *funcs)` - returns compiled expression from the given -string. If expression uses variables - they are bound to `vars`, so you can -modify values before evaluation or check the results after the evaluation. +Cleans up. Parameters can be `NULL` (e.g. if you want to clean up expression, +but reuse variables for another expression). -`float expr_eval(struct expr *e)` - evaluates compiled expression. +```c +static expr_num_t expr_eval(struct expr *e); +``` -`void expr_destroy(struct expr *e, struct expr_var_list *vars)` - cleans up -memory. Parameters can be NULL (e.g. if you want to clean up expression, but -reuse variables for another expression). +Evaluates compiled expression. + +```c +static struct expr_var *expr_var(struct expr_var_list *vars, const char *s, + size_t len); +``` -`struct expr_var *expr_var(struct expr_var *vars, const char *s, size_t len)` - -returns/creates variable of the given name in the given list. This can be used +Returns/creates variable of the given name in the given list. This can be used to get variable references to get/set them manually. +```c +static expr_num_t expr_calc(const char *s); +``` + +Takes an expression and immediately returns the result of it. If there is a parse error, `expr_calc()` returns `NAN`. + ## Supported operators -* Arithmetics: `+`, `-`, `*`, `/`, `%` (remainder), `**` (power) -* Bitwise: `<<`, `>>`, `&`, `|`, `^` (xor or unary bitwise negation) -* Logical: `<`, `>`, `==`, `!=`, `<=`, `>=`, `&&`, `||`, `!` (unary not) -* Other: `=` (assignment, e.g. `x=y=5`), `,` (separates expressions or function parameters) +- Arithmetics: `+`, `-`, `*`, `/`, `%` (remainder), `**` (power) +- Bitwise: `<<`, `>>`, `&`, `|`, `^` (xor or unary bitwise negation) +- Logical: `<`, `>`, `==`, `!=`, `<=`, `>=`, `&&`, `||`, `!` (unary not) +- Other: `=` (assignment, e.g. `x=y=5`), `,` (separates expressions or function parameters) Only the following functions from libc are used to reduce the footprint and make it easier to use: -* calloc, realloc and free - memory management -* isnan, isinf, fmodf, powf - math operations -* strlen, strncmp, strncpy, strtof - tokenizing and parsing +- `calloc()`, `realloc()`, `free()` and `memcpy()` - memory management (all replaceable via macro) +- `isnan()`, `isinf()`, `fmod()`, `pow()` - math operations (`fmod()` and `pow()` replaceable via macro) +- `strlen()`, `strncmp()` and `snprintf()` - tokenizing and parsing (all replaceable via macro) ## Running tests @@ -94,8 +101,30 @@ depending on whether you use GCC or LLVM/Clang. Since people may have different compiler versions, one may specify a version explicitly, e.g. `make llvm-cov LLVM_VER=-3.8` or `make gcov GCC_VER=-5`. +## Building with CMake + +To build all the examples, tests and benchmarks using [CMake](https://cmake.org), do: + +```bash +mkdir build && cd build/ +cmake .. +make +``` + +## Running PVS Studio analysis + +Download and install the PVS's command line tool [`how-to-use-pvs-studio-free`](https://github.com/viva64/how-to-use-pvs-studio-free) according its site instructions. After that, in the library root directory, perform the following commands: + +```bash +how-to-use-pvs-studio-free -c 2 -m . +mkdir build && cd build/ +cmake -DEXPR_PVS_STUDIO=ON .. +make pvs_studio_analysis +``` + +The full PVS report will be generated in the `build/pvs_studio_fullhtml` directory. + ## License Code is distributed under MIT license, feel free to use it in your proprietary projects as well. - diff --git a/cmake/PVS-Studio.cmake b/cmake/PVS-Studio.cmake new file mode 100644 index 0000000..66e4cfb --- /dev/null +++ b/cmake/PVS-Studio.cmake @@ -0,0 +1,552 @@ +# 2006-2008 (c) Viva64.com Team +# 2008-2018 (c) OOO "Program Verification Systems" +# +# Version 12 + +cmake_minimum_required(VERSION 2.8.12) +cmake_policy(SET CMP0054 NEW) + +if (PVS_STUDIO_AS_SCRIPT) + # This code runs at build time. + # It executes pvs-studio-analyzer and propagates its return value. + + set(in_cl_params FALSE) + set(additional_args) + + foreach (arg ${PVS_STUDIO_COMMAND}) + if (NOT in_cl_params) + if ("${arg}" STREQUAL "--cl-params") + set(in_cl_params TRUE) + endif () + else () + # A workaround for macOS frameworks (e.g. QtWidgets.framework) + # You can test this workaround on this project: https://github.com/easyaspi314/MidiEditor/tree/gba + if (APPLE AND "${arg}" MATCHES "^-I(.*)\\.framework$") + STRING(REGEX REPLACE "^-I(.*)\\.framework$" "\\1.framework" framework "${arg}") + if (IS_ABSOLUTE "${framework}") + get_filename_component(framework "${framework}" DIRECTORY) + list(APPEND additional_args "-iframework") + list(APPEND additional_args "${framework}") + endif () + endif () + endif () + endforeach () + + execute_process(COMMAND ${PVS_STUDIO_COMMAND} ${additional_args} + ERROR_VARIABLE error + RESULT_VARIABLE result) + + set(stderr_type "") + + if (result) + set(stderr_type FATAL_ERROR) + endif () + + if (result OR error) + message(${stderr_type} "${error}") + endif () + + return() +endif () + +if(__PVS_STUDIO_INCLUDED) + return() +endif() +set(__PVS_STUDIO_INCLUDED TRUE) + +set(PVS_STUDIO_SCRIPT "${CMAKE_CURRENT_LIST_FILE}") + +function (pvs_studio_log TEXT) + if (PVS_STUDIO_DEBUG) + message("PVS-Studio: ${TEXT}") + endif () +endfunction () + +function (pvs_studio_relative_path VAR ROOT FILEPATH) + set("${VAR}" "${FILEPATH}" PARENT_SCOPE) + if ("${FILEPATH}" MATCHES "^/.*$" OR "${FILEPATH}" MATCHES "^.:/.*$") + file(RELATIVE_PATH RPATH "${ROOT}" "${FILEPATH}") + if (NOT "${RPATH}" MATCHES "^\\.\\..*$") + set("${VAR}" "${RPATH}" PARENT_SCOPE) + endif () + endif () +endfunction () + +function (pvs_studio_join_path VAR DIR1 DIR2) + if ("${DIR2}" MATCHES "^(/|~|.:/).*$" OR "${DIR1}" STREQUAL "") + set("${VAR}" "${DIR2}" PARENT_SCOPE) + else () + set("${VAR}" "${DIR1}/${DIR2}" PARENT_SCOPE) + endif () +endfunction () + +macro (pvs_studio_append_flags_from_property CXX C DIR PREFIX) + if (NOT "${PROPERTY}" STREQUAL "NOTFOUND" AND NOT "${PROPERTY}" STREQUAL "PROPERTY-NOTFOUND") + foreach (PROP ${PROPERTY}) + pvs_studio_join_path(PROP "${DIR}" "${PROP}") + + if (APPLE AND "${PREFIX}" STREQUAL "-I" AND IS_ABSOLUTE "${PROP}" AND "${PROP}" MATCHES "\\.framework$") + get_filename_component(FRAMEWORK "${PROP}" DIRECTORY) + list(APPEND "${CXX}" "-iframework") + list(APPEND "${CXX}" "${FRAMEWORK}") + list(APPEND "${C}" "-iframework") + list(APPEND "${C}" "${FRAMEWORK}") + pvs_studio_log("framework: ${FRAMEWORK}") + elseif (NOT "${PROP}" STREQUAL "") + list(APPEND "${CXX}" "${PREFIX}${PROP}") + list(APPEND "${C}" "${PREFIX}${PROP}") + endif() + endforeach () + endif () +endmacro () + +macro (pvs_studio_append_standard_flag FLAGS STANDARD) + if ("${STANDARD}" MATCHES "^(99|11|14|17)$") + if ("${PVS_STUDIO_PREPROCESSOR}" MATCHES "gcc|clang") + list(APPEND "${FLAGS}" "-std=c++${STANDARD}") + endif () + endif () +endmacro () + +function (pvs_studio_set_directory_flags DIRECTORY CXX C) + set(CXX_FLAGS "${${CXX}}") + set(C_FLAGS "${${C}}") + + get_directory_property(PROPERTY DIRECTORY "${DIRECTORY}" INCLUDE_DIRECTORIES) + pvs_studio_append_flags_from_property(CXX_FLAGS C_FLAGS "${DIRECTORY}" "-I") + + get_directory_property(PROPERTY DIRECTORY "${DIRECTORY}" COMPILE_DEFINITIONS) + pvs_studio_append_flags_from_property(CXX_FLAGS C_FLAGS "" "-D") + + set("${CXX}" "${CXX_FLAGS}" PARENT_SCOPE) + set("${C}" "${C_FLAGS}" PARENT_SCOPE) +endfunction () + +function (pvs_studio_set_target_flags TARGET CXX C) + set(CXX_FLAGS "${${CXX}}") + set(C_FLAGS "${${C}}") + + set(prop_incdirs "$") + list(APPEND CXX_FLAGS "$<$:-I$-I>>") + list(APPEND C_FLAGS "$<$:-I$-I>>") + + set(prop_compdefs "$") + list(APPEND CXX_FLAGS "$<$:-D$-D>>") + list(APPEND C_FLAGS "$<$:-D$-D>>") + + set("${CXX}" "${CXX_FLAGS}" PARENT_SCOPE) + set("${C}" "${C_FLAGS}" PARENT_SCOPE) +endfunction () + +function (pvs_studio_set_source_file_flags SOURCE) + set(LANGUAGE "") + + string(TOLOWER "${SOURCE}" SOURCE_LOWER) + if ("${LANGUAGE}" STREQUAL "" AND "${SOURCE_LOWER}" MATCHES "^.*\\.(c|cpp|cc|cx|cxx|cp|c\\+\\+)$") + if ("${SOURCE}" MATCHES "^.*\\.c$") + set(LANGUAGE C) + else () + set(LANGUAGE CXX) + endif () + endif () + + if ("${LANGUAGE}" STREQUAL "C") + set(CL_PARAMS ${PVS_STUDIO_C_FLAGS} ${PVS_STUDIO_TARGET_C_FLAGS} -DPVS_STUDIO) + elseif ("${LANGUAGE}" STREQUAL "CXX") + set(CL_PARAMS ${PVS_STUDIO_CXX_FLAGS} ${PVS_STUDIO_TARGET_CXX_FLAGS} -DPVS_STUDIO) + endif () + + set(PVS_STUDIO_LANGUAGE "${LANGUAGE}" PARENT_SCOPE) + set(PVS_STUDIO_CL_PARAMS "${CL_PARAMS}" PARENT_SCOPE) +endfunction () + +function (pvs_studio_analyze_file SOURCE SOURCE_DIR BINARY_DIR) + set(PLOGS ${PVS_STUDIO_PLOGS}) + pvs_studio_set_source_file_flags("${SOURCE}") + + get_filename_component(SOURCE "${SOURCE}" REALPATH) + + get_source_file_property(PROPERTY "${SOURCE}" HEADER_FILE_ONLY) + if (PROPERTY) + return() + endif () + + pvs_studio_relative_path(SOURCE_RELATIVE "${SOURCE_DIR}" "${SOURCE}") + pvs_studio_join_path(SOURCE "${SOURCE_DIR}" "${SOURCE}") + + set(LOG "${BINARY_DIR}/PVS-Studio/${SOURCE_RELATIVE}.plog") + get_filename_component(LOG "${LOG}" REALPATH) + get_filename_component(PARENT_DIR "${LOG}" DIRECTORY) + + if (EXISTS "${SOURCE}" AND NOT TARGET "${LOG}" AND NOT "${PVS_STUDIO_LANGUAGE}" STREQUAL "") + # A workaround to support implicit dependencies for ninja generators. + set(depPvsArg) + set(depCommandArg) + if (CMAKE_VERSION VERSION_GREATER 3.6 AND "${CMAKE_GENERATOR}" STREQUAL "Ninja") + pvs_studio_relative_path(relLog "${CMAKE_BINARY_DIR}" "${LOG}") + set(depPvsArg --dep-file "${LOG}.d" --dep-file-target "${relLog}") + set(depCommandArg DEPFILE "${LOG}.d") + endif () + + # https://public.kitware.com/Bug/print_bug_page.php?bug_id=14353 + # https://public.kitware.com/Bug/file/5436/expand_command.cmake + # + # It is a workaround to expand generator expressions. + set(cmdline "${PVS_STUDIO_BIN}" analyze + --output-file "${LOG}" + --source-file "${SOURCE}" + ${depPvsArg} + ${PVS_STUDIO_ARGS} + --cl-params "${PVS_STUDIO_CL_PARAMS}" "${SOURCE}") + + string(REPLACE ";" "$" cmdline "${cmdline}") + set(pvscmd "${CMAKE_COMMAND}" + -D PVS_STUDIO_AS_SCRIPT=TRUE + -D "PVS_STUDIO_COMMAND=${cmdline}" + -P "${PVS_STUDIO_SCRIPT}" + ) + + add_custom_command(OUTPUT "${LOG}" + COMMAND "${CMAKE_COMMAND}" -E make_directory "${PARENT_DIR}" + COMMAND "${CMAKE_COMMAND}" -E remove_directory "${LOG}" + COMMAND ${pvscmd} + WORKING_DIRECTORY "${BINARY_DIR}" + DEPENDS "${SOURCE}" "${PVS_STUDIO_CONFIG}" "${PVS_STUDIO_SUPPRESS_BASE}" + IMPLICIT_DEPENDS "${PVS_STUDIO_LANGUAGE}" "${SOURCE}" + ${depCommandArg} + VERBATIM + COMMENT "Analyzing ${PVS_STUDIO_LANGUAGE} file ${SOURCE_RELATIVE}") + list(APPEND PLOGS "${LOG}") + endif () + set(PVS_STUDIO_PLOGS "${PLOGS}" PARENT_SCOPE) +endfunction () + +function (pvs_studio_analyze_target TARGET DIR) + set(PVS_STUDIO_PLOGS "${PVS_STUDIO_PLOGS}") + set(PVS_STUDIO_TARGET_CXX_FLAGS "") + set(PVS_STUDIO_TARGET_C_FLAGS "") + + get_target_property(PROPERTY "${TARGET}" SOURCES) + pvs_studio_relative_path(BINARY_DIR "${CMAKE_SOURCE_DIR}" "${DIR}") + if ("${BINARY_DIR}" MATCHES "^/.*$") + pvs_studio_join_path(BINARY_DIR "${CMAKE_BINARY_DIR}" "PVS-Studio/__${BINARY_DIR}") + else () + pvs_studio_join_path(BINARY_DIR "${CMAKE_BINARY_DIR}" "${BINARY_DIR}") + endif () + + file(MAKE_DIRECTORY "${BINARY_DIR}") + + pvs_studio_set_directory_flags("${DIR}" PVS_STUDIO_TARGET_CXX_FLAGS PVS_STUDIO_TARGET_C_FLAGS) + pvs_studio_set_target_flags("${TARGET}" PVS_STUDIO_TARGET_CXX_FLAGS PVS_STUDIO_TARGET_C_FLAGS) + + if (NOT "${PROPERTY}" STREQUAL "NOTFOUND" AND NOT "${PROPERTY}" STREQUAL "PROPERTY-NOTFOUND") + foreach (SOURCE ${PROPERTY}) + pvs_studio_join_path(SOURCE "${DIR}" "${SOURCE}") + pvs_studio_analyze_file("${SOURCE}" "${DIR}" "${BINARY_DIR}") + endforeach () + endif () + + set(PVS_STUDIO_PLOGS "${PVS_STUDIO_PLOGS}" PARENT_SCOPE) +endfunction () + +set(PVS_STUDIO_RECURSIVE_TARGETS) +set(PVS_STUDIO_RECURSIVE_TARGETS_NEW) + +macro(pvs_studio_get_recursive_targets TARGET) + get_target_property(libs "${TARGET}" LINK_LIBRARIES) + foreach (lib IN LISTS libs) + list(FIND PVS_STUDIO_RECURSIVE_TARGETS "${lib}" index) + if (TARGET "${lib}" AND "${index}" STREQUAL -1) + get_target_property(target_type "${lib}" TYPE) + if (NOT "${target_type}" STREQUAL "INTERFACE_LIBRARY") + list(APPEND PVS_STUDIO_RECURSIVE_TARGETS "${lib}") + list(APPEND PVS_STUDIO_RECURSIVE_TARGETS_NEW "${lib}") + pvs_studio_get_recursive_targets("${lib}") + endif () + endif () + endforeach () +endmacro() + +option(PVS_STUDIO_DISABLE OFF "Disable PVS-Studio targets") +option(PVS_STUDIO_DEBUG OFF "Add debug info") + +# pvs_studio_add_target +# Target options: +# ALL add PVS-Studio target to default build (default: off) +# TARGET target name of analysis target (default: pvs) +# ANALYZE targets... targets to analyze +# RECURSIVE analyze target's dependencies (requires CMake 3.5+) +# COMPILE_COMMANDS use compile_commands.json instead of targets (specified by the 'ANALYZE' option) to determine files for analysis +# (set CMAKE_EXPORT_COMPILE_COMMANDS, available only for Makefile and Ninja generators) +# +# Output options: +# OUTPUT prints report to stdout +# LOG path path to report (default: ${CMAKE_CURRENT_BINARY_DIR}/PVS-Studio.log) +# FORMAT format format of report +# MODE mode analyzers/levels filter (default: GA:1,2) +# HIDE_HELP do not print help message +# +# Analyzer options: +# PLATFORM name linux32/linux64 (default: linux64) +# PREPROCESSOR name preprocessor type: gcc/clang (default: auto detected) +# LICENSE path path to PVS-Studio.lic (default: ~/.config/PVS-Studio/PVS-Studio.lic) +# CONFIG path path to PVS-Studio.cfg +# CFG_TEXT text embedded PVS-Studio.cfg +# SUPPRESS_BASE path to suppress base file +# KEEP_COMBINED_PLOG do not delete combined plog file *.pvs.raw for further processing with plog-converter +# +# Misc options: +# DEPENDS targets.. additional target dependencies +# SOURCES path... list of source files to analyze +# BIN path path to pvs-studio-analyzer (Unix) or CompilerCommandsAnalyzer.exe (Windows) +# CONVERTER path path to plog-converter (Unix) or HtmlGenerator.exe (Windows) +# C_FLAGS flags... additional C_FLAGS +# CXX_FLAGS flags... additional CXX_FLAGS +# ARGS args... additional pvs-studio-analyzer/CompilerCommandsAnalyzer.exe flags +function (pvs_studio_add_target) + macro (default VAR VALUE) + if ("${${VAR}}" STREQUAL "") + set("${VAR}" "${VALUE}") + endif () + endmacro () + + set(PVS_STUDIO_SUPPORTED_PREPROCESSORS "gcc|clang|visualcpp") + if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + set(DEFAULT_PREPROCESSOR "clang") + elseif (MSVC) + set(DEFAULT_PREPROCESSOR "visualcpp") + else () + set(DEFAULT_PREPROCESSOR "gcc") + endif () + + set(OPTIONAL OUTPUT ALL RECURSIVE HIDE_HELP KEEP_COMBINED_PLOG COMPILE_COMMANDS) + set(SINGLE LICENSE CONFIG TARGET LOG FORMAT BIN CONVERTER PLATFORM PREPROCESSOR CFG_TEXT SUPPRESS_BASE) + set(MULTI SOURCES C_FLAGS CXX_FLAGS ARGS DEPENDS ANALYZE MODE) + cmake_parse_arguments(PVS_STUDIO "${OPTIONAL}" "${SINGLE}" "${MULTI}" ${ARGN}) + + if ("${PVS_STUDIO_CONFIG}" STREQUAL "" OR NOT "${PVS_STUDIO_CFG_TEXT}" STREQUAL "") + set(PVS_STUDIO_EMPTY_CONFIG ON) + else () + set(PVS_STUDIO_EMPTY_CONFIG OFF) + endif () + + default(PVS_STUDIO_CFG_TEXT "analysis-mode=31") + default(PVS_STUDIO_CONFIG "${CMAKE_BINARY_DIR}/PVS-Studio.cfg") + default(PVS_STUDIO_C_FLAGS "") + default(PVS_STUDIO_CXX_FLAGS "") + default(PVS_STUDIO_TARGET "pvs") + default(PVS_STUDIO_LOG "PVS-Studio.log") + + set(PATHS) + if (WIN32) + set(ROOT "PROGRAMFILES(X86)") + set(ROOT "$ENV{${ROOT}}/PVS-Studio") + string(REPLACE \\ / ROOT "${ROOT}") + + if (EXISTS "${ROOT}") + set(PATHS "${ROOT}") + endif () + + default(PVS_STUDIO_BIN "CompilerCommandsAnalyzer.exe") + default(PVS_STUDIO_CONVERTER "HtmlGenerator.exe") + else () + default(PVS_STUDIO_BIN "pvs-studio-analyzer") + default(PVS_STUDIO_CONVERTER "plog-converter") + endif () + + find_program(PVS_STUDIO_BIN_PATH "${PVS_STUDIO_BIN}" ${PATHS}) + set(PVS_STUDIO_BIN "${PVS_STUDIO_BIN_PATH}") + + if (NOT EXISTS "${PVS_STUDIO_BIN}") + message(FATAL_ERROR "pvs-studio-analyzer is not found") + endif () + + find_program(PVS_STUDIO_CONVERTER_PATH "${PVS_STUDIO_CONVERTER}" ${PATHS}) + set(PVS_STUDIO_CONVERTER "${PVS_STUDIO_CONVERTER_PATH}") + + if (NOT EXISTS "${PVS_STUDIO_CONVERTER}") + message(FATAL_ERROR "plog-converter is not found") + endif () + + default(PVS_STUDIO_MODE "GA:1,2") + default(PVS_STUDIO_PREPROCESSOR "${DEFAULT_PREPROCESSOR}") + if (WIN32) + default(PVS_STUDIO_PLATFORM "x64") + else () + default(PVS_STUDIO_PLATFORM "linux64") + endif () + + string(REPLACE ";" "+" PVS_STUDIO_MODE "${PVS_STUDIO_MODE}") + + if (PVS_STUDIO_EMPTY_CONFIG) + set(PVS_STUDIO_CONFIG_COMMAND "${CMAKE_COMMAND}" -E echo "${PVS_STUDIO_CFG_TEXT}" > "${PVS_STUDIO_CONFIG}") + else () + set(PVS_STUDIO_CONFIG_COMMAND "${CMAKE_COMMAND}" -E touch "${PVS_STUDIO_CONFIG}") + endif () + + add_custom_command(OUTPUT "${PVS_STUDIO_CONFIG}" + COMMAND ${PVS_STUDIO_CONFIG_COMMAND} + WORKING_DIRECTORY "${BINARY_DIR}" + COMMENT "Generating PVS-Studio.cfg") + + if (NOT "${PVS_STUDIO_PREPROCESSOR}" MATCHES "^${PVS_STUDIO_SUPPORTED_PREPROCESSORS}$") + message(FATAL_ERROR "Preprocessor ${PVS_STUDIO_PREPROCESSOR} isn't supported. Available options: ${PVS_STUDIO_SUPPORTED_PREPROCESSORS}.") + endif () + + pvs_studio_append_standard_flag(PVS_STUDIO_CXX_FLAGS "${CMAKE_CXX_STANDARD}") + pvs_studio_set_directory_flags("${CMAKE_CURRENT_SOURCE_DIR}" PVS_STUDIO_CXX_FLAGS PVS_STUDIO_C_FLAGS) + + if (NOT "${PVS_STUDIO_LICENSE}" STREQUAL "") + pvs_studio_join_path(PVS_STUDIO_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}" "${PVS_STUDIO_LICENSE}") + list(APPEND PVS_STUDIO_ARGS --lic-file "${PVS_STUDIO_LICENSE}") + endif () + + list(APPEND PVS_STUDIO_ARGS --cfg "${PVS_STUDIO_CONFIG}" + --platform "${PVS_STUDIO_PLATFORM}" + --preprocessor "${PVS_STUDIO_PREPROCESSOR}") + + if (NOT "${PVS_STUDIO_SUPPRESS_BASE}" STREQUAL "") + pvs_studio_join_path(PVS_STUDIO_SUPPRESS_BASE "${CMAKE_CURRENT_SOURCE_DIR}" "${PVS_STUDIO_SUPPRESS_BASE}") + list(APPEND PVS_STUDIO_ARGS --suppress-file "${PVS_STUDIO_SUPPRESS_BASE}") + endif () + + if (NOT "${CMAKE_CXX_COMPILER}" STREQUAL "") + list(APPEND PVS_STUDIO_ARGS --cxx "${CMAKE_CXX_COMPILER}") + endif () + + if (NOT "${CMAKE_C_COMPILER}" STREQUAL "") + list(APPEND PVS_STUDIO_ARGS --cc "${CMAKE_C_COMPILER}") + endif () + + set(PVS_STUDIO_PLOGS "") + + set(PVS_STUDIO_RECURSIVE_TARGETS_NEW) + if (${PVS_STUDIO_RECURSIVE}) + foreach (TARGET IN LISTS PVS_STUDIO_ANALYZE) + list(APPEND PVS_STUDIO_RECURSIVE_TARGETS_NEW "${TARGET}") + pvs_studio_get_recursive_targets("${TARGET}") + endforeach () + endif () + + set(inc_path) + + foreach (TARGET ${PVS_STUDIO_ANALYZE}) + set(DIR "${CMAKE_CURRENT_SOURCE_DIR}") + string(FIND "${TARGET}" ":" DELIM) + if ("${DELIM}" GREATER "-1") + math(EXPR DELIMI "${DELIM}+1") + string(SUBSTRING "${TARGET}" "${DELIMI}" "-1" DIR) + string(SUBSTRING "${TARGET}" "0" "${DELIM}" TARGET) + pvs_studio_join_path(DIR "${CMAKE_CURRENT_SOURCE_DIR}" "${DIR}") + else () + get_target_property(TARGET_SOURCE_DIR "${TARGET}" SOURCE_DIR) + if (EXISTS "${TARGET_SOURCE_DIR}") + set(DIR "${TARGET_SOURCE_DIR}") + endif () + endif () + pvs_studio_analyze_target("${TARGET}" "${DIR}") + list(APPEND PVS_STUDIO_DEPENDS "${TARGET}") + + if ("${inc_path}" STREQUAL "") + set(inc_path "$") + else () + set(inc_path "${inc_path}$$") + endif () + endforeach () + + foreach (TARGET ${PVS_STUDIO_RECURSIVE_TARGETS_NEW}) + set(DIR "${CMAKE_CURRENT_SOURCE_DIR}") + get_target_property(TARGET_SOURCE_DIR "${TARGET}" SOURCE_DIR) + if (EXISTS "${TARGET_SOURCE_DIR}") + set(DIR "${TARGET_SOURCE_DIR}") + endif () + pvs_studio_analyze_target("${TARGET}" "${DIR}") + list(APPEND PVS_STUDIO_DEPENDS "${TARGET}") + endforeach () + + set(PVS_STUDIO_TARGET_CXX_FLAGS "") + set(PVS_STUDIO_TARGET_C_FLAGS "") + foreach (SOURCE ${PVS_STUDIO_SOURCES}) + pvs_studio_analyze_file("${SOURCE}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}") + endforeach () + + if (PVS_STUDIO_COMPILE_COMMANDS) + set(COMPILE_COMMANDS_LOG "${PVS_STUDIO_LOG}.pvs.analyzer.raw") + if (NOT CMAKE_EXPORT_COMPILE_COMMANDS) + message(FATAL_ERROR "You should set CMAKE_EXPORT_COMPILE_COMMANDS to TRUE") + endif () + add_custom_command( + OUTPUT "${COMPILE_COMMANDS_LOG}" + COMMAND "${PVS_STUDIO_BIN}" analyze -i + --output-file "${COMPILE_COMMANDS_LOG}.always" + ${PVS_STUDIO_ARGS} + COMMENT "Analyzing with PVS-Studio" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + DEPENDS "${PVS_STUDIO_CONFIG}" "${PVS_STUDIO_SUPPRESS_BASE}" + ) + list(APPEND PVS_STUDIO_PLOGS_LOGS "${COMPILE_COMMANDS_LOG}.always") + list(APPEND PVS_STUDIO_PLOGS_DEPENDENCIES "${COMPILE_COMMANDS_LOG}") + endif () + + pvs_studio_relative_path(LOG_RELATIVE "${CMAKE_BINARY_DIR}" "${PVS_STUDIO_LOG}") + if (PVS_STUDIO_PLOGS OR PVS_STUDIO_COMPILE_COMMANDS) + if (WIN32) + string(REPLACE / \\ PVS_STUDIO_PLOGS "${PVS_STUDIO_PLOGS}") + endif () + if (WIN32) + set(COMMANDS COMMAND type ${PVS_STUDIO_PLOGS} ${PVS_STUDIO_PLOGS_LOGS} > "${PVS_STUDIO_LOG}" 2>nul) + else () + set(COMMANDS COMMAND cat ${PVS_STUDIO_PLOGS} ${PVS_STUDIO_PLOGS_LOGS} > "${PVS_STUDIO_LOG}") + endif () + set(COMMENT "Generating ${LOG_RELATIVE}") + if (NOT "${PVS_STUDIO_FORMAT}" STREQUAL "" OR PVS_STUDIO_OUTPUT) + if ("${PVS_STUDIO_FORMAT}" STREQUAL "") + set(PVS_STUDIO_FORMAT "errorfile") + endif () + list(APPEND COMMANDS + COMMAND "${CMAKE_COMMAND}" -E remove -f "${PVS_STUDIO_LOG}.pvs.raw" + COMMAND "${CMAKE_COMMAND}" -E rename "${PVS_STUDIO_LOG}" "${PVS_STUDIO_LOG}.pvs.raw" + COMMAND "${PVS_STUDIO_CONVERTER}" -t "${PVS_STUDIO_FORMAT}" "${PVS_STUDIO_LOG}.pvs.raw" -o "${PVS_STUDIO_LOG}" -a "${PVS_STUDIO_MODE}" + ) + if(NOT PVS_STUDIO_KEEP_COMBINED_PLOG) + list(APPEND COMMANDS COMMAND "${CMAKE_COMMAND}" -E remove -f "${PVS_STUDIO_LOG}.pvs.raw") + endif() + endif () + else () + set(COMMANDS COMMAND "${CMAKE_COMMAND}" -E touch "${PVS_STUDIO_LOG}") + set(COMMENT "Generating ${LOG_RELATIVE}: no sources found") + endif () + + if (WIN32) + string(REPLACE / \\ PVS_STUDIO_LOG "${PVS_STUDIO_LOG}") + endif () + + add_custom_command(OUTPUT "${PVS_STUDIO_LOG}" + ${COMMANDS} + COMMENT "${COMMENT}" + DEPENDS ${PVS_STUDIO_PLOGS} ${PVS_STUDIO_PLOGS_DEPENDENCIES} + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") + + if (PVS_STUDIO_ALL) + set(ALL "ALL") + else () + set(ALL "") + endif () + + if (PVS_STUDIO_OUTPUT) + if (PVS_STUDIO_HIDE_HELP AND NOT WIN32) + set(COMMANDS COMMAND grep -v " error: Help:" ${PVS_STUDIO_LOG} 1>&2 || exit 0) + elseif (WIN32) + set(COMMANDS COMMAND type "${PVS_STUDIO_LOG}" 1>&2) + else () + set(COMMANDS COMMAND cat "${PVS_STUDIO_LOG}" 1>&2) + endif() + else () + set(COMMANDS "") + endif () + + add_custom_target("${PVS_STUDIO_TARGET}" ${ALL} ${COMMANDS} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" DEPENDS ${PVS_STUDIO_DEPENDS} "${PVS_STUDIO_LOG}") + + # A workaround to add implicit dependencies of source files from include directories + set_target_properties("${PVS_STUDIO_TARGET}" PROPERTIES INCLUDE_DIRECTORIES "${inc_path}") +endfunction () diff --git a/examples/example1.c b/examples/example1.c new file mode 100644 index 0000000..80c6a10 --- /dev/null +++ b/examples/example1.c @@ -0,0 +1,7 @@ +#include "expr.h" + +int main(void) { + int near, error; + printf("result: %f\n", expr_calc("2 + 3", &near, &error)); + return 0; +} diff --git a/examples/example2.c b/examples/example2.c new file mode 100644 index 0000000..4085ad7 --- /dev/null +++ b/examples/example2.c @@ -0,0 +1,12 @@ +#include "expr.h" + +int main(void) { + int near, error; + printf("macro definition: %f\n", + expr_calc("$(mysum, $1 + $2), mysum(2, 3)", &near, &error)); + printf("variable assignment: %f\n", + expr_calc("a=2, b=3, a+b", &near, &error)); + printf("all together: %f\n", + expr_calc("$(mysum, $1 + $2), a=2, b=3, mysum(a, b)", &near, &error)); + return 0; +} diff --git a/examples/example3.c b/examples/example3.c new file mode 100644 index 0000000..af5b8f9 --- /dev/null +++ b/examples/example3.c @@ -0,0 +1,28 @@ +#include "expr.h" + +// Custom function that returns the sum of its two arguments +static expr_num_t sum(struct expr_func *f, vec_expr_t *args, void *c) { + expr_num_t a = expr_eval(&vec_nth(args, 0)); + expr_num_t b = expr_eval(&vec_nth(args, 1)); + (void) f, (void) c; + return a + b; +} + +static struct expr_func user_funcs[] = { + {"sum", sum, NULL, 0}, + {NULL, NULL, NULL, 0}, +}; + +int main(void) { + int near, error; + const char *s = "x = 5, sum(2, x)"; + struct expr_var_list vars = {0}; + struct expr *e = expr_create2(s, strlen(s), &vars, user_funcs, &near, &error); + if (e == NULL) { + printf("Syntax error"); + return 1; + } + printf("result: %f\n", expr_eval(e)); + expr_destroy(e, &vars); + return 0; +} diff --git a/expr.h b/expr.h deleted file mode 100644 index 7484c5c..0000000 --- a/expr.h +++ /dev/null @@ -1,911 +0,0 @@ -#ifndef EXPR_H -#define EXPR_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include /* for isspace */ -#include -#include /* for pow */ -#include -#include -#include - -/* - * Simple expandable vector implementation - */ -static int vec_expand(char **buf, int *length, int *cap, int memsz) { - if (*length + 1 > *cap) { - void *ptr; - int n = (*cap == 0) ? 1 : *cap << 1; - ptr = realloc(*buf, n * memsz); - if (ptr == NULL) { - return -1; /* allocation failed */ - } - *buf = (char *)ptr; - *cap = n; - } - return 0; -} -#define vec(T) \ - struct { \ - T *buf; \ - int len; \ - int cap; \ - } -#define vec_init() \ - { NULL, 0, 0 } -#define vec_len(v) ((v)->len) -#define vec_unpack(v) \ - (char **)&(v)->buf, &(v)->len, &(v)->cap, sizeof(*(v)->buf) -#define vec_push(v, val) \ - vec_expand(vec_unpack(v)) ? -1 : ((v)->buf[(v)->len++] = (val), 0) -#define vec_nth(v, i) (v)->buf[i] -#define vec_peek(v) (v)->buf[(v)->len - 1] -#define vec_pop(v) (v)->buf[--(v)->len] -#define vec_free(v) (free((v)->buf), (v)->buf = NULL, (v)->len = (v)->cap = 0) -#define vec_foreach(v, var, iter) \ - if ((v)->len > 0) \ - for ((iter) = 0; (iter) < (v)->len && (((var) = (v)->buf[(iter)]), 1); \ - ++(iter)) - -/* - * Expression data types - */ -struct expr; -struct expr_func; - -enum expr_type { - OP_UNKNOWN, - OP_UNARY_MINUS, - OP_UNARY_LOGICAL_NOT, - OP_UNARY_BITWISE_NOT, - - OP_POWER, - OP_DIVIDE, - OP_MULTIPLY, - OP_REMAINDER, - - OP_PLUS, - OP_MINUS, - - OP_SHL, - OP_SHR, - - OP_LT, - OP_LE, - OP_GT, - OP_GE, - OP_EQ, - OP_NE, - - OP_BITWISE_AND, - OP_BITWISE_OR, - OP_BITWISE_XOR, - - OP_LOGICAL_AND, - OP_LOGICAL_OR, - - OP_ASSIGN, - OP_COMMA, - - OP_CONST, - OP_VAR, - OP_FUNC, -}; - -static int prec[] = {0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5, - 5, 5, 5, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0}; - -typedef vec(struct expr) vec_expr_t; -typedef void (*exprfn_cleanup_t)(struct expr_func *f, void *context); -typedef float (*exprfn_t)(struct expr_func *f, vec_expr_t *args, void *context); - -struct expr { - enum expr_type type; - union { - struct { - float value; - } num; - struct { - float *value; - } var; - struct { - vec_expr_t args; - } op; - struct { - struct expr_func *f; - vec_expr_t args; - void *context; - } func; - } param; -}; - -#define expr_init() \ - { .type = (enum expr_type)0 } - -struct expr_string { - const char *s; - int n; -}; -struct expr_arg { - int oslen; - int eslen; - vec_expr_t args; -}; - -typedef vec(struct expr_string) vec_str_t; -typedef vec(struct expr_arg) vec_arg_t; - -static int expr_is_unary(enum expr_type op) { - return op == OP_UNARY_MINUS || op == OP_UNARY_LOGICAL_NOT || - op == OP_UNARY_BITWISE_NOT; -} - -static int expr_is_binary(enum expr_type op) { - return !expr_is_unary(op) && op != OP_CONST && op != OP_VAR && - op != OP_FUNC && op != OP_UNKNOWN; -} - -static int expr_prec(enum expr_type a, enum expr_type b) { - int left = - expr_is_binary(a) && a != OP_ASSIGN && a != OP_POWER && a != OP_COMMA; - return (left && prec[a] >= prec[b]) || (prec[a] > prec[b]); -} - -#define isfirstvarchr(c) \ - (((unsigned char)c >= '@' && c != '^' && c != '|') || c == '$') -#define isvarchr(c) \ - (((unsigned char)c >= '@' && c != '^' && c != '|') || c == '$' || \ - c == '#' || (c >= '0' && c <= '9')) - -static struct { - const char *s; - const enum expr_type op; -} OPS[] = { - {"-u", OP_UNARY_MINUS}, - {"!u", OP_UNARY_LOGICAL_NOT}, - {"^u", OP_UNARY_BITWISE_NOT}, - {"**", OP_POWER}, - {"*", OP_MULTIPLY}, - {"/", OP_DIVIDE}, - {"%", OP_REMAINDER}, - {"+", OP_PLUS}, - {"-", OP_MINUS}, - {"<<", OP_SHL}, - {">>", OP_SHR}, - {"<", OP_LT}, - {"<=", OP_LE}, - {">", OP_GT}, - {">=", OP_GE}, - {"==", OP_EQ}, - {"!=", OP_NE}, - {"&", OP_BITWISE_AND}, - {"|", OP_BITWISE_OR}, - {"^", OP_BITWISE_XOR}, - {"&&", OP_LOGICAL_AND}, - {"||", OP_LOGICAL_OR}, - {"=", OP_ASSIGN}, - {",", OP_COMMA}, - - /* These are used by lexer and must be ignored by parser, so we put - them at the end */ - {"-", OP_UNARY_MINUS}, - {"!", OP_UNARY_LOGICAL_NOT}, - {"^", OP_UNARY_BITWISE_NOT}, -}; - -static enum expr_type expr_op(const char *s, size_t len, int unary) { - for (unsigned int i = 0; i < sizeof(OPS) / sizeof(OPS[0]); i++) { - if (strlen(OPS[i].s) == len && strncmp(OPS[i].s, s, len) == 0 && - (unary == -1 || expr_is_unary(OPS[i].op) == unary)) { - return OPS[i].op; - } - } - return OP_UNKNOWN; -} - -static float expr_parse_number(const char *s, size_t len) { - float num = 0; - unsigned int frac = 0; - unsigned int digits = 0; - for (unsigned int i = 0; i < len; i++) { - if (s[i] == '.' && frac == 0) { - frac++; - continue; - } - if (isdigit(s[i])) { - digits++; - if (frac > 0) { - frac++; - } - num = num * 10 + (s[i] - '0'); - } else { - return NAN; - } - } - while (frac > 1) { - num = num / 10; - frac--; - } - return (digits > 0 ? num : NAN); -} - -/* - * Functions - */ -struct expr_func { - const char *name; - exprfn_t f; - exprfn_cleanup_t cleanup; - size_t ctxsz; -}; - -static struct expr_func *expr_func(struct expr_func *funcs, const char *s, - size_t len) { - for (struct expr_func *f = funcs; f->name; f++) { - if (strlen(f->name) == len && strncmp(f->name, s, len) == 0) { - return f; - } - } - return NULL; -} - -/* - * Variables - */ -struct expr_var { - float value; - struct expr_var *next; - char name[]; -}; - -struct expr_var_list { - struct expr_var *head; -}; - -static struct expr_var *expr_var(struct expr_var_list *vars, const char *s, - size_t len) { - struct expr_var *v = NULL; - if (len == 0 || !isfirstvarchr(*s)) { - return NULL; - } - for (v = vars->head; v; v = v->next) { - if (strlen(v->name) == len && strncmp(v->name, s, len) == 0) { - return v; - } - } - v = (struct expr_var *)calloc(1, sizeof(struct expr_var) + len + 1); - if (v == NULL) { - return NULL; /* allocation failed */ - } - v->next = vars->head; - v->value = 0; - strncpy(v->name, s, len); - v->name[len] = '\0'; - vars->head = v; - return v; -} - -static int to_int(float x) { - if (isnan(x)) { - return 0; - } else if (isinf(x) != 0) { - return INT_MAX * isinf(x); - } else { - return (int)x; - } -} - -static float expr_eval(struct expr *e) { - float n; - switch (e->type) { - case OP_UNARY_MINUS: - return -(expr_eval(&e->param.op.args.buf[0])); - case OP_UNARY_LOGICAL_NOT: - return !(expr_eval(&e->param.op.args.buf[0])); - case OP_UNARY_BITWISE_NOT: - return ~(to_int(expr_eval(&e->param.op.args.buf[0]))); - case OP_POWER: - return powf(expr_eval(&e->param.op.args.buf[0]), - expr_eval(&e->param.op.args.buf[1])); - case OP_MULTIPLY: - return expr_eval(&e->param.op.args.buf[0]) * - expr_eval(&e->param.op.args.buf[1]); - case OP_DIVIDE: - return expr_eval(&e->param.op.args.buf[0]) / - expr_eval(&e->param.op.args.buf[1]); - case OP_REMAINDER: - return fmodf(expr_eval(&e->param.op.args.buf[0]), - expr_eval(&e->param.op.args.buf[1])); - case OP_PLUS: - return expr_eval(&e->param.op.args.buf[0]) + - expr_eval(&e->param.op.args.buf[1]); - case OP_MINUS: - return expr_eval(&e->param.op.args.buf[0]) - - expr_eval(&e->param.op.args.buf[1]); - case OP_SHL: - return to_int(expr_eval(&e->param.op.args.buf[0])) - << to_int(expr_eval(&e->param.op.args.buf[1])); - case OP_SHR: - return to_int(expr_eval(&e->param.op.args.buf[0])) >> - to_int(expr_eval(&e->param.op.args.buf[1])); - case OP_LT: - return expr_eval(&e->param.op.args.buf[0]) < - expr_eval(&e->param.op.args.buf[1]); - case OP_LE: - return expr_eval(&e->param.op.args.buf[0]) <= - expr_eval(&e->param.op.args.buf[1]); - case OP_GT: - return expr_eval(&e->param.op.args.buf[0]) > - expr_eval(&e->param.op.args.buf[1]); - case OP_GE: - return expr_eval(&e->param.op.args.buf[0]) >= - expr_eval(&e->param.op.args.buf[1]); - case OP_EQ: - return expr_eval(&e->param.op.args.buf[0]) == - expr_eval(&e->param.op.args.buf[1]); - case OP_NE: - return expr_eval(&e->param.op.args.buf[0]) != - expr_eval(&e->param.op.args.buf[1]); - case OP_BITWISE_AND: - return to_int(expr_eval(&e->param.op.args.buf[0])) & - to_int(expr_eval(&e->param.op.args.buf[1])); - case OP_BITWISE_OR: - return to_int(expr_eval(&e->param.op.args.buf[0])) | - to_int(expr_eval(&e->param.op.args.buf[1])); - case OP_BITWISE_XOR: - return to_int(expr_eval(&e->param.op.args.buf[0])) ^ - to_int(expr_eval(&e->param.op.args.buf[1])); - case OP_LOGICAL_AND: - n = expr_eval(&e->param.op.args.buf[0]); - if (n != 0) { - n = expr_eval(&e->param.op.args.buf[1]); - if (n != 0) { - return n; - } - } - return 0; - case OP_LOGICAL_OR: - n = expr_eval(&e->param.op.args.buf[0]); - if (n != 0 && !isnan(n)) { - return n; - } else { - n = expr_eval(&e->param.op.args.buf[1]); - if (n != 0) { - return n; - } - } - return 0; - case OP_ASSIGN: - n = expr_eval(&e->param.op.args.buf[1]); - if (vec_nth(&e->param.op.args, 0).type == OP_VAR) { - *e->param.op.args.buf[0].param.var.value = n; - } - return n; - case OP_COMMA: - expr_eval(&e->param.op.args.buf[0]); - return expr_eval(&e->param.op.args.buf[1]); - case OP_CONST: - return e->param.num.value; - case OP_VAR: - return *e->param.var.value; - case OP_FUNC: - return e->param.func.f->f(e->param.func.f, &e->param.func.args, - e->param.func.context); - default: - return NAN; - } -} - -#define EXPR_TOP (1 << 0) -#define EXPR_TOPEN (1 << 1) -#define EXPR_TCLOSE (1 << 2) -#define EXPR_TNUMBER (1 << 3) -#define EXPR_TWORD (1 << 4) -#define EXPR_TDEFAULT (EXPR_TOPEN | EXPR_TNUMBER | EXPR_TWORD) - -#define EXPR_UNARY (1 << 5) -#define EXPR_COMMA (1 << 6) - -static int expr_next_token(const char *s, size_t len, int *flags) { - unsigned int i = 0; - if (len == 0) { - return 0; - } - char c = s[0]; - if (c == '#') { - for (; i < len && s[i] != '\n'; i++) - ; - return i; - } else if (c == '\n') { - for (; i < len && isspace(s[i]); i++) - ; - if (*flags & EXPR_TOP) { - if (i == len || s[i] == ')') { - *flags = *flags & (~EXPR_COMMA); - } else { - *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_COMMA; - } - } - return i; - } else if (isspace(c)) { - while (i < len && isspace(s[i]) && s[i] != '\n') { - i++; - } - return i; - } else if (isdigit(c)) { - if ((*flags & EXPR_TNUMBER) == 0) { - return -1; // unexpected number - } - *flags = EXPR_TOP | EXPR_TCLOSE; - while ((c == '.' || isdigit(c)) && i < len) { - i++; - c = s[i]; - } - return i; - } else if (isfirstvarchr(c)) { - if ((*flags & EXPR_TWORD) == 0) { - return -2; // unexpected word - } - *flags = EXPR_TOP | EXPR_TOPEN | EXPR_TCLOSE; - while ((isvarchr(c)) && i < len) { - i++; - c = s[i]; - } - return i; - } else if (c == '(' || c == ')') { - if (c == '(' && (*flags & EXPR_TOPEN) != 0) { - *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_TCLOSE; - } else if (c == ')' && (*flags & EXPR_TCLOSE) != 0) { - *flags = EXPR_TOP | EXPR_TCLOSE; - } else { - return -3; // unexpected parenthesis - } - return 1; - } else { - if ((*flags & EXPR_TOP) == 0) { - if (expr_op(&c, 1, 1) == OP_UNKNOWN) { - return -4; // missing expected operand - } - *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_UNARY; - return 1; - } else { - int found = 0; - while (!isvarchr(c) && !isspace(c) && c != '(' && c != ')' && i < len) { - if (expr_op(s, i + 1, 0) != OP_UNKNOWN) { - found = 1; - } else if (found) { - break; - } - i++; - c = s[i]; - } - if (!found) { - return -5; // unknown operator - } - *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN; - return i; - } - } -} - -#define EXPR_PAREN_ALLOWED 0 -#define EXPR_PAREN_EXPECTED 1 -#define EXPR_PAREN_FORBIDDEN 2 - -static int expr_bind(const char *s, size_t len, vec_expr_t *es) { - enum expr_type op = expr_op(s, len, -1); - if (op == OP_UNKNOWN) { - return -1; - } - - if (expr_is_unary(op)) { - if (vec_len(es) < 1) { - return -1; - } - struct expr arg = vec_pop(es); - struct expr unary = expr_init(); - unary.type = op; - vec_push(&unary.param.op.args, arg); - vec_push(es, unary); - } else { - if (vec_len(es) < 2) { - return -1; - } - struct expr b = vec_pop(es); - struct expr a = vec_pop(es); - struct expr binary = expr_init(); - binary.type = op; - if (op == OP_ASSIGN && a.type != OP_VAR) { - return -1; /* Bad assignment */ - } - vec_push(&binary.param.op.args, a); - vec_push(&binary.param.op.args, b); - vec_push(es, binary); - } - return 0; -} - -static struct expr expr_const(float value) { - struct expr e = expr_init(); - e.type = OP_CONST; - e.param.num.value = value; - return e; -} - -static struct expr expr_varref(struct expr_var *v) { - struct expr e = expr_init(); - e.type = OP_VAR; - e.param.var.value = &v->value; - return e; -} - -static struct expr expr_binary(enum expr_type type, struct expr a, - struct expr b) { - struct expr e = expr_init(); - e.type = type; - vec_push(&e.param.op.args, a); - vec_push(&e.param.op.args, b); - return e; -} - -static inline void expr_copy(struct expr *dst, struct expr *src) { - int i; - struct expr arg; - dst->type = src->type; - if (src->type == OP_FUNC) { - dst->param.func.f = src->param.func.f; - vec_foreach(&src->param.func.args, arg, i) { - struct expr tmp = expr_init(); - expr_copy(&tmp, &arg); - vec_push(&dst->param.func.args, tmp); - } - if (src->param.func.f->ctxsz > 0) { - dst->param.func.context = calloc(1, src->param.func.f->ctxsz); - } - } else if (src->type == OP_CONST) { - dst->param.num.value = src->param.num.value; - } else if (src->type == OP_VAR) { - dst->param.var.value = src->param.var.value; - } else { - vec_foreach(&src->param.op.args, arg, i) { - struct expr tmp = expr_init(); - expr_copy(&tmp, &arg); - vec_push(&dst->param.op.args, tmp); - } - } -} - -static void expr_destroy_args(struct expr *e); - -static struct expr *expr_create(const char *s, size_t len, - struct expr_var_list *vars, - struct expr_func *funcs) { - float num; - struct expr_var *v; - const char *id = NULL; - size_t idn = 0; - - struct expr *result = NULL; - - vec_expr_t es = vec_init(); - vec_str_t os = vec_init(); - vec_arg_t as = vec_init(); - - struct macro { - char *name; - vec_expr_t body; - }; - vec(struct macro) macros = vec_init(); - - int flags = EXPR_TDEFAULT; - int paren = EXPR_PAREN_ALLOWED; - for (;;) { - int n = expr_next_token(s, len, &flags); - if (n == 0) { - break; - } else if (n < 0) { - goto cleanup; - } - const char *tok = s; - s = s + n; - len = len - n; - if (*tok == '#') { - continue; - } - if (flags & EXPR_UNARY) { - if (n == 1) { - switch (*tok) { - case '-': - tok = "-u"; - break; - case '^': - tok = "^u"; - break; - case '!': - tok = "!u"; - break; - default: - goto cleanup; - } - n = 2; - } - } - if (*tok == '\n' && (flags & EXPR_COMMA)) { - flags = flags & (~EXPR_COMMA); - n = 1; - tok = ","; - } - if (isspace(*tok)) { - continue; - } - int paren_next = EXPR_PAREN_ALLOWED; - - if (idn > 0) { - if (n == 1 && *tok == '(') { - int i; - int has_macro = 0; - struct macro m; - vec_foreach(¯os, m, i) { - if (strlen(m.name) == idn && strncmp(m.name, id, idn) == 0) { - has_macro = 1; - break; - } - } - if ((idn == 1 && id[0] == '$') || has_macro || - expr_func(funcs, id, idn) != NULL) { - struct expr_string str = {id, (int)idn}; - vec_push(&os, str); - paren = EXPR_PAREN_EXPECTED; - } else { - goto cleanup; /* invalid function name */ - } - } else if ((v = expr_var(vars, id, idn)) != NULL) { - vec_push(&es, expr_varref(v)); - paren = EXPR_PAREN_FORBIDDEN; - } - id = NULL; - idn = 0; - } - - if (n == 1 && *tok == '(') { - if (paren == EXPR_PAREN_EXPECTED) { - struct expr_string str = {"{", 1}; - vec_push(&os, str); - struct expr_arg arg = {vec_len(&os), vec_len(&es), vec_init()}; - vec_push(&as, arg); - } else if (paren == EXPR_PAREN_ALLOWED) { - struct expr_string str = {"(", 1}; - vec_push(&os, str); - } else { - goto cleanup; // Bad call - } - } else if (paren == EXPR_PAREN_EXPECTED) { - goto cleanup; // Bad call - } else if (n == 1 && *tok == ')') { - int minlen = (vec_len(&as) > 0 ? vec_peek(&as).oslen : 0); - while (vec_len(&os) > minlen && *vec_peek(&os).s != '(' && - *vec_peek(&os).s != '{') { - struct expr_string str = vec_pop(&os); - if (expr_bind(str.s, str.n, &es) == -1) { - goto cleanup; - } - } - if (vec_len(&os) == 0) { - goto cleanup; // Bad parens - } - struct expr_string str = vec_pop(&os); - if (str.n == 1 && *str.s == '{') { - str = vec_pop(&os); - struct expr_arg arg = vec_pop(&as); - if (vec_len(&es) > arg.eslen) { - vec_push(&arg.args, vec_pop(&es)); - } - if (str.n == 1 && str.s[0] == '$') { - if (vec_len(&arg.args) < 1) { - vec_free(&arg.args); - goto cleanup; /* too few arguments for $() function */ - } - struct expr *u = &vec_nth(&arg.args, 0); - if (u->type != OP_VAR) { - vec_free(&arg.args); - goto cleanup; /* first argument is not a variable */ - } - for (struct expr_var *v = vars->head; v; v = v->next) { - if (&v->value == u->param.var.value) { - struct macro m = {v->name, arg.args}; - vec_push(¯os, m); - break; - } - } - vec_push(&es, expr_const(0)); - } else { - int i = 0; - int found = -1; - struct macro m; - vec_foreach(¯os, m, i) { - if (strlen(m.name) == (size_t)str.n && - strncmp(m.name, str.s, str.n) == 0) { - found = i; - } - } - if (found != -1) { - m = vec_nth(¯os, found); - struct expr root = expr_const(0); - struct expr *p = &root; - /* Assign macro parameters */ - for (int j = 0; j < vec_len(&arg.args); j++) { - char varname[4]; - snprintf(varname, sizeof(varname) - 1, "$%d", (j + 1)); - struct expr_var *v = expr_var(vars, varname, strlen(varname)); - struct expr ev = expr_varref(v); - struct expr assign = - expr_binary(OP_ASSIGN, ev, vec_nth(&arg.args, j)); - *p = expr_binary(OP_COMMA, assign, expr_const(0)); - p = &vec_nth(&p->param.op.args, 1); - } - /* Expand macro body */ - for (int j = 1; j < vec_len(&m.body); j++) { - if (j < vec_len(&m.body) - 1) { - *p = expr_binary(OP_COMMA, expr_const(0), expr_const(0)); - expr_copy(&vec_nth(&p->param.op.args, 0), &vec_nth(&m.body, j)); - } else { - expr_copy(p, &vec_nth(&m.body, j)); - } - p = &vec_nth(&p->param.op.args, 1); - } - vec_push(&es, root); - vec_free(&arg.args); - } else { - struct expr_func *f = expr_func(funcs, str.s, str.n); - struct expr bound_func = expr_init(); - bound_func.type = OP_FUNC; - bound_func.param.func.f = f; - bound_func.param.func.args = arg.args; - if (f->ctxsz > 0) { - void *p = calloc(1, f->ctxsz); - if (p == NULL) { - goto cleanup; /* allocation failed */ - } - bound_func.param.func.context = p; - } - vec_push(&es, bound_func); - } - } - } - paren_next = EXPR_PAREN_FORBIDDEN; - } else if (!isnan(num = expr_parse_number(tok, n))) { - vec_push(&es, expr_const(num)); - paren_next = EXPR_PAREN_FORBIDDEN; - } else if (expr_op(tok, n, -1) != OP_UNKNOWN) { - enum expr_type op = expr_op(tok, n, -1); - struct expr_string o2 = {NULL, 0}; - if (vec_len(&os) > 0) { - o2 = vec_peek(&os); - } - for (;;) { - if (n == 1 && *tok == ',' && vec_len(&os) > 0) { - struct expr_string str = vec_peek(&os); - if (str.n == 1 && *str.s == '{') { - struct expr e = vec_pop(&es); - vec_push(&vec_peek(&as).args, e); - break; - } - } - enum expr_type type2 = expr_op(o2.s, o2.n, -1); - if (!(type2 != OP_UNKNOWN && expr_prec(op, type2))) { - struct expr_string str = {tok, n}; - vec_push(&os, str); - break; - } - - if (expr_bind(o2.s, o2.n, &es) == -1) { - goto cleanup; - } - (void)vec_pop(&os); - if (vec_len(&os) > 0) { - o2 = vec_peek(&os); - } else { - o2.n = 0; - } - } - } else { - if (n > 0 && !isdigit(*tok)) { - /* Valid identifier, a variable or a function */ - id = tok; - idn = n; - } else { - goto cleanup; // Bad variable name, e.g. '2.3.4' or '4ever' - } - } - paren = paren_next; - } - - if (idn > 0) { - vec_push(&es, expr_varref(expr_var(vars, id, idn))); - } - - while (vec_len(&os) > 0) { - struct expr_string rest = vec_pop(&os); - if (rest.n == 1 && (*rest.s == '(' || *rest.s == ')')) { - goto cleanup; // Bad paren - } - if (expr_bind(rest.s, rest.n, &es) == -1) { - goto cleanup; - } - } - - result = (struct expr *)calloc(1, sizeof(struct expr)); - if (result != NULL) { - if (vec_len(&es) == 0) { - result->type = OP_CONST; - } else { - *result = vec_pop(&es); - } - } - - int i, j; - struct macro m; - struct expr e; - struct expr_arg a; -cleanup: - vec_foreach(¯os, m, i) { - struct expr e; - vec_foreach(&m.body, e, j) { expr_destroy_args(&e); } - vec_free(&m.body); - } - vec_free(¯os); - - vec_foreach(&es, e, i) { expr_destroy_args(&e); } - vec_free(&es); - - vec_foreach(&as, a, i) { - vec_foreach(&a.args, e, j) { expr_destroy_args(&e); } - vec_free(&a.args); - } - vec_free(&as); - - /*vec_foreach(&os, o, i) {vec_free(&m.body);}*/ - vec_free(&os); - return result; -} - -static void expr_destroy_args(struct expr *e) { - int i; - struct expr arg; - if (e->type == OP_FUNC) { - vec_foreach(&e->param.func.args, arg, i) { expr_destroy_args(&arg); } - vec_free(&e->param.func.args); - if (e->param.func.context != NULL) { - if (e->param.func.f->cleanup != NULL) { - e->param.func.f->cleanup(e->param.func.f, e->param.func.context); - } - free(e->param.func.context); - } - } else if (e->type != OP_CONST && e->type != OP_VAR) { - vec_foreach(&e->param.op.args, arg, i) { expr_destroy_args(&arg); } - vec_free(&e->param.op.args); - } -} - -static void expr_destroy(struct expr *e, struct expr_var_list *vars) { - if (e != NULL) { - expr_destroy_args(e); - free(e); - } - if (vars != NULL) { - for (struct expr_var *v = vars->head; v;) { - struct expr_var *next = v->next; - free(v); - v = next; - } - } -} - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* EXPR_H */ diff --git a/expr_debug.h b/expr_debug.h deleted file mode 100644 index f4d013a..0000000 --- a/expr_debug.h +++ /dev/null @@ -1,189 +0,0 @@ -#ifndef EXPR_DEBUG_H -#define EXPR_DEBUG_H - -#include - -static void expr_print(struct expr *e) { - switch (e->type) { - case OP_UNKNOWN: - break; - case OP_UNARY_MINUS: - printf("-("); - expr_print(&e->param.op.args.buf[0]); - printf(")"); - break; - case OP_UNARY_LOGICAL_NOT: - printf("!("); - expr_print(&e->param.op.args.buf[0]); - printf(")"); - break; - case OP_UNARY_BITWISE_NOT: - printf("^("); - expr_print(&e->param.op.args.buf[0]); - printf(")"); - break; - case OP_POWER: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("**"); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_MULTIPLY: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("*"); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_DIVIDE: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("/"); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_REMAINDER: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("%%"); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_PLUS: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("+"); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_MINUS: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("-"); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_SHL: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("<<"); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_SHR: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf(">>"); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_LT: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("<"); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_LE: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("<="); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_GT: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf(">"); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_GE: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf(">="); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_EQ: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("=="); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_NE: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("!="); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_BITWISE_AND: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("&"); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_BITWISE_OR: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("|"); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_BITWISE_XOR: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("^"); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_LOGICAL_AND: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("&&"); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_LOGICAL_OR: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf("||"); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_ASSIGN: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf(":="); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_COMMA: - printf("("); - expr_print(&e->param.op.args.buf[0]); - printf(","); - expr_print(&e->param.op.args.buf[1]); - printf(")"); - break; - case OP_CONST: - printf("%.2f", e->param.num.value); - break; - case OP_VAR: - printf("[%.2f@%p]", *e->param.var.value, (void *)e->param.var.value); - break; - case OP_FUNC: - printf("func(todo)"); - break; - } -} - -static void expr_println(struct expr *e) { - expr_print(e); - printf("\n"); -} - -#endif /* EXPR_DEBUG_H */ diff --git a/expr_jit.dasc b/expr_jit.dasc index 3099569..8bb130b 100644 --- a/expr_jit.dasc +++ b/expr_jit.dasc @@ -106,7 +106,7 @@ static int expr_compile_dynasm(struct expr *e, dasm_State **Dst) { |.endmacro |.macro MOVIMM, reg, value - | push value + | push value | movss reg, dword [rsp] | add rsp, aword*1 |.endmacro @@ -162,7 +162,7 @@ static int expr_compile_dynasm(struct expr *e, dasm_State **Dst) { expr_compile_dynasm(&e->param.op.args.buf[0], Dst); | PUSH_XMM0 expr_compile_dynasm(&e->param.op.args.buf[1], Dst); - | POP_XMM0 + | POP_XMM0 | mulss xmm0, xmm1 break; case OP_DIVIDE: diff --git a/expr_test.c b/expr_test.c old mode 100644 new mode 100755 index b82e7d5..3406905 --- a/expr_test.c +++ b/expr_test.c @@ -7,17 +7,43 @@ #include #include +#ifdef _MSC_VER +#include +#else /* _MSC_VER */ #include +#endif /* _MSC_VER */ + +#ifdef _MSC_VER + +int gettimeofday(struct timeval *tv, struct timezone *tz) { + static LONGLONG birthunixhnsec = 116444736000000000; /* in units of 100 ns */ + FILETIME systemtime; + GetSystemTimeAsFileTime(&systemtime); + ULARGE_INTEGER utime; + utime.LowPart = systemtime.dwLowDateTime; + utime.HighPart = systemtime.dwHighDateTime; + ULARGE_INTEGER birthunix; + birthunix.LowPart = (DWORD) birthunixhnsec; + birthunix.HighPart = birthunixhnsec >> 32; + LONGLONG usecs; + usecs = (LONGLONG)((utime.QuadPart - birthunix.QuadPart) / 10); + tv->tv_sec = (long) (usecs / 1000000); + tv->tv_usec = (long) (usecs % 1000000); + return 0; +} + +#endif /* _MSC_VER */ int status = 0; /* * VECTOR TESTS */ + typedef vec(int) test_vec_int_t; typedef vec(char *) test_vec_str_t; -static void test_vector() { +static void test_vector(void) { test_vec_int_t ints = vec_init(); test_vec_str_t strings = vec_init(); @@ -32,16 +58,21 @@ static void test_vector() { vec_push(&strings, "world"); vec_push(&strings, "foo"); assert(vec_len(&strings) == 3); - int i; - char *el; - vec_foreach(&strings, el, i) { printf("%s %d\n", el, i); } + { + int i; + char *el; + vec_foreach(&strings, el, i) { + printf("%s %d\n", el, i); + } + } vec_free(&strings); } /* * VARIABLES VECTOR TEST */ -static void test_vars() { + +static void test_vars(void) { struct expr_var_list vars = {0}; struct expr_var *a = expr_var(&vars, "a", 1); @@ -50,17 +81,20 @@ static void test_vars() { expr_var(&vars, "b", 1); expr_var(&vars, "ab", 2); - struct expr_var *again = expr_var(&vars, "a", 1); - assert(again == a); - assert(again->value == 4); + { + struct expr_var *again = expr_var(&vars, "a", 1); + assert(again == a); + assert(again->value == 4); + } expr_destroy(NULL, &vars); } /* * LEXER TESTS */ + static int assert_tokens(char *s, char **expected) { - int len = strlen(s); + size_t len = strlen(s); int flags = EXPR_TDEFAULT; char *test = s; for (;;) { @@ -89,16 +123,17 @@ static int assert_tokens(char *s, char **expected) { } } -static void test_tokizer() { +static void test_tokenizer(void) { + unsigned int i; char **TESTS[] = { - (char *[]){"", NULL}, - (char *[]){"1", "1", NULL}, - (char *[]){"1+11", "1", "+", "11", NULL}, - (char *[]){"1*11", "1", "*", "11", NULL}, - (char *[]){"1**11", "1", "**", "11", NULL}, - (char *[]){"1**-11", "1", "**", "-", "11", NULL}, + (char *[]){"", NULL}, + (char *[]){"1", "1", NULL}, + (char *[]){"1+11", "1", "+", "11", NULL}, + (char *[]){"1*11", "1", "*", "11", NULL}, + (char *[]){"1**11", "1", "**", "11", NULL}, + (char *[]){"1**-11", "1", "**", "-", "11", NULL}, }; - for (unsigned int i = 0; i < sizeof(TESTS) / sizeof(TESTS[0]); i++) { + for (i = 0; i < sizeof(TESTS) / sizeof(TESTS[0]); i++) { assert_tokens(TESTS[i][0], TESTS[i] + 1); } } @@ -109,56 +144,59 @@ static void test_tokizer() { struct nop_context { void *p; }; + +static int fake_context = 123; + static void user_func_nop_cleanup(struct expr_func *f, void *c) { - (void)f; - struct nop_context *nop = (struct nop_context *)c; - free(nop->p); + (void) f; + if (c != &fake_context) + printf("FAIL: user_func_nop_cleanup()\n"); } -static float user_func_nop(struct expr_func *f, vec_expr_t *args, void *c) { - (void)args; - struct nop_context *nop = (struct nop_context *)c; - if (f->ctxsz == 0) { - free(nop->p); - return 0; - } - if (nop->p == NULL) { - nop->p = malloc(10000); - } - return 0; + +static expr_num_t user_func_nop(struct expr_func *f, vec_expr_t *args, + void *c) { + (void) f; + (void) args; + return (c == &fake_context) ? 1 : 0; } -static float user_func_add(struct expr_func *f, vec_expr_t *args, void *c) { - (void)f, (void)c; - float a = expr_eval(&vec_nth(args, 0)); - float b = expr_eval(&vec_nth(args, 1)); +static expr_num_t user_func_add(struct expr_func *f, vec_expr_t *args, + void *c) { + expr_num_t a = expr_eval(&vec_nth(args, 0)); + expr_num_t b = expr_eval(&vec_nth(args, 1)); + (void) f, (void) c; return a + b; } -static float user_func_next(struct expr_func *f, vec_expr_t *args, void *c) { - (void)f, (void)c; - float a = expr_eval(&vec_nth(args, 0)); +static expr_num_t user_func_next(struct expr_func *f, vec_expr_t *args, + void *c) { + expr_num_t a = expr_eval(&vec_nth(args, 0)); + (void) f, (void) c; return a + 1; } -static float user_func_print(struct expr_func *f, vec_expr_t *args, void *c) { - (void)f, (void)c; +static expr_num_t user_func_print(struct expr_func *f, vec_expr_t *args, + void *c) { int i; struct expr e; + (void) f, (void) c; fprintf(stderr, ">> "); - vec_foreach(args, e, i) { fprintf(stderr, "%f ", expr_eval(&e)); } + vec_foreach(args, e, i) { + fprintf(stderr, "%f ", expr_eval(&e)); + } fprintf(stderr, "\n"); return 0; } static struct expr_func user_funcs[] = { - {"nop", user_func_nop, user_func_nop_cleanup, sizeof(struct nop_context)}, - {"add", user_func_add, NULL, 0}, - {"next", user_func_next, NULL, 0}, - {"print", user_func_print, NULL, 0}, - {NULL, NULL, NULL, 0}, + {"nop", user_func_nop, user_func_nop_cleanup, &fake_context}, + {"add", user_func_add, NULL, 0}, + {"next", user_func_next, NULL, 0}, + {"print", user_func_print, NULL, 0}, + {NULL, NULL, NULL, 0}, }; -static void test_expr(char *s, float expected) { +static void test_expr(char *s, expr_num_t expected) { struct expr_var_list vars = {0}; struct expr *e = expr_create(s, strlen(s), &vars, user_funcs); if (e == NULL) { @@ -166,51 +204,87 @@ static void test_expr(char *s, float expected) { status = 1; return; } - float result = expr_eval(e); + { + expr_num_t result = expr_eval(e); + + size_t l; + char *it; + char *p = (char *) malloc(strlen(s) + 1); + if (p == NULL) { + return; + } + l = strlen(s); + memcpy(p, s, l + 1); + p[l] = '\0'; - char *p = (char *)malloc(strlen(s) + 1); - strncpy(p, s, strlen(s) + 1); - for (char *it = p; *it; it++) { - if (*it == '\n') { - *it = '\\'; + for (it = p; *it; it++) { + if (*it == '\n') { + *it = '\\'; + } } - } - if ((isnan(result) && !isnan(expected)) || - fabs(result - expected) > 0.00001f) { - printf("FAIL: %s: %f != %f\n", p, result, expected); - status = 1; - } else { - printf("OK: %s == %f\n", p, expected); + if ((isnan(result) && !isnan(expected)) || + fabs(result - expected) > 0.00001f) { + printf("FAIL: %s: %f != %f\n", p, result, expected); + status = 1; + } else { + printf("OK: %s == %f\n", p, expected); + } + expr_destroy(e, &vars); + free(p); } - expr_destroy(e, &vars); - free(p); } -static void test_expr_error(char *s) { +static void test_expr_error(char *s, int pos, int error) { + const char *ERRORS[] = { + "EXPR_ERR_UNKNOWN", + "EXPR_ERR_UNEXPECTED_NUMBER", + "EXPR_ERR_UNEXPECTED_WORD", + "EXPR_ERR_UNEXPECTED_PARENS", + "EXPR_ERR_MISS_EXPECTED_OPERAND", + "EXPR_ERR_UNKNOWN_OPERATOR", + "EXPR_ERR_INVALID_FUNC_NAME", + "EXPR_ERR_BAD_CALL", + "EXPR_ERR_BAD_PARENS", + "EXPR_ERR_TOO_FEW_FUNC_ARGS", + "EXPR_ERR_FIRST_ARG_IS_NOT_VAR", + "EXPR_ERR_BAD_VARIABLE_NAME", + "EXPR_ERR_BAD_ASSIGNMENT", + }; struct expr_var_list vars = {0}; - struct expr *e = expr_create(s, strlen(s), &vars, user_funcs); + int n, f; + struct expr *e = expr_create2(s, strlen(s), &vars, user_funcs, &n, &f); if (e != NULL) { printf("FAIL: %s should return error\n", s); status = 1; } + if (n != pos) { + printf("FAIL: %s should return error near to %d, but returned at %d\n", s, + pos, n); + status = 1; + } + if (f != error) { + printf("FAIL: %s should return error %s, but returned %s\n", s, + ERRORS[-error], ERRORS[-f]); + status = 1; + } expr_destroy(e, &vars); } -static void test_empty() { +static void test_empty(void) { test_expr("", 0); test_expr(" ", 0); test_expr(" \t \n ", 0); } -static void test_const() { +static void test_const(void) { test_expr("1", 1); test_expr(" 1 ", 1); test_expr("12", 12); test_expr("12.3", 12.3); } -static void test_unary() { +static void test_unary(void) { test_expr("-1", -1); test_expr("--1", -(-1)); test_expr("!0 ", !0); @@ -218,7 +292,7 @@ static void test_unary() { test_expr("^3", ~3); } -static void test_binary() { +static void test_binary(void) { test_expr("1+2", 1 + 2); test_expr("10-2", 10 - 2); test_expr("2*3", 2 * 3); @@ -262,7 +336,7 @@ static void test_binary() { test_expr("2**2**3", 256); /* 2^(2^3), not (2^2)^3 */ } -static void test_logical() { +static void test_logical(void) { test_expr("2&&3", 3); test_expr("0&&3", 0); test_expr("3&&0", 0); @@ -277,7 +351,7 @@ static void test_logical() { test_expr("(3%0)||1", 1); } -static void test_parens() { +static void test_parens(void) { test_expr("(1+2)*3", (1 + 2) * 3); test_expr("(1)", 1); test_expr("(2.4)", 2.4); @@ -286,12 +360,12 @@ static void test_parens() { test_expr("(((3)))*(1+(2))", 9); } -static void test_assign() { +static void test_assign(void) { test_expr("x=5", 5); test_expr("x=y=3", 3); } -static void test_comma() { +static void test_comma(void) { test_expr("2,3,4", 4); test_expr("2+3,4*5", 4 * 5); test_expr("x=5, x", 5); @@ -300,11 +374,11 @@ static void test_comma() { test_expr("x=5, x = x+1", 6); } -static void test_funcs() { +static void test_funcs(void) { test_expr("add(1,2) + next(3)", 7); test_expr("add(1,next(2))", 4); test_expr("add(1,1+1) + add(2*2+1,2)", 10); - test_expr("nop()", 0); + test_expr("nop()", 1); test_expr("x=2,add(1, next(x))", 4); test_expr("$(zero), zero()", 0); test_expr("$(zero), zero(1, 2, 3)", 0); @@ -314,23 +388,27 @@ static void test_funcs() { test_expr("$(triw, ($1 * 256) & 255), triw(0.1)+triw(0.7)+triw(0.2)", 255); } -static void test_name_collision() { +static void test_name_collision(void) { test_expr("next=5", 5); test_expr("next=2,next(5)+next", 8); } -static void test_fancy_variable_names() { +static void test_fancy_variable_names(void) { test_expr("one=1", 1); +#ifndef _MSC_VER test_expr("один=1", 1); +#endif test_expr("six=6, seven=7, six*seven", 42); +#ifndef _MSC_VER test_expr("шість=6, сім=7, шість*сім", 42); test_expr("六=6, 七=7, 六*七", 42); test_expr("ταῦ=1.618, 3*ταῦ", 3 * 1.618); test_expr("$(ταῦ, 1.618), 3*ταῦ()", 3 * 1.618); +#endif test_expr("x#4=12, x#3=3, x#4+x#3", 15); } -static void test_auto_comma() { +static void test_auto_comma(void) { test_expr("a=3\na+2\n", 5); test_expr("a=3\n\n\na+2\n", 5); test_expr("\n\na=\n3\n\n\na+2\n", 5); @@ -345,66 +423,87 @@ static void test_auto_comma() { static void test_benchmark(const char *s) { struct timeval t; gettimeofday(&t, NULL); - double start = t.tv_sec + t.tv_usec * 1e-6; - struct expr_var_list vars = {0}; - struct expr *e = expr_create(s, strlen(s), &vars, user_funcs); - if (e == NULL) { - printf("FAIL: %s can't be compiled\n", s); - status = 1; - return; - } - long N = 1000000L; - for (long i = 0; i < N; i++) { - expr_eval(e); + { + double start = t.tv_sec + t.tv_usec * 1e-6; + double end, ns; + long N, i; + struct expr_var_list vars = {0}; + struct expr *e = expr_create(s, strlen(s), &vars, user_funcs); + if (e == NULL) { + printf("FAIL: %s can't be compiled\n", s); + status = 1; + return; + } + N = 1000000L; + for (i = 0; i < N; i++) { + expr_eval(e); + } + gettimeofday(&t, NULL); + end = t.tv_sec + t.tv_usec * 1e-6; + expr_destroy(e, &vars); + ns = 1000000000 * (end - start) / N; + printf("BENCH %40s:\t%f ns/op (%dM op/sec)\n", s, ns, (int) (1000 / ns)); } - gettimeofday(&t, NULL); - double end = t.tv_sec + t.tv_usec * 1e-6; - expr_destroy(e, &vars); - double ns = 1000000000 * (end - start) / N; - printf("BENCH %40s:\t%f ns/op (%dM op/sec)\n", s, ns, (int)(1000 / ns)); } -static void test_bad_syntax() { - test_expr_error("("); - test_expr_error(")"); - test_expr_error("()3"); - test_expr_error("()x"); - test_expr_error("0^+1"); - test_expr_error("()\\"); - test_expr_error("()."); - test_expr_error("4ever"); - test_expr_error("(2+3"); - test_expr_error("(-2"); - test_expr_error("*2"); - test_expr_error("nop="); - test_expr_error("nop("); - test_expr_error("unknownfunc()"); - test_expr_error("$(recurse, recurse()), recurse()"); - test_expr_error("),"); - test_expr_error("+("); - test_expr_error("2=3"); - test_expr_error("2.3.4"); - test_expr_error("1()"); - test_expr_error("x()"); - test_expr_error(","); - test_expr_error("1,,2"); - test_expr_error("nop(,x)"); - test_expr_error("nop(x=)>1"); - test_expr_error("1 x"); - test_expr_error("1++"); - test_expr_error("foo((x))"); - test_expr_error("nop(x))"); - test_expr_error("nop((x)"); - test_expr_error("$($())"); - test_expr_error("$(1)"); - test_expr_error("$()"); +static void test_bad_syntax(void) { + test_expr_error("(", 1, EXPR_ERR_BAD_PARENS); + test_expr_error(")", 1, EXPR_ERR_UNEXPECTED_PARENS); + test_expr_error("()3", 2, EXPR_ERR_UNEXPECTED_NUMBER); + test_expr_error("()x", 2, EXPR_ERR_UNEXPECTED_WORD); + test_expr_error("0^+1", 2, EXPR_ERR_MISS_EXPECTED_OPERAND); + test_expr_error("()\\", 2, EXPR_ERR_UNEXPECTED_WORD); + test_expr_error("().", 2, EXPR_ERR_UNKNOWN_OPERATOR); + test_expr_error("4ever", 1, EXPR_ERR_UNEXPECTED_WORD); + test_expr_error("(2+3", 4, EXPR_ERR_BAD_PARENS); + test_expr_error("(-2", 3, EXPR_ERR_BAD_PARENS); + test_expr_error("*2", 1, EXPR_ERR_MISS_EXPECTED_OPERAND); + test_expr_error("nop=", 4, EXPR_ERR_BAD_ASSIGNMENT); + test_expr_error("nop(", 4, EXPR_ERR_BAD_PARENS); + test_expr_error("unknownfunc()", 12, EXPR_ERR_INVALID_FUNC_NAME); + test_expr_error("$(recurse, recurse()), recurse()", 19, + EXPR_ERR_INVALID_FUNC_NAME); + test_expr_error("),", 1, EXPR_ERR_UNEXPECTED_PARENS); + test_expr_error("+(", 1, EXPR_ERR_MISS_EXPECTED_OPERAND); + test_expr_error("2=3", 3, EXPR_ERR_BAD_ASSIGNMENT); + test_expr_error("2.3.4", 5, EXPR_ERR_BAD_VARIABLE_NAME); + test_expr_error("1()", 1, EXPR_ERR_UNEXPECTED_PARENS); + test_expr_error("x()", 2, EXPR_ERR_INVALID_FUNC_NAME); + test_expr_error(",", 1, EXPR_ERR_MISS_EXPECTED_OPERAND); + test_expr_error("1,,2", 2, EXPR_ERR_MISS_EXPECTED_OPERAND); + test_expr_error("nop(,x)", 4, EXPR_ERR_MISS_EXPECTED_OPERAND); + test_expr_error("nop(x=)>1", 6, EXPR_ERR_UNEXPECTED_PARENS); + test_expr_error("1 x", 2, EXPR_ERR_UNEXPECTED_WORD); + test_expr_error("1++", 2, EXPR_ERR_MISS_EXPECTED_OPERAND); + test_expr_error("foo((x))", 4, EXPR_ERR_INVALID_FUNC_NAME); + test_expr_error("nop(x))", 7, EXPR_ERR_BAD_PARENS); + test_expr_error("nop((x)", 7, EXPR_ERR_BAD_PARENS); + test_expr_error("$($())", 5, EXPR_ERR_TOO_FEW_FUNC_ARGS); + test_expr_error("$(1)", 4, EXPR_ERR_FIRST_ARG_IS_NOT_VAR); + test_expr_error("$()", 3, EXPR_ERR_TOO_FEW_FUNC_ARGS); + test_expr_error("n=", 2, EXPR_ERR_BAD_ASSIGNMENT); + test_expr_error("a+10/((1+x)-b)-((5-(8/2))", 25, EXPR_ERR_BAD_PARENS); +} + +static void test_calc(void) { + int near, error; + const char *p = "2+3"; + expr_num_t result = expr_calc(p, &near, &error); + expr_num_t expected = 5; + if ((isnan(result) && !isnan(expected)) || + fabs(result - expected) > 0.00001f) { + printf("FAIL: %s: %f != %f\n", p, result, expected); + status = 1; + } else { + printf("OK: %s == %f\n", p, expected); + } } -int main() { +int main(void) { test_vector(); test_vars(); - test_tokizer(); + test_tokenizer(); test_empty(); test_const(); @@ -423,6 +522,8 @@ int main() { test_bad_syntax(); + test_calc(); + test_benchmark("5"); test_benchmark("5+5+5+5+5+5+5+5+5+5"); test_benchmark("5*5*5*5*5*5*5*5*5*5"); diff --git a/include/expr.h b/include/expr.h new file mode 100644 index 0000000..9b40d08 --- /dev/null +++ b/include/expr.h @@ -0,0 +1,1063 @@ +#ifndef EXPR_H +#define EXPR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include /* for isspace */ +#include +#include /* for pow */ +#include +#include +#include + +#ifndef NAN +#define NAN (0.0 / 0.0) +#endif /* NAN */ + +#ifndef INFINITY +#define INFINITY (1.0 / 0.0) +#endif /* INFINITY */ + +#ifdef __GNUC__ +#define EXPR_UNUSED __attribute__((used)) +#else /* __GNUC__ */ +#define EXPR_UNUSED +#endif /* __GNUC__ */ + +/* + * Expression number type + */ +#ifndef expr_num_t +#define expr_num_t double +#endif /* expr_num_t */ + +/* + * Memory management + */ +#ifndef expr_alloc +#define expr_alloc(sz) calloc(1, (sz)) +#endif /* expr_alloc */ +#ifndef expr_realloc +#define expr_realloc realloc +#endif /* expr_realloc */ +#ifndef expr_free +#define expr_free free +#endif /* expr_free */ +#ifndef expr_memcpy +#define expr_memcpy memcpy +#endif /* expr_memcpy */ + +/* + * String handling + */ +#ifndef expr_strlen +#define expr_strlen strlen +#endif /* expr_strlen */ +#ifndef expr_strncmp +#define expr_strncmp strncmp +#endif /* expr_strncmp */ +#ifndef expr_snprintf +#define expr_snprintf snprintf +#endif /* expr_snprintf */ + +/* + * Math + */ +#ifndef expr_pow +#define expr_pow pow +#endif /* expr_powf */ +#ifndef expr_fmod +#define expr_fmod fmod +#endif /* expr_fmodf */ + +/* + * Simple expandable vector implementation + */ +static int vec_expand(char **buf, int *length, int *cap, int memsz) { + if (*length + 1 > *cap) { + void *ptr; + int n = (*cap == 0) ? 1 : *cap << 1; + ptr = expr_realloc(*buf, n * memsz); + if (ptr == NULL) { + return -1; /* allocation failed */ + } + *buf = (char *) ptr; + *cap = n; + } + return 0; +} +#define vec(T) \ + struct { \ + T *buf; \ + int len; \ + int cap; \ + } +#define vec_init() \ + { NULL, 0, 0 } +#define vec_len(v) ((v)->len) +#define vec_unpack(v) \ + (char **) &(v)->buf, &(v)->len, &(v)->cap, sizeof(*(v)->buf) +#define vec_push(v, val) \ + vec_expand(vec_unpack(v)) ? -1 : ((v)->buf[(v)->len++] = (val), 0) +#define vec_nth(v, i) (v)->buf[i] +#define vec_peek(v) (v)->buf[(v)->len - 1] +#define vec_pop(v) (v)->buf[--(v)->len] +#define vec_free(v) \ + (expr_free((v)->buf), (v)->buf = NULL, (v)->len = (v)->cap = 0) +#define vec_foreach(v, var, iter) \ + if ((v)->len > 0) \ + for ((iter) = 0; (iter) < (v)->len && (((var) = (v)->buf[(iter)]), 1); \ + ++(iter)) + +/* + * Expression data types + */ +struct expr; +struct expr_func; + +enum expr_type { + OP_UNKNOWN, + OP_UNARY_MINUS, + OP_UNARY_LOGICAL_NOT, + OP_UNARY_BITWISE_NOT, + + OP_POWER, + OP_DIVIDE, + OP_MULTIPLY, + OP_REMAINDER, + + OP_PLUS, + OP_MINUS, + + OP_SHL, + OP_SHR, + + OP_LT, + OP_LE, + OP_GT, + OP_GE, + OP_EQ, + OP_NE, + + OP_BITWISE_AND, + OP_BITWISE_OR, + OP_BITWISE_XOR, + + OP_LOGICAL_AND, + OP_LOGICAL_OR, + + OP_ASSIGN, + OP_COMMA, + + OP_CONST, + OP_VAR, + OP_FUNC, +}; + +static int prec[] = {0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5, + 5, 5, 5, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0}; + +typedef vec(struct expr) vec_expr_t; +typedef void (*exprfn_cleanup_t)(struct expr_func *f, void *context); +typedef expr_num_t (*exprfn_t)(struct expr_func *f, vec_expr_t *args, + void *context); + +struct expr { + enum expr_type type; + union { + struct { + expr_num_t value; + } num; + struct { + expr_num_t *value; + } var; + struct { + vec_expr_t args; + } op; + struct { + struct expr_func *f; + vec_expr_t args; + void *context; + } func; + } param; +}; + +#define expr_init() \ + { .type = (enum expr_type) 0 } + +struct expr_string { + const char *s; + int n; +}; +struct expr_arg { + int oslen; + int eslen; + vec_expr_t args; +}; + +typedef vec(struct expr_string) vec_str_t; +typedef vec(struct expr_arg) vec_arg_t; + +static int expr_is_unary(enum expr_type op) { + return op == OP_UNARY_MINUS || op == OP_UNARY_LOGICAL_NOT || + op == OP_UNARY_BITWISE_NOT; +} + +static int expr_is_binary(enum expr_type op) { + return !expr_is_unary(op) && op != OP_CONST && op != OP_VAR && + op != OP_FUNC && op != OP_UNKNOWN; +} + +static int expr_prec(enum expr_type a, enum expr_type b) { + int left = + expr_is_binary(a) && a != OP_ASSIGN && a != OP_POWER && a != OP_COMMA; + return (left && prec[a] >= prec[b]) || (prec[a] > prec[b]); +} + +#define isfirstvarchr(c) \ + (((unsigned char) c >= '@' && c != '^' && c != '|') || c == '$') +#define isvarchr(c) \ + (((unsigned char) c >= '@' && c != '^' && c != '|') || c == '$' || \ + c == '#' || (c >= '0' && c <= '9')) + +static struct { + const char *s; + const enum expr_type op; +} OPS[] = { + {"-u", OP_UNARY_MINUS}, + {"!u", OP_UNARY_LOGICAL_NOT}, + {"^u", OP_UNARY_BITWISE_NOT}, + {"**", OP_POWER}, + {"*", OP_MULTIPLY}, + {"/", OP_DIVIDE}, + {"%", OP_REMAINDER}, + {"+", OP_PLUS}, + {"-", OP_MINUS}, + {"<<", OP_SHL}, + {">>", OP_SHR}, + {"<", OP_LT}, + {"<=", OP_LE}, + {">", OP_GT}, + {">=", OP_GE}, + {"==", OP_EQ}, + {"!=", OP_NE}, + {"&", OP_BITWISE_AND}, + {"|", OP_BITWISE_OR}, + {"^", OP_BITWISE_XOR}, + {"&&", OP_LOGICAL_AND}, + {"||", OP_LOGICAL_OR}, + {"=", OP_ASSIGN}, + {",", OP_COMMA}, + + /* These are used by lexer and must be ignored by parser, so we put + them at the end */ + {"-", OP_UNARY_MINUS}, + {"!", OP_UNARY_LOGICAL_NOT}, + {"^", OP_UNARY_BITWISE_NOT}, +}; + +static enum expr_type expr_op(const char *s, size_t len, int unary) { + unsigned int i; + for (i = 0; i < sizeof(OPS) / sizeof(OPS[0]); i++) { + if (expr_strlen(OPS[i].s) == len && expr_strncmp(OPS[i].s, s, len) == 0 && + (unary == -1 || expr_is_unary(OPS[i].op) == unary)) { + return OPS[i].op; + } + } + return OP_UNKNOWN; +} + +static expr_num_t expr_parse_number(const char *s, size_t len) { + expr_num_t num = 0; + unsigned int frac = 0; + unsigned int digits = 0; + unsigned int i; + for (i = 0; i < len; i++) { + if (s[i] == '.' && frac == 0) { + frac++; + continue; + } + if (isdigit(s[i])) { + digits++; + if (frac > 0) { + frac++; + } + num = num * 10 + (s[i] - '0'); + } else { + return NAN; + } + } + while (frac > 1) { + num = num / 10; + frac--; + } + return (digits > 0 ? num : NAN); +} + +/* + * Functions + */ +struct expr_func { + const char *name; + exprfn_t f; + exprfn_cleanup_t cleanup; + void *context; +}; + +static struct expr_func *expr_func(struct expr_func *funcs, const char *s, + size_t len) { + struct expr_func *f; + if (funcs != NULL) { + for (f = funcs; f->name; f++) { + if (expr_strlen(f->name) == len && expr_strncmp(f->name, s, len) == 0) { + return f; + } + } + } + return NULL; +} + +/* + * Variables + */ +struct expr_var { + expr_num_t value; + struct expr_var *next; + char name[]; +}; + +struct expr_var_list { + struct expr_var *head; +}; + +static struct expr_var *expr_var(struct expr_var_list *vars, const char *s, + size_t len) { + struct expr_var *v = NULL; + if (len == 0 || !isfirstvarchr(*s)) { + return NULL; + } + for (v = vars->head; v; v = v->next) { + if (expr_strlen(v->name) == len && expr_strncmp(v->name, s, len) == 0) { + return v; + } + } + v = expr_alloc(sizeof(struct expr_var) + len + 1); + if (v == NULL) { + return NULL; /* allocation failed */ + } + v->next = vars->head; + v->value = 0; + expr_memcpy(v->name, s, len); + v->name[len] = '\0'; + vars->head = v; + return v; +} + +static int to_int(expr_num_t x) { + if (isnan(x)) { + return 0; + } else if (isinf(x) != 0) { + return INT_MAX * isinf(x); + } else { + return (int) x; + } +} + +static expr_num_t expr_eval(struct expr *e) { + expr_num_t n; + switch (e->type) { + case OP_UNARY_MINUS: + return -(expr_eval(&e->param.op.args.buf[0])); + case OP_UNARY_LOGICAL_NOT: + return !(expr_eval(&e->param.op.args.buf[0])); + case OP_UNARY_BITWISE_NOT: + return ~(to_int(expr_eval(&e->param.op.args.buf[0]))); + case OP_POWER: + return expr_pow(expr_eval(&e->param.op.args.buf[0]), + expr_eval(&e->param.op.args.buf[1])); + case OP_MULTIPLY: + return expr_eval(&e->param.op.args.buf[0]) * + expr_eval(&e->param.op.args.buf[1]); + case OP_DIVIDE: + return expr_eval(&e->param.op.args.buf[0]) / + expr_eval(&e->param.op.args.buf[1]); + case OP_REMAINDER: + return expr_fmod(expr_eval(&e->param.op.args.buf[0]), + expr_eval(&e->param.op.args.buf[1])); + case OP_PLUS: + return expr_eval(&e->param.op.args.buf[0]) + + expr_eval(&e->param.op.args.buf[1]); + case OP_MINUS: + return expr_eval(&e->param.op.args.buf[0]) - + expr_eval(&e->param.op.args.buf[1]); + case OP_SHL: + return to_int(expr_eval(&e->param.op.args.buf[0])) + << to_int(expr_eval(&e->param.op.args.buf[1])); + case OP_SHR: + return to_int(expr_eval(&e->param.op.args.buf[0])) >> + to_int(expr_eval(&e->param.op.args.buf[1])); + case OP_LT: + return expr_eval(&e->param.op.args.buf[0]) < + expr_eval(&e->param.op.args.buf[1]); + case OP_LE: + return expr_eval(&e->param.op.args.buf[0]) <= + expr_eval(&e->param.op.args.buf[1]); + case OP_GT: + return expr_eval(&e->param.op.args.buf[0]) > + expr_eval(&e->param.op.args.buf[1]); + case OP_GE: + return expr_eval(&e->param.op.args.buf[0]) >= + expr_eval(&e->param.op.args.buf[1]); + case OP_EQ: + return expr_eval(&e->param.op.args.buf[0]) == + expr_eval(&e->param.op.args.buf[1]); + case OP_NE: + return expr_eval(&e->param.op.args.buf[0]) != + expr_eval(&e->param.op.args.buf[1]); + case OP_BITWISE_AND: + return to_int(expr_eval(&e->param.op.args.buf[0])) & + to_int(expr_eval(&e->param.op.args.buf[1])); + case OP_BITWISE_OR: + return to_int(expr_eval(&e->param.op.args.buf[0])) | + to_int(expr_eval(&e->param.op.args.buf[1])); + case OP_BITWISE_XOR: + return to_int(expr_eval(&e->param.op.args.buf[0])) ^ + to_int(expr_eval(&e->param.op.args.buf[1])); + case OP_LOGICAL_AND: + n = expr_eval(&e->param.op.args.buf[0]); + if (n != 0) { + n = expr_eval(&e->param.op.args.buf[1]); + if (n != 0) { + return n; + } + } + return 0; + case OP_LOGICAL_OR: + n = expr_eval(&e->param.op.args.buf[0]); + if (n != 0 && !isnan(n)) { + return n; + } else { + n = expr_eval(&e->param.op.args.buf[1]); + if (n != 0) { + return n; + } + } + return 0; + case OP_ASSIGN: + n = expr_eval(&e->param.op.args.buf[1]); + if (vec_nth(&e->param.op.args, 0).type == OP_VAR) { + *e->param.op.args.buf[0].param.var.value = n; + } + return n; + case OP_COMMA: + expr_eval(&e->param.op.args.buf[0]); + return expr_eval(&e->param.op.args.buf[1]); + case OP_CONST: + return e->param.num.value; + case OP_VAR: + return *e->param.var.value; + case OP_FUNC: + return e->param.func.f->f(e->param.func.f, &e->param.func.args, + e->param.func.context); + default: + return NAN; + } +} + +#define EXPR_TOP (1 << 0) +#define EXPR_TOPEN (1 << 1) +#define EXPR_TCLOSE (1 << 2) +#define EXPR_TNUMBER (1 << 3) +#define EXPR_TWORD (1 << 4) +#define EXPR_TDEFAULT (EXPR_TOPEN | EXPR_TNUMBER | EXPR_TWORD) + +#define EXPR_UNARY (1 << 5) +#define EXPR_COMMA (1 << 6) + +#define EXPR_ERR_UNKNOWN (0) +#define EXPR_ERR_UNEXPECTED_NUMBER (-1) +#define EXPR_ERR_UNEXPECTED_WORD (-2) +#define EXPR_ERR_UNEXPECTED_PARENS (-3) +#define EXPR_ERR_MISS_EXPECTED_OPERAND (-4) +#define EXPR_ERR_UNKNOWN_OPERATOR (-5) +#define EXPR_ERR_INVALID_FUNC_NAME (-6) +#define EXPR_ERR_BAD_CALL (-7) +#define EXPR_ERR_BAD_PARENS (-8) +#define EXPR_ERR_TOO_FEW_FUNC_ARGS (-9) +#define EXPR_ERR_FIRST_ARG_IS_NOT_VAR (-10) +#define EXPR_ERR_BAD_VARIABLE_NAME (-11) +#define EXPR_ERR_BAD_ASSIGNMENT (-12) + +static int expr_next_token(const char *s, size_t len, int *flags) { + unsigned int i = 0; + char c; + if (len == 0) { + return 0; + } + c = s[0]; + if (c == '#') { + for (; i < len && s[i] != '\n'; i++) + ; + return i; + } else if (c == '\n') { + for (; i < len && isspace(s[i]); i++) + ; + if (*flags & EXPR_TOP) { + if (i == len || s[i] == ')') { + *flags = *flags & (~EXPR_COMMA); + } else { + *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_COMMA; + } + } + return i; + } else if (isspace(c)) { + while (i < len && isspace(s[i]) && s[i] != '\n') { + i++; + } + return i; + } else if (isdigit(c)) { + if ((*flags & EXPR_TNUMBER) == 0) { + return EXPR_ERR_UNEXPECTED_NUMBER; /* unexpected number */ + } + *flags = EXPR_TOP | EXPR_TCLOSE; + while ((c == '.' || isdigit(c)) && i < len) { + i++; + c = s[i]; + } + return i; + } else if (isfirstvarchr(c)) { + if ((*flags & EXPR_TWORD) == 0) { + return EXPR_ERR_UNEXPECTED_WORD; /* unexpected word */ + } + *flags = EXPR_TOP | EXPR_TOPEN | EXPR_TCLOSE; + while ((isvarchr(c)) && i < len) { + i++; + c = s[i]; + } + return i; + } else if (c == '(' || c == ')') { + if (c == '(' && (*flags & EXPR_TOPEN) != 0) { + *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_TCLOSE; + } else if (c == ')' && (*flags & EXPR_TCLOSE) != 0) { + *flags = EXPR_TOP | EXPR_TCLOSE; + } else { + return EXPR_ERR_UNEXPECTED_PARENS; /* unexpected parenthesis */ + } + return 1; + } else { + if ((*flags & EXPR_TOP) == 0) { + if (expr_op(&c, 1, 1) == OP_UNKNOWN) { + return EXPR_ERR_MISS_EXPECTED_OPERAND; /* missing expected operand */ + } + *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_UNARY; + return 1; + } else { + int found = 0; + while (!isvarchr(c) && !isspace(c) && c != '(' && c != ')' && i < len) { + if (expr_op(s, i + 1, 0) != OP_UNKNOWN) { + found = 1; + } else if (found) { + break; + } + i++; + c = s[i]; + } + if (!found) { + return EXPR_ERR_UNKNOWN_OPERATOR; /* unknown operator */ + } + *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN; + return i; + } + } +} + +#define EXPR_PAREN_ALLOWED 0 +#define EXPR_PAREN_EXPECTED 1 +#define EXPR_PAREN_FORBIDDEN 2 + +static int expr_bind(const char *s, size_t len, vec_expr_t *es) { + enum expr_type op = expr_op(s, len, -1); + if (op == OP_UNKNOWN) { + return -1; + } + + if (expr_is_unary(op)) { + if (vec_len(es) < 1) { + return -1; + } + { + struct expr arg = vec_pop(es); + struct expr unary = expr_init(); + unary.type = op; + vec_push(&unary.param.op.args, arg); + vec_push(es, unary); + } + } else { + if (vec_len(es) < 2) { + return -1; + } + { + struct expr b = vec_pop(es); + struct expr a = vec_pop(es); + struct expr binary = expr_init(); + binary.type = op; + if (op == OP_ASSIGN && a.type != OP_VAR) { + return -1; /* Bad assignment */ + } + vec_push(&binary.param.op.args, a); + vec_push(&binary.param.op.args, b); + vec_push(es, binary); + } + } + return 0; +} + +static struct expr expr_const(expr_num_t value) { + struct expr e = expr_init(); + e.type = OP_CONST; + e.param.num.value = value; + return e; +} + +static struct expr expr_varref(struct expr_var *v) { + struct expr e = expr_init(); + e.type = OP_VAR; + e.param.var.value = &v->value; + return e; +} + +static struct expr expr_binary(enum expr_type type, struct expr a, + struct expr b) { + struct expr e = expr_init(); + e.type = type; + vec_push(&e.param.op.args, a); + vec_push(&e.param.op.args, b); + return e; +} + +static void expr_copy(struct expr *dst, struct expr *src) { + int i; + struct expr arg; + dst->type = src->type; + if (src->type == OP_FUNC) { + dst->param.func.f = src->param.func.f; + vec_foreach(&src->param.func.args, arg, i) { + struct expr tmp = expr_init(); + expr_copy(&tmp, &arg); + vec_push(&dst->param.func.args, tmp); + } + dst->param.func.context = src->param.func.f->context; + } else if (src->type == OP_CONST) { + dst->param.num.value = src->param.num.value; + } else if (src->type == OP_VAR) { + dst->param.var.value = src->param.var.value; + } else { + vec_foreach(&src->param.op.args, arg, i) { + struct expr tmp = expr_init(); + expr_copy(&tmp, &arg); + vec_push(&dst->param.op.args, tmp); + } + } +} + +static void expr_destroy_args(struct expr *e); + +static struct expr *expr_create2(const char *s, size_t len, + struct expr_var_list *vars, + struct expr_func *funcs, int *near, + int *error) { + expr_num_t num; + struct expr_var *v; + const char *id = NULL; + size_t idn = 0; + + struct expr *result = NULL; + + vec_expr_t es = vec_init(); + vec_str_t os = vec_init(); + vec_arg_t as = vec_init(); + + struct macro { + char *name; + vec_expr_t body; + }; + vec(struct macro) macros = vec_init(); + + int flags = EXPR_TDEFAULT; + int paren = EXPR_PAREN_ALLOWED; + *near = 0; + *error = EXPR_ERR_UNKNOWN; + for (;;) { + int n = expr_next_token(s, len, &flags); + const char *tok; + int paren_next; + if (n == 0) { + break; + } else if (n < 0) { + *error = n; + goto cleanup; + } + *near += n; + tok = s; + s = s + n; + len = len - n; + if (*tok == '#') { + continue; + } + if (flags & EXPR_UNARY) { + if (n == 1) { + switch (*tok) { + case '-': + tok = "-u"; + break; + case '^': + tok = "^u"; + break; + case '!': + tok = "!u"; + break; + default: + goto cleanup; + } + n = 2; + } + } + if (*tok == '\n' && (flags & EXPR_COMMA)) { + flags = flags & (~EXPR_COMMA); + n = 1; + tok = ","; + } + if (isspace(*tok)) { + continue; + } + paren_next = EXPR_PAREN_ALLOWED; + + if (idn > 0) { + if (n == 1 && *tok == '(') { + int i; + int has_macro = 0; + struct macro m; + vec_foreach(¯os, m, i) { + if (expr_strlen(m.name) == idn && + expr_strncmp(m.name, id, idn) == 0) { + has_macro = 1; + break; + } + } + if ((idn == 1 && id[0] == '$') || has_macro || + expr_func(funcs, id, idn) != NULL) { + struct expr_string str = {id, (int) idn}; + vec_push(&os, str); + paren = EXPR_PAREN_EXPECTED; + } else { + *error = EXPR_ERR_INVALID_FUNC_NAME; + goto cleanup; /* invalid function name */ + } + } else if ((v = expr_var(vars, id, idn)) != NULL) { + vec_push(&es, expr_varref(v)); + paren = EXPR_PAREN_FORBIDDEN; + } + id = NULL; + idn = 0; + } + + if (n == 1 && *tok == '(') { + if (paren == EXPR_PAREN_EXPECTED) { + struct expr_string str = {"{", 1}; + vec_push(&os, str); + { + struct expr_arg arg = {vec_len(&os), vec_len(&es), vec_init()}; + vec_push(&as, arg); + } + } else if (paren == EXPR_PAREN_ALLOWED) { + struct expr_string str = {"(", 1}; + vec_push(&os, str); + } else { + *error = EXPR_ERR_BAD_CALL; + goto cleanup; /* bad call */ + } + } else if (paren == EXPR_PAREN_EXPECTED) { + *error = EXPR_ERR_BAD_CALL; + goto cleanup; /* bad call */ + } else if (n == 1 && *tok == ')') { + int minlen = (vec_len(&as) > 0 ? vec_peek(&as).oslen : 0); + while (vec_len(&os) > minlen && *vec_peek(&os).s != '(' && + *vec_peek(&os).s != '{') { + struct expr_string str = vec_pop(&os); + if (expr_bind(str.s, str.n, &es) == -1) { + goto cleanup; + } + } + if (vec_len(&os) == 0) { + *error = EXPR_ERR_BAD_PARENS; + goto cleanup; /* bad parens */ + } + { + struct expr_string str = vec_pop(&os); + if (str.n == 1 && *str.s == '{') { + struct expr_arg arg; + str = vec_pop(&os); + arg = vec_pop(&as); + if (vec_len(&es) > arg.eslen) { + vec_push(&arg.args, vec_pop(&es)); + } + if (str.n == 1 && str.s[0] == '$') { + struct expr *u; + struct expr_var *v; + if (vec_len(&arg.args) < 1) { + vec_free(&arg.args); + *error = EXPR_ERR_TOO_FEW_FUNC_ARGS; + goto cleanup; /* too few arguments for $() function */ + } + u = &vec_nth(&arg.args, 0); + if (u->type != OP_VAR) { + vec_free(&arg.args); + *error = EXPR_ERR_FIRST_ARG_IS_NOT_VAR; + goto cleanup; /* first argument is not a variable */ + } + for (v = vars->head; v; v = v->next) { + if (&v->value == u->param.var.value) { + struct macro m = {v->name, arg.args}; + vec_push(¯os, m); + break; + } + } + vec_push(&es, expr_const(0)); + } else { + int i = 0; + int found = -1; + struct macro m; + vec_foreach(¯os, m, i) { + if (expr_strlen(m.name) == (size_t) str.n && + expr_strncmp(m.name, str.s, str.n) == 0) { + found = i; + } + } + if (found != -1) { + struct expr root; + struct expr *p; + int j; + m = vec_nth(¯os, found); + root = expr_const(0); + p = &root; + /* Assign macro parameters */ + for (j = 0; j < vec_len(&arg.args); j++) { + char varname[13]; + expr_snprintf(varname, sizeof(varname) - 1, "$%d", (j + 1)); + { + struct expr_var *v = + expr_var(vars, varname, expr_strlen(varname)); + struct expr ev = expr_varref(v); + struct expr assign = + expr_binary(OP_ASSIGN, ev, vec_nth(&arg.args, j)); + *p = expr_binary(OP_COMMA, assign, expr_const(0)); + } + p = &vec_nth(&p->param.op.args, 1); + } + /* Expand macro body */ + for (j = 1; j < vec_len(&m.body); j++) { + if (j < vec_len(&m.body) - 1) { + *p = expr_binary(OP_COMMA, expr_const(0), expr_const(0)); + expr_copy(&vec_nth(&p->param.op.args, 0), + &vec_nth(&m.body, j)); + } else { + expr_copy(p, &vec_nth(&m.body, j)); + } + p = &vec_nth(&p->param.op.args, 1); + } + vec_push(&es, root); + vec_free(&arg.args); + } else { + struct expr_func *f = expr_func(funcs, str.s, str.n); + struct expr bound_func = expr_init(); + bound_func.type = OP_FUNC; + bound_func.param.func.f = f; + bound_func.param.func.args = arg.args; + bound_func.param.func.context = f->context; + vec_push(&es, bound_func); + } + } + } + } + paren_next = EXPR_PAREN_FORBIDDEN; + } else if (!isnan(num = expr_parse_number(tok, n))) { + vec_push(&es, expr_const(num)); + paren_next = EXPR_PAREN_FORBIDDEN; + } else if (expr_op(tok, n, -1) != OP_UNKNOWN) { + enum expr_type op = expr_op(tok, n, -1); + struct expr_string o2 = {NULL, 0}; + if (vec_len(&os) > 0) { + o2 = vec_peek(&os); + } + for (;;) { + enum expr_type type2; + if (n == 1 && *tok == ',' && vec_len(&os) > 0) { + struct expr_string str = vec_peek(&os); + if (str.n == 1 && *str.s == '{') { + struct expr e = vec_pop(&es); + vec_push(&vec_peek(&as).args, e); + break; + } + } + type2 = expr_op(o2.s, o2.n, -1); + if (!(type2 != OP_UNKNOWN && expr_prec(op, type2))) { + struct expr_string str = {tok, n}; + vec_push(&os, str); + break; + } + + if (expr_bind(o2.s, o2.n, &es) == -1) { + goto cleanup; + } + (void) vec_pop(&os); + if (vec_len(&os) > 0) { + o2 = vec_peek(&os); + } else { + o2.n = 0; + } + } + } else { + if (/*n > 0 &&*/ !isdigit(*tok)) { + /* Valid identifier, a variable or a function */ + id = tok; + idn = n; + } else { + *error = EXPR_ERR_BAD_VARIABLE_NAME; + goto cleanup; /* bad variable name, e.g. '2.3.4' or '4ever' */ + } + } + paren = paren_next; + } + + if (idn > 0) { + vec_push(&es, expr_varref(expr_var(vars, id, idn))); + } + + while (vec_len(&os) > 0) { + struct expr_string rest = vec_pop(&os); + if (rest.n == 1 && (*rest.s == '(' || *rest.s == ')')) { + *error = EXPR_ERR_BAD_PARENS; + goto cleanup; /* bad paren */ + } + if (expr_bind(rest.s, rest.n, &es) == -1) { + *error = (*rest.s == '=') ? EXPR_ERR_BAD_ASSIGNMENT : EXPR_ERR_BAD_PARENS; + goto cleanup; + } + } + + result = (struct expr *) expr_alloc(sizeof(struct expr)); + if (result != NULL) { + if (vec_len(&es) == 0) { + result->type = OP_CONST; + } else { + *result = vec_pop(&es); + } + } + +cleanup : { + int i, j; + struct macro m; + struct expr e; + struct expr_arg a; + vec_foreach(¯os, m, i) { + struct expr e; + vec_foreach(&m.body, e, j) { + expr_destroy_args(&e); + } + vec_free(&m.body); + } + vec_free(¯os); + + vec_foreach(&es, e, i) { + expr_destroy_args(&e); + } + vec_free(&es); + + vec_foreach(&as, a, i) { + vec_foreach(&a.args, e, j) { + expr_destroy_args(&e); + } + vec_free(&a.args); + } +} + vec_free(&as); + + /*vec_foreach(&os, o, i) {vec_free(&m.body);}*/ + vec_free(&os); + + if (*near == 0) { + *near = 1; + } + return result; +} + +EXPR_UNUSED static struct expr *expr_create(const char *s, size_t len, + struct expr_var_list *vars, + struct expr_func *funcs) { + int near; + int error; + return expr_create2(s, len, vars, funcs, &near, &error); +} + +static void expr_destroy_args(struct expr *e) { + int i; + struct expr arg; + if (e->type == OP_FUNC) { + vec_foreach(&e->param.func.args, arg, i) { + expr_destroy_args(&arg); + } + vec_free(&e->param.func.args); + if (e->param.func.f->cleanup != NULL) { + e->param.func.f->cleanup(e->param.func.f, e->param.func.context); + } + } else if (e->type != OP_CONST && e->type != OP_VAR) { + vec_foreach(&e->param.op.args, arg, i) { + expr_destroy_args(&arg); + } + vec_free(&e->param.op.args); + } +} + +static void expr_destroy(struct expr *e, struct expr_var_list *vars) { + if (e != NULL) { + expr_destroy_args(e); + expr_free(e); + } + if (vars != NULL) { + struct expr_var *v; + for (v = vars->head; v;) { + struct expr_var *next = v->next; + expr_free(v); + v = next; + } + } +} + +EXPR_UNUSED static expr_num_t expr_calc2(const char *s, size_t len, int *near, + int *error) { + struct expr_var_list vars = {0}; + struct expr_func funcs[] = {{NULL, NULL, NULL, 0}}; + struct expr *e; + expr_num_t r; + e = expr_create2(s, len, &vars, funcs, near, error); + if (e == NULL) { + return NAN; + } + *near = 0; + *error = 0; + r = expr_eval(e); + expr_destroy(e, &vars); + return r; +} + +EXPR_UNUSED static expr_num_t expr_calc(const char *s, int *near, int *error) { + return expr_calc2(s, expr_strlen(s), near, error); +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* EXPR_H */ diff --git a/include/expr_debug.h b/include/expr_debug.h new file mode 100644 index 0000000..f51ece7 --- /dev/null +++ b/include/expr_debug.h @@ -0,0 +1,189 @@ +#ifndef EXPR_DEBUG_H +#define EXPR_DEBUG_H + +#include + +static void expr_print(struct expr *e) { + switch (e->type) { + case OP_UNKNOWN: + break; + case OP_UNARY_MINUS: + printf("-("); + expr_print(&e->param.op.args.buf[0]); + printf(")"); + break; + case OP_UNARY_LOGICAL_NOT: + printf("!("); + expr_print(&e->param.op.args.buf[0]); + printf(")"); + break; + case OP_UNARY_BITWISE_NOT: + printf("^("); + expr_print(&e->param.op.args.buf[0]); + printf(")"); + break; + case OP_POWER: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("**"); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_MULTIPLY: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("*"); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_DIVIDE: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("/"); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_REMAINDER: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("%%"); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_PLUS: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("+"); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_MINUS: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("-"); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_SHL: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("<<"); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_SHR: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf(">>"); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_LT: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("<"); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_LE: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("<="); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_GT: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf(">"); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_GE: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf(">="); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_EQ: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("=="); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_NE: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("!="); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_BITWISE_AND: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("&"); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_BITWISE_OR: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("|"); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_BITWISE_XOR: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("^"); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_LOGICAL_AND: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("&&"); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_LOGICAL_OR: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf("||"); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_ASSIGN: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf(":="); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_COMMA: + printf("("); + expr_print(&e->param.op.args.buf[0]); + printf(","); + expr_print(&e->param.op.args.buf[1]); + printf(")"); + break; + case OP_CONST: + printf("%.2f", e->param.num.value); + break; + case OP_VAR: + printf("[%.2f@%p]", *e->param.var.value, (void *) e->param.var.value); + break; + case OP_FUNC: + printf("func(todo)"); + break; + } +} + +static void expr_println(struct expr *e) { + expr_print(e); + printf("\n"); +} + +#endif /* EXPR_DEBUG_H */