├── .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 |
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 |
--------------------------------------------------------------------------------