├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── CPPLINT.cfg ├── LICENSE ├── README.md ├── cmake ├── FindGMP.cmake └── FindMPFR.cmake ├── docs ├── libsimplify │ ├── index.html │ ├── search.json │ └── xml │ │ ├── Expression Structures.expression_function.xml │ │ ├── Expression Structures.expression_number.xml │ │ ├── Expression Structures.expression_operator.xml │ │ ├── Expression Structures.expression_prefix.xml │ │ ├── Expression Structures.expression_variable.xml │ │ ├── Expression Structures.xml │ │ ├── error_t.xml │ │ ├── expression_list.xml │ │ ├── expression_list_t.xml │ │ ├── expression_parser.xml │ │ ├── expression_parser_t.xml │ │ ├── expression_t.xml │ │ ├── index.xml │ │ ├── lexer.xml │ │ ├── lexer_t.xml │ │ ├── operator_t.xml │ │ ├── rbtree.xml │ │ ├── rbtree_node.xml │ │ ├── rbtree_t.xml │ │ ├── scope.xml │ │ ├── scope_t.xml │ │ ├── token.xml │ │ ├── variable_info.xml │ │ └── variable_info_t.xml ├── simplify.1 ├── simplify.1.html ├── simplify.1.md ├── simplify.7 ├── simplify.7.html └── simplify.7.md ├── get-deps.sh ├── src ├── flags │ └── flags.h ├── simplify.c └── simplify │ ├── builtins.h │ ├── errors.h │ ├── expression │ ├── evaluate.c │ ├── evaluate.h │ ├── expr_types.c │ ├── expr_types.h │ ├── expression.c │ ├── expression.h │ ├── isolate.c │ ├── isolate.h │ ├── simplify.c │ ├── simplify.h │ ├── stringify.c │ └── stringify.h │ ├── lexer.c │ ├── lexer.h │ ├── parser.c │ ├── parser.h │ └── rbtree │ ├── rbtree.c │ └── rbtree.h └── test ├── compare.c ├── expression.c ├── lexer.c ├── parser.c ├── rbtree.c ├── stringify.c └── test.h /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build 3 | vgcore.* 4 | deps -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | langauge: c 2 | python: 3 | - "2.7" 4 | 5 | sudo: required 6 | dist: trusty 7 | 8 | compiler: 9 | - gcc 10 | 11 | install: bash ./get-deps.sh 12 | 13 | env: 14 | global: 15 | - CTEST_EXT_COLOR_OUTPUT=TRUE 16 | - CTEST_BUILD_FLAGS=-j4 17 | 18 | script: 19 | - python ./deps/cpplint.py ./src/**/*.* ./src/*.* 20 | - mkdir build 21 | - cd build 22 | - cmake .. 23 | - cmake --build . 24 | - ctest -VV 25 | 26 | after_success: 27 | - coveralls -e 'test' -e 'src/simplify.c' --root .. --gcov-options '\-lp' 28 | 29 | addons: 30 | apt: 31 | packages: 32 | - valgrind 33 | 34 | before_install: 35 | - pip install --user cpp-coveralls 36 | 37 | cache: 38 | directories: 39 | - ./deps -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.0) 2 | project(simplify C) 3 | 4 | set(CMAKE_MODULE_PATH CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") 5 | set(CMAKE_EXPORT_COMPILE_COMMAND ON) 6 | 7 | include(CTest) 8 | 9 | file(GLOB SOURCES "${CMAKE_SOURCE_DIR}/src/simplify/*.c" "${CMAKE_SOURCE_DIR}/src/simplify/*/*.c") 10 | file(GLOB HEADERS "${CMAKE_SOURCE_DIR}/src/simplify/*.h" "${CMAKE_SOURCE_DIR}/src/simplify/*/*.h") 11 | file(GLOB MANUALS "${CMAKE_SOURCE_DIR}/docs/simplify.*.md") 12 | string(REPLACE ";" "\t" HEADERS_STR "${HEADERS}") 13 | string(REPLACE ";" "\t" SOURCES_STR "${SOURCES}") 14 | string(REPLACE ";" "\t" MANUALS_STR "${MANUALS}") 15 | set(CLDOC_FLAGS ${CLDOC_FLAGS} --language c --output ${CMAKE_SOURCE_DIR}/docs/libsimplify ${HEADERS_STR}) 16 | 17 | if(EXISTS "${CMAKE_SOURCE_DIR}/deps/root/usr/local") 18 | set(CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/deps/root/usr/local") 19 | endif() 20 | 21 | if (SIMPLIFY_LINK_STATIC) 22 | set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") 23 | endif() 24 | 25 | if (SIMPLIFY_MINIMIZE_ALLOCATIONS) 26 | add_definitions(-DRBTREE_USE_CHUNKS=1) 27 | endif() 28 | 29 | find_package(GMP REQUIRED) 30 | find_package(MPFR REQUIRED) 31 | 32 | find_program(CLDOC cldoc cldoc.py DOC "c/c++/obj-c documentation generator") 33 | find_program(RONN ronn ronn.rb DOC "markdown to man converter") 34 | find_program(GCOV gcov DOC "GNU test coverage tool") 35 | 36 | include_directories("${CMAKE_SOURCE_DIR}/src" "${CMAKE_SOURCE_DIR}" "${GMP_INCLUDE_DIR}" "${MPFR_INCLUDES}") 37 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=c99 -Werror") 38 | 39 | add_custom_target("docs") 40 | 41 | if (CLDOC STREQUAL "CLDOC-NOTFOUND") 42 | message(WARNING "cldoc was not found, the docs.lib.* targets will not be available") 43 | else() 44 | add_custom_target("docs.lib.html" COMMAND ${CLDOC} generate -std=c99 -I${CMAKE_SOURCE_DIR}/src -- --type html ${CLDOC_FLAGS}) 45 | add_custom_target("docs.lib.xml" COMMAND ${CLDOC} generate -std=c99 -I${CMAKE_SOURCE_DIR}/src -- --type xml ${CLDOC_FLAGS}) 46 | 47 | add_custom_target("docs.lib" DEPENDS "docs.lib.html" "docs.lib.xml") 48 | 49 | add_dependencies("docs" "docs.lib") 50 | endif() 51 | 52 | if (RONN STREQUAL "RONN-NOTFOUND") 53 | message(WARNING "ronn was not found, the docs.man.* targets will not be available") 54 | else() 55 | add_custom_target("docs.man.html" COMMAND ${RONN} -5 "${MANUALS_STR}" ) 56 | add_custom_target("docs.man.roff" COMMAND ${RONN} -r "${MANUALS_STR}" ) 57 | 58 | add_custom_target("docs.man" DEPENDS "docs.man.html" "docs.man.roff") 59 | 60 | add_dependencies("docs" "docs.man") 61 | endif() 62 | 63 | if (GCOV STREQUAL "GCOV-NOTFOUND") 64 | message(WARNING "gcov was not found, the coverage target will not be available") 65 | set(GCOV "") 66 | set(HAVE_GCOV FALSE) 67 | else() 68 | add_custom_target("coverage" COMMAND ${GCOV} ${SOURCES_STR} ${CMAKE_BINARY_DIR}/CMakeFiles/simplify.dir/src/simplify/**/*.gcno) 69 | set(HAVE_GCOV TRUE) 70 | endif() 71 | 72 | add_library(simplify STATIC ${SOURCES}) 73 | 74 | add_executable(simplify-bin ${CMAKE_SOURCE_DIR}/src/simplify.c) 75 | set_target_properties(simplify-bin PROPERTIES OUTPUT_NAME simplify) 76 | 77 | target_link_libraries(simplify ${MPFR_LIBRARIES}) 78 | target_link_libraries(simplify ${GMP_LIBRARIES}) 79 | target_link_libraries(simplify m) 80 | 81 | target_link_libraries(simplify-bin simplify) 82 | 83 | if(BUILD_TESTING) 84 | if (HAVE_GCOV) 85 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g --coverage") 86 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") 87 | endif() 88 | 89 | add_executable(test_rbtree ${CMAKE_SOURCE_DIR}/test/rbtree.c) 90 | add_executable(test_lexer ${CMAKE_SOURCE_DIR}/test/lexer.c) 91 | add_executable(test_parser ${CMAKE_SOURCE_DIR}/test/parser.c) 92 | add_executable(test_expression ${CMAKE_SOURCE_DIR}/test/expression.c) 93 | add_executable(test_compare ${CMAKE_SOURCE_DIR}/test/compare.c) 94 | add_executable(test_stringify ${CMAKE_SOURCE_DIR}/test/stringify.c) 95 | 96 | target_link_libraries(test_rbtree simplify) 97 | target_link_libraries(test_lexer simplify) 98 | target_link_libraries(test_parser simplify) 99 | target_link_libraries(test_expression simplify) 100 | target_link_libraries(test_compare simplify) 101 | target_link_libraries(test_stringify simplify) 102 | 103 | add_test(NAME rbtree COMMAND test_rbtree) 104 | add_test(NAME lexer COMMAND test_lexer) 105 | add_test(NAME parser COMMAND test_parser) 106 | add_test(NAME expression COMMAND test_expression) 107 | add_test(NAME compare COMMAND test_compare) 108 | add_test(NAME stringify COMMAND test_stringify) 109 | endif() -------------------------------------------------------------------------------- /CPPLINT.cfg: -------------------------------------------------------------------------------- 1 | set noparent 2 | root=src 3 | linelength=120 4 | extensions=h,c 5 | filter=-readability/casting,-runtime/int,-runtime/references,-runtime/arrays,-readability/braces -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ian R. Shehadeh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notice 2 | 3 | Simplify was a project I worked on while taking algebra 2 to teach myself the concepts and improve my C skills. 4 | I don't plan on working on it anymore, even though it's buggy and incomplete. 5 | 6 | I'm leaving the repo up and archiving it because I think it will be fun to look back on. :) 7 | 8 | # Simplify 9 | 10 | [![Build Status](https://travis-ci.org/IanS5/simplify.svg?branch=master)](https://travis-ci.org/IanS5/simplify) 11 | [![Coverage Status](https://coveralls.io/repos/github/IanS5/simplify/badge.svg?branch=master)](https://coveralls.io/github/IanS5/simplify?branch=master) 12 | 13 | Simplify evaluates a mathematical expression, putting it in its simplest terms. 14 | 15 | For example: 16 | 17 | `simplify --isolate x '2(x + 2) = 5'`\ 18 | `x = 0.5` 19 | 20 | Simplify also works with multiple variables: 21 | 22 | `simplify --isolate y '2 + x * y^4 = 10`\ 23 | `y = (8 / x) \ 4`. 24 | 25 | Variables can also be assigned in an expression: 26 | 27 | `simplify 'x : 2' 'x - 5'`\ 28 | `2`\ 29 | `-3` 30 | 31 | For a more detailed explanation check out the [wiki](https://github.com/IanS5/simplify/wiki). 32 | 33 | ## Building 34 | 35 | Simplify depends on [GMP](https://www.gmplib.org) and [MPFR](https://www.mpfr.org) 36 | You can use the `get-deps.sh` script to download them, but most package managers will 37 | have up-to-date prebuilt versions. 38 | 39 | 1. `./get-deps.sh` _[optional]_ 40 | 2. `mkdir build; cd build` 41 | 3. `cmake ..` 42 | 4. `cmake --build .` 43 | 44 | ## Testing 45 | 46 | _To run the tests ctest must be in your PATH_\ 47 | Follow build steps `1` and `2`, then run `make test` 48 | 49 | ## Regenerate Documentation 50 | 51 | _To generate documentation cldoc and ronn must be in your PATH_\ 52 | Similar to making the tests do build steps `1` and `2`, but run `make docs` instead of `make test`. 53 | -------------------------------------------------------------------------------- /cmake/FindGMP.cmake: -------------------------------------------------------------------------------- 1 | 2 | # Try to find the GMP librairies 3 | # GMP_FOUND - system has GMP lib 4 | # GMP_INCLUDE_DIR - the GMP include directory 5 | # GMP_LIBRARIES - Libraries needed to use GMP 6 | 7 | if (GMP_INCLUDE_DIR AND GMP_LIBRARIES) 8 | # Already in cache, be silent 9 | set(GMP_FIND_QUIETLY TRUE) 10 | endif (GMP_INCLUDE_DIR AND GMP_LIBRARIES) 11 | 12 | find_path(GMP_INCLUDE_DIR NAMES gmp.h ) 13 | find_library(GMP_LIBRARIES NAMES gmp libgmp ) 14 | find_library(GMPXX_LIBRARIES NAMES gmpxx libgmpxx ) 15 | 16 | include(FindPackageHandleStandardArgs) 17 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMP DEFAULT_MSG GMP_INCLUDE_DIR GMP_LIBRARIES) 18 | 19 | mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARIES) -------------------------------------------------------------------------------- /cmake/FindMPFR.cmake: -------------------------------------------------------------------------------- 1 | # Try to find the MPFR library 2 | # See http://www.mpfr.org/ 3 | # 4 | # This module supports requiring a minimum version, e.g. you can do 5 | # find_package(MPFR 2.3.0) 6 | # to require version 2.3.0 to newer of MPFR. 7 | # 8 | # Once done this will define 9 | # 10 | # MPFR_FOUND - system has MPFR lib with correct version 11 | # MPFR_INCLUDES - the MPFR include directory 12 | # MPFR_LIBRARIES - the MPFR library 13 | # MPFR_VERSION - MPFR version 14 | 15 | # Copyright (c) 2006, 2007 Montel Laurent, 16 | # Copyright (c) 2008, 2009 Gael Guennebaud, 17 | # Copyright (c) 2010 Jitse Niesen, 18 | # Copyright (c) 2015 Jack Poulson, 19 | # Redistribution and use is allowed according to the terms of the BSD license. 20 | 21 | find_path(MPFR_INCLUDES NAMES mpfr.h PATHS $ENV{GMPDIR} $ENV{MPFRDIR} 22 | ${INCLUDE_INSTALL_DIR}) 23 | 24 | # Set MPFR_FIND_VERSION to 1.0.0 if no minimum version is specified 25 | if(NOT MPFR_FIND_VERSION) 26 | if(NOT MPFR_FIND_VERSION_MAJOR) 27 | set(MPFR_FIND_VERSION_MAJOR 1) 28 | endif() 29 | if(NOT MPFR_FIND_VERSION_MINOR) 30 | set(MPFR_FIND_VERSION_MINOR 0) 31 | endif() 32 | if(NOT MPFR_FIND_VERSION_PATCH) 33 | set(MPFR_FIND_VERSION_PATCH 0) 34 | endif() 35 | set(MPFR_FIND_VERSION 36 | "${MPFR_FIND_VERSION_MAJOR}.${MPFR_FIND_VERSION_MINOR}.${MPFR_FIND_VERSION_PATCH}") 37 | endif() 38 | 39 | if(MPFR_INCLUDES) 40 | # Query MPFR_VERSION 41 | file(READ "${MPFR_INCLUDES}/mpfr.h" _mpfr_version_header) 42 | 43 | string(REGEX MATCH "define[ \t]+MPFR_VERSION_MAJOR[ \t]+([0-9]+)" 44 | _mpfr_major_version_match "${_mpfr_version_header}") 45 | set(MPFR_MAJOR_VERSION "${CMAKE_MATCH_1}") 46 | string(REGEX MATCH "define[ \t]+MPFR_VERSION_MINOR[ \t]+([0-9]+)" 47 | _mpfr_minor_version_match "${_mpfr_version_header}") 48 | set(MPFR_MINOR_VERSION "${CMAKE_MATCH_1}") 49 | string(REGEX MATCH "define[ \t]+MPFR_VERSION_PATCHLEVEL[ \t]+([0-9]+)" 50 | _mpfr_patchlevel_version_match "${_mpfr_version_header}") 51 | set(MPFR_PATCHLEVEL_VERSION "${CMAKE_MATCH_1}") 52 | 53 | set(MPFR_VERSION 54 | ${MPFR_MAJOR_VERSION}.${MPFR_MINOR_VERSION}.${MPFR_PATCHLEVEL_VERSION}) 55 | 56 | # Check whether found version exceeds minimum required 57 | if(${MPFR_VERSION} VERSION_LESS ${MPFR_FIND_VERSION}) 58 | set(MPFR_VERSION_OK FALSE) 59 | message(STATUS "MPFR version ${MPFR_VERSION} found in ${MPFR_INCLUDES}, " 60 | "but at least version ${MPFR_FIND_VERSION} is required") 61 | else() 62 | set(MPFR_VERSION_OK TRUE) 63 | endif() 64 | endif() 65 | 66 | find_library(MPFR_LIBRARIES mpfr 67 | PATHS $ENV{GMPDIR} $ENV{MPFRDIR} ${LIB_INSTALL_DIR}) 68 | 69 | include(FindPackageHandleStandardArgs) 70 | find_package_handle_standard_args(MPFR DEFAULT_MSG 71 | MPFR_INCLUDES MPFR_LIBRARIES MPFR_VERSION_OK) 72 | mark_as_advanced(MPFR_INCLUDES MPFR_LIBRARIES) -------------------------------------------------------------------------------- /docs/libsimplify/xml/Expression Structures.expression_function.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/Expression Structures.expression_number.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/Expression Structures.expression_operator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/Expression Structures.expression_prefix.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/Expression Structures.expression_variable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/Expression Structures.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/error_t.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Enumerates all errors that may occur. 4 | Error also has the value `ERROR_NO_ERROR`, meaning no error occurred. This value 5 | will always be zero, so it's safe to check if an error occurred with `if (error) { ... }`. 6 | 7 | 8 | 9 | get a description of the error 10 | 11 | returns a static string describing the error 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/expression_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/expression_list_t.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | A singly linked list of expressions 4 | 5 | 6 | append a new element to the end of `list` 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | copy all elements in one list to another 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | free all expressions and units in the list 31 | 32 | 33 | 34 | 35 | the list to clean 36 | 37 | 38 | 39 | 40 | initialize an expression list 41 | 42 | 43 | 44 | 45 | the list to initialize 46 | 47 | 48 | 49 | 50 | free the last element of the list (that may be `list` itself) 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/expression_parser.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/expression_parser_t.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | transforms a stream of tokens into an expression. 4 | 5 | 6 | clean all resources associated with a parser 7 | 8 | 9 | 10 | 11 | the parser to clean 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | parse an expression 28 | NOTE: most of the time it's safer and easier to use the `parse_string` or `parse_file` functions 29 | 30 | 31 | 32 | returns an error code 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/expression_t.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | A parsed mathmatical expression. 4 | An expression is a union of four structures: 5 | 6 | - number 7 | 8 | - prefix 9 | 10 | - variable 11 | 12 | - operator 13 | 14 | Which one of these structures is currently in use is stored in the `type` option. 15 | The EXPRESSION_IS_XXX macros can be used to conveniently check the type of an expression. 16 | See the Expression Structures category for more information on each structure. 17 | 18 | 19 | 20 | free all memory referenced by expression recursively, but not expression itself. 21 | 22 | 23 | 24 | 25 | the expression to clean 26 | 27 | 28 | 29 | 30 | free the left branch of an expression, make the expressions equal to it's right branch 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | free the right branch of an expression, make the expressions equal to it's left branch 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | deep copy expression into expression2. 49 | 50 | 51 | 52 | 53 | the source expression 54 | 55 | 56 | 57 | the destination expression 58 | 59 | 60 | 61 | 62 | get the name of the first function that appears in an expression 63 | 64 | returns the function's name, or NULL if it was not found 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | get the name of the first variable that appears in an expression 73 | 74 | returns the variable's name, or NULL if it was not found 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | free all memory referenced by the expression and the expression. 83 | 84 | 85 | 86 | 87 | the expression to free 88 | 89 | 90 | 91 | 92 | check for a variable or function in the expression 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | initialize a function expression 105 | NOTE: this function copies the value `name` is pointing to up through `length`. 106 | 107 | 108 | 109 | 110 | 111 | the expression to initialize 112 | 113 | 114 | 115 | the function's name 116 | 117 | 118 | 119 | the length of the function's name 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | initialize a new number expression 128 | 129 | 130 | 131 | 132 | the expression to initialize 133 | 134 | 135 | 136 | the number to use as the expression's initial value 137 | 138 | 139 | 140 | 141 | initialize a new number expression with a double 142 | 143 | 144 | 145 | 146 | the expression to initialize 147 | 148 | 149 | 150 | the number to use as the expression initial value 151 | 152 | 153 | 154 | 155 | initialize a new number expression with an integer 156 | 157 | 158 | 159 | 160 | the expression to initialize 161 | 162 | 163 | 164 | the number to use as the expression initial value 165 | 166 | 167 | 168 | 169 | initialize an operator expression 170 | 171 | 172 | 173 | 174 | the expression to initialize 175 | 176 | 177 | 178 | the left side of the operator expression 179 | 180 | 181 | 182 | the infix operator in the expression 183 | 184 | 185 | 186 | the right side of the operator expression 187 | 188 | 189 | 190 | 191 | initialize a prefix expression 192 | 193 | 194 | 195 | 196 | the expression to initialize 197 | 198 | 199 | 200 | the prefix operator in the expression 201 | 202 | 203 | 204 | the expression to the right of the prefix 205 | 206 | 207 | 208 | 209 | initialize a variable expression 210 | NOTE: this function copies the value `name` is pointing to up through `length`. 211 | 212 | 213 | 214 | 215 | 216 | the expression to initialize 217 | 218 | 219 | 220 | the variable's name 221 | 222 | 223 | 224 | the length of the variable's name 225 | 226 | 227 | 228 | 229 | check if the expression is an operator expression that compares it's left & right branches. 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/lexer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/lexer_t.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lexical analyzer 4 | The lexer walks through a buffer or file, picking out tokens 5 | 6 | 7 | 8 | free all the lexer's resources, except the file 9 | 10 | 11 | 12 | 13 | the lexer to clean 14 | 15 | 16 | 17 | 18 | initialize a lexer from a file 19 | 20 | 21 | 22 | 23 | the lexer to initialize 24 | 25 | 26 | 27 | the file read 28 | 29 | 30 | 31 | 32 | initialize lexer from a null terminated string 33 | 34 | 35 | 36 | 37 | the lexer to initialize 38 | 39 | 40 | 41 | the buffer to read, the buffer is copied 42 | 43 | 44 | 45 | 46 | draw the next token from the lexer 47 | 48 | 49 | 50 | 51 | the lexer to draw from 52 | 53 | 54 | 55 | a location to store the token 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/operator_t.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Specifies an operator's type. 4 | An operator represents a specific operation performed on one or more numbers. 5 | At the moment an operator is just the literal token stored in a `char`. This is liable to change in the future 6 | 7 | 8 | 9 | get the precedence of a given operator (higher = should be executed first) 10 | 11 | 12 | 13 | 14 | the operator to check 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/rbtree.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/rbtree_node.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/rbtree_t.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | A Red-Black Tree 4 | A Red-Black Tree is a type of self-balancing binary search tree. 5 | 6 | 7 | 8 | free all resources from a tree (both keys and data) 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | initialize an rbtree 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | insert a new item into the tree 37 | 38 | an error code 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | get an item from the tree 53 | 54 | an error is returned if the key is not found, and `data_out` is not assigned to. 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/scope.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/scope_t.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | A scope holds information about an expression's local variables and functions 4 | 5 | 6 | clean all resources associated with a scope 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | define a variable in the scope 16 | 17 | return an error code 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | define a constant in the scope 32 | 33 | return an error code 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | define a function in the scope 48 | 49 | return an error code 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | search a scope for a function 67 | 68 | 69 | 70 | 71 | the scope to search 72 | 73 | 74 | 75 | the name to look for 76 | 77 | 78 | 79 | location for function's body 80 | 81 | 82 | 83 | location for argument list 84 | 85 | 86 | 87 | 88 | get the value of a variable or constant 89 | 90 | 91 | 92 | 93 | the scope to search 94 | 95 | 96 | 97 | the name to look for 98 | 99 | 100 | 101 | location for result 102 | 103 | 104 | 105 | 106 | get information about a variable 107 | 108 | returns an error code 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | Initialize a scope 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/token.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/variable_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/libsimplify/xml/variable_info_t.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Holds information about a variable, constant, or function 4 | 5 | 6 | free variable's information 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/simplify.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "SIMPLIFY" "1" "April 2018" "" "" 5 | . 6 | .SH "NAME" 7 | \fBsimplify\fR \- rewrite a mathematical expression in it\'s simplest terms 8 | . 9 | .SH "SYNOPSIS" 10 | \fBsimplify\fR [\fB\-q\fR] [\fB\-v\fR] [\fB\-f\fR \fIFILE\fR] [\fB\-i\fR \fIVARIABLE\fR] [\fB\-d\fR \fIVARIABLE\fR=\fIEXPR\fR] \.\.\.EXPRESSION 11 | . 12 | .SH "DESCIPTION" 13 | Simplify takes a series of expression and tries to reduce them to their simplest form\. Simplify may just evaluate the expression, if that is possible, for example, \fB2 + 2\fR will be \fBsimplify\fR\'d to \fB4\fR\. 14 | . 15 | .P 16 | Simplify takes a series of expressions, it prints a simplified version for each of them\. 17 | . 18 | .P 19 | Simplify can also work with unknowns\. By default, it will try to evaluate as much as possible around any unknowns\. By using the \fB\-i\fR flag, simplify can also solve for an unknown\. \fB\-i\fR will try to isolate that variable on one side of a comparison or assignment operator (\fB:\fR, \fB=\fR, \fB<\fR, or \fB>\fR), it appends \fB= 0\fR to the equation if no equality operator is present\. For more information on how to write \fBsimplify\fR expressions see simplify(7)\. 20 | . 21 | .SH "OPTIONS" 22 | . 23 | .TP 24 | \fB\-q\fR, \fB\-\-quiet\fR 25 | Only print errors, ignore all the expressions\' results 26 | . 27 | .TP 28 | \fB\-v\fR, \fB\-\-verbose\fR 29 | Print status updates while running 30 | . 31 | .TP 32 | \fB\-f\fR, \fB\-\-file\fR=[\fBFILE\fR] 33 | Evaluate \fBFILE\fR before any other expressions\. 34 | . 35 | .TP 36 | \fB\-i\fR, \fB\-\-isolate\fR=[\fBVARIABLE\fR] 37 | Try to isolate \fBVARIABLE\fR on one side of an equality operator\. 38 | . 39 | .TP 40 | \fB\-d\fR, \fB\-\-define\fR=[\fBVARIABLE\fR=\fBEXPRESSION\fR] 41 | Define \fBVARIABLE\fR as \fBEXPRESSION\fR, do try to evaluate either side simplify 42 | . 43 | .SH "SEE ALSO" 44 | simplify(7) 45 | -------------------------------------------------------------------------------- /docs/simplify.1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | simplify(1) - rewrite a mathematical expression in it's simplest terms 7 | 44 | 45 | 52 | 53 |
54 | 55 | 62 | 63 |
    64 |
  1. simplify(1)
  2. 65 |
  3. 66 |
  4. simplify(1)
  5. 67 |
68 | 69 |

NAME

70 |

71 | simplify - rewrite a mathematical expression in it's simplest terms 72 |

73 | 74 |

SYNOPSIS

75 | 76 |

simplify [-q] [-v] [-f FILE] [-i VARIABLE] [-d VARIABLE=EXPR] ...EXPRESSION

77 | 78 |

DESCIPTION

79 | 80 |

Simplify takes a series of expression and tries to reduce them to their simplest form. 81 | Simplify may just evaluate the expression, if that is possible, for example, 2 + 2 will be 82 | simplify'd to 4.

83 | 84 |

Simplify takes a series of expressions, it prints a simplified version for each of them.

85 | 86 |

Simplify can also work with unknowns. By default, it will try to evaluate as much as possible around any unknowns. 87 | By using the -i flag, simplify can also solve for an unknown. -i will try to isolate that variable on one side of 88 | a comparison or assignment operator (:, =, <, or >), it appends = 0 to the equation if no equality operator is present. 89 | For more information on how to write simplify expressions see simplify(7).

90 | 91 |

OPTIONS

92 | 93 |
94 |
-q, --quiet

Only print errors, ignore all the expressions' results

95 |
-v, --verbose

Print status updates while running

96 |
-f, --file=[FILE]

Evaluate FILE before any other expressions.

97 |
-i, --isolate=[VARIABLE]

Try to isolate VARIABLE on one side of an equality operator.

98 |
-d, --define=[VARIABLE=EXPRESSION]

Define VARIABLE as EXPRESSION, do try to evaluate either side simplify

99 |
100 | 101 | 102 |

SEE ALSO

103 | 104 |

simplify(7)

105 | 106 | 107 |
    108 |
  1. 109 |
  2. April 2018
  3. 110 |
  4. simplify(1)
  5. 111 |
112 | 113 |
114 | 115 | 116 | -------------------------------------------------------------------------------- /docs/simplify.1.md: -------------------------------------------------------------------------------- 1 | simplify(1) -- rewrite a mathematical expression in it's simplest terms 2 | ======================================================================== 3 | 4 | ## SYNOPSIS 5 | 6 | `simplify` [__-q__] [__-v__] [__-f__ _FILE_] [__-i__ _VARIABLE_] [__-d__ _VARIABLE_=_EXPR_] ...EXPRESSION 7 | 8 | ## DESCIPTION 9 | 10 | Simplify takes a series of expression and tries to reduce them to their simplest form. 11 | Simplify may just evaluate the expression, if that is possible, for example, `2 + 2` will be 12 | `simplify`'d to `4`. 13 | 14 | Simplify takes a series of expressions, it prints a simplified version for each of them. 15 | 16 | Simplify can also work with unknowns. By default, it will try to evaluate as much as possible around any unknowns. 17 | By using the `-i` flag, simplify can also solve for an unknown. `-i` will try to isolate that variable on one side of 18 | a comparison or assignment operator (`:`, `=`, `<`, or `>`), it appends `= 0` to the equation if no equality operator is present. 19 | For more information on how to write `simplify` expressions see simplify(7). 20 | 21 | ## OPTIONS 22 | 23 | * `-q`, `--quiet`: 24 | Only print errors, ignore all the expressions' results 25 | 26 | * `-v`, `--verbose`: 27 | Print status updates while running 28 | 29 | * `-f`, `--file`=[__FILE__]: 30 | Evaluate `FILE` before any other expressions. 31 | 32 | * `-i`, `--isolate`=[__VARIABLE__]: 33 | Try to isolate __VARIABLE__ on one side of an equality operator. 34 | 35 | * `-d`, `--define`=[__VARIABLE__=__EXPRESSION__]: 36 | Define __VARIABLE__ as __EXPRESSION__, do try to evaluate either side simplify 37 | 38 | ## SEE ALSO 39 | 40 | simplify(7) -------------------------------------------------------------------------------- /docs/simplify.7: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "SIMPLIFY" "7" "April 2018" "" "" 5 | . 6 | .SH "NAME" 7 | \fBsimplify\fR \- expression syntax for simplify 8 | . 9 | .SH "SYNOPSIS" 10 | . 11 | .SS "Operator List" 12 | . 13 | .IP "1." 4 14 | \fB+\fR addition, or as a prefix to make a number positive\. 15 | . 16 | .IP "2." 4 17 | \fB\-\fR subtraction, or as a prefix to make a number negative\. 18 | . 19 | .IP "3." 4 20 | \fB*\fR multiplication 21 | . 22 | .IP "4." 4 23 | \fB/\fR division 24 | . 25 | .IP "5." 4 26 | \fB^\fR exponent 27 | . 28 | .IP "6." 4 29 | \fB\e\fR root 30 | . 31 | .IP "7." 4 32 | \fB=\fR equality 33 | . 34 | .IP "8." 4 35 | \fB<\fR less than 36 | . 37 | .IP "9." 4 38 | \fB>\fR greater than 39 | . 40 | .IP "10." 4 41 | \fB:\fR assignment 42 | . 43 | .IP "" 0 44 | . 45 | .SS "Variable syntax:" 46 | Variables are a sequence of at least one letter (uppercase or lowercase), or underscores\. Variables can be assigned to with the \fB:\fR operator\. 47 | . 48 | .SS "Function syntax" 49 | Function names follow the same rules as variable names\. Following the function\'s name is a parameter list surrounded by parentheses\. Parameters are separated by commas\. The parameter list cannot be empty\. 50 | . 51 | .SH "DESCRIPTION" 52 | . 53 | .SS "Operators 1 \- 4, Basic Operations" 54 | The first 4 operators (\fB+\fR, \fB\-\fR, \fB*\fR and \fB/\fR) perform basic arithmetic operations\. For the sake of brevity in expressions, it\'s possible to omit them in some cases\. 55 | . 56 | .P 57 | When a number and a variable are adjacent, in any order, a multiplication operator is implied\. For instance \fB2x\fR and \fBx2\fR are equivalent to \fB2 * x\fR\. Similarly, when a number is used as a prefix to left parentheses, the multiplication operator is implied\. For example \fB2(x + 5)\fR is equivalent to \fB2 * (x + 5)\fR\. 58 | . 59 | .SS "Operators 5 \- 6, Exponents and Roots" 60 | Operators 5 and 6 (\fB^\fR and \fB\e\fR) perform power and root operations\. 61 | . 62 | .P 63 | The \fB^\fR operator\'s left operand is the number to be operated on\. The right operand is the exponent\. For instance \fB2^4\fR is equivalent to \fB2 * 2 * 2 * 2\fR\. 64 | . 65 | .P 66 | The \fB\e\fR operator\'s left operand is the number to be operated on\. The right operand is the root\. The root \fBmust\fR be an integer, if it is not it will be rounded, For example \fB27 \e 3\.34\fR is equivalent to \fB27 \e 3\fR\. 67 | . 68 | .SS "Operators 7 \- 9, Comparison Operators" 69 | Operators 7 to 9 (\fB=\fR, \fB<\fR, and \fB>\fR) are the comparison operators\. They imply that the user is comparing their left and right operands\. Depending on the how the \fBsimplify\fR command was invoked they may perform different operations\. 70 | . 71 | .P 72 | By default, these operators check if their left operand is \fIequal\fR, \fIless than\fR, or \fIgreater than\fR, their right operand, respectively\. If all equality operations in an expression are true then the expression\'s result is \fBtrue\fR otherwise the expression\'s result is \fBfalse\fR 73 | . 74 | .P 75 | Alternatively, if the \fB\-i\fR flag was specified with a variable the expression will not evaluate to \fBtrue\fR or \fBfalse\fR\. Instead the variable specified will be isolated on one side of an equality operator\. Note the value of the expression does not change, this operation only makes the variable\'s value more clear\. 76 | . 77 | .SS "Operator 10, Assignment" 78 | Operator 10 (\fB:\fR) is the assignment operator\. It assumes its left operand is a variable or function\. If it\'s left operand is variable than the right operand is simplified and then assigned to it\'s left operand\. Otherwise, the left operand is immediately assigned to the function named in the right operand\. 79 | . 80 | .SS "Parentheses" 81 | Parentheses serve two purposes: to encase functions parameters or to indicate an expression should have a higher priority\. The latter case is assumed if the parentheses are not immediately preceded by an identifier\. If \fB(\fR occurs, then a \fB)\fR must follow eventually, otherwise \fBsimplify\fR throws an error\. 82 | . 83 | .SS "Variables" 84 | Variables associate a name with an expression\. The name consists of lowercase letters, capital letters, and underscores\. Variables can be assigned to using the \fB:\fR operator\. The expression being assigned will be simplified before assignment\. 85 | . 86 | .SS "Functions" 87 | Functions are similar to variables, the only difference is they take a parameter list when defined or called, and their definition is not evaluated\. Function\'s argument list is surrounded by parentheses (\fB(\fR and \fB)\fR)\. When defining a function every argument must contain a variable\. Arguments are separated by commas (\fB,\fR)\. A function may not have zero arguments\. 88 | . 89 | .P 90 | Function parameters can be defined as an expression\. When the function is called the variable in the expression will be isolated automatically\. 91 | . 92 | .SH "EXAMPLES" 93 | If an expression can be evaluated it will be: 94 | . 95 | .P 96 | \fB$ simplify \'2 + 2\'\fR 97 | . 98 | .P 99 | \fB4\fR 100 | . 101 | .P 102 | If part of an expression is unknown, that part will be ignored: 103 | . 104 | .P 105 | \fB$ simplify \'x + 2 ^ 5\'\fR 106 | . 107 | .P 108 | \fBx + 32\fR 109 | . 110 | .P 111 | Variables can be assigned to using the \fB:\fR operator: 112 | . 113 | .P 114 | \fB$ simplify \'x : 2 + 5\'\fR 115 | . 116 | .P 117 | \fB7\fR 118 | . 119 | .P 120 | Functions are similar to variables, except they take an argument list: 121 | . 122 | .P 123 | \fB$ simplify \'f(x) : x * 3\' \'f(10)\'\fR 124 | . 125 | .P 126 | \fBx * 3\fR 127 | . 128 | .P 129 | \fB30\fR 130 | . 131 | .P 132 | Functions can take multiple arguments: 133 | . 134 | .P 135 | \fB$ simplify \'avg(x, y) : (x + y) / 2\' \'avg(5, 10)\'\fR 136 | . 137 | .P 138 | \fB(x + y) / 2\fR 139 | . 140 | .P 141 | \fB7\.5\fR 142 | -------------------------------------------------------------------------------- /docs/simplify.7.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | simplify(7) - expression syntax for simplify 7 | 44 | 45 | 52 | 53 |
54 | 55 | 61 | 62 |
    63 |
  1. simplify(7)
  2. 64 |
  3. 65 |
  4. simplify(7)
  5. 66 |
67 | 68 |

NAME

69 |

70 | simplify - expression syntax for simplify 71 |

72 | 73 |

SYNOPSIS

74 | 75 |

Operator List

76 | 77 |
    78 |
  1. + addition, or as a prefix to make a number positive.
  2. 79 |
  3. - subtraction, or as a prefix to make a number negative.
  4. 80 |
  5. * multiplication
  6. 81 |
  7. / division
  8. 82 |
  9. ^ exponent
  10. 83 |
  11. \ root
  12. 84 |
  13. = equality
  14. 85 |
  15. < less than
  16. 86 |
  17. > greater than
  18. 87 |
  19. : assignment
  20. 88 |
89 | 90 | 91 |

Variable syntax:

92 | 93 |

Variables are a sequence of at least one letter (uppercase or lowercase), or underscores. 94 | Variables can be assigned to with the : operator.

95 | 96 |

Function syntax

97 | 98 |

Function names follow the same rules as variable names. Following the function's name is a parameter list 99 | surrounded by parentheses. Parameters are separated by commas. The parameter list cannot be empty.

100 | 101 |

DESCRIPTION

102 | 103 |

Operators 1 - 4, Basic Operations

104 | 105 |

The first 4 operators (+, -, * and /) perform basic arithmetic operations. 106 | For the sake of brevity in expressions, it's possible to omit them in some cases.

107 | 108 |

When a number and a variable are adjacent, in any order, a multiplication operator is implied. 109 | For instance 2x and x2 are equivalent to 2 * x. Similarly, when a number is used as a prefix 110 | to left parentheses, the multiplication operator is implied. For example 2(x + 5) is equivalent to 111 | 2 * (x + 5).

112 | 113 |

Operators 5 - 6, Exponents and Roots

114 | 115 |

Operators 5 and 6 (^ and \) perform power and root operations.

116 | 117 |

The ^ operator's left operand is the number to be operated on. The right operand 118 | is the exponent. For instance 2^4 is equivalent to 2 * 2 * 2 * 2.

119 | 120 |

The \ operator's left operand is the number to be operated on. The right operand 121 | is the root. The root must be an integer, if it is not it will be rounded, 122 | For example 27 \ 3.34 is equivalent to 27 \ 3.

123 | 124 |

Operators 7 - 9, Comparison Operators

125 | 126 |

Operators 7 to 9 (=, <, and >) are the comparison operators. They imply 127 | that the user is comparing their left and right operands. Depending on the 128 | how the simplify command was invoked they may perform different operations.

129 | 130 |

By default, these operators check if their left operand is 131 | equal, less than, or greater than, their right operand, respectively. 132 | If all equality operations in an expression are true then the expression's result 133 | is true otherwise the expression's result is false

134 | 135 |

Alternatively, if the -i flag was specified with a variable the expression will not 136 | evaluate to true or false. Instead the variable specified will be isolated on one side 137 | of an equality operator. Note the value of the expression does not change, this operation only makes the 138 | variable's value more clear.

139 | 140 |

Operator 10, Assignment

141 | 142 |

Operator 10 (:) is the assignment operator. It assumes its left operand is a variable or function. 143 | If it's left operand is variable than the right operand is simplified and then assigned to it's left operand. 144 | Otherwise, the left operand is immediately assigned to the function named in the right operand.

145 | 146 |

Parentheses

147 | 148 |

Parentheses serve two purposes: to encase functions parameters or to indicate an expression should have a higher 149 | priority. The latter case is assumed if the parentheses are not immediately preceded by an identifier. If ( occurs, 150 | then a ) must follow eventually, otherwise simplify throws an error.

151 | 152 |

Variables

153 | 154 |

Variables associate a name with an expression. The name consists of lowercase letters, capital letters, and underscores. 155 | Variables can be assigned to using the : operator. The expression being assigned will be simplified before assignment.

156 | 157 |

Functions

158 | 159 |

Functions are similar to variables, the only difference is they take a parameter list when defined or called, and their definition 160 | is not evaluated. Function's argument list is surrounded by parentheses (( and )). When defining a function every argument must 161 | contain a variable. Arguments are separated by commas (,). A function may not have zero arguments.

162 | 163 |

Function parameters can be defined as an expression. When the function is called the variable in the expression will be isolated 164 | automatically.

165 | 166 |

EXAMPLES

167 | 168 |

If an expression can be evaluated it will be:

169 | 170 |

$ simplify '2 + 2'

171 | 172 |

4

173 | 174 |

If part of an expression is unknown, that part will be ignored:

175 | 176 |

$ simplify 'x + 2 ^ 5'

177 | 178 |

x + 32

179 | 180 |

Variables can be assigned to using the : operator:

181 | 182 |

$ simplify 'x : 2 + 5'

183 | 184 |

7

185 | 186 |

Functions are similar to variables, except they take an argument list:

187 | 188 |

$ simplify 'f(x) : x * 3' 'f(10)'

189 | 190 |

x * 3

191 | 192 |

30

193 | 194 |

Functions can take multiple arguments:

195 | 196 |

$ simplify 'avg(x, y) : (x + y) / 2' 'avg(5, 10)'

197 | 198 |

(x + y) / 2

199 | 200 |

7.5

201 | 202 | 203 |
    204 |
  1. 205 |
  2. April 2018
  3. 206 |
  4. simplify(7)
  5. 207 |
208 | 209 |
210 | 211 | 212 | -------------------------------------------------------------------------------- /docs/simplify.7.md: -------------------------------------------------------------------------------- 1 | simplify(7) -- expression syntax for simplify 2 | ============================================= 3 | 4 | ## SYNOPSIS 5 | 6 | ### Operator List 7 | 8 | 1. `+` addition, or as a prefix to make a number positive. 9 | 2. `-` subtraction, or as a prefix to make a number negative. 10 | 3. `*` multiplication 11 | 4. `/` division 12 | 5. `^` exponent 13 | 6. `\` root 14 | 7. `=` equality 15 | 8. `<` less than 16 | 9. `>` greater than 17 | 10. `:` assignment 18 | 19 | ### Variable syntax: 20 | 21 | Variables are a sequence of at least one letter (uppercase or lowercase), or underscores. 22 | Variables can be assigned to with the `:` operator. 23 | 24 | ### Function syntax 25 | 26 | Function names follow the same rules as variable names. Following the function's name is a parameter list 27 | surrounded by parentheses. Parameters are separated by commas. The parameter list cannot be empty. 28 | 29 | ## DESCRIPTION 30 | 31 | ### Operators 1 - 4, Basic Operations 32 | 33 | The first 4 operators (`+`, `-`, `*` and `/`) perform basic arithmetic operations. 34 | For the sake of brevity in expressions, it's possible to omit them in some cases. 35 | 36 | When a number and a variable are adjacent, in any order, a multiplication operator is implied. 37 | For instance `2x` and `x2` are equivalent to `2 * x`. Similarly, when a number is used as a prefix 38 | to left parentheses, the multiplication operator is implied. For example `2(x + 5)` is equivalent to 39 | `2 * (x + 5)`. 40 | 41 | ### Operators 5 - 6, Exponents and Roots 42 | 43 | Operators 5 and 6 (`^` and `\`) perform power and root operations. 44 | 45 | The `^` operator's left operand is the number to be operated on. The right operand 46 | is the exponent. For instance `2^4` is equivalent to `2 * 2 * 2 * 2`. 47 | 48 | The `\` operator's left operand is the number to be operated on. The right operand 49 | is the root. The root __must__ be an integer, if it is not it will be rounded, 50 | For example `27 \ 3.34` is equivalent to `27 \ 3`. 51 | 52 | ### Operators 7 - 9, Comparison Operators 53 | 54 | Operators 7 to 9 (`=`, `<`, and `>`) are the comparison operators. They imply 55 | that the user is comparing their left and right operands. Depending on the 56 | how the `simplify` command was invoked they may perform different operations. 57 | 58 | By default, these operators check if their left operand is 59 | _equal_, _less than_, or _greater than_, their right operand, respectively. 60 | If all equality operations in an expression are true then the expression's result 61 | is `true` otherwise the expression's result is `false` 62 | 63 | Alternatively, if the `-i` flag was specified with a variable the expression will not 64 | evaluate to `true` or `false`. Instead the variable specified will be isolated on one side 65 | of an equality operator. Note the value of the expression does not change, this operation only makes the 66 | variable's value more clear. 67 | 68 | ### Operator 10, Assignment 69 | 70 | Operator 10 (`:`) is the assignment operator. It assumes its left operand is a variable or function. 71 | If it's left operand is variable than the right operand is simplified and then assigned to it's left operand. 72 | Otherwise, the left operand is immediately assigned to the function named in the right operand. 73 | 74 | ### Parentheses 75 | 76 | Parentheses serve two purposes: to encase functions parameters or to indicate an expression should have a higher 77 | priority. The latter case is assumed if the parentheses are not immediately preceded by an identifier. If `(` occurs, 78 | then a `)` must follow eventually, otherwise `simplify` throws an error. 79 | 80 | ### Variables 81 | 82 | Variables associate a name with an expression. The name consists of lowercase letters, capital letters, and underscores. 83 | Variables can be assigned to using the `:` operator. The expression being assigned will be simplified before assignment. 84 | 85 | ### Functions 86 | 87 | Functions are similar to variables, the only difference is they take a parameter list when defined or called, and their definition 88 | is not evaluated. Function's argument list is surrounded by parentheses (`(` and `)`). When defining a function every argument must 89 | contain a variable. Arguments are separated by commas (`,`). A function may not have zero arguments. 90 | 91 | Function parameters can be defined as an expression. When the function is called the variable in the expression will be isolated 92 | automatically. 93 | 94 | ## EXAMPLES 95 | 96 | If an expression can be evaluated it will be: 97 | 98 | `$ simplify '2 + 2'` 99 | 100 | `4` 101 | 102 | If part of an expression is unknown, that part will be ignored: 103 | 104 | `$ simplify 'x + 2 ^ 5'` 105 | 106 | `x + 32` 107 | 108 | Variables can be assigned to using the `:` operator: 109 | 110 | `$ simplify 'x : 2 + 5'` 111 | 112 | `7` 113 | 114 | Functions are similar to variables, except they take an argument list: 115 | 116 | `$ simplify 'f(x) : x * 3' 'f(10)'` 117 | 118 | `x * 3` 119 | 120 | `30` 121 | 122 | Functions can take multiple arguments: 123 | 124 | `$ simplify 'avg(x, y) : (x + y) / 2' 'avg(5, 10)'` 125 | 126 | `(x + y) / 2` 127 | 128 | `7.5` 129 | -------------------------------------------------------------------------------- /get-deps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | set -e 3 | 4 | MPFR_VERSION="4.0.1" 5 | GMP_VERSION="6.1.2" 6 | 7 | ROOT_DIR="$PWD/deps/root" 8 | 9 | CURL=`which curl` 10 | TAR=`which tar` 11 | MAKE=`which make` 12 | 13 | mkdir -p deps 14 | 15 | if [[ ! -d deps/mpfr-$MPFR_VERSION ]]; then 16 | $CURL "http://www.mpfr.org/mpfr-current/mpfr-$MPFR_VERSION.tar.xz" | $TAR -xJC deps 17 | fi 18 | 19 | if [[ ! -d deps/gmp-$GMP_VERSION ]]; then 20 | $CURL "https://gmplib.org/download/gmp/gmp-$GMP_VERSION.tar.xz" | $TAR -xJC deps 21 | fi 22 | 23 | if [[ ! -f deps/cpplint.py ]]; then 24 | $CURL https://raw.githubusercontent.com/cpplint/cpplint/master/cpplint.py > deps/cpplint.py 25 | fi 26 | 27 | # Build GMP 28 | if [[ ! -f $ROOT_DIR/usr/local/lib/libgmp.a ]]; then 29 | pushd deps/gmp-$GMP_VERSION 30 | 31 | mkdir -p build && cd build 32 | ../configure 33 | $MAKE 34 | $MAKE install DESTDIR="$ROOT_DIR" 35 | popd 36 | fi 37 | 38 | # Build MPFR 39 | if [[ ! -f $ROOT_DIR/usr/local/lib/libmpfr.a ]]; then 40 | pushd deps/mpfr-$MPFR_VERSION 41 | 42 | mkdir -p build && cd build 43 | ../configure --with-gmp-build=../../gmp-$GMP_VERSION/build 44 | $MAKE 45 | $MAKE install DESTDIR="$ROOT_DIR" 46 | 47 | popd 48 | fi -------------------------------------------------------------------------------- /src/flags/flags.h: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #ifndef FLAGS_FLAGS_H_ 4 | #define FLAGS_FLAGS_H_ 5 | 6 | #define FLAG(SHORT, LONG, RESULT) \ 7 | if ((*flag_argv)[1] == (SHORT) \ 8 | || ((*flag_argv)[2] && strcmp(LONG, (*flag_argv) + 2) == 0)) { \ 9 | RESULT; \ 10 | } else 11 | 12 | #define PARSE_FLAGS(FLAGS) _PARSE_FLAGS(argv, argc, FLAGS) 13 | 14 | #define _PARSE_FLAGS(ARGV, ARGC, BLOCK) \ 15 | char** flag_argv = (ARGV) + 1; \ 16 | int flag_argc = (ARGC) - 1; \ 17 | { \ 18 | for (; flag_argc != 0; ++flag_argv, --flag_argc) { \ 19 | if ((*flag_argv)[0] == '-') { \ 20 | BLOCK \ 21 | { \ 22 | printf("unkown argument '%s'\n", (*flag_argv)); \ 23 | exit(1); \ 24 | } \ 25 | } else { \ 26 | break; \ 27 | } \ 28 | } \ 29 | } \ 30 | 31 | 32 | #define FLAG_VALUE (((*flag_argv)[1] != '-' && (*flag_argv)[2] != 0) ? \ 33 | (*flag_argv) + 2 : ((flag_argc >= 0 && --flag_argc >= 0) ? *(++flag_argv) : "")) 34 | 35 | 36 | #endif // FLAGS_FLAGS_H_ 37 | -------------------------------------------------------------------------------- /src/simplify/builtins.h: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #ifndef SIMPLIFY_BUILTINS_H_ 4 | #define SIMPLIFY_BUILTINS_H_ 5 | 6 | /* this file includes utilities for creating builtin functions and constants */ 7 | 8 | #define DEFINE_MPFR_FUNCTION(NAME) \ 9 | error_t builtin_func_ ## NAME(scope_t* scope, expression_t** out) { \ 10 | expression_t input; \ 11 | scope_get_value(scope, "__arg0", &input); \ 12 | if (!EXPRESSION_IS_NUMBER(&input)) { \ 13 | return ERROR_NO_ERROR; \ 14 | expression_clean(&input); \ 15 | } \ 16 | mpfr_ptr num = malloc(sizeof(mpfr_t)); \ 17 | mpfr_init(num); \ 18 | mpfr_## NAME(num, input.number.value, MPFR_RNDN); \ 19 | *out = expression_new_number(num); \ 20 | expression_clean(&input); \ 21 | return ERROR_NO_ERROR; \ 22 | } 23 | 24 | #define DEFINE_MPFR_FUNCTION_NRND(NAME) \ 25 | error_t builtin_func_ ## NAME(scope_t* scope, expression_t** out) { \ 26 | expression_t input; \ 27 | scope_get_value(scope, "__arg0", &input); \ 28 | if (!EXPRESSION_IS_NUMBER(&input)) { \ 29 | return ERROR_NO_ERROR; \ 30 | expression_clean(&input); \ 31 | } \ 32 | mpfr_ptr num = malloc(sizeof(mpfr_t)); \ 33 | mpfr_init(num); \ 34 | mpfr_## NAME(num, input.number.value); \ 35 | *out = expression_new_number(num); \ 36 | expression_clean(&input); \ 37 | return ERROR_NO_ERROR; \ 38 | } 39 | 40 | #define DEFINE_MPFR_FUNCTION2(NAME) \ 41 | error_t builtin_func_ ## NAME(scope_t* scope, expression_t** out) { \ 42 | expression_t input; \ 43 | expression_t input2; \ 44 | scope_get_value(scope, "__arg0", &input); \ 45 | scope_get_value(scope, "__arg1", &input2); \ 46 | if (!EXPRESSION_IS_NUMBER(&input) || !EXPRESSION_IS_NUMBER(&input2)) { \ 47 | expression_clean(&input); \ 48 | expression_clean(&input2); \ 49 | return ERROR_NO_ERROR; \ 50 | } \ 51 | mpfr_ptr num = malloc(sizeof(mpfr_t)); \ 52 | mpfr_init(num); \ 53 | mpfr_## NAME(num, input.number.value, input2.number.value, MPFR_RNDN); \ 54 | *out = expression_new_number(num); \ 55 | expression_clean(&input); \ 56 | expression_clean(&input2); \ 57 | return ERROR_NO_ERROR; \ 58 | } 59 | 60 | #define EXPORT_BUILTIN_FUNCTION(SCOPE, NAME) \ 61 | scope_define_internal_function((SCOPE), #NAME, builtin_func_ ## NAME, 1, "__arg0"); 62 | 63 | #define EXPORT_BUILTIN_FUNCTION2(SCOPE, NAME) \ 64 | scope_define_internal_function((SCOPE), #NAME, builtin_func_ ## NAME, 2, "__arg0", "__arg1"); 65 | 66 | #define DEFINE_MPFR_CONST(NAME) \ 67 | error_t builtin_const_ ## NAME(scope_t* scope, expression_t** out) { \ 68 | (void)scope; \ 69 | mpfr_ptr num = malloc(sizeof(mpfr_t)); \ 70 | mpfr_init(num); \ 71 | mpfr_const_ ## NAME(num, MPFR_RNDF); \ 72 | *out = expression_new_number(num); \ 73 | return ERROR_NO_ERROR; \ 74 | } 75 | 76 | #define EXPORT_BUILTIN_CONST(SCOPE, NAME) \ 77 | scope_define_internal_const((SCOPE), #NAME, builtin_const_ ## NAME); 78 | 79 | #define ALIAS(SCOPE, X, Y) scope_define_constant((SCOPE), #X, expression_new_variable(#Y)) 80 | 81 | #endif // SIMPLIFY_BUILTINS_H_ 82 | -------------------------------------------------------------------------------- /src/simplify/errors.h: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #ifndef SIMPLIFY_ERRORS_H_ 4 | #define SIMPLIFY_ERRORS_H_ 5 | 6 | #include 7 | #include 8 | 9 | /* Enumerates all errors that may occur. 10 | * Error also has the value `ERROR_NO_ERROR`, meaning no error occurred. This value 11 | * will always be zero, so it's safe to check if an error occurred with `if (error) { ... }`. 12 | */ 13 | typedef enum error error_t; 14 | 15 | enum error { 16 | ERROR_NO_ERROR = 0, 17 | ERROR_INVALID_TOKEN, 18 | ERROR_INVALID_CHARACTER, 19 | ERROR_INVALID_PREFIX, 20 | ERROR_INVALID_OPERATOR, 21 | ERROR_INVALID_NUMBER, 22 | ERROR_INVALID_IDENTIFIER, 23 | 24 | ERROR_FAILED_TO_ALLOCATE, 25 | ERROR_FAILED_TO_REALLOCATE, 26 | 27 | ERROR_UNABLE_TO_OPEN_FILE, 28 | ERROR_FILE_CLOSED_UNEXPECTEDLY, 29 | 30 | ERROR_NUMBER_IS_NAN, 31 | ERROR_NUMBER_IS_INFINITY, 32 | 33 | ERROR_CANNOT_COMPARE, 34 | 35 | ERROR_UNEXPECTED_EOF, 36 | ERROR_UNEXPECTED_EOL, 37 | ERROR_STRAY_RIGHT_PAREN, 38 | ERROR_STRAY_LEFT_PAREN, 39 | 40 | ERROR_NONEXISTANT_KEY, 41 | ERROR_VARIABLE_NOT_PRESENT, 42 | 43 | ERROR_INVALID_ASSIGNMENT_EXPRESSION, 44 | ERROR_UNRECOGNIZED_ARGUMENT, 45 | 46 | ERROR_IS_A_FUNCTION, 47 | ERROR_IS_A_VARIABLE, 48 | ERROR_MISSING_ARGUMENTS, 49 | }; 50 | 51 | /* get a description of the error 52 | * 53 | * @err the error to describe 54 | * @return returns a static string describing the error 55 | */ 56 | static inline const char* error_string(error_t err) { 57 | switch (err) { 58 | case ERROR_NO_ERROR: 59 | return "no error"; 60 | case ERROR_INVALID_TOKEN: 61 | return "invalid token"; 62 | case ERROR_INVALID_CHARACTER: 63 | return "invalid character"; 64 | case ERROR_INVALID_PREFIX: 65 | return "invalid prefix operator"; 66 | case ERROR_INVALID_OPERATOR: 67 | return "invalid operator"; 68 | case ERROR_INVALID_NUMBER: 69 | return "invalid number"; 70 | case ERROR_INVALID_IDENTIFIER: 71 | return "invalid identifier"; 72 | case ERROR_FAILED_TO_ALLOCATE: 73 | return "failed to allocate memory"; 74 | case ERROR_FAILED_TO_REALLOCATE: 75 | return "failed to reallocate buffer"; 76 | case ERROR_UNABLE_TO_OPEN_FILE: 77 | return "failed to open file"; 78 | case ERROR_FILE_CLOSED_UNEXPECTEDLY: 79 | return "failed to close file"; 80 | case ERROR_NUMBER_IS_NAN: 81 | return "number is NaN"; 82 | case ERROR_NUMBER_IS_INFINITY: 83 | return "number is Infinity"; 84 | case ERROR_CANNOT_COMPARE: 85 | return "cannot compare values"; 86 | case ERROR_UNEXPECTED_EOF: 87 | return "unexpected end of equation"; 88 | case ERROR_STRAY_RIGHT_PAREN: 89 | return "random right parentheses in expression"; 90 | case ERROR_STRAY_LEFT_PAREN: 91 | return "random left parentheses in expression"; 92 | case ERROR_NONEXISTANT_KEY: 93 | return "no value associated with key"; 94 | case ERROR_VARIABLE_NOT_PRESENT: 95 | return "that variable was not found in this expression"; 96 | case ERROR_INVALID_ASSIGNMENT_EXPRESSION: 97 | return "expected an expression in the form of VARIABLE=EXPRESSION"; 98 | case ERROR_UNRECOGNIZED_ARGUMENT: 99 | return "invalid command line argument"; 100 | case ERROR_UNEXPECTED_EOL: 101 | return "line ended prematurely"; 102 | case ERROR_IS_A_FUNCTION: 103 | return "you're trying to get the value of a function, instead call it using parentheses."; 104 | case ERROR_IS_A_VARIABLE: 105 | return "you're trying to call a variable!"; 106 | case ERROR_MISSING_ARGUMENTS: 107 | return "missing arguments to function call"; 108 | } 109 | return "unkown error type"; 110 | } 111 | 112 | 113 | #endif // SIMPLIFY_ERRORS_H_ 114 | -------------------------------------------------------------------------------- /src/simplify/expression/evaluate.c: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #include "simplify/expression/isolate.h" 4 | #include "simplify/expression/evaluate.h" 5 | 6 | /* evaluate an expression, try to put it in it's simplest terms. Only apply operator and expand variables and function. 7 | * 8 | * @expr the expression to evaluate 9 | * @scope the expression's scope 10 | * @return returns an error code 11 | */ 12 | error_t _expression_evaluate_recursive(expression_t* expr, scope_t* scope); 13 | 14 | /* apply a assignment operator expression, assume the left side of the expression contains a variable or function. 15 | * 16 | * @expr the expression to apply 17 | * @scope the expression's scope 18 | * @return returns an error code 19 | */ 20 | error_t _expression_apply_assignment(expression_t* expr, scope_t* scope) { 21 | assert(EXPRESSION_IS_OPERATOR(expr)); 22 | 23 | error_t err; 24 | if (EXPRESSION_IS_FUNCTION(expr->operator.left)) { 25 | expression_t* body = malloc(sizeof(expression_t)); 26 | expression_list_t* params = malloc(sizeof(expression_list_t)); 27 | 28 | expression_list_init(params); 29 | expression_list_copy(expr->operator.left->function.parameters, params); 30 | expression_copy(expr->operator.right, body); 31 | 32 | err = scope_define_function(scope, expr->operator.left->function.name, body, params); 33 | if (err) return err; 34 | 35 | expression_collapse_left(expr); 36 | } else { 37 | err = _expression_evaluate_recursive(expr->operator.right, scope); 38 | if (err) return err; 39 | 40 | /* if there's not a variable on the left try to simplify the expression to get one */ 41 | if (!EXPRESSION_IS_VARIABLE(expr->operator.left)) { 42 | err = _expression_evaluate_recursive(expr->operator.left, scope); 43 | if (err) return err; 44 | 45 | variable_t varname = expression_find_variable(expr->operator.left); 46 | if (varname) { 47 | expression_isolate_variable(expr, varname); 48 | err = _expression_evaluate_recursive(expr->operator.right, scope); 49 | if (err) return err; 50 | } 51 | } 52 | 53 | if (EXPRESSION_IS_VARIABLE(expr->operator.left)) { 54 | expression_t* value_copy = malloc(sizeof(expression_t)); 55 | expression_copy(expr->operator.right, value_copy); 56 | scope_define(scope, expr->operator.left->variable.value, value_copy); 57 | 58 | expression_collapse_left(expr); 59 | } 60 | } 61 | 62 | return err; 63 | } 64 | 65 | /* apply a prefix expression's prefix to it's right arm 66 | * 67 | * @expr the prefix expression to apply 68 | * @scope the expression's scope 69 | * @return returns an error code 70 | */ 71 | error_t _expression_apply_prefix(expression_t* expr, scope_t* scope) { 72 | assert(EXPRESSION_IS_PREFIX(expr)); 73 | error_t err; 74 | 75 | err = _expression_evaluate_recursive(expr->prefix.right, scope); 76 | if (err) return err; 77 | 78 | if (EXPRESSION_IS_NUMBER(EXPRESSION_RIGHT(expr))) { 79 | switch (expr->prefix.prefix) { 80 | case '+': 81 | break; 82 | case '-': 83 | mpfr_neg(expr->prefix.right->number.value, expr->prefix.right->number.value, MPFR_RNDF); 84 | break; 85 | default: 86 | return ERROR_INVALID_PREFIX; 87 | } 88 | expression_collapse_left(expr); 89 | } 90 | 91 | return ERROR_NO_ERROR; 92 | } 93 | 94 | /* apply an operator expression. 95 | * 96 | * @expr input operator expression 97 | * @scope the expression's scope 98 | * @return returns an error code 99 | */ 100 | error_t _expression_apply_operator(expression_t* expr) { 101 | assert(EXPRESSION_IS_OPERATOR(expr)); 102 | 103 | static const int round_mode = MPFR_RNDF; 104 | 105 | mpfr_ptr result; 106 | 107 | mpfr_ptr left = expr->operator.left->number.value; 108 | mpfr_ptr right = expr->operator.right->number.value; 109 | 110 | switch (expr->operator.infix) { 111 | case '+': 112 | result = malloc(sizeof(mpfr_t)); 113 | mpfr_init(result); 114 | mpfr_add(result, left, right, round_mode); 115 | break; 116 | case '-': 117 | result = malloc(sizeof(mpfr_t)); 118 | mpfr_init(result); 119 | mpfr_sub(result, left, right, round_mode); 120 | break; 121 | case '/': 122 | result = malloc(sizeof(mpfr_t)); 123 | mpfr_init(result); 124 | mpfr_div(result, left, right, round_mode); 125 | break; 126 | case '*': 127 | case '(': 128 | result = malloc(sizeof(mpfr_t)); 129 | mpfr_init(result); 130 | mpfr_mul(result, left, right, round_mode); 131 | break; 132 | case '^': 133 | result = malloc(sizeof(mpfr_t)); 134 | mpfr_init(result); 135 | mpfr_pow(result, left, right, round_mode); 136 | break; 137 | case '\\': 138 | result = malloc(sizeof(mpfr_t)); 139 | mpfr_init(result); 140 | mpfr_rootn_ui(result, left, mpfr_get_ui(right, MPFR_RNDN), round_mode); 141 | break; 142 | case '=': 143 | case '>': 144 | case '<': 145 | return ERROR_NO_ERROR; 146 | default: 147 | return ERROR_INVALID_OPERATOR; 148 | } 149 | 150 | expression_free(expr->operator.left); 151 | expression_free(expr->operator.right); 152 | 153 | expression_init_number(expr, result); 154 | 155 | return ERROR_NO_ERROR; 156 | } 157 | 158 | 159 | /* subsitute a variable expression with the variable's value, if available. 160 | * 161 | * This function will __not__ simplify the variable's result. 162 | * 163 | * @expr the variable expression to substitute 164 | * @scope the scope to search for the variable 165 | * @return returns an error code 166 | */ 167 | error_t _expression_substitute_variable(expression_t* expr, scope_t* scope) { 168 | assert(EXPRESSION_IS_VARIABLE(expr)); 169 | 170 | expression_t variable_value; 171 | error_t err; 172 | 173 | if (expr->variable.binding) 174 | err = scope_get_value(expr->variable.binding, expr->variable.value, &variable_value); 175 | else 176 | err = scope_get_value(scope, expr->variable.value, &variable_value); 177 | 178 | if (!err) { 179 | expression_clean(expr); 180 | *expr = variable_value; 181 | _expression_evaluate_recursive(expr, scope); 182 | } else if (!expr->variable.binding) { 183 | /* couldn't find the variable. let future executor know that this is the 184 | scope where the variable's value should be found */ 185 | expr->variable.binding = scope; 186 | } 187 | return ERROR_NO_ERROR; 188 | } 189 | 190 | error_t _expression_evaluate_recursive(expression_t* expr, scope_t* scope) { 191 | switch (expr->type) { 192 | case EXPRESSION_TYPE_NUMBER: 193 | /* numbers can't be evaluated */ 194 | break; 195 | case EXPRESSION_TYPE_VARIABLE: 196 | return _expression_substitute_variable(expr, scope); 197 | case EXPRESSION_TYPE_FUNCTION: 198 | { 199 | expression_t old = *expr; 200 | error_t err = scope_call(scope, expr->function.name, expr->function.parameters, expr); 201 | if (err && err != ERROR_NONEXISTANT_KEY) return err; 202 | if (!err) expression_clean(&old); 203 | return ERROR_NO_ERROR; 204 | } 205 | case EXPRESSION_TYPE_PREFIX: 206 | return _expression_apply_prefix(expr, scope); 207 | case EXPRESSION_TYPE_OPERATOR: 208 | { 209 | if (expr->operator.infix == ':') { 210 | return _expression_apply_assignment(expr, scope); 211 | } else { 212 | error_t err = _expression_evaluate_recursive(expr->operator.right, scope); 213 | if (err) return err; 214 | 215 | err = _expression_evaluate_recursive(expr->operator.left, scope); 216 | if (!expression_is_comparison(expr) && expr->operator.right->type == expr->operator.left->type && 217 | expr->operator.left->type == EXPRESSION_TYPE_NUMBER) { 218 | return _expression_apply_operator(expr); 219 | } 220 | return err; 221 | } 222 | } 223 | } 224 | return ERROR_NO_ERROR; 225 | } 226 | 227 | expression_result_t _expression_evaluate_comparisons_recursive(expression_t* expr) { 228 | expression_result_t result; 229 | 230 | switch (expr->type) { 231 | case EXPRESSION_TYPE_NUMBER: 232 | return EXPRESSION_RESULT_NONBINARY; 233 | case EXPRESSION_TYPE_VARIABLE: 234 | return EXPRESSION_RESULT_NONBINARY; 235 | case EXPRESSION_TYPE_FUNCTION: 236 | return EXPRESSION_RESULT_NONBINARY; 237 | case EXPRESSION_TYPE_PREFIX: 238 | return EXPRESSION_RESULT_NONBINARY; 239 | case EXPRESSION_TYPE_OPERATOR: 240 | { 241 | expression_result_t right = _expression_evaluate_comparisons_recursive(expr->operator.right); 242 | expression_result_t left = _expression_evaluate_comparisons_recursive(expr->operator.left); 243 | 244 | if (right == EXPRESSION_RESULT_FALSE || left == EXPRESSION_RESULT_FALSE) { 245 | result = EXPRESSION_RESULT_FALSE; 246 | break; 247 | } 248 | 249 | if (expression_is_comparison(expr)) { 250 | compare_result_t x = expression_compare(EXPRESSION_LEFT(expr), EXPRESSION_RIGHT(expr)); 251 | if (x == COMPARE_RESULT_INCOMPARABLE) 252 | return EXPRESSION_RESULT_NONBINARY; 253 | else if (expr->operator.infix == '<' && x == COMPARE_RESULT_LESS) 254 | result = EXPRESSION_RESULT_TRUE; 255 | else if (expr->operator.infix == '>' && x == COMPARE_RESULT_GREATER) 256 | result = EXPRESSION_RESULT_TRUE; 257 | else if (expr->operator.infix == '=' && x == COMPARE_RESULT_EQUAL) 258 | result = EXPRESSION_RESULT_TRUE; 259 | else 260 | result = EXPRESSION_RESULT_FALSE; 261 | break; 262 | } 263 | 264 | if (right == EXPRESSION_RESULT_TRUE || left == EXPRESSION_RESULT_TRUE) { 265 | result = EXPRESSION_RESULT_TRUE; 266 | } else { 267 | return EXPRESSION_RESULT_NONBINARY; 268 | } 269 | } 270 | } 271 | 272 | expression_collapse_left(expr); 273 | return result; 274 | } 275 | 276 | error_t expression_evaluate(expression_t* expr, scope_t* scope) { 277 | return _expression_evaluate_recursive(expr, scope); 278 | } 279 | 280 | expression_result_t expression_evaluate_comparisons(expression_t* expr) { 281 | return _expression_evaluate_comparisons_recursive(expr); 282 | } 283 | -------------------------------------------------------------------------------- /src/simplify/expression/evaluate.h: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #ifndef SIMPLIFY_EXPRESSION_EVALUATE_H_ 4 | #define SIMPLIFY_EXPRESSION_EVALUATE_H_ 5 | 6 | #include 7 | 8 | #include "simplify/expression/expression.h" 9 | 10 | /* The boolean result of an expression, No Result (if there was no condition operator), true or false */ 11 | typedef enum expression_result expression_result_t; 12 | 13 | enum expression_result { 14 | EXPRESSION_RESULT_NONBINARY, 15 | EXPRESSION_RESULT_TRUE, 16 | EXPRESSION_RESULT_FALSE, 17 | }; 18 | 19 | /* evaluate an expression as much as possible 20 | * 21 | * @expr the expression to simplify 22 | * @scope the where variables should be assigned and looked up. 23 | * @return returns an error 24 | */ 25 | error_t expression_evaluate(expression_t* expr, scope_t* scope); 26 | 27 | expression_result_t expression_evaluate_comparisons(expression_t* expr); 28 | 29 | #endif // SIMPLIFY_EXPRESSION_EVALUATE_H_ 30 | -------------------------------------------------------------------------------- /src/simplify/expression/expression.c: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #include "simplify/expression/expression.h" 4 | #include "simplify/expression/stringify.h" 5 | 6 | 7 | int index_of(char* x, char* y) { 8 | int i = 0; 9 | int yi = 0; 10 | while (x[i]) { 11 | if (!y[yi]) 12 | return i; 13 | if (y[yi] == x[i]) 14 | ++yi; 15 | else 16 | yi = 0; 17 | ++i; 18 | } 19 | 20 | return -1; 21 | } 22 | 23 | int strintcmp(char* x, char* y, int lenx, int leny, bool dec) { 24 | int xi = 0; 25 | int yi = 0; 26 | int cmp_mul = 1; 27 | 28 | int xp; 29 | int yp; 30 | 31 | if (!dec) { 32 | xp = pow(10, lenx - 1); 33 | yp = pow(10, leny - 1); 34 | } else { 35 | xp = 1; 36 | yp = 1; 37 | } 38 | 39 | if (y[0] == '-' || x[0] == '-') { 40 | if (y[0] != '-') { 41 | return -1; 42 | } else if (x[0] != '-') { 43 | return 1; 44 | } else { 45 | cmp_mul = -1; 46 | ++yi; 47 | ++xi; 48 | } 49 | } 50 | 51 | while (xi < lenx || yi < leny) { 52 | int cmp = cmp_mul * ((x[xi] * xp) - (y[yi] * yp)); 53 | if (cmp != 0) { 54 | return cmp; 55 | } 56 | 57 | if (!dec) { 58 | xp /= 10; 59 | yp /= 10; 60 | } else { 61 | xp *= 10; 62 | yp *= 10; 63 | } 64 | 65 | if (xi < lenx) 66 | ++xi; 67 | else 68 | xp = 0; 69 | 70 | if (yi < leny) 71 | ++yi; 72 | else 73 | yp = 0; 74 | } 75 | return 0; 76 | } 77 | 78 | int strnumcmp(char* x, char* y, int lenx, int leny) { 79 | int decx = index_of(x, ".") - 1; 80 | int decy = index_of(y, ".") - 1; 81 | if (decx < 0) decx = lenx; 82 | if (decy < 0) decy = leny; 83 | 84 | int cmp = strintcmp(x, y, decx, decy, false); 85 | if (cmp != 0 || (decx == lenx && decy == leny)) 86 | return cmp; 87 | 88 | return strintcmp(x + decx, y + decy, lenx - decx, leny - decy, true); 89 | } 90 | 91 | compare_result_t _expression_compare_numbers(expression_t* expr1, expression_t* expr2) { 92 | stringifier_t st; 93 | st.buffer = malloc(128); 94 | st.length = 128; 95 | st.index = 0; 96 | /* incase these expressions aren't numbers, use invalid variable names for NAN and INF */ 97 | st.nan_string = "@"; 98 | st.inf_string = "#"; 99 | st.whitespace = ""; 100 | st.current_precedence = OPERATOR_PRECEDENCE_MINIMUM; 101 | st.approximate_numbers = true; 102 | st.approximate_tolerance = 5; 103 | 104 | stringifier_write_expression(&st, expr1); 105 | char* expr1str = st.buffer; 106 | size_t expr1len = st.index; 107 | expr1str[st.index] = 0; 108 | st.buffer = malloc(128); 109 | st.length = 128; 110 | st.index = 0; 111 | stringifier_write_expression(&st, expr2); 112 | char* expr2str = st.buffer; 113 | size_t expr2len = st.index; 114 | expr2str[st.index] = 0; 115 | 116 | int result = strnumcmp(expr1str, expr2str, expr1len, expr2len); 117 | 118 | free(expr1str); 119 | free(expr2str); 120 | 121 | if (result < 0) { 122 | return COMPARE_RESULT_LESS; 123 | } else if (result > 0) { 124 | return COMPARE_RESULT_GREATER; 125 | } else if (result == 0) { 126 | return COMPARE_RESULT_EQUAL; 127 | } 128 | 129 | return COMPARE_RESULT_INCOMPARABLE; 130 | } 131 | 132 | compare_result_t _expression_compare_recursive(expression_t* expr1, expression_t* expr2) { 133 | if (expr1->type != expr2->type) 134 | return COMPARE_RESULT_INCOMPARABLE; 135 | 136 | switch (expr1->type) { 137 | case EXPRESSION_TYPE_NUMBER: 138 | return _expression_compare_numbers(expr1, expr2); 139 | case EXPRESSION_TYPE_VARIABLE: 140 | if (!strcmp(expr1->variable.value, expr2->variable.value)) 141 | return COMPARE_RESULT_EQUAL; 142 | else 143 | return COMPARE_RESULT_INCOMPARABLE; 144 | case EXPRESSION_TYPE_FUNCTION: 145 | if (!strcmp(expr1->function.name, expr2->function.name)) { 146 | expression_t* arg1; 147 | expression_t* arg2; 148 | compare_result_t current = COMPARE_RESULT_EQUAL; 149 | EXPRESSION_LIST_FOREACH2(arg1, arg2, expr1->function.parameters, expr2->function.parameters) { 150 | compare_result_t next = _expression_compare_recursive(arg1, arg2); 151 | if (current == COMPARE_RESULT_EQUAL && next == COMPARE_RESULT_EQUAL) { 152 | current = next; 153 | } else { 154 | return COMPARE_RESULT_INCOMPARABLE; 155 | } 156 | } 157 | return current; 158 | } else { 159 | return COMPARE_RESULT_INCOMPARABLE; 160 | } 161 | case EXPRESSION_TYPE_PREFIX: 162 | if (expr1->prefix.prefix != expr2->prefix.prefix) 163 | return COMPARE_RESULT_INCOMPARABLE; 164 | else 165 | return _expression_compare_recursive(expr1->prefix.right, expr2->prefix.right); 166 | case EXPRESSION_TYPE_OPERATOR: 167 | { 168 | if (expr1->operator.infix != expr2->operator.infix) 169 | return COMPARE_RESULT_INCOMPARABLE; 170 | compare_result_t result_left = _expression_compare_recursive(expr1->operator.left, expr2->operator.left); 171 | compare_result_t result_right = _expression_compare_recursive(expr1->operator.right, expr2->operator.right); 172 | if (result_left == result_right && result_left != COMPARE_RESULT_INCOMPARABLE) { 173 | return result_left; 174 | } else if (expr1->operator.infix == '*' || expr1->operator.infix == '+') { 175 | if (result_left == COMPARE_RESULT_INCOMPARABLE || result_right == COMPARE_RESULT_INCOMPARABLE) { 176 | result_left = _expression_compare_recursive(expr1->operator.left, expr2->operator.right); 177 | result_right = _expression_compare_recursive(expr1->operator.right, expr2->operator.left); 178 | if (result_left == result_right && result_left != COMPARE_RESULT_INCOMPARABLE) { 179 | return result_left; 180 | } 181 | } 182 | } 183 | return COMPARE_RESULT_INCOMPARABLE; 184 | } 185 | } 186 | return COMPARE_RESULT_INCOMPARABLE; 187 | } 188 | 189 | compare_result_t expression_compare(expression_t* expr1, expression_t* expr2) { 190 | return _expression_compare_recursive(expr1, expr2); 191 | } 192 | 193 | 194 | variable_t _expression_find_variable_recursive(expression_t* expr) { 195 | switch (expr->type) { 196 | case EXPRESSION_TYPE_FUNCTION: 197 | case EXPRESSION_TYPE_NUMBER: 198 | return NULL; 199 | case EXPRESSION_TYPE_OPERATOR: 200 | { 201 | variable_t var = _expression_find_variable_recursive(expr->operator.left); 202 | if (var) return var; 203 | var = _expression_find_variable_recursive(expr->operator.right); 204 | return var; 205 | } 206 | case EXPRESSION_TYPE_PREFIX: 207 | return _expression_find_variable_recursive(expr->operator.right); 208 | case EXPRESSION_TYPE_VARIABLE: 209 | return expr->variable.value; 210 | } 211 | return NULL; 212 | } 213 | 214 | 215 | int _expression_has_variable_or_function_recursive(expression_t* expr, variable_t var) { 216 | switch (expr->type) { 217 | case EXPRESSION_TYPE_NUMBER: 218 | return 0; 219 | case EXPRESSION_TYPE_OPERATOR: 220 | { 221 | if (_expression_has_variable_or_function_recursive(expr->operator.left, var)) 222 | return 1; 223 | if (_expression_has_variable_or_function_recursive(expr->operator.right, var)) 224 | return 1; 225 | return 0; 226 | } 227 | case EXPRESSION_TYPE_PREFIX: 228 | { 229 | if (_expression_has_variable_or_function_recursive(expr->prefix.right, var)) 230 | return 1; 231 | return 0; 232 | } 233 | case EXPRESSION_TYPE_VARIABLE: 234 | return strcmp(var, expr->variable.value) == 0; 235 | case EXPRESSION_TYPE_FUNCTION: 236 | return strcmp(var, expr->function.name) == 0; 237 | } 238 | return 0; 239 | } 240 | 241 | 242 | int expression_has_variable_or_function(expression_t* expr, variable_t var) { 243 | return _expression_has_variable_or_function_recursive(expr, var); 244 | } 245 | 246 | variable_t expression_find_variable(expression_t* expr) { 247 | return _expression_find_variable_recursive(expr); 248 | } -------------------------------------------------------------------------------- /src/simplify/expression/expression.h: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #ifndef SIMPLIFY_EXPRESSION_EXPRESSION_H_ 4 | #define SIMPLIFY_EXPRESSION_EXPRESSION_H_ 5 | 6 | #include 7 | 8 | #include "simplify/errors.h" 9 | #include "simplify/rbtree/rbtree.h" 10 | #include "simplify/expression/expr_types.h" 11 | 12 | /* the result of comparing two expressions */ 13 | typedef enum compare_result compare_result_t; 14 | 15 | enum compare_result { 16 | COMPARE_RESULT_INCOMPARABLE = 0x0, 17 | COMPARE_RESULT_EQUAL = 0x1, 18 | COMPARE_RESULT_LESS = 0x2, 19 | COMPARE_RESULT_GREATER = 0x4, 20 | }; 21 | 22 | 23 | /* check for a variable or function in the expression 24 | * 25 | * @expr the expression to search 26 | * @var the variable to search for 27 | */ 28 | int expression_has_variable_or_function(expression_t* expr, variable_t var); 29 | 30 | /* Compare two expressions 31 | * 32 | * @expr1 33 | * @expr2 34 | * @return returns a comparison result 35 | */ 36 | compare_result_t expression_compare(expression_t* expr1, expression_t* expr2); 37 | 38 | /* get the name of the first variable that appears in an expression 39 | * 40 | * @expr the expression to search 41 | * @return returns the variable's name, or NULL if it was not found 42 | */ 43 | variable_t expression_find_variable(expression_t* expr); 44 | 45 | 46 | /* swap an operator expression's left and right branch 47 | * 48 | * @expr _expr->type must be EXPRESSION_TYPE_OPERATOR_ expression to swap 49 | */ 50 | static inline void expression_swap(expression_t* expr) { 51 | expression_t* lx = EXPRESSION_LEFT(expr); 52 | expr->operator.left = EXPRESSION_RIGHT(expr); 53 | expr->operator.right = lx; 54 | } 55 | 56 | static inline expression_t* expression_new_operator(expression_t* left, operator_t op, expression_t* right) { 57 | expression_t* x = malloc(sizeof(expression_t)); 58 | expression_init_operator(x, left, op, right); 59 | return x; 60 | } 61 | 62 | static inline expression_t* expression_new_prefix(operator_t op, expression_t* right) { 63 | expression_t* x = malloc(sizeof(expression_t)); 64 | expression_init_prefix(x, op, right); 65 | return x; 66 | } 67 | 68 | 69 | static inline expression_t* expression_new_number(mpfr_ptr num) { 70 | expression_t* x = malloc(sizeof(expression_t)); 71 | expression_init_number(x, num); 72 | return x; 73 | } 74 | 75 | 76 | static inline expression_t* expression_new_number_d(double num) { 77 | expression_t* x = malloc(sizeof(expression_t)); 78 | expression_init_number_d(x, num); 79 | return x; 80 | } 81 | 82 | static inline expression_t* expression_new_number_si(long num) { 83 | expression_t* x = malloc(sizeof(expression_t)); 84 | expression_init_number_si(x, num); 85 | return x; 86 | } 87 | 88 | static inline expression_t* expression_new_variable(variable_t var) { 89 | expression_t* x = malloc(sizeof(expression_t)); 90 | expression_init_variable(x, var, strlen(var)); 91 | return x; 92 | } 93 | 94 | static inline expression_t* expression_new_function(variable_t name, int param_count, ...) { 95 | expression_t* x = malloc(sizeof(expression_t)); 96 | va_list args; 97 | va_start(args, param_count); 98 | expression_list_t* params = malloc(sizeof(expression_list_t)); 99 | expression_list_init(params); 100 | 101 | for (int i = 0; i < param_count; ++i) { 102 | expression_list_append(params, va_arg(args, expression_t*)); 103 | } 104 | va_end(args); 105 | expression_init_function(x, name, strlen(name), params); 106 | return x; 107 | } 108 | 109 | 110 | #endif // SIMPLIFY_EXPRESSION_EXPRESSION_H_ 111 | -------------------------------------------------------------------------------- /src/simplify/expression/isolate.c: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #include 4 | 5 | #include "simplify/expression/isolate.h" 6 | #include "simplify/expression/expression.h" 7 | #include "simplify/expression/evaluate.h" 8 | 9 | error_t _expression_isolate_variable_recursive(expression_t* expr, expression_t** target, variable_t var); 10 | 11 | void _expression_invert_operand(expression_t* expr) { 12 | switch (expr->operator.infix) { 13 | case '+': 14 | expr->operator.infix = '-'; 15 | break; 16 | case '-': 17 | expr->operator.infix = '+'; 18 | break; 19 | case '/': 20 | expr->operator.infix = '*'; 21 | break; 22 | case '*': 23 | case '(': 24 | expr->operator.infix = '/'; 25 | break; 26 | case '^': 27 | expr->operator.infix = '\\'; 28 | break; 29 | case '\\': 30 | expr->operator.infix = '^'; 31 | break; 32 | default: 33 | break; 34 | } 35 | } 36 | 37 | bool _expression_operator_is_reversible(expression_t* expr) { 38 | switch (expr->operator.infix) { 39 | case '-': 40 | case '/': 41 | case '^': 42 | case '\\': 43 | return false; 44 | default: 45 | return true; 46 | } 47 | } 48 | 49 | #include "simplify/expression/stringify.h" 50 | 51 | /* transform expr into `e^x = y`, the call _expression_isolate_variable_recursive on `y` 52 | * @y the y value of the logarithm 53 | * @expr the output expression 54 | * @returns returns an error code 55 | */ 56 | error_t _expression_setup_natural_log(expression_t* y, expression_t* expr) { 57 | error_t err = ERROR_NO_ERROR; 58 | 59 | if (EXPRESSION_IS_NUMBER(y)) { 60 | mpfr_ptr loge = malloc(sizeof(mpfr_t)); 61 | mpfr_init(loge); 62 | mpfr_log(loge, y->number.value, MPFR_RNDF); 63 | expression_init_number(expr, loge); 64 | expression_clean(y); 65 | return ERROR_NO_ERROR; 66 | } 67 | 68 | # if defined(NATURAL_LOG_BUILTIN) 69 | expression_list_t* args = malloc(sizeof(expression_list_t)); 70 | expression_list_init(args); 71 | expression_list_append(args, y); 72 | expression_init_function(expr, NATURAL_LOG_BUILTIN, sizeof NATURAL_LOG_BUILTIN, args); 73 | # else 74 | expression_t* expr_e; 75 | expr_e = expression_new_variable(E_BUILTIN); 76 | expression_init_operator(expr, y, '=', 77 | expression_new_operator( 78 | expr_e, 79 | '^', 80 | expression_new_variable("x"))); 81 | 82 | err = _expression_isolate_variable_recursive(expr->operator.right, &expr->operator.left, "x"); 83 | if (err) return err; 84 | expression_collapse_right(expr); 85 | # endif 86 | return err; 87 | } 88 | 89 | error_t _expression_isolate_variable_recursive(expression_t* expr, expression_t** target, variable_t var) { 90 | switch (expr->type) { 91 | case EXPRESSION_TYPE_NUMBER: 92 | return ERROR_VARIABLE_NOT_PRESENT; 93 | case EXPRESSION_TYPE_OPERATOR: 94 | { 95 | if (expression_is_comparison(expr) || expr->operator.infix == ':') { 96 | error_t err = _expression_isolate_variable_recursive(expr->operator.left, &expr->operator.right, var); 97 | if (err && err != ERROR_VARIABLE_NOT_PRESENT) 98 | return err; 99 | 100 | if (err) { 101 | err = _expression_isolate_variable_recursive(expr->operator.right, &expr->operator.left, var); 102 | if (err) return err; 103 | } 104 | return ERROR_NO_ERROR; 105 | } 106 | 107 | expression_t* new_target = malloc(sizeof(expression_t)); 108 | new_target->type = EXPRESSION_TYPE_OPERATOR; 109 | new_target->operator.infix = expr->operator.infix; 110 | 111 | if (expression_has_variable_or_function(expr->operator.left, var)) { 112 | new_target->operator.right = expr->operator.right; 113 | new_target->operator.left = *target; 114 | _expression_invert_operand(new_target); 115 | } else if (expression_has_variable_or_function(expr->operator.right, var)) { 116 | if (expr->operator.infix == '^') { 117 | // Looks Logarithm-ish so handle it as a special case 118 | expression_t* y = expr->operator.right; 119 | expression_t* b = expr->operator.left; 120 | expression_t* x = *target; 121 | 122 | if ((EXPRESSION_IS_VARIABLE(b) && !strcmp(b->variable.value, E_BUILTIN))) { 123 | *expr = *y; 124 | *target = expression_new_operator(expression_new_variable(E_BUILTIN), '^', *target); 125 | _expression_isolate_variable_recursive(expr, target, var); 126 | return ERROR_NO_ERROR; 127 | } 128 | error_t err = _expression_setup_natural_log(x, new_target); 129 | if (err) return err; 130 | 131 | expression_t oldy = *y; 132 | err = _expression_setup_natural_log(b, expr->operator.right); 133 | if (err) return err; 134 | 135 | *expr->operator.left = oldy; 136 | expr->operator.infix = '*'; 137 | *target = new_target; 138 | _expression_isolate_variable_recursive(expr, target, var); 139 | break; 140 | } else if (_expression_operator_is_reversible(expr)) { 141 | new_target->operator.right = expr->operator.left; 142 | new_target->operator.left = *target; 143 | _expression_invert_operand(new_target); 144 | } else { 145 | new_target->operator.left = expr->operator.left; 146 | new_target->operator.right = *target; 147 | } 148 | } else { 149 | free(new_target); 150 | return ERROR_VARIABLE_NOT_PRESENT; 151 | } 152 | 153 | *target = new_target; 154 | expression_t new_expr; 155 | if (!_expression_isolate_variable_recursive(expr->operator.left, target, var)) { 156 | new_expr = *expr->operator.left; 157 | } else if (!_expression_isolate_variable_recursive(expr->operator.right, target, var)) { 158 | new_expr = *expr->operator.right; 159 | } else { 160 | return ERROR_VARIABLE_NOT_PRESENT; 161 | } 162 | 163 | *expr = new_expr; 164 | return ERROR_NO_ERROR; 165 | } 166 | case EXPRESSION_TYPE_PREFIX: 167 | { 168 | if (expression_has_variable_or_function(expr->prefix.right, var)) { 169 | expression_t* new_target = malloc(sizeof(expression_t)); 170 | new_target->type = EXPRESSION_TYPE_PREFIX; 171 | new_target->prefix.right = *target; 172 | switch (EXPRESSION_OPERATOR(expr)) { 173 | case '+': 174 | new_target->prefix.prefix = '+'; 175 | break; 176 | case '-': 177 | new_target->prefix.prefix = '-'; 178 | break; 179 | default: 180 | return ERROR_INVALID_PREFIX; 181 | } 182 | *target = new_target; 183 | if (!_expression_isolate_variable_recursive(expr->prefix.right, target, var)) { 184 | *expr = *expr->prefix.right; 185 | } else { 186 | return ERROR_VARIABLE_NOT_PRESENT; 187 | } 188 | return ERROR_NO_ERROR; 189 | } 190 | return ERROR_VARIABLE_NOT_PRESENT; 191 | } 192 | case EXPRESSION_TYPE_FUNCTION: 193 | if (strcmp(var, expr->function.name) == 0) { 194 | return ERROR_NO_ERROR; 195 | } else { 196 | return ERROR_VARIABLE_NOT_PRESENT; 197 | } 198 | case EXPRESSION_TYPE_VARIABLE: 199 | if (strcmp(var, expr->variable.value) == 0) { 200 | return ERROR_NO_ERROR; 201 | } else { 202 | return ERROR_VARIABLE_NOT_PRESENT; 203 | } 204 | } 205 | return ERROR_NO_ERROR; 206 | } 207 | 208 | error_t expression_isolate_variable(expression_t* expr, variable_t var) { 209 | if (!expression_has_variable_or_function(expr, var)) 210 | return ERROR_VARIABLE_NOT_PRESENT; 211 | 212 | if (!expression_is_comparison(expr) && expr->operator.infix != ':') { 213 | expression_t* new_left = malloc(sizeof(expression_t)); 214 | 215 | *new_left = *expr; 216 | expr->type = EXPRESSION_TYPE_OPERATOR; 217 | expr->operator.infix = '='; 218 | expr->operator.left = new_left; 219 | expr->operator.right = malloc(sizeof(expression_t)); 220 | expression_init_number_si(expr->operator.right, 0); 221 | } 222 | 223 | error_t err = _expression_isolate_variable_recursive(expr, NULL, var); 224 | if (err) return err; 225 | 226 | /* make sure the variable is always on the left */ 227 | if (EXPRESSION_IS_VARIABLE(expr->operator.right) || EXPRESSION_IS_FUNCTION(expr->operator.right)) { 228 | expression_t* right = expr->operator.right; 229 | expr->operator.right = expr->operator.left; 230 | expr->operator.left = right; 231 | } else { 232 | /* flip the sign if it was a < or > */ 233 | if (expr->operator.infix == '<') { 234 | expr->operator.infix = '>'; 235 | } else if (expr->operator.infix == '>') { 236 | expr->operator.infix = '<'; 237 | } 238 | } 239 | 240 | return err; 241 | } 242 | -------------------------------------------------------------------------------- /src/simplify/expression/isolate.h: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #ifndef SIMPLIFY_EXPRESSION_ISOLATE_H_ 4 | #define SIMPLIFY_EXPRESSION_ISOLATE_H_ 5 | 6 | #include "simplify/expression/expression.h" 7 | 8 | // Expressions like `e ^ X = Y` may be replaced by the NUTURAL_LOG_BUILTIN(Y) 9 | // #define NATURAL_LOG_BUILTIN "ln" 10 | 11 | #define E_BUILTIN "e" 12 | 13 | /* isolate a variable on one side of an comparison operator. 14 | * If no comparison operator is present, append `= 0` 15 | * 16 | * @expr the expression to work with 17 | * @var the variable to isolate 18 | * @return returns an error code 19 | */ 20 | error_t expression_isolate_variable(expression_t* expr, variable_t var); 21 | 22 | 23 | #endif // SIMPLIFY_EXPRESSION_ISOLATE_H_ 24 | -------------------------------------------------------------------------------- /src/simplify/expression/simplify.c: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #include "simplify/expression/simplify.h" 4 | #include "simplify/expression/isolate.h" 5 | 6 | /* get the operator that would could be used to collapse a chain of `op` operators 7 | * 8 | * @op the source op 9 | * @return returns the collapse operator, or zero if `op` is not collapseable 10 | */ 11 | operator_t _operator_collapsed_equivelent(operator_t op) { 12 | switch (op) { 13 | case '+': 14 | return '*'; 15 | case '*': 16 | return '^'; 17 | } 18 | 19 | return 0; 20 | } 21 | 22 | static inline int _diff_precedence(expression_t* expr1, expression_t* expr2) { 23 | return (int)operator_precedence(expr1->operator.infix) - (int)operator_precedence(expr2->operator.infix); 24 | } 25 | 26 | int _expression_init_chain(expression_t* root, expression_t* var) { 27 | expression_t* right = EXPRESSION_RIGHT(var); 28 | 29 | if (EXPRESSION_IS_VARIABLE(right) 30 | && _diff_precedence(root, var) >= 0 31 | && !strcmp(right->variable.value, EXPRESSION_RIGHT(root)->variable.value)) { 32 | char* varname = right->variable.value; 33 | expression_init_operator(right, 34 | expression_new_variable(varname), 35 | _operator_collapsed_equivelent(root->operator.infix), 36 | expression_new_number_si(2)); 37 | return 0; 38 | } 39 | 40 | return 1; 41 | } 42 | 43 | error_t _expression_collapse_variables_recursive(expression_t* expr) { 44 | switch (expr->type) { 45 | case EXPRESSION_TYPE_NUMBER: 46 | case EXPRESSION_TYPE_VARIABLE: 47 | case EXPRESSION_TYPE_FUNCTION: 48 | case EXPRESSION_TYPE_PREFIX: 49 | break; 50 | case EXPRESSION_TYPE_OPERATOR: 51 | { 52 | error_t err = _expression_collapse_variables_recursive(EXPRESSION_LEFT(expr)); 53 | if (err) return err; 54 | 55 | err = _expression_collapse_variables_recursive(EXPRESSION_RIGHT(expr)); 56 | if (err) return err; 57 | 58 | operator_t equiv_op = _operator_collapsed_equivelent(expr->operator.infix); 59 | 60 | // Don't know how to deal with this operator yet 61 | if (!equiv_op) 62 | break; 63 | 64 | // If there are two variables right next to each other try to combine them 65 | if (EXPRESSION_IS_VARIABLE(EXPRESSION_LEFT(expr)) 66 | && EXPRESSION_IS_VARIABLE(EXPRESSION_RIGHT(expr))) { 67 | expr->operator.infix = equiv_op; 68 | expression_clean(expr->operator.right); 69 | expression_init_number_si(EXPRESSION_RIGHT(expr), 2); 70 | 71 | // the `while` loop condition will fail immediately, so just break here 72 | break; 73 | } 74 | 75 | /* If possible try to add to any expressions up the tree 76 | To add to an expression it must look like ( eqiv_op ) */ 77 | while (EXPRESSION_IS_VARIABLE(EXPRESSION_RIGHT(expr)) && EXPRESSION_IS_OPERATOR(EXPRESSION_LEFT(expr))) { 78 | /* While possible worm our way up the tree, looking for the expression */ 79 | expression_t* right = EXPRESSION_LEFT(expr); 80 | while (EXPRESSION_IS_OPERATOR(right) 81 | && EXPRESSION_IS_OPERATOR(EXPRESSION_RIGHT(right)) 82 | && _diff_precedence(right, expr) >= 0) 83 | right = EXPRESSION_RIGHT(right); 84 | 85 | expression_t* variable = EXPRESSION_LEFT(right); 86 | expression_t* count = EXPRESSION_RIGHT(right); 87 | 88 | /* If the variable / count is an operator expression, if it includes the right 89 | variable we may be able to start a new chain */ 90 | if (!EXPRESSION_IS_NUMBER(count) ||!EXPRESSION_IS_VARIABLE(variable)) { 91 | if (!EXPRESSION_IS_VARIABLE(variable)) { 92 | if (!_expression_init_chain(expr, right)) { 93 | *expr = *EXPRESSION_LEFT(expr); 94 | continue; 95 | } 96 | } 97 | 98 | if (!EXPRESSION_IS_NUMBER(count)) { 99 | if (!_expression_init_chain(expr, right)) { 100 | *expr = *EXPRESSION_LEFT(expr); 101 | continue; 102 | } 103 | } 104 | break; 105 | } 106 | 107 | if (strcmp(variable->variable.value, EXPRESSION_RIGHT(expr)->variable.value) != 0) break; 108 | 109 | if (right->operator.infix == equiv_op) { 110 | mpfr_ptr x = count->number.value; 111 | mpfr_add_si(x, x, 1, MPFR_RNDN); 112 | *expr = *EXPRESSION_LEFT(expr); 113 | } else { 114 | expr->operator.infix = equiv_op; 115 | expression_clean(expr->operator.right); 116 | expression_init_number_si(EXPRESSION_RIGHT(expr), 2); 117 | } 118 | } 119 | } 120 | } 121 | return ERROR_NO_ERROR; 122 | } 123 | 124 | error_t expression_do_logarithm(expression_t* b, expression_t* y, expression_t** out) { 125 | /* expression_isolate_variable can solve logarithms, 126 | so this function will set that up expand to b^x = y, then call 127 | expression_isolate_variable to solve it 128 | */ 129 | error_t err; 130 | *out = expression_new_operator( 131 | expression_new_operator(b, '^', expression_new_variable("x") ), 132 | '=', 133 | y); 134 | 135 | err = expression_isolate_variable(*out, "x"); 136 | if (err) return err; 137 | 138 | expression_collapse_left(*out); 139 | 140 | return err; 141 | } 142 | 143 | error_t expression_simplify(expression_t* expr) { 144 | return _expression_collapse_variables_recursive(expr); 145 | } 146 | -------------------------------------------------------------------------------- /src/simplify/expression/simplify.h: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #ifndef SIMPLIFY_EXPRESSION_SIMPLIFY_H_ 4 | #define SIMPLIFY_EXPRESSION_SIMPLIFY_H_ 5 | 6 | #include "simplify/expression/expression.h" 7 | 8 | /* try to make `expr` as short as possible by combining child expressions 9 | * 10 | * @expr the expression to shorten 11 | * @return returns an error code 12 | */ 13 | error_t expression_simplify(expression_t* expr); 14 | 15 | 16 | /* find the value of x in the expression `b^x = y` 17 | * @b 18 | * @y 19 | * @out out will be filled with the value of `x`, or NULL if an error is returned 20 | * @returns a error code 21 | */ 22 | error_t expression_do_logarithm(expression_t* b, expression_t* y, expression_t** out); 23 | 24 | #endif // SIMPLIFY_EXPRESSION_SIMPLIFY_H_ 25 | -------------------------------------------------------------------------------- /src/simplify/expression/stringify.c: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #include "simplify/expression/stringify.h" 4 | 5 | void _stringifier_round_number(stringifier_t* st, size_t start, size_t numstart) { 6 | /* round direction is `1` to round up or `-1` to round down */ 7 | int round_direction = st->buffer[start - 1] >= '5' ? 1 : -1; 8 | 9 | /* round the digits before the decimal point */ 10 | size_t i; 11 | for (i = start; i > numstart && isdigit(st->buffer[i]); --i) { 12 | if (st->buffer[i] + round_direction < '0' || st->buffer[i] + round_direction > '9') { 13 | /* if the current character can't safely be rounded than zero it and continue */ 14 | st->buffer[i] = '0'; 15 | } else { 16 | /* apply the the round direction, the exit. */ 17 | st->buffer[i] += round_direction; 18 | ++i; 19 | goto finished; 20 | } 21 | } 22 | 23 | /* if the earlier loop ran out of room 24 | than that means we need to move past the decimal point */ 25 | if (i == numstart) { 26 | if (round_direction > 0) 27 | st->buffer[i] = '1'; 28 | else 29 | st->buffer[i] = '0'; 30 | goto finished; 31 | } else if (st->buffer[i] == '.') { 32 | --i; 33 | } else { 34 | goto finished; 35 | } 36 | 37 | size_t d = i; 38 | /* try to round everything before the decimal */ 39 | for (; d >= numstart && isdigit(st->buffer[d]); --d) { 40 | if (st->buffer[d] + round_direction < '0' || st->buffer[d] + round_direction > '9') { 41 | st->buffer[d] = '0'; 42 | } else { 43 | st->buffer[d] += round_direction; 44 | ++i; 45 | goto finished; 46 | } 47 | } 48 | 49 | if (i == numstart) { 50 | /* push back the string to make room for a new digit */ 51 | memmove(st->buffer + d + 1, st->buffer + d, start - d); 52 | if (round_direction > 0) 53 | st->buffer[d] = '1'; 54 | else 55 | st->buffer[d] = '0'; 56 | } 57 | 58 | finished: 59 | st->index = i; 60 | return; 61 | } 62 | 63 | /* try to trim a floating point number that was convert to a string, to improve accuracy 64 | * @st the stringifier that the number was written to, assume that the number was the last thing written to the stringifier 65 | * @tolerance how easily the string should be rounded off. (five is probably a good default for this parameter) 66 | * @length the numbers's length 67 | */ 68 | void _stringifier_approximate_number(stringifier_t* st, size_t length) { 69 | size_t decimal_index = st->index - 1; 70 | 71 | for (; st->buffer[decimal_index] != '.'; --decimal_index) { 72 | /* if there is no decimal point then exit, there is nothing left to do */ 73 | if (decimal_index < st->index - length) return; 74 | } 75 | 76 | size_t chain = 0; 77 | char last = 0; 78 | 79 | for (size_t i = decimal_index + 1; i < st->index && isdigit(st->buffer[i]); ++i) { 80 | if (st->buffer[i] == last) { 81 | ++chain; 82 | } else { 83 | if (chain >= st->approximate_tolerance) { 84 | if (last == '9') { 85 | _stringifier_round_number(st, i - 1, st->index - length - 1); 86 | } else if (last == '0') { 87 | st->index = i - chain - 2; 88 | return; 89 | } 90 | } 91 | last = st->buffer[i]; 92 | chain = 0; 93 | } 94 | } 95 | 96 | if (chain >= st->approximate_tolerance) { 97 | if (last == '9') { 98 | _stringifier_round_number(st, st->index - 2, st->index - length - 1); 99 | } else if (last == '0') { 100 | st->index = st->index - chain - 1; 101 | } 102 | } 103 | if (st->buffer[st->index - 1] == '.') { 104 | --st->index; 105 | } 106 | return; 107 | } 108 | 109 | size_t stringifier_write_variable(stringifier_t* st, expression_t* variable) { 110 | assert(EXPRESSION_IS_VARIABLE(variable)); 111 | return stringifier_write(st, variable->variable.value); 112 | } 113 | 114 | size_t stringifier_write_operator(stringifier_t* st, expression_t* op) { 115 | assert(EXPRESSION_IS_OPERATOR(op)); 116 | operator_precedence_t oldprec = st->current_precedence; 117 | st->current_precedence = operator_precedence(op->operator.infix); 118 | size_t written = 0; 119 | if (st->current_precedence < oldprec) 120 | written += stringifier_write_byte(st, '('); 121 | 122 | written += stringifier_write_expression(st, op->operator.left); 123 | 124 | written +=stringifier_write_whitespace(st); 125 | written += stringifier_write_byte(st, op->operator.infix); 126 | written += stringifier_write_whitespace(st); 127 | 128 | written += stringifier_write_expression(st, op->operator.right); 129 | 130 | if (st->current_precedence < oldprec) 131 | written += stringifier_write_byte(st, ')'); 132 | 133 | st->current_precedence = oldprec; 134 | return written; 135 | } 136 | 137 | size_t stringifier_write_prefix(stringifier_t* st, expression_t* pre) { 138 | assert(EXPRESSION_IS_PREFIX(pre)); 139 | size_t written = stringifier_write_byte(st, pre->prefix.prefix); 140 | written += stringifier_write_expression(st, pre->prefix.right); 141 | return written; 142 | } 143 | 144 | size_t stringifier_write_number(stringifier_t* st, expression_t* number) { 145 | assert(EXPRESSION_IS_NUMBER(number)); 146 | 147 | mpfr_ptr num = number->number.value; 148 | static const int base = 10; 149 | const bool neg = mpfr_sgn(num) < 0; 150 | 151 | if (!mpfr_regular_p(num)) { 152 | if (mpfr_nan_p(num)) { 153 | return stringifier_write(st, st->nan_string); 154 | } else if (mpfr_inf_p(num)) { 155 | return (neg ? stringifier_write_byte(st, '-') : 0) 156 | + stringifier_write(st, st->inf_string); 157 | } else { 158 | assert(mpfr_zero_p(num)); 159 | return (neg ? stringifier_write_byte(st, '-') : 0) 160 | + stringifier_write_byte(st, '0'); 161 | } 162 | } 163 | 164 | size_t written = 0; 165 | mpfr_prec_t prec = mpfr_get_prec(num); 166 | mpfr_exp_t exponent; 167 | 168 | if (neg) { 169 | written += stringifier_write_byte(st, '-'); 170 | mpfr_abs(num, num, MPFR_RNDN); 171 | } 172 | size_t numlen = ceil(prec * log(2)/log(base)) + 3; 173 | 174 | _STRINGIFIER_FIT(st, numlen); 175 | char* numstart = st->buffer + st->index; 176 | mpfr_get_str(numstart, &exponent, base, numlen, number->number.value, MPFR_RNDN); 177 | st->index += numlen; 178 | 179 | if (exponent > 0) { 180 | memmove(numstart + exponent + 1, numstart + exponent, numlen - exponent); 181 | numstart[exponent] = '.'; 182 | ++st->index; 183 | } else { 184 | exponent = abs(exponent); 185 | 186 | memmove(numstart + exponent + 2, numstart, numlen - exponent); 187 | numstart[0] = '0'; 188 | numstart[1] = '.'; 189 | for (int i = 2; i < exponent + 2; ++i) { 190 | numstart[i] = '0'; 191 | } 192 | ++st->index; 193 | } 194 | 195 | if (st->approximate_numbers) 196 | _stringifier_approximate_number(st, numlen); 197 | 198 | return written + st->index - (size_t)numstart; 199 | } 200 | 201 | size_t stringifier_write_function(stringifier_t* st, expression_t* func) { 202 | assert(EXPRESSION_IS_FUNCTION(func)); 203 | size_t written = stringifier_write(st, func->function.name); 204 | written += stringifier_write_byte(st, '('); 205 | expression_t* arg; 206 | EXPRESSION_LIST_FOREACH(arg, func->function.parameters) { 207 | written += stringifier_write_expression(st, arg); 208 | if (__item->next) { 209 | written += stringifier_write_byte(st, ','); 210 | written += stringifier_write_whitespace(st); 211 | } 212 | } 213 | written += stringifier_write_byte(st, ')'); 214 | return written; 215 | } 216 | 217 | size_t stringifier_write_expression(stringifier_t* st, expression_t* expr) { 218 | switch (expr->type) { 219 | case EXPRESSION_TYPE_FUNCTION: 220 | return stringifier_write_function(st, expr); 221 | case EXPRESSION_TYPE_PREFIX: 222 | return stringifier_write_prefix(st, expr); 223 | case EXPRESSION_TYPE_OPERATOR: 224 | return stringifier_write_operator(st, expr); 225 | case EXPRESSION_TYPE_VARIABLE: 226 | return stringifier_write_variable(st, expr); 227 | case EXPRESSION_TYPE_NUMBER: 228 | return stringifier_write_number(st, expr); 229 | } 230 | return 0; 231 | } 232 | 233 | char* stringify(expression_t* expr) { 234 | stringifier_t st = STRINGIFIER_DEFAULT(); 235 | stringifier_write_expression(&st, expr); 236 | stringifier_write_byte(&st, 0); 237 | return st.buffer; 238 | } 239 | 240 | error_t expression_fprint(expression_t* expr, FILE* f) { 241 | char* str = stringify(expr); 242 | fputs(str, f); 243 | free(str); 244 | return ERROR_NO_ERROR; 245 | } 246 | -------------------------------------------------------------------------------- /src/simplify/expression/stringify.h: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #ifndef SIMPLIFY_EXPRESSION_STRINGIFY_H_ 4 | #define SIMPLIFY_EXPRESSION_STRINGIFY_H_ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "simplify/expression/expression.h" 13 | 14 | #ifndef STRINGIFIER_DEFAULT_SIZE 15 | # define STRINGIFIER_DEFAULT_SIZE 256 16 | #endif 17 | 18 | 19 | /* get a stringifier with the default, settings. Optimized for pretty-ness */ 20 | #define STRINGIFIER_DEFAULT() \ 21 | { \ 22 | .buffer = malloc(256), \ 23 | .length = 256, \ 24 | .index = 0, \ 25 | .approximate_tolerance = 5, \ 26 | .approximate_numbers = true, \ 27 | .nan_string = "NaN", \ 28 | .inf_string = "Inf", \ 29 | .whitespace = " ", \ 30 | .current_precedence = OPERATOR_PRECEDENCE_MINIMUM \ 31 | } 32 | 33 | #define _STRINGIFIER_FIT(ST, X) \ 34 | while (st->index + (X) >= st->length - 1) { \ 35 | stringifier_grow(st); \ 36 | } 37 | 38 | /* A stringifier writes an expression to a variable length buffer 39 | * 40 | * The stringifier type keeps information about the buffer, and stores the buffer itself. 41 | */ 42 | typedef struct stringifier stringifier_t; 43 | 44 | 45 | struct stringifier { 46 | char* buffer; 47 | size_t length; 48 | size_t index; 49 | 50 | size_t approximate_tolerance; 51 | bool approximate_numbers; 52 | 53 | char* nan_string; 54 | char* inf_string; 55 | char* whitespace; 56 | 57 | operator_precedence_t current_precedence; 58 | }; 59 | 60 | 61 | /* Write the expression `expr` as a c string 62 | * 63 | * The `stringify` function stringifies an expression usinging the 64 | * default stringifier settings. 65 | * 66 | * @expr the expression to stringify 67 | * @return returns a null-terminated string. This string must be freed using `free` by the user. 68 | */ 69 | char* stringify(expression_t* expr); 70 | 71 | size_t stringifier_write_expression(stringifier_t* st, expression_t* expr); 72 | size_t stringifier_write_function(stringifier_t* st, expression_t* func); 73 | size_t stringifier_write_number(stringifier_t* st, expression_t* number); 74 | size_t stringifier_write_variable(stringifier_t* st, expression_t* variable); 75 | size_t stringifier_write_operator(stringifier_t* st, expression_t* op); 76 | size_t stringifier_write_prefix(stringifier_t* st, expression_t* pre); 77 | 78 | /* grow the stringifier's internal buffer. 79 | * 80 | * This method should be called automatically when writing, there is no need to call it by hand 81 | * 82 | * @st the stringifier to grow 83 | */ 84 | static inline void stringifier_grow(stringifier_t* st) { 85 | st->length = st->length ? st->length * 2 : STRINGIFIER_DEFAULT_SIZE; 86 | st->buffer = realloc(st->buffer, st->length); 87 | } 88 | 89 | /* write a single byte to the stringifier's buffer 90 | * 91 | * @st the stringifier to write to 92 | * @b the byte to write 93 | * @return returns the number of bytes written 94 | */ 95 | static inline size_t stringifier_write_byte(stringifier_t* st, char b) { 96 | _STRINGIFIER_FIT(st, 1) 97 | 98 | st->buffer[st->index++] = b; 99 | return 1; 100 | } 101 | 102 | /* write a string to the stringifier's buffer 103 | * 104 | * @st the stringifier to write to 105 | * @str the base of the string to begin writing 106 | * @len the number of bytes to write 107 | * @return returns the number of bytes written 108 | */ 109 | static inline size_t stringifier_write_len(stringifier_t* st, char* str, size_t len) { 110 | _STRINGIFIER_FIT(st, len) 111 | 112 | strncpy(st->buffer + st->index, str, len); 113 | st->index += len; 114 | return len; 115 | } 116 | 117 | /* write a null terminated string to the stringifier's buffer 118 | * 119 | * @st the stringifier to write to 120 | * @str the string to write 121 | * @return returns the number of bytes written 122 | */ 123 | static inline size_t stringifier_write(stringifier_t* st, char* str) { 124 | size_t len = strlen(str); 125 | if (len > 0) 126 | len = stringifier_write_len(st, str, len); 127 | return len; 128 | } 129 | 130 | static inline size_t stringifier_write_whitespace(stringifier_t* st) { 131 | if (st->whitespace[0]) 132 | return stringifier_write(st, st->whitespace); 133 | return 0; 134 | } 135 | 136 | /* print an expression to `file` 137 | * @file the file to write to 138 | * @expr the expression to print 139 | * @return an error code 140 | */ 141 | error_t expression_fprint(expression_t* expr, FILE* file); 142 | 143 | /* print an expression to stdout 144 | * @expr the expression to print 145 | * @return an error code 146 | */ 147 | static inline error_t expression_print(expression_t* expr) { 148 | return expression_fprint(expr, stdout); 149 | } 150 | 151 | #endif // SIMPLIFY_EXPRESSION_STRINGIFY_H_ 152 | -------------------------------------------------------------------------------- /src/simplify/lexer.c: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #include "simplify/lexer.h" 4 | #include "simplify/errors.h" 5 | 6 | #define __LEXER_LOOP_END() __lexer_loop_continue = 0 7 | 8 | #define __LEXER_LOOP(LEXER, BLOCK) { \ 9 | int __lexer_loop_continue = 1; \ 10 | while (1) { \ 11 | if ((LEXER)->buffer_position >= (LEXER)->buffer_length)\ 12 | break; \ 13 | BLOCK; \ 14 | if (__lexer_loop_continue) { \ 15 | lexer_advance(LEXER); \ 16 | } else { \ 17 | break; \ 18 | } \ 19 | } \ 20 | } 21 | 22 | #define __LEXER_SKIP_WHILE(LEXER, FUNC) __LEXER_LOOP(LEXER, if (!FUNC(lexer_current(LEXER))) __LEXER_LOOP_END()) 23 | 24 | /* initialize a lexer from a file 25 | * 26 | * This function reads the file block-by-block into an intermediate buffer, 27 | * instead of immediately checking it's length and reading the entire file. 28 | * Generally it's optimal to use lexer_init_from_file, instead of this function. 29 | * `lexer_init_from_file` will call this function if it can't determine the length anyway. 30 | * 31 | * @lexer the lexer to initialize 32 | * @file the file read 33 | */ 34 | void lexer_init_from_file_buffered(lexer_t* lexer, FILE* file) { 35 | lexer->buffer = malloc(LEXER_FILE_BUFFER_SIZE); 36 | char buffer[LEXER_FILE_BUFFER_SIZE]; 37 | 38 | size_t size = 0; 39 | while (fgets(buffer, LEXER_FILE_BUFFER_SIZE, file)) { 40 | size_t buf_size = strlen(buffer); 41 | lexer->buffer = realloc(lexer->buffer, size + buf_size + 1); 42 | strncpy(lexer->buffer + size, buffer, buf_size); 43 | size += buf_size; 44 | } 45 | 46 | lexer->buffer_length = size; 47 | lexer->buffer_position = 0; 48 | } 49 | 50 | void lexer_init_from_file(lexer_t* lexer, FILE* file) { 51 | fseek(file, 0, SEEK_END); 52 | size_t len = (size_t)ftell(file); 53 | if (len == (size_t)-1) { 54 | lexer_init_from_file_buffered(lexer, file); 55 | return; 56 | } 57 | 58 | lexer->buffer = malloc(len + 1); 59 | lexer->buffer_length = len; 60 | lexer->buffer_position = 0; 61 | 62 | lexer->buffer[len] = 0; 63 | printf("%s\n", lexer->buffer); 64 | fseek(file, 0, SEEK_SET); 65 | fread(lexer->buffer, len, 1, file); 66 | } 67 | 68 | static inline char lexer_current(lexer_t* lexer) { 69 | return lexer->buffer[lexer->buffer_position]; 70 | } 71 | 72 | static inline void lexer_advance(lexer_t* lexer) { 73 | ++lexer->buffer_position; 74 | } 75 | 76 | static inline int lexer_eof(lexer_t* lexer) { 77 | return lexer->buffer_position >= lexer->buffer_length; 78 | } 79 | 80 | error_t lexer_get_number(lexer_t* lexer, token_t* token) { 81 | token->start = lexer->buffer + lexer->buffer_position; 82 | 83 | __LEXER_SKIP_WHILE(lexer, isdigit); 84 | 85 | token->length = lexer->buffer + lexer->buffer_position - token->start; 86 | if (!lexer_eof(lexer) && lexer_current(lexer) == '.') { 87 | lexer_advance(lexer); 88 | __LEXER_SKIP_WHILE(lexer, isdigit); 89 | } 90 | 91 | if (!lexer_eof(lexer) && (lexer_current(lexer) == 'e' || lexer_current(lexer) == 'E')) { 92 | lexer_advance(lexer); 93 | if (lexer_current(lexer) == '-') 94 | lexer_advance(lexer); 95 | __LEXER_SKIP_WHILE(lexer, isdigit); 96 | } 97 | 98 | token->type = TOKEN_TYPE_NUMBER; 99 | 100 | token->length = lexer->buffer + lexer->buffer_position - token->start; 101 | return ERROR_NO_ERROR; 102 | } 103 | 104 | error_t lexer_next(lexer_t* lexer, token_t* token) { 105 | __LEXER_SKIP_WHILE(lexer, isspace); 106 | 107 | if (lexer_eof(lexer)) { 108 | token->type = TOKEN_TYPE_EOF; 109 | token->length = 0; 110 | token->start = lexer->buffer + lexer->buffer_position; 111 | return ERROR_NO_ERROR; 112 | } 113 | 114 | switch (lexer_current(lexer)) { 115 | case '+': 116 | case '-': 117 | case '/': 118 | case '\\': 119 | case '*': 120 | case '^': 121 | case '=': 122 | case '>': 123 | case '<': 124 | case ':': 125 | token->type = TOKEN_TYPE_OPERATOR; 126 | token->start = lexer->buffer + lexer->buffer_position; 127 | token->length = 1; 128 | ++lexer->buffer_position; 129 | break; 130 | case '(': 131 | token->type = TOKEN_TYPE_LEFT_PAREN; 132 | token->start = lexer->buffer + lexer->buffer_position; 133 | token->length = 1; 134 | ++lexer->buffer_position; 135 | break; 136 | case ')': 137 | token->type = TOKEN_TYPE_RIGHT_PAREN; 138 | token->start = lexer->buffer + lexer->buffer_position; 139 | token->length = 1; 140 | ++lexer->buffer_position; 141 | break; 142 | case ',': 143 | token->type = TOKEN_TYPE_COMMA; 144 | token->start = lexer->buffer + lexer->buffer_position; 145 | token->length = 1; 146 | ++lexer->buffer_position; 147 | break; 148 | case 'a' ... 'z': 149 | case 'A' ... 'Z': 150 | case '_': 151 | token->type = TOKEN_TYPE_IDENTIFIER; 152 | token->start = lexer->buffer + lexer->buffer_position; 153 | __LEXER_SKIP_WHILE(lexer, isident) 154 | token->length = lexer->buffer + lexer->buffer_position - token->start; 155 | break; 156 | case '0' ... '9': 157 | case '.': 158 | return lexer_get_number(lexer, token); 159 | default: 160 | return ERROR_INVALID_CHARACTER; 161 | } 162 | 163 | return ERROR_NO_ERROR; 164 | } 165 | -------------------------------------------------------------------------------- /src/simplify/lexer.h: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #ifndef SIMPLIFY_LEXER_H_ 4 | #define SIMPLIFY_LEXER_H_ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "simplify/errors.h" 12 | 13 | #ifndef LEXER_FILE_BUFFER_SIZE 14 | # define LEXER_FILE_BUFFER_SIZE 1024 15 | #endif 16 | 17 | /* lexical analyzer 18 | * The lexer walks through a buffer or file, picking out tokens 19 | */ 20 | typedef struct lexer lexer_t; 21 | 22 | /* A token labels part of a buffer 23 | */ 24 | typedef struct token token_t; 25 | 26 | /* Enumerates the different kinds of tokens 27 | */ 28 | typedef enum token_type token_type_t; 29 | 30 | enum token_type { 31 | TOKEN_TYPE_OPERATOR, 32 | TOKEN_TYPE_NUMBER, 33 | TOKEN_TYPE_IDENTIFIER, 34 | TOKEN_TYPE_LEFT_PAREN, 35 | TOKEN_TYPE_RIGHT_PAREN, 36 | TOKEN_TYPE_COMMA, 37 | TOKEN_TYPE_EOF, 38 | }; 39 | 40 | struct token { 41 | token_type_t type; 42 | 43 | char* start; 44 | size_t length; 45 | }; 46 | 47 | 48 | struct lexer { 49 | token_t token; 50 | 51 | char* buffer; 52 | size_t buffer_length; 53 | size_t buffer_position; 54 | }; 55 | 56 | /* initialize lexer from a null terminated string 57 | * @lexer the lexer to initialize 58 | * @buffer the buffer to read, the buffer is copied 59 | */ 60 | static inline void lexer_init_from_string(lexer_t* lexer, char* buffer) { 61 | lexer->buffer_length = strlen(buffer); 62 | lexer->buffer = malloc(lexer->buffer_length); 63 | lexer->buffer_position = 0; 64 | 65 | strncpy(lexer->buffer, buffer, lexer->buffer_length); 66 | } 67 | 68 | /* initialize a lexer from a file 69 | * 70 | * This function reads the file block-by-block into an intermediate buffer, 71 | * instead of immediately checking it's length and reading the entire file. 72 | * Generally it's optimal to use lexer_init_from_file, instead of this function. 73 | * `lexer_init_from_file` will call this function if it can't determine the length anyway. 74 | * 75 | * @lexer the lexer to initialize 76 | * @file the file read 77 | */ 78 | void lexer_init_from_file_buffered(lexer_t* lexer, FILE* file); 79 | 80 | /* initialize a lexer from a file 81 | * @lexer the lexer to initialize 82 | * @file the file read 83 | */ 84 | 85 | void lexer_init_from_file(lexer_t* lexer, FILE* file); 86 | 87 | /* free all the lexer's resources, except the file 88 | * @lexer the lexer to clean 89 | */ 90 | static inline void lexer_clean(lexer_t* lexer) { 91 | if (lexer->buffer) 92 | free(lexer->buffer); 93 | lexer->buffer_length = 0; 94 | lexer->buffer_position = 0; 95 | } 96 | 97 | 98 | /* draw the next token from the lexer 99 | * @lexer the lexer to draw from 100 | * @token a location to store the token 101 | * @return returns an error code 102 | */ 103 | error_t lexer_next(lexer_t* lexer, token_t* token); 104 | 105 | /* check if a character is a valid identifier 106 | * @c the character to check 107 | * @return a boolean integer, true if `c` is valid 108 | */ 109 | static inline int isident(char c) { 110 | return (c <= 'Z' && c >= 'A') 111 | || (c <= 'z' && c >= 'a') 112 | || c == '_'; 113 | } 114 | 115 | #endif // SIMPLIFY_LEXER_H_ 116 | -------------------------------------------------------------------------------- /src/simplify/parser.c: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "simplify/parser.h" 8 | #include "simplify/errors.h" 9 | error_t _parser_parse_expression_precedence_recursive(expression_parser_t* parser, 10 | expression_t* expression, 11 | operator_precedence_t precedence); 12 | 13 | error_t _parser_parse_expression_list_precedence(expression_parser_t* parser, 14 | expression_list_t* list, 15 | operator_precedence_t precedence); 16 | 17 | /* get the next token from the parser's internal lexer 18 | * 19 | * @parser 20 | * @out token output, this may be null 21 | * @return returns an error code 22 | */ 23 | static inline error_t _parser_next_token(expression_parser_t* parser, token_t* out) { 24 | if (out) 25 | *out = parser->previous; 26 | return lexer_next(parser->lexer, &parser->previous); 27 | } 28 | 29 | /* grab the parser's cached token 30 | * 31 | * @parser 32 | * @out token output 33 | */ 34 | static inline void _parser_peek_token(expression_parser_t* parser, token_t* out) { 35 | *out = parser->previous; 36 | } 37 | 38 | error_t parse_string(char* source, expression_t* result) { 39 | lexer_t lexer; 40 | expression_parser_t parser; 41 | 42 | lexer_init_from_string(&lexer, source); 43 | expression_parser_init(&parser, &lexer); 44 | 45 | error_t err = parser_parse_expression(&parser, result); 46 | expression_parser_clean(&parser); 47 | lexer_clean(&lexer); 48 | return err; 49 | } 50 | 51 | error_t parse_file(FILE* source, expression_list_t* result) { 52 | lexer_t lexer; 53 | expression_parser_t parser; 54 | 55 | lexer_init_from_file(&lexer, source); 56 | expression_parser_init(&parser, &lexer); 57 | 58 | error_t err = _parser_parse_expression_list_precedence(&parser, result, OPERATOR_PRECEDENCE_MINIMUM); 59 | expression_parser_clean(&parser); 60 | lexer_clean(&lexer); 61 | return err; 62 | } 63 | 64 | error_t _parser_parse_number(expression_parser_t* parser, expression_t* expr) { 65 | expr->type = EXPRESSION_TYPE_NUMBER; 66 | token_t token; 67 | error_t err; 68 | 69 | err = _parser_next_token(parser, &token); 70 | if (err) return err; 71 | 72 | char number_buffer[token.length + 1]; 73 | number_buffer[token.length] = 0; 74 | 75 | strncpy(&number_buffer[0], token.start, token.length); 76 | expr->number.value = malloc(sizeof(mpfr_t)); 77 | mpfr_init2(expr->number.value, ((mpfr_prec_t)token.length * 2) > mpfr_get_default_prec() ? 78 | (mpfr_prec_t)token.length * 2 : mpfr_get_default_prec()); 79 | mpfr_set_str(expr->number.value, &number_buffer[0], 10, MPFR_RNDF); 80 | 81 | return ERROR_NO_ERROR; 82 | } 83 | 84 | error_t _parser_parse_prefix_operator(expression_parser_t* parser, expression_t* expr) { 85 | expr->type = EXPRESSION_TYPE_PREFIX; 86 | token_t token; 87 | error_t err; 88 | 89 | err = _parser_next_token(parser, &token); 90 | if (err) return err; 91 | 92 | expr->prefix.prefix = *token.start; 93 | expr->prefix.right = malloc(sizeof(expression_t)); 94 | 95 | return _parser_parse_expression_precedence_recursive(parser, expr->prefix.right, OPERATOR_PRECEDENCE_MAXIMUM); 96 | } 97 | 98 | 99 | error_t _parser_parse_expression_list_precedence(expression_parser_t* parser, 100 | expression_list_t* list, 101 | operator_precedence_t precedence) { 102 | expression_t* next = malloc(sizeof(expression_t)); 103 | error_t err = _parser_parse_expression_precedence_recursive(parser, next, precedence); 104 | if (err) return err; 105 | expression_list_append(list, next); 106 | 107 | while (parser->previous.type == TOKEN_TYPE_COMMA) { 108 | _parser_next_token(parser, NULL); 109 | expression_t* next = malloc(sizeof(expression_t)); 110 | error_t err = _parser_parse_expression_precedence_recursive(parser, next, precedence); 111 | if (err) return err; 112 | expression_list_append(list, next); 113 | } 114 | return ERROR_NO_ERROR; 115 | } 116 | 117 | error_t _parser_parse_parentheses(expression_parser_t* parser, expression_t* expr) { 118 | token_t token; 119 | error_t err; 120 | 121 | err = parser_parse_expression(parser, expr); 122 | if (err) return err; 123 | 124 | err = _parser_next_token(parser, &token); 125 | if (err) return err; 126 | 127 | if (token.type != TOKEN_TYPE_RIGHT_PAREN) 128 | err = ERROR_STRAY_LEFT_PAREN; 129 | 130 | return err; 131 | } 132 | 133 | 134 | error_t _parser_parse_identifier(expression_parser_t* parser, expression_t* expr) { 135 | token_t identifier; 136 | token_t maybe_left_paren; 137 | error_t err; 138 | 139 | err = _parser_next_token(parser, &identifier); 140 | if (err) return err; 141 | 142 | _parser_peek_token(parser, &maybe_left_paren); 143 | // if the identifier is followed by a left-paren then assume it's a function 144 | if (maybe_left_paren.type == TOKEN_TYPE_LEFT_PAREN) { 145 | expr->type = EXPRESSION_TYPE_FUNCTION; 146 | expr->function.parameters = malloc(sizeof(expression_list_t)); 147 | 148 | expression_list_init(expr->function.parameters); 149 | 150 | token_t tok; 151 | err = _parser_next_token(parser, NULL); 152 | if (err) return err; 153 | _parser_peek_token(parser, &tok); 154 | 155 | if (tok.type != TOKEN_TYPE_RIGHT_PAREN) 156 | err = _parser_parse_expression_list_precedence(parser, 157 | expr->function.parameters, 158 | OPERATOR_PRECEDENCE_ASSIGN); 159 | _parser_next_token(parser, NULL); 160 | if (err) { 161 | expression_list_free(expr->function.parameters); 162 | return err; 163 | } 164 | 165 | // copy the function's name from the underlying buffer 166 | expr->function.name = malloc(identifier.length + 1); 167 | expr->function.name[identifier.length] = 0; 168 | strncpy(expr->function.name, identifier.start, identifier.length); 169 | } else { 170 | expr->type = EXPRESSION_TYPE_VARIABLE; 171 | 172 | // copy the variable's name from the underlying buffer 173 | expr->variable.value = malloc(identifier.length + 1); 174 | expr->variable.value[identifier.length] = 0; 175 | expr->variable.binding = NULL; 176 | strncpy(expr->variable.value, identifier.start, identifier.length); 177 | } 178 | 179 | return err; 180 | } 181 | 182 | /* _parse_expression_precedence_recursive parses an expression recursively. 183 | * 184 | * @parser the parser to draw from 185 | * @expression the parse result 186 | * @precedence The minimum operator precedence where this function will recurse. 187 | * @return returns an error code 188 | */ 189 | error_t _parser_parse_expression_precedence_recursive(expression_parser_t* parser, 190 | expression_t* expression, 191 | operator_precedence_t precedence) { 192 | token_t token; 193 | error_t err; 194 | 195 | _parser_peek_token(parser, &token); 196 | expression_t* left = malloc(sizeof(expression_t)); 197 | switch (token.type) { 198 | case TOKEN_TYPE_OPERATOR: 199 | err = _parser_parse_prefix_operator(parser, left); 200 | break; 201 | case TOKEN_TYPE_NUMBER: 202 | err = _parser_parse_number(parser, left); 203 | break; 204 | case TOKEN_TYPE_IDENTIFIER: 205 | err = _parser_parse_identifier(parser, left); 206 | break; 207 | case TOKEN_TYPE_LEFT_PAREN: 208 | { 209 | // when a left paren is hit, try to parse a sub-expression 210 | err = _parser_next_token(parser, NULL); 211 | if (err) goto cleanup; 212 | 213 | err = _parser_parse_parentheses(parser, left); 214 | break; 215 | } 216 | case TOKEN_TYPE_EOF: 217 | err = ERROR_UNEXPECTED_EOF; 218 | goto cleanup; 219 | case TOKEN_TYPE_COMMA: 220 | err = ERROR_UNEXPECTED_EOL; 221 | goto cleanup; 222 | default: 223 | err = ERROR_INVALID_TOKEN; 224 | goto cleanup; 225 | } 226 | if (err) goto cleanup; 227 | 228 | _parser_peek_token(parser, &token); 229 | if (token.type == TOKEN_TYPE_EOF) { 230 | goto cleanup; 231 | } 232 | 233 | operator_precedence_t operator_prec = operator_precedence(*token.start); 234 | while (left && precedence < operator_prec) { 235 | operator_t infix; 236 | 237 | // comma's are the end-of-expression delemiter, so if the parser hits one exit immediately 238 | if (token.type == TOKEN_TYPE_COMMA) { 239 | goto cleanup; 240 | } 241 | 242 | // left parens can be used as a multiplication operator 243 | if (token.type == TOKEN_TYPE_LEFT_PAREN) { 244 | expression_t* new_left = malloc(sizeof(expression_t)); 245 | expression_t* subexpr = malloc(sizeof(expression_t)); 246 | 247 | err = _parser_next_token(parser, NULL); 248 | if (err) goto cleanup; 249 | 250 | err = _parser_parse_parentheses(parser, subexpr); 251 | expression_init_operator(new_left, left, '*', subexpr); 252 | left = new_left; 253 | err = _parser_next_token(parser, &token); 254 | if (err) goto cleanup; 255 | if (token.type == TOKEN_TYPE_EOF) goto cleanup; 256 | 257 | infix = *token.start; 258 | } else if (token.type == TOKEN_TYPE_NUMBER || token.type == TOKEN_TYPE_IDENTIFIER) { 259 | infix = '*'; 260 | } else if (token.type == TOKEN_TYPE_OPERATOR) { 261 | infix = *token.start; 262 | err = _parser_next_token(parser, &token); 263 | } else { 264 | return ERROR_INVALID_TOKEN; 265 | } 266 | if (err) goto cleanup; 267 | 268 | expression_t* right_operand = malloc(sizeof(expression_t)); 269 | err = _parser_parse_expression_precedence_recursive(parser, right_operand, operator_prec); 270 | if (err) { 271 | free(right_operand); 272 | goto cleanup; 273 | } 274 | 275 | _parser_peek_token(parser, &token); 276 | expression_t* new_left = malloc(sizeof(expression_t)); 277 | expression_init_operator(new_left, left, infix, right_operand); 278 | left = new_left; 279 | 280 | if (token.type == TOKEN_TYPE_EOF) goto cleanup; 281 | operator_prec = operator_precedence(*token.start); 282 | } 283 | 284 | cleanup: 285 | *expression = *left; 286 | free(left); 287 | return err; 288 | } 289 | 290 | error_t parser_parse_expression(expression_parser_t* parser, expression_t* result) { 291 | return _parser_parse_expression_precedence_recursive(parser, result, OPERATOR_PRECEDENCE_MINIMUM); 292 | } 293 | -------------------------------------------------------------------------------- /src/simplify/parser.h: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #ifndef SIMPLIFY_PARSER_H_ 4 | #define SIMPLIFY_PARSER_H_ 5 | 6 | #include "simplify/expression/expression.h" 7 | #include "simplify/lexer.h" 8 | 9 | /* transforms a stream of tokens into an expression. 10 | */ 11 | typedef struct expression_parser expression_parser_t; 12 | 13 | struct expression_parser { 14 | lexer_t* lexer; 15 | token_t previous; 16 | }; 17 | 18 | static inline void expression_parser_init(expression_parser_t* parser, lexer_t* lexer) { 19 | parser->lexer = lexer, 20 | lexer_next(lexer, &parser->previous); 21 | } 22 | 23 | /* clean all resources associated with a parser 24 | * @parser the parser to clean 25 | */ 26 | static inline void expression_parser_clean(expression_parser_t* parser) { 27 | (void)parser; 28 | } 29 | 30 | /* parse an expression 31 | * NOTE: most of the time it's safer and easier to use the `parse_string` or `parse_file` functions 32 | * 33 | * @parser The parser used to draw the expression. 34 | * @result The expression that should be filled. 35 | * @return returns an error code 36 | */ 37 | error_t parser_parse_expression(expression_parser_t* parser, expression_t* result); 38 | 39 | /* parses an expression from a string 40 | * 41 | * @source the string to parse 42 | * @result the expression to be filled 43 | * @return returns an error code 44 | */ 45 | error_t parse_string(char* source, expression_t* result); 46 | 47 | /* parses an expression from a file 48 | * 49 | * @source the file to parse 50 | * @result the expression to be filled 51 | * @return returns an error code 52 | */ 53 | error_t parse_file(FILE* source, expression_list_t* result); 54 | 55 | 56 | #endif // SIMPLIFY_PARSER_H_ 57 | -------------------------------------------------------------------------------- /src/simplify/rbtree/rbtree.c: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include "simplify/rbtree/rbtree.h" 7 | 8 | 9 | static inline rbtree_node_t* rbtree_grandparent(rbtree_node_t* node) { 10 | return node->parent != NULL ? node->parent->parent : NULL; 11 | } 12 | 13 | static inline rbtree_node_t* rbtree_uncle(rbtree_node_t* node) { 14 | rbtree_node_t* gp = rbtree_grandparent(node); 15 | if (!gp) return NULL; 16 | return gp->left == node->parent ? gp->right : gp->left; 17 | } 18 | 19 | static inline void rbtree_rotate_left(rbtree_t* tree, rbtree_node_t* node) { 20 | rbtree_node_t* right = node->right; 21 | 22 | // Swap node's right subtree with it's child 23 | node->right = node->right->left; 24 | 25 | // if right's left child is not root, make it's parent node. 26 | if (right->left) 27 | right->left->parent = node; 28 | 29 | right->parent = node->parent; 30 | 31 | if (!node->parent) 32 | // since root doesn't have an uncle there is a special case 33 | tree->root = right; 34 | 35 | // otherwise swap the uncle with node's old left child 36 | else if (node == node->parent->left) 37 | node->parent->left = right; 38 | else 39 | node->parent->right = right; 40 | 41 | right->left = node; 42 | node->parent = right; 43 | } 44 | 45 | static inline void rbtree_rotate_right(rbtree_t* tree, rbtree_node_t* node) { 46 | rbtree_node_t* left = node->left; 47 | 48 | // Swap node's left subtree with it's child 49 | node->left = node->left->right; 50 | 51 | // if right's right child is not root, make it's parent node. 52 | if (left->right) 53 | left->right->parent = node; 54 | 55 | left->parent = node->parent; 56 | 57 | if (!node->parent) 58 | // since root doesn't have an uncle there is a special case 59 | tree->root = left; 60 | 61 | // otherwise swap the uncle with node's old right child 62 | else if (node == node->parent->right) 63 | node->parent->right = left; 64 | else 65 | node->parent->left = left; 66 | 67 | left->right = node; 68 | node->parent = left; 69 | } 70 | 71 | void rbtree_free_node_recursive(rbtree_node_t* node, void(*free_func)(void*)) { 72 | #if !defined(RBTREE_USE_CHUNKS) 73 | free(node->key); 74 | #endif 75 | 76 | if (free_func) 77 | free_func(node->data); 78 | if (node->left) 79 | rbtree_free_node_recursive(node->left, free_func); 80 | 81 | if (node->right) 82 | rbtree_free_node_recursive(node->right, free_func); 83 | 84 | #if !defined(RBTREE_USE_CHUNKS) 85 | free(node); 86 | #endif 87 | } 88 | 89 | error_t rbtree_basic_insert(rbtree_t* tree, rbtree_node_t* node) { 90 | if (!tree->root) { 91 | tree->root = node; 92 | node->parent = NULL; 93 | return ERROR_NO_ERROR; 94 | } 95 | 96 | rbtree_node_t* current = tree->root; 97 | while (1) { 98 | int result = strcmp(node->key, current->key); 99 | if (result == 0) { 100 | current->data = node->data; 101 | return ERROR_NO_ERROR; 102 | } else if (result > 0) { 103 | if (current->left) { 104 | current = current->left; 105 | } else { 106 | current->left = node; 107 | break; 108 | } 109 | } else { 110 | if (current->right) { 111 | current = current->right; 112 | } else { 113 | current->right = node; 114 | break; 115 | } 116 | } 117 | } 118 | node->parent = current; 119 | 120 | return ERROR_NO_ERROR; 121 | } 122 | 123 | error_t rbtree_search(rbtree_t* tree, char* key, void** dataout) { 124 | if (!tree->root) 125 | return ERROR_NONEXISTANT_KEY; 126 | 127 | rbtree_node_t* current = tree->root; 128 | while (current) { 129 | int result = strcmp(key, current->key); 130 | if (result == 0) { 131 | *dataout = current->data; 132 | return ERROR_NO_ERROR; 133 | } else if (result > 0) { 134 | current = current->left; 135 | } else { 136 | current = current->right; 137 | } 138 | } 139 | 140 | return ERROR_NONEXISTANT_KEY; 141 | } 142 | 143 | void rbtree_clean(rbtree_t* tree, void(*free_func)(void*)) { 144 | if (tree->root) 145 | rbtree_free_node_recursive(tree->root, free_func); 146 | 147 | #if defined(RBTREE_USE_CHUNKS) 148 | struct rbtree_chunk* slab = tree->slab; 149 | while (slab) { 150 | struct rbtree_chunk* last = slab->last; 151 | free(slab->data); 152 | free(slab); 153 | slab = last; 154 | } 155 | #endif 156 | } 157 | 158 | error_t rbtree_balance(rbtree_t* tree, rbtree_node_t* node) { 159 | rbtree_node_t* gp; 160 | rbtree_node_t* uncle; 161 | while (node != tree->root && node->parent->color == RBTREE_COLOR_RED) { 162 | gp = rbtree_grandparent(node); 163 | if (!gp) 164 | break; 165 | uncle = rbtree_uncle(node); 166 | 167 | if (uncle && uncle->color == RBTREE_COLOR_RED) { 168 | gp->color = RBTREE_COLOR_RED; 169 | node->parent->color = RBTREE_COLOR_BLACK; 170 | uncle->color = RBTREE_COLOR_BLACK; 171 | node = gp; 172 | } else { 173 | if (gp->left == node->parent) { 174 | if (node == node->parent->right) { 175 | rbtree_rotate_left(tree, node->parent); 176 | } 177 | 178 | rbtree_rotate_right(tree, gp); 179 | } else if (gp->right == node->parent) { 180 | if (node == node->parent->left) { 181 | rbtree_rotate_right(tree, node->parent); 182 | } 183 | 184 | rbtree_rotate_left(tree, gp); 185 | } 186 | if (node->parent) { 187 | rbtree_color_t t = node->parent->color; 188 | node->parent->color = gp->color; 189 | gp->color = t; 190 | } 191 | } 192 | } 193 | tree->root->color = RBTREE_COLOR_BLACK; 194 | return ERROR_NO_ERROR; 195 | } 196 | 197 | error_t rbtree_insert(rbtree_t* tree, char* key, void* value) { 198 | size_t key_len = strlen(key); 199 | 200 | #if defined(RBTREE_USE_CHUNKS) 201 | if (tree->slab == NULL) { 202 | tree->slab = malloc(sizeof(struct rbtree_chunk)); 203 | tree->slab->used = 0; 204 | tree->slab->data = malloc(RBTREE_CHUNK_SIZE); 205 | tree->slab->last = NULL; 206 | } 207 | 208 | if (RBTREE_CHUNK_SIZE < sizeof(rbtree_node_t) + key_len + 1 + tree->slab->used) { 209 | struct rbtree_chunk* old = tree->slab; 210 | tree->slab = malloc(sizeof(struct rbtree_chunk)); 211 | tree->slab->used = 0; 212 | tree->slab->data = malloc(RBTREE_CHUNK_SIZE); 213 | tree->slab->last = old; 214 | } 215 | 216 | rbtree_node_t* node = (rbtree_node_t*)(tree->slab->data + tree->slab->used); 217 | node->key = (char*)(sizeof(rbtree_node_t) + tree->slab->data + tree->slab->used); 218 | tree->slab->used += sizeof(rbtree_node_t) + key_len + 1; 219 | #else 220 | rbtree_node_t* node = malloc(sizeof(rbtree_node_t)); 221 | node->key = malloc(key_len + 1); 222 | #endif 223 | 224 | node->left = NULL; 225 | node->right = NULL; 226 | node->parent = NULL; 227 | node->color = RBTREE_COLOR_RED; 228 | node->data = value; 229 | 230 | node->key[key_len] = 0; 231 | strncpy(node->key, key, key_len); 232 | 233 | error_t err = rbtree_basic_insert(tree, node); 234 | if (err) return err; 235 | 236 | if (!node->parent) { 237 | node->color = RBTREE_COLOR_BLACK; 238 | return ERROR_NO_ERROR; 239 | } 240 | 241 | return rbtree_balance(tree, node); 242 | } 243 | -------------------------------------------------------------------------------- /src/simplify/rbtree/rbtree.h: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #ifndef SIMPLIFY_RBTREE_RBTREE_H_ 4 | #define SIMPLIFY_RBTREE_RBTREE_H_ 5 | 6 | #include "simplify/errors.h" 7 | 8 | /* default to 2KB for RBTREE_CHUNK_SIZE 9 | This gaurentees the all the builtins can fit in one chunk 10 | */ 11 | #if !defined(RBTREE_CHUNK_SIZE) && defined(RBTREE_USE_CHUNKS) 12 | # define RBTREE_CHUNK_SIZE 2048 13 | #endif 14 | 15 | /* A node in the Red-Black Tree 16 | */ 17 | typedef struct rbtree_node rbtree_node_t; 18 | 19 | /* A Red-Black Tree 20 | * 21 | * A Red-Black Tree is a type of self-balancing binary search tree. 22 | */ 23 | typedef struct rbtree rbtree_t; 24 | 25 | /* Either Red, or Black 26 | */ 27 | typedef enum rbtree_color rbtree_color_t; 28 | 29 | 30 | enum rbtree_color { 31 | RBTREE_COLOR_RED, 32 | RBTREE_COLOR_BLACK, 33 | }; 34 | 35 | struct rbtree_node { 36 | rbtree_color_t color; 37 | 38 | struct rbtree_node* left; 39 | struct rbtree_node* right; 40 | struct rbtree_node* parent; 41 | 42 | void* data; 43 | char* key; 44 | }; 45 | 46 | #if defined(RBTREE_USE_CHUNKS) 47 | struct rbtree_chunk { 48 | void* data; 49 | size_t used; 50 | struct rbtree_chunk* last; 51 | }; 52 | 53 | struct rbtree { 54 | /* to avoid allocations allocate rbtree memory to the slab */ 55 | struct rbtree_chunk* slab; 56 | struct rbtree_node* root; 57 | }; 58 | #else 59 | struct rbtree { 60 | struct rbtree_node* root; 61 | }; 62 | #endif 63 | 64 | /* insert a new item into the tree 65 | * 66 | * @tree the tree to insert into 67 | * @key the key to assign to `data` 68 | * @data the data to insert into the tree 69 | * @return an error code 70 | */ 71 | error_t rbtree_insert(rbtree_t* tree, char* key, void* data); 72 | 73 | /* get an item from the tree 74 | * 75 | * @tree the tree to search 76 | * @key the key to look for 77 | * @out_data a pointer to a pointer that will be assigned the pointer passed into the tree with rbtree_insert 78 | * @return an error is returned if the key is not found, and `data_out` is not assigned to. 79 | */ 80 | error_t rbtree_search(rbtree_t* tree, char* key, void** data_out); 81 | 82 | /* free all resources from a tree (both keys and data) 83 | * 84 | * @tree the tree to clean 85 | * @data_cleaner a function to clean up the tree's data 86 | */ 87 | void rbtree_clean(rbtree_t* tree, void(*data_cleaner)(void*)); 88 | 89 | /* initialize an rbtree 90 | * 91 | * @tree the tree to initialize 92 | */ 93 | static inline void rbtree_init(rbtree_t* tree) { 94 | tree->root = NULL; 95 | 96 | #if defined(RBTREE_USE_CHUNKS) 97 | tree->slab = NULL; 98 | #endif 99 | } 100 | 101 | #endif // SIMPLIFY_RBTREE_RBTREE_H_ 102 | -------------------------------------------------------------------------------- /test/compare.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include "simplify/expression/expression.h" 3 | #include "simplify/expression/stringify.h" 4 | #include "simplify/expression/isolate.h" 5 | #include "simplify/expression/simplify.h" 6 | #include "simplify/expression/evaluate.h" 7 | 8 | static struct { 9 | char* expr1; 10 | char* expr2; 11 | compare_result_t result; 12 | } __expr_result_pairs[] = { 13 | {"2", "4", COMPARE_RESULT_LESS}, 14 | {"10", "10", COMPARE_RESULT_EQUAL}, 15 | {"10", "-10", COMPARE_RESULT_GREATER}, 16 | {"-0.2", "0.2", COMPARE_RESULT_LESS}, 17 | {"-0.0002", "-1", COMPARE_RESULT_GREATER}, 18 | {"-5", "-6", COMPARE_RESULT_GREATER}, 19 | {"10", "1", COMPARE_RESULT_GREATER}, 20 | {"-2", "-2", COMPARE_RESULT_EQUAL}, 21 | {"x + 5", "1", COMPARE_RESULT_INCOMPARABLE}, 22 | {"2 * x", "2x", COMPARE_RESULT_EQUAL}, 23 | {"2.25", "2.2", COMPARE_RESULT_GREATER}, 24 | {"2.0003", "2.2", COMPARE_RESULT_LESS}, 25 | {".0001", "0.00002", COMPARE_RESULT_GREATER}, 26 | {"1.000001", "1.00002", COMPARE_RESULT_LESS}, 27 | {"5e2", "5e3", COMPARE_RESULT_LESS}, 28 | {"y * (x2) + x^4", "x^4 + y * (2 * x)", COMPARE_RESULT_EQUAL}, 29 | {"(y)f(x + 5)", "f(x + 5) * y", COMPARE_RESULT_EQUAL}, 30 | {"x * x * x", "x ^ 2 * x", COMPARE_RESULT_EQUAL}, 31 | }; 32 | 33 | int main() { 34 | error_t err; 35 | for (int i = 0; i < (int) (sizeof(__expr_result_pairs) / sizeof(__expr_result_pairs[0])); ++i) { 36 | expression_t expr1; 37 | expression_t expr2; 38 | scope_t scope; 39 | 40 | printf("starting test #%d...", i + 1); 41 | err = parse_string(__expr_result_pairs[i].expr1, &expr1); 42 | if (err) 43 | FATAL("failed to parse expression 1/2 \"%s\": %s", __expr_result_pairs[i].expr1, error_string(err)); 44 | 45 | err = parse_string(__expr_result_pairs[i].expr2, &expr2); 46 | if (err) 47 | FATAL("failed to parse expression 2/2 \"%s\": %s", __expr_result_pairs[i].expr2, error_string(err)); 48 | 49 | scope_init(&scope); 50 | 51 | 52 | err = expression_evaluate(&expr1, &scope); 53 | if (err) 54 | FATAL("failed to evaluate 1/2 \"%s\": %s", __expr_result_pairs[i].expr1, error_string(err)); 55 | 56 | err = expression_evaluate(&expr2, &scope); 57 | if (err) 58 | FATAL("failed to evaluate 2/2 \"%s\": %s", __expr_result_pairs[i].expr2, error_string(err)); 59 | 60 | err = expression_simplify(&expr1); 61 | if (err) 62 | FATAL("failed to simplify expression 1/2 \"%s\": %s", __expr_result_pairs[i].expr1, error_string(err)); 63 | 64 | err = expression_simplify(&expr2); 65 | if (err) 66 | FATAL("failed to simplify expression 2/2 \"%s\": %s", __expr_result_pairs[i].expr2, error_string(err)); 67 | 68 | compare_result_t cmp = expression_compare(&expr1, &expr2); 69 | if (cmp != __expr_result_pairs[i].result) { 70 | FATAL("Test failed, expected the relationship of '%s' and '%s' to be %s, got %s", 71 | __expr_result_pairs[i].expr1, __expr_result_pairs[i].expr2, 72 | print_compare_result(__expr_result_pairs[i].result), print_compare_result(cmp)); 73 | } 74 | scope_clean(&scope); 75 | expression_clean(&expr1); 76 | expression_clean(&expr2); 77 | printf("done\n"); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /test/expression.c: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #include "test/test.h" 4 | #include "simplify/expression/expression.h" 5 | #include "simplify/expression/stringify.h" 6 | #include "simplify/expression/isolate.h" 7 | #include "simplify/expression/simplify.h" 8 | #include "simplify/expression/evaluate.h" 9 | #include "simplify/builtins.h" 10 | 11 | #define OP_EVALUATE 1 12 | #define OP_ISOLATE_X 2 13 | #define OP_COMPLEX_SIMPLIFY 4 14 | #define OP_BOOLEAN_TRUE 8 15 | #define OP_BOOLEAN_FALSE 16 16 | 17 | DEFINE_MPFR_FUNCTION(cos) 18 | DEFINE_MPFR_CONST(pi) 19 | 20 | 21 | error_t builtin_func_log(scope_t* scope, expression_t** out) { 22 | error_t err; 23 | expression_t* b = malloc(sizeof(expression_t)); 24 | expression_t* y = malloc(sizeof(expression_t)); 25 | 26 | scope_get_value(scope, "__arg0", b); 27 | scope_get_value(scope, "__arg1", y); 28 | 29 | err = expression_do_logarithm(b, y, out); 30 | if (err) return err; 31 | 32 | return expression_evaluate(*out, scope); 33 | } 34 | 35 | error_t builtin_const_e(scope_t* _, expression_t** out) { 36 | (void)_; 37 | 38 | *out = expression_new_number_si(0); 39 | mpfr_t n; 40 | mpfr_t x; 41 | mpfr_init(n); 42 | mpfr_init(x); 43 | mpfr_init((*out)->number.value); 44 | 45 | mpfr_set_ui(n, 9999999999UL, MPFR_RNDN); 46 | mpfr_ui_div(x, 1, n, MPFR_RNDN); 47 | mpfr_add_ui(x, x, 1, MPFR_RNDN); 48 | mpfr_pow((*out)->number.value, x, n, MPFR_RNDN); 49 | 50 | mpfr_clear(n); 51 | mpfr_clear(x); 52 | 53 | return ERROR_NO_ERROR; 54 | } 55 | 56 | 57 | int main() { 58 | struct { 59 | char* string; 60 | int ops; 61 | expression_t* expr; 62 | } __string_expr_pairs[] = { 63 | { "2 * 5.5", OP_EVALUATE, 64 | expression_new_number_d(11), 65 | }, 66 | { "2 * 9x", OP_EVALUATE, 67 | expression_new_operator( 68 | expression_new_number_d(18), 69 | '*', 70 | expression_new_variable("x")) 71 | }, 72 | { "4 \\ 2 = 5", OP_EVALUATE | OP_BOOLEAN_FALSE, 73 | expression_new_number_d(5) 74 | }, 75 | { "4 < 10", OP_EVALUATE | OP_BOOLEAN_TRUE, 76 | expression_new_number_d(10) 77 | }, 78 | { "9 > 51", OP_EVALUATE | OP_BOOLEAN_FALSE, 79 | expression_new_number_d(51) 80 | }, 81 | { "9 > 2", OP_EVALUATE | OP_BOOLEAN_TRUE, 82 | expression_new_number_d(2) 83 | }, 84 | { "9 = 9", OP_EVALUATE | OP_BOOLEAN_TRUE, 85 | expression_new_number_d(9) 86 | }, 87 | { "x ^ 3 * 5(6 + 0 \\ 1)", OP_EVALUATE, 88 | expression_new_operator( 89 | expression_new_operator( 90 | expression_new_variable("x"), 91 | '^', 92 | expression_new_number_d(3)), 93 | '*', 94 | expression_new_number_d(30)) 95 | }, 96 | {"(0 - x) = 1", OP_EVALUATE | OP_ISOLATE_X, 97 | expression_new_operator( 98 | expression_new_variable("x"), 99 | '=', 100 | expression_new_number_d(-1)) 101 | }, 102 | {"5 / x = 2", OP_EVALUATE | OP_ISOLATE_X, 103 | expression_new_operator( 104 | expression_new_variable("x"), 105 | '=', 106 | expression_new_number_d(2.5)) 107 | }, 108 | {"4 \\ x = 2", OP_EVALUATE | OP_ISOLATE_X, 109 | expression_new_operator( 110 | expression_new_variable("x"), 111 | '=', 112 | expression_new_number_d(2)) 113 | }, 114 | {"cos(pi2)", OP_EVALUATE, 115 | expression_new_number_si(1), 116 | }, 117 | {"f(x, y): 10x^y", OP_EVALUATE, 118 | expression_new_operator( 119 | expression_new_number_d(10), 120 | '*', 121 | expression_new_operator( 122 | expression_new_variable("x"), 123 | '^', 124 | expression_new_variable("y"))) 125 | }, 126 | {"5 / x : 2 * 10", OP_EVALUATE, 127 | expression_new_number_d(0.25) 128 | }, 129 | {"+(-5 * 2)", OP_EVALUATE, 130 | expression_new_number_d(-10) 131 | }, 132 | {"-(3 ^ 4)", OP_EVALUATE, 133 | expression_new_number_d(-81) 134 | }, 135 | {"-x = 4", OP_EVALUATE | OP_ISOLATE_X, 136 | expression_new_operator( 137 | expression_new_variable("x"), 138 | '=', 139 | expression_new_number_d(-4)) 140 | }, 141 | {"99 = -x * 2", OP_EVALUATE | OP_ISOLATE_X, 142 | expression_new_operator( 143 | expression_new_variable("x"), 144 | '=', 145 | expression_new_number_d(-49.5)) 146 | }, 147 | {"99 = x / 2", OP_EVALUATE | OP_ISOLATE_X, 148 | expression_new_operator( 149 | expression_new_variable("x"), 150 | '=', 151 | expression_new_number_d(198)) 152 | }, 153 | {"-8 = 2 * x ^ 2", OP_EVALUATE | OP_ISOLATE_X, 154 | expression_new_operator( 155 | expression_new_variable("x"), 156 | '=', 157 | expression_new_number_d(-2)) 158 | }, 159 | {"11 = x \\ 2", OP_EVALUATE | OP_ISOLATE_X, 160 | expression_new_operator( 161 | expression_new_variable("x"), 162 | '=', 163 | expression_new_number_d(121)) 164 | }, 165 | {"0 = x - 2", OP_EVALUATE | OP_ISOLATE_X, 166 | expression_new_operator( 167 | expression_new_variable("x"), 168 | '=', 169 | expression_new_number_d(2)) 170 | }, 171 | {"-8 = +x + 2", OP_EVALUATE | OP_ISOLATE_X, 172 | expression_new_operator( 173 | expression_new_variable("x"), 174 | '=', 175 | expression_new_number_d(-10)) 176 | }, 177 | {"-8 = x(y) + 2", OP_EVALUATE | OP_ISOLATE_X, 178 | expression_new_operator( 179 | expression_new_function("x", 1, expression_new_variable("y")), 180 | '=', 181 | expression_new_number_d(-10)) 182 | }, 183 | {"x(y, z) + 10", OP_EVALUATE | OP_ISOLATE_X, 184 | expression_new_operator( 185 | expression_new_function("x", 2, expression_new_variable("y"), expression_new_variable("z")), 186 | '=', 187 | expression_new_number_d(-10)) 188 | }, 189 | {"x * x * 3", OP_EVALUATE | OP_COMPLEX_SIMPLIFY, 190 | expression_new_operator( 191 | expression_new_operator( 192 | expression_new_variable("x"), 193 | '^', 194 | expression_new_number_d(2)), 195 | '*', 196 | expression_new_number_d(3)) 197 | }, 198 | {"3 + x * x * x * 3", OP_EVALUATE | OP_COMPLEX_SIMPLIFY, 199 | expression_new_operator( 200 | expression_new_number_d(3), 201 | '+', 202 | expression_new_operator( 203 | expression_new_operator( 204 | expression_new_variable("x"), 205 | '^', 206 | expression_new_number_d(3)), 207 | '*', 208 | expression_new_number_d(3))) 209 | }, 210 | {"3 * x * x * x * 3", OP_EVALUATE | OP_COMPLEX_SIMPLIFY, 211 | expression_new_operator( 212 | expression_new_operator( 213 | expression_new_number_d(3), 214 | '*', 215 | expression_new_operator( 216 | expression_new_variable("x"), 217 | '^', 218 | expression_new_number_d(3))), 219 | '*', 220 | expression_new_number_d(3)) 221 | }, 222 | {"100 ^ x * x * x * 3", OP_EVALUATE | OP_COMPLEX_SIMPLIFY, 223 | expression_new_operator( 224 | expression_new_operator( 225 | expression_new_operator( 226 | expression_new_number_d(100), 227 | '^', 228 | expression_new_variable("x")), 229 | '*', 230 | expression_new_operator( 231 | expression_new_variable("x"), 232 | '^', 233 | expression_new_number_d(2))), 234 | '*', 235 | expression_new_number_d(3)) 236 | }, 237 | {"5 + x", OP_EVALUATE | OP_ISOLATE_X, 238 | expression_new_operator( 239 | expression_new_variable("x"), 240 | '=', 241 | expression_new_number_si(-5)) 242 | }, 243 | {"23 ^ x = 2", OP_EVALUATE | OP_ISOLATE_X, 244 | expression_new_operator( 245 | expression_new_variable("x"), 246 | '=', 247 | expression_new_number_d(0.2210647294575037169472)) 248 | }, 249 | {"2 ^ x = 10", OP_EVALUATE | OP_ISOLATE_X, 250 | expression_new_operator( 251 | expression_new_variable("x"), 252 | '=', 253 | expression_new_number_d(3.3219280948873617376193)) 254 | }, 255 | /* TODO(IanS5) this test should be corrected once better approximating is implemented 256 | For now it will be just .0000007132118753361282 off */ 257 | {"log(2, x + 5) = 3", OP_EVALUATE | OP_ISOLATE_X, 258 | expression_new_operator( 259 | expression_new_variable("x"), 260 | '=', 261 | expression_new_number_d(3.0000007132118753361282)) 262 | }, 263 | }; 264 | 265 | 266 | error_t err; 267 | for (int i = 0; i < (int) (sizeof(__string_expr_pairs) / sizeof(__string_expr_pairs[0])); ++i) { 268 | expression_t expr; 269 | scope_t scope; 270 | 271 | printf("starting test #%d (%s)...", i + 1, __string_expr_pairs[i].string); 272 | err = parse_string(__string_expr_pairs[i].string, &expr); 273 | if (err) 274 | FATAL("failed to parse string \"%s\": %s", __string_expr_pairs[i].string, error_string(err)); 275 | 276 | scope_init(&scope); 277 | EXPORT_BUILTIN_FUNCTION2(&scope, log); 278 | EXPORT_BUILTIN_FUNCTION(&scope, cos); 279 | EXPORT_BUILTIN_CONST(&scope, pi); 280 | EXPORT_BUILTIN_CONST(&scope, e); 281 | 282 | 283 | if (__string_expr_pairs[i].ops & OP_EVALUATE) { 284 | err = expression_evaluate(&expr, &scope); 285 | if (err) 286 | FATAL("failed to evaluate \"%s\": %s", __string_expr_pairs[i].string, error_string(err)); 287 | } 288 | 289 | if (__string_expr_pairs[i].ops & OP_COMPLEX_SIMPLIFY) { 290 | err = expression_simplify(&expr); 291 | if (err) 292 | FATAL("failed to simplify expression \"%s\": %s", __string_expr_pairs[i].string, error_string(err)); 293 | } 294 | 295 | if (__string_expr_pairs[i].ops & OP_ISOLATE_X) { 296 | err = expression_isolate_variable(&expr, "x"); 297 | if (err) 298 | FATAL("failed to isolate 'x' in expression \"%s\": %s", 299 | __string_expr_pairs[i].string, error_string(err)); 300 | err = expression_evaluate(&expr, &scope); 301 | if (err) 302 | FATAL("failed to evaluate (pass 2) \"%s\": %s", __string_expr_pairs[i].string, error_string(err)); 303 | } 304 | 305 | if (__string_expr_pairs[i].ops & OP_BOOLEAN_FALSE 306 | && expression_evaluate_comparisons(&expr) != EXPRESSION_RESULT_FALSE) { 307 | FATAL("failed to evaluate, expression was not false \"%s\"", __string_expr_pairs[i].string); 308 | } 309 | 310 | if (__string_expr_pairs[i].ops & OP_BOOLEAN_TRUE 311 | && expression_evaluate_comparisons(&expr) != EXPRESSION_RESULT_TRUE) { 312 | FATAL("failed to evaluate, expression was not true \"%s\"", __string_expr_pairs[i].string); 313 | } 314 | 315 | 316 | expression_assert_eq(&expr, __string_expr_pairs[i].expr); 317 | scope_clean(&scope); 318 | expression_clean(&expr); 319 | printf("done\n"); 320 | } 321 | } -------------------------------------------------------------------------------- /test/lexer.c: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #include "test/test.h" 4 | #include "simplify/lexer.h" 5 | 6 | struct { 7 | char* string; 8 | token_t tokens[7]; 9 | } __string_token_pairs[] = { 10 | {"", 11 | {{TOKEN_TYPE_EOF, "", 0}} 12 | }, 13 | {"5", 14 | {{TOKEN_TYPE_NUMBER, "5", 1}, 15 | {TOKEN_TYPE_EOF, "", 0}}, 16 | }, 17 | {" 094351267 ", 18 | {{TOKEN_TYPE_NUMBER, "094351267", 9}, 19 | {TOKEN_TYPE_EOF, "", 0}}, 20 | }, 21 | {"1E", 22 | {{TOKEN_TYPE_NUMBER, "1E", 2}, 23 | {TOKEN_TYPE_EOF, "", 0}}, 24 | }, 25 | {"1e90", 26 | {{TOKEN_TYPE_NUMBER, "1e90", 4}, 27 | {TOKEN_TYPE_EOF, "", 0}}, 28 | }, 29 | {"1.1e02", 30 | {{TOKEN_TYPE_NUMBER, "1.1e02", 6}, 31 | {TOKEN_TYPE_EOF, "", 0}}, 32 | }, 33 | {"1.4e-2", 34 | {{TOKEN_TYPE_NUMBER, "1.4e-2", 6}, 35 | {TOKEN_TYPE_EOF, "", 0}}, 36 | }, 37 | {"_", 38 | {{TOKEN_TYPE_IDENTIFIER, "_", 1}, 39 | {TOKEN_TYPE_EOF, "", 0}}, 40 | }, 41 | {"ALL CAPS", 42 | {{TOKEN_TYPE_IDENTIFIER, "ALL", 3}, 43 | {TOKEN_TYPE_IDENTIFIER, "CAPS", 4}, 44 | {TOKEN_TYPE_EOF, "", 0}}, 45 | }, 46 | {"12345 + 32134 ) \\", 47 | {{TOKEN_TYPE_NUMBER, "12345", 5}, 48 | {TOKEN_TYPE_OPERATOR, "+", 1}, 49 | {TOKEN_TYPE_NUMBER, "32134", 5}, 50 | {TOKEN_TYPE_RIGHT_PAREN, ")", 1}, 51 | {TOKEN_TYPE_OPERATOR, "\\", 1}, 52 | {TOKEN_TYPE_EOF, "", 0}} 53 | }, 54 | {"var / * () other_var", 55 | {{TOKEN_TYPE_IDENTIFIER, "var", 3}, 56 | {TOKEN_TYPE_OPERATOR, "/", 1}, 57 | {TOKEN_TYPE_OPERATOR, "*", 1}, 58 | {TOKEN_TYPE_LEFT_PAREN, "(", 1}, 59 | {TOKEN_TYPE_RIGHT_PAREN, ")", 1}, 60 | {TOKEN_TYPE_IDENTIFIER, "other_var", 9}, 61 | {TOKEN_TYPE_EOF, "", 0}} 62 | }, 63 | }; 64 | 65 | struct { 66 | char* string; 67 | error_t error; 68 | } __token_error_pairs[] = { 69 | {"$", ERROR_INVALID_CHARACTER}, 70 | {"easda$_", ERROR_INVALID_CHARACTER}, 71 | {"123e&", ERROR_INVALID_CHARACTER}, 72 | }; 73 | 74 | 75 | int main() { 76 | token_t tok; 77 | token_t ftok; 78 | error_t err; 79 | tok.type = 0; 80 | 81 | for (size_t i = 0; i < sizeof(__string_token_pairs) / sizeof(__string_token_pairs[0]); ++i) { 82 | lexer_t lexer; 83 | lexer_t flexer; 84 | 85 | FILE* f = fopen("lexer_test.txt", "w+"); 86 | if (!f) FATAL("Failed to open file lexer_test.txt"); 87 | 88 | lexer_init_from_string(&lexer, __string_token_pairs[i].string); 89 | fwrite(__string_token_pairs[i].string, strlen(__string_token_pairs[i].string), 1, f); 90 | fflush(f); 91 | fseek(f, 0, SEEK_SET); 92 | 93 | // switch file read methods every other string 94 | if (i % 2 == 0) { 95 | lexer_init_from_file(&flexer, f); 96 | } else { 97 | lexer_init_from_file_buffered(&flexer, f); 98 | } 99 | fclose(f); 100 | 101 | for (int j = 0; ; ++j) { 102 | err = lexer_next(&lexer, &tok); 103 | if (err) { 104 | FATAL("failed to get next token (string lexer): %s", error_string(err)); 105 | } 106 | 107 | err = lexer_next(&flexer, &ftok); 108 | if (err) { 109 | FATAL("failed to get next token (file lexer): %s", error_string(err)); 110 | } 111 | assert_token_eq(&tok, &ftok); 112 | assert_token_eq(&tok, &__string_token_pairs[i].tokens[j]); 113 | assert_token_eq(&ftok, &__string_token_pairs[i].tokens[j]); 114 | if (tok.type == TOKEN_TYPE_EOF) 115 | break; 116 | } 117 | lexer_clean(&lexer); 118 | } 119 | 120 | for (size_t i = 0; i < sizeof(__token_error_pairs) / sizeof(__token_error_pairs[0]); ++i) { 121 | INFO("analyzing: '%s'", __token_error_pairs[i].string); 122 | 123 | lexer_t lexer; 124 | lexer_init_from_string(&lexer, __token_error_pairs[i].string); 125 | 126 | for (int j = 0; ; ++j) { 127 | err = lexer_next(&lexer, &tok); 128 | if (err == __token_error_pairs[i].error) 129 | break; 130 | if (tok.type == TOKEN_TYPE_EOF) 131 | FATAL("error never encountered"); 132 | } 133 | lexer_clean(&lexer); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /test/parser.c: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #include "test/test.h" 4 | #include "simplify/parser.h" 5 | 6 | 7 | int main() { 8 | struct { 9 | char* string; 10 | expression_t* expr; 11 | } __string_expr_pairs[] = { 12 | { "2 * 5.5", 13 | expression_new_operator(expression_new_number_d(2), '*', expression_new_number_d(5.5)) 14 | }, 15 | { 16 | "2.5x", 17 | expression_new_operator(expression_new_number_d(2.5), '*', expression_new_variable("x")) 18 | }, 19 | { "2 * ( 5 + 9 )", 20 | expression_new_operator( 21 | expression_new_number_d(2), 22 | '*', 23 | expression_new_operator(expression_new_number_d(5), '+', expression_new_number_d(9))), 24 | }, 25 | { "2 + 3^2 * 5.5", 26 | expression_new_operator( 27 | expression_new_number_d(2), 28 | '+', 29 | expression_new_operator( 30 | expression_new_operator(expression_new_number_d(3), '^', expression_new_number_d(2)), 31 | '*', 32 | expression_new_number_d(5.5))) 33 | }, 34 | { "1 \\ 2 = 73", 35 | expression_new_operator( 36 | expression_new_operator(expression_new_number_d(1), '\\', expression_new_number_d(2)), 37 | '=', 38 | expression_new_number_d(73)) 39 | }, 40 | { "p = n * 8 - t", 41 | expression_new_operator( 42 | expression_new_variable("p"), 43 | '=', 44 | expression_new_operator( 45 | expression_new_operator( 46 | expression_new_variable("n"), 47 | '*', 48 | expression_new_number_d(8)), 49 | '-', 50 | expression_new_variable("t"))) 51 | }, 52 | { "10 * 20 * 30^1 * 40", 53 | expression_new_operator( 54 | expression_new_operator( 55 | expression_new_operator( 56 | expression_new_number_d(10), 57 | '*', 58 | expression_new_number_d(20)), 59 | '*', 60 | expression_new_operator( 61 | expression_new_number_d(30), 62 | '^', 63 | expression_new_number_d(1))), 64 | '*', 65 | expression_new_number_d(40)) 66 | }, 67 | { "2 * -5.5", 68 | expression_new_operator( 69 | expression_new_number_d(2), 70 | '*', 71 | expression_new_prefix('-', expression_new_number_d(5.5))) 72 | }, 73 | { "f(x, y)", 74 | expression_new_function( 75 | "f", 2, 76 | expression_new_variable("x"), 77 | expression_new_variable("y")), 78 | }, 79 | { "4 * (3 * x) \\ 4 - 36", 80 | expression_new_operator( 81 | expression_new_operator( 82 | expression_new_number_d(4), 83 | '*', 84 | expression_new_operator( 85 | expression_new_operator( 86 | expression_new_number_d(3), 87 | '*', 88 | expression_new_variable("x")), 89 | '\\', 90 | expression_new_number_d(4))), 91 | '-', 92 | expression_new_number_d(36)) 93 | }, 94 | { "f(x + 2) : 5 * x * 3", 95 | expression_new_operator( 96 | expression_new_function( 97 | "f", 1, 98 | expression_new_operator( 99 | expression_new_variable("x"), 100 | '+', 101 | expression_new_number_d(2))), 102 | ':', 103 | expression_new_operator( 104 | expression_new_operator( 105 | expression_new_number_d(5), 106 | '*', 107 | expression_new_variable("x")), 108 | '*', 109 | expression_new_number_d(3))), 110 | }, 111 | { "x^ 3 * 5(6 + 0 \\ 1)", 112 | expression_new_operator( 113 | expression_new_operator( 114 | expression_new_variable("x"), 115 | '^', 116 | expression_new_number_d(3)), 117 | '*', 118 | expression_new_operator( 119 | expression_new_number_d(5), 120 | '*', 121 | expression_new_operator( 122 | expression_new_number_d(6), 123 | '+', 124 | expression_new_operator( 125 | expression_new_number_d(0), 126 | '\\', 127 | expression_new_number_d(1))))) 128 | } 129 | }; 130 | 131 | 132 | error_t err; 133 | for (int i = 0; i < (int) (sizeof(__string_expr_pairs) / sizeof(__string_expr_pairs[0])); ++i) { 134 | expression_t expr; 135 | err = parse_string(__string_expr_pairs[i].string, &expr); 136 | printf("starting test #%d...\n", i + 1); 137 | if (err) 138 | FATAL("failed to parse string: %s", error_string(err)); 139 | expression_assert_eq(&expr, __string_expr_pairs[i].expr); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /test/rbtree.c: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #include "test/test.h" 4 | #include "simplify/rbtree/rbtree.h" 5 | 6 | #define ITERATE_PAIRS(FIRST, SECOND) \ 7 | FIRST = __key_list[0]; \ 8 | SECOND = __value_list[0]; \ 9 | for (int i = 0; i < 36; ++i, FIRST = __key_list[i], SECOND = __value_list[i]) 10 | 11 | 12 | static char* __key_list[] = { 13 | "Peter", "Piper", "picked", "a peck", "of pickled", "peppers", 14 | "A peck", "of pickled ", "peppers Peter", "Piper picked", 15 | "If Peter", "Piper picked ", "a peck of", "pickled peppers", 16 | "Where's the peck of", "pickled peppers Peter Piper", "picked?", 17 | "I saw", "Susie sitting" "in a shoe shine shop", 18 | "Where she sits she", "shines,", "and where she", "shines", "she", "sits.", 19 | "How can", "a clam", " cram in a clean", "cream can?", 20 | "Coy", "knows", "pseudonoise", "codes.", 21 | "Six sick hicks nick", "six slick bricks", "with picks", "and" 22 | }; 23 | 24 | static char* __value_list[] = { 25 | "sticks.", "To sit", "in solemn silence in a dull", "dark dock", 26 | "In a pestilential", "prison, with", "a life-long lock", 27 | "Awaiting the", " sensation of a short", "sharp shock", 28 | "From a cheap ", "and chippy chopper on", "a big black block!", 29 | "To sit in solemn", " silence in a dull", ", dark dock,", 30 | "In a pestilential prison,", "with a life", "-long lock", 31 | "Awaiting the sensation", "of a short", ", sharp shock", 32 | "From a cheap and chippy", "chopper on a big black block!", 33 | "A dull,", "dark dock, a", "life-long lock,", 34 | "A short", ", sharp shock, a", "big black block!", 35 | "To sit in solemn silence", "in a pestilential prison,", 36 | "And awaiting the", "sensation", 37 | "From a cheap", " and chippy chopper", "on a big black block!", 38 | }; 39 | 40 | 41 | 42 | int main() { 43 | rbtree_t tree; 44 | error_t err; 45 | rbtree_init(&tree); 46 | 47 | char* key; 48 | char* value; 49 | ITERATE_PAIRS(key, value) { 50 | err = rbtree_insert(&tree, key, value); 51 | if (err) { 52 | FATAL("(iteration %d) %s", i, error_string(err)); 53 | } 54 | } 55 | 56 | ITERATE_PAIRS(key, value) { 57 | char* data; 58 | err = rbtree_search(&tree, key, (void**)&data); 59 | if (err) { 60 | FATAL("(iteration %d) \"%s\"", i, error_string(err)); 61 | } 62 | if (data != value) { 63 | FATAL("(iteration %d) data did not match: expected \"%s\", got \"%s\"", i, value, data); 64 | } 65 | } 66 | 67 | 68 | rbtree_clean(&tree, NULL); 69 | } 70 | -------------------------------------------------------------------------------- /test/stringify.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | int main() { 4 | mpfr_t bad_number; 5 | mpfr_t infinity; 6 | mpfr_t neg_infinity; 7 | 8 | mpfr_init(bad_number); 9 | mpfr_init(infinity); 10 | mpfr_init(neg_infinity); 11 | mpfr_set_nan(bad_number); 12 | mpfr_set_inf(infinity, 1); 13 | mpfr_set_inf(neg_infinity, -1); 14 | 15 | struct { 16 | expression_t* expr; 17 | char* string; 18 | } __expr_string_pairs[] = { 19 | { expression_new_number_si(5), "5"}, 20 | { expression_new_number_d(2.25), "2.25"}, 21 | { expression_new_operator( 22 | expression_new_function("f", 2, expression_new_number_d(5), expression_new_variable("x")), 23 | '*', 24 | expression_new_variable("y")), 25 | "f(5, x) * y" 26 | }, 27 | { expression_new_operator( 28 | expression_new_prefix('+', expression_new_variable("OwO")), 29 | '-', 30 | expression_new_number_si(3)), 31 | "+OwO - 3" 32 | }, 33 | { expression_new_number_d(3.9), "3.9" }, 34 | { expression_new_number(infinity), "Inf" }, 35 | { expression_new_number(bad_number), "NaN" }, 36 | { expression_new_number(neg_infinity), "-Inf" }, 37 | }; 38 | 39 | for (int i = 0; i < (int) (sizeof(__expr_string_pairs) / sizeof(__expr_string_pairs[0])); ++i) { 40 | printf("starting test #%d...", i + 1); 41 | char* str = stringify(__expr_string_pairs[i].expr); 42 | if (strcmp(str, __expr_string_pairs[i].string) != 0) { 43 | FATAL("strings do not match! expecting string '%s' got '%s'", __expr_string_pairs[i].string, str); 44 | } 45 | free(str); 46 | printf("done\n"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/test.h: -------------------------------------------------------------------------------- 1 | /* Copyright Ian Shehadeh 2018 */ 2 | 3 | #ifndef TEST_TEST_H_ 4 | #define TEST_TEST_H_ 5 | 6 | /* file: test/test.h 7 | * Utilities and data to help create tests 8 | * 9 | * Macros: 10 | * - FATAL(MSG, ...FMT) : print an error and exit 11 | * - ERROR(MSG, ...FMT) : print an error, but do not exit 12 | * - INFO(MSG, ...FMT) : print an info message 13 | * - WARN(MSG, ...FMT) : print a warning message (goes to stderr) 14 | */ 15 | 16 | #include 17 | #include 18 | 19 | #include "simplify/expression/expression.h" 20 | #include "simplify/expression/stringify.h" 21 | #include "simplify/parser.h" 22 | 23 | #define FATAL(MSG, ...) { \ 24 | fprintf(stdout, __FILE__ ":%d FATAL: " MSG "\n", __LINE__, ##__VA_ARGS__); \ 25 | exit(1); \ 26 | } 27 | #define ERROR(MSG, ...) fprintf(stdout, __FILE__ ":%d ERROR: " MSG "\n", __LINE__, ##__VA_ARGS__); 28 | #define INFO(MSG, ...) fprintf(stdout, __FILE__ ":%d INFO: " MSG "\n", __LINE__, ##__VA_ARGS__); 29 | #define WARN(MSG, ...) fprintf(stdout, __FILE__ ":%d WARN: " MSG "\n", __LINE__, ##__VA_ARGS__); 30 | 31 | static inline const char* print_type(expression_type_t t) { 32 | switch (t) { 33 | case EXPRESSION_TYPE_FUNCTION: 34 | return "EXPRESSION_TYPE_FUNCTION"; 35 | case EXPRESSION_TYPE_NUMBER: 36 | return "EXPRESSION_TYPE_NUMBER"; 37 | case EXPRESSION_TYPE_OPERATOR: 38 | return "EXPRESSION_TYPE_OPERATOR"; 39 | case EXPRESSION_TYPE_PREFIX: 40 | return "EXPRESSION_TYPE_PREFIX"; 41 | case EXPRESSION_TYPE_VARIABLE: 42 | return "EXPRESSION_TYPE_VARIABLE"; 43 | } 44 | return "UNKOWN"; 45 | } 46 | 47 | 48 | static inline const char* print_compare_result(compare_result_t t) { 49 | switch (t) { 50 | case COMPARE_RESULT_LESS: 51 | return "COMPARE_RESULT_LESS"; 52 | case COMPARE_RESULT_GREATER: 53 | return "COMPARE_RESULT_GREATER"; 54 | case COMPARE_RESULT_EQUAL: 55 | return "COMPARE_RESULT_EQUAL"; 56 | case COMPARE_RESULT_INCOMPARABLE: 57 | return "COMPARE_RESULT_INCOMPARABLE"; 58 | } 59 | return "UNKOWN"; 60 | } 61 | 62 | void expression_assert_eq(expression_t* expr1, expression_t* expr2) { 63 | if (expr1->type != expr2->type) { 64 | printf("EXPECTED:\n"); 65 | expression_print(expr2); 66 | printf("\n\nGOT:\n"); 67 | expression_print(expr1); 68 | puts(""); 69 | FATAL("ASSERT FAILED (%s != %s): expression types don't match", 70 | print_type(expr1->type), print_type(expr2->type)); 71 | } 72 | 73 | switch (expr1->type) { 74 | case EXPRESSION_TYPE_NUMBER: 75 | if (mpfr_cmp(expr1->number.value, expr2->number.value) != 0) { 76 | char* expr1num = stringify(expr1); 77 | char* expr2num = stringify(expr2); 78 | FATAL("ASSERT FAILED ('%s' != '%s'): numeric expressions don't match", expr1num, expr2num); 79 | free(expr1num); 80 | free(expr2num); 81 | } 82 | break; 83 | case EXPRESSION_TYPE_PREFIX: 84 | case EXPRESSION_TYPE_OPERATOR: 85 | { 86 | if (EXPRESSION_OPERATOR(expr1) != EXPRESSION_OPERATOR(expr2)) 87 | FATAL("ASSERT FAILED ('%c' != '%c'): expression's operators don't match", 88 | EXPRESSION_OPERATOR(expr1), EXPRESSION_OPERATOR(expr2)); 89 | if (EXPRESSION_LEFT(expr1)) 90 | expression_assert_eq(EXPRESSION_LEFT(expr1), EXPRESSION_LEFT(expr2)); 91 | expression_assert_eq(EXPRESSION_RIGHT(expr1), EXPRESSION_RIGHT(expr2)); 92 | break; 93 | } 94 | case EXPRESSION_TYPE_VARIABLE: 95 | if (strcmp(expr1->variable.value, expr2->variable.value) != 0) 96 | FATAL("ASSERT FAILED ('%s' != '%s'): variable names don't match", 97 | expr1->variable.value, expr2->variable.value); 98 | break; 99 | case EXPRESSION_TYPE_FUNCTION: 100 | { 101 | if (strcmp(expr1->function.name, expr2->function.name) != 0) 102 | FATAL("ASSERT FAILED ('%s' != '%s'): function names don't match", 103 | expr1->function.name, expr2->function.name); 104 | 105 | expression_t* param1; 106 | expression_list_t* other = expr2->function.parameters; 107 | EXPRESSION_LIST_FOREACH(param1, expr1->function.parameters) { 108 | if (!other) 109 | FATAL("ASSERT FAILED: argument count doesn't match"); 110 | expression_assert_eq(param1, other->value); 111 | other = other->next; 112 | } 113 | if (other) 114 | FATAL("ASSERT FAILED: argument count doesn't match"); 115 | } 116 | } 117 | } 118 | 119 | void assert_token_eq(token_t* tok1, token_t* tok2) { 120 | if (tok1->length != tok2->length || strncmp(tok1->start, tok2->start, tok1->length) != 0) { 121 | FATAL("token strings don't match! ('%.*s' != '%.*s')", 122 | (int)tok1->length, tok1->start, (int)tok2->length, tok2->start); 123 | } 124 | 125 | if (tok1->type != tok2->type) { 126 | FATAL("tokens are not of the same type! (%d != %d)", tok1->type, tok2->type); 127 | } 128 | } 129 | 130 | #endif // TEST_TEST_H_ 131 | --------------------------------------------------------------------------------