├── .gitignore ├── Test ├── stdlib.expect ├── elseif.expect ├── run3.expect ├── fibo.expect ├── array.expect ├── run1.expect ├── run2.expect ├── fibo.ts ├── run2.ts ├── runtests.sh ├── run3.ts ├── elseif.ts ├── run1.ts ├── stdlib.ts └── array.ts ├── fibo.ts ├── Makefile ├── tinyscript_lib.h ├── LICENSE.linenoise ├── COPYING ├── fibo.c ├── linenoise.h ├── tinyscript.h ├── main.c ├── tinyscript_lib.c ├── README.md ├── tinyscript.c └── linenoise.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.o 3 | *.zip 4 | *~ 5 | tstest 6 | -------------------------------------------------------------------------------- /Test/stdlib.expect: -------------------------------------------------------------------------------- 1 | with bool coercing our logic works! 2 | 3 3 | 98 4 | 6 5 | 99 6 | 50 7 | 0 8 | 2 9 | 2 10 | x42 11 | -------------------------------------------------------------------------------- /Test/elseif.expect: -------------------------------------------------------------------------------- 1 | a less 2 | b less 3 | a one 4 | b one 5 | a two 6 | b two 7 | a three 8 | b three 9 | a more 10 | b more 11 | -------------------------------------------------------------------------------- /Test/run3.expect: -------------------------------------------------------------------------------- 1 | x=1 y=2 2 | 3 3 | x=1 y=4 4 | x=5 y=3 5 | 8 6 | x=8 y=9 7 | x=7 y=17 8 | 24 9 | 4 10 | 6 11 | 8 12 | 42 13 | 1000 14 | 559 15 | 42 16 | -------------------------------------------------------------------------------- /Test/fibo.expect: -------------------------------------------------------------------------------- 1 | fibo(1) = 1 2 | fibo(2) = 1 3 | fibo(3) = 2 4 | fibo(4) = 3 5 | fibo(5) = 5 6 | fibo(6) = 8 7 | fibo(7) = 13 8 | fibo(8) = 21 9 | fibo(9) = 34 10 | fibo(10) = 55 11 | fibo(11) = 89 12 | fibo(12) = 144 13 | -------------------------------------------------------------------------------- /Test/array.expect: -------------------------------------------------------------------------------- 1 | x: 0, 0, 0 2 | x: 1, 0, 0 3 | x: 1, 2, 3 4 | x: 4, 5, 6 5 | y: 12, 34, 56 6 | sum of y = 102 7 | array len=3, first element=12 8 | 9 | the array length can be read but not changed 10 | x(-1) is 3 11 | out of bounds in: x(-1) = 20 12 | script error -6 13 | -------------------------------------------------------------------------------- /Test/run1.expect: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 4 5 | 5 6 | 6 7 | 7 8 | 8 9 | 9 10 | 10 11 | 3 12 | 16 13 | 291 14 | 10 15 | 65261 16 | 76476 17 | 48 18 | 10 19 | 39 20 | x=7 21 | or we can say it with {}: x+1=8 22 | now test if statements 23 | x=7 y=0 24 | x and y differ 25 | x=2 26 | now x=7 27 | -------------------------------------------------------------------------------- /Test/run2.expect: -------------------------------------------------------------------------------- 1 | 1 : 1 usrfunc = 2 2 | 2 : 4 usrfunc = 5 3 | 3 : 9 usrfunc = 13 4 | 4 : 16 usrfunc = 20 5 | 5 : 25 usrfunc = 34 6 | 6 : 36 usrfunc = 45 7 | 7 : 49 usrfunc = 65 8 | 8 : 64 usrfunc = 80 9 | 9 : 81 usrfunc = 106 10 | 10 : 100 usrfunc = 125 11 | global= 99 12 | global= 123 13 | global= 99 14 | -------------------------------------------------------------------------------- /Test/fibo.ts: -------------------------------------------------------------------------------- 1 | # 2 | # simple fibonacci demo 3 | # 4 | 5 | # calculate fibo(n) 6 | # parameter in n 7 | # returns result in r 8 | var r=0 9 | func fibo(n) { 10 | if (n<2) { 11 | return n 12 | } else { 13 | return fibo(n-1) + fibo(n-2) 14 | } 15 | } 16 | 17 | var i=1 18 | while i<=12 { 19 | r=fibo(i) 20 | print "fibo(",i,") = ",r 21 | i=i+1 22 | } 23 | -------------------------------------------------------------------------------- /Test/run2.ts: -------------------------------------------------------------------------------- 1 | # 2 | # test while statements 3 | # 4 | var i=1 5 | 6 | while i<=10 { 7 | var z=(i+1)/2 8 | print i, " : ", i*i, " usrfunc = ", dsqr(i, z) 9 | i=i+1 10 | } 11 | 12 | # 13 | # test procedures 14 | # 15 | var global=99 16 | 17 | func printglobal { 18 | print "global= ", global 19 | } 20 | func myfunc() { 21 | var global=123 22 | printglobal() 23 | } 24 | 25 | printglobal() 26 | myfunc() 27 | printglobal() 28 | -------------------------------------------------------------------------------- /Test/runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # run regression tests on interpreter 4 | 5 | PROG=../tstest 6 | ok="ok" 7 | endmsg=$ok 8 | 9 | # 10 | # run regression tests 11 | # 12 | for i in *.ts 13 | do 14 | j=`basename $i .ts` 15 | echo $i ":" $j 16 | $PROG $i > $j.txt 17 | if diff -ub $j.expect $j.txt 18 | then 19 | echo $j passed 20 | rm -f $j.txt 21 | else 22 | echo $j failed 23 | endmsg="TEST FAILURES" 24 | fi 25 | done 26 | echo $endmsg 27 | -------------------------------------------------------------------------------- /fibo.ts: -------------------------------------------------------------------------------- 1 | # 2 | # simple fibonacci demo 3 | # 4 | 5 | # calculate fibo(n) 6 | 7 | func fibo(n) { 8 | if (n<2){ 9 | return n 10 | }else{ 11 | return fibo(n-1)+fibo(n-2) 12 | } 13 | } 14 | 15 | # convert cycles to milliseconds 16 | func calcms() { 17 | ms=(cycles+40000)/80000 18 | } 19 | 20 | var i=1 21 | var cycles=0 22 | var ms=0 23 | var r=0 24 | while i<=8 { 25 | cycles=getcnt() 26 | r=fibo(i) 27 | cycles=getcnt()-cycles 28 | calcms() 29 | print "fibo(",i,") = ",r, " ", ms, " ms (", cycles, " cycles)" 30 | i=i+1 31 | } 32 | -------------------------------------------------------------------------------- /Test/run3.ts: -------------------------------------------------------------------------------- 1 | func f(x,y) { 2 | print "x=", x, " y=", y 3 | return x+y 4 | } 5 | 6 | print f(1,2) 7 | print f(f(1,4),3) 8 | print f(7,f(8,9)) 9 | 10 | func g(x) { 11 | return x<<1 12 | return x 13 | } 14 | 15 | print g(2) 16 | print g(3) 17 | 18 | func in_if(x) { 19 | if x < 10 { 20 | return x 21 | } 22 | return 42 23 | } 24 | 25 | func inner(x) { 26 | return 555 27 | } 28 | 29 | func outer(x) { 30 | var y = inner(x) 31 | return x + y 32 | } 33 | 34 | print in_if(8) 35 | print in_if(11) 36 | print outer(445) 37 | print outer(in_if(4)) 38 | print in_if(outer(1)) 39 | -------------------------------------------------------------------------------- /Test/elseif.ts: -------------------------------------------------------------------------------- 1 | func test_a(x){ 2 | if x<1{ 3 | print "a less" 4 | }else{ 5 | if(x=1){ 6 | print "a one" 7 | }else{ 8 | if(x=2){ 9 | print "a two" 10 | }else{ 11 | if(x=3){ 12 | print "a three" 13 | }else{ 14 | print "a more" 15 | } 16 | } 17 | } 18 | } 19 | } 20 | 21 | func test_b(x){ 22 | if(x<1){ 23 | print "b less" 24 | }elseif(x=1){ 25 | print "b one" 26 | }elseif(x=2) { 27 | print "b two" 28 | }elseif(x=3){ 29 | print "b three" 30 | }else{ 31 | print "b more" 32 | } 33 | } 34 | 35 | var i=0 36 | while i<5{ 37 | test_a(i) 38 | test_b(i) 39 | i=i+1 40 | } 41 | -------------------------------------------------------------------------------- /Test/run1.ts: -------------------------------------------------------------------------------- 1 | 2 | var x=2 3 | print 1 4 | print x 5 | print x+1 6 | print x*x 7 | print (7-2) 8 | print 1+2+3 9 | x=7 10 | print x 11 | print (-x)+15 12 | print 6+x/2 13 | print x*2-4 14 | print 123%10 15 | print 0x10 16 | print 0x123 17 | print 0xa 18 | print 0xFEED 19 | print 0x12abc 20 | print '0' 21 | print '\n' 22 | print '\'' 23 | 24 | # now test strings 25 | print "x=", x 26 | print {or we can say it with {}: x+1=}, (x+(x-(x))) + 1 27 | 28 | print "now test if statements" 29 | var y=0 30 | print "x=", x, " y=", y 31 | if (y = x) { 32 | print "x and y are equal" 33 | } else { 34 | print "x and y differ" 35 | } 36 | if (1) { 37 | var x=2 38 | print "x=", x 39 | } 40 | print "now x=", x 41 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # define the library you're using for READLINE by selecting the 2 | # appropriate two lines 3 | 4 | # for GNU Readline 5 | #READLINE=-lreadline 6 | #READLINE_DEFS=-DREADLINE 7 | 8 | # for linenoise 9 | #READLINE=linenoise.o 10 | #READLINE_DEFS=-DLINENOISE 11 | 12 | # for neither, just leave both undefined 13 | 14 | OPTS=-g -Og 15 | CC=gcc 16 | CFLAGS=$(OPTS) $(READLINE_DEFS) -Wall 17 | 18 | OBJS=main.o tinyscript.o tinyscript_lib.o 19 | 20 | tstest: $(OBJS) $(READLINE) 21 | $(CC) $(CFLAGS) -o tstest $(OBJS) $(READLINE) 22 | 23 | clean: 24 | rm -f *.o *.elf 25 | 26 | test: tstest 27 | (cd Test; ./runtests.sh) 28 | 29 | fibo.elf: fibo.c fibo.h tinyscript.c 30 | propeller-elf-gcc -o fibo.elf -mlmm -Os fibo.c fibo.h tinyscript.c 31 | 32 | fibo.h: fibo.ts 33 | xxd -i fibo.ts > fibo.h 34 | -------------------------------------------------------------------------------- /Test/stdlib.ts: -------------------------------------------------------------------------------- 1 | var fd_sequence = 0 2 | 3 | # mock file opening function to demonstrate bool coercing 4 | func fopen(name) { 5 | fd_sequence = fd_sequence + 1 6 | return fd_sequence 7 | } 8 | 9 | var first_fd = fopen(0) 10 | var second_fd = fopen(0) 11 | 12 | if first_fd & second_fd { 13 | print "both are succesfully open, but this never gets printed" 14 | } 15 | 16 | if bool(first_fd) & bool(second_fd) { 17 | print "with bool coercing our logic works!" 18 | } 19 | 20 | var lst = list_new(5) 21 | list_push__(lst, 'a', 'b', 'c') 22 | print list_size(lst) 23 | print list_get(lst, 1) 24 | 25 | var new_list = list_cat(lst, lst) 26 | print list_size(new_list) 27 | list_free(lst) 28 | print list_pop(new_list) 29 | list_set(new_list, 0, 50) 30 | list_truncate(new_list, 1) 31 | print list_pop(new_list) 32 | print list_size(new_list) 33 | list_free(new_list) 34 | 35 | var small_list = list_new(1) 36 | list_push(small_list, 1) 37 | small_list = list_expand(small_list, 2) 38 | list_push(small_list, 1) 39 | print list_size(small_list) 40 | var duped_list = list_dup(small_list) 41 | print list_size(duped_list) 42 | 43 | list_free(duped_list) 44 | list_free(small_list) 45 | 46 | var format = list_new(10) 47 | list_push__(format, 'x', '%', 'd') 48 | list_push(format, '\n') 49 | printf(format, 42) 50 | 51 | -------------------------------------------------------------------------------- /tinyscript_lib.h: -------------------------------------------------------------------------------- 1 | #ifndef TINYSCRIPT_LIB_H 2 | #define TINYSCRIPT_LIB_H 3 | 4 | #include 5 | #include "tinyscript.h" 6 | 7 | /* User needs to define these */ 8 | void *ts_malloc(Val size); 9 | void ts_free(void *p); 10 | 11 | /* Call this to initialize the standard library */ 12 | int ts_define_funcs(); 13 | 14 | /* List type */ 15 | typedef struct ts_list { 16 | Val size; 17 | uint8_t * data; 18 | Val capacity; 19 | } ts_list; 20 | 21 | ts_list * ts_list_new(Val capacity); 22 | ts_list * ts_list_dup(ts_list * list); 23 | void ts_list_free(ts_list * list); 24 | 25 | /* Get element from list end or index, returns -1 when no element available */ 26 | Val ts_list_pop(ts_list * list); 27 | Val ts_list_get(ts_list * list, Val idx); 28 | 29 | /* Add element or elements to list end or set index. Return success status */ 30 | /* The push methods are otherwise identical, but the underscore ones are 31 | convenience methods to push more in a single line */ 32 | bool ts_list_push(ts_list * list, Val val); 33 | bool ts_list_push_(ts_list * list, Val val1, Val val2); 34 | bool ts_list_push__(ts_list * list, Val val1, Val val2, Val val3); 35 | bool ts_list_set(ts_list * list, Val idx, Val val); 36 | Val ts_list_size(ts_list * list); 37 | 38 | /* Utility functions */ 39 | char * ts_list_to_string(const ts_list * list); 40 | ts_list * ts_string_to_list(const char * str); 41 | ts_list * ts_bytes_to_list(const char * str, int num_bytes); 42 | 43 | #endif /* TINYSCRIPT_LIB_H */ 44 | -------------------------------------------------------------------------------- /LICENSE.linenoise: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2014, Salvatore Sanfilippo 2 | Copyright (c) 2010-2013, Pieter Noordhuis 3 | 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | /* Tinyscript interpreter 2 | * 3 | * Copyright 2016-2021 Total Spectrum Software Inc. 4 | * 5 | * +-------------------------------------------------------------------- 6 | * ¦ TERMS OF USE: MIT License 7 | * +-------------------------------------------------------------------- 8 | * Permission is hereby granted, free of charge, to any person obtaining 9 | * a copy of this software and associated documentation files 10 | * (the "Software"), to deal in the Software without restriction, 11 | * including without limitation the rights to use, copy, modify, merge, 12 | * publish, distribute, sublicense, and/or sell copies of the Software, 13 | * and to permit persons to whom the Software is furnished to do so, 14 | * subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be 17 | * included in all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | * +-------------------------------------------------------------------- 27 | */ 28 | 29 | -------------------------------------------------------------------------------- /fibo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "tinyscript.h" 4 | #include "fibo.h" 5 | 6 | #include 7 | #define ARENA_SIZE 4096 8 | 9 | int inchar() { 10 | return -1; 11 | } 12 | void outchar(int c) { 13 | putchar(c); 14 | } 15 | 16 | static Val getcnt_fn() 17 | { 18 | #ifdef CNT 19 | return CNT; 20 | #else 21 | return _cnt(); 22 | #endif 23 | } 24 | static Val waitcnt_fn(Val when) 25 | { 26 | waitcnt(when); 27 | return when; 28 | } 29 | static Val pinout_fn(Val pin, Val onoff) 30 | { 31 | #if defined(__riscv) || defined(__propeller2__) 32 | _pinw(pin, onoff); 33 | #else 34 | unsigned mask = 1< 11 | * Copyright (c) 2010-2013, Pieter Noordhuis 12 | * 13 | * All rights reserved. 14 | * 15 | * Redistribution and use in source and binary forms, with or without 16 | * modification, are permitted provided that the following conditions are 17 | * met: 18 | * 19 | * * Redistributions of source code must retain the above copyright 20 | * notice, this list of conditions and the following disclaimer. 21 | * 22 | * * Redistributions in binary form must reproduce the above copyright 23 | * notice, this list of conditions and the following disclaimer in the 24 | * documentation and/or other materials provided with the distribution. 25 | * 26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 29 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 30 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 32 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 33 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 34 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 35 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | #ifndef __LINENOISE_H 40 | #define __LINENOISE_H 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif 45 | 46 | typedef struct linenoiseCompletions { 47 | size_t len; 48 | char **cvec; 49 | } linenoiseCompletions; 50 | 51 | typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); 52 | typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold); 53 | typedef void(linenoiseFreeHintsCallback)(void *); 54 | void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); 55 | void linenoiseSetHintsCallback(linenoiseHintsCallback *); 56 | void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); 57 | void linenoiseAddCompletion(linenoiseCompletions *, const char *); 58 | 59 | char *linenoise(const char *prompt); 60 | void linenoiseFree(void *ptr); 61 | int linenoiseHistoryAdd(const char *line); 62 | int linenoiseHistorySetMaxLen(int len); 63 | int linenoiseHistorySave(const char *filename); 64 | int linenoiseHistoryLoad(const char *filename); 65 | void linenoiseClearScreen(void); 66 | void linenoiseSetMultiLine(int ml); 67 | void linenoisePrintKeyCodes(void); 68 | void linenoiseMaskModeEnable(void); 69 | void linenoiseMaskModeDisable(void); 70 | 71 | #ifdef __cplusplus 72 | } 73 | #endif 74 | 75 | #endif /* __LINENOISE_H */ 76 | -------------------------------------------------------------------------------- /tinyscript.h: -------------------------------------------------------------------------------- 1 | #ifndef TINYSCRIPT_H 2 | #define TINYSCRIPT_H 3 | 4 | #include 5 | 6 | // language configuration options 7 | 8 | // define VERBOSE_ERRORS to get nicer error messages at a small cost in space 9 | // costs about 500 bytes on the Propeller 10 | #define VERBOSE_ERRORS 11 | 12 | // define ARRAY_SUPPORT to get support for integer arrays 13 | // costs about 1K on the Propeller 14 | #define ARRAY_SUPPORT 15 | 16 | #ifdef __propeller__ 17 | // define SMALL_PTRS to use 16 bits for pointers 18 | // useful for machines with <= 64KB of RAM 19 | #define SMALL_PTRS 20 | #endif 21 | 22 | // Comment this out if you have provided a function to 23 | // check whether a running script should stop. This 24 | // function should return non-zero when if the script 25 | // should stop, 0 if not. 26 | #define TinyScript_Stop() (0) 27 | 28 | // errors 29 | // all the ParseXXX functions return 0 on success, a negative 30 | // error code otherwise 31 | enum { 32 | TS_ERR_OK = 0, 33 | TS_ERR_NOMEM = -1, 34 | TS_ERR_SYNTAX = -2, 35 | TS_ERR_UNKNOWN_SYM = -3, 36 | TS_ERR_BADARGS = -4, 37 | TS_ERR_TOOMANYARGS = -5, 38 | TS_ERR_OUTOFBOUNDS = -6, 39 | TS_ERR_STOPPED = -7, 40 | TS_ERR_OK_ELSE = 1, // special internal condition 41 | }; 42 | 43 | // we use this a lot 44 | typedef char Byte; 45 | 46 | //our target is machines with < 64KB of memory, so 16 bit pointers 47 | //will do 48 | typedef Byte *Ptr; 49 | 50 | // strings are represented as (length,ptr) pairs 51 | // this is done so that we can re-use variable names and similar 52 | // substrings directly from the script text, without having 53 | // to insert 0 into them 54 | typedef struct { 55 | #ifdef SMALL_PTRS 56 | uint16_t len_; 57 | uint16_t ptr_; 58 | #else 59 | unsigned len_; 60 | const char *ptr_; 61 | #endif 62 | } String; 63 | 64 | // val has to be able to hold a pointer 65 | typedef intptr_t Val; 66 | 67 | static inline unsigned StringGetLen(String s) { return (unsigned)s.len_; } 68 | static inline const char *StringGetPtr(String s) { return (const char *)(intptr_t)s.ptr_; } 69 | #ifdef SMALL_PTRS 70 | static inline void StringSetLen(String *s, unsigned len) { s->len_ = (uint16_t)len; } 71 | static inline void StringSetPtr(String *s, const char *ptr) { s->ptr_ = (uint16_t)(intptr_t)ptr; } 72 | #else 73 | static inline void StringSetLen(String *s, unsigned len) { s->len_ = len; } 74 | static inline void StringSetPtr(String *s, const char *ptr) { s->ptr_ = ptr; } 75 | #endif 76 | 77 | // symbols can take the following forms: 78 | #define INT 0x0 // integer 79 | #define STRING 0x1 // string 80 | #define OPERATOR 0x2 // operator; precedence in high 8 bits 81 | #define ARG 0x3 // argument; value is offset on stack 82 | #ifdef ARRAY_SUPPORT 83 | #define ARRAY 0x4 // integer array 84 | #endif 85 | #define BUILTIN 'B' // builtin: number of operands in high 8 bits 86 | #define USRFUNC 'f' // user defined a procedure; number of operands in high 8 bits 87 | #define TOK_BINOP 'o' 88 | 89 | #define BINOP(x) (((x)<<8)+TOK_BINOP) 90 | #define CFUNC(x) (((x)<<8)+BUILTIN) 91 | 92 | typedef struct symbol { 93 | String name; 94 | int type; // symbol type 95 | Val value; // symbol value, or string ptr 96 | } Sym; 97 | 98 | #define MAX_BUILTIN_PARAMS 4 99 | 100 | typedef Val (*Cfunc)(Val, Val, Val, Val); 101 | typedef Val (*Opfunc)(Val, Val); 102 | 103 | // structure to describe a user function 104 | typedef struct ufunc { 105 | String body; // pointer to the body of the function 106 | int nargs; // number of args 107 | // names of arguments 108 | String argName[MAX_BUILTIN_PARAMS]; 109 | } UserFunc; 110 | 111 | // 112 | // global interface 113 | // 114 | int TinyScript_Init(void *mem, int mem_size); 115 | int TinyScript_Define(const char *name, int toktype, Val value); 116 | int TinyScript_Run(const char *s, int saveStrings, int topLevel); 117 | 118 | // provided by our caller 119 | extern void outchar(int c); 120 | 121 | // if an external function is provided, comment out the define, and uncomment the declaration 122 | #ifndef TinyScript_Stop 123 | extern int TinyScript_Stop(); 124 | #endif 125 | 126 | #endif 127 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #ifdef READLINE 5 | #include 6 | #include 7 | #elif defined(LINENOISE) 8 | #include "linenoise.h" 9 | #endif 10 | 11 | #include "tinyscript.h" 12 | #include "tinyscript_lib.h" 13 | 14 | #ifdef __propeller__ 15 | #include 16 | #define ARENA_SIZE 2048 17 | #else 18 | #define ARENA_SIZE 8192 19 | #define MAX_SCRIPT_SIZE 100000 20 | #endif 21 | 22 | int inchar() { 23 | return getchar(); 24 | } 25 | void outchar(int c) { 26 | putchar(c); 27 | } 28 | 29 | void * ts_malloc(Val size) { 30 | return malloc(size); 31 | } 32 | 33 | void ts_free(void * pointer) { 34 | free(pointer); 35 | } 36 | 37 | void _ts_printf(ts_list *format, ...) { 38 | va_list args; 39 | va_start(args, format); 40 | char *format_string = ts_list_to_string(format); 41 | vprintf(format_string, args); 42 | ts_free(format_string); 43 | } 44 | 45 | void ts_printf(ts_list *format, Val a) { 46 | _ts_printf(format, a); 47 | } 48 | 49 | void ts_printf_(ts_list *format, Val a, Val b) { 50 | _ts_printf(format, a, b); 51 | } 52 | 53 | void ts_printf__(ts_list *format, Val a, Val b, Val c) { 54 | _ts_printf(format, a, b, c); 55 | } 56 | 57 | #ifdef MAX_SCRIPT_SIZE 58 | char script[MAX_SCRIPT_SIZE]; 59 | 60 | void 61 | runscript(const char *filename) 62 | { 63 | FILE *f = fopen(filename, "r"); 64 | int r; 65 | if (!f) { 66 | perror(filename); 67 | return; 68 | } 69 | r=fread(script, 1, MAX_SCRIPT_SIZE, f); 70 | fclose(f); 71 | if (r <= 0) { 72 | fprintf(stderr, "File read error on %s\n", filename); 73 | return; 74 | } 75 | script[r] = 0; 76 | r = TinyScript_Run(script, 0, 1); 77 | if (r != 0) { 78 | printf("script error %d\n", r); 79 | } 80 | exit(r); 81 | } 82 | #endif 83 | 84 | #ifdef __propeller__ 85 | static Val getcnt_fn() 86 | { 87 | #ifdef CNT 88 | return CNT; 89 | #else 90 | return _cnt(); 91 | #endif 92 | } 93 | static Val waitcnt_fn(Val when) 94 | { 95 | waitcnt(when); 96 | return when; 97 | } 98 | static Val pinout_fn(Val pin, Val onoff) 99 | { 100 | unsigned mask = 1< "); 159 | if (!buf) break; 160 | add_history(buf); 161 | write_history("tinyscript_history"); 162 | #elif defined(LINENOISE) 163 | buf = linenoise("ts> "); 164 | if (!buf) break; 165 | linenoiseHistoryAdd(buf); 166 | linenoiseHistorySave("tinyscript_history"); 167 | #else 168 | static char sbuf[128]; 169 | 170 | printf("ts> "); fflush(stdout); 171 | buf = fgets(sbuf, sizeof(sbuf), stdin); 172 | if (!buf) break; 173 | #endif 174 | r = TinyScript_Run(buf, 1, 1); 175 | if (r != 0) { 176 | printf("error %d\n", r); 177 | } 178 | #if defined(READLINE) || defined(LINENOISE) 179 | free(buf); 180 | #endif 181 | } 182 | } 183 | 184 | char memarena[ARENA_SIZE]; 185 | 186 | int 187 | main(int argc, char **argv) 188 | { 189 | int err; 190 | int i; 191 | 192 | err = TinyScript_Init(memarena, sizeof(memarena)); 193 | for (i = 0; funcdefs[i].name; i++) { 194 | err |= TinyScript_Define(funcdefs[i].name, CFUNC(funcdefs[i].nargs), funcdefs[i].val); 195 | } 196 | err |= ts_define_funcs(); 197 | if (err != 0) { 198 | printf("Initialization of interpreter failed!\n"); 199 | return 1; 200 | } 201 | #ifdef __propeller__ 202 | REPL(); 203 | #else 204 | if (argc > 2) { 205 | printf("Usage: tinyscript [file]\n"); 206 | } 207 | if (argv[1]) { 208 | runscript(argv[1]); 209 | } else { 210 | REPL(); 211 | } 212 | #endif 213 | return 0; 214 | } 215 | 216 | -------------------------------------------------------------------------------- /tinyscript_lib.c: -------------------------------------------------------------------------------- 1 | /* Tinyscript Library 2 | * 3 | * Copyright 2020 Daniel Landau 4 | * 5 | * +-------------------------------------------------------------------- 6 | * ¦ TERMS OF USE: MIT License 7 | * +-------------------------------------------------------------------- 8 | * Permission is hereby granted, free of charge, to any person obtaining 9 | * a copy of this software and associated documentation files 10 | * (the "Software"), to deal in the Software without restriction, 11 | * including without limitation the rights to use, copy, modify, merge, 12 | * publish, distribute, sublicense, and/or sell copies of the Software, 13 | * and to permit persons to whom the Software is furnished to do so, 14 | * subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be 17 | * included in all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | * +-------------------------------------------------------------------- 27 | */ 28 | 29 | #include "tinyscript.h" 30 | #include "tinyscript_lib.h" 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | ts_list * ts_list_new(Val capacity) { 37 | ts_list * list = ts_malloc(sizeof(ts_list)); 38 | list->data = ts_malloc(capacity); 39 | list->size = 0; 40 | list->capacity = capacity; 41 | return list; 42 | } 43 | 44 | ts_list * ts_list_dup(ts_list * old) { 45 | ts_list * list = ts_malloc(sizeof(ts_list)); 46 | list->data = ts_malloc(old->capacity); 47 | list->size = old->size; 48 | list->capacity = old->capacity; 49 | for (Val i = 0; i < old->size; ++i) 50 | list->data[i] = old->data[i]; 51 | return list; 52 | } 53 | 54 | void ts_list_free(ts_list * list) { 55 | ts_free(list->data); 56 | ts_free(list); 57 | } 58 | 59 | Val ts_list_pop(ts_list * list) { 60 | if (list->size == 0) 61 | return -1; 62 | list->size--; 63 | return list->data[list->size]; 64 | } 65 | 66 | bool ts_list_push(ts_list * list, Val val) { 67 | if (list->size == list->capacity) 68 | return false; 69 | list->data[list->size] = (uint8_t)val; 70 | list->size++; 71 | return true; 72 | } 73 | 74 | bool ts_list_push_(ts_list * list, Val val1, Val val2) { 75 | bool success; 76 | success = ts_list_push(list, val1); 77 | if (success) success = ts_list_push(list, val2); 78 | return success; 79 | } 80 | bool ts_list_push__(ts_list * list, Val val1, Val val2, Val val3) { 81 | bool success; 82 | success = ts_list_push(list, val1); 83 | if (success) success = ts_list_push(list, val2); 84 | if (success) success = ts_list_push(list, val3); 85 | return success; 86 | } 87 | 88 | bool ts_list_set(ts_list * list, Val idx, Val val) { 89 | if (idx < list->capacity) { 90 | // initialize to 0 everything that is between previous list size and new list end 91 | if (list->size <= idx) { 92 | for (Val i = list->size; i < idx; ++i) { 93 | list->data[i] = 0; 94 | } 95 | list->size = idx + 1; 96 | } 97 | list->data[idx] = (uint8_t)val; 98 | return true; 99 | } 100 | return false; 101 | } 102 | 103 | Val ts_list_get(ts_list * list, Val idx) { 104 | if (idx < list->size) { 105 | return list->data[idx]; 106 | } 107 | return -1; 108 | } 109 | 110 | Val ts_list_size(ts_list * list) { 111 | return list->size; 112 | } 113 | 114 | void ts_list_truncate(ts_list * list, Val new_size) { 115 | if (new_size < list->size && new_size >= 0) list->size = new_size; 116 | } 117 | 118 | ts_list * ts_list_expand(ts_list * list, Val new_capacity) { 119 | if (list->capacity >= new_capacity) { 120 | return list; 121 | } 122 | ts_list * new_list = ts_list_new(new_capacity); 123 | for (int i = 0; i < list->size; ++i) { 124 | new_list->data[i] = list->data[i]; 125 | } 126 | new_list->size = list->size; 127 | ts_list_free(list); 128 | return new_list; 129 | } 130 | 131 | ts_list * ts_list_cat(ts_list * list_a, ts_list * list_b) { 132 | Val new_size = list_a->size + list_b->size; 133 | ts_list *new_list = ts_list_new(new_size); 134 | for (int i = 0; i < list_a->size; ++i) 135 | new_list->data[i] = list_a->data[i]; 136 | for (int i = 0; i < list_b->size; ++i) 137 | new_list->data[list_a->size + i] = list_b->data[i]; 138 | 139 | new_list->size = new_size; 140 | 141 | return new_list; 142 | } 143 | 144 | 145 | ts_list * ts_string_to_list(const char * str) { 146 | Val len = strlen(str); 147 | ts_list * list = ts_list_new(len + 1); 148 | for (Val i = 0; i < len; ++i) 149 | ts_list_push(list, str[i]); 150 | ts_list_push(list, '\0'); 151 | return list; 152 | } 153 | 154 | ts_list * ts_bytes_to_list(const char * str, int num_bytes) { 155 | ts_list * list = ts_list_new(num_bytes); 156 | for (Val i = 0; i < num_bytes; ++i) 157 | ts_list_push(list, str[i]); 158 | return list; 159 | } 160 | 161 | char * ts_list_to_string(const ts_list *list) { 162 | char *str = ts_malloc(list->size + 1); 163 | for (Val i = 0; i < list->size; ++i) 164 | str[i] = list->data[i]; 165 | str[list->size] = '\0'; 166 | return str; 167 | } 168 | 169 | Val ts_not(Val value) { 170 | return !value; 171 | } 172 | 173 | Val ts_bool(Val value) { 174 | return !!value; 175 | } 176 | 177 | int ts_define_funcs() { 178 | int err = 0; 179 | err |= TinyScript_Define("not", CFUNC(1), (Val)ts_not); 180 | err |= TinyScript_Define("bool", CFUNC(1), (Val)ts_bool); 181 | 182 | err |= TinyScript_Define("list_new", CFUNC(1), (Val)ts_list_new); 183 | err |= TinyScript_Define("list_dup", CFUNC(1), (Val)ts_list_dup); 184 | err |= TinyScript_Define("list_free", CFUNC(1), (Val)ts_list_free); 185 | err |= TinyScript_Define("list_pop", CFUNC(1), (Val)ts_list_pop); 186 | err |= TinyScript_Define("list_get", CFUNC(2), (Val)ts_list_get); 187 | err |= TinyScript_Define("list_push", CFUNC(2), (Val)ts_list_push); 188 | err |= TinyScript_Define("list_push_", CFUNC(3), (Val)ts_list_push_); 189 | err |= TinyScript_Define("list_push__", CFUNC(4), (Val)ts_list_push__); 190 | err |= TinyScript_Define("list_set", CFUNC(3), (Val)ts_list_set); 191 | err |= TinyScript_Define("list_size", CFUNC(1), (Val)ts_list_size); 192 | err |= TinyScript_Define("list_truncate", CFUNC(2), (Val)ts_list_truncate); 193 | err |= TinyScript_Define("list_expand", CFUNC(2), (Val)ts_list_expand); 194 | err |= TinyScript_Define("list_cat", CFUNC(2), (Val)ts_list_cat); 195 | 196 | err |= TinyScript_Define("free", CFUNC(1), (Val)ts_free); 197 | return err; 198 | } 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | This is tinyscript, a scripting language designed for very tiny 5 | machines. The initial target is boards using the Parallax Propeller, 6 | which has 32KB of RAM, but the code is written in ANSI C so it should 7 | work on any platform (e.g. testing is done on x86-64 Linux). 8 | 9 | On the propeller, the interpreter code needs about 3K of memory in CMM 10 | mode or 5K in LMM. On the x86-64 the interpreter code is 6K. The size 11 | of the workspace you give to the interpreter is up to you, although in 12 | practice it would not be very useful to use less than 2K of RAM. The 13 | processor stack is used as well, so it will need some space. 14 | 15 | tinyscript is copyright 2016-2021 Total Spectrum Software Inc. and released 16 | under the MIT license. See the COPYING file for details. 17 | 18 | The Language 19 | ============ 20 | The scripting language itself is pretty minimalistic. The grammar for it 21 | looks like: 22 | 23 | ::= | 24 | ::= | | | 25 | | | 26 | | | 27 | | | 28 | 29 | The statements in a program are separated by newlines or ';'. 30 | 31 | Either variables or functions may be declared. 32 | 33 | ::= "var" 34 | ::= "array" "(" ")" | "array" 35 | ::= "func" "(" ")" 36 | ::= "=" 37 | ::= [ "," ]+ 38 | 39 | Variables must always be given a value when declared (unless they are 40 | arrays). All non-array variables simply hold 32 bit quantities, 41 | normally interpreted as an integer. The symbol in an assignment 42 | outside of a vardecl must already have been declared. 43 | 44 | Arrays are simple one dimensional arrays. Support for arrays does add 45 | a little bit of code, so they are optional (included if ARRAY_SUPPORT 46 | is defined in tinyscript.h). If the array declaration includes a size, 47 | then a new (uninitialized) array is created. If it does not include a 48 | size, then it must match one of the enclosing function's parameters, 49 | in which case that parameter is checked and must be an array. 50 | 51 | Array indices start at 0. Array index -1 is special and holds the 52 | length of the array, 53 | 54 | Functions point to a string. When a procedure is called, the string 55 | is interpreted as a script (so at that time it is parsed using the 56 | language grammar). If a function is never called then it is never 57 | parsed, so it need not contain legal code if it is not called. 58 | 59 | Strings may be enclosed either in double quotes or between { and }. 60 | The latter case is more useful for functions and similar code uses, 61 | since the brackets nest. Also note that it is legal for newlines to 62 | appear in {} strings, but not in strings enclosed by ". 63 | 64 | ::= "if" [] 65 | ::= "else" | "elseif" [] 66 | ::= "while" [] 67 | 68 | As with functions, the strings in if and while statements are parsed 69 | and interpreted on an as-needed basis. Any non-zero expression is 70 | treated as true, and zero is treated as false. As a quirk of the 71 | implementation, it is permitted to add an "else" clause to a while statement; 72 | any such clause will always be executed after the loop exits. 73 | 74 | ::= "return" 75 | 76 | Return statements are used to terminate a function and return a value 77 | to its caller. 78 | 79 | ::= "print" [ "," ]+ 80 | ::= | 81 | 82 | Expressions are built up from symbols, numbers (decimal or hex integers), and 83 | operators. The operators have precedence levels 1-4. Level 0 of expressions is 84 | the most basic, consisting of numbers or variables optionally preceded by a 85 | unary operator: 86 | 87 | ::= | 88 | | 89 | | "(" ")" 90 | | 91 | ::= "(" [] ")" 92 | ::= ["," ]* 93 | 94 | ::= + | "0x"+ | "'""'" 95 | 96 | ::= | "\'" | "\\" | "\n" | "\t" | "\r" 97 | 98 | ::= ' ' to '~' excluding ' and \ 99 | 100 | ::= [ ]* 101 | ::= "*" | "/" | "%" 102 | 103 | ::= [ ]* 104 | ::= "+" | "-" 105 | 106 | ::= []* 107 | ::= "&" | "|" | "^" | "<<" | ">>" 108 | 109 | ::= []* 110 | ::= "=" | "<>" | ">" | "<" | ">=" | "<=" 111 | 112 | ::= | | | 113 | 114 | Builtin functions are defined by the runtime, as are operators. The ones 115 | listed above are merely the ones defined by default. Operators may use 116 | any of the characters `!=<>+-*/&|^%`. Any string of the characters 117 | `!=<>&|^` is processed together, but the operator characters `+-*/` may only 118 | appear on their own. 119 | 120 | Note that any operator may be used as a unary operator, and in this case 121 | `x` is interpreted as `0 x` for any operator ``. This is useful 122 | for `+` and `-`, less so for other operators. 123 | 124 | `%` is the modulo operator, as in C. 125 | 126 | Variable Scope 127 | -------------- 128 | 129 | Variables are dynamically scoped. For example, in: 130 | ``` 131 | var x=2 132 | 133 | func printx() { 134 | print x 135 | } 136 | func myfunc() { 137 | var x=3 138 | printx() 139 | } 140 | ``` 141 | invoking `myfunc` will cause 3 to be printed, not 2 as in statically scoped 142 | languages. 143 | 144 | Variable Types 145 | -------------- 146 | 147 | Basically the only type that may be held in variables is an integer. If array support is compiled in, then arrays of integers may be created. Some functions may treat arrays of integers as strings, but there is no native support for this, unfortunately. 148 | 149 | Interface to C 150 | ============== 151 | 152 | Environment Requirements 153 | ------------------------ 154 | 155 | The interpreter is quite self-contained. The functions needed to 156 | interface with it are `outchar` (called to print a single character), 157 | and `memcpy`. `outchar` is the only one of these that is 158 | non-standard. It takes a single int as parameter and prints it as a 159 | character. This is the function the interpreter uses for output 160 | e.g. in the `print` statement. 161 | 162 | Optionally you can provide a function TinyScript_Stop() to check whether 163 | a running script should stop. To do this, edit the tinyscript.h file 164 | to remove the default definition (0) for TinyScript_Stop(). 165 | 166 | 167 | Configuration 168 | ------------- 169 | 170 | Language configuration options are in the form of defines at the top 171 | of tinyscript.h: 172 | 173 | ``` 174 | VERBOSE_ERRORS - gives better error messages (costs a tiny bit of space) 175 | SMALL_PTRS - use 16 bits for pointers (for very small machines) 176 | ARRAY_SUPPORT - include support for integer arrays 177 | ``` 178 | 179 | The demo app main.c has some configuration options in the Makefile: 180 | 181 | ``` 182 | READLINE - use the GNU readline library for entering text 183 | LINENOISE - use the linenoise.c library for entering text 184 | ``` 185 | 186 | Standard Library 187 | ---------------- 188 | 189 | There is an optional standard library in tinyscript_lib.{c,h} that adds 190 | `strlen` as a standard requirement and requires the user to define two 191 | functions: `ts_malloc` and `ts_free`. These can be wrappers for `malloc`/`free` 192 | or perhaps `pvPortMalloc` / `vPortFree` on FreeRTOS systems. 193 | 194 | Application Usage 195 | ----------------- 196 | 197 | As mentioned above, the function `outchar` must be defined by the application 198 | to allow for printing. The following definition will work for standard C: 199 | 200 | ``` 201 | #include 202 | void outchar(int c) { putchar(c); } 203 | ``` 204 | 205 | Embedded systems may want to provide a definition that uses the serial port 206 | or an attached display. 207 | 208 | For the optional standard library `ts_malloc` and `ts_free` must be defined by 209 | the application. The following definitions will work for standard C: 210 | 211 | ``` 212 | #include 213 | void * ts_malloc(Val size) { 214 | return malloc(size); 215 | } 216 | void ts_free(void * pointer) { 217 | free(pointer); 218 | } 219 | ``` 220 | 221 | The application must initialize the interpreter with `TinyScript_Init` before 222 | making any other calls. `TinyScript_Init` takes two parameters: the base 223 | of a memory region the interpreter can use, and the size of that region. 224 | It returns `TS_ERR_OK` on success, or an error on failure. It is recommended 225 | to provide at least 2K of space to the interpreter. 226 | 227 | If `TinyScript_Init` succeeds, the application may then define builtin 228 | symbols with `TinyScript_Define(name, BUILTIN, (Val)func)`, where 229 | `name` is the name of the symbol in scripts and `func` is the C 230 | function. Technically the function should have prototype: 231 | 232 | Val func(Val a, Val b, Val c, Val d) 233 | 234 | However, most C compiler calling conventions are such that any C function 235 | (other than varargs ones) will work. (Certainly this is true of GCC on 236 | the Propeller). On the script side, the interpreter 237 | will supply 0 for any arguments the user does not supply, and will silently 238 | ignore arguments given beyond the fourth. 239 | 240 | In order to use the optional standard library, its functions need to be added 241 | to the Tinyscript context by calling `ts_define_funcs()`. Check the source code 242 | and tests for more information about what is included in the standard library. 243 | 244 | To run a script, use `TinyScript_Run(script, saveStrings, topLevel)`. Here 245 | `script` is a C string, `saveStrings` is 1 if any variable names created 246 | in the script need to be saved in newly allocated memory -- this is necessary 247 | if the space `script` is stored in may later be overwritten, e.g. in 248 | a REPL loop by new commands typed by the user. `topLevel` is 1 if the 249 | variables created by the script should be kept after it finishes. 250 | 251 | Standard Library 252 | ----------------- 253 | The standard library is optional, and is found in the file `tinyscript_lib.c`. It must be initialized with `ts_define_funcs()` before use. Functions provided are: 254 | 255 | `not(x)`: return 1 if x == 0, 0 otherwise 256 | 257 | `bool(x)`: returns 0 if x == 0, 1 otherwise 258 | 259 | `list_new(n)`: returns a handle to a new list which may contain up to `n` elements 260 | 261 | `list_dup(x)`: duplicates the list `x` 262 | 263 | `list_free(x)`: frees a list 264 | 265 | `list_pop(x)`: removes the last element from list `x` and returns it; returns -1 if no elements have been added to the list 266 | 267 | `list_push(x, a)`: appends the value `a` to the list whose handle is `x` 268 | 269 | `list_get(x, i)`: retrieves the `i`th element of the list `x`, or -1 if there is no such element 270 | 271 | `list_set(x, i, a)`: sets the `i`th element of list `x` to `a`. 272 | 273 | `list_size(x)`: returns the current length of the list 274 | 275 | Acknowledgements 276 | ================ 277 | I'd like to thank Mickey Delp and Daniel Landau for their contributions to tinyscript. Daniel's bug reports have been invaluable, and he contributed (among other things) the optional standard library, readline support, and hex support. Mickey contributed better error handling, the modulo operator, and optional array support. 278 | 279 | -------------------------------------------------------------------------------- /tinyscript.c: -------------------------------------------------------------------------------- 1 | /* Tinyscript interpreter 2 | * 3 | * Copyright 2016-2021 Total Spectrum Software Inc. 4 | * 5 | * +-------------------------------------------------------------------- 6 | * ¦ TERMS OF USE: MIT License 7 | * +-------------------------------------------------------------------- 8 | * Permission is hereby granted, free of charge, to any person obtaining 9 | * a copy of this software and associated documentation files 10 | * (the "Software"), to deal in the Software without restriction, 11 | * including without limitation the rights to use, copy, modify, merge, 12 | * publish, distribute, sublicense, and/or sell copies of the Software, 13 | * and to permit persons to whom the Software is furnished to do so, 14 | * subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be 17 | * included in all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | * +-------------------------------------------------------------------- 27 | */ 28 | 29 | // 30 | // a very tiny scripting language 31 | // 32 | 33 | // define this to debug the interpreter itself; otherwise leave it out 34 | //#define TSDEBUG 35 | 36 | #define MAX_EXPR_LEVEL 5 37 | 38 | #include 39 | #include 40 | #include "tinyscript.h" 41 | 42 | // where our data is stored 43 | // value stack grows from the top of the area to the bottom 44 | // symbol stack grows from the bottom up 45 | static int arena_size; 46 | static Byte *arena; 47 | 48 | static Sym *symptr; 49 | static Val *valptr; 50 | static String parseptr; // acts as instruction pointer 51 | 52 | #ifdef VERBOSE_ERRORS 53 | static const char *script_buffer; 54 | #endif 55 | 56 | // arguments to functions 57 | static Val fArgs[MAX_BUILTIN_PARAMS]; 58 | static Val fResult = 0; 59 | 60 | // variables for parsing 61 | static int curToken; // what kind of token is current 62 | static int tokenArgs; // number of arguments for this token 63 | static String token; // the actual string representing the token 64 | static Val tokenVal; // for symbolic tokens, the symbol's value 65 | static Sym *tokenSym; 66 | static int didReturn = 0; 67 | 68 | #ifdef ARRAY_SUPPORT 69 | static int ParseArrayDef(int saveStrings); 70 | static int ParseArrayGet(Val *vp); 71 | static int ParseArraySet(); 72 | #endif 73 | 74 | // compare two Strings for equality 75 | Val stringeq(String ai, String bi) 76 | { 77 | const Byte *a, *b; 78 | int i, len; 79 | 80 | len = StringGetLen(ai); 81 | if (len != StringGetLen(bi)) { 82 | return 0; 83 | } 84 | a = StringGetPtr(ai); 85 | b = StringGetPtr(bi); 86 | for (i = 0; i < len; i++) { 87 | if (*a != *b) return 0; 88 | a++; b++; 89 | } 90 | return 1; 91 | } 92 | 93 | // 94 | // Utility functions 95 | // 96 | 97 | // print a string 98 | void 99 | PrintString(String s) 100 | { 101 | unsigned len = StringGetLen(s); 102 | const char *ptr = (const char *)StringGetPtr(s); 103 | while (len > 0) { 104 | outchar(*ptr); 105 | ptr++; 106 | --len; 107 | } 108 | } 109 | 110 | // print a newline 111 | void 112 | Newline(void) 113 | { 114 | outchar('\n'); 115 | } 116 | 117 | // print a number 118 | // FIXME: should this be delegated to the application? 119 | static void 120 | PrintNumber(Val v) 121 | { 122 | unsigned long x; 123 | unsigned base = 10; 124 | int prec = 1; 125 | int digits = 0; 126 | int c; 127 | char buf[32]; 128 | 129 | if (v < 0) { 130 | outchar('-'); 131 | x = -v; 132 | } else { 133 | x = v; 134 | } 135 | while (x > 0 || digits < prec) { 136 | c = x % base; 137 | x = x / base; 138 | if (c < 10) c += '0'; 139 | else c = (c - 10) + 'a'; 140 | buf[digits++] = c; 141 | } 142 | // now output 143 | while (digits > 0) { 144 | --digits; 145 | outchar(buf[digits]); 146 | } 147 | } 148 | 149 | // look up a symbol by name 150 | static Sym * 151 | LookupSym(String name) 152 | { 153 | Sym *s; 154 | 155 | s = symptr; 156 | while ((intptr_t)s > (intptr_t)arena) { 157 | --s; 158 | if (stringeq(s->name, name)) { 159 | return s; 160 | } 161 | } 162 | return NULL; 163 | } 164 | 165 | static void 166 | outcstr(const char *ptr) 167 | { 168 | while (*ptr) { 169 | outchar(*ptr++); 170 | } 171 | } 172 | 173 | // return true if a character is in a string 174 | static int charin(int c, const char *str) 175 | { 176 | while (*str) { 177 | if (c == *str++) return 1; 178 | } 179 | return 0; 180 | } 181 | 182 | #ifdef VERBOSE_ERRORS 183 | // 184 | // some functions to print an error and return 185 | // 186 | static void ErrorAt() { 187 | const char* ptr = StringGetPtr(parseptr); 188 | // back up to beginning of statement 189 | while (ptr > script_buffer && !charin(*(ptr - 1), ";\n")) { 190 | ptr--; 191 | } 192 | outcstr(" in: "); 193 | // print until end of statement 194 | while (*ptr && !charin(*ptr, ";\n")) { 195 | outchar(*ptr); 196 | ptr++; 197 | } 198 | outchar('\n'); 199 | } 200 | static int SyntaxError() { 201 | outcstr("syntax error"); 202 | ErrorAt(); 203 | return TS_ERR_SYNTAX; 204 | } 205 | static int ArgMismatch() { 206 | outcstr("argument mismatch"); 207 | ErrorAt(); 208 | return TS_ERR_BADARGS; 209 | } 210 | static int TooManyArgs() { 211 | outcstr("too many arguments"); 212 | ErrorAt(); 213 | return TS_ERR_TOOMANYARGS; 214 | } 215 | static int OutOfMem() { 216 | outcstr("out of memory"); 217 | ErrorAt(); 218 | return TS_ERR_NOMEM; 219 | } 220 | static int UnknownSymbol() { 221 | outcstr(": unknown symbol\n"); 222 | return TS_ERR_UNKNOWN_SYM; 223 | } 224 | #ifdef ARRAY_SUPPORT 225 | static int OutOfBounds() { 226 | outcstr("out of bounds"); 227 | ErrorAt(); 228 | return TS_ERR_OUTOFBOUNDS; 229 | } 230 | #endif 231 | #else 232 | #define SyntaxError() TS_ERR_SYNTAX 233 | #define ArgMismatch() TS_ERR_BADARGS 234 | #define TooManyArgs() TS_ERR_TOOMANYARGS 235 | #define OutOfMem() TS_ERR_NOMEM 236 | #define UnknownSymbol() TS_ERR_UNKNOWN_SYM 237 | #ifdef ARRAY_SUPPORT 238 | #define OutOfBounds() TS_ERR_OUTOFBOUNDS 239 | #endif 240 | #endif 241 | 242 | // 243 | // parse an expression or statement 244 | // 245 | 246 | #define TOK_SYMBOL 'A' 247 | #define TOK_NUMBER 'N' 248 | #define TOK_HEX_NUMBER 'X' 249 | #define TOK_CHAR 'C' 250 | #define TOK_STRING 'S' 251 | #define TOK_IF 'i' 252 | #define TOK_ELSE 'e' 253 | #define TOK_ELSEIF 'l' 254 | #define TOK_WHILE 'w' 255 | #define TOK_PRINT 'p' 256 | #define TOK_VAR 'v' 257 | #define TOK_VARDEF 'V' 258 | #ifdef ARRAY_SUPPORT 259 | #define TOK_ARY 'y' 260 | #define TOK_ARYDEF 'Y' 261 | #endif 262 | #define TOK_BUILTIN 'B' 263 | #define TOK_BINOP 'o' 264 | #define TOK_FUNCDEF 'F' 265 | #define TOK_SYNTAX_ERR 'Z' 266 | #define TOK_RETURN 'r' 267 | 268 | static void ResetToken() 269 | { 270 | StringSetLen(&token, 0); 271 | StringSetPtr(&token, StringGetPtr(parseptr)); 272 | } 273 | 274 | // 275 | // get next character from the program counter 276 | // returns -1 on end of file 277 | static int 278 | GetChar() 279 | { 280 | int c; 281 | unsigned len = StringGetLen(parseptr); 282 | const char *ptr; 283 | if (len == 0) 284 | return -1; 285 | ptr = StringGetPtr(parseptr); 286 | c = *ptr++; 287 | --len; 288 | 289 | StringSetPtr(&parseptr, ptr); 290 | StringSetLen(&parseptr, len); 291 | StringSetLen(&token, StringGetLen(token)+1); 292 | return c; 293 | } 294 | 295 | // 296 | // peek n characters forward from the program counter 297 | // returns -1 on end of file 298 | static int 299 | PeekChar(unsigned int n) 300 | { 301 | if (StringGetLen(parseptr) <= n) 302 | return -1; 303 | return *(StringGetPtr(parseptr) + n); 304 | } 305 | 306 | // remove the last character read from the token 307 | static void 308 | IgnoreLastChar() 309 | { 310 | StringSetLen(&token, StringGetLen(token)-1); 311 | } 312 | 313 | // remove the last character read from the token 314 | static void 315 | IgnoreFirstChar() 316 | { 317 | StringSetPtr(&token, StringGetPtr(token) + 1); 318 | StringSetLen(&token, StringGetLen(token)-1); 319 | } 320 | // 321 | // undo last getchar 322 | // 323 | static void 324 | UngetChar() 325 | { 326 | StringSetLen(&parseptr, StringGetLen(parseptr)+1); 327 | StringSetPtr(&parseptr, StringGetPtr(parseptr)-1); 328 | IgnoreLastChar(); 329 | } 330 | 331 | // these appear in too, but 332 | // we don't want macros and we don't want to 333 | // drag in a table of character flags 334 | static int isspace(int c) 335 | { 336 | return (c == ' ') || (c == '\t') || (c == '\r'); 337 | } 338 | 339 | static int isdigit(int c) 340 | { 341 | return (c >= '0' && c <= '9'); 342 | } 343 | static int ishexchar(int c) 344 | { 345 | return (c >= '0' && c <= '9') || charin(c, "abcdefABCDEF"); 346 | } 347 | static int islower(int c) 348 | { 349 | return (c >= 'a' && c <= 'z'); 350 | } 351 | static int isupper(int c) 352 | { 353 | return (c >= 'A' && c <= 'Z'); 354 | } 355 | static int isalpha(int c) 356 | { 357 | return islower(c) || isupper(c); 358 | } 359 | static int isidpunct(int c) 360 | { 361 | return charin(c, ".:_"); 362 | } 363 | static int isidentifier(int c) 364 | { 365 | return isalpha(c) || isdigit(c) || isidpunct(c); 366 | } 367 | 368 | static int notquote(int c) 369 | { 370 | return (c >= 0) && !charin(c, "\"\n"); 371 | } 372 | 373 | static void 374 | GetSpan(int (*testfn)(int)) 375 | { 376 | int c; 377 | do { 378 | c = GetChar(); 379 | } while (testfn(c)); 380 | if (c != -1) { 381 | UngetChar(); 382 | } 383 | } 384 | 385 | static int 386 | isoperator(int c) { 387 | return charin(c, "+-!/*%=<>&|^"); 388 | } 389 | 390 | static int 391 | isoperatorchar2(int c) { 392 | return charin(c, "=<>&|^"); 393 | } 394 | 395 | static int 396 | doNextToken(int israw) 397 | { 398 | int c; 399 | int r = -1; 400 | Sym *sym = NULL; 401 | 402 | tokenSym = NULL; 403 | ResetToken(); 404 | for(;;) { 405 | c = GetChar(); 406 | if (isspace(c)) { 407 | ResetToken(); 408 | } else { 409 | break; 410 | } 411 | } 412 | 413 | if (c == '#') { 414 | // comment 415 | do { 416 | c = GetChar(); 417 | } while (c >= 0 && c != '\n'); 418 | r = c; 419 | } else if (isdigit(c)) { 420 | if (c == '0' && charin(PeekChar(0), "xX") && ishexchar(PeekChar(1))) { 421 | GetChar(); 422 | IgnoreFirstChar(); 423 | IgnoreFirstChar(); 424 | GetSpan(ishexchar); 425 | r = TOK_HEX_NUMBER; 426 | } else { 427 | GetSpan(isdigit); 428 | r = TOK_NUMBER; 429 | } 430 | } else if (c == '\'') { 431 | c = GetChar(); // get first 432 | if (c == '\\') GetChar(); 433 | c = GetChar(); // get closing ' 434 | // ignore ' on both sides 435 | if (c == '\'') { 436 | IgnoreFirstChar(); 437 | IgnoreLastChar(); 438 | r = TOK_CHAR; 439 | } else { 440 | r = TOK_SYNTAX_ERR; 441 | } 442 | } else if ( isalpha(c) ) { 443 | GetSpan(isidentifier); 444 | r = TOK_SYMBOL; 445 | // check for special tokens 446 | if (!israw) { 447 | tokenSym = sym = LookupSym(token); 448 | if (sym) { 449 | r = sym->type & 0xff; 450 | tokenArgs = (sym->type >> 8) & 0xff; 451 | #ifdef ARRAY_SUPPORT 452 | if (r == ARRAY) 453 | r = TOK_ARY; 454 | else 455 | #endif 456 | if (r < '@') 457 | r = TOK_VAR; 458 | tokenVal = sym->value; 459 | } 460 | } 461 | } else if (isoperator(c)) { 462 | GetSpan(isoperatorchar2); 463 | tokenSym = sym = LookupSym(token); 464 | if (sym) { 465 | r = sym->type; 466 | tokenVal = sym->value; 467 | } else { 468 | r = TOK_SYNTAX_ERR; 469 | } 470 | } else if (c == '{') { 471 | int bracket = 1; 472 | ResetToken(); 473 | while (bracket > 0) { 474 | c = GetChar(); 475 | if (c < 0) { 476 | return TOK_SYNTAX_ERR; 477 | } 478 | if (c == '}') { 479 | --bracket; 480 | } else if (c == '{') { 481 | ++bracket; 482 | } 483 | } 484 | IgnoreLastChar(); 485 | r = TOK_STRING; 486 | } else if (c == '"') { 487 | ResetToken(); 488 | GetSpan(notquote); 489 | c = GetChar(); 490 | if (c < 0) return TOK_SYNTAX_ERR; 491 | IgnoreLastChar(); 492 | r = TOK_STRING; 493 | } else { 494 | r = c; 495 | } 496 | #ifdef TSDEBUG 497 | outcstr("Token["); 498 | outchar(r & 0xff); 499 | outcstr(" / "); 500 | PrintNumber(r); 501 | outcstr("] = "); 502 | PrintString(token); 503 | outchar('\n'); 504 | #endif 505 | curToken = r; 506 | return r; 507 | } 508 | 509 | static int NextToken() { return doNextToken(0); } 510 | static int NextRawToken() { return doNextToken(1); } 511 | 512 | // push a number on the result stack 513 | // this stack grows down from the top of the arena 514 | 515 | Val 516 | Push(Val x) 517 | { 518 | --valptr; 519 | if ((intptr_t)valptr < (intptr_t)symptr) { 520 | return OutOfMem(); 521 | } 522 | *valptr = x; 523 | return TS_ERR_OK; 524 | } 525 | 526 | // pop a number off the result stack 527 | Val 528 | Pop() 529 | { 530 | Val r = 0; 531 | if ((intptr_t)valptr < (intptr_t)(arena+arena_size)) { 532 | r = *valptr++; 533 | } 534 | return r; 535 | } 536 | 537 | // convert a string to a number 538 | Val 539 | StringToNum(String s) 540 | { 541 | Val r = 0; 542 | int c; 543 | const Byte *ptr = StringGetPtr(s); 544 | int len = StringGetLen(s); 545 | while (len-- > 0) { 546 | c = *ptr++; 547 | if (!isdigit(c)) break; 548 | r = 10*r + (c-'0'); 549 | } 550 | return r; 551 | } 552 | 553 | // convert a hex string to a number 554 | Val 555 | HexStringToNum(String s) 556 | { 557 | Val r = 0; 558 | int c; 559 | const Byte *ptr = StringGetPtr(s); 560 | int len = StringGetLen(s); 561 | while (len-- > 0) { 562 | c = *ptr++; 563 | if (!ishexchar(c)) break; 564 | if (c <= '9') 565 | r = 16 * r + (c - '0'); 566 | else if (c <= 'F') 567 | r = 16 * r + (c - 'A' + 10); 568 | else 569 | r = 16 * r + (c - 'a' + 10); 570 | } 571 | return r; 572 | } 573 | 574 | // define a symbol 575 | Sym * 576 | DefineSym(String name, int typ, Val value) 577 | { 578 | Sym *s = symptr; 579 | 580 | if (StringGetPtr(name) == NULL) { 581 | return NULL; 582 | } 583 | symptr++; 584 | if ( (intptr_t)symptr >= (intptr_t)valptr) { 585 | //out of memory 586 | return NULL; 587 | } 588 | s->name = name; 589 | s->value = value; 590 | s->type = typ; 591 | return s; 592 | } 593 | 594 | static Sym * 595 | DefineVar(String name) 596 | { 597 | return DefineSym(name, INT, 0); 598 | } 599 | 600 | String 601 | Cstring(const char *str) 602 | { 603 | String x; 604 | 605 | StringSetLen(&x, strlen(str)); 606 | StringSetPtr(&x, str); 607 | return x; 608 | } 609 | 610 | int 611 | TinyScript_Define(const char *name, int typ, Val val) 612 | { 613 | Sym *s; 614 | s = DefineSym(Cstring(name), typ, val); 615 | if (!s) return OutOfMem(); 616 | return TS_ERR_OK; 617 | } 618 | 619 | extern int ParseExpr(Val *result); 620 | 621 | // parse an expression list, and push the various results 622 | // returns the number of items pushed, or a negative error 623 | static int 624 | ParseExprList(void) 625 | { 626 | int err; 627 | int count = 0; 628 | int c; 629 | Val v; 630 | 631 | do { 632 | err = ParseExpr(&v); 633 | if (err != TS_ERR_OK) { 634 | return err; 635 | } 636 | err = Push(v); 637 | if (err != TS_ERR_OK) { 638 | return err; 639 | } 640 | count++; 641 | c = curToken; 642 | if (c == ',') { 643 | NextToken(); 644 | } 645 | } while (c==','); 646 | return count; 647 | } 648 | 649 | int 650 | ParseChar(Val *vp, String token) 651 | { 652 | const Byte *ptr = StringGetPtr(token); 653 | if (ptr[0] == '\'') return SyntaxError(); 654 | if (ptr[0] == '\\') { 655 | /* if (StringGetLen(token) != 2) return SyntaxError(); */ 656 | if (ptr[1] == 'n') { *vp = '\n'; return TS_ERR_OK; } 657 | if (ptr[1] == 't') { *vp = '\t'; return TS_ERR_OK; } 658 | if (ptr[1] == 'r') { *vp = '\r'; return TS_ERR_OK; } 659 | if (ptr[1] == '\\') { *vp = '\\'; return TS_ERR_OK; } 660 | if (ptr[1] == '\'') { *vp = '\''; return TS_ERR_OK; } 661 | return SyntaxError(); 662 | } 663 | if (ptr[0] >= ' ' && ptr[0] <= '~') { *vp = ptr[0]; return TS_ERR_OK; } 664 | return SyntaxError(); 665 | } 666 | 667 | static int ParseString(String str, int saveStrings, int topLevel); 668 | 669 | // parse a function call 670 | // this may be a builtin (if script == NULL) 671 | // or a user defined script 672 | 673 | static int 674 | ParseFuncCall(Cfunc op, Val *vp, UserFunc *uf) 675 | { 676 | int paramCount = 0; 677 | int expectargs; 678 | int c; 679 | 680 | if (uf) { 681 | expectargs = uf->nargs; 682 | } else { 683 | expectargs = tokenArgs; 684 | } 685 | c = NextToken(); 686 | if (c != '(') return SyntaxError(); 687 | c = NextToken(); 688 | if (c != ')') { 689 | paramCount = ParseExprList(); 690 | c = curToken; 691 | if (paramCount < 0) return paramCount; 692 | } 693 | if (c!=')') { 694 | return SyntaxError(); 695 | } 696 | // make sure the right number of params is on the stack 697 | if (expectargs != paramCount) { 698 | return ArgMismatch(); 699 | } 700 | // we now have "paramCount" items pushed on to the stack 701 | // pop em off 702 | while (paramCount > 0) { 703 | --paramCount; 704 | fArgs[paramCount] = Pop(); 705 | } 706 | if (uf) { 707 | // need to invoke the script here 708 | // set up an environment for the script 709 | int i; 710 | int err; 711 | Sym* savesymptr = symptr; 712 | for (i = 0; i < expectargs; i++) { 713 | DefineSym(uf->argName[i], INT, fArgs[i]); 714 | } 715 | didReturn = 0; 716 | err = ParseString(uf->body, 0, 0); 717 | didReturn = 0; 718 | *vp = fResult; 719 | symptr = savesymptr; 720 | return err; 721 | } else { 722 | *vp = op(fArgs[0], fArgs[1], fArgs[2], fArgs[3]); 723 | } 724 | NextToken(); 725 | return TS_ERR_OK; 726 | } 727 | 728 | // parse a primary value; for now, just a number 729 | // or variable 730 | // returns 0 if valid, non-zero if syntax error 731 | // puts result into *vp 732 | static int 733 | ParsePrimary(Val *vp) 734 | { 735 | int c; 736 | int err; 737 | 738 | c = curToken; 739 | if (c == '(') { 740 | NextToken(); 741 | err = ParseExpr(vp); 742 | if (err == TS_ERR_OK) { 743 | c = curToken; 744 | if (c == ')') { 745 | NextToken(); 746 | return TS_ERR_OK; 747 | } 748 | } 749 | return err; 750 | } else if (c == TOK_NUMBER) { 751 | *vp = StringToNum(token); 752 | NextToken(); 753 | return TS_ERR_OK; 754 | } else if (c == TOK_HEX_NUMBER) { 755 | *vp = HexStringToNum(token); 756 | NextToken(); 757 | return TS_ERR_OK; 758 | } else if (c == TOK_CHAR) { 759 | err = ParseChar(vp, token); 760 | NextToken(); 761 | return err; 762 | } else if (c == TOK_VAR) { 763 | *vp = tokenVal; 764 | NextToken(); 765 | return TS_ERR_OK; 766 | #ifdef ARRAY_SUPPORT 767 | } else if (c == TOK_ARY) { 768 | return ParseArrayGet(vp); 769 | #endif 770 | } else if (c == TOK_BUILTIN) { 771 | Cfunc cop = (Cfunc)tokenVal; 772 | return ParseFuncCall(cop, vp, NULL); 773 | } else if (c == USRFUNC) { 774 | Sym *sym = tokenSym; 775 | if (!sym) return SyntaxError(); 776 | err = ParseFuncCall(NULL, vp, (UserFunc *)sym->value); 777 | NextToken(); 778 | return err; 779 | } else if ( (c & 0xff) == TOK_BINOP ) { 780 | // unary operator 781 | Opfunc op = (Opfunc)tokenVal; 782 | Val v; 783 | NextToken(); 784 | err = ParseExpr(&v); 785 | if (err == TS_ERR_OK) { 786 | *vp = op(0, v); 787 | } 788 | return err; 789 | } else { 790 | return SyntaxError(); 791 | } 792 | } 793 | 794 | // parse a level n expression 795 | // level 0 is the lowest level (highest precedence) 796 | // result goes in *vp 797 | static int 798 | ParseExprLevel(int max_level, Val *vp) 799 | { 800 | int err = TS_ERR_OK; 801 | int c; 802 | Val lhs; 803 | Val rhs; 804 | 805 | lhs = *vp; 806 | c = curToken; 807 | while ( (c & 0xff) == TOK_BINOP ) { 808 | Opfunc op; 809 | int level = (c>>8) & 0xff; 810 | if (level > max_level) break; 811 | op = (Opfunc)tokenVal; 812 | NextToken(); 813 | err = ParsePrimary(&rhs); 814 | if (err != TS_ERR_OK) return err; 815 | c = curToken; 816 | while ( (c&0xff) == TOK_BINOP ) { 817 | int nextlevel = (c>>8) & 0xff; 818 | if (level <= nextlevel) break; 819 | err = ParseExprLevel(nextlevel, &rhs); 820 | if (err != TS_ERR_OK) return err; 821 | c = curToken; 822 | } 823 | lhs = op(lhs, rhs); 824 | } 825 | *vp = lhs; 826 | return err; 827 | } 828 | 829 | int 830 | ParseExpr(Val *vp) 831 | { 832 | int err; 833 | 834 | err = ParsePrimary(vp); 835 | if (err == TS_ERR_OK) { 836 | err = ParseExprLevel(MAX_EXPR_LEVEL, vp); 837 | } 838 | return err; 839 | } 840 | 841 | static char * 842 | stack_alloc(int len) 843 | { 844 | int mask = sizeof(Val)-1; 845 | intptr_t base; 846 | 847 | len = (len + mask) & ~mask; 848 | base = ((intptr_t)valptr) - len; 849 | if (base < (intptr_t)symptr) { 850 | return NULL; 851 | } 852 | valptr = (Val *)base; 853 | return (char *)base; 854 | } 855 | 856 | static String 857 | DupString(String orig) 858 | { 859 | String x; 860 | char *ptr; 861 | unsigned len = StringGetLen(orig); 862 | ptr = stack_alloc(len); 863 | if (ptr) { 864 | memcpy(ptr, StringGetPtr(orig), len); 865 | } 866 | StringSetLen(&x, len); 867 | StringSetPtr(&x, ptr); 868 | return x; 869 | } 870 | 871 | static int ParseString(String str, int saveStrings, int topLevel); 872 | 873 | // 874 | // this is slightly different in that it may return the non-erro TS_ERR_ELSE 875 | // to signify that the condition was false 876 | // 877 | static int ParseIf() 878 | { 879 | String then; 880 | Val cond; 881 | int c; 882 | int err; 883 | 884 | NextToken(); 885 | err = ParseExpr(&cond); 886 | if (err != TS_ERR_OK) { 887 | return err; 888 | } 889 | if (curToken != TOK_STRING) { 890 | return SyntaxError(); 891 | } 892 | then = token; 893 | c = NextToken(); 894 | if (cond) { 895 | err = ParseString(then, 0, 0); 896 | while (c == TOK_ELSEIF || c == TOK_ELSE) { 897 | if (c == TOK_ELSEIF) { 898 | while (c != '{') { 899 | c = GetChar(); 900 | if (c < 0) { 901 | return SyntaxError(); 902 | } 903 | } 904 | UngetChar(); 905 | } 906 | NextToken(); 907 | if (curToken != TOK_STRING) { 908 | return SyntaxError(); 909 | } 910 | c = NextToken(); 911 | } 912 | } else if (c == TOK_ELSE) { 913 | if (NextToken() != TOK_STRING) { 914 | return SyntaxError(); 915 | } 916 | then = token; 917 | NextToken(); 918 | err = ParseString(then, 0, 0); 919 | } else if (c == TOK_ELSEIF) { 920 | return ParseIf(); 921 | } 922 | if (err == TS_ERR_OK && !cond) err = TS_ERR_OK_ELSE; 923 | return err; 924 | } 925 | 926 | static int 927 | ParseVarList(UserFunc *uf, int saveStrings) 928 | { 929 | int c; 930 | int nargs = 0; 931 | c = NextRawToken(); 932 | for(;;) { 933 | if (c == TOK_SYMBOL) { 934 | String name = token; 935 | if (saveStrings) { 936 | name = DupString(name); 937 | } 938 | if (nargs >= MAX_BUILTIN_PARAMS) { 939 | return TooManyArgs(); 940 | } 941 | uf->argName[nargs] = name; 942 | nargs++; 943 | c = NextToken(); 944 | if (c == ')') break; 945 | if (c == ',') { 946 | c = NextToken(); 947 | } 948 | } else if (c == ')') { 949 | break; 950 | } else { 951 | return SyntaxError(); 952 | } 953 | } 954 | uf->nargs = nargs; 955 | return nargs; 956 | } 957 | 958 | static int 959 | ParseFuncDef(int saveStrings) 960 | { 961 | Sym *sym; 962 | String name; 963 | String body; 964 | int c; 965 | int nargs = 0; 966 | UserFunc *uf; 967 | 968 | c = NextRawToken(); // do not interpret the symbol 969 | if (c != TOK_SYMBOL) return SyntaxError(); 970 | name = token; 971 | c = NextToken(); 972 | uf = (UserFunc *)stack_alloc(sizeof(*uf)); 973 | if (!uf) return OutOfMem(); 974 | uf->nargs = 0; 975 | if (c == '(') { 976 | nargs = ParseVarList(uf, saveStrings); 977 | if (nargs < 0) return nargs; 978 | c = NextToken(); 979 | } 980 | if (c != TOK_STRING) return SyntaxError(); 981 | body = token; 982 | 983 | if (saveStrings) { 984 | // copy the strings into safe memory 985 | name = DupString(name); 986 | body = DupString(body); 987 | } 988 | uf->body = body; 989 | sym = DefineSym(name, USRFUNC | (nargs<<8), (intptr_t)uf); 990 | if (!sym) return OutOfMem(); 991 | NextToken(); 992 | return TS_ERR_OK; 993 | } 994 | 995 | #ifdef ARRAY_SUPPORT 996 | // assign a value or list of values to an array 997 | static int 998 | ArrayAssign(Val* ary, Val ix) 999 | { 1000 | int err; 1001 | Val val; 1002 | do { 1003 | if (ix < 0 || ix >= ary[0]) { 1004 | return OutOfBounds(); 1005 | } 1006 | NextToken(); 1007 | err = ParseExpr(&val); 1008 | if (err != TS_ERR_OK) { 1009 | return err; 1010 | } 1011 | ary[ix + 1] = val; 1012 | ix++; 1013 | } while (curToken == ','); 1014 | return TS_ERR_OK; 1015 | } 1016 | 1017 | // handle defining an array 1018 | static int 1019 | ParseArrayDef(int saveStrings) 1020 | { 1021 | String name; 1022 | int c; 1023 | int err; 1024 | Val len; 1025 | 1026 | c = NextRawToken(); 1027 | if (c != TOK_SYMBOL) { 1028 | return SyntaxError(); 1029 | } 1030 | name = token; 1031 | c = NextToken(); 1032 | 1033 | if (c == ';' || c == '\n') { 1034 | Sym* sym = LookupSym(name); 1035 | // symbol exists, and its value points to a valid array area 1036 | if (sym && sym->value > (Val)valptr && sym->value + *((Val*)sym->value - 1) <= (Val)(arena + arena_size)) { 1037 | sym->type = ARRAY; 1038 | return TS_ERR_OK; 1039 | } 1040 | UngetChar(); 1041 | return SyntaxError(); 1042 | } 1043 | 1044 | if (saveStrings) { 1045 | name = DupString(name); 1046 | } 1047 | if (c != '(') { 1048 | return SyntaxError(); 1049 | } 1050 | err = ParsePrimary(&len); 1051 | if (err != TS_ERR_OK) { 1052 | return err; 1053 | } 1054 | len++; 1055 | if ( (intptr_t)symptr >= (intptr_t)(valptr - len)) { 1056 | return OutOfMem(); 1057 | } 1058 | char *ary = stack_alloc(len * sizeof(Val)); 1059 | if (!ary) { 1060 | return OutOfMem(); 1061 | } 1062 | memset(ary, 0, len * sizeof(Val)); 1063 | ((Val*)ary)[0] = len - 1; 1064 | tokenSym = DefineSym(name, ARRAY, (Val)ary); 1065 | if (!tokenSym) { 1066 | return OutOfMem(); 1067 | } 1068 | if (StringGetPtr(token)[0] == '=' && StringGetLen(token) == 1) { 1069 | return ArrayAssign((Val*)ary, 0); 1070 | } else { 1071 | return TS_ERR_OK; 1072 | } 1073 | } 1074 | 1075 | // handle setting an array value 1076 | static int 1077 | ParseArraySet() 1078 | { 1079 | int err; 1080 | Val ix = 0; 1081 | Val* ary = (Val*)tokenVal; 1082 | int c = NextToken(); 1083 | if (c == '(') 1084 | { 1085 | err = ParsePrimary(&ix); 1086 | if (err != TS_ERR_OK) { 1087 | return err; 1088 | } 1089 | } 1090 | if (StringGetPtr(token)[0] != '=' || StringGetLen(token) != 1) { 1091 | return SyntaxError(); 1092 | } 1093 | return ArrayAssign(ary, ix); 1094 | } 1095 | 1096 | // handle getting an array value 1097 | static int 1098 | ParseArrayGet(Val *vp) 1099 | { 1100 | Val* ary = (Val*)tokenVal; 1101 | int c = NextToken(); 1102 | if (c == '(') { 1103 | Val ix; 1104 | int err = ParsePrimary(&ix); 1105 | if (err != TS_ERR_OK) { 1106 | return err; 1107 | } 1108 | if (ix < -1 || ix >= ary[0]) { 1109 | return OutOfBounds(); 1110 | } 1111 | *vp = ary[ix + 1]; 1112 | } else { 1113 | // if no parens, then return the pointer to the array 1114 | // needed for passing to C functions 1115 | *vp = (Val)ary; 1116 | } 1117 | return TS_ERR_OK; 1118 | } 1119 | #endif // ARRAY 1120 | 1121 | // handle print statement 1122 | static int 1123 | ParsePrint() 1124 | { 1125 | int c; 1126 | int err = TS_ERR_OK; 1127 | 1128 | print_more: 1129 | c = NextToken(); 1130 | if (c == TOK_STRING) { 1131 | PrintString(token); 1132 | NextToken(); 1133 | } else { 1134 | Val val; 1135 | err = ParseExpr(&val); 1136 | if (err != TS_ERR_OK) { 1137 | return err; 1138 | } 1139 | PrintNumber(val); 1140 | } 1141 | if (curToken == ',') { 1142 | goto print_more; 1143 | } 1144 | Newline(); 1145 | return err; 1146 | } 1147 | 1148 | static int 1149 | ParseReturn() 1150 | { 1151 | int err; 1152 | NextToken(); 1153 | err = ParseExpr(&fResult); 1154 | // terminate the script 1155 | StringSetLen(&parseptr, 0); 1156 | didReturn = 1; 1157 | return err; 1158 | } 1159 | 1160 | static int 1161 | ParseWhile() 1162 | { 1163 | int err; 1164 | String savepc = parseptr; 1165 | 1166 | again: 1167 | err = ParseIf(); 1168 | if (err == TS_ERR_OK_ELSE || didReturn) { 1169 | return TS_ERR_OK; 1170 | } else if (err == TS_ERR_OK) { 1171 | parseptr = savepc; 1172 | goto again; 1173 | } 1174 | return err; 1175 | } 1176 | 1177 | // 1178 | // parse one statement 1179 | // 1 is true if we need to save strings we encounter (we've been passed 1180 | // a temporary string) 1181 | // 1182 | static int 1183 | ParseStmt(int saveStrings) 1184 | { 1185 | int c; 1186 | String name; 1187 | Val val; 1188 | int err = TS_ERR_OK; 1189 | 1190 | if (TinyScript_Stop()) 1191 | { 1192 | return TS_ERR_STOPPED; 1193 | } 1194 | 1195 | if (didReturn) 1196 | { 1197 | do { 1198 | c = GetChar(); 1199 | } while (c >= 0 && c != '\n' && c != ';' && c != '}'); 1200 | UngetChar(); 1201 | NextToken(); 1202 | return TS_ERR_OK; 1203 | } 1204 | 1205 | c = curToken; 1206 | 1207 | if (c == TOK_VARDEF) { 1208 | // a definition var a=x 1209 | c=NextRawToken(); // we want to get VAR_SYMBOL directly 1210 | if (c != TOK_SYMBOL) return SyntaxError(); 1211 | if (saveStrings) { 1212 | name = DupString(token); 1213 | } else { 1214 | name = token; 1215 | } 1216 | tokenSym = DefineVar(name); 1217 | if (!tokenSym) { 1218 | return TS_ERR_NOMEM; 1219 | } 1220 | c = TOK_VAR; 1221 | /* fall through */ 1222 | } 1223 | if (c == TOK_VAR) { 1224 | // is this a=expr? 1225 | Sym *s; 1226 | name = token; 1227 | s = tokenSym; 1228 | c = NextToken(); 1229 | // we expect the "=" operator 1230 | // verify that it is "=" 1231 | if (StringGetPtr(token)[0] != '=' || StringGetLen(token) != 1) { 1232 | return SyntaxError(); 1233 | } 1234 | if (!s) { 1235 | #ifdef VERBOSE_ERRORS 1236 | PrintString(name); 1237 | #endif 1238 | return UnknownSymbol(); // unknown symbol 1239 | } 1240 | NextToken(); 1241 | err = ParseExpr(&val); 1242 | if (err != TS_ERR_OK) { 1243 | return err; 1244 | } 1245 | s->value = val; 1246 | #ifdef ARRAY_SUPPORT 1247 | } else if (c == TOK_ARY) { 1248 | err = ParseArraySet(); 1249 | #endif 1250 | } else if (c == TOK_BUILTIN || c == USRFUNC) { 1251 | err = ParsePrimary(&val); 1252 | return err; 1253 | } else if (tokenSym && tokenVal) { 1254 | int (*func)(int) = (void *)tokenVal; 1255 | err = (*func)(saveStrings); 1256 | } else { 1257 | return SyntaxError(); 1258 | } 1259 | if (err == TS_ERR_OK_ELSE) { 1260 | err = TS_ERR_OK; 1261 | } 1262 | return err; 1263 | } 1264 | 1265 | static int 1266 | ParseString(String str, int saveStrings, int topLevel) 1267 | { 1268 | String savepc = parseptr; 1269 | Sym* savesymptr = symptr; 1270 | int c; 1271 | int r; 1272 | 1273 | parseptr = str; 1274 | for(;;) { 1275 | c = NextToken(); 1276 | while (c == '\n' || c == ';') { 1277 | c = NextToken(); 1278 | } 1279 | if (c < 0) break; 1280 | r = ParseStmt(saveStrings); 1281 | if (r != TS_ERR_OK) return r; 1282 | c = curToken; 1283 | if (c == '\n' || c == ';' || c < 0) { 1284 | /* ok */ 1285 | } else { 1286 | return SyntaxError(); 1287 | } 1288 | } 1289 | parseptr = savepc; 1290 | if (!topLevel) { 1291 | // restore variable context 1292 | symptr = savesymptr; 1293 | } 1294 | return TS_ERR_OK; 1295 | } 1296 | 1297 | // 1298 | // builtin functions 1299 | // 1300 | static Val prod(Val x, Val y) { return x*y; } 1301 | static Val quot(Val x, Val y) { return x/y; } 1302 | static Val mod(Val x, Val y) { return x%y; } 1303 | static Val sum(Val x, Val y) { return x+y; } 1304 | static Val diff(Val x, Val y) { return x-y; } 1305 | static Val bitand(Val x, Val y) { return x&y; } 1306 | static Val bitor(Val x, Val y) { return x|y; } 1307 | static Val bitxor(Val x, Val y) { return x^y; } 1308 | static Val shl(Val x, Val y) { return x<>y; } 1310 | static Val equals(Val x, Val y) { return x==y; } 1311 | static Val ne(Val x, Val y) { return x!=y; } 1312 | static Val lt(Val x, Val y) { return xy; } 1315 | static Val ge(Val x, Val y) { return x>=y; } 1316 | 1317 | static struct def { 1318 | const char *name; 1319 | int toktype; 1320 | intptr_t val; 1321 | } defs[] = { 1322 | // keywords 1323 | { "if", TOK_IF, (intptr_t)ParseIf }, 1324 | { "else", TOK_ELSE, 0 }, 1325 | { "elseif",TOK_ELSEIF, 0 }, 1326 | { "while", TOK_WHILE, (intptr_t)ParseWhile }, 1327 | { "print", TOK_PRINT, (intptr_t)ParsePrint }, 1328 | { "var", TOK_VARDEF, 0 }, 1329 | { "func", TOK_FUNCDEF, (intptr_t)ParseFuncDef }, 1330 | { "return", TOK_RETURN, (intptr_t)ParseReturn }, 1331 | #ifdef ARRAY_SUPPORT 1332 | { "array", TOK_ARYDEF, (intptr_t)ParseArrayDef }, 1333 | #endif 1334 | // operators 1335 | { "*", BINOP(1), (intptr_t)prod }, 1336 | { "/", BINOP(1), (intptr_t)quot }, 1337 | { "%", BINOP(1), (intptr_t)mod }, 1338 | { "+", BINOP(2), (intptr_t)sum }, 1339 | { "-", BINOP(2), (intptr_t)diff }, 1340 | { "!", BINOP(2), (intptr_t)equals }, 1341 | { "&", BINOP(3), (intptr_t)bitand }, 1342 | { "|", BINOP(3), (intptr_t)bitor }, 1343 | { "^", BINOP(3), (intptr_t)bitxor }, 1344 | { ">>", BINOP(3), (intptr_t)shr }, 1345 | { "<<", BINOP(3), (intptr_t)shl }, 1346 | { "=", BINOP(4), (intptr_t)equals }, 1347 | { "<>", BINOP(4), (intptr_t)ne }, 1348 | { "<", BINOP(4), (intptr_t)lt }, 1349 | { "<=", BINOP(4), (intptr_t)le }, 1350 | { ">", BINOP(4), (intptr_t)gt }, 1351 | { ">=", BINOP(4), (intptr_t)ge }, 1352 | 1353 | { NULL, 0, 0 } 1354 | }; 1355 | 1356 | int 1357 | TinyScript_Init(void *mem, int mem_size) 1358 | { 1359 | int i; 1360 | int err; 1361 | 1362 | arena = (Byte *)mem; 1363 | arena_size = mem_size; 1364 | symptr = (Sym *)arena; 1365 | valptr = (Val *)(arena + arena_size); 1366 | for (i = 0; defs[i].name; i++) { 1367 | err = TinyScript_Define(defs[i].name, defs[i].toktype, defs[i].val); 1368 | if (err != TS_ERR_OK) 1369 | return err; 1370 | } 1371 | return TS_ERR_OK; 1372 | } 1373 | 1374 | int 1375 | TinyScript_Run(const char *buf, int saveStrings, int topLevel) 1376 | { 1377 | #ifdef VERBOSE_ERRORS 1378 | script_buffer = buf; 1379 | #endif 1380 | return ParseString(Cstring(buf), saveStrings, topLevel); 1381 | } 1382 | -------------------------------------------------------------------------------- /linenoise.c: -------------------------------------------------------------------------------- 1 | /* linenoise.c -- guerrilla line editing library against the idea that a 2 | * line editing lib needs to be 20,000 lines of C code. 3 | * 4 | * You can find the latest source code at: 5 | * 6 | * http://github.com/antirez/linenoise 7 | * 8 | * Does a number of crazy assumptions that happen to be true in 99.9999% of 9 | * the 2010 UNIX computers around. 10 | * 11 | * ------------------------------------------------------------------------ 12 | * 13 | * Copyright (c) 2010-2016, Salvatore Sanfilippo 14 | * Copyright (c) 2010-2013, Pieter Noordhuis 15 | * 16 | * All rights reserved. 17 | * 18 | * Redistribution and use in source and binary forms, with or without 19 | * modification, are permitted provided that the following conditions are 20 | * met: 21 | * 22 | * * Redistributions of source code must retain the above copyright 23 | * notice, this list of conditions and the following disclaimer. 24 | * 25 | * * Redistributions in binary form must reproduce the above copyright 26 | * notice, this list of conditions and the following disclaimer in the 27 | * documentation and/or other materials provided with the distribution. 28 | * 29 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 30 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 31 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 32 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 33 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 34 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 35 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 36 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 38 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 39 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 40 | * 41 | * ------------------------------------------------------------------------ 42 | * 43 | * References: 44 | * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html 45 | * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html 46 | * 47 | * Todo list: 48 | * - Filter bogus Ctrl+ combinations. 49 | * - Win32 support 50 | * 51 | * Bloat: 52 | * - History search like Ctrl+r in readline? 53 | * 54 | * List of escape sequences used by this program, we do everything just 55 | * with three sequences. In order to be so cheap we may have some 56 | * flickering effect with some slow terminal, but the lesser sequences 57 | * the more compatible. 58 | * 59 | * EL (Erase Line) 60 | * Sequence: ESC [ n K 61 | * Effect: if n is 0 or missing, clear from cursor to end of line 62 | * Effect: if n is 1, clear from beginning of line to cursor 63 | * Effect: if n is 2, clear entire line 64 | * 65 | * CUF (CUrsor Forward) 66 | * Sequence: ESC [ n C 67 | * Effect: moves cursor forward n chars 68 | * 69 | * CUB (CUrsor Backward) 70 | * Sequence: ESC [ n D 71 | * Effect: moves cursor backward n chars 72 | * 73 | * The following is used to get the terminal width if getting 74 | * the width with the TIOCGWINSZ ioctl fails 75 | * 76 | * DSR (Device Status Report) 77 | * Sequence: ESC [ 6 n 78 | * Effect: reports the current cusor position as ESC [ n ; m R 79 | * where n is the row and m is the column 80 | * 81 | * When multi line mode is enabled, we also use an additional escape 82 | * sequence. However multi line editing is disabled by default. 83 | * 84 | * CUU (Cursor Up) 85 | * Sequence: ESC [ n A 86 | * Effect: moves cursor up of n chars. 87 | * 88 | * CUD (Cursor Down) 89 | * Sequence: ESC [ n B 90 | * Effect: moves cursor down of n chars. 91 | * 92 | * When linenoiseClearScreen() is called, two additional escape sequences 93 | * are used in order to clear the screen and position the cursor at home 94 | * position. 95 | * 96 | * CUP (Cursor position) 97 | * Sequence: ESC [ H 98 | * Effect: moves the cursor to upper left corner 99 | * 100 | * ED (Erase display) 101 | * Sequence: ESC [ 2 J 102 | * Effect: clear the whole screen 103 | * 104 | */ 105 | 106 | #include 107 | #include 108 | #include 109 | #include 110 | #include 111 | #include 112 | #include 113 | #include 114 | #include 115 | #include 116 | #include 117 | #include 118 | #include "linenoise.h" 119 | 120 | #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 121 | #define LINENOISE_MAX_LINE 4096 122 | static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; 123 | static linenoiseCompletionCallback *completionCallback = NULL; 124 | static linenoiseHintsCallback *hintsCallback = NULL; 125 | static linenoiseFreeHintsCallback *freeHintsCallback = NULL; 126 | 127 | static struct termios orig_termios; /* In order to restore at exit.*/ 128 | static int maskmode = 0; /* Show "***" instead of input. For passwords. */ 129 | static int rawmode = 0; /* For atexit() function to check if restore is needed*/ 130 | static int mlmode = 0; /* Multi line mode. Default is single line. */ 131 | static int atexit_registered = 0; /* Register atexit just 1 time. */ 132 | static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; 133 | static int history_len = 0; 134 | static char **history = NULL; 135 | 136 | /* The linenoiseState structure represents the state during line editing. 137 | * We pass this state to functions implementing specific editing 138 | * functionalities. */ 139 | struct linenoiseState { 140 | int ifd; /* Terminal stdin file descriptor. */ 141 | int ofd; /* Terminal stdout file descriptor. */ 142 | char *buf; /* Edited line buffer. */ 143 | size_t buflen; /* Edited line buffer size. */ 144 | const char *prompt; /* Prompt to display. */ 145 | size_t plen; /* Prompt length. */ 146 | size_t pos; /* Current cursor position. */ 147 | size_t oldpos; /* Previous refresh cursor position. */ 148 | size_t len; /* Current edited line length. */ 149 | size_t cols; /* Number of columns in terminal. */ 150 | size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ 151 | int history_index; /* The history index we are currently editing. */ 152 | }; 153 | 154 | enum KEY_ACTION{ 155 | KEY_NULL = 0, /* NULL */ 156 | CTRL_A = 1, /* Ctrl+a */ 157 | CTRL_B = 2, /* Ctrl-b */ 158 | CTRL_C = 3, /* Ctrl-c */ 159 | CTRL_D = 4, /* Ctrl-d */ 160 | CTRL_E = 5, /* Ctrl-e */ 161 | CTRL_F = 6, /* Ctrl-f */ 162 | CTRL_H = 8, /* Ctrl-h */ 163 | TAB = 9, /* Tab */ 164 | CTRL_K = 11, /* Ctrl+k */ 165 | CTRL_L = 12, /* Ctrl+l */ 166 | ENTER = 13, /* Enter */ 167 | CTRL_N = 14, /* Ctrl-n */ 168 | CTRL_P = 16, /* Ctrl-p */ 169 | CTRL_T = 20, /* Ctrl-t */ 170 | CTRL_U = 21, /* Ctrl+u */ 171 | CTRL_W = 23, /* Ctrl+w */ 172 | ESC = 27, /* Escape */ 173 | BACKSPACE = 127 /* Backspace */ 174 | }; 175 | 176 | static void linenoiseAtExit(void); 177 | int linenoiseHistoryAdd(const char *line); 178 | static void refreshLine(struct linenoiseState *l); 179 | 180 | /* Debugging macro. */ 181 | #if 0 182 | FILE *lndebug_fp = NULL; 183 | #define lndebug(...) \ 184 | do { \ 185 | if (lndebug_fp == NULL) { \ 186 | lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ 187 | fprintf(lndebug_fp, \ 188 | "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ 189 | (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ 190 | (int)l->maxrows,old_rows); \ 191 | } \ 192 | fprintf(lndebug_fp, ", " __VA_ARGS__); \ 193 | fflush(lndebug_fp); \ 194 | } while (0) 195 | #else 196 | #define lndebug(fmt, ...) 197 | #endif 198 | 199 | /* ======================= Low level terminal handling ====================== */ 200 | 201 | /* Enable "mask mode". When it is enabled, instead of the input that 202 | * the user is typing, the terminal will just display a corresponding 203 | * number of asterisks, like "****". This is useful for passwords and other 204 | * secrets that should not be displayed. */ 205 | void linenoiseMaskModeEnable(void) { 206 | maskmode = 1; 207 | } 208 | 209 | /* Disable mask mode. */ 210 | void linenoiseMaskModeDisable(void) { 211 | maskmode = 0; 212 | } 213 | 214 | /* Set if to use or not the multi line mode. */ 215 | void linenoiseSetMultiLine(int ml) { 216 | mlmode = ml; 217 | } 218 | 219 | /* Return true if the terminal name is in the list of terminals we know are 220 | * not able to understand basic escape sequences. */ 221 | static int isUnsupportedTerm(void) { 222 | char *term = getenv("TERM"); 223 | int j; 224 | 225 | if (term == NULL) return 0; 226 | for (j = 0; unsupported_term[j]; j++) 227 | if (!strcasecmp(term,unsupported_term[j])) return 1; 228 | return 0; 229 | } 230 | 231 | /* Raw mode: 1960 magic shit. */ 232 | static int enableRawMode(int fd) { 233 | struct termios raw; 234 | 235 | if (!isatty(STDIN_FILENO)) goto fatal; 236 | if (!atexit_registered) { 237 | atexit(linenoiseAtExit); 238 | atexit_registered = 1; 239 | } 240 | if (tcgetattr(fd,&orig_termios) == -1) goto fatal; 241 | 242 | raw = orig_termios; /* modify the original mode */ 243 | /* input modes: no break, no CR to NL, no parity check, no strip char, 244 | * no start/stop output control. */ 245 | raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 246 | /* output modes - disable post processing */ 247 | raw.c_oflag &= ~(OPOST); 248 | /* control modes - set 8 bit chars */ 249 | raw.c_cflag |= (CS8); 250 | /* local modes - choing off, canonical off, no extended functions, 251 | * no signal chars (^Z,^C) */ 252 | raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 253 | /* control chars - set return condition: min number of bytes and timer. 254 | * We want read to return every single byte, without timeout. */ 255 | raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ 256 | 257 | /* put terminal in raw mode after flushing */ 258 | if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; 259 | rawmode = 1; 260 | return 0; 261 | 262 | fatal: 263 | errno = ENOTTY; 264 | return -1; 265 | } 266 | 267 | static void disableRawMode(int fd) { 268 | /* Don't even check the return value as it's too late. */ 269 | if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) 270 | rawmode = 0; 271 | } 272 | 273 | /* Use the ESC [6n escape sequence to query the horizontal cursor position 274 | * and return it. On error -1 is returned, on success the position of the 275 | * cursor. */ 276 | static int getCursorPosition(int ifd, int ofd) { 277 | char buf[32]; 278 | int cols, rows; 279 | unsigned int i = 0; 280 | 281 | /* Report cursor location */ 282 | if (write(ofd, "\x1b[6n", 4) != 4) return -1; 283 | 284 | /* Read the response: ESC [ rows ; cols R */ 285 | while (i < sizeof(buf)-1) { 286 | if (read(ifd,buf+i,1) != 1) break; 287 | if (buf[i] == 'R') break; 288 | i++; 289 | } 290 | buf[i] = '\0'; 291 | 292 | /* Parse it. */ 293 | if (buf[0] != ESC || buf[1] != '[') return -1; 294 | if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; 295 | return cols; 296 | } 297 | 298 | /* Try to get the number of columns in the current terminal, or assume 80 299 | * if it fails. */ 300 | static int getColumns(int ifd, int ofd) { 301 | struct winsize ws; 302 | 303 | if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { 304 | /* ioctl() failed. Try to query the terminal itself. */ 305 | int start, cols; 306 | 307 | /* Get the initial position so we can restore it later. */ 308 | start = getCursorPosition(ifd,ofd); 309 | if (start == -1) goto failed; 310 | 311 | /* Go to right margin and get position. */ 312 | if (write(ofd,"\x1b[999C",6) != 6) goto failed; 313 | cols = getCursorPosition(ifd,ofd); 314 | if (cols == -1) goto failed; 315 | 316 | /* Restore position. */ 317 | if (cols > start) { 318 | char seq[32]; 319 | snprintf(seq,32,"\x1b[%dD",cols-start); 320 | if (write(ofd,seq,strlen(seq)) == -1) { 321 | /* Can't recover... */ 322 | } 323 | } 324 | return cols; 325 | } else { 326 | return ws.ws_col; 327 | } 328 | 329 | failed: 330 | return 80; 331 | } 332 | 333 | /* Clear the screen. Used to handle ctrl+l */ 334 | void linenoiseClearScreen(void) { 335 | if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { 336 | /* nothing to do, just to avoid warning. */ 337 | } 338 | } 339 | 340 | /* Beep, used for completion when there is nothing to complete or when all 341 | * the choices were already shown. */ 342 | static void linenoiseBeep(void) { 343 | fprintf(stderr, "\x7"); 344 | fflush(stderr); 345 | } 346 | 347 | /* ============================== Completion ================================ */ 348 | 349 | /* Free a list of completion option populated by linenoiseAddCompletion(). */ 350 | static void freeCompletions(linenoiseCompletions *lc) { 351 | size_t i; 352 | for (i = 0; i < lc->len; i++) 353 | free(lc->cvec[i]); 354 | if (lc->cvec != NULL) 355 | free(lc->cvec); 356 | } 357 | 358 | /* This is an helper function for linenoiseEdit() and is called when the 359 | * user types the key in order to complete the string currently in the 360 | * input. 361 | * 362 | * The state of the editing is encapsulated into the pointed linenoiseState 363 | * structure as described in the structure definition. */ 364 | static int completeLine(struct linenoiseState *ls) { 365 | linenoiseCompletions lc = { 0, NULL }; 366 | int nread, nwritten; 367 | char c = 0; 368 | 369 | completionCallback(ls->buf,&lc); 370 | if (lc.len == 0) { 371 | linenoiseBeep(); 372 | } else { 373 | size_t stop = 0, i = 0; 374 | 375 | while(!stop) { 376 | /* Show completion or original buffer */ 377 | if (i < lc.len) { 378 | struct linenoiseState saved = *ls; 379 | 380 | ls->len = ls->pos = strlen(lc.cvec[i]); 381 | ls->buf = lc.cvec[i]; 382 | refreshLine(ls); 383 | ls->len = saved.len; 384 | ls->pos = saved.pos; 385 | ls->buf = saved.buf; 386 | } else { 387 | refreshLine(ls); 388 | } 389 | 390 | nread = read(ls->ifd,&c,1); 391 | if (nread <= 0) { 392 | freeCompletions(&lc); 393 | return -1; 394 | } 395 | 396 | switch(c) { 397 | case 9: /* tab */ 398 | i = (i+1) % (lc.len+1); 399 | if (i == lc.len) linenoiseBeep(); 400 | break; 401 | case 27: /* escape */ 402 | /* Re-show original buffer */ 403 | if (i < lc.len) refreshLine(ls); 404 | stop = 1; 405 | break; 406 | default: 407 | /* Update buffer and return */ 408 | if (i < lc.len) { 409 | nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); 410 | ls->len = ls->pos = nwritten; 411 | } 412 | stop = 1; 413 | break; 414 | } 415 | } 416 | } 417 | 418 | freeCompletions(&lc); 419 | return c; /* Return last read character */ 420 | } 421 | 422 | /* Register a callback function to be called for tab-completion. */ 423 | void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { 424 | completionCallback = fn; 425 | } 426 | 427 | /* Register a hits function to be called to show hits to the user at the 428 | * right of the prompt. */ 429 | void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { 430 | hintsCallback = fn; 431 | } 432 | 433 | /* Register a function to free the hints returned by the hints callback 434 | * registered with linenoiseSetHintsCallback(). */ 435 | void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { 436 | freeHintsCallback = fn; 437 | } 438 | 439 | /* This function is used by the callback function registered by the user 440 | * in order to add completion options given the input string when the 441 | * user typed . See the example.c source code for a very easy to 442 | * understand example. */ 443 | void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { 444 | size_t len = strlen(str); 445 | char *copy, **cvec; 446 | 447 | copy = malloc(len+1); 448 | if (copy == NULL) return; 449 | memcpy(copy,str,len+1); 450 | cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); 451 | if (cvec == NULL) { 452 | free(copy); 453 | return; 454 | } 455 | lc->cvec = cvec; 456 | lc->cvec[lc->len++] = copy; 457 | } 458 | 459 | /* =========================== Line editing ================================= */ 460 | 461 | /* We define a very simple "append buffer" structure, that is an heap 462 | * allocated string where we can append to. This is useful in order to 463 | * write all the escape sequences in a buffer and flush them to the standard 464 | * output in a single call, to avoid flickering effects. */ 465 | struct abuf { 466 | char *b; 467 | int len; 468 | }; 469 | 470 | static void abInit(struct abuf *ab) { 471 | ab->b = NULL; 472 | ab->len = 0; 473 | } 474 | 475 | static void abAppend(struct abuf *ab, const char *s, int len) { 476 | char *new = realloc(ab->b,ab->len+len); 477 | 478 | if (new == NULL) return; 479 | memcpy(new+ab->len,s,len); 480 | ab->b = new; 481 | ab->len += len; 482 | } 483 | 484 | static void abFree(struct abuf *ab) { 485 | free(ab->b); 486 | } 487 | 488 | /* Helper of refreshSingleLine() and refreshMultiLine() to show hints 489 | * to the right of the prompt. */ 490 | void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { 491 | char seq[64]; 492 | if (hintsCallback && plen+l->len < l->cols) { 493 | int color = -1, bold = 0; 494 | char *hint = hintsCallback(l->buf,&color,&bold); 495 | if (hint) { 496 | int hintlen = strlen(hint); 497 | int hintmaxlen = l->cols-(plen+l->len); 498 | if (hintlen > hintmaxlen) hintlen = hintmaxlen; 499 | if (bold == 1 && color == -1) color = 37; 500 | if (color != -1 || bold != 0) 501 | snprintf(seq,64,"\033[%d;%d;49m",bold,color); 502 | else 503 | seq[0] = '\0'; 504 | abAppend(ab,seq,strlen(seq)); 505 | abAppend(ab,hint,hintlen); 506 | if (color != -1 || bold != 0) 507 | abAppend(ab,"\033[0m",4); 508 | /* Call the function to free the hint returned. */ 509 | if (freeHintsCallback) freeHintsCallback(hint); 510 | } 511 | } 512 | } 513 | 514 | /* Single line low level line refresh. 515 | * 516 | * Rewrite the currently edited line accordingly to the buffer content, 517 | * cursor position, and number of columns of the terminal. */ 518 | static void refreshSingleLine(struct linenoiseState *l) { 519 | char seq[64]; 520 | size_t plen = strlen(l->prompt); 521 | int fd = l->ofd; 522 | char *buf = l->buf; 523 | size_t len = l->len; 524 | size_t pos = l->pos; 525 | struct abuf ab; 526 | 527 | while((plen+pos) >= l->cols) { 528 | buf++; 529 | len--; 530 | pos--; 531 | } 532 | while (plen+len > l->cols) { 533 | len--; 534 | } 535 | 536 | abInit(&ab); 537 | /* Cursor to left edge */ 538 | snprintf(seq,64,"\r"); 539 | abAppend(&ab,seq,strlen(seq)); 540 | /* Write the prompt and the current buffer content */ 541 | abAppend(&ab,l->prompt,strlen(l->prompt)); 542 | if (maskmode == 1) { 543 | while (len--) abAppend(&ab,"*",1); 544 | } else { 545 | abAppend(&ab,buf,len); 546 | } 547 | /* Show hits if any. */ 548 | refreshShowHints(&ab,l,plen); 549 | /* Erase to right */ 550 | snprintf(seq,64,"\x1b[0K"); 551 | abAppend(&ab,seq,strlen(seq)); 552 | /* Move cursor to original position. */ 553 | snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); 554 | abAppend(&ab,seq,strlen(seq)); 555 | if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ 556 | abFree(&ab); 557 | } 558 | 559 | /* Multi line low level line refresh. 560 | * 561 | * Rewrite the currently edited line accordingly to the buffer content, 562 | * cursor position, and number of columns of the terminal. */ 563 | static void refreshMultiLine(struct linenoiseState *l) { 564 | char seq[64]; 565 | int plen = strlen(l->prompt); 566 | int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ 567 | int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ 568 | int rpos2; /* rpos after refresh. */ 569 | int col; /* colum position, zero-based. */ 570 | int old_rows = l->maxrows; 571 | int fd = l->ofd, j; 572 | struct abuf ab; 573 | 574 | /* Update maxrows if needed. */ 575 | if (rows > (int)l->maxrows) l->maxrows = rows; 576 | 577 | /* First step: clear all the lines used before. To do so start by 578 | * going to the last row. */ 579 | abInit(&ab); 580 | if (old_rows-rpos > 0) { 581 | lndebug("go down %d", old_rows-rpos); 582 | snprintf(seq,64,"\x1b[%dB", old_rows-rpos); 583 | abAppend(&ab,seq,strlen(seq)); 584 | } 585 | 586 | /* Now for every row clear it, go up. */ 587 | for (j = 0; j < old_rows-1; j++) { 588 | lndebug("clear+up"); 589 | snprintf(seq,64,"\r\x1b[0K\x1b[1A"); 590 | abAppend(&ab,seq,strlen(seq)); 591 | } 592 | 593 | /* Clean the top line. */ 594 | lndebug("clear"); 595 | snprintf(seq,64,"\r\x1b[0K"); 596 | abAppend(&ab,seq,strlen(seq)); 597 | 598 | /* Write the prompt and the current buffer content */ 599 | abAppend(&ab,l->prompt,strlen(l->prompt)); 600 | if (maskmode == 1) { 601 | unsigned int i; 602 | for (i = 0; i < l->len; i++) abAppend(&ab,"*",1); 603 | } else { 604 | abAppend(&ab,l->buf,l->len); 605 | } 606 | 607 | /* Show hits if any. */ 608 | refreshShowHints(&ab,l,plen); 609 | 610 | /* If we are at the very end of the screen with our prompt, we need to 611 | * emit a newline and move the prompt to the first column. */ 612 | if (l->pos && 613 | l->pos == l->len && 614 | (l->pos+plen) % l->cols == 0) 615 | { 616 | lndebug(""); 617 | abAppend(&ab,"\n",1); 618 | snprintf(seq,64,"\r"); 619 | abAppend(&ab,seq,strlen(seq)); 620 | rows++; 621 | if (rows > (int)l->maxrows) l->maxrows = rows; 622 | } 623 | 624 | /* Move cursor to right position. */ 625 | rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ 626 | lndebug("rpos2 %d", rpos2); 627 | 628 | /* Go up till we reach the expected positon. */ 629 | if (rows-rpos2 > 0) { 630 | lndebug("go-up %d", rows-rpos2); 631 | snprintf(seq,64,"\x1b[%dA", rows-rpos2); 632 | abAppend(&ab,seq,strlen(seq)); 633 | } 634 | 635 | /* Set column. */ 636 | col = (plen+(int)l->pos) % (int)l->cols; 637 | lndebug("set col %d", 1+col); 638 | if (col) 639 | snprintf(seq,64,"\r\x1b[%dC", col); 640 | else 641 | snprintf(seq,64,"\r"); 642 | abAppend(&ab,seq,strlen(seq)); 643 | 644 | lndebug("\n"); 645 | l->oldpos = l->pos; 646 | 647 | if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ 648 | abFree(&ab); 649 | } 650 | 651 | /* Calls the two low level functions refreshSingleLine() or 652 | * refreshMultiLine() according to the selected mode. */ 653 | static void refreshLine(struct linenoiseState *l) { 654 | if (mlmode) 655 | refreshMultiLine(l); 656 | else 657 | refreshSingleLine(l); 658 | } 659 | 660 | /* Insert the character 'c' at cursor current position. 661 | * 662 | * On error writing to the terminal -1 is returned, otherwise 0. */ 663 | int linenoiseEditInsert(struct linenoiseState *l, char c) { 664 | if (l->len < l->buflen) { 665 | if (l->len == l->pos) { 666 | l->buf[l->pos] = c; 667 | l->pos++; 668 | l->len++; 669 | l->buf[l->len] = '\0'; 670 | if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { 671 | /* Avoid a full update of the line in the 672 | * trivial case. */ 673 | char d = (maskmode==1) ? '*' : c; 674 | if (write(l->ofd,&d,1) == -1) return -1; 675 | } else { 676 | refreshLine(l); 677 | } 678 | } else { 679 | memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); 680 | l->buf[l->pos] = c; 681 | l->len++; 682 | l->pos++; 683 | l->buf[l->len] = '\0'; 684 | refreshLine(l); 685 | } 686 | } 687 | return 0; 688 | } 689 | 690 | /* Move cursor on the left. */ 691 | void linenoiseEditMoveLeft(struct linenoiseState *l) { 692 | if (l->pos > 0) { 693 | l->pos--; 694 | refreshLine(l); 695 | } 696 | } 697 | 698 | /* Move cursor on the right. */ 699 | void linenoiseEditMoveRight(struct linenoiseState *l) { 700 | if (l->pos != l->len) { 701 | l->pos++; 702 | refreshLine(l); 703 | } 704 | } 705 | 706 | /* Move cursor to the start of the line. */ 707 | void linenoiseEditMoveHome(struct linenoiseState *l) { 708 | if (l->pos != 0) { 709 | l->pos = 0; 710 | refreshLine(l); 711 | } 712 | } 713 | 714 | /* Move cursor to the end of the line. */ 715 | void linenoiseEditMoveEnd(struct linenoiseState *l) { 716 | if (l->pos != l->len) { 717 | l->pos = l->len; 718 | refreshLine(l); 719 | } 720 | } 721 | 722 | /* Substitute the currently edited line with the next or previous history 723 | * entry as specified by 'dir'. */ 724 | #define LINENOISE_HISTORY_NEXT 0 725 | #define LINENOISE_HISTORY_PREV 1 726 | void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { 727 | if (history_len > 1) { 728 | /* Update the current history entry before to 729 | * overwrite it with the next one. */ 730 | free(history[history_len - 1 - l->history_index]); 731 | history[history_len - 1 - l->history_index] = strdup(l->buf); 732 | /* Show the new entry */ 733 | l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; 734 | if (l->history_index < 0) { 735 | l->history_index = 0; 736 | return; 737 | } else if (l->history_index >= history_len) { 738 | l->history_index = history_len-1; 739 | return; 740 | } 741 | strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); 742 | l->buf[l->buflen-1] = '\0'; 743 | l->len = l->pos = strlen(l->buf); 744 | refreshLine(l); 745 | } 746 | } 747 | 748 | /* Delete the character at the right of the cursor without altering the cursor 749 | * position. Basically this is what happens with the "Delete" keyboard key. */ 750 | void linenoiseEditDelete(struct linenoiseState *l) { 751 | if (l->len > 0 && l->pos < l->len) { 752 | memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); 753 | l->len--; 754 | l->buf[l->len] = '\0'; 755 | refreshLine(l); 756 | } 757 | } 758 | 759 | /* Backspace implementation. */ 760 | void linenoiseEditBackspace(struct linenoiseState *l) { 761 | if (l->pos > 0 && l->len > 0) { 762 | memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); 763 | l->pos--; 764 | l->len--; 765 | l->buf[l->len] = '\0'; 766 | refreshLine(l); 767 | } 768 | } 769 | 770 | /* Delete the previosu word, maintaining the cursor at the start of the 771 | * current word. */ 772 | void linenoiseEditDeletePrevWord(struct linenoiseState *l) { 773 | size_t old_pos = l->pos; 774 | size_t diff; 775 | 776 | while (l->pos > 0 && l->buf[l->pos-1] == ' ') 777 | l->pos--; 778 | while (l->pos > 0 && l->buf[l->pos-1] != ' ') 779 | l->pos--; 780 | diff = old_pos - l->pos; 781 | memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); 782 | l->len -= diff; 783 | refreshLine(l); 784 | } 785 | 786 | /* This function is the core of the line editing capability of linenoise. 787 | * It expects 'fd' to be already in "raw mode" so that every key pressed 788 | * will be returned ASAP to read(). 789 | * 790 | * The resulting string is put into 'buf' when the user type enter, or 791 | * when ctrl+d is typed. 792 | * 793 | * The function returns the length of the current buffer. */ 794 | static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) 795 | { 796 | struct linenoiseState l; 797 | 798 | /* Populate the linenoise state that we pass to functions implementing 799 | * specific editing functionalities. */ 800 | l.ifd = stdin_fd; 801 | l.ofd = stdout_fd; 802 | l.buf = buf; 803 | l.buflen = buflen; 804 | l.prompt = prompt; 805 | l.plen = strlen(prompt); 806 | l.oldpos = l.pos = 0; 807 | l.len = 0; 808 | l.cols = getColumns(stdin_fd, stdout_fd); 809 | l.maxrows = 0; 810 | l.history_index = 0; 811 | 812 | /* Buffer starts empty. */ 813 | l.buf[0] = '\0'; 814 | l.buflen--; /* Make sure there is always space for the nulterm */ 815 | 816 | /* The latest history entry is always our current buffer, that 817 | * initially is just an empty string. */ 818 | linenoiseHistoryAdd(""); 819 | 820 | if (write(l.ofd,prompt,l.plen) == -1) return -1; 821 | while(1) { 822 | char c; 823 | int nread; 824 | char seq[3]; 825 | 826 | nread = read(l.ifd,&c,1); 827 | if (nread <= 0) return l.len; 828 | 829 | /* Only autocomplete when the callback is set. It returns < 0 when 830 | * there was an error reading from fd. Otherwise it will return the 831 | * character that should be handled next. */ 832 | if (c == 9 && completionCallback != NULL) { 833 | c = completeLine(&l); 834 | /* Return on errors */ 835 | if (c < 0) return l.len; 836 | /* Read next character when 0 */ 837 | if (c == 0) continue; 838 | } 839 | 840 | switch(c) { 841 | case ENTER: /* enter */ 842 | history_len--; 843 | free(history[history_len]); 844 | if (mlmode) linenoiseEditMoveEnd(&l); 845 | if (hintsCallback) { 846 | /* Force a refresh without hints to leave the previous 847 | * line as the user typed it after a newline. */ 848 | linenoiseHintsCallback *hc = hintsCallback; 849 | hintsCallback = NULL; 850 | refreshLine(&l); 851 | hintsCallback = hc; 852 | } 853 | return (int)l.len; 854 | case CTRL_C: /* ctrl-c */ 855 | errno = EAGAIN; 856 | return -1; 857 | case BACKSPACE: /* backspace */ 858 | case 8: /* ctrl-h */ 859 | linenoiseEditBackspace(&l); 860 | break; 861 | case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the 862 | line is empty, act as end-of-file. */ 863 | if (l.len > 0) { 864 | linenoiseEditDelete(&l); 865 | } else { 866 | history_len--; 867 | free(history[history_len]); 868 | return -1; 869 | } 870 | break; 871 | case CTRL_T: /* ctrl-t, swaps current character with previous. */ 872 | if (l.pos > 0 && l.pos < l.len) { 873 | int aux = buf[l.pos-1]; 874 | buf[l.pos-1] = buf[l.pos]; 875 | buf[l.pos] = aux; 876 | if (l.pos != l.len-1) l.pos++; 877 | refreshLine(&l); 878 | } 879 | break; 880 | case CTRL_B: /* ctrl-b */ 881 | linenoiseEditMoveLeft(&l); 882 | break; 883 | case CTRL_F: /* ctrl-f */ 884 | linenoiseEditMoveRight(&l); 885 | break; 886 | case CTRL_P: /* ctrl-p */ 887 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); 888 | break; 889 | case CTRL_N: /* ctrl-n */ 890 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); 891 | break; 892 | case ESC: /* escape sequence */ 893 | /* Read the next two bytes representing the escape sequence. 894 | * Use two calls to handle slow terminals returning the two 895 | * chars at different times. */ 896 | if (read(l.ifd,seq,1) == -1) break; 897 | if (read(l.ifd,seq+1,1) == -1) break; 898 | 899 | /* ESC [ sequences. */ 900 | if (seq[0] == '[') { 901 | if (seq[1] >= '0' && seq[1] <= '9') { 902 | /* Extended escape, read additional byte. */ 903 | if (read(l.ifd,seq+2,1) == -1) break; 904 | if (seq[2] == '~') { 905 | switch(seq[1]) { 906 | case '3': /* Delete key. */ 907 | linenoiseEditDelete(&l); 908 | break; 909 | } 910 | } 911 | } else { 912 | switch(seq[1]) { 913 | case 'A': /* Up */ 914 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); 915 | break; 916 | case 'B': /* Down */ 917 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); 918 | break; 919 | case 'C': /* Right */ 920 | linenoiseEditMoveRight(&l); 921 | break; 922 | case 'D': /* Left */ 923 | linenoiseEditMoveLeft(&l); 924 | break; 925 | case 'H': /* Home */ 926 | linenoiseEditMoveHome(&l); 927 | break; 928 | case 'F': /* End*/ 929 | linenoiseEditMoveEnd(&l); 930 | break; 931 | } 932 | } 933 | } 934 | 935 | /* ESC O sequences. */ 936 | else if (seq[0] == 'O') { 937 | switch(seq[1]) { 938 | case 'H': /* Home */ 939 | linenoiseEditMoveHome(&l); 940 | break; 941 | case 'F': /* End*/ 942 | linenoiseEditMoveEnd(&l); 943 | break; 944 | } 945 | } 946 | break; 947 | default: 948 | if (linenoiseEditInsert(&l,c)) return -1; 949 | break; 950 | case CTRL_U: /* Ctrl+u, delete the whole line. */ 951 | buf[0] = '\0'; 952 | l.pos = l.len = 0; 953 | refreshLine(&l); 954 | break; 955 | case CTRL_K: /* Ctrl+k, delete from current to end of line. */ 956 | buf[l.pos] = '\0'; 957 | l.len = l.pos; 958 | refreshLine(&l); 959 | break; 960 | case CTRL_A: /* Ctrl+a, go to the start of the line */ 961 | linenoiseEditMoveHome(&l); 962 | break; 963 | case CTRL_E: /* ctrl+e, go to the end of the line */ 964 | linenoiseEditMoveEnd(&l); 965 | break; 966 | case CTRL_L: /* ctrl+l, clear screen */ 967 | linenoiseClearScreen(); 968 | refreshLine(&l); 969 | break; 970 | case CTRL_W: /* ctrl+w, delete previous word */ 971 | linenoiseEditDeletePrevWord(&l); 972 | break; 973 | } 974 | } 975 | return l.len; 976 | } 977 | 978 | /* This special mode is used by linenoise in order to print scan codes 979 | * on screen for debugging / development purposes. It is implemented 980 | * by the linenoise_example program using the --keycodes option. */ 981 | void linenoisePrintKeyCodes(void) { 982 | char quit[4]; 983 | 984 | printf("Linenoise key codes debugging mode.\n" 985 | "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); 986 | if (enableRawMode(STDIN_FILENO) == -1) return; 987 | memset(quit,' ',4); 988 | while(1) { 989 | char c; 990 | int nread; 991 | 992 | nread = read(STDIN_FILENO,&c,1); 993 | if (nread <= 0) continue; 994 | memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ 995 | quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ 996 | if (memcmp(quit,"quit",sizeof(quit)) == 0) break; 997 | 998 | printf("'%c' %02x (%d) (type quit to exit)\n", 999 | isprint(c) ? c : '?', (int)c, (int)c); 1000 | printf("\r"); /* Go left edge manually, we are in raw mode. */ 1001 | fflush(stdout); 1002 | } 1003 | disableRawMode(STDIN_FILENO); 1004 | } 1005 | 1006 | /* This function calls the line editing function linenoiseEdit() using 1007 | * the STDIN file descriptor set in raw mode. */ 1008 | static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { 1009 | int count; 1010 | 1011 | if (buflen == 0) { 1012 | errno = EINVAL; 1013 | return -1; 1014 | } 1015 | 1016 | if (enableRawMode(STDIN_FILENO) == -1) return -1; 1017 | count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); 1018 | disableRawMode(STDIN_FILENO); 1019 | printf("\n"); 1020 | return count; 1021 | } 1022 | 1023 | /* This function is called when linenoise() is called with the standard 1024 | * input file descriptor not attached to a TTY. So for example when the 1025 | * program using linenoise is called in pipe or with a file redirected 1026 | * to its standard input. In this case, we want to be able to return the 1027 | * line regardless of its length (by default we are limited to 4k). */ 1028 | static char *linenoiseNoTTY(void) { 1029 | char *line = NULL; 1030 | size_t len = 0, maxlen = 0; 1031 | 1032 | while(1) { 1033 | if (len == maxlen) { 1034 | if (maxlen == 0) maxlen = 16; 1035 | maxlen *= 2; 1036 | char *oldval = line; 1037 | line = realloc(line,maxlen); 1038 | if (line == NULL) { 1039 | if (oldval) free(oldval); 1040 | return NULL; 1041 | } 1042 | } 1043 | int c = fgetc(stdin); 1044 | if (c == EOF || c == '\n') { 1045 | if (c == EOF && len == 0) { 1046 | free(line); 1047 | return NULL; 1048 | } else { 1049 | line[len] = '\0'; 1050 | return line; 1051 | } 1052 | } else { 1053 | line[len] = c; 1054 | len++; 1055 | } 1056 | } 1057 | } 1058 | 1059 | /* The high level function that is the main API of the linenoise library. 1060 | * This function checks if the terminal has basic capabilities, just checking 1061 | * for a blacklist of stupid terminals, and later either calls the line 1062 | * editing function or uses dummy fgets() so that you will be able to type 1063 | * something even in the most desperate of the conditions. */ 1064 | char *linenoise(const char *prompt) { 1065 | char buf[LINENOISE_MAX_LINE]; 1066 | int count; 1067 | 1068 | if (!isatty(STDIN_FILENO)) { 1069 | /* Not a tty: read from file / pipe. In this mode we don't want any 1070 | * limit to the line size, so we call a function to handle that. */ 1071 | return linenoiseNoTTY(); 1072 | } else if (isUnsupportedTerm()) { 1073 | size_t len; 1074 | 1075 | printf("%s",prompt); 1076 | fflush(stdout); 1077 | if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; 1078 | len = strlen(buf); 1079 | while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { 1080 | len--; 1081 | buf[len] = '\0'; 1082 | } 1083 | return strdup(buf); 1084 | } else { 1085 | count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); 1086 | if (count == -1) return NULL; 1087 | return strdup(buf); 1088 | } 1089 | } 1090 | 1091 | /* This is just a wrapper the user may want to call in order to make sure 1092 | * the linenoise returned buffer is freed with the same allocator it was 1093 | * created with. Useful when the main program is using an alternative 1094 | * allocator. */ 1095 | void linenoiseFree(void *ptr) { 1096 | free(ptr); 1097 | } 1098 | 1099 | /* ================================ History ================================= */ 1100 | 1101 | /* Free the history, but does not reset it. Only used when we have to 1102 | * exit() to avoid memory leaks are reported by valgrind & co. */ 1103 | static void freeHistory(void) { 1104 | if (history) { 1105 | int j; 1106 | 1107 | for (j = 0; j < history_len; j++) 1108 | free(history[j]); 1109 | free(history); 1110 | } 1111 | } 1112 | 1113 | /* At exit we'll try to fix the terminal to the initial conditions. */ 1114 | static void linenoiseAtExit(void) { 1115 | disableRawMode(STDIN_FILENO); 1116 | freeHistory(); 1117 | } 1118 | 1119 | /* This is the API call to add a new entry in the linenoise history. 1120 | * It uses a fixed array of char pointers that are shifted (memmoved) 1121 | * when the history max length is reached in order to remove the older 1122 | * entry and make room for the new one, so it is not exactly suitable for huge 1123 | * histories, but will work well for a few hundred of entries. 1124 | * 1125 | * Using a circular buffer is smarter, but a bit more complex to handle. */ 1126 | int linenoiseHistoryAdd(const char *line) { 1127 | char *linecopy; 1128 | 1129 | if (history_max_len == 0) return 0; 1130 | 1131 | /* Initialization on first call. */ 1132 | if (history == NULL) { 1133 | history = malloc(sizeof(char*)*history_max_len); 1134 | if (history == NULL) return 0; 1135 | memset(history,0,(sizeof(char*)*history_max_len)); 1136 | } 1137 | 1138 | /* Don't add duplicated lines. */ 1139 | if (history_len && !strcmp(history[history_len-1], line)) return 0; 1140 | 1141 | /* Add an heap allocated copy of the line in the history. 1142 | * If we reached the max length, remove the older line. */ 1143 | linecopy = strdup(line); 1144 | if (!linecopy) return 0; 1145 | if (history_len == history_max_len) { 1146 | free(history[0]); 1147 | memmove(history,history+1,sizeof(char*)*(history_max_len-1)); 1148 | history_len--; 1149 | } 1150 | history[history_len] = linecopy; 1151 | history_len++; 1152 | return 1; 1153 | } 1154 | 1155 | /* Set the maximum length for the history. This function can be called even 1156 | * if there is already some history, the function will make sure to retain 1157 | * just the latest 'len' elements if the new history length value is smaller 1158 | * than the amount of items already inside the history. */ 1159 | int linenoiseHistorySetMaxLen(int len) { 1160 | char **new; 1161 | 1162 | if (len < 1) return 0; 1163 | if (history) { 1164 | int tocopy = history_len; 1165 | 1166 | new = malloc(sizeof(char*)*len); 1167 | if (new == NULL) return 0; 1168 | 1169 | /* If we can't copy everything, free the elements we'll not use. */ 1170 | if (len < tocopy) { 1171 | int j; 1172 | 1173 | for (j = 0; j < tocopy-len; j++) free(history[j]); 1174 | tocopy = len; 1175 | } 1176 | memset(new,0,sizeof(char*)*len); 1177 | memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); 1178 | free(history); 1179 | history = new; 1180 | } 1181 | history_max_len = len; 1182 | if (history_len > history_max_len) 1183 | history_len = history_max_len; 1184 | return 1; 1185 | } 1186 | 1187 | /* Save the history in the specified file. On success 0 is returned 1188 | * otherwise -1 is returned. */ 1189 | int linenoiseHistorySave(const char *filename) { 1190 | mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); 1191 | FILE *fp; 1192 | int j; 1193 | 1194 | fp = fopen(filename,"w"); 1195 | umask(old_umask); 1196 | if (fp == NULL) return -1; 1197 | chmod(filename,S_IRUSR|S_IWUSR); 1198 | for (j = 0; j < history_len; j++) 1199 | fprintf(fp,"%s\n",history[j]); 1200 | fclose(fp); 1201 | return 0; 1202 | } 1203 | 1204 | /* Load the history from the specified file. If the file does not exist 1205 | * zero is returned and no operation is performed. 1206 | * 1207 | * If the file exists and the operation succeeded 0 is returned, otherwise 1208 | * on error -1 is returned. */ 1209 | int linenoiseHistoryLoad(const char *filename) { 1210 | FILE *fp = fopen(filename,"r"); 1211 | char buf[LINENOISE_MAX_LINE]; 1212 | 1213 | if (fp == NULL) return -1; 1214 | 1215 | while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { 1216 | char *p; 1217 | 1218 | p = strchr(buf,'\r'); 1219 | if (!p) p = strchr(buf,'\n'); 1220 | if (p) *p = '\0'; 1221 | linenoiseHistoryAdd(buf); 1222 | } 1223 | fclose(fp); 1224 | return 0; 1225 | } 1226 | --------------------------------------------------------------------------------