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

nntrac.c (43227B)


      1 /*
      2  * no-nonsense TRAC programming language implementation (T64 standard)
      3  * 
      4  * Build with: cc [-static] -std=c99 -Os -s nntrac.c -o nntrac
      5  *
      6  * see README.md for details
      7  *
      8  * Created by Luxferre in 2023, released into public domain
      9  */
     10 
     11 #define POSIX_SOURCE
     12 #define POSIX_C_SOURCE 1
     13 #include <stdlib.h>
     14 #include <stdio.h>
     15 #include <string.h>
     16 #include <time.h>
     17 
     18 #define uint unsigned int
     19 #define uchar unsigned char
     20 #ifndef NNT_SHARP
     21 #define NNT_SHARP '#' /* can be redefined to : or other unused character */
     22 #endif
     23 #ifndef NNT_SYMNAMELEN
     24 #define NNT_SYMNAMELEN 32 /* max symbol name length, including null */
     25 #endif
     26 
     27 #ifndef strrev
     28 char *strrev(char *str) {
     29   char *p1, *p2;
     30   if(!str || !*str) return str;
     31   for(p1=str,p2=str+strlen(str)-1;p2>p1;++p1,--p2) {
     32     *p1 ^= *p2; *p2 ^= *p1; *p1 ^= *p2;
     33   }
     34   return str;
     35 }
     36 #endif
     37 
     38 enum NNT_MARKERS { /* various markers: 248 to 255 never occur in UTF-8 */
     39   NNT_AFST=-8,  /* active function start */
     40   NNT_NFST,     /* neutral function start */
     41   NNT_EOF,      /* end of function */
     42   NNT_ADEL,     /* argument delimiter */
     43   NNT_SEGGAP    /* segment gap character */
     44 };
     45 enum NNT_MODES {NNT_NORMAL=1, NNT_LEGACY, NNT_SECURE}; /* operation modes */
     46 static const char NNT_ADEL_S[2] = {NNT_ADEL, 0};
     47 static uchar nnt_meta = '\''; /* apostrophe by default */
     48 
     49 /* combined active + neutral string buffer and evaluation result buffer */
     50 static char *nnt_prog, *nnt_res, nnt_mode = NNT_NORMAL, nnt_mlock = 0;
     51 /* scanning pointers and buffer lengths */
     52 static int nnt_ascan, nnt_alen, nnt_flen, nnt_reslen, nnt_trace = 0;
     53 
     54 /* string to number */
     55 long long snum(char *str) { /* strtoll wrapper */
     56   char *endptr;
     57   return strtoll(str, &endptr, 0);
     58 }
     59 
     60 /* number to string */
     61 char *n2s(long long num, char *s, int *slen) {
     62   *slen = snprintf(NULL, 0, "%lld", num) + 1;
     63   s = realloc(s, *slen);
     64   memset(s, 0, *slen); /* fill it with zeroes*/
     65   snprintf(s, *slen, "%lld", num);
     66   s = realloc(s, (*slen) = strlen(s));
     67   return s;
     68 }
     69 
     70 /* primitive function definition */
     71 
     72 typedef struct nnt_primitive_t {
     73   char name[NNT_SYMNAMELEN];       /* primitive function name */
     74   /* registered function pointer */
     75   char* (*handler)(char *arglist, char *res, int *reslen);
     76 } nnt_primitive;
     77 
     78 static nnt_primitive *nnt_primitives; /* primitives table */
     79 static uint nnt_primitive_len = 0; /* primitives table size */
     80 
     81 /* find a primitive function index by name, -1 if not found */
     82 int nnt_findprimitive(const char *name) {
     83   int i;
     84   for(i=0;i<nnt_primitive_len;i++)
     85     if(strncmp(nnt_primitives[i].name, name, NNT_SYMNAMELEN) == 0)
     86       return i; /* found */
     87   return -1;  /* nothing found */
     88 }
     89 
     90 /* register a primitive function, overwrite if already exists */
     91 void nnt_regprimitive(const char *name, void *fptr) {
     92   int pindex = nnt_findprimitive(name);
     93   nnt_primitive newprim; /* create a new primitive template */
     94   strncpy(newprim.name, name, NNT_SYMNAMELEN); /* fill the name */
     95   newprim.handler = fptr; /* fill the handler pointer */
     96   if(pindex == -1) { /* create a new table entry */
     97     nnt_primitive_len++;
     98     nnt_primitives = realloc(nnt_primitives,
     99                               nnt_primitive_len * sizeof(nnt_primitive));
    100     nnt_primitives[nnt_primitive_len - 1] = newprim; 
    101   }
    102   else nnt_primitives[pindex] = newprim; /* replace the entry */
    103 }
    104 
    105 /* forms definition */
    106 
    107 typedef struct nnt_form_t {
    108   char name[NNT_SYMNAMELEN]; /* form (var/func) name */
    109   char *value;       /* (reallocatable) value pointer (null-terminated) */
    110   uint len;          /* form value length */
    111   int fptr;          /* form character pointer */
    112 } nnt_form;
    113 
    114 static nnt_form *nnt_forms; /* forms table */
    115 static uint nnt_forms_len = 0; /* forms table size */
    116 
    117 /* find a form index by name, -1 if not found */
    118 int nnt_findform(char *name) {
    119   int i;
    120   for(i=0;i<nnt_forms_len;i++)
    121     if(strncmp(nnt_forms[i].name, name, NNT_SYMNAMELEN) == 0)
    122       return i; /* found */
    123   return -1;  /* nothing found */
    124 }
    125 
    126 /* find a free form among previously deleted entries */
    127 int nnt_findfreeform() {
    128   int i;
    129   for(i=0;i<nnt_forms_len;i++)
    130     if(nnt_forms[i].name[0] == NNT_EOF) return i; /* found */
    131   return -1;  /* nothing found */
    132 }
    133 
    134 /* assign a value to a form, creating it if doesn't exist */
    135 void nnt_assignform(char *name, char *value, uint len, int ptr) {
    136   int findex = nnt_findform(name);
    137   if(findex < 0) findex = nnt_findfreeform(); /* search among deleted */
    138   if(findex > -1) { /* existing form: just update the entry */
    139     strncpy(nnt_forms[findex].name, name, NNT_SYMNAMELEN);
    140     nnt_forms[findex].value = realloc(nnt_forms[findex].value, len);
    141     memcpy(nnt_forms[findex].value, value, len);
    142     nnt_forms[findex].len = len; /* also update the length */
    143     nnt_forms[findex].fptr = ptr; /* reset the pointer */
    144   } else { /* create a new form and update the table */
    145     nnt_form newform;
    146     strncpy(newform.name, name, NNT_SYMNAMELEN); /* fill the name */
    147     newform.value = calloc(len, 1); /* allocate the space */
    148     memcpy(newform.value, value, len); /* populate the value */
    149     newform.len = len; /* populate the length */
    150     newform.fptr = ptr;  /* init the pointer */
    151     nnt_forms_len++;
    152     nnt_forms = realloc(nnt_forms, nnt_forms_len * sizeof(nnt_form));
    153     nnt_forms[nnt_forms_len - 1] = newform;
    154   }
    155 }
    156 
    157 /* delete a form by its name */
    158 /* doesn't fully delete the structure, just marks it as free to use */
    159 void nnt_delform(char *name) {
    160   int findex = nnt_findform(name);
    161   if(findex > -1) {
    162     nnt_forms[findex].value = realloc(nnt_forms[findex].value, 0);
    163     nnt_forms[findex].len = 0;
    164     nnt_forms[findex].fptr = 0;
    165     memset(nnt_forms[findex].name, 0, NNT_SYMNAMELEN);
    166     /* null name is a valid form name in TRAC, so we use a marker char */
    167     nnt_forms[findex].name[0] = NNT_EOF;
    168   }
    169 }
    170 
    171 /* form serialization/deserialization using netstrings */
    172 
    173 /* serialize the form and populate the buffer and length */
    174 char* nnt_serializeform(nnt_form *form, char *buf, int *len) {
    175   *len = NNT_SYMNAMELEN + form->len + sizeof(int);
    176   int lbl = snprintf(NULL, 0, "%d:", *len); /* length buffer length */
    177   char *lenbuf = calloc(lbl + 1, 1); 
    178   snprintf(lenbuf, lbl+1, "%d:", *len); /* fill in length buffer */
    179   *len += lbl;
    180   buf = realloc(buf, *len);
    181   memset(buf, 0, *len); /* zero out the buffer */
    182   memcpy(buf, lenbuf, lbl); /* fill the length buffer */
    183   memcpy(buf + lbl, form->name, NNT_SYMNAMELEN); /* fill name */
    184   memcpy(buf + lbl + NNT_SYMNAMELEN, &(form->fptr), sizeof(int)); /* fill fptr */
    185   /* fill value */
    186   memcpy(buf + lbl + NNT_SYMNAMELEN + sizeof(int), form->value, form->len);
    187   buf[(*len) - 1] = ','; /* trailing comma */
    188   free(lenbuf); return buf;
    189 }
    190 
    191 /* deserialize the form given the netstring buffer
    192 (push it into the form storage using nnt_assignform) */
    193 int nnt_deserializeform(char *buf) {
    194   char lenbuf[11] = {0};
    195   int i, rdlen, fullen, rdptr, fid;
    196   for(i=0;buf[i]!=':' && i<11;i++) lenbuf[i] = buf[i];
    197   rdlen = snum(lenbuf); /* practical read length */
    198   fullen = rdlen + strlen(lenbuf);
    199   if(buf[fullen] != ',') return -1; /* invalid netstring detected */
    200   memcpy(&rdptr, &buf[i+1+NNT_SYMNAMELEN], sizeof(int)); /* fetch pointer */
    201   /* name is at i + 1, value is NNT_SYMNAMELEN + sizeof(int) away */
    202   nnt_assignform(&buf[i+1], &buf[i+1+NNT_SYMNAMELEN+sizeof(int)],
    203     rdlen-NNT_SYMNAMELEN-sizeof(int)-1, rdptr);
    204   /* return the total length of processed data */
    205   return fullen;
    206 }
    207 
    208 /* parser logic */
    209 
    210 /* find the matching character */
    211 int nnt_findmatch(char *str, uint len, int curpos, 
    212       uchar c1, uchar c2, int dir) {
    213   int balance = 0, xpos = curpos;
    214   if(xpos >= len) return -1;
    215   for(;xpos >=0 && xpos < len ;xpos += dir) {
    216     if(str[xpos] == c1) balance++;
    217     if(str[xpos] == c2) balance--;
    218     if(!balance) break;
    219   }
    220   return (xpos >= 0 && xpos < len) ? xpos : -1;
    221 }
    222 
    223 /* delete len chars from buf at pos, return new buf length */
    224 int nnt_delchars(char *buf, int buflen, int pos, int len) {
    225   if(len < 1) len = 1; /* safeguard */
    226   if(pos < 0) pos = 0; /* safeguard */
    227   memmove(&buf[pos], &buf[pos+len], buflen - pos - len);
    228   return buflen - len;
    229 }
    230 
    231 /* delete a single character under the pos in the active buffer */
    232 int nnt_delchar(int pos) {
    233   nnt_flen = nnt_delchars(nnt_prog, nnt_flen, pos, 1);
    234   nnt_prog[nnt_flen] = 0;
    235   nnt_alen = nnt_flen - nnt_ascan; /* update active length */
    236   return nnt_flen;
    237 }
    238 
    239 /* insert len chars into buffer dest of length dlen from buffer src at pos */
    240 /* it reallocs dest, updates dlen and returns the resulting dest pointer */
    241 char* nnt_inschars(char *dest, int *dlen, char *src, int len, int pos) {
    242   *dlen += len; /* increase the length */
    243   dest = realloc(dest, *dlen); /* reallocate the buffer */
    244   memmove(&dest[pos+len], &dest[pos], (*dlen)-pos-len); /* free up the space */
    245   memmove(&dest[pos], src, len); /* copy the source */
    246   return dest;
    247 }
    248 
    249 /* replace all occurrences of from with to in str (all null-terminated) */
    250 /* returns the new str pointer (str must be dynamically allocated before) */
    251 char *nnt_replace(char *str, char *from, char *to) {
    252   int len = strlen(str), fromlen = strlen(from), tolen = strlen(to), i;
    253   char *ptr = str;
    254   while((ptr = strstr(ptr, from)) != NULL) { /* find the occurrence */
    255     len = nnt_delchars(str, len, (int) (ptr - str), fromlen); /* delete */
    256     str = nnt_inschars(str, &len, to, tolen, (int) (ptr - str)); /* add */
    257     ptr += tolen; /* move ptr */
    258   }
    259   str[len] = 0; /* ensure null termination in the result */
    260   return str;
    261 }
    262 
    263 /* cl primitive: call string (form) */
    264 char* prim_cl(char *arglist, char *res, int *reslen) {
    265   char *arg = strtok(arglist, NNT_ADEL_S), *name, *seg, snum = 0;
    266   char idstr[3] = {NNT_SEGGAP, 0, 0}; /* update idstr[1] then */
    267   int fid = -1;
    268   /* skip the first one */
    269   name = strtok(NULL, NNT_ADEL_S); /* get the name */
    270   if(name != NULL && (fid = nnt_findform(name)) > -1) {
    271     /* populate the result */
    272     *reslen = nnt_forms[fid].len;
    273     res = realloc(res, *reslen);
    274     memcpy(res, nnt_forms[fid].value, *reslen);  
    275     do { /* receive the segment ids */
    276       seg = strtok(NULL, NNT_ADEL_S);
    277       if(seg != NULL) {
    278         snum++; /* we start from 1 to avoid null bytes */
    279         idstr[1] = snum; /* update the segmentid byte */
    280         res = nnt_replace(res, idstr, seg);
    281         *reslen = strlen(res) + 1;
    282       }
    283     } while(seg != NULL);
    284   }
    285   return res;
    286 }
    287 
    288 /* evaluate a TRAC function based on the neutral string
    289    storing the results into nnt_res and nnt_reslen */
    290 void nnt_evalfunc(int startptr) {
    291   char *arglist, *primname, *arg;
    292   int i, l=0, primindex;
    293   for(i=startptr;nnt_prog[i]!=NNT_EOF;i++,l++);
    294   arglist = calloc(l, 1);
    295   for(i=0;i<l;i++) arglist[i] = nnt_prog[startptr + i];
    296   /* now we have NNT_ADEL-delimited argument list to pass */
    297   nnt_reslen = 0; /* null value is by default */
    298   primname = strtok(arglist, NNT_ADEL_S); /* get primitive name */
    299   primindex = nnt_findprimitive(primname);
    300   if(primindex > -1) { /* primitive found */
    301     if(nnt_trace) { /* trace mode */
    302       fprintf(stderr, "[nntrace] (%s", primname);
    303       while((arg = strtok(NULL, NNT_ADEL_S)) != NULL)
    304         fprintf(stderr, ",%s", arg);
    305       fprintf(stderr, ")");
    306     }
    307     for(i=0;i<l;i++) arglist[i] = nnt_prog[startptr + i]; /* restore */
    308     nnt_res = (*(nnt_primitives[primindex].handler))(arglist, nnt_res, &nnt_reslen);
    309   }
    310   /* now we have some core-level primitives */
    311   else if(strncmp(primname, "hl", 3) == 0) nnt_reslen = -1; /* halt */
    312   else if(strncmp(primname, "tn", 3) == 0) nnt_trace = 1; /* trace on */
    313   else if(strncmp(primname, "tf", 3) == 0) nnt_trace = 0; /* trace off */
    314   else { /* not found, run the default call logic */
    315     if(nnt_trace) { /* trace mode */
    316       fprintf(stderr, "[nntrace] (cl,%s",primname);
    317       while((arg = strtok(NULL, NNT_ADEL_S)) != NULL)
    318         fprintf(stderr, ",%s", arg);
    319       fprintf(stderr, ")");
    320     }
    321     /* expand the arglist */
    322     arglist = realloc(arglist, l + 2);
    323     /* insert a dummy char and the argument delimiter at the start */
    324     arglist[0] = '0'; arglist[1] = NNT_ADEL;
    325     for(i=0;i<l;i++) arglist[i+2] = nnt_prog[startptr + i]; /* restore */
    326     nnt_res = prim_cl(arglist, nnt_res, &nnt_reslen);
    327   }
    328   if(nnt_trace) { /* trace mode */
    329     if(nnt_reslen)
    330       fprintf(stderr, " => %*.*s\r\n", nnt_reslen, nnt_reslen, nnt_res);
    331     else fprintf(stderr, " => null\r\n"); 
    332   }
    333   if(arglist) free(arglist);
    334 }
    335 
    336 /* main TRAC processing algorithm */ 
    337 void nnt_proc(char *prog, uint len) {
    338   nnt_res = malloc(nnt_reslen = 0); /* init the result buffer */
    339   nnt_prog = calloc(len+1, 1);  /* init the neutral+active string buffer */
    340   /* load the entire program into the active string */
    341   memcpy(nnt_prog, prog, len);
    342   uchar cc; /* currently processed character */
    343   int bp, rl, j; /* position buffers for various needs */
    344   nnt_ascan = 0; /* init the pointer and NS length */
    345   nnt_alen = nnt_flen = len; /* init active length with the full length */
    346   while(nnt_alen >= 0) { /* processing loop start */
    347     switch((cc = nnt_prog[nnt_ascan])) { /* rule 1 */
    348       case '(': /* rule 2 */
    349         bp = nnt_findmatch(nnt_prog, nnt_flen, nnt_ascan, '(', ')', 1);
    350         if(bp > -1) { /* copy the entire region into the neutral string */
    351           rl = bp - nnt_ascan; /* nested region length */
    352           nnt_delchar(nnt_ascan); /* delete this paren */
    353           nnt_ascan += rl - 1; /* increment active pointer */
    354           nnt_delchar(nnt_ascan); /* delete matching paren */
    355         }
    356         break;
    357       case '\r': case '\n': case '\t': /* rule 3 */
    358         nnt_delchar(nnt_ascan);
    359         break;
    360       case ',': /* rule 4 */
    361         nnt_prog[nnt_ascan++] = NNT_ADEL; /* argument delimiter */ 
    362         break;
    363       case NNT_SHARP: /* rules 5 to 7 */
    364         switch(nnt_prog[nnt_ascan+1]) {
    365           case '(': /* rule 5 - active function start */
    366             nnt_delchar(nnt_ascan); /* remove # character */
    367             nnt_prog[nnt_ascan++] = NNT_AFST;
    368             break;
    369           case NNT_SHARP: /* rule 6 */
    370             if(nnt_prog[nnt_ascan+2] == '(') { /* neutral function start */
    371               nnt_delchar(nnt_ascan); /* remove # character */
    372               nnt_delchar(nnt_ascan); /* remove another # character */
    373               nnt_prog[nnt_ascan++] = NNT_NFST;
    374             } else nnt_ascan++; /* rule 7 */
    375             break;
    376           default: nnt_ascan++; /* rule 7 */
    377         }
    378         break;
    379       case ')': /* rule 8 */
    380         nnt_prog[nnt_ascan] = NNT_EOF;
    381         for(bp = nnt_ascan-1;bp>0;bp--) /* find function start */
    382           if(nnt_prog[bp] == NNT_AFST || nnt_prog[bp] == NNT_NFST) break;
    383         /* evaluate the function: the result is stored into nnt_res,
    384            its length into nnt_reslen */
    385         nnt_evalfunc(bp + 1); /* skip function start marker */
    386         if(nnt_reslen > 0) {
    387           nnt_prog = nnt_inschars(nnt_prog, &nnt_flen,
    388                                   nnt_res, nnt_reslen, nnt_ascan + 1);
    389           if(nnt_prog[bp] == NNT_AFST) { /* rule 11 */
    390             nnt_flen = nnt_delchars(nnt_prog, nnt_flen, bp, nnt_ascan - bp + 1);
    391             nnt_ascan = bp;
    392           } else if(nnt_prog[bp] == NNT_NFST) { /* rule 12 */
    393             nnt_flen = nnt_delchars(nnt_prog, nnt_flen, bp, nnt_ascan - bp + 1);
    394             nnt_ascan = bp + nnt_reslen;
    395           }
    396         } else if(nnt_reslen == 0) { /* rule 10 */
    397           nnt_flen = nnt_delchars(nnt_prog, nnt_flen, bp, nnt_ascan - bp + 1);
    398           nnt_ascan = bp;    
    399         } else nnt_ascan = nnt_flen + 1; /* negative reslen causes the halt */ 
    400         break;
    401       default: nnt_ascan++; /* rule 9 */
    402     }    
    403     nnt_alen = nnt_flen - nnt_ascan; /* update the length on every step */
    404     nnt_prog[nnt_flen] = 0;
    405   }
    406   /* free the buffers */  
    407   free(nnt_prog);
    408   free(nnt_res);
    409 }
    410 
    411 /* all other nntrac primitives */ 
    412 
    413 /* ps primitive: print string */
    414 char* prim_ps(char *arglist, char *res, int *reslen) {
    415   char *arg = strtok(arglist, NNT_ADEL_S);
    416   /* skip the first one (the ps string) */
    417   do {
    418     arg = strtok(NULL, NNT_ADEL_S);
    419     if(arg != NULL) printf("%s", arg);
    420   } while(arg != NULL);
    421   return res;
    422 }
    423 
    424 /* rc primitive: read char */
    425 char* prim_rc(char *arglist, char *res, int *reslen) {
    426   res = realloc(res, 1);
    427   res[0] = getchar();
    428   *reslen = 1;
    429   return res;
    430 }
    431 
    432 /* rs primitive: read string */
    433 char* prim_rs(char *arglist, char *res, int *reslen) {
    434   int c, i = 0;
    435   res = realloc(res, (*reslen) = 0);
    436   while((c = getchar()) > 0 && c != nnt_meta) {
    437     (*reslen)++;
    438     res = realloc(res, (*reslen) + 1);
    439     res[(*reslen) - 1] = c&255;
    440     res[*reslen] = 0; /* prefill with null terminator */
    441   }
    442   return res = realloc(res, (*reslen) = strlen(res));
    443 }
    444 
    445 /* cm primitive: change meta */
    446 char* prim_cm(char *arglist, char *res, int *reslen) {
    447   char *arg = strtok(arglist, NNT_ADEL_S);
    448   arg = strtok(NULL, NNT_ADEL_S);
    449   if(arg != NULL) nnt_meta = arg[0];
    450   return res;
    451 }
    452 
    453 /* ds primitive: define string (form) */
    454 char* prim_ds(char *arglist, char *res, int *reslen) {
    455   char *arg = strtok(arglist, NNT_ADEL_S), *name, *value;
    456   name = strtok(NULL, NNT_ADEL_S); /* get the name */
    457   if(name != NULL) {
    458     value = strtok(NULL, NNT_ADEL_S); /* get the value */
    459     if(value != NULL) nnt_assignform(name, value, strlen(value), 0);
    460   }
    461   return res;
    462 }
    463 
    464 /* ss primitive: segment string (form) */
    465 char* prim_ss(char *arglist, char *res, int *reslen) {
    466   char *arg = strtok(arglist, NNT_ADEL_S), *name, *seg, sid = 0;
    467   char idstr[3] = {NNT_SEGGAP, 0, 0}; /* update idstr[1] then */
    468   int fid = -1;
    469   /* skip the first one (the ps string) */
    470   name = strtok(NULL, NNT_ADEL_S); /* get the name */
    471   if(name != NULL && (fid = nnt_findform(name)) > -1) {
    472     do { /* receive the segment ids */
    473       seg = strtok(NULL, NNT_ADEL_S);
    474       if(seg != NULL) {
    475         sid++; /* we start from 1 to avoid null bytes */
    476         idstr[1] = sid; /* update the segmentid byte */
    477         nnt_forms[fid].value = nnt_replace(nnt_forms[fid].value, seg, idstr);
    478         nnt_forms[fid].len = strlen(nnt_forms[fid].value) + 1;
    479       }
    480     } while(seg != NULL);
    481   }
    482   nnt_forms[fid].fptr = 0;
    483   return res;
    484 }
    485 
    486 /* cr primitive: call restore */
    487 char* prim_cr(char *arglist, char *res, int *reslen) {
    488   char *arg = strtok(arglist, NNT_ADEL_S), *name;
    489   int fid = -1;
    490   name = strtok(NULL, NNT_ADEL_S);
    491   if(name != NULL && (fid = nnt_findform(name)) > -1)
    492     nnt_forms[fid].fptr = 0;
    493   return res;
    494 }
    495 
    496 /* move form pointer helper */
    497 void nnt_moveptr(nnt_form *form, int offs) {
    498   int i = 0; if(offs < 1) offs = 1;
    499   for(i=0;i<offs;i++) {
    500     form->fptr++;
    501     while(form->value[form->fptr] == NNT_SEGGAP && form->fptr < form->len)
    502       form->fptr += 2; /* skip the gap and the segment id */
    503     if(form->fptr > form->len) {
    504       form->fptr = form->len;
    505       break;
    506     }
    507   }
    508 }
    509 
    510 /* partial character fetch helper (returns -1 if unavailable) */
    511 char nnt_fetchformchar(nnt_form *form, char dir) {
    512   if(dir >= 0) { /* move forward */
    513     while(form->value[form->fptr] == NNT_SEGGAP) {
    514       form->fptr += 2;
    515       if(form->fptr > form->len) { form->fptr = form->len; return -1; }
    516     }
    517   } else { /* move back */
    518     while(form->fptr >= 0 && form->value[form->fptr] == NNT_SEGGAP) {
    519       form->fptr--;
    520       if(form->fptr > 0 && form->value[form->fptr - 1] == NNT_SEGGAP)
    521         form->fptr--; /* skip the segment id byte */
    522       if(form->fptr < 0) { form->fptr = 0; return -1; }
    523     };
    524   }
    525   if(form->fptr >= 0 && form->fptr < form->len)
    526     return form->value[form->fptr];
    527   else return -1;
    528 }
    529 
    530 /* cs primitive: call segment */
    531 char* prim_cs(char *arglist, char *res, int *reslen) {
    532   char *arg = strtok(arglist, NNT_ADEL_S), *name, *def;
    533   int fid = -1;
    534   name = strtok(NULL, NNT_ADEL_S);
    535   def = strtok(NULL, NNT_ADEL_S);
    536   if(name != NULL && (fid = nnt_findform(name)) > -1) {
    537     int sl; /* segment length */
    538     for(sl=0;sl<nnt_forms[fid].len;sl++)
    539       if(nnt_forms[fid].value[nnt_forms[fid].fptr+sl] == NNT_SEGGAP) break;
    540     if(nnt_forms[fid].fptr + sl > nnt_forms[fid].len)
    541       sl = nnt_forms[fid].len - nnt_forms[fid].fptr;
    542     if(sl > 0) {
    543       res = realloc(res, (*reslen) = sl);
    544       memcpy(res, &nnt_forms[fid].value[nnt_forms[fid].fptr], sl);
    545       nnt_moveptr(&nnt_forms[fid], sl); /* move the pointer */
    546     } else if(def != NULL) { /* set the default value */
    547       res = realloc(res, (*reslen) = strlen(def));
    548       memcpy(res, def, *reslen);    
    549     }
    550   }
    551   return res;
    552 }
    553 
    554 /* cc primitive: call character */
    555 char* prim_cc(char *arglist, char *res, int *reslen) {
    556   char *arg = strtok(arglist, NNT_ADEL_S), *name, *def;
    557   int fid = -1;
    558   name = strtok(NULL, NNT_ADEL_S);
    559   def = strtok(NULL, NNT_ADEL_S);
    560   if(name != NULL && (fid = nnt_findform(name)) > -1) {
    561     res = realloc(res, (*reslen) = 1);
    562     res[0] = nnt_fetchformchar(&nnt_forms[fid], 1);
    563     if(res[0] == -1) { /* set the default value */
    564       if(def != NULL) {
    565         res = realloc(res, (*reslen) = strlen(def));
    566         memcpy(res, def, *reslen);
    567       } else *reslen = 0;
    568     }
    569     nnt_moveptr(&nnt_forms[fid], 1); /* move the pointer */
    570   }
    571   return res;
    572 }
    573 
    574 /* cn primitive: call N characters */
    575 char* prim_cn(char *arglist, char *res, int *reslen) {
    576   char *arg = strtok(arglist, NNT_ADEL_S), *name, *offs, *def, c;
    577   int fid = -1, i, roff, ccount = 0;
    578   name = strtok(NULL, NNT_ADEL_S);
    579   offs = strtok(NULL, NNT_ADEL_S);
    580   def = strtok(NULL, NNT_ADEL_S);
    581   if(name != NULL && (fid = nnt_findform(name)) > -1) {
    582     roff = snum(offs); /* real offset */
    583     if(roff > 0) { /* positive offset */
    584       res = realloc(res, (*reslen) = roff + 1);
    585       memset(res, 0, *reslen);
    586       for(i=0;i<roff;i++) {
    587         c = nnt_fetchformchar(&nnt_forms[fid], 1);
    588         if(c < 0) break; else res[i] = c;
    589         nnt_forms[fid].fptr++; ccount++;
    590       }
    591       if(nnt_forms[fid].fptr > nnt_forms[fid].len)
    592         nnt_forms[fid].fptr = nnt_forms[fid].len;
    593       *reslen = strlen(res); /* update the length */
    594     } else if(roff < 0) { /* negative offset */
    595       roff = -roff; /* get absolute value */
    596       res = realloc(res, (*reslen) = roff + 1);    
    597       memset(res, 0, *reslen);
    598       nnt_forms[fid].fptr--; /* start counting from the previous char */
    599       for(i=0;i<roff;i++) {
    600         c = nnt_fetchformchar(&nnt_forms[fid], -1);
    601         if(c < 0) break; else res[i] = c;
    602         nnt_forms[fid].fptr--; ccount++;
    603       }
    604       if(nnt_forms[fid].fptr < 0) nnt_forms[fid].fptr = 0;
    605       *reslen = strlen(res); /* update the length */
    606       res = strrev(res); /* reverse the order */
    607     }
    608     if(ccount && *reslen) /* finalize the allocation */
    609       res = realloc(res, *reslen);
    610     else if(def != NULL) { /* return the default value */
    611       res = realloc(res, (*reslen) = strlen(def));
    612       memcpy(res, def, *reslen);
    613     } else *reslen = 0;
    614   }  
    615   return res;
    616 }
    617 
    618 /* in primitive: initial */
    619 char* prim_in(char *arglist, char *res, int *reslen) {
    620   char *arg = strtok(arglist, NNT_ADEL_S), *name, *subs, *def;
    621   name = strtok(NULL, NNT_ADEL_S);
    622   subs = strtok(NULL, NNT_ADEL_S);
    623   def = strtok(NULL, NNT_ADEL_S);
    624   int fid, i, slen;
    625   if(name != NULL && subs != NULL && (fid = nnt_findform(name)) > -1) {
    626     char *ntv = calloc(nnt_forms[fid].len + 1, 1); /* null-terminated value */
    627     memcpy(ntv, nnt_forms[fid].value, nnt_forms[fid].len);
    628     char *found = strstr(ntv, subs);
    629     if(found != NULL && nnt_forms[fid].fptr < nnt_forms[fid].len) {
    630       slen = ((long) (found - ntv)) - nnt_forms[fid].fptr;
    631       if(slen > 0) {
    632         res = realloc(res, slen + 1);
    633         memset(res, 0, slen + 1); /* null-terminate */
    634         for(i=0;i<slen;i++) { /* copy ignoring the seggaps */
    635           res[i] = nnt_fetchformchar(&nnt_forms[fid], 1);
    636           nnt_moveptr(&nnt_forms[fid], 1);
    637         }
    638         *reslen = strlen(res); /* update the length */
    639         res = realloc(res, *reslen); /* finalize */
    640         nnt_moveptr(&nnt_forms[fid], strlen(subs)); /* move after the fptr */
    641       } else found = NULL;
    642     } else found = NULL;
    643     if(found == NULL && def != NULL) { /* return the default value */
    644       res = realloc(res, (*reslen) = strlen(def));
    645       memcpy(res, def, *reslen);
    646     }
    647     free(ntv);
    648   }
    649   return res;
    650 }
    651 
    652 /* dd primitive: delete definition(s) */
    653 char* prim_dd(char *arglist, char *res, int *reslen) {
    654   char *arg = strtok(arglist, NNT_ADEL_S);
    655   do {
    656     arg = strtok(NULL, NNT_ADEL_S);
    657     if(arg != NULL) nnt_delform(arg);
    658   } while(arg != NULL);
    659   return res;
    660 }
    661 
    662 /* da primitive: delete all forms */
    663 char* prim_da(char *arglist, char *res, int *reslen) {
    664   int i = 0;
    665   for(i=0;i<nnt_forms_len;i++) free(nnt_forms[i].value);
    666   nnt_forms_len = 0;
    667   nnt_forms = realloc(nnt_forms, 0);
    668   return res;
    669 }
    670 
    671 /* eq primitive: string equality */
    672 char* prim_eq(char *arglist, char *res, int *reslen) {
    673   char *arg = strtok(arglist, NNT_ADEL_S), *x1, *x2, *x3, *x4;
    674   x1 = strtok(NULL, NNT_ADEL_S); x2 = strtok(NULL, NNT_ADEL_S);
    675   x3 = strtok(NULL, NNT_ADEL_S); x4 = strtok(NULL, NNT_ADEL_S);
    676   if(x1 && x2 && x3 && x4) {
    677     arg = strcmp(x1, x2) ? x4 : x3;
    678     *reslen = strlen(arg);
    679     res = realloc(res, *reslen);
    680     memcpy(res, arg, *reslen);
    681   }
    682   return res;
    683 }
    684 
    685 /* gr primitive: number inequality */
    686 char* prim_gr(char *arglist, char *res, int *reslen) {
    687   char *arg = strtok(arglist, NNT_ADEL_S), *d1, *d2, *x1, *x2;
    688   d1 = strtok(NULL, NNT_ADEL_S); d2 = strtok(NULL, NNT_ADEL_S);
    689   x1 = strtok(NULL, NNT_ADEL_S); x2 = strtok(NULL, NNT_ADEL_S);
    690   if(x1 && x2 && d1 && d2) {
    691     arg = (snum(d1) > snum(d2)) ? x1 : x2;
    692     *reslen = strlen(arg);
    693     res = realloc(res, *reslen);
    694     memcpy(res, arg, *reslen);
    695   }
    696   return res;
    697 }
    698 
    699 /* theoretically we can safely operate from -2**62 to 2**62 */
    700 #define ARITH_LIMIT ((long long) (((unsigned long long) - 1LL) >> 2))
    701 #define BITWISE_BITS 32
    702 #define BITWISE_LIMIT (unsigned long long) ((1LL<<BITWISE_BITS) - 1LL)
    703 
    704 /* reusable arithmetic ops helper */
    705 char* handle_dec_result(long long nres, char *ovr, char *res, int *reslen) {
    706   if(nres <= ARITH_LIMIT && nres > -ARITH_LIMIT) { /* valid result */
    707     res = n2s(nres, res, reslen);
    708   } else if(ovr) { /* return the overflow/underflow value */
    709     *reslen = strlen(ovr);
    710     res = realloc(res, *reslen); /* resize to actual length */
    711     memcpy(res, ovr, *reslen); /* fill the overflow value */
    712   }
    713   return res;
    714 }
    715 
    716 /* ad primitive: addition (overflow arg is optional) */
    717 char* prim_ad(char *arglist, char *res, int *reslen) {
    718   char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs, *ovr;
    719   as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S);
    720   ovr = strtok(NULL, NNT_ADEL_S);
    721   if(as && bs) res = handle_dec_result(snum(as) + snum(bs), ovr, res, reslen);
    722   return res;
    723 }
    724 
    725 /* su primitive: subtraction (overflow arg is optional) */
    726 char* prim_su(char *arglist, char *res, int *reslen) {
    727   char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs, *ovr;
    728   as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S);
    729   ovr = strtok(NULL, NNT_ADEL_S);
    730   if(as && bs) res = handle_dec_result(snum(as) - snum(bs), ovr, res, reslen);
    731   return res;
    732 }
    733 
    734 /* ml primitive: multiplication (overflow arg is optional) */
    735 char* prim_ml(char *arglist, char *res, int *reslen) {
    736   char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs, *ovr;
    737   as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S);
    738   ovr = strtok(NULL, NNT_ADEL_S);
    739   if(as && bs) res = handle_dec_result(snum(as) * snum(bs), ovr, res, reslen);
    740   return res;
    741 }
    742 
    743 /* dv primitive: division (overflow arg is optional) */
    744 char* prim_dv(char *arglist, char *res, int *reslen) {
    745   char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs, *ovr;
    746   as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S);
    747   ovr = strtok(NULL, NNT_ADEL_S);
    748   if(as && bs) {
    749    long long b = snum(bs);
    750    if(b) res = handle_dec_result(snum(as) / b, ovr, res, reslen);
    751    else if(ovr) { /* division by zero, return ovr */
    752      *reslen = strlen(ovr);
    753      res = realloc(res, *reslen); /* resize to actual length */
    754      memcpy(res, ovr, *reslen); /* fill the overflow value */
    755    }
    756   }
    757   return res;
    758 }
    759 
    760 /* bu primitive: bitwise union (OR) */
    761 char* prim_bu(char *arglist, char *res, int *reslen) {
    762   char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs;
    763   as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S);
    764   if(as && bs) res = handle_dec_result((snum(as) | snum(bs)) & BITWISE_LIMIT, 
    765                        NULL, res, reslen);
    766   return res;
    767 }
    768 
    769 /* bi primitive: bitwise intersect (AND) */
    770 char* prim_bi(char *arglist, char *res, int *reslen) {
    771   char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs;
    772   as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S);
    773   if(as && bs) res = handle_dec_result((snum(as) & snum(bs)) & BITWISE_LIMIT,
    774                        NULL, res, reslen);
    775   return res;
    776 }
    777 
    778 /* [new] bx primitive: bitwise exclusive or (XOR) */
    779 char* prim_bx(char *arglist, char *res, int *reslen) {
    780   if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */
    781   char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs;
    782   as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S);
    783   if(as && bs) res = handle_dec_result((snum(as) ^ snum(bs)) & BITWISE_LIMIT,
    784                        NULL, res, reslen);
    785   return res;
    786 }
    787 
    788 /* bc primitive: bitwise complement (NOT) */
    789 char* prim_bc(char *arglist, char *res, int *reslen) {
    790   char *arg = strtok(arglist, NNT_ADEL_S), *as = strtok(NULL, NNT_ADEL_S);
    791   if(as) res = handle_dec_result((~snum(as)) & BITWISE_LIMIT,
    792                  NULL, res, reslen);
    793   return res;
    794 }
    795 
    796 /* br primitive: bitwise rotation */
    797 char* prim_br(char *arglist, char *res, int *reslen) {
    798   char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs;
    799   as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S);
    800   if(as && bs) { 
    801     long long a = snum(as), b = snum(bs), nres;
    802     /* we rotate b according to the value of a */
    803     b &= BITWISE_LIMIT; /* bit-clip the value */
    804     if(a > 0) { /* rotate left */
    805       if(a > BITWISE_BITS) a %= BITWISE_BITS;
    806       nres = (b << a) | (b >> (BITWISE_BITS - a));
    807     } else if(a < 0) { /* rotate right */
    808       a = -a;
    809       if(a > BITWISE_BITS) a %= BITWISE_BITS;
    810       nres = (b << (BITWISE_BITS - a)) | (b >> a);
    811     }
    812     else nres = b; /* no rotation */
    813     res = handle_dec_result(nres & BITWISE_LIMIT, NULL, res, reslen);
    814   }
    815   return res;
    816 }
    817 
    818 /* bs primitive: bitwise shift */
    819 char* prim_bs(char *arglist, char *res, int *reslen) {
    820   char *arg = strtok(arglist, NNT_ADEL_S), *as, *bs;
    821   as = strtok(NULL, NNT_ADEL_S); bs = strtok(NULL, NNT_ADEL_S);
    822   if(as && bs) { 
    823     long long a = snum(as), b = snum(bs), nres;
    824     /* we shift b according to the value of a */
    825     if(a > 0) nres = b << a; /* shift left */
    826     else if(a < 0) nres = b >> (-a); /* shift right */
    827     else nres = b; /* no shift */
    828     res = handle_dec_result(nres & BITWISE_LIMIT, NULL, res, reslen);
    829   }
    830   return res;
    831 }
    832 
    833 /* ln primitive: list names */
    834 char* prim_ln(char *arglist, char *res, int *reslen) {
    835   char *arg = strtok(arglist, NNT_ADEL_S), *pref = strtok(NULL, NNT_ADEL_S);
    836   int i;
    837   for(i=0;i<nnt_forms_len;i++) {
    838     if(nnt_forms[i].name[0] != NNT_EOF) {
    839       if(pref) printf("%s", pref);
    840       printf("%s", nnt_forms[i].name);
    841     }  
    842   }
    843   return res;
    844 }
    845 
    846 /* pf primitive: print form */
    847 char* prim_pf(char *arglist, char *res, int *reslen) {
    848   char *arg = strtok(arglist, NNT_ADEL_S), *fname = strtok(NULL, NNT_ADEL_S);
    849   if(fname != NULL) {
    850     int i = nnt_findform(fname), j;
    851     if(i > -1) { /* form found */
    852       for(j=0;j<=nnt_forms[i].len;j++) {
    853         if(j == nnt_forms[i].fptr) printf("<^>");
    854         if(nnt_forms[i].value[j] == NNT_SEGGAP)
    855           printf("<%u>", (unsigned int) nnt_forms[i].value[++j]);
    856         else printf("%c",nnt_forms[i].value[j]);
    857       }
    858     }
    859   }
    860   return res;
    861 }
    862 
    863 /* filesystem primitives */
    864 
    865 /* sb primitive: store block */
    866 char *prim_sb(char *arglist, char *res, int *reslen) {
    867   if(nnt_mode == NNT_SECURE) return res; /* prevent running in secure mode */
    868   char *arg = strtok(arglist, NNT_ADEL_S), *fname, *fmname;
    869   fname = strtok(NULL, NNT_ADEL_S);
    870   if(fname != NULL) { /* output file name */
    871     FILE *hnd = fopen(fname, "wb"); /* open the output file */
    872     if(hnd != NULL) {
    873       char *vbuf = malloc(0); /* value buffer */
    874       int findex, vlen = 0; /* value len */
    875       do {
    876         fmname = strtok(NULL, NNT_ADEL_S); /* form name */
    877         if(fmname != NULL) {
    878           findex = nnt_findform(fmname);
    879           if(findex > -1) {
    880             vbuf = nnt_serializeform(&nnt_forms[findex], vbuf, &vlen);
    881             fwrite(vbuf, vlen, 1, hnd); /* write the value out */
    882             nnt_delform(fmname); /* delete after exporting as per the spec */
    883           }
    884         }
    885       } while(fmname != NULL);
    886       fclose(hnd); /* close the output file */
    887       free(vbuf); /* free value buffer */
    888     }
    889   }  
    890   return res;
    891 }
    892 
    893 /* full file read helper */
    894 char* nnt_readfile(FILE *hnd, char *fbuf, int *buflen) {
    895   fseek(hnd, 0, SEEK_END);
    896   *buflen = ftell(hnd);
    897   fseek(hnd, 0, SEEK_SET);
    898   fbuf = realloc(fbuf, *buflen); /* allocate the full buffer */
    899   if(fbuf == NULL) return NULL; /* if allocation failed */    
    900   fread(fbuf, 1, *buflen, hnd); /* read the entire file */
    901   return fbuf;
    902 }
    903 
    904 /* fb primitive: fetch block */
    905 char *prim_fb(char *arglist, char *res, int *reslen) {
    906   if(nnt_mode == NNT_SECURE) return res; /* prevent running in secure mode */
    907   char *arg = strtok(arglist, NNT_ADEL_S), *fname = strtok(NULL, NNT_ADEL_S);
    908   if(fname != NULL) { /* input file name */
    909     FILE *hnd = fopen(fname, "rb"); /* open the input file */
    910     if(hnd != NULL) {
    911       char *fbuf = malloc(0);
    912       int flen;
    913       fbuf = nnt_readfile(hnd, fbuf, &flen);
    914       fclose(hnd);
    915       int i = 0, dlen; /* running index and length */
    916       while(i < flen) { /* form queue to deserialize */
    917         dlen = nnt_deserializeform(&fbuf[i]);
    918         if(dlen < 0) break;
    919         i += dlen + 1;
    920       }
    921       free(fbuf); /* free file buffer */
    922     }
    923   }
    924   return res;
    925 }
    926 
    927 /* [new] sf primitive: store raw file */
    928 char *prim_sf(char *arglist, char *res, int *reslen) {
    929   if(nnt_mode == NNT_SECURE) return res; /* prevent running in secure mode */
    930   if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */
    931   char *arg = strtok(arglist, NNT_ADEL_S), *fname, *fmname;
    932   fname = strtok(NULL, NNT_ADEL_S);  /* file name */
    933   fmname = strtok(NULL, NNT_ADEL_S); /* form name */
    934   if(fname != NULL && fmname != NULL) {
    935     int fid = nnt_findform(fmname);
    936     if(fid > -1) {
    937       FILE *hnd = fopen(fname, "wb"); /* open the output file */
    938       if(hnd != NULL) { /* write the value out */
    939         fwrite(nnt_forms[fid].value, nnt_forms[fid].len, 1, hnd); 
    940         fclose(hnd);
    941       }
    942     }
    943   }
    944   return res;
    945 }
    946 
    947 /* [new] ff primitive: fetch raw file */
    948 char *prim_ff(char *arglist, char *res, int *reslen) {
    949   if(nnt_mode == NNT_SECURE) return res; /* prevent running in secure mode */
    950   if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */
    951   char *arg = strtok(arglist, NNT_ADEL_S), *fname, *fmname;
    952   fname = strtok(NULL, NNT_ADEL_S);  /* file name */
    953   fmname = strtok(NULL, NNT_ADEL_S); /* form name */
    954   if(fname != NULL && fmname != NULL) {
    955     FILE *hnd = fopen(fname, "rb"); /* open the input file */
    956     if(hnd != NULL) {
    957       char *fbuf = malloc(0);
    958       int flen;
    959       fbuf = nnt_readfile(hnd, fbuf, &flen);
    960       fclose(hnd);
    961       nnt_assignform(fmname, fbuf, flen, 0); /* store it into the form */
    962       free(fbuf); /* and free the resources */
    963     }  
    964   }
    965   return res;
    966 }
    967 
    968 /* eb primitive: erase block or raw file */
    969 char *prim_eb(char *arglist, char *res, int *reslen) {
    970   if(nnt_mode == NNT_SECURE) return res; /* prevent running in secure mode */
    971   char *arg = strtok(arglist, NNT_ADEL_S), *fname = strtok(NULL, NNT_ADEL_S);
    972   if(fname != NULL) remove(fname); /* delete the input file name */
    973   return res;
    974 }
    975 
    976 /* mode primitive: set operation mode */
    977 char *prim_mo(char *arglist, char *res, int *reslen) {
    978   if(nnt_mlock == 1) return res; /* mode change is already locked */
    979   char *arg = strtok(arglist, NNT_ADEL_S), *modechar, *mlk;
    980   modechar = strtok(NULL, NNT_ADEL_S);
    981   if(modechar != NULL) {
    982     switch(modechar[0]) {
    983       case 'S': nnt_mode = NNT_SECURE; break;
    984       case 'L': nnt_mode = NNT_LEGACY; break;
    985       case 'E': nnt_mode = NNT_NORMAL; break;
    986     }
    987     mlk = strtok(NULL, NNT_ADEL_S);
    988     if(mlk != NULL && mlk[0] == 'L') nnt_mlock = 1; /* set mode lock */
    989   }
    990   return res;
    991 }
    992 
    993 /* [new] ac primitive: ASCII code */
    994 char *prim_ac(char *arglist, char *res, int *reslen) {
    995   if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */
    996   char *arg = strtok(arglist, NNT_ADEL_S), *cvs = strtok(NULL, NNT_ADEL_S);
    997   if(cvs != NULL) res = n2s((uchar) cvs[0], res, reslen);
    998   return res;
    999 }
   1000 
   1001 /* [new] av primitive: ASCII value */
   1002 char *prim_av(char *arglist, char *res, int *reslen) {
   1003   if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */
   1004   char *arg = strtok(arglist, NNT_ADEL_S), *cns = strtok(NULL, NNT_ADEL_S);
   1005   long long rcode = 0;
   1006   if(cns != NULL) rcode = snum(cns);
   1007   res = realloc(res, (*reslen) = 1);
   1008   res[0] = rcode&255;
   1009   return res;
   1010 }
   1011 
   1012 /* [new] fn primitive: format number */
   1013 char *prim_fn(char *arglist, char *res, int *reslen) {
   1014   if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */
   1015   char *arg = strtok(arglist, NNT_ADEL_S), *fmt, *cns;
   1016   fmt = strtok(NULL, NNT_ADEL_S);
   1017   cns = strtok(NULL, NNT_ADEL_S);
   1018   long long num = 0;
   1019   if(fmt != NULL && cns != NULL) {
   1020     num = snum(cns);
   1021     *reslen = snprintf(NULL, 0, fmt, num); /* estimate the size */
   1022     res = realloc(res, 1 + (*reslen));
   1023     snprintf(res, 1 + (*reslen), fmt, num); /* actually format the number */
   1024     res = realloc(res, (*reslen) = strlen(res)); /* final reallocation */   
   1025   }
   1026   return res;
   1027 }
   1028 
   1029 #ifndef NNT_NO_EXTSHELL
   1030 /* [new] os primitive: run external OS command */
   1031 char *prim_os(char *arglist, char *res, int *reslen) {
   1032   if(nnt_mode == NNT_SECURE) return res; /* prevent running in secure mode */
   1033   if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */
   1034   char *arg = strtok(arglist, NNT_ADEL_S), *cmd = strtok(NULL, NNT_ADEL_S);
   1035   int rescode;
   1036   if(cmd != NULL) {
   1037     rescode = system(cmd); /* run the command and get the status code */
   1038     res = n2s(rescode, res, reslen);
   1039   }
   1040   return res;
   1041 }
   1042 #endif
   1043 
   1044 /* [new] tm primitive: local/UTC/Epoch time */
   1045 char *prim_tm(char *arglist, char *res, int *reslen) {
   1046   if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */
   1047   char *arg = strtok(arglist, NNT_ADEL_S), *fmt = strtok(NULL, NNT_ADEL_S),
   1048        *opt = strtok(NULL, NNT_ADEL_S), utc = !!(opt!=NULL && opt[0]=='U');
   1049   if(fmt != NULL) { /* format string exists */
   1050     time_t curtime = time(NULL);
   1051     if(fmt[0] == 'E' && fmt[1] == 0) res = n2s(curtime, res, reslen);
   1052     else {
   1053       struct tm *tminfo = utc ? gmtime(&curtime) : localtime(&curtime);
   1054       res = realloc(res, (*reslen) = 512);
   1055       memset(res, 0, *reslen);
   1056       *reslen = strftime(res, *reslen, fmt, tminfo); /* populate the time */
   1057       res = realloc(res, *reslen); /* shrink the result */
   1058     }
   1059   }
   1060   return res;
   1061 }
   1062 
   1063 static unsigned long long nnt_rngs; /* PRNG state*/    
   1064 unsigned long long xorshift64s() {
   1065   nnt_rngs^=(nnt_rngs>>12);nnt_rngs^=(nnt_rngs<<25);nnt_rngs^=(nnt_rngs>>27);
   1066   unsigned long long inter = (nnt_rngs * 0x2545F4914F6CDD1DULL) >> 32;
   1067   nnt_rngs^=(nnt_rngs>>12);nnt_rngs^=(nnt_rngs<<25);nnt_rngs^=(nnt_rngs>>27);
   1068   return (inter << 32) | ((nnt_rngs * 0x2545F4914F6CDD1DULL) & 0xFFFFFFFF);
   1069 }
   1070 
   1071 /* [new] rn primitive: (pseudo)random number */
   1072 char *prim_rn(char *arglist, char *res, int *reslen) {
   1073   if(nnt_mode == NNT_LEGACY) return res; /* prevent running in legacy mode */
   1074   char *arg = strtok(arglist, NNT_ADEL_S), *froms = strtok(NULL, NNT_ADEL_S),
   1075        *tos = strtok(NULL, NNT_ADEL_S);
   1076   long long a = (froms == NULL) ? 0 : snum(froms),
   1077             b = (tos == NULL) ? ARITH_LIMIT : snum(tos);
   1078   res = n2s(a == b ? a : (a + xorshift64s() % (b - a)), res, reslen);
   1079   return res;
   1080 }
   1081 
   1082 /* init the resources and built-in primitives */
   1083 void nnt_init() {
   1084   nnt_forms = malloc(0); /* init the forms table */
   1085   nnt_primitives = malloc(0); /* init the primitives table */
   1086   nnt_rngs = time(NULL); xorshift64s(); /* init the PRNG */
   1087   nnt_regprimitive("ps", &prim_ps);
   1088   nnt_regprimitive("rc", &prim_rc);
   1089   nnt_regprimitive("rs", &prim_rs);
   1090   nnt_regprimitive("cm", &prim_cm);
   1091   nnt_regprimitive("ds", &prim_ds);
   1092   nnt_regprimitive("ss", &prim_ss);
   1093   nnt_regprimitive("cl", &prim_cl);
   1094   nnt_regprimitive("cr", &prim_cr);
   1095   nnt_regprimitive("cc", &prim_cc);
   1096   nnt_regprimitive("cs", &prim_cs);
   1097   nnt_regprimitive("cn", &prim_cn);
   1098   nnt_regprimitive("in", &prim_in);
   1099   nnt_regprimitive("dd", &prim_dd);
   1100   nnt_regprimitive("da", &prim_da);
   1101   nnt_regprimitive("eq", &prim_eq);
   1102   nnt_regprimitive("gr", &prim_gr);
   1103   nnt_regprimitive("ad", &prim_ad);
   1104   nnt_regprimitive("su", &prim_su);
   1105   nnt_regprimitive("ml", &prim_ml);
   1106   nnt_regprimitive("dv", &prim_dv);
   1107   nnt_regprimitive("bu", &prim_bu);
   1108   nnt_regprimitive("bi", &prim_bi);
   1109   nnt_regprimitive("bx", &prim_bx);
   1110   nnt_regprimitive("bc", &prim_bc);
   1111   nnt_regprimitive("br", &prim_br);
   1112   nnt_regprimitive("bs", &prim_bs);
   1113   nnt_regprimitive("ln", &prim_ln);
   1114   nnt_regprimitive("pf", &prim_pf);
   1115   nnt_regprimitive("sb", &prim_sb);
   1116   nnt_regprimitive("fb", &prim_fb);
   1117   nnt_regprimitive("eb", &prim_eb);
   1118   nnt_regprimitive("mo", &prim_mo);
   1119   nnt_regprimitive("ac", &prim_ac);
   1120   nnt_regprimitive("av", &prim_av);
   1121   nnt_regprimitive("fn", &prim_fn);
   1122   nnt_regprimitive("ff", &prim_ff);
   1123   nnt_regprimitive("sf", &prim_sf);
   1124 #ifndef NNT_NO_EXTSHELL
   1125   nnt_regprimitive("os", &prim_os);
   1126 #endif
   1127   nnt_regprimitive("tm", &prim_tm);
   1128   nnt_regprimitive("rn", &prim_rn);
   1129 }
   1130 
   1131 /* free the interpreter resources */
   1132 void nnt_finish() {  
   1133   prim_da(NULL, nnt_res, &nnt_reslen); /* free internal form resources */
   1134   if(nnt_primitives) free(nnt_primitives);
   1135   if(nnt_forms) free(nnt_forms);
   1136 } 
   1137 
   1138 /* non-embed entry point */
   1139 #ifndef NNT_EMBED
   1140 int main(int argc, char *argv[]) {
   1141   char *fname = "-", *prog, *buf; /* stdin by default */
   1142   int proglen, i, nlen;
   1143   if(argc > 1) fname = argv[1];
   1144   FILE* fd = stdin;
   1145   if(!(fname[0] == '-' && fname[1] == 0)) {
   1146     fd = fopen(fname, "rb");
   1147     if(fd == NULL) {perror("Error");return 1;}
   1148   }
   1149   if(fd == stdin) { /* interactive session */
   1150     proglen = strlen(prog = "#(ps,#(rs))");
   1151     fprintf(stderr, "%s\n", "nntrac by Luxferre, 2023, public domain");
   1152   } else prog = nnt_readfile(fd, malloc(0), &proglen);
   1153   nnt_init(); /* init processing resources */
   1154   if(argc > 1) { /* populate nnt-argc and nnt-argv forms */
   1155     nlen = snprintf(NULL, 0, "%d", argc - 1);
   1156     buf = calloc(nlen+1, 1);
   1157     snprintf(buf, nlen+1, "%d", argc - 1);
   1158     nnt_assignform("nnt-argc", buf, nlen, 0);
   1159     buf = realloc(buf, 1); /* reuse the buffer for nnt-argv */
   1160     buf[0] = 0; /* ensure it's an empty string */
   1161     char segsep[3] = {NNT_SEGGAP, 1, 0};
   1162     for(i=1,nlen=0;i<argc;i++) {
   1163       nlen += strlen(argv[i]) + 2;
   1164       buf = realloc(buf, nlen);
   1165       strcat(buf, argv[i]); /* append the parameter */
   1166       strcat(buf, segsep);  /* and the segment separator */
   1167     }
   1168     nnt_assignform("nnt-argv", buf, nlen, 0);
   1169     free(buf);
   1170   }
   1171   nnt_proc(prog, proglen); /* start main processor */
   1172   nnt_finish(); /* finalize processing resources */
   1173   fclose(fd);
   1174   puts(""); /* just print a newline before exiting */
   1175   if(fd != stdin) free(prog); /* free the input buffer */
   1176   return 0;
   1177 }
   1178 #endif