├── .gitignore ├── LICENSE ├── README.md ├── doc ├── Arithmethic.pdf ├── The Theory of Concatenative Combinators.pdf └── abstraction.md └── src ├── abstract.c ├── abstract.h ├── dict.c ├── dict.h ├── eval.c ├── eval.h ├── gerku.c ├── gerku.h ├── grk ├── all.grk ├── default.grk ├── gerku.grk ├── kerby.grk ├── mlatu.grk ├── runtest1.grk └── types.grk ├── hardwired.c ├── hardwired.h ├── libs.c ├── libs.h ├── libs ├── dbg.h ├── linenoise.h ├── skp.h ├── stk.h ├── try.h ├── vec.h └── vrg.h ├── makefile ├── repl.c ├── repl.h └── vm.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Remo Dentato 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gerku 2 | 3 | ``` 4 | _, 5 | | | 6 | __ _, ___ ,_ __ | | __ _, _, /\___ 7 | / _` | / _ \| '__)| |/ /| | | | ( @\___ 8 | ( (_) |( __/| | | ( | |_| | / ) 9 | \_, | \___)|_| |_|\_\ \__,_| / (_____/ 10 | __/ | (_____/ U 11 | (____/ 12 | 13 | ``` 14 | An interepreter for Concatenative Combinators as described in: 15 | 16 | "The Theory of Concatenative Combinators by Brent Kerby" 17 | ([link](http://tunes.org/~iepos/joy.html#applic)) 18 | 19 | `gerku` started as sidekick project of `mlatu` to check if using 20 | stack based semantics would be equivalent to using rewriting rules. 21 | Given the type of rewriting rules `mlatu` is aiming to handle, the 22 | answer is *NO*! 23 | 24 | mlatu is a project by Caden Haustein (carmysilna) 25 | ([link](https://github.com/mlatu-lang/mlatu)) 26 | 27 | `gerku` is now only aimed to be a tool for those wanting to explore 28 | the concatenative Combinatory Logic described in the Kerby's article. 29 | 30 | Visit this channel on [Discord](https://discord.gg/vPgsxHcgXX) to 31 | discuss and provide feedback. 32 | 33 | ## Compile & Run 34 | 35 | Clone (or fork) the repository on [Github](https://github.com/rdentato/gerku). 36 | 37 | In the `src` directory just type `make` (tested on linux and on Windos MSYS2) 38 | 39 | ``` 40 | prj/gerku/src> make 41 | prj/gerku/src> ./gerku 42 | ``` 43 | 44 | All dependencies are in the `src/libs` directory, all libraries used 45 | are by me except [`linenoise`](https://github.com/antirez/linenoise), a small, 46 | fast, self-contained, MIT-licensed, replacement of `readline`. 47 | 48 | If the terminal you're using has issues with `linenoise`, you 49 | can change the settings in the makefile comment the line: 50 | ``` 51 | LINENOISE=-DUSE_LINENOISE 52 | ``` 53 | and recompile. You will lose history and line editing features. 54 | 55 | ## Command line 56 | `gerku` has few options that can be specified on the command line: 57 | 58 | ``` 59 | prj/gerku/src> ./gerku -h 60 | GERKU 0.0.6-beta (C) 2021 https://github.com/rdentato/gerku 61 | Usage: gerku [OPTIONS] ... 62 | -d file Load definitions from the specified file 63 | -r Run mode (no REPL) 64 | -v Version 65 | ``` 66 | 67 | All arguments after the options are considered to be source files 68 | to be loaded and executed before entering the REPL. 69 | The directory `grk` has some example. 70 | 71 | ## The REPL 72 | 73 | The list of REPL commands is accessible within the REPL iteself: 74 | 75 | ``` 76 | prj/gerku/src> ./gerku 77 | GERKU 0.0.6-beta (C) 2021 https://github.com/rdentato/gerku 78 | Type ! for available commands. 79 | |-> 80 | gerku> ! 81 | Available commands: 82 | !help this help 83 | !list list of defined words 84 | !load file load definitions from file 85 | !save file save definitions to file 86 | !print print current stack 87 | !trace toggle reduction tracing 88 | !quit exit the repl 89 | !def ... define a new word 90 | !del word | !all delete a word [all words!] 91 | !wipe [auto] wipe the stack [and toggles autowipe] 92 | |-> 93 | 94 | ``` 95 | 96 | ## Defining a combinator 97 | 98 | A new combinator is defined through the "!def" command: 99 | 100 | ``` 101 | prj/gerku/src> ./gerku -h 102 | ./gerku -d 103 | GERKU 0.0.6-beta (C) 2021 https://github.com/rdentato/gerku 104 | Type ! for available commands. 105 | |-> 106 | gerku> !def (@) (@) COMB = ((@1) @2) 107 | |-> 108 | gerku> !list 109 | (@) (@) COMB = ((@1) @2) 110 | |-> 111 | gerku> (x) (y) COMB 112 | |-> ((x) y) 113 | ``` 114 | 115 | ## Default combinators 116 | 117 | By default `gerku` loads the definitions stored in `grk/default.grk`. 118 | 119 | You can load a different definition file using the `-d` flag. 120 | 121 | For example if you only want to load the primitive Kerby's combinators. 122 | 123 | ``` 124 | prj/gerku/src> ./gerku -d grk/kerby.grk 125 | ``` 126 | -------------------------------------------------------------------------------- /doc/Arithmethic.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdentato/gerku/4f447c45ae8762462e6843f7ac767bc2a55c8f60/doc/Arithmethic.pdf -------------------------------------------------------------------------------- /doc/The Theory of Concatenative Combinators.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdentato/gerku/4f447c45ae8762462e6843f7ac767bc2a55c8f60/doc/The Theory of Concatenative Combinators.pdf -------------------------------------------------------------------------------- /doc/abstraction.md: -------------------------------------------------------------------------------- 1 | # Concatenative Combinators abstraction algorithm 2 | 3 | ## Abstract 4 | In [1], Kerby discuss *abstraction* by converting expressions containg concatenative combinators to lambda expressions with variables and then proceeds by abstracting variables from the lambda expressions. 5 | 6 | This article follows a more direct approach and provides abstraction rules that are aimed to make the algorithm easier to apply and implement. 7 | 8 | Even if we are mostly interested in showing that such algorithm exists we have made an effort to choose a base of combinators that will keep the resulting expression as short as possible (abstraction usually leads to very long and complex expressions). Further improvements can be done while implementing the algorithm, but we won't discuss them here. 9 | 10 | ## Expressions 11 | The language of concatenative combinators is defined by the following EBNF: 12 | 13 | ``` 14 | expression := term+ 15 | term := combinator | var | quote 16 | quote := '(' expression? ')' 17 | combinator := [A-Za-z_][A-Za-z_0-9]* 18 | var := [𝑎𝑏𝑐-𝑥𝑦𝑧] 19 | ``` 20 | 21 | That can be summarized as: 22 | 23 | - An *expression* is a list of terms 24 | - A *combinator* is a term 25 | - A *variable* is a term 26 | - A *quote* (an expression enclosed in parenthesis) is a term 27 | - The *nil* quote `()` is a term 28 | 29 | Note that variables are notated with a different set of symbols. 30 | 31 | Some examples: 32 | ``` 33 | (𝑦) dup 34 | (cons (𝑧)) sip (𝑦) 35 | (alpha) (beta) dip 36 | ``` 37 | 38 | The *evaluation* of an expression proceeds from left to right by finding the first combinator that can be successfully reduced (i.e. the one that has enough quotes at its left) and replacing it and its arguments with the result of its application to the arguments. 39 | 40 | We assume that, as in classical Combinatory Logic, the Church-Rosser theorem holds. While not a real proof, at the end of this paper we'll provide a reasoning that reassure us on the validity of the Church-Rosser theorem. 41 | 42 | ## Combinators 43 | Let's use an intuitive definition of what a combinator is: 44 | 45 | > A *combinator* is an operator that shuffles, duplicates or removes 46 | > *quotes* there are at his left in an expression. 47 | 48 | The behaviour of a combinator is defined as an equivalence like this: 49 | 50 | ``` 51 | (𝑦) (𝑥) swap = (𝑥) (𝑦) 52 | ``` 53 | 54 | You can interpret the definition of `swap` above by saying that in any given expression you can replace any occurence of: 55 | ``` 56 | (𝑦) (𝑥) swap 57 | ``` 58 | with: 59 | ``` 60 | (𝑥) (𝑦) 61 | ``` 62 | 63 | For the sake of reasoning (and as a possible strategy of implementation) we can think of a combinator as a program that operates on a stack: 64 | 65 | > A combinator is a *program* that pulls a certain number of quoted expressions from a stack and push back in the stack a number of expressions containing any number of them (even zero), possibily in a different order 66 | 67 | To simplify the abstraction algorithm we'll use the following combinators: 68 | 69 | ``` 70 | (𝑥) i = 𝑥 71 | (𝑥) zap = 72 | (𝑥) run = 𝑥 (𝑥) 73 | (𝑥) dup = (𝑥) (𝑥) 74 | (𝑦) (𝑥) cons = ((𝑦) 𝑥) 75 | (𝑦) (𝑥) cosp = ((𝑦) 𝑥) (𝑦) 76 | (𝑦) (𝑥) dip = 𝑥 (𝑦) 77 | (𝑦) (𝑥) sip = (𝑦) 𝑥 (𝑦) 78 | ``` 79 | 80 | which are a superset of the bases defined in [1] and, hence, ensure that every possible expression can be represented using those combinators. 81 | 82 | We'll extend the concept of combinator *defintions* by allowing a combinator to add other elements that were not in the original arguments. 83 | 84 | For example: 85 | ``` 86 | (𝑦) (𝑥) add = (𝑦) (succ) 𝑥 87 | (𝑦) (𝑥) mult = (zero) ((𝑦) add) 𝑥 88 | ``` 89 | 90 | ## Abstraction 91 | 92 | The *abstraction* of an expression `𝓕` with respect to the variable `𝑥` is denoted with `{𝓕}[(𝑥)]`. 93 | 94 | The result of the *abstraction* of the variable `𝑥` from the expression `𝓕` is an expression `𝓖` that does not contain `𝑥` and that, when applied to `(𝑥)`. will return `𝓕`: 95 | 96 | ``` 97 | 𝓖 = {𝓕}[(𝑥)] -> (𝑥) 𝓖 = 𝓕 98 | ``` 99 | 100 | In a sense, abstraction is similar to compilation. Given an expression `𝓕` containing a variable `𝑥` (the *source code*) we can see `{𝓕}[(𝑥)]` as a program that when applied to a quoted expression `(𝓡)` will result in the original expression where any occurence of the variable `𝑥` is replaced by `𝓡`. 101 | 102 | When abstracting multiple variable we'll have: 103 | 104 | ``` 105 | {𝓕}[(𝑦) (𝑥)] = {{𝓕}[(𝑥)]}[(𝑦)] 106 | ``` 107 | 108 | In other words, we first abstract wrt `𝑥`, then `𝑦` and so on. 109 | 110 | Note that abstraction is defined with respect to a quoted variable as concatenative combinators are defined to only operate on quotes. 111 | 112 | 113 | ## Abstraction rules 114 | 115 | We'll use the following definitions: 116 | 117 | - `𝑥` is a generic variable 118 | - `𝓖` is a non empty expression that *does not* contain `𝑥` 119 | (i.e. `𝑥` *does not occur* in `𝓖`) 120 | - `𝓜` is an expressions that *may* contain `𝑥` 121 | (`𝑥` *may occur* in `𝓜`) 122 | - `𝓝` is an expressions that *do* contain `𝑥` 123 | (`𝑥` *occurs* in `𝓝`) 124 | 125 | Given an expression, looking at the list of terms from left to right there can only be the following cases: 126 | 127 | 1. The expression is empty; 128 | 129 | 2. The expression can be split in two parts with first terms containing the variable `𝑥` followed by other terms not containing `𝑥`; 130 | 131 | 3. The expression can be split in two parts with first terms not containing the variable `𝑥` followed by other terms containing `𝑥`; 132 | 133 | 4. The first term is a quote containing `𝑥` (otherwise it would have been accounted for in case 3) followed by an expression containing `𝑥` (otherwise it would have been accounted for in case 0); 134 | 135 | 5. The fist term is the variable `𝑥` (unquoted). 136 | 137 | This leads to the following abstraction rules: 138 | 139 | ``` 140 | 1 {}[(𝑥)] = zap 141 | 2 {𝓝 𝓖}[(𝑥)] = {𝓝}[(𝑥)] 𝓖 142 | 3 {𝓖 𝓝}[(𝑥)] = (𝓖) dip {𝓝}[(𝑥)] 143 | 4 {(𝓝) 𝓜}[(𝑥)] = ({𝓝}[(𝑥)]) cosp {𝓜}[(𝑥)] 144 | 5 {𝑥 𝓜}[(𝑥)] = run {𝓜}[(𝑥)] 145 | 146 | ``` 147 | 148 | Note that the expressions on the right side can't be reduced further without being applied to a quote. 149 | 150 | It's easy to prove, by induction on the length of the expressions, that the algorithm converges: at each step the expressions to be abstracted become smaller and smaller. 151 | 152 | In the following subsection we'll show that the rules do hold by applying them to `(𝑥)` and checking that the result is, indeed, the original expression. 153 | 154 | ### Rule 1 155 | This is the base case for when the expression is empty. 156 | 157 | ``` 158 | 1 {}[(𝑥)] = zap 159 | ``` 160 | 161 | Let's check that applying the result to (𝑥) we got the empty expression: 162 | 163 | ``` 164 | (𝑥) zap 165 | ╰─────╯ by def. of zap 166 | ◄── empty expression 167 | ╰╯ by def. of abstraction 168 | (𝑥) {}[(𝑥)] 169 | ``` 170 | 171 | 172 | ### Rule 2 173 | This rule allows us to stop earlier in the abstraction process: trailing terms not containing `𝑥` can be left untouched. 174 | 175 | This is implied by the fact that the combinators are concatenative. 176 | 177 | ``` 178 | 2 {𝓝 𝓖}[(𝑥)] = {𝓝}[(𝑥)] 𝓖 179 | ``` 180 | 181 | Let's check that rule `2` holds: 182 | 183 | ``` 184 | (𝑥) {𝓝}[(𝑥)] 𝓖 185 | ╰───────────╯ by definition of abstraction 186 | 𝓝 𝓖 187 | ╰──╯ by definition of abstraction 188 | (𝑥) {𝓝 𝓖}[(𝑥)] 189 | ``` 190 | 191 | ### Rule 3 192 | 193 | This rule is to be applied when the expression consists of a list of terms which do not contain `𝑥` followed by a list of terms which contain `𝑥`. 194 | 195 | ``` 196 | 3 {𝓖 𝓝}[(𝑥)] = (𝓖) dip {𝓝}[(𝑥)] 197 | ``` 198 | 199 | To prove that this rule holds, let's apply it to `(𝑥)` and check that the result is `𝓖 𝓜`. 200 | 201 | ``` 202 | (𝑥) (𝓖) dip {𝓝}[(𝑥)] 203 | ╰─────────╯ by def. of dip 204 | 𝓖 (𝑥) {𝓝}[(𝑥)] 205 | ╰───────────╯ by def of abstraction 206 | 𝓖 𝓝 207 | ╰──╯ by def of abstraction 208 | (𝑥) {𝓖 𝓝}[(𝑥)] 209 | ``` 210 | 211 | ### Rule 4 212 | This rule is to be applied when the expression consist of 213 | a quote that contains `𝑥` followed by a list of terms which 214 | contain `𝑥` (if they didn't we would have used rule 0). 215 | 216 | ``` 217 | 4 {(𝓝) 𝓜}[(𝑥)] = ({𝓝}[(𝑥)]) cosp {𝓜}[(𝑥)] 218 | ``` 219 | Let's apply it to `(𝑥)`: 220 | 221 | ``` 222 | (𝑥) ({𝓝}[(𝑥)]) cosp {𝓜}[(𝑥)] 223 | ╰──────────────────╯ by def. of cosp 224 | ((𝑥) {𝓝}[(𝑥)]) (𝑥) {𝓜}[(𝑥)] 225 | ╰───────────╯ by def. of abstraction 226 | (𝓝) (𝑥) {𝓜}[(𝑥)] 227 | ╰────────────╯ by def. of abstraction 228 | (𝓝) 𝓜 229 | ╰─────╯ by def. of abstraction 230 | (𝑥) {(𝓝) 𝓜}[(𝑥)] 231 | ``` 232 | 233 | ### Rule 5 234 | 235 | This is the rule to apply when the first term is `𝑥`. 236 | 237 | ``` 238 | 5 {𝑥 𝓜}[(𝑥)] = run {𝓜}[(𝑥)] 239 | ``` 240 | 241 | To show that rules `5` holds, let's apply it to `(𝑥)`: 242 | 243 | ``` 244 | (𝑥) run {𝓜}[(𝑥)] 245 | ╰─────╯ by def. of run 246 | 𝑥 (𝑥) {𝓜}[(𝑥)] 247 | ╰───────────╯ by def. of abstraction 248 | 𝑥 𝓜 249 | ╰──╯ by def. of abstraction 250 | (𝑥) {𝑥 𝓜}[(𝑥)] 251 | ``` 252 | 253 | ## Optimization 254 | 255 | The only optimization we mention here is the possibility of 256 | simplifying some special cases: 257 | 258 | ``` 259 | 3a {𝓖}[(𝑥)] = zap 𝓖 260 | 4a {(𝑥) 𝓜}[(𝑥)] = dup {𝓜}[(𝑥)] 261 | 4b {(𝓝)}[(𝑥)] = ({𝓝}[(𝑥)]) cons 262 | 4c {(𝑥)}[(𝑥)] = 263 | 5a {𝑥}[(𝑥)] = i 264 | 265 | ``` 266 | 267 | They can be easily checked as we did in the previous section. 268 | 269 | Note that those special cases are included in the general case when one of the expressions is empty or has a special form. 270 | 271 | Let's give just one example that shows that rule `4b` is *implied* in rule `4` when the expression 𝓜 is empty. 272 | 273 | ``` 274 | 4 {(𝓝) 𝓜}[(𝑥)] = ({𝓝}[(𝑥)]) cosp {𝓜}[(𝑥)] 275 | 4b {(𝓝)}[(𝑥)] = ({𝓝}[(𝑥)]) cons 276 | ``` 277 | 278 | It's easy to see that, under the assumption of 𝓜 being empty, the two are equivalent: 279 | 280 | ``` 281 | 282 | (𝑥) {(𝓝) 𝓜}[(𝑥)] 283 | ╰────────────╯ by rule 4 284 | (𝑥) ({𝓝}[(𝑥)]) cosp {𝓜}[(𝑥)] 285 | ╰────────╯ by hypotesis that 𝓜 is empty 286 | (𝑥) ({𝓝}[(𝑥)]) cosp {}[(𝑥)] 287 | ╰──────────────────╯ by def. of cosp 288 | ((𝑥) ({𝓝}[(𝑥)])) (𝑥) {}[(𝑥)] 289 | ╰──────╯ by rule 1 290 | ((𝑥) ({𝓝}[(𝑥)])) (𝑥) zap 291 | ╰──────╯ by def. of zap 292 | ((𝑥) ({𝓝}[(𝑥)])) 293 | ╰───────────────╯ by def. of cons 294 | (𝑥) ({𝓝}[(𝑥)]) cons 295 | ╰──────────────────╯ by rule 4b 296 | (𝑥) {(𝓝)}[(𝑥)] 297 | 298 | ``` 299 | ## Quotes 300 | As said at the beginning. combinators only operate on quotes. 301 | 302 | This is need since (as in CL and 𝜆-calculus) there is no distinction between functions (programs) and data. Quotes are what make this distinction. 303 | 304 | Note that we have assumed that quotes are *transparent*. i.e. that reductions may happen within a quote. This has the advantage that all expressions are reduced to their minimal form but also has the disadvantage of having to compute any single quotes even if they might be discarded at a later time. *Opaque* quotes allow for lazy evaluation and the content of a quote is evaluated only when (and if) it is really need, not before. 305 | 306 | From the abstraction algorithm there is no difference as the two type of quotes are equivalent. 307 | 308 | ## The Church-Rosser theorem 309 | The Church-Rosser theorem, plays a key role in the evaluation of an expression and the fact that it holds for concatenative combinators is an assumption for the abstraction algorithm to work. It is also essential for the discussion on transparte/opaque quotes in the preceding section. 310 | 311 | It can be formulated in many different ways, this is one of them: 312 | 313 | > Let 𝓐 ⊳ 𝓑 be the reduction of the expression 𝓐 to the expression 𝓑; then 314 | > 𝓤 ⊳ 𝓧 ∧ 𝓤 ⊳ 𝓨 ⇒ ∃𝓩: 𝓧 ⊳ 𝓩 ∧ 𝓨 ⊳ 𝓩 315 | 316 | In plain words, if an expression 𝓤 can be reduced to two different expressions 𝓧 and 𝓨, then there is an expression 𝓩 to whom both 𝓧 and 𝓨 can be reduced. 317 | 318 | Which means that the strategy of reductions is irrelevant as any of them will lead to the same expression 𝓩. 319 | 320 | While the general proof of this theorem is quite complex, in our specific case (concatenative combinators that only act on quotes) it's pretty straightforwad to convince ourselves that the theorem holds. 321 | 322 | Let's consider the following expression: 323 | ``` 324 | (𝓊) A (𝓋) B 325 | ``` 326 | where `A` and `B` are combinators and `𝓊` and `𝓋` are generic expressions. 327 | 328 | The only interesting case is when both of them can be reduced (i.e. they both are a *redex*). Let's consider one step of reduction. 329 | 330 | If we reduce `(𝓊) A`, there will be no consequence on `(𝓋) B`, since it is already a redex. 331 | 332 | If we reduce `(𝓋) B` the result can be: 333 | - a redex itself, which brings us in the same situation we were before the reduction, 334 | - a non reducible expression like `(𝓈) (𝓉) C`. 335 | 336 | The resulting expression `(𝓊) A (𝓈) (𝓉) C` now contains only one redex (`(𝓊) A`) becase the combinator `C` only operates on quotes and `A` is unquoted. 337 | 338 | This reasoning can be repeated for a more general case but it's easy to see that the redex in an expressions to do not interfere with each other and, hence, the order in which they are reduced is irrelevant for the end result. 339 | 340 | 341 | ## Conclusion 342 | We have defined an abstraction algorithm for Concatenative combinators that is simple enough to be implemented and even being applied by hand 343 | 344 | This frees us from the need of handling variables when using Concatenative Combinators. 345 | 346 | We have also argumented around the validity of the Church-Rosser theorem that is an assumption for the abstraction algorithm to work. 347 | 348 | Here are the list of all the abstraction rules (including the special cases): 349 | 350 | ``` 351 | 1 {}[(𝑥)] = zap 352 | 2 {𝓝 𝓖}[(𝑥)] = {𝓝}[(𝑥)] 𝓖 353 | 3 {𝓖 𝓝}[(𝑥)] = (𝓖) dip {𝓝}[(𝑥)] 354 | 3a {𝓖}[(𝑥)] = zap 𝓖 355 | 4 {(𝓝) 𝓜}[(𝑥)] = ({𝓝}[(𝑥)]) cosp {𝓜}[(𝑥)] 356 | 4a {(𝑥) 𝓜}[(𝑥)] = dup {𝓜}[(𝑥)] 357 | 4b {(𝓝)}[(𝑥)] = ({𝓝}[(𝑥)]) cons 358 | 4c {(𝑥)}[(𝑥)] = 359 | 5 {𝑥 𝓜}[(𝑥)] = run {𝓜}[(𝑥)] 360 | 5a {𝑥}[(𝑥)] = i 361 | 362 | 363 | ``` 364 | 365 | ## Bibliography 366 | 367 | [1] *The Theory of Concatenative Combinators*, 368 | Brent Kerby (bkerby at byu dot net). 369 | Completed June 19, 2002. Updated February 5, 2007. 370 | ([link](http://tunes.org/~iepos/joy.html)) 371 | 372 | 373 | [2] *Lambda-Calculus and Combinators, an introduction*, 374 | J. Roger Hindley, Jonathan P. Seldin 375 | ([link](http://www.cambridge.org/9780521898850)) 376 | -------------------------------------------------------------------------------- /src/abstract.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** (C) by Remo Dentato (rdentato@gmail.com) 3 | ** 4 | ** This software is distributed under the terms of the MIT license: 5 | ** https://opensource.org/licenses/MIT 6 | */ 7 | 8 | #include 9 | #include "libs.h" 10 | #include "dict.h" 11 | 12 | 13 | static int check_tail(char *skp_chk, char *frst, int var) 14 | { 15 | char *s; 16 | s = skp(skp_chk, frst); 17 | if (!errno) { 18 | if (*s == '\0') return 1; // no tail 19 | while (*s) { 20 | s = skp("&+![@]",s); 21 | if (*s == '@' && s[1] == var) 22 | return 2; // var occurs in tail 23 | s++; 24 | } 25 | return 3; // var does not occur in tail 26 | } 27 | 28 | return 0; 29 | } 30 | 31 | int select_rule(char *expr, char *frst, int var) 32 | { 33 | int ret = 0; 34 | char skp_chk[16]; 35 | 36 | _dbgtrc("RULE: expr: '%s' frst: '%s' var: '%c'",expr,frst?frst:"",var); 37 | 38 | // 1 {}[(@)] = zap 39 | // 2 {#..#}[(@)] = zap #..# 40 | // 3 {#..# %..%}[(@)] = (#..#) dip {%..%}[(@)] 41 | skp("&+s",expr, &expr); 42 | if (*expr == '\0') return 1; 43 | if (frst == NULL) return 2; 44 | if (frst > expr) return 3; 45 | 46 | // 4 {@}[(@)] = i 47 | // 5 {@ $..$}[(@)] = run {$..$}[(@)] 48 | // 6 {@ #..#}[(@)] = i #..# 49 | strcpy(skp_chk,"@x&*s"); 50 | skp_chk[1] = var; 51 | ret = check_tail(skp_chk, frst, var); 52 | if (ret) return 3+ret; 53 | 54 | // 7 {(@)}[(@)] = 55 | // 8 {(@) $..$}[(@)] = dup {$..$}[(@)] 56 | // 9 {(@) #..#}[(@)] = #..# 57 | strcpy(skp_chk,"(&*s@x&*s)&*s"); 58 | skp_chk[5] = var; 59 | ret = check_tail(skp_chk, frst, var); 60 | if (ret) return 6+ret; 61 | 62 | // 10 {(%..%)}[(@)] = ({%..%}[(@)]) cons 63 | // 11 {(%..%) $..$}[(@)] = (({%..%}[(@)]) cons) sip {$..$}[(@)] 64 | // 12 {(%..%) #..#}[(@)] = ({%..%}[(@)]) cons #..# 65 | ret = check_tail("&()&*s", frst, var); 66 | if (ret) return 9+ret; 67 | 68 | return ret; 69 | } 70 | 71 | // #..# $..$ 72 | // ^------ returns the pointer to the first 73 | // term that contains the variable, 74 | char *occurs(char *expr, int var) 75 | { 76 | char *s, *end; 77 | char *ret = NULL; 78 | char *t; 79 | 80 | s = expr; 81 | s = skp("&+s",s); 82 | while (*s && (ret == NULL)) { 83 | 84 | end = skp("&()\1$&.\1{\1}\2&+![ (]\3",s); 85 | 86 | if (errno) break; 87 | 88 | // found a term between s and end 89 | // Check if @x occurs in the term. 90 | t = s; 91 | _dbgtrc("ABST: term= '%.*s' (%c)",(int)(end-s),s,var); 92 | while (t0 && isspace(s[l1-1])) l1--; 124 | } 125 | _dbgtrc("ABST: rule: %d '%.*s' '%s' ",rule, l1,s,frst?frst:""); 126 | } 127 | 128 | switch (rule) { 129 | // 1 {}[(@)] = zap 130 | case 1 : vecprintf(buf,"$z "); 131 | break; 132 | // 2 {#..#}[(@)] = zap #..# 133 | case 2 : vecprintf(buf,"$z %s", expr); 134 | break; 135 | 136 | // 3 {#..# %..%}[(@)] = (#..#) dip {%..%}[(@)] 137 | case 3 : s = frst; 138 | while (s>expr && isspace(s[-1])) s--; 139 | vecprintf(buf,"(%.*s) $D ", ((int)(s-expr)),expr); 140 | abstract_var(var,frst,buf); 141 | break; 142 | // 4 {@}[(@)] = i 143 | case 4: vecprintf(buf,"$i "); 144 | break; 145 | 146 | // 5 {@ $..$}[(@)] = run {$..$}[(@)] 147 | case 5: vecprintf(buf,"$r "); 148 | skp("@&d&*s",frst,&frst); 149 | abstract_var(var,frst,buf); 150 | break; 151 | 152 | // 6 {@ #..#}[(@)] = i #..# 153 | case 6: vecprintf(buf,"$i %s ", expr+2); 154 | break; 155 | 156 | // 7 {(@)}[(@)] = 157 | case 7: 158 | break; 159 | 160 | // 8 {(@) $..$}[(@)] = dup {$..$}[(@)] 161 | case 8: vecprintf(buf,"$d "); 162 | skp("&()&*s",frst,&frst); 163 | abstract_var(var,frst,buf); 164 | break; 165 | 166 | // 9 {(@) #..#}[(@)] = #..# 167 | case 9: skp("&()&*s",frst,&frst); 168 | vecprintf(buf,"%s ", frst); 169 | break; 170 | 171 | // 10 {(%..%)}[(@)] = ({%..%}[(@)]) cons 172 | // 12 {(%..%) #..#}[(@)] = ({%..%}[(@)]) cons #..# 173 | case 10: 174 | case 12: 175 | skp("&()",frst,&s); 176 | if (s>frst) s[-1] = '\0'; 177 | skp("&+s",s,&s); 178 | vecprintf(buf,"("); 179 | abstract_var(var,frst+1,buf); 180 | vecprintf(buf,") $c %s ",s); 181 | break; 182 | 183 | // 11 {(%..%) $..$}[(@)] = ({%..%}[(@)]) cosp {$..$}[(@)] 184 | case 11: 185 | skp("&()",frst,&s); 186 | if (s>frst) s[-1] = '\0'; 187 | skp("&+s",s,&s); 188 | vecprintf(buf,"("); 189 | abstract_var(var,frst+1,buf); 190 | vecprintf(buf,") $C "); 191 | abstract_var(var,s,buf); 192 | break; 193 | 194 | } 195 | 196 | } 197 | 198 | char *abstract(char *expr, int exprlen, int var, char *name, int namelen) 199 | { 200 | char *ret = NULL; 201 | vec_t buf = NULL; 202 | int from, to; 203 | 204 | char *dup_expr; 205 | 206 | dup_expr = (exprlen <=0)? dupstr(expr) : dupnstr(expr,exprlen); 207 | throwif(!dup_expr,EINVAL); 208 | 209 | if (var <= 0) { 210 | 211 | from = '1'; to ='0'-var; 212 | for( char *s=dup_expr; *s ; s++) { 213 | if (*s == '@' && isdigit(s[1]) && s[1]>to) 214 | to = s[1]; 215 | } 216 | } 217 | else { 218 | from = var; 219 | to = var; 220 | } 221 | 222 | 223 | for (var = from; var <= to; var++) { 224 | buf = vecnew(char); 225 | vecputs(buf, ""); 226 | 227 | abstract_var(var, dup_expr, buf); 228 | free(dup_expr); 229 | 230 | dup_expr = (char *) vectoarray(buf); 231 | } 232 | 233 | throwif(!dup_expr,EINVAL); 234 | 235 | char *s = dup_expr; 236 | while (*s) s++; 237 | while (s > dup_expr && isspace(s[-1])) *--s = '\0'; 238 | 239 | _dbgtrc("ABST: pre-name: '%s'",dup_expr); 240 | if (name) { 241 | buf = vecnew(char); 242 | vecprintf(buf,"%.*s__%s",namelen,name,dup_expr); 243 | _dbgtrc("VLN: %d",veccount(buf)); 244 | dup_expr = (char *) vectoarray(buf); 245 | dup_expr[namelen] = '\0'; 246 | dup_expr[namelen+1] = to; 247 | } 248 | 249 | // printf(" > !def %s = %s\n",dup_expr, dup_expr+namelen+1); 250 | 251 | ret = dup_expr; 252 | return ret; 253 | } 254 | -------------------------------------------------------------------------------- /src/abstract.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** (C) by Remo Dentato (rdentato@gmail.com) 3 | ** 4 | ** This software is distributed under the terms of the MIT license: 5 | ** https://opensource.org/licenses/MIT 6 | */ 7 | 8 | #ifndef ABSTRACT_H 9 | #define ABSTRACT_H 10 | 11 | 12 | char *abstract(char *expr, int exprlen, int var, char *name, int namelen); 13 | #endif -------------------------------------------------------------------------------- /src/dict.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** (C) by Remo Dentato (rdentato@gmail.com) 3 | ** 4 | ** This software is distributed under the terms of the MIT license: 5 | ** https://opensource.org/licenses/MIT 6 | */ 7 | 8 | // DICTIONARY ****** 9 | 10 | #include "libs.h" 11 | #include "dict.h" 12 | #include "abstract.h" 13 | 14 | static vec_t dict = NULL; 15 | 16 | char **search_word(char *word) 17 | { 18 | char **v=vec(dict); 19 | for (int k=0; kbody_start && isspace(body_end[-1])) --body_end; 53 | 54 | if (body_end>body_start && body_end[-1] == ')') body_end--; 55 | 56 | _dbgtrc("[%.*s] '%.*s'",(int)(name_end - name_start),name_start,(int)(body_end - body_start),body_start); 57 | if (name_end <= name_start || body_end <= body_start) { 58 | fprintf(stderr, "Error: Syntax error in definition.\n"); 59 | fprintf(stderr, "%s\n",body_start); 60 | return 0; 61 | } 62 | 63 | dict_str = abstract(body_start,body_end - body_start,var,name_start, name_end - name_start); 64 | 65 | char **w = search_word(dict_str); 66 | 67 | if (w) { // Word already exists -> replace it! 68 | free(*w); 69 | *w = dict_str; 70 | } 71 | else { // New word -> add to the dictionary 72 | vecpush(dict, &dict_str); 73 | } 74 | 75 | return 0; 76 | } 77 | 78 | int add_word(char *def) 79 | { 80 | int var =0; 81 | char *dict_str; 82 | 83 | char *name_start, *name_end; 84 | 85 | 86 | if (!def || !*def) return 0; 87 | _dbgtrc("Defining %s",def); 88 | 89 | name_end = def; 90 | name_start = skp("&*s",def); 91 | name_end = skp("&I&?[?!]",name_start); 92 | 93 | if (*name_end == '/') { 94 | var = -atoi(name_end+1); 95 | } 96 | 97 | _dbgtrc("NAME: '%.*s'",(int)(name_end - name_start),name_start); 98 | 99 | skp("&>=&*s",name_end,&def); 100 | 101 | _dbgtrc("DEF: %d '%s'",var,def); 102 | 103 | if (name_end <= name_start || def<=name_end) { 104 | fprintf(stderr, "Error: Syntax error in definition.\n"); 105 | fprintf(stderr, "%s\n",def); 106 | return 0; 107 | } 108 | 109 | dict_str = abstract(def,0,var,name_start, name_end - name_start); 110 | 111 | char **w = search_word(dict_str); 112 | 113 | if (w) { // Word already exists -> replace it! 114 | free(*w); 115 | *w = dict_str; 116 | } 117 | else { // New word -> add to the dictionary 118 | vecpush(dict, &dict_str); 119 | } 120 | 121 | return 0; 122 | } 123 | 124 | int del_word(char *word) 125 | { 126 | char **w; 127 | char **x; 128 | 129 | while (isspace(*word)) word++; 130 | w = search_word(word); 131 | if (w) { 132 | // swap the defintion with the 133 | // last one and remove the last one. 134 | free(*w); *w = NULL; 135 | x = vectop(dict); 136 | *w = *x; 137 | vecdrop(dict); 138 | } 139 | return 0; 140 | } 141 | 142 | int del_dict() 143 | { 144 | char **w; 145 | int k; 146 | 147 | k = veccount(dict); 148 | if (veccount(dict) > 0) { 149 | w = vec(dict); 150 | while (k-- > 0) { 151 | free(*w++); 152 | } 153 | } 154 | vecclean(dict); 155 | return 0; 156 | } 157 | 158 | int list_words(FILE *out,int def) 159 | { 160 | char **v = vec(dict); 161 | char *s; 162 | char arity; 163 | 164 | for (int k=0; k]&*d&?[?!]\2" 24 | 25 | #endif -------------------------------------------------------------------------------- /src/eval.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** (C) by Remo Dentato (rdentato@gmail.com) 3 | ** 4 | ** This software is distributed under the terms of the MIT license: 5 | ** https://opensource.org/licenses/MIT 6 | */ 7 | 8 | #include "libs.h" 9 | #include "dict.h" 10 | #include "eval.h" 11 | 12 | #include "hardwired.h" 13 | 14 | vec_t init_stack() 15 | { 16 | vec_t stack = vecnew(term_t); 17 | throwif(!stack,ENOMEM); 18 | return stack; 19 | } 20 | 21 | void print_stack(vec_t stack) 22 | { 23 | term_t *v; 24 | 25 | v=vec(stack); 26 | fputs("|-> ",stdout); 27 | for (int k = 0; kstr); 37 | trm->str = NULL; 38 | trm->len = 0; 39 | } 40 | 41 | void wipe_stack(vec_t stack) 42 | { 43 | term_t *v; 44 | v=vec(stack); 45 | for(int k=0; k0) { 60 | wipeterm(vectop(stack)); 61 | vecdrop(stack); 62 | } 63 | return 0; 64 | } 65 | 66 | int pushterm(vec_t stack, char *start, int len) 67 | { 68 | char *term; 69 | 70 | term = malloc(len+1 + (*start == '`')); 71 | throwif(!term, ENOMEM); 72 | 73 | memcpy(term,start,len); 74 | 75 | if (*start == '`') { 76 | *term = '('; 77 | term[len++] = ')'; 78 | } 79 | 80 | term[len] = '\0'; 81 | 82 | vecpush(stack,&((term_t){term, len})); 83 | return 0; 84 | } 85 | 86 | 87 | static char *reduce_word(vec_t stack, char *word) 88 | { 89 | char *ret = NULL; 90 | char *s; 91 | char arity; 92 | term_t *trm; 93 | char **w; 94 | 95 | w = search_word(word); 96 | 97 | if (w) { 98 | s = skp("&*.",*w); 99 | s++; arity = *s++ - '0'; 100 | if (veccount(stack) < arity+1) return NULL; 101 | for (int i=1; i <= arity; i++) { 102 | trm =vectop(stack,-i); 103 | if (!trm || trm->str[0] != '(') return NULL; 104 | } 105 | 106 | _dbgtrc("WORD: '%s' '%s'",*w,s); 107 | if (*s) ret = dupstr(s); 108 | popterm(stack); 109 | } 110 | return ret; 111 | } 112 | 113 | static int is_num(char *start, int len, int *num) 114 | { 115 | int n=0; 116 | char *end = start+len; 117 | char *tmp; 118 | int ret = 0; 119 | 120 | _dbgtrc("ISNUM: '%s' ",start); 121 | if (*start != '(') return 0; 122 | 123 | skp("&*s&+d",start+1); 124 | _dbgtrc("ISNUM: '%s' (err: %d)",start,errno); 125 | if (!errno) { 126 | n = atoi(start+1); 127 | ret=1; 128 | } 129 | else { 130 | 131 | skp("&+[( ]",start,&tmp); 132 | 133 | _dbgtrc("ISNUM: '%s' (err: %d)",start,errno); 134 | if (errno) return 0; 135 | 136 | start = tmp; 137 | 138 | skp("&+d&*[ )]",start,&tmp); 139 | _dbgtrc("ISNUM: '%s' (err: %d)",start,errno); 140 | 141 | if (errno) return 0; 142 | 143 | n = atoi(start); 144 | 145 | while ((start = tmp) < end ) { 146 | skp("&*s++&*s)",start, &tmp); 147 | _dbgtrc("ISNUM: '%s' (err: %d)",start,errno); 148 | if (errno) return 0; 149 | ret=1; 150 | n++; 151 | } 152 | } 153 | 154 | *num = n; 155 | return ret; 156 | } 157 | 158 | static char *reduce_incr(vec_t stack, char *word) 159 | { 160 | char *ret = NULL; 161 | int n = -1; 162 | 163 | term_t *trm =vectop(stack,-1); 164 | if (!trm) return NULL; 165 | 166 | if (!is_num(trm->str,trm->len,&n)) 167 | return NULL; 168 | 169 | ret = malloc(20); 170 | throwif(!ret,ENOMEM); 171 | 172 | sprintf(ret,"%d",n+1); 173 | popterm(stack); 174 | popterm(stack); 175 | 176 | return ret; 177 | } 178 | 179 | static char *reduce_decr(vec_t stack, char *word) 180 | { 181 | char *ret = NULL; 182 | int n=0; 183 | 184 | term_t *trm =vectop(stack,-1); 185 | if (!trm) return NULL; 186 | 187 | if (!is_num(trm->str,trm->len,&n)) 188 | return NULL; 189 | 190 | if (n>0) { 191 | ret = malloc(20); 192 | throwif(!ret,ENOMEM); 193 | sprintf(ret,"%d",n-1); 194 | popterm(stack); 195 | } 196 | popterm(stack); 197 | 198 | return ret; 199 | 200 | } 201 | 202 | static char *reduce_eq_zero(vec_t stack, char *word) 203 | { 204 | char *ret = NULL; 205 | int n=0; 206 | 207 | term_t *trm =vectop(stack,-1); 208 | if (!trm) return NULL; 209 | 210 | if (!is_num(trm->str,trm->len,&n)) 211 | return NULL; 212 | 213 | ret = dupstr(n==0?"$T":"$F"); 214 | throwif(!ret,ENOMEM); 215 | 216 | popterm(stack); 217 | popterm(stack); 218 | 219 | return ret; 220 | 221 | } 222 | 223 | static char *reduce_add(vec_t stack, char *word) 224 | { 225 | char *ret = NULL; 226 | int n1=0, n2=0; 227 | 228 | term_t *trm1 =vectop(stack,-1); 229 | if (!trm1 || !is_num(trm1->str,trm1->len,&n1)) 230 | return NULL; 231 | 232 | term_t *trm2 =vectop(stack,-2); 233 | if (!trm2 || !is_num(trm2->str,trm2->len,&n2)) 234 | return NULL; 235 | 236 | n2 += n1; 237 | 238 | ret = malloc(20); 239 | throwif(!ret,ENOMEM); 240 | 241 | sprintf(ret,"%d",n2); 242 | 243 | popterm(stack); 244 | popterm(stack); 245 | popterm(stack); 246 | 247 | return ret; 248 | 249 | } 250 | 251 | static char *reduce_sub(vec_t stack, char *word) 252 | { 253 | char *ret = NULL; 254 | int n1=0, n2=0; 255 | 256 | term_t *trm1 =vectop(stack,-1); 257 | if (!trm1 || !is_num(trm1->str,trm1->len,&n1)) 258 | return NULL; 259 | 260 | term_t *trm2 =vectop(stack,-2); 261 | if (!trm2 || !is_num(trm2->str,trm2->len,&n2)) 262 | return NULL; 263 | 264 | n2 -= n1; 265 | if (n2<0) n2 = 0; 266 | 267 | ret = malloc(20); 268 | throwif(!ret,ENOMEM); 269 | 270 | sprintf(ret,"%d",n2); 271 | 272 | popterm(stack); 273 | popterm(stack); 274 | popterm(stack); 275 | 276 | return ret; 277 | 278 | } 279 | 280 | static char *reduce_mult(vec_t stack, char *word) 281 | { 282 | char *ret = NULL; 283 | int n1=0, n2=0; 284 | 285 | term_t *trm1 =vectop(stack,-1); 286 | if (!trm1 || !is_num(trm1->str,trm1->len,&n1)) 287 | return NULL; 288 | 289 | term_t *trm2 =vectop(stack,-2); 290 | if (!trm2 || !is_num(trm2->str,trm2->len,&n2)) 291 | return NULL; 292 | 293 | n2 *= n1; 294 | 295 | ret = malloc(20); 296 | throwif(!ret,ENOMEM); 297 | 298 | sprintf(ret,"%d",n2); 299 | 300 | popterm(stack); 301 | popterm(stack); 302 | popterm(stack); 303 | 304 | return ret; 305 | } 306 | 307 | 308 | typedef struct { 309 | int16_t len; 310 | char *word; 311 | char *list; 312 | char *(*reduce)(vec_t stack, char *word); 313 | } hardwired_t; 314 | 315 | hardwired_t hardwired [] = { 316 | {2,"++", "++ = *hardwired* // increment", reduce_incr}, 317 | {2,"--", "-- = *hardwired* // decrement", reduce_decr}, 318 | {3,"=0?", "=0? = *hardwired* // zero?", reduce_eq_zero}, 319 | {1,"+", "+ = *hardwired* // add", reduce_add}, 320 | {1,"-", "- = *hardwired* // subtract", reduce_sub}, 321 | {1,"*", "* = *hardwired* // multiply", reduce_mult}, 322 | {0,NULL, NULL} 323 | }; 324 | 325 | void list_hardwired(FILE *out) 326 | { 327 | for (hardwired_t *hw = hardwired; hw->word; hw++) { 328 | fprintf(out," %s\n",hw->list); 329 | } 330 | } 331 | 332 | static char *reduce_hardwired(vec_t stack, char *word) 333 | { 334 | int ishardwired=0; 335 | for (hardwired_t *hw = hardwired; hw->word; hw++) { 336 | ishardwired = (strncmp(word, hw->word,hw->len) == 0) 337 | && ( word[hw->len] == '\0'); 338 | _dbgtrc("HW: '%s' '%s' %d",word,hw->word,ishardwired); 339 | if (ishardwired) { 340 | return hw->reduce(stack,word); 341 | } 342 | } 343 | return NULL; 344 | } 345 | 346 | char *reduce_transparent(vec_t stack, char *word) 347 | { 348 | char *ret = NULL; 349 | term_t *trm; 350 | int cnt = -1; 351 | int nest = 1; 352 | int size = 0; 353 | int pos; 354 | 355 | _dbgtrc("Closing"); 356 | while (1) { 357 | trm = vectop(stack, cnt); 358 | _dbgtrc("@ %d (%d) '%s'",cnt,nest,trm->str); 359 | if (!trm) return NULL; 360 | if (trm->str[0] == '}') nest++; 361 | if (trm->str[0] == '{') nest--; 362 | if (nest == 0) break; 363 | size += trm->len; 364 | cnt --; 365 | } 366 | _dbgtrc("Close at:[%d] size: %d '%s'",cnt,size, trm->str); 367 | ret = malloc(size-3*cnt+1); 368 | throwif(!ret, ENOMEM); 369 | 370 | pos = 0; 371 | ret[pos++] = '('; 372 | for (int i=cnt+1; i<0; i++) { 373 | trm = vectop(stack, i); 374 | pos += sprintf(ret+pos,"%s ",trm->str); 375 | } 376 | while (pos>0 && isspace(ret[pos-1])) pos--; 377 | ret[pos++] = ')'; 378 | ret[pos] = '\0'; 379 | 380 | for (int i=0; i>=cnt; i--) { 381 | popterm(stack); 382 | } 383 | 384 | return ret; 385 | } 386 | 387 | static char *reduce(vec_t stack) 388 | { 389 | term_t *trm; 390 | 391 | if (veccount(stack) == 0) return NULL; 392 | 393 | trm = vectop(stack); 394 | 395 | if (trm->str[0] == '{') return NULL; 396 | 397 | if (trm->str[0] == '}') return reduce_transparent(stack, trm->str); 398 | 399 | if (trm->str[0] == '(') return NULL; 400 | 401 | if (trm->str[0] == '$') return hw_combinator(stack, trm->str); 402 | 403 | if (isdigit(trm->str[0])) return hw_numeral(stack, trm->str); 404 | 405 | if (isalpha(trm->str[0]) || trm->str[0] == '_') 406 | return reduce_word(stack, trm->str); 407 | 408 | return reduce_hardwired(stack, trm->str); 409 | 410 | } 411 | 412 | typedef struct { 413 | char *ln; 414 | char *pos; 415 | } expr_t; 416 | 417 | static int eval_expressions(vec_t stack, vec_t expressions, int trace) 418 | { 419 | char *end; 420 | expr_t *cur_expr; 421 | char *new_expr; 422 | int prev_depth; 423 | 424 | // Evaluate all the expressions in the stack; 425 | while (veccount(expressions) > 0) { 426 | cur_expr = vectop(expressions); 427 | new_expr = NULL; 428 | 429 | skp("&+s",cur_expr->pos,&(cur_expr->pos)); 430 | 431 | _dbgtrc("EVAL: %s (%p)",cur_expr->pos,(void *)(cur_expr->pos)); 432 | 433 | // evaluate current expressions till the end, 434 | // unless a new expression is provided! 435 | while (!new_expr && *(cur_expr->pos)) { 436 | 437 | // terms are sequence of letters/numbers or a quote 438 | // (a sequence of terms in parenthesis ) 439 | end = skp(WORD_DEF "&D\3&()\4$&.\5`&+!s\6{\7}\7",cur_expr->pos); 440 | 441 | if (end > cur_expr->pos) { //found a term! 442 | _dbgtrc("PUSH: '%.*s' (%d)",(int)(end-cur_expr->pos),cur_expr->pos,(int)(end-cur_expr->pos)); 443 | // Push the term on the evaluation stack 444 | pushterm(stack, cur_expr->pos, (end - cur_expr->pos)); 445 | 446 | cur_expr->pos = skp("&+s",end); 447 | 448 | if (trace) print_stack(stack); 449 | prev_depth = veccount(stack); // Used to avoid printing the same stack twice 450 | 451 | // evaluate the top of the stack. 452 | // If the result is an expression, return it! 453 | new_expr = reduce(stack); 454 | _dbgtrc("REDUCED: '%s'",new_expr?new_expr:"\"\""); 455 | 456 | if (new_expr) { 457 | vecpush(expressions,&((expr_t){new_expr, new_expr})); 458 | _dbgtrc("PUSHED: %s (%d)",new_expr,veccount(expressions)); 459 | break; 460 | } 461 | 462 | if (trace && (veccount(stack) != prev_depth)) 463 | print_stack(stack); 464 | } 465 | else { 466 | fprintf(stderr, "ERROR: Invalid term.\n"); 467 | fprintf(stderr, "%.*s... (%02X)\n",(int)(cur_expr->pos - cur_expr->ln + 1), cur_expr->pos,*cur_expr->pos); 468 | return 1; 469 | } 470 | } 471 | if (!new_expr) { // We reached the end of the expression. Done for this line! 472 | free(cur_expr->ln); 473 | cur_expr->ln = NULL; 474 | vecdrop(expressions); 475 | } 476 | } 477 | return 0; 478 | } 479 | 480 | // We'll manage the inevitable recursion with an explicit stack of expressions instead 481 | // of recursive calls to the eval function. 482 | 483 | int eval(vec_t stack, char *ln, int trace) 484 | { 485 | int ret=0; 486 | char *firstln; 487 | vec_t exprs=NULL; 488 | 489 | if (!ln || !*ln) return 0; 490 | 491 | // Create the stack for expressions to be evaluated 492 | exprs = vecnew(expr_t); 493 | throwif(!exprs,ENOMEM); 494 | 495 | // Push the line got as an argument as the first line 496 | firstln = dupstr(ln); // Will be freed by eval_expressions 497 | throwif(!firstln,ENOMEM); 498 | vecpush(exprs,&((expr_t){firstln,firstln})); 499 | 500 | // evaluate the stack of expressions 501 | ret = eval_expressions(stack,exprs,trace); 502 | 503 | // Get rid of the stack of expressions 504 | exprs = vecfree(exprs); 505 | 506 | return ret; 507 | } 508 | 509 | -------------------------------------------------------------------------------- /src/eval.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** (C) by Remo Dentato (rdentato@gmail.com) 3 | ** 4 | ** This software is distributed under the terms of the MIT license: 5 | ** https://opensource.org/licenses/MIT 6 | */ 7 | 8 | #ifndef EVAL_H 9 | #define EVAL_H 10 | 11 | #include "libs.h" 12 | #include "dict.h" 13 | 14 | vec_t init_stack(); 15 | vec_t free_stack(vec_t stack); 16 | void print_stack(vec_t stack); 17 | void wipe_stack(vec_t stack); 18 | int eval(vec_t stack, char *ln, int trace); 19 | void list_hardwired(FILE *out); 20 | 21 | 22 | typedef struct { 23 | char *str; 24 | uint16_t len; 25 | } term_t; 26 | 27 | int popterm(vec_t stack); 28 | int pushterm(vec_t stack, char *start, int len); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/gerku.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** (C) by Remo Dentato (rdentato@gmail.com) 3 | ** 4 | ** This software is distributed under the terms of the MIT license: 5 | ** https://opensource.org/licenses/MIT 6 | */ 7 | 8 | #include "gerku.h" 9 | 10 | char *gerku_version="GERKU 0.0.7-beta (C) 2021 https://github.com/rdentato/gerku"; 11 | 12 | int main(int argc, char *argv[]) 13 | { 14 | int ret = 0; 15 | vec_t stack = NULL; 16 | int start_repl = 1; 17 | char *default_grk = "grk/default.grk"; 18 | 19 | try { 20 | 21 | vrgver(gerku_version); 22 | vrgoptions(argc,argv) { 23 | vrgopt("-v", "Version") { 24 | fprintf(stderr, "%s\n", gerku_version); 25 | } 26 | 27 | vrgopt("-r", "Run mode (no REPL)") { 28 | start_repl = 0; 29 | } 30 | 31 | vrgopt("-d [default]", "Replace default combinators") { 32 | default_grk = vrgoptarg; 33 | } 34 | } 35 | 36 | if (default_grk) init_dict(default_grk); 37 | 38 | stack = init_stack(); 39 | 40 | //load files 41 | for (int i = vrgargn; i 37 | #include 38 | #include 39 | 40 | #include "libs.h" 41 | #include "dict.h" 42 | #include "eval.h" 43 | #include "repl.h" 44 | 45 | #endif 46 | 47 | -------------------------------------------------------------------------------- /src/grk/all.grk: -------------------------------------------------------------------------------- 1 | # gerku 2 | 3 | !def (@) (@) W = (@1) (@1) @2 4 | !def (@) (@) K = @2 5 | !def (@) (@) O = @1 6 | !def (@) (@) (@) B = ((@1) @2) @3 7 | !def (@) (@) (@) C = (@2) (@1) @3 8 | !def (@) I = @1 9 | !def (@) D = @1 @1 10 | !def (@) (@) E = @2 @1 11 | !def (@) (@) J = (@1 @2) 12 | !def (@) Q = ((@1)) 13 | !def (@) (@) (@) S = ((@1) @2) (@1) @3 14 | 15 | # mlatu 16 | !def @ d = @1 @1 17 | !def @ @ c = (@1 @1) 18 | !def @ q = (@1) 19 | !def (@) u = @1 20 | !def @ r = 21 | !def @ @ s = @2 @1 22 | 23 | # mlatu fullname 24 | !def dup = d 25 | !def concat = c 26 | !def quote = q 27 | !def unquote = u 28 | !def remove = r 29 | !def swap = s 30 | -------------------------------------------------------------------------------- /src/grk/default.grk: -------------------------------------------------------------------------------- 1 | # Combinators from the article: 2 | # "The Theory of Concatenative Combinators" 3 | # by Brent Kerby 4 | 5 | !def zap/1 = 6 | !def i = @1 7 | !def unit = ((@1)) 8 | !def rep = @1 @1 9 | !def m = (@1) @1 10 | !def run = @1 (@1) 11 | !def dup = (@1) (@1) 12 | !def k = @2 13 | !def z/2 = @1 14 | !def nip = (@2) 15 | !def sap = @2 @1 16 | !def t = (@2) @1 17 | !def dip = @2 (@1) 18 | !def cat = (@1 @2) 19 | !def swat = (@2 @1) 20 | !def swap = (@2) (@1) 21 | !def cons = ((@1) @2) 22 | !def cosp = ((@1) @2) (@1) 23 | !def take = (@2 (@1)) 24 | !def tack = (@1 (@2)) 25 | !def sip = (@1) @2 (@1) 26 | !def w = (@1) (@1) @2 27 | !def peek = (@1) (@2) (@1) 28 | !def cake = ((@1) @2) (@2 (@1)) 29 | !def poke = (@3) (@2) 30 | !def b = ((@1) @2) @3 31 | !def c = (@2) (@1) @3 32 | !def dig = (@2) (@3) (@1) 33 | !def bury = (@3) (@1) (@2) 34 | !def flip = (@3) (@2) (@1) 35 | !def s = ((@1) @2) (@1) @3 36 | !def s1 = ((@1) @2) @4 (@1) @3 37 | !def j = ((@2) (@1) @4) (@3) @4 38 | !def j1 = ((@2) @5 (@1) @4) (@3) @4 39 | 40 | # Booleans 41 | 42 | !def true = @2 43 | !def false/2 = @1 44 | 45 | !def true!/1 = true 46 | !def false!/1 = false 47 | 48 | !def not = (true) (false) @1 49 | 50 | !def and = (false) (@1) @2 51 | !def or = (@1) (true) @2 52 | 53 | !def xor = (@1) ((@1) not) @2 54 | 55 | !def nand = ((@1) (@2) and) not 56 | !def nor = ((@1) (@2) or ) not 57 | !def xnor = ((@1) (@2) xor) not 58 | 59 | 60 | # Pairs 61 | !def pair = (@2) (@3) @1 62 | !def second = (true) @1 63 | !def first = (false) @1 64 | 65 | 66 | # Naturals (quoted Church-like numerals) 67 | !def zero/2 = @1 68 | !def succ = {{@1} @2} succ_ @1 69 | !def succ_ = {{@1} @2} 70 | 71 | #!def succ = {{@1} {{@1} @2 } } @1 72 | 73 | ## Operations on Naturals 74 | 75 | !def add = (@2) (succ) @1 76 | !def mult = (zero) ((@2) add) @1 77 | 78 | # Move first element to second, set first as succ of second 79 | 80 | !def pred_step = {(@1) pred_second} pred_pair 81 | !def pred_first = i pred_first_q 82 | !def pred_second = i pred_second_q 83 | !def pred_first_q/2 = (@1) 84 | !def pred_second_q = (@2) 85 | !def pred_pair = {(@1) ((@1) succ)} 86 | 87 | !def pred = ((zero) (zero)) (pred_step) @1 pred_first 88 | 89 | 90 | 91 | ## Some Constant 92 | 93 | !def one = (zero) succ 94 | !def two = (one) succ 95 | !def three = (two) succ 96 | !def four = (three) succ 97 | !def five = (four) succ 98 | !def six = (five) succ 99 | !def seven = (six) succ 100 | !def eight = (seven) succ 101 | !def nine = (eight) succ 102 | !def ten = (nine) succ 103 | 104 | ## Predicates on numbers 105 | 106 | !def zero? = (true) (false!) @1 107 | 108 | !def leq? = (@2) (@1) sub zero? 109 | !def geq? = (@1) (@2) sub zero? 110 | !def eq? = (@1) (@2) leq? (@1) (@2) geq? and 111 | 112 | 113 | # lists 114 | !def nil = () 115 | !def lcons = (@1) ((@2)) cons 116 | 117 | # Y Combinators 118 | # \f.(\x.f (x x)) (\x.f (x x)) 119 | 120 | 121 | # Loop 122 | 123 | ## For 124 | !def for = ((@1)) (i @2) @3 i 125 | 126 | ## While 127 | # Execute F on X while top of stack is not zero 128 | 129 | 130 | ## factorial 131 | 132 | !def fact_step = (@1) (@2) mult (@2) pred 133 | !def fact = (1) (@1) (fact_step) (@1) for zap 134 | 135 | # With hardwired numbers and operations 136 | !def Fact_step = (@1) (@2) * (@2) -- 137 | !def Fact = (1) (@1) (Fact_step) (@1) for zap 138 | 139 | !def fct = ((@1) -- fct (@1) *) (1) (@1) zero? i 140 | 141 | -------------------------------------------------------------------------------- /src/grk/gerku.grk: -------------------------------------------------------------------------------- 1 | # gerku 2 | 3 | !def (@) (@) W = (@1) (@1) @2 4 | !def (@) (@) K = @2 5 | !def (@) (@) O = @1 6 | !def (@) (@) (@) B = ((@1) @2) @3 7 | !def (@) (@) (@) C = (@2) (@1) @3 8 | !def (@) I = @1 9 | !def (@) D = (@1) (@1) 10 | !def (@) (@) E = (@2) (@1) 11 | !def (@) (@) J = (@1 @2) 12 | !def (@) Q = ((@1)) 13 | !def (@) (@) (@) S = ((@1) @2) (@1) @3 14 | -------------------------------------------------------------------------------- /src/grk/kerby.grk: -------------------------------------------------------------------------------- 1 | # Combinators from the article: 2 | # "The Theory of Concatenative Combinators" 3 | # by Brent Kerby 4 | 5 | !def (@) zap = 6 | !def (@) i = @1 7 | !def (@) unit = ((@1)) 8 | !def (@) rep = @1 @1 9 | !def (@) m = (@1) @1 10 | !def (@) run = @1 (@1) 11 | !def (@) dup = (@1) (@1) 12 | !def (@) (@) k = @2 13 | !def (@) (@) z = @1 14 | !def (@) (@) nip = (@2) 15 | !def (@) (@) sap = @2 @1 16 | !def (@) (@) t = (@2) @1 17 | !def (@) (@) dip = @2 (@1) 18 | !def (@) (@) cat = (@1 @2) 19 | !def (@) (@) swat = (@2 @1) 20 | !def (@) (@) swap = (@2) (@1) 21 | !def (@) (@) cons = ((@1) @2) 22 | !def (@) (@) take = (@2 (@1)) 23 | !def (@) (@) tack = (@1 (@2)) 24 | !def (@) (@) sip = (@1) @2 (@1) 25 | !def (@) (@) w = (@1) (@1) @2 26 | !def (@) (@) peek = (@1) (@2) (@1) 27 | !def (@) (@) cake = ((@1) @2) (@2 (@1)) 28 | !def (@) (@) (@) poke = (@3) (@2) 29 | !def (@) (@) (@) b = ((@1) @2) @3 30 | !def (@) (@) (@) c = (@2) (@1) @3 31 | !def (@) (@) (@) dig = (@2) (@3) (@1) 32 | !def (@) (@) (@) bury = (@3) (@1) (@2) 33 | !def (@) (@) (@) flip = (@3) (@2) (@1) 34 | !def (@) (@) (@) s = ((@1) @2) (@1) @3 35 | !def (@) (@) (@) (@) s1 = ((@1) @2) @4 (@1) @3 36 | !def (@) (@) (@) (@) j = ((@2) (@1) @4) (@3) @4 37 | !def (@) (@) (@) (@) (@) j1 = ((@2) @5 (@1) @4) (@3) @4 38 | 39 | # This is a combinator that create a list out of an atom 40 | !def @ q = (@1) -------------------------------------------------------------------------------- /src/grk/mlatu.grk: -------------------------------------------------------------------------------- 1 | # Standard combinators for mlatu 2 | 3 | !def @ d = @1 @1 4 | !def (@) (@) c = (@1 @2) 5 | !def @ q = (@1) 6 | !def (@) u = @1 7 | !def @ r = 8 | !def @ @ s = @2 @1 9 | 10 | !def dup = d 11 | !def concat = c 12 | !def quote = q 13 | !def unquote = u 14 | !def remove = r 15 | !def swap = s 16 | 17 | -------------------------------------------------------------------------------- /src/grk/runtest1.grk: -------------------------------------------------------------------------------- 1 | !del !all 2 | !def @ @ XX = (@2 (@1)) 3 | (x) (y) XX 4 | !print 5 | !list -------------------------------------------------------------------------------- /src/grk/types.grk: -------------------------------------------------------------------------------- 1 | 2 | # Booleans (quoted) 3 | 4 | !def (@) (@) true = (@2) 5 | !def (@) (@) false = (@1 6 | 7 | ## Constant functions 8 | !def (@) true! = (true) 9 | !def (@) false! = (false) 10 | 11 | ## Logical operators 12 | 13 | !def (@) not = (true) (false) @1 14 | !def (@) (@) and = (false) (@1) @2 15 | !def (@) (@) or = (@1) (true) @2 16 | !def (@) (@) xor = ((@1)) ((@1) not) @2 i 17 | 18 | !def (@) (@) nand = ((@1) (@2) and) not i 19 | !def (@) (@) nor = ((@1) (@2) or ) not i 20 | !def (@) (@) xnor = ((@1) (@2) xor) not i 21 | 22 | # Naturals (quoted Church-like numerals) 23 | 24 | !def (@) zero = 25 | !def (@) succ = (cons) sip @1 26 | 27 | ## Operatons on Naturals 28 | 29 | !def (@) (@) add = (@2) (succ) @1 30 | !def (@) (@) mult = (zero) ((@2) add i) @1 31 | 32 | ## Some Constant 33 | 34 | !def one = (zero) succ 35 | !def two = (one) succ 36 | !def three = (two) succ 37 | !def four = (three) succ 38 | !def five = (four) succ 39 | 40 | ## Predicate to detect zero 41 | !def (@) zero? = ((true)) (false!) @1 i 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/hardwired.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "hardwired.h" 4 | 5 | // $i i = @1 6 | char *hw_comb_i(vec_t stack, char *word) 7 | { 8 | char *ret = NULL; 9 | 10 | term_t *trm1 =vectop(stack,-1); 11 | if (!trm1 || trm1->str[0] != '(') 12 | return NULL; 13 | 14 | ret = malloc(trm1->len-1); 15 | throwif(!ret, ENOMEM); 16 | 17 | memcpy(ret,trm1->str+1,trm1->len-2); 18 | ret[trm1->len-2] = '\0'; 19 | 20 | popterm(stack); 21 | popterm(stack); // remove comb 22 | return ret; 23 | } 24 | 25 | // $z zap = 26 | char *hw_comb_z(vec_t stack, char *word) 27 | { 28 | char *ret = NULL; 29 | 30 | term_t *trm1 =vectop(stack,-1); 31 | if (!trm1 || trm1->str[0] != '(') 32 | return NULL; 33 | 34 | popterm(stack); 35 | popterm(stack); // remove comb 36 | return ret; 37 | } 38 | 39 | // $r run = @1 (@1) 40 | char *hw_comb_r(vec_t stack, char *word) 41 | { 42 | char *ret = NULL; 43 | 44 | term_t *trm1 =vectop(stack,-1); 45 | if (!trm1 || trm1->str[0] != '(') 46 | return NULL; 47 | 48 | ret = malloc(2 * trm1->len + 4); 49 | throwif(!ret, ENOMEM); 50 | 51 | sprintf(ret,"%.*s %s",trm1->len-2,trm1->str+1,trm1->str); 52 | 53 | popterm(stack); 54 | popterm(stack); // remove comb 55 | return ret; 56 | } 57 | 58 | // $d dup = (@1) (@1) 59 | char *hw_comb_d(vec_t stack, char *word) 60 | { 61 | char *ret = NULL; 62 | 63 | term_t *trm1 =vectop(stack,-1); 64 | if (!trm1 || trm1->str[0] != '(') 65 | return NULL; 66 | 67 | ret = malloc(2 * trm1->len + 4); 68 | throwif(!ret, ENOMEM); 69 | 70 | sprintf(ret,"%s %s",trm1->str,trm1->str); 71 | 72 | popterm(stack); 73 | popterm(stack); // remove comb 74 | return ret; 75 | } 76 | 77 | // $c cons = ((@1) @2) 78 | char *hw_comb_c(vec_t stack, char *word) 79 | { 80 | char *ret = NULL; 81 | 82 | term_t *trm1 =vectop(stack,-2); 83 | if (!trm1 || trm1->str[0] != '(') 84 | return NULL; 85 | 86 | term_t *trm2 =vectop(stack,-1); 87 | if (!trm2 || trm2->str[0] != '(') 88 | return NULL; 89 | 90 | ret = malloc(trm1->len + trm2->len +8); 91 | throwif(!ret,ENOMEM); 92 | 93 | sprintf(ret,"(%s %.*s)",trm1->str,trm2->len-2,trm2->str+1); 94 | 95 | popterm(stack); 96 | popterm(stack); 97 | popterm(stack); 98 | 99 | return ret; 100 | } 101 | 102 | // $C cosp = ((@1) @2) (@1) 103 | char *hw_comb_C(vec_t stack, char *word) 104 | { 105 | char *ret = NULL; 106 | 107 | term_t *trm1 =vectop(stack,-2); 108 | if (!trm1 || trm1->str[0] != '(') 109 | return NULL; 110 | 111 | term_t *trm2 =vectop(stack,-1); 112 | if (!trm2 || trm2->str[0] != '(') 113 | return NULL; 114 | 115 | ret = malloc(2*trm1->len + trm2->len +8); 116 | throwif(!ret,ENOMEM); 117 | 118 | sprintf(ret,"(%s %.*s) %s",trm1->str,trm2->len-2,trm2->str+1,trm1->str); 119 | 120 | popterm(stack); 121 | popterm(stack); 122 | popterm(stack); 123 | 124 | return ret; 125 | } 126 | 127 | // $D dip = @2 (@1) 128 | char *hw_comb_D(vec_t stack, char *word) 129 | { 130 | char *ret = NULL; 131 | 132 | term_t *trm1 =vectop(stack,-2); 133 | if (!trm1 || trm1->str[0] != '(') 134 | return NULL; 135 | 136 | term_t *trm2 =vectop(stack,-1); 137 | if (!trm2 || trm2->str[0] != '(') 138 | return NULL; 139 | 140 | ret = malloc(trm1->len + trm2->len +8); 141 | throwif(!ret,ENOMEM); 142 | 143 | sprintf(ret,"%.*s %s",trm2->len-2,trm2->str+1,trm1->str); 144 | 145 | popterm(stack); 146 | popterm(stack); 147 | popterm(stack); 148 | 149 | return ret; 150 | } 151 | 152 | // $S sip = (@1) @2 (@1) 153 | char *hw_comb_S(vec_t stack, char *word) 154 | { 155 | char *ret = NULL; 156 | 157 | term_t *trm1 =vectop(stack,-2); 158 | if (!trm1 || trm1->str[0] != '(') 159 | return NULL; 160 | 161 | term_t *trm2 =vectop(stack,-1); 162 | if (!trm2 || trm2->str[0] != '(') 163 | return NULL; 164 | 165 | ret = malloc(2*trm1->len + trm2->len +8); 166 | throwif(!ret,ENOMEM); 167 | 168 | sprintf(ret,"%s %.*s %s",trm1->str,trm2->len-2,trm2->str+1,trm1->str); 169 | 170 | popterm(stack); 171 | popterm(stack); 172 | popterm(stack); 173 | 174 | return ret; 175 | } 176 | 177 | // $T true = (@2) 178 | char *hw_comb_T(vec_t stack, char *word) 179 | { 180 | char *ret = NULL; 181 | 182 | term_t *trm1 =vectop(stack,-2); 183 | if (!trm1 || trm1->str[0] != '(') 184 | return NULL; 185 | 186 | term_t *trm2 =vectop(stack,-1); 187 | if (!trm2 || trm2->str[0] != '(') 188 | return NULL; 189 | 190 | ret = trm2->str; 191 | trm2->str = NULL; 192 | 193 | char *s=ret; 194 | while (s[1]) { 195 | s[0] = s[1]; 196 | s++; 197 | } 198 | if (s>ret && s[-1] == ')') s[-1] = '\0'; 199 | popterm(stack); 200 | popterm(stack); 201 | popterm(stack); 202 | 203 | return ret; 204 | } 205 | 206 | // $F false/2 = (@1) 207 | char *hw_comb_F(vec_t stack, char *word) 208 | { 209 | char *ret = NULL; 210 | 211 | term_t *trm1 =vectop(stack,-2); 212 | if (!trm1 || trm1->str[0] != '(') 213 | return NULL; 214 | 215 | term_t *trm2 =vectop(stack,-1); 216 | if (!trm2 || trm2->str[0] != '(') 217 | return NULL; 218 | 219 | ret = trm1->str; 220 | trm1->str = NULL; 221 | 222 | char *s=ret; 223 | while (s[1]) { 224 | s[0] = s[1]; 225 | s++; 226 | } 227 | if (s>ret && s[-1] == ')') s[-1] = '\0'; 228 | popterm(stack); 229 | popterm(stack); 230 | popterm(stack); 231 | 232 | return ret; 233 | } 234 | 235 | char *hw_define(vec_t stack, char *word) 236 | { 237 | char *ret = NULL; 238 | 239 | term_t *trm1 =vectop(stack,-2); 240 | if (!trm1 || trm1->str[0] != '(') 241 | return NULL; 242 | 243 | term_t *trm2 =vectop(stack,-1); 244 | if (!trm2 || trm2->str[0] != '(') 245 | return NULL; 246 | 247 | def_word(trm2->str,trm1->str); 248 | 249 | popterm(stack); 250 | popterm(stack); 251 | popterm(stack); 252 | 253 | return ret; 254 | } 255 | 256 | // $N next = ((@1) @2) (@2) 257 | char *hw_comb_nxt(vec_t stack, char *word) 258 | { 259 | char *ret = NULL; 260 | 261 | term_t *trm1 =vectop(stack,-2); 262 | if (!trm1 || trm1->str[0] != '(') 263 | return NULL; 264 | 265 | term_t *trm2 =vectop(stack,-1); 266 | if (!trm2 || trm2->str[0] != '(') 267 | return NULL; 268 | 269 | ret = malloc(trm1->len + 2*trm2->len +8); 270 | throwif(!ret,ENOMEM); 271 | 272 | sprintf(ret,"(%s %.*s) %s",trm1->str,trm2->len-2,trm2->str+1,trm2->str); 273 | 274 | popterm(stack); 275 | popterm(stack); 276 | popterm(stack); 277 | 278 | return ret; 279 | } 280 | 281 | char *hw_closepar(vec_t stack, char *word) 282 | { 283 | char *ret = NULL; 284 | term_t *trm; 285 | int cnt = -1; 286 | int nest = 1; 287 | int size = 0; 288 | int pos; 289 | 290 | _dbgtrc("Closing"); 291 | while (1) { 292 | trm = vectop(stack, cnt); 293 | _dbgtrc("@ %d (%d) '%s'",cnt,nest,trm->str); 294 | if (!trm) return NULL; 295 | if (trm->str[0] == '$') { 296 | if (trm->str[1] == '}') nest++; 297 | if (trm->str[1] == '{') nest--; 298 | } 299 | if (nest == 0) break; 300 | size += trm->len; 301 | cnt --; 302 | } 303 | _dbgtrc("Close at:[%d] size: %d '%s'",cnt,size, trm->str); 304 | ret = malloc(size-3*cnt+1); 305 | throwif(!ret, ENOMEM); 306 | 307 | pos = 0; 308 | ret[pos++] = '('; 309 | for (int i=cnt+1; i<0; i++) { 310 | trm = vectop(stack, i); 311 | pos += sprintf(ret+pos,"%s ",trm->str); 312 | } 313 | while (pos>0 && isspace(ret[pos-1])) pos--; 314 | ret[pos++] = ')'; 315 | ret[pos] = '\0'; 316 | 317 | for (int i=0; i>=cnt; i--) { 318 | popterm(stack); 319 | } 320 | 321 | return ret; 322 | } 323 | 324 | 325 | char *hw_combinator(vec_t stack, char *word) 326 | { 327 | switch (word[1]) { 328 | case 'i' : return hw_comb_i(stack, word); // i 329 | case 'z' : return hw_comb_z(stack, word); // zap 330 | case 'r' : return hw_comb_r(stack, word); // run 331 | case 'd' : return hw_comb_d(stack, word); // dup 332 | case 'c' : return hw_comb_c(stack, word); // cons 333 | case 'C' : return hw_comb_C(stack, word); // cosp 334 | case 'D' : return hw_comb_D(stack, word); // dip 335 | case 'S' : return hw_comb_S(stack, word); // sip 336 | 337 | case 'n' : return hw_comb_nxt(stack, word); // sip 338 | 339 | case 'T' : return hw_comb_T(stack, word); // true 340 | case 'F' : return hw_comb_F(stack, word); // false 341 | 342 | case '=' : return hw_define(stack, word); 343 | 344 | case '}' : return hw_closepar(stack, word); 345 | } 346 | return NULL; 347 | } 348 | 349 | char *hw_numeral(vec_t stack, char *word) 350 | { 351 | char *ret = NULL; 352 | 353 | int32_t size = 0; 354 | // Check for args to be quotes 355 | term_t *trm1 =vectop(stack,-1); 356 | if (!trm1 || trm1->str[0] != '(') return NULL; 357 | 358 | term_t *trm2 =vectop(stack,-2); 359 | if (!trm2 || trm2->str[0] != '(') return NULL; 360 | 361 | int n = atoi(word); 362 | 363 | size = n + (trm2->len) + n * (trm1->len-1+1)+4; 364 | 365 | ret = malloc(size); 366 | throwif(!ret,ENOMEM); 367 | 368 | char *s; 369 | s = ret; 370 | for (int i=0; istr,trm2->len); 375 | s += trm2->len; 376 | 377 | for (int i=0; istr+1,trm1->len-1); 380 | s += trm1->len-1; 381 | } 382 | if (s>ret &&s[-1] == ')') s--; 383 | *s = '\0'; 384 | throwif(s >= ret+size,ERANGE); 385 | 386 | popterm(stack); 387 | popterm(stack); 388 | popterm(stack); 389 | 390 | return ret; 391 | } 392 | 393 | -------------------------------------------------------------------------------- /src/hardwired.h: -------------------------------------------------------------------------------- 1 | #ifndef HARDWIRED_H 2 | #define HARDWIRED_H 3 | 4 | #include "libs.h" 5 | #include "dict.h" 6 | #include "abstract.h" 7 | #include "eval.h" 8 | 9 | char *hw_combinator(vec_t stack, char *word); 10 | char *hw_numeral(vec_t stack, char *word); 11 | 12 | #endif -------------------------------------------------------------------------------- /src/libs.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** (C) by Remo Dentato (rdentato@gmail.com) 3 | ** 4 | ** This software is distributed under the terms of the MIT license: 5 | ** https://opensource.org/licenses/MIT 6 | */ 7 | 8 | #define LIBS_MAIN 9 | #include "libs.h" 10 | 11 | // Don't trust strdup exists 12 | char *dupstr(char *s) 13 | { 14 | char *new_s; 15 | int n = strlen(s)+1; 16 | if ((new_s = malloc(n))) memcpy(new_s,s,n); 17 | return new_s; 18 | } 19 | 20 | // Don't trust strdup exists 21 | char *dupnstr(char *s, int n) 22 | { 23 | char *new_s; 24 | if ((new_s = malloc(n+1))) { memcpy(new_s,s,n); new_s[n] = '\0'; } 25 | return new_s; 26 | } 27 | -------------------------------------------------------------------------------- /src/libs.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** (C) by Remo Dentato (rdentato@gmail.com) 3 | ** 4 | ** This software is distributed under the terms of the MIT license: 5 | ** https://opensource.org/licenses/MIT 6 | */ 7 | 8 | #ifndef LIBS_H 9 | #define LIBS_H 10 | 11 | #ifdef LIBS_MAIN 12 | #define DBG_MAIN 13 | #define SKP_MAIN 14 | #define VEC_MAIN 15 | #define TRY_MAIN 16 | #define STK_MAIN 17 | #define VRG_MAIN 18 | #ifdef USE_LINENOISE 19 | #define LINENOISE_MAIN 20 | #endif 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "libs/dbg.h" 29 | #include "libs/skp.h" 30 | #include "libs/vec.h" 31 | #include "libs/try.h" 32 | #include "libs/stk.h" 33 | #include "libs/vrg.h" 34 | #ifdef USE_LINENOISE 35 | #include "libs/linenoise.h" 36 | #endif 37 | 38 | char *dupstr(char *s); 39 | char *dupnstr(char *s, int n); 40 | 41 | #endif // LIBS_H 42 | -------------------------------------------------------------------------------- /src/libs/dbg.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** (C) by Remo Dentato (rdentato@gmail.com) 3 | ** 4 | ** This software is distributed under the terms of the MIT license: 5 | ** https://opensource.org/licenses/MIT 6 | */ 7 | 8 | // # DEBUG AND TESTING MACROS 9 | // 10 | // ## Index 11 | // - ## Usage 12 | // - ## Disabling functions 13 | // - ## Debugging Levels 14 | // - ## No DEBUG 15 | // - ## Standard Includes 16 | // - ## Testing 17 | // - ## Tracking 18 | // - ## Profiling 19 | // - ## Grouping 20 | // - ## Memory usage 21 | 22 | #ifndef DBG_VERSION 23 | #define DBG_VERSION 0x0103000C 24 | #define DBG_VERSION_STR "dbg 1.3.0-rc" 25 | 26 | // ## Usage 27 | // To enable the debugging functions, DEBUG must be defined before 28 | // including dbg.h. See section "## Debugging levels" for more details. 29 | // 30 | // Note that NDEBUG has higher priority than DEBUG: if NDEBUG 31 | // is defined, then DEBUG will be undefined. 32 | 33 | #ifdef NDEBUG 34 | #ifdef DEBUG 35 | #undef DEBUG 36 | #endif 37 | #endif 38 | 39 | // ## Disabling functions 40 | // For each function provided by `dbg.h`, there is a *disabled* 41 | // counterpart whose name begins with one underscore. 42 | // This avoids having to comment out or delete debugging functions that 43 | // are not needed at the moment but can be useful at a later time. 44 | // Note that from a C standard point of view, this is a compliant usage 45 | // as all these symbols have file scope. (7.1.3) 46 | // The meaning and usage of `DBG_OFF()` is detailed in the section 47 | // titled "Grouping". 48 | 49 | #define DBG_OFF(...) 50 | 51 | #define _dbgmsg DBG_OFF 52 | #define _dbgprt DBG_OFF 53 | #define _dbgtst(...) while(0) 54 | #define _dbginf DBG_OFF 55 | #define _dbgtrc DBG_OFF 56 | #define _dbgwrn DBG_OFF 57 | #define _dbgerr DBG_OFF 58 | #define _dbgchk DBG_OFF 59 | #define _dbgmst DBG_OFF 60 | #define _dbgtrk DBG_OFF 61 | #define _dbgptr DBG_OFF 62 | #define _dbgclk DBG_OFF 63 | #define _dbgblk while(0) 64 | 65 | // ## No DEBUG 66 | // If DEBUG is not defined, the dbgxxx macros should atill be 67 | // defined to ensure the code compiles, but they should do nothing. 68 | 69 | #define DBG_ON DBG_OFF 70 | #define dbgmsg _dbgmsg 71 | #define dbgprt _dbgprt 72 | #define dbgtst _dbgtst 73 | #define dbginf _dbginf 74 | #define dbgtrc _dbgtrc 75 | #define dbgtrk _dbgtrk 76 | #define dbgwrn _dbgwrn 77 | #define dbgerr _dbgerr 78 | #define dbgchk _dbgchk 79 | #define dbgmst _dbgmst 80 | #define dbgtrk _dbgtrk 81 | #define dbgclk _dbgclk 82 | #define dbgblk _dbgblk 83 | #define dbgptr _dbgptr 84 | 85 | #ifdef DEBUG 86 | 87 | // ## Standard Includes 88 | 89 | #include 90 | #include 91 | #include 92 | #include 93 | 94 | // This variable will always be 0. It's used to suppress warnings 95 | // and ensures that some debugging code is not optimized. 96 | static volatile int dbg_zero = 0; 97 | 98 | // ## Debugging Level 99 | // 100 | // DEBUG If undefined, removes all debug instructions. 101 | // If defined sets the level of debugging and enables 102 | // the dbg functions: 103 | // 104 | // level enabled functions 105 | // ------------ -------------------------- 106 | // DEBUG_ERROR dbgerr() dbgmsg() dbgprt() 107 | // DEBUG_WARN as above plus dbgwrn() 108 | // DEBUG_INFO as above plus dbginf() 109 | // DEBUG_TEST all dbg functions except dbgptr() 110 | // DEBUG_MEM all dbg functions plus redefinition 111 | // of malloc(), free(), realloc(), calloc(), 112 | // strdup() and strndup() (if available) 113 | 114 | #define DEBUG_ERROR 0 115 | #define DEBUG_WARN 1 116 | #define DEBUG_INFO 2 117 | #define DEBUG_TEST 3 118 | #define DEBUG_MEM 4 119 | 120 | // ## Printing messages 121 | // 122 | // dbgprt(char *, ...) Prints a message on stderr (works as printf(...)). 123 | // dbgmsg(char *, ...) Prints a message on stderr (works as printf(...)). 124 | // Adds filename and line of the instruction. 125 | // dbginf(char *, ...) Prints an "INFO:" message depending on the DEBUG level. 126 | // dbgwrn(char *, ...) Prints a "WARN:" message depending on the DEBUG level. 127 | // dbgerr(char *, ...) Prints a "FAIL:" message. 128 | 129 | #undef dbgprt 130 | #define dbgprt(...) (fprintf(stderr,"" __VA_ARGS__), dbg_zero=0) 131 | 132 | #undef dbgmsg 133 | #define dbgmsg(...) (fprintf(stderr,"" __VA_ARGS__), \ 134 | fprintf(stderr," \xF%s:%d\n",__FILE__,__LINE__), \ 135 | dbg_zero=0) 136 | #undef dbgerr 137 | #define dbgerr(...) dbgmsg("FAIL: " __VA_ARGS__) 138 | 139 | #if DEBUG >= DEBUG_WARN 140 | #undef dbgwrn 141 | #define dbgwrn(...) dbgmsg("WARN: " __VA_ARGS__) 142 | #endif 143 | 144 | #if DEBUG >= DEBUG_INFO 145 | #undef dbginf 146 | #define dbginf(...) dbgmsg("INFO: " __VA_ARGS__) 147 | #endif 148 | 149 | #if DEBUG >= DEBUG_TEST 150 | #undef dbgtrc 151 | #define dbgtrc(...) dbgmsg("TRCE: " __VA_ARGS__) 152 | #endif 153 | 154 | // ## Testing 155 | // dbgtst(char *, ...){ ...} Starts a test case. 156 | // If DEBUG is undefined or lower than DEBUG_TEST, do nothing. 157 | // Stastics will be collected separately for each test case by dbg 158 | // 159 | // dbgchk(test, char *, ...) Perform the test and set errno (0: ok, 1: ko). If test fails 160 | // prints a message on stderr (works as printf(...)). 161 | // If DEBUG is undefined or lower than DEBUG_TEST, do nothing. 162 | // 163 | // dbgmst(test, char *, ...) As dbgchk() but if test fails exit the program with abort(). 164 | // 165 | // dbgblk {...} Execute the block only if DEBUG is defined as DBGLVEL_TEST. 166 | // 167 | 168 | #if DEBUG >= DEBUG_TEST 169 | 170 | #undef dbgtst 171 | #define dbgtst(...) for (dbg_zero = 1; \ 172 | dbg_zero-- && !dbgmsg("TST[: " __VA_ARGS__); \ 173 | dbg_zero = dbgmsg("TST]:")) 174 | 175 | #undef dbgchk 176 | #define dbgchk(e,...) \ 177 | do { \ 178 | int dbg_err=!(e); \ 179 | fprintf(stderr,"%s: (%s) \xF%s:%d\n",(dbg_err?"FAIL":"PASS"),#e,__FILE__,__LINE__); \ 180 | if (dbg_err) { fprintf(stderr," : " __VA_ARGS__); fputc('\n',stderr); } \ 181 | errno = dbg_err; \ 182 | } while(0) 183 | 184 | #undef dbgmst 185 | #define dbgmst(e,...) do { dbgchk(e,__VA_ARGS__); if (errno) abort();} while(0) 186 | 187 | #undef dbgblk 188 | #define dbgblk if (dbg_zero) ; else 189 | 190 | // ## Tracking 191 | // Tracking is inspired to a novel idea presented by Kartik Agaram in his blog 192 | // quite some time ago: http://akkartik.name/post/tracing-tests 193 | // 194 | // The basic idea is that we can flexibly set up test by monitoring the appearnce 195 | // (or the lack) of certain strings in a log. This will lessen the ties between 196 | // the test and the code. 197 | // 198 | // Consider the following test that checks that the string "INGESTION SUCCESSFUL" 199 | // appears in the log but not one of the other two strings: 200 | // 201 | // dbgtrk(",=INGESTION SUCCESSFUL" ",!INGESTION FAILED" ",!INGESTION HALTED") { 202 | // ... some code and function calls 203 | // } 204 | // 205 | // As long as the code emits those messages you are free to re-factor the code as 206 | // you please whereas a test like 207 | // 208 | // ingest_err = ingest(file); 209 | // dbgchk(ingest_err == 0, "Ingestion failed with code %d",ingest_err); 210 | // if (!ingest_err) ... 211 | // 212 | // relies on the exitance of a specific integer variable. For example you could not 213 | // simply write `if (!ingest(file)) ...` just because the test is there and needs 214 | // ingest_err to be there as well. 215 | // Sure, you still have to count on the code to emit certain strings, but this is 216 | // a much weaker coupling between the code and the test than before. 217 | // 218 | // Note that the tracking is *not* done during the execution as this might have 219 | // too great of an impact on the performance. Instead the check is done on the 220 | // log itself by the `dbg` tool (see `dbg.c`). 221 | // 222 | // dbgtrk(char *) {...} Specify the strings to be tracked within the scope of the 223 | // block. If DEBUG is not defined or lower than DEBUG_TEST, 224 | // execute the block but don't mark track strings. 225 | // 226 | // Theres NO comma between the strings, the separator is 227 | // the first character of the first pattern. 228 | // Example: dbgtrk(",!test" ",=prod") { 229 | // ... 230 | // } 231 | // 232 | // _dbgtrk(char *) {...} Execute the block but don't mark string tracking. 233 | // 234 | 235 | #undef dbgtrk 236 | #define dbgtrk(x) for (int dbg_trk=!dbgmsg("TRK[: %s",x); \ 237 | dbg_trk; \ 238 | dbg_trk=dbgmsg("TRK]:")) 239 | 240 | // ## Profiling 241 | // The `dbgchk` function is intended as a quick and dirty way to determine the elapsed time 242 | // spent in a block of code. It's accuracy depends on the implementation of the `clock()` 243 | // function. The elapsed time is reported as a fraction: 244 | // Examples: 245 | // CLK]: +64000/1000000 sec. ut_test.c:67 -> 64 milliseconds 246 | // CLK]: +64/1000 sec. ut_test.c:67 -> 64 milliseconds 247 | // 248 | // dbgclk(char *, ...) {...} Measure the time needed to execute the block. If DEBUG is 249 | // undefined or lower than DEBUG_TEST, execute the block but 250 | // don't measure the elapsed time. 251 | // 252 | // _dbgclk(char *, ...) {...} Execute the block but don't measure time. 253 | // 254 | 255 | typedef struct { 256 | clock_t clk; time_t time; 257 | char tstr[32]; struct tm *time_tm; 258 | long int elapsed; 259 | } dbgclk_t; 260 | 261 | #undef dbgclk 262 | #define dbgclk(...) \ 263 | for (dbgclk_t dbg_ = {.elapsed = -1}; \ 264 | \ 265 | (dbg_.elapsed < 0) && ( \ 266 | time(&dbg_.time), dbg_.time_tm=localtime(&dbg_.time), \ 267 | strftime(dbg_.tstr,32,"%Y-%m-%d %H:%M:%S",dbg_.time_tm),\ 268 | dbgprt("CLK[: %s ",dbg_.tstr), dbgmsg(__VA_ARGS__) , \ 269 | dbg_.clk = clock() \ 270 | ) ; \ 271 | \ 272 | dbg_.elapsed=(long int)(clock()-dbg_.clk), \ 273 | dbgmsg("CLK]: +%ld/%ld sec.", dbg_.elapsed, (long int)CLOCKS_PER_SEC) \ 274 | ) 275 | 276 | // ## Grouping 277 | // 278 | // Say you are testing/developing a function and you placed some debugging 279 | // code related to that function in various places in your code. 280 | // Now that everything works fine, you have to go back and delete or 281 | // comment out them to avoid having your log cluttered with now useless 282 | // messages. A little bit inconvenient, expecially if you feel 283 | // you may have to re-enable them at a later stage at a later time. 284 | // In cases like this, you can plan in advance and define a "debugging 285 | // group" and wrap the relevant messages with that group. 286 | // An example may clarify the concept better: 287 | // 288 | // // Define a group to debug/trace ingestion phase 289 | // // Set it to DBG_ON to enable it, set to DBG_OFF to disable it 290 | // #define DBG_CHECK_INGEST DBG_ON 291 | // ... 292 | // DBG_CHECK_INGEST(dbgchk(dataread > 0,"No data read! (%d)",dataread)); 293 | // ... (some functions later) 294 | // DBG_CHECK_INGEST(dbginf("Read %d read so far", dataread)); 295 | // ... (into a function further away) 296 | // DBG_CHECK_INGEST(dbgchk(feof(f),"File had still data to read!")); 297 | // 298 | // Defining `DBG_CHECK_INGEST` as `DBG_ON` the code will be compiled in, 299 | // defining it as `DBG_OFF` the code won't be compiled. 300 | // 301 | // DBG_ON Turn a debugging group ON 302 | // DBG_OFF Turn a debugging group OFF 303 | // 304 | 305 | #undef DBG_ON 306 | #define DBG_ON(...) __VA_ARGS__ 307 | 308 | #endif // DEBUG >= DEBUG_TEST 309 | 310 | #if DEBUG >= DEBUG_MEM 311 | 312 | // ## Memory usage 313 | // 314 | // If DEBUG is set to DEBUG_MEM, each call to the memory 315 | // related functions will produce a line in the log like this: 316 | // 317 | // MTRK: function(args) ptr_in size ptr_out 318 | // 319 | // 0 N P malloc, calloc, strdup, realloc(NULL,N) 320 | // P 0 0 free, realloc(P,0) 321 | // P N P realloc(P,N) 322 | // 323 | // The function dbgptr is meant to verify that a pointer to allocated 324 | // memory is "valid", i.e. that it belongs to a block previously allocated: 325 | // 326 | // dbgptr(void *p) Checks if the pointer p is witin a block 327 | // allocated with malloc(), calloc(), etc. 328 | // 329 | // It can be helpful to check for buffer overruns 330 | // Also the more common functions that cause issues with allocated 331 | // memory (strcpy, memset, ...) now emit a line in the log: 332 | // 333 | // MCHK: function(args) 32ABFE0 32ABFE4 334 | // 335 | // If the first pointer is within an allocated block, the second one must 336 | // be as well. 337 | // If there is only one pointer at the end of a MCHK: line, that pointer 338 | // must be within a block allocated with malloc. 339 | // 340 | // The checks are performed on the log by the dbg tool, not at runtime! 341 | 342 | 343 | #include 344 | #include 345 | 346 | #define dbg_write(...) (fprintf(stderr,__VA_ARGS__)) 347 | #define dbg_writeln(...) (fprintf(stderr,__VA_ARGS__) , fprintf(stderr," \xF%s:%d\n",file,line)) 348 | 349 | // The only reason we need dbg_ptr2int is that %p representation of NULL pointers is 350 | // compiler dependant. This way the pointer is uniquely converted in an integer. 351 | // We'll need it in dbg as a label, we'll never convert it back to a pointer. 352 | #define dbg_ptr2int(x) ((uintptr_t)(x)) 353 | 354 | static inline void *dbg_malloc(size_t size, char *file, int line) 355 | { 356 | dbg_write("MTRK: malloc(%zd) 0 %zd ",size,size); 357 | void *ptr = malloc(size); 358 | dbg_writeln("%zX",dbg_ptr2int(ptr)); 359 | return ptr; 360 | } 361 | 362 | static inline void dbg_free(void *ptr, char *file, int line) 363 | { 364 | dbg_writeln("MTRK: free(%zX) %zX 0 0",dbg_ptr2int(ptr),dbg_ptr2int(ptr)); 365 | free(ptr); 366 | } 367 | 368 | static inline void *dbg_calloc(size_t count, size_t size, char *file, int32_t line) 369 | { 370 | dbg_write("MTRK: calloc(%zd,%zd) 0 %zd ",count,size,count*size); 371 | void *ptr = calloc(count,size); 372 | dbg_writeln("%zX",dbg_ptr2int(ptr)); 373 | return ptr; 374 | } 375 | 376 | static inline void *dbg_realloc(void *ptr, size_t size, char *file, int32_t line) 377 | { 378 | dbg_write("MTRK: realloc(%zX,%zd) %zX %zd ",dbg_ptr2int(ptr),size,dbg_ptr2int(ptr),size); 379 | ptr = realloc(ptr,size); 380 | dbg_writeln("%zX",dbg_ptr2int(ptr)); 381 | return ptr; 382 | } 383 | 384 | // strdup() is somewhat special being a Posix but not a C standard function 385 | // I decided to provide an implementation of strdup/strndup rather than 386 | // relying on the compiler library to have it. It's not 100% correct but 387 | // I didn't want to make it more complicated than it is already. 388 | 389 | static inline char *dbg_strdup(const char *s, char *file, int line) 390 | { 391 | dbg_write("MTRK: strdup(%zX) 0 ",dbg_ptr2int(s)); 392 | size_t size = strlen(s)+1; 393 | char *ptr = (char *)malloc(size); 394 | if (ptr) strcpy(ptr,s); 395 | dbg_writeln("%zd %zX",size,dbg_ptr2int(ptr)); 396 | return ptr; 397 | } 398 | 399 | static inline char *dbg_strndup(const char *s, size_t size, char *file, int line) 400 | { 401 | dbg_write("MTRK: strndup(%zX,%zd) 0 ",dbg_ptr2int(s),size); 402 | char *ptr = (char *)malloc(size+1); 403 | if (ptr) { strncpy(ptr,s,size); ptr[size] = '\0'; } 404 | dbg_writeln("%zd %zX",size+1,dbg_ptr2int(ptr)); 405 | return ptr; 406 | } 407 | 408 | #define strdup(s) dbg_strdup(s,__FILE__, __LINE__) 409 | #define strndup(s,n) dbg_strndup(s,n,__FILE__, __LINE__) 410 | 411 | 412 | // Check boundaries for the most common functions 413 | 414 | static inline char *dbg_strcpy(char *dest, char *src,char *file, int line) 415 | { 416 | size_t size = src? strlen(src)+1 : 0; 417 | dbg_writeln("MCHK: strcpy(%zX,%zX) %zX %zX",dbg_ptr2int(dest),dbg_ptr2int(src),dbg_ptr2int(dest),dbg_ptr2int(dest+size)); 418 | return strcpy(dest,src); 419 | } 420 | 421 | static inline char *dbg_strncpy(char *dest, char *src, size_t size, char *file, int line) 422 | { 423 | dbg_writeln("MCHK: strncpy(%zX,%zX,%zd) %zX %zX",dbg_ptr2int(dest),dbg_ptr2int(src),size,dbg_ptr2int(dest),dbg_ptr2int(dest+size)); 424 | return strncpy(dest,src,size); 425 | } 426 | 427 | static inline char *dbg_strcat(char *dest, char *src,char *file, int line) 428 | { 429 | size_t size = (dest ? strlen(dest) : 0) + (src? strlen(src)+1 : 0); 430 | dbg_writeln("MCHK: strcat(%zX,%zX) %zX %zX",dbg_ptr2int(dest),dbg_ptr2int(src),dbg_ptr2int(dest),dbg_ptr2int(dest+size)); 431 | return strcpy(dest,src); 432 | } 433 | 434 | static inline char *dbg_strncat(char *dest, char *src,size_t size, char *file, int line) 435 | { 436 | dbg_writeln("MCHK: strncat(%zX,%zX,%zd) %zX %zX",dbg_ptr2int(dest),dbg_ptr2int(src),size,dbg_ptr2int(dest),dbg_ptr2int(dest+((dest ? strlen(dest) : 0) + size))); 437 | return strncpy(dest,src,size); 438 | } 439 | 440 | static inline char *dbg_memcpy(void *dest, void *src, size_t size, char *file, int line) 441 | { 442 | dbg_writeln("MCHK: memcpy(%zX,%zX,%zd) %zX %zX",dbg_ptr2int(dest),dbg_ptr2int(src),size,dbg_ptr2int(dest),dbg_ptr2int((char *)dest+size)); 443 | return memcpy(dest,src,size); 444 | } 445 | 446 | static inline char *dbg_memmove(void *dest, void *src, size_t size, char *file, int line) 447 | { 448 | dbg_writeln("MCHK: memmove(%zX,%zX,%zd) %zX %zX",dbg_ptr2int(dest),dbg_ptr2int(src),size,dbg_ptr2int(dest),dbg_ptr2int((char *)dest+size)); 449 | return memmove(dest,src,size); 450 | } 451 | 452 | static inline void *dbg_memset(void *dest, int c, size_t size, char *file, int line) 453 | { 454 | dbg_writeln("MCHK: memset(%zX,%zd) %zX %zX",dbg_ptr2int(dest),size,dbg_ptr2int(dest),dbg_ptr2int((char *)dest+size)); 455 | return memset(dest,c,size); 456 | } 457 | 458 | #define malloc(sz) dbg_malloc(sz,__FILE__, __LINE__) 459 | #define calloc(n,sz) dbg_calloc(n,sz,__FILE__, __LINE__) 460 | #define realloc(p,sz) dbg_realloc(p,sz,__FILE__, __LINE__) 461 | #define free(p) dbg_free(p,__FILE__, __LINE__) 462 | 463 | #define strcpy(d,s) dbg_strcpy(d,s,__FILE__, __LINE__) 464 | #define strncpy(d,s,n) dbg_strncpy(d,s,n,__FILE__, __LINE__) 465 | #define strcat(d,s) dbg_strcat(d,s,__FILE__, __LINE__) 466 | #define strncat(d,s,n) dbg_strncat(d,s,n,__FILE__, __LINE__) 467 | #define memcpy(d,s,n) dbg_memcpy(d,(void*)(s),n,__FILE__, __LINE__) 468 | #define memmove(d,s,n) dbg_memmove(d,(void*)(s),n,__FILE__, __LINE__) 469 | 470 | #undef dbgptr 471 | #define dbgptr(p) dbgmsg("MCHK: ptr %zX", dbg_ptr2int(p)) 472 | 473 | #endif // DEBUG >= DEBUG_MEM 474 | 475 | 476 | #endif // DEBUG 477 | 478 | #endif // DBG_H_VER 479 | -------------------------------------------------------------------------------- /src/libs/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 | #ifdef LINENOISE_MAIN 76 | 77 | #include 78 | #include 79 | #include 80 | #include 81 | #include 82 | #include 83 | #include 84 | #include 85 | #include 86 | #include 87 | #include 88 | #include 89 | #include "linenoise.h" 90 | 91 | #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 92 | #define LINENOISE_MAX_LINE 4096 93 | static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; 94 | static linenoiseCompletionCallback *completionCallback = NULL; 95 | static linenoiseHintsCallback *hintsCallback = NULL; 96 | static linenoiseFreeHintsCallback *freeHintsCallback = NULL; 97 | 98 | static struct termios orig_termios; /* In order to restore at exit.*/ 99 | static int maskmode = 0; /* Show "***" instead of input. For passwords. */ 100 | static int rawmode = 0; /* For atexit() function to check if restore is needed*/ 101 | static int mlmode = 0; /* Multi line mode. Default is single line. */ 102 | static int atexit_registered = 0; /* Register atexit just 1 time. */ 103 | static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; 104 | static int history_len = 0; 105 | static char **history = NULL; 106 | 107 | /* The linenoiseState structure represents the state during line editing. 108 | * We pass this state to functions implementing specific editing 109 | * functionalities. */ 110 | struct linenoiseState { 111 | int ifd; /* Terminal stdin file descriptor. */ 112 | int ofd; /* Terminal stdout file descriptor. */ 113 | char *buf; /* Edited line buffer. */ 114 | size_t buflen; /* Edited line buffer size. */ 115 | const char *prompt; /* Prompt to display. */ 116 | size_t plen; /* Prompt length. */ 117 | size_t pos; /* Current cursor position. */ 118 | size_t oldpos; /* Previous refresh cursor position. */ 119 | size_t len; /* Current edited line length. */ 120 | size_t cols; /* Number of columns in terminal. */ 121 | size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ 122 | int history_index; /* The history index we are currently editing. */ 123 | }; 124 | 125 | enum KEY_ACTION{ 126 | KEY_NULL = 0, /* NULL */ 127 | CTRL_A = 1, /* Ctrl+a */ 128 | CTRL_B = 2, /* Ctrl-b */ 129 | CTRL_C = 3, /* Ctrl-c */ 130 | CTRL_D = 4, /* Ctrl-d */ 131 | CTRL_E = 5, /* Ctrl-e */ 132 | CTRL_F = 6, /* Ctrl-f */ 133 | CTRL_H = 8, /* Ctrl-h */ 134 | TAB = 9, /* Tab */ 135 | CTRL_K = 11, /* Ctrl+k */ 136 | CTRL_L = 12, /* Ctrl+l */ 137 | ENTER = 13, /* Enter */ 138 | CTRL_N = 14, /* Ctrl-n */ 139 | CTRL_P = 16, /* Ctrl-p */ 140 | CTRL_T = 20, /* Ctrl-t */ 141 | CTRL_U = 21, /* Ctrl+u */ 142 | CTRL_W = 23, /* Ctrl+w */ 143 | ESC = 27, /* Escape */ 144 | BACKSPACE = 127 /* Backspace */ 145 | }; 146 | 147 | static void linenoiseAtExit(void); 148 | int linenoiseHistoryAdd(const char *line); 149 | static void refreshLine(struct linenoiseState *l); 150 | 151 | /* Debugging macro. */ 152 | #if 0 153 | FILE *lndebug_fp = NULL; 154 | #define lndebug(...) \ 155 | do { \ 156 | if (lndebug_fp == NULL) { \ 157 | lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ 158 | fprintf(lndebug_fp, \ 159 | "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ 160 | (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ 161 | (int)l->maxrows,old_rows); \ 162 | } \ 163 | fprintf(lndebug_fp, ", " __VA_ARGS__); \ 164 | fflush(lndebug_fp); \ 165 | } while (0) 166 | #else 167 | #define lndebug(fmt, ...) 168 | #endif 169 | 170 | /* ======================= Low level terminal handling ====================== */ 171 | 172 | /* Enable "mask mode". When it is enabled, instead of the input that 173 | * the user is typing, the terminal will just display a corresponding 174 | * number of asterisks, like "****". This is useful for passwords and other 175 | * secrets that should not be displayed. */ 176 | void linenoiseMaskModeEnable(void) { 177 | maskmode = 1; 178 | } 179 | 180 | /* Disable mask mode. */ 181 | void linenoiseMaskModeDisable(void) { 182 | maskmode = 0; 183 | } 184 | 185 | /* Set if to use or not the multi line mode. */ 186 | void linenoiseSetMultiLine(int ml) { 187 | mlmode = ml; 188 | } 189 | 190 | /* Return true if the terminal name is in the list of terminals we know are 191 | * not able to understand basic escape sequences. */ 192 | static int isUnsupportedTerm(void) { 193 | char *term = getenv("TERM"); 194 | int j; 195 | 196 | if (term == NULL) return 0; 197 | for (j = 0; unsupported_term[j]; j++) 198 | if (!strcasecmp(term,unsupported_term[j])) return 1; 199 | return 0; 200 | } 201 | 202 | /* Raw mode: 1960 magic shit. */ 203 | static int enableRawMode(int fd) { 204 | struct termios raw; 205 | 206 | if (!isatty(STDIN_FILENO)) goto fatal; 207 | if (!atexit_registered) { 208 | atexit(linenoiseAtExit); 209 | atexit_registered = 1; 210 | } 211 | if (tcgetattr(fd,&orig_termios) == -1) goto fatal; 212 | 213 | raw = orig_termios; /* modify the original mode */ 214 | /* input modes: no break, no CR to NL, no parity check, no strip char, 215 | * no start/stop output control. */ 216 | raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 217 | /* output modes - disable post processing */ 218 | raw.c_oflag &= ~(OPOST); 219 | /* control modes - set 8 bit chars */ 220 | raw.c_cflag |= (CS8); 221 | /* local modes - choing off, canonical off, no extended functions, 222 | * no signal chars (^Z,^C) */ 223 | raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 224 | /* control chars - set return condition: min number of bytes and timer. 225 | * We want read to return every single byte, without timeout. */ 226 | raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ 227 | 228 | /* put terminal in raw mode after flushing */ 229 | if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; 230 | rawmode = 1; 231 | return 0; 232 | 233 | fatal: 234 | errno = ENOTTY; 235 | return -1; 236 | } 237 | 238 | static void disableRawMode(int fd) { 239 | /* Don't even check the return value as it's too late. */ 240 | if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) 241 | rawmode = 0; 242 | } 243 | 244 | /* Use the ESC [6n escape sequence to query the horizontal cursor position 245 | * and return it. On error -1 is returned, on success the position of the 246 | * cursor. */ 247 | static int getCursorPosition(int ifd, int ofd) { 248 | char buf[32]; 249 | int cols, rows; 250 | unsigned int i = 0; 251 | 252 | /* Report cursor location */ 253 | if (write(ofd, "\x1b[6n", 4) != 4) return -1; 254 | 255 | /* Read the response: ESC [ rows ; cols R */ 256 | while (i < sizeof(buf)-1) { 257 | if (read(ifd,buf+i,1) != 1) break; 258 | if (buf[i] == 'R') break; 259 | i++; 260 | } 261 | buf[i] = '\0'; 262 | 263 | /* Parse it. */ 264 | if (buf[0] != ESC || buf[1] != '[') return -1; 265 | if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; 266 | return cols; 267 | } 268 | 269 | /* Try to get the number of columns in the current terminal, or assume 80 270 | * if it fails. */ 271 | static int getColumns(int ifd, int ofd) { 272 | struct winsize ws; 273 | 274 | if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { 275 | /* ioctl() failed. Try to query the terminal itself. */ 276 | int start, cols; 277 | 278 | /* Get the initial position so we can restore it later. */ 279 | start = getCursorPosition(ifd,ofd); 280 | if (start == -1) goto failed; 281 | 282 | /* Go to right margin and get position. */ 283 | if (write(ofd,"\x1b[999C",6) != 6) goto failed; 284 | cols = getCursorPosition(ifd,ofd); 285 | if (cols == -1) goto failed; 286 | 287 | /* Restore position. */ 288 | if (cols > start) { 289 | char seq[32]; 290 | snprintf(seq,32,"\x1b[%dD",cols-start); 291 | if (write(ofd,seq,strlen(seq)) == -1) { 292 | /* Can't recover... */ 293 | } 294 | } 295 | return cols; 296 | } else { 297 | return ws.ws_col; 298 | } 299 | 300 | failed: 301 | return 80; 302 | } 303 | 304 | /* Clear the screen. Used to handle ctrl+l */ 305 | void linenoiseClearScreen(void) { 306 | if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { 307 | /* nothing to do, just to avoid warning. */ 308 | } 309 | } 310 | 311 | /* Beep, used for completion when there is nothing to complete or when all 312 | * the choices were already shown. */ 313 | static void linenoiseBeep(void) { 314 | fprintf(stderr, "\x7"); 315 | fflush(stderr); 316 | } 317 | 318 | /* ============================== Completion ================================ */ 319 | 320 | /* Free a list of completion option populated by linenoiseAddCompletion(). */ 321 | static void freeCompletions(linenoiseCompletions *lc) { 322 | size_t i; 323 | for (i = 0; i < lc->len; i++) 324 | free(lc->cvec[i]); 325 | if (lc->cvec != NULL) 326 | free(lc->cvec); 327 | } 328 | 329 | /* This is an helper function for linenoiseEdit() and is called when the 330 | * user types the key in order to complete the string currently in the 331 | * input. 332 | * 333 | * The state of the editing is encapsulated into the pointed linenoiseState 334 | * structure as described in the structure definition. */ 335 | static int completeLine(struct linenoiseState *ls) { 336 | linenoiseCompletions lc = { 0, NULL }; 337 | int nread, nwritten; 338 | char c = 0; 339 | 340 | completionCallback(ls->buf,&lc); 341 | if (lc.len == 0) { 342 | linenoiseBeep(); 343 | } else { 344 | size_t stop = 0, i = 0; 345 | 346 | while(!stop) { 347 | /* Show completion or original buffer */ 348 | if (i < lc.len) { 349 | struct linenoiseState saved = *ls; 350 | 351 | ls->len = ls->pos = strlen(lc.cvec[i]); 352 | ls->buf = lc.cvec[i]; 353 | refreshLine(ls); 354 | ls->len = saved.len; 355 | ls->pos = saved.pos; 356 | ls->buf = saved.buf; 357 | } else { 358 | refreshLine(ls); 359 | } 360 | 361 | nread = read(ls->ifd,&c,1); 362 | if (nread <= 0) { 363 | freeCompletions(&lc); 364 | return -1; 365 | } 366 | 367 | switch(c) { 368 | case 9: /* tab */ 369 | i = (i+1) % (lc.len+1); 370 | if (i == lc.len) linenoiseBeep(); 371 | break; 372 | case 27: /* escape */ 373 | /* Re-show original buffer */ 374 | if (i < lc.len) refreshLine(ls); 375 | stop = 1; 376 | break; 377 | default: 378 | /* Update buffer and return */ 379 | if (i < lc.len) { 380 | nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); 381 | ls->len = ls->pos = nwritten; 382 | } 383 | stop = 1; 384 | break; 385 | } 386 | } 387 | } 388 | 389 | freeCompletions(&lc); 390 | return c; /* Return last read character */ 391 | } 392 | 393 | /* Register a callback function to be called for tab-completion. */ 394 | void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { 395 | completionCallback = fn; 396 | } 397 | 398 | /* Register a hits function to be called to show hits to the user at the 399 | * right of the prompt. */ 400 | void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { 401 | hintsCallback = fn; 402 | } 403 | 404 | /* Register a function to free the hints returned by the hints callback 405 | * registered with linenoiseSetHintsCallback(). */ 406 | void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { 407 | freeHintsCallback = fn; 408 | } 409 | 410 | /* This function is used by the callback function registered by the user 411 | * in order to add completion options given the input string when the 412 | * user typed . See the example.c source code for a very easy to 413 | * understand example. */ 414 | void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { 415 | size_t len = strlen(str); 416 | char *copy, **cvec; 417 | 418 | copy = malloc(len+1); 419 | if (copy == NULL) return; 420 | memcpy(copy,str,len+1); 421 | cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); 422 | if (cvec == NULL) { 423 | free(copy); 424 | return; 425 | } 426 | lc->cvec = cvec; 427 | lc->cvec[lc->len++] = copy; 428 | } 429 | 430 | /* =========================== Line editing ================================= */ 431 | 432 | /* We define a very simple "append buffer" structure, that is an heap 433 | * allocated string where we can append to. This is useful in order to 434 | * write all the escape sequences in a buffer and flush them to the standard 435 | * output in a single call, to avoid flickering effects. */ 436 | struct abuf { 437 | char *b; 438 | int len; 439 | }; 440 | 441 | static void abInit(struct abuf *ab) { 442 | ab->b = NULL; 443 | ab->len = 0; 444 | } 445 | 446 | static void abAppend(struct abuf *ab, const char *s, int len) { 447 | char *new = realloc(ab->b,ab->len+len); 448 | 449 | if (new == NULL) return; 450 | memcpy(new+ab->len,s,len); 451 | ab->b = new; 452 | ab->len += len; 453 | } 454 | 455 | static void abFree(struct abuf *ab) { 456 | free(ab->b); 457 | } 458 | 459 | /* Helper of refreshSingleLine() and refreshMultiLine() to show hints 460 | * to the right of the prompt. */ 461 | void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { 462 | char seq[64]; 463 | if (hintsCallback && plen+l->len < l->cols) { 464 | int color = -1, bold = 0; 465 | char *hint = hintsCallback(l->buf,&color,&bold); 466 | if (hint) { 467 | int hintlen = strlen(hint); 468 | int hintmaxlen = l->cols-(plen+l->len); 469 | if (hintlen > hintmaxlen) hintlen = hintmaxlen; 470 | if (bold == 1 && color == -1) color = 37; 471 | if (color != -1 || bold != 0) 472 | snprintf(seq,64,"\033[%d;%d;49m",bold,color); 473 | else 474 | seq[0] = '\0'; 475 | abAppend(ab,seq,strlen(seq)); 476 | abAppend(ab,hint,hintlen); 477 | if (color != -1 || bold != 0) 478 | abAppend(ab,"\033[0m",4); 479 | /* Call the function to free the hint returned. */ 480 | if (freeHintsCallback) freeHintsCallback(hint); 481 | } 482 | } 483 | } 484 | 485 | /* Single line low level line refresh. 486 | * 487 | * Rewrite the currently edited line accordingly to the buffer content, 488 | * cursor position, and number of columns of the terminal. */ 489 | static void refreshSingleLine(struct linenoiseState *l) { 490 | char seq[64]; 491 | size_t plen = strlen(l->prompt); 492 | int fd = l->ofd; 493 | char *buf = l->buf; 494 | size_t len = l->len; 495 | size_t pos = l->pos; 496 | struct abuf ab; 497 | 498 | while((plen+pos) >= l->cols) { 499 | buf++; 500 | len--; 501 | pos--; 502 | } 503 | while (plen+len > l->cols) { 504 | len--; 505 | } 506 | 507 | abInit(&ab); 508 | /* Cursor to left edge */ 509 | snprintf(seq,64,"\r"); 510 | abAppend(&ab,seq,strlen(seq)); 511 | /* Write the prompt and the current buffer content */ 512 | abAppend(&ab,l->prompt,strlen(l->prompt)); 513 | if (maskmode == 1) { 514 | while (len--) abAppend(&ab,"*",1); 515 | } else { 516 | abAppend(&ab,buf,len); 517 | } 518 | /* Show hits if any. */ 519 | refreshShowHints(&ab,l,plen); 520 | /* Erase to right */ 521 | snprintf(seq,64,"\x1b[0K"); 522 | abAppend(&ab,seq,strlen(seq)); 523 | /* Move cursor to original position. */ 524 | snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); 525 | abAppend(&ab,seq,strlen(seq)); 526 | if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ 527 | abFree(&ab); 528 | } 529 | 530 | /* Multi line low level line refresh. 531 | * 532 | * Rewrite the currently edited line accordingly to the buffer content, 533 | * cursor position, and number of columns of the terminal. */ 534 | static void refreshMultiLine(struct linenoiseState *l) { 535 | char seq[64]; 536 | int plen = strlen(l->prompt); 537 | int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ 538 | int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ 539 | int rpos2; /* rpos after refresh. */ 540 | int col; /* colum position, zero-based. */ 541 | int old_rows = l->maxrows; 542 | int fd = l->ofd, j; 543 | struct abuf ab; 544 | 545 | /* Update maxrows if needed. */ 546 | if (rows > (int)l->maxrows) l->maxrows = rows; 547 | 548 | /* First step: clear all the lines used before. To do so start by 549 | * going to the last row. */ 550 | abInit(&ab); 551 | if (old_rows-rpos > 0) { 552 | lndebug("go down %d", old_rows-rpos); 553 | snprintf(seq,64,"\x1b[%dB", old_rows-rpos); 554 | abAppend(&ab,seq,strlen(seq)); 555 | } 556 | 557 | /* Now for every row clear it, go up. */ 558 | for (j = 0; j < old_rows-1; j++) { 559 | lndebug("clear+up"); 560 | snprintf(seq,64,"\r\x1b[0K\x1b[1A"); 561 | abAppend(&ab,seq,strlen(seq)); 562 | } 563 | 564 | /* Clean the top line. */ 565 | lndebug("clear"); 566 | snprintf(seq,64,"\r\x1b[0K"); 567 | abAppend(&ab,seq,strlen(seq)); 568 | 569 | /* Write the prompt and the current buffer content */ 570 | abAppend(&ab,l->prompt,strlen(l->prompt)); 571 | if (maskmode == 1) { 572 | unsigned int i; 573 | for (i = 0; i < l->len; i++) abAppend(&ab,"*",1); 574 | } else { 575 | abAppend(&ab,l->buf,l->len); 576 | } 577 | 578 | /* Show hits if any. */ 579 | refreshShowHints(&ab,l,plen); 580 | 581 | /* If we are at the very end of the screen with our prompt, we need to 582 | * emit a newline and move the prompt to the first column. */ 583 | if (l->pos && 584 | l->pos == l->len && 585 | (l->pos+plen) % l->cols == 0) 586 | { 587 | lndebug(""); 588 | abAppend(&ab,"\n",1); 589 | snprintf(seq,64,"\r"); 590 | abAppend(&ab,seq,strlen(seq)); 591 | rows++; 592 | if (rows > (int)l->maxrows) l->maxrows = rows; 593 | } 594 | 595 | /* Move cursor to right position. */ 596 | rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ 597 | lndebug("rpos2 %d", rpos2); 598 | 599 | /* Go up till we reach the expected positon. */ 600 | if (rows-rpos2 > 0) { 601 | lndebug("go-up %d", rows-rpos2); 602 | snprintf(seq,64,"\x1b[%dA", rows-rpos2); 603 | abAppend(&ab,seq,strlen(seq)); 604 | } 605 | 606 | /* Set column. */ 607 | col = (plen+(int)l->pos) % (int)l->cols; 608 | lndebug("set col %d", 1+col); 609 | if (col) 610 | snprintf(seq,64,"\r\x1b[%dC", col); 611 | else 612 | snprintf(seq,64,"\r"); 613 | abAppend(&ab,seq,strlen(seq)); 614 | 615 | lndebug("\n"); 616 | l->oldpos = l->pos; 617 | 618 | if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ 619 | abFree(&ab); 620 | } 621 | 622 | /* Calls the two low level functions refreshSingleLine() or 623 | * refreshMultiLine() according to the selected mode. */ 624 | static void refreshLine(struct linenoiseState *l) { 625 | if (mlmode) 626 | refreshMultiLine(l); 627 | else 628 | refreshSingleLine(l); 629 | } 630 | 631 | /* Insert the character 'c' at cursor current position. 632 | * 633 | * On error writing to the terminal -1 is returned, otherwise 0. */ 634 | int linenoiseEditInsert(struct linenoiseState *l, char c) { 635 | if (l->len < l->buflen) { 636 | if (l->len == l->pos) { 637 | l->buf[l->pos] = c; 638 | l->pos++; 639 | l->len++; 640 | l->buf[l->len] = '\0'; 641 | if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { 642 | /* Avoid a full update of the line in the 643 | * trivial case. */ 644 | char d = (maskmode==1) ? '*' : c; 645 | if (write(l->ofd,&d,1) == -1) return -1; 646 | } else { 647 | refreshLine(l); 648 | } 649 | } else { 650 | memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); 651 | l->buf[l->pos] = c; 652 | l->len++; 653 | l->pos++; 654 | l->buf[l->len] = '\0'; 655 | refreshLine(l); 656 | } 657 | } 658 | return 0; 659 | } 660 | 661 | /* Move cursor on the left. */ 662 | void linenoiseEditMoveLeft(struct linenoiseState *l) { 663 | if (l->pos > 0) { 664 | l->pos--; 665 | refreshLine(l); 666 | } 667 | } 668 | 669 | /* Move cursor on the right. */ 670 | void linenoiseEditMoveRight(struct linenoiseState *l) { 671 | if (l->pos != l->len) { 672 | l->pos++; 673 | refreshLine(l); 674 | } 675 | } 676 | 677 | /* Move cursor to the start of the line. */ 678 | void linenoiseEditMoveHome(struct linenoiseState *l) { 679 | if (l->pos != 0) { 680 | l->pos = 0; 681 | refreshLine(l); 682 | } 683 | } 684 | 685 | /* Move cursor to the end of the line. */ 686 | void linenoiseEditMoveEnd(struct linenoiseState *l) { 687 | if (l->pos != l->len) { 688 | l->pos = l->len; 689 | refreshLine(l); 690 | } 691 | } 692 | 693 | /* Substitute the currently edited line with the next or previous history 694 | * entry as specified by 'dir'. */ 695 | #define LINENOISE_HISTORY_NEXT 0 696 | #define LINENOISE_HISTORY_PREV 1 697 | void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { 698 | if (history_len > 1) { 699 | /* Update the current history entry before to 700 | * overwrite it with the next one. */ 701 | free(history[history_len - 1 - l->history_index]); 702 | history[history_len - 1 - l->history_index] = strdup(l->buf); 703 | /* Show the new entry */ 704 | l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; 705 | if (l->history_index < 0) { 706 | l->history_index = 0; 707 | return; 708 | } else if (l->history_index >= history_len) { 709 | l->history_index = history_len-1; 710 | return; 711 | } 712 | strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); 713 | l->buf[l->buflen-1] = '\0'; 714 | l->len = l->pos = strlen(l->buf); 715 | refreshLine(l); 716 | } 717 | } 718 | 719 | /* Delete the character at the right of the cursor without altering the cursor 720 | * position. Basically this is what happens with the "Delete" keyboard key. */ 721 | void linenoiseEditDelete(struct linenoiseState *l) { 722 | if (l->len > 0 && l->pos < l->len) { 723 | memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); 724 | l->len--; 725 | l->buf[l->len] = '\0'; 726 | refreshLine(l); 727 | } 728 | } 729 | 730 | /* Backspace implementation. */ 731 | void linenoiseEditBackspace(struct linenoiseState *l) { 732 | if (l->pos > 0 && l->len > 0) { 733 | memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); 734 | l->pos--; 735 | l->len--; 736 | l->buf[l->len] = '\0'; 737 | refreshLine(l); 738 | } 739 | } 740 | 741 | /* Delete the previosu word, maintaining the cursor at the start of the 742 | * current word. */ 743 | void linenoiseEditDeletePrevWord(struct linenoiseState *l) { 744 | size_t old_pos = l->pos; 745 | size_t diff; 746 | 747 | while (l->pos > 0 && l->buf[l->pos-1] == ' ') 748 | l->pos--; 749 | while (l->pos > 0 && l->buf[l->pos-1] != ' ') 750 | l->pos--; 751 | diff = old_pos - l->pos; 752 | memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); 753 | l->len -= diff; 754 | refreshLine(l); 755 | } 756 | 757 | /* This function is the core of the line editing capability of linenoise. 758 | * It expects 'fd' to be already in "raw mode" so that every key pressed 759 | * will be returned ASAP to read(). 760 | * 761 | * The resulting string is put into 'buf' when the user type enter, or 762 | * when ctrl+d is typed. 763 | * 764 | * The function returns the length of the current buffer. */ 765 | static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) 766 | { 767 | struct linenoiseState l; 768 | 769 | /* Populate the linenoise state that we pass to functions implementing 770 | * specific editing functionalities. */ 771 | l.ifd = stdin_fd; 772 | l.ofd = stdout_fd; 773 | l.buf = buf; 774 | l.buflen = buflen; 775 | l.prompt = prompt; 776 | l.plen = strlen(prompt); 777 | l.oldpos = l.pos = 0; 778 | l.len = 0; 779 | l.cols = getColumns(stdin_fd, stdout_fd); 780 | l.maxrows = 0; 781 | l.history_index = 0; 782 | 783 | /* Buffer starts empty. */ 784 | l.buf[0] = '\0'; 785 | l.buflen--; /* Make sure there is always space for the nulterm */ 786 | 787 | /* The latest history entry is always our current buffer, that 788 | * initially is just an empty string. */ 789 | linenoiseHistoryAdd(""); 790 | 791 | if (write(l.ofd,prompt,l.plen) == -1) return -1; 792 | while(1) { 793 | char c; 794 | int nread; 795 | char seq[3]; 796 | 797 | nread = read(l.ifd,&c,1); 798 | if (nread <= 0) return l.len; 799 | 800 | /* Only autocomplete when the callback is set. It returns < 0 when 801 | * there was an error reading from fd. Otherwise it will return the 802 | * character that should be handled next. */ 803 | if (c == 9 && completionCallback != NULL) { 804 | c = completeLine(&l); 805 | /* Return on errors */ 806 | if (c < 0) return l.len; 807 | /* Read next character when 0 */ 808 | if (c == 0) continue; 809 | } 810 | 811 | switch(c) { 812 | case ENTER: /* enter */ 813 | history_len--; 814 | free(history[history_len]); 815 | if (mlmode) linenoiseEditMoveEnd(&l); 816 | if (hintsCallback) { 817 | /* Force a refresh without hints to leave the previous 818 | * line as the user typed it after a newline. */ 819 | linenoiseHintsCallback *hc = hintsCallback; 820 | hintsCallback = NULL; 821 | refreshLine(&l); 822 | hintsCallback = hc; 823 | } 824 | return (int)l.len; 825 | case CTRL_C: /* ctrl-c */ 826 | errno = EAGAIN; 827 | return -1; 828 | case BACKSPACE: /* backspace */ 829 | case 8: /* ctrl-h */ 830 | linenoiseEditBackspace(&l); 831 | break; 832 | case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the 833 | line is empty, act as end-of-file. */ 834 | if (l.len > 0) { 835 | linenoiseEditDelete(&l); 836 | } else { 837 | history_len--; 838 | free(history[history_len]); 839 | return -1; 840 | } 841 | break; 842 | case CTRL_T: /* ctrl-t, swaps current character with previous. */ 843 | if (l.pos > 0 && l.pos < l.len) { 844 | int aux = buf[l.pos-1]; 845 | buf[l.pos-1] = buf[l.pos]; 846 | buf[l.pos] = aux; 847 | if (l.pos != l.len-1) l.pos++; 848 | refreshLine(&l); 849 | } 850 | break; 851 | case CTRL_B: /* ctrl-b */ 852 | linenoiseEditMoveLeft(&l); 853 | break; 854 | case CTRL_F: /* ctrl-f */ 855 | linenoiseEditMoveRight(&l); 856 | break; 857 | case CTRL_P: /* ctrl-p */ 858 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); 859 | break; 860 | case CTRL_N: /* ctrl-n */ 861 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); 862 | break; 863 | case ESC: /* escape sequence */ 864 | /* Read the next two bytes representing the escape sequence. 865 | * Use two calls to handle slow terminals returning the two 866 | * chars at different times. */ 867 | if (read(l.ifd,seq,1) == -1) break; 868 | if (read(l.ifd,seq+1,1) == -1) break; 869 | 870 | /* ESC [ sequences. */ 871 | if (seq[0] == '[') { 872 | if (seq[1] >= '0' && seq[1] <= '9') { 873 | /* Extended escape, read additional byte. */ 874 | if (read(l.ifd,seq+2,1) == -1) break; 875 | if (seq[2] == '~') { 876 | switch(seq[1]) { 877 | case '3': /* Delete key. */ 878 | linenoiseEditDelete(&l); 879 | break; 880 | } 881 | } 882 | } else { 883 | switch(seq[1]) { 884 | case 'A': /* Up */ 885 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); 886 | break; 887 | case 'B': /* Down */ 888 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); 889 | break; 890 | case 'C': /* Right */ 891 | linenoiseEditMoveRight(&l); 892 | break; 893 | case 'D': /* Left */ 894 | linenoiseEditMoveLeft(&l); 895 | break; 896 | case 'H': /* Home */ 897 | linenoiseEditMoveHome(&l); 898 | break; 899 | case 'F': /* End*/ 900 | linenoiseEditMoveEnd(&l); 901 | break; 902 | } 903 | } 904 | } 905 | 906 | /* ESC O sequences. */ 907 | else if (seq[0] == 'O') { 908 | switch(seq[1]) { 909 | case 'H': /* Home */ 910 | linenoiseEditMoveHome(&l); 911 | break; 912 | case 'F': /* End*/ 913 | linenoiseEditMoveEnd(&l); 914 | break; 915 | } 916 | } 917 | break; 918 | default: 919 | if (linenoiseEditInsert(&l,c)) return -1; 920 | break; 921 | case CTRL_U: /* Ctrl+u, delete the whole line. */ 922 | buf[0] = '\0'; 923 | l.pos = l.len = 0; 924 | refreshLine(&l); 925 | break; 926 | case CTRL_K: /* Ctrl+k, delete from current to end of line. */ 927 | buf[l.pos] = '\0'; 928 | l.len = l.pos; 929 | refreshLine(&l); 930 | break; 931 | case CTRL_A: /* Ctrl+a, go to the start of the line */ 932 | linenoiseEditMoveHome(&l); 933 | break; 934 | case CTRL_E: /* ctrl+e, go to the end of the line */ 935 | linenoiseEditMoveEnd(&l); 936 | break; 937 | case CTRL_L: /* ctrl+l, clear screen */ 938 | linenoiseClearScreen(); 939 | refreshLine(&l); 940 | break; 941 | case CTRL_W: /* ctrl+w, delete previous word */ 942 | linenoiseEditDeletePrevWord(&l); 943 | break; 944 | } 945 | } 946 | return l.len; 947 | } 948 | 949 | /* This special mode is used by linenoise in order to print scan codes 950 | * on screen for debugging / development purposes. It is implemented 951 | * by the linenoise_example program using the --keycodes option. */ 952 | void linenoisePrintKeyCodes(void) { 953 | char quit[4]; 954 | 955 | printf("Linenoise key codes debugging mode.\n" 956 | "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); 957 | if (enableRawMode(STDIN_FILENO) == -1) return; 958 | memset(quit,' ',4); 959 | while(1) { 960 | char c; 961 | int nread; 962 | 963 | nread = read(STDIN_FILENO,&c,1); 964 | if (nread <= 0) continue; 965 | memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ 966 | quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ 967 | if (memcmp(quit,"quit",sizeof(quit)) == 0) break; 968 | 969 | printf("'%c' %02x (%d) (type quit to exit)\n", 970 | isprint(c) ? c : '?', (int)c, (int)c); 971 | printf("\r"); /* Go left edge manually, we are in raw mode. */ 972 | fflush(stdout); 973 | } 974 | disableRawMode(STDIN_FILENO); 975 | } 976 | 977 | /* This function calls the line editing function linenoiseEdit() using 978 | * the STDIN file descriptor set in raw mode. */ 979 | static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { 980 | int count; 981 | 982 | if (buflen == 0) { 983 | errno = EINVAL; 984 | return -1; 985 | } 986 | 987 | if (enableRawMode(STDIN_FILENO) == -1) return -1; 988 | count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); 989 | disableRawMode(STDIN_FILENO); 990 | printf("\n"); 991 | return count; 992 | } 993 | 994 | /* This function is called when linenoise() is called with the standard 995 | * input file descriptor not attached to a TTY. So for example when the 996 | * program using linenoise is called in pipe or with a file redirected 997 | * to its standard input. In this case, we want to be able to return the 998 | * line regardless of its length (by default we are limited to 4k). */ 999 | static char *linenoiseNoTTY(void) { 1000 | char *line = NULL; 1001 | size_t len = 0, maxlen = 0; 1002 | 1003 | while(1) { 1004 | if (len == maxlen) { 1005 | if (maxlen == 0) maxlen = 16; 1006 | maxlen *= 2; 1007 | char *oldval = line; 1008 | line = realloc(line,maxlen); 1009 | if (line == NULL) { 1010 | if (oldval) free(oldval); 1011 | return NULL; 1012 | } 1013 | } 1014 | int c = fgetc(stdin); 1015 | if (c == EOF || c == '\n') { 1016 | if (c == EOF && len == 0) { 1017 | free(line); 1018 | return NULL; 1019 | } else { 1020 | line[len] = '\0'; 1021 | return line; 1022 | } 1023 | } else { 1024 | line[len] = c; 1025 | len++; 1026 | } 1027 | } 1028 | } 1029 | 1030 | /* The high level function that is the main API of the linenoise library. 1031 | * This function checks if the terminal has basic capabilities, just checking 1032 | * for a blacklist of stupid terminals, and later either calls the line 1033 | * editing function or uses dummy fgets() so that you will be able to type 1034 | * something even in the most desperate of the conditions. */ 1035 | char *linenoise(const char *prompt) { 1036 | char buf[LINENOISE_MAX_LINE]; 1037 | int count; 1038 | 1039 | if (!isatty(STDIN_FILENO)) { 1040 | /* Not a tty: read from file / pipe. In this mode we don't want any 1041 | * limit to the line size, so we call a function to handle that. */ 1042 | return linenoiseNoTTY(); 1043 | } else if (isUnsupportedTerm()) { 1044 | size_t len; 1045 | 1046 | printf("%s",prompt); 1047 | fflush(stdout); 1048 | if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; 1049 | len = strlen(buf); 1050 | while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { 1051 | len--; 1052 | buf[len] = '\0'; 1053 | } 1054 | return strdup(buf); 1055 | } else { 1056 | count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); 1057 | if (count == -1) return NULL; 1058 | return strdup(buf); 1059 | } 1060 | } 1061 | 1062 | /* This is just a wrapper the user may want to call in order to make sure 1063 | * the linenoise returned buffer is freed with the same allocator it was 1064 | * created with. Useful when the main program is using an alternative 1065 | * allocator. */ 1066 | void linenoiseFree(void *ptr) { 1067 | free(ptr); 1068 | } 1069 | 1070 | /* ================================ History ================================= */ 1071 | 1072 | /* Free the history, but does not reset it. Only used when we have to 1073 | * exit() to avoid memory leaks are reported by valgrind & co. */ 1074 | static void freeHistory(void) { 1075 | if (history) { 1076 | int j; 1077 | 1078 | for (j = 0; j < history_len; j++) 1079 | free(history[j]); 1080 | free(history); 1081 | } 1082 | } 1083 | 1084 | /* At exit we'll try to fix the terminal to the initial conditions. */ 1085 | static void linenoiseAtExit(void) { 1086 | disableRawMode(STDIN_FILENO); 1087 | freeHistory(); 1088 | } 1089 | 1090 | /* This is the API call to add a new entry in the linenoise history. 1091 | * It uses a fixed array of char pointers that are shifted (memmoved) 1092 | * when the history max length is reached in order to remove the older 1093 | * entry and make room for the new one, so it is not exactly suitable for huge 1094 | * histories, but will work well for a few hundred of entries. 1095 | * 1096 | * Using a circular buffer is smarter, but a bit more complex to handle. */ 1097 | int linenoiseHistoryAdd(const char *line) { 1098 | char *linecopy; 1099 | 1100 | if (history_max_len == 0) return 0; 1101 | 1102 | /* Initialization on first call. */ 1103 | if (history == NULL) { 1104 | history = malloc(sizeof(char*)*history_max_len); 1105 | if (history == NULL) return 0; 1106 | memset(history,0,(sizeof(char*)*history_max_len)); 1107 | } 1108 | 1109 | /* Don't add duplicated lines. */ 1110 | if (history_len && !strcmp(history[history_len-1], line)) return 0; 1111 | 1112 | /* Add an heap allocated copy of the line in the history. 1113 | * If we reached the max length, remove the older line. */ 1114 | linecopy = strdup(line); 1115 | if (!linecopy) return 0; 1116 | if (history_len == history_max_len) { 1117 | free(history[0]); 1118 | memmove(history,history+1,sizeof(char*)*(history_max_len-1)); 1119 | history_len--; 1120 | } 1121 | history[history_len] = linecopy; 1122 | history_len++; 1123 | return 1; 1124 | } 1125 | 1126 | /* Set the maximum length for the history. This function can be called even 1127 | * if there is already some history, the function will make sure to retain 1128 | * just the latest 'len' elements if the new history length value is smaller 1129 | * than the amount of items already inside the history. */ 1130 | int linenoiseHistorySetMaxLen(int len) { 1131 | char **new; 1132 | 1133 | if (len < 1) return 0; 1134 | if (history) { 1135 | int tocopy = history_len; 1136 | 1137 | new = malloc(sizeof(char*)*len); 1138 | if (new == NULL) return 0; 1139 | 1140 | /* If we can't copy everything, free the elements we'll not use. */ 1141 | if (len < tocopy) { 1142 | int j; 1143 | 1144 | for (j = 0; j < tocopy-len; j++) free(history[j]); 1145 | tocopy = len; 1146 | } 1147 | memset(new,0,sizeof(char*)*len); 1148 | memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); 1149 | free(history); 1150 | history = new; 1151 | } 1152 | history_max_len = len; 1153 | if (history_len > history_max_len) 1154 | history_len = history_max_len; 1155 | return 1; 1156 | } 1157 | 1158 | /* Save the history in the specified file. On success 0 is returned 1159 | * otherwise -1 is returned. */ 1160 | int linenoiseHistorySave(const char *filename) { 1161 | mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); 1162 | FILE *fp; 1163 | int j; 1164 | 1165 | fp = fopen(filename,"w"); 1166 | umask(old_umask); 1167 | if (fp == NULL) return -1; 1168 | chmod(filename,S_IRUSR|S_IWUSR); 1169 | for (j = 0; j < history_len; j++) 1170 | fprintf(fp,"%s\n",history[j]); 1171 | fclose(fp); 1172 | return 0; 1173 | } 1174 | 1175 | /* Load the history from the specified file. If the file does not exist 1176 | * zero is returned and no operation is performed. 1177 | * 1178 | * If the file exists and the operation succeeded 0 is returned, otherwise 1179 | * on error -1 is returned. */ 1180 | int linenoiseHistoryLoad(const char *filename) { 1181 | FILE *fp = fopen(filename,"r"); 1182 | char buf[LINENOISE_MAX_LINE]; 1183 | 1184 | if (fp == NULL) return -1; 1185 | 1186 | while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { 1187 | char *p; 1188 | 1189 | p = strchr(buf,'\r'); 1190 | if (!p) p = strchr(buf,'\n'); 1191 | if (p) *p = '\0'; 1192 | linenoiseHistoryAdd(buf); 1193 | } 1194 | fclose(fp); 1195 | return 0; 1196 | } 1197 | 1198 | #endif 1199 | 1200 | #endif /* __LINENOISE_H */ 1201 | -------------------------------------------------------------------------------- /src/libs/skp.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** (C) by Remo Dentato (rdentato@gmail.com) 3 | ** 4 | ** This software is distributed under the terms of the MIT license: 5 | ** https://opensource.org/licenses/MIT 6 | */ 7 | 8 | #ifndef SKP_VER 9 | #define SKP_VER 0x0002001C 10 | #define SKP_VER_STR "0.2.1rc" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #define skp_exp(x) x 20 | #define skp_0(x,...) x 21 | #define skp_1(x,y,...) y 22 | #define skp_2(x,y,z,...) z 23 | 24 | char *skp_(char *pat, char *src,char **end, int *alt); 25 | 26 | #define skp(pat,...) skp_(pat, \ 27 | skp_exp(skp_0(__VA_ARGS__,NULL)), \ 28 | skp_exp(skp_1(__VA_ARGS__,NULL,NULL)), \ 29 | skp_exp(skp_2(__VA_ARGS__,NULL,NULL,NULL)) ) 30 | 31 | #define skp_join(x,y) x ## y 32 | #define skp_cat(x,y) skp_join(x,y) 33 | #define skp_lbl skp_cat(skp_lbl_,__LINE__) 34 | 35 | 36 | #define skpwhile(s_) for(int skp_flg = 1; skp_flg; ) { \ 37 | char *skpstart; \ 38 | char *skpend; \ 39 | char *skp_last; \ 40 | int skp_matched; \ 41 | if (skp_flg == 1) { skpend = s_; skp_last = NULL; skp_flg = 2; }\ 42 | skpstart = skpend; \ 43 | _dbgtrc("END: %c",*skpend); \ 44 | skp_matched = 0; \ 45 | if (!*skpstart || (skpstart == skp_last)) break; \ 46 | skp_last = skpstart; \ 47 | 48 | #define skpcase(e_) } \ 49 | if (!skp_matched) skp(e_,skpstart,&skpend); \ 50 | if (!skp_matched && (skp_matched = !errno)) { \ 51 | goto skp_lbl; skp_lbl 52 | 53 | #define skpdefault } \ 54 | if (skp_matched) continue; \ 55 | goto skp_lbl; skp_lbl 56 | 57 | 58 | 59 | 60 | #ifdef SKP_MAIN 61 | 62 | #include "dbg.h" 63 | 64 | /* 65 | a ASCII alphabetic char 66 | l ASCII lower case 67 | u ASCII upper case 68 | d decimal digit 69 | x hex digit 70 | w white space (includes some Unicode spaces) 71 | s white space and vertical spaces (e.g. LF) 72 | c control 73 | n newline 74 | 75 | . any (UTF-8 or ISO character) 76 | 77 | Q Quoted string with '\' as escape 78 | B Balanced sequence of parenthesis 79 | I Identifier ([_A-Za-z][_0-9A-Za-z]*) 80 | N Any number (the longest among the three below ) 81 | D integer decimal number (possibly signed) 82 | F floating point number (possibly with sign and exponent) 83 | X hex number (possibly with leading 0x) 84 | 85 | C case sensitive (ASCII) comparison 86 | U utf-8 encoding (or ASCII/ISO-8859) 87 | 88 | * zero or more match 89 | ? zero or one match 90 | + one or more match 91 | 92 | ! negate test 93 | 94 | @ set goal 95 | 96 | [...] set 97 | 98 | &. (any non \0 character) 99 | &!. (eol) 100 | 101 | &> skip to the start of pattern 102 | 103 | && 104 | 105 | */ 106 | 107 | static uint32_t skp_next(char *s,char **end,int iso) 108 | { 109 | uint32_t c = 0; 110 | 111 | if (*s) { 112 | c = *s++; 113 | if (!iso) { 114 | while ((*s & 0xC0) == 0x80) { 115 | c = (c << 8) | *s++; 116 | } 117 | } 118 | if (c == 0x0D && *s == 0x0A) { 119 | c = 0x0D0A; s++; 120 | } 121 | } 122 | 123 | if (end) *end = s; 124 | return c; 125 | } 126 | 127 | static int chr_cmp(uint32_t a, uint32_t b, int fold) 128 | { 129 | if (fold && a <= 0xFF && b <= 0xFF) { 130 | a = tolower(a); 131 | b = tolower(b); 132 | } 133 | return (a == b); 134 | } 135 | 136 | static int is_blank(uint32_t c) 137 | { 138 | return (c == 0x20) || (c == 0x09) 139 | || (c == 0xA0) || (c == 0xC2A0) 140 | || (c == 0xE19A80) 141 | || ((0xE28080 <= c) && (c <= 0xE2808A)) 142 | || (c == 0xE280AF) 143 | || (c == 0xE2819F) 144 | || (c == 0xE38080) 145 | ; 146 | } 147 | 148 | static int is_break(uint32_t c) 149 | { 150 | return (c == 0x0A) // U+000A LF line feed 151 | || (c == 0x0C) // U+000C FF form feed 152 | || (c == 0x0D) // U+000D CR carriage return 153 | || (c == 0x85) // U+0085 NEL next line ISO-8859-15 154 | || (c == 0x0D0A) // CRLF (not a real UTF-8 CODEPOINT!!!) 155 | || (c == 0xC285) // U+0085 NEL next line 156 | || (c == 0xE280A8) // U+2028 LS line separator 157 | || (c == 0xE280A9) // U+2029 PS paragraph separator 158 | ; 159 | } 160 | 161 | static int is_space(uint32_t c) 162 | { return is_blank(c) || is_break(c); } 163 | 164 | static int is_digit(uint32_t c) 165 | { return ('0' <= c && c <= '9'); } 166 | 167 | static int is_xdigit(uint32_t c) 168 | { 169 | return ('0' <= c && c <= '9') 170 | || ('A' <= c && c <= 'F') 171 | || ('a' <= c && c <= 'f'); 172 | } 173 | 174 | static int is_upper(uint32_t c) 175 | { return ('A' <= c && c <= 'Z'); } 176 | 177 | static int is_lower(uint32_t c) 178 | { return ('a' <= c && c <= 'z'); } 179 | 180 | static int is_alpha(uint32_t c) 181 | { return ('A' <= c && c <= 'Z') 182 | || ('a' <= c && c <= 'z'); } 183 | 184 | static int is_alnum(uint32_t c) 185 | { return (is_alpha(c) || is_digit(c)); } 186 | 187 | static int is_ctrl(uint32_t c) 188 | { return (c < 0x20) 189 | || (0xC280 <= c && c <= 0xC2A0) 190 | || (0x80 <= c && c <= 0xA0); 191 | } 192 | 193 | static int is_oneof(uint32_t ch, char *set, int iso) 194 | { 195 | uint32_t p_ch,q_ch; 196 | char *s; 197 | if (ch == '\0') return 0; 198 | _dbgtrc("set: [%s] chr: %c",set,ch); 199 | p_ch = skp_next(set,&s,iso); 200 | 201 | if (p_ch == ']' && ch == ']') return 1; 202 | 203 | while (p_ch != ']') { 204 | if (p_ch == ch) return 1; 205 | q_ch = p_ch; 206 | p_ch = skp_next(s,&s,iso); 207 | if ((p_ch == '-') && (*s != ']')) { 208 | p_ch = skp_next(s,&s,iso); 209 | if ((q_ch < ch) && (ch <= p_ch)) return 1; 210 | p_ch = skp_next(s,&s,iso); 211 | } 212 | } 213 | p_ch = skp_next(s,&s,iso); 214 | return 0; 215 | } 216 | 217 | static uint32_t get_close(uint32_t open) 218 | { 219 | switch(open) { 220 | case '(': return ')'; 221 | case '[': return ']'; 222 | case '{': return '}'; 223 | case '<': return '>'; 224 | } 225 | return 0; 226 | } 227 | 228 | #define MATCHED_FAIL 0 229 | #define MATCHED 1 230 | #define MATCHED_GOAL 2 231 | #define MATCHED_GOALNOT 3 232 | 233 | static int match(char *pat, char *src, char **pat_end, char **src_end) 234 | { 235 | uint32_t p_chr, s_chr; 236 | char *p_end, *s_end; 237 | int ret = 0; 238 | uint32_t match_min = 1; 239 | uint32_t match_max = 1; 240 | uint32_t match_cnt = 0; 241 | uint32_t match_not = 0; 242 | int fold = false; 243 | int iso = false; 244 | int intnumber = false; 245 | char *s_tmp = src; 246 | 247 | s_end = src; 248 | s_chr = skp_next(s_end, &s_tmp,iso); 249 | 250 | if (*pat == '&') { 251 | pat++; 252 | 253 | if (*pat == '*') { match_min = 0; match_max = UINT32_MAX; pat++; } 254 | else if (*pat == '+') { match_max = UINT32_MAX; pat++; } 255 | else if (*pat == '?') { match_min = 0; pat++; } 256 | 257 | if (*pat == '!') { match_not = 1; pat++; } 258 | _dbgtrc("min: %u max: %u not: %u",match_min, match_max, match_not); 259 | 260 | #define W(x) \ 261 | do { \ 262 | _dbgtrc("matchedW: '%s' schr: %X test: %d (%s)",s_end,s_chr,(x),#x); \ 263 | for (match_cnt = 0; \ 264 | (match_cnt < match_max) && s_chr && (!!(x) != match_not); \ 265 | match_cnt++) { \ 266 | s_end = s_tmp; s_chr = skp_next(s_end,&s_tmp,iso); \ 267 | } \ 268 | ret = (match_cnt >= match_min); \ 269 | _dbgtrc("cnt: %d ret: %d s: %c end: %c",match_cnt,ret,*s_tmp, *s_end); \ 270 | } while (0) 271 | 272 | #define get_next_s_chr() do {s_end = s_tmp; s_chr = *s_end ; s_tmp++;} while(0) 273 | 274 | intnumber = false; 275 | 276 | switch (*pat++) { 277 | case '&' : ret = (s_chr == '&') ; break; 278 | 279 | case '.' : if (match_not) ret = (s_chr == 0); 280 | else W(s_chr != 0); 281 | break; 282 | 283 | case 'd' : W(is_digit(s_chr)); break; 284 | case 'x' : W(is_xdigit(s_chr)); break; 285 | case 'a' : W(is_alpha(s_chr)); break; 286 | case 'u' : W(is_upper(s_chr)); break; 287 | case 'l' : W(is_lower(s_chr)); break; 288 | case 's' : W(is_space(s_chr)); break; 289 | case 'w' : W(is_blank(s_chr)); break; 290 | case 'n' : W(is_break(s_chr)); break; 291 | case 'c' : W(is_ctrl(s_chr)); break; 292 | 293 | case '@' : ret = match_not? MATCHED_GOALNOT : MATCHED_GOAL; 294 | break; 295 | 296 | case '[' : W(is_oneof(s_chr,pat,iso)); 297 | while (*pat && *pat != ']') pat++; 298 | if (*pat && pat[1]==']') pat++; 299 | pat++; 300 | break; 301 | 302 | case 'C' : fold = !match_not; ret = MATCHED; 303 | break; 304 | 305 | case 'U' : iso = match_not; ret = MATCHED; 306 | break; 307 | 308 | case 'I' : // Identifier 309 | if (is_alpha(s_chr) || (s_chr == '_')) { 310 | do { 311 | get_next_s_chr(); 312 | } while (is_alnum(s_chr) || (s_chr == '_')); 313 | ret = MATCHED; 314 | } 315 | break; 316 | 317 | case '(' : if (*pat != ')' || s_chr != '(') break; 318 | pat++; 319 | 320 | case 'B' : // Balanced parenthesis 321 | { 322 | uint32_t open; 323 | uint32_t close; 324 | int32_t count; 325 | open = s_chr; 326 | close = get_close(open); 327 | if (close != '\0') { 328 | count=1; 329 | while (s_chr && count > 0) { 330 | get_next_s_chr(); 331 | if (s_chr == open) count++; 332 | if (s_chr == close) count--; 333 | } 334 | if (count == 0) { 335 | get_next_s_chr(); 336 | ret = MATCHED; 337 | } 338 | } 339 | } 340 | break; 341 | 342 | case 'X' : // hex number 343 | if ( (s_chr == '0') 344 | && (s_end[1] == 'x' || s_end[1] == 'X') 345 | && is_xdigit(s_end[2]) 346 | ) { 347 | get_next_s_chr(); 348 | get_next_s_chr(); 349 | get_next_s_chr(); 350 | ret = MATCHED; 351 | } 352 | while (is_xdigit(s_chr)) { 353 | ret = MATCHED; 354 | get_next_s_chr(); 355 | } 356 | break; 357 | 358 | case 'D' : // Integer number 359 | intnumber = true; 360 | 361 | case 'F' : // Floating point number 362 | if (s_chr == '+' || s_chr == '-') { 363 | do { 364 | get_next_s_chr(); 365 | } while (is_space(s_chr)); 366 | } 367 | 368 | while (is_digit(s_chr)) { 369 | ret = MATCHED; 370 | get_next_s_chr(); 371 | } 372 | 373 | if (intnumber) break; 374 | 375 | if (s_chr == '.') { 376 | get_next_s_chr(); 377 | } 378 | 379 | while (is_digit(s_chr)) { 380 | ret = MATCHED; 381 | get_next_s_chr(); 382 | } 383 | 384 | if ((ret == MATCHED) && (s_chr == 'E' || s_chr == 'e')) { 385 | get_next_s_chr(); 386 | if (s_chr == '+' || s_chr == '-') get_next_s_chr(); 387 | while (is_digit(s_chr)) get_next_s_chr(); 388 | if (s_chr == '.') get_next_s_chr(); 389 | while (is_digit(s_chr)) get_next_s_chr(); 390 | } 391 | 392 | break; 393 | 394 | default : ret = MATCHED_FAIL; pat--; break; 395 | } 396 | p_end = pat; 397 | } 398 | else { 399 | p_chr = skp_next(pat,&p_end,iso); 400 | s_end = s_tmp; 401 | ret = chr_cmp(s_chr,p_chr,fold); 402 | } 403 | 404 | if (ret != MATCHED_FAIL) { 405 | if (pat_end) *pat_end = p_end; 406 | if (src_end) *src_end = s_end; 407 | } 408 | return ret; 409 | } 410 | 411 | // skp("&*!s") skp("&>&s") 412 | 413 | char *skp_(char *pat, char *src, char **end, int *alt) 414 | { 415 | char *start = src; 416 | char *s; 417 | char *p; 418 | char *s_end; 419 | char *p_end; 420 | int skpto = 0; 421 | int matched = 0; 422 | char *goal = NULL; 423 | char *goalnot = NULL; 424 | 425 | if (!pat || !src ) { errno = 1; return src; } 426 | 427 | if (alt) *alt = -1; 428 | 429 | if (pat[0] == '&' && pat[1] == '>') { 430 | skpto = 1; 431 | pat += 2; 432 | } 433 | 434 | p = pat; 435 | s = start; 436 | while (*p > '\7') { 437 | if ((matched = match(p,s,&p_end,&s_end))) { 438 | _dbgtrc("matched( '%s' '%s'",s,p); 439 | s = s_end; p = p_end; 440 | _dbgtrc("matched) '%s' '%s'",s,p); 441 | if (matched == MATCHED_GOAL && !goalnot) goal = s; 442 | else if (matched == MATCHED_GOALNOT) goalnot = s; 443 | } 444 | else { 445 | while (*p > '\7') p++; 446 | if (*p > '\0') { 447 | s = start; 448 | p++; 449 | _dbgtrc("resume from: %s (%c)", p,*s); 450 | } 451 | else if (skpto) { 452 | goal = NULL; goalnot = NULL; 453 | p = pat; 454 | s = ++start; 455 | if (*s == '\0') break; 456 | } 457 | else break; 458 | } 459 | } 460 | _dbgtrc("pat: '%s'",p); 461 | 462 | if (!matched && goalnot) { 463 | goal = goalnot; 464 | matched = MATCHED; 465 | p=""; 466 | } 467 | 468 | if (goal) s = goal; 469 | 470 | if (matched && (*p <= '\7')) { 471 | errno = 0; 472 | if (alt) *alt = *p; 473 | if (end) *end = s; 474 | if (skpto) s = start; 475 | return s; 476 | } 477 | 478 | errno = 1; 479 | if (end) *end = src; 480 | return src; 481 | } 482 | 483 | #endif // SKP_MAIN 484 | #endif // SKP_VER -------------------------------------------------------------------------------- /src/libs/stk.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** (C) by Remo Dentato (rdentato@gmail.com) 3 | ** 4 | ** This software is distributed under the terms of the MIT license: 5 | ** https://opensource.org/licenses/MIT 6 | */ 7 | 8 | #ifndef STK_VERSION 9 | #define STK_VERSION 0x0000001B 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #define STK_argcnt(x1,x2,x3,x4,xN, ...) xN 16 | #define STK_argn(...) STK_argcnt(__VA_ARGS__,4,3,2,1,0) 17 | #define STK_argjoin(x,y) x ## y 18 | #define STK_argcat(x,y) STK_argjoin(x,y) 19 | #define STK_varargs(f,...) STK_argcat(f, STK_argn(__VA_ARGS__))(__VA_ARGS__) 20 | 21 | typedef struct stk_s *stk_t; 22 | stk_t stkfree(stk_t s); 23 | stk_t stknew(); 24 | void stkdrop(stk_t s); 25 | 26 | int32_t stkcount(stk_t s); 27 | 28 | #define stkpush(...) STK_varargs(stk_push, __VA_ARGS__) 29 | #define stk_push1(s) stk_push(s,0,NULL) 30 | #define stk_push2(s,n) stk_push(s,n,NULL) 31 | #define stk_push3(s,n,e) stk_push(s,n,e) 32 | 33 | void *stk_push(stk_t s, uint16_t sz, void *elm); 34 | 35 | #define stktop(...) STK_varargs(stk_top, __VA_ARGS__) 36 | #define stk_top1(s) stk_top(s,0,NULL) 37 | #define stk_top2(s,n) stk_top(s,n,NULL) 38 | #define stk_top3(s,n,e) stk_top(s,n,e) 39 | void *stk_top(stk_t s, int delta, int *elmsize); 40 | 41 | 42 | #ifdef STK_MAIN 43 | 44 | /* 45 | *stk_t 46 | ┏━━━━┓ 47 | ┃ ┃ Each slot is two bytes 48 | ┗━┿━━┛ top╭─────────────────────────╮ 49 | page│ ┏━━━━━━━━━┿━┓┏━━━━━━━━━━━━━━━━━━━━━━▼━━━━━━┓ 50 | ╰───►┃ ○ ○ ┃┃█░░░█░░░░░░░░░░█░░░░░░█ ┃ 51 | ┗━┿━━━━━━━━━┛┗▲━━━━━━━━━━━━━━▲━━━━━━━━━━━━━┛ 52 | prev│ │ ╰── n/2 ─╯│ 53 | ▼ │ n (len of element) 54 | │ 55 | ╰─ 0xFFFF(BOTTOM) 56 | */ 57 | 58 | #define STK_PAGEBOTTOM 0xFFFE 59 | #define STK_STACKBOTTOM 0xFFFF 60 | #define STK_MAXELMSZ 0xFFF0 61 | 62 | #define STK_PAGESIZE (16*1024) 63 | #define STK_MAXTOP (STK_PAGESIZE/2) 64 | 65 | typedef struct stk_pg_s { 66 | struct stk_pg_s *prev; 67 | uint16_t top; 68 | uint16_t data[STK_MAXTOP+1]; // 69 | } stk_page_t; 70 | 71 | typedef struct stk_s { 72 | stk_page_t *page; 73 | int32_t count; 74 | } *stk_t; 75 | 76 | stk_t stknew() 77 | { 78 | stk_t s = NULL; 79 | 80 | s = malloc(sizeof(struct stk_s)); 81 | if (s) { 82 | s->count = 0; 83 | s->page = NULL; 84 | } 85 | return s; 86 | } 87 | 88 | stk_t stkfree(stk_t s) 89 | { 90 | if (s) { 91 | stk_page_t *page = s->page; 92 | stk_page_t *p; 93 | while (page) { 94 | p = page; 95 | page = page->prev; 96 | free(p); 97 | } 98 | free(s); 99 | } 100 | return NULL; 101 | } 102 | 103 | void *stk_push(stk_t s, uint16_t sz, void *elm) 104 | { 105 | uint32_t slots_needed; 106 | void *ret; 107 | 108 | if (!s) {errno= EINVAL; return NULL;} 109 | if (sz == 0 || sz > STK_MAXELMSZ) {errno = EINVAL; return NULL;} 110 | 111 | slots_needed = (sz+1)/2; // Convert to number of uint16_t (in excess) 112 | slots_needed++ ; // Add one slot to store the size 113 | 114 | if (!s->page || ((STK_MAXTOP - s->page->top) < slots_needed)) { 115 | stk_page_t *p; 116 | p = malloc(sizeof(stk_page_t)); 117 | if (!p) {errno = ENOMEM; return NULL;} 118 | p->data[0] = STK_PAGEBOTTOM; 119 | p->top = 0; 120 | p->prev = s->page; 121 | s->page = p; 122 | } 123 | 124 | ret= &(s->page->data[s->page->top+1]); 125 | if (elm) memcpy(ret,elm,sz); 126 | 127 | s->page->top += slots_needed; 128 | s->page->data[s->page->top] = sz; 129 | s->count++; 130 | return ret; 131 | } 132 | 133 | void stkdrop(stk_t s) 134 | { 135 | uint16_t len; 136 | 137 | if (!s || s->count == 0) {errno= EINVAL; return ;} 138 | 139 | len = s->page->data[s->page->top] ; 140 | 141 | s->page->top -= (1+(len+1)/2); 142 | s->count--; 143 | 144 | if (s->page->top == 0) { 145 | stk_page_t *page = s->page; 146 | s->page = page->prev; 147 | free(page); 148 | } 149 | } 150 | 151 | void *stk_top(stk_t s, int delta, int *elmsize) 152 | { 153 | uint16_t *ret = NULL; 154 | uint16_t len = 0; 155 | uint16_t cur; 156 | stk_page_t *page; 157 | 158 | if (!s || s->count == 0) return NULL; 159 | 160 | if (delta < 0) delta = -delta; 161 | page = s->page; 162 | cur = page->top; 163 | 164 | for (int i = 0; i <= delta; i++) { 165 | if (cur == 0) { 166 | page = page->prev; 167 | if (!page) { errno = EINVAL; return NULL; } 168 | cur = page->top; 169 | } 170 | 171 | len = page->data[cur]; 172 | cur -= (1+(len+1)/2); 173 | ret = page->data + cur +1; 174 | } 175 | 176 | if (elmsize) *elmsize = len; 177 | return ret; 178 | } 179 | 180 | int32_t stkcount(stk_t s) 181 | { 182 | if (!s) {errno = EINVAL; return 0;} 183 | return s->count; 184 | } 185 | 186 | #endif // STK_MAIN 187 | #endif // STK_VERSION -------------------------------------------------------------------------------- /src/libs/try.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** (C) by Remo Dentato (rdentato@gmail.com) 3 | ** 4 | ** This software is distributed under the terms of the MIT license: 5 | ** https://opensource.org/licenses/MIT 6 | */ 7 | /* 8 | # Exceptions 9 | 10 | Simple implementation of try/catch. try blocks can be nested. 11 | Exceptions are positive integers 12 | 13 | #define OUTOFMEM 1 14 | #define WRONGINPUT 2 15 | #define INTERNALERR 3 16 | 17 | try { 18 | ... code ... 19 | if (something_failed) throw(execption_num) // must be > 0 20 | some_other_func(); // you can throw exceptions from other functions too 21 | ... code ... 22 | } 23 | catch(OUTOFMEM) { 24 | ... code ... 25 | } 26 | catch(WRONGINPUT) { 27 | ... code ... 28 | } 29 | catchall { // if not handled ... 30 | ... code ... 31 | } 32 | 33 | ]]] */ 34 | 35 | #ifndef TRY_VERSION 36 | #define TRY_VERSION 0x0100002C 37 | // version 1.0.2rc" 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | typedef struct try_jb_s { 45 | jmp_buf jb; // Jump buffer for setjmp/longjmp 46 | struct try_jb_s *pv; // Link to parent for nested try 47 | const char *fn; // Filename 48 | int ln; // Line number 49 | int ex; // Exception number 50 | int16_t ax; // Auxiliary information 51 | int16_t nn; // Counter 52 | } try_jb_t; 53 | 54 | #ifdef _MSC_VER 55 | #define TRY_THREAD __declspec( thread ) 56 | #else 57 | #define TRY_THREAD __thread 58 | #endif 59 | 60 | extern TRY_THREAD try_jb_t *try_jmp_list; 61 | extern char const *try_emptystring; 62 | 63 | #define TRY_CATCH_HANDLER 0 64 | 65 | #define try_INIT {.pv = try_jmp_list, .nn = 0, .fn = try_emptystring, .ln = 0, .ax = 0} 66 | 67 | #define try for ( try_jb_t try_jb = try_INIT; \ 68 | (try_jb.nn-- <= 0) && (try_jmp_list = &try_jb); \ 69 | try_jmp_list = try_jb.pv, try_jb.nn = (try_jb.ex == 0? 2 : try_jb.nn)) \ 70 | if (try_jb.nn < -1) assert(TRY_CATCH_HANDLER); \ 71 | else if (((try_jb.ex = setjmp(try_jb.jb)) == 0)) 72 | 73 | #define catch(x) else if ((try_jb.ex == (x)) && (try_jmp_list=try_jb.pv, try_jb.nn=2)) 74 | 75 | #define catchall else for ( try_jmp_list=try_jb.pv; try_jb.nn < 0; try_jb.nn=2) 76 | 77 | void try_throw(int x, int y, char *fname, int line); 78 | 79 | #define try_exp(x) x 80 | #define try_0(x,...) x 81 | #define try_1(x,y,...) y 82 | #define throw(...) if (try_jmp_list) \ 83 | try_throw(try_exp(try_0(__VA_ARGS__,1)), \ 84 | try_exp(try_1(__VA_ARGS__,0,0)),\ 85 | __FILE__, __LINE__); \ 86 | else assert(TRY_CATCH_HANDLER) 87 | 88 | #define throwagain() throw(try_jb.ex, try_jb.ax, __FILE__, __LINE__) 89 | #define thrown() try_jb.ex 90 | #define thrownaux() try_jb.ax 91 | #define thrownfile() try_jb.fn 92 | #define thrownline() try_jb.ln 93 | 94 | #define thrownerr(...) (fprintf(stderr, "" __VA_ARGS__), \ 95 | fprintf(stderr," (%d.%d):%s:%d\n",try_jb.ex,try_jb.ax,try_jb.fn,try_jb.ln)) 96 | 97 | #define throwif(e,...) if (!(e)) {} else throw( __VA_ARGS__ ) 98 | #define _throwif(...) 99 | 100 | #ifdef TRY_MAIN 101 | char const *try_emptystring = ""; 102 | TRY_THREAD try_jb_t *try_jmp_list=NULL; 103 | 104 | void try_throw(int x, int y, char *fname, int line) 105 | { 106 | if (x > 0) {\ 107 | try_jmp_list->fn = fname; \ 108 | try_jmp_list->ln = line; 109 | try_jmp_list->ax = y; 110 | longjmp(try_jmp_list->jb, x); 111 | } 112 | } 113 | 114 | #endif // TRY_MAIN 115 | 116 | #endif // TRY_VER 117 | -------------------------------------------------------------------------------- /src/libs/vec.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** (C) by Remo Dentato (rdentato@gmail.com) 3 | ** 4 | ** This software is distributed under the terms of the MIT license: 5 | ** https://opensource.org/licenses/MIT 6 | */ 7 | 8 | #ifndef VEC_VERSION 9 | #define VEC_VERSION 0x0003000B 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #define VEC_argcnt(x1,x2,x3,x4,xN, ...) xN 22 | #define VEC_argn(...) VEC_argcnt(__VA_ARGS__,4,3,2,1,0) 23 | #define VEC_argjoin(x,y) x ## y 24 | #define VEC_argcat(x,y) VEC_argjoin(x,y) 25 | #define VEC_varargs(f,...) VEC_argcat(f, VEC_argn(__VA_ARGS__))(__VA_ARGS__) 26 | 27 | #define VEC_NONDX -1 28 | #define VEC_AUXNDX -2 29 | #define VEC_ALL INT32_MAX 30 | 31 | #define VEC_FILE 0x01 32 | #define VEC_QUEUE 0x02 33 | #define VEC_MAP 0x04 34 | #define VEC_ARRAY 0x08 35 | 36 | typedef struct vec_s { 37 | uint8_t *vec; 38 | union { 39 | uint8_t *p; 40 | int32_t i; 41 | uint32_t u; 42 | } aux; 43 | int32_t cnt; 44 | int32_t sze; 45 | int32_t fst; 46 | int16_t esz; 47 | uint8_t flg; 48 | uint8_t lgn; 49 | } *vec_t; 50 | 51 | typedef union { 52 | uint32_t h; 53 | union { 54 | uint8_t *s; 55 | uint64_t u; 56 | } k; 57 | } veckey_t; 58 | 59 | #define vecnew(t) vec_new(sizeof(t)) 60 | vec_t vec_new(int32_t esz); 61 | vec_t vecfree(vec_t v); 62 | void *vectoarray(vec_t v); 63 | 64 | int vecdeq(vec_t v); 65 | void *vecenq(vec_t v, void *e); 66 | 67 | #define veccount(...) VEC_varargs(veccount,__VA_ARGS__) 68 | #define veccount1(v) vec_count(v,-1) 69 | #define veccount2(v,n) vec_count(v,n) 70 | 71 | int32_t vec_count(vec_t v,int32_t n); 72 | 73 | 74 | void *vecget(vec_t v, int32_t n); 75 | void *vecset(vec_t v,int32_t n,void *e); 76 | 77 | #define vecpush(v,e) vecset(v, VEC_NONDX, e) 78 | 79 | #define vectop(...) VEC_varargs(vec_top,__VA_ARGS__) 80 | #define vec_top1(v) vec_top(v,0) 81 | #define vec_top2(v,n) vec_top(v,n) 82 | 83 | void *vec_top(vec_t v, int32_t n); 84 | 85 | #define vecdrop(...) VEC_varargs(vec_drop,__VA_ARGS__) 86 | #define vec_drop1(v) vec_drop(v,1) 87 | #define vec_drop2(v,n) vec_drop(v,n) 88 | 89 | int vec_drop(vec_t v, int32_t n); 90 | 91 | #define vechead(v) vecget(v,VEC_AUXNDX); 92 | #define vectail(v) vectop(v) 93 | 94 | static inline void *vec(vec_t v) 95 | { return (v? v->vec : NULL); } 96 | 97 | static inline int vecclean(vec_t v) 98 | { return (v && (v->cnt = v->fst = v->flg = 0)); } 99 | 100 | #define vecauxptr(...) VEC_varargs(vec_aux_ptr,__VA_ARGS__) 101 | #define vec_aux_ptr1(v) vec_aux_getp(v) 102 | #define vec_aux_ptr2(v,p) vec_aux_setp(v,p) 103 | 104 | static inline void *vec_aux_getp(vec_t v) 105 | { return (v? v->aux.p : NULL); } 106 | 107 | static inline void *vec_aux_setp(vec_t v, void *p) 108 | { return (v? (v->aux.p = (void *)p) : NULL); } 109 | 110 | #define vecauxint(...) VEC_varargs(vec_aux_int,__VA_ARGS__) 111 | #define vec_aux_int1(v) vec_aux_geti(v) 112 | #define vec_aux_int2(v,i) vec_aux_seti(v,i) 113 | 114 | static inline int32_t vec_aux_geti(vec_t v) 115 | { return (v? v->aux.i : 0); } 116 | 117 | static inline int32_t vec_aux_seti(vec_t v, int32_t i) 118 | { return (v? (v->aux.i = i) : 0); } 119 | 120 | #define vecauxuint(...) VEC_varargs(vec_aux_uint,__VA_ARGS__) 121 | #define vec_aux_uint1(v) vec_aux_getu(v) 122 | #define vec_aux_uint2(v,u) vec_aux_setu(v,u) 123 | 124 | static inline uint32_t vec_aux_getu(vec_t v) 125 | { return (v? v->aux.u : 0); } 126 | 127 | static inline uint32_t vec_aux_setu(vec_t v, uint32_t u) 128 | { return (v? (v->aux.u = u) : 0); } 129 | 130 | 131 | #define vecconst(t,...) &((t){__VA_ARGS__}) 132 | 133 | void *vec_ptr(void *ptr); 134 | #define vecvalue(t,p) (t)(*(vec_ptr(p))); 135 | 136 | int32_t vecwrite(vec_t v, char *s, int32_t n); 137 | int32_t vecputs(vec_t v, char *s); 138 | int32_t vecprintf(vec_t v, char *fmt , ...); 139 | 140 | 141 | #ifdef VEC_MAIN 142 | 143 | void *vec_ptr(void *ptr) 144 | { 145 | static char ptrnull[16] = {0}; 146 | errno = 0; 147 | if (ptr == NULL) { errno=EINVAL; ptr=ptrnull; } 148 | return ptr; 149 | } 150 | 151 | vec_t vec_new(int32_t esz) 152 | { 153 | vec_t v = malloc(sizeof(struct vec_s)); 154 | if (v) { 155 | v->vec = NULL; 156 | v->esz = esz; v->sze = 0; 157 | v->cnt = 0; v->fst = 0; v->flg = 0; 158 | } 159 | return v; 160 | } 161 | 162 | vec_t vecfree(vec_t v) 163 | { 164 | if (v) { 165 | if (v->vec) free(v->vec); 166 | v->vec = NULL; 167 | v->esz = 0; v->sze = 0; 168 | v->cnt = 0; v->fst = 0; 169 | free(v); 170 | } 171 | return NULL; 172 | } 173 | 174 | void *vectoarray(vec_t v) 175 | { 176 | void *arr = NULL; 177 | 178 | if (v) { 179 | arr = v->vec; 180 | v->vec = NULL; 181 | vecfree(v); 182 | } 183 | 184 | return arr; 185 | } 186 | 187 | static int8_t *get_elm(vec_t v,int32_t n) 188 | { 189 | int32_t new_sze; 190 | uint8_t *new_vec; 191 | 192 | if (!v) {errno = EINVAL; return NULL;} 193 | if (n == VEC_NONDX) n = v->cnt; 194 | if (n >= v->sze) { 195 | new_sze = v->sze ? v->sze : 4; 196 | while (new_sze <= n) { 197 | new_sze = (new_sze + (new_sze/2)); 198 | } 199 | new_sze += (new_sze & 1); 200 | 201 | new_vec = realloc(v->vec, new_sze * v->esz); 202 | if (new_vec) { 203 | v->vec = new_vec; 204 | v->sze = new_sze; 205 | } 206 | else { errno = ENOMEM; return NULL; } 207 | } 208 | return (int8_t *)((v->vec) + (n * v->esz)); 209 | } 210 | 211 | void *vecset(vec_t v, int32_t n, void *e) 212 | { 213 | int8_t *elm; 214 | 215 | if (!v || !e) { errno = EINVAL; return NULL; } 216 | if (n==VEC_NONDX) n = veccount(v); 217 | elm = get_elm(v,n); 218 | if (elm) { 219 | memcpy(elm,e,v->esz); 220 | if (n >= v->cnt) v->cnt = n+1; 221 | } 222 | return elm; 223 | } 224 | 225 | void *vecget(vec_t v, int32_t n) 226 | { 227 | if (v) { 228 | if (n == VEC_NONDX) n = v->cnt-1; 229 | if (n >= 0) return (v->vec) + (n * (v->esz)); 230 | } 231 | errno = ESRCH; 232 | return NULL; 233 | } 234 | 235 | int vec_drop(vec_t v, int32_t n) 236 | { 237 | if (!v || (v->cnt == 0)) return 0; 238 | if (n >= v->cnt) n = v->cnt; 239 | return (v->cnt -= n); 240 | } 241 | 242 | void *vec_top(vec_t v, int32_t n) 243 | { 244 | int32_t k; 245 | if (n<0) n=-n; 246 | if (v) { 247 | k = (v->cnt -1) - n; 248 | if (k >= 0) return vecget(v,k); 249 | } 250 | errno = ESRCH; 251 | return NULL; 252 | } 253 | 254 | int32_t vec_count(vec_t v, int32_t newcnt) 255 | { 256 | if (!v) { errno = EINVAL; return 0; } 257 | if (v->flg & VEC_QUEUE) { 258 | if (v->fst < v->cnt) return v->cnt - v->fst; 259 | else { v->fst = 0; v->cnt = 0; } 260 | } 261 | else { 262 | if (newcnt >= 0) v->cnt = newcnt; 263 | } 264 | return v->cnt; 265 | } 266 | 267 | /* Queues are using a vector 268 | v-- cnt points to the tail 269 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 270 | | |B|C|D|E|F| | | | | | | | | | | 271 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 272 | ^-- fst points to the head 273 | */ 274 | // Enque 275 | 276 | static int vec_isnot_QUEUE(vec_t v) 277 | { 278 | if (!v) { errno = EINVAL; return 1; } 279 | if (v->vec == NULL) { v->flg = VEC_QUEUE; v->fst = 0; v->cnt = 0; } 280 | if (!(v->flg & VEC_QUEUE)) { errno = EINVAL; return 1; } 281 | return 0; 282 | } 283 | 284 | void *vecenq(vec_t v, void *e) 285 | { 286 | if (!e || vec_isnot_QUEUE(v)) { errno = EINVAL; return NULL; } 287 | 288 | if ((v->fst > (v->cnt / 2)) && (v->cnt >= v->sze)) { 289 | memmove(v->vec, v->vec+(v->fst * v->esz), (v->cnt - v->fst) * v->esz); 290 | v->cnt -= v->fst; v->fst = 0; 291 | } 292 | return vecset(v, VEC_NONDX, e); 293 | } 294 | 295 | // Deque (delete from head) 296 | int vecdeq(vec_t v) 297 | { 298 | if (vec_isnot_QUEUE(v)) return 0; 299 | v->fst++; 300 | if (v->fst >= v->cnt) {v->fst = 0; v->cnt = 0;} 301 | return (v->cnt - v->fst); 302 | } 303 | 304 | // FILE like 305 | 306 | static int vec_isnot_FILE(vec_t v) 307 | { 308 | if (!v) { errno = EINVAL; return 1; } 309 | if (v->vec == NULL) { v->flg = VEC_FILE; v->fst = 0; v->cnt = 0; v->sze = 0; v->esz = 1; } 310 | if (!(v->flg & VEC_FILE)) { errno = EINVAL; return 1; } 311 | return 0; 312 | } 313 | 314 | int32_t vecprintf(vec_t v, char *fmt , ...) 315 | { 316 | if (vec_isnot_FILE(v)) return -1; 317 | 318 | char *end, *dst; 319 | int32_t max_n; 320 | int16_t safe_limit = 16384; 321 | va_list args; 322 | int32_t n; 323 | 324 | max_n = v->sze - v->fst; 325 | 326 | if ((max_n < 32) && !(end = (char *)get_elm(v,v->fst + 32))) 327 | return -1; 328 | 329 | while (safe_limit--) { 330 | max_n = v->sze - v->fst; 331 | if (!(dst = (char *)get_elm(v,v->fst))) return -1; 332 | va_start(args, fmt); 333 | n = vsnprintf(dst, max_n, fmt, args); 334 | va_end(args); 335 | 336 | if (n < max_n) break; 337 | 338 | if (!(end = (char *)get_elm(v,(v->fst + 2*n)))) return -1; 339 | } 340 | 341 | if (!safe_limit) { errno=ENOSPC; return -1; } 342 | 343 | v->fst += n; 344 | if (v->fst > v->cnt) v->cnt = v->fst; 345 | return v->fst; 346 | } 347 | 348 | int32_t vecputs(vec_t v, char *s) 349 | { 350 | if (vec_isnot_FILE(v)) return -1; 351 | 352 | if (vecwrite(v,s,strlen(s)+1) <= 0) return -1; 353 | v->fst -= 1; 354 | return v->fst; 355 | } 356 | 357 | int32_t vecwrite(vec_t v, char *s, int32_t n) 358 | { 359 | if (vec_isnot_FILE(v)) return -1; 360 | char *end; 361 | char *dst; 362 | 363 | if (!(end = (char *)get_elm(v,v->fst+n))) return -1; 364 | if (!(dst = (char *)get_elm(v,v->fst))) return -1; 365 | memcpy(dst,s,n); 366 | v->fst += n; 367 | if (v->fst >= v->cnt) v->cnt = v->fst; 368 | return v->fst; 369 | } 370 | 371 | int32_t vecfread(vec_t v, int32_t len, FILE *f) 372 | { 373 | if (vec_isnot_FILE(v)) return -1; 374 | return v->fst; 375 | } 376 | 377 | int32_t vecseek(vec_t v, int32_t pos) 378 | { 379 | if (vec_isnot_FILE(v)) return -1; 380 | return v->fst; 381 | } 382 | 383 | int32_t vecpos(vec_t v) 384 | { 385 | if (vec_isnot_FILE(v)) return -1; 386 | return v->fst; 387 | } 388 | 389 | #if 0 // TODO ⚒ 390 | // Mapping (hashtable) 391 | int vec_isnot_MAP(vec_t v) 392 | { 393 | if (!v) { errno = EINVAL; return 0; } 394 | if (v->vec == NULL) v->flg = VEC_MAP; 395 | if (!(v->flg & VEC_MAP)) { errno = EINVAL; return 0; } 396 | return 0; 397 | } 398 | 399 | void *vec_map(vec_t v, void *e) 400 | { 401 | if (!e || vec_isnot_MAP(v)) { errno = EINVAL; return NULL; } 402 | if (v->vec == NULL) v->flg = VEC_MAP; 403 | if (!(v->flg & VEC_MAP)) { errno = EINVAL; return NULL; } 404 | 405 | return NULL; 406 | } 407 | 408 | int vec_unmap(vec_t v, veckey_t *k) 409 | { 410 | return 0; 411 | } 412 | 413 | void *vecmapint(vec_t v, int32_t k, void *e) 414 | { 415 | return NULL; 416 | } 417 | 418 | void *vecmapuint(vec_t v, uint32_t k, void *e) 419 | { 420 | return NULL; 421 | } 422 | 423 | void *vecmapstr(vec_t v, char *k, void *e) 424 | { 425 | return NULL; 426 | } 427 | 428 | void *vec_search(vec_t v,veckey_t *k) 429 | { 430 | return NULL; 431 | } 432 | #endif // TODO 433 | 434 | #endif // VEC_MAIN 435 | #endif // VEC_VERSION 436 | -------------------------------------------------------------------------------- /src/libs/vrg.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** (C) by Remo Dentato (rdentato@gmail.com) 3 | ** 4 | ** This software is distributed under the terms of the MIT license: 5 | ** https://opensource.org/licenses/MIT 6 | */ 7 | 8 | /* 9 | # Variadic functions 10 | 11 | Say you want to define a variadic function with the following prototype: 12 | 13 | myfunc(int a [, char b [, void *c]]) 14 | 15 | In other words, you want `b` and `c` to be optional. 16 | 17 | Simply, define your function with another name (say `my_func()`) and specify 18 | how it should be called when invoked with 1, 2 or 3 paramenters as shown 19 | in the example below. 20 | 21 | Example: 22 | 23 | #include "utl.h" 24 | 25 | int my_func(int a, char b, void *c); 26 | 27 | #define myfunc(...) vrg(myfunc, __VA_ARGS__) 28 | #define myfunc1(a) my_func(a,'\0',NULL) 29 | #define myfunc2(a,b) my_func(a,b,NULL) 30 | #define myfunc3(a,b,c) my_func(a,b,c) 31 | 32 | # Command line options 33 | A minimal replacement of getopt. 34 | 35 | void vrgerror(char *s) 36 | { 37 | // Will be called if an error occurs 38 | fprintf(stderr,"Missing argument for option: `%s`\n",s); 39 | } 40 | 41 | ... In the code: 42 | 43 | vrgoptions(argc,argv) { // as received by main 44 | 45 | vrgopt("-f filename","Load file") { 46 | // Will work both for `-f pippo` and `-fpippo` 47 | // Set what you need to set 48 | printf("file: `.*s`",vrglen, vrgoptarg); 49 | } 50 | 51 | vrgopt("-o [filename]", "Enable output (on stdout by default)") { 52 | // Here filename is optional 53 | } 54 | } 55 | // when here the vrgargn variable contains the current argument 56 | // to be processed 57 | // Example: prg -f fname pluto 58 | // if -f takes fname as argument, vrgargn will be 3 59 | // 60 | 61 | vrghelp(); // Will print the list of defined options 62 | 63 | 64 | ***/ 65 | 66 | #ifndef VRG_VERSION 67 | #define VRG_VERSION 0x0001000C 68 | 69 | #include 70 | #include 71 | #include 72 | #include 73 | 74 | #define vrg_cnt(vrg1,vrg2,vrg3,vrg4,vrg5,vrg6,vrg7,vrg8,vrgN, ...) vrgN 75 | #define vrg_argn(...) vrg_cnt(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0) 76 | #define vrg_cat0(x,y) x ## y 77 | #define vrg_cat(x,y) vrg_cat0(x,y) 78 | 79 | #define vrg(vrg_f,...) vrg_cat(vrg_f, vrg_argn(__VA_ARGS__))(__VA_ARGS__) 80 | 81 | // Command line options arguments 82 | 83 | typedef struct vrg_opt_s { 84 | struct vrg_opt_s *next; 85 | char *opt; 86 | char *desc; 87 | } vrg_opt_t; 88 | 89 | extern int vrgargn; 90 | extern int vrg_argc; 91 | extern char **vrg_argv; 92 | 93 | extern char *vrgoptarg; 94 | extern int vrglen; 95 | char *vrg_ver; 96 | 97 | #define vrgver(s) vrg_ver = (s) 98 | 99 | extern vrg_opt_t *vrg_opt_list; 100 | 101 | int vrg_isopt(char *opt); 102 | int vrghelp(); 103 | 104 | int vrg_checkopt(); 105 | 106 | #define vrgoptions(vrg_argc_,vrg_argv_) \ 107 | for (vrgargn = 0, vrg_argc = vrg_argc_, vrg_argv = vrg_argv_, errno = 0 , vrglen = 1\ 108 | ; vrg_checkopt() \ 109 | ; vrgargn++ ) 110 | 111 | #define vrgopt(vrg_opt_,vrg_desc_) \ 112 | static vrg_opt_t vrg_cat(vrg_OPT_,__LINE__);\ 113 | if (vrgargn == 0) { \ 114 | vrg_cat(vrg_OPT_,__LINE__) .next = vrg_opt_list; \ 115 | vrg_opt_list = &(vrg_cat(vrg_OPT_,__LINE__)); \ 116 | vrg_cat(vrg_OPT_,__LINE__) .opt = vrg_opt_; \ 117 | vrg_cat(vrg_OPT_,__LINE__) .desc = vrg_desc_; \ 118 | } \ 119 | else if (!vrg_isopt(vrg_cat(vrg_OPT_,__LINE__) .opt)) ; \ 120 | else 121 | 122 | 123 | #ifdef VRG_MAIN 124 | 125 | #include "dbg.h" 126 | 127 | int vrgargn; 128 | int vrg_argc; 129 | char **vrg_argv=NULL; 130 | 131 | char *vrgoptarg; 132 | int vrglen; 133 | 134 | char *vrgver = NULL; 135 | 136 | vrg_opt_t *vrg_opt_list = NULL; 137 | 138 | static int vrg_err(char *opt) 139 | { 140 | char *s = opt; 141 | while (*s && !isspace(*s)) s++; 142 | fprintf(stderr,"ERROR: Invalid '%.*s'\n", (int)(s-opt),opt); 143 | return vrghelp(); 144 | } 145 | 146 | int vrghelp() 147 | { 148 | char *s = vrg_argv[0]; 149 | while (*s) s++; 150 | while ((s > vrg_argv[0]) && (s[-1] !='\\') && (s[-1] != '/')) s--; 151 | if (vrg_ver && *vrg_ver) 152 | fprintf(stderr, "%s\n",vrg_ver); 153 | fprintf(stderr, "Usage: %s [OPTIONS] ...\n",s); 154 | for (vrg_opt_t *opt=vrg_opt_list; opt; opt=opt->next) { 155 | fprintf(stderr, " %s\t\t%s\n",opt->opt,opt->desc); 156 | } 157 | exit(1); 158 | } 159 | 160 | int vrg_checkopt() 161 | { 162 | 163 | if (vrgargn == 0) return 1; 164 | 165 | if (vrglen == 0) { 166 | vrg_err(vrg_argv[vrgargn-1]); 167 | return 0; 168 | } 169 | vrglen = 0; 170 | 171 | if (vrgargn >= vrg_argc) return 0; 172 | 173 | if (!(vrg_argv[vrgargn][0] == '-' && vrg_argv[vrgargn][1])) 174 | return 0; 175 | 176 | 177 | if (vrg_isopt("--")) { 178 | vrgargn++; return 0; 179 | } 180 | 181 | if (vrg_isopt("-h")) { 182 | vrgargn++; 183 | vrghelp(); return 1; 184 | } 185 | 186 | return 1; 187 | } 188 | 189 | 190 | // Example: "-o [filename]" 191 | // "myprg -fpippo" 192 | // 193 | int vrg_isopt(char *opt) 194 | { 195 | int ret = 0; 196 | int opt_ndx=0; 197 | int arg_ndx=0; 198 | char *arg=vrg_argv[vrgargn]; 199 | 200 | vrgoptarg = NULL; 201 | 202 | if (arg == NULL) return 0; 203 | 204 | _dbgtrc("ISOPT: '%s' '%s'",opt,arg); 205 | 206 | while (opt[opt_ndx] && !isspace(opt[opt_ndx])) opt_ndx++ ; 207 | if (strncmp(opt,arg,opt_ndx) == 0) { 208 | ret = 1; 209 | arg_ndx = opt_ndx; 210 | while (opt[opt_ndx] && isspace(opt[opt_ndx])) opt_ndx++; // I'm on the arg. 211 | if (opt[opt_ndx]) { // Need arg 212 | _dbgtrc("OPT USE ARG (%c)",opt[opt_ndx]); 213 | if (arg[arg_ndx]) 214 | vrgoptarg = arg+arg_ndx; 215 | else if (vrgargn+1 < vrg_argc) { 216 | if (vrg_argv[vrgargn+1][0] != '-') 217 | vrgoptarg = vrg_argv[++vrgargn]; 218 | } 219 | if (vrgoptarg == NULL) vrgoptarg = ""; 220 | 221 | _dbgtrc("OPT USE ARG: '%s'",vrgoptarg); 222 | 223 | if (*vrgoptarg == '\0' && opt[opt_ndx] != '[') { 224 | errno = vrg_err(opt); 225 | ret = 0; 226 | } 227 | } 228 | vrglen = ret; 229 | } 230 | return ret; 231 | } 232 | 233 | #endif 234 | #endif -------------------------------------------------------------------------------- /src/makefile: -------------------------------------------------------------------------------- 1 | ## 2 | ## (C) by Remo Dentato (rdentato@gmail.com) 3 | ## 4 | ## This software is distributed under the terms of the MIT license: 5 | ## https://opensource.org/licenses/MIT 6 | ## 7 | 8 | # Handle msys adding .exe to the program 9 | _EXE=.exe 10 | 11 | ifeq "$(COMSPEC)" "" 12 | _EXE= 13 | endif 14 | 15 | # Force gcc as compiler 16 | CC=gcc 17 | #CC=clang 18 | 19 | # Comment the following if you want to use fgets based REPL 20 | LINENOISE=-DUSE_LINENOISE 21 | 22 | # Leave uncommented the desired level of debug 23 | DBG=-DNDEBUG 24 | DBG=-DDEBUG=DEBUG_ERROR 25 | DBG=-DDEBUG=DEBUG_WARN 26 | DBG=-DDEBUG=DEBUG_INFO 27 | DBG=-DDEBUG=DEBUG_TEST 28 | #DBG=-DDEBUG=DEBUG_MEM 29 | 30 | # Optimization level (regular, gdb, valgrind) 31 | OPT=-O2 32 | #OPT=-g 33 | #OPT=-ggdb3 34 | 35 | # Uncomment gdb read executable 36 | CFLAGS= $(OPT) -Wall $(LINENOISE) $(DBG) 37 | 38 | OBJS=libs.o dict.o eval.o repl.o gerku.o abstract.o hardwired.o 39 | 40 | 41 | gerku$(_EXE): $(OBJS) 42 | $(CC) -o gerku$(_EXE) $(OBJS) 43 | 44 | libs.o: $(wildcard libs/*.h) 45 | $(CC) $(CFLAGS) -c -o libs.o libs.c 46 | 47 | vm$(_EXE): vm.o libs.o 48 | $(CC) -o vm$(_EXE) vm.o libs.o 49 | 50 | %.o: %.c 51 | $(CC) $(CFLAGS) -c -o $@ $< 52 | 53 | clean: 54 | rm -f gerku$(_EXE) *.o 55 | 56 | -------------------------------------------------------------------------------- /src/repl.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | ** (C) by Remo Dentato (rdentato@gmail.com) 4 | ** 5 | ** This software is distributed under the terms of the MIT license: 6 | ** https://opensource.org/licenses/MIT 7 | */ 8 | 9 | #include "libs.h" 10 | #include "dict.h" 11 | #include "eval.h" 12 | #include "abstract.h" 13 | 14 | 15 | #define QUIT 255 16 | #define WIPE 254 17 | 18 | static int trace = 0; 19 | static int wipe = 1; 20 | 21 | #define chkcmd(s,l,n) ((strncmp(s,l,n) == 0) && ((l[n] == '\0') || isspace(l[n]))) 22 | static int command(char *ln) 23 | { 24 | if (chkcmd("list",ln,4)) { 25 | list_words(stdout,0); 26 | list_hardwired(stdout); 27 | return 0; 28 | } 29 | 30 | if (chkcmd("trace",ln,5)) { 31 | trace = !trace; 32 | fprintf(stderr,"Trace is %s\n",&("OFF\0ON"[(trace*4)])); 33 | return 0; 34 | } 35 | 36 | if (chkcmd("print",ln,5)) { 37 | return 0; 38 | } 39 | 40 | if (chkcmd("def",ln,3)) { 41 | 42 | return add_word(ln+3); 43 | } 44 | 45 | if (chkcmd("del",ln,3)) { 46 | skp("&+s",ln+3,&ln); 47 | if (strncmp(ln,"!all",4) == 0) 48 | return del_dict(); 49 | else 50 | return del_word(ln); 51 | } 52 | 53 | if (chkcmd("wipe",ln,4)) { 54 | skp("&+![a]",ln,&ln); 55 | if (strncmp(ln,"auto",4) == 0) { 56 | wipe = !wipe; 57 | fprintf(stderr,"Wipe is %s\n",&("OFF\0ON"[(wipe*4)])); 58 | } 59 | return WIPE; 60 | } 61 | 62 | if (chkcmd("abstract",ln,8)) { 63 | char *a; 64 | skp("&+s",ln+8,&ln); 65 | a = abstract(ln,0,0,NULL,0); 66 | free(a); 67 | return 0; 68 | } 69 | 70 | if (chkcmd("load",ln,4)) { 71 | skp("&+s",ln+4,&ln); 72 | if (load_defs(ln)) 73 | fprintf(stderr,"Unable to load definitions.\n"); 74 | return 0; 75 | } 76 | 77 | if (chkcmd("save",ln,4)) { 78 | skp("&+s",ln+4,&ln); 79 | if (save_defs(ln)) 80 | fprintf(stderr,"Unable to save definitions.\n"); 81 | return 0; 82 | } 83 | 84 | if (chkcmd("quit",ln,4)) { 85 | return QUIT; 86 | } 87 | 88 | if (!chkcmd("help",ln,4)) { 89 | fputs("Available commands:\n",stderr); 90 | } 91 | 92 | fputs(" !help this help\n",stderr ); 93 | fputs(" !list list of defined words\n",stderr ); 94 | fputs(" !load file load definitions from file\n",stderr ); 95 | fputs(" !save file save definitions to file\n",stderr ); 96 | fputs(" !print print current stack\n",stderr ); 97 | fputs(" !trace toggle reduction tracing\n",stderr ); 98 | fputs(" !quit exit the repl\n",stderr ); 99 | fputs(" !def ... define a new word\n",stderr ); 100 | fputs(" !del word | !all delete a word [all words!]\n",stderr ); 101 | fputs(" !wipe [auto] wipe the stack [and toggles autowipe]\n",stderr ); 102 | 103 | return 0; 104 | } 105 | 106 | #define MAXLEN 1024 107 | 108 | #ifdef USE_LINENOISE 109 | #define get_line(l) (l = linenoise("gerku> ")) 110 | char *buf=NULL; 111 | #define clear_line(l) (l? free(l) : 0, l = NULL) 112 | 113 | #define load_history() do { linenoiseHistorySetMaxLen(50);\ 114 | linenoiseHistoryLoad(".history.grk"); \ 115 | } while(0) 116 | 117 | #define add_history(line) do { linenoiseHistoryAdd(line); \ 118 | linenoiseHistorySave(".history.grk"); \ 119 | } while(0) 120 | #else 121 | char buf[MAXLEN]; 122 | #define get_line(l) (fprintf(stdout,"gerku> "),fgets(l,MAXLEN,stdin)) 123 | #define clear_line(l) (*l='\0'); 124 | #define load_history() 125 | #define add_history(line) 126 | #endif 127 | 128 | 129 | int run_file(char *filename, vec_t stack) 130 | { 131 | int ret =1; 132 | FILE *f = NULL; 133 | char *ln; 134 | char *line = NULL; 135 | 136 | int trc = trace; 137 | int wpe = wipe; 138 | 139 | f=fopen(filename,"r"); 140 | if (f) { 141 | ret = 0; 142 | wipe = 0; 143 | trace = 0; 144 | 145 | line = malloc(MAXLEN); 146 | throwif(!line,ENOMEM); 147 | 148 | _dbgtrc("Running %s",filename); 149 | 150 | while (fgets(line,MAXLEN,f)) { 151 | ln = line; 152 | skp("&+s",ln,&ln); 153 | _dbgtrc("line: '%s'",ln); 154 | if (*ln && *ln != '#') { 155 | ret = (*ln == '!')? command(ln+1) : eval(stack,ln,trace); 156 | } 157 | if (ret == WIPE || wipe) wipe_stack(stack); 158 | } 159 | 160 | free(line); 161 | fclose(f); 162 | } 163 | 164 | print_stack(stack); 165 | trace = trc; 166 | wipe = wpe; 167 | return ret; 168 | } 169 | 170 | 171 | extern char *gerku_version; 172 | int repl(vec_t stack) 173 | { 174 | int ret = 0; 175 | char *ln; 176 | char *line = buf; 177 | 178 | printf("%s\nType ! for available commands.\n",gerku_version); 179 | print_stack(stack); 180 | _dbgtrc("DBG TRACE"); 181 | load_history(); 182 | 183 | while((ret != QUIT) && (get_line(line) != NULL)) { 184 | add_history(line); 185 | 186 | ln = line; 187 | skp("&+s",ln,&ln); 188 | 189 | _dbgclk("elapsed") { 190 | ret = (*ln == '!')? command(ln+1) : eval(stack,ln,trace); 191 | } 192 | 193 | clear_line(line); 194 | if (ret == WIPE) wipe_stack(stack); 195 | print_stack(stack); 196 | if (wipe) wipe_stack(stack); 197 | } 198 | return ret; 199 | } 200 | 201 | -------------------------------------------------------------------------------- /src/repl.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** (C) by Remo Dentato (rdentato@gmail.com) 3 | ** 4 | ** This software is distributed under the terms of the MIT license: 5 | ** https://opensource.org/licenses/MIT 6 | */ 7 | 8 | #ifndef REPL_H 9 | #define REPL_H 10 | 11 | #include "libs.h" 12 | 13 | int run_file(char *filename, vec_t stack); 14 | int repl(vec_t stack); 15 | 16 | #endif -------------------------------------------------------------------------------- /src/vm.c: -------------------------------------------------------------------------------- 1 | 2 | #include "libs.h" 3 | 4 | /* 5 | 6 | Quote 7 | Word 8 | 9 | Number 10 | hardw 11 | 12 | */ 13 | 14 | 15 | 16 | 17 | 18 | int main(int argc, char *argv[]) 19 | { 20 | stk_t evalstack; 21 | char *s; 22 | int l; 23 | 24 | evalstack = stknew(); 25 | 26 | stkpush(evalstack,6,"pippo"); 27 | stkpush(evalstack,6,"pluto"); 28 | stkpush(evalstack,5,"topo"); 29 | 30 | s = stktop(evalstack,-1); 31 | 32 | printf("%d:[%s]\n",stkcount(evalstack),s); 33 | 34 | s = stktop(evalstack,-7); 35 | dbgchk(s==NULL); 36 | stkdrop(evalstack); 37 | dbgchk(stkcount(evalstack) == 2); 38 | 39 | s = stktop(evalstack,0,&l); 40 | stkdrop(evalstack); 41 | stkdrop(evalstack); 42 | dbgchk(stkcount(evalstack) == 0); 43 | stkdrop(evalstack); 44 | dbgchk(stkcount(evalstack) == 0); 45 | 46 | evalstack = stkfree(evalstack); 47 | 48 | } 49 | 50 | --------------------------------------------------------------------------------