nntrac

No-nonsense TRAC T-64 reimplementation under 1000 SLOC of ANSI C
git clone git://git.luxferre.top/nntrac.git
Log | Files | Refs | README | LICENSE

commit 255ba2c015683c728344def46ea2e3919afe11f8
Author: Luxferre <lux@ferre>
Date:   Wed,  9 Aug 2023 21:41:34 +0300

initial upload

Diffstat:
ACOPYING | 24++++++++++++++++++++++++
AREADME.md | 192+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anntrac.c | 1177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1393 insertions(+), 0 deletions(-)

diff --git a/COPYING b/COPYING @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to <https://unlicense.org> diff --git a/README.md b/README.md @@ -0,0 +1,192 @@ +# nntrac: no-nonsense TRAC implementation in ANSI C + +The nntrac language (all-lowercase) is a portable, lightweight (under 1000 SLOC of ANSI C) and embeddable derivative of the TRAC T-64 language originally designed by Calvin N. Mooers in the 1960's. Compared to the original, nntrac is created from scratch with modern systems in mind and adds several useful feature to interact with current operating environments. + +## Building + +Just invoke your C compiler like this (replacing `cc` with the specific command and updating the flags if necessary): + +``` +cc [-static] -std=c99 -Os -s nntrac.c -o nntrac [-DNNT_EMBED] [-DNNT_NO_SHELLEXT] [-DNNT_SHARP="#"] [-DNNT_SYMNAMELEN=32] +``` + +where `cc` is the C compiler of your choice. Compilation was tested on GCC, Clang, Zig cc, TCC (dynamic linking only), Cproc and chibicc. + +Supported compiler flags (all optional): + +- `-DNNT_EMBED`: build nntrac without the main function (entry point). See "Embedding nntrac into your projects" section for more information on the embedded usage. +- `-DNNT_NO_SHELLEXT`: disable the `os` primitive. See "New primitives" section for more information. +- `-DNNT_SHARP`: change the `#` character (start of active or neutral function call) to something else. +- `-DNNT_SYMNAMELEN`: maximum length of TRAC form or primitive function **names** (default 32). + +## Usage + +### Main differences from the T-64 standard + +- the `ps` (print string) primitive can accept multiple arguments (concatenating them on the output); +- default call results can be neutral and don't differ from explicit call results in any way; +- up to 255 segment ordinals are supported (from 1 to 255); +- extended operation mode (`#(mo,E)`) is the default one; +- arithmetic primitives operate on 63-bit signed integers (if the target architecture allows, otherwise it's 31-bit signed integers) and accept numbers in any base supported by C's `strtoll` function in base autodetection mode (i.e. decimal, octal and hexadecimal) but they don't support prepending any string prefixes to the output and always return the result in base-10; +- in arithmetic primitives, overflow argument is fully optional (in this case, a null value is returned if an overflow occurs); +- all bitwise primitive results are returned in base-10 as well and truncated to unsigned 32-bit values, and `br` bit rotations are also done on 32 bits width; +- form storage primitives (`fb`, `sb`, `eb`) directly accept a filename instead of a form name with the address (like in Nat Kuhn's Trac-in-Python); +- the trace mode (`tn`, `tf`) is fully non-interactive and just prints out every function run into stderr, also it doesn't trace the `tn`, `tf` and `hl` primitives call; +- other diagnostic functions (`ln`, `pf`) output to stdout; +- 9 new primitives have been added (see below). + +### Original T-64 primitives implementation status + +Name|Args|Implemented?|UTF-8 safe?|Meaning +----|----|------------|-----------|------- +`rs`|1 |yes |yes |Read string +`rc`|1 |yes |no |Read char +`cm`|2 |yes |no |Change meta +`ps`|var |yes |yes |Print string +`ds`|3 |yes |yes |Define string +`dd`|var |yes |yes |Delete definition +`da`|1 |yes |yes |Delete all +`ss`|var |yes |yes |Segment string +`cl`|var |yes |yes |Call string +`cs`|3 |yes |yes |Call segment +`cc`|3 |yes |no |Call character +`cn`|4 |yes |no |Call N characters +`cr`|2 |yes |yes |Call [pointer] restore +`in`|4 |yes |yes |Initial +`eq`|5 |yes |yes |String equality +`gr`|5 |yes |yes |Greater than +`ad`|3, 4|yes |yes |Add +`su`|3, 4|yes |yes |Subtract +`ml`|3, 4|yes |yes |Multiply +`dv`|3, 4|yes |yes |Divide +`bu`|3 |yes |yes |Bitwise union (OR) +`bi`|3 |yes |yes |Bitwise intersection (AND) +`bc`|2 |yes |yes |Bitwise complement (NOT) +`br`|3 |yes |yes |Bitwise rotation +`bs`|3 |yes |yes |Bitwise shift +`sb`|var |yes |yes |Store block (file): `#(sb,fname,f1,f2...)` +`fb`|2 |yes |yes |Fetch block (file): `#(fb,fname)` +`eb`|2 |yes |yes |Erase block (file): `#(eb,fname)` +`ln`|2 |yes |yes |List names +`pf`|2 |yes |yes |Print form +`tn`|1 |yes |yes |Trace on +`tf`|1 |yes |yes |Trace off +`hl`|1 |yes |yes |Halt +`mo`|2, 3|yes |yes |Mode (see below) + +### New primitives + +- `bx`: bitwise XOR. 3 arguments: `#(bx,A,B)`. Returns the operation result. Invocation is the same as for the `bi` or `bu` primitives. +- `ac`: ASCII code. 2 arguments: `#(ac,S)`. Returns the numeric unsigned value of the first character in `S`. +- `av`: ASCII value. 2 arguments: `#(av,N)`. Returns the single byte corresponding to the ASCII code `N` (unsigned). +- `fn`: format number. 3 arguments: `#(fn,fmt,N)`. Returns the sprintf-formatted string representation of the number `N` according to the format `fmt`. +- `sf`: store (raw) file. 3 arguments: `#(sf,fname,form)`. Stores the raw value from `form` into a named file. The value is always written in its entirety (the form pointer is ignored) and segment gap bytes, if there are any, are written "as is". Returns a null value. The form doesn't get deleted from the internal form storage. The file is fully overwritten if it already exists. +- `ff`: fetch (raw) file. 3 arguments: `#(ff,fname,form)`. Reads a raw string from the named file into `form`. Returns null value (check the target form afterwards). +- `tm`: local/UTC/Epoch time. 2 or 3 arguments: `#(tl,fmt[,U])`. Returns the local (or UTC, if the third parameter `U` is specified) time formatted according to the strftime-compatible `fmt` string or `E` format. If the format string is just `E` (Epoch), returns the amount of seconds since 00:00:00 UTC, January 1, 1970. +- `rn`: random number. 3 arguments: `#(rn,n1,n2)`. Returns a (pseudo-)random integer number in the range `n1` (included) to `n2` (not included). Implemented using the xorshift64 algorithm. +- `os`: run an OS command. 2 arguments: `#(os,cmd)`. Runs a command in the external OS shell (the one determined by the `system()` C call) and returns the command exit code. The output is not captured. + +For self-contained nntrac environments that have no external shell (or the shell is nntrac itself), you can disable the `os` primitive by building nntrac with the `-DNNT_NO_EXTSHELL` flag. + +### Modes + +The nntrac processor can run in one of the three modes that can be switched with the `mo` primitive: + +- `E` (extended): all primitives are available, including the custom ones — this mode is the default one (unlike the original spec); +- `L` (legacy): only the original 34 primitives from T-64 standard are available, no (built-in) extensions are permitted; +- `S` (secure): all primitives are available except those that can interact with the filesystem and outside operating environment (`sb`, `fb`, `eb`, `sf`, `ff`, `os`). + +To lock the mode set with `#(mo,L)` or `#(mo,S)` until the end of the program, use the third `L` parameter (`#(mo,S,L)` or `#(mo,L,L)`). This way, no code inside will be able to extend nntrac's privileges back to unsafe level. + +### Accessing command-line parameters from nntrac scripts + +On normal script invocation (`nntrac script.trac [param1 param2 ...]`), nntrac automatically creates two forms: `nnt-argc` and `nnt-argv`. The `nnt-argc` form is a number containing the amount of command-line parameters (the script file name + everything after it, akin to Python). The `nnt-argv` form contains the parameters themselves and **already is segmented** so that you can use the `cs` primitive for easier parameter access. + +## Embedding nntrac into your projects + +Besides being lightweight, nntrac is also fully embeddable. You can use it as the smallest scripting engine that can be tailored for the specific needs of your own software. + +### Invoking nntrac from other C code + +Place `nntrac.c` and `nntrac-embed.h` files inside your project. In your C source code, append `#include "nntrac-embed.h"` to the top. Then, the following prototypes are available to you: + +- `void nnt_init()`: allocate the forms and primitives storage, register the basic primitives and prepare the engine for work. You must start every session with the `nnt_init();` call before being able to call other functions in this list. +- `void nnt_regprimitive(const char *name, void *handler)`: register your own primitive function to the nntrac interpreter. See below for details. +- `void nnt_proc(char *prog, unsigned int len)`: run a script contained in the string `prog` of length `len`. Any `#(hl)` call will exit this function. +- `void nnt_finish()`: free all forms and primitive function resources. Must be called when you no longer need the nntrac engine. + +Then, you must build your project along with the `nntrac.c` file with the `-DNNT_EMBED` compiler flag. This flag disables the `main()` function in the nntrac source code itself. + +### Extending nntrac with your own primitives + +For your project scripting needs, you might have to introduce your own primitive functions to nntrac. First, define your functions according to the following prototype: `char* handler(char *arglist, char *res, int *reslen);`, and your handler must do two things to be a valid primitive: return the `res` pointer back and, if necessary, update the `*reslen` field, which is 0 by default, with the actual result string length. Also, **to resize the `res` buffer, you must only use `realloc`!** Doing otherwise will eventually lead to segfaults or memory leaks. + +E.g. the function `pr_custom` might look like this: +``` +char* pr_custom(char *arglist, char *res, int *reslen) { + /* ...some actions that update the result string... */ + *reslen = strlen(res); + return res; +} +``` +Then, in your main code, somewhere between the calls to `nnt_init` and `nnt_proc`, you register the pointer to your primitive function with the name of your choice using the `nnt_regprimitive` call: +``` +nnt_init(); +/* ... */ +nnt_regprimitive("my-custom", &pr_custom); +``` +And the `#(my-custom)` call becomes available in your nntrac script code. + +Now, how do we process the function arguments in our custom primitive definition? The `arglist` parameter is a string of function arguments (starting with the registered primitive name itself) delimited with a special `NNT_ADEL` character. For usage with `strtok` C function, it's more convenient to use a predefined null-terminated string with the same delimiter, called `NNT_ADEL_S`. Both `NNT_ADEL` and `NNT_ADEL_S` definitions are available in the `nntrac-embed.h` header, as well as the inclusion of `stdlib.h` and `string.h` for your convenience. + +Here's an example of how we would implement some RGB light API for nntrac, returning the status: +``` +char *pr_rgbled(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *r, *g, *b; + r = strtok(NULL, NNT_ADEL_S); /* get the first parameter */ + g = strtok(NULL, NNT_ADEL_S); /* get the second parameter */ + b = strtok(NULL, NNT_ADEL_S); /* get the third parameter */ + if(r != NULL && g != NULL && b != NULL) { /* all read successfully */ + int val_r = atoi(r), val_g = atoi(g), val_b = atoi(b); /* convert to int */ + rgbled_set_lights(val_r, val_g, val_b); /* call your internal API */ + rgbled_get_lights(&val_r, &val_g, &val_b); /* read back the status */ + char *fmt = "R=%d, G=%d, B=%d\n"; /* set the formatting string */ + /* estimate the size and initialize the resulting buffer */ + res = realloc(res, (*reslen) = 1 + snprintf(NULL, 0, fmt, val_r, val_g, val_b)); + memset(res, 0, *reslen); /* zero it out */ + snprintf(res, *reslen, fmt, val_r, val_g, val_b); /* render */ + res = realloc(res, (*reslen) = strlen(res)); /* resize to the actual written size */ + } + return res; /* return the result pointer */ +} +``` +Then you can register this primitive with `nnt_regprimitive("rgb", &pr_rgbled);` in your main C code, and then, calling `#(rgb,43,67,133)` in your script will return the string `R=43, G=67, B=133` if the API succeeds. + +## FAQ + +### Why reimplement TRAC and not another scripting language? + +Because this is probably the only functional scripting language that can be fully, and even with some useful extensions, be implemented in under 1000 SLOC of ANSI C in a truly portable manner. Besides, C implementations of other embeddable scripting languages are easy to find and pick up, but for TRAC, at the time of nntrac creation, there existed nothing like that except a GPL-ed T-84 version that's hard to build with any modern C compiler. + +### Why was T-64 standard chosen as the basis, not T-84 or T2001? + +While having more "batteries included", T-84/T2001 had diverged from the original elegant design by switching to name suffixes to decide what to do with the function return value. This is much less flexible and less convenient for large-scale programs. T-64, on the other hand, can be easily extended (when really necessary) to do all the same things as T-84 allowed out of the box without sacrificing its core simplicity and flexibility. + +### Is nntrac UTF-8-safe? + +Mostly. All the internal meta characters are chosen so that they never occur in any valid UTF-8 sequence. All primitives, however, operate on individual bytes, so the primitives that allow you to input/output/manipulate a single byte or some numbered bytes are not UTF-8-safe. These include `rc`, `cm`, `cc`, `cn`, `ac` and `av` primitives. + +### Why do `rc` and `rs` primitives require pressing Return (Enter) even after the metacharacter (`'`) was entered? + +They don't require it, your OS does. If you absolutely need per-character input then you need to set your terminal into the unbuffered input mode. For Unix-like systems, it can be done using a wrapper shell script with `stty` command. + +### Why doesn't the `os` primitive capture the shell command output? + +Because there is no truly portable way to do this. For capturing the output, it's recommended to redirect the command into a file (usually with `>` or `>>` shell operator) and then read the file contents with the `ff` primitive. + +## Credits + +Implemented by Luxferre in 2023, released into public domain with no warranty. + +Based on the original specification according to ["Definition and Standard for TRAC T-64 Language"](http://web.archive.org/web/20040531054816/http://tracfoundation.org/trac64/docs/T64definition.pdf) by Calvin N. Mooers (1972). + diff --git a/nntrac.c b/nntrac.c @@ -0,0 +1,1176 @@ +/* + * no-nonsense TRAC programming language implementation (T64 standard) + * + * Build with: cc [-static] -std=c99 -Os -s nntrac.c -o nntrac + * + * see README.md for details + * + * Created by Luxferre in 2023, released into public domain + */ + +#define POSIX_SOURCE +#define POSIX_C_SOURCE 1 +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +#define uint unsigned int +#define uchar unsigned char +#ifndef NNT_SHARP +#define NNT_SHARP '#' /* can be redefined to : or other unused character */ +#endif +#ifndef NNT_SYMNAMELEN +#define NNT_SYMNAMELEN 32 /* max symbol name length, including null */ +#endif + +#ifndef strrev +char *strrev(char *str) { + char *p1, *p2; + if(!str || !*str) return str; + for(p1=str,p2=str+strlen(str)-1;p2>p1;++p1,--p2) { + *p1 ^= *p2; *p2 ^= *p1; *p1 ^= *p2; + } + return str; +} +#endif + +enum NNT_MARKERS { /* various markers: 248 to 255 never occur in UTF-8 */ + NNT_AFST=-8, /* active function start */ + NNT_NFST, /* neutral function start */ + NNT_EOF, /* end of function */ + NNT_ADEL, /* argument delimiter */ + NNT_SEGGAP /* segment gap character */ +}; +enum NNT_MODES {NNT_NORMAL=1, NNT_LEGACY, NNT_SECURE}; /* operation modes */ +static const char NNT_ADEL_S[2] = {NNT_ADEL, 0}; +static uchar nnt_meta = '\''; /* apostrophe by default */ + +/* combined active + neutral string buffer and evaluation result buffer */ +static char *nnt_prog, *nnt_res, nnt_mode = NNT_NORMAL, nnt_mlock = 0; +/* scanning pointers and buffer lengths */ +static int nnt_ascan, nnt_alen, nnt_flen, nnt_reslen, nnt_trace = 0; + +/* string to number */ +long long snum(char *str) { /* strtoll wrapper */ + char *endptr; + return strtoll(str, &endptr, 0); +} + +/* number to string */ +char *n2s(long long num, char *s, int *slen) { + *slen = snprintf(NULL, 0, "%lld", num) + 1; + s = realloc(s, *slen); + memset(s, 0, *slen); /* fill it with zeroes*/ + snprintf(s, *slen, "%lld", num); + s = realloc(s, (*slen) = strlen(s)); + return s; +} + +/* primitive function definition */ + +typedef struct nnt_primitive_t { + char name[NNT_SYMNAMELEN]; /* primitive function name */ + /* registered function pointer */ + char* (*handler)(char *arglist, char *res, int *reslen); +} nnt_primitive; + +static nnt_primitive *nnt_primitives; /* primitives table */ +static uint nnt_primitive_len = 0; /* primitives table size */ + +/* find a primitive function index by name, -1 if not found */ +int nnt_findprimitive(const char *name) { + int i; + for(i=0;i<nnt_primitive_len;i++) + if(strncmp(nnt_primitives[i].name, name, NNT_SYMNAMELEN) == 0) + return i; /* found */ + return -1; /* nothing found */ +} + +/* register a primitive function, overwrite if already exists */ +void nnt_regprimitive(const char *name, void *fptr) { + int pindex = nnt_findprimitive(name); + nnt_primitive newprim; /* create a new primitive template */ + strncpy(newprim.name, name, NNT_SYMNAMELEN); /* fill the name */ + newprim.handler = fptr; /* fill the handler pointer */ + if(pindex == -1) { /* create a new table entry */ + nnt_primitive_len++; + nnt_primitives = realloc(nnt_primitives, + nnt_primitive_len * sizeof(nnt_primitive)); + nnt_primitives[nnt_primitive_len - 1] = newprim; + } + else nnt_primitives[pindex] = newprim; /* replace the entry */ +} + +/* forms definition */ + +typedef struct nnt_form_t { + char name[NNT_SYMNAMELEN]; /* form (var/func) name */ + char *value; /* (reallocatable) value pointer (null-terminated) */ + uint len; /* form value length */ + int fptr; /* form character pointer */ +} nnt_form; + +static nnt_form *nnt_forms; /* forms table */ +static uint nnt_forms_len = 0; /* forms table size */ + +/* find a form index by name, -1 if not found */ +int nnt_findform(char *name) { + int i; + for(i=0;i<nnt_forms_len;i++) + if(strncmp(nnt_forms[i].name, name, NNT_SYMNAMELEN) == 0) + return i; /* found */ + return -1; /* nothing found */ +} + +/* find a free form among previously deleted entries */ +int nnt_findfreeform() { + int i; + for(i=0;i<nnt_forms_len;i++) + if(nnt_forms[i].name[0] == NNT_EOF) return i; /* found */ + return -1; /* nothing found */ +} + +/* assign a value to a form, creating it if doesn't exist */ +void nnt_assignform(char *name, char *value, uint len, int ptr) { + int findex = nnt_findform(name); + if(findex < 0) findex = nnt_findfreeform(); /* search among deleted */ + if(findex > -1) { /* existing form: just update the entry */ + strncpy(nnt_forms[findex].name, name, NNT_SYMNAMELEN); + nnt_forms[findex].value = realloc(nnt_forms[findex].value, len); + memcpy(nnt_forms[findex].value, value, len); + nnt_forms[findex].len = len; /* also update the length */ + nnt_forms[findex].fptr = ptr; /* reset the pointer */ + } else { /* create a new form and update the table */ + nnt_form newform; + strncpy(newform.name, name, NNT_SYMNAMELEN); /* fill the name */ + newform.value = calloc(len, 1); /* allocate the space */ + memcpy(newform.value, value, len); /* populate the value */ + newform.len = len; /* populate the length */ + newform.fptr = ptr; /* init the pointer */ + nnt_forms_len++; + nnt_forms = realloc(nnt_forms, nnt_forms_len * sizeof(nnt_form)); + nnt_forms[nnt_forms_len - 1] = newform; + } +} + +/* delete a form by its name */ +/* doesn't fully delete the structure, just marks it as free to use */ +void nnt_delform(char *name) { + int findex = nnt_findform(name); + if(findex > -1) { + nnt_forms[findex].value = realloc(nnt_forms[findex].value, 0); + nnt_forms[findex].len = 0; + nnt_forms[findex].fptr = 0; + memset(nnt_forms[findex].name, 0, NNT_SYMNAMELEN); + /* null name is a valid form name in TRAC, so we use a marker char */ + nnt_forms[findex].name[0] = NNT_EOF; + } +} + +/* form serialization/deserialization using netstrings */ + +/* serialize the form and populate the buffer and length */ +char* nnt_serializeform(nnt_form *form, char *buf, int *len) { + *len = NNT_SYMNAMELEN + form->len + sizeof(int); + int lbl = snprintf(NULL, 0, "%d:", *len); /* length buffer length */ + char *lenbuf = calloc(lbl + 1, 1); + snprintf(lenbuf, lbl+1, "%d:", *len); /* fill in length buffer */ + *len += lbl; + buf = realloc(buf, *len); + memset(buf, 0, *len); /* zero out the buffer */ + memcpy(buf, lenbuf, lbl); /* fill the length buffer */ + memcpy(buf + lbl, form->name, NNT_SYMNAMELEN); /* fill name */ + memcpy(buf + lbl + NNT_SYMNAMELEN, &(form->fptr), sizeof(int)); /* fill fptr */ + /* fill value */ + memcpy(buf + lbl + NNT_SYMNAMELEN + sizeof(int), form->value, form->len); + buf[(*len) - 1] = ','; /* trailing comma */ + free(lenbuf); return buf; +} + +/* deserialize the form given the netstring buffer +(push it into the form storage using nnt_assignform) */ +int nnt_deserializeform(char *buf) { + char lenbuf[11] = {0}; + int i, rdlen, fullen, rdptr, fid; + for(i=0;buf[i]!=':' && i<11;i++) lenbuf[i] = buf[i]; + rdlen = snum(lenbuf); /* practical read length */ + fullen = rdlen + strlen(lenbuf); + if(buf[fullen] != ',') return -1; /* invalid netstring detected */ + memcpy(&rdptr, &buf[i+1+NNT_SYMNAMELEN], sizeof(int)); /* fetch pointer */ + /* name is at i + 1, value is NNT_SYMNAMELEN + sizeof(int) away */ + nnt_assignform(&buf[i+1], &buf[i+1+NNT_SYMNAMELEN+sizeof(int)], + rdlen-NNT_SYMNAMELEN-sizeof(int)-1, rdptr); + /* return the total length of processed data */ + return fullen; +} + +/* parser logic */ + +/* find the matching character */ +int nnt_findmatch(char *str, uint len, int curpos, + uchar c1, uchar c2, int dir) { + int balance = 0, xpos = curpos; + if(xpos >= len) return -1; + for(;xpos >=0 && xpos < len ;xpos += dir) { + if(str[xpos] == c1) balance++; + if(str[xpos] == c2) balance--; + if(!balance) break; + } + return (xpos >= 0 && xpos < len) ? xpos : -1; +} + +/* delete len chars from buf at pos, return new buf length */ +int nnt_delchars(char *buf, int buflen, int pos, int len) { + if(len < 1) len = 1; /* safeguard */ + if(pos < 0) pos = 0; /* safeguard */ + memmove(&buf[pos], &buf[pos+len], buflen - pos - len); + return buflen - len; +} + +/* delete a single character under the pos in the active buffer */ +int nnt_delchar(int pos) { + nnt_flen = nnt_delchars(nnt_prog, nnt_flen, pos, 1); + nnt_prog[nnt_flen] = 0; + nnt_alen = nnt_flen - nnt_ascan; /* update active length */ + return nnt_flen; +} + +/* insert len chars into buffer dest of length dlen from buffer src at pos */ +/* it reallocs dest, updates dlen and returns the resulting dest pointer */ +char* nnt_inschars(char *dest, int *dlen, char *src, int len, int pos) { + *dlen += len; /* increase the length */ + dest = realloc(dest, *dlen); /* reallocate the buffer */ + memmove(&dest[pos+len], &dest[pos], (*dlen)-pos-len); /* free up the space */ + memmove(&dest[pos], src, len); /* copy the source */ + return dest; +} + +/* replace all occurrences of from with to in str (all null-terminated) */ +/* returns the new str pointer (str must be dynamically allocated before) */ +char *nnt_replace(char *str, char *from, char *to) { + int len = strlen(str), fromlen = strlen(from), tolen = strlen(to), i; + char *ptr = str; + while((ptr = strstr(ptr, from)) != NULL) { /* find the occurrence */ + len = nnt_delchars(str, len, (int) (ptr - str), fromlen); /* delete */ + str = nnt_inschars(str, &len, to, tolen, (int) (ptr - str)); /* add */ + ptr += tolen; /* move ptr */ + } + str[len] = 0; /* ensure null termination in the result */ + return str; +} + +/* cl primitive: call string (form) */ +char* prim_cl(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *name, *seg, snum = 0; + char idstr[3] = {NNT_SEGGAP, 0, 0}; /* update idstr[1] then */ + int fid = -1; + /* skip the first one */ + name = strtok(NULL, NNT_ADEL_S); /* get the name */ + if(name != NULL && (fid = nnt_findform(name)) > -1) { + /* populate the result */ + *reslen = nnt_forms[fid].len; + res = realloc(res, *reslen); + memcpy(res, nnt_forms[fid].value, *reslen); + do { /* receive the segment ids */ + seg = strtok(NULL, NNT_ADEL_S); + if(seg != NULL) { + snum++; /* we start from 1 to avoid null bytes */ + idstr[1] = snum; /* update the segmentid byte */ + res = nnt_replace(res, idstr, seg); + *reslen = strlen(res) + 1; + } + } while(seg != NULL); + } + return res; +} + +/* evaluate a TRAC function based on the neutral string + storing the results into nnt_res and nnt_reslen */ +void nnt_evalfunc(int startptr) { + char *arglist, *primname, *arg; + int i, l=0, primindex; + for(i=startptr;nnt_prog[i]!=NNT_EOF;i++,l++); + arglist = calloc(l, 1); + for(i=0;i<l;i++) arglist[i] = nnt_prog[startptr + i]; + /* now we have NNT_ADEL-delimited argument list to pass */ + nnt_reslen = 0; /* null value is by default */ + primname = strtok(arglist, NNT_ADEL_S); /* get primitive name */ + primindex = nnt_findprimitive(primname); + if(primindex > -1) { /* primitive found */ + if(nnt_trace) { /* trace mode */ + fprintf(stderr, "[nntrace] (%s", primname); + while((arg = strtok(NULL, NNT_ADEL_S)) != NULL) + fprintf(stderr, ",%s", arg); + fprintf(stderr, ")"); + } + for(i=0;i<l;i++) arglist[i] = nnt_prog[startptr + i]; /* restore */ + nnt_res = (*(nnt_primitives[primindex].handler))(arglist, nnt_res, &nnt_reslen); + } + /* now we have some core-level primitives */ + else if(strncmp(primname, "hl", 3) == 0) nnt_reslen = -1; /* halt */ + else if(strncmp(primname, "tn", 3) == 0) nnt_trace = 1; /* trace on */ + else if(strncmp(primname, "tf", 3) == 0) nnt_trace = 0; /* trace off */ + else { /* not found, run the default call logic */ + if(nnt_trace) { /* trace mode */ + fprintf(stderr, "[nntrace] (cl,%s",primname); + while((arg = strtok(NULL, NNT_ADEL_S)) != NULL) + fprintf(stderr, ",%s", arg); + fprintf(stderr, ")"); + } + /* expand the arglist */ + arglist = realloc(arglist, l + 2); + /* insert a dummy char and the argument delimiter at the start */ + arglist[0] = '0'; arglist[1] = NNT_ADEL; + for(i=0;i<l;i++) arglist[i+2] = nnt_prog[startptr + i]; /* restore */ + nnt_res = prim_cl(arglist, nnt_res, &nnt_reslen); + } + if(nnt_trace) { /* trace mode */ + if(nnt_reslen) + fprintf(stderr, " => %*.*s\r\n", nnt_reslen, nnt_reslen, nnt_res); + else fprintf(stderr, " => null\r\n"); + } + if(arglist) free(arglist); +} + +/* main TRAC processing algorithm */ +void nnt_proc(char *prog, uint len) { + nnt_res = malloc(nnt_reslen = 0); /* init the result buffer */ + nnt_prog = calloc(len+1, 1); /* init the neutral+active string buffer */ + /* load the entire program into the active string */ + memcpy(nnt_prog, prog, len); + uchar cc; /* currently processed character */ + int bp, rl, j; /* position buffers for various needs */ + nnt_ascan = 0; /* init the pointer and NS length */ + nnt_alen = nnt_flen = len; /* init active length with the full length */ + while(nnt_alen >= 0) { /* processing loop start */ + switch((cc = nnt_prog[nnt_ascan])) { /* rule 1 */ + case '(': /* rule 2 */ + bp = nnt_findmatch(nnt_prog, nnt_flen, nnt_ascan, '(', ')', 1); + if(bp > -1) { /* copy the entire region into the neutral string */ + rl = bp - nnt_ascan; /* nested region length */ + nnt_delchar(nnt_ascan); /* delete this paren */ + nnt_ascan += rl - 1; /* increment active pointer */ + nnt_delchar(nnt_ascan); /* delete matching paren */ + } + break; + case '\r': case '\n': case '\t': /* rule 3 */ + nnt_delchar(nnt_ascan); + break; + case ',': /* rule 4 */ + nnt_prog[nnt_ascan++] = NNT_ADEL; /* argument delimiter */ + break; + case NNT_SHARP: /* rules 5 to 7 */ + switch(nnt_prog[nnt_ascan+1]) { + case '(': /* rule 5 - active function start */ + nnt_delchar(nnt_ascan); /* remove # character */ + nnt_prog[nnt_ascan++] = NNT_AFST; + break; + case NNT_SHARP: /* rule 6 */ + if(nnt_prog[nnt_ascan+2] == '(') { /* neutral function start */ + nnt_delchar(nnt_ascan); /* remove # character */ + nnt_delchar(nnt_ascan); /* remove another # character */ + nnt_prog[nnt_ascan++] = NNT_NFST; + } else nnt_ascan++; /* rule 7 */ + break; + default: nnt_ascan++; /* rule 7 */ + } + break; + case ')': /* rule 8 */ + nnt_prog[nnt_ascan] = NNT_EOF; + for(bp = nnt_ascan-1;bp>0;bp--) /* find function start */ + if(nnt_prog[bp] == NNT_AFST || nnt_prog[bp] == NNT_NFST) break; + /* evaluate the function: the result is stored into nnt_res, + its length into nnt_reslen */ + nnt_evalfunc(bp + 1); /* skip function start marker */ + if(nnt_reslen > 0) { + nnt_prog = nnt_inschars(nnt_prog, &nnt_flen, + nnt_res, nnt_reslen, nnt_ascan + 1); + if(nnt_prog[bp] == NNT_AFST) { /* rule 11 */ + nnt_flen = nnt_delchars(nnt_prog, nnt_flen, bp, nnt_ascan - bp + 1); + nnt_ascan = bp; + } else if(nnt_prog[bp] == NNT_NFST) { /* rule 12 */ + nnt_flen = nnt_delchars(nnt_prog, nnt_flen, bp, nnt_ascan - bp + 1); + nnt_ascan = bp + nnt_reslen; + } + } else if(nnt_reslen == 0) { /* rule 10 */ + nnt_flen = nnt_delchars(nnt_prog, nnt_flen, bp, nnt_ascan - bp + 1); + nnt_ascan = bp; + } else nnt_ascan = nnt_flen + 1; /* negative reslen causes the halt */ + break; + default: nnt_ascan++; /* rule 9 */ + } + nnt_alen = nnt_flen - nnt_ascan; /* update the length on every step */ + nnt_prog[nnt_flen] = 0; + } + /* free the buffers */ + free(nnt_prog); + free(nnt_res); +} + +/* all other nntrac primitives */ + +/* ps primitive: print string */ +char* prim_ps(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S); + /* skip the first one (the ps string) */ + do { + arg = strtok(NULL, NNT_ADEL_S); + if(arg != NULL) printf("%s", arg); + } while(arg != NULL); + return res; +} + +/* rc primitive: read char */ +char* prim_rc(char *arglist, char *res, int *reslen) { + res = realloc(res, 1); + res[0] = getchar(); + *reslen = 1; + return res; +} + +/* rs primitive: read string */ +char* prim_rs(char *arglist, char *res, int *reslen) { + int c, i = 0; + res = realloc(res, (*reslen) = 0); + while((c = getchar()) > 0 && c != nnt_meta) { + (*reslen)++; + res = realloc(res, (*reslen) + 1); + res[(*reslen) - 1] = c&255; + res[*reslen] = 0; /* prefill with null terminator */ + } + return res = realloc(res, (*reslen) = strlen(res)); +} + +/* cm primitive: change meta */ +char* prim_cm(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S); + arg = strtok(NULL, NNT_ADEL_S); + if(arg != NULL) nnt_meta = arg[0]; + return res; +} + +/* ds primitive: define string (form) */ +char* prim_ds(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *name, *value; + name = strtok(NULL, NNT_ADEL_S); /* get the name */ + if(name != NULL) { + value = strtok(NULL, NNT_ADEL_S); /* get the value */ + if(value != NULL) nnt_assignform(name, value, strlen(value), 0); + } + return res; +} + +/* ss primitive: segment string (form) */ +char* prim_ss(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *name, *seg, sid = 0; + char idstr[3] = {NNT_SEGGAP, 0, 0}; /* update idstr[1] then */ + int fid = -1; + /* skip the first one (the ps string) */ + name = strtok(NULL, NNT_ADEL_S); /* get the name */ + if(name != NULL && (fid = nnt_findform(name)) > -1) { + do { /* receive the segment ids */ + seg = strtok(NULL, NNT_ADEL_S); + if(seg != NULL) { + sid++; /* we start from 1 to avoid null bytes */ + idstr[1] = sid; /* update the segmentid byte */ + nnt_forms[fid].value = nnt_replace(nnt_forms[fid].value, seg, idstr); + nnt_forms[fid].len = strlen(nnt_forms[fid].value) + 1; + } + } while(seg != NULL); + } + nnt_forms[fid].fptr = 0; + return res; +} + +/* cr primitive: call restore */ +char* prim_cr(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *name; + int fid = -1; + name = strtok(NULL, NNT_ADEL_S); + if(name != NULL && (fid = nnt_findform(name)) > -1) + nnt_forms[fid].fptr = 0; + return res; +} + +/* move form pointer helper */ +void nnt_moveptr(nnt_form *form, int offs) { + int i = 0; if(offs < 1) offs = 1; + for(i=0;i<offs;i++) { + form->fptr++; + while(form->value[form->fptr] == NNT_SEGGAP && form->fptr < form->len) + form->fptr += 2; /* skip the gap and the segment id */ + if(form->fptr > form->len) { + form->fptr = form->len; + break; + } + } +} + +/* partial character fetch helper (returns -1 if unavailable) */ +char nnt_fetchformchar(nnt_form *form, char dir) { + if(dir >= 0) { /* move forward */ + while(form->value[form->fptr] == NNT_SEGGAP) { + form->fptr += 2; + if(form->fptr > form->len) { form->fptr = form->len; return -1; } + } + } else { /* move back */ + while(form->fptr >= 0 && form->value[form->fptr] == NNT_SEGGAP) { + form->fptr--; + if(form->fptr > 0 && form->value[form->fptr - 1] == NNT_SEGGAP) + form->fptr--; /* skip the segment id byte */ + if(form->fptr < 0) { form->fptr = 0; return -1; } + }; + } + if(form->fptr >= 0 && form->fptr < form->len) + return form->value[form->fptr]; + else return -1; +} + +/* cs primitive: call segment */ +char* prim_cs(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *name, *def; + int fid = -1; + name = strtok(NULL, NNT_ADEL_S); + def = strtok(NULL, NNT_ADEL_S); + if(name != NULL && (fid = nnt_findform(name)) > -1) { + int sl; /* segment length */ + for(sl=0;sl<nnt_forms[fid].len;sl++) + if(nnt_forms[fid].value[nnt_forms[fid].fptr+sl] == NNT_SEGGAP) break; + if(nnt_forms[fid].fptr + sl > nnt_forms[fid].len) + sl = nnt_forms[fid].len - nnt_forms[fid].fptr; + if(sl > 0) { + res = realloc(res, (*reslen) = sl); + memcpy(res, &nnt_forms[fid].value[nnt_forms[fid].fptr], sl); + nnt_moveptr(&nnt_forms[fid], sl); /* move the pointer */ + } else if(def != NULL) { /* set the default value */ + res = realloc(res, (*reslen) = strlen(def)); + memcpy(res, def, *reslen); + } + } + return res; +} + +/* cc primitive: call character */ +char* prim_cc(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *name, *def; + int fid = -1; + name = strtok(NULL, NNT_ADEL_S); + def = strtok(NULL, NNT_ADEL_S); + if(name != NULL && (fid = nnt_findform(name)) > -1) { + res = realloc(res, (*reslen) = 1); + res[0] = nnt_fetchformchar(&nnt_forms[fid], 1); + if(res[0] == -1) { /* set the default value */ + if(def != NULL) { + res = realloc(res, (*reslen) = strlen(def)); + memcpy(res, def, *reslen); + } else *reslen = 0; + } + nnt_moveptr(&nnt_forms[fid], 1); /* move the pointer */ + } + return res; +} + +/* cn primitive: call N characters */ +char* prim_cn(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *name, *offs, *def, c; + int fid = -1, i, roff, ccount = 0; + name = strtok(NULL, NNT_ADEL_S); + offs = strtok(NULL, NNT_ADEL_S); + def = strtok(NULL, NNT_ADEL_S); + if(name != NULL && (fid = nnt_findform(name)) > -1) { + roff = snum(offs); /* real offset */ + if(roff > 0) { /* positive offset */ + res = realloc(res, (*reslen) = roff + 1); + memset(res, 0, *reslen); + for(i=0;i<roff;i++) { + c = nnt_fetchformchar(&nnt_forms[fid], 1); + if(c < 0) break; else res[i] = c; + nnt_forms[fid].fptr++; ccount++; + } + if(nnt_forms[fid].fptr > nnt_forms[fid].len) + nnt_forms[fid].fptr = nnt_forms[fid].len; + *reslen = strlen(res); /* update the length */ + } else if(roff < 0) { /* negative offset */ + roff = -roff; /* get absolute value */ + res = realloc(res, (*reslen) = roff + 1); + memset(res, 0, *reslen); + nnt_forms[fid].fptr--; /* start counting from the previous char */ + for(i=0;i<roff;i++) { + c = nnt_fetchformchar(&nnt_forms[fid], -1); + if(c < 0) break; else res[i] = c; + nnt_forms[fid].fptr--; ccount++; + } + if(nnt_forms[fid].fptr < 0) nnt_forms[fid].fptr = 0; + *reslen = strlen(res); /* update the length */ + res = strrev(res); /* reverse the order */ + } + if(ccount && *reslen) /* finalize the allocation */ + res = realloc(res, *reslen); + else if(def != NULL) { /* return the default value */ + res = realloc(res, (*reslen) = strlen(def)); + memcpy(res, def, *reslen); + } else *reslen = 0; + } + return res; +} + +/* in primitive: initial */ +char* prim_in(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *name, *subs, *def; + name = strtok(NULL, NNT_ADEL_S); + subs = strtok(NULL, NNT_ADEL_S); + def = strtok(NULL, NNT_ADEL_S); + int fid, i, slen; + if(name != NULL && subs != NULL && (fid = nnt_findform(name)) > -1) { + char *ntv = calloc(nnt_forms[fid].len + 1, 1); /* null-terminated value */ + memcpy(ntv, nnt_forms[fid].value, nnt_forms[fid].len); + char *found = strstr(ntv, subs); + if(found != NULL && nnt_forms[fid].fptr < nnt_forms[fid].len) { + slen = ((long) (found - ntv)) - nnt_forms[fid].fptr; + if(slen > 0) { + res = realloc(res, slen + 1); + memset(res, 0, slen + 1); /* null-terminate */ + for(i=0;i<slen;i++) { /* copy ignoring the seggaps */ + res[i] = nnt_fetchformchar(&nnt_forms[fid], 1); + nnt_moveptr(&nnt_forms[fid], 1); + } + *reslen = strlen(res); /* update the length */ + res = realloc(res, *reslen); /* finalize */ + nnt_moveptr(&nnt_forms[fid], strlen(subs)); /* move after the fptr */ + } else found = NULL; + } else found = NULL; + if(found == NULL && def != NULL) { /* return the default value */ + res = realloc(res, (*reslen) = strlen(def)); + memcpy(res, def, *reslen); + } + free(ntv); + } + return res; +} + +/* dd primitive: delete definition(s) */ +char* prim_dd(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S); + do { + arg = strtok(NULL, NNT_ADEL_S); + if(arg != NULL) nnt_delform(arg); + } while(arg != NULL); + return res; +} + +/* da primitive: delete all forms */ +char* prim_da(char *arglist, char *res, int *reslen) { + int i = 0; + for(i=0;i<nnt_forms_len;i++) free(nnt_forms[i].value); + nnt_forms_len = 0; + nnt_forms = realloc(nnt_forms, 0); + return res; +} + +/* eq primitive: string equality */ +char* prim_eq(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *x1, *x2, *x3, *x4; + x1 = strtok(NULL, NNT_ADEL_S); x2 = strtok(NULL, NNT_ADEL_S); + x3 = strtok(NULL, NNT_ADEL_S); x4 = strtok(NULL, NNT_ADEL_S); + if(x1 && x2 && x3 && x4) { + arg = strcmp(x1, x2) ? x4 : x3; + *reslen = strlen(arg); + res = realloc(res, *reslen); + memcpy(res, arg, *reslen); + } + return res; +} + +/* gr primitive: number inequality */ +char* prim_gr(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *d1, *d2, *x1, *x2; + d1 = strtok(NULL, NNT_ADEL_S); d2 = strtok(NULL, NNT_ADEL_S); + x1 = strtok(NULL, NNT_ADEL_S); x2 = strtok(NULL, NNT_ADEL_S); + if(x1 && x2 && d1 && d2) { + arg = (snum(d1) > snum(d2)) ? x1 : x2; + *reslen = strlen(arg); + res = realloc(res, *reslen); + memcpy(res, arg, *reslen); + } + return res; +} + +/* theoretically we can safely operate from -2**62 to 2**62 */ +#define ARITH_LIMIT ((long long) (((unsigned long long) - 1LL) >> 2)) +#define BITWISE_BITS 32 +#define BITWISE_LIMIT (unsigned long long) ((1LL<<BITWISE_BITS) - 1LL) + +/* reusable arithmetic ops helper */ +char* handle_dec_result(long long nres, char *ovr, char *res, int *reslen) { + if(nres <= ARITH_LIMIT && nres > -ARITH_LIMIT) { /* valid result */ + res = n2s(nres, res, reslen); + } else if(ovr) { /* return the overflow/underflow value */ + *reslen = strlen(ovr); + res = realloc(res, *reslen); /* resize to actual length */ + memcpy(res, ovr, *reslen); /* fill the overflow value */ + } + return res; +} + +/* ad primitive: addition (overflow arg is optional) */ +char* prim_ad(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs, *ovr; + as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S); + ovr = strtok(NULL, NNT_ADEL_S); + if(as && bs) res = handle_dec_result(snum(as) + snum(bs), ovr, res, reslen); + return res; +} + +/* su primitive: subtraction (overflow arg is optional) */ +char* prim_su(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs, *ovr; + as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S); + ovr = strtok(NULL, NNT_ADEL_S); + if(as && bs) res = handle_dec_result(snum(as) - snum(bs), ovr, res, reslen); + return res; +} + +/* ml primitive: multiplication (overflow arg is optional) */ +char* prim_ml(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs, *ovr; + as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S); + ovr = strtok(NULL, NNT_ADEL_S); + if(as && bs) res = handle_dec_result(snum(as) * snum(bs), ovr, res, reslen); + return res; +} + +/* dv primitive: division (overflow arg is optional) */ +char* prim_dv(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs, *ovr; + as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S); + ovr = strtok(NULL, NNT_ADEL_S); + if(as && bs) { + long long b = snum(bs); + if(b) res = handle_dec_result(snum(as) / b, ovr, res, reslen); + else if(ovr) { /* division by zero, return ovr */ + *reslen = strlen(ovr); + res = realloc(res, *reslen); /* resize to actual length */ + memcpy(res, ovr, *reslen); /* fill the overflow value */ + } + } + return res; +} + +/* bu primitive: bitwise union (OR) */ +char* prim_bu(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs; + as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S); + if(as && bs) res = handle_dec_result((snum(as) | snum(bs)) & BITWISE_LIMIT, + NULL, res, reslen); + return res; +} + +/* bi primitive: bitwise intersect (AND) */ +char* prim_bi(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs; + as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S); + if(as && bs) res = handle_dec_result((snum(as) & snum(bs)) & BITWISE_LIMIT, + NULL, res, reslen); + return res; +} + +/* [new] bx primitive: bitwise exclusive or (XOR) */ +char* prim_bx(char *arglist, char *res, int *reslen) { + if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */ + char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs; + as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S); + if(as && bs) res = handle_dec_result((snum(as) ^ snum(bs)) & BITWISE_LIMIT, + NULL, res, reslen); + return res; +} + +/* bc primitive: bitwise complement (NOT) */ +char* prim_bc(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *as = strtok(NULL, NNT_ADEL_S); + if(as) res = handle_dec_result((~snum(as)) & BITWISE_LIMIT, + NULL, res, reslen); + return res; +} + +/* br primitive: bitwise rotation */ +char* prim_br(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs; + as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S); + if(as && bs) { + long long a = snum(as), b = snum(bs), nres; + /* we rotate b according to the value of a */ + b &= BITWISE_LIMIT; /* bit-clip the value */ + if(a > 0) { /* rotate left */ + if(a > BITWISE_BITS) a %= BITWISE_BITS; + nres = (b << a) | (b >> (BITWISE_BITS - a)); + } else if(a < 0) { /* rotate right */ + a = -a; + if(a > BITWISE_BITS) a %= BITWISE_BITS; + nres = (b << (BITWISE_BITS - a)) | (b >> a); + } + else nres = b; /* no rotation */ + res = handle_dec_result(nres & BITWISE_LIMIT, NULL, res, reslen); + } + return res; +} + +/* bs primitive: bitwise shift */ +char* prim_bs(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs; + as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S); + if(as && bs) { + long long a = snum(as), b = snum(bs), nres; + /* we shift b according to the value of a */ + if(a > 0) nres = b << a; /* shift left */ + else if(a < 0) nres = b >> (-a); /* shift right */ + else nres = b; /* no shift */ + res = handle_dec_result(nres & BITWISE_LIMIT, NULL, res, reslen); + } + return res; +} + +/* ln primitive: list names */ +char* prim_ln(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *pref = strtok(NULL, NNT_ADEL_S); + int i; + for(i=0;i<nnt_forms_len;i++) { + if(nnt_forms[i].name[0] != NNT_EOF) { + if(pref) printf("%s", pref); + printf("%s", nnt_forms[i].name); + } + } + return res; +} + +/* pf primitive: print form */ +char* prim_pf(char *arglist, char *res, int *reslen) { + char *arg = strtok(arglist, NNT_ADEL_S), *fname = strtok(NULL, NNT_ADEL_S); + if(fname != NULL) { + int i = nnt_findform(fname), j; + if(i > -1) { /* form found */ + for(j=0;j<=nnt_forms[i].len;j++) { + if(j == nnt_forms[i].fptr) printf("<^>"); + if(nnt_forms[i].value[j] == NNT_SEGGAP) + printf("<%u>", (unsigned int) nnt_forms[i].value[++j]); + else printf("%c",nnt_forms[i].value[j]); + } + } + } + return res; +} + +/* filesystem primitives */ + +/* sb primitive: store block */ +char *prim_sb(char *arglist, char *res, int *reslen) { + if(nnt_mode == NNT_SECURE) return res; /* prevent running in secure mode */ + char *arg = strtok(arglist, NNT_ADEL_S), *fname, *fmname; + fname = strtok(NULL, NNT_ADEL_S); + if(fname != NULL) { /* output file name */ + FILE *hnd = fopen(fname, "wb"); /* open the output file */ + if(hnd != NULL) { + char *vbuf = malloc(0); /* value buffer */ + int findex, vlen = 0; /* value len */ + do { + fmname = strtok(NULL, NNT_ADEL_S); /* form name */ + if(fmname != NULL) { + findex = nnt_findform(fmname); + if(findex > -1) { + vbuf = nnt_serializeform(&nnt_forms[findex], vbuf, &vlen); + fwrite(vbuf, vlen, 1, hnd); /* write the value out */ + nnt_delform(fmname); /* delete after exporting as per the spec */ + } + } + } while(fmname != NULL); + fclose(hnd); /* close the output file */ + free(vbuf); /* free value buffer */ + } + } + return res; +} + +/* full file read helper */ +char* nnt_readfile(FILE *hnd, char *fbuf, int *buflen) { + fseek(hnd, 0, SEEK_END); + *buflen = ftell(hnd); + fseek(hnd, 0, SEEK_SET); + fbuf = realloc(fbuf, *buflen); /* allocate the full buffer */ + if(fbuf == NULL) return NULL; /* if allocation failed */ + fread(fbuf, 1, *buflen, hnd); /* read the entire file */ + return fbuf; +} + +/* fb primitive: fetch block */ +char *prim_fb(char *arglist, char *res, int *reslen) { + if(nnt_mode == NNT_SECURE) return res; /* prevent running in secure mode */ + char *arg = strtok(arglist, NNT_ADEL_S), *fname = strtok(NULL, NNT_ADEL_S); + if(fname != NULL) { /* input file name */ + FILE *hnd = fopen(fname, "rb"); /* open the input file */ + if(hnd != NULL) { + char *fbuf = malloc(0); + int flen; + fbuf = nnt_readfile(hnd, fbuf, &flen); + fclose(hnd); + int i = 0, dlen; /* running index and length */ + while(i < flen) { /* form queue to deserialize */ + dlen = nnt_deserializeform(&fbuf[i]); + if(dlen < 0) break; + i += dlen + 1; + } + free(fbuf); /* free file buffer */ + } + } + return res; +} + +/* [new] sf primitive: store raw file */ +char *prim_sf(char *arglist, char *res, int *reslen) { + if(nnt_mode == NNT_SECURE) return res; /* prevent running in secure mode */ + if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */ + char *arg = strtok(arglist, NNT_ADEL_S), *fname, *fmname; + fname = strtok(NULL, NNT_ADEL_S); /* file name */ + fmname = strtok(NULL, NNT_ADEL_S); /* form name */ + if(fname != NULL && fmname != NULL) { + int fid = nnt_findform(fmname); + if(fid > -1) { + FILE *hnd = fopen(fname, "wb"); /* open the output file */ + if(hnd != NULL) { /* write the value out */ + fwrite(nnt_forms[fid].value, nnt_forms[fid].len, 1, hnd); + fclose(hnd); + } + } + } + return res; +} + +/* [new] ff primitive: fetch raw file */ +char *prim_ff(char *arglist, char *res, int *reslen) { + if(nnt_mode == NNT_SECURE) return res; /* prevent running in secure mode */ + if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */ + char *arg = strtok(arglist, NNT_ADEL_S), *fname, *fmname; + fname = strtok(NULL, NNT_ADEL_S); /* file name */ + fmname = strtok(NULL, NNT_ADEL_S); /* form name */ + if(fname != NULL && fmname != NULL) { + FILE *hnd = fopen(fname, "rb"); /* open the input file */ + if(hnd != NULL) { + char *fbuf = malloc(0); + int flen; + fbuf = nnt_readfile(hnd, fbuf, &flen); + fclose(hnd); + nnt_assignform(fmname, fbuf, flen, 0); /* store it into the form */ + free(fbuf); /* and free the resources */ + } + } + return res; +} + +/* eb primitive: erase block or raw file */ +char *prim_eb(char *arglist, char *res, int *reslen) { + if(nnt_mode == NNT_SECURE) return res; /* prevent running in secure mode */ + char *arg = strtok(arglist, NNT_ADEL_S), *fname = strtok(NULL, NNT_ADEL_S); + if(fname != NULL) remove(fname); /* delete the input file name */ + return res; +} + +/* mode primitive: set operation mode */ +char *prim_mo(char *arglist, char *res, int *reslen) { + if(nnt_mlock == 1) return res; /* mode change is already locked */ + char *arg = strtok(arglist, NNT_ADEL_S), *modechar, *mlk; + modechar = strtok(NULL, NNT_ADEL_S); + if(modechar != NULL) { + switch(modechar[0]) { + case 'S': nnt_mode = NNT_SECURE; break; + case 'L': nnt_mode = NNT_LEGACY; break; + case 'E': nnt_mode = NNT_NORMAL; break; + } + mlk = strtok(NULL, NNT_ADEL_S); + if(mlk != NULL && mlk[0] == 'L') nnt_mlock = 1; /* set mode lock */ + } + return res; +} + +/* [new] ac primitive: ASCII code */ +char *prim_ac(char *arglist, char *res, int *reslen) { + if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */ + char *arg = strtok(arglist, NNT_ADEL_S), *cvs = strtok(NULL, NNT_ADEL_S); + if(cvs != NULL) res = n2s((uchar) cvs[0], res, reslen); + return res; +} + +/* [new] av primitive: ASCII value */ +char *prim_av(char *arglist, char *res, int *reslen) { + if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */ + char *arg = strtok(arglist, NNT_ADEL_S), *cns = strtok(NULL, NNT_ADEL_S); + long long rcode = 0; + if(cns != NULL) rcode = snum(cns); + res = realloc(res, (*reslen) = 1); + res[0] = rcode&255; + return res; +} + +/* [new] fn primitive: format number */ +char *prim_fn(char *arglist, char *res, int *reslen) { + if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */ + char *arg = strtok(arglist, NNT_ADEL_S), *fmt, *cns; + fmt = strtok(NULL, NNT_ADEL_S); + cns = strtok(NULL, NNT_ADEL_S); + long long num = 0; + if(fmt != NULL && cns != NULL) { + num = snum(cns); + *reslen = snprintf(NULL, 0, fmt, num); /* estimate the size */ + res = realloc(res, 1 + (*reslen)); + snprintf(res, 1 + (*reslen), fmt, num); /* actually format the number */ + res = realloc(res, (*reslen) = strlen(res)); /* final reallocation */ + } + return res; +} + +#ifndef NNT_NO_EXTSHELL +/* [new] os primitive: run external OS command */ +char *prim_os(char *arglist, char *res, int *reslen) { + if(nnt_mode == NNT_SECURE) return res; /* prevent running in secure mode */ + if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */ + char *arg = strtok(arglist, NNT_ADEL_S), *cmd = strtok(NULL, NNT_ADEL_S); + int rescode; + if(cmd != NULL) { + rescode = system(cmd); /* run the command and get the status code */ + res = n2s(rescode, res, reslen); + } + return res; +} +#endif + +/* [new] tm primitive: local/UTC/Epoch time */ +char *prim_tm(char *arglist, char *res, int *reslen) { + if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */ + char *arg = strtok(arglist, NNT_ADEL_S), *fmt = strtok(NULL, NNT_ADEL_S), + *opt = strtok(NULL, NNT_ADEL_S), utc = !!(opt!=NULL && opt[0]=='U'); + if(fmt != NULL) { /* format string exists */ + time_t curtime = time(NULL); + if(fmt[0] == 'E' && fmt[1] == 0) res = n2s(curtime, res, reslen); + else { + struct tm *tminfo = utc ? gmtime(&curtime) : localtime(&curtime); + res = realloc(res, (*reslen) = 512); + memset(res, 0, *reslen); + *reslen = strftime(res, *reslen, fmt, tminfo); /* populate the time */ + res = realloc(res, *reslen); /* shrink the result */ + } + } + return res; +} + +static unsigned long long nnt_rngs; /* PRNG state*/ +unsigned long long xorshift64() { + nnt_rngs^=(nnt_rngs<<13);nnt_rngs^=(nnt_rngs>>7);nnt_rngs^=(nnt_rngs<<17); + return nnt_rngs; +} + +/* [new] rn primitive: (pseudo)random number */ +char *prim_rn(char *arglist, char *res, int *reslen) { + if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */ + char *arg = strtok(arglist, NNT_ADEL_S), *froms = strtok(NULL, NNT_ADEL_S), + *tos = strtok(NULL, NNT_ADEL_S); + long long a = (froms == NULL) ? 0 : snum(froms), + b = (tos == NULL) ? ARITH_LIMIT : snum(tos); + res = n2s(a == b ? a : (a + xorshift64() % (b - a)), res, reslen); + return res; +} + +/* init the resources and built-in primitives */ +void nnt_init() { + nnt_forms = malloc(0); /* init the forms table */ + nnt_primitives = malloc(0); /* init the primitives table */ + nnt_rngs = time(NULL); xorshift64(); /* init the PRNG */ + nnt_regprimitive("ps", &prim_ps); + nnt_regprimitive("rc", &prim_rc); + nnt_regprimitive("rs", &prim_rs); + nnt_regprimitive("cm", &prim_cm); + nnt_regprimitive("ds", &prim_ds); + nnt_regprimitive("ss", &prim_ss); + nnt_regprimitive("cl", &prim_cl); + nnt_regprimitive("cr", &prim_cr); + nnt_regprimitive("cc", &prim_cc); + nnt_regprimitive("cs", &prim_cs); + nnt_regprimitive("cn", &prim_cn); + nnt_regprimitive("in", &prim_in); + nnt_regprimitive("dd", &prim_dd); + nnt_regprimitive("da", &prim_da); + nnt_regprimitive("eq", &prim_eq); + nnt_regprimitive("gr", &prim_gr); + nnt_regprimitive("ad", &prim_ad); + nnt_regprimitive("su", &prim_su); + nnt_regprimitive("ml", &prim_ml); + nnt_regprimitive("dv", &prim_dv); + nnt_regprimitive("bu", &prim_bu); + nnt_regprimitive("bi", &prim_bi); + nnt_regprimitive("bx", &prim_bx); + nnt_regprimitive("bc", &prim_bc); + nnt_regprimitive("br", &prim_br); + nnt_regprimitive("bs", &prim_bs); + nnt_regprimitive("ln", &prim_ln); + nnt_regprimitive("pf", &prim_pf); + nnt_regprimitive("sb", &prim_sb); + nnt_regprimitive("fb", &prim_fb); + nnt_regprimitive("eb", &prim_eb); + nnt_regprimitive("mo", &prim_mo); + nnt_regprimitive("ac", &prim_ac); + nnt_regprimitive("av", &prim_av); + nnt_regprimitive("fn", &prim_fn); + nnt_regprimitive("ff", &prim_ff); + nnt_regprimitive("sf", &prim_sf); +#ifndef NNT_NO_EXTSHELL + nnt_regprimitive("os", &prim_os); +#endif + nnt_regprimitive("tm", &prim_tm); + nnt_regprimitive("rn", &prim_rn); +} + +/* free the interpreter resources */ +void nnt_finish() { + prim_da(NULL, nnt_res, &nnt_reslen); /* free internal form resources */ + if(nnt_primitives) free(nnt_primitives); + if(nnt_forms) free(nnt_forms); +} + +/* non-embed entry point */ +#ifndef NNT_EMBED +int main(int argc, char *argv[]) { + char *fname = "-", *prog, *buf; /* stdin by default */ + int proglen, i, nlen; + if(argc > 1) fname = argv[1]; + FILE* fd = stdin; + if(!(fname[0] == '-' && fname[1] == 0)) { + fd = fopen(fname, "rb"); + if(fd == NULL) {perror("Error");return 1;} + } + if(fd == stdin) { /* interactive session */ + proglen = strlen(prog = "#(ps,#(rs))"); + fprintf(stderr, "%s\n", "nntrac by Luxferre, 2023, public domain"); + } else prog = nnt_readfile(fd, malloc(0), &proglen); + nnt_init(); /* init processing resources */ + if(argc > 1) { /* populate nnt-argc and nnt-argv forms */ + nlen = snprintf(NULL, 0, "%d", argc - 1); + buf = calloc(nlen+1, 1); + snprintf(buf, nlen+1, "%d", argc - 1); + nnt_assignform("nnt-argc", buf, nlen, 0); + buf = realloc(buf, 1); /* reuse the buffer for nnt-argv */ + buf[0] = 0; /* ensure it's an empty string */ + char segsep[3] = {NNT_SEGGAP, 1, 0}; + for(i=1,nlen=0;i<argc;i++) { + nlen += strlen(argv[i]) + 2; + buf = realloc(buf, nlen); + strcat(buf, argv[i]); /* append the parameter */ + strcat(buf, segsep); /* and the segment separator */ + } + nnt_assignform("nnt-argv", buf, nlen, 0); + free(buf); + } + nnt_proc(prog, proglen); /* start main processor */ + nnt_finish(); /* finalize processing resources */ + fclose(fd); + puts(""); /* just print a newline before exiting */ + if(fd != stdin) free(prog); /* free the input buffer */ + return 0; +} +#endif +\ No newline at end of file