Fossil

Check-in [726f998b]
Login

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

Overview
Comment:Started adding infrastructure to allow us to expand the ob handler support to include more types. Added another th1 test script.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | th1-query-api
Files: files | file ages | folders
SHA1:726f998b5f6565800a9125bd788bbc70053a55e7
User & Date: stephan 2012-07-15 17:33:55
Context
2012-07-16
14:36
Minor doc correction. check-in: 42957281 user: stephan tags: th1-query-api
2012-07-15
17:33
Started adding infrastructure to allow us to expand the ob handler support to include more types. Added another th1 test script. check-in: 726f998b user: stephan tags: th1-query-api
15:28
Added query reset, refactored bind commands to accept their indexes in the same way as the col commands do (and expanded the remaining col commands which did not do so). check-in: f2ee33d4 user: stephan tags: th1-query-api
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/th.c.

24
25
26
27
28
29
30

31
32
33







34
35
36
37
38
39
40
....
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
....
1460
1461
1462
1463
1464
1465
1466











1467
1468
1469
1470
1471
1472
1473
....
2770
2771
2772
2773
2774
2775
2776




2777
2778


2779
2780
2781
2782
2783

2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
....
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818

2819
2820





2821
2822
2823
2824
2825


2826
2827



2828
2829
2830
2831
2832
2833
2834
....
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
....
2872
2873
2874
2875
2876
2877
2878

2879
2880
2881
2882
2883




2884
2885
2886
2887
2888
2889
2890
....
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
#endif

extern void *fossil_realloc(void *p, size_t n);
static void * th_fossil_realloc(void *p, unsigned int n){
  return fossil_realloc( p, n );
}
static int Th_output_f_ob( char const * zData, int len, void * pState );

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








/*
** Holds client-provided "garbage collected" data for
** a Th_Interp instance.
*/
struct Th_GcEntry {
  void * pData;                        /* arbitrary data */
................................................................................
void *Th_Realloc(Th_Interp *pInterp, void *z, int nByte){
  void *p = pInterp->pVtab->xRealloc(z, nByte);
  return p;
}


int Th_Vtab_output( Th_Vtab *vTab, char const * zData, int nData ){
  if(!vTab->out.f){
    return -1;
  }else if(!vTab->out.enabled){
    return 0;
  }else{
    return vTab->out.f( zData, nData, vTab->out.pState );
  }
}


int Th_output( Th_Interp *pInterp, char const * zData, int nData ){
  return Th_Vtab_output( pInterp->pVtab, zData, nData );
}
................................................................................

int Th_output_f_FILE( char const * zData, int nData, void * pState ){
  FILE * dest = pState ? (FILE*)pState : stdout;
  int rc = (int)fwrite(zData, 1, nData, dest);
  fflush(dest);
  return rc;
}












/*
** Install a new th1 command. 
**
** If a command of the same name already exists, it is deleted automatically.
*/
int Th_CreateCommand(
................................................................................
#define Th_Ob_Man_empty_m { \
  NULL/*aBuf*/,           \
  0/*nBuf*/,            \
  -1/*cursor*/,       \
  NULL/*interp*/,     \
  NULL/*aOutput*/       \
}




#define Th_Vtab_Output_empty_m { \
  Th_output_f_ob /*f*/, \


  NULL /*pState*/,\
  1/*enabled*/\
}
#define Th_Vtab_Output_ob_m {                \
  Th_output_f_ob /*f*/, \

  NULL /*pState*/,\
  1/*enabled*/\
}
static const Th_Ob_Man Th_Ob_Man_empty = Th_Ob_Man_empty_m;
static Th_Vtab_Output Th_Vtab_Output_ob = Th_Vtab_Output_ob_m;
static Th_Vtab_Output Th_Vtab_Output_empty = Th_Vtab_Output_empty_m;
#define Th_Ob_Man_KEY "Th_Ob_Man"
Th_Ob_Man * Th_ob_manager(Th_Interp *interp){
  return (Th_Ob_Man*) Th_Data_Get(interp, Th_Ob_Man_KEY );
}


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


/*
................................................................................
  Blob * b = Th_ob_current( pMan );
  assert( NULL != pMan );
  assert( b );
  blob_append( b, zData, len );
  return len;
}

/*
** Vtab impl for the ob buffering layer.
*/
static Th_Vtab Th_Vtab_Ob = { th_fossil_realloc,
  {

    Th_output_f_ob,
    NULL,





    1
  }
};

int Th_ob_push( Th_Ob_Man * pMan, Blob ** pOut ){


  Blob * pBlob;
  int x, i;



  assert( NULL != pMan->interp );
  pBlob = (Blob *)Th_Malloc(pMan->interp, sizeof(Blob));
  *pBlob = empty_blob;

  if( pMan->cursor >= pMan->nBuf-2 ){
    /* expand if needed */
    x = pMan->nBuf + 5;
................................................................................
    pMan->nBuf = x;
  }
  assert( pMan->nBuf > pMan->cursor );
  assert( pMan->cursor >= -1 );
  ++pMan->cursor;
  pMan->aBuf[pMan->cursor] = pBlob;
  pMan->aOutput[pMan->cursor] = pMan->interp->pVtab->out;
  pMan->interp->pVtab->out = Th_Vtab_Ob.out;
  pMan->interp->pVtab->out.pState = pMan;
  if( pOut ){
    *pOut = pBlob;
  }
  /*printf( "push: pMan->nBuf=%d, pMan->cursor=%d\n", pMan->nBuf, pMan->cursor);*/
  return TH_OK;
  error:
................................................................................
}

Blob * Th_ob_pop( Th_Ob_Man * pMan ){
  if( pMan->cursor < 0 ){
    return NULL;
  }else{
    Blob * rc;

    /*printf( "pop: pMan->nBuf=%d, pMan->cursor=%d\n", pMan->nBuf, pMan->cursor);*/
    assert( pMan->nBuf > pMan->cursor );
    rc = pMan->aBuf[pMan->cursor];
    pMan->aBuf[pMan->cursor] = NULL;
    pMan->interp->pVtab->out = pMan->aOutput[pMan->cursor];




    pMan->aOutput[pMan->cursor] = Th_Vtab_Output_empty;
    if(-1 == --pMan->cursor){
      Th_Interp * interp = pMan->interp;
      Th_Free( pMan->interp, pMan->aBuf );
      Th_Free( pMan->interp, pMan->aOutput );
      *pMan = Th_Ob_Man_empty;
      pMan->interp = interp;
................................................................................
  Th_SetResultInt( interp, 1 + pMan->cursor );
  return TH_OK;
}

/*
** TH Syntax:
**
** ob start
**
** Pushes a new level of buffering onto the buffer stack.
** Returns the new buffering level (1-based).




*/
static int ob_start_command( Th_Interp *interp, void *ctx,
                             int argc,  const char **argv, int *argl
){
  Th_Ob_Man * pMan = (Th_Ob_Man *)ctx;
  Blob * b = NULL;
  int 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;







>



>
>
>
>
>
>
>







 







|




|







 







>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>

<
>
>



|
|
>










<







 







|
|
|
|
<
>
|
<
>
>
>
>
>
|
|
<

|
>
>


>
>
>







 







|







 







>




|
>
>
>
>







 







|



>
>
>
>







>

|







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
....
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
....
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
....
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800

2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818

2819
2820
2821
2822
2823
2824
2825
....
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
....
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
....
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
....
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
#endif

extern void *fossil_realloc(void *p, size_t n);
static void * th_fossil_realloc(void *p, unsigned int n){
  return fossil_realloc( p, n );
}
static int Th_output_f_ob( char const * zData, int len, void * pState );
static void Th_output_dispose_ob( void * pState );
typedef struct Th_Command   Th_Command;
typedef struct Th_Frame     Th_Frame;
typedef struct Th_Variable  Th_Variable;

const Th_Vtab_Output Th_Vtab_Output_FILE = {
  Th_output_f_FILE /* write() */,
  Th_output_dispose_FILE /* dispose() */,
  NULL /*pState*/,
  1/*enabled*/
};

/*
** Holds client-provided "garbage collected" data for
** a Th_Interp instance.
*/
struct Th_GcEntry {
  void * pData;                        /* arbitrary data */
................................................................................
void *Th_Realloc(Th_Interp *pInterp, void *z, int nByte){
  void *p = pInterp->pVtab->xRealloc(z, nByte);
  return p;
}


int Th_Vtab_output( Th_Vtab *vTab, char const * zData, int nData ){
  if(!vTab->out.write){
    return -1;
  }else if(!vTab->out.enabled){
    return 0;
  }else{
    return vTab->out.write( zData, nData, vTab->out.pState );
  }
}


int Th_output( Th_Interp *pInterp, char const * zData, int nData ){
  return Th_Vtab_output( pInterp->pVtab, zData, nData );
}
................................................................................

int Th_output_f_FILE( char const * zData, int nData, void * pState ){
  FILE * dest = pState ? (FILE*)pState : stdout;
  int rc = (int)fwrite(zData, 1, nData, dest);
  fflush(dest);
  return rc;
}

void Th_output_dispose_FILE( void * pState ){
  FILE * f = pState ? (FILE*)pState : NULL;
  if(f
     && (f != stdout)
     && (f != stderr)
     && (f != stdin)){
    fflush(f);
    fclose(f);
  }
}

/*
** Install a new th1 command. 
**
** If a command of the same name already exists, it is deleted automatically.
*/
int Th_CreateCommand(
................................................................................
#define Th_Ob_Man_empty_m { \
  NULL/*aBuf*/,           \
  0/*nBuf*/,            \
  -1/*cursor*/,       \
  NULL/*interp*/,     \
  NULL/*aOutput*/       \
}

/*
** Vtab impl for the ob buffering layer.
*/
#define Th_Vtab_Output_empty_m { \

  NULL /* write() */, \
  NULL /* dispose() */, \
  NULL /*pState*/,\
  1/*enabled*/\
}
#define Th_Vtab_Output_ob_m { \
  Th_output_f_ob /*write()*/, \
  Th_output_dispose_ob /* dispose() */, \
  NULL /*pState*/,\
  1/*enabled*/\
}
static const Th_Ob_Man Th_Ob_Man_empty = Th_Ob_Man_empty_m;
static Th_Vtab_Output Th_Vtab_Output_ob = Th_Vtab_Output_ob_m;
static Th_Vtab_Output Th_Vtab_Output_empty = Th_Vtab_Output_empty_m;
#define Th_Ob_Man_KEY "Th_Ob_Man"
Th_Ob_Man * Th_ob_manager(Th_Interp *interp){
  return (Th_Ob_Man*) Th_Data_Get(interp, Th_Ob_Man_KEY );
}


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


/*
................................................................................
  Blob * b = Th_ob_current( pMan );
  assert( NULL != pMan );
  assert( b );
  blob_append( b, zData, len );
  return len;
}

static void Th_output_dispose_ob( void * pState ){
  /* possible todo: move the cleanup logic from
     Th_ob_pop() to here? */
  /*printf("disposing() ob vtab.\n"); */

#if 0
  Th_Ob_Man * pMan = (Th_Ob_Man*)pState;

  Blob * b = Th_ob_current( pMan );
  assert( NULL != pMan );
  assert( b );
#endif
}




int Th_ob_push( Th_Ob_Man * pMan,
                Th_Vtab_Output const * pWriter,
                Blob ** pOut ){
  Blob * pBlob;
  int x, i;
  if( NULL == pWriter ){
    pWriter = &Th_Vtab_Output_ob;
  }
  assert( NULL != pMan->interp );
  pBlob = (Blob *)Th_Malloc(pMan->interp, sizeof(Blob));
  *pBlob = empty_blob;

  if( pMan->cursor >= pMan->nBuf-2 ){
    /* expand if needed */
    x = pMan->nBuf + 5;
................................................................................
    pMan->nBuf = x;
  }
  assert( pMan->nBuf > pMan->cursor );
  assert( pMan->cursor >= -1 );
  ++pMan->cursor;
  pMan->aBuf[pMan->cursor] = pBlob;
  pMan->aOutput[pMan->cursor] = pMan->interp->pVtab->out;
  pMan->interp->pVtab->out = *pWriter;
  pMan->interp->pVtab->out.pState = pMan;
  if( pOut ){
    *pOut = pBlob;
  }
  /*printf( "push: pMan->nBuf=%d, pMan->cursor=%d\n", pMan->nBuf, pMan->cursor);*/
  return TH_OK;
  error:
................................................................................
}

Blob * Th_ob_pop( Th_Ob_Man * pMan ){
  if( pMan->cursor < 0 ){
    return NULL;
  }else{
    Blob * rc;
    Th_Vtab_Output * theOut;
    /*printf( "pop: pMan->nBuf=%d, pMan->cursor=%d\n", pMan->nBuf, pMan->cursor);*/
    assert( pMan->nBuf > pMan->cursor );
    rc = pMan->aBuf[pMan->cursor];
    pMan->aBuf[pMan->cursor] = NULL;
    theOut = &pMan->aOutput[pMan->cursor];
    if( theOut->dispose ){
      theOut->dispose( theOut->pState );
    }
    pMan->interp->pVtab->out = *theOut;
    pMan->aOutput[pMan->cursor] = Th_Vtab_Output_empty;
    if(-1 == --pMan->cursor){
      Th_Interp * interp = pMan->interp;
      Th_Free( pMan->interp, pMan->aBuf );
      Th_Free( pMan->interp, pMan->aOutput );
      *pMan = Th_Ob_Man_empty;
      pMan->interp = interp;
................................................................................
  Th_SetResultInt( interp, 1 + pMan->cursor );
  return TH_OK;
}

/*
** TH Syntax:
**
** ob start|push
**
** Pushes a new level of buffering onto the buffer stack.
** Returns the new buffering level (1-based).
**
** TODO: take an optional final argument naming the output handler.
** e.g. "stdout" or "cgi" or "default"
** 
*/
static int ob_start_command( Th_Interp *interp, void *ctx,
                             int argc,  const char **argv, int *argl
){
  Th_Ob_Man * pMan = (Th_Ob_Man *)ctx;
  Blob * b = NULL;
  int rc;
  Th_Vtab_Output const * pWriter = &Th_Vtab_Output_ob;
  assert( pMan && (interp == pMan->interp) );
  rc = Th_ob_push(pMan, NULL, &b);
  if( TH_OK != rc ){
    assert( NULL == b );
    return rc;
  }
  assert( NULL != b );
  Th_SetResultInt( interp, 1 + pMan->cursor );
  return TH_OK;

Changes to src/th.h.

38
39
40
41
42
43
44






45
46

47
48
49
50
51





52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
...
258
259
260
261
262
263
264





265
266
267
268
269
270
271
...
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
** implementation-specific state pointer (may be NULL, depending on
** the implementation). The return value is the number of bytes output
** (which may differ from len due to encoding and whatnot).  On error
** a negative value must be returned.
*/
typedef int (*Th_output_f)( char const * zData, int len, void * pState );







struct Th_Vtab_Output {
  Th_output_f f;   /* output handler */

  void * pState;   /* final argument for xOut() */
  char enabled;    /* if 0, Th_output() does nothing. */
};
typedef struct Th_Vtab_Output Th_Vtab_Output;






/*
** Before creating an interpreter, the application must allocate and
** populate an instance of the following structure. It must remain valid
** for the lifetime of the interpreter.
*/
struct Th_Vtab {
  void *(*xRealloc)(void *, unsigned int);
  Th_Vtab_Output out;
};
typedef struct Th_Vtab Th_Vtab;


/*
** Opaque handle for interpeter.
*/
................................................................................

/*
** Th_output_f() implementation which sends its output to either
** pState (which must be NULL or a (FILE*)) or stdout (if pState is
** NULL).
*/
int Th_output_f_FILE( char const * zData, int len, void * pState );






typedef struct Th_Command_Reg Th_Command_Reg;
/*
** A helper type for holding lists of function registration information.
** For use with Th_register_commands().
*/
struct Th_Command_Reg {
................................................................................

/*
** Pushes a new blob onto pMan's stack. On success
** returns TH_OK and assigns *pOut (if pOut is not NULL)
** to the new blob (which is owned by pMan). On error
** pOut is not modified and non-0 is returned.
*/
int Th_ob_push( Th_Ob_Man * pMan, Blob ** pOut );

/*
** Pops the top-most output buffer off the stack and returns
** it. Returns NULL if there is no current buffer. When the last
** buffer is popped, pMan's internals are cleaned up (but pMan is not
** freed).
**







>
>
>
>
>
>

|
>
|




>
>
>
>
>






|
|







 







>
>
>
>
>







 







|







38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
...
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
...
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
** implementation-specific state pointer (may be NULL, depending on
** the implementation). The return value is the number of bytes output
** (which may differ from len due to encoding and whatnot).  On error
** a negative value must be returned.
*/
typedef int (*Th_output_f)( char const * zData, int len, void * pState );

/*
** This structure defines the output state associated with a
** Th_Vtab. It is intended that a given Vtab be able to swap out
** output back-ends during its lifetime, e.g. to form a stack of
** buffers.
*/
struct Th_Vtab_Output {
  Th_output_f write;   /* output handler */
  void (*dispose)( void * pState );
  void * pState;   /* final argument for xOut() and dispose()*/
  char enabled;    /* if 0, Th_output() does nothing. */
};
typedef struct Th_Vtab_Output Th_Vtab_Output;

/*
** Shared Th_Vtab_Output instance used for copy-initialization.
*/
extern const Th_Vtab_Output Th_Vtab_Output_FILE;

/*
** Before creating an interpreter, the application must allocate and
** populate an instance of the following structure. It must remain valid
** for the lifetime of the interpreter.
*/
struct Th_Vtab {
  void *(*xRealloc)(void *, unsigned int); /* Re/deallocation routine. */
  Th_Vtab_Output out;                      /* output implementation */
};
typedef struct Th_Vtab Th_Vtab;


/*
** Opaque handle for interpeter.
*/
................................................................................

/*
** Th_output_f() implementation which sends its output to either
** pState (which must be NULL or a (FILE*)) or stdout (if pState is
** NULL).
*/
int Th_output_f_FILE( char const * zData, int len, void * pState );
/*
** Th_Vtab_Output::dispose impl for FILE handles. If pState is not
** one of the standard streams then it is fclose()d.
*/
void Th_output_dispose_FILE( void * pState );

typedef struct Th_Command_Reg Th_Command_Reg;
/*
** A helper type for holding lists of function registration information.
** For use with Th_register_commands().
*/
struct Th_Command_Reg {
................................................................................

/*
** Pushes a new blob onto pMan's stack. On success
** returns TH_OK and assigns *pOut (if pOut is not NULL)
** to the new blob (which is owned by pMan). On error
** pOut is not modified and non-0 is returned.
*/
int Th_ob_push( Th_Ob_Man * pMan, Th_Vtab_Output const * pWriter, Blob ** pOut );

/*
** Pops the top-most output buffer off the stack and returns
** it. Returns NULL if there is no current buffer. When the last
** buffer is popped, pMan's internals are cleaned up (but pMan is not
** freed).
**

Changes to src/th_main.c.

63
64
65
66
67
68
69
70

71
72

73
74
75
76
77
78
79
....
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
      /* FIXME: try to find some reasonable nOutstandingMalloc
         heuristics, e.g. if !p then ++, if !n then --, etc.
      */;
  }
}

static Th_Vtab vtab = { xRealloc, {
    NULL,

    NULL,
    1

  }
};

/*
** Generate a TH1 trace message if debugging is enabled.
*/
void Th_Trace(const char *zFormat, ...){
................................................................................
    {"wiki",          wikiCmd,              0},

    {0, 0, 0}
  };
  if( g.interp==0 ){
    int i;
    if(g.cgiOutput){
      vtab.out.f = Th_output_f_cgi_content;
    }else{
      vtab.out.f = Th_output_f_FILE;
      vtab.out.pState = stdout;
    }
    vtab.out.enabled = enableOutput;
    g.interp = Th_CreateInterp(&vtab);
    th_register_language(g.interp);       /* Basic scripting commands. */
#ifdef FOSSIL_ENABLE_TCL
    if( getenv("TH1_ENABLE_TCL")!=0 || db_get_boolean("tcl", 0) ){







|
>
|
<
>







 







|

|







63
64
65
66
67
68
69
70
71
72

73
74
75
76
77
78
79
80
....
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
      /* FIXME: try to find some reasonable nOutstandingMalloc
         heuristics, e.g. if !p then ++, if !n then --, etc.
      */;
  }
}

static Th_Vtab vtab = { xRealloc, {
  NULL /*write()*/,
  NULL/*dispose()*/,
  NULL/*pState*/,

  1/*enabled*/
  }
};

/*
** Generate a TH1 trace message if debugging is enabled.
*/
void Th_Trace(const char *zFormat, ...){
................................................................................
    {"wiki",          wikiCmd,              0},

    {0, 0, 0}
  };
  if( g.interp==0 ){
    int i;
    if(g.cgiOutput){
      vtab.out.write = Th_output_f_cgi_content;
    }else{
      vtab.out = Th_Vtab_Output_FILE;
      vtab.out.pState = stdout;
    }
    vtab.out.enabled = enableOutput;
    g.interp = Th_CreateInterp(&vtab);
    th_register_language(g.interp);       /* Basic scripting commands. */
#ifdef FOSSIL_ENABLE_TCL
    if( getenv("TH1_ENABLE_TCL")!=0 || db_get_boolean("tcl", 0) ){

Added test/th1-ob-1.th1.



































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<th1>
set i 0
set max 6
for {} {$i < $max} {incr i} {
    ob start
    puts "this is level " [ob level]
    set buf($i) [ob get]
}

for {set i [expr $max-1]} {$i >= 0} {incr i -1} {
    ob pop
}
for {set i [expr $max-1]} {$i >= 0} {incr i -1} {
    puts buf($i) = $buf($i) "\n"
}
puts "buffering level = " [ob level] \n
</th1>