├── .gitignore ├── Makefile ├── scripts ├── install-git-hooks ├── eval.sh ├── pre-push.hook ├── aspell-pws ├── test.sh ├── pre-commit.hook └── commit-msg.hook ├── .clang-format ├── LICENSE ├── livepatch-calc.c ├── README.md ├── expression.h ├── main.c └── expression.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | .cache.mk 4 | 5 | # Object files 6 | *.o 7 | *.ko 8 | *.elf 9 | 10 | # Linker output 11 | *.map 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Libraries 18 | *.a 19 | 20 | # Kernel Module Compile Results 21 | *.mod* 22 | *.cmd 23 | .tmp_versions/ 24 | modules.order 25 | Module.symvers 26 | Mkfile.old 27 | dkms.conf 28 | 29 | # test-oriented 30 | mathex/build/ 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | KDIR=/lib/modules/$(shell uname -r)/build 2 | 3 | obj-m += calc.o 4 | obj-m += livepatch-calc.o 5 | calc-objs += main.o expression.o 6 | ccflags-y := -std=gnu99 -Wno-declaration-after-statement 7 | 8 | GIT_HOOKS := .git/hooks/applied 9 | 10 | all: $(GIT_HOOKS) 11 | make -C $(KDIR) M=$(PWD) modules 12 | 13 | $(GIT_HOOKS): 14 | @scripts/install-git-hooks 15 | @echo 16 | 17 | check: all 18 | scripts/test.sh 19 | 20 | clean: 21 | make -C $(KDIR) M=$(PWD) clean 22 | -------------------------------------------------------------------------------- /scripts/install-git-hooks: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if ! test -d .git; then 4 | echo "Execute scripts/install-git-hooks in the top-level directory." 5 | exit 1 6 | fi 7 | 8 | ln -sf ../../scripts/pre-commit.hook .git/hooks/pre-commit || exit 1 9 | chmod +x .git/hooks/pre-commit 10 | 11 | ln -sf ../../scripts/commit-msg.hook .git/hooks/commit-msg || exit 1 12 | chmod +x .git/hooks/commit-msg 13 | 14 | ln -sf ../../scripts/pre-push.hook .git/hooks/pre-push || exit 1 15 | chmod +x .git/hooks/pre-push 16 | 17 | touch .git/hooks/applied || exit 1 18 | 19 | echo 20 | echo "Git hooks are installed successfully." 21 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | Language: Cpp 3 | MaxEmptyLinesToKeep: 3 4 | IndentCaseLabels: false 5 | AllowShortIfStatementsOnASingleLine: false 6 | AllowShortCaseLabelsOnASingleLine: false 7 | AllowShortLoopsOnASingleLine: false 8 | DerivePointerAlignment: false 9 | PointerAlignment: Right 10 | SpaceAfterCStyleCast: true 11 | TabWidth: 4 12 | UseTab: Never 13 | IndentWidth: 4 14 | BreakBeforeBraces: Linux 15 | AccessModifierOffset: -4 16 | ForEachMacros: 17 | - foreach 18 | - Q_FOREACH 19 | - BOOST_FOREACH 20 | - list_for_each 21 | - list_for_each_safe 22 | - list_for_each_entry 23 | - list_for_each_entry_safe 24 | - hlist_for_each_entry 25 | - rb_list_foreach 26 | - rb_list_foreach_safe 27 | - vec_foreach 28 | -------------------------------------------------------------------------------- /scripts/eval.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Check the availability of 'bc' utility 4 | command -v bc >/dev/null 2>&1 || \ 5 | { echo >&2 "This script requires 'bc' but it's not installed. Aborting."; 6 | exit 1; } 7 | 8 | # Transfer the self-defined representation to real number 9 | fromfixed() { 10 | local ret=$1 11 | local NAN_INT=31 12 | local INF_INT=47 13 | 14 | num=$(($ret >> 4)) 15 | frac=$(($ret & 15)) 16 | neg=$((($frac & 8) >> 3)) 17 | 18 | [[ $neg -eq 1 ]] && frac=$((-((~$frac & 15) + 1))) 19 | 20 | if [ "$ret" -eq "$NAN_INT" ] 21 | then 22 | echo "NAN_INT" 23 | elif [ "$ret" -eq "$INF_INT" ] 24 | then 25 | echo "INF_INT" 26 | else 27 | echo "$num*(10^$frac)" | bc -l 28 | fi 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) 2017, 2019-2020 National Cheng Kung University, Taiwan. 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 | -------------------------------------------------------------------------------- /scripts/pre-push.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | protected_branch='master' 4 | current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,') 5 | RED='\033[0;31m' 6 | GREEN='\033[1;32m' 7 | YELLOW='\033[1;33m' 8 | NC='\033[0m' # No Color 9 | 10 | # Validate repository 11 | # commit 62c15c2f49013564748d3f1fd59b548585dd02c7 12 | # Author: Jim Huang 13 | # AuthorDate: Mon Feb 10 22:23:50 2020 +0000 14 | # Commit: Jim Huang 15 | # CommitDate: Mon Feb 10 22:23:50 2020 +0000 16 | # 17 | # Clarify license terms 18 | commit=`git rev-list -n 1 --grep '^Clarify license terms' 79ec30a7d86351dfa646303f38d4bff4cd288dbb...HEAD` 19 | if [ x"$commit" != x"62c15c2f49013564748d3f1fd59b548585dd02c7" ] ; then 20 | echo -e "${RED}ERROR${NC}: This repository is insane." 21 | echo -e "Make sure you did fork from https://github.com/sysprog21/kcalc recently." 22 | echo "" 23 | exit 1 24 | fi 25 | 26 | # Show hints 27 | echo -e "${YELLOW}Hint${NC}: You might want to know why Git is always ${GREEN}asking for my password${NC}." 28 | echo -e " https://help.github.com/en/github/using-git/why-is-git-always-asking-for-my-password" 29 | echo "" 30 | 31 | # only run this if you are pushing to master 32 | if [[ $current_branch = $protected_branch ]] ; then 33 | echo -e "${YELLOW}Running pre push to master check...${NC}" 34 | 35 | echo -e "${YELLOW}Trying to build tests project...${NC}" 36 | 37 | # build the project 38 | make 39 | 40 | # $? is a shell variable which stores the return code from what we just ran 41 | rc=$? 42 | if [[ $rc != 0 ]] ; then 43 | echo -e "${RED}Failed to build the project, please fix this and push again${NC}" 44 | echo "" 45 | exit $rc 46 | fi 47 | 48 | # Everything went OK so we can exit with a zero 49 | echo -e "${GREEN}Pre-push check passed!${NC}" 50 | echo "" 51 | fi 52 | 53 | exit 0 54 | -------------------------------------------------------------------------------- /scripts/aspell-pws: -------------------------------------------------------------------------------- 1 | personal_ws-1.1 en 500 2 | usr 3 | lib 4 | sbin 5 | env 6 | bash 7 | etc 8 | var 9 | dudect 10 | runtime 11 | todo 12 | fixme 13 | hotfix 14 | qtest 15 | vscode 16 | sanitizer 17 | unix 18 | linux 19 | valgrind 20 | ubuntu 21 | gdb 22 | sdk 23 | aspell 24 | cppcheck 25 | glibc 26 | git 27 | pre 28 | gcc 29 | clang 30 | enqueue 31 | dequeue 32 | fifo 33 | lifo 34 | stdin 35 | stdout 36 | stderr 37 | strdup 38 | strcmp 39 | strcasecmp 40 | snprintf 41 | sprintf 42 | strcat 43 | strchr 44 | strcmp 45 | strcoll 46 | strcpy 47 | strcspn 48 | strerror 49 | strlen 50 | strncasecmp 51 | strncat 52 | strncmp 53 | strncpy 54 | strpbrk 55 | strrchr 56 | strspn 57 | strstr 58 | strtod 59 | strtof 60 | strtok 61 | strtol 62 | strtold 63 | strtoul 64 | atexit 65 | atof 66 | atoi 67 | atol 68 | bsearch 69 | calloc 70 | fclose 71 | fdopen 72 | feof 73 | ferror 74 | fflush 75 | fgetc 76 | fgetpos 77 | fgets 78 | fileno 79 | fopen 80 | fprintf 81 | fputc 82 | fputs 83 | fread 84 | freopen 85 | fscanf 86 | fseek 87 | fsetpos 88 | ftell 89 | fwrite 90 | getc 91 | getchar 92 | getenv 93 | gets 94 | isalnum 95 | isalpha 96 | isascii 97 | iscntrl 98 | isdigit 99 | isgraph 100 | islower 101 | isprint 102 | ispunct 103 | isspace 104 | isupper 105 | longjmp 106 | memchr 107 | memcmp 108 | memcpy 109 | memmove 110 | memset 111 | printf 112 | putc 113 | putchar 114 | putenv 115 | puts 116 | qsort 117 | rand 118 | realloc 119 | regcomp 120 | regerror 121 | regexec 122 | regfree 123 | rewind 124 | scanf 125 | setbuf 126 | setjmp 127 | signal 128 | srand 129 | sscanf 130 | macOS 131 | Fibonacci 132 | fib 133 | pow 134 | Binet 135 | Vorobev 136 | GMP 137 | MPFR 138 | mutex 139 | trylock 140 | unlock 141 | lseek 142 | llseek 143 | cdev 144 | inode 145 | sysfs 146 | printk 147 | clz 148 | fops 149 | init 150 | alloc 151 | ktime 152 | getres 153 | gettime 154 | settime 155 | ns 156 | timespec 157 | timeval 158 | NaN 159 | livepatch 160 | MathEx 161 | vec 162 | expr 163 | -------------------------------------------------------------------------------- /livepatch-calc.c: -------------------------------------------------------------------------------- 1 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "expression.h" 10 | 11 | MODULE_LICENSE("Dual MIT/GPL"); 12 | MODULE_AUTHOR("National Cheng Kung University, Taiwan"); 13 | MODULE_DESCRIPTION("Patch calc kernel module"); 14 | MODULE_VERSION("0.1"); 15 | 16 | void livepatch_nop_cleanup(struct expr_func *f, void *c) 17 | { 18 | /* suppress compilation warnings */ 19 | (void) f; 20 | (void) c; 21 | } 22 | 23 | int livepatch_nop(struct expr_func *f, vec_expr_t args, void *c) 24 | { 25 | (void) args; 26 | (void) c; 27 | pr_err("function nop is now patched\n"); 28 | return 0; 29 | } 30 | 31 | /* clang-format off */ 32 | static struct klp_func funcs[] = { 33 | { 34 | .old_name = "user_func_nop", 35 | .new_func = livepatch_nop, 36 | }, 37 | { 38 | .old_name = "user_func_nop_cleanup", 39 | .new_func = livepatch_nop_cleanup, 40 | }, 41 | {}, 42 | }; 43 | static struct klp_object objs[] = { 44 | { 45 | .name = "calc", 46 | .funcs = funcs, 47 | }, 48 | {}, 49 | }; 50 | /* clang-format on */ 51 | 52 | static struct klp_patch patch = { 53 | .mod = THIS_MODULE, 54 | .objs = objs, 55 | }; 56 | 57 | static int livepatch_calc_init(void) 58 | { 59 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0) 60 | return klp_enable_patch(&patch); 61 | #else 62 | int ret = klp_register_patch(&patch); 63 | if (ret) 64 | return ret; 65 | ret = klp_enable_patch(&patch); 66 | if (ret) { 67 | WARN_ON(klp_unregister_patch(&patch)); 68 | return ret; 69 | } 70 | return 0; 71 | #endif 72 | } 73 | 74 | static void livepatch_calc_exit(void) 75 | { 76 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 1, 0) 77 | WARN_ON(klp_unregister_patch(&patch)); 78 | #endif 79 | } 80 | 81 | module_init(livepatch_calc_init); 82 | module_exit(livepatch_calc_exit); 83 | MODULE_INFO(livepatch, "Y"); 84 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CALC_DEV=/dev/calc 4 | CALC_MOD=calc.ko 5 | LIVEPATCH_CALC_MOD=livepatch-calc.ko 6 | 7 | source scripts/eval.sh 8 | 9 | test_op() { 10 | local expression=$1 11 | echo "Testing " ${expression} "..." 12 | echo -ne ${expression}'\0' > $CALC_DEV 13 | fromfixed $(cat $CALC_DEV) 14 | } 15 | 16 | if [ "$EUID" -eq 0 ] 17 | then echo "Don't run this script as root" 18 | exit 19 | fi 20 | 21 | sudo rmmod -f livepatch-calc 2>/dev/null 22 | sudo rmmod -f calc 2>/dev/null 23 | sleep 1 24 | 25 | modinfo $CALC_MOD || exit 1 26 | sudo insmod $CALC_MOD 27 | sudo chmod 0666 $CALC_DEV 28 | echo 29 | 30 | # multiply 31 | test_op '6*7' 32 | 33 | # add 34 | test_op '1980+1' 35 | 36 | # sub 37 | test_op '2019-1' 38 | 39 | # div 40 | test_op '42/6' 41 | test_op '1/3' 42 | test_op '1/3*6+2/4' 43 | test_op '(1/3)+(2/3)' 44 | test_op '(2145%31)+23' 45 | test_op '0/0' # should be NAN_INT 46 | 47 | # binary 48 | test_op '(3%0)|0' # should be 0 49 | test_op '1+2<<3' # should be (1 + 2) << 3 = 24 50 | test_op '123&42' # should be 42 51 | test_op '123^42' # should be 81 52 | 53 | # parens 54 | test_op '(((3)))*(1+(2))' # should be 9 55 | 56 | # assign 57 | test_op 'x=5, x=(x!=0)' # should be 1 58 | test_op 'x=5, x = x+1' # should be 6 59 | 60 | # fancy variable name 61 | test_op 'six=6, seven=7, six*seven' # should be 42 62 | test_op '小熊=6, 維尼=7, 小熊*維尼' # should be 42 63 | test_op 'τ=1.618, 3*τ' # should be 3 * 1.618 = 4.854 64 | test_op '$(τ, 1.618), 3*τ()' # shold be 3 * 1.618 = 4.854 65 | 66 | # functions 67 | test_op '$(zero), zero()' # should be 0 68 | test_op '$(one, 1), one()+one(1)+one(1, 2, 4)' # should be 3 69 | test_op '$(number, 1), $(number, 2+3), number()' # should be 5 70 | 71 | # pre-defined function 72 | test_op 'nop()' 73 | 74 | # Livepatch 75 | sudo insmod $LIVEPATCH_CALC_MOD 76 | sleep 1 77 | echo "livepatch was applied" 78 | test_op 'nop()' 79 | dmesg | tail -n 6 80 | echo "Disabling livepatch..." 81 | sudo sh -c "echo 0 > /sys/kernel/livepatch/livepatch_calc/enabled" 82 | sleep 2 83 | sudo rmmod livepatch-calc 84 | 85 | sudo rmmod calc 86 | 87 | # epilogue 88 | echo "Complete" 89 | -------------------------------------------------------------------------------- /scripts/pre-commit.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CPPCHECK_suppresses="--suppress=missingIncludeSystem" 4 | CPPCHECK_OPTS="-I. --enable=all --error-exitcode=1 --force $CPPCHECK_suppresses ." 5 | 6 | RETURN=0 7 | CLANG_FORMAT=$(which clang-format) 8 | if [ $? -ne 0 ]; then 9 | echo "[!] clang-format not installed. Unable to check source file format policy." >&2 10 | exit 1 11 | fi 12 | 13 | CPPCHECK=$(which cppcheck) 14 | if [ $? -ne 0 ]; then 15 | echo "[!] cppcheck not installed. Unable to perform static analysis." >&2 16 | exit 1 17 | fi 18 | 19 | ASPELL=$(which aspell) 20 | if [ $? -ne 0 ]; then 21 | echo "[!] aspell not installed. Unable to do spelling check." >&2 22 | exit 1 23 | fi 24 | 25 | DIFF=$(which colordiff) 26 | if [ $? -ne 0 ]; then 27 | DIFF=diff 28 | fi 29 | 30 | FILES=`git diff --cached --name-only --diff-filter=ACMR | grep -E "\.(c|cpp|h)$"` 31 | for FILE in $FILES; do 32 | nf=`git checkout-index --temp $FILE | cut -f 1` 33 | tempdir=`mktemp -d` || exit 1 34 | newfile=`mktemp ${tempdir}/${nf}.XXXXXX` || exit 1 35 | basename=`basename $FILE` 36 | 37 | source="${tempdir}/${basename}" 38 | mv $nf $source 39 | cp .clang-format $tempdir 40 | $CLANG_FORMAT $source > $newfile 2>> /dev/null 41 | $DIFF -u -p -B --label="modified $FILE" --label="expected coding style" \ 42 | "${source}" "${newfile}" 43 | r=$? 44 | rm -rf "${tempdir}" 45 | if [ $r != 0 ] ; then 46 | echo "[!] $FILE does not follow the consistent coding style." >&2 47 | RETURN=1 48 | fi 49 | if [ $RETURN -eq 1 ]; then 50 | echo "" >&2 51 | echo "Make sure you indent as the following:" >&2 52 | echo " clang-format -i $FILE" >&2 53 | echo 54 | fi 55 | done 56 | 57 | # Prevent unsafe functions 58 | root=$(git rev-parse --show-toplevel) 59 | banned="([^f]gets\()|(sprintf\()|(strcpy\()" 60 | status=0 61 | for file in $(git diff --staged --name-only | grep -E "\.(c|cc|cpp|h|hh|hpp)\$") 62 | do 63 | filepath="${root}/${file}" 64 | output=$(grep -nrE "${banned}" "${filepath}") 65 | if [ ! -z "${output}" ]; then 66 | echo "Dangerous function detected in ${filepath}" 67 | echo "${output}" 68 | echo 69 | echo "Read 'Common vulnerabilities guide for C programmers' carefully." 70 | echo " https://security.web.cern.ch/security/recommendations/en/codetools/c.shtml" 71 | RETURN=1 72 | fi 73 | done 74 | 75 | # static analysis 76 | $CPPCHECK $CPPCHECK_OPTS >/dev/null 77 | if [ $? -ne 0 ]; then 78 | RETURN=1 79 | echo "" >&2 80 | echo "Fail to pass static analysis." >&2 81 | echo 82 | fi 83 | 84 | exit $RETURN 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kcalc: in-kernel math expression evaluation for Linux 2 | 3 | kcalc is a mathematical expression evaluator and takes string as 4 | input, returning fixed-point numbers. 5 | 6 | ## Features 7 | 8 | * Arithmetic, bitwise and logical operators; 9 | * Flexible variables; 10 | * Customized functions; 11 | 12 | ## Fixed-point representation 13 | 14 | kcalc introduces a fixed-point number representation for fractional values: 15 | one 32-bit size is divided into mantissa (28 bits), sign (1 bit) and 16 | exponent (3 bits). 17 | 18 | ``` 19 | MSB 31 4 3 2 1 0 LSB 20 | +----------------------------------+------+----------+ 21 | | mantissa | sign | exponent | 22 | +----------------------------------+------+----------+ 23 | ``` 24 | 25 | ## Usage: 26 | 27 | Build and install the module 28 | 29 | ```shell 30 | $ make 31 | $ sudo insmod calc.ko 32 | $ sudo chmod 0666 /dev/calc 33 | ``` 34 | 35 | Then simply send the expression to the module 36 | 37 | ```shell 38 | $ echo -ne "3*5\0" > /dev/calc 39 | ``` 40 | 41 | The expected output in `dmesg` should be: 42 | 43 | ```shell 44 | calc: Received 3 -> 3*5 45 | Result: 240 46 | ``` 47 | 48 | The result seems incorrect because we did not transform the value to normal representation. 49 | You can use the utlity to convert values into human readable form: 50 | ```shell 51 | $ source scripts/eval.sh 52 | $ fromfixed 240 53 | ``` 54 | 55 | Then, you can get `15`, which is the exact result for expression `3*5`. 56 | 57 | You can check file `scripts/test.sh` for more examples about the expressions. Alteratively, 58 | execue `make check` for the same script. 59 | 60 | ```shell 61 | $ scripts/test.sh 62 | 63 | ... Information generated by modinfo ... 64 | 65 | Testing 6*7 ... 66 | 42 67 | Testing 1980+1 ... 68 | 1981 69 | Testing 2019-1 ... 70 | 2018 71 | Testing 42/6 ... 72 | 7 73 | Testing 1/3 ... 74 | .33333330000000000000 75 | Testing 1/3*6+2/4 ... 76 | 2.49999980000000000000 77 | Testing (1/3)+(2/3) ... 78 | .99999990000000000000 79 | Testing (2145%31)+23 ... 80 | 29 81 | ``` 82 | 83 | ## Internals 84 | 85 | `struct expr *expr_create(const char *s, size_t len, struct expr_var_list 86 | *vars, struct expr_func *funcs)` - returns compiled expression from the given 87 | string. If expression uses variables - they are bound to `vars`, so you can 88 | modify values before evaluation or check the results after the evaluation. 89 | 90 | `int expr_eval(struct expr *e)` - evaluates compiled expression. 91 | 92 | `void expr_destroy(struct expr *e, struct expr_var_list *vars)` - cleans up 93 | memory. Parameters can be NULL (e.g. if you want to clean up expression, but 94 | reuse variables for another expression). 95 | 96 | `struct expr_var *expr_var(struct expr_var *vars, const char *s, size_t len)` - 97 | returns/creates variable of the given name in the given list. This can be used 98 | to get variable references to get/set them manually. 99 | 100 | ## Supported operators 101 | 102 | * Arithmetics: `+`, `-`, `*`, `/`, `%` (remainder), `**` (power) 103 | * Bitwise: `<<`, `>>`, `&`, `|`, `^` (xor or unary bitwise negation) 104 | * Logical: `<`, `>`, `==`, `!=`, `<=`, `>=`, `&&`, `||`, `!` (unary not) 105 | * Other: `=` (assignment, e.g. `x=y=5`), `,` (separates expressions or function parameters) 106 | 107 | ## License 108 | 109 | `kcalc`is released under the MIT License. Use of this source code is governed by 110 | a MIT License that can be found in the LICENSE file. 111 | -------------------------------------------------------------------------------- /expression.h: -------------------------------------------------------------------------------- 1 | /* MathEX is a mathematical expression evaluator. 2 | * It takes string as input and returns floating-point number as a result. 3 | */ 4 | 5 | #ifndef EXPRESSION_H_ 6 | #define EXPRESSION_H_ 7 | 8 | #ifdef __KERNEL__ 9 | #include 10 | #else 11 | #include 12 | #include 13 | #endif 14 | 15 | #ifdef __cplusplus 16 | extern "C" { 17 | #endif 18 | 19 | #define vec(T) \ 20 | struct { \ 21 | T *buf; \ 22 | int len; \ 23 | int cap; \ 24 | } 25 | #define vec_init() \ 26 | { \ 27 | NULL, 0, 0 \ 28 | } 29 | #define vec_len(v) ((v)->len) 30 | #define vec_unpack(v) \ 31 | (char **) &(v)->buf, &(v)->len, &(v)->cap, sizeof(*(v)->buf) 32 | #define vec_push(v, val) \ 33 | vec_expand(vec_unpack(v)) ? -1 : ((v)->buf[(v)->len++] = (val), 0) 34 | #define vec_nth(v, i) (v)->buf[i] 35 | #define vec_peek(v) (v)->buf[(v)->len - 1] 36 | #define vec_pop(v) (v)->buf[--(v)->len] 37 | #ifdef __KERNEL__ 38 | #define vec_free(v) (kfree((v)->buf), (v)->buf = NULL, (v)->len = (v)->cap = 0) 39 | #else 40 | #define vec_free(v) (free((v)->buf), (v)->buf = NULL, (v)->len = (v)->cap = 0) 41 | #endif 42 | #define vec_foreach(v, var, iter) \ 43 | if ((v)->len > 0) \ 44 | for ((iter) = 0; (iter) < (v)->len && (((var) = (v)->buf[(iter)]), 1); \ 45 | ++(iter)) 46 | 47 | /* Simple expandable vector implementation */ 48 | static inline int vec_expand(char **buf, int *length, int *cap, int memsz) 49 | { 50 | if (*length + 1 > *cap) { 51 | void *ptr; 52 | int n = (*cap == 0) ? 1 : *cap << 1; 53 | #ifdef __KERNEL__ 54 | ptr = krealloc(*buf, n * memsz, GFP_KERNEL); 55 | #else 56 | ptr = realloc(*buf, n * memsz); 57 | #endif 58 | if (!ptr) 59 | return -1; /* allocation failed */ 60 | *buf = (char *) ptr; 61 | *cap = n; 62 | } 63 | return 0; 64 | } 65 | 66 | /* 67 | * Expression data types 68 | */ 69 | struct expr_func; 70 | typedef vec(struct expr) vec_expr_t; 71 | typedef void (*exprfn_cleanup_t)(struct expr_func *f, void *context); 72 | typedef int (*exprfn_t)(struct expr_func *f, vec_expr_t args, void *context); 73 | 74 | struct expr { 75 | int type; 76 | union { 77 | struct { 78 | int value; 79 | } num; 80 | struct { 81 | int *value; 82 | } var; 83 | struct { 84 | vec_expr_t args; 85 | } op; 86 | struct { 87 | struct expr_func *f; 88 | vec_expr_t args; 89 | void *context; 90 | } func; 91 | } param; 92 | }; 93 | 94 | struct expr_string { 95 | const char *s; 96 | int n; 97 | }; 98 | struct expr_arg { 99 | int oslen; 100 | int eslen; 101 | vec_expr_t args; 102 | }; 103 | 104 | typedef vec(struct expr_string) vec_str_t; 105 | typedef vec(struct expr_arg) vec_arg_t; 106 | 107 | /* 108 | * Functions 109 | */ 110 | struct expr_func { 111 | const char *name; 112 | exprfn_t f; 113 | exprfn_cleanup_t cleanup; 114 | size_t ctxsz; 115 | }; 116 | 117 | struct expr_func *expr_func(struct expr_func *funcs, const char *s, size_t len); 118 | 119 | /* 120 | * Variables 121 | */ 122 | struct expr_var { 123 | int value; 124 | struct expr_var *next; 125 | char name[]; 126 | }; 127 | 128 | struct expr_var_list { 129 | struct expr_var *head; 130 | }; 131 | 132 | struct expr_var *expr_var(struct expr_var_list *vars, 133 | const char *s, 134 | size_t len); 135 | 136 | int expr_eval(struct expr *e); 137 | 138 | #define EXPR_TOP (1 << 0) 139 | #define EXPR_TOPEN (1 << 1) 140 | #define EXPR_TCLOSE (1 << 2) 141 | #define EXPR_TNUMBER (1 << 3) 142 | #define EXPR_TWORD (1 << 4) 143 | #define EXPR_TDEFAULT (EXPR_TOPEN | EXPR_TNUMBER | EXPR_TWORD) 144 | 145 | #define EXPR_UNARY (1 << 5) 146 | #define EXPR_COMMA (1 << 6) 147 | 148 | int expr_next_token(const char *s, size_t len, int *flags); 149 | 150 | struct expr *expr_create(const char *s, 151 | size_t len, 152 | struct expr_var_list *vars, 153 | struct expr_func *funcs); 154 | 155 | void expr_destroy(struct expr *e, struct expr_var_list *vars); 156 | 157 | #ifdef __cplusplus 158 | } /* extern "C" */ 159 | #endif 160 | 161 | #endif /* EXPRESSION_H_ */ 162 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include /* for memset() and memcpy() */ 12 | #include "expression.h" 13 | 14 | MODULE_LICENSE("Dual MIT/GPL"); 15 | MODULE_AUTHOR("National Cheng Kung University, Taiwan"); 16 | MODULE_DESCRIPTION("Math expression evaluation"); 17 | MODULE_VERSION("0.1"); 18 | 19 | #define DEVICE_NAME "calc" 20 | #define CLASS_NAME "calc" 21 | #define BUFF_SIZE 256 22 | 23 | static int major; 24 | static char message[BUFF_SIZE] = {0}; 25 | static short size_of_message; 26 | static struct class *char_class = NULL; 27 | static struct device *char_dev = NULL; 28 | static char *msg_ptr = NULL; 29 | static int result = 0; 30 | 31 | /* The prototype functions for the character driver */ 32 | static int dev_open(struct inode *, struct file *); 33 | static int dev_release(struct inode *, struct file *); 34 | static ssize_t dev_read(struct file *, char *, size_t, loff_t *); 35 | static ssize_t dev_write(struct file *, const char *, size_t, loff_t *); 36 | 37 | static void calc(void); 38 | 39 | static struct file_operations fops = { 40 | .open = dev_open, 41 | .read = dev_read, 42 | .write = dev_write, 43 | .release = dev_release, 44 | }; 45 | 46 | static int dev_open(struct inode *inodep, struct file *filep) 47 | { 48 | msg_ptr = message; 49 | return 0; 50 | } 51 | 52 | static ssize_t dev_read(struct file *filep, 53 | char *buffer, 54 | size_t len, 55 | loff_t *offset) 56 | { 57 | int error_count = 0; 58 | 59 | if (*msg_ptr == 0) 60 | return 0; 61 | 62 | memset(message, 0, sizeof(char) * BUFF_SIZE); 63 | 64 | snprintf(message, 64, "%d\n", result); 65 | size_of_message = strlen(message); 66 | 67 | error_count = copy_to_user(buffer, message, size_of_message); 68 | if (error_count == 0) { 69 | pr_info("size: %d result: %d\n", size_of_message, result); 70 | while (len && *msg_ptr) { 71 | error_count = put_user(*(msg_ptr++), buffer++); 72 | len--; 73 | } 74 | 75 | if (error_count == 0) 76 | return (size_of_message); 77 | return -EFAULT; 78 | } else { 79 | pr_info("Failed to send %d characters to the user\n", error_count); 80 | return -EFAULT; 81 | } 82 | } 83 | 84 | static ssize_t dev_write(struct file *filep, 85 | const char *buffer, 86 | size_t len, 87 | loff_t *offset) 88 | { 89 | memset(message, 0, sizeof(char) * BUFF_SIZE); 90 | 91 | if (len >= BUFF_SIZE) { 92 | pr_alert("Expression too long"); 93 | return 0; 94 | } 95 | 96 | copy_from_user(message, buffer, len); 97 | pr_info("Received %ld -> %s\n", len, message); 98 | 99 | calc(); 100 | return len; 101 | } 102 | 103 | noinline void user_func_nop_cleanup(struct expr_func *f, void *c) 104 | { 105 | /* suppress compilation warnings */ 106 | (void) f; 107 | (void) c; 108 | } 109 | 110 | noinline int user_func_nop(struct expr_func *f, vec_expr_t args, void *c) 111 | { 112 | (void) args; 113 | (void) c; 114 | if (f->ctxsz == 0) 115 | return -1; 116 | return 0; 117 | } 118 | 119 | static struct expr_func user_funcs[] = { 120 | {"nop", user_func_nop, user_func_nop_cleanup, 0}, 121 | {NULL, NULL, NULL, 0}, 122 | }; 123 | 124 | static void calc(void) 125 | { 126 | struct expr_var_list vars = {0}; 127 | struct expr *e = expr_create(message, strlen(message), &vars, user_funcs); 128 | if (!e) { 129 | pr_alert("Syntax error"); 130 | return; 131 | } 132 | 133 | result = expr_eval(e); 134 | pr_info("Result: %d\n", result); 135 | expr_destroy(e, &vars); 136 | } 137 | 138 | static int dev_release(struct inode *inodep, struct file *filep) 139 | { 140 | pr_info("Device successfully closed\n"); 141 | return 0; 142 | } 143 | 144 | static int __init calc_init(void) 145 | { 146 | pr_info("Initializing the module\n"); 147 | 148 | /* Try to dynamically allocate a major number for the device -- more 149 | * difficult but worth it 150 | */ 151 | major = register_chrdev(0, DEVICE_NAME, &fops); 152 | if (major < 0) { 153 | pr_alert("Failed to register a major number\n"); 154 | return major; 155 | } 156 | pr_info("registered correctly with major number %d\n", major); 157 | 158 | /* Register the device class */ 159 | char_class = class_create(THIS_MODULE, CLASS_NAME); 160 | if (IS_ERR(char_class)) { // Check for error and clean up if there is 161 | unregister_chrdev(major, DEVICE_NAME); 162 | pr_alert("Failed to register device class\n"); 163 | return PTR_ERR(char_class); /* return an error on a pointer */ 164 | } 165 | pr_info("device class registered correctly\n"); 166 | 167 | /* Register the device driver */ 168 | char_dev = 169 | device_create(char_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME); 170 | if (IS_ERR(char_dev)) { /* Clean up if there is an error */ 171 | class_destroy(char_class); /* Repeated code but the alternative is 172 | * goto statements 173 | */ 174 | unregister_chrdev(major, DEVICE_NAME); 175 | pr_alert("Failed to create the device\n"); 176 | return PTR_ERR(char_dev); 177 | } 178 | pr_info("device class created correctly\n"); 179 | return 0; 180 | } 181 | 182 | static void __exit calc_exit(void) 183 | { 184 | device_destroy(char_class, MKDEV(major, 0)); /* remove the device */ 185 | class_unregister(char_class); /* unregister the device class */ 186 | class_destroy(char_class); /* remove the device class */ 187 | unregister_chrdev(major, DEVICE_NAME); /* unregister the major number */ 188 | pr_info("Goodbye!\n"); 189 | } 190 | 191 | module_init(calc_init); 192 | module_exit(calc_exit); 193 | -------------------------------------------------------------------------------- /scripts/commit-msg.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # git-good-commit(1) - Git hook to help you write good commit messages. 4 | # Released under the MIT License. 5 | # 6 | # https://github.com/tommarshall/git-good-commit 7 | 8 | COMMIT_MSG_FILE="$1" 9 | COMMIT_MSG_LINES= 10 | HOOK_EDITOR= 11 | SKIP_DISPLAY_WARNINGS=0 12 | WARNINGS= 13 | 14 | RED= 15 | YELLOW= 16 | BLUE= 17 | WHITE= 18 | CYAN= 19 | NC= 20 | 21 | # 22 | # Set colour variables if the output should be coloured. 23 | # 24 | 25 | set_colors() { 26 | local default_color=$(git config --get hooks.goodcommit.color || git config --get color.ui || echo 'auto') 27 | if [[ $default_color == 'always' ]] || [[ $default_color == 'auto' && -t 1 ]]; then 28 | RED='\033[1;31m' 29 | YELLOW='\033[1;33m' 30 | BLUE='\033[1;34m' 31 | WHITE='\033[1;37m' 32 | CYAN='\033[1;36m' 33 | NC='\033[0m' # No Color 34 | fi 35 | } 36 | 37 | # 38 | # Set the hook editor, using the same approach as git. 39 | # 40 | 41 | set_editor() { 42 | # $GIT_EDITOR appears to always be set to `:` when the hook is executed by Git? 43 | # ref: http://stackoverflow.com/q/41468839/885540 44 | # ref: https://github.com/tommarshall/git-good-commit/issues/11 45 | # HOOK_EDITOR=$GIT_EDITOR 46 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR=$(git config --get core.editor) 47 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR=$VISUAL 48 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR=$EDITOR 49 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR='vi' 50 | } 51 | 52 | # 53 | # Output prompt help information. 54 | # 55 | 56 | prompt_help() { 57 | echo -e "${RED}$(cat <<-EOF 58 | e - edit commit message 59 | n - abort commit 60 | ? - print help 61 | EOF 62 | )${NC}" 63 | } 64 | 65 | # 66 | # Add a warning with and . 67 | # 68 | 69 | add_warning() { 70 | local line_number=$1 71 | local warning=$2 72 | WARNINGS[$line_number]="${WARNINGS[$line_number]}$warning;" 73 | } 74 | 75 | # 76 | # Output warnings. 77 | # 78 | 79 | display_warnings() { 80 | if [ $SKIP_DISPLAY_WARNINGS -eq 1 ]; then 81 | # if the warnings were skipped then they should be displayed next time 82 | SKIP_DISPLAY_WARNINGS=0 83 | return 84 | fi 85 | 86 | for i in "${!WARNINGS[@]}"; do 87 | printf "%-74s ${WHITE}%s${NC}\n" "${COMMIT_MSG_LINES[$(($i-1))]}" "[line ${i}]" 88 | IFS=';' read -ra WARNINGS_ARRAY <<< "${WARNINGS[$i]}" 89 | for ERROR in "${WARNINGS_ARRAY[@]}"; do 90 | echo -e " ${YELLOW}- ${ERROR}${NC}" 91 | done 92 | done 93 | 94 | echo 95 | echo -e "${RED}$(cat <<-EOF 96 | How to Write a Git Commit Message: https://chris.beams.io/posts/git-commit/ 97 | EOF 98 | )${NC}" 99 | } 100 | 101 | # 102 | # Read the contents of the commit msg into an array of lines. 103 | # 104 | 105 | read_commit_message() { 106 | # reset commit_msg_lines 107 | COMMIT_MSG_LINES=() 108 | 109 | # read commit message into lines array 110 | while IFS= read -r; do 111 | 112 | # trim trailing spaces from commit lines 113 | shopt -s extglob 114 | REPLY="${REPLY%%*( )}" 115 | shopt -u extglob 116 | 117 | # ignore comments 118 | [[ $REPLY =~ ^# ]] 119 | test $? -eq 0 || COMMIT_MSG_LINES+=("$REPLY") 120 | 121 | done < $COMMIT_MSG_FILE 122 | } 123 | 124 | # 125 | # Validate the contents of the commmit msg agains the good commit guidelines. 126 | # 127 | 128 | validate_commit_message() { 129 | # reset warnings 130 | WARNINGS=() 131 | 132 | # capture the subject, and remove the 'squash! ' prefix if present 133 | COMMIT_SUBJECT=${COMMIT_MSG_LINES[0]/#squash! /} 134 | 135 | # if the commit is empty there's nothing to validate, we can return here 136 | COMMIT_MSG_STR="${COMMIT_MSG_LINES[*]}" 137 | test -z "${COMMIT_MSG_STR[*]// }" && return; 138 | 139 | # if the commit subject starts with 'fixup! ' there's nothing to validate, we can return here 140 | [[ $COMMIT_SUBJECT == 'fixup! '* ]] && return; 141 | 142 | # skip first token in subject (e.g. issue ID from bugtracker which is validated otherwise) 143 | skipfirsttokeninsubject=$(git config --get hooks.goodcommit.subjectskipfirsttoken || echo 'false') 144 | if [ "$skipfirsttokeninsubject" == "true" ]; then 145 | COMMIT_SUBJECT_TO_PROCESS=${COMMIT_SUBJECT#* } 146 | else 147 | COMMIT_SUBJECT_TO_PROCESS=$COMMIT_SUBJECT 148 | fi 149 | 150 | # 0. Check spelling 151 | # ------------------------------------------------------------------------------ 152 | ASPELL=$(which aspell) 153 | if [ $? -ne 0 ]; then 154 | echo "Aspell not installed - unable to check spelling" 155 | else 156 | LINE_NUMBER=1 157 | MISSPELLED_WORDS=`echo "$COMMIT_MSG_LINES[LINE_NUMBER]" | $ASPELL --lang=en --list --home-dir=scripts --personal=aspell-pws` 158 | if [ -n "$MISSPELLED_WORDS" ]; then 159 | add_warning LINE_NUMBER "Possible misspelled word(s): $MISSPELLED_WORDS" 160 | fi 161 | fi 162 | 163 | # 1. Separate subject from body with a blank line 164 | # ------------------------------------------------------------------------------ 165 | 166 | test ${#COMMIT_MSG_LINES[@]} -lt 1 || test -z "${COMMIT_MSG_LINES[1]}" 167 | test $? -eq 0 || add_warning 2 "Separate subject from body with a blank line" 168 | 169 | # 2. Limit the subject line to configured number of characters 170 | # ------------------------------------------------------------------------------ 171 | 172 | subject_max_length=$(git config --get hooks.goodcommit.subjectmaxlength || echo '60') 173 | test "${#COMMIT_SUBJECT}" -le $subject_max_length 174 | test $? -eq 0 || add_warning 1 "Limit the subject line to $subject_max_length characters (${#COMMIT_SUBJECT} chars)" 175 | 176 | # 3. Capitalize the subject line 177 | # ------------------------------------------------------------------------------ 178 | 179 | [[ ${COMMIT_SUBJECT_TO_PROCESS} =~ ^[[:blank:]]*([[:upper:]]{1}[[:lower:]]*|[[:digit:]]+)([[:blank:]]|[[:punct:]]|$) ]] 180 | test $? -eq 0 || add_warning 1 "Capitalize the subject line" 181 | 182 | # 4. Do not end the subject line with a period 183 | # ------------------------------------------------------------------------------ 184 | 185 | [[ ${COMMIT_SUBJECT} =~ [^\.]$ ]] 186 | test $? -eq 0 || add_warning 1 "Do not end the subject line with a period" 187 | 188 | # 5. Use the imperative mood in the subject line 189 | # ------------------------------------------------------------------------------ 190 | 191 | IMPERATIVE_MOOD_BLACKLIST=( 192 | added adds adding 193 | adjusted adjusts adjusting 194 | amended amends amending 195 | avoided avoids avoiding 196 | bumped bumps bumping 197 | changed changes changing 198 | checked checks checking 199 | committed commits committing 200 | copied copies copying 201 | corrected corrects correcting 202 | created creates creating 203 | decreased decreases decreasing 204 | deleted deletes deleting 205 | disabled disables disabling 206 | dropped drops dropping 207 | duplicated duplicates duplicating 208 | enabled enables enabling 209 | excluded excludes excluding 210 | fixed fixes fixing 211 | handled handles handling 212 | implemented implements implementing 213 | improved improves improving 214 | included includes including 215 | increased increases increasing 216 | installed installs installing 217 | introduced introduces introducing 218 | merged merges merging 219 | moved moves moving 220 | pruned prunes pruning 221 | refactored refactors refactoring 222 | released releases releasing 223 | removed removes removing 224 | renamed renames renaming 225 | replaced replaces replacing 226 | resolved resolves resolving 227 | reverted reverts reverting 228 | showed shows showing 229 | tested tests testing 230 | tidied tidies tidying 231 | updated updates updating 232 | used uses using 233 | ) 234 | 235 | # enable case insensitive match 236 | shopt -s nocasematch 237 | 238 | for BLACKLISTED_WORD in "${IMPERATIVE_MOOD_BLACKLIST[@]}"; do 239 | [[ ${COMMIT_SUBJECT_TO_PROCESS} =~ ^[[:blank:]]*$BLACKLISTED_WORD ]] 240 | test $? -eq 0 && add_warning 1 "Use the imperative mood in the subject line, e.g 'fix' not 'fixes'" && break 241 | done 242 | 243 | # disable case insensitive match 244 | shopt -u nocasematch 245 | 246 | # 6. Wrap the body at 72 characters 247 | # ------------------------------------------------------------------------------ 248 | 249 | URL_REGEX='^[[:blank:]]*(https?|ftp|file)://[-A-Za-z0-9\+&@#/%?=~_|!:,.;]*[-A-Za-z0-9\+&@#/%=~_|]' 250 | 251 | for i in "${!COMMIT_MSG_LINES[@]}"; do 252 | LINE_NUMBER=$((i+1)) 253 | test "${#COMMIT_MSG_LINES[$i]}" -le 72 || [[ ${COMMIT_MSG_LINES[$i]} =~ $URL_REGEX ]] 254 | test $? -eq 0 || add_warning $LINE_NUMBER "Wrap the body at 72 characters (${#COMMIT_MSG_LINES[$i]} chars)" 255 | done 256 | 257 | # 7. Use the body to explain what and why vs. how 258 | # ------------------------------------------------------------------------------ 259 | 260 | # ? 261 | 262 | # 8. Do no write single worded commits 263 | # ------------------------------------------------------------------------------ 264 | 265 | COMMIT_SUBJECT_WORDS=(${COMMIT_SUBJECT_TO_PROCESS}) 266 | test "${#COMMIT_SUBJECT_WORDS[@]}" -gt 1 267 | test $? -eq 0 || add_warning 1 "Do no write single worded commits" 268 | 269 | # 9. Do not start the subject line with whitespace 270 | # ------------------------------------------------------------------------------ 271 | 272 | [[ ${COMMIT_SUBJECT_TO_PROCESS} =~ ^[[:blank:]]+ ]] 273 | test $? -eq 1 || add_warning 1 "Do not start the subject line with whitespace" 274 | } 275 | 276 | # 277 | # It's showtime. 278 | # 279 | 280 | set_colors 281 | 282 | set_editor 283 | 284 | if tty >/dev/null 2>&1; then 285 | TTY=$(tty) 286 | else 287 | TTY=/dev/tty 288 | fi 289 | 290 | while true; do 291 | 292 | read_commit_message 293 | 294 | validate_commit_message 295 | 296 | # if there are no WARNINGS are empty then we're good to break out of here 297 | test ${#WARNINGS[@]} -eq 0 && exit 0; 298 | 299 | display_warnings 300 | 301 | # Ask the question (not using "read -p" as it uses stderr not stdout) 302 | echo -en "${CYAN}Proceed with commit? [e/n/?] ${NC}" 303 | 304 | # Read the answer 305 | read REPLY < "$TTY" 306 | 307 | # Check if the reply is valid 308 | case "$REPLY" in 309 | E*|e*) $HOOK_EDITOR "$COMMIT_MSG_FILE" < $TTY; continue ;; 310 | N*|n*) exit 1 ;; 311 | *) SKIP_DISPLAY_WARNINGS=1; prompt_help; continue ;; 312 | esac 313 | 314 | done 315 | -------------------------------------------------------------------------------- /expression.c: -------------------------------------------------------------------------------- 1 | #include "expression.h" 2 | 3 | #include /* for isspace */ 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define GET_NUM(n) ((n) >> 4) 10 | #define NAN_INT ((1 << 4) | ((1 << 4) - 1)) 11 | #define INF_INT ((1 << 5) | ((1 << 4) - 1)) 12 | #define MASK(n) (((n) > 0) << 4) 13 | /* 14 | * LSB 4 bits for precision, 2^3, one for sign 15 | * MSB 28 bits for integer 16 | * LSB all 1 is NaN 17 | */ 18 | 19 | /* 20 | * Expression data types 21 | */ 22 | 23 | static int GET_FRAC(int n) 24 | { 25 | int x = n & 15; 26 | if (x & 8) 27 | return -(((~x) & 15) + 1); 28 | 29 | return x; 30 | } 31 | 32 | static int FP2INT(int n, int d) 33 | { 34 | while (n && n % 10 == 0) { 35 | ++d; 36 | n /= 10; 37 | } 38 | if (d == -1) { 39 | n *= 10; 40 | --d; 41 | } 42 | 43 | return ((n << 4) | (d & 15)); 44 | } 45 | 46 | static int isNan(int x) 47 | { 48 | return GET_FRAC(x) == GET_FRAC(NAN_INT); 49 | } 50 | 51 | enum compare_type { LOWER = 1, EQUAL = 2, GREATER = 4 }; 52 | 53 | enum expr_type { 54 | OP_UNKNOWN, 55 | OP_UNARY_MINUS, 56 | OP_UNARY_LOGICAL_NOT, 57 | OP_UNARY_BITWISE_NOT, 58 | 59 | OP_POWER, 60 | OP_DIVIDE, 61 | OP_MULTIPLY, 62 | OP_REMAINDER, 63 | 64 | OP_PLUS, 65 | OP_MINUS, 66 | 67 | OP_SHL, 68 | OP_SHR, 69 | 70 | OP_LT, 71 | OP_LE, 72 | OP_GT, 73 | OP_GE, 74 | OP_EQ, 75 | OP_NE, 76 | 77 | OP_BITWISE_AND, 78 | OP_BITWISE_OR, 79 | OP_BITWISE_XOR, 80 | 81 | OP_LOGICAL_AND, 82 | OP_LOGICAL_OR, 83 | 84 | OP_ASSIGN, 85 | OP_COMMA, 86 | 87 | OP_CONST, 88 | OP_VAR, 89 | OP_FUNC, 90 | }; 91 | 92 | static int prec[] = {0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5, 93 | 5, 5, 5, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0}; 94 | 95 | /* clang-format off */ 96 | #define expr_init() \ 97 | { \ 98 | (enum expr_type) 0, \ 99 | { { 0 } } \ 100 | } 101 | /* clang-format on */ 102 | 103 | static int expr_is_unary(enum expr_type op) 104 | { 105 | return op == OP_UNARY_MINUS || op == OP_UNARY_LOGICAL_NOT || 106 | op == OP_UNARY_BITWISE_NOT; 107 | } 108 | 109 | static int expr_is_binary(enum expr_type op) 110 | { 111 | return !expr_is_unary(op) && op != OP_CONST && op != OP_VAR && 112 | op != OP_FUNC && op != OP_UNKNOWN; 113 | } 114 | 115 | static int expr_prec(enum expr_type a, enum expr_type b) 116 | { 117 | int left = 118 | expr_is_binary(a) && a != OP_ASSIGN && a != OP_POWER && a != OP_COMMA; 119 | return (left && prec[a] >= prec[b]) || (prec[a] > prec[b]); 120 | } 121 | 122 | #define isfirstvarchr(c) \ 123 | (((unsigned char) c >= '@' && c != '^' && c != '|') || c == '$') 124 | #define isvarchr(c) \ 125 | (((unsigned char) c >= '@' && c != '^' && c != '|') || c == '$' || \ 126 | c == '#' || (c >= '0' && c <= '9')) 127 | 128 | static struct { 129 | const char *s; 130 | const enum expr_type op; 131 | } OPS[] = { 132 | {"-u", OP_UNARY_MINUS}, 133 | {"!u", OP_UNARY_LOGICAL_NOT}, 134 | {"^u", OP_UNARY_BITWISE_NOT}, 135 | {"**", OP_POWER}, 136 | {"*", OP_MULTIPLY}, 137 | {"/", OP_DIVIDE}, 138 | {"%", OP_REMAINDER}, 139 | {"+", OP_PLUS}, 140 | {"-", OP_MINUS}, 141 | {"<<", OP_SHL}, 142 | {">>", OP_SHR}, 143 | {"<", OP_LT}, 144 | {"<=", OP_LE}, 145 | {">", OP_GT}, 146 | {">=", OP_GE}, 147 | {"==", OP_EQ}, 148 | {"!=", OP_NE}, 149 | {"&", OP_BITWISE_AND}, 150 | {"|", OP_BITWISE_OR}, 151 | {"^", OP_BITWISE_XOR}, 152 | {"&&", OP_LOGICAL_AND}, 153 | {"||", OP_LOGICAL_OR}, 154 | {"=", OP_ASSIGN}, 155 | {",", OP_COMMA}, 156 | 157 | /* These are used by lexer and must be ignored by parser, so we put 158 | them at the end */ 159 | {"-", OP_UNARY_MINUS}, 160 | {"!", OP_UNARY_LOGICAL_NOT}, 161 | {"^", OP_UNARY_BITWISE_NOT}, 162 | }; 163 | 164 | static enum expr_type expr_op(const char *s, size_t len, int unary) 165 | { 166 | for (unsigned int i = 0; i < sizeof(OPS) / sizeof(OPS[0]); i++) { 167 | if (strlen(OPS[i].s) == len && strncmp(OPS[i].s, s, len) == 0 && 168 | (unary == -1 || expr_is_unary(OPS[i].op) == unary)) { 169 | return OPS[i].op; 170 | } 171 | } 172 | return OP_UNKNOWN; 173 | } 174 | 175 | static int expr_parse_number(const char *s, size_t len) 176 | { 177 | int num = 0; 178 | int frac = 0; 179 | int dot = 0; /* FIXME: not good enough */ 180 | unsigned int digits = 0; 181 | for (unsigned int i = 0; i < len; i++) { 182 | if (s[i] == '.' && dot == 0) { 183 | dot = 1; 184 | continue; 185 | } 186 | if (isdigit(s[i])) { 187 | digits++; 188 | if (dot) 189 | --frac; 190 | num = num * 10 + (s[i] - '0'); 191 | } else 192 | return NAN_INT; 193 | } 194 | 195 | num = FP2INT(num, frac); 196 | return (digits > 0 ? num : NAN_INT); 197 | } 198 | 199 | /* 200 | * Functions 201 | */ 202 | struct expr_func *expr_func(struct expr_func *funcs, const char *s, size_t len) 203 | { 204 | for (struct expr_func *f = funcs; f->name; f++) { 205 | if (strlen(f->name) == len && strncmp(f->name, s, len) == 0) 206 | return f; 207 | } 208 | return NULL; 209 | } 210 | 211 | /* 212 | * Variables 213 | */ 214 | 215 | struct expr_var *expr_var(struct expr_var_list *vars, const char *s, size_t len) 216 | { 217 | struct expr_var *v = NULL; 218 | if (len == 0 || !isfirstvarchr(*s)) 219 | return NULL; 220 | for (v = vars->head; v; v = v->next) { 221 | if (strlen(v->name) == len && strncmp(v->name, s, len) == 0) 222 | return v; 223 | } 224 | v = (struct expr_var *) kcalloc(1, sizeof(struct expr_var) + len + 1, 225 | GFP_KERNEL); 226 | if (!v) 227 | return NULL; /* allocation failed */ 228 | v->next = vars->head; 229 | v->value = 0; 230 | strncpy(v->name, s, len); 231 | v->name[len] = '\0'; 232 | vars->head = v; 233 | return v; 234 | } 235 | 236 | static int mult(int a, int b) 237 | { 238 | int frac1 = GET_FRAC(a); 239 | int frac2 = GET_FRAC(b); 240 | int n1 = GET_NUM(a); 241 | int n2 = GET_NUM(b); 242 | int n3 = n1 * n2; 243 | 244 | return FP2INT(n3, (frac1 + frac2)); 245 | } 246 | 247 | static int divid(int a, int b) 248 | { 249 | int frac1 = GET_FRAC(a); 250 | int frac2 = GET_FRAC(b); 251 | int n1 = GET_NUM(a); 252 | int n2 = GET_NUM(b); 253 | if (n1 == 0 && n2 == 0) 254 | return NAN_INT; 255 | if (n2 == 0) 256 | return INF_INT; 257 | 258 | while (n1 * 10 < ((1 << 25) - 1)) { 259 | --frac1; 260 | n1 *= 10; 261 | } 262 | int n3 = n1 / n2; 263 | int frac3 = frac1 - frac2; 264 | 265 | return FP2INT(n3, frac3); 266 | } 267 | 268 | static int remain(int a, int b) 269 | { 270 | int frac1 = GET_FRAC(a); 271 | int frac2 = GET_FRAC(b); 272 | int n1 = GET_NUM(a); 273 | int n2 = GET_NUM(b); 274 | if (n2 == 0) 275 | return NAN_INT; 276 | 277 | /* to int */ 278 | while (frac1 > 0) { 279 | n1 /= 10; 280 | --frac1; 281 | } 282 | while (frac2 > 0) { 283 | n2 /= 10; 284 | --frac2; 285 | } 286 | 287 | n1 %= n2; 288 | 289 | return FP2INT(n1, frac1); 290 | } 291 | 292 | static int right_shift(int a, int b) 293 | { 294 | /* FIXME: should use 2-base? */ 295 | return divid(a, mult(2 << 4, b)); 296 | } 297 | 298 | static int power(int a, int b) 299 | { 300 | int frac1 = GET_FRAC(a); 301 | int frac2 = GET_FRAC(b); 302 | int n1 = GET_NUM(a); 303 | int n2 = GET_NUM(b); 304 | 305 | while (frac2 != 0) { 306 | if (frac2 < 0) { 307 | n2 /= 10; 308 | ++frac2; 309 | } else { 310 | n2 *= 10; 311 | --frac2; 312 | } 313 | } 314 | 315 | int on1 = n1; 316 | int of1 = frac1; 317 | n1 = 1; 318 | for (int i = 0; i < n2; ++i) { 319 | frac1 += of1; 320 | n1 *= on1; 321 | } 322 | return FP2INT(n1, frac1); 323 | } 324 | 325 | static int left_shift(int a, int b) 326 | { 327 | /* FIXME: should use 2-base? */ 328 | return mult(a, power(2 << 4, b)); 329 | } 330 | 331 | static int plus(int a, int b) 332 | { 333 | int frac1 = GET_FRAC(a); 334 | int frac2 = GET_FRAC(b); 335 | int n1 = GET_NUM(a); 336 | int n2 = GET_NUM(b); 337 | 338 | while (frac1 != frac2) { 339 | if (frac1 > frac2) { 340 | --frac1; 341 | n1 *= 10; 342 | } else if (frac1 < frac2) { 343 | --frac2; 344 | n2 *= 10; 345 | } 346 | } 347 | 348 | n1 += n2; 349 | 350 | return FP2INT(n1, frac1); 351 | } 352 | 353 | static int minus(int a, int b) 354 | { 355 | int frac1 = GET_FRAC(a); 356 | int frac2 = GET_FRAC(b); 357 | int n1 = GET_NUM(a); 358 | int n2 = GET_NUM(b); 359 | 360 | while (frac1 != frac2) { 361 | if (frac1 > frac2) { 362 | --frac1; 363 | n1 *= 10; 364 | } else { 365 | --frac2; 366 | n2 *= 10; 367 | } 368 | } 369 | 370 | n1 -= n2; 371 | return FP2INT(n1, frac1); 372 | } 373 | 374 | static int compare(int a, int b) 375 | { 376 | int frac1 = GET_FRAC(a); 377 | int frac2 = GET_FRAC(b); 378 | int n1 = GET_NUM(a); 379 | int n2 = GET_NUM(b); 380 | while (frac1 != frac2) { 381 | if (frac1 > frac2) { 382 | --frac1; 383 | n1 *= 10; 384 | } else { 385 | --frac2; 386 | n2 *= 10; 387 | } 388 | } 389 | 390 | int flags = 0; 391 | if (n1 < n2) 392 | flags |= LOWER; 393 | if (n1 > n2) 394 | flags |= GREATER; 395 | if (n1 == n2) 396 | flags |= EQUAL; 397 | 398 | return flags; 399 | } 400 | 401 | static int bitwise_op(int a, int b, int op) 402 | { 403 | int frac1 = GET_FRAC(a); 404 | int frac2 = GET_FRAC(b); 405 | int n1 = GET_NUM(a); 406 | int n2 = GET_NUM(b); 407 | if (op == 1 && (a == INF_INT || b == INF_INT)) 408 | return INF_INT; 409 | if (op == 1 && (a == NAN_INT || b == NAN_INT)) 410 | return 0; 411 | if (frac1 == -1 || frac2 == -1) 412 | return NAN_INT; 413 | 414 | /* to int */ 415 | while (frac1 > 0) { 416 | n1 /= 10; 417 | --frac1; 418 | } 419 | while (frac2 > 0) { 420 | n2 /= 10; 421 | --frac2; 422 | } 423 | 424 | if (op == 0) 425 | n1 &= n2; 426 | else if (op == 1) 427 | n1 |= n2; 428 | else if (op == 2) 429 | n1 ^= n2; 430 | else if (op == 3) 431 | n1 = ~n1; 432 | 433 | while (n1 && n1 % 10 == 0) { 434 | ++frac1; 435 | n1 /= 10; 436 | } 437 | 438 | if (frac1 == -1) { 439 | n1 *= 10; 440 | --frac1; 441 | } 442 | 443 | return FP2INT(n1, frac1); 444 | } 445 | 446 | static int not(int a) 447 | { 448 | int frac = GET_FRAC(a); 449 | int n = GET_NUM(a); 450 | 451 | return FP2INT(!n, frac); 452 | } 453 | 454 | /* TODO: change logic */ 455 | int expr_eval(struct expr *e) 456 | { 457 | int n; 458 | switch (e->type) { 459 | case OP_UNARY_MINUS: /* OK */ 460 | return minus(FP2INT(0, 0), expr_eval(&e->param.op.args.buf[0])); 461 | case OP_UNARY_LOGICAL_NOT: /* OK */ 462 | return not(expr_eval(&e->param.op.args.buf[0])); 463 | case OP_UNARY_BITWISE_NOT: /* OK */ 464 | return bitwise_op(expr_eval(&e->param.op.args.buf[0]), 0, 3); 465 | case OP_POWER: 466 | return power(expr_eval(&e->param.op.args.buf[0]), 467 | expr_eval(&e->param.op.args.buf[1])); 468 | case OP_MULTIPLY: /* OK */ 469 | return mult(expr_eval(&e->param.op.args.buf[0]), 470 | expr_eval(&e->param.op.args.buf[1])); 471 | case OP_DIVIDE: /* OK, precision */ 472 | return divid(expr_eval(&e->param.op.args.buf[0]), 473 | expr_eval(&e->param.op.args.buf[1])); 474 | case OP_REMAINDER: /* OK, no floating point */ 475 | return remain(expr_eval(&e->param.op.args.buf[0]), 476 | expr_eval(&e->param.op.args.buf[1])); 477 | case OP_PLUS: /* OK */ 478 | return plus(expr_eval(&e->param.op.args.buf[0]), 479 | expr_eval(&e->param.op.args.buf[1])); 480 | case OP_MINUS: /* OK */ 481 | return minus(expr_eval(&e->param.op.args.buf[0]), 482 | expr_eval(&e->param.op.args.buf[1])); 483 | case OP_SHL: /* oK */ 484 | return left_shift(expr_eval(&e->param.op.args.buf[0]), 485 | expr_eval(&e->param.op.args.buf[1])); 486 | case OP_SHR: /* OK */ 487 | return right_shift(expr_eval(&e->param.op.args.buf[0]), 488 | expr_eval(&e->param.op.args.buf[1])); 489 | case OP_LT: /* OK */ 490 | return MASK(compare(expr_eval(&e->param.op.args.buf[0]), 491 | expr_eval(&e->param.op.args.buf[1])) & 492 | (LOWER)); 493 | case OP_LE: /* OK */ 494 | return MASK(compare(expr_eval(&e->param.op.args.buf[0]), 495 | expr_eval(&e->param.op.args.buf[1])) & 496 | (LOWER | EQUAL)); 497 | case OP_GT: /* OK */ 498 | return MASK(compare(expr_eval(&e->param.op.args.buf[0]), 499 | expr_eval(&e->param.op.args.buf[1])) & 500 | (GREATER)); 501 | case OP_GE: /* OK */ 502 | return MASK(compare(expr_eval(&e->param.op.args.buf[0]), 503 | expr_eval(&e->param.op.args.buf[1])) & 504 | (GREATER | EQUAL)); 505 | case OP_EQ: /* OK */ 506 | return MASK(compare(expr_eval(&e->param.op.args.buf[0]), 507 | expr_eval(&e->param.op.args.buf[1])) & 508 | (EQUAL)); 509 | case OP_NE: /* OK */ 510 | return MASK(!(compare(expr_eval(&e->param.op.args.buf[0]), 511 | expr_eval(&e->param.op.args.buf[1])) & 512 | (EQUAL))); 513 | case OP_BITWISE_AND: /* OK */ 514 | return (bitwise_op(expr_eval(&e->param.op.args.buf[0]), 515 | expr_eval(&e->param.op.args.buf[1]), 0)); 516 | case OP_BITWISE_OR: /* OK */ 517 | return MASK(bitwise_op(expr_eval(&e->param.op.args.buf[0]), 518 | expr_eval(&e->param.op.args.buf[1]), 1)); 519 | case OP_BITWISE_XOR: /* OK */ 520 | return (bitwise_op(expr_eval(&e->param.op.args.buf[0]), 521 | expr_eval(&e->param.op.args.buf[1]), 2)); 522 | case OP_LOGICAL_AND: /* OK */ 523 | n = expr_eval(&e->param.op.args.buf[0]); 524 | if (GET_NUM(n) != 0) { 525 | n = expr_eval(&e->param.op.args.buf[1]); 526 | if (GET_NUM(n) != 0) 527 | return n; 528 | } 529 | return 0; 530 | case OP_LOGICAL_OR: /* OK */ 531 | n = expr_eval(&e->param.op.args.buf[0]); 532 | if (GET_NUM(n) != 0 && !isNan(n)) { 533 | return n; 534 | } else { 535 | n = expr_eval(&e->param.op.args.buf[1]); 536 | if (GET_NUM(n) != 0) 537 | return n; 538 | } 539 | return 0; 540 | case OP_ASSIGN: /* OK */ 541 | n = expr_eval(&e->param.op.args.buf[1]); 542 | if (vec_nth(&e->param.op.args, 0).type == OP_VAR) 543 | *e->param.op.args.buf[0].param.var.value = n; 544 | return n; 545 | case OP_COMMA: /* OK */ 546 | expr_eval(&e->param.op.args.buf[0]); 547 | return expr_eval(&e->param.op.args.buf[1]); 548 | case OP_CONST: 549 | return e->param.num.value; 550 | case OP_VAR: 551 | return *e->param.var.value; 552 | case OP_FUNC: 553 | return e->param.func.f->f(e->param.func.f, e->param.func.args, 554 | e->param.func.context); 555 | default: 556 | return NAN_INT; 557 | } 558 | } 559 | 560 | int expr_next_token(const char *s, size_t len, int *flags) 561 | { 562 | unsigned int i = 0; 563 | if (len == 0) { 564 | return 0; 565 | } 566 | char c = s[0]; 567 | if (c == '#') { 568 | for (; i < len && s[i] != '\n'; i++) 569 | ; 570 | return i; 571 | } else if (c == '\n') { 572 | for (; i < len && isspace(s[i]); i++) 573 | ; 574 | if (*flags & EXPR_TOP) { 575 | if (i == len || s[i] == ')') 576 | *flags = *flags & (~EXPR_COMMA); 577 | else 578 | *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_COMMA; 579 | } 580 | return i; 581 | } else if (isspace(c)) { 582 | while (i < len && isspace(s[i]) && s[i] != '\n') { 583 | i++; 584 | } 585 | return i; 586 | } else if (isdigit(c)) { 587 | if ((*flags & EXPR_TNUMBER) == 0) 588 | return -1; /* unexpected number */ 589 | *flags = EXPR_TOP | EXPR_TCLOSE; 590 | while ((c == '.' || isdigit(c)) && i < len) { 591 | i++; 592 | c = s[i]; 593 | } 594 | return i; 595 | } else if (isfirstvarchr(c)) { 596 | if ((*flags & EXPR_TWORD) == 0) 597 | return -2; /* unexpected word */ 598 | *flags = EXPR_TOP | EXPR_TOPEN | EXPR_TCLOSE; 599 | while ((isvarchr(c)) && i < len) { 600 | i++; 601 | c = s[i]; 602 | } 603 | return i; 604 | } else if (c == '(' || c == ')') { 605 | if (c == '(' && (*flags & EXPR_TOPEN) != 0) 606 | *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_TCLOSE; 607 | else if (c == ')' && (*flags & EXPR_TCLOSE) != 0) 608 | *flags = EXPR_TOP | EXPR_TCLOSE; 609 | else 610 | return -3; /* unexpected parenthesis */ 611 | return 1; 612 | } else { 613 | if ((*flags & EXPR_TOP) == 0) { 614 | if (expr_op(&c, 1, 1) == OP_UNKNOWN) 615 | return -4; /* missing expected operand */ 616 | *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_UNARY; 617 | return 1; 618 | } else { 619 | int found = 0; 620 | while (!isvarchr(c) && !isspace(c) && c != '(' && c != ')' && 621 | i < len) { 622 | if (expr_op(s, i + 1, 0) != OP_UNKNOWN) 623 | found = 1; 624 | else if (found) 625 | break; 626 | i++; 627 | c = s[i]; 628 | } 629 | if (!found) 630 | return -5; /* unknown operator */ 631 | *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN; 632 | return i; 633 | } 634 | } 635 | } 636 | 637 | #define EXPR_PAREN_ALLOWED 0 638 | #define EXPR_PAREN_EXPECTED 1 639 | #define EXPR_PAREN_FORBIDDEN 2 640 | 641 | static int expr_bind(const char *s, size_t len, vec_expr_t *es) 642 | { 643 | enum expr_type op = expr_op(s, len, -1); 644 | if (op == OP_UNKNOWN) 645 | return -1; 646 | 647 | if (expr_is_unary(op)) { 648 | if (vec_len(es) < 1) 649 | return -1; 650 | struct expr arg = vec_pop(es); 651 | struct expr unary = expr_init(); 652 | unary.type = op; 653 | vec_push(&unary.param.op.args, arg); 654 | vec_push(es, unary); 655 | } else { 656 | if (vec_len(es) < 2) 657 | return -1; 658 | struct expr b = vec_pop(es); 659 | struct expr a = vec_pop(es); 660 | struct expr binary = expr_init(); 661 | binary.type = op; 662 | if (op == OP_ASSIGN && a.type != OP_VAR) 663 | return -1; /* Bad assignment */ 664 | vec_push(&binary.param.op.args, a); 665 | vec_push(&binary.param.op.args, b); 666 | vec_push(es, binary); 667 | } 668 | return 0; 669 | } 670 | 671 | static struct expr expr_const(int value) 672 | { 673 | struct expr e = expr_init(); 674 | e.type = OP_CONST; 675 | e.param.num.value = value; 676 | return e; 677 | } 678 | 679 | static struct expr expr_varref(struct expr_var *v) 680 | { 681 | struct expr e = expr_init(); 682 | e.type = OP_VAR; 683 | e.param.var.value = &v->value; 684 | return e; 685 | } 686 | 687 | static struct expr expr_binary(enum expr_type type, 688 | struct expr a, 689 | struct expr b) 690 | { 691 | struct expr e = expr_init(); 692 | e.type = type; 693 | vec_push(&e.param.op.args, a); 694 | vec_push(&e.param.op.args, b); 695 | return e; 696 | } 697 | 698 | static inline void expr_copy(struct expr *dst, struct expr *src) 699 | { 700 | int i; 701 | struct expr arg; 702 | dst->type = src->type; 703 | if (src->type == OP_FUNC) { 704 | dst->param.func.f = src->param.func.f; 705 | vec_foreach (&src->param.func.args, arg, i) { 706 | struct expr tmp = expr_init(); 707 | expr_copy(&tmp, &arg); 708 | vec_push(&dst->param.func.args, tmp); 709 | } 710 | if (src->param.func.f->ctxsz > 0) { 711 | dst->param.func.context = 712 | kcalloc(1, src->param.func.f->ctxsz, GFP_KERNEL); 713 | } 714 | } else if (src->type == OP_CONST) { 715 | dst->param.num.value = src->param.num.value; 716 | } else if (src->type == OP_VAR) { 717 | dst->param.var.value = src->param.var.value; 718 | } else { 719 | vec_foreach (&src->param.op.args, arg, i) { 720 | struct expr tmp = expr_init(); 721 | expr_copy(&tmp, &arg); 722 | vec_push(&dst->param.op.args, tmp); 723 | } 724 | } 725 | } 726 | 727 | static void expr_destroy_args(struct expr *e); 728 | 729 | struct expr *expr_create(const char *s, 730 | size_t len, 731 | struct expr_var_list *vars, 732 | struct expr_func *funcs) 733 | { 734 | int num; 735 | struct expr_var *v; 736 | const char *id = NULL; 737 | size_t idn = 0; 738 | 739 | struct expr *result = NULL; 740 | 741 | vec_expr_t es = vec_init(); 742 | vec_str_t os = vec_init(); 743 | vec_arg_t as = vec_init(); 744 | 745 | struct macro { 746 | char *name; 747 | vec_expr_t body; 748 | }; 749 | vec(struct macro) macros = vec_init(); 750 | 751 | int flags = EXPR_TDEFAULT; 752 | int paren = EXPR_PAREN_ALLOWED; 753 | for (;;) { 754 | int n = expr_next_token(s, len, &flags); 755 | if (n == 0) 756 | break; 757 | else if (n < 0) 758 | goto cleanup; 759 | const char *tok = s; 760 | s = s + n; 761 | len = len - n; 762 | if (*tok == '#') 763 | continue; 764 | if (flags & EXPR_UNARY) { 765 | if (n == 1) { 766 | switch (*tok) { 767 | case '-': 768 | tok = "-u"; 769 | break; 770 | case '^': 771 | tok = "^u"; 772 | break; 773 | case '!': 774 | tok = "!u"; 775 | break; 776 | default: 777 | goto cleanup; 778 | } 779 | n = 2; 780 | } 781 | } 782 | if (*tok == '\n' && (flags & EXPR_COMMA)) { 783 | flags = flags & (~EXPR_COMMA); 784 | n = 1; 785 | tok = ","; 786 | } 787 | if (isspace(*tok)) 788 | continue; 789 | int paren_next = EXPR_PAREN_ALLOWED; 790 | 791 | if (idn > 0) { 792 | if (n == 1 && *tok == '(') { 793 | int i; 794 | int has_macro = 0; 795 | struct macro m; 796 | vec_foreach (¯os, m, i) { 797 | if (strlen(m.name) == idn && 798 | strncmp(m.name, id, idn) == 0) { 799 | has_macro = 1; 800 | break; 801 | } 802 | } 803 | if ((idn == 1 && id[0] == '$') || has_macro || 804 | expr_func(funcs, id, idn)) { 805 | struct expr_string str = {id, (int) idn}; 806 | vec_push(&os, str); 807 | paren = EXPR_PAREN_EXPECTED; 808 | } else 809 | goto cleanup; /* invalid function name */ 810 | } else if ((v = expr_var(vars, id, idn))) { 811 | vec_push(&es, expr_varref(v)); 812 | paren = EXPR_PAREN_FORBIDDEN; 813 | } 814 | id = NULL; 815 | idn = 0; 816 | } 817 | 818 | if (n == 1 && *tok == '(') { 819 | if (paren == EXPR_PAREN_EXPECTED) { 820 | struct expr_string str = {"{", 1}; 821 | vec_push(&os, str); 822 | struct expr_arg arg = {vec_len(&os), vec_len(&es), vec_init()}; 823 | vec_push(&as, arg); 824 | } else if (paren == EXPR_PAREN_ALLOWED) { 825 | struct expr_string str = {"(", 1}; 826 | vec_push(&os, str); 827 | } else 828 | goto cleanup; /* Bad call */ 829 | } else if (paren == EXPR_PAREN_EXPECTED) { 830 | goto cleanup; /* Bad call */ 831 | } else if (n == 1 && *tok == ')') { 832 | int minlen = (vec_len(&as) > 0 ? vec_peek(&as).oslen : 0); 833 | while (vec_len(&os) > minlen && *vec_peek(&os).s != '(' && 834 | *vec_peek(&os).s != '{') { 835 | struct expr_string str = vec_pop(&os); 836 | if (expr_bind(str.s, str.n, &es) == -1) 837 | goto cleanup; 838 | } 839 | if (vec_len(&os) == 0) 840 | goto cleanup; /* Bad parens */ 841 | struct expr_string str = vec_pop(&os); 842 | if (str.n == 1 && *str.s == '{') { 843 | str = vec_pop(&os); 844 | struct expr_arg arg = vec_pop(&as); 845 | if (vec_len(&es) > arg.eslen) 846 | vec_push(&arg.args, vec_pop(&es)); 847 | if (str.n == 1 && str.s[0] == '$') { 848 | if (vec_len(&arg.args) < 1) { 849 | vec_free(&arg.args); 850 | goto cleanup; /* too few arguments for $() function */ 851 | } 852 | struct expr *u = &vec_nth(&arg.args, 0); 853 | if (u->type != OP_VAR) { 854 | vec_free(&arg.args); 855 | goto cleanup; /* first argument is not a variable */ 856 | } 857 | for (v = vars->head; v; v = v->next) { 858 | if (&v->value == u->param.var.value) { 859 | struct macro m = {v->name, arg.args}; 860 | vec_push(¯os, m); 861 | break; 862 | } 863 | } 864 | vec_push(&es, expr_const(0)); 865 | } else { 866 | int i = 0; 867 | int found = -1; 868 | struct macro m; 869 | vec_foreach (¯os, m, i) { 870 | if (strlen(m.name) == (size_t) str.n && 871 | strncmp(m.name, str.s, str.n) == 0) 872 | found = i; 873 | } 874 | if (found != -1) { 875 | m = vec_nth(¯os, found); 876 | struct expr root = expr_const(0); 877 | struct expr *p = &root; 878 | /* Assign macro parameters */ 879 | for (int j = 0; j < vec_len(&arg.args); j++) { 880 | char varname[4]; 881 | snprintf(varname, sizeof(varname) - 1, "$%d", 882 | (j + 1)); 883 | v = expr_var(vars, varname, strlen(varname)); 884 | struct expr ev = expr_varref(v); 885 | struct expr assign = expr_binary( 886 | OP_ASSIGN, ev, vec_nth(&arg.args, j)); 887 | *p = expr_binary(OP_COMMA, assign, expr_const(0)); 888 | p = &vec_nth(&p->param.op.args, 1); 889 | } 890 | /* Expand macro body */ 891 | for (int j = 1; j < vec_len(&m.body); j++) { 892 | if (j < vec_len(&m.body) - 1) { 893 | *p = expr_binary(OP_COMMA, expr_const(0), 894 | expr_const(0)); 895 | expr_copy(&vec_nth(&p->param.op.args, 0), 896 | &vec_nth(&m.body, j)); 897 | } else 898 | expr_copy(p, &vec_nth(&m.body, j)); 899 | p = &vec_nth(&p->param.op.args, 1); 900 | } 901 | vec_push(&es, root); 902 | vec_free(&arg.args); 903 | } else { 904 | struct expr_func *f = expr_func(funcs, str.s, str.n); 905 | struct expr bound_func = expr_init(); 906 | bound_func.type = OP_FUNC; 907 | bound_func.param.func.f = f; 908 | bound_func.param.func.args = arg.args; 909 | if (f->ctxsz > 0) { 910 | void *p = kcalloc(1, f->ctxsz, GFP_KERNEL); 911 | if (!p) 912 | goto cleanup; /* allocation failed */ 913 | bound_func.param.func.context = p; 914 | } 915 | vec_push(&es, bound_func); 916 | } 917 | } 918 | } 919 | paren_next = EXPR_PAREN_FORBIDDEN; 920 | } else if (!isNan(num = expr_parse_number(tok, n))) { 921 | vec_push(&es, expr_const(num)); 922 | paren_next = EXPR_PAREN_FORBIDDEN; 923 | } else if (expr_op(tok, n, -1) != OP_UNKNOWN) { 924 | enum expr_type op = expr_op(tok, n, -1); 925 | struct expr_string o2 = {NULL, 0}; 926 | if (vec_len(&os) > 0) 927 | o2 = vec_peek(&os); 928 | for (;;) { 929 | if (n == 1 && *tok == ',' && vec_len(&os) > 0) { 930 | struct expr_string str = vec_peek(&os); 931 | if (str.n == 1 && *str.s == '{') { 932 | struct expr e = vec_pop(&es); 933 | vec_push(&vec_peek(&as).args, e); 934 | break; 935 | } 936 | } 937 | enum expr_type type2 = expr_op(o2.s, o2.n, -1); 938 | if (!(type2 != OP_UNKNOWN && expr_prec(op, type2))) { 939 | struct expr_string str = {tok, n}; 940 | vec_push(&os, str); 941 | break; 942 | } 943 | 944 | if (expr_bind(o2.s, o2.n, &es) == -1) 945 | goto cleanup; 946 | (void) vec_pop(&os); 947 | if (vec_len(&os) > 0) 948 | o2 = vec_peek(&os); 949 | else 950 | o2.n = 0; 951 | } 952 | } else { 953 | if (n > 0 && !isdigit(*tok)) { 954 | /* Valid identifier, a variable or a function */ 955 | id = tok; 956 | idn = n; 957 | } else 958 | goto cleanup; /* Bad variable name, e.g. '2.3.4' or '4ever' */ 959 | } 960 | paren = paren_next; 961 | } 962 | 963 | if (idn > 0) 964 | vec_push(&es, expr_varref(expr_var(vars, id, idn))); 965 | 966 | while (vec_len(&os) > 0) { 967 | struct expr_string rest = vec_pop(&os); 968 | if (rest.n == 1 && (*rest.s == '(' || *rest.s == ')')) 969 | goto cleanup; /* Bad paren */ 970 | if (expr_bind(rest.s, rest.n, &es) == -1) 971 | goto cleanup; 972 | } 973 | 974 | result = (struct expr *) kcalloc(1, sizeof(struct expr), GFP_KERNEL); 975 | if (result) { 976 | if (vec_len(&es) == 0) 977 | result->type = OP_CONST; 978 | else 979 | *result = vec_pop(&es); 980 | } 981 | 982 | int i, j; 983 | struct macro m; 984 | struct expr e; 985 | struct expr_arg a; 986 | cleanup: 987 | vec_foreach (¯os, m, i) { 988 | vec_foreach (&m.body, e, j) { 989 | expr_destroy_args(&e); 990 | } 991 | vec_free(&m.body); 992 | } 993 | vec_free(¯os); 994 | 995 | vec_foreach (&es, e, i) { 996 | expr_destroy_args(&e); 997 | } 998 | vec_free(&es); 999 | 1000 | vec_foreach (&as, a, i) { 1001 | vec_foreach (&a.args, e, j) { 1002 | expr_destroy_args(&e); 1003 | } 1004 | vec_free(&a.args); 1005 | } 1006 | vec_free(&as); 1007 | 1008 | /*vec_foreach(&os, o, i) {vec_free(&m.body);}*/ 1009 | vec_free(&os); 1010 | return result; 1011 | } 1012 | 1013 | static void expr_destroy_args(struct expr *e) 1014 | { 1015 | int i; 1016 | struct expr arg; 1017 | if (e->type == OP_FUNC) { 1018 | vec_foreach (&e->param.func.args, arg, i) { 1019 | expr_destroy_args(&arg); 1020 | } 1021 | vec_free(&e->param.func.args); 1022 | if (e->param.func.context) { 1023 | if (e->param.func.f->cleanup) { 1024 | e->param.func.f->cleanup(e->param.func.f, 1025 | e->param.func.context); 1026 | } 1027 | kfree(e->param.func.context); 1028 | } 1029 | } else if (e->type != OP_CONST && e->type != OP_VAR) { 1030 | vec_foreach (&e->param.op.args, arg, i) { 1031 | expr_destroy_args(&arg); 1032 | } 1033 | vec_free(&e->param.op.args); 1034 | } 1035 | } 1036 | 1037 | void expr_destroy(struct expr *e, struct expr_var_list *vars) 1038 | { 1039 | if (e) { 1040 | expr_destroy_args(e); 1041 | kfree(e); 1042 | } 1043 | if (vars) { 1044 | for (struct expr_var *v = vars->head; v;) { 1045 | struct expr_var *next = v->next; 1046 | kfree(v); 1047 | v = next; 1048 | } 1049 | } 1050 | } 1051 | --------------------------------------------------------------------------------