Login
shell.c at [ee03f7343f]
Login

File th1ish/shell.c artifact e8a96f5b85 part of check-in ee03f7343f


/**
   Test/demo code for the cwal library and th1ish scripting engine.
*/
#if defined(NDEBUG)
/* force assert() to always work */
#  undef NDEBUG
#endif
#include <assert.h>
#include <string.h>
#include <stdio.h>

#if 0
#include <float.h> /* DBL_MAX */
#endif

#if !defined(TH1ISH_AMALGAMATION_BUILD)
#include "wh/cwal/cwal.h"
#include "th1ish.h"
#if 1
#include "../cwal_internal.h" /* only for some cwal debuggering stuff */
#endif
#else
#include "th1ish_amalgamation.h"
extern void cwal_dump_interned_strings_table( cwal_engine * e, char showEntries, cwal_size_t includeStrings);
#endif

#if defined(TH1ISH_SHELL_EXTEND)
extern int th1ish_shell_extend(th1ish_interp * ie, int argc, char const * const * argv);
/* #  include "shell_extend.c" */
#endif


/**
   For access() resp. _access().

   FIXME: move the platform determination somewhere more global.
*/
#if defined(_WIN32)
#  define SMELLS_LIKE_UNIX 0
#else
#  define SMELLS_LIKE_UNIX 1
#endif

#if SMELLS_LIKE_UNIX
#  include <unistd.h> /* access(), fork() */
#  include <errno.h> /* fork() error codes */
#  include <sys/types.h> /* pid_t */
#else
#  include  <io.h>
#endif

#define MARKER(pfexp)                                                \
    do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
        printf pfexp; \
    } while(0)

enum { SHELL_MAX_E_SCRIPTS = 10 };

static struct {
    char const * inFile;
    char const * outFile;
    char tokenizeOnly;
    char traceAssertions;
    char enableStringInterning;
    char verbose;
    char enableTracing;
    char enableValueRecycling;
    char enableTokenRecycling;
    char enableApiNamespace;
    char enableStackTraces;
    char enableConventionalCallOp;
    char enableExtraLoopScopes;
    char enableProcTokenCaching;
    char enableArgv0Autoload;
    char dumpStringsTable;
    char dumpAllocMetrics;
    char testMode;
    int sweepInterval;
    int vacuumInterval;
    char sweepIntervalLog;
    cwal_size_t tokenRecyclerSize;
    cwal_json_output_opt JsonOutOpt;
    struct {
        int count;
        char const * list[SHELL_MAX_E_SCRIPTS];
    } escript;
} App = {
0/*inFile*/,
0/*outFile*/,
0/*tokenizeOnly*/,
0/*traceAssertions*/,
1/*enableStringInterning*/,
0/*verbose*/,
0/*enableTracing*/,
1/*enableValueRecycling*/,
1/*enableTokenRecycling*/,
1/*enableApiNamespace*/,
1/*enableStackTraces*/,
1/*enableConventionalCallOp*/,
0/*enableExtraLoopScopes*/,
1/*enableProcTokenCaching*/,
1/*enableArgv0Autoload*/,
0/*dumpStringsTable*/,
0/*dumpAllocMetrics*/,
0/*testMode*/,
-1/*sweepInterval*/,
-1/*vacuumInterval*/,
0/*sweepIntervalLog*/,
/*
  tokenRecyclerSize

  Notes: sizeof(cwal_simple_token) is currently 20 on 32-bits and
  32 on 64-bits. (8kb/that_sizeof) == 409 (32-bits) or 256
  (64-bits).
*/
#if 0
1024 * 16 / sizeof(cwal_simple_token),
#else
200,
#endif
cwal_json_output_opt_empty_m/*JsonOutOpt*/,
{/*escript*/ 0/*count*/, {/*list*/0}}
};
#define VERBOSE(pfexp)                                                  \
    if(App.verbose){                                                    \
        printf("VERBOSE: %s:%d:%s():\t",__FILE__,__LINE__,__func__);    \
        printf pfexp;                                                   \
    } (void)0

static void dump_json( cwal_value * v ){
    assert(v);
    cwal_json_output_FILE( v, stdout, &App.JsonOutOpt );
}
static void dump_exception( cwal_value * e ){
    cwal_value * xV = e;
    cwal_exception * x = xV ? cwal_value_get_exception(xV) : 0;
    int code = x ? cwal_exception_code_get(x) : CWAL_RC_ERROR;
    if(!x) return;
    MARKER(("EXCEPTION@%p code=%d\n",
            (void const *)xV, code ));
    if(xV){
        cwal_value const * l = cwal_prop_get(xV, "line", 4);
        cwal_value const * c = l ? cwal_prop_get(xV, "column", 6) : 0;
        cwal_value const * m = l ? cwal_prop_get(xV, "message", 7) : 0;
        cwal_string const * ms = m ? cwal_value_get_string(m) : 0;
        if(l && c){
            MARKER(("@line %"CWAL_INT_T_PFMT" column %"CWAL_INT_T_PFMT": %.*s\n",
                   cwal_value_get_integer(l),
                   cwal_value_get_integer(c),
                   (int)cwal_string_length_bytes(ms),
                    cwal_string_cstr(ms)));
        }
        dump_json(xV);
    }
}

#if 1
static void dump_val2( cwal_value * v, char const * msg, char const * func, int line ){
    cwal_scope const * sc = cwal_value_scope(v);
    MARKER(("%s:%d: %s%stype=%s@%p[scope=#%d@%p] refcount=%d: ",
           func, line,
           msg ? msg : "",
           msg?": ":"",
           cwal_value_type_name(v),
           (void const *)v,
           (int)(sc ? sc->level : 0),
           (void const *)sc,
            (int)cwal_value_refcount(v)));
    switch( cwal_value_type_id(v) ){
      case CWAL_TYPE_FUNCTION:
      case CWAL_TYPE_BUFFER:
      case CWAL_TYPE_NATIVE:
          cwal_outputf(cwal_value_engine(v), "%s@%p\n", cwal_value_type_name(v), (void const*)v);
          break;
      default:
          cwal_json_output_FILE( v, stdout, &App.JsonOutOpt );
          break;
    }
}
#define dump_val(V,MSG) dump_val2((V),(MSG),__FUNCTION__, __LINE__)
#endif

/**
   cwal_callback_f() implementing fork().

   Script usage:

   [fork Function]

   The parent process returns from that call. The child process runs
   the given Function and then exists the interpreter as if the 'exit'
   keyword had been used.
*/
static int th1ish_f_fork( cwal_callback_args const * args,
                          cwal_value **rv ){
    th1ish_interp * ie = (th1ish_interp *)args->state;
#if !SMELLS_LIKE_UNIX
    return th1ish_toss(ie, CWAL_RC_UNSUPPORTED,
                       "fork is not available in this configuration.");
#else
    pid_t pid;
    char doReturn = 0;
    assert(ie);
    if(!args->argc || !cwal_value_is_function(args->argv[0])){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Expecting a Function argument.");
    }
    if(args->argc>1){
        doReturn = cwal_value_get_bool(args->argv[1]);
    }
    pid = fork();
    if(-1==pid){
        return (ENOMEM==errno)
                ? CWAL_RC_OOM
                : th1ish_toss(ie, CWAL_RC_ERROR,
                              "Fork failed with errno %d (%s).",
                              errno, strerror(errno));
    }
    else if(pid){
        /* Parent */
        *rv = cwal_new_integer(args->engine, (cwal_int_t)pid);
        return *rv ? 0 : CWAL_RC_OOM;
    }else{
        /* Child */
        cwal_function * f = cwal_value_get_function(args->argv[0]);
        cwal_value * frv = NULL;
        int const frc = cwal_function_call(f, args->self, &frv, 0, NULL);
        switch(frc){
          case CWAL_RC_OOM:
          case CWAL_RC_EXIT: /* assume exit value was already set. */
              break;
          case 0:
          default:
              if(frv){
                  if(doReturn){
                      if(rv) *rv = frv;
                  }else{
                      th1ish_exit_value_set(ie, frv);
                  }
              }
              break;
        }
        return doReturn ? frc : CWAL_RC_EXIT;
    }
#endif
}

static int th1ish_f_dump_interned_table( cwal_callback_args const * args, cwal_value **rv ){
    cwal_dump_interned_strings_table( args->engine, 1, 16 );
    return 0;
}


/**
   Example cwal_callback_f() implementation. Implements a simple
   output-to-console mechanism. All arguments are output with spaces
   between them and a newline at the end. Output goes to
   cwal_output(), so it might not go to the console.
*/
int th1ish_f_print( cwal_callback_args const * args, cwal_value **rv ){
    uint16_t i;
    int rc = 0;
    char const * sep = " ";
    cwal_engine * e = args->engine;
    cwal_size_t const sepLen = strlen(sep);
    /* dump_val(args->self, "'this' for print()"); */
    /* MARKER(("th1ish_f_print() called with %"PRIu16" arg(s).\n", args->argc)); */
    for(i = 0; !rc && (i < args->argc); ++i ){
        cwal_value * v = args->argv[i];
        if(i) rc = cwal_output(e, sep, sepLen);
        if(rc) break;
        switch(cwal_value_type_id(v)){
          case CWAL_TYPE_INTEGER:
          case CWAL_TYPE_DOUBLE:
          case CWAL_TYPE_BOOL:
          case CWAL_TYPE_OBJECT:
          case CWAL_TYPE_ARRAY:
          case CWAL_TYPE_NULL:
          case CWAL_TYPE_EXCEPTION:
              rc = cwal_json_output_engine( e, v, NULL );
              break;
          case CWAL_TYPE_UNDEF:
              rc = cwal_output(e, "undefined", 9);
              break;
          case CWAL_TYPE_STRING:{
              cwal_size_t slen = 0;
              char const * cstr = cwal_value_get_cstr(v, &slen);
              rc = slen ? cwal_output(e, cstr, slen) : 0;
              break;
          }
          case CWAL_TYPE_BUFFER:{
              cwal_buffer const * vb = cwal_value_get_buffer(v);
              rc = vb->used ? cwal_output(e, vb->mem, vb->used) : 0;
              break;
          }
          case CWAL_TYPE_HASH:
          case CWAL_TYPE_FUNCTION:
          case CWAL_TYPE_NATIVE:
              rc = cwal_outputf(e, "%s@%p", cwal_value_type_name(v), (void const*)v);
              break;
          default:
              break;
        }
    }
    if(rc && (CWAL_RC_EXCEPTION!=rc)){
        rc = cwal_exception_setf(args->engine, rc, "Output error #%d (%s).",
                                 rc, cwal_rc_cstr(rc));
    }
    else if(!rc){
        cwal_output(args->engine, "\n", 1);
    }
    *rv = NULL;
    cwal_output_flush(args->engine);
    return rc;
}



/**
   Called via cwal_engine_init() using our customized
   cwal_engine_vtab.
*/
static int e_init_engine(cwal_engine *e, cwal_engine_vtab * vtab){
    int rc = 0;

    /*Enable tracing.*/
    int32_t flags = CWAL_TRACE_NONE;
    if(App.enableTracing>0) {
        /* flags |= CWAL_TRACE_ENGINE_MASK; */
        flags |= CWAL_TRACE_VALUE_MASK;
        flags |= CWAL_TRACE_SCOPE_MASK;
    }
    if(App.enableTracing>1) {
        /* flags |= CWAL_TRACE_ENGINE_MASK; */
        flags |= CWAL_TRACE_MEM_MASK;
        flags |= CWAL_TRACE_FYI_MASK;
    }
    flags |= CWAL_TRACE_ERROR_MASK;
    cwal_engine_trace_flags( e, flags );

#define REMAX(T,N) cwal_engine_recycle_max( e, CWAL_TYPE_ ## T, (N) )
    REMAX(UNDEF,0);
    if(App.enableValueRecycling){
        REMAX(ARRAY,20);
        REMAX(BUFFER,2);
        REMAX(DOUBLE,20);
        REMAX(EXCEPTION,2);
        REMAX(FUNCTION,20);
        REMAX(INTEGER,20);
        REMAX(KVP,50);
        REMAX(NATIVE,5);
        REMAX(OBJECT,20);
        REMAX(STRING,50)/*Reminder: strings are not "as recycleable"
                          as other types because we can only recycle
                          strings if their lengths match the new
                          string's length. */;
        REMAX(SCOPE,5);
        REMAX(WEAK_REF,4);
    }
#undef REMAX
    return rc;
}


static int test_script(th1ish_interp * ie, char const * filename,
                       cwal_value ** rv ){
    int rc = 0;
#define STACK_SCRIPT 1
#if STACK_SCRIPT
    th1ish_script SC = th1ish_script_empty;
    th1ish_script * sc = &SC;
#else
    th1ish_script * sc = 0;
#endif
    rc = th1ish_script_compile_filename(ie, filename, &sc);
    /* MARKER(("compile rc=%s\n", cwal_rc_cstr(rc))); */
    if(rc) return rc;
    /* MARKER("Script name=%s\n", sc->name); */
    assert(0==strcmp(filename,(char const *)sc->name));
    /* MARKER(("<SCRIPT>%s</SCRIPT>\n", (char const *)sc->buffer.mem)); */
    /* th1ish_set_current_script( ie, sc ); */
    /* MARKER(("<SCRIPT>%s</SCRIPT>\n", (char const *)ie->src.begin)); */
#if !STACK_SCRIPT
    th1ish_script_add(ie, sc);
#endif
    assert(sc->head);
    assert(sc->cursor);
    rc = th1ish_script_run( sc, 1, rv );
    /* MARKER(("eval rc=%s\n", cwal_rc_cstr(rc))); */
    /* MARKER(("Done with th1ish_script test.\n")); */
#if STACK_SCRIPT
    th1ish_script_finalize(sc);
#else
    /*
      NO: th1ish_script_finalize(sc);

      The problem is that the result value might need to reference
      tokens inside that script and we then might illegally try to
      clean them after the script has finalized.

      Strangely enough, the valgrind trace that revealed this wasn't
      using any such values.
    */
#endif
    return rc;
}

/**
   The cwal_cstr_internable_predicate_f() impl used by the shell.

   This implementation restricts interning to:

   - Arbitrary length-1 ASCII strings

   - Strings under a certain max byte length

   - Valid th1ish identifiers (with the length restriction)

*/
static char cstr_is_internable( void * state,
                                char const * str_,
                                cwal_size_t len ){
    enum { MaxLen = 32U };
    unsigned char const * str = (unsigned char const *)str_;
    assert(str);
    assert(len>0);
#if 0
    /* Just testing... */
    if(len<100) return 1;
#endif
    if(len>MaxLen) return 0;
    else if(1==len){
        /*if(*str<32) return 0;
        else
        */if(*str>127 && *str<256) return 0;
        return 1;
    }
    /* else if(len<4) return 1; */
    else{
#if 1
        /* Read a UTF8 identifier... */
        char const * zEnd = str_+len;
        char const * tail = str_;
        th1ish_read_identifier(str_, zEnd, &tail);
        if((tail>str_)
           && (len==(cwal_size_t)(tail-str_))
           ){
            return 1;
        }
        /* MARKER(("Not internable: %s\n", str_)); */
        return 0;
#else
        /* A very early attempt, left here for example
           purposes... */
        cwal_size_t i = 0;
        for( ; i < len; ++i ){
            switch(str[i]){
              case '.': case ',': case ':': case ';':
              case '!': case ' ': case '"': case '\'': 
              case '<': case '>': case '-': case '+':
              case '*': case '$': case '%':
              case '\\': case '/':
                  return 0;
            }
            if(str[i]<32) return 0;
            else if(str[i]>127 && str[i]<256) return 0;
        }
        return 1;
#endif
    }
}

static int shell_main(int argc, char const * const * argv){
    enum {
    UseStackEngine = 1,
    UseStackInterp = 1
    };
    int rc;
    cwal_engine E = cwal_engine_empty;
    cwal_engine * e = UseStackEngine ? &E : 0;
    cwal_engine_vtab vtab = cwal_engine_vtab_basic;
    th1ish_interp IE = th1ish_interp_empty;
    th1ish_interp * ie = UseStackInterp ? &IE : 0;
    cwal_value * rv = 0;
    cwal_value * exc = 0;
    vtab.tracer = cwal_engine_tracer_FILE;
    vtab.tracer.state = stdout;
    vtab.hook.on_init = e_init_engine;
    vtab.interning.is_internable = cstr_is_internable;
    rc = cwal_engine_init( &e, &vtab );
    if(rc) goto end;
    assert(e);
    assert(UseStackEngine ? (e == &E) : (e != &E));

    rc = ie
        ? th1ish_interp_init( ie, e )
        : th1ish_interp_init2( &ie, e )
        ;
    assert(!rc);
    assert(e == ie->e);
    assert((ie == &IE) ? (ie->allocStamp == 0) : (ie->allocStamp == e));

    rc = th1ish_token_recycler_size_set( ie,
                                         App.enableTokenRecycling
                                         ? App.tokenRecyclerSize
                                         : 0 );
    assert(!rc);
    th1ish_trace_assertions( ie, App.traceAssertions );
    th1ish_enable_stacktrace( ie, App.enableStackTraces );
    th1ish_enable_string_interning( ie, App.enableStringInterning );
    th1ish_enable_conventional_call_op( ie, App.enableConventionalCallOp );
    th1ish_enable_extra_loop_scopes( ie, App.enableExtraLoopScopes );
    th1ish_enable_proc_token_caching( ie, App.enableProcTokenCaching );
    th1ish_autosweep_config(ie, App.sweepInterval, App.vacuumInterval,
                            App.sweepIntervalLog );
    VERBOSE(("Sweeping config: sweepInterval=%d, "
             "vacuumInterval=%d, log=%d\n",
             ie->sweepInterval, ie->vacuumInterval,
             App.sweepIntervalLog));

    if(App.enableApiNamespace){
        rc = th1ish_install_api( ie );
        assert(!rc);
        if(rc) goto end;
    }

    rc = th1ish_install_argv(ie, NULL, argc, argv);
    if(rc) goto end;

#if defined(TH1ISH_SHELL_EXTEND)
    rc = th1ish_shell_extend(ie, argc, argv);
    if(rc) goto end;
#endif
    
    if(App.outFile){
        FILE * of = (0==strcmp("-",App.outFile))
            ? stdout
            : fopen(App.outFile,"wb");
        if(!of){
            rc = CWAL_RC_IO;
            MARKER(("Could not open output file [%s].", App.outFile));
            goto end;
        }
        else {
            vtab.outputer.state.data = of;
            vtab.outputer.state.finalize = cwal_finalizer_f_fclose;
            vtab.tracer.state = of;
            vtab.tracer.close = NULL
                /* Reminder: we won't want both outputer and tracer
                   to close 'of'.
                */;
        }
    }
    
    {
        /*
          Bind some functions...
        */
#define DECL(K,F,V) if(!rc) {                                   \
            cwal_value * key = cwal_new_string_value(e,K,0);    \
            cwal_value * v = (V);                               \
            assert(v);                                          \
            if(!key) assert(!"OOM!");                    \
            rc = key ? cwal_var_decl_v(e, 0, key, v, (F)) : CWAL_RC_OOM; \
            assert(0==rc);                                              \
            if(rc){ if(key) cwal_value_unref(key); goto end; }           \
        }

#define DFUNC(NAME, FUNC) \
        DECL(NAME, 0, th1ish_new_function2(ie, FUNC));
        
        DFUNC("compare", th1ish_f_value_compare);
        DFUNC("dumpInternedStrings", th1ish_f_dump_interned_table);
        DFUNC("fileAccessible", th1ish_f_file_accessible);
        DFUNC("getenv", th1ish_f_getenv);
        DFUNC("hwtime", th1ish_f_hwtime);
        DFUNC("import", th1ish_f_import_script);
        /* DFUNC("parseJson", th1ish_f_json_parse_string); */
        DFUNC("loadModule", th1ish_f_module_load);
        DFUNC("mssleep", th1ish_f_mssleep);
        DFUNC("print", th1ish_f_print);
        DFUNC("randInt", th1ish_f_rand_int);
        DFUNC("sleep", th1ish_f_sleep);
        DFUNC("slurpFile", th1ish_f_slurp_file);
        DFUNC("strftime", th1ish_f_strftime);
        DFUNC("tmplish", th1ish_f_tmplish_to_code);
        DFUNC("time", th1ish_f_time);
#if SMELLS_LIKE_UNIX
        DFUNC("fork", th1ish_f_fork);
#endif
#if 1
        /* Cloning is quite limited, e.g. fails for uncloneable
           data types and cyclic structures. */
        DFUNC("clone", th1ish_f_clone_value);
#endif


#undef DFUNC
#undef DECL

        if(0 &&
           (CWAL_FEATURE_INTERN_STRINGS & cwal_engine_feature_flags(e, -1)) ){
            /* Internalize the typename strings, since we'll likely use
               them often.
            */
            char const * tname;
            cwal_size_t nlen;
#define INTERN(T) tname = cwal_value_type_id_name(CWAL_TYPE_##T); \
            assert(tname);\
            nlen = strlen(tname); \
            cwal_value_ref( cwal_new_string_value(e, tname, nlen) );
            INTERN(ARRAY);
            INTERN(BOOL);
            INTERN(BUFFER);
            INTERN(DOUBLE);
            INTERN(EXCEPTION);
            INTERN(FUNCTION);
            INTERN(INTEGER);
            INTERN(NATIVE);
            INTERN(NULL);
            INTERN(OBJECT);
            INTERN(STRING);
            INTERN(UNDEF);
#undef INTERN
        }
    }
    if(!App.escript.count && (!App.inFile || !*App.inFile)){
        /* Do this check near the end so that i can measure the
           startup memory costs.
        */
        MARKER(("Error: missing -f FILENAME argument.\n"));
        rc = CWAL_RC_MISUSE;
        goto end;
    }



    if(App.tokenizeOnly){
        ++ie->skipLevel;
    }
    else if(App.enableArgv0Autoload){
        /*
          If we find a file named argv[0].th1ish, load it as a
          script.
        */
        enum { BufSize = 1024 * 2 };
        char fn[BufSize];
        th1ish_script * scr = NULL;
        unsigned int slen;
        char const * exe = strstr(argv[0], ".exe");
        if(!exe) exe = strstr(argv[0], ".EXE");
        slen = strlen(argv[0]) - (exe ? 4 : 0);
        rc = sprintf(fn, "%.*s.th1ish", (int)slen, argv[0]);
        assert(rc<BufSize-1);
        if(th1ish_file_is_accessible(fn, 0)){
            VERBOSE(("Auto-loading script [%s]\n", fn));
            rc = th1ish_script_compile_filename(ie, fn, &scr);
            if(rc) goto autoload_error;
            rc = th1ish_script_add(ie, scr);
            if(rc){
                th1ish_script_finalize(scr);
                goto autoload_error;
            }
            rc = th1ish_script_run(scr, 0, NULL);
            autoload_error:
            if(rc){
                MARKER(("Error auto-loading init script [%s]: "
                        "error #%d (%s).\n", fn, rc, cwal_rc_cstr(rc)));
                goto end;
            }
        }
    }

    if(App.escript.count){
        int i = 0;
        for( ; i < App.escript.count; ++i ){
            unsigned char const * escr = (unsigned char const *)App.escript.list[i];
            th1ish_script * scr = NULL;
            unsigned int slen = strlen(App.escript.list[i]);
            if(!slen) continue;
            rc = th1ish_script_compile(ie, "-e script", escr, slen, &scr);
            if(scr){
                rc = th1ish_script_add(ie, scr);
                if(rc){
                    th1ish_script_finalize( scr );
                    scr = NULL;
                }
            }
            if(!rc){
                assert(scr);
                rc = th1ish_script_run( scr, 0, &rv );
                if(rc){
                    MARKER(("Evaluating -e script #%d failed.\n", i+1));
                    goto end;
                }
            }
        }
    }

    if(App.inFile && *App.inFile){
        /**
           These two approaches to running scripts are _basically_
           equivalent:
        */
        if(1){
            /* BUG reminder: wrong line/col values for exception thrown
               from this script code IF we use the tokenizer-based
               {string}-slurper:

               {  1a  }

               The same works as expected via th1ish_eval_filename().
            */
            rc = test_script(ie, App.inFile, &rv);
        }
        else {
            rc = th1ish_eval_filename( ie, 0, App.inFile, &rv );
        }
    }

    end:
    if(App.tokenizeOnly){
        assert(1==ie->skipLevel);
        --ie->skipLevel;
    }

    if(App.testMode){
        if(0){
            MARKER(("Compilability check tests...\n"));
#define CHECK(INP,EXPECT)                                        \
            rc = th1ish_basic_compile_check(ie, INP, -1, 0);            \
            MARKER(("%s\ncompile check rc=%d %s\n", INP, rc, cwal_rc_cstr(rc))); \
            assert(rc EXPECT); cwal_engine_sweep(ie->e) /* clean up exceptions */
            
            CHECK("{hi}", ==0);
            CHECK("{hi};", ==0);
            CHECK("", ==CWAL_SCR_EOF);
            CHECK(" \n/*comment*/\n{hi}; bye there; 123\n\n13;", ==0);
            CHECK(" \n/*comment*/\n{hi}; 0 ? x 1;", !=0);
            CHECK("\"...", !=0);
            CHECK("toss", !=0);
            CHECK("catch{toss}", ==0);
            CHECK("catch{toss", !=0);
#undef CHECK
            rc = 0;
        }
        if(1){
            cwal_buffer src = cwal_buffer_empty;
            cwal_buffer dest = cwal_buffer_empty;
            cwal_buffer_printf(e, &src,
                                "   \n\n\n<?\nvar x = 17, y = 13; if(1)<<<_EOIF\n?>"
                                "blah blee bloo <%% x %%> blue green <%% y %%>, y=<%%y%%>,"
                               " x+y=<%%x+\ny%%>\n<?_EOIF?>");
            rc = th1ish_tmplish_to_code(ie->e, &src, &dest, 0);
            /* MARKER(("Processed code:\n%s\n", (char const *)dest.mem)); */
            cwal_output(e, dest.mem, dest.used);
            cwal_buffer_reserve(e, &src, 0);
            cwal_buffer_reserve(e, &dest, 0);
        }
    }

    switch(rc){
      case CWAL_RC_EXIT:
          exc = cwal_exception_get(ie->e)
              /* We do this to get any exception
                 thrown via fork().
              */;
          rc = exc ? CWAL_RC_EXCEPTION : 0;
          break;
      case CWAL_RC_RETURN:
          assert(rv);
          rc = 0;
          break;
      default: break;
    }
    if(!rc){
        if(App.tokenizeOnly){
            VERBOSE(("Tokenization successful.\n"));
            if(rv && !cwal_value_is_undef(rv)){
                dump_val( rv, "WARNING: script evaluated _something_" );
            }
        }
        else if(rv && App.verbose){
            dump_val( rv, "Script result" );
        }
    }

    if(rc){
        MARKER(("failing with rc=%d (%s)\n", rc, cwal_rc_cstr(rc)));
        switch(rc){
          case CWAL_RC_EXCEPTION:
          case CWAL_RC_FATAL:
              dump_exception( exc ? exc :cwal_exception_get(e) );
        }
    }

    if(ie->e){
        if(App.dumpStringsTable){
            cwal_dump_interned_strings_table( ie->e, 1, 16 );
        }
        if(App.dumpAllocMetrics){
            cwal_outputf(ie->e, "--mem flag: libcwal-level allocation metrics:\n");
            cwal_dump_allocation_metrics(ie->e);
        }
        th1ish_interp_finalize( ie );
    }else if(e){
        /* cwal_dump_interned_strings_table( e, 1, 16 ); */
        cwal_engine_destroy( e );
    }
    return rc;
}

#undef SMELLS_LIKE_UNIX


void show_sizeofs(){
    cwal_size_t total = 0;
    MARKER(("Various library-level sizeof()s...\n"));
#define C(M) MARKER((#M"=%u\n", M));
    C(CWAL_SIZE_T_BITS);
    C(CWAL_INT_T_BITS);
    C(CWAL_VOID_PTR_IS_BIG);
    
#undef C
#define SO(T) total += sizeof(T); MARKER(("sizeof(%s)=%u\n", #T, (unsigned int)sizeof(T)))
    SO(void*);
    SO(cwal_engine);
    SO(cwal_scope);
    SO(th1ish_interp);
    SO(th1ish_script);
    SO(cwal_simple_token);
    SO(cwal_simple_tokenizer);
#undef SO
    MARKER(("sizeof() total: %u\n", (unsigned)total ));
}


void show_help(char const * appName){
    struct Opts {
        char const * flag;
        char const * help;
    } opts[] = {
    {"-f FILENAME", "Runs the given th1ish script file. "
     "Use '-' for stdin. "
     "If -f is not specified, the first non-flag argument "
     "is interpreted as a script file."
    },
    {"-e SCRIPT", "Runs the given string as script code. "
        "May be specified multiple times and all -e scripts are run "
        "in their given order before the primary script."
    },
    {"-o FILENAME", "Redirects the output channel to the given file. "
     "Use '-' for stdout."
    },
    {"-A", "Traces _successful_ assertions to the output channel."
    },
    {"-s, --s", "Enables/disables automatic interning of strings. "
     "All but the simplest scripts benefit greatly from interning."
    },
    {"-x, --x", "Enables/disables generation of exception call "
     "stacks in scripts. Disabling saves memory but complicates "
     "debugging."
    },
    {"-a, --a", "Enables/disables the 'api' script-side namespace. "
     "Disabling saves some memory but leaves out some script-side APIs."
    },
    {"-r, --r", "Enables/disables cwal-level value recycling. "
     "Disabling costs much more memory."
    },
    {"-t, --t", "Enables/disables th1ish token recycling. "
     "Disabling generally costs more memory."
    },
    {"-p, --p", "Enables/disables 'proc' token caching. "
     "Costs a small bit of memory but speeds up script-side "
     "functions if they are called more than once."
    },
    {"-c, --c", "Enables/disables conventional func(call) syntax support."
    },
    {"-l, --l", "Enables/disables an extra per-iteration scope in "
                "FOR/WHILE loops. Enabling costs memory and speed but "
                "allows vars to be declared in loop bodies."
    },
    {"-L, --L", "Enables/disables automatic loading of a script named "
                "argv[0].th1ish (if found)."
    },
    {"-T", "Enable cwal-level tracing details (lots of output)."
    },
    {"-TT","Even more tracing details (LOTS of output)."},
    {"-w, --w", "Enables/disables logging of auto-sweep messages (debugging only)."},
    {"-W, --W", "Enables/disables auto-sweeping."},
    {"-n", "Enables 'dry-run' mode: "
     "only tokenizes the top-most level of input tokens."
    },
    {"-v", "Verbose mode - more output."},
    {"-I", "Dumps the cwal string interning table to the configured "
           "output channel just before the interpreter is cleaned up."
    },
    {"-z", "Dumps some lib-level sizeof()s to stdout."},
    {"-mem", "Dumps internal allocation metrics."},
    {"-?, --help", "Show this help text and exit with code 0."
    },
    {0,0}
    };
    struct Opts * op;        
    printf("%s: a test/demo th1ish application\n", appName);
    printf("Usage:\n\t%s [options] [script]\n", appName);

    puts("Options:\n");
    for( op = opts ; op->flag; ++op ){
        printf("\t%s\t%s\n\n", op->flag, op->help);
    }
    
    puts("\nProject home page:\n\n\t"
         "http://fossil.wanderinghorse.net/repos/cwal/\n");
    
}

int main(int argc, char const * const * argv){
    int i;
    int rc = 0;
    char showSizes = 0;
    App.JsonOutOpt.addNewline = 1;
    App.JsonOutOpt.indent = -1;
#define CMP(FL) 0==strcmp("-" FL,arg)
#define FLAG(FL,VAR) if(0==strcmp("-" FL,arg)) VAR = 1; \
        else if(0==strcmp("--" FL,arg)) VAR = 0
    for( i = 1; i < argc; ++i ){
        char const * arg = argv[i];
        if(0==strcmp("--",arg)){
            /* Flags after this point are parsed separately
               for injection into scripts.
            */
            break;
        }
        else if(0==strcmp("-f",arg)){
            App.inFile = (i==argc-1) ? "-" : argv[++i];
            continue;
        }
        else if(0==strcmp("-e",arg)){
            if(App.escript.count==SHELL_MAX_E_SCRIPTS){
                MARKER(("Too many -e scripts\n"));
                rc = CWAL_RC_RANGE;
                goto end;
            }
            App.escript.list[App.escript.count++] = argv[++i];
            continue;
        }
        if(CMP("o")){
            App.outFile = (i==argc-1) ? "-" : argv[++i];
            continue;
        }
        else if(CMP("A")){
            App.traceAssertions = 1;
            continue;
        }
        else FLAG("a",App.enableApiNamespace);
        else FLAG("I", App.dumpStringsTable);
        else FLAG("s",App.enableStringInterning);
        else FLAG("x",App.enableStackTraces);
        else FLAG("r",App.enableValueRecycling);
        else FLAG("t",App.enableTokenRecycling);
        else FLAG("c",App.enableConventionalCallOp);
        else FLAG("l",App.enableExtraLoopScopes);
        else FLAG("p",App.enableProcTokenCaching);
        else FLAG("L",App.enableArgv0Autoload);
        else FLAG("w",App.sweepIntervalLog);
        else if(0==strcmp(arg,"-W")){
            if(App.sweepInterval<0) App.sweepInterval = 1;
            else App.sweepInterval += 4;
            continue;
        }
        else if(0==strcmp(arg,"--W")){
            App.sweepInterval = 0; 
            continue;
        }
        else if(0==strcmp(arg,"-V")){
            if(App.vacuumInterval<0) App.vacuumInterval = 1;
            else App.vacuumInterval += 5;
            continue;
        }
        else if(0==strcmp(arg,"--V")){
            App.vacuumInterval = 0; 
            continue;
        }
        else if(CMP("T")){
            App.enableTracing = 1;
            continue;
        }
        else if(CMP("TT")){
            App.enableTracing = 2;
            continue;
        }
        else FLAG("v",App.verbose);
        else if(CMP("z")){
            showSizes = 1;
            continue;
        }
        else if(CMP("?")||CMP("-help")){
            show_help(argv[0]);
            return 0;
        }
        else if(0==strcmp("-n",arg)){ /* "dry-run" mode */
            App.tokenizeOnly = 1;
            continue;
        }
        else if(CMP("mem")) App.dumpAllocMetrics = 1;
        else if(CMP("test")) App.testMode = 1;
        else if(('-' != *arg) && !App.inFile){
            App.inFile = arg;
            continue;
        }
        else {
            MARKER(("Unknown argument or flag: [%s]\n", arg));
            rc = CWAL_RC_MISUSE;
            goto end;
        }
#undef CMP
#undef FLAG

    }

    if(0){
        /* http://stackoverflow.com/questions/1923837/how-to-use-nan-and-inf-in-c */
        double inf = 1.0/0.0;
        double ninf = -1.0/0.0;
        double nnAn = 0.0/0.0;
        double nAn = -0.0/0.0;
        /* i get -nan for both nAn and nnAn. */
        assert( nAn != nAn );
        printf("inf=%f, ninf=%f, nAn=%f, nnAn=%f\n", inf, ninf, nAn, nnAn);

#if 0
        /* http://tdistler.com/2011/03/24/how-to-define-nan-not-a-number-on-windows */
#define myInf (DBL_MAX+DBL_MAX)
#define myNan (myInf-myInf) /* == 0.0 for me */
        printf("inf=%f, nAn=%f\n", myInf, myNan);
#endif
    }

    if(showSizes) show_sizeofs();
    else if(!App.inFile && !App.escript.count){
        show_help(argv[0]);
    }
    else rc = shell_main(argc, argv);
    end:
#if 0
    if(rc){
        MARKER(("Failing with code %d (%s)\n", rc, cwal_rc_cstr(rc)));
    }
#endif
    return rc ? 1 : 0;
}

#undef MARKER