Overview
SHA1 Hash: | 39aa870f8f1caada590d02d1345fb6e87571f92b |
---|---|
Date: | 2007-10-14 00:29:14 |
User: | drh |
Comment: | Work toward a new mechanism for configuring the ticket subsystem and rendering ticket entry, editing, and viewing screens. Nothing works, but it is all commented out so the code code compile. |
Timelines: | ancestors | descendants | both | trunk |
Other Links: | files | ZIP archive | manifest |
Tags And Properties
- branch=trunk inherited from [a28c83647d]
- sym-trunk inherited from [a28c83647d]
Changes
[hide diffs]Added src/subscript.c version [dd44ba1520]
@@ -1,1 +1,587 @@ +/* +** Copyright (c) 2007 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public +** License version 2 as published by the Free Software Foundation. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** +** You should have received a copy of the GNU General Public +** License along with this library; if not, write to the +** Free Software Foundation, Inc., 59 Temple Place - Suite 330, +** Boston, MA 02111-1307, USA. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains an implementation of the "subscript" interpreter. +** +** Subscript attempts to be an extremely light-weight scripting +** language. It contains the barest of bare essentials. It is +** stack-based and forth-like. Everything is in a single global +** namespace. There is only a single datatype of zero-terminated +** string. The stack is of fixed, limited depth. The hash table +** is of a limited and fixed size. +** +** This module attempts to be completely self-contained so that it can +** be portable to other projects. +*/ +#include "config.h" +#include "subscript.h" +#include <assert.h> + +/* +** Configuration constants +*/ +#define SBSCONFIG_NHASH 41 /* Size of the hash table */ +#define SBSCONFIG_NSTACK 10 /* Maximum stack depth */ +#define SBSCONFIG_ERRSIZE 100 /* Maximum size of an error message */ + +/* +** Available token types: +*/ +#define SBSTT_WHITESPACE 1 /* ex: \040 */ +#define SBSTT_NAME 2 /* ex: /abcde */ +#define SBSTT_VERB 3 /* ex: abcde */ +#define SBSTT_STRING 4 /* ex: {...} */ +#define SBSTT_INTEGER 5 /* Integer including option sign */ +#define SBSTT_INCOMPLETE 6 /* Unterminated string token */ +#define SBSTT_UNKNOWN 7 /* Unknown token */ +#define SBSTT_EOF 8 /* End of input */ + +/* +** Given an input string z of length n, identify the token that +** starts at z[0]. Write the token type into *pTokenType and +** return the length of the token. +*/ +static int sbs_next_token(const char *z, int n, int *pTokenType){ + int c; + if( n<=0 || z[0]==0 ){ + *pTokenType = SBSTT_EOF; + return 0; + } + c = z[0]; + if( isspace(c) ){ + int i; + *pTokenType = SBSTT_WHITESPACE; + for(i=1; i<n && isspace(z[i]); i++){} + return i; + } + if( c=='#' ){ + int i; + for(i=1; i<n && z[i] && z[i-1]!='\n'; i++){} + *pTokenType = SBSTT_WHITESPACE; + return i; + } + if( c=='{' ){ + int depth = 1; + int i; + for(i=1; i<n && z[i]; i++){ + if( z[i]=='{' ){ + depth++; + }else if( z[i]=='}' ){ + depth--; + if( depth==0 ){ + i++; + break; + } + } + } + if( depth ){ + *pTokenType = SBSTT_INCOMPLETE; + }else{ + *pTokenType = SBSTT_STRING; + } + return i; + } + if( c=='/' && n>=2 && isalpha(z[1]) ){ + int i; + for(i=2; i<n && (isalnum(z[i]) || z[i]=='_'); i++){} + *pTokenType = SBSTT_NAME; + return i; + } + if( isalpha(c) ){ + int i; + for(i=1; i<n && (isalnum(z[i]) || z[i]=='_'); i++){} + *pTokenType = SBSTT_VERB; + return i; + } + if( isdigit(c) || ((c=='-' || c=='+') && n>=2 && isdigit(z[1])) ){ + int i; + for(i=1; i<n && isdigit(z[i]); i++){} + *pTokenType = SBSTT_INTEGER; + return i; + } + *pTokenType = SBSTT_UNKNOWN; + return 1; +} + + +/* +** Values are stored in the hash table as instances of the following +** structure. +*/ +typedef struct SbSValue SbSValue; +struct SbSValue { + int flags; /* Bitmask of SBSVAL_* values */ + union { + struct { + int size; /* Number of bytes in string, not counting final zero */ + char *z; /* Pointer to string content */ + } str; /* Value if SBSVAL_STR */ + struct { + int (*xVerb)(Subscript*), void*); /* Function to do the work */ + void *pArg; /* 2nd parameter to xVerb */ + } verb; /* Value if SBSVAL_VERB */ + } u; +}; +#define SBSVAL_VERB 0x0001 /* Value stored in u.verb */ +#define SBSVAL_STR 0x0002 /* Value stored in u.str */ +#define SBSVAL_DYN 0x0004 /* u.str.z is dynamically allocated */ +#define SBSVAL_EXEC 0x0008 /* u.str.z is a script */ + +/* +** Release any memory allocated by a value. +*/ +static void sbs_value_reset(SbSValue *p){ + if( p->flags & SBSVAL_DYN ){ + free(p->u.str.z); + p->flags = SBSVAL_STR; + p->u.str.z = ""; + p->u.str.size = 0; + } +} + + +/* +** An entry in the hash table is an instance of this structure. +*/ +typedef struct UsHashEntry UsHashEntry; +struct UsHashEntry { + UsHashEntry *pNext; /* Next entry with the same hash on zKey */ + SbSValue val; /* The payload */ + int nKey; /* Length of the key */ + char zKey[0]; /* The key */ +}; + +/* +** A hash table is an instance of the following structure. +*/ +typedef struct UsHashTab UsHashTab; +struct UsHashTab { + UsHashEntry *aHash[SBSCONFIG_NHASH]; /* The hash table */ +}; + +/* +** Compute a hash on a string. +*/ +static int sbs_hash(const char *z, int n){ + int h = 0; + int i; + for(i=0; i<n; i++){ + h ^= (h<<1) | z[i]; + } + h &= 0x7ffffff; + return h % SBSCONFIG_NHASH; +} + +/* +** Look up a value in the hash table. Return a pointer to the value. +** Return NULL if not found. +*/ +static const SbSValue *sbs_fetch( + sbs_hash *pHash, + const char *zKey, + int nKey +){ + int h; + UsHashEntry *p; + + if( nKey<0 ) nKey = strlen(zKey); + h = sbs_hash(zKey, nKey); + for(p = pHash->aHash[h]; p; p=p->pNext){ + if( p->nKey==nKey && memcmp(p->zKey,zKey,nKey)==0 ){ + return &p->val; + } + } + return 0; +} + +/* +** Store a value in the hash table. Overwrite any prior value stored +** under the same name. +** +** If the value in the 4th argument needs to be reset or freed, +** the hash table will take over responsibiliity for doing so. +*/ +static int sbs_store( + sbs_hash *pHash, /* Insert into this hash table */ + const char *zKey, /* The key */ + int nKey, /* Size of the key */ + const SbSValue *pValue /* The value to be stored */ +){ + int h; + UsHashEntry *p; + + if( nKey<0 ) nKey = strlen(zKey); + h = sbs_hash(zKey, nKey); + for(p = pHash->aHash[h]; p; p=p->pNext){ + if( p->nKey==nKey && memcmp(p->zKey,zKey,nKey)==0 ){ + sbs_value_reset(&p->val); + memcpy(&p->val, pValue, sizeof(p->val)); + return SBS_OK; + } + } + pNew = malloc( sizeof(*pNew) + nKey ); + if( pNew ){ + pNew->nKey = nKey; + memcpy(pNew->zKey, zKey, nKey+1); + memcpy(&pNew->val, pValue, sizeof(pNew->val)); + pNew->pNext = pHash->aHash[h]; + pHash->aHash[h] = pNew; + return SBS_OK; + } + return SBS_ERROR; +} + +/* +** Reset a hash table. +*/ +static void sbs_hash_reset(sbs_hash *pHash){ + int i; + UsHashEntry *p, *pNext; + for(i=0; i<SBSCONFIG_NHASH; i++){ + for(p=pHash->aHash[i]; p; p=pNext){ + pNext = p->pNext; + sbs_value_reset(&p->val); + free(p); + } + } + memset(pHash, 0, sizeof(*pHash)); +} + +/* +** An instance of the Subscript interpreter +*/ +struct Subscript { + int nStack; /* Number of entries on stack */ + UsHashTab symTab; /* The symbol table */ + char zErrMsg[SBSCONFIG_ERRSIZE]; /* Space to write an error message */ + SbSValue aStack[SBSCONFIG_NSTACK]; /* The stack */ +}; + + +/* +** Push a value onto the stack of an interpreter +*/ +static int sbs_push(Subscript *p, SbSValue *pVal){ + SbSValue *pStk; + if( p->nStack>=SBSCONFIG_NSTACK ){ + sqlite3_snprintf(SBSCONFIG_ERRSIZE, p->zErrMsg, "stack overflow"); + return SBS_ERROR; + } + p->aStack[p->nStack++] = *pVal; + return SBS_OK; +} + +/* +** Destroy an underscore interpreter +*/ +void SbS_Destroy(struct Subscript *p){ + int i; + sbs_hash_reset(&p->symTab); + for(i=0; i<p->nStack; i++){ + sbs_value_reset(&p->aStack[i]); + } + free(p); +} + +/* +** Set the error message for an interpreter. Verb implementations +** use this routine when they encounter an error. +*/ +void SbS_SetErrorMessage(struct Subscript *p, const char *zErr){ + int nErr = strlen(zErr); + if( nErr>sizeof(p->zErrMsg)-1 ){ + nErr = sizeof(p->zErrMsg)-1; + } + memcpy(p->zErrMsg, zErr, nErr); + p->zErrMsg[nErr] = 0; +} + +/* +** Return a pointer to the current error message for the +** interpreter. +*/ +const char *SbS_GetErrorMessage(struct Subscript *p){ + return p->zErrMsg; +} + +/* +** Add a new verb the given interpreter +*/ +int SbS_AddVerb( + struct Subscript *p, + const char *zVerb, + int (*xVerb)(struct Subscript*,void*), + void *pArg +){ + SbSValue v; + v.flags = SBSVAL_VERB; + v.u.verb.xVerb = xVerb; + v.u.verb.pArg = pArg; + return sbs_store(&p->symTab, zVerb, -1, &v); +} + +/* +** Push a string value onto the stack. +** +** If the 4th parameter is 0, then the string is static. +** If the 4th parameter is non-zero then the string was obtained +** from malloc and Subscript will take responsibility for freeing +** it. +** +** Return 0 on success and non-zero if there is an error. +*/ +int SbS_Push( + struct Subscript *p, /* Push onto this interpreter */ + const char *z, /* String value to push */ + int n, /* Length of the string, or -1 */ + int dyn /* If true, z was obtained from malloc */ +){ + SbSValue v; + v.flags = SBSVAL_STR; + if( needToFree ){ + v.flags |= SBSVAL_DYN; + } + if( n<0 ) n = strlen(z); + v.u.str.size = n; + v.u.str.z = z; + return sbs_push(p, &v); +} + +/* +** Push an integer value onto the stack. +** +** This routine really just converts the integer into a string +** then calls SbS_Push. +*/ +int SbS_PushInt(struct Subscript *p, int iVal){ + if( iVal==0 ){ + return SbS_Push(p, "0", 1, 0); + }else if( iVal==1 ){ + return SbS_Push(p, "1", 1, 0); + }else{ + char *z; + int n; + char zVal[50]; + sprintf(zVal, "%d", iVal); + n = strlen(zVal); + z = malloc( n+1 ); + if( z ){ + strcpy(z, zVal); + return SbS_Push(p, z, n, 1); + }else{ + return SBS_ERROR; + } + } +} + +/* +** Pop and destroy zero or more values from the stack. +** Return the number of values remaining on the stack after +** the pops occur. +*/ +int SbS_Pop(struct Subscript *p, int N){ + while( N>0 && p->nStack>0 ){ + p->nStack--; + sbs_value_reset(&p->aStack[p->nStack]); + N--; + } + return p->nStack; +} + +/* +** Return the N-th element of the stack. 0 is the top of the stack. +** 1 is the first element down. 2 is the second element. And so forth. +** Return NULL if there is no N-th element. +** +** The pointer returned is only valid until the value is popped +** from the stack. +*/ +const char *SbS_StackValue(struct Subscript *p, int N, int *pSize){ + SbSValue *pVal; + if( N<0 || N>=p->nStack ){ + return 0; + } + pVal = &p->aStack[p->nStack-N-1]; + if( (pVal->flags & SBSVAL_STR)==0 ){ + return 0; + } + *pSize = pVal->u.str.size; + return pVal->u.str.z; +} + +/* +** A convenience routine for extracting an integer value from the +** stack. +*/ +int SbS_StackValueInt(struct Subscript *p, int N){ + int n, v; + int isNeg = 0; + char *z = SbS_StackValue(p, N, &n); + v = 0; + if( n==0 ) return 0; + if( z[0]=='-' ){ + isNeg = 1; + z++; + n--; + }else if( z[0]=='+' ){ + z++; + n--; + } + while( n>0 && isdigit(z[0]) ){ + v = v*10 + z[0] - '0'; + z++; + n--; + } + if( isNeg ){ + v = -v; + } + return v; +} + +/* +** Retrieve the value of a variable from the interpreter. Return +** NULL if no such variable is defined. +** +** The returned string is not necessarily (probably not) zero-terminated. +** The string may be deallocated the next time anything is done to +** the interpreter. Make a copy if you need it to persist. +*/ +const char *SbS_Fetch( + struct Subscript *p, /* The interpreter we are interrogating */ + const char *zKey, /* Name of the variable. Case sensitive */ + int *pLength /* Write the length here */ +){ + SbSValue *pVal; + + pVal = sbs_fetch(&p->symTab, zKey, -1); + if( pVal==0 || (pVal->flags & SBSVAL_STR)==0 ){ + *pLength = 0; + return 0; + }else{ + *pLength = pVal->u.str.size; + return pVal->u.str.z; + } +} + +/* +** Generate an error and return non-zero if the stack has +** fewer than N elements. This is utility routine used in +** the implementation of verbs. +*/ +int SbS_RequireStack(struct Subscript *p, int N, const char *zCmd){ + if( p->nStack>N ) return 0; + sqlite3_snprintf(sizeof(p->zErrMsg), p->zErrMsg, + "\"%s\" requires at least %d stack elements - only found %d", + zCmd, N, p->nStack); + return 1; +} + +/* +** The built-in "set" command: +** +** STRING NAME set +** +** Write the value of STRING into variable called NAME. +*/ +static int setCmd(Subscript *p, void *pNotUsed){ + SbSValue *pTos; + SbSValue *pNos; + if( SbS_RequireStack(p, 2, "set") ) return SBS_ERROR; + pTos = p->aStack[--p->nStack]; + pNos = p->aStack[--p->nStack]; + sbs_store(&p->symTab, pTos->u.str.z, pTos->u.str.size, pNos); + sbs_value_reset(pTos); + return 0; +} + + +/* +** Create a new underscore interpreter +*/ +struct Subscript *SbS_Create(void){ + Subscript *p; + + p = malloc( sizeof(*p) ); + if( p ){ + memset(p, 0, sizeof(*p)); + SbS_AddVerb(p, "set", setCmd, 0); + } + return p; +} +/* +** Evaluate the script given by the first nScript bytes of zScript[]. +** Return 0 on success and non-zero for an error. +*/ +int SbS_Eval(struct Subscript *p, const char *zScript, int nScript){ + int rc = SBS_OK; + if( nScript<0 ) nScript = strlen(zScript); + while( nScript>0 && rc==SBS_OK ){ + int n; + int ttype; + n = sbs_token_type(zScript, nScript, &ttype); + switch( ttype ){ + case SBSTT_WHITESPACE: { + break; + } + case SBSTT_EOF: { + nScript = 0; + break; + } + case SBSTT_INCOMPLETE: + case SBSTT_UNKNOWN: { + rc = SBS_ERROR; + nScript = n; + break; + } + case SBSTT_INTEGER: { + rc = sbs_push(p, zScript, n); + break; + } + case SBSTT_NAME: { + rc = sbs_push(p, &zScript[1], n-1); + break; + } + case SBSTT_STRING: { + rc = sbs_push(p, &zScript[1], n-2); + break; + } + case SBSTT_VERB: { + SbSValue *pVal = sbs_fetch(p->pHash, zScript, nScript); + if( pVal==0 ){ + rc = SBS_ERROR; + }else if( pVal->flags & SBSVAL_VERB ){ + rc = pVal->u.verb.xVerb(p, pVal->u.verb.pArg); + }else if( pVal->flags & SBSVAL_EXEC ){ + rc = SbS_Eval(p, pVal->u.str.z, pVal->u.str.size); + }else{ + rc = sbs_push(p, pVal->u.str.z, pVal->u.str.size); + } + break; + } + } + zScript += n; + nScript -= n; + } + return rc; +}
Modified src/tkt.c from [98ba13ea1e] to [0661d23511].
@@ -22,17 +22,185 @@ ******************************************************************************* ** ** This file contains code used render and control ticket entry ** and display pages. */ +#if 0 #include "config.h" #include "tkt.h" #include <assert.h> /* +** Flags to indicate what kind of ticket string is being generated. +** A bitmask of these is associated with each verb in order to indicate +** which verbs go on which pages. +*/ +#define M_NEW 0x01 +#define M_EDIT 0x02 +#define M_VIEW 0x04 + +/* +** The Subscript interpreter used to parse the ticket configure +** and to render ticket screens. +*/ +static struct Subscript *pInterp = 0; + +/* +** The list of database fields in the ticket table. +** This is the user-defined list in the configuration file. +** Add the "tkt_" prefix to all of these names in the real table. +** The real table also contains some addition fields not found +** here. +*/ +static int nField = 0; +static Blob fieldList; +static char **azField = 0; + +/* +** Subscript command: LIST setfields +** +** Parse up the list and populate the nField and azField variables. +*/ +static int setFieldsCmd(struct Subscript *p, void *pNotUsed){ + if( SbS_RequireStack(p, 1) ) return 1; + if( nField==0 ){ + char *zFieldList; + int nFieldList, i; + Blob field; + blob_zero(&fieldList); + zFieldList = SbS_StackValue(p, 0, &nFieldList); + blob_appendf(&fieldList, zFieldList, nFieldList); + while( blob_token(&fieldList, &field) ){ + nField++; + } + azField = malloc( sizeof(azField[0])*nField ); + blob_rewind(&fieldList); + i = 0; + while( blob_token(&fieldList, &field) ){ + azField[i] = blob_terminate(&field); + } + } + SbS_Pop(p, 1); + return 0; +} + +/* +** Subscript command: INTEGER not INTEGER +*/ +static int notCmd(struct Subscript *p, void *pNotUsed){ + int n; + if( SbS_RequireStack(p, 1) ) return 1; + n = SbS_StackValueInt(p, 0); + SbS_Pop(p, 1); + SbS_PushInt(p, !n); + return 0; +} + +/* +** Subscript command: INTEGER INTEGER max INTEGER +*/ +static int maxCmd(struct Subscript *p, void *pNotUsed){ + int a, b; + if( SbS_RequireStack(p, 2) ) return 1; + a = SbS_StackValueInt(p, 0); + b = SbS_StackValueInt(p, 1); + SbS_Pop(p, 2); + SbS_PushInt(p, a>b ? a : b); + return 0; +} + +/* +** Subscript command: INTEGER INTEGER and INTEGER +*/ +static int andCmd(struct Subscript *p, void *pNotUsed){ + int a, b; + if( SbS_RequireStack(p, 2) ) return 1; + a = SbS_StackValueInt(p, 0); + b = SbS_StackValueInt(p, 1); + SbS_Pop(p, 2); + SbS_PushInt(p, a && b); + return 0; +} + +/* +** Subscript command: FIELD wikiview +*/ +static int wikiViewCmd(struct Subscript *p, void *pNotUsed){ + if( SbS_RequireStack(p, 2) ) return 1; + + SbS_Pop(p, 1); + return 0; +} + + +/* +** Create an Subscript interpreter appropriate for processing +** Ticket pages. +*/ +static void tkt_screen_init(int flags){ + char *zConfig; + int i; + static const struct { + const char *zName; + int (*xVerb); + int mask; + } aVerb[] = { + { "not", notCmd, M_NEW|M_EDIT|M_VIEW }, + { "max", maxCmd, M_NEW|M_EDIT|M_VIEW }, + { "and", andCmd, M_NEW|M_EDIT|M_VIEW }, + { "wikiview", wikiViewCmd, M_NEW|M_EDIT|M_VIEW }, + { "textview", textViewCmd, M_NEW|M_EDIT|M_VIEW }, + { "linecount", lineCountCmd, M_NEW|M_EDIT|M_VIEW }, + { "cgiparam", cgiParamCmd, M_NEW|M_EDIT|M_VIEW }, + { "enable_output", enableOutputCmd, M_NEW|M_EDIT|M_VIEW }, + { "is_anon", isAnonCmd, M_NEW|M_EDIT|M_VIEW }, + { "ok_wrtkt", okWrTktCmd, M_NEW|M_EDIT|M_VIEW }, + { "default_value", dfltValueCmd, M_NEW }, + { "textedit", textEditCmd, M_NEW|M_EDIT }, + { "combobox", comboBoxCmd, M_NEW|M_EDIT }, + { "multilineedit", multiLineEditCmd, M_NEW|M_EDIT }, + { "multilineappend", multiAppendCmd, M_EDIT }, + { "auxbutton", auxButtonCmd, M_NEW|M_EDIT }, + { "submitbutton", submitButtonCmd, M_NEW|M_EDIT }, + }; + + pInterp = SbS_Create(); + SbS_AddVerb(pInterp, "setfields", setFieldsCmd, 0); + zConfig = db_get("ticket-config",""); + SbS_Eval(pInter, zConfig, -1); + for(i=0; i<sizeof(aVerb)/sizeof(aVerb[0]); i++){ + SbS_AddVerb(pInterp, aVerb[i].zName, aVerb[i].xVerb, 0); + } + /* Extract appropriate template */ + return pInterp; +} + +/* ** PAGE: tktnew */ void tktnew_page(void){ + struct Subscript *pInterp; + const char *zPage; + int nPage; + + tkt_screen_init(M_NEW); + style_header("New Ticket"); + @ This will become a page for entering new tickets. + style_footer(); +} + +/* +** PAGE: tktview +** URL: tktview?name=UUID +** +*/ +void tktnew_page(void){ + struct Subscript *pInterp; + const char *zPage; + int nPage; + + tkt_screen_init(M_NEW); style_header("New Ticket"); @ This will become a page for entering new tickets. style_footer(); } +#endif
Modified src/tktconf.c from [d2929cd226] to [d769ab0345].
@@ -430,17 +430,17 @@ @ template new END-OF-NEW-TEMPLATE @ <table cellpadding="5"> @ <tr> @ <td cellpadding="2"> @ Enter a one-line summary of the problem:<br> - @ [entrywidget title] + @ [/title 60 1 textentry] @ </td> @ </tr> @ @ <tr> @ <td align="right">Type: - @ [entrywidget type] + @ [/type entrywidget] @ </td> @ <td>What type of ticket is this?</td> @ </tr> @ @ <tr>