Fossil

Check-in [147c602d]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Added ability to tie client data/finalizer to th1, allowing a refactoring of OB manager to use per-interpreter-instance state instead of global state.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | th1-query-api
Files: files | file ages | folders
SHA1: 147c602d935c272b1f754581427fab0b326cbdba
User & Date: stephan 2012-07-15 10:59:44
Context
2012-07-15
11:04
minor doc additions. check-in: 236cf135 user: stephan tags: th1-query-api
10:59
Added ability to tie client data/finalizer to th1, allowing a refactoring of OB manager to use per-interpreter-instance state instead of global state. check-in: 147c602d user: stephan tags: th1-query-api
09:23
Added push/pop as aliases for start/end in the ob API. Fixed a horrible size calculation bug which triggered an assert() for ob nesting levels deeper than 2. check-in: 9b3a11e1 user: stephan tags: th1-query-api
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/th.c.

16
17
18
19
20
21
22






23
24
25
26
27
28
29
30
31
32

33
34
35
36
37
38
39
40
41
42
43
44
45
...
119
120
121
122
123
124
125

126
127
128
129
130
131
132
...
301
302
303
304
305
306
307
















308
309
310
311
312
313
314
....
1680
1681
1682
1683
1684
1685
1686







1687
1688
1689
1690
1691
1692
1693
....
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
....
2695
2696
2697
2698
2699
2700
2701






























2702
2703
2704
2705
2706
2707
2708
....
2751
2752
2753
2754
2755
2756
2757
2758






2759
2760
2761

2762

2763


2764
2765
2766
2767
2768
2769
2770
2771
2772
....
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
....
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844


2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856

2857
2858
2859

2860
2861
2862
2863
2864









2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876

2877
2878
2879
2880
2881
2882
2883
2884
2885

2886

2887
2888
2889
2890
2891
2892
2893
....
2894
2895
2896
2897
2898
2899
2900

2901
2902
2903
2904
2905
2906
2907
2908
2909
2910

2911

2912
2913
2914
2915
2916
2917
2918
....
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
....
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
....
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044












3045
3046
3047
3048
3049
3050
3051

3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085

3086
3087
3088
3089
3090











3091
3092
3093

3094
3095
3096
  return fossil_realloc( p, n );
}

typedef struct Th_Command   Th_Command;
typedef struct Th_Frame     Th_Frame;
typedef struct Th_Variable  Th_Variable;







/*
** Interpreter structure.
*/
struct Th_Interp {
  Th_Vtab *pVtab;     /* Copy of the argument passed to Th_CreateInterp() */
  char *zResult;     /* Current interpreter result (Th_Malloc()ed) */
  int nResult;        /* number of bytes in zResult */
  Th_Hash *paCmd;     /* Table of registered commands */
  Th_Frame *pFrame;   /* Current execution frame */
  int isListMode;     /* True if thSplitList() should operate in "list" mode */

#ifdef TH_USE_SQLITE
  struct {
    sqlite3_stmt ** aStmt;
    int nStmt;
    int rc;
  } stmt;
#endif
};

/*
** Each TH command registered using Th_CreateCommand() is represented
** by an instance of the following structure stored in the Th_Interp.paCmd
** hash-table.
................................................................................
static int thEndOfLine(const char *, int);

static int  thPushFrame(Th_Interp*, Th_Frame*);
static void thPopFrame(Th_Interp*);

static void thFreeVariable(Th_HashEntry*, void*);
static void thFreeCommand(Th_HashEntry*, void*);


/*
** The following are used by both the expression and language parsers.
** Given that the start of the input string (z, n) is a language 
** construct of the relevant type (a command enclosed in [], an escape
** sequence etc.), these functions determine the number of bytes
** of the input consumed by the construct. For example:
................................................................................
  Th_Command *pCommand = (Th_Command *)pEntry->pData;
  if( pCommand->xDel ){
    pCommand->xDel((Th_Interp *)pContext, pCommand->pContext);
  }
  Th_Free((Th_Interp *)pContext, pEntry->pData);
  pEntry->pData = 0;
}

















/*
** Push a new frame onto the stack.
*/
static int thPushFrame(Th_Interp *interp, Th_Frame *pFrame){
  pFrame->paVar = Th_HashNew(interp);
  pFrame->pCaller = interp->pFrame;
................................................................................
/* 
** Delete an interpreter.
*/
void Th_DeleteInterp(Th_Interp *interp){
  assert(interp->pFrame);
  assert(0==interp->pFrame->pCaller);








  /* Delete the contents of the global frame. */
  thPopFrame(interp);

  /* Delete any result currently stored in the interpreter. */
  Th_SetResult(interp, 0, 0);

  /* Delete all registered commands and the command hash-table itself. */
................................................................................
** This function is the same as the standard strlen() function, except
** that it returns 0 (instead of being undefined) if the argument is
** a null pointer.
*/
int th_strlen(const char *zStr){
  int n = 0;
  if( zStr ){
    while( zStr[n] ) n++;
  }
  return n;
}

/* Whitespace characters:
**
**     ' '    0x20
................................................................................
    }
  }

  *z = '\0';
  return Th_SetResult(interp, zBuf, -1);
}
































#ifdef TH_USE_SQLITE
int Th_AddStmt(Th_Interp *interp, sqlite3_stmt * pStmt){
  int i, x;
  sqlite3_stmt * s;
  sqlite3_stmt ** list = interp->stmt.aStmt;
  for( i = 0; i < interp->stmt.nStmt; ++i ){
................................................................................

#ifdef TH_USE_OUTBUF
/* Reminder: the ob code "really" belongs in th_lang.c,
   but we need access to Th_Interp internals in order to
   swap out Th_Vtab parts for purposes of stacking layers
   of buffers.
*/
#define Th_Ob_Man_empty_m { NULL, 0, -1, NULL, NULL }






static const Th_Ob_Man Th_Ob_Man_empty = Th_Ob_Man_empty_m;
static Th_Ob_Man Th_Ob_Man_instance = Th_Ob_Man_empty_m;


Th_Ob_Man * Th_ob_manager(Th_Interp *ignored){

  return &Th_Ob_Man_instance;


}
  

Blob * Th_ob_current( Th_Ob_Man * pMan ){
  return pMan->nBuf>0 ? pMan->aBuf[pMan->cursor] : 0;
}


/*
................................................................................
  if( pMan->cursor >= pMan->nBuf-2 ){
    /* expand if needed */
    x = pMan->nBuf + 5;
    if( pMan->cursor >= x ) {
      assert( 0 && "This really should not happen." );
      x = pMan->cursor + 5;
    }
    /*fprintf(stderr,"OB EXPAND x=%d\n",x);*/
    void * re = Th_Realloc( pMan->interp, pMan->aBuf, x * sizeof(Blob*) );
    if(NULL==re){
      goto error;
    }
    pMan->aBuf = (Blob **)re;
    re = Th_Realloc( pMan->interp, pMan->aVtab, x * sizeof(Th_Vtab*) );
    if(NULL==re){
................................................................................
  pMan->aBuf[pMan->cursor] = pBlob;
  pMan->aVtab[pMan->cursor] = pMan->interp->pVtab;
  pMan->interp->pVtab = &Th_Vtab_Ob;
  Th_Vtab_Ob.out.pState = pMan;
  if( pOut ){
    *pOut = pBlob;
  }
  /*fprintf(stderr,"OB PUSH: %p\n", pBlob);*/
  return TH_OK;
  error:
  if( pBlob ){
    Th_Free( pMan->interp, pBlob );
  }
  return TH_ERROR;
}



Blob * Th_ob_pop( Th_Ob_Man * pMan ){
  if( pMan->cursor < 0 ){
    return NULL;
  }else{
    Blob * rc;
    assert( pMan->nBuf > pMan->cursor );
    rc = pMan->aBuf[pMan->cursor];
    pMan->aBuf[pMan->cursor] = NULL;
    pMan->interp->pVtab = pMan->aVtab[pMan->cursor];
    pMan->aVtab[pMan->cursor] = NULL;
    if(-1 == --pMan->cursor){

      Th_Free( pMan->interp, pMan->aBuf );
      Th_Free( pMan->interp, pMan->aVtab );
      *pMan = Th_Ob_Man_empty;

    }
    /*fprintf(stderr,"OB pop: %p level=%d\n", rc, pMan->cursor-1);*/
    return rc;
  }
}










/*
** TH Syntax:
**
** ob clean
**
** Erases any currently buffered contents but does not modify
** the buffering level.
*/
static int ob_clean_command( Th_Interp *interp, void *ctx,
                             int argc,  const char **argv, int *argl
){

  Th_Ob_Man * pMan = (Th_Ob_Man *)ctx;
  Blob * b;
  assert( pMan && (interp == pMan->interp) );
  b = pMan ? Th_ob_current(pMan) : NULL;
  if(!b){
    Th_ErrorMessage( interp, "Not currently buffering.", NULL, 0 );
    return TH_ERROR;
  }else{
    blob_reset(b);

    Th_SetResultInt( interp, 0 );

    return TH_OK;
  }
}

/*
** TH Syntax:
**
................................................................................
** ob end
**
** Erases any currently buffered contents and pops the current buffer
** from the stack.
*/
static int ob_end_command( Th_Interp *interp, void *ctx,
                           int argc,  const char **argv, int *argl ){

  Th_Ob_Man * pMan = (Th_Ob_Man *)ctx;
  Blob * b;
  assert( pMan && (interp == pMan->interp) );
  b = Th_ob_pop(pMan);
  if(!b){
    Th_ErrorMessage( interp, "Not currently buffering.", NULL, 0 );
    return TH_ERROR;
  }else{
    blob_reset(b);
    Th_Free( interp, b );

    Th_SetResultInt( interp, 0 );

    return TH_OK;
  }
}

/*
** TH Syntax:
**
................................................................................
    int argPos = 2;
    char const * sub = argv[argPos];
    int subL = argl[argPos];
    /* "flush end" */
    if(th_strlen(sub)==3 &&
       ((0==memcmp("end", sub, subL)
         || (0==memcmp("pop", sub, subL))))){
      rc |= ob_end_command(interp, ctx, argc-1, argv+1, argl+1);
    }
  }
  Th_SetResultInt( interp, 0 );
  return rc;
}

/*
................................................................................
    int rc = TH_OK;
    Th_SetResult( interp, blob_str(b), b->nUsed );
    if(argc>argPos){
      sub = argv[argPos];
      subL = argl[argPos];
      /* "ob get clean" */
      if(!rc && th_strlen(sub)==5 && 0==memcmp("clean", sub, subL)){
        rc |= ob_clean_command(interp, ctx, argc-1, argv+1, argl+1);
      }/* "ob get end" */
      else if(!rc && th_strlen(sub)==3 &&
              ((0==memcmp("end", sub, subL))
               || (0==memcmp("pop", sub, subL)))){
        rc |= ob_end_command(interp, ctx, argc-1, argv+1, argl+1);
      }
    }
    return rc;
  }
}

/*
................................................................................
  assert( pMan && (interp == pMan->interp) );
  rc = Th_ob_push(pMan, &b);
  if( TH_OK != rc ){
    assert( NULL == b );
    return rc;
  }
  assert( NULL != b );
  /*fprintf(stderr,"OB STARTED: %p level=%d\n", b, pMan->cursor);*/
  Th_SetResultInt( interp, 1 + pMan->cursor );
  return TH_OK;
}













/*
** TH Syntax:
**
** ob clean|end|flush|get|level|start
**
** Runs the given subcommand.

** 
*/
static int ob_cmd(
  Th_Interp *interp, 
  void *ignored, 
  int argc, 
  const char **argv, 
  int *argl
){
  static Th_Ob_Man * pMan = &Th_Ob_Man_instance;
  Th_SubCommand aSub[] = {
    { "clean",     ob_clean_command },
    { "end",       ob_end_command },
    { "flush",     ob_flush_command },
    { "get",       ob_get_command },
    { "level",     ob_level_command },
    { "pop",       ob_end_command },
    { "push",      ob_start_command },
    { "start",     ob_start_command },
    { 0, 0 }
  };
  if(NULL == pMan->interp){
    pMan->interp = interp;
    /*
      FIXME: add rudamentary at-finalization GC to Th_Interp and clean
      this up there. We currently leak only if the client does not
      close all buffering levels properly.
    */
  }
  return Th_CallSubCommand(interp, pMan, argc, argv, argl, aSub);
  
}

int th_register_ob(Th_Interp * interp){

  static Th_Command_Reg aCommand[] = {
    {"ob",    ob_cmd,   0},
    {0,0,0}
  };
  return Th_register_commands( interp, aCommand );











}

#undef Th_Ob_Man_empty_m

#endif
/* end TH_USE_OUTBUF */








>
>
>
>
>
>




|

|
|
|
|
>




|
<







 







>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>







 







|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|
>
>
>
>
>
>


<
>
|
>
|
>
>

|







 







<







 







<







>
>












>



>

<



>
>
>
>
>
>
>
>
>












>
|








>
|
>







 







>
|









>
|
>







 







|







 







|




|







 







<



>
>
>
>
>
>
>
>
>
>
>
>




|

|
>









|











<
|
<
<
<
<
<
<

<



>




|
>
>
>
>
>
>
>
>
>
>
>



>



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

45
46
47
48
49
50
51
...
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
...
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
....
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
....
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
....
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
....
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826

2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
....
2872
2873
2874
2875
2876
2877
2878

2879
2880
2881
2882
2883
2884
2885
....
2898
2899
2900
2901
2902
2903
2904

2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931

2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
....
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
....
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
....
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
....
3119
3120
3121
3122
3123
3124
3125

3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169

3170






3171

3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
  return fossil_realloc( p, n );
}

typedef struct Th_Command   Th_Command;
typedef struct Th_Frame     Th_Frame;
typedef struct Th_Variable  Th_Variable;

struct Th_GcEntry {
  void * pData;
  void (*xDel)( Th_Interp *, void * );
};
typedef struct Th_GcEntry Th_GcEntry;

/*
** Interpreter structure.
*/
struct Th_Interp {
  Th_Vtab *pVtab;    /* Copy of the argument passed to Th_CreateInterp() */
  char *zResult;     /* Current interpreter result (Th_Malloc()ed) */
  int nResult;       /* number of bytes in zResult */
  Th_Hash *paCmd;    /* Table of registered commands */
  Th_Frame *pFrame;  /* Current execution frame */
  int isListMode;    /* True if thSplitList() should operate in "list" mode */
  Th_Hash * paGc;    /* holds client-provided data owned by this object */
#ifdef TH_USE_SQLITE
  struct {
    sqlite3_stmt ** aStmt;
    int nStmt;
  } stmt;           /* list of prepared statements */

#endif
};

/*
** Each TH command registered using Th_CreateCommand() is represented
** by an instance of the following structure stored in the Th_Interp.paCmd
** hash-table.
................................................................................
static int thEndOfLine(const char *, int);

static int  thPushFrame(Th_Interp*, Th_Frame*);
static void thPopFrame(Th_Interp*);

static void thFreeVariable(Th_HashEntry*, void*);
static void thFreeCommand(Th_HashEntry*, void*);
static void thFreeGc(Th_HashEntry*, void*);

/*
** The following are used by both the expression and language parsers.
** Given that the start of the input string (z, n) is a language 
** construct of the relevant type (a command enclosed in [], an escape
** sequence etc.), these functions determine the number of bytes
** of the input consumed by the construct. For example:
................................................................................
  Th_Command *pCommand = (Th_Command *)pEntry->pData;
  if( pCommand->xDel ){
    pCommand->xDel((Th_Interp *)pContext, pCommand->pContext);
  }
  Th_Free((Th_Interp *)pContext, pEntry->pData);
  pEntry->pData = 0;
}

/*
** Th_Hash visitor/destructor for Th_Interp::paGc entries. Frees
** pEntry->pData but not pEntry.
*/
static void thFreeGc(Th_HashEntry *pEntry, void *pContext){
  Th_GcEntry *gc = (Th_GcEntry *)pEntry->pData;
  if(gc){
    if( gc->xDel ){
      gc->xDel( (Th_Interp*)pContext, gc->pData );
    }
    Th_Free((Th_Interp *)pContext, pEntry->pData);
    pEntry->pData = 0;
  }
}


/*
** Push a new frame onto the stack.
*/
static int thPushFrame(Th_Interp *interp, Th_Frame *pFrame){
  pFrame->paVar = Th_HashNew(interp);
  pFrame->pCaller = interp->pFrame;
................................................................................
/* 
** Delete an interpreter.
*/
void Th_DeleteInterp(Th_Interp *interp){
  assert(interp->pFrame);
  assert(0==interp->pFrame->pCaller);

  /* Delete any client-side gc entries first. */
  if( interp->paGc ){
    Th_HashIterate(interp, interp->paGc, thFreeGc, (void *)interp);
    Th_HashDelete(interp, interp->paGc);
    interp->paGc = NULL;
  }
  
  /* Delete the contents of the global frame. */
  thPopFrame(interp);

  /* Delete any result currently stored in the interpreter. */
  Th_SetResult(interp, 0, 0);

  /* Delete all registered commands and the command hash-table itself. */
................................................................................
** This function is the same as the standard strlen() function, except
** that it returns 0 (instead of being undefined) if the argument is
** a null pointer.
*/
int th_strlen(const char *zStr){
  int n = 0;
  if( zStr ){
    while( zStr[n] ) ++n;
  }
  return n;
}

/* Whitespace characters:
**
**     ' '    0x20
................................................................................
    }
  }

  *z = '\0';
  return Th_SetResult(interp, zBuf, -1);
}


int Th_Data_Set( Th_Interp * interp, char const * key,
                 void * pData,
                 void (*finalizer)( Th_Interp *, void * ) ){
  Th_HashEntry * pEnt;
  Th_GcEntry * pGc;
  if(NULL == interp->paGc){
    interp->paGc = Th_HashNew(interp);
    assert(NULL != interp->paGc);
    if(!interp->paGc){
      return TH_ERROR;
    }
  }
  pEnt = Th_HashFind(interp, interp->paGc, key, th_strlen(key), 1);
  if( pEnt->pData ){
    thFreeGc( pEnt, interp );
  }
  assert( NULL == pEnt->pData );
  pEnt->pData = pGc = (Th_GcEntry*)Th_Malloc(interp, sizeof(Th_GcEntry));
  pGc->pData = pData;
  pGc->xDel = finalizer;
  return 0;
 
}
void * Th_Data_Get( Th_Interp * interp, char const * key ){
  Th_HashEntry * e = interp->paGc
    ? Th_HashFind(interp, interp->paGc, key, th_strlen(key), 0)
    : NULL;
  return e ? e->pData : NULL;
}

#ifdef TH_USE_SQLITE
int Th_AddStmt(Th_Interp *interp, sqlite3_stmt * pStmt){
  int i, x;
  sqlite3_stmt * s;
  sqlite3_stmt ** list = interp->stmt.aStmt;
  for( i = 0; i < interp->stmt.nStmt; ++i ){
................................................................................

#ifdef TH_USE_OUTBUF
/* Reminder: the ob code "really" belongs in th_lang.c,
   but we need access to Th_Interp internals in order to
   swap out Th_Vtab parts for purposes of stacking layers
   of buffers.
*/
#define Th_Ob_Man_empty_m { \
  NULL/*aBuf*/,           \
  0/*nBuf*/,            \
  -1/*cursor*/,       \
  NULL/*interp*/,     \
  NULL/*aVtab*/       \
}
static const Th_Ob_Man Th_Ob_Man_empty = Th_Ob_Man_empty_m;
static Th_Ob_Man Th_Ob_Man_instance = Th_Ob_Man_empty_m;

#define Th_Ob_Man_KEY "Th_Ob_Man"
Th_Ob_Man * Th_ob_manager(Th_Interp *interp){
  void * rc = Th_Data_Get(interp, Th_Ob_Man_KEY );
  return rc
    ? ((Th_GcEntry*)rc)->pData
    : NULL;
}


Blob * Th_ob_current( Th_Ob_Man * pMan ){
  return pMan->nBuf>0 ? pMan->aBuf[pMan->cursor] : 0;
}


/*
................................................................................
  if( pMan->cursor >= pMan->nBuf-2 ){
    /* expand if needed */
    x = pMan->nBuf + 5;
    if( pMan->cursor >= x ) {
      assert( 0 && "This really should not happen." );
      x = pMan->cursor + 5;
    }

    void * re = Th_Realloc( pMan->interp, pMan->aBuf, x * sizeof(Blob*) );
    if(NULL==re){
      goto error;
    }
    pMan->aBuf = (Blob **)re;
    re = Th_Realloc( pMan->interp, pMan->aVtab, x * sizeof(Th_Vtab*) );
    if(NULL==re){
................................................................................
  pMan->aBuf[pMan->cursor] = pBlob;
  pMan->aVtab[pMan->cursor] = pMan->interp->pVtab;
  pMan->interp->pVtab = &Th_Vtab_Ob;
  Th_Vtab_Ob.out.pState = pMan;
  if( pOut ){
    *pOut = pBlob;
  }

  return TH_OK;
  error:
  if( pBlob ){
    Th_Free( pMan->interp, pBlob );
  }
  return TH_ERROR;
}



Blob * Th_ob_pop( Th_Ob_Man * pMan ){
  if( pMan->cursor < 0 ){
    return NULL;
  }else{
    Blob * rc;
    assert( pMan->nBuf > pMan->cursor );
    rc = pMan->aBuf[pMan->cursor];
    pMan->aBuf[pMan->cursor] = NULL;
    pMan->interp->pVtab = pMan->aVtab[pMan->cursor];
    pMan->aVtab[pMan->cursor] = NULL;
    if(-1 == --pMan->cursor){
      Th_Interp * interp = pMan->interp;
      Th_Free( pMan->interp, pMan->aBuf );
      Th_Free( pMan->interp, pMan->aVtab );
      *pMan = Th_Ob_Man_empty;
      pMan->interp = interp;
    }

    return rc;
  }
}

void Th_ob_cleanup( Th_Ob_Man * man ){
  Blob * b;
  while( (b = Th_ob_pop(man)) ){
    blob_reset(b);
    Th_Free( man->interp, b );
  }
}


/*
** TH Syntax:
**
** ob clean
**
** Erases any currently buffered contents but does not modify
** the buffering level.
*/
static int ob_clean_command( Th_Interp *interp, void *ctx,
                             int argc,  const char **argv, int *argl
){
  const char doRc = ctx ? 1 : 0;
  Th_Ob_Man * pMan = ctx ? (Th_Ob_Man *)ctx : Th_ob_manager(interp);
  Blob * b;
  assert( pMan && (interp == pMan->interp) );
  b = pMan ? Th_ob_current(pMan) : NULL;
  if(!b){
    Th_ErrorMessage( interp, "Not currently buffering.", NULL, 0 );
    return TH_ERROR;
  }else{
    blob_reset(b);
    if( doRc ) {
      Th_SetResultInt( interp, 0 );
    }
    return TH_OK;
  }
}

/*
** TH Syntax:
**
................................................................................
** ob end
**
** Erases any currently buffered contents and pops the current buffer
** from the stack.
*/
static int ob_end_command( Th_Interp *interp, void *ctx,
                           int argc,  const char **argv, int *argl ){
  const char doRc = ctx ? 1 : 0;
  Th_Ob_Man * pMan = ctx ? (Th_Ob_Man *)ctx : Th_ob_manager(interp);
  Blob * b;
  assert( pMan && (interp == pMan->interp) );
  b = Th_ob_pop(pMan);
  if(!b){
    Th_ErrorMessage( interp, "Not currently buffering.", NULL, 0 );
    return TH_ERROR;
  }else{
    blob_reset(b);
    Th_Free( interp, b );
    if(doRc){
      Th_SetResultInt( interp, 0 );
    }
    return TH_OK;
  }
}

/*
** TH Syntax:
**
................................................................................
    int argPos = 2;
    char const * sub = argv[argPos];
    int subL = argl[argPos];
    /* "flush end" */
    if(th_strlen(sub)==3 &&
       ((0==memcmp("end", sub, subL)
         || (0==memcmp("pop", sub, subL))))){
      rc |= ob_end_command(interp, NULL, argc-1, argv+1, argl+1);
    }
  }
  Th_SetResultInt( interp, 0 );
  return rc;
}

/*
................................................................................
    int rc = TH_OK;
    Th_SetResult( interp, blob_str(b), b->nUsed );
    if(argc>argPos){
      sub = argv[argPos];
      subL = argl[argPos];
      /* "ob get clean" */
      if(!rc && th_strlen(sub)==5 && 0==memcmp("clean", sub, subL)){
        rc |= ob_clean_command(interp, NULL, argc-1, argv+1, argl+1);
      }/* "ob get end" */
      else if(!rc && th_strlen(sub)==3 &&
              ((0==memcmp("end", sub, subL))
               || (0==memcmp("pop", sub, subL)))){
        rc |= ob_end_command(interp, NULL, argc-1, argv+1, argl+1);
      }
    }
    return rc;
  }
}

/*
................................................................................
  assert( pMan && (interp == pMan->interp) );
  rc = Th_ob_push(pMan, &b);
  if( TH_OK != rc ){
    assert( NULL == b );
    return rc;
  }
  assert( NULL != b );

  Th_SetResultInt( interp, 1 + pMan->cursor );
  return TH_OK;
}

static void finalizerObMan( Th_Interp * interp, void * p ){
  Th_Ob_Man * man = (Th_Ob_Man*)p;
  /*printf("finalizerObMan(%p,%p)\n", interp, p );*/
  if(man){
    assert( interp == man->interp );
    Th_ob_cleanup( man );
    if( man != &Th_Ob_Man_instance ){
      Th_Free( interp, man );
    }
  }
}

/*
** TH Syntax:
**
** ob clean|(end|pop)|flush|get|level|(start|push)
**
** Runs the given subcommand. Some subcommands have other subcommands
** (see their docs for details).
** 
*/
static int ob_cmd(
  Th_Interp *interp, 
  void *ignored, 
  int argc, 
  const char **argv, 
  int *argl
){
  Th_Ob_Man * pMan = Th_ob_manager(interp);
  Th_SubCommand aSub[] = {
    { "clean",     ob_clean_command },
    { "end",       ob_end_command },
    { "flush",     ob_flush_command },
    { "get",       ob_get_command },
    { "level",     ob_level_command },
    { "pop",       ob_end_command },
    { "push",      ob_start_command },
    { "start",     ob_start_command },
    { 0, 0 }
  };

  assert(NULL != pMan && pMan->interp==interp);






  return Th_CallSubCommand(interp, pMan, argc, argv, argl, aSub);

}

int th_register_ob(Th_Interp * interp){
  int rc;
  static Th_Command_Reg aCommand[] = {
    {"ob",    ob_cmd,   0},
    {0,0,0}
  };
  rc = Th_register_commands( interp, aCommand );
  if(NULL == Th_ob_manager(interp)){
    Th_Ob_Man * pMan;
    pMan = 1
      ? &Th_Ob_Man_instance
      : Th_Malloc(interp, sizeof(Th_Ob_Man));
    /* *pMan = Th_Ob_Man_empty;*/
    pMan->interp = interp;
    Th_Data_Set( interp, Th_Ob_Man_KEY, pMan, finalizerObMan );
    assert( NULL != Th_ob_manager(interp) );
  }
  return rc;
}

#undef Th_Ob_Man_empty_m
#undef Th_Ob_Man_KEY
#endif
/* end TH_USE_OUTBUF */

Changes to src/th.h.

267
268
269
270
271
272
273

























274
275
276
277
278
279
280

/* mkindex cannot do enums enum Th_Render_Flags { */
#define Th_Render_Flags_DEFAULT 0
#define Th_Render_Flags_NO_DOLLAR_DEREF (1 << 1)
/*};*/

int Th_Render(const char *z, int flags);


























/*
** Registers a list of commands with the interpreter. pList must be a non-NULL
** pointer to an array of Th_Command_Reg objects, the last one of which MUST
** have a NULL zName field (that is the end-of-list marker).
** Returns TH_OK on success, "something else" on error.
*/







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305

/* mkindex cannot do enums enum Th_Render_Flags { */
#define Th_Render_Flags_DEFAULT 0
#define Th_Render_Flags_NO_DOLLAR_DEREF (1 << 1)
/*};*/

int Th_Render(const char *z, int flags);

/*
** Adds a piece of memory to the given interpreter, such that:
**
** a) it will be cleaned up when the interpreter is destroyed, by
** calling finalizer(interp, pData). The finalizer may be NULL.
** Cleanup happens in an unspecified/unpredictable order.
**
** b) it can be fetched via Th_Data_Get().
**
** If a given key is added more than once then any previous
** entry is cleaned up before adding it.
**
** Returns 0 on success, non-0 on allocation error.
*/
int Th_Data_Set( Th_Interp * interp, char const * key,
                 void * pData,
                 void (*finalizer)( Th_Interp *, void * ) );

/*
** Fetches data added via Th_Data_Set(), or NULL if no data
** has been associated with the given key.
*/
void * Th_Data_Get( Th_Interp * interp, char const * key );


/*
** Registers a list of commands with the interpreter. pList must be a non-NULL
** pointer to an array of Th_Command_Reg objects, the last one of which MUST
** have a NULL zName field (that is the end-of-list marker).
** Returns TH_OK on success, "something else" on error.
*/