├── README.md ├── UNLICENSE ├── min.c └── min.tests /README.md: -------------------------------------------------------------------------------- 1 | # min: a minimal scripting language 2 | - min is a scripting language, whose specification & syntax aim to be minimalist. 3 | - min is inspired by [C](https://en.wikipedia.org/wiki/C_(programming_language)), [Python](https://python.org), [Miniscript](https://miniscript.org), [Lua](https://lua.org), [Wren](https://wren.io), and [UCS](https://en.wikipedia.org/wiki/Uniform_Function_Call_Syntax). 4 | - min is work-in-progress. everything can change. also, there are no complete compilers/interpreters available yet. 5 | - min is free. specification and sources are mit0/bsd0/cc0/unlicense multi-licensed at your own choice. 6 | 7 | ## existing implementations 8 | - Vanilla, naive, **incomplete WIP C interpreter** can be found in [min.c](min.c) file (CC0/Unlicensed). 9 | - goals: simple interpreter. but also a C transpiler and a Lua transpiler. 10 | - goals: free, single-file, no deps, meaningful subset of the language, aims to <1000 LOC. 11 | - non-goals: being efficent, robust, bulletproof, JIT, AOT and/or maintained. 12 | - Others: xxx 13 | 14 | ## language 15 | ```python 16 | // min language spec (v1.00 wip - 2022.10) 17 | // - public domain. 18 | 19 | /////////////////////////////////////////// operators 20 | & | ~ ^ ! << >> // bitwise 21 | < <= == != => > && || // conditional, logical 22 | + - * ** / % ++ -- @ [] . -> // arithmetical, indexing, member accessing (** for pow, [] or @ for indexing) 23 | = += -= *= **= /= %= &= |= ~= ^= <<= >>= // assignments 24 | 25 | //////////////////////////// types 26 | '!' '\x1' '\u1234' // 32-bit char (ascii, hexadecimal and utf notations) 27 | 16 0x10 // 53-bit integer (decimal and hexadecimal notations) 28 | 16. 16.0 16.f // 64-bit float (different notations) 29 | "16" '16' // string: both pairing quotes allowed 30 | true false // bools: any mismatching condition, zero value or empty string/list/map eval to `false`; `true` otherwise 31 | 32 | //////////////////////////// statements 33 | statement(); // semi-colons at end of line are optional 34 | multiple(); statements() // semi-colons between statements within same line are mandatory 35 | { more(); statements() } // multi-line scopes use {braced scopes} 36 | : more(); statements() // single-line scopes from colon `:` till end of line 37 | 38 | //////////////////////////// conditions 39 | if(2+2 >= 4): put("Four") // parentheses in expressions are optional 40 | elif "a"<"b": put("Sort") // unconstrained `else if` could also be used 41 | else {put("Last resort")} // notice that classic {braced scopes} can also be used 42 | // 43 | sign = PI > 0 ? +1 : -1 // ternary operator `expr ? ... : ...` 44 | 45 | //////////////////////////// loops 46 | s = "Hello" // 47 | while len(s) < 25: s+="!" // `while(expr) ...` 48 | // 49 | do { s += "Hi!" } // `do ... while(expr)` 50 | while len(s) < 50 // loops may use `break`/`continue`/`return` to alter flow 51 | 52 | //////////////////////////// functions (hint: `self` or `self_[anything]` named args are mutables) 53 | triple(n=1): n*3 // optional arg `n`. optional `return` keyword; last stack value implicitly returned 54 | triple() // 3 55 | triple(5) // 15 56 | triple(n=10) // 30. named argument 57 | copy = triple // capture function 58 | copy(10) // also 30 59 | 60 | //////////////////////////// unified call syntax 61 | len("hi"); "hi".len() // these two calls are equivalent... 62 | add(s1,s2); s1.add(s2) // ...so these two as well 63 | s1.add(s2).add(s3) // UCS allows chaining 64 | 65 | //////////////////////////// strings 66 | hi='hello' // strings can use "quotes" or 'quotes' at your discretion 67 | string=f"{hi} world" // "hello world". f-interpolated string 68 | string[1] // 'e'. positive indexing from first element [0] 69 | string[-3] // 'r'. negative indexing from last element [-1] 70 | string < string // comparison (<,>,==,!=) 71 | string + string // "hello worldhello world". concatenation 72 | string - string // "". removes all occurences of right string in left string 73 | .hash() .len() .less() .index() .put() .quote() .assert() 74 | .pop() .back() .del([:]) .find(ch) .rfind(ch) .starts(s) .ends(s) .split(s) .slice(i,j) 75 | 76 | //////////////////////////// lists (either vector or linked-list at vendor discretion) 77 | list = [2, 4, 6, 8] // 78 | list[0] // 2. positive indexing from first element [0] 79 | list[-2]=5 // [2,4,5,8]. negative indexing from last element [-1] 80 | .len() .del([:]) .find(lst) .starts(lst) .ends(lst) .join(sep) .slice(i,j[,k]) .reverse() .shuffle() .index(i) .put() 81 | 82 | ///////////////////////////////// EXTENSIONS BELOW ///////////////////////////////////////////// 83 | 84 | //////////////////////////// EXTENSION: maps (either ordered or unordered at vendor discretion) 85 | map = {"a":1,'b':2,c:3} // keys can be specified with different styles 86 | map.a // 1 87 | map->b // 2 88 | map["c"] // 3 89 | .len() .del([:]) .find(x) .keys(wc) .values(wc) .sort(op) .slice(i,j) .index(k) .put() 90 | 91 | ///////////////////////////////////////////////// EXTENSION: classes 92 | // classes are maps with a special `isa` entry // 93 | // that points to the parent class and is set // 94 | // automatically at instance time. // 95 | ///////////////////////////////////////////////// 96 | Shape = { sides: 0 } // plain data struct. methods below: 97 | Shape(Shape self, n=1): self.sides=n // constructor 98 | ~Shape(Shape self): // destructor 99 | degrees(Shape self): self.sides*180-360 // method 100 | // instance base class ////////////////////////// 101 | Square = Shape(4) // 102 | Square.isa // "Shape" 103 | Square.sides // 4 104 | // instance derived class /////////////////////// 105 | x = Square() // 106 | x.isa // "Square" 107 | x.degrees() // 360 108 | 109 | /////////////////////////////////// EXTENSION: extended if/while conditionals 110 | if a = 10; a < b: put(a) // `init[...]; if cond:` alias 111 | while a = 10; a < b: put(a) // `init[...]; while cond:` alias 112 | 113 | /////////////////////////////////// EXTENSION: for 114 | for a = 10; a < b; ++a: put(a) // `init[...]; while cond: {code;post}` alias 115 | for i in 10..1: put(i + "...") // 116 | 117 | /////////////////////////////////// EXTENSION: casts 118 | int(val) // cast value to int. only if specialization exists 119 | bool(val) // cast value to bool. only if specialization exists 120 | char(val) // cast value to char. only if specialization exists 121 | float(val) // cast value to float. only if specialization exists 122 | string(val) // cast value to string. only if specialization exists 123 | vec2(vec3 a) { vec2(a.x,a.y) } // define a custom vec3->vec2 specialization 124 | vec2(v3) // vec3 to vec2 casting is allowed now 125 | 126 | /////////////////////////////////// EXTENSION: match 127 | match var { // 128 | 1: put('one') // 129 | 1..99: put('number') // 130 | 'a'..'z': put('letter') // 131 | } // 132 | 133 | /////////////////////////////////// EXTENSION: exceptions 134 | throw("my exception message") // `throw(value)`, `thrown` keywords 135 | msg = try(fn(x)) ? "ok" : thrown // `try(expression)` returns `true` if no exceptions were thrown; `false` otherwise 136 | 137 | /////////////////////////////// EXTENSION: set theory 138 | list+list or map+map // union [1,2]+[2,3] -> [1,2,3] 139 | list-list or map-map // difference [1,2]-[2,3] -> [1] 140 | list^list or map^map // intersection [1,2]^[2,3] -> [2] 141 | 142 | /////////////////////////////// EXTENSION: slices [i:j] `i,j` are optional 143 | map["b":"a"] // {b:2,a:1}. slices [i:j] from i up to j (included). defaults to [first_key:last_key] 144 | list[-1:1] // [8,5,4]. slices [i:j] from i up to j (included). defaults to [0:-1] 145 | string[7:9] // "wo". slices [i:j] from i up to j (included). defaults to [0:-1] 146 | string[7:] // "world" 147 | string[-1:0] // "dlrow olleh" 148 | 149 | /////////////////////////////// EXTENSION: ranges [i..j[..k]] 150 | 3..0 // [3,2,1,0]. returns list of integers from 3 up to 0 (included) 151 | 7..0..3 // [7,4,1,0]. returns list of integers from 7 up to 0 (included) 3 steps each 152 | 'A'..'D' // [A,B,C,D]. returns list of chars from A up to D (included) 153 | 154 | //////////////////////////// EXTENSION: in 155 | for i in -1..1: put(i) // returns iterator that walks lists,maps and objects -> -1 0 1 156 | for k,v in vec2: put(k,v) // iterate key/value pairs of given object type -> "isa""vec2" "x"0 "y"0 157 | // `while i in 10..1` is also valid 158 | 159 | ////////////////////////// EXTENSION: lambdas 160 | mul = 5 // 161 | calc = fn(n): n*mul // `fn` keyword denotes a following lambda function 162 | calc(10) // 50 163 | 164 | /////////////////////// EXTENSION: eval 165 | eval("1+2") // 3 166 | put(f"{1+2}==3") // "3==3". any {moustache} in a f-string redirects to an internal `eval` operator call 167 | 168 | ///////////////////// EXTENSION: templates 169 | fn min(T a, T b): // single-capital-letters will expand to different types when used 170 | a < b ? a : b // 171 | 172 | /////////////// EXTENSION: dlsym 173 | sin(3.14159) // foreign calls are implicitly passed to underlying runtime via `dlsym()` call 174 | 175 | ////////////// EXTENSION: typeof 176 | typeof(3.0) // "float" 177 | typeof(v2) // "vec2" 178 | 179 | //////////////////////////////////////////// 180 | // preprocessor and pragmas: @todoc 181 | // ffi and C abi convention: @todoc 182 | // constness, symbol visibility: @todoc 183 | // embedding, calling from/to C: @todoc func = dlsym("int puts(const char *)@my_dll"); put(func) 184 | // std modules: matching math.h, string.h, stdio.h @todoc 185 | // match: see rust 186 | // keywords: true/false if/elif/else for/in/do/while break/continue/return fn isa @todoc 187 | // modules: put("once") ; return fn(x) { ... } --- x = import('file.src', 'init args'...) 188 | ``` 189 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /min.c: -------------------------------------------------------------------------------- 1 | // min.c scripting language: reference implementation (WIP! WIP! WIP!) 2 | // - rlyeh, public domain. 3 | 4 | int VERBOSE = 0; // 0|no debug info, ~0|any debug info, 1|parser, 2|infix, 4|bytecode, 8|stack, 5 | 6 | #line 1 "utils.h" // ----------------------------------------------------------- 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | // macros ---------------------------------------------------------------------- 20 | 21 | #ifdef _MSC_VER 22 | #define __thread __declspec(thread) 23 | #elif defined __TINYC__ 24 | #define __thread 25 | #endif 26 | 27 | #define do_once static int once = 0; for(;!once;once=1) 28 | 29 | // hashes ---------------------------------------------------------------------- 30 | 31 | uint64_t hash_u64(uint64_t x) { 32 | x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); 33 | x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb); 34 | return x ^ (x >> 31); 35 | } 36 | uint64_t hash_flt(double x) { 37 | union { double d; uint64_t i; } c; 38 | return c.d = x, hash_u64(c.i); 39 | } 40 | uint64_t hash_str(const char* str) { // @fixme: maybe limit to 32chars? 41 | uint64_t hash = UINT64_C(14695981039346656037); // hash(0),mul(131) faster than fnv1a, a few more collisions though 42 | while( *str ) hash = ( (unsigned char)*str++ ^ hash ) * UINT64_C(0x100000001b3); 43 | return hash; 44 | } 45 | uint64_t hash_ptr(const void *ptr) { 46 | return hash_u64( (uint64_t)(uintptr_t)ptr ); // @fixme: >>3 needed? 47 | } 48 | 49 | // memory leaks ---------------------------------------------------------------- 50 | 51 | #if defined REPORT_MEMORY_LEAKS && REPORT_MEMORY_LEAKS 52 | #define vrealloc(ptr,len) memleak(vrealloc(memleak(ptr,0,0,0,0),len),len,__func__,__FILE__,__LINE__) 53 | void memreport(void) { system("type 0*.mem 2> nul > report.mem && del 0*.mem && type report.mem | sort && find /V /C \"\" report.mem"); } // cat 0*.mem > report.mem ; rm 0*.mem ; cat report.mem | sort ; wc -l report.mem 54 | void* memleak(void *ptr, int len, const char *func, const char *file, int line) { 55 | do_once system("del *.mem 2> nul"), atexit(memreport); 56 | char n[sizeof(void*)*2+8] = {0}; sprintf(n,"%p.mem",ptr); 57 | for(FILE *fp = line ? fopen(n,"wb") : 0; fp; fprintf(fp, "%8d bytes, %-20s, %s:%d\n", len,func,file,line), fclose(fp), fp = 0) {} 58 | return line ? ptr : (unlink(n), ptr); 59 | } 60 | #endif 61 | 62 | // memory (vector based allocator; x1.75 enlarge factor) ----------------------- 63 | 64 | static __thread size_t vstats = 0; 65 | void* (vrealloc)( void* p, size_t sz ) { 66 | size_t *ret; 67 | if( !sz ) { 68 | if( p ) { 69 | ret = (size_t*)p - 2; vstats -= sizeof(size_t) * 2 + ret[1]; 70 | ret[0] = 0; 71 | ret[1] = 0; 72 | ret = (size_t*)realloc( ret, 0 ); 73 | } 74 | return 0; 75 | } else { 76 | if( !p ) { 77 | ret = (size_t*)realloc( 0, sizeof(size_t) * 2 + sz ); vstats += sizeof(size_t) * 2 + sz; 78 | ret[0] = sz; 79 | ret[1] = 0; 80 | } else { 81 | ret = (size_t*)p - 2; 82 | size_t osz = ret[0], cap = ret[1]; // original size and capacity 83 | if( sz == (size_t)-1 ) return (void*)osz; 84 | if( sz <= (osz + cap) ) { 85 | ret[0] = sz; 86 | ret[1] = cap - (sz - osz); 87 | } else { 88 | ret = (size_t*)realloc( ret, sizeof(size_t) * 2 + sz * 1.75 ); 89 | ret[0] = sz; 90 | ret[1] = (size_t)(sz * 1.75) - sz; vstats += ret[1] - osz; 91 | } 92 | } 93 | return &ret[2]; 94 | } 95 | } 96 | size_t vlen( void* p ) { 97 | return p == (void*)-1 ? vstats : p ? (size_t)(vrealloc)( p, (size_t)-1 ) : 0; // @addme to fwk 98 | } 99 | 100 | #define strdup(s) (strcpy(vrealloc(0,strlen(s)+1),s)) 101 | 102 | // arrays ---------------------------------------------------------------------- 103 | 104 | #define array(t) t* 105 | #define array_resize(t, n) ( array_c_ = array_count(t), array_n_ = (n), array_realloc_((t),array_n_), (array_n_>array_c_? memset(array_c_+(t),0,(array_n_-array_c_)*sizeof(0[t])) : (void*)0), (t) ) 106 | #define array_push(t, ...) ( array_realloc_((t),array_count(t)+1), (t)[ array_count(t) - 1 ] = (__VA_ARGS__) ) 107 | #define array_pop(t) ( array_c_ = array_count(t), array_c_ ? array_realloc_((t), array_c_-1) : array_cast_(t)(NULL) ) // @addme to fwk 108 | #define array_back(t) ( &(t)[ array_count(t)-1 ] ) 109 | #define array_count(t) (int)( (t) ? ( vlen(t) - sizeof(0[t]) ) / sizeof(0[t]) : 0u ) 110 | #define array_free(t) ( array_realloc_((t),-1), (t) = 0 ) // -1 111 | #define array_realloc_(t,n) ( (t) = array_cast_(t) vrealloc((t), ((n)+1) * sizeof(0[t])) ) // +1 112 | #define array_cast_(x) (void *) // cpp: (decltype x) 113 | 114 | static __thread unsigned array_c_, array_n_; 115 | 116 | // strings --------------------------------------------------------------------- 117 | 118 | char* va(const char *fmt, ...) { 119 | va_list vl, copy; 120 | va_start(vl, fmt); 121 | 122 | va_copy(copy, vl); 123 | int sz = /*stbsp_*/vsnprintf( 0, 0, fmt, copy ) + 1; 124 | va_end(copy); 125 | 126 | enum { STACK_ALLOC = 64*1024 }; assert(sz < STACK_ALLOC && "no stack enough, increase STACK_ALLOC value"); 127 | static __thread char *buf = 0; if(!buf) buf = (vrealloc)(0, STACK_ALLOC); // @leak 128 | static __thread int cur = 0, len = STACK_ALLOC - 1; 129 | 130 | char* ptr = buf + (cur *= (cur+sz) < len, (cur += sz) - sz); 131 | /*stbsp_*/vsnprintf( ptr, sz, fmt, vl ); 132 | 133 | va_end(vl); 134 | return ptr; 135 | } 136 | char* append(char **src_, const char *buf) { 137 | if( !buf || !buf[0] ) return *src_; 138 | int srclen = (*src_ ? strlen(*src_) : 0), buflen = strlen(buf); 139 | char *src = (char*)vrealloc(*src_, srclen + buflen + 1 ); 140 | memcpy(src + srclen, buf, buflen + 1 ); 141 | return *src_ = src, src; 142 | } 143 | char *replace(char **copy, const char *target, const char *replacement) { // replaced only if replacement is shorter than target 144 | int rlen = strlen(replacement), diff = strlen(target) - rlen; 145 | if( diff >= 0 ) 146 | for( char *s = *copy, *e = s + strlen(*copy); /*s < e &&*/ 0 != (s = strstr(s, target)); ) { 147 | if( rlen ) s = (char*)memcpy( s, replacement, rlen ) + rlen; 148 | if( diff ) memmove( s, s + diff, (e - (s + diff)) + 1 ); 149 | } 150 | return *copy; 151 | } 152 | char* intern(const char* s) { 153 | static __thread array(char*) depot = 0; 154 | for(int i = 0; i < array_count(depot); ++i) if( !strcmp(s, depot[i]) ) return depot[i]; 155 | return array_push(depot, strdup(s)), *array_back(depot); 156 | } 157 | 158 | #define va(...) ((printf || printf(__VA_ARGS__), va(__VA_ARGS__))) // vs2015 check trick 159 | #define append(s,fmt,...) append((s), va(fmt, __VA_ARGS__)) 160 | 161 | // maps ------------------------------------------------------------------------ 162 | 163 | #define map(v) array(array(v)) 164 | #define map_free(m) do { for(int j=0;j<('z'+1-'A');++j) { for(int i=0;i= 0); } 237 | int stack_count() { return sp_; } 238 | var* stack_sp(int i) { return &stack[sp_ - i]; } 239 | 240 | #line 1 "lib.c" // ------------------------------------------------------------- 241 | 242 | #define expand_argv_(x, eq, num) \ 243 | if( !(argc eq num) && argc == 1 && x->type == TYPE_LIST) argc = array_count(x->l), x = x->l; \ 244 | if( !(argc eq num) ) return Error("Wrong number of arguments") 245 | 246 | var float_(int argc, var *x) { expand_argv_(x, ==, 1); 247 | if(x->type==TYPE_BOOL) return (var){ TYPE_FLOAT, .d = x->d }; 248 | if(x->type==TYPE_CHAR) return (var){ TYPE_FLOAT, .d = x->d }; 249 | if(x->type==TYPE_FLOAT) return (var){ TYPE_FLOAT, .d = x->d }; 250 | if(x->type==TYPE_STRING) return (var){ TYPE_FLOAT, .d = x->s ? atof(x->s) : 0.f }; 251 | return Error("Unsupported type"); // @fixme: TYPE_REF, TYPE_LIST, >=TYPE_FUNCTION 252 | } 253 | var int_(int argc, var *x) { 254 | return ((var){ TYPE_INT, .d = ((uint64_t)float_(argc, x).d) & UINT64_C(0x1fffffffffffff) }); 255 | } 256 | var bool_(int argc, var *x) { expand_argv_(x, ==, 1); 257 | if(x->type==TYPE_BOOL) return *x; 258 | if(x->type==TYPE_CHAR) return (var){ TYPE_BOOL, .d = !!(x->d) }; 259 | if(x->type==TYPE_LIST) return (var){ TYPE_BOOL, .d = !!(!!x->l * array_count(x->l)) }; 260 | if(x->type==TYPE_FLOAT) return (var){ TYPE_BOOL, .d = !!(x->d) }; 261 | if(x->type==TYPE_STRING) return (var){ TYPE_BOOL, .d = !!(!!x->s * !!x->s[0]) }; 262 | if(x->type==TYPE_REF) return (var){ TYPE_BOOL, .d = !!(!!x->s * !!x->s[0]) }; // @fixme: map_find(env,x->s) 263 | if(x->type>=TYPE_FUNCTION) return (var){ TYPE_BOOL, .d = !!(x->call) }; 264 | return Error("Unsupported type"); 265 | } 266 | var string_(int argc, var *x) { expand_argv_(x, ==, 1); 267 | if(x->type==TYPE_VOID) return ((var){ TYPE_STRING, .s = va("")}); 268 | if(x->type==TYPE_NULL) return ((var){ TYPE_STRING, .s = va("%s", "(null)")}); 269 | if(x->type==TYPE_BOOL) return ((var){ TYPE_STRING, .s = va("%s", x->d ? "true" : "false")}); 270 | if(x->type==TYPE_CHAR) return ((var){ TYPE_STRING, .s = x->d < 32 ? va("\\x%02x", (char)x->d) : x->d < 127 ? va("%c", (char)x->d) : va("\\u%04x", (int)x->d)}); 271 | if(x->type==TYPE_FLOAT) return ((var){ TYPE_STRING, .s = va("%g", x->d)}); // %f, or va("%"PRIi64, (int64_t)x->d) for ints 272 | if(x->type==TYPE_STRING) return ((var){ TYPE_STRING, .s = va("%s", x->s ? x->s : "(null)")}); 273 | if(x->type==TYPE_REF) return ((var){ TYPE_STRING, .s = x->s ? string_(1, map_find(env,x->s)).s : va("(null)")}); // look-up 274 | if(x->type>=TYPE_FUNCTION) return ((var){ TYPE_STRING, .s = va("%p", x->call)}); 275 | if(x->type==TYPE_LIST) { 276 | static __thread char *out = 0; if( out ) *out = 0; 277 | for( int i = 0, end = array_count(x->l); i < end; ++ i ) append(&out, ",%s" + !i, string_(1, x->l+i).s); 278 | return ((var){ TYPE_STRING, .s = va("[%s]", out ? out : "") }); 279 | } 280 | return Error("Unsupported type"); 281 | } 282 | var char_(int argc, var *x) { expand_argv_(x, ==, 1); 283 | return ((var){ TYPE_CHAR, .d = string_(1, x).s[0] }); 284 | } 285 | var quote_(int argc, var *x) { expand_argv_(x, ==, 1); 286 | if(x->type==TYPE_CHAR) return ((var){ TYPE_STRING, .s = va("\'%s\'", string_(1, x).s) }); 287 | if(x->type==TYPE_STRING) return ((var){ TYPE_STRING, .s = va("\"%s\"", string_(1, x).s) }); 288 | if(x->type==TYPE_LIST) { 289 | static __thread char *out = 0; if( out ) *out = 0; 290 | for( int i = 0, end = array_count(x->l); i < end; ++ i ) append(&out, ",%s" + !i, quote_(1, x->l+i).s); 291 | return ((var){ TYPE_STRING, .s = va("[%s]", out ? out : "") }); 292 | } 293 | return string_(1, x); 294 | } 295 | var len_(int argc, var* x) { expand_argv_(x, ==, 1); 296 | int sizes[] = SIZES; 297 | if(x->type==TYPE_LIST) return (var){ TYPE_INT, .d = !!x->l * array_count(x->l) }; 298 | if(x->type==TYPE_STRING) return (var){ TYPE_INT, .d = !!x->s * strlen(x->s) }; 299 | return ((var){TYPE_INT, .d = sizes[x->type]}); 300 | } 301 | var typeof_(int argc, var* x) { expand_argv_(x, ==, 1); 302 | const char *typeofs[] = TYPEOFS; 303 | if( x->type != TYPE_REF ) return ((var){TYPE_STRING, .s = va("%s", typeofs[x->type])}); 304 | return (var){TYPE_STRING, .s = va("%s%s%s", typeof_(1, map_find(env,x->s)).s, x->s ? " ":"", typeofs[x->type]) }; 305 | } 306 | var put_(int argc, var *argv) { 307 | for(int i = 0; i < argc; ++i ) puts( string_(1, argv+i).s ); 308 | return True; 309 | } 310 | var equal_(int argc, var *x) { expand_argv_(x, ==, 2); 311 | if( x->type == x[1].type && x->type == TYPE_STRING ) return (var){ TYPE_BOOL, .d = !strcmp(x->s, x[1].s) }; 312 | if( x->type == x[1].type && x->type == TYPE_LIST ) return (var){ TYPE_BOOL, .d = (array_count(x->l) == array_count(x[1].l)) && !memcmp(x->l,x[1].l, array_count(x->l) * sizeof(x->l[0])) }; 313 | return (var){ TYPE_BOOL, .d = x[0].d == x[1].d }; // @fixme: missing types 314 | } 315 | var hash_(int argc, var* x) { expand_argv_(x, ==, 1); // @note: hash truncated to 53-bit, as max lossless uint value in a double is 2^53 316 | if(x->type==TYPE_BOOL) return (var){ TYPE_INT, .d = hash_flt(!!x->d) & UINT64_C(0x1fffffffffffff) }; 317 | if(x->type==TYPE_CHAR) return (var){ TYPE_INT, .d = hash_flt(x->d) & UINT64_C(0x1fffffffffffff) }; 318 | if(x->type==TYPE_INT) return (var){ TYPE_INT, .d = hash_flt(x->d) & UINT64_C(0x1fffffffffffff) }; 319 | if(x->type==TYPE_FLOAT) return (var){ TYPE_INT, .d = hash_flt(x->d) & UINT64_C(0x1fffffffffffff) }; 320 | if(x->type==TYPE_STRING) return (var){ TYPE_INT, .d = (!!x->s * !!x->s[0] * hash_str(x->s)) & UINT64_C(0x1fffffffffffff) }; 321 | if(x->type>=TYPE_FUNCTION) return (var){ TYPE_INT, .d = hash_ptr(x->call) & UINT64_C(0x1fffffffffffff) }; 322 | return Error("Unsupported type"); // @fixme: TYPE_LIST, TYPE_REF 323 | } 324 | var index_(int argc, var* x) { expand_argv_(x, ==, 2); // convert any positive/negative index into a positive form 325 | int pos = (int)x[1].d, len; 326 | if( x->type==TYPE_LIST ) return len = !!x->l * array_count(x->l), !len ? Void : x->l[((pos<0)*(len-1) + (pos+(pos<0)) % len)]; 327 | if( x->type==TYPE_STRING ) return len = !!x->s * strlen(x->s), !len ? Void : (var){ TYPE_CHAR, .d = x->s[((pos<0)*(len-1) + (pos+(pos<0)) % len)] }; 328 | return x->type == TYPE_CHAR ? *x : Error("Unsupported type"); 329 | } 330 | var eval_(int argc, var *argv) { expand_argv_(argv, ==, 1); // @fixme: remove this limit 331 | // save context 332 | int sp2 = sp_; 333 | var stack2[sizeof(stack)/sizeof(0[stack])]; memcpy(stack2, stack, sizeof(stack)); 334 | 335 | const char *min_eval(const char *); 336 | var out = (min_eval(string_(1, argv).s), *stack_sp(0)); 337 | 338 | // restore context 339 | memcpy(stack, stack2, sizeof(stack)); 340 | sp_ = sp2; 341 | 342 | return out; 343 | } 344 | static __thread int tested_, failed_, line_ = 1; static __thread const char *source_; void summary_(void) { printf("%d/%d tests passed\n", tested_-failed_, tested_); } 345 | var test_(int argc, var *argv) { 346 | do_once atexit(summary_); 347 | int errors = failed_; 348 | for(int i = 0; i < argc; ++i, ++tested_) if(!bool_(1, argv+i).d) printf("Test failed: line %d -- %s\n", line_, source_), ++failed_; 349 | return errors != failed_ ? False : True; 350 | } 351 | var assert_(int argc, var *argv) { 352 | for(int i = 0; i < argc; ++i ) if( !bool_(1, argv+i).d ) throw("Assertion failed"); 353 | return True; 354 | } 355 | 356 | // conv 357 | uint64_t strtoull_(const char *s, char **end, int b) { uint64_t x; return sscanf(s,"%llx",&x), x; } // strtoull(s,0,16) replacement (tcc) 358 | var make_number(const char *str) { // parse c_string->number (float,double,hex,integer) 359 | if( str[1] == 'x' ) return (var) { TYPE_INT, .d = strtoull_(str, NULL, 16) }; // is_hex 360 | if( !strpbrk(str, ".fe") ) return (var) { TYPE_INT, .d = atoi(str) }; // is_int @fixme: assumes 'f' at eos or 'e' in between floats 361 | return (var) { TYPE_FLOAT, .d = atof(str) }; // is_float 362 | } 363 | var make_char(const char *str) { // parse c_string->char (a,\x20,\uABCD) 364 | if( str[0] == '\\' && str[1] == 'x' ) return (var){TYPE_CHAR, .d = strtoull_(str+2, NULL, 16) }; 365 | if( str[0] == '\\' && str[1] == 'u' ) return (var){TYPE_CHAR, .d = strtoull_(str+2, NULL, 16) }; 366 | if( str[0] != '\0' && str[1] == '\'') return (var){TYPE_CHAR, .d = str[0] }; 367 | return Error("Unsupported type"); 368 | } 369 | var make_cstring(const char *str, unsigned num_truncated_ending_chars) { // parse c_string->string ('hi',"hi") 370 | var out = (var){TYPE_STRING, .s = va("%s",str) }; 371 | return num_truncated_ending_chars ? out.s[ strlen(out.s) - num_truncated_ending_chars ] = '\0', out : out; 372 | } 373 | var make_fstring(const char *input, unsigned num_truncated_ending_chars) { // parse c_string->f-interpolated string (f'{hi}',f'{2*3}==6') 374 | char *output = ""; 375 | while(*input) { 376 | char *seek = strchr(input, '{'), *eos = seek ? strchr(seek+1, '}') : 0; 377 | if( !(seek && eos && eos > seek+1) ) break; 378 | const char *source = va("%.*s", (int)(eos-seek), seek); 379 | var sym = make_cstring(source+1, 0), *z = (sym = eval_(1, &sym), &sym); 380 | if( z->type == Fail.type ) { output = va("%s%c",output, *input); input++; continue; } 381 | output = va("%s%.*s%s", output, (int)(seek-input), input, string_(1, z).s); 382 | input = eos + 1; 383 | } 384 | return (var){TYPE_STRING, .s = va("%s%.*s", output, (int)(strlen(input)-num_truncated_ending_chars), input) }; // remove ending \" if needed 385 | } 386 | var make_string(const char *str) { 387 | /**/ if( str[0] == 'f' ) return make_fstring(str+2, 1); // f-interpolated string 388 | else if( str[0] == '\"') return make_cstring(str+1, 1); // "string" 389 | var out = make_char(str+1); return out.type != Fail.type ? out : make_cstring(str+1, 1); // char or 'string' 390 | } 391 | 392 | #line 1 "rpn.c" // RPN / Shunting-Yard ----------------------------------------- 393 | 394 | array(char*) rpn(array(char*) tokens) { // [0]1 [+]- [0]2 -> [0]1 [0]2 [+]- 395 | const char *OPERATORS = // excluded from C: (type){list} compounds, (casts), *indirection, &address-of, sizeof, _Alignof, +-unary 396 | "\x01`++`\x01`--`" // ++ -- Suffix/postfix increment and decrement Left-to-right 397 | /**/"\x01`@`\x01`[[`" //"\x01`[`\x01`]`" // [] Array subscripting 398 | "\x01`.`\x01`->`" // . -> Structure and union member accesses 399 | /**/"\x02`+*`\x02`-*`" // ++ -- Prefix increment and decrement Right-to-left 400 | "\x02`!`\x02`~`" // ! ~ Logical NOT and bitwise NOT 401 | "\x02`**`" // ** Pow 402 | "\x03`*`\x03`/`\x03`%`" // * / % Multiplication, division, and remainder Left-to-right 403 | "\x04`+`\x04`-`" // + - Addition and subtraction 404 | "\x05`<<`\x05`>>`" // << >> Bitwise left shift and right shift 405 | "\x06`<`\x06`<=`\x06`>`\x06`>=`" // < <= For relational operators 406 | "\x07`==`\x07`!=`" // == != For relational respectively 407 | "\x08`&`" // & Bitwise AND 408 | "\x09`^`" // ^ Bitwise XOR (exclusive or) 409 | "\x10`|`" // | Bitwise OR (inclusive or) 410 | "\x11`&&`" // && Logical AND 411 | "\x12`||`" // || Logical OR 412 | "\x13`?`\x13`:`" // ?: Ternary conditional[note 3] Right-to-left 413 | "\x14`=`" // = Simple assignment 414 | "\x14`+=`\x14`-=`" // += -= Assignment by sum and difference 415 | "\x14`*=`\x14`/=`\x14`%=`" // *= /= %= Assignment by product, quotient, and remainder 416 | /**/"\x14`<<=`\x14`>>=`\x14`**=`" // <<= >>= Assignment by bitwise left shift and right shift 417 | "\x14`&=`\x14`^=`\x14`|=`" // &= ^= |= Assignment by bitwise AND, XOR, and OR 418 | "\x15`,`"; // , Comma Left-to-right 419 | 420 | static __thread int counter = 0; counter = ++counter % 4; 421 | // stack of indexes to operators 422 | static __thread array(char*) ss[4] = {0}; array(char*)* s = &ss[counter]; array_resize(*s, 0); // @leak 423 | // stack of operators (output) 424 | static __thread array(char*) outs[4] = {0}; array(char*)* out = &outs[counter]; array_resize(*out, 0); // @leak 425 | 426 | for( int i = 0, end = array_count(tokens); i < end; ++i ) { 427 | char *c = tokens[i]; 428 | // flush pending streams at every statement/keyword 429 | // else do rpn: functions, ()[], operators and values 430 | /**/ if( c[-1] == ';' || c[-1] == 'k' ) { 431 | while (array_count(*s)) { 432 | const char *token = *array_back(*s); 433 | array_push(*out, 1+va("%c%s",token[-1],token)); 434 | array_pop(*s); 435 | } 436 | } 437 | else if( c[-1] == 'f' ) { array_push(*s, c); continue; } // functions 438 | else if( c[-1] == '(' || c[-1] == '[' ) array_push(*s, c[-1] == '(' ? 1+"((" : 1+"[["); // opening xoperators 439 | else if( c[-1] == ')' || c[-1] == ']' ) { // closing operators 440 | int open = c[-1] == ')' ? '(' : '['; 441 | while( array_count(*s) && (*array_back(*s))[-1] != open ) { // until '(' on stack, pop operators. 442 | const char *token = *array_back(*s); 443 | array_push(*out, 1+va("%c%s",token[-1],token)); 444 | array_pop(*s); 445 | } 446 | if( array_count(*s) ) array_pop(*s); // else perror(unbalanced parentheses) 447 | } else { // operators 448 | char *operator = strstr(OPERATORS, va("`%s`", c)); 449 | if( !operator ) { array_push(*out, c); continue; } else ++operator; 450 | while( array_count(*s) && ((*array_back(*s))[-1] != '(' && (*array_back(*s))[-1] != '[') ) { 451 | char* back = *array_back(*s); 452 | int prec2 = operator[-2]; 453 | int prec1 = back[-1] == 'f' ? 0x01 : back[-1] == 'p' ? 0x02 : strstr(OPERATORS, va("`%s`", back))[-1]; // back/s are in %c%s format, not %d`%s` 454 | int left1 = prec1 != 0x02 && prec1 != 0x13 && prec1 != 0x14; 455 | if ((100-prec1) > (100-prec2) || (prec2 == prec1 && left1)) { 456 | const char *token = *array_back(*s); 457 | array_push(*out, 1+va("%c%s",token[-1],token)); 458 | array_pop(*s); 459 | } else break; 460 | } 461 | array_push(*s, 1+va("+%.*s", (int)(strchr(operator,'`')-operator), operator) ); 462 | } 463 | } 464 | 465 | // flush pending streams 466 | while( array_count(*s) ) { 467 | const char *token = *array_back(*s); 468 | array_push(*out, 1+va("%c%s",token[-1],token)); 469 | array_pop(*s); 470 | } 471 | 472 | return *out; // complete 473 | } 474 | 475 | #line 1 "parser.c" //----------------------------------------------------------- 476 | 477 | static __thread int parse_string_extended; // if true, function below will decode strings in addition to regular 'abc' and "abc" strings 478 | int parse_string(const char *begin, const char *end) { 479 | if( *begin == '\'' || *begin == '"' || (parse_string_extended ? *begin == '<' : 0) ) { 480 | const char *s = begin, *close = *s != '\'' && *s != '"' && parse_string_extended ? ">" : s; 481 | for( ++s; s <= end && *s != *close; ) s += *s == '\\' && ( s[1] == '\\' || s[1] == *close ) ? 2 : 1; 482 | return s <= end && *s == *close ? (int)(s + 1 - begin) : 0; 483 | } 484 | int ret = *begin == 'f' ? parse_string(begin+1, end) : 0; // f-interpolated strings 485 | return ret ? ret + 1 : 0; 486 | } 487 | 488 | static __thread char found; 489 | int parse_token(const char *begin, const char *end) { 490 | # define eos (s > end) 491 | const char *s = begin; 492 | found = 0; 493 | 494 | if(!found) if((s+=parse_string(s,end))>begin) found = '\"'; // string literals 495 | if(!found) if( !eos && strchr("{}()[],#",*s)) found = *s++; // () [] {} sublocks, comma separator and pp 496 | if(!found) while( !eos && *s == ';' ) found = ';', ++s; // semi-colons 497 | if(!found) while( !eos && *s <= ' ' && *s) found = ' ', ++s; // whitespaces, tabs & linefeeds 498 | if(!found) while( !eos && *s == '\\' && (s[1] == '\r' && s[2] == '\n') ) found = ' ', s+=3; // pp line 499 | if(!found) while( !eos && *s == '\\' && (s[1] == '\r' || s[1] == '\n') ) found = ' ', s+=2; // pp line 500 | if(!found) while( !eos && *s == '/' && s[1] == '*' ) { for(s+=2;!eos && (*s!='*'||s[1]!='/');++s); found='/',s+=2;if(eos)s=end; } // C comments 501 | if(!found) if( !eos && *s == '/' && s[1] == '/' ) { while( !eos && (*s >= 32) ) ++s; found = '/'; } // C++ comments 502 | if(!found) if( !eos && *s == '.' && (s[1] == '_' || isalpha(s[1])) ) found = '.', ++s; // oop method invoke: 4.len() "hi".len() 503 | 504 | if(!found) if( !eos && (*s == '_' || isalpha(*s)) ) { // keywords and identifiers 505 | do ++s; while( !eos && (*s == '_' || isalnum(*s)) ); 506 | found = strstr(",if,elif,else,do,while,for,in,return,break,continue,", va(",%.*s,",(int)(s-begin), begin)) ? 'k' : '_'; 507 | } 508 | 509 | if(!found) if(!eos) { // hex, numbers and ranges. @fixme: write a more canonical float parser 510 | const char *bak = s; s += s[0] == '-'; 511 | 512 | if( s[0] == '.' || isdigit(*s) ) { 513 | found = '0', ++s; 514 | 515 | const char *hex = "ABCDEFabcd.0123456789ef", *fmt = hex + (s[0] == 'x' ? ++s, 0 : 10); // .3f 1e4 516 | while( !eos && *s && strchr(fmt, *s) ) { 517 | if( s[0] == '.' && s[1] == '.' ) s += 2 + (s[2] == '-'), found = 'R'; // not a number; a range instead! 3..-10 518 | if( s[0] == 'e' && (s[1] == '-' || s[1] == '+') ) ++s; // allow 1e-4 519 | ++s; 520 | } 521 | 522 | // throw on '123abc' case. also exclude dot from method invokation: 4.say() 523 | if(*s == '_' || isalpha(*s)) { if( s[-1] != '.' ) throw("Malformed number"); --s; } 524 | } 525 | 526 | // handle -1..10 negative range case. abort if not a (-)(R), as previously found (-) lexem is an operator instead 527 | if( *bak == '-' && found != 'R' ) found = 0, s = bak; 528 | } 529 | 530 | if(!found) if( !eos && strchr("<=>!&|~^+-/*%.?:\\@", *s) ) { // single/multi digit operators 531 | /**/ if( s[1] == *s && s[2] == '=' && strchr("*<>", *s)) s+=3; // **= <<= >>= 532 | else if( s[1] == *s && strchr("&|+-*<>", *s) ) s+=2; // && || ++ -- ** << >> 533 | else if( s[1] == '=' && strchr("<=>!&|~^+-/*%", *s) ) s+=2; // <= == >= != &= |= ~= ^= += -= /= *= %= 534 | else if( s[0] == '-' && s[1] == '>' ) s+=2; // -> 535 | else ++s; // @fixme: patch ': ... \r\n' as '{ ... }' here? beware of ?: operator and sli:ces 536 | found = '+'; 537 | } 538 | 539 | return found ? (int)(s - begin) : 0; 540 | # undef eos 541 | } 542 | 543 | #line 1 "grammar.c" // --------------------------------------------------------- 544 | // @todo: preprocessor(): remove c/cpp comments, merge \r\n -> \n and \\+\n -> \space\space, trim extra \f\v\t\spaces 545 | // @todo: preprocessor(): if/n/def elif define defined, replace defines, __FILE__, __LINE__, line 1 file, __COUNTER__, STRINGIZE 546 | 547 | static __thread const char *src, *ends; 548 | 549 | int see(const char *word) { 550 | int wordlen = strlen(word); 551 | const char *next = src + strspn(src, " \t\r\n"); // @fixme: missing parse_comment() 552 | return !strncmp(next, word, wordlen) && (isalnum(word[wordlen-1]) ? !isalnum(next[wordlen]) : 1); 553 | } 554 | int match(const char *word) { 555 | if( !see(word) ) throw("unexpected `%s` token while matching `%s`", src+strspn(src," \t\r\n"), word); 556 | return src += strspn(src, " \t\r\n") + strlen(word), 1; 557 | } 558 | int try_match(const char *word) { 559 | return see(word) ? match(word) : 0; 560 | } 561 | 562 | static __thread array(char*) infix; 563 | 564 | void expression() { // amalgamation of operands and operators that can be reduced into a single value 565 | if(!*src) throw("unexpected end-of-string found while parsing expression"); 566 | 567 | for( int read = 1; *src; src += read ) { 568 | read = parse_token(src, ends); 569 | 570 | if( !found ) throw("unexpected token %s", src); 571 | if( found == ' ' || found == '/' ) continue; 572 | 573 | if( VERBOSE & 1 ) printf(";; L%d parse: %c %.*s\n", line_, found, read, src); 574 | 575 | // patch: either: [l,i,s,t] -> [X] as [[(X) or var[index] -> [X] as @(X) (depends on previous token) 576 | if( found == '[' ) { 577 | int is_index = array_count(infix) && strchr("_\"])", 0[*array_back(infix)-1]); 578 | array_push(infix, is_index ? 1+"@@" : 1+"+[[" ); 579 | } 580 | if( try_match("[") ) { read = 0; array_push(infix, 1+"[["); expression(); array_push(infix, 1+"]]"); match("]"); continue; } 581 | if( try_match("(") ) { read = 0; array_push(infix, 1+"(("); expression(); array_push(infix, 1+"))"); match(")"); continue; } 582 | 583 | if( strchr("_+R0\",.", found) ) { array_push(infix, 1+va("%c%.*s", found, read, src)); continue; } 584 | if( strchr(")]};{", *src) ) { /*src+=read;*/ return; } // may be ok 585 | 586 | throw("unexpected token %s", read <= 1 ? va("%c", *src) : va("%c `%.*s`", found, read, src)); 587 | } 588 | } 589 | 590 | void emit(const char *keyword) { 591 | array_push(infix, 1+va("k%s", keyword)); 592 | // @todo 593 | // L label 594 | // jeol jump to end of LOOP scope; begin in do/while loop 595 | // jbol jump to begin of LOOP scope; end in do/while loop 596 | // jeoi jump to end of IF scope, bypass [elif(cond) {st}]... [else(cond){st}] 597 | // jeof jump to end of FUNC scope; ebp=ebp[0],pc=ebp[1] 598 | // jf+2 jump if stack is false: +2 opcode 599 | // jf-2 jump if stack is false: -2 opcodes 600 | } 601 | 602 | void statement() { // mixture of keywords and expressions that cannot be assigned into a value 603 | // skip ws + comments 604 | for( int read = parse_token(src, ends); found == ' ' || found == '/'; src += read, read = parse_token(src,ends)) {} 605 | 606 | /**/ if(!*src) return; // 607 | /**/ if( strchr("}])",*src)) throw("unexpected %c token found", *src); // 608 | /**/ if(!strchr(";{:bcrfwdi",*src)) expression(); // 609 | else if( try_match(";") ) {} // ; 610 | else if( try_match("{") ) { emit("Lb"); while( *src && !try_match("}") ) statement(); emit("Le"); } // { ... } 611 | else if( try_match(":") ) { emit("Lb"); while( *src && !strchr("\r\n",*src)) statement(); emit("Le"); } // : ... \r\n\0 612 | else if( try_match("break") ) emit("jeol"); // break 613 | else if( try_match("continue") ) emit("jbol"); // continue 614 | else if( try_match("return") ) expression(), emit("jeof"); // return 615 | else if( try_match("for") ) expression(), match("in"), expression(), statement(); // for in 616 | else if( try_match("while") ) expression(), emit("jf+1"), statement(); // while (...) 617 | else if( try_match("do") ) statement(), match("while"), expression(), emit("jf-2"); // do while (...) 618 | else if( try_match("if") ) { expression(), emit("jf+2"), statement(), emit("jeoi"); // if (...) [elif(...) ]... [else ] 619 | while( try_match("elif") ) expression(), emit("jf+2"), statement(), emit("jeoi"); 620 | if( try_match("else") ) statement(); } 621 | else expression(); // 622 | 623 | array_push(infix, 1+";;"); 624 | } 625 | 626 | void declaration() { // any statement that references an unknown identifier 627 | // a; // variables 628 | // add1(a) { b = a+1 } // functions 629 | // global variables are in the text segment and they are directly/immutably addressed 630 | // arguments and local vars (a,b) are both in stack and thus, they are indirectly/variably addressed 631 | // bp is the base pointer to the stack frame that holds a+b stack. bp[0] is return address; then bp[0]=>a, bp[1]=>b 632 | // /--------stack frame---------\ 633 | // [ stack... ][ old BP ][ old PC ][ a ][ b ] 634 | statement(); 635 | } 636 | 637 | void program() { // list of declarations + unique entry point 638 | while(*src) declaration(); 639 | } 640 | 641 | array(char*) compile() { 642 | for( int j = (VERBOSE & 2) && printf(";; L%d infix: ", line_); j; j = puts("") ) 643 | for( int i = 0; i < array_count(infix); ++i ) printf(" %s" + !i, infix[i] ); 644 | 645 | // apply last minute infix patches 646 | static __thread array(char*) patched = 0; array_resize(patched, 0); // @leak 647 | for( int i = 0, end = array_count(infix); i < end; ++i) { 648 | char *tk = infix[i], *type = tk - 1; // current token+type 649 | char *prev = array_count(patched) ? *array_back(patched) : 0, *prev_type = prev ? prev-1 : 0; // previous token+type 650 | 651 | // patch pointer access: A->B as A.B 652 | /**/ if( !strcmp(type, "+->") ) tk[0]='.', tk[1]='\0'; 653 | // patch functions 654 | else if( *type == '(' && prev && *prev_type == '_' ) *prev_type = 'f'; 655 | // patch unary into binary: -X as 0-X ; cases: -100 a=-100 a+=-100 @fixme: "hi"@-3 @fixed: `-` 656 | else if( (i",*tk) && tk[1] == '='); // include = += -= etc. exclude == <= >= 666 | int post_op = (*tk == tk[1] && (*tk == '+' || *tk == '-')); // v++ v-- 667 | if( three_op || assign_op || post_op ) *prev_type = '&'; 668 | } 669 | 670 | if( tk ) array_push(patched, tk); 671 | } 672 | return rpn(patched); 673 | } 674 | 675 | #line 1 "eval.c" // ---------------------------------------------------------- 676 | 677 | var eval_rpn( array(char*) tokens ) { 678 | for( int j = (VERBOSE & 4) && printf(";; L%d bytec: ", line_); j; j = puts("") ) 679 | for( int i = 0; i < array_count(tokens); ++i ) printf(" %s%s" + !i, tokens[i][-1] == '&' ? "&" : "", tokens[i] ); 680 | 681 | for( int tk = 0, end = array_count(tokens); tk < end; ++tk ) { 682 | char type = tokens[tk][-1], *val = tokens[tk]; 683 | 684 | int count = stack_count(); 685 | var *argv1 = &stack[count-1], *y = argv1; // x 686 | var *argv2 = &stack[count-2], *x = argv2; // x,y 687 | 688 | /**/ if( type == '0' ) stack_push(make_number(val)); // number 689 | else if( type == '\"') stack_push(make_string(val)); // string 690 | else if( type == '&' ) stack_push(((var){TYPE_REF,.s=val})); // reference 691 | else if( type == '_' ) { var *out = map_find(env,val); if(!out) throw("Variable %s not found", val); stack_push(*out); } // identifier 692 | else if( type == 'R' ) { // range i..j[..k] 693 | int i,j,k = 0; 694 | sscanf(val, "%d..%d..%d", &i, &j, &k); 695 | array(var) list = 0; 696 | if( i < j ) { for( k = abs(k ? k : 1); i <= j; i += k) array_push(list, ((var){ TYPE_INT, .d = i })); } 697 | else { for( k = abs(k ? k : -1); i >= j; i -= k) array_push(list, ((var){ TYPE_INT, .d = i })); } 698 | if( array_back(list)->d != j ) array_push(list, ((var){ TYPE_INT, .d = j })); 699 | stack_push( ((var){ TYPE_LIST, .l = list }) ); // @leak 700 | } 701 | else if( *val == '.' ) {} // @fixme: type '+'->'.' 702 | else if( *val == ',' ) { // @fixme: type '+'->',' 703 | if( argv2[0].type == TYPE_LIST ) { 704 | array_push(argv2[0].l, argv2[1]); 705 | stack_pop(1); 706 | } else { 707 | array(var) list = 0; 708 | array_push(list, argv2[0]); 709 | array_push(list, argv2[1]); 710 | stack_pop(2); 711 | stack_push((var){TYPE_LIST, .l=list}); // @leak 712 | } 713 | } 714 | else if( *val == '@' ) { // indexing // @fixme: type '+'->'@' 715 | argv2[0] = index_(2, argv2); 716 | stack_pop(1); 717 | } 718 | else if( *val == '[' ) { // list // @fixme: type '+'->'[' 719 | int skip = 0; 720 | array(var) list = 0; 721 | if( count ) { 722 | if( argv1[0].type != TYPE_LIST ) { 723 | array_push(list, argv1[0]); 724 | stack_pop(1); 725 | } else skip = 1; 726 | } 727 | if(!skip) stack_push( ((var){ TYPE_LIST, .l = list }) ); // @leak 728 | } 729 | else if( type == 'k' ) {} // keyword 730 | else if( type == 'f' ) { // function 731 | var *fn = map_find(env,val); 732 | if(!fn) throw("Function declarations not yet supported"); 733 | 734 | int argc = 1; 735 | var *argv = stack_sp(argc), out; 736 | 737 | // UCS: "hi".index(0) -> "hi" 0 index . @todo: verify argc >= 3 cases 738 | if( tk > 0 && tk < (end-1) ? tokens[tk+1][0] == '.' && (tokens[tk-1][0] != ',' && strcmp(tokens[tk-1], "[[")) : 0 ) { 739 | argc = stack_count(), argv = stack_sp(argc); // @fixme: 100;"hi".index(0) figure out a better way to detect argc 740 | } 741 | 742 | /**/ if( fn->type == TYPE_FUNCTION ) out = fn->call( argc, argv ); 743 | else if( fn->type == TYPE_FFI_CALL_DD && argc == 1 ) out = ((var){ TYPE_FLOAT, .d = fn->ffi_call_dd(float_(1,argv).d) }); 744 | else if( fn->type == TYPE_FFI_CALL_IS && argc == 1 ) out = ((var){ TYPE_INT, .d = fn->ffi_call_is(string_(1,argv).s) }); 745 | else throw("Unsupported function type"); // @todo: expand FFI cases 746 | 747 | stack_pop(1); 748 | stack_push(out); 749 | } 750 | else if( type == '+' ) { // operators 751 | /**/ if( *val == ':' /*&& tokens[i+1][0] == '?'*/ ) { var *w = &stack[count-3]; *w = w->d ? *x : *y; stack_pop(2); ++tk; /*skip next '?' token*/ } 752 | 753 | // specializations 754 | else if( x->type == TYPE_STRING && y->type == TYPE_STRING ) { if(0); 755 | else if( *val == '+' ) { append(&x->s, "%s", y->s); stack_pop(1); } // @todo: string+number -> ref+idx 756 | else if( *val == '-' ) { replace(&x->s, y->s, ""); stack_pop(1); } 757 | else if( val[1] != '=' ) { if(0); 758 | else if( *val == '<' ) { *x = ((var){ TYPE_BOOL, .d=strcmp(x->s, y->s) < 0 }); stack_pop(1); } 759 | else if( *val == '>' ) { *x = ((var){ TYPE_BOOL, .d=strcmp(x->s, y->s) > 0 }); stack_pop(1); } 760 | else throw("Unknown operator"); 761 | } else { if(0); // @todo: add += -= 762 | else if( *val == '<' ) { *x = ((var){ TYPE_BOOL, .d=strcmp(x->s, y->s) <= 0 }); stack_pop(1); } 763 | else if( *val == '!' ) { *x = equal_(2, x), x->d = !x->d; stack_pop(1); } 764 | else if( *val == '=' ) { *x = equal_(2, x); stack_pop(1); } 765 | else if( *val == '>' ) { *x = ((var){ TYPE_BOOL, .d=strcmp(x->s, y->s) >= 0 }); stack_pop(1); } 766 | else throw("Unknown operator"); 767 | } 768 | } 769 | 770 | else if( x->type == TYPE_LIST && y->type == TYPE_LIST ) { if(0); // @todo: add + - += -= == != 771 | else if( val[1] == '=' ) { if(0); 772 | else if( *val == '!' ) { *x = equal_(2, x); x->d = !x->d; stack_pop(1); } 773 | else if( *val == '=' ) { *x = equal_(2, x); stack_pop(1); } 774 | else throw("Unknown operator"); 775 | } else { if(0); 776 | else throw("Unknown operator"); 777 | } 778 | } 779 | 780 | // unary 781 | else if( *val == '+' && val[1] == '*' ) { var *z = map_find(env,y->s); ++z->d; *y = *z; } // ++v 782 | else if( *val == '-' && val[1] == '*' ) { var *z = map_find(env,y->s); --z->d; *y = *z; } // --v 783 | else if( *val == '+' && val[1] == '+' ) { var *z = map_find(env,y->s), w = *z; ++z->d; *y = w; } // v++ 784 | else if( *val == '-' && val[1] == '-' ) { var *z = map_find(env,y->s), w = *z; --z->d; *y = w; } // v-- 785 | else if( *val == '!' && val[1] == '\0' ) { *y = ((var){ TYPE_INT, .d = !y->d }); } 786 | else if( *val == '~' && val[1] == '\0' ) { *y = ((var){ TYPE_INT, .d = ~(int)y->d }); } 787 | 788 | // binary 789 | #define IFx(op, ...) else if( *val == 0[#op] ) { *x = __VA_ARGS__; stack_pop(1); } 790 | #define IFv(op, ...) IFx(op, ((var){ __VA_ARGS__ })) 791 | #define IFd(op) IFv(op, TYPE_FLOAT, .d = x->d op y->d) 792 | #define IFdREF(op) IFv(op, TYPE_FLOAT, .d = map_find(env,x->s)->d op##= y->d) 793 | #define IFi(op) IFv(op, TYPE_INT, .d = (int)x->d op (int)y->d) 794 | #define IFiREF(op) IFv(op, TYPE_INT, .d = map_find(env,x->s)->d = (int)map_find(env,x->s)->d op (int)y->d) 795 | 796 | else if( val[1] == '=' ) { if(0); // != == <= >= %= &= |= ^= += -= *= /= 797 | IFx(!, equal_(2,x), x->d=!x->d ) IFx(=, equal_(2,x) ) //IFv(!, TYPE_BOOL, .d = x->d != y->d ) IFv(=, TYPE_BOOL, .d = x->d == y->d ) 798 | IFv(<, TYPE_BOOL, .d = x->d <= y->d ) IFv(>, TYPE_BOOL, .d = x->d >= y->d ) 799 | IFiREF(%) IFiREF(&) IFiREF(|) IFiREF(^) IFdREF(+) IFdREF(-) IFdREF(*) IFdREF(/) 800 | else throw("Unknown operator"); 801 | } 802 | 803 | else if( val[0] == val[1] && val[2] == '=' ) { if(0); // <<= >>= **= 804 | IFiREF(<<) IFiREF(>>) else if(*val == '*') { *x = ((var){TYPE_FLOAT, .d = map_find(env,x->s)->d = pow(map_find(env,x->s)->d,y->d)}); stack_pop(1); } 805 | else throw("Unknown operator"); 806 | } 807 | else if( val[0] == val[1] ) { if(0); // && || << >> ** 808 | IFi(&&) IFi(||) IFi(<<) IFi(>>) IFv(*, TYPE_FLOAT,.d=pow(x->d,y->d)) 809 | else throw("Unknown operator"); 810 | } 811 | 812 | IFx(=, *map_insert(env, x->s, *y) ) 813 | IFv(<, TYPE_BOOL, .d = x->d < y->d) 814 | IFv(>, TYPE_BOOL, .d = x->d > y->d) 815 | IFd(+) IFd(-) IFd(*) IFd(/) IFi(%) IFi(|) IFi(&) IFi(^) 816 | else throw("Unknown operator"); 817 | } 818 | else throw("Cannot eval token `%s` type(%c)\n", val, type); 819 | 820 | for( int j = (VERBOSE & 8) /*&& (tk == end-1)*/ && printf(";; L%d stack: ", line_); j; j = puts("") ) 821 | for( int k = 0; k < stack_count(); ++k ) 822 | printf(",%s" + !k, stack[k].type == TYPE_REF ? va("&%s", stack[k].s) : quote_(1,stack+k).s ); 823 | } 824 | 825 | return stack_count() ? stack_pop(1), *stack_sp(-1) : Error("Empty stack"); 826 | } 827 | 828 | #line 1 "api.c" // ------------------------------------------------------------- 829 | 830 | const char* min_eval(const char *line) { 831 | static __thread char *output = 0; if(output) output[0] = 0; // @leak 832 | 833 | try { 834 | // parse program 835 | src = source_ = line; ends = line + strlen(line); 836 | stack_reset(); 837 | array_resize(infix, 0); 838 | program(); 839 | 840 | // compile & run program 841 | array(char*) code = compile(); 842 | if( array_count(code) ) { // ; else throw("empty expression"); 843 | var *x = (eval_rpn(code), stack_sp(0)); 844 | append(&output, "!Error: %s %s %s" + 8 * (x->type != Fail.type), x->type == Fail.type ? x->s : "", typeof_(1, x).s, quote_(1, x).s); 845 | } 846 | } 847 | else append(&output, "!Error: %s on line %d %s", thrown, line_, source_); 848 | 849 | return output ? output : ""; 850 | } 851 | 852 | const char* min_file(const char *file) { 853 | const char *out = "!Cannot read file"; line_ = 0; 854 | for( FILE *fp = fopen(file, "rb"); fp; fclose(fp), fp = 0 ) 855 | for( size_t len = (fseek(fp, 0L, SEEK_END), ftell(fp)); len; len = 0 ) 856 | for( char *p = vrealloc(0, len + 1); p && (p[len] = 0, p); p = vrealloc(p, 0) ) 857 | for( int read = fread( (char*)p, !fseek(fp, 0L, SEEK_SET), len, fp ) == len; read; read = 0 ) 858 | for( char *s = p, *s2; s && *s && NULL != (s2 = s+strcspn(s, "\r\n")); ) 859 | *s2 = 0, ++line_, out = min_eval(s), s=s2+1, s+=*s=='\r', s+=*s=='\n'; 860 | return out; 861 | } 862 | 863 | void min_declare_int(const char *k, int value) { map_insert(env, k, ((var){ TYPE_INT, .d = value }) ); } 864 | void min_declare_double(const char *k, double value) { map_insert(env, k, ((var){ TYPE_FLOAT, .d = value }) ); } 865 | void min_declare_string(const char *k, const char *value) { map_insert(env, k, ((var){ TYPE_STRING, .s = strdup(value) }) ); } // @leak 866 | void min_declare_function(const char *k, var (func)(int, var*) ) { map_insert(env, k, ((var){ TYPE_FUNCTION, .call = func })); } 867 | void min_declare_function_extern_is(const char *k, int (func)(const char *) ) { map_insert(env, k, ((var){ TYPE_FFI_CALL_IS, .ffi_call_is = (void*)func })); } 868 | void min_declare_function_extern_dd(const char *k, double (func)(double) ) { map_insert(env, k, ((var){ TYPE_FFI_CALL_DD, .ffi_call_dd = func })); } 869 | 870 | void min_quit(void) { map_free(env); } 871 | void min_init() { // @todo: add config flags { realloc, dlsym, verbosity } 872 | int is_asserting = 0; assert( ++is_asserting ); 873 | stack_reset(); 874 | map_insert(env, "true", True); map_insert(env, "false", False); 875 | min_declare_string("_BUILD", __DATE__ " " __TIME__); 876 | min_declare_string("_VERSION", va("min.c 2022.10-var%d%s", (int)sizeof(var), is_asserting ? "-DEBUG" : "")); 877 | min_declare_string("_EXTENSION", ",eval,typeof,"); 878 | # define min_builtin(fn) min_declare_function(#fn, fn##_) 879 | min_builtin(len); min_builtin(index); min_builtin(hash); min_builtin(put); min_builtin(assert); min_builtin(test); 880 | min_builtin(int); min_builtin(bool); min_builtin(char); min_builtin(float); min_builtin(string); min_builtin(quote); 881 | min_builtin(eval); min_builtin(typeof); min_builtin(equal); 882 | } 883 | 884 | #line 1 "bench.c" // ----------------------------------------------------------- 885 | 886 | int fib(int n) { 887 | return n < 2 ? n : fib(n-1) + fib(n-2); 888 | } 889 | 890 | void benchmarks() { 891 | clock_t t0; 892 | 893 | # define benchmark(s,...) (puts(s), t0 = -clock(), __VA_ARGS__, t0 += clock(), t0*1. / CLOCKS_PER_SEC) 894 | double native = benchmark("Running native fib(40)...", fib(40)); 895 | double vm = benchmark("Running min.vm fib(40)...", min_eval("fib(n) { n < 2 ? n : fib(n-1) + fib(n-2) } fib(40)")); // @fixme 896 | 897 | const char *vendors[] = { // using latest versions as of Sep 2022 898 | va("%f c(native)", native), va("%f min.vm(broken)", vm), 899 | "0.59 cl-19.31","1.21 tcc-0.9.27","1.34 twok","1.94 luajit-2.1","2.39 c4x86", 900 | "23.96 lua-5.4","30.35 quickjs-2021","32.7 wren","42.69 xc","51.54 python-3.10", 901 | "63.4 c4","71.3 ape","NAN little","82.40 duktape-2.7.0","872.28 mjs-2021","172800 chaiscript-2022" // computer crashed after running chaiscript for 48 hours... not going to test again. 902 | }; 903 | 904 | char vendor[64]; 905 | double baseline = native, speed; 906 | for( int i = 0; i < sizeof(vendors)/sizeof(vendors[0]); ++i ) { 907 | if( sscanf(vendors[i], "%lg %[^\n]", &speed, vendor) != 2 ) continue; 908 | char *time = va("%10.2fs", speed != speed ? 0. : speed ); 909 | char *info = va("%12.2f%% %s", 100*fabs((speed / baseline)-1), speed != speed ? "broken" : baselinespeed ? "faster" : "(baseline)"); 910 | printf("%s %s %16s relative speed is %s\n", speed != speed ? "!" : i <= 1 ? "*" : " ", time, vendor, info); 911 | } 912 | } 913 | 914 | #line 1 "repl.c" // ------------------------------------------------------------ 915 | 916 | int main(int argc, char **argv) { 917 | min_init(); atexit(min_quit); 918 | min_declare_double("PI", 3.1415926); 919 | min_declare_function_extern_dd("sin", sin); 920 | min_declare_function_extern_dd("cos", cos); 921 | min_declare_function_extern_is("puts", puts); 922 | 923 | time_t before = clock(); 924 | // interactive repl 925 | if( argc <= 1 ) for( printf("type ^Z to exit\n>"); !fflush(stdout) && !feof(stdin); printf(">") ) { 926 | char line[256] = {0}; 927 | if( feof(stdin) || fgets(line, sizeof(line), stdin) < 0 ) break; 928 | line[ strcspn(line, "\r\n") ] = '\0'; if( !line[0] ) continue; 929 | if(line[0] == '!') { if( !strcmp(line, "!V") ) VERBOSE = !VERBOSE * 0xFF; else system(line+1); continue; } 930 | const char *rc = (before = clock(), min_eval(line)); 931 | printf("%s (%.3fs, %5.2fKiB)\n", rc, (clock() - before)*1. / CLOCKS_PER_SEC, vlen((void*)-1) / 1024.0); 932 | } 933 | // benchmarks, eval string and help flags 934 | else if( !strcmp(argv[1], "-b") ) benchmarks(), exit(0); 935 | else if( !strcmp(argv[1], "-e") ) puts(min_eval(argc > 2 ? argv[2] : "")), exit(0); 936 | else if( !strcmp(argv[1], "-h") ) printf("%s\n%s \t\t\tconsole\n%s -b\t\t\tbenchmarks\n%s -e \"string...\"\teval string\n%s file1 [file2...]\teval file(s)\n",map_find(env,"_VERSION")->s,argv[0],argv[0],argv[0],argv[0]), exit(0); 937 | // external files 938 | else for( int i = 1; i < argc; ++i ) puts(min_file(argv[i])); 939 | printf("(%.3fs, %5.2fKiB)\n", (clock() - before)*1. / CLOCKS_PER_SEC, vlen((void*)-1) / 1024.0); 940 | } 941 | -------------------------------------------------------------------------------- /min.tests: -------------------------------------------------------------------------------- 1 | // comments 2 | /*c*/1//c++ 3 | //c++ /*c*/ 4 | 5 | // syntax errors 6 | ( 7 | ) 8 | [ 9 | ] 10 | { 11 | } 12 | () 13 | [] 14 | {} 15 | ([) 16 | ({[) 17 | {([}) 18 | 19 | // functions 20 | test(1) 21 | test/*c*/(/*c*/1/*c*/) 22 | 23 | // preprocessor multi-line 24 | test( \ 25 | true ) 26 | 27 | // values 28 | test('@' == 64 ) 29 | test('\x20' == ' ') 30 | test('\u21' == '!') 31 | test(101 == 101) 32 | test(0x65 == 101) 33 | test(101.0 == 101) 34 | test(101.f == 101) 35 | test(101. == 101) 36 | test(1e2 == 100) 37 | test(.5 == 0.5) 38 | 39 | // strings 40 | test('hello'=="hello") 41 | test(".101 " == '.101 ') 42 | 43 | // assignments 44 | a=100 45 | test(a==100) 46 | hi="hello" 47 | test(hi=="hello") 48 | pi=3.14159 49 | test(pi<3.2) 50 | 51 | // statements (comma, semi-colon, {braces}, :\n) 52 | a=99, b=199; test(a==99); test(b=199) 53 | a=100; b=200; c=300; test(a==100); test(b=200); test(c==300); 54 | { a=101; b=201; c=301 } test(a==101); test(b=201); test(c==301); 55 | { a=102; {b=202; { c=302 }}} test(a==102); test(b=202); test(c==302); 56 | {{{ a=103; } b=203; } c=303 } test(a==103); test(b=203); test(c==303); 57 | :a=104;b=204;c=304; test(a==104); test(b=204); test(c==304); 58 | // {1}2 59 | // 1{2}3 1{2;3} 60 | // 1{2} vs {1{2}} vs {1{;2}} vs {1;{2}} 61 | // 1 2 vs 1;2 vs {1;2} vs 1;2; vs ;1 2; vs ;1 ;2 vs 1; 2; vs ;1;;2; vs ;1 2 62 | // vs {1;2;} vs {1 2;} 63 | // {1 2} {;1 2} // 1 \n 2 64 | // {1{2;3}{4{5}} 65 | 66 | // f-interpolated strings 67 | a=100, b=101; 68 | test( f"{a} is 100" == '100 is 100' ) 69 | test( f"{a} is {a}" == '100 is 100' ) 70 | test( f"{a} is not {x}{b}" == '100 is not {x}101' ) 71 | name1="John", name2="Doe", test( "Hello JohnDoe" == f"Hello {name1}{name2}" ) 72 | test(f'{PI} is 3.14159' == '3.14159 is 3.14159') 73 | test(f'PI*2 is {PI*2}' == 'PI*2 is 6.28319') 74 | 75 | // builtins 76 | test( len(true) == 1) 77 | test( len('\x20') == 4) 78 | test( len(3) == 8) 79 | test( len(3.0) == 8) 80 | test( len("hello world") == 11) 81 | test( hash("") == 0) 82 | test( hash("hello world") == 0x1a65e7023cd2e7) 83 | test( hash("hello wor1d") == 0xf67e7034325ba) 84 | test( string(true) == "true") 85 | test( bool("") == 0) 86 | test( bool("x") == 1) 87 | test( bool([]) == 0) 88 | test( bool([1]) == 1) 89 | test( len("hi") == 2) 90 | test( index("hi", -3) == 'i') 91 | test( bool("hi") == true) 92 | test( char("hi") == 'h') 93 | test( int("10") == 10) 94 | test( float(".3") == .3) 95 | test( "3".float() == 3) 96 | test( '3'.float() == 51) // char, not string 97 | test( string(100) == "100") 98 | test( quote("hi") == '"hi"') 99 | test( assert("hi") == true) 100 | test( hash("hi") != 0) 101 | test( test("hi") == true) 102 | test( string(10) == "10" ) 103 | test( string((9+1)**4) == "10000" ) 104 | test( string(PI) == f"{PI}" ) 105 | 106 | // UCS 107 | 1.test() 108 | 2->test() 109 | test(index("hi",2) == 'h') 110 | test(index(["hi",2]) == 'h') 111 | test(["hi",2].index() == 'h') 112 | test("hi".index(2) == 'h') 113 | 100;test("hi".index(2) == 'h') 114 | test(len("hello") == 5) 115 | test("hello".len() == 5) 116 | test("hello"->len() == 5) 117 | test(index("hello", 0) == 'h') 118 | test("hello".index(0) == 'h') 119 | test("hello"->index(0) == 'h') 120 | test(len([1,2]) == 2) 121 | test([1,2].len() == 2) 122 | test([1,2]->len() == 2) 123 | test( string(10).len() == 2 ) 124 | test( "hi"[3].len() == 4 ) 125 | test( "hi".index(-3) == 'i') 126 | 127 | // indexing 128 | test(hi[0] == 'h') 129 | test(hi[1] == 'e') 130 | test(hi[2] == 'l') 131 | test(hi[3] == 'l') 132 | test(hi[4] == 'o') 133 | test(hi[5] == 'h') 134 | 135 | test(hi[-6] == 'o') 136 | test(hi[-5] == 'h') 137 | test(hi[-4] == 'e') 138 | test(hi[-3] == 'l') 139 | test(hi[-2] == 'l') 140 | test(hi[-1] == 'o') 141 | 142 | test( "hello"[1] == 'e' ) 143 | test( ("hello")[1] == 'e' ) 144 | test( (("hello"))[1] == 'e' ) 145 | test( "hello"@3 == 'l' ) 146 | test( ("hello")@3 == 'l' ) 147 | test( (("hello"))@3 == 'l' ) 148 | //test( "hello"@-1 == 'o' ) // @fixme 149 | 150 | // lists 151 | a = [1,2,3] 152 | test( [1,2,3] == [1,2,3] ) 153 | test( a.len() == 3 ) 154 | test( [1,2,3].len() == 3 ) 155 | test( [1,2]==[1,2] ) 156 | test( string([]) == "[]" ) 157 | test( string(['a']) == "[a]" ) 158 | test( string([true]) == "[true]" ) 159 | test( string(["hello".len()]) == "[5]" ) 160 | test( string([1]) == "[1]" ) 161 | test( string([1,2]) == "[1,2]" ) 162 | test( string([1,2,3]) == "[1,2,3]" ) 163 | test( len([1,2,3]) == 3 ) 164 | test( [-100].string() == "[-100]" ) 165 | test( [-100,-100].string() == "[-100,-100]" ) 166 | test( [10,20].string() == "[10,20]" ) 167 | test( (10,20).string() == "[10,20]" ) 168 | test( [1,2].string() == string([1,2]) ) 169 | test( (a=1, b=2).string() == [1,2].string() ); test(a==1); test(b==2); test(a==1 && b==2) 170 | test( [10,20,30][1] == ([10,20,30])[1] ); test( [10,20,30][1] == 20 ) 171 | 172 | // tuples 173 | test( (1+1+1,5).string() == "[3,5]" ) 174 | a=[3,4,5]; test( a[1] == 4 ); test( a@1 == 4 ); test( (a)[1] == 4); test( (a)@1 == 4) 175 | a=(3,4,5); test( a[1] == 4 ); test( a@1 == 4 ); test( (a)[1] == 4); test( (a)@1 == 4) 176 | 177 | // list indexing 178 | test(a[0] == 3) 179 | test(a[1] == 4) 180 | test(a[2] == 5) 181 | test(a[3] == 3) 182 | test(a[-1] == 5) 183 | test(a[-2] == 4) 184 | test(a[-3] == 3) 185 | test(a[-4] == 5) 186 | 187 | // ranges 188 | test(1..10 == [1,2,3,4,5,6,7,8,9,10]) 189 | test(1..10..3 == [1,4,7,10]) 190 | test(-1..-10 == [-1,-2,-3,-4,-5,-6,-7,-8,-9,-10]) 191 | test( 1..3.string() == "[1,2,3]") 192 | test( -1..3.string() == "[-1,0,1,2,3]") 193 | test( 1..-3.string() == "[1,0,-1,-2,-3]") 194 | test( -1..-3.string() == "[-1,-2,-3]") 195 | test( 1..7..4.string() == "[1,5,7]") 196 | test( 7..1.string() == "[7,6,5,4,3,2,1]") 197 | test( 7..1..4.string() == "[7,3,1]") 198 | //test( 'A'..'C'.string() == "[A,B,C]") // @todo 199 | test( (0..10).string() == "[0,1,2,3,4,5,6,7,8,9,10]" ) 200 | test( [0..10].string() == "[0,1,2,3,4,5,6,7,8,9,10]" ) 201 | test( [0..10..7].string() == "[0,7,10]" ) 202 | test( [0..10,11,12].string() == "[0,1,2,3,4,5,6,7,8,9,10,11,12]" ) 203 | 204 | // conditions 205 | a=100 206 | test( 1 < 2 ) 207 | test( 2 > 1 ) 208 | test( 1 == 1 ) 209 | test( 1 != 0 ) 210 | test( a <= 100 ) 211 | test( a == 100 ) 212 | test( a >= 100 ) 213 | test( a < 101 ) 214 | test( a > 99 ) 215 | test( b = 100 ) 216 | test( a == b ) 217 | test( a != b+1 ) 218 | test( c = 101 ) 219 | test( a == c-1 ) 220 | test( a != c ) 221 | test( !0 ) 222 | test( !!1 ) 223 | test( 1 < 2 ) 224 | test( 2 > 1 ) 225 | test( 1 <= 2 ) 226 | test( 2 >= 1 ) 227 | test( 2 <= 2 ) 228 | test( 2 >= 2 ) 229 | test( 2 == 2 ) 230 | test( 1 != 2 ) 231 | 232 | // operators 233 | test( 1+2 == 3 ) 234 | test( 3-1 == 2 ) 235 | test( 2*3 == 6 ) 236 | test( 2**3 == 8 ) 237 | test( 1+2**3 == 9 ) 238 | test( 1*2+3*4 == 14 ) 239 | test( 1-1-1 == -1 ) 240 | test( 1-3+4 == 2 ) 241 | test( 8/2 == 4 ) 242 | test( 9%2 == 1 ) 243 | test( 100|2 == 102 ) 244 | test( 0xFF&0x1F == 0x1F ) 245 | test( 0xF0|0x1F == 0xFF ) 246 | test( !102 == 0 ) 247 | test( !0 == 1 ) 248 | test( 1^1 == 0 ) 249 | test( 0^1 == 1 ) 250 | test( ~0 == -1 ) 251 | test( ~100 == -101 ) 252 | test( (a=100) == 100 ) 253 | test( a == 100 ) 254 | test( (a+=2) == 102 ) 255 | test( (a-=2) == 100 ) 256 | test( (a*=2) == 200 ) 257 | test( (a/=2) == 100 ) 258 | test( (a%=5) == 0 ) 259 | test( (1<2?100:200) == 100 ) 260 | test( (1>2?100:200) == 200 ) 261 | test( (10+(20)+30) == 60 ) 262 | test( (1+2==3) == true ) 263 | test( (3==1+2) == true ) 264 | test( (1+1) == 2 ) 265 | test( (-10-2) == -12 ) 266 | test( (-10+2) == -8 ) 267 | test( (-(10-2)) == -8 ) 268 | test( (-(10+2)) == -12 ) 269 | test( (1*2+3*4) == 14 ) 270 | test( (1+2**3) == 9 ) 271 | test( (2**3+1) == 9 ) 272 | 273 | // unary operators 274 | test( +100 == 100 ) 275 | test( -100 == 0-100 ) 276 | test( !100 == 0 ) 277 | test( !0 == 1 ) 278 | test( !1 == 0 ) 279 | test( ~100 == -101 ) 280 | 281 | // arithmetical operators 282 | test( 1+1 == 2) 283 | test( 1+1+1 == 3) 284 | test( -1-1 == -2) 285 | test( -1-1-1 == -3) 286 | test( 2+3*4 == 14) 287 | test( 2+3*4+5 == 19) 288 | test( 2**3+1 == 9) 289 | test( 1+2**3 == 9) 290 | test( 100/4 == 25) 291 | test( 101%4 == 1) 292 | 293 | // bitwise operators 294 | test( 100<<1==200 ) 295 | test( 200>>1==100 ) 296 | test( 0xFF&0x1F==31 ) 297 | test( 0xF0|0x1F==255 ) 298 | test( 1^1==0 ) 299 | test( 0^0==0 ) 300 | test( 0^1==1 ) 301 | 302 | // assign operators: += -= *= /= %= &= |= ^= <<= >>= **= 303 | a=100; test(a == 100) 304 | a+=10; test(a == 110) 305 | a+=-10; test(a == 100) 306 | a-=-10; test(a == 110) 307 | a-=+10; test(a == 100) 308 | a=10-3; test(a == 7) 309 | a=1+1+1; test(a == 3) 310 | 311 | // relational operators: < > == != >= <= && || 312 | 313 | // string operators: + - < <= == != >= > @todo lists, maps 314 | test( "hi"+"ho" == "hiho" ) 315 | test( "hiii"-"i"+"o" == "ho" ) 316 | test( "hi" == "hi" ) 317 | test( "hi" != "ho" ) 318 | 319 | // pre/post inc/decrements 320 | a=100 321 | test( --a == 99 ); test( a == 99 ) 322 | test( --a == 98 ); test( a == 98 ) 323 | test( ++a == 99 ); test( a == 99 ) 324 | test( ++a == 100); test( a == 100) 325 | test( a-- == 100); test( a == 99) 326 | test( a++ == 99); test( a == 100) 327 | 328 | // ternary operator: ?: 329 | test((1+3<5?7:9)==7) 330 | test((1+3>5?7:9)==9) 331 | 332 | // infix assignments 333 | sin(a=1+2**3); test(a==9) 334 | cos(b=PI*2); test(b==PI+PI) 335 | 336 | // misc 337 | r="hello"; test( r[0].len() == 'c'.len() ) 338 | r="hello"; test( (1+(2&len(3,4+5*6))+7,8,r[1+r[1*2]*3*(4+5)]).string() == "[10,8,l]" ) 339 | 340 | // variadic calls 341 | test(len(1) == 8) 342 | test(len(1,'2') == 2) 343 | test(len(1,'2',3.0) == 3) 344 | 345 | // ffi calls: imported C functions 346 | test(puts) 347 | test((sin(PI)*sin(PI)+cos(PI)*cos(PI)) == 1) 348 | 349 | // extensions 350 | test( eval("2*3")==6 ) 351 | test( typeof('3') == "CHAR") // @fixme CHR 352 | test( typeof("3") == "STR") 353 | test( typeof(3) == "NUM") // @fixme INT 354 | test( typeof(3.0) == "NUM") // @fixme FLT 355 | 356 | // branches @todo 357 | a=0; test(a==0); if(1<2){a=100}; test(a==100) 358 | a=0; test(a==0); if(1<2){a=100} else {a=200} test(a==100) 359 | a=0; test(a==0); if(1<2){a=100} elif(3<4) {a=200} test(a==100) 360 | a=0; test(a==0); if(1<2){a=100} elif(3<4) {a=200} else {a=300} test(a==100) 361 | a=0; test(a==0); if(1<2){a=100} elif(3>4) {a=200} test(a==100) 362 | a=0; test(a==0); if(1<2){a=100} elif(3>4) {a=200} else {a=300} test(a==100) 363 | 364 | a=0; test(a==0); if(1>2){a=100}; test(a==0) 365 | a=0; test(a==0); if(1>2){a=100} else {a=200} test(a==200) 366 | a=0; test(a==0); if(1>2){a=100} elif(3<4) {a=200} test(a==200) 367 | a=0; test(a==0); if(1>2){a=100} elif(3<4) {a=200} else {a=300} test(a==200) 368 | a=0; test(a==0); if(1>2){a=100} elif(3>4) {a=200} test(a==0) 369 | a=0; test(a==0); if(1>2){a=100} elif(3>4) {a=200} else {a=300} test(a==300) 370 | 371 | a=0; test(a==0); if(1>2){a=100} elif(3>4) {a=200} elif(5>6) {a=300} test(a==0) 372 | a=0; test(a==0); if(1>2){a=100} elif(3>4) {a=200} elif(5>6) {a=300} else {a=400} test(a==400) 373 | 374 | // {if(1<2)100+50;else 200{if(2<3)300;else 400{put(('j'));put((('k')));for(0;1;2) '3';}}} 375 | // if(1+(1))2+3*put(1); elif true: 200/**/+ 300 else {400+500;600{700;if(1)2+3}};4 376 | // if 1+(1)<0: 2+3 else 3+4*5 377 | 378 | // do/while loops @todo 379 | a = 3; test(a==3); while(a--) a; test(a==-1); 380 | a = 3; test(a==3); while(a<10) ++a; test(a==10); 381 | a = 3; test(a==3); do { a++; } while(a!=10); test(a==10); 382 | a = 3; test(a==3); do { --a; } while(a); test(a==0); 383 | 384 | // for loops @todo 385 | for i in 10..1: put(i) 386 | for i in 10..1..3: put(i) 387 | 388 | // functions 389 | copy = len; test(copy == len); test(copy("hello from copy") == len("hello from copy")) 390 | 391 | // functions @todo 392 | simple(n=0) { 393 | nested(n) { 394 | put(n) 395 | } 396 | nested(n) 397 | } 398 | simple(1) 399 | simple(n=2) 400 | simple(123) 401 | simple("hello") 402 | 403 | fib(n) { 404 | n < 2 ? n : fib(n-1) + fib(n-2) 405 | } 406 | 407 | fib(n): n < 2 ? n : fib(n-1) + fib(n-2) 408 | 409 | //put(fib(10)) 410 | --------------------------------------------------------------------------------