├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── lib ├── linenoise.c └── linenoise.h ├── quich.1 ├── src ├── helper.c ├── helper.h ├── lexer.c ├── lexer.h ├── parser.c ├── parser.h ├── quich.c ├── quich.h ├── variable.c └── variable.h └── tests └── main.c /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.c,*.h] 13 | indent_size = 4 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | quich 2 | quich_test 3 | .idea 4 | .vscode 5 | cmake-build-debug 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | sudo: false 3 | compiler: gcc 4 | script: 5 | - make test 6 | - ./quich_test 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | ## v4.0.0 (2021-07-07) 4 | 5 | ### Changed 6 | 7 | - REPL functionality. 8 | 9 | ### Fixed 10 | 11 | - Operations validation. 12 | 13 | ## v3.2.1 (2021-04-21) 14 | 15 | ### Fixed 16 | 17 | - Bug in lexer related with signed numbers. 18 | 19 | - Segmentation fault in operations with an invalid number of opening/closing parentheses. 20 | 21 | ## v3.2.0 (2021-04-20) 22 | 23 | ### Added 24 | 25 | - Warning message for wrong number of parenthesis. 26 | 27 | ## v3.1.1 (2021-04-20) 28 | 29 | ### Fixed 30 | 31 | - Memory leaks. 32 | 33 | ## v3.1.0 (2021-03-04) 34 | 35 | ### Added 36 | 37 | - `GR` constant. 38 | 39 | ## v3.0.0 (2020-09-02) 40 | 41 | ### Changed 42 | 43 | - Variables work by value instead of by reference. 44 | 45 | - Minor performance improvements. 46 | 47 | ## v2.6.2 (2020-08-05) 48 | 49 | ### Fixed 50 | 51 | - Memory leaks. 52 | 53 | ## v2.6.1 (2020-06-10) 54 | 55 | ### Fixed 56 | 57 | - Bug related to variable declaration. 58 | 59 | ## v2.5.0 (2020-06-08) 60 | 61 | ### Added 62 | 63 | - `rand` function that returns a random number between 0 and 1. 64 | 65 | - `format` flag for formatting the result. 66 | 67 | - `G` constant. 68 | 69 | ## v2.4.0 (2020-06-06) 70 | 71 | ### Added 72 | 73 | - `thousands` flags to display thousands separators. 74 | 75 | ## v2.3.0 (2020-05-30) 76 | 77 | ### Added 78 | 79 | - Support for numbers with commas (,). 80 | 81 | - Data measurement units (mb, gb, tb and pt). 82 | 83 | ## v2.2.0 (2020-03-31) 84 | 85 | ### Added 86 | 87 | - `asin`, `acos` and `atan` functions. 88 | 89 | - `interactive` flag to force interactive mode. 90 | 91 | ### Fixed 92 | 93 | - Memory error message. 94 | 95 | ## v2.1.0 (2020-03-23) 96 | 97 | ### Added 98 | 99 | - Validation for division by zero. 100 | 101 | - Question mark for every undefined token in verbose mode. 102 | 103 | ### Fixed 104 | 105 | - Bug related to operands and parenthesis. 106 | 107 | ## v2.0.0 (2020-03-22) 108 | 109 | ### Added 110 | 111 | - Flags in interactive mode. 112 | 113 | - `degree` and `round` flags. 114 | 115 | - `PI` and `E` constants. 116 | 117 | ### Fixed 118 | 119 | - Bug related to negative exponent numbers. 120 | 121 | ### Removed 122 | 123 | - The `%` operator. 124 | 125 | ## v1.1.2 (2020-03-04) 126 | 127 | ### Fixed 128 | 129 | - Removed extra whitespaces. 130 | 131 | ## v1.0.0 (2020-03-03) 132 | 133 | - Initial release. 134 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9) 2 | project(quich) 3 | 4 | set(CMAKE_CXX_STANDARD 11) 5 | 6 | add_executable(quich 7 | lib/linenoise.c lib/linenoise.h 8 | src/lexer.c src/lexer.h 9 | src/parser.c src/parser.h 10 | src/helper.c src/helper.h 11 | src/variable.c src/variable.h 12 | src/quich.c src/quich.h) 13 | 14 | target_link_libraries(quich m) 15 | 16 | add_executable(quich_test 17 | lib/linenoise.c lib/linenoise.h 18 | src/lexer.c src/lexer.h 19 | src/parser.c src/parser.h 20 | src/helper.c src/helper.h 21 | src/variable.c src/variable.h 22 | tests/main.c) 23 | 24 | target_link_libraries(quich_test m) 25 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Any kind of contribution will be highly appreciated. This can be done by submitting an issue or a pull request to the Quich repository. 2 | 3 | Contributing for the first time to an open source project can make you feel a little insecure, but don't worry, any misunderstanding or mistake will be understood. 4 | 5 | ## Code of conduct 6 | 7 | Before making a contribution to the project, please read the following points and be sure to follow them: 8 | 9 | * Avoid being harsh and using bad language. 10 | * Don't be discriminatory. 11 | * Assume always good intentions. 12 | 13 | Any behavior that can be qualified as harassment won't be tolerated. 14 | 15 | ## Making a contribution 16 | 17 | ### Pull request 18 | 19 | * Fork the repository on GitHub. 20 | * Create a feature branch based on the master branch (or in the respective version branch that you will update). 21 | * Make your changes and be sure that your commit messages are easy to understand. 22 | * Update the version tag, tests or documentation if you think it's necessary. 23 | * Submit the pull request to the respective branch. 24 | 25 | ### Issue 26 | 27 | * Make sure that the same issue does not exists before making a new one. 28 | * Take advantage of the available labels, and use them correctly. 29 | * For writing a issue based on a bug or error, it's recommended to answer the following questions in the issue description: 30 | * What are the steps to reproduce? 31 | * What is the expected behaviour? 32 | * What is the environment? (GCC version, OS...) 33 | * Do you have any additional information? 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alejandro 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS=-lm -std=c99 -g -pedantic -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition 3 | LDFLAGS= 4 | FILES=src/helper.c src/parser.c src/lexer.c src/variable.c lib/linenoise.c 5 | PREFIX=/usr/local 6 | ifndef NAME 7 | NAME=quich 8 | endif 9 | 10 | all: quich 11 | 12 | quich: $(FILES) 13 | $(CC) -o $(NAME) $(FILES) src/quich.c $(CFLAGS) $(LDFLAGS) 14 | 15 | install: quich 16 | mkdir -p $(DESTDIR)$(PREFIX)/bin 17 | cp $(NAME) $(DESTDIR)$(PREFIX)/bin/$(NAME) 18 | 19 | test: 20 | $(CC) -o quich_test $(FILES) tests/main.c -lm 21 | 22 | clean: 23 | $(RM) $(NAME) 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | 4 |
5 | Quich 6 |
7 |

8 | 9 |

Just an advanced terminal calculator.

10 | 11 |

12 | 13 | 14 | 15 |

16 | 17 | Quich is a compact, fast, powerful and useful calculator for your terminal with numerous features, supporting Linux, Mac and Windows, written in [ANSI C](https://en.wikipedia.org/wiki/ANSI_C). 18 | 19 | You can enter in `interactive mode` by calling Quich in your terminal without an operation. 20 | 21 | ## Usage 22 | 23 | ### Options 24 | 25 | `-d` `--degree` Manage the given angles in degrees 26 | 27 | `-f` `--format` `[%s]` The format to display the result 28 | 29 | `-h` `--help` Get help and information 30 | 31 | `-i` `--interactive` Force interactive mode 32 | 33 | `-p` `--precision` `[%i]` The number of decimals to be used for the internal numbers 34 | 35 | `-r` `--round` `[%i]` The number of decimals to round the result 36 | 37 | `-t` `--thousands` Display the result with thousands separators 38 | 39 | `-vvv` `--verbose` Display the result with details 40 | 41 | `-v` `--version` Show the application version 42 | 43 | ### Functions 44 | 45 | Syntax: `func(operand)` 46 | 47 | * `sqrt` Square root 48 | 49 | * `abs` Absolute value (positive) 50 | 51 | * `log` Natural logarithm 52 | 53 | * `sin`, `cos`, `tan` Sine, Cosine and Tangent trigonometric functions 54 | 55 | * `asin`, `acos`, `atan` Arc sine, Arc cosine and Arc tangent trigonometric functions 56 | 57 | * `rand` Random number between 0 and 1 58 | 59 | * `round` Round to the nearest integer value 60 | 61 | * `floor` Round down 62 | 63 | * `ceil` Round up 64 | 65 | ### Variables 66 | 67 | For defining a variable just write an equal operator between its name and its value. Like this: `a=4+20`. 68 | 69 | ### Available 70 | 71 | Values `PI`, `E`, `GR` (Golden Ratio) and `G` (earth gravity). 72 | 73 | The units of measurement for data storage `mb`, `gb`, `tb` and `pt` (they return the result in kb). 74 | 75 | ## Install 76 | 77 | Packaging status 78 | 79 | You can download the bundles [here](https://github.com/Usbac/quich/releases/tag/v4.0.0). 80 | 81 | Or move to your Quich folder and run the following command: 82 | 83 | `make && sudo make install` 84 | 85 | Now you should be able to access Quich by running `quich` in your terminal. 86 | 87 | ### Naming 88 | 89 | You can specify the Quich name when installing it with the `NAME` makefile variable: 90 | 91 | `sudo make install NAME=calc` 92 | 93 | Now you will be able to access Quich with the `calc` command. 94 | 95 | ## Testing 96 | 97 | Move to your Quich folder and run the following command: 98 | 99 | `make test && ./quich_test` 100 | 101 | ## Examples 102 | 103 | ```console 104 | $ quich 5+3 105 | 8 106 | ``` 107 | ```console 108 | $ quich "a=20;a+1" 109 | 21 110 | ``` 111 | ```console 112 | $ quich "5+(cos(2)-2)^2" 113 | 10.8377655357568 114 | ``` 115 | ```console 116 | $ quich "5+(cos(2)-2)^2" -p 2 117 | 10.86 118 | ``` 119 | ```console 120 | $ quich 1234567+1 -t 121 | 1,234,568 122 | ``` 123 | ```console 124 | $ quich 1gb+1mb 125 | 1049600 126 | ``` 127 | ```console 128 | $ quich 12345 -f '%.1g' 129 | 1e+04 130 | ``` 131 | ```console 132 | $ quich 5+PI -vvv 133 | Tokens > '5' '+' 'PI' 134 | Posfix > 5 PI + 135 | Result > 8.14159265358979 136 | ``` 137 | 138 | ## Contributing 139 | 140 | Any contribution or support to this project in the form of a pull request or message will be highly appreciated. ❤️ 141 | 142 | You can read more about it [right here](CONTRIBUTING.md). Don't be shy :) 143 | 144 | ## License 145 | 146 | Quich is open-source software licensed under the [MIT license](https://github.com/Usbac/quich/blob/master/LICENSE). 147 | -------------------------------------------------------------------------------- /lib/linenoise.c: -------------------------------------------------------------------------------- 1 | /* linenoise.c -- guerrilla line editing library against the idea that a 2 | * line editing lib needs to be 20,000 lines of C code. 3 | * 4 | * You can find the latest source code at: 5 | * 6 | * http://github.com/antirez/linenoise 7 | * 8 | * Does a number of crazy assumptions that happen to be true in 99.9999% of 9 | * the 2010 UNIX computers around. 10 | * 11 | * ------------------------------------------------------------------------ 12 | * 13 | * Copyright (c) 2010-2016, Salvatore Sanfilippo 14 | * Copyright (c) 2010-2013, Pieter Noordhuis 15 | * 16 | * All rights reserved. 17 | * 18 | * Redistribution and use in source and binary forms, with or without 19 | * modification, are permitted provided that the following conditions are 20 | * met: 21 | * 22 | * * Redistributions of source code must retain the above copyright 23 | * notice, this list of conditions and the following disclaimer. 24 | * 25 | * * Redistributions in binary form must reproduce the above copyright 26 | * notice, this list of conditions and the following disclaimer in the 27 | * documentation and/or other materials provided with the distribution. 28 | * 29 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 30 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 31 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 32 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 33 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 34 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 35 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 36 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 38 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 39 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 40 | * 41 | * ------------------------------------------------------------------------ 42 | * 43 | * References: 44 | * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html 45 | * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html 46 | * 47 | * Todo list: 48 | * - Filter bogus Ctrl+ combinations. 49 | * - Win32 support 50 | * 51 | * Bloat: 52 | * - History search like Ctrl+r in readline? 53 | * 54 | * List of escape sequences used by this program, we do everything just 55 | * with three sequences. In order to be so cheap we may have some 56 | * flickering effect with some slow terminal, but the lesser sequences 57 | * the more compatible. 58 | * 59 | * EL (Erase Line) 60 | * Sequence: ESC [ n K 61 | * Effect: if n is 0 or missing, clear from cursor to end of line 62 | * Effect: if n is 1, clear from beginning of line to cursor 63 | * Effect: if n is 2, clear entire line 64 | * 65 | * CUF (CUrsor Forward) 66 | * Sequence: ESC [ n C 67 | * Effect: moves cursor forward n chars 68 | * 69 | * CUB (CUrsor Backward) 70 | * Sequence: ESC [ n D 71 | * Effect: moves cursor backward n chars 72 | * 73 | * The following is used to get the terminal width if getting 74 | * the width with the TIOCGWINSZ ioctl fails 75 | * 76 | * DSR (Device Status Report) 77 | * Sequence: ESC [ 6 n 78 | * Effect: reports the current cusor position as ESC [ n ; m R 79 | * where n is the row and m is the column 80 | * 81 | * When multi line mode is enabled, we also use an additional escape 82 | * sequence. However multi line editing is disabled by default. 83 | * 84 | * CUU (Cursor Up) 85 | * Sequence: ESC [ n A 86 | * Effect: moves cursor up of n chars. 87 | * 88 | * CUD (Cursor Down) 89 | * Sequence: ESC [ n B 90 | * Effect: moves cursor down of n chars. 91 | * 92 | * When linenoiseClearScreen() is called, two additional escape sequences 93 | * are used in order to clear the screen and position the cursor at home 94 | * position. 95 | * 96 | * CUP (Cursor position) 97 | * Sequence: ESC [ H 98 | * Effect: moves the cursor to upper left corner 99 | * 100 | * ED (Erase display) 101 | * Sequence: ESC [ 2 J 102 | * Effect: clear the whole screen 103 | * 104 | */ 105 | 106 | #include 107 | #include 108 | #include 109 | #include 110 | #include 111 | #include 112 | #include 113 | #include 114 | #include 115 | #include 116 | #include 117 | #include 118 | #include "linenoise.h" 119 | 120 | #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 121 | #define LINENOISE_MAX_LINE 4096 122 | static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; 123 | static linenoiseCompletionCallback *completionCallback = NULL; 124 | static linenoiseHintsCallback *hintsCallback = NULL; 125 | static linenoiseFreeHintsCallback *freeHintsCallback = NULL; 126 | 127 | static struct termios orig_termios; /* In order to restore at exit.*/ 128 | static int maskmode = 0; /* Show "***" instead of input. For passwords. */ 129 | static int rawmode = 0; /* For atexit() function to check if restore is needed*/ 130 | static int mlmode = 0; /* Multi line mode. Default is single line. */ 131 | static int atexit_registered = 0; /* Register atexit just 1 time. */ 132 | static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; 133 | static int history_len = 0; 134 | static char **history = NULL; 135 | 136 | /* The linenoiseState structure represents the state during line editing. 137 | * We pass this state to functions implementing specific editing 138 | * functionalities. */ 139 | struct linenoiseState { 140 | int ifd; /* Terminal stdin file descriptor. */ 141 | int ofd; /* Terminal stdout file descriptor. */ 142 | char *buf; /* Edited line buffer. */ 143 | size_t buflen; /* Edited line buffer size. */ 144 | const char *prompt; /* Prompt to display. */ 145 | size_t plen; /* Prompt length. */ 146 | size_t pos; /* Current cursor position. */ 147 | size_t oldpos; /* Previous refresh cursor position. */ 148 | size_t len; /* Current edited line length. */ 149 | size_t cols; /* Number of columns in terminal. */ 150 | size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ 151 | int history_index; /* The history index we are currently editing. */ 152 | }; 153 | 154 | enum KEY_ACTION{ 155 | KEY_NULL = 0, /* NULL */ 156 | CTRL_A = 1, /* Ctrl+a */ 157 | CTRL_B = 2, /* Ctrl-b */ 158 | CTRL_C = 3, /* Ctrl-c */ 159 | CTRL_D = 4, /* Ctrl-d */ 160 | CTRL_E = 5, /* Ctrl-e */ 161 | CTRL_F = 6, /* Ctrl-f */ 162 | CTRL_H = 8, /* Ctrl-h */ 163 | TAB = 9, /* Tab */ 164 | CTRL_K = 11, /* Ctrl+k */ 165 | CTRL_L = 12, /* Ctrl+l */ 166 | ENTER = 13, /* Enter */ 167 | CTRL_N = 14, /* Ctrl-n */ 168 | CTRL_P = 16, /* Ctrl-p */ 169 | CTRL_T = 20, /* Ctrl-t */ 170 | CTRL_U = 21, /* Ctrl+u */ 171 | CTRL_W = 23, /* Ctrl+w */ 172 | ESC = 27, /* Escape */ 173 | BACKSPACE = 127 /* Backspace */ 174 | }; 175 | 176 | static void linenoiseAtExit(void); 177 | int linenoiseHistoryAdd(const char *line); 178 | static void refreshLine(struct linenoiseState *l); 179 | 180 | /* Debugging macro. */ 181 | #if 0 182 | FILE *lndebug_fp = NULL; 183 | #define lndebug(...) \ 184 | do { \ 185 | if (lndebug_fp == NULL) { \ 186 | lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ 187 | fprintf(lndebug_fp, \ 188 | "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ 189 | (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ 190 | (int)l->maxrows,old_rows); \ 191 | } \ 192 | fprintf(lndebug_fp, ", " __VA_ARGS__); \ 193 | fflush(lndebug_fp); \ 194 | } while (0) 195 | #else 196 | #define lndebug(fmt, ...) 197 | #endif 198 | 199 | static char *_strdup(const char *str) 200 | { 201 | size_t len = strlen(str) + 1; 202 | char *new = malloc(len); 203 | memcpy(new, str, len); 204 | return new; 205 | } 206 | 207 | static int _strcasecmp(const char *X, const char *Y) 208 | { 209 | while (*X) { 210 | if (tolower(*X) != tolower(*Y)) { 211 | break; 212 | } 213 | 214 | X++; 215 | Y++; 216 | } 217 | 218 | return *(const unsigned char*) X - *(const unsigned char*) Y; 219 | } 220 | 221 | /* ======================= Low level terminal handling ====================== */ 222 | 223 | /* Enable "mask mode". When it is enabled, instead of the input that 224 | * the user is typing, the terminal will just display a corresponding 225 | * number of asterisks, like "****". This is useful for passwords and other 226 | * secrets that should not be displayed. */ 227 | void linenoiseMaskModeEnable(void) { 228 | maskmode = 1; 229 | } 230 | 231 | /* Disable mask mode. */ 232 | void linenoiseMaskModeDisable(void) { 233 | maskmode = 0; 234 | } 235 | 236 | /* Set if to use or not the multi line mode. */ 237 | void linenoiseSetMultiLine(int ml) { 238 | mlmode = ml; 239 | } 240 | 241 | /* Return true if the terminal name is in the list of terminals we know are 242 | * not able to understand basic escape sequences. */ 243 | static int isUnsupportedTerm(void) { 244 | char *term = getenv("TERM"); 245 | int j; 246 | 247 | if (term == NULL) return 0; 248 | for (j = 0; unsupported_term[j]; j++) 249 | if (!_strcasecmp(term,unsupported_term[j])) return 1; 250 | return 0; 251 | } 252 | 253 | /* Raw mode: 1960 magic shit. */ 254 | static int enableRawMode(int fd) { 255 | struct termios raw; 256 | 257 | if (!isatty(STDIN_FILENO)) goto fatal; 258 | if (!atexit_registered) { 259 | atexit(linenoiseAtExit); 260 | atexit_registered = 1; 261 | } 262 | if (tcgetattr(fd,&orig_termios) == -1) goto fatal; 263 | 264 | raw = orig_termios; /* modify the original mode */ 265 | /* input modes: no break, no CR to NL, no parity check, no strip char, 266 | * no start/stop output control. */ 267 | raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 268 | /* output modes - disable post processing */ 269 | raw.c_oflag &= ~(OPOST); 270 | /* control modes - set 8 bit chars */ 271 | raw.c_cflag |= (CS8); 272 | /* local modes - choing off, canonical off, no extended functions, 273 | * no signal chars (^Z,^C) */ 274 | raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 275 | /* control chars - set return condition: min number of bytes and timer. 276 | * We want read to return every single byte, without timeout. */ 277 | raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ 278 | 279 | /* put terminal in raw mode after flushing */ 280 | if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; 281 | rawmode = 1; 282 | return 0; 283 | 284 | fatal: 285 | errno = ENOTTY; 286 | return -1; 287 | } 288 | 289 | static void disableRawMode(int fd) { 290 | /* Don't even check the return value as it's too late. */ 291 | if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) 292 | rawmode = 0; 293 | } 294 | 295 | /* Use the ESC [6n escape sequence to query the horizontal cursor position 296 | * and return it. On error -1 is returned, on success the position of the 297 | * cursor. */ 298 | static int getCursorPosition(int ifd, int ofd) { 299 | char buf[32]; 300 | int cols, rows; 301 | unsigned int i = 0; 302 | 303 | /* Report cursor location */ 304 | if (write(ofd, "\x1b[6n", 4) != 4) return -1; 305 | 306 | /* Read the response: ESC [ rows ; cols R */ 307 | while (i < sizeof(buf)-1) { 308 | if (read(ifd,buf+i,1) != 1) break; 309 | if (buf[i] == 'R') break; 310 | i++; 311 | } 312 | buf[i] = '\0'; 313 | 314 | /* Parse it. */ 315 | if (buf[0] != ESC || buf[1] != '[') return -1; 316 | if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; 317 | return cols; 318 | } 319 | 320 | /* Try to get the number of columns in the current terminal, or assume 80 321 | * if it fails. */ 322 | static int getColumns(int ifd, int ofd) { 323 | struct winsize ws; 324 | 325 | if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { 326 | /* ioctl() failed. Try to query the terminal itself. */ 327 | int start, cols; 328 | 329 | /* Get the initial position so we can restore it later. */ 330 | start = getCursorPosition(ifd,ofd); 331 | if (start == -1) goto failed; 332 | 333 | /* Go to right margin and get position. */ 334 | if (write(ofd,"\x1b[999C",6) != 6) goto failed; 335 | cols = getCursorPosition(ifd,ofd); 336 | if (cols == -1) goto failed; 337 | 338 | /* Restore position. */ 339 | if (cols > start) { 340 | char seq[32]; 341 | snprintf(seq,32,"\x1b[%dD",cols-start); 342 | if (write(ofd,seq,strlen(seq)) == -1) { 343 | /* Can't recover... */ 344 | } 345 | } 346 | return cols; 347 | } else { 348 | return ws.ws_col; 349 | } 350 | 351 | failed: 352 | return 80; 353 | } 354 | 355 | /* Clear the screen. Used to handle ctrl+l */ 356 | void linenoiseClearScreen(void) { 357 | if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { 358 | /* nothing to do, just to avoid warning. */ 359 | } 360 | } 361 | 362 | /* Beep, used for completion when there is nothing to complete or when all 363 | * the choices were already shown. */ 364 | static void linenoiseBeep(void) { 365 | fprintf(stderr, "\x7"); 366 | fflush(stderr); 367 | } 368 | 369 | /* ============================== Completion ================================ */ 370 | 371 | /* Free a list of completion option populated by linenoiseAddCompletion(). */ 372 | static void freeCompletions(linenoiseCompletions *lc) { 373 | size_t i; 374 | for (i = 0; i < lc->len; i++) 375 | free(lc->cvec[i]); 376 | if (lc->cvec != NULL) 377 | free(lc->cvec); 378 | } 379 | 380 | /* This is an helper function for linenoiseEdit() and is called when the 381 | * user types the key in order to complete the string currently in the 382 | * input. 383 | * 384 | * The state of the editing is encapsulated into the pointed linenoiseState 385 | * structure as described in the structure definition. */ 386 | static int completeLine(struct linenoiseState *ls) { 387 | linenoiseCompletions lc = { 0, NULL }; 388 | int nread, nwritten; 389 | char c = 0; 390 | 391 | completionCallback(ls->buf,&lc); 392 | if (lc.len == 0) { 393 | linenoiseBeep(); 394 | } else { 395 | size_t stop = 0, i = 0; 396 | 397 | while(!stop) { 398 | /* Show completion or original buffer */ 399 | if (i < lc.len) { 400 | struct linenoiseState saved = *ls; 401 | 402 | ls->len = ls->pos = strlen(lc.cvec[i]); 403 | ls->buf = lc.cvec[i]; 404 | refreshLine(ls); 405 | ls->len = saved.len; 406 | ls->pos = saved.pos; 407 | ls->buf = saved.buf; 408 | } else { 409 | refreshLine(ls); 410 | } 411 | 412 | nread = read(ls->ifd,&c,1); 413 | if (nread <= 0) { 414 | freeCompletions(&lc); 415 | return -1; 416 | } 417 | 418 | switch(c) { 419 | case 9: /* tab */ 420 | i = (i+1) % (lc.len+1); 421 | if (i == lc.len) linenoiseBeep(); 422 | break; 423 | case 27: /* escape */ 424 | /* Re-show original buffer */ 425 | if (i < lc.len) refreshLine(ls); 426 | stop = 1; 427 | break; 428 | default: 429 | /* Update buffer and return */ 430 | if (i < lc.len) { 431 | nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); 432 | ls->len = ls->pos = nwritten; 433 | } 434 | stop = 1; 435 | break; 436 | } 437 | } 438 | } 439 | 440 | freeCompletions(&lc); 441 | return c; /* Return last read character */ 442 | } 443 | 444 | /* Register a callback function to be called for tab-completion. */ 445 | void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { 446 | completionCallback = fn; 447 | } 448 | 449 | /* Register a hits function to be called to show hits to the user at the 450 | * right of the prompt. */ 451 | void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { 452 | hintsCallback = fn; 453 | } 454 | 455 | /* Register a function to free the hints returned by the hints callback 456 | * registered with linenoiseSetHintsCallback(). */ 457 | void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { 458 | freeHintsCallback = fn; 459 | } 460 | 461 | /* This function is used by the callback function registered by the user 462 | * in order to add completion options given the input string when the 463 | * user typed . See the example.c source code for a very easy to 464 | * understand example. */ 465 | void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { 466 | size_t len = strlen(str); 467 | char *copy, **cvec; 468 | 469 | copy = malloc(len+1); 470 | if (copy == NULL) return; 471 | memcpy(copy,str,len+1); 472 | cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); 473 | if (cvec == NULL) { 474 | free(copy); 475 | return; 476 | } 477 | lc->cvec = cvec; 478 | lc->cvec[lc->len++] = copy; 479 | } 480 | 481 | /* =========================== Line editing ================================= */ 482 | 483 | /* We define a very simple "append buffer" structure, that is an heap 484 | * allocated string where we can append to. This is useful in order to 485 | * write all the escape sequences in a buffer and flush them to the standard 486 | * output in a single call, to avoid flickering effects. */ 487 | struct abuf { 488 | char *b; 489 | int len; 490 | }; 491 | 492 | static void abInit(struct abuf *ab) { 493 | ab->b = NULL; 494 | ab->len = 0; 495 | } 496 | 497 | static void abAppend(struct abuf *ab, const char *s, int len) { 498 | char *new = realloc(ab->b,ab->len+len); 499 | 500 | if (new == NULL) return; 501 | memcpy(new+ab->len,s,len); 502 | ab->b = new; 503 | ab->len += len; 504 | } 505 | 506 | static void abFree(struct abuf *ab) { 507 | free(ab->b); 508 | } 509 | 510 | /* Helper of refreshSingleLine() and refreshMultiLine() to show hints 511 | * to the right of the prompt. */ 512 | static void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { 513 | char seq[64]; 514 | if (hintsCallback && plen+l->len < l->cols) { 515 | int color = -1, bold = 0; 516 | char *hint = hintsCallback(l->buf,&color,&bold); 517 | if (hint) { 518 | int hintlen = strlen(hint); 519 | int hintmaxlen = l->cols-(plen+l->len); 520 | if (hintlen > hintmaxlen) hintlen = hintmaxlen; 521 | if (bold == 1 && color == -1) color = 37; 522 | if (color != -1 || bold != 0) 523 | snprintf(seq,64,"\033[%d;%d;49m",bold,color); 524 | else 525 | seq[0] = '\0'; 526 | abAppend(ab,seq,strlen(seq)); 527 | abAppend(ab,hint,hintlen); 528 | if (color != -1 || bold != 0) 529 | abAppend(ab,"\033[0m",4); 530 | /* Call the function to free the hint returned. */ 531 | if (freeHintsCallback) freeHintsCallback(hint); 532 | } 533 | } 534 | } 535 | 536 | /* Single line low level line refresh. 537 | * 538 | * Rewrite the currently edited line accordingly to the buffer content, 539 | * cursor position, and number of columns of the terminal. */ 540 | static void refreshSingleLine(struct linenoiseState *l) { 541 | char seq[64]; 542 | size_t plen = strlen(l->prompt); 543 | int fd = l->ofd; 544 | char *buf = l->buf; 545 | size_t len = l->len; 546 | size_t pos = l->pos; 547 | struct abuf ab; 548 | 549 | while((plen+pos) >= l->cols) { 550 | buf++; 551 | len--; 552 | pos--; 553 | } 554 | while (plen+len > l->cols) { 555 | len--; 556 | } 557 | 558 | abInit(&ab); 559 | /* Cursor to left edge */ 560 | snprintf(seq,64,"\r"); 561 | abAppend(&ab,seq,strlen(seq)); 562 | /* Write the prompt and the current buffer content */ 563 | abAppend(&ab,l->prompt,strlen(l->prompt)); 564 | if (maskmode == 1) { 565 | while (len--) abAppend(&ab,"*",1); 566 | } else { 567 | abAppend(&ab,buf,len); 568 | } 569 | /* Show hits if any. */ 570 | refreshShowHints(&ab,l,plen); 571 | /* Erase to right */ 572 | snprintf(seq,64,"\x1b[0K"); 573 | abAppend(&ab,seq,strlen(seq)); 574 | /* Move cursor to original position. */ 575 | snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); 576 | abAppend(&ab,seq,strlen(seq)); 577 | if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ 578 | abFree(&ab); 579 | } 580 | 581 | /* Multi line low level line refresh. 582 | * 583 | * Rewrite the currently edited line accordingly to the buffer content, 584 | * cursor position, and number of columns of the terminal. */ 585 | static void refreshMultiLine(struct linenoiseState *l) { 586 | char seq[64]; 587 | int plen = strlen(l->prompt); 588 | int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ 589 | int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ 590 | int rpos2; /* rpos after refresh. */ 591 | int col; /* colum position, zero-based. */ 592 | int old_rows = l->maxrows; 593 | int fd = l->ofd, j; 594 | struct abuf ab; 595 | 596 | /* Update maxrows if needed. */ 597 | if (rows > (int)l->maxrows) l->maxrows = rows; 598 | 599 | /* First step: clear all the lines used before. To do so start by 600 | * going to the last row. */ 601 | abInit(&ab); 602 | if (old_rows-rpos > 0) { 603 | lndebug("go down %d", old_rows-rpos); 604 | snprintf(seq,64,"\x1b[%dB", old_rows-rpos); 605 | abAppend(&ab,seq,strlen(seq)); 606 | } 607 | 608 | /* Now for every row clear it, go up. */ 609 | for (j = 0; j < old_rows-1; j++) { 610 | lndebug("clear+up", NULL); 611 | snprintf(seq,64,"\r\x1b[0K\x1b[1A"); 612 | abAppend(&ab,seq,strlen(seq)); 613 | } 614 | 615 | /* Clean the top line. */ 616 | lndebug("clear", NULL); 617 | snprintf(seq,64,"\r\x1b[0K"); 618 | abAppend(&ab,seq,strlen(seq)); 619 | 620 | /* Write the prompt and the current buffer content */ 621 | abAppend(&ab,l->prompt,strlen(l->prompt)); 622 | if (maskmode == 1) { 623 | unsigned int i; 624 | for (i = 0; i < l->len; i++) abAppend(&ab,"*",1); 625 | } else { 626 | abAppend(&ab,l->buf,l->len); 627 | } 628 | 629 | /* Show hits if any. */ 630 | refreshShowHints(&ab,l,plen); 631 | 632 | /* If we are at the very end of the screen with our prompt, we need to 633 | * emit a newline and move the prompt to the first column. */ 634 | if (l->pos && 635 | l->pos == l->len && 636 | (l->pos+plen) % l->cols == 0) 637 | { 638 | lndebug("", NULL); 639 | abAppend(&ab,"\n",1); 640 | snprintf(seq,64,"\r"); 641 | abAppend(&ab,seq,strlen(seq)); 642 | rows++; 643 | if (rows > (int)l->maxrows) l->maxrows = rows; 644 | } 645 | 646 | /* Move cursor to right position. */ 647 | rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ 648 | lndebug("rpos2 %d", rpos2); 649 | 650 | /* Go up till we reach the expected positon. */ 651 | if (rows-rpos2 > 0) { 652 | lndebug("go-up %d", rows-rpos2); 653 | snprintf(seq,64,"\x1b[%dA", rows-rpos2); 654 | abAppend(&ab,seq,strlen(seq)); 655 | } 656 | 657 | /* Set column. */ 658 | col = (plen+(int)l->pos) % (int)l->cols; 659 | lndebug("set col %d", 1+col); 660 | if (col) 661 | snprintf(seq,64,"\r\x1b[%dC", col); 662 | else 663 | snprintf(seq,64,"\r"); 664 | abAppend(&ab,seq,strlen(seq)); 665 | 666 | lndebug("\n", NULL); 667 | l->oldpos = l->pos; 668 | 669 | if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ 670 | abFree(&ab); 671 | } 672 | 673 | /* Calls the two low level functions refreshSingleLine() or 674 | * refreshMultiLine() according to the selected mode. */ 675 | static void refreshLine(struct linenoiseState *l) { 676 | if (mlmode) 677 | refreshMultiLine(l); 678 | else 679 | refreshSingleLine(l); 680 | } 681 | 682 | /* Insert the character 'c' at cursor current position. 683 | * 684 | * On error writing to the terminal -1 is returned, otherwise 0. */ 685 | static int linenoiseEditInsert(struct linenoiseState *l, char c) { 686 | if (l->len < l->buflen) { 687 | if (l->len == l->pos) { 688 | l->buf[l->pos] = c; 689 | l->pos++; 690 | l->len++; 691 | l->buf[l->len] = '\0'; 692 | if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { 693 | /* Avoid a full update of the line in the 694 | * trivial case. */ 695 | char d = (maskmode==1) ? '*' : c; 696 | if (write(l->ofd,&d,1) == -1) return -1; 697 | } else { 698 | refreshLine(l); 699 | } 700 | } else { 701 | memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); 702 | l->buf[l->pos] = c; 703 | l->len++; 704 | l->pos++; 705 | l->buf[l->len] = '\0'; 706 | refreshLine(l); 707 | } 708 | } 709 | return 0; 710 | } 711 | 712 | /* Move cursor on the left. */ 713 | static void linenoiseEditMoveLeft(struct linenoiseState *l) { 714 | if (l->pos > 0) { 715 | l->pos--; 716 | refreshLine(l); 717 | } 718 | } 719 | 720 | /* Move cursor on the right. */ 721 | static void linenoiseEditMoveRight(struct linenoiseState *l) { 722 | if (l->pos != l->len) { 723 | l->pos++; 724 | refreshLine(l); 725 | } 726 | } 727 | 728 | /* Move cursor to the start of the line. */ 729 | static void linenoiseEditMoveHome(struct linenoiseState *l) { 730 | if (l->pos != 0) { 731 | l->pos = 0; 732 | refreshLine(l); 733 | } 734 | } 735 | 736 | /* Move cursor to the end of the line. */ 737 | static void linenoiseEditMoveEnd(struct linenoiseState *l) { 738 | if (l->pos != l->len) { 739 | l->pos = l->len; 740 | refreshLine(l); 741 | } 742 | } 743 | 744 | /* Substitute the currently edited line with the next or previous history 745 | * entry as specified by 'dir'. */ 746 | #define LINENOISE_HISTORY_NEXT 0 747 | #define LINENOISE_HISTORY_PREV 1 748 | static void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { 749 | if (history_len > 1) { 750 | /* Update the current history entry before to 751 | * overwrite it with the next one. */ 752 | free(history[history_len - 1 - l->history_index]); 753 | history[history_len - 1 - l->history_index] = _strdup(l->buf); 754 | /* Show the new entry */ 755 | l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; 756 | if (l->history_index < 0) { 757 | l->history_index = 0; 758 | return; 759 | } else if (l->history_index >= history_len) { 760 | l->history_index = history_len-1; 761 | return; 762 | } 763 | strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); 764 | l->buf[l->buflen-1] = '\0'; 765 | l->len = l->pos = strlen(l->buf); 766 | refreshLine(l); 767 | } 768 | } 769 | 770 | /* Delete the character at the right of the cursor without altering the cursor 771 | * position. Basically this is what happens with the "Delete" keyboard key. */ 772 | static void linenoiseEditDelete(struct linenoiseState *l) { 773 | if (l->len > 0 && l->pos < l->len) { 774 | memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); 775 | l->len--; 776 | l->buf[l->len] = '\0'; 777 | refreshLine(l); 778 | } 779 | } 780 | 781 | /* Backspace implementation. */ 782 | static void linenoiseEditBackspace(struct linenoiseState *l) { 783 | if (l->pos > 0 && l->len > 0) { 784 | memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); 785 | l->pos--; 786 | l->len--; 787 | l->buf[l->len] = '\0'; 788 | refreshLine(l); 789 | } 790 | } 791 | 792 | /* Delete the previosu word, maintaining the cursor at the start of the 793 | * current word. */ 794 | static void linenoiseEditDeletePrevWord(struct linenoiseState *l) { 795 | size_t old_pos = l->pos; 796 | size_t diff; 797 | 798 | while (l->pos > 0 && l->buf[l->pos-1] == ' ') 799 | l->pos--; 800 | while (l->pos > 0 && l->buf[l->pos-1] != ' ') 801 | l->pos--; 802 | diff = old_pos - l->pos; 803 | memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); 804 | l->len -= diff; 805 | refreshLine(l); 806 | } 807 | 808 | /* This function is the core of the line editing capability of linenoise. 809 | * It expects 'fd' to be already in "raw mode" so that every key pressed 810 | * will be returned ASAP to read(). 811 | * 812 | * The resulting string is put into 'buf' when the user type enter, or 813 | * when ctrl+d is typed. 814 | * 815 | * The function returns the length of the current buffer. */ 816 | static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) 817 | { 818 | struct linenoiseState l; 819 | 820 | /* Populate the linenoise state that we pass to functions implementing 821 | * specific editing functionalities. */ 822 | l.ifd = stdin_fd; 823 | l.ofd = stdout_fd; 824 | l.buf = buf; 825 | l.buflen = buflen; 826 | l.prompt = prompt; 827 | l.plen = strlen(prompt); 828 | l.oldpos = l.pos = 0; 829 | l.len = 0; 830 | l.cols = getColumns(stdin_fd, stdout_fd); 831 | l.maxrows = 0; 832 | l.history_index = 0; 833 | 834 | /* Buffer starts empty. */ 835 | l.buf[0] = '\0'; 836 | l.buflen--; /* Make sure there is always space for the nulterm */ 837 | 838 | /* The latest history entry is always our current buffer, that 839 | * initially is just an empty string. */ 840 | linenoiseHistoryAdd(""); 841 | 842 | if (write(l.ofd,prompt,l.plen) == -1) return -1; 843 | while(1) { 844 | char c; 845 | int nread; 846 | char seq[3]; 847 | 848 | nread = read(l.ifd,&c,1); 849 | if (nread <= 0) return l.len; 850 | 851 | /* Only autocomplete when the callback is set. It returns < 0 when 852 | * there was an error reading from fd. Otherwise it will return the 853 | * character that should be handled next. */ 854 | if (c == 9 && completionCallback != NULL) { 855 | c = completeLine(&l); 856 | /* Return on errors */ 857 | if (c < 0) return l.len; 858 | /* Read next character when 0 */ 859 | if (c == 0) continue; 860 | } 861 | 862 | switch(c) { 863 | case ENTER: /* enter */ 864 | history_len--; 865 | free(history[history_len]); 866 | if (mlmode) linenoiseEditMoveEnd(&l); 867 | if (hintsCallback) { 868 | /* Force a refresh without hints to leave the previous 869 | * line as the user typed it after a newline. */ 870 | linenoiseHintsCallback *hc = hintsCallback; 871 | hintsCallback = NULL; 872 | refreshLine(&l); 873 | hintsCallback = hc; 874 | } 875 | return (int)l.len; 876 | case CTRL_C: /* ctrl-c */ 877 | errno = EAGAIN; 878 | return -1; 879 | case BACKSPACE: /* backspace */ 880 | case 8: /* ctrl-h */ 881 | linenoiseEditBackspace(&l); 882 | break; 883 | case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the 884 | line is empty, act as end-of-file. */ 885 | if (l.len > 0) { 886 | linenoiseEditDelete(&l); 887 | } else { 888 | history_len--; 889 | free(history[history_len]); 890 | return -1; 891 | } 892 | break; 893 | case CTRL_T: /* ctrl-t, swaps current character with previous. */ 894 | if (l.pos > 0 && l.pos < l.len) { 895 | int aux = buf[l.pos-1]; 896 | buf[l.pos-1] = buf[l.pos]; 897 | buf[l.pos] = aux; 898 | if (l.pos != l.len-1) l.pos++; 899 | refreshLine(&l); 900 | } 901 | break; 902 | case CTRL_B: /* ctrl-b */ 903 | linenoiseEditMoveLeft(&l); 904 | break; 905 | case CTRL_F: /* ctrl-f */ 906 | linenoiseEditMoveRight(&l); 907 | break; 908 | case CTRL_P: /* ctrl-p */ 909 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); 910 | break; 911 | case CTRL_N: /* ctrl-n */ 912 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); 913 | break; 914 | case ESC: /* escape sequence */ 915 | /* Read the next two bytes representing the escape sequence. 916 | * Use two calls to handle slow terminals returning the two 917 | * chars at different times. */ 918 | if (read(l.ifd,seq,1) == -1) break; 919 | if (read(l.ifd,seq+1,1) == -1) break; 920 | 921 | /* ESC [ sequences. */ 922 | if (seq[0] == '[') { 923 | if (seq[1] >= '0' && seq[1] <= '9') { 924 | /* Extended escape, read additional byte. */ 925 | if (read(l.ifd,seq+2,1) == -1) break; 926 | if (seq[2] == '~') { 927 | switch(seq[1]) { 928 | case '3': /* Delete key. */ 929 | linenoiseEditDelete(&l); 930 | break; 931 | } 932 | } 933 | } else { 934 | switch(seq[1]) { 935 | case 'A': /* Up */ 936 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); 937 | break; 938 | case 'B': /* Down */ 939 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); 940 | break; 941 | case 'C': /* Right */ 942 | linenoiseEditMoveRight(&l); 943 | break; 944 | case 'D': /* Left */ 945 | linenoiseEditMoveLeft(&l); 946 | break; 947 | case 'H': /* Home */ 948 | linenoiseEditMoveHome(&l); 949 | break; 950 | case 'F': /* End*/ 951 | linenoiseEditMoveEnd(&l); 952 | break; 953 | } 954 | } 955 | } 956 | 957 | /* ESC O sequences. */ 958 | else if (seq[0] == 'O') { 959 | switch(seq[1]) { 960 | case 'H': /* Home */ 961 | linenoiseEditMoveHome(&l); 962 | break; 963 | case 'F': /* End*/ 964 | linenoiseEditMoveEnd(&l); 965 | break; 966 | } 967 | } 968 | break; 969 | default: 970 | if (linenoiseEditInsert(&l,c)) return -1; 971 | break; 972 | case CTRL_U: /* Ctrl+u, delete the whole line. */ 973 | buf[0] = '\0'; 974 | l.pos = l.len = 0; 975 | refreshLine(&l); 976 | break; 977 | case CTRL_K: /* Ctrl+k, delete from current to end of line. */ 978 | buf[l.pos] = '\0'; 979 | l.len = l.pos; 980 | refreshLine(&l); 981 | break; 982 | case CTRL_A: /* Ctrl+a, go to the start of the line */ 983 | linenoiseEditMoveHome(&l); 984 | break; 985 | case CTRL_E: /* ctrl+e, go to the end of the line */ 986 | linenoiseEditMoveEnd(&l); 987 | break; 988 | case CTRL_L: /* ctrl+l, clear screen */ 989 | linenoiseClearScreen(); 990 | refreshLine(&l); 991 | break; 992 | case CTRL_W: /* ctrl+w, delete previous word */ 993 | linenoiseEditDeletePrevWord(&l); 994 | break; 995 | } 996 | } 997 | return l.len; 998 | } 999 | 1000 | /* This special mode is used by linenoise in order to print scan codes 1001 | * on screen for debugging / development purposes. It is implemented 1002 | * by the linenoise_example program using the --keycodes option. */ 1003 | void linenoisePrintKeyCodes(void) { 1004 | char quit[4]; 1005 | 1006 | printf("Linenoise key codes debugging mode.\n" 1007 | "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); 1008 | if (enableRawMode(STDIN_FILENO) == -1) return; 1009 | memset(quit,' ',4); 1010 | while(1) { 1011 | char c; 1012 | int nread; 1013 | 1014 | nread = read(STDIN_FILENO,&c,1); 1015 | if (nread <= 0) continue; 1016 | memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ 1017 | quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ 1018 | if (memcmp(quit,"quit",sizeof(quit)) == 0) break; 1019 | 1020 | printf("'%c' %02x (%d) (type quit to exit)\n", 1021 | isprint(c) ? c : '?', (int)c, (int)c); 1022 | printf("\r"); /* Go left edge manually, we are in raw mode. */ 1023 | fflush(stdout); 1024 | } 1025 | disableRawMode(STDIN_FILENO); 1026 | } 1027 | 1028 | /* This function calls the line editing function linenoiseEdit() using 1029 | * the STDIN file descriptor set in raw mode. */ 1030 | static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { 1031 | int count; 1032 | 1033 | if (buflen == 0) { 1034 | errno = EINVAL; 1035 | return -1; 1036 | } 1037 | 1038 | if (enableRawMode(STDIN_FILENO) == -1) return -1; 1039 | count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); 1040 | disableRawMode(STDIN_FILENO); 1041 | printf("\n"); 1042 | return count; 1043 | } 1044 | 1045 | /* This function is called when linenoise() is called with the standard 1046 | * input file descriptor not attached to a TTY. So for example when the 1047 | * program using linenoise is called in pipe or with a file redirected 1048 | * to its standard input. In this case, we want to be able to return the 1049 | * line regardless of its length (by default we are limited to 4k). */ 1050 | static char *linenoiseNoTTY(void) { 1051 | char *line = NULL; 1052 | size_t len = 0, maxlen = 0; 1053 | 1054 | while(1) { 1055 | if (len == maxlen) { 1056 | if (maxlen == 0) maxlen = 16; 1057 | maxlen *= 2; 1058 | char *oldval = line; 1059 | line = realloc(line,maxlen); 1060 | if (line == NULL) { 1061 | if (oldval) free(oldval); 1062 | return NULL; 1063 | } 1064 | } 1065 | int c = fgetc(stdin); 1066 | if (c == EOF || c == '\n') { 1067 | if (c == EOF && len == 0) { 1068 | free(line); 1069 | return NULL; 1070 | } else { 1071 | line[len] = '\0'; 1072 | return line; 1073 | } 1074 | } else { 1075 | line[len] = c; 1076 | len++; 1077 | } 1078 | } 1079 | } 1080 | 1081 | /* The high level function that is the main API of the linenoise library. 1082 | * This function checks if the terminal has basic capabilities, just checking 1083 | * for a blacklist of stupid terminals, and later either calls the line 1084 | * editing function or uses dummy fgets() so that you will be able to type 1085 | * something even in the most desperate of the conditions. */ 1086 | char *linenoise(const char *prompt) { 1087 | char buf[LINENOISE_MAX_LINE]; 1088 | int count; 1089 | 1090 | if (!isatty(STDIN_FILENO)) { 1091 | /* Not a tty: read from file / pipe. In this mode we don't want any 1092 | * limit to the line size, so we call a function to handle that. */ 1093 | return linenoiseNoTTY(); 1094 | } else if (isUnsupportedTerm()) { 1095 | size_t len; 1096 | 1097 | printf("%s",prompt); 1098 | fflush(stdout); 1099 | if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; 1100 | len = strlen(buf); 1101 | while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { 1102 | len--; 1103 | buf[len] = '\0'; 1104 | } 1105 | return _strdup(buf); 1106 | } else { 1107 | count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); 1108 | if (count == -1) return NULL; 1109 | return _strdup(buf); 1110 | } 1111 | } 1112 | 1113 | /* This is just a wrapper the user may want to call in order to make sure 1114 | * the linenoise returned buffer is freed with the same allocator it was 1115 | * created with. Useful when the main program is using an alternative 1116 | * allocator. */ 1117 | void linenoiseFree(void *ptr) { 1118 | free(ptr); 1119 | } 1120 | 1121 | /* ================================ History ================================= */ 1122 | 1123 | /* Free the history, but does not reset it. Only used when we have to 1124 | * exit() to avoid memory leaks are reported by valgrind & co. */ 1125 | static void freeHistory(void) { 1126 | if (history) { 1127 | int j; 1128 | 1129 | for (j = 0; j < history_len; j++) 1130 | free(history[j]); 1131 | free(history); 1132 | } 1133 | } 1134 | 1135 | /* At exit we'll try to fix the terminal to the initial conditions. */ 1136 | static void linenoiseAtExit(void) { 1137 | disableRawMode(STDIN_FILENO); 1138 | freeHistory(); 1139 | } 1140 | 1141 | /* This is the API call to add a new entry in the linenoise history. 1142 | * It uses a fixed array of char pointers that are shifted (memmoved) 1143 | * when the history max length is reached in order to remove the older 1144 | * entry and make room for the new one, so it is not exactly suitable for huge 1145 | * histories, but will work well for a few hundred of entries. 1146 | * 1147 | * Using a circular buffer is smarter, but a bit more complex to handle. */ 1148 | int linenoiseHistoryAdd(const char *line) { 1149 | char *linecopy; 1150 | 1151 | if (history_max_len == 0) return 0; 1152 | 1153 | /* Initialization on first call. */ 1154 | if (history == NULL) { 1155 | history = malloc(sizeof(char*)*history_max_len); 1156 | if (history == NULL) return 0; 1157 | memset(history,0,(sizeof(char*)*history_max_len)); 1158 | } 1159 | 1160 | /* Don't add duplicated lines. */ 1161 | if (history_len && !strcmp(history[history_len-1], line)) return 0; 1162 | 1163 | /* Add an heap allocated copy of the line in the history. 1164 | * If we reached the max length, remove the older line. */ 1165 | linecopy = _strdup(line); 1166 | if (!linecopy) return 0; 1167 | if (history_len == history_max_len) { 1168 | free(history[0]); 1169 | memmove(history,history+1,sizeof(char*)*(history_max_len-1)); 1170 | history_len--; 1171 | } 1172 | history[history_len] = linecopy; 1173 | history_len++; 1174 | return 1; 1175 | } 1176 | 1177 | /* Set the maximum length for the history. This function can be called even 1178 | * if there is already some history, the function will make sure to retain 1179 | * just the latest 'len' elements if the new history length value is smaller 1180 | * than the amount of items already inside the history. */ 1181 | int linenoiseHistorySetMaxLen(int len) { 1182 | char **new; 1183 | 1184 | if (len < 1) return 0; 1185 | if (history) { 1186 | int tocopy = history_len; 1187 | 1188 | new = malloc(sizeof(char*)*len); 1189 | if (new == NULL) return 0; 1190 | 1191 | /* If we can't copy everything, free the elements we'll not use. */ 1192 | if (len < tocopy) { 1193 | int j; 1194 | 1195 | for (j = 0; j < tocopy-len; j++) free(history[j]); 1196 | tocopy = len; 1197 | } 1198 | memset(new,0,sizeof(char*)*len); 1199 | memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); 1200 | free(history); 1201 | history = new; 1202 | } 1203 | history_max_len = len; 1204 | if (history_len > history_max_len) 1205 | history_len = history_max_len; 1206 | return 1; 1207 | } 1208 | 1209 | /* Save the history in the specified file. On success 0 is returned 1210 | * otherwise -1 is returned. */ 1211 | int linenoiseHistorySave(const char *filename) { 1212 | mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); 1213 | FILE *fp; 1214 | int j; 1215 | 1216 | fp = fopen(filename,"w"); 1217 | umask(old_umask); 1218 | if (fp == NULL) return -1; 1219 | chmod(filename,S_IRUSR|S_IWUSR); 1220 | for (j = 0; j < history_len; j++) 1221 | fprintf(fp,"%s\n",history[j]); 1222 | fclose(fp); 1223 | return 0; 1224 | } 1225 | 1226 | /* Load the history from the specified file. If the file does not exist 1227 | * zero is returned and no operation is performed. 1228 | * 1229 | * If the file exists and the operation succeeded 0 is returned, otherwise 1230 | * on error -1 is returned. */ 1231 | int linenoiseHistoryLoad(const char *filename) { 1232 | FILE *fp = fopen(filename,"r"); 1233 | char buf[LINENOISE_MAX_LINE]; 1234 | 1235 | if (fp == NULL) return -1; 1236 | 1237 | while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { 1238 | char *p; 1239 | 1240 | p = strchr(buf,'\r'); 1241 | if (!p) p = strchr(buf,'\n'); 1242 | if (p) *p = '\0'; 1243 | linenoiseHistoryAdd(buf); 1244 | } 1245 | fclose(fp); 1246 | return 0; 1247 | } 1248 | -------------------------------------------------------------------------------- /lib/linenoise.h: -------------------------------------------------------------------------------- 1 | /* linenoise.h -- VERSION 1.0 2 | * 3 | * Guerrilla line editing library against the idea that a line editing lib 4 | * needs to be 20,000 lines of C code. 5 | * 6 | * See linenoise.c for more information. 7 | * 8 | * ------------------------------------------------------------------------ 9 | * 10 | * Copyright (c) 2010-2014, Salvatore Sanfilippo 11 | * Copyright (c) 2010-2013, Pieter Noordhuis 12 | * 13 | * All rights reserved. 14 | * 15 | * Redistribution and use in source and binary forms, with or without 16 | * modification, are permitted provided that the following conditions are 17 | * met: 18 | * 19 | * * Redistributions of source code must retain the above copyright 20 | * notice, this list of conditions and the following disclaimer. 21 | * 22 | * * Redistributions in binary form must reproduce the above copyright 23 | * notice, this list of conditions and the following disclaimer in the 24 | * documentation and/or other materials provided with the distribution. 25 | * 26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 29 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 30 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 32 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 33 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 34 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 35 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | #ifndef __LINENOISE_H 40 | #define __LINENOISE_H 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif 45 | 46 | typedef struct linenoiseCompletions { 47 | size_t len; 48 | char **cvec; 49 | } linenoiseCompletions; 50 | 51 | typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); 52 | typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold); 53 | typedef void(linenoiseFreeHintsCallback)(void *); 54 | void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); 55 | void linenoiseSetHintsCallback(linenoiseHintsCallback *); 56 | void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); 57 | void linenoiseAddCompletion(linenoiseCompletions *, const char *); 58 | 59 | char *linenoise(const char *prompt); 60 | void linenoiseFree(void *ptr); 61 | int linenoiseHistoryAdd(const char *line); 62 | int linenoiseHistorySetMaxLen(int len); 63 | int linenoiseHistorySave(const char *filename); 64 | int linenoiseHistoryLoad(const char *filename); 65 | void linenoiseClearScreen(void); 66 | void linenoiseSetMultiLine(int ml); 67 | void linenoisePrintKeyCodes(void); 68 | void linenoiseMaskModeEnable(void); 69 | void linenoiseMaskModeDisable(void); 70 | 71 | #ifdef __cplusplus 72 | } 73 | #endif 74 | 75 | #endif /* __LINENOISE_H */ 76 | -------------------------------------------------------------------------------- /quich.1: -------------------------------------------------------------------------------- 1 | .\" Manpage for quich. 2 | 3 | .TH QUICH 1 2021-09-23 "1.0" quich 4 | 5 | .SH NAME 6 | quich \- Just an advanced terminal calculator. 7 | 8 | .SH SYNOPSIS 9 | .B quich 10 | [\fBOPTIONS\fR] 11 | 12 | .SH DESCRIPTION 13 | .B Quich 14 | is a compact, fast, powerful and useful calculator for your terminal with numerous features, supporting Linux, Mac and Windows, written in ANSI C. 15 | You can enter in interactive mode by calling Quich in your terminal without an operation. 16 | 17 | .SS Functions 18 | .B Syntax: 19 | \fIfunc(operand)\fR 20 | .TP 21 | .BR sqrt 22 | Square root 23 | .TP 24 | .BR abs 25 | Absolute value (positive) 26 | .TP 27 | .BR log 28 | Natural logarithm 29 | .TP 30 | .BR sin ", " cos ", " tan 31 | Sine, Cosine and Tangent trigonometric functions 32 | .TP 33 | .BR asin ", " acos ", " atan 34 | Arc sine, Arc cosine and Arc tangent trigonometric functions 35 | .TP 36 | .BR rand 37 | Random number between 0 and 1 38 | .TP 39 | .BR round 40 | Round to the nearest integer value 41 | .TP 42 | .BR floor 43 | Round down 44 | .TP 45 | .BR ceil 46 | Round up 47 | 48 | .SH OPTIONS 49 | .TP 50 | .BR \-d ", " \-\-degree 51 | Manage the given angles in degrees 52 | .TP 53 | .BR \-f ", " \-\-format\ \fI[%s]\fR 54 | The format to display the result 55 | .TP 56 | .BR \-h ", " \-\-help 57 | Get help and information 58 | .TP 59 | .BR \-i ", " \-\-interactive 60 | Force interactive mode 61 | .TP 62 | .BR \-p ", " \-\-precision\ \fI[%i]\fR 63 | The number of decimals to be used for the internal numbers 64 | .TP 65 | .BR \-r ", " \-\-round\ \fI[%i]\fR 66 | The number of decimals to round the result 67 | .TP 68 | .BR \-t ", " \-\-thousands 69 | Display the result with thousands separators 70 | .TP 71 | .BR \-vvv ", " \-\-verbose 72 | Display the result with details 73 | .TP 74 | .BR \-v ", " \-\-version 75 | Show the application version 76 | 77 | .SH EXAMPLES 78 | 79 | quich 5+3 80 | 8 81 | 82 | quich "a=20;a+1" 83 | 21 84 | 85 | quich "5+(cos( 2 )-2)^2" 86 | 10.8377655357568 87 | 88 | quich "5+(cos( 2 )-2)^2" -p 2 89 | 10.86 90 | 91 | quich 1234567+1 -t 92 | 1,234,568 93 | 94 | quich 1gb+1mb 95 | 1049600 96 | 97 | quich 12345 -f '%.1g' 98 | 1e+04 99 | 100 | quich 5+PI -vvv 101 | Tokens > '5' '+' 'PI' 102 | Posfix > 5 PI + 103 | Result > 8.14159265358979 104 | -------------------------------------------------------------------------------- /src/helper.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "helper.h" 8 | 9 | 10 | static void prependChar(char **str, char ch) 11 | { 12 | size_t len = strlen(*str) + 2; 13 | char *tmp = strDup(*str); 14 | 15 | free(*str); 16 | *str = malloc_(len); 17 | *str[0] = '\0'; 18 | snprintf(*str, len, "%c%s", ch, tmp); 19 | 20 | free(tmp); 21 | } 22 | 23 | 24 | void *malloc_(size_t size) 25 | { 26 | void *alloc_mem = malloc(size); 27 | 28 | if (!alloc_mem) { 29 | printf(ERROR_MEM_MSG); 30 | exit(-1); 31 | } 32 | 33 | return alloc_mem; 34 | } 35 | 36 | 37 | char *strDup(const char *src) 38 | { 39 | char *new; 40 | size_t len; 41 | 42 | if (src == NULL) { 43 | return NULL; 44 | } 45 | 46 | len = strlen(src) + 1; 47 | new = malloc_(len); 48 | snprintf(new, len, "%s", src); 49 | 50 | return new; 51 | } 52 | 53 | 54 | int strncpy_(char *dest, const char *src, size_t n) 55 | { 56 | snprintf(dest, n, "%s", src); 57 | 58 | return n; 59 | } 60 | 61 | 62 | double getRand(void) 63 | { 64 | srand(time(NULL)); 65 | return (double) abs(rand() * 100) / RAND_MAX; 66 | } 67 | 68 | 69 | double round_(double n, size_t digits) 70 | { 71 | double decimals = pow(10, digits); 72 | n *= decimals; 73 | n = (n >= floor(n) + 0.5f) ? 74 | ceil(n) : 75 | floor(n); 76 | 77 | return n / decimals; 78 | } 79 | 80 | 81 | double strToDouble(const char *str) 82 | { 83 | if (str == NULL) { 84 | return 0.0; 85 | } 86 | 87 | return strtod(str, NULL); 88 | } 89 | 90 | 91 | void addThousandsSep(char *str) 92 | { 93 | int i, dot_index, char_n = 0; 94 | size_t len; 95 | size_t new_len; 96 | char *dot, *tmp = NULL; 97 | 98 | if (str == NULL) { 99 | return; 100 | } 101 | 102 | len = strlen(str); 103 | new_len = len; 104 | dot = strchr(str, '.'); 105 | 106 | if (dot != NULL) { 107 | dot_index = dot - str; 108 | tmp = malloc_(len - dot_index + 1); 109 | strncpy_(tmp, str + dot_index, len - dot_index + 1); 110 | } else { 111 | dot_index = len; 112 | tmp = malloc_(1); 113 | tmp[0] = '\0'; 114 | } 115 | 116 | for (i = dot_index - 1; i >= 0; i--) { 117 | if (i >= 0 && char_n > 0 && char_n % 3 == 0) { 118 | prependChar(&tmp, ','); 119 | new_len++; 120 | } 121 | 122 | prependChar(&tmp, str[i]); 123 | char_n++; 124 | } 125 | 126 | strncpy_(str, tmp, new_len + 1); 127 | free(tmp); 128 | } 129 | 130 | 131 | long int fact(long int n) 132 | { 133 | long int next; 134 | 135 | if (n == 1 || n == 0) { 136 | return 1; 137 | } 138 | 139 | next = (n < 0) ? n + 1 : n - 1; 140 | 141 | return n * fact(next); 142 | } 143 | 144 | 145 | void appendChar(char **str, char ch) 146 | { 147 | const size_t len = strlen(*str); 148 | *str = realloc(*str, len + 2); 149 | snprintf((*str) + len, 2, "%c", ch); 150 | } 151 | 152 | 153 | bool isEmpty(const char *str) 154 | { 155 | return str == NULL || !strcmp(str, ""); 156 | } 157 | -------------------------------------------------------------------------------- /src/helper.h: -------------------------------------------------------------------------------- 1 | #ifndef HELPER_H_ 2 | #define HELPER_H_ 3 | #define ERROR_MEM_MSG "Error: Could not allocate memory\n" 4 | 5 | #include 6 | 7 | #define BUFFER 32 8 | 9 | /** 10 | * The functions defined here are general helper functions 11 | * that aren't related to any specific module of this software. 12 | */ 13 | 14 | /** 15 | * Just a safer malloc function. 16 | * @param size the memory to allocate. 17 | */ 18 | void *malloc_(size_t size); 19 | 20 | /** 21 | * Returns a copy of the given string. 22 | * @param src the source string. 23 | * @return a copy of the given string. 24 | */ 25 | char *strDup(const char *src); 26 | 27 | /** 28 | * Just a safer strncpy function. 29 | * @param dest the destination string. 30 | * @param src the source string. 31 | * @param n the number of characters to copy 32 | * @return the number of characters to copy. 33 | */ 34 | int strncpy_(char *dest, const char *src, size_t n); 35 | 36 | /** 37 | * Returns a random number between 0 and 1. 38 | * This function follows the PRNG. 39 | * @return the random number. 40 | */ 41 | double getRand(void); 42 | 43 | /** 44 | * Returns the number rounded to the given digits. 45 | * @param n the number. 46 | * @param digits the digits to round after the decimal point. 47 | * @return the number rounded. 48 | */ 49 | double round_(double n, size_t digits); 50 | 51 | /** 52 | * Returns the double value of the giving string. 53 | * @param str the string. 54 | * @return the double value of the giving string. 55 | */ 56 | double strToDouble(const char *str); 57 | 58 | /** 59 | * Adds thousands separators to the given 60 | * string. 61 | * @param str the string. 62 | */ 63 | void addThousandsSep(char *str); 64 | 65 | /** 66 | * Returns the factorial of the giving number. 67 | * @param n the number. 68 | * @return the factorial of the giving number. 69 | */ 70 | long int fact(long int n); 71 | 72 | /** 73 | * Appends a char to the given string 74 | * @param str the string. 75 | * @param ch the char to append to the string. 76 | */ 77 | void appendChar(char **str, char ch); 78 | 79 | /** 80 | * Returns true if the given string is empty, 81 | * false otherwise. 82 | * @param str the string 83 | * @return true if the given string is empty, 84 | * false otherwise. 85 | */ 86 | bool isEmpty(const char *str); 87 | 88 | #if defined(_WIN32) || defined(WIN32) 89 | /** 90 | * Reference to the snprintf function of the stdio library. 91 | * Because you know, some compilers are not compliant with 92 | * the C standard. 93 | */ 94 | int snprintf(char *buf, size_t size, const char *fmt, ...); 95 | #endif 96 | 97 | #endif /* HELPER_H_ */ 98 | -------------------------------------------------------------------------------- /src/lexer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "helper.h" 6 | #include "variable.h" 7 | #include "lexer.h" 8 | 9 | /** 10 | * Current token being used. 11 | */ 12 | char *current_token; 13 | 14 | /** 15 | * Type of the current token being used. 16 | */ 17 | enum TOKEN_TYPE current_type; 18 | 19 | 20 | static enum TOKEN_TYPE getType(const char ch) 21 | { 22 | if ((ch >= '0' && ch <= '9') || ch == '.') { 23 | return T_Operand; 24 | } 25 | 26 | if (ch == '(' || ch == ')' || 27 | ch == '+' || ch == '-' || 28 | ch == '/' || ch == '*' || 29 | ch == '^' || ch == '!' || 30 | ch == '=') { 31 | return T_Operator; 32 | } 33 | 34 | if ((ch >= 'a' && ch <= 'z') || 35 | (ch >= 'A' && ch <= 'Z')) { 36 | return T_Word; 37 | } 38 | 39 | return T_None; 40 | } 41 | 42 | 43 | static bool isIgnorableC(const char ch) 44 | { 45 | return ch == ' ' || ch == ','; 46 | } 47 | 48 | 49 | static bool isSign(const char ch) 50 | { 51 | return ch == '-' || ch == '+'; 52 | } 53 | 54 | 55 | static bool isNumber(const char *str) 56 | { 57 | size_t len = strlen(str); 58 | size_t i = 0; 59 | 60 | if (isSign(str[i])) { 61 | if (i == len - 1) { 62 | return false; 63 | } 64 | 65 | i++; 66 | } 67 | 68 | for (; i < len; i++) { 69 | /* Exponent number */ 70 | if (i != 0 && i+1 < len && str[i] == 'e' && isSign(str[i+1])) { 71 | i++; 72 | continue; 73 | } 74 | 75 | if (str[i] != '.' && !isdigit(str[i])) { 76 | return false; 77 | } 78 | } 79 | 80 | return true; 81 | } 82 | 83 | 84 | static bool isSigned(struct list *list, const char *str, const int i) 85 | { 86 | if (str[i] != '+' && str[i] != '-') { 87 | return false; 88 | } 89 | 90 | if (i-1 < 0) { 91 | return true; 92 | } 93 | 94 | return 95 | (getType(str[i-1]) == T_Operator && str[i-1] != ')' && str[i - 1] != '!') && 96 | (list->last == NULL || !isNumber(list->last->value)); 97 | } 98 | 99 | 100 | static enum OPCODE getOpcode(const char *str) 101 | { 102 | if (!strcmp(str, "+")) { 103 | return OP_Plus; 104 | } else if (!strcmp(str, "-")) { 105 | return OP_Minus; 106 | } else if (!strcmp(str, "*")) { 107 | return OP_Multi; 108 | } else if (!strcmp(str, "/")) { 109 | return OP_Div; 110 | } else if (!strcmp(str, "=")) { 111 | return OP_Equal; 112 | } else if (!strcmp(str, "^")) { 113 | return OP_Pow; 114 | } else if (!strcmp(str, "!")) { 115 | return OP_Fact; 116 | } else if (!strcmp(str, "sqrt")) { 117 | return OP_Sqrt; 118 | } else if (!strcmp(str, "abs")) { 119 | return OP_Abs; 120 | } else if (!strcmp(str, "log")) { 121 | return OP_Log; 122 | } else if (!strcmp(str, "floor")) { 123 | return OP_Floor; 124 | } else if (!strcmp(str, "ceil")) { 125 | return OP_Ceil; 126 | } else if (!strcmp(str, "round")) { 127 | return OP_Round; 128 | } else if (!strcmp(str, "rand")) { 129 | return OP_Rand; 130 | } else if (!strcmp(str, "mb")) { 131 | return OP_Mb; 132 | } else if (!strcmp(str, "gb")) { 133 | return OP_Gb; 134 | } else if (!strcmp(str, "tb")) { 135 | return OP_Tb; 136 | } else if (!strcmp(str, "pb")) { 137 | return OP_Pb; 138 | } else if (!strcmp(str, "sin")) { 139 | return OP_Sin; 140 | } else if (!strcmp(str, "cos")) { 141 | return OP_Cos; 142 | } else if (!strcmp(str, "tan")) { 143 | return OP_Tan; 144 | } else if (!strcmp(str, "asin")) { 145 | return OP_Asin; 146 | } else if (!strcmp(str, "acos")) { 147 | return OP_Acos; 148 | } else if (!strcmp(str, "atan")) { 149 | return OP_Atan; 150 | } else if (!strcmp(str, "(")) { 151 | return OP_Open_parenthesis; 152 | } else if (!strcmp(str, ")")) { 153 | return OP_Closed_parenthesis; 154 | } 155 | 156 | return OP_None; 157 | } 158 | 159 | 160 | static void addToken(struct list *list, const char *token) 161 | { 162 | struct token *new; 163 | 164 | if (isEmpty(token)) { 165 | return; 166 | } 167 | 168 | new = malloc_(sizeof(struct token)); 169 | new->opcode = getOpcode(token); 170 | new->value = strDup(token); 171 | new->next = NULL; 172 | 173 | /* Set zero as argument if no argument is provided */ 174 | if (list->last != NULL && !strcmp(list->last->value, "(") && 175 | !strcmp(token, ")")) { 176 | addToken(list, "0"); 177 | } 178 | 179 | if (list->last == NULL) { 180 | list->last = new; 181 | list->first = list->last; 182 | return; 183 | } 184 | 185 | list->last->next = new; 186 | list->last = new; 187 | } 188 | 189 | 190 | static void processChar(struct list *list, 191 | const char *str, 192 | const int i) 193 | { 194 | if (isIgnorableC(str[i])) { 195 | return; 196 | } 197 | 198 | if (current_type == T_None) { 199 | current_type = getType(str[i]); 200 | } 201 | 202 | /* Add token */ 203 | if (getType(str[i]) != current_type || current_type == T_Operator) { 204 | addToken(list, current_token); 205 | 206 | free(current_token); 207 | current_token = malloc_(1); 208 | current_token[0] = '\0'; 209 | current_type = getType(str[i]); 210 | } 211 | 212 | /* Allow signed numbers */ 213 | if (isSigned(list, str, i)) { 214 | current_type = T_Operand; 215 | free(current_token); 216 | current_token = malloc_(2); 217 | current_token[0] = str[i]; 218 | current_token[1] = '\0'; 219 | 220 | return; 221 | } 222 | 223 | appendChar(¤t_token, str[i]); 224 | } 225 | 226 | 227 | void tokenize(struct list *list, const char *func) 228 | { 229 | size_t i; 230 | 231 | current_type = T_None; 232 | current_token = malloc_(1); 233 | current_token[0] = '\0'; 234 | 235 | for (i = 0; i <= strlen(func); i++) { 236 | processChar(list, func, i); 237 | } 238 | 239 | if (strlen(current_token) > 0) { 240 | addToken(list, current_token); 241 | } 242 | 243 | free(current_token); 244 | } 245 | 246 | 247 | void freeList(struct list *list) 248 | { 249 | struct token *node; 250 | 251 | while ((node = list->first) != NULL) { 252 | list->first = list->first->next; 253 | free(node->value); 254 | free(node); 255 | } 256 | 257 | free(list); 258 | } 259 | 260 | 261 | void initList(struct list **list) 262 | { 263 | (*list) = malloc_(sizeof(struct list)); 264 | (*list)->last = NULL; 265 | (*list)->first = NULL; 266 | } 267 | 268 | 269 | int getPrec(enum OPCODE opcode) 270 | { 271 | if (opcode == OP_Equal) { 272 | return 5; 273 | } 274 | 275 | if (opcode == OP_Open_parenthesis || 276 | opcode == OP_Closed_parenthesis) { 277 | return 4; 278 | } 279 | 280 | if (opcode == OP_Plus || 281 | opcode == OP_Minus) { 282 | return 3; 283 | } 284 | 285 | if (opcode == OP_Multi || 286 | opcode == OP_Div) { 287 | return 2; 288 | } 289 | 290 | if (opcode == OP_Pow) { 291 | return 1; 292 | } 293 | 294 | return 0; 295 | } 296 | 297 | 298 | bool isOperator(enum OPCODE opcode) 299 | { 300 | return 301 | opcode == OP_Plus || opcode == OP_Minus || 302 | opcode == OP_Multi || opcode == OP_Div || 303 | opcode == OP_Pow || opcode == OP_Equal; 304 | } 305 | 306 | 307 | bool isFunction(enum OPCODE opcode) 308 | { 309 | return isTrigonometric(opcode) || 310 | opcode == OP_Sqrt || opcode == OP_Log || 311 | opcode == OP_Floor || opcode == OP_Ceil || 312 | opcode == OP_Round || opcode == OP_Abs || 313 | opcode == OP_Rand || opcode == OP_Fact; 314 | } 315 | 316 | 317 | bool isTrigonometric(enum OPCODE opcode) 318 | { 319 | return 320 | opcode == OP_Sin || opcode == OP_Cos || 321 | opcode == OP_Tan || opcode == OP_Asin || 322 | opcode == OP_Acos || opcode == OP_Atan; 323 | } 324 | 325 | 326 | bool isDataUnit(enum OPCODE opcode) 327 | { 328 | return 329 | opcode == OP_Mb || opcode == OP_Gb || 330 | opcode == OP_Tb || opcode == OP_Pb; 331 | } 332 | 333 | 334 | bool isValid(struct token *node) 335 | { 336 | return isOperator(node->opcode) || 337 | isFunction(node->opcode) || 338 | isDataUnit(node->opcode) || 339 | node->opcode == OP_Open_parenthesis || 340 | node->opcode == OP_Closed_parenthesis || 341 | isVariable(node->value) || 342 | isNumber(node->value); 343 | } 344 | -------------------------------------------------------------------------------- /src/lexer.h: -------------------------------------------------------------------------------- 1 | #ifndef LEXER_H_ 2 | #define LEXER_H_ 3 | 4 | #include 5 | 6 | enum TOKEN_TYPE { 7 | T_Operand, 8 | T_Operator, 9 | T_Word, 10 | T_None 11 | }; 12 | 13 | enum OPCODE { 14 | OP_Plus, 15 | OP_Minus, 16 | OP_Multi, 17 | OP_Div, 18 | OP_Equal, 19 | OP_Pow, 20 | OP_Fact, 21 | OP_Sqrt, 22 | OP_Abs, 23 | OP_Log, 24 | OP_Floor, 25 | OP_Ceil, 26 | OP_Round, 27 | OP_Rand, 28 | OP_Mb, 29 | OP_Gb, 30 | OP_Tb, 31 | OP_Pb, 32 | OP_Sin, 33 | OP_Cos, 34 | OP_Tan, 35 | OP_Asin, 36 | OP_Acos, 37 | OP_Atan, 38 | OP_Closed_parenthesis, 39 | OP_Open_parenthesis, 40 | OP_None, 41 | }; 42 | 43 | struct token { 44 | char *value; 45 | enum OPCODE opcode; 46 | struct token *prev; 47 | struct token *next; 48 | }; 49 | 50 | struct list { 51 | struct token *first; 52 | struct token *last; 53 | }; 54 | 55 | 56 | /** 57 | * Creates a list based on the given string 58 | * with infix notation. 59 | * @param list the list. 60 | * @param func the infix function. 61 | */ 62 | void tokenize(struct list *list, const char *func); 63 | 64 | /** 65 | * Frees the given list. 66 | * @param list the list. 67 | */ 68 | void freeList(struct list *list); 69 | 70 | /** 71 | * Initializes the given list. 72 | * @param list the list. 73 | */ 74 | void initList(struct list **list); 75 | 76 | /** 77 | * Returns the precedence of the given operator. 78 | * Operators with a lower precedence are evaluated first. 79 | * @param opcode the opcode. 80 | * @return the precedence of the given operator. 81 | */ 82 | int getPrec(enum OPCODE opcode); 83 | 84 | /** 85 | * Returns true if the given opcode represents an operator, 86 | * false otherwise. 87 | * @param opcode the opcode. 88 | * @return true if the given opcode represents an operator, 89 | * false otherwise. 90 | */ 91 | bool isOperator(enum OPCODE opcode); 92 | 93 | /** 94 | * Returns true if the given opcode represents a function, 95 | * false otherwise. 96 | * @param opcode the opcode. 97 | * @return true if the given opcode represents a function, 98 | * false otherwise. 99 | */ 100 | bool isFunction(enum OPCODE opcode); 101 | 102 | /** 103 | * Returns true if the given opcode represents a trigonometric function, 104 | * false otherwise. 105 | * @param opcode the opcode. 106 | * @return true if the given opcode represents a trigonometric function, 107 | * false otherwise. 108 | */ 109 | bool isTrigonometric(enum OPCODE opcode); 110 | 111 | /** 112 | * Returns true if the given opcode represents an 113 | * unit of measurement for data storage (megabyte to petabyte), 114 | * false otherwise. 115 | * @param opcode the opcode. 116 | * @return true if the given opcode represents an 117 | * unit of measurement for data storage, 118 | * false otherwise. 119 | */ 120 | bool isDataUnit(enum OPCODE opcode); 121 | 122 | /** 123 | * Returns true if the given token is a valid value 124 | * (function, operator or number), false otherwise. 125 | * @param node the token. 126 | * @return Returns true if the given node is a valid value, 127 | * false otherwise. 128 | */ 129 | bool isValid(struct token *node); 130 | 131 | #endif /* LEXER_H_ */ 132 | -------------------------------------------------------------------------------- /src/parser.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "helper.h" 7 | #include "lexer.h" 8 | #include "variable.h" 9 | #include "parser.h" 10 | 11 | /** 12 | * Number of decimals used for the numbers. 13 | */ 14 | int precision = -1; 15 | 16 | /** 17 | * Number of decimals used for the result. 18 | */ 19 | int result_precision = -1; 20 | 21 | /** 22 | * Manage angles in degree or not. 23 | */ 24 | bool degree = false; 25 | 26 | /** 27 | * Warning about division by zero. 28 | */ 29 | bool division_warning = false; 30 | 31 | /** 32 | * Warning about an invalid value for a trigonometric function. 33 | */ 34 | bool trigonometric_warning = false; 35 | 36 | /** 37 | * Warning about wrong number of parenthesis. 38 | */ 39 | bool parenthesis_warning = false; 40 | 41 | /** 42 | * The current operation is defining a variable or not. 43 | */ 44 | bool inside_def = false; 45 | 46 | 47 | static void push(struct list **list, const struct token *node) 48 | { 49 | struct token *new = malloc_(sizeof(struct token)); 50 | 51 | if (isEmpty(node->value)) { 52 | return; 53 | } 54 | 55 | new->next = NULL; 56 | new->prev = NULL; 57 | new->opcode = node->opcode; 58 | new->value = strDup(node->value); 59 | 60 | if ((*list)->last == NULL) { 61 | (*list)->last = new; 62 | (*list)->first = new; 63 | } else { 64 | (*list)->last->next = new; 65 | new->prev = (*list)->last; 66 | (*list)->last = new; 67 | } 68 | } 69 | 70 | 71 | static void moveToken(struct list **dest, struct list **src) 72 | { 73 | struct token *cpy, *tmp; 74 | 75 | if (!strcmp((*src)->last->value, "(")) { 76 | tmp = (*src)->last; 77 | (*src)->last = (*src)->last->prev; 78 | free(tmp->value); 79 | free(tmp); 80 | } 81 | 82 | cpy = malloc_(sizeof(struct token)); 83 | cpy->prev = (*dest)->last; 84 | cpy->next = NULL; 85 | cpy->opcode = (*src)->last->opcode; 86 | cpy->value = strDup((*src)->last->value); 87 | 88 | if ((*dest)->last != NULL) { 89 | (*dest)->last->next = cpy; 90 | } else { 91 | (*dest)->first = cpy; 92 | } 93 | 94 | (*dest)->last = cpy; 95 | tmp = (*src)->last; 96 | (*src)->last = (*src)->last->prev; 97 | free(tmp->value); 98 | free(tmp); 99 | 100 | if ((*src)->last != NULL) { 101 | (*src)->last->next = NULL; 102 | } else { 103 | (*src)->first = NULL; 104 | } 105 | } 106 | 107 | 108 | static void migrateUntilParenthesis(struct list *output, 109 | struct list *operators) 110 | { 111 | struct token *tmp; 112 | 113 | while (operators->last != NULL && 114 | strcmp(operators->last->value, "(") != 0) { 115 | moveToken(&output, &operators); 116 | } 117 | 118 | if (operators->last != NULL && !strcmp(operators->last->value, "(")) { 119 | if (operators->first == operators->last) { 120 | operators->first = NULL; 121 | } 122 | 123 | tmp = operators->last; 124 | operators->last = operators->last->prev; 125 | free(tmp->value); 126 | free(tmp); 127 | } 128 | } 129 | 130 | 131 | static int hasHigherEqualPrec(struct token *first, struct token *second) 132 | { 133 | return first != NULL && second != NULL && 134 | getPrec(first->opcode) >= getPrec(second->opcode); 135 | } 136 | 137 | 138 | static void infixToPostfix(struct list *tokens, 139 | struct list *output, 140 | struct list *operators) 141 | { 142 | struct token *node = tokens->first; 143 | 144 | division_warning = false; 145 | trigonometric_warning = false; 146 | 147 | while (node != NULL && !parenthesis_warning) { 148 | if (node->opcode == OP_Closed_parenthesis) { 149 | migrateUntilParenthesis(output, operators); 150 | } else if (node->opcode == OP_Open_parenthesis || isFunction(node->opcode)) { 151 | push(&operators, node); 152 | } else if (isOperator(node->opcode)) { 153 | while (hasHigherEqualPrec(node, operators->last)) { 154 | moveToken(&output, &operators); 155 | } 156 | 157 | push(&operators, node); 158 | } else { 159 | push(&output, node); 160 | } 161 | 162 | node = node->next; 163 | } 164 | 165 | while (operators->last != NULL) { 166 | moveToken(&output, &operators); 167 | } 168 | } 169 | 170 | 171 | static double getValue(const char *str) 172 | { 173 | return isVariable(str) ? 174 | getVariableValue(str) : 175 | strToDouble(str); 176 | } 177 | 178 | 179 | static double getOpResult(enum OPCODE op, const char *a, const char *b) 180 | { 181 | double x, y; 182 | 183 | inside_def = op == OP_Equal; 184 | if (inside_def) { 185 | addVariable(a, getValue(b)); 186 | return 0; 187 | } 188 | 189 | x = getValue(a); 190 | y = getValue(b); 191 | 192 | if (precision >= 0) { 193 | x = round_(x, precision); 194 | y = round_(y, precision); 195 | } 196 | 197 | if (op == OP_Plus) { 198 | return x + y; 199 | } 200 | 201 | if (op == OP_Minus) { 202 | return x - y; 203 | } 204 | 205 | if (op == OP_Multi) { 206 | return x * y; 207 | } 208 | 209 | if (op == OP_Div) { 210 | if (y == 0) { 211 | division_warning = true; 212 | return 0; 213 | } 214 | 215 | return x / y; 216 | } 217 | 218 | if (op == OP_Pow) { 219 | return pow(x, y); 220 | } 221 | 222 | if (op == OP_Fact) { 223 | return fact((int)y); 224 | } 225 | 226 | if (op == OP_Sqrt) { 227 | return sqrt(y); 228 | } 229 | 230 | if (op == OP_Abs) { 231 | return fabs(y); 232 | } 233 | 234 | if (op == OP_Log) { 235 | return log(y); 236 | } 237 | 238 | if (op == OP_Floor) { 239 | return floor(y); 240 | } 241 | 242 | if (op == OP_Ceil) { 243 | return ceil(y); 244 | } 245 | 246 | if (op == OP_Round) { 247 | return round(y); 248 | } 249 | 250 | if (op == OP_Rand) { 251 | return getRand(); 252 | } 253 | 254 | if (op == OP_Mb) { 255 | return y * ONE_MB; 256 | } 257 | 258 | if (op == OP_Gb) { 259 | return y * ONE_GB; 260 | } 261 | 262 | if (op == OP_Tb) { 263 | return y * ONE_TB; 264 | } 265 | 266 | if (op == OP_Pb) { 267 | return y * ONE_PT; 268 | } 269 | 270 | if (degree && isTrigonometric(op)) { 271 | y = y / 180 * MATH_PI; 272 | } 273 | 274 | if (op == OP_Sin) { 275 | return sin(y); 276 | } 277 | 278 | if (op == OP_Cos) { 279 | return cos(y); 280 | } 281 | 282 | if (op == OP_Tan) { 283 | return tan(y); 284 | } 285 | 286 | if ((op == OP_Asin || 287 | op == OP_Acos || 288 | op == OP_Atan) && 289 | (y < -1 || y > 1)) { 290 | trigonometric_warning = true; 291 | return 0; 292 | } 293 | 294 | if (op == OP_Asin) { 295 | return asin(y); 296 | } 297 | 298 | if (op == OP_Acos) { 299 | return acos(y); 300 | } 301 | 302 | if (op == OP_Atan) { 303 | return atan(y); 304 | } 305 | 306 | return 0; 307 | } 308 | 309 | 310 | static char *pop(struct list *list) 311 | { 312 | struct token *tmp; 313 | char *str; 314 | 315 | if (list->last == NULL) { 316 | return 0; 317 | } 318 | 319 | str = strDup(list->last->value); 320 | tmp = list->last; 321 | list->last = list->last->prev; 322 | free(tmp->value); 323 | free(tmp); 324 | 325 | return str; 326 | } 327 | 328 | 329 | static void pushResult(struct list *list, const struct token *node) 330 | { 331 | struct token *new; 332 | char *x = NULL, *y = NULL; 333 | double result; 334 | 335 | if (list == NULL || list->last == NULL) { 336 | return; 337 | } 338 | 339 | y = pop(list); 340 | 341 | if (!isFunction(node->opcode) && 342 | !isDataUnit(node->opcode)) { 343 | x = pop(list); 344 | } 345 | 346 | result = getOpResult(node->opcode, x, y); 347 | if (precision >= 0) { 348 | result = round_(result, precision); 349 | } 350 | 351 | new = malloc_(sizeof(struct token)); 352 | new->value = malloc_(BUFFER); 353 | snprintf(new->value, BUFFER, NUMBER_FORMAT, result); 354 | 355 | push(&list, new); 356 | free(new->value); 357 | free(new); 358 | free(y); 359 | 360 | if (x != NULL) { 361 | free(x); 362 | } 363 | } 364 | 365 | 366 | static double getPostfixResult(const struct list *postfix) 367 | { 368 | struct list *result_list; 369 | struct token *node = postfix->first; 370 | double result = 0.0; 371 | 372 | initList(&result_list); 373 | 374 | while (node != NULL) { 375 | if (isOperator(node->opcode) || 376 | isFunction(node->opcode) || 377 | isDataUnit(node->opcode)) { 378 | pushResult(result_list, node); 379 | } else { 380 | push(&result_list, node); 381 | } 382 | 383 | node = node->next; 384 | } 385 | 386 | if (result_list != NULL && result_list->last != NULL) { 387 | result = getValue(result_list->last->value); 388 | } 389 | 390 | if (result_precision >= 0) { 391 | result = round_(result, result_precision); 392 | } 393 | 394 | freeList(result_list); 395 | 396 | return result; 397 | } 398 | 399 | 400 | static void validateParenthesis(struct list *tokens) 401 | { 402 | struct token *node = tokens->first; 403 | int scope = 0; 404 | 405 | while (node != NULL) { 406 | if (node->opcode == OP_Open_parenthesis) { 407 | scope++; 408 | } else if (node->opcode == OP_Closed_parenthesis) { 409 | scope--; 410 | } 411 | 412 | node = node->next; 413 | } 414 | 415 | parenthesis_warning = scope != 0; 416 | } 417 | 418 | 419 | char *getResult(const char *func, struct list *tokens, struct list *output) 420 | { 421 | struct list *operators; 422 | char *result = malloc_(BUFFER); 423 | inside_def = false; 424 | parenthesis_warning = false; 425 | 426 | initList(&operators); 427 | 428 | tokenize(tokens, func); 429 | validateParenthesis(tokens); 430 | infixToPostfix(tokens, output, operators); 431 | snprintf(result, BUFFER, NUMBER_FORMAT, getPostfixResult(output)); 432 | freeList(operators); 433 | 434 | if (inside_def) { 435 | free(result); 436 | return NULL; 437 | } 438 | 439 | return result; 440 | } 441 | 442 | 443 | void printWarnings(const struct list *list) 444 | { 445 | struct token *node = list->first; 446 | size_t warnings_quantity = 0; 447 | 448 | while (node != NULL) { 449 | if (!isValid(node)) { 450 | printf(WARNING_TOKEN, node->value); 451 | warnings_quantity++; 452 | } 453 | 454 | node = node->next; 455 | } 456 | 457 | if (division_warning) { 458 | printf(WARNING_ZERO_DIV); 459 | warnings_quantity++; 460 | } 461 | 462 | if (trigonometric_warning) { 463 | printf(WARNING_TRIGONOMETRIC); 464 | warnings_quantity++; 465 | } 466 | 467 | if (parenthesis_warning) { 468 | printf(WARNING_PARENTHESIS); 469 | warnings_quantity++; 470 | } 471 | 472 | if (warnings_quantity > 0) { 473 | printf(MSG_INACCURATE_RESULT); 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /src/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef PARSER_H_ 2 | #define PARSER_H_ 3 | 4 | #define WARNING_PREFIX "\nWarning: " 5 | #define WARNING_TOKEN WARNING_PREFIX"Invalid token '%s'" 6 | #define WARNING_ZERO_DIV WARNING_PREFIX"Division by zero/undefined." 7 | #define WARNING_TRIGONOMETRIC WARNING_PREFIX"Invalid trigonometric value." 8 | #define WARNING_PARENTHESIS WARNING_PREFIX"Invalid number of parenthesis." 9 | #define MSG_INACCURATE_RESULT "\nResult may not be correct!\n" 10 | #define STMT_SEPARATOR ";" 11 | #define NUMBER_FORMAT "%.15g" 12 | /* Math values */ 13 | #define MATH_PI acos(-1) 14 | #define MATH_E 2.71828182845904523536 15 | #define MATH_GR 1.6180339887498948482 16 | #define MATH_G 9.80665 17 | /* Units of measurement for data storage (in kb) */ 18 | #define ONE_MB 1024 19 | #define ONE_GB 1024 * 1024 20 | #define ONE_TB 1024 * 1024 * 1024 21 | #define ONE_PT 1024 * 1024 * 1024 * 1024 22 | 23 | extern int precision; 24 | extern int result_precision; 25 | extern bool degree; 26 | 27 | /** 28 | * Returns the result of the given infix operation. 29 | * @param func the infix function. 30 | * @param tokens the tokens list. 31 | * @param output the output list. 32 | * @return the result of the given function. 33 | */ 34 | char *getResult(const char *func, 35 | struct list *tokens, 36 | struct list *output); 37 | 38 | /** 39 | * Prints all the warnings. 40 | * @param list the list. 41 | */ 42 | void printWarnings(const struct list *list); 43 | 44 | #endif /* PARSER_H_ */ 45 | -------------------------------------------------------------------------------- /src/quich.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "../lib/linenoise.h" 7 | #include "helper.h" 8 | #include "lexer.h" 9 | #include "parser.h" 10 | #include "variable.h" 11 | #include "quich.h" 12 | 13 | /** 14 | * Verbose mode or not. 15 | */ 16 | bool verbose = false; 17 | 18 | /** 19 | * Number of flags used. 20 | */ 21 | int flags_quantity = 0; 22 | 23 | /** 24 | * Interactive mode or not. 25 | */ 26 | bool interactive_mode = false; 27 | 28 | /** 29 | * Using thousands separator or not. 30 | */ 31 | bool thousands_sep = false; 32 | 33 | /** 34 | * Format to display the results. 35 | */ 36 | char *format = NULL; 37 | 38 | 39 | static void printVerbose(struct list *tokens, struct list *output) 40 | { 41 | struct token *node = tokens->first; 42 | 43 | /* Tokens */ 44 | printf("Tokens > "); 45 | while (node != NULL) { 46 | printf(isValid(node) ? "'%s' " : "'%s'? ", node->value); 47 | node = node->next; 48 | } 49 | 50 | node = output->first; 51 | 52 | /* Postfix operation */ 53 | printf("\nPosfix > "); 54 | while (node != NULL) { 55 | printf("%s ", node->value); 56 | node = node->next; 57 | } 58 | 59 | printf("\nResult > "); 60 | } 61 | 62 | 63 | static void printResult(char *func) 64 | { 65 | struct list *tokens, *output; 66 | char *result; 67 | 68 | initList(&tokens); 69 | initList(&output); 70 | 71 | result = getResult(func, tokens, output); 72 | 73 | if (!isEmpty(format)) { 74 | snprintf(result, BUFFER, format, strToDouble(result)); 75 | } 76 | 77 | if (verbose) { 78 | printVerbose(tokens, output); 79 | } 80 | 81 | if (thousands_sep) { 82 | addThousandsSep(result); 83 | } 84 | 85 | if (result != NULL) { 86 | printf("%s\n", result); 87 | free(result); 88 | } else if (verbose) { 89 | printf(MSG_DEFINITION); 90 | } 91 | 92 | printWarnings(output); 93 | freeList(tokens); 94 | freeList(output); 95 | } 96 | 97 | 98 | static void printAll(char *func) 99 | { 100 | char *statements = strtok(func, STMT_SEPARATOR); 101 | 102 | while (statements != NULL) { 103 | printResult(statements); 104 | statements = strtok(NULL, STMT_SEPARATOR); 105 | 106 | if (statements != NULL && verbose) { 107 | printf("\n"); 108 | } 109 | } 110 | } 111 | 112 | 113 | /** 114 | * Returns true if the program flow must be stopped, 115 | * false otherwise. 116 | */ 117 | static bool mapArgs(int argc, char *argv[]) 118 | { 119 | int i; 120 | 121 | for (i = 0; i < argc; i++) { 122 | /* Angles in degree */ 123 | if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--degree")) { 124 | degree = true; 125 | flags_quantity++; 126 | } 127 | 128 | /* Verbose mode */ 129 | if (!strcmp(argv[i], "-vvv") || !strcmp(argv[i], "--verbose")) { 130 | verbose = true; 131 | flags_quantity++; 132 | } 133 | 134 | /* Interactive */ 135 | if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--interactive")) { 136 | interactive_mode = true; 137 | flags_quantity++; 138 | } 139 | 140 | /* Version */ 141 | if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--version")) { 142 | printf(MSG_VERSION); 143 | return true; 144 | } 145 | 146 | /* Help */ 147 | if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { 148 | printf(MSG_HELP); 149 | return true; 150 | } 151 | 152 | /* Thousands separator */ 153 | if (!strcmp(argv[i], "-t") || !strcmp(argv[i], "--thousands")) { 154 | thousands_sep = true; 155 | flags_quantity++; 156 | } 157 | 158 | /* The flags below work with values */ 159 | if (i+1 >= argc) { 160 | return false; 161 | } 162 | 163 | /* Result format */ 164 | if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--format")) { 165 | format = argv[++i]; 166 | flags_quantity += 2; 167 | } 168 | 169 | /* Precision */ 170 | if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--precision")) { 171 | precision = (int)strToDouble(argv[++i]); 172 | flags_quantity += 2; 173 | } 174 | 175 | /* Result precision */ 176 | if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--round")) { 177 | result_precision = (int)strToDouble(argv[++i]); 178 | flags_quantity += 2; 179 | } 180 | } 181 | 182 | return false; 183 | } 184 | 185 | 186 | static void interactive(void) 187 | { 188 | char *line = NULL; 189 | printf(MSG_INIT); 190 | 191 | while ((line = linenoise("> ")) != NULL && strcmp(line, EXIT_COMMAND) != 0) { 192 | linenoiseHistoryAdd(line); 193 | printAll(line); 194 | linenoiseFree(line); 195 | } 196 | 197 | linenoiseFree(line); 198 | printf(MSG_BYE); 199 | } 200 | 201 | 202 | static void addPredefValues(void) 203 | { 204 | addVariable("PI", MATH_PI); 205 | addVariable("E", MATH_E); 206 | addVariable("G", MATH_G); 207 | addVariable("GR", MATH_GR); 208 | } 209 | 210 | 211 | int main(int argc, char* argv[]) 212 | { 213 | if (mapArgs(argc, argv)) { 214 | return 0; 215 | } 216 | 217 | addPredefValues(); 218 | if (interactive_mode || flags_quantity >= argc - 1) { 219 | interactive(); 220 | } else { 221 | printAll(argv[1]); 222 | } 223 | 224 | freeVariables(); 225 | 226 | return 0; 227 | } 228 | -------------------------------------------------------------------------------- /src/quich.h: -------------------------------------------------------------------------------- 1 | #ifndef QUICH_H_ 2 | #define QUICH_H_ 3 | #define MSG_VERSION "QUICH v4.0.0\n" 4 | #define MSG_DEFINITION "(definition)\n" 5 | #define EXIT_COMMAND "exit" 6 | #define MSG_INIT "Running "MSG_VERSION \ 7 | "Type '"EXIT_COMMAND"' to exit the program.\n" 8 | #define MSG_BYE "Bye...\n" 9 | #define MSG_HELP "QUICH: Just an advanced terminal calculator\n\n" \ 10 | "USAGE: operation [options...]\n\n" \ 11 | "OPTIONS:\n\n" \ 12 | "-d --degree Manage the given angles in degrees.\n" \ 13 | "-f --format [%%s] The format to display the result.\n" \ 14 | "-h --help Show help about the software.\n" \ 15 | "-i --interactive Force interactive mode.\n" \ 16 | "-p --precision [%%i] The number of decimals used for the internal numbers.\n" \ 17 | "-r --round [%%i] The number of decimals to round the result.\n" \ 18 | "-t --thousands Display the result with thousands separators.\n" \ 19 | "-vvv --verbose Display the result with details.\n" \ 20 | "-v --version Show the version.\n\n" \ 21 | "FUNCTIONS:\n\n" \ 22 | "sqrt Square root.\n" \ 23 | "abs Absolute value (positive).\n" \ 24 | "log Natural logarithm.\n" \ 25 | "sin Sine trigonometric function.\n" \ 26 | "cos Cosine trigonometric function.\n" \ 27 | "tan Tangent trigonometric function.\n" \ 28 | "asin Arc sine trigonometric function.\n" \ 29 | "acos Arc cosine trigonometric function.\n" \ 30 | "atan Arc tangent trigonometric function.\n" \ 31 | "rand Random number between 0 and 1.\n" \ 32 | "round Round to the nearest integer value.\n" \ 33 | "floor Round down.\n" \ 34 | "ceil Round up.\n\n" \ 35 | "AVAILABLE:\n\n" \ 36 | "Constants 'PI' (3.14...), 'E' (2.71...), 'GR' (1.61...) and 'G' (9.80...).\n" \ 37 | "Units of measurement for data storage 'mb', 'gb', 'tb' and 'pt' (returned in kb).\n\n" 38 | 39 | #endif /* QUICH_H_ */ 40 | -------------------------------------------------------------------------------- /src/variable.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "helper.h" 4 | #include "variable.h" 5 | 6 | /** 7 | * Pointer to the first variable of the variables list. 8 | */ 9 | static struct variable *variables_first; 10 | 11 | 12 | static bool replaceVariable(const char *key, double val) 13 | { 14 | struct variable *var = variables_first; 15 | 16 | while (var != NULL) { 17 | if (!strcmp(key, var->key)) { 18 | var->value = val; 19 | return true; 20 | } 21 | 22 | var = var->next; 23 | } 24 | 25 | return false; 26 | } 27 | 28 | 29 | bool isVariable(const char *str) 30 | { 31 | struct variable *var = variables_first; 32 | 33 | if (str == NULL) { 34 | return false; 35 | } 36 | 37 | while (var != NULL) { 38 | if (!strcmp(str, var->key)) { 39 | return true; 40 | } 41 | 42 | var = var->next; 43 | } 44 | 45 | return false; 46 | } 47 | 48 | 49 | void addVariable(const char *key, double val) 50 | { 51 | struct variable *var; 52 | 53 | if (replaceVariable(key, val)) { 54 | return; 55 | } 56 | 57 | var = malloc_(sizeof(struct variable)); 58 | var->key = strDup(key); 59 | var->value = val; 60 | 61 | var->next = variables_first; 62 | variables_first = var; 63 | } 64 | 65 | 66 | double getVariableValue(const char *key) 67 | { 68 | struct variable *var = variables_first; 69 | 70 | while (var != NULL) { 71 | if (!strcmp(key, var->key)) { 72 | return var->value; 73 | } 74 | 75 | var = var->next; 76 | } 77 | 78 | return 0; 79 | } 80 | 81 | 82 | void freeVariables(void) 83 | { 84 | struct variable *var; 85 | 86 | while ((var = variables_first) != NULL) { 87 | variables_first = variables_first->next; 88 | free(var->key); 89 | free(var); 90 | } 91 | 92 | free(variables_first); 93 | } 94 | -------------------------------------------------------------------------------- /src/variable.h: -------------------------------------------------------------------------------- 1 | #ifndef VARIABLE_H 2 | #define VARIABLE_H 3 | 4 | struct variable { 5 | char *key; 6 | double value; 7 | struct variable *next; 8 | }; 9 | 10 | 11 | /** 12 | * Returns true if the given string is an existing variable, 13 | * false otherwise. 14 | * @param str the string. 15 | * @return Returns true if the given string is an existing variable, 16 | * false otherwise. 17 | */ 18 | bool isVariable(const char *str); 19 | 20 | /** 21 | * Adds a variable. 22 | * @param key the variable key. 23 | * @param val the variable value. 24 | */ 25 | void addVariable(const char *key, double val); 26 | 27 | /** 28 | * Returns the value of the variable with the given key. 29 | * @param key the variable key. 30 | * @return the value of the variable. 31 | */ 32 | double getVariableValue(const char *key); 33 | 34 | /** 35 | * Frees the variable list. 36 | */ 37 | void freeVariables(void); 38 | 39 | #endif /* VARIABLE_H */ 40 | -------------------------------------------------------------------------------- /tests/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "../src/helper.h" 7 | #include "../src/lexer.h" 8 | #include "../src/parser.h" 9 | #include "../src/variable.h" 10 | 11 | size_t failed = 0; 12 | 13 | 14 | /** 15 | * -1 ignore. 16 | * 0 failure. 17 | * 1 success. 18 | */ 19 | static char *getOpResult(const char *op) 20 | { 21 | struct list *tokens, *output; 22 | char *result; 23 | 24 | initList(&tokens); 25 | initList(&output); 26 | 27 | result = getResult(op, tokens, output); 28 | 29 | freeList(tokens); 30 | freeList(output); 31 | 32 | return result; 33 | } 34 | 35 | 36 | static void assertEqual(const char *expected, char *op) 37 | { 38 | char *cpy = malloc_(strlen(op) + 1); 39 | char *statement; 40 | 41 | strcpy(cpy, op); 42 | statement = strtok(cpy, STMT_SEPARATOR); 43 | 44 | while (statement != NULL) { 45 | char *result = getOpResult(statement); 46 | bool success = result == NULL || !strcmp(expected, result); 47 | 48 | if (result != NULL) { 49 | printf("%s -> %s\n", success ? "success" : "failure", op); 50 | 51 | if (!success) { 52 | printf(" Expected '%s' got '%s'.\n", expected, result); 53 | failed++; 54 | } 55 | } 56 | 57 | free(result); 58 | statement = strtok(NULL, STMT_SEPARATOR); 59 | } 60 | 61 | free(cpy); 62 | } 63 | 64 | 65 | static void setUp(void) 66 | { 67 | addVariable("PI", MATH_PI); 68 | addVariable("E", MATH_E); 69 | addVariable("G", MATH_G); 70 | addVariable("GR", MATH_GR); 71 | } 72 | 73 | 74 | static void tearDown(void) 75 | { 76 | freeVariables(); 77 | } 78 | 79 | 80 | int main(int argc, char* argv[]) 81 | { 82 | setUp(); 83 | assertEqual("9", "9"); 84 | assertEqual("5", "(3+2)"); 85 | assertEqual("4", "(3+2)-1"); 86 | assertEqual("7", "5--2"); 87 | assertEqual("5", "a=1;4+a"); 88 | assertEqual("21", "a=20;a+1"); 89 | assertEqual("10.8377655357568", "5+(cos(2)-2)^2"); 90 | assertEqual("8.14159265358979", "5+PI"); 91 | assertEqual("7.61803398874989", "6+GR"); 92 | assertEqual("25", "a=12;b=a;a+b+1"); 93 | assertEqual("0.282777605186477", "sin(1)+cos(2)+tan(3)"); 94 | assertEqual("51", "abs(-53 + 2)"); 95 | assertEqual("5", "floor(4.8)+ceil(0.3)"); 96 | assertEqual("0.693147180559945", "log(2)"); 97 | assertEqual("1.4142135623731", "sqrt(1+0.5+0.5)"); 98 | assertEqual("nan", "sqrt(-2)"); 99 | result_precision = 2; 100 | assertEqual("0.12", "0.123456"); 101 | degree = 1; 102 | assertEqual("1", "sin(90)"); 103 | tearDown(); 104 | 105 | if (failed >= 1) { 106 | printf("\nFailed tests: %zu.\n", failed); 107 | return 1; 108 | } 109 | 110 | printf("\nAll tests have succeeded.\n"); 111 | return 0; 112 | } 113 | --------------------------------------------------------------------------------