/*      Copyright (C) 2001, 2002, 2003, 2004 Stijn van Dongen
 *
 * This file is part of Zoem. You can redistribute and/or modify Zoem under the
 * terms of the GNU General Public License;  either version 2 of the License or
 * (at your option) any later  version.  You should have received a copy of the
 * GPL along with Zoem, in the file COPYING.
*/

/* TODO
 *    inspect#4 is bugly and uggy.
*/

#include "ops.h"

#include <ctype.h>
#include <stdlib.h>
#include <regex.h>
#include <limits.h>
#include <float.h>
#include <unistd.h>
#include <math.h>

#include "ops-xtag.h"
#include "ops-grape.h"
#include "ops-ref.h"
#include "ops-counter.h"
#include "ops-constant.h"
#include "ops-env.h"
#include "op-inspect.h"
#include "op-format.h"

#include "util.h"
#include "digest.h"
#include "key.h"
#include "filter.h"
#include "source.h"
#include "segment.h"
#include "parse.h"
#include "read.h"
#include "curly.h"
#include "iface.h"

#include "util/ting.h"
#include "util/ding.h"
#include "util/hash.h"
#include "util/io.h"
#include "util/minmax.h"
#include "util/err.h"
#include "util/types.h"
#include "util/let.h"
#include "util/minmax.h"

static  double precision_g     =  1e-8;

long roundl (double f) { return (long) floor(f + 0.5) ; }

#define I_BANG_1     "{any}"
#define J_BANG_1     "strip curlies, put any"

#define I_DONE_0     ""
#define J_DONE_0     "quit current stack (use at file level)"

#define I_BANG_0     ""
#define J_BANG_0     "make slash"

#define I_XTAG_1     "{_any_}"
#define J_XTAG_1     "xml tag sugar"

#define I_XTAG_2     "{_any_}{_any_}"
#define J_XTAG_2     "xml tag+content sugar"

#define I_DEF_2      "{ks}{any}"
#define J_DEF_2      "define key; complain if key exists"

#define I_FORMAT_2   "{cst}{_va_}"
#define J_FORMAT_2   "format varargs according to format string"

#define I_DEFX_2     "{ks}{_any_}"
#define J_DEFX_2     "define key; expand before storing"

#define I_LET_1      "{algebra}"
#define J_LET_1      "evaluate arithmetic expression"

#define I_VANISH_1   "{any}"
#define J_VANISH_1   "process any for side effects only"

#define I_TRY_1      "{any}"
#define J_TRY_1      "write status/result in __zoemstat__/__zoemput__"

#define I_SET_2      "{ks}{any}"
#define J_SET_2      "do not complain if key exists"

#define I_SETX_2      "{ks}{_any_}"
#define J_SETX_2      "expand definition before storing"

#define I_THROW_2     "{error|towel}{_any_}"
#define J_THROW_2     "throw error/towell, print msg _any_"

#define I_UCASE_1     "{_any_}"
#define J_UCASE_1     "convert any to uppercase (ASCII only)"

#define I_ALPHA_1     "{_int_}"
#define J_ALPHA_1     "put letter in place"

#define I_ROMAN_1     "{_int_}"
#define J_ROMAN_1     "put roman representation of numeral in place"

#define I_APPLY_2     "{_ks|ak_}{_va_}"
#define J_APPLY_2     "apply (possibly anon) key ex to vararg"

#define I_LENGTH_1     "{_any_}"
#define J_LENGTH_1     "evaluate and put length in place"

#define I_WHILE_2     "{_int_}{_any_}"
#define J_WHILE_2     "eval any while int nonzero"

#define I_DOWHILE_2   "{_any_}{_int_}"
#define J_DOWHILE_2   "eval any until int nonzero"

#define I_TABLE_5     "{_int_}{lft}{sep}{rgt}{_va_}"
#define J_TABLE_5     "create array-like structure"

#define I_SYSTEM_3    "{cmd}{_va_}{_any_}"
#define J_SYSTEM_3    "pipe any through cmd with arguments va"

#define I_ENV_3       "{lbl}{any}{any}"
#define J_ENV_3       "associate strings with env lbl"

#define I_BEGIN_2     "{lbl}{va}"
#define J_BEGIN_2     "begin env with args"

#define I_END_1       "{lbl}"
#define J_END_1       "end env"

#define I_TRACE_1     "{_int_}"
#define J_TRACE_1     "set trace flags"

#define I_WRITE_3     "{_fn_}{cst}{_any_}"
#define J_WRITE_3     "write any via filter (copy|txt|device) to file"

#define I_DOFILE_2    "{_fn_}{chr(!?)chr(+-)}"
#define J_DOFILE_2    "read file with input/existence modes"

#define I_PROTECT_1  "{_any_}"
#define J_PROTECT_1  "escape contents"

#define I_FINSERT_1  "{_fn_}"
#define J_FINSERT_1  "put escaped contents in place"

#define I_ZINSERT_1  "{_fn_}"
#define J_ZINSERT_1  "put unmodified contents in place"

#define I_INSPECT_4   "{cst+}{_reg_}{_any|ak_}{_any_}"
#define J_INSPECT_4   "apply regex to any"

#define I_TR_4       "{chr(cds)*{list}{list}}{_any_}"
#define J_TR_4       "translate chars in any"

#define I_REF_2      "{lbl}{chr(ntlcm)}"
#define J_REF_2      "get field associated with lbl"

#define I_REFLOAD_6  "{lbl}{int}{any}{any}{any}{any}"
#define J_REFLOAD_6  "load lev, typ, num, title, misc"

#define I_LINE_0     ""
#define J_LINE_0     "put current line number in current file"

#define I_REDIRECT_1 "{_fn_}"
#define J_REDIRECT_1 "change default output file"

#define I_QUIT_0     ""
#define J_QUIT_0     "quit parsing current stack in current file"

#define I_ERROR_1     "_any_"
#define J_ERROR_1     "expand any, print error message, raise error"

#define I_EVAL_1     "_any_"
#define J_EVAL_1     "expand any, pass it on for further expansion"

#define I_EXIT_0     ""
#define J_EXIT_0     "out through the door without opening it"

#define I_PUSH_1     "{user|dollar}"
#define J_PUSH_1     "push a new dollar/user dictionary"

#define I_POP_1      "{user|dollar}"
#define J_POP_1      "pop the top dollar/user dictionary"

#define I_CTRSET_2   "{_lbl_}{_int_}"
#define J_CTRSET_2   "set counter"

#define I_CTRPUT_1   "{_lbl_}"
#define J_CTRPUT_1   "put counter"

#define I_CTRADD_2   "{_lbl_}{_int_}"
#define J_CTRADD_2   "add to counter"

#define I_FV_2     "{nm}{_va_}"
#define J_FV_2     "apply operator to ops"

#define I_F_3     "{nm}{_num_}{_num_}"
#define J_F_3     "apply operator to ops"

#define I_F_2     "{nm}{_num_}"
#define J_F_2     "apply operator to op"

#define I_CMP_3      "{cst}{_any_}{_any_}"
#define J_CMP_3      "apply (lt|lq|eq|gq|gt|ne|cp) to ops; put int"

#define I_EQT_3     "{cst}{_num_}{_num_}"
#define J_EQT_3     "apply (lt|lq|eq|gq|gt|ne|cp) to ops; put int"

#define I_IF_3       "{_int_}{any}{any}"
#define J_IF_3       "if int any else any"

#define I_DEFINED_2  "{nm}{_any_}"
#define J_DEFINED_2  "puts int; nm in (key|lkey|data|ctr)"

#define I_UNDEF_1    "{ks}"
#define J_UNDEF_1    "delete key"

#define I_SWITCH_2   "{_any_}{va}"
#define J_SWITCH_2   "use pivot to select from va"

#define I_BRANCH_1   "{va}"
#define J_BRANCH_1   "va contains condition/branch pairs"

#define I_DSET_2     "{_any|va_}{any|va}"
#define J_DSET_2     "access sequence, value or values"

#define I_DSETX_2    "{_any|va_}{_any|va_}"
#define J_DSETX_2    "access sequence, value or values"

#define I_GRAPE1_1     "{_any|va_}"
#define J_GRAPE1_1     "access sequence for getting"

#define I_GRAPE2_1     "{_any|va_}"
#define J_GRAPE2_1     "access sequence for freeing"

#define I_GRAPE3_1     "{_any|va_}"
#define J_GRAPE3_1     "access sequence for dumping"

#define I_DFREE_1    "{_any|va_}"
#define J_DFREE_1    "access sequence"

#define I_DPRINT_1   "{_any|va_}"
#define J_DPRINT_1   "access sequence"

#define I_SPECIAL_1  "{va}"
#define J_SPECIAL_1  "pairs of ascii num/mapping"

#define I_CONSTANT_1 "{va}"
#define J_CONSTANT_1 "pairs of label/mapping"

#define I_CATCH_2   "{towel|error}{any}"
#define J_CATCH_2   "catch exception=towel and/or error"

#define I_DOLLAR_2   "{str}{any}"
#define J_DOLLAR_2   "put any if \\__device__==str"

#define I_FORMATTED_1 "{any}"
#define J_FORMATTED_1 "remove ws in any, translate \\%nst<>%"


#define F_DEVICE     "device filter (customize with \\special#1)"
#define F_TXT        "interprets [\\\\][\\~][\\,][\\|][\\}][\\{]"
#define F_COPY       "identity filter (literal copy)"

#define  READ_INPUT     1           /* uses default file & filter */
#define  READ_IMPORT    2           /* interpretation only        */
#define  READ_READ      4           /* uses default file & filter */
#define  READ_LOAD      8           /* interpretation only        */

const char *strLegend[]
=  {
""
"L e g e n d",
"Ab   Meaning            Examples/explanation",
"--| |----------------| |---------------------------------------------------|",
"ks   key signature      e.g. <foo#2> or <\"+\"#2> (without the <>)",
"ak   anonymous key      e.g. <_#2{foo{\\1}\\bar{\\1}{\\2}}>",
"any  anything           first expanded by the zoem primitive",
"va   vararg             first expanded by the zoem primitve",
"int  integer            e.g. '123', '-6', no arithmetic expressions allowed",
"lbl  label              names for counters, refs, constants, env",
"cst  constant           constant; one of a fixed set",
"nm   name               name; one of a fixed set",
"chr  character          presumably a switch of some kind",
"str  string             presumably a label of some kind",
"fn   file name          a string that will be used as name of file",
"",
"[]-enclosed stuff is optional",
"()-enclosed stuff denotes a choice between alternatives",
"* denotes zero or more occurrences",
"_-enclosed arguments are first expanded by the primitive.",
"So, _int_ denotes an arbitrary expression that should evaluate to an integer",
NULL
   }
;

static   mcxHash*    yamTable_g        =  NULL;    /* primitives        */
static   mcxTing*    devtxt_g          =  NULL;    /* "__device__"    */

const char *strComposites[]
=  {
   "\\def{input#1}{\\dofile{\\1}{!+}}",
   "\\def{import#1}{\\dofile{\\1}{!-}}",
   "\\def{read#1}{\\dofile{\\1}{?+}}",
   "\\def{load#1}{\\dofile{\\1}{?-}}",
   "\\def{refcaption#1}{\\ref{\\1}{c}}",
   "\\def{refnumber#1}{\\ref{\\1}{n}}",
   "\\def{reflevel#1}{\\ref{\\1}{l}}",
   "\\def{refmisc#1}{\\ref{\\1}{m}}",
   "\\def{reftype#1}{\\ref{\\1}{t}}",
   "\\def{ctrinc#1}{\\ctradd{\\1}{1}}",
   "\\def{ctrdec#1}{\\ctradd{\\1}{-1}}",
   "\\def{throw#1}{\\throw{\\1}{}}",
   "\\def{inform#1}{\\write{stderr}{device}{\\1\\@{\\N}}}",
   "\\def{and#1}{\\fv{and}{\\1}}",
   "\\def{or#1}{\\fv{or}{\\1}}",
   "\\def{and#2}{\\f{and}{\\1}{\\2}}",
   "\\def{or#2}{\\f{or}{\\1}{\\2}}",
   "\\def{not#1}{\\if{\\1}{0}{1}}",
   "\\def{begin#1}{\\begin{\\1}{}}",
   "\\def{\"\"#1}{}",
   "\\def{group#1}{\\1}",
   "\\def{system#2}{\\system{\\1}{\\2}{}}",
   "\\def{system#1}{\\system{\\1}{}{}}",
   "\\def{div#2}{\\f{div}{\\1}{\\2}}",
   "\\def{mod#2}{\\f{mod}{\\1}{\\2}}",
   "\\def{PI}{3.1415926536}",
              /* 3.1415926535897932384626433832795028841971693993751 */
   "\\def{E}{2.71828182846}",
   "\\def{\"+\"#2}{\\f{+}{\\1}{\\2}}",
   "\\def{\"*\"#2}{\\f{*}{\\1}{\\2}}",
   "\\def{\"%\"#2}{\\f{%}{\\1}{\\2}}",
   "\\def{\"-\"#2}{\\f{-}{\\1}{\\2}}",
   "\\def{\"/\"#2}{\\f{/}{\\1}{\\2}}",
   "\\def{\"\"}{}",
   NULL
   }
;


typedef struct
{  const char*       name
;  const char*       tag
;  const char*       descr
;  yamSeg*           (*yamfunc)(yamSeg* seg)
;
}  cmdHook           ;


static   cmdHook     cmdHookDir[]      =  
{  {  "!#1"        ,  I_BANG_0        ,  J_BANG_0        ,  expandBang1       }
,  {  "!#2"        ,  I_BANG_1        ,  J_BANG_1        ,  expandBang2       }
,  {  "$#2"        ,  I_DOLLAR_2      ,  J_DOLLAR_2      ,  expandDollar2     }
,  {  "%#1"        ,  I_GRAPE1_1      ,  J_GRAPE1_1      ,  expandGrapeGet    }
,  {  "%free#1"    ,  I_GRAPE2_1      ,  J_GRAPE2_1      ,  expandGrapeFree   }
,  {  "%dump#1"    ,  I_GRAPE3_1      ,  J_GRAPE3_1      ,  expandGrapeDump   }
,  {  "<>#1"       ,  I_XTAG_1        ,  J_XTAG_1        ,  expandXtag1       }
,  {  "<>#2"       ,  I_XTAG_2        ,  J_XTAG_2        ,  expandXtag2       }
,  {  "alpha#1"    ,  I_ALPHA_1       ,  J_ALPHA_1       ,  expandAlpha1      }
,  {  "apply#2"    ,  I_APPLY_2       ,  J_APPLY_2       ,  expandApply2      }
,  {  "begin#2"    ,  I_BEGIN_2       ,  J_BEGIN_2       ,  expandBegin2      }
,  {  "branch#1"   ,  I_BRANCH_1      ,  J_BRANCH_1      ,  expandBranch1     }
,  {  "catch#2"    ,  I_CATCH_2       ,  J_CATCH_2       ,  expandCatch2      }
,  {  "cmp#3"      ,  I_CMP_3         ,  J_CMP_3         ,  expandCmp3        }
,  {  "constant#1" ,  I_CONSTANT_1    ,  J_CONSTANT_1    ,  expandConstant1   }
,  {  "ctradd#2"   ,  I_CTRADD_2      ,  J_CTRADD_2      ,  expandCtradd2     }
,  {  "ctrput#1"   ,  I_CTRPUT_1      ,  J_CTRPUT_1      ,  expandCtrput1     }
,  {  "ctrset#2"   ,  I_CTRSET_2      ,  J_CTRSET_2      ,  expandCtrset2     }
,  {  "def#2"      ,  I_DEF_2         ,  J_DEF_2         ,  expandDef2        }
,  {  "defined#2"  ,  I_DEFINED_2     ,  J_DEFINED_2     ,  expandDefined2    }
,  {  "defx#2"     ,  I_DEFX_2        ,  J_DEFX_2        ,  expandDefx2       }
,  {  "dofile#2"   ,  I_DOFILE_2      ,  J_DOFILE_2      ,  expandDofile2     }
,  {  "done"       ,  I_DONE_0        ,  J_DONE_0        ,  expandDone0       }
,  {  "dowhile#2"  ,  I_DOWHILE_2     ,  J_DOWHILE_2     ,  expandDowhile2    }
,  {  "end#1"      ,  I_END_1         ,  J_END_1         ,  expandEnd1        }
,  {  "env#3"      ,  I_ENV_3         ,  J_ENV_3         ,  expandEnv3        }
,  {  "eqt#3"      ,  I_EQT_3         ,  J_EQT_3         ,  expandEqt3        }
,  {  "eval#1"     ,  I_EVAL_1        ,  J_EVAL_1        ,  expandEval1       }
,  {  "exit"       ,  I_EXIT_0        ,  J_EXIT_0        ,  expandExit0       }
,  {  "f#2"        ,  I_F_2           ,  J_F_2           ,  expandF2          }
,  {  "f#3"        ,  I_F_3           ,  J_F_3           ,  expandF3          }
,  {  "finsert#1"  ,  I_FINSERT_1     ,  J_FINSERT_1     ,  expandFinsert1    }
,  {  "format#2"   ,  I_FORMAT_2      ,  J_FORMAT_2      ,  expandFormat2     }
,  {  "formatted#1",  I_FORMATTED_1   ,  J_FORMATTED_1   ,  expandFormatted1  }
,  {  "fv#2"       ,  I_FV_2          ,  J_FV_2          ,  expandFv2         }
,  {  "if#3"       ,  I_IF_3          ,  J_IF_3          ,  expandIf3         }
,  {  "inspect#4"  ,  I_INSPECT_4     ,  J_INSPECT_4     ,  expandInspect4    }
,  {  "length#1"   ,  I_LENGTH_1      ,  J_LENGTH_1      ,  expandLength1     }
,  {  "let#1"      ,  I_LET_1         ,  J_LET_1         ,  expandLet1        }
,  {  "pop#1"      ,  I_POP_1         ,  J_POP_1         ,  expandPop1        }
,  {  "protect#1"  ,  I_PROTECT_1     ,  J_PROTECT_1     ,  expandProtect1    }
,  {  "push#1"     ,  I_PUSH_1        ,  J_PUSH_1        ,  expandPush1       }
,  {  "ref#2"      ,  I_REF_2         ,  J_REF_2         ,  expandRef2        }
,  {  "refload#6"  ,  I_REFLOAD_6     ,  J_REFLOAD_6     ,  expandRefload6    }
,  {  "roman#1"    ,  I_ROMAN_1       ,  J_ROMAN_1       ,  expandRoman1      }
,  {  "set#2"      ,  I_SET_2         ,  J_SET_2         ,  expandSet2        }
,  {  "setx#2"     ,  I_SETX_2        ,  J_SETX_2        ,  expandSetx2       }
,  {  "special#1"  ,  I_SPECIAL_1     ,  J_SPECIAL_1     ,  expandSpecial1    }
,  {  "switch#2"   ,  I_SWITCH_2      ,  J_SWITCH_2      ,  expandSwitch2     }
,  {  "system#3"   ,  I_SYSTEM_3      ,  J_SYSTEM_3      ,  expandSystem3     }
,  {  "table#5"    ,  I_TABLE_5       ,  J_TABLE_5       ,  expandTable5      }
,  {  "throw#2"    ,  I_THROW_2       ,  J_THROW_2       ,  expandThrow2      }
,  {  "tr#4"       ,  I_TR_4          ,  J_TR_4          ,  expandTr4         }
,  {  "trace#1"    ,  I_TRACE_1       ,  J_TRACE_1       ,  expandTrace1      }
,  {  "try#1"      ,  I_TRY_1         ,  J_TRY_1         ,  expandTry1        }
,  {  "ucase#1"    ,  I_UCASE_1       ,  J_UCASE_1       ,  expandUcase1      }
,  {  "undef#1"    ,  I_UNDEF_1       ,  J_UNDEF_1       ,  expandUndef1      }
,  {  "vanish#1"   ,  I_VANISH_1      ,  J_VANISH_1      ,  expandVanish1     }
,  {  "while#2"    ,  I_WHILE_2       ,  J_WHILE_2       ,  expandWhile2      }
,  {  "write#3"    ,  I_WRITE_3       ,  J_WRITE_3       ,  expandWrite3      }
,  {  "writeto#1"  ,  I_REDIRECT_1    ,  J_REDIRECT_1    ,  expandRedirect1   }
,  {  "zinsert#1"  ,  I_ZINSERT_1     ,  J_ZINSERT_1     ,  expandZinsert1    }
,  {  "___jump_lc___#1", NULL         ,  NULL            ,  expandJumpLc1     }
,  {  "__line__"   ,  NULL            ,  NULL            ,  expandLine        }
,  {  "__test__#3" ,  NULL            ,  NULL            ,  expandTest3       }
,  {  NULL         ,  NULL            ,  NULL            ,  NULL              }
}  ;


mcxstatus yamOpsDataAccess
(  const mcxTing*    access
)  ;

mcxbool  yamOpList
(  const char* mode
)
   {  cmdHook*    cmdhook     =  cmdHookDir
   ;  mcxbool     listAll     =  strstr(mode, "all") != NULL
   ;  mcxbool     match       =  listAll || 0

   ;  if (listAll || strstr(mode, "zoem"))
      {  while (cmdhook && cmdhook->name)
         {  if (cmdhook->descr)
            fprintf
            (  stdout
            ,  "%-11s %-20s %s\n"
            ,  cmdhook->name
            ,  *(cmdhook->tag) ? cmdhook->tag : "..."
            ,  cmdhook->descr
            )
         ;  cmdhook++
      ;  }
         if (!strstr(mode, "legend"))
         fprintf(stdout, "Additionally supplying \"-l legend\" prints legend\n")
      ;  match = 1
   ;  }

      if (listAll || strstr(mode, "legend"))
      {  int m
      ;  for (m=0;strLegend[m];m++)
         fprintf(stdout, "%s\n", strLegend[m])
      ;  match = 1
   ;  }

      if (listAll || strstr(mode, "macro"))
      {  int m
      ;  fputs("\nBuilt-in aliases and macro's\n", stdout)
      ;  for (m=0;strComposites[m];m++)
         fprintf(stdout, "%s\n", strComposites[m])
      ;  match = 1
   ;  }

      return match ? TRUE : FALSE
;  }


yamSeg* expandLine
(  yamSeg*  seg
)
   {  return yamSegPush(seg, mcxTingInteger(NULL, sourceGetLc()))
;  }


yamSeg* expandJumpLc1
(  yamSeg*  seg
)
   {  int ct = strtol(arg1_g->str, NULL, 10)
   ;  sourceIncrLc(NULL, ct)
   ;  return seg
;  }


yamSeg* expandThrow2
(  yamSeg*  seg
)
   {  int mode =     !strcmp(arg1_g->str, "towel")
                  ?  SEGMENT_THROW
                  :  SEGMENT_ERROR
   ;  const char* type
               =     mode == SEGMENT_THROW
                  ?  "exception"
                  :  "error"

   ;  mcxTing* msg = arg2_g->len ? mcxTingNew(arg2_g->str) : NULL

   ;  if (msg)
         yamDigest(msg, msg, NULL)
      ,  yamErr(NULL, "[%s :: %s]", type, msg->str)
      ,  mcxTingFree(&msg)

   ;  seg->flags |= mode
   ;  return yamSegPushEmpty(seg)
;  }


yamSeg* expandTest3
(  yamSeg*  seg
)
   {  return seg
;  }


yamSeg* expandCatch2
(  yamSeg*  seg
)
   {  int accept  =     !strcmp(arg1_g->str, "towel")
                     ?  1
                     :     !strcmp(arg1_g->str, "error")
                        ?  2
                        :  0
   ;  mcxTing* stuff = mcxTingNew(arg2_g->str)
   ;  mcxstatus status = yamDigest(stuff, stuff, seg)

   ;  mcxbool error  = seg->flags & SEGMENT_ERROR
   ;  mcxbool throw  = seg->flags & SEGMENT_THROW

   ;  status = status

   ;  yamKeySet
      (  "__zoemstat__"
      ,     error
         ?  "error"
         :     throw
            ?  "towel"
            :  "ok"
      )

   ;  if (error || throw)
      {  if ((error && accept >= 2) || (throw && accept >= 1))
         {  seg->flags |= SEGMENT_INTERRUPT    /* clear */
         ;  seg->flags ^= SEGMENT_INTERRUPT    /* clear */
      ;  }
      }

   ;  return yamSegPush(seg, stuff)
;  }


yamSeg* expandTry1
(  yamSeg*  seg
)
   {  mcxTing* stuff    =  mcxTingNew(arg1_g->str)
   ;  mcxstatus status  =  yamDigest(stuff, stuff, seg)
   ;  mcxbool error     =  seg->flags & SEGMENT_ERROR
   ;  mcxbool throw     =  seg->flags & SEGMENT_THROW

   ;  throw = throw

   ;  seg->flags |= SEGMENT_INTERRUPT    /* clear */
   ;  seg->flags ^= SEGMENT_INTERRUPT    /* clear */

   ;  yamKeySet("__zoemput__", stuff->str)
   ;  yamKeySet("__zoemstat__", !status ? "ok" : error ? "error" : "towel")

   ;  mcxTingFree(&stuff)
   ;  return seg
;  }


void op_stdia
(  mcxTing* filetxt
)
   {  mcxIO* xfin          =  mcxIOnew("-", "r")
   ;  mcxIO* xfout         =  mcxIOnew("-", "w")
   ;  mcxTing*  tmp        =  mcxTingNew("__parmode__")
   ;  mcxTing*  ump        =  yamKeyGet(tmp)
   ;  int       parmode    =  ump ? atoi(ump->str) : MCX_READLINE_DOT
   ;  const char* me       =  "stdia"
   ;  sink *sd

   ;  mcxTingFree(&tmp)

         /* we don't issue yamOutputNew for xfin, it is not cached */

   ;  if
      (  mcxIOopen(xfin, RETURN_ON_FAIL)
      || mcxIOopen(xfout, RETURN_ON_FAIL)
      )
         yamErr("init PBD", "failure opening interactive session")
      ,  exit(1)

   ;  sd = sinkNew(xfout)

#define VERBOSE_IA 1
   ;  if (VERBOSE_IA)
      fprintf
      (  stdout
      ,  "%s%s"
      ,  "=== Interactive session, I should recover from errors.\n"
         "=== If I exit unexpectedly, consider sending a bug report.\n"
      ,     parmode & MCX_READLINE_DOT
        ?  "=== A single dot on a line of its own triggers interpretation.\n"
        :  ""
      )
   ,  fflush(stdout)

   ;  while (STATUS_OK == mcxIOreadLine(xfin, filetxt, parmode))
      {  mcxstatus status = STATUS_OK
      ;  if (VERBOSE_IA)
            fputs("----------------------------------------\n", stdout)
         ,  fflush(stdout)
      ;  sourcePush(me, filetxt)
      ;  if ((status = yamOutput(filetxt, sd, ZOEM_FILTER_DEVICE)))
         {  const char* type = status == STATUS_THROW ?"exception":"error"
         ;  fprintf(stdout, "(interactive %s)\n", type)
      ;  }
         fflush(stdout)
      ;  sourcePop()

      ;  while (sd->fd->n_newlines < 1)  
         {  fputc('\n', stdout)
         ;  sd->fd->n_newlines++
      ;  }
         if (xfin->ateof)
         break
      ;  if (VERBOSE_IA)
         fputs("----------------------------------------\n", stdout)
      ;  fflush(stdout)
   ;  }

      sinkFree(sd)  /* fixme, member of xfout (so tie this) */
   ;  mcxIOfree(&xfin)
   ;  mcxIOfree(&xfout)
;  }

/*
 *  fixme improve control
*/

yamSeg* expandZinsert1
(  yamSeg*  seg
)
   {  mcxTing*  fname      =  mcxTingNew(arg1_g->str)
   ;  mcxTing*  filetxt    =  mcxTingEmpty(NULL, 100)
   ;  mcxIO*  xf           =  NULL
   ;  yamSeg* newseg       =  NULL

   ;  if (yamDigest(fname, fname, seg))
      {  mcxTingFree(&filetxt)
      ;  mcxTingFree(&fname)
      ;  return yamSegPushEmpty(seg)
   ;  }
      else if (!strcmp(fname->str, "stdia"))
      {  op_stdia(filetxt)
      ;  newseg = seg
   ;  }
      else
      {  mcxTing* newtxt
      ;  mcxTing* fnorig = mcxTingNew(fname->str)
      ;  const mcxTing* infile = NULL
      ;  if (!(xf = yamTryOpen(fname, &infile, 1)) && !infile)
         {  mcxTell
            (  "zinsert#1"
            ,  "failure opening file <%s> (rerun?)"
            ,  fnorig->str
            )
         ;  newseg = seg
      ;  }
         else if (infile)
         {  newtxt = mcxTingPrint(NULL, "\\!{%s}", infile->str)
         ;  newseg =  yamSegPush(seg, newtxt)
      ;  }
         else
         {  if (mcxIOreadFile(xf, filetxt))
            yamErr("zinsert#1", "error reading file <%s>", xf->fn->str)
         ;  else
               newtxt = mcxTingPrint(NULL, "\\!{%s}", filetxt->str)
            ,  newseg =  yamSegPush(seg, newtxt)
      ;  }
         mcxTingFree(&fnorig)
   ;  }

      mcxIOfree(&xf)
   ;  mcxTingFree(&filetxt)
   ;  mcxTingFree(&fname)
   ;  return newseg
;  }


yamSeg* expandAlpha1
(  yamSeg*  seg
)
   {  mcxTing*  a =  mcxTingNew(arg1_g->str)
   ;  int x, r, l, i, d = 0

   ;  if (yamDigest(a,a, seg))
      return yamSegPush(seg, a)     /* pushes error flags */

   ;  x = atoi(a->str)

   ;  if (x <0)
         mcxTingWrite(a, "-")
      ,  x = -x
      ,  d = 1
   ;  else
      mcxTingWrite(a, "")

   ;  do
      {  r = x % 27
      ;  x = x / 27
      ;  mcxTingNAppend(a, "_abcdefghijklmnopqrstuvwxyz"+r, 1)
   ;  }
      while (x)

   ;  l = a->len

   ;  for (i=d;i<l/2;i++)
      {  char f = a->str[i], g = a->str[l-i-1]
      ;  a->str[i] = g
      ;  a->str[l-i-1] = f
   ;  }

      return yamSegPush(seg, a)
;  }


yamSeg* expandUcase1
(  yamSeg*  seg
)
   {  mcxTing*  a = mcxTingNew(arg1_g->str)
   ;  char* p

   ;  if (yamDigest(a, a, seg))
      return yamSegPush(seg, a)     /* pushes error flags */

   ;  for (p=a->str;p<a->str+a->len;p++)
      if (*p >= 'a' && *p <= 'z')
      *p = (char) (((unsigned char) *p) + 'A' - 'a')

   ;  return yamSegPush(seg, a)
;  }


yamSeg* expandRoman1
(  yamSeg*  seg
)
   {  mcxTing*  a = mcxTingNew(arg1_g->str)
   ;  if (yamDigest(a,a, seg))
      return yamSegPush(seg, a)     /* pushes error flags */

   ;  mcxTingRoman(a, atoi(a->str), 0)
   ;  return yamSegPush(seg, a)
;  }


yamSeg* expandProtect1
(  yamSeg*  seg
)
   {  mcxTing*  protected = yamProtect(arg1_g)
   ;  return yamSegPush(seg, protected)
;  }


yamSeg* expandFinsert1
(  yamSeg*  seg
)
   {  mcxTing*  fname   =  mcxTingNew(arg1_g->str)
   ;  mcxTing*  filetxt =  mcxTingEmpty(NULL, 100)
   ;  mcxIO* xf         =  NULL
   ;  mcxstatus status  =  yamDigest(fname, fname, seg)

   ;  if (!status && !(xf = yamTryOpen(fname, NULL, 1)))
      {  status = STATUS_FAIL
      ;  seg_check_status(status, seg)
   ;  }

      if (!status)
      yamReadData(xf, filetxt)
   ;  else
      mcxTingEmpty(filetxt, 0)

   ;  mcxIOfree(&xf)
   ;  mcxTingFree(&fname)

   ;  return yamSegPush(seg, filetxt)
;  }


yamSeg* expandDofile2
(  yamSeg*  seg
)
   {  mcxTing*  fnsearch   =  mcxTingNew(arg1_g->str)
   ;  mcxTing*  fname      =  NULL
   ;  mcxTing*  opts       =  arg2_g
   ;  mcxbool   ok         =  FALSE
   ;  const char* me       =  "\\dofile#2"

   /*  DON'T DIGEST OR OUTPUT AS LONG AS OPTS IS IN SCOPE */

   ;  int            mode
   ;  sink*   sd =  NULL
   ;  int          fltidx  =  0

   ;  while(1)
      {  if (opts->len != 2)
         {  yamErr(me, "Second arg <%s> not in {!?}x{+-}", opts->str)
         ;  break
      ;  }
         if (*(opts->str+0) == '!')
         {  if (*(opts->str+1) == '+')
            mode = READ_INPUT
         ;  else if (*(opts->str+1) == '-')
            mode = READ_IMPORT
         ;  else
            {  yamErr(me, "Second arg <%s> not in {!?}x{+-}", opts->str)
            ;  break
         ;  }
         }
         else if (*(opts->str+0) == '?')
         {  if (*(opts->str+1) == '+')
            mode = READ_READ
         ;  else if (*(opts->str+1) == '-')
            mode = READ_LOAD
         ;  else
            {  yamErr(me, "Second arg <%s> not in {!?}x{+-}", opts->str)
            ;  break
         ;  }
         }
         else
         {  yamErr(me, "Second arg <%s> not in {!?}x{+-}", opts->str)
         ;  break
      ;  }


      /* fixme: must pass mode information to inputascend,
       * rather than create filedata stuff over here.
      */

         sd =     mode & (READ_INPUT | READ_READ)
                  ?  sinkGetDefault()
                  :  NULL
      ;  fltidx=     mode & (READ_INPUT | READ_READ)
                  ?  ZOEM_FILTER_DEFAULT
                  :  ZOEM_FILTER_NONE

      ;  if (!sourceCanPush())
         {  yamErr
            (  me, "maximum file include depth (9) reached"
               "___ when presented with file <%s>"
            ,  fnsearch->str
            )
         ;  break
      ;  }

         if (yamDigest(fnsearch, fnsearch, seg))
         break
      ;  fname = mcxTingNew(fnsearch->str)
                                             /* 0 == !debug
                                              * 1 == use_searchpath
                                              * 1 == allow_inline
                                             */
      ;  {  mcxstatus status
            =  sourceAscend(fnsearch, sd, fltidx, chunk_size, 0, 1, 1)
         ;  if (status == STATUS_FAIL_OPEN)
            {  if (mode & (READ_READ | READ_LOAD))
               {  ok = TRUE
               ;  break
            ;  }
               else
               {  mcxErr(me, "failed to open file <%s>", fname->str)
               ;  break
            ;  }
            }
            else if (status != STATUS_OK)
            {  mcxErr
               (  me
               ,  "error (%d) occurred while reading file <%s>"
               ,  (int) status
               ,  fname->str
               )
            ;  break
         ;  }
         }
         ok = TRUE
      ;  break
   ;  }

      seg_check_ok(ok, seg)

   ;  mcxTingFree(&fnsearch)
   ;  mcxTingFree(&fname)
   ;  return seg
;  }


yamSeg* expandXtag2
(  yamSeg*  seg
)
   {  mcxTing* tag = mcxTingNew(arg1_g->str)
   ;  mcxTing* ops = mcxTingNew(arg2_g->str)
   ;  return yamXtag(seg, tag, ops)  
;  }


yamSeg* expandXtag1
(  yamSeg*  seg
)
   {  mcxTing* tag = mcxTingNew(arg1_g->str)
   ;  return yamXtag(seg, tag, NULL)  
;  }


yamSeg* expandBang1
(  yamSeg*  seg
)
   {  mcxTing* stripped = mcxTingNew("\\")
   ;  if (arg1_g->len > 1)
         mcxTingAppend(stripped, arg1_g->str)
      ,  mcxTingShrink(stripped, -1)
   ;  return yamSegPushx(seg, stripped, SEGMENT_CONSTANT)
;  }


yamSeg* expandBang2
(  yamSeg*  seg
)
   {  if (arg1_g->len == 1)
      return
      yamSegPushx(seg, mcxTingNew(arg2_g->str), seg->flags | SEGMENT_CONSTANT)
   ;  else
      {  mcxTing* txt
         =  mcxTingPrint
            (NULL, "\\%.*s{%s}", (int) (arg1_g->len-1), arg1_g->str, arg2_g->str)
      ;  return yamSegPushx(seg, txt, SEGMENT_CONSTANT)
   ;  }
      return NULL
;  }


yamSeg* expand_while_
(  yamSeg*  seg
,  int      dowhile
)
   {  mcxTing*  condition  =  mcxTingNew(dowhile ? arg2_g->str : arg1_g->str)
   ;  mcxTing*  data       =  mcxTingNew(dowhile ? arg1_g->str : arg2_g->str)
   ;  mcxTing*  condition_ =  mcxTingEmpty(NULL, 10)
   ;  mcxTing*  data_      =  mcxTingEmpty(NULL, 10)
   ;  mcxTing*  newtxt     =  mcxTingEmpty(NULL, 10)
   ;  mcxstatus stat       =  STATUS_OK
   ;  mcxbool   guard      =  TRUE
   ;

      if (!dowhile)
      {  mcxTingWrite(condition_, condition->str)
      ;  if
         (  (stat = yamDigest(condition_, condition_, seg))
         || !atol(condition_->str)
         )
         guard = FALSE
   ;  }

      do
      {  if (!guard)
         break

      ;  mcxTingWrite(data_, data->str)
      ;  stat = yamDigest(data_, data_, seg)
      ;  mcxTingAppend(newtxt, data_->str)  

      ;  if (stat)
         break

      ;  mcxTingWrite(condition_, condition->str)
      ;  if ((stat = yamDigest(condition_, condition_, seg)))
         break
      ;  guard =  atol(condition_->str) ? TRUE : FALSE
   ;  }
      while (1)

   ;  mcxTingFree(&data)
   ;  mcxTingFree(&data_)
   ;  mcxTingFree(&condition)
   ;  mcxTingFree(&condition_)

   ;  return yamSegPush(seg, newtxt)
;  }


yamSeg* expandDowhile2
(  yamSeg*  seg
)
   {  return expand_while_(seg, 1)
;  }


yamSeg* expandWhile2
(  yamSeg*  seg
)
   {  return expand_while_(seg, 0)
;  }


yamSeg* expandApply2
(  yamSeg*  seg
)
   {  mcxTing *data        =  mcxTingNew(arg2_g->str)
   ;  mcxTing *newtxt      =  mcxTingEmpty(NULL, 10)
   ;  char* p              =  arg1_g->str
   ;  int delta            =  0
   ;  const char* me       =  "\\apply#2"
   ;  mcxTing *key         =  NULL
   ;  yamSeg *tblseg       =  NULL
   ;  int   x, k, keylen, namelen
   ;  mcxbool ok           =  FALSE

   ;  while (isspace((unsigned char) *p))
      p++

   ;  key = mcxTingNew(p)

   ;  while(1)
      {  if (yamDigest(data, data, seg))
         break
      ;  if (yamDigest(key, key, seg))
         break

      ;  keylen   =  checkusrsig(key->str, key->len, &k)
      ;  namelen  =  checkusrname(key->str, key->len)

      ;  if (keylen < 0 || namelen < 0)
         {  yamErr(me, "key part not ok")
         ;  break
      ;  }

         if (k<=0 || k > 9)
         {  yamErr
            (me, "loop number <%d> not in [1,9] for key <%s>", k, key->str)
         ;  break
      ;  }

         if (namelen == 1 && *(key->str) == '_')      /* anonymous key */
         {  int cc
         ;  if ((cc = yamClosingCurly(key, keylen, NULL, RETURN_ON_FAIL))<0)
            {  yamErr
               (  me
               ,  "anonymous key <%s> not ok (%d/%d)"
               ,  key->str
               ,  cc+keylen+1
               ,  key->len
               )
            ;  break
         ;  }
            mcxTingNWrite(key_g, key->str, keylen)
         ;  mcxTingNWrite(arg1_g, key->str+keylen+1, cc-1)
         ;  delta = 1
      ;  }
         else if (keylen != key->len)
         {  yamErr
            (  me
            ,  "key <%s> is not of the right \\foo, \\\"foo::foo\", and \\$foo"
            ,  key->str
            )
         ;  break
      ;  }
         else
         {  mcxTingWrite(key_g, key->str)
      ;  }

         tblseg = yamStackPushTmp(data)

               /* perhaps this block should be encapsulated by parse.c
                * pity we have yamExpandKey here.
                * Also, yamStackPushTmp would like to set bit
                * that is checked by yamSegNew, but we cannot do that
                * because of the yamExpandKey below.
                * Seems we do full expansion here.
               */
      ;  while ((x = yamParseScopes(tblseg, k, delta)) == k)
         {  yamSeg* newseg  = yamExpandKey(tblseg)
         ;  if (!newseg)
            {  yamErr(me, "key does not expand")  
            ;  goto done
         ;  }
            else if (newseg != tblseg) /* primitives may return same segment */
            {  mcxTingAppend(newtxt,newseg->txt->str)
            ;  yamSegFree(&newseg)
         ;  }
         }

         if (!x)

      ;  else if ( x < 0)
         {  mcxErr(me, "parse error!")
         ;  break
      ;  }
         else if (x < k)
         mcxErr(me, "(ignoring) trailing arguments")

      ;  ok = TRUE
      ;  break
   ;  }

   done
      :
      seg_check_ok(ok, seg)

   ;  mcxTingFree(&data)
   ;  mcxTingFree(&key)
   ;  yamStackFreeTmp(&tblseg)
   ;  return yamSegPush(seg, newtxt)
;  }


yamSeg* expandTable5
(  yamSeg*  seg
)
   {  mcxTing *txtnum   =  mcxTingNew(arg1_g->str)
   ;  mcxTing *txtlft   =  mcxTingNew(arg2_g->str)
   ;  mcxTing *txtmdl   =  mcxTingNew(arg3_g->str)
   ;  mcxTing *txtrgt   =  mcxTingNew(arg4_g->str)
   ;  mcxTing *data     =  mcxTingNew(arg5_g->str)
   ;  mcxbool ok        =  FALSE
   ;  yamSeg *tmpseg    =  NULL

   ;  mcxTing *txtall   =  mcxTingEmpty(NULL, 100)

   ;  int  x, k

   ;  while(1)
      {  if (yamDigest(data, data, seg) || yamDigest(txtnum, txtnum, seg))
         break

      ;  k = atoi(txtnum->str)

      ;  if (k<=0)
         {  yamErr("\\table#5", "nonpositive loop number <%d>", k)
         ;  break
      ;  }

         tmpseg = yamStackPushTmp(data)

      ;  while ((x = yamParseScopes(tmpseg, k, 0)) == k)
         {  int i
         ;  mcxTingAppend(txtall, txtlft->str)
         ;  for (i=1;i<k;i++)
            {  mcxTingAppend(txtall, (key_and_args_g+i)->str)
            ;  mcxTingAppend(txtall, txtmdl->str)
         ;  }
            mcxTingAppend(txtall, (key_and_args_g+k)->str)
         ;  mcxTingAppend(txtall, txtrgt->str)
      ;  }

         if (x < 0)
         break
      ;  ok =  TRUE
      ;  break
   ;  }

      seg_check_ok(ok, seg)

   ;  mcxTingFree(&txtnum)
   ;  mcxTingFree(&txtlft)
   ;  mcxTingFree(&txtmdl)
   ;  mcxTingFree(&txtrgt)

   ;  yamStackFreeTmp(&tmpseg)
   ;  mcxTingFree(&data)

   ;  return yamSegPush(seg, txtall)
;  }


yamSeg*  expandFormat2
(  yamSeg* seg
)
   {  return yamFormat2(seg)
;  }


yamSeg* expandFormatted1
(  yamSeg*  seg
)
   {  return yamFormatted1(seg, arg1_g->str)
;  }


yamSeg* expandWrite3
(  yamSeg*  seg
)
   {  mcxTing*    fname    =  mcxTingNew(arg1_g->str)
   ;  mcxTing*    yamtxt   =  mcxTingNew(arg3_g->str)
   ;  mcxIO       *xfout   =  NULL
   ;  mcxstatus   status   =  STATUS_FAIL
   ;  int         fltidx   =  1

   ;  fltidx =    !strcmp(arg2_g->str, "device")
               ?  ZOEM_FILTER_DEVICE
               :     !strcmp(arg2_g->str, "txt")
                  ?  ZOEM_FILTER_TXT
                  :     !strcmp(arg2_g->str, "copy")
                     ?  ZOEM_FILTER_COPY
                     :  -1
   ;  while(1)
      {  if (fltidx < 0)
         {  yamErr("\\write#3", "unknown filter <%s>", arg2_g->str)
         ;  break
      ;  }

         if (yamDigest(fname, fname, seg))
         break

      ;  if (!(xfout =  yamOutputNew(fname->str)))
         break
      ;  if
         (  (status = yamOutput(yamtxt, xfout->usr, fltidx))
         && !stressWrite
         )
         break
      ;  status =  STATUS_OK
      ;  break
   ;  }

      if (xfout)
      fflush(xfout->fp)

   ;  seg_check_status(status, seg)

   ;  mcxTingFree(&fname)
   ;  mcxTingFree(&yamtxt)
   ;  return status ? yamSegPushEmpty(seg) : seg
;  }


yamSeg* expandDollar2
(  yamSeg*  seg
)
   {  mcxTing*  device     =  yamKeyGet(devtxt_g)
   ;  if (!device)
      {  yamErr
         (  "\\$"
         ,  "key [\\__device__] not defined, rendering use of <%s> useless"
         ,  key_g->str
         )
      ;  seg_check_ok(FALSE, seg)
   ;  }
      else if (!strcmp(device->str, arg1_g->str))
      {  mcxTing* txt    =  mcxTingNew(arg2_g->str)
      ;  return yamSegPush(seg, txt)
   ;  }
      return  seg
;  }


yamSeg* expandUndef1
(  yamSeg*  seg
)
   {  mcxTing*  val     =  yamKeyDelete(arg1_g)
   ;  if (!val)
      yamErr("\\undef#1", "key <%s> not defined in this scope", arg1_g->str)
   ;  else
      mcxTingFree(&val)
   ;  return seg
;  }


yamSeg* expandDefined2
(  yamSeg*  seg
)
   {  mcxTing *val
   ;  mcxTing *type  =  mcxTingNew(arg1_g->str)
   ;  mcxTing *access=  mcxTingNew(arg2_g->str)
   ;  char*    yes   = "1"
   ;  char*    no    = "0"
   ;  char*    yn    = "no"
   ;  mcxbool  ok    =  FALSE
   ;  const char* me =  "\\defined#3"

   ;  while(1)
      {  if (yamDigest(access, access, seg))
         {  yamErr(me, "access string does not eval")
         ;  break
      ;  }

         if (!strcmp(type->str, "key") || !strcmp(type->str, "lkey"))
         {  if (checkusrsig(access->str, access->len, NULL) != access->len)
            {  yamErr
               (  me
               ,  "argument <%s> is not a valid key signature"
               ,  type->str
               )
            ;  break
         ;  }
         }

         if (!strcmp(type->str, "key"))
         {  val   =  yamKeyGet(access)
         ;  yn    =  val ? yes : no
      ;  }
         else if (!strcmp(type->str, "lkey"))
         {  val   =  yamKeyGetLocal(access)
         ;  yn    =  val ? yes : no
      ;  }
         else if (!strcmp(type->str, "data"))
         {  if (yamOpsDataAccess(access))
            break
         ;  yn    =  yamDataGet() ? yes : no
      ;  }
         else if (!strcmp(type->str, "ctr"))
         {  yn    =  yamCtrGet(access) ?  yes : no
      ;  }
         else
         {  yamErr(me, "invalid type <%s>", type->str)
         ;  break
      ;  }
         ok  = TRUE
      ;  break
   ;  }

      seg_check_ok(ok, seg)

   ;  if (seg->flags & SEGMENT_INTERRUPT)
      yn = "error"

   ;  mcxTingFree(&access)
   ;  mcxTingFree(&type)
   ;  return yamSegPush(seg, mcxTingNew(yn))
;  }


yamSeg* expandIf3
(  yamSeg*  seg
)
   {  mcxTing* bool  =  mcxTingNew(arg1_g->str)
   ;  mcxTing* case1 =  mcxTingNew(arg2_g->str)
   ;  mcxTing* case0 =  mcxTingNew(arg3_g->str)
   ;  int b

   ;  if (yamDigest(bool, bool, seg))
      {  yamErr("\\if#3", "condition does not parse")
      ;  mcxTingFree(&case0)
      ;  mcxTingFree(&case1)
      ;  mcxTingFree(&bool)
      ;  return yamSegPushEmpty(seg)
   ;  }

      b =   bool->len ? atoi(bool->str) :  0

   ;  mcxTingFree(&bool)

   ;  if (b)
      {  mcxTingFree(&case0)
      ;  return yamSegPush(seg, case1)
   ;  }
      else
      {  mcxTingFree(&case1)
      ;  return yamSegPush(seg, case0)
   ;  }

      return NULL
;  }


/*
 * Does not change the contents of access, does not claim ownership.
 * *DOES* take ownership of argk_g.
*/

mcxstatus yamOpsDataAccess
(  const mcxTing*    access
)
   {  if (access->len == 0)
      {  n_args_g = 0
   ;  }
      else if (seescope(access->str, access->len) >= 0)
      {  yamSeg* tmpseg = yamStackPushTmp((mcxTing*) access)
      ;  if (yamParseScopes(tmpseg, 9, 0) < 0)
         {  yamStackFreeTmp(&tmpseg)
         ;  return STATUS_FAIL
      ;  }
         yamStackFreeTmp(&tmpseg)
   ;  }
      else
      {  n_args_g = 1
      ;  mcxTingWrite(arg1_g, access->str)
   ;  }
      return STATUS_OK
;  }


yamSeg* expandGrapeDump
(  yamSeg*  seg
)
   {  mcxTing* access = mcxTingNew(arg1_g->str)
   ;  mcxbool ok = TRUE

   ;  while(1)
      {  if (yamDigest(access, access, seg))
         break
      ;  if (yamOpsDataAccess(access))
         break
      ;  if (yamDataPrint())
         {  yamErr("\\dump#1", "no value associated with <%s>", access->str)
         ;  break
      ;  }
         ok =  TRUE
      ;  break
   ;  }

      seg_check_ok(ok, seg)

   ;  mcxTingFree(&access)
   ;  return seg
;  }


yamSeg* expandGrapeFree
(  yamSeg*  seg
)
   {  mcxTing*  access = mcxTingNew(arg1_g->str)
   ;  mcxbool ok = TRUE

  /*  NOTE this routine never fails, we start with ok = TRUE */

   ;  while(1)
      {  if (yamDigest(access, access, seg))
         break
      ;  if (yamOpsDataAccess(access))
         break
      ;  if (yamDataFree())
         {  yamErr("\\free#1", "no value associated with <%s>", access->str)
         ;  break
      ;  }
         ok =  TRUE
      ;  break
   ;  }

      seg_check_ok(ok, seg)

   ;  mcxTingFree(&access)
   ;  return seg
;  }


yamSeg* expandGrapeGet
(  yamSeg*  seg
)
   {  const char* str = NULL
   ;  mcxTing* access = mcxTingNew(arg1_g->str)
   ;  mcxbool ok  =  TRUE   /* on purpose; grape key absence -> "" */
   
   ;  while(1)
      {  if (yamDigest(access, access, seg))
         break
      ;  if (yamOpsDataAccess(access))
         break
      ;  if (!(str = yamDataGet()))
         {  yamErr("\\%#1", "no value associated with <%s>", access->str)
         ;  break
      ;  }
         ok =  TRUE
      ;  break
   ;  }

      seg_check_ok(ok, seg)

   ;  mcxTingFree(&access)
   ;  return yamSegPush(seg, mcxTingNew(str ? str : ""))
;  }


yamSeg* expandSystem3
(  yamSeg*  seg
)
   {  mcxTing*    cmd   =  mcxTingNew(arg1_g->str) 
   ;  mcxTing*    cmd_  =  mcxTingPrint(NULL, ":%s:", arg1_g->str)
   ;  mcxTing*    arx   =  mcxTingNew(arg2_g->str) 
   ;  mcxTing*    data  =  mcxTingNew(arg3_g->str) 
   ;  mcxTing*    out   =  NULL
   ;  yamSeg*     argseg=  NULL
   ;  char*       args[YAM_ARG_MAX+1]
   ;  int         k, i
   ;  mcxbool     ok    =  FALSE
   ;  const char* me    =  "\\system#3"
   ;  mcxbool     listed=  system_allow && strstr(system_allow->str, cmd_->str)
                           ?  TRUE
                           :  FALSE

   ;  mcxTingFree(&cmd_)
   ;  while(1)
      {  if (!listed && systemAccess == SYSTEM_SAFE)
         {  yamErr
            (  me
            ,  "system calls are not allowed (use --unsafe or --unsafe-silent)"
            )
         ;  break
      ;  }
         if (yamDigest(arx, arx, seg))
         break
      ;  if (yamDigest(data, data, seg))
         break
      ;  argseg = yamStackPushTmp(arx)

      ;  args[0]  =  cmd->str
      ;  if ((k = yamParseScopes(argseg, YAM_ARG_MAX, 0)) < 0)
         {  yamErr(me, "second argument not a vararg")
         ;  break
      ;  }
         for (i=0; i<k; i++)
         {  yamUnprotect(key_and_args_g+i+1)
         ;  args[i+1] = key_and_args_g[i+1].str
      ;  }
         if (!listed && systemAccess == SYSTEM_UNSAFE)
         {  mcxIO* prompt = mcxIOnew("-", "r")
         ;  mcxTing* ln = mcxTingEmpty(NULL, 80)

         ;  if (!isatty(fileno(stdin)))
            {  yamErr(me, "unsafe mode; cannot prompt terminal!")
            ;  break
         ;  }
            if (mcxIOopen(prompt, RETURN_ON_FAIL))
            {  mcxIOfree(&prompt)
            ;  break
         ;  }

            fputs
            (  "\n> do you want this command to be exercised? (y/n)\n> ["
            ,  stdout
            )
         ;  for (i=0; i<k+1; i++)
               fputs(args[i], stdout)
            ,  fputs(i < k ? " " : "]\n> ", stdout)
         ;  fflush(stdout)    /* mq fflush/fputs mix ? */

         ;  mcxIOreadLine(prompt, ln, MCX_READLINE_CHOMP)
         ;  mcxIOfree(&prompt)

         ;  if (ln && ln->len == 1 && (ln->str[0] == 'y' || ln->str[0] == 'Y'))
            mcxTingFree(&ln)
         ;  else
            {  mcxTingFree(&ln)
            ;  break
         ;  }
            /* mq todo: encapsulate this prompting mechanism. */
         }
         args[k+1] = NULL
      ;  if (!(out = yamSystem(args[0], args, data)))
         break
      ;  ok = TRUE
      ;  break
   ;  }        /* fixmefixme yamSystem returns != NULL for some failures */

      mcxTingFree(&data)
   ;  mcxTingFree(&cmd)
   ;  yamStackFreeTmp(&argseg)
   ;  mcxTingFree(&arx)   /* mq must this be done after yamSegFree? */

   ;  if (!out)
      out = mcxTingEmpty(NULL, 0)

   ;  if (!ok && !systemHonor && !(seg->flags & SEGMENT_INTERRUPT))
      mcxErr(me, "continuing (cf --system-honor)")
   ;  else if (!ok)
      seg_check_ok(ok, seg)

   ;  return yamSegPush(seg, out)  /* fixme: systemHonor logic still ok ? */
;  }


                                 /* pivot ? switch : branch */
yamSeg* expand_switch_
(  yamSeg*  seg
,  mcxTing* pivot                /* we claim ownership */
,  mcxTing* body                 /* we claim ownership */
)
   {  mcxTing*  clause  =  mcxTingEmpty(NULL, 30)
   ;  mcxTing*  yamtxt  =  mcxTingEmpty(NULL, 30)
   ;  mcxbool  ok       =  TRUE
   ;  int   x           =  -1

   ;  yamSeg*  tmpseg   =  yamStackPushTmp(body)

   ;  if
      (  (tmpseg->flags & SEGMENT_ERROR)
      || (pivot && yamDigest(pivot, pivot, seg))
      )
      ok  = FALSE

   ;  while (ok && (x = yamParseScopes(tmpseg, 2, 0)) == 2)
      {  mcxTingWrite(clause, arg1_g->str)
      ;  mcxTingWrite(yamtxt, arg2_g->str)

      ;  if (yamDigest(clause, clause, seg))
         {  ok = FALSE
         ;  break
      ;  }
         if
         (  (pivot && !strcmp(clause->str, pivot->str))  /* switch */
         || (!pivot && clause->len && atol(clause->str)) /* branch */
         )
         break             /* branch: empty string fails */
   ;  }


      if (!ok)                      /* clause parse error or stack size */
      mcxTingEmpty(yamtxt, 0)
   ;  else if (x < 0)               /* parse error (e.g. no closing scope) */
      ok = FALSE
   ;  else if (x == 1)              /* fall through / else clause */
      mcxTingWrite(yamtxt, arg1_g->str)
   ;  else if (x == 0)              /* nothing matched */
      mcxTingEmpty(yamtxt, 0)

   ;  seg_check_ok(ok, seg)

   ;  mcxTingFree(&pivot)
   ;  mcxTingFree(&clause)

   ;  yamStackFreeTmp(&tmpseg)
   ;  mcxTingFree(&body)

   ;  return yamSegPush(seg, yamtxt)
;  }


yamSeg* expandSwitch2
(  yamSeg*  seg
)
   {  mcxTing*  pivot      =  mcxTingNew(arg1_g->str)     
   ;  mcxTing*  body       =  mcxTingNew(arg2_g->str)     
   ;  return expand_switch_(seg, pivot, body)
;  }


yamSeg* expandBranch1
(  yamSeg*  seg
)
   {  mcxTing*  body       =  mcxTingNew(arg1_g->str)     
   ;  return expand_switch_(seg, NULL, body)
;  }


yamSeg* expandConstant1
(  yamSeg*  seg
)
   {  mcxTing*  yamtxt  =  mcxTingNew(arg1_g->str)     
   ;  yamSeg*  newseg   =  yamStackPushTmp(yamtxt)
   ;  mcxbool  ok       =  TRUE
   ;  int x             =  -1

   ;  while ((x = yamParseScopes(newseg, 2, 0)) == 2)
      {  mcxTing* key      =  mcxTingNew(arg1_g->str)
      ;  if (yamConstantNew(key, arg2_g->str) != key)
         mcxTingFree(&key)
   ;  }

      if (x < 0)
      ok  = FALSE
   ;  else if (x == 1)
      yamErr("\\constant#1", "spurious element")

   ;  yamStackFreeTmp(&newseg)
   ;  mcxTingFree(&yamtxt)

   ;  seg_check_ok(ok, seg)
   ;  return seg
;  }


yamSeg* expandSpecial1
(  yamSeg*  seg
)
   {  mcxTing*  yamtxt     =  mcxTingNew(arg1_g->str)     
   ;  int      x           =  -1
   ;  int      ct          =  0
   ;  mcxbool  ok          =  TRUE  
   ;  yamSeg*  newseg

   ;  if (yamDigest(yamtxt, yamtxt, seg))
      return yamSegPush(seg, mcxTingEmpty(yamtxt, 0))

  ;   newseg = yamStackPushTmp(yamtxt)

   ;  while ((x = yamParseScopes(newseg,2, 0)) == 2)
      {  int   c           =  atoi(arg1_g->str)
      ;  yamSpecialSet(c, arg2_g->str)
      ;  ct += x
   ;  }

      if (x < 0)
      ok  = FALSE
   ;  else if (!ct)
      yamSpecialClean()
         /* perhaps we should reset the special level of the default output
          * stream to 1.
         */
   ;  else if (x == 1)
      yamErr("\\special#1", "spurious element")

   ;  yamStackFreeTmp(&newseg)
   ;  mcxTingFree(&yamtxt)

   ;  seg_check_ok(ok, seg)
   ;  return seg
;  }


/*
 *    arguments:  1  anchor
 *                2  level
 *                3  type
 *                4  counter
 *                5  caption
 *                6  misc
*/

yamSeg*  expandRefload6
(  yamSeg* seg
)
   {  mcxbool newhdl =  yamRefNew
                        (  arg1_g->str ,  arg2_g->str ,  arg3_g->str
                        ,  arg4_g->str ,  arg5_g->str ,  arg6_g->str
                        )
   ;  if (!newhdl)
      yamErr("\\refload#6", "ref <%s> multiply defined\n", arg1_g->str)

   ;  return seg
;  }


yamSeg*  expandCtrput1
(  yamSeg* seg
)
   {  mcxTing*   label      =  mcxTingNew(arg1_g->str)
   ;  mcxTing*   ctr, *txt = mcxTingEmpty(NULL, 8)

   ;  if (yamDigest(label, label, seg))   /* fixme issue/push some error? */
   ;  else
         ctr = yamCtrGet(label)
      ,  mcxTingWrite(txt, ctr ? ctr->str : "0")

   ;  mcxTingFree(&label)
   ;  return yamSegPush(seg, txt)
;  }


yamSeg*  expandCtrset2
(  yamSeg* seg
)
   {  mcxTing*  label      =  mcxTingNew(arg1_g->str)
   ;  mcxTing*  newval     =  mcxTingNew(arg2_g->str)
   ;  mcxTing*  ctr

   ;  if (yamDigest(label, label, seg) || yamDigest(newval, newval, seg))
      {  mcxTingFree(&label)
      ;  mcxTingFree(&newval)
      ;  return seg        /* fixme issue/push some error? */
   ;  }

      if ((ctr = yamCtrGet(label)))
      mcxTingFree(&label)
   ;  else
      ctr = yamCtrMake(label)

   ;  yamCtrWrite(ctr, newval->str)
   ;  mcxTingFree(&newval)

   ;  return seg
;  }


yamSeg*  expandCtradd2
(  yamSeg* seg
)
   {  mcxTing* label       =  mcxTingNew(arg1_g->str)
   ;  mcxTing* addtxt      =  mcxTingNew(arg2_g->str)
   ;  mcxTing* ctr
   ;  int      a           =  0
   ;  int      c           =  0

   ;  if (yamDigest(label, label, seg) || yamDigest(addtxt, addtxt, seg))
      {  mcxTingFree(&label)
      ;  mcxTingFree(&addtxt)
      ;  return seg        /* fixme issue/push some error? */
   ;  }

      ctr = yamCtrGet(label)

   ;  a  =  atoi(addtxt->str)
   ;  mcxTingFree(&addtxt)

   ;  if (ctr)
      {  c  =  atoi(ctr->str)
      ;  mcxTingFree(&label)
   ;  }
      else
      ctr = yamCtrMake(label)

   ;  c +=  a
   ;  yamCtrSet(ctr, c)

   ;  return seg
;  }


yamSeg*  expand_cmp_
(  yamSeg*  seg
,  char     mode
)
   {  mcxTing*  test     =  mcxTingNew(arg1_g->str)
   ;  mcxTing*  op1      =  mcxTingNew(arg2_g->str)
   ;  mcxTing*  op2      =  mcxTingNew(arg3_g->str)
   ;  char*     yes      =  "1"
   ;  char*     no       =  "0"
   ;  char*     bool     =  no
   ;  double    diff, abs

   ;  if (yamDigest(op1, op1, seg) || yamDigest(op2, op2, seg))
      {  mcxTingFree(&op1)
      ;  mcxTingFree(&op2)
      ;  mcxTingFree(&test)
      ;  return seg        /* fixme issue/push some error? */
   ;  }

      diff  =     (mode == 's')
               ?  strcmp(op1->str, op2->str)
               :  atof(op1->str) - atof(op2->str)
   ;  abs   =  diff*SIGN(diff)

   ;  mcxTingFree(&op1)
   ;  mcxTingFree(&op2)

   ;  if (!strcmp(test->str, "cp"))
      {  mcxTingFree(&test)
      ;  return
         yamSegPush
         (seg, mcxTingNew(abs < precision_g ? "0" : diff > 0 ? "1" : "-1"))
   ;  }
      else if (!strcmp(test->str, "lt") && diff < -precision_g)
      bool =  yes
   ;  else if (!strcmp(test->str, "lq") && diff < precision_g)
      bool =  yes
   ;  else if (!strcmp(test->str, "eq") && abs  <= precision_g)
      bool =  yes
   ;  else if (!strcmp(test->str, "gq") && diff > -precision_g)
      bool =  yes
   ;  else if (!strcmp(test->str, "gt") && diff > precision_g)
      bool =  yes
   ;  else if (!strcmp(test->str, "ne") && abs  > precision_g)
      bool =  yes
   ;  else if (!strstr("cp\001lt\001lq\001eq\001gq\001gt\001ne", test->str))
         mcxErr("cmp/eqt", "[%c] not a mode: <%s>", (int) mode, test->str)
      ,  seg_check_ok(FALSE, seg)
      ,  bool = ""

   ;  mcxTingFree(&test)
   ;  return yamSegPush(seg, mcxTingNew(bool))
;  }


yamSeg*  expandEqt3
(  yamSeg* seg
)
   {  return expand_cmp_(seg, 'i')
;  }

yamSeg*  expandCmp3
(  yamSeg* seg
)
   {  return expand_cmp_(seg, 's')
;  }


yamSeg*  expandInspect4
(  yamSeg* seg
)
   {  return yamInspect4(seg)
;  }


yamSeg*  expandTr4
(  yamSeg* seg
)
   {  mcxTing* mods  =  mcxTingNew(arg1_g->str)
   ;  mcxTing* pat   =  mcxTingNew(arg2_g->str)
   ;  mcxTing* sub   =  mcxTingNew(arg3_g->str)
   ;  mcxTing* data  =  mcxTingNew(arg4_g->str)
   ;  int trflags    =  0
   ;  mcxbool  ok    =  FALSE

   ;  while(1)
      {  if (strchr(mods->str, 'd') != NULL)
         trflags |=  TR_DELETE
      ;  if (strchr(mods->str, 's') != NULL)
         trflags |=  TR_SQUASH
      ;  if (strchr(mods->str, 'c') != NULL)
         trflags |=  TR_COMPLEMENT

      ;  if (yamDigest(data, data, seg))
         break
      ;  if (mcxTingTr(data, pat->str, sub->str, trflags)<0)
         {  yamErr("\\tr#2", "spec did not parse")
         ;  break
      ;  }
         ok = TRUE
      ;  break
   ;  }

      seg_check_ok(ok, seg)

   ;  mcxTingFree(&mods)
   ;  mcxTingFree(&pat)
   ;  mcxTingFree(&sub)

   ;  return yamSegPush(seg, data)
;  }


/* strcmp should be factored out */

enum
{  F_UNKNOWN = 1
,  F_MUL ,  F_ADD ,  F_SUB ,  F_POW ,  F_FRAC ,  F_DIV ,  F_MOD
,  F_AND ,  F_OR  ,  F_NOT ,  F_INV
,  F_MAX ,  F_MIN
,  F_CEIL,  F_FLOOR  
,  F_ABS ,  F_SIGN,  F_ROUND , F_DEC,  F_INC  
}  ;


int fType
(  mcxTing* mode
)
   {  const char* s = mode->str
   ;  if (mode->len == 1)
      switch(*s)
      {  case '+' :                    return F_ADD     ;  break
      ;  case '-' :                    return F_SUB     ;  break
      ;  case '/' :                    return F_FRAC    ;  break
      ;  case '*' :                    return F_MUL     ;  break
      ;  case '%' :                    return F_MOD     ;  break
   ;  }
      else if (mode->len == 3)   /* very poor addressing scheme, I know */
      {       if (!strcmp(s, "abs"))   return F_ABS

      ;  else if (!strcmp(s, "and"))   return F_AND
      ;  else if (!strcmp(s, "min"))   return F_MIN
      ;  else if (!strcmp(s, "max"))   return F_MAX
      ;  else if (!strcmp(s, "div"))   return F_DIV
      ;  else if (!strcmp(s, "mod"))   return F_MOD
      ;  else if (!strcmp(s, "pow"))   return F_POW
      ;  else if (!strcmp(s, "mul"))   return F_MUL
      ;  else if (!strcmp(s, "sub"))   return F_SUB
      ;  else if (!strcmp(s, "add"))   return F_ADD
      ;  else if (!strcmp(s, "sum"))   return F_ADD

      ;  else if (!strcmp(s, "dec"))   return F_DEC
      ;  else if (!strcmp(s, "inc"))   return F_INC
   ;  }
      else if (mode->len == 4)
      {       if (!strcmp(s, "sign"))  return F_SIGN
      ;  else if (!strcmp(s, "ceil"))  return F_CEIL
   ;  }
      else
      {       if (!strcmp(s, "round")) return F_ROUND
      ;  else if (!strcmp(s, "floor")) return F_FLOOR
      ;  else if (!strcmp(s, "or"))    return F_OR
      ;  else if (!strcmp(s, "**"))    return F_POW
      ;  else if (!strcmp(s, "//"))    return F_DIV
      ;  else if (!strcmp(s, "&&"))    return F_AND
      ;  else if (!strcmp(s, "||"))    return F_OR
   ;  }
      return F_UNKNOWN
;  }


yamSeg*  expandF2
(  yamSeg* seg
)
   {  mcxTing*  mode       =  mcxTingNew(arg1_g->str)
   ;  mcxTing*  ftxt       =  mcxTingNew(arg2_g->str)
   ;  mcxTing*  yamtxt     =  NULL
   ;  double f, g =  0.0
   ;  long   i, j =  0
   ;  int modus = fType(mode)
   ;  mcxbool ok  =  TRUE

   ;  if (yamDigest(ftxt, ftxt, seg))
      goto fail

   ;  f  =  atof(ftxt->str)
   ;  i  =  atoi(ftxt->str)

   ;  switch(modus)
      {  case F_FLOOR   : g = floor(f)          ;  break
      ;  case F_CEIL    : g = ceil(f)           ;  break
      ;  case F_ROUND   : g = floor(f+0.5)      ;  break
      ;  case F_ABS     : g = f < 0 ? -f : f    ;  break
      ;  case F_SIGN    : j = f * SIGN(f) <= precision_g ? 0 : f<0 ? -1.0 : 1
                                                ;  break
      ;  case F_INC     : g = f + 1.0           ;  break
      ;  case F_DEC     : g = f - 1.0           ;  break
      ;  case F_NOT     : g = f ? 0.0 : 1.0     ;  break
      ;  default        :
         yamErr("\\f#2", "unknown mode <%s>", mode->str)
      ;  goto fail
   ;  }

      if (j)
      yamtxt = mcxTingInteger(NULL, j)
   ;  else
      {  double eps = g - floor(g+0.5)
      ;  if (eps * SIGN(eps) <= precision_g && g*SIGN(g) <= LONG_MAX)
         yamtxt = mcxTingInteger(NULL,  roundl(g))
      ;  else
         yamtxt = mcxTingDouble(NULL, g, 10)
   ;  }

      if(0)
      fail: ok =  FALSE       /* fixme ugly goto, even harmful */

   ;  if (!ok)
      yamtxt = mcxTingEmpty(yamtxt, 0)

   ;  seg_check_ok(ok, seg)

   ;  mcxTingFree(&ftxt)
   ;  mcxTingFree(&mode)
   ;  return yamSegPush(seg, yamtxt)
;  }


yamSeg*  expandF3
(  yamSeg* seg
)
   {  mcxTing*  mode       =  mcxTingNew(arg1_g->str)
   ;  mcxTing*  f1txt      =  mcxTingNew(arg2_g->str)
   ;  mcxTing*  f2txt      =  mcxTingNew(arg3_g->str)
   ;  mcxTing*  f3txt      =  NULL
   ;  double eps, f1 = 0.0, f2 = 0.0, f3 = 0.0
   ;  long i1 = 0, i2 = 0, done = 0
   ;  int modus = fType(mode)
   ;  mcxbool ok  =  TRUE

   ;  if (yamDigest(f1txt, f1txt, seg))
      goto fail

   ;  f1  =  atof(f1txt->str)
   ;  i1  =  atoi(f1txt->str)

   ;  if ((modus == F_AND && !i1) || (modus == F_OR && i1))
      {  f3 = i1 ? 1.0 : 0.0
      ;  done  =  1              /* short-circuit */
   ;  }
      else
      {  if (yamDigest(f2txt, f2txt, seg))
         goto fail
      ;  f2  =  atof(f2txt->str)
      ;  i2  =  atoi(f2txt->str)
   ;  }

   ;  if (done)
     /*  nothing */
   ;  else
      switch(modus)
      {  
         case F_ADD     :  f3 = f1 + f2                  ; break
      ;  case F_SUB     :  f3 = f1 - f2                  ; break
      ;  case F_FRAC    :  f3 = f2 ? (f1/f2) : 0         ; break
      ;  case F_DIV     :  f3 = f2 ? floor (f1/f2) : 0   ; break
      ;  case F_MUL     :  f3 = f1 * f2                  ; break
      ;  case F_POW     :  f3 = pow(fabs(f1), f2)        ; break

      ;  case F_AND     :  f3 = i1 && i2                 ; break
      ;  case F_OR      :  f3 = i1 || i2                 ; break

      ;  case F_MOD     :  f3 = f2 ? ((f1/f2)-floor(f1/f2))*f2 : 0  ; break
      ;  case F_MAX     :  f3 = MAX(f1, f2)              ; break
      ;  case F_MIN     :  f3 = MIN(f1, f2)              ; break

      ;  case F_CEIL    :  f3 = (ceil(f1/f2))*f2         ; break
      ;  case F_FLOOR   :  f3 = (floor(f1/f2))*f2        ; break
      ;  default        :
         yamErr("\\f#3", "unknown mode <%s>", mode->str)
      ;  goto fail
   ;  }

      if
      (  (modus == F_DIV || modus == F_FRAC || modus == F_MOD)
      && !f2
      )
      yamErr("\\f#3", "arithmetic exception for operator <%s>", mode->str)

   ;  eps = f3 - floor(f3+0.5)

   ;  if (eps * SIGN(eps) <= precision_g && f3*SIGN(f3) <= LONG_MAX)
      f3txt = mcxTingInteger(NULL,  roundl(f3))
   ;  else
      f3txt = mcxTingDouble(NULL, f3, 10)

   ;  if(0)
      fail: ok = FALSE       /* fixme ugly goto, even harmful */

   ;  if (!ok)
      f3txt = mcxTingEmpty(f3txt, 0)

   ;  seg_check_ok(ok, seg)

   ;  mcxTingFree(&f1txt)
   ;  mcxTingFree(&f2txt)
   ;  mcxTingFree(&mode)

   ;  return yamSegPush(seg, f3txt)
;  }



yamSeg*  expandFv2
(  yamSeg* seg
)
   {  mcxTing*    mode        =  mcxTingNew(arg1_g->str)
   ;  mcxTing*    data        =  mcxTingNew(arg2_g->str)
   ;  long        ival        =  0
   ;  double      fval        =  0.0
   ;  yamSeg*     valseg      =  NULL
   ;  mcxTing*    valtxt      =  NULL
   ;  int         x           =  -1
   ;  int         modus       =  fType(mode)
   ;  int         ct          =  0
   ;  mcxbool     ok          =  TRUE

   ;  if (modus == F_MIN)
      fval = DBL_MAX
   ;  else if (modus == F_MAX)
      fval = -DBL_MAX
   ;  else if (modus == F_MUL)
      fval = 1.0
   ;  else if (modus == F_AND)
      ival = 1
   ;  else if (modus == F_OR)
      ival = 0

   ;  valseg = yamStackPushTmp(data)

   ;  while (ok && (x = yamParseScopes(valseg, 1, 0)) == 1)
      {  long i
      ;  double f
      ;  mcxTing* op = mcxTingNew(arg1_g->str)
      ;  if (yamDigest(op, op, seg))
         {  mcxTingFree(&op)
         ;  goto fail         /* fixme ugly goto */
      ;  }
         i = atoi(op->str)
      ;  f = atof(op->str)
      ;  mcxTingFree(&op)

      ;  switch(modus)
         {  case F_ADD     :  fval +=  f  ;  break
         ;  case F_MUL     :  fval *=  f  ;  break
         ;  case F_AND     :  ival = ival && i     ; break
         ;  case F_OR      :  ival = ival || i     ; break
         ;  case F_MAX     :  fval = MAX(fval, f)  ; break
         ;  case F_MIN     :  fval = MIN(fval, f)  ; break
         ;  default :
            yamErr("\\fv#2", "unknown mode <%s>", mode->str)
         ;  goto fail
      ;  }

         ct++
      ;  if ((modus == F_OR && ival) || (modus == F_AND && !ival))
         break                                  /* short-circuit */
   ;  }
      if (x < 0)
      {  goto fail
   ;  }
      else if (ct == 0)
      {  fval = 0.0
      ;  ival = 0
      ;  yamErr("\\fv#2", "I have an empty vararg!")
   ;  }

      if (!fval)
      valtxt = mcxTingInteger(NULL, ival)
   ;  else
      {  double eps = fval - floor(fval+0.5)
      ;  if (eps * SIGN(eps) <= precision_g && fval*SIGN(fval) <= LONG_MAX)
         valtxt = mcxTingInteger(NULL, roundl(fval))
      ;  else
         valtxt = mcxTingDouble(NULL, fval, 10)
   ;  }

      if(0)
      fail: ok = FALSE

   ;  if (!ok)
      valtxt = mcxTingEmpty(valtxt, 0)

   ;  seg_check_ok(ok, seg)

   ;  yamStackFreeTmp(&valseg)
   ;  mcxTingFree(&data)
   ;  mcxTingFree(&mode)

   ;  return yamSegPush(seg, valtxt)
;  }


yamSeg*  expandRedirect1
(  yamSeg* seg
)
   {  mcxTing* newname = mcxTingNew(arg1_g->str)
   ;  mcxTing* curname = sinkGetDefaultName()  
   ;  mcxIO* xf  
   ;  mcxbool ok = FALSE

   ;  while (1)
      {  if (yamDigest(newname, newname, seg))
         break

      ;  if (!curname)
            mcxErr("\\redirect#1", "no current file")
         ,  mcxExit(1)
                           /* fixme, or not? pbb PBD */

      ;  yamOutputClose(curname->str)

      ;  if (!(xf = yamOutputNew(newname->str)))
            mcxErr("\\redirect#1", "unable to open file <%s>", newname->str)
         ,  mcxExit(1)
                           /* fixme (no exit) */
      ;  mcxTell
         (  "zoem"
         ,  "changing default output from <%s> to <%s>"
         ,  curname->str
         ,  newname->str
         )

      ;  sinkSetDefault(xf->usr, ZOEM_FILTER_DEFAULT)
      ;  ok = TRUE
      ;  break
   ;  }

      mcxTingFree(&newname)
   ;  mcxTingFree(&curname)
   ;  return seg
;  }


/*
 * Dependency with yamRefMember. Agreement:
 * it only returns NULL if second arg not in [ntlcm].
*/

yamSeg*  expandRef2
(  yamSeg* seg
)
   {  const char* member   =  yamRefMember(arg1_g, *(arg2_g->str+0))
   ;  mcxTing* memtxt      =  member ? mcxTingNew(member) : NULL
   ;  if (!memtxt)
      {  yamErr("\\ref#2", "second argument invalid (not in [ntlcm]")
      ;  seg_check_ok(FALSE, seg)
      ;  return seg
   ;  }
      return yamSegPush(seg, memtxt)
;  }


yamSeg*  expandPush1
(  yamSeg* seg
)
   {  return yamScopePush(arg1_g->str) ? NULL : seg
;  }


yamSeg*  expandPop1
(  yamSeg* seg
)
   {  return yamScopePop(arg1_g->str) ? NULL : seg
;  }


yamSeg*  expandEval1
(  yamSeg* seg
)
   {  mcxTing*  txt = mcxTingNew(arg1_g->str)

   ;  yamDigest(txt, txt, seg)
   ;  return yamSegPush(seg, txt)
;  }


yamSeg*  expandExit0
(  yamSeg* seg
)
   {  yamErr("\\exit", "premature exit -- goodbye")
   ;  exit(1)
;  }


yamSeg* expandTrace1
(  yamSeg*   seg
)
   {  mcxTing* t =  mcxTingNew(arg1_g->str)
   ;  static int tracing_prev = 0
   ;  int val

   ;  if (yamDigest(t, t, seg))
      {  mcxTingFree(&t)
      ;  return seg
   ;  }

      val = atoi(t->str)

   ;  if (val == -3)
      tracing_prev = yamTracingSet(tracing_prev)
   ;  else if (val == -4)
      showTracebits()
   ;  else
      tracing_prev = yamTracingSet(val)

   ;  mcxTingFree(&t)
   ;  return   val == -4
            ?  seg
            :  yamSegPush(seg, mcxTingInteger(NULL, tracing_prev))
;  }


yamSeg* expandEnv3
(  yamSeg*   seg
)
   {  return yamEnvNew(arg1_g->str, arg2_g->str, arg3_g->str) ? NULL : seg
;  }


yamSeg* expandBegin2
(  yamSeg*   seg
)
   {  if (yamScopePush("dollar"))         /* localize everything */
      return NULL

   ;  {  const char* b = yamEnvOpen(arg1_g->str, arg2_g->str, seg)
      ;  mcxTing* val = b ? mcxTingNew(b) : NULL

      ;  if (!val)
         {  yamScopePop("dollar")
         ;  seg_check_ok(FALSE, seg)
         ;  return seg
      ;  }
         return yamSegPush(seg, val)
   ;  }
      return NULL
;  }


yamSeg* expandEnd1
(  yamSeg*   seg
)
   {  mcxTing* end      =  mcxTingNew(arg1_g->str) 
   ;  const char* def   =  yamEnvEnd(end->str, seg)
   ;  mcxTing* val      =  def ? mcxTingNew(def) : NULL
   ;  mcxbool  ok       =  FALSE

   ;  while(1)
      {  if (!val)
         {  yamErr("\\end#2", "env <%s> not found", end->str)
         ;  val = mcxTingEmpty(NULL, 0)
         ;  break
      ;  }
         if (yamDigest(val, val, seg))
         break
                        /* must eval now, because we need to pop here */
      ;  if (yamEnvClose(end->str))  /* this pops the env stack */
         break
      ;  if (yamScopePop("dollar"))  /* no trailing garbage */
         break
      ;  ok = TRUE
      ;  break
   ;  }

      seg_check_ok(ok, seg)

   ;  mcxTingFree(&end)
   ;  return yamSegPush(seg, val)
;  }


yamSeg* expand_set_
(  yamSeg*  seg
,  const char* me
,  mcxbool  warn
,  mcxbool  expand
)
   {  mcxbool     data     =  arg1_g->str[0] == '%'
   ;  mcxTing*    valtxt   =  mcxTingNew(arg2_g->str)
   ;  mcxbool     ok       =  FALSE
   ;  mcxTing*    key      =  NULL
   ;  mcxTing*    access   =  NULL
   ;  int keylen

   ;  if (data)
      while (1)
      {  mcxbool overwrite =  FALSE
      ;  access     =  mcxTingNew(arg1_g->str+1)
      ;  if (expand && yamDigest(valtxt, valtxt, seg))
         break
      ;  if (yamDigest(access, access, seg))
         break
      ;  if (yamOpsDataAccess(access))
         break
      ;  if (yamDataSet(valtxt->str, warn, &overwrite))
         break
      ;  if (warn && overwrite)
         yamErr
         (me, "overwrite in access/data <%s><%s>", access->str, valtxt->str)
      ;  ok = TRUE
      ;  break
   ;  }
      else
      while(1)
      {  key = mcxTingNew(arg1_g->str)
      ;  keylen = checkusrsig(key->str, key->len, NULL)
      ;  if (keylen <= 0 || keylen != key->len)
         {  yamErr(me, "not a valid key signature: <%s>", key->str)
         ;  break
      ;  }
         else if (mcxHashSearch(key, yamTable_g, MCX_DATUM_FIND))
         {  yamErr(me, "key tagged <%s> is a zoem primitive", key->str)
         ;  break
      ;  }

         if (expand && yamDigest(valtxt, valtxt, seg))
         {  yamErr(me, "argument did not parse")
         ;  break
      ;  }

         if (yamKeyInsert(key, valtxt->str) != key)
         {  if (warn)
            yamErr(me, "overwriting key <%s>",key->str)
         ;  mcxTingFree(&key)
      ;  }
         ok =  TRUE
      ;  break
   ;  }

      if (!ok)
      mcxTingFree(&key)  /* fixme better key-init-free logic */

   ;  seg_check_ok(ok, seg)

   ;  mcxTingFree(&access)
   ;  mcxTingFree(&valtxt)

   ;  return seg
;  }


yamSeg* expandDef2
(  yamSeg*   seg
)
   {  return expand_set_(seg, "\\def#2", TRUE, FALSE)
;  }


yamSeg* expandDefx2
(  yamSeg*  seg
)
   {  return expand_set_(seg, "\\defx#2", TRUE, TRUE)
;  }


yamSeg* expandSet2
(  yamSeg*   seg
)
   {  return expand_set_(seg, "\\set#2", FALSE, FALSE)
;  }


yamSeg* expandVanish1
(  yamSeg*   seg
)
   {  mcxTing* stuff    =  mcxTingNew(arg1_g->str)
   ;  mcxstatus status  =  yamOutput(stuff, NULL, ZOEM_FILTER_NONE)

   ;  mcxTingFree(&stuff)
   ;  seg_check_status(status, seg)
   ;  return status ? yamSegPushEmpty(seg) : seg
;  }


yamSeg* expandSetx2
(  yamSeg*  seg
)
   {  return expand_set_(seg, "\\setx#2", FALSE, TRUE)
;  }


mcxenum let_cb
(  const char* token
,  long *ival
,  double *fval
)
   {  mcxTing* txt = mcxTingNew(token)
   ;  mcxenum stat = TRM_FAIL

   ;  if (yamDigest(txt, txt, NULL))
      {  mcxTingWrite(txt, "0")
      ;  stat = TRM_FAIL
   ;  }
      else if (mcxStrChrAint(txt->str, isdigit, -1))
      {  *fval = atof(txt->str)
      ;  stat = TRM_ISREAL
   ;  }
      else
      {  *ival = atol(txt->str)
      ;  stat = TRM_ISNUM
      ;  *fval = *ival
   ;  }

      mcxTingFree(&txt)
   ;  return stat
;  }


yamSeg* expandDone0
(  yamSeg* seg
)
   {  while (seg->prev)
      {  yamSeg* prev_seg  =  seg
      ;  seg               =  seg->prev
      ;  yamSegFree(&prev_seg)
   ;  }
      seg->offset = seg->txt->len
   ;  return seg
;  }


/* fixme; try to account for at scope as well [uh, seriously?]
 * (best shot should be possible simply by ignoring escape sequences).
*/

yamSeg* expandLength1
(  yamSeg*   seg
)
   {  mcxTing* stuff = mcxTingNew(arg1_g->str)
   ;  char* p
   ;  int len = 0, c, mode = 1

   ;  if (yamDigest(stuff, stuff, seg))
      return yamSegPush(seg, mcxTingEmpty(stuff, 0))

   ;  p = stuff->str

   ;  while((c = (p++)[0]))
      len += (mode = (c == '\\' && mode ? 0 : 1))

   ;  return yamSegPush(seg, mcxTingPrint(stuff, "%d", len))
;  }


yamSeg* expandLet1
(  yamSeg*   seg
)
   {  telRaam* raam = trmInit(arg1_g->str)
   ;  long ival   =  0
   ;  double fval =  0.0
   ;  int  stat

   ;  trmRegister(raam, checkusrcall, let_cb, '\\')

   ;  if (trmParse(raam))
      {  yamErr("\\let#1", "expression did not parse")
      ;  trmExit(raam)
      ;  seg_check_ok(FALSE, seg)
      ;  return yamSegPush(seg, mcxTingInteger(NULL, 0))
   ;  }

      if (tracing_g & ZOEM_TRACE_LET)
      trmDump(raam, "let")

   ;  stat =  trmEval(raam, &ival, &fval)

   ;  if (tracing_g & ZOEM_TRACE_LET)
      trmDump(raam, "let")

   ;  if (trmError(stat))
      {  yamErr("\\let#1", "arithmetic error occurred")
      ;  trmExit(raam)
      ;  seg_check_ok(FALSE, seg)
      ;  return yamSegPush(seg, mcxTingInteger(NULL, 0))
   ;  }

      trmExit(raam)

   ;  if (trmIsNum(stat))
      return yamSegPush(seg, mcxTingInteger(NULL, ival))
   ;  else if (trmIsReal(stat))
      return yamSegPush(seg, mcxTingDouble(NULL, fval, 10))
   ;  else
      return yamSegPush(seg, mcxTingInteger(NULL, 0))

   ;  return seg
;  }


void yamOpsStats
(  void
)
   {  mcxHashStats(stdout, yamTable_g)
;  }


void mod_ops_exit
(  void
)
   {  if (yamTable_g)
      mcxHashFree(&yamTable_g, mcxTingFree_v, NULL)
   ;  if (devtxt_g)
      mcxTingFree(&devtxt_g)
;  }


void mod_ops_init
(  int   n
)
   {  cmdHook* cmdhook     =  cmdHookDir

   ;  devtxt_g             =  mcxTingNew("__device__")
   ;  yamTable_g           =  yamHashNew(n)

   ;  while (cmdhook && cmdhook->name)
      {  mcxTing*  cmdtxt  =  mcxTingNew(cmdhook->name)
      ;  mcxKV*   kv       =  mcxHashSearch(cmdtxt,yamTable_g,MCX_DATUM_INSERT)
      ;  kv->val           =  cmdhook
      ;  cmdhook++
   ;  }
   }


void yamOpsMakeComposites
(  void
)
   {  mcxTing* t = mcxTingEmpty(NULL, 40)
   ;  int m
   ;  for (m=0;strComposites[m];m++)
      {  mcxTingWrite(t, strComposites[m])
      ;  if (yamDigest(t, t, NULL))
         yamErr("init PBD", "syntax error in predefined macros (embarassing)")
      ,  exit(1)
   ;  }
      mcxTingFree(&t)
;  }


xpnfnc yamOpGet
(  mcxTing* txt
)
   {  mcxKV* kv = mcxHashSearch(txt, yamTable_g, MCX_DATUM_FIND)
   ;  if (kv)
      return ((cmdHook*) kv->val)->yamfunc

   ;  return NULL
;  }

