Fossil SCM
Fix several issues with the TH1 unset command, including a memory leak. Add more tests. Keep the original branch open in case further changes are needed.
Commit
1aeb2726b02778874ff21a2e9cb54d1a7ebe9710
Parent
6228efbb7cb9521…
3 files changed
+112
-34
+1
-1
+55
M
src/th.c
+112
-34
| --- src/th.c | ||
| +++ src/th.c | ||
| @@ -105,12 +105,12 @@ | ||
| 105 | 105 | static int thEndOfLine(const char *, int); |
| 106 | 106 | |
| 107 | 107 | static int thPushFrame(Th_Interp*, Th_Frame*); |
| 108 | 108 | static void thPopFrame(Th_Interp*); |
| 109 | 109 | |
| 110 | -static void thFreeVariable(Th_HashEntry*, void*); | |
| 111 | -static void thFreeCommand(Th_HashEntry*, void*); | |
| 110 | +static int thFreeVariable(Th_HashEntry*, void*); | |
| 111 | +static int thFreeCommand(Th_HashEntry*, void*); | |
| 112 | 112 | |
| 113 | 113 | /* |
| 114 | 114 | ** The following are used by both the expression and language parsers. |
| 115 | 115 | ** Given that the start of the input string (z, n) is a language |
| 116 | 116 | ** construct of the relevant type (a command enclosed in [], an escape |
| @@ -258,12 +258,14 @@ | ||
| 258 | 258 | ** (Th_Frame.paVar). Decrement the reference count of the Th_Variable |
| 259 | 259 | ** structure that the entry points to. Free the Th_Variable if its |
| 260 | 260 | ** reference count reaches 0. |
| 261 | 261 | ** |
| 262 | 262 | ** Argument pContext is a pointer to the interpreter structure. |
| 263 | +** | |
| 264 | +** Returns non-zero if the Th_Variable was actually freed. | |
| 263 | 265 | */ |
| 264 | -static void thFreeVariable(Th_HashEntry *pEntry, void *pContext){ | |
| 266 | +static int thFreeVariable(Th_HashEntry *pEntry, void *pContext){ | |
| 265 | 267 | Th_Variable *pValue = (Th_Variable *)pEntry->pData; |
| 266 | 268 | pValue->nRef--; |
| 267 | 269 | assert( pValue->nRef>=0 ); |
| 268 | 270 | if( pValue->nRef==0 ){ |
| 269 | 271 | Th_Interp *interp = (Th_Interp *)pContext; |
| @@ -271,27 +273,33 @@ | ||
| 271 | 273 | if( pValue->pHash ){ |
| 272 | 274 | Th_HashIterate(interp, pValue->pHash, thFreeVariable, pContext); |
| 273 | 275 | Th_HashDelete(interp, pValue->pHash); |
| 274 | 276 | } |
| 275 | 277 | Th_Free(interp, pValue); |
| 278 | + pEntry->pData = 0; | |
| 279 | + return 1; | |
| 276 | 280 | } |
| 281 | + return 0; | |
| 277 | 282 | } |
| 278 | 283 | |
| 279 | 284 | /* |
| 280 | 285 | ** Argument pEntry points to an entry in the command hash table |
| 281 | 286 | ** (Th_Interp.paCmd). Delete the Th_Command structure that the |
| 282 | 287 | ** entry points to. |
| 283 | 288 | ** |
| 284 | 289 | ** Argument pContext is a pointer to the interpreter structure. |
| 290 | +** | |
| 291 | +** Always returns non-zero. | |
| 285 | 292 | */ |
| 286 | -static void thFreeCommand(Th_HashEntry *pEntry, void *pContext){ | |
| 293 | +static int thFreeCommand(Th_HashEntry *pEntry, void *pContext){ | |
| 287 | 294 | Th_Command *pCommand = (Th_Command *)pEntry->pData; |
| 288 | 295 | if( pCommand->xDel ){ |
| 289 | 296 | pCommand->xDel((Th_Interp *)pContext, pCommand->pContext); |
| 290 | 297 | } |
| 291 | 298 | Th_Free((Th_Interp *)pContext, pEntry->pData); |
| 292 | 299 | pEntry->pData = 0; |
| 300 | + return 1; | |
| 293 | 301 | } |
| 294 | 302 | |
| 295 | 303 | /* |
| 296 | 304 | ** Push a new frame onto the stack. |
| 297 | 305 | */ |
| @@ -1042,10 +1050,25 @@ | ||
| 1042 | 1050 | *pnInner = nInner; |
| 1043 | 1051 | *pisGlobal = isGlobal; |
| 1044 | 1052 | return TH_OK; |
| 1045 | 1053 | } |
| 1046 | 1054 | |
| 1055 | +/* | |
| 1056 | +** The Find structure is used to return extra information to callers of the | |
| 1057 | +** thFindValue function. The fields within it are populated by thFindValue | |
| 1058 | +** as soon as the necessary information is available. Callers should check | |
| 1059 | +** each field of interest upon return. | |
| 1060 | +*/ | |
| 1061 | + | |
| 1062 | +struct Find { | |
| 1063 | + Th_HashEntry *pValueEntry; /* Pointer to the scalar or array hash entry */ | |
| 1064 | + Th_HashEntry *pElemEntry; /* Pointer to the element hash entry, if any */ | |
| 1065 | + const char *zElem; /* Name of array element, if applicable */ | |
| 1066 | + int nElem; /* Length of array element name, if applicable */ | |
| 1067 | +}; | |
| 1068 | +typedef struct Find Find; | |
| 1069 | + | |
| 1047 | 1070 | /* |
| 1048 | 1071 | ** Input string (zVar, nVar) contains a variable name. This function locates |
| 1049 | 1072 | ** the Th_Variable structure associated with the named variable. The |
| 1050 | 1073 | ** variable name may be a global or local scalar or array variable |
| 1051 | 1074 | ** |
| @@ -1055,17 +1078,19 @@ | ||
| 1055 | 1078 | ** |
| 1056 | 1079 | ** If the arrayok argument is false and the named variable is an array, |
| 1057 | 1080 | ** an error is left in the interpreter result and NULL returned. If |
| 1058 | 1081 | ** arrayok is true an array name is Ok. |
| 1059 | 1082 | */ |
| 1083 | + | |
| 1060 | 1084 | static Th_Variable *thFindValue( |
| 1061 | 1085 | Th_Interp *interp, |
| 1062 | - const char *zVar, /* Pointer to variable name */ | |
| 1063 | - int nVar, /* Number of bytes at nVar */ | |
| 1064 | - int create, /* If true, create the variable if not found */ | |
| 1065 | - int arrayok, /* If true, an array is Ok. Otherwise array==error */ | |
| 1066 | - int noerror /* If false, set interpreter result to error message */ | |
| 1086 | + const char *zVar, /* Pointer to variable name */ | |
| 1087 | + int nVar, /* Number of bytes at nVar */ | |
| 1088 | + int create, /* If true, create the variable if not found */ | |
| 1089 | + int arrayok, /* If true, an array is Ok. Otherwise array==error */ | |
| 1090 | + int noerror, /* If false, set interpreter result to error */ | |
| 1091 | + Find *pFind /* If non-zero, place output here */ | |
| 1067 | 1092 | ){ |
| 1068 | 1093 | const char *zOuter; |
| 1069 | 1094 | int nOuter; |
| 1070 | 1095 | const char *zInner; |
| 1071 | 1096 | int nInner; |
| @@ -1074,16 +1099,24 @@ | ||
| 1074 | 1099 | Th_HashEntry *pEntry; |
| 1075 | 1100 | Th_Frame *pFrame = interp->pFrame; |
| 1076 | 1101 | Th_Variable *pValue; |
| 1077 | 1102 | |
| 1078 | 1103 | thAnalyseVarname(zVar, nVar, &zOuter, &nOuter, &zInner, &nInner, &isGlobal); |
| 1104 | + if( pFind ){ | |
| 1105 | + memset(pFind, 0, sizeof(Find)); | |
| 1106 | + pFind->zElem = zInner; | |
| 1107 | + pFind->nElem = nInner; | |
| 1108 | + } | |
| 1079 | 1109 | if( isGlobal ){ |
| 1080 | 1110 | while( pFrame->pCaller ) pFrame = pFrame->pCaller; |
| 1081 | 1111 | } |
| 1082 | 1112 | |
| 1083 | 1113 | pEntry = Th_HashFind(interp, pFrame->paVar, zOuter, nOuter, create); |
| 1084 | 1114 | assert(pEntry || create<=0); |
| 1115 | + if( pFind ){ | |
| 1116 | + pFind->pValueEntry = pEntry; | |
| 1117 | + } | |
| 1085 | 1118 | if( !pEntry ){ |
| 1086 | 1119 | goto no_such_var; |
| 1087 | 1120 | } |
| 1088 | 1121 | |
| 1089 | 1122 | pValue = (Th_Variable *)pEntry->pData; |
| @@ -1106,10 +1139,14 @@ | ||
| 1106 | 1139 | goto no_such_var; |
| 1107 | 1140 | } |
| 1108 | 1141 | pValue->pHash = Th_HashNew(interp); |
| 1109 | 1142 | } |
| 1110 | 1143 | pEntry = Th_HashFind(interp, pValue->pHash, zInner, nInner, create); |
| 1144 | + assert(pEntry || create<=0); | |
| 1145 | + if( pFind ){ | |
| 1146 | + pFind->pElemEntry = pEntry; | |
| 1147 | + } | |
| 1111 | 1148 | if( !pEntry ){ |
| 1112 | 1149 | goto no_such_var; |
| 1113 | 1150 | } |
| 1114 | 1151 | pValue = (Th_Variable *)pEntry->pData; |
| 1115 | 1152 | if( !pValue ){ |
| @@ -1145,11 +1182,11 @@ | ||
| 1145 | 1182 | ** an error message in the interpreter result. |
| 1146 | 1183 | */ |
| 1147 | 1184 | int Th_GetVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1148 | 1185 | Th_Variable *pValue; |
| 1149 | 1186 | |
| 1150 | - pValue = thFindValue(interp, zVar, nVar, 0, 0, 0); | |
| 1187 | + pValue = thFindValue(interp, zVar, nVar, 0, 0, 0, 0); | |
| 1151 | 1188 | if( !pValue ){ |
| 1152 | 1189 | return TH_ERROR; |
| 1153 | 1190 | } |
| 1154 | 1191 | if( !pValue->zData ){ |
| 1155 | 1192 | Th_ErrorMessage(interp, "no such variable:", zVar, nVar); |
| @@ -1161,12 +1198,12 @@ | ||
| 1161 | 1198 | |
| 1162 | 1199 | /* |
| 1163 | 1200 | ** Return true if variable (zVar, nVar) exists. |
| 1164 | 1201 | */ |
| 1165 | 1202 | int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1166 | - Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 0, 1); | |
| 1167 | - return pValue && pValue->zData; | |
| 1203 | + Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0); | |
| 1204 | + return pValue && (pValue->zData || pValue->pHash); | |
| 1168 | 1205 | } |
| 1169 | 1206 | |
| 1170 | 1207 | /* |
| 1171 | 1208 | ** String (zVar, nVar) must contain the name of a scalar variable or |
| 1172 | 1209 | ** array member. If the variable does not exist it is created. The |
| @@ -1182,11 +1219,11 @@ | ||
| 1182 | 1219 | const char *zValue, |
| 1183 | 1220 | int nValue |
| 1184 | 1221 | ){ |
| 1185 | 1222 | Th_Variable *pValue; |
| 1186 | 1223 | |
| 1187 | - pValue = thFindValue(interp, zVar, nVar, 1, 0, 0); | |
| 1224 | + pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0); | |
| 1188 | 1225 | if( !pValue ){ |
| 1189 | 1226 | return TH_ERROR; |
| 1190 | 1227 | } |
| 1191 | 1228 | |
| 1192 | 1229 | if( nValue<0 ){ |
| @@ -1225,11 +1262,11 @@ | ||
| 1225 | 1262 | if( !pFrame ){ |
| 1226 | 1263 | return TH_ERROR; |
| 1227 | 1264 | } |
| 1228 | 1265 | pSavedFrame = interp->pFrame; |
| 1229 | 1266 | interp->pFrame = pFrame; |
| 1230 | - pValue = thFindValue(interp, zLink, nLink, 1, 1, 0); | |
| 1267 | + pValue = thFindValue(interp, zLink, nLink, 1, 1, 0, 0); | |
| 1231 | 1268 | interp->pFrame = pSavedFrame; |
| 1232 | 1269 | |
| 1233 | 1270 | pEntry = Th_HashFind(interp, interp->pFrame->paVar, zLocal, nLocal, 1); |
| 1234 | 1271 | if( pEntry->pData ){ |
| 1235 | 1272 | Th_ErrorMessage(interp, "variable exists:", zLocal, nLocal); |
| @@ -1246,29 +1283,68 @@ | ||
| 1246 | 1283 | ** an array, or an array member. If the identified variable exists, it |
| 1247 | 1284 | ** is deleted and TH_OK returned. Otherwise, an error message is left |
| 1248 | 1285 | ** in the interpreter result and TH_ERROR is returned. |
| 1249 | 1286 | */ |
| 1250 | 1287 | int Th_UnsetVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1288 | + Find find; | |
| 1251 | 1289 | Th_Variable *pValue; |
| 1290 | + Th_HashEntry *pEntry; | |
| 1291 | + int rc = TH_ERROR; | |
| 1252 | 1292 | |
| 1253 | - pValue = thFindValue(interp, zVar, nVar, 0, 1, 0); | |
| 1293 | + pValue = thFindValue(interp, zVar, nVar, 0, 1, 0, &find); | |
| 1254 | 1294 | if( !pValue ){ |
| 1255 | - return TH_ERROR; | |
| 1256 | - } | |
| 1257 | - | |
| 1258 | - if( pValue->zData ){ | |
| 1259 | - Th_Free(interp, pValue->zData); | |
| 1260 | - pValue->zData = 0; | |
| 1261 | - } | |
| 1262 | - if( pValue->pHash ){ | |
| 1263 | - Th_HashIterate(interp, pValue->pHash, thFreeVariable, (void *)interp); | |
| 1264 | - Th_HashDelete(interp, pValue->pHash); | |
| 1265 | - pValue->pHash = 0; | |
| 1266 | - } | |
| 1267 | - | |
| 1268 | - thFindValue(interp, zVar, nVar, -1, 1, 1); /* Finally, delete from frame */ | |
| 1269 | - return TH_OK; | |
| 1295 | + return rc; | |
| 1296 | + } | |
| 1297 | + | |
| 1298 | + if( pValue->zData || pValue->pHash ){ | |
| 1299 | + rc = TH_OK; | |
| 1300 | + }else { | |
| 1301 | + Th_ErrorMessage(interp, "no such variable:", zVar, nVar); | |
| 1302 | + } | |
| 1303 | + | |
| 1304 | + /* | |
| 1305 | + ** The variable may be shared by more than one frame; therefore, make sure | |
| 1306 | + ** it is actually freed prior to freeing the parent structure. The values | |
| 1307 | + ** for the variable must be freed now so the variable appears undefined in | |
| 1308 | + ** all frames. The hash entry in the current frame must also be deleted | |
| 1309 | + ** now; otherwise, if the current stack frame is later popped, it will try | |
| 1310 | + ** to delete a variable which has already been freed. | |
| 1311 | + */ | |
| 1312 | + if( find.zElem ){ | |
| 1313 | + pEntry = find.pElemEntry; | |
| 1314 | + }else{ | |
| 1315 | + pEntry = find.pValueEntry; | |
| 1316 | + } | |
| 1317 | + assert( pEntry ); | |
| 1318 | + assert( pValue ); | |
| 1319 | + if( thFreeVariable(pEntry, (void *)interp) ){ | |
| 1320 | + if( find.zElem ){ | |
| 1321 | + Th_Variable *pValue2 = find.pValueEntry->pData; | |
| 1322 | + Th_HashFind(interp, pValue2->pHash, find.zElem, find.nElem, -1); | |
| 1323 | + }else if( pEntry->pData ){ | |
| 1324 | + Th_Free(interp, pEntry->pData); | |
| 1325 | + pEntry->pData = 0; | |
| 1326 | + } | |
| 1327 | + }else{ | |
| 1328 | + if( pValue->zData ){ | |
| 1329 | + Th_Free(interp, pValue->zData); | |
| 1330 | + pValue->zData = 0; | |
| 1331 | + } | |
| 1332 | + if( pValue->pHash ){ | |
| 1333 | + Th_HashIterate(interp, pValue->pHash, thFreeVariable, (void *)interp); | |
| 1334 | + Th_HashDelete(interp, pValue->pHash); | |
| 1335 | + pValue->pHash = 0; | |
| 1336 | + } | |
| 1337 | + if( find.zElem ){ | |
| 1338 | + Th_Variable *pValue2 = find.pValueEntry->pData; | |
| 1339 | + Th_HashFind(interp, pValue2->pHash, find.zElem, find.nElem, -1); | |
| 1340 | + } | |
| 1341 | + } | |
| 1342 | + if( !find.zElem ){ | |
| 1343 | + thFindValue(interp, zVar, nVar, -1, 1, 1, 0); | |
| 1344 | + } | |
| 1345 | + return rc; | |
| 1270 | 1346 | } |
| 1271 | 1347 | |
| 1272 | 1348 | /* |
| 1273 | 1349 | ** Return an allocated buffer containing a copy of string (z, n). The |
| 1274 | 1350 | ** caller is responsible for eventually calling Th_Free() to free |
| @@ -2211,16 +2287,17 @@ | ||
| 2211 | 2287 | |
| 2212 | 2288 | /* |
| 2213 | 2289 | ** Iterate through all values currently stored in the hash table. Invoke |
| 2214 | 2290 | ** the callback function xCallback for each entry. The second argument |
| 2215 | 2291 | ** passed to xCallback is a copy of the fourth argument passed to this |
| 2216 | -** function. | |
| 2292 | +** function. The return value from the callback function xCallback is | |
| 2293 | +** ignored. | |
| 2217 | 2294 | */ |
| 2218 | 2295 | void Th_HashIterate( |
| 2219 | 2296 | Th_Interp *interp, |
| 2220 | 2297 | Th_Hash *pHash, |
| 2221 | - void (*xCallback)(Th_HashEntry *pEntry, void *pContext), | |
| 2298 | + int (*xCallback)(Th_HashEntry *pEntry, void *pContext), | |
| 2222 | 2299 | void *pContext |
| 2223 | 2300 | ){ |
| 2224 | 2301 | int i; |
| 2225 | 2302 | for(i=0; i<TH_HASHSIZE; i++){ |
| 2226 | 2303 | Th_HashEntry *pEntry; |
| @@ -2231,14 +2308,15 @@ | ||
| 2231 | 2308 | } |
| 2232 | 2309 | } |
| 2233 | 2310 | } |
| 2234 | 2311 | |
| 2235 | 2312 | /* |
| 2236 | -** Helper function for Th_HashDelete(). | |
| 2313 | +** Helper function for Th_HashDelete(). Always returns non-zero. | |
| 2237 | 2314 | */ |
| 2238 | -static void xFreeHashEntry(Th_HashEntry *pEntry, void *pContext){ | |
| 2315 | +static int xFreeHashEntry(Th_HashEntry *pEntry, void *pContext){ | |
| 2239 | 2316 | Th_Free((Th_Interp *)pContext, (void *)pEntry); |
| 2317 | + return 1; | |
| 2240 | 2318 | } |
| 2241 | 2319 | |
| 2242 | 2320 | /* |
| 2243 | 2321 | ** Free a hash-table previously allocated by Th_HashNew(). |
| 2244 | 2322 | */ |
| 2245 | 2323 |
| --- src/th.c | |
| +++ src/th.c | |
| @@ -105,12 +105,12 @@ | |
| 105 | static int thEndOfLine(const char *, int); |
| 106 | |
| 107 | static int thPushFrame(Th_Interp*, Th_Frame*); |
| 108 | static void thPopFrame(Th_Interp*); |
| 109 | |
| 110 | static void thFreeVariable(Th_HashEntry*, void*); |
| 111 | static void thFreeCommand(Th_HashEntry*, void*); |
| 112 | |
| 113 | /* |
| 114 | ** The following are used by both the expression and language parsers. |
| 115 | ** Given that the start of the input string (z, n) is a language |
| 116 | ** construct of the relevant type (a command enclosed in [], an escape |
| @@ -258,12 +258,14 @@ | |
| 258 | ** (Th_Frame.paVar). Decrement the reference count of the Th_Variable |
| 259 | ** structure that the entry points to. Free the Th_Variable if its |
| 260 | ** reference count reaches 0. |
| 261 | ** |
| 262 | ** Argument pContext is a pointer to the interpreter structure. |
| 263 | */ |
| 264 | static void thFreeVariable(Th_HashEntry *pEntry, void *pContext){ |
| 265 | Th_Variable *pValue = (Th_Variable *)pEntry->pData; |
| 266 | pValue->nRef--; |
| 267 | assert( pValue->nRef>=0 ); |
| 268 | if( pValue->nRef==0 ){ |
| 269 | Th_Interp *interp = (Th_Interp *)pContext; |
| @@ -271,27 +273,33 @@ | |
| 271 | if( pValue->pHash ){ |
| 272 | Th_HashIterate(interp, pValue->pHash, thFreeVariable, pContext); |
| 273 | Th_HashDelete(interp, pValue->pHash); |
| 274 | } |
| 275 | Th_Free(interp, pValue); |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | /* |
| 280 | ** Argument pEntry points to an entry in the command hash table |
| 281 | ** (Th_Interp.paCmd). Delete the Th_Command structure that the |
| 282 | ** entry points to. |
| 283 | ** |
| 284 | ** Argument pContext is a pointer to the interpreter structure. |
| 285 | */ |
| 286 | static void thFreeCommand(Th_HashEntry *pEntry, void *pContext){ |
| 287 | Th_Command *pCommand = (Th_Command *)pEntry->pData; |
| 288 | if( pCommand->xDel ){ |
| 289 | pCommand->xDel((Th_Interp *)pContext, pCommand->pContext); |
| 290 | } |
| 291 | Th_Free((Th_Interp *)pContext, pEntry->pData); |
| 292 | pEntry->pData = 0; |
| 293 | } |
| 294 | |
| 295 | /* |
| 296 | ** Push a new frame onto the stack. |
| 297 | */ |
| @@ -1042,10 +1050,25 @@ | |
| 1042 | *pnInner = nInner; |
| 1043 | *pisGlobal = isGlobal; |
| 1044 | return TH_OK; |
| 1045 | } |
| 1046 | |
| 1047 | /* |
| 1048 | ** Input string (zVar, nVar) contains a variable name. This function locates |
| 1049 | ** the Th_Variable structure associated with the named variable. The |
| 1050 | ** variable name may be a global or local scalar or array variable |
| 1051 | ** |
| @@ -1055,17 +1078,19 @@ | |
| 1055 | ** |
| 1056 | ** If the arrayok argument is false and the named variable is an array, |
| 1057 | ** an error is left in the interpreter result and NULL returned. If |
| 1058 | ** arrayok is true an array name is Ok. |
| 1059 | */ |
| 1060 | static Th_Variable *thFindValue( |
| 1061 | Th_Interp *interp, |
| 1062 | const char *zVar, /* Pointer to variable name */ |
| 1063 | int nVar, /* Number of bytes at nVar */ |
| 1064 | int create, /* If true, create the variable if not found */ |
| 1065 | int arrayok, /* If true, an array is Ok. Otherwise array==error */ |
| 1066 | int noerror /* If false, set interpreter result to error message */ |
| 1067 | ){ |
| 1068 | const char *zOuter; |
| 1069 | int nOuter; |
| 1070 | const char *zInner; |
| 1071 | int nInner; |
| @@ -1074,16 +1099,24 @@ | |
| 1074 | Th_HashEntry *pEntry; |
| 1075 | Th_Frame *pFrame = interp->pFrame; |
| 1076 | Th_Variable *pValue; |
| 1077 | |
| 1078 | thAnalyseVarname(zVar, nVar, &zOuter, &nOuter, &zInner, &nInner, &isGlobal); |
| 1079 | if( isGlobal ){ |
| 1080 | while( pFrame->pCaller ) pFrame = pFrame->pCaller; |
| 1081 | } |
| 1082 | |
| 1083 | pEntry = Th_HashFind(interp, pFrame->paVar, zOuter, nOuter, create); |
| 1084 | assert(pEntry || create<=0); |
| 1085 | if( !pEntry ){ |
| 1086 | goto no_such_var; |
| 1087 | } |
| 1088 | |
| 1089 | pValue = (Th_Variable *)pEntry->pData; |
| @@ -1106,10 +1139,14 @@ | |
| 1106 | goto no_such_var; |
| 1107 | } |
| 1108 | pValue->pHash = Th_HashNew(interp); |
| 1109 | } |
| 1110 | pEntry = Th_HashFind(interp, pValue->pHash, zInner, nInner, create); |
| 1111 | if( !pEntry ){ |
| 1112 | goto no_such_var; |
| 1113 | } |
| 1114 | pValue = (Th_Variable *)pEntry->pData; |
| 1115 | if( !pValue ){ |
| @@ -1145,11 +1182,11 @@ | |
| 1145 | ** an error message in the interpreter result. |
| 1146 | */ |
| 1147 | int Th_GetVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1148 | Th_Variable *pValue; |
| 1149 | |
| 1150 | pValue = thFindValue(interp, zVar, nVar, 0, 0, 0); |
| 1151 | if( !pValue ){ |
| 1152 | return TH_ERROR; |
| 1153 | } |
| 1154 | if( !pValue->zData ){ |
| 1155 | Th_ErrorMessage(interp, "no such variable:", zVar, nVar); |
| @@ -1161,12 +1198,12 @@ | |
| 1161 | |
| 1162 | /* |
| 1163 | ** Return true if variable (zVar, nVar) exists. |
| 1164 | */ |
| 1165 | int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1166 | Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 0, 1); |
| 1167 | return pValue && pValue->zData; |
| 1168 | } |
| 1169 | |
| 1170 | /* |
| 1171 | ** String (zVar, nVar) must contain the name of a scalar variable or |
| 1172 | ** array member. If the variable does not exist it is created. The |
| @@ -1182,11 +1219,11 @@ | |
| 1182 | const char *zValue, |
| 1183 | int nValue |
| 1184 | ){ |
| 1185 | Th_Variable *pValue; |
| 1186 | |
| 1187 | pValue = thFindValue(interp, zVar, nVar, 1, 0, 0); |
| 1188 | if( !pValue ){ |
| 1189 | return TH_ERROR; |
| 1190 | } |
| 1191 | |
| 1192 | if( nValue<0 ){ |
| @@ -1225,11 +1262,11 @@ | |
| 1225 | if( !pFrame ){ |
| 1226 | return TH_ERROR; |
| 1227 | } |
| 1228 | pSavedFrame = interp->pFrame; |
| 1229 | interp->pFrame = pFrame; |
| 1230 | pValue = thFindValue(interp, zLink, nLink, 1, 1, 0); |
| 1231 | interp->pFrame = pSavedFrame; |
| 1232 | |
| 1233 | pEntry = Th_HashFind(interp, interp->pFrame->paVar, zLocal, nLocal, 1); |
| 1234 | if( pEntry->pData ){ |
| 1235 | Th_ErrorMessage(interp, "variable exists:", zLocal, nLocal); |
| @@ -1246,29 +1283,68 @@ | |
| 1246 | ** an array, or an array member. If the identified variable exists, it |
| 1247 | ** is deleted and TH_OK returned. Otherwise, an error message is left |
| 1248 | ** in the interpreter result and TH_ERROR is returned. |
| 1249 | */ |
| 1250 | int Th_UnsetVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1251 | Th_Variable *pValue; |
| 1252 | |
| 1253 | pValue = thFindValue(interp, zVar, nVar, 0, 1, 0); |
| 1254 | if( !pValue ){ |
| 1255 | return TH_ERROR; |
| 1256 | } |
| 1257 | |
| 1258 | if( pValue->zData ){ |
| 1259 | Th_Free(interp, pValue->zData); |
| 1260 | pValue->zData = 0; |
| 1261 | } |
| 1262 | if( pValue->pHash ){ |
| 1263 | Th_HashIterate(interp, pValue->pHash, thFreeVariable, (void *)interp); |
| 1264 | Th_HashDelete(interp, pValue->pHash); |
| 1265 | pValue->pHash = 0; |
| 1266 | } |
| 1267 | |
| 1268 | thFindValue(interp, zVar, nVar, -1, 1, 1); /* Finally, delete from frame */ |
| 1269 | return TH_OK; |
| 1270 | } |
| 1271 | |
| 1272 | /* |
| 1273 | ** Return an allocated buffer containing a copy of string (z, n). The |
| 1274 | ** caller is responsible for eventually calling Th_Free() to free |
| @@ -2211,16 +2287,17 @@ | |
| 2211 | |
| 2212 | /* |
| 2213 | ** Iterate through all values currently stored in the hash table. Invoke |
| 2214 | ** the callback function xCallback for each entry. The second argument |
| 2215 | ** passed to xCallback is a copy of the fourth argument passed to this |
| 2216 | ** function. |
| 2217 | */ |
| 2218 | void Th_HashIterate( |
| 2219 | Th_Interp *interp, |
| 2220 | Th_Hash *pHash, |
| 2221 | void (*xCallback)(Th_HashEntry *pEntry, void *pContext), |
| 2222 | void *pContext |
| 2223 | ){ |
| 2224 | int i; |
| 2225 | for(i=0; i<TH_HASHSIZE; i++){ |
| 2226 | Th_HashEntry *pEntry; |
| @@ -2231,14 +2308,15 @@ | |
| 2231 | } |
| 2232 | } |
| 2233 | } |
| 2234 | |
| 2235 | /* |
| 2236 | ** Helper function for Th_HashDelete(). |
| 2237 | */ |
| 2238 | static void xFreeHashEntry(Th_HashEntry *pEntry, void *pContext){ |
| 2239 | Th_Free((Th_Interp *)pContext, (void *)pEntry); |
| 2240 | } |
| 2241 | |
| 2242 | /* |
| 2243 | ** Free a hash-table previously allocated by Th_HashNew(). |
| 2244 | */ |
| 2245 |
| --- src/th.c | |
| +++ src/th.c | |
| @@ -105,12 +105,12 @@ | |
| 105 | static int thEndOfLine(const char *, int); |
| 106 | |
| 107 | static int thPushFrame(Th_Interp*, Th_Frame*); |
| 108 | static void thPopFrame(Th_Interp*); |
| 109 | |
| 110 | static int thFreeVariable(Th_HashEntry*, void*); |
| 111 | static int thFreeCommand(Th_HashEntry*, void*); |
| 112 | |
| 113 | /* |
| 114 | ** The following are used by both the expression and language parsers. |
| 115 | ** Given that the start of the input string (z, n) is a language |
| 116 | ** construct of the relevant type (a command enclosed in [], an escape |
| @@ -258,12 +258,14 @@ | |
| 258 | ** (Th_Frame.paVar). Decrement the reference count of the Th_Variable |
| 259 | ** structure that the entry points to. Free the Th_Variable if its |
| 260 | ** reference count reaches 0. |
| 261 | ** |
| 262 | ** Argument pContext is a pointer to the interpreter structure. |
| 263 | ** |
| 264 | ** Returns non-zero if the Th_Variable was actually freed. |
| 265 | */ |
| 266 | static int thFreeVariable(Th_HashEntry *pEntry, void *pContext){ |
| 267 | Th_Variable *pValue = (Th_Variable *)pEntry->pData; |
| 268 | pValue->nRef--; |
| 269 | assert( pValue->nRef>=0 ); |
| 270 | if( pValue->nRef==0 ){ |
| 271 | Th_Interp *interp = (Th_Interp *)pContext; |
| @@ -271,27 +273,33 @@ | |
| 273 | if( pValue->pHash ){ |
| 274 | Th_HashIterate(interp, pValue->pHash, thFreeVariable, pContext); |
| 275 | Th_HashDelete(interp, pValue->pHash); |
| 276 | } |
| 277 | Th_Free(interp, pValue); |
| 278 | pEntry->pData = 0; |
| 279 | return 1; |
| 280 | } |
| 281 | return 0; |
| 282 | } |
| 283 | |
| 284 | /* |
| 285 | ** Argument pEntry points to an entry in the command hash table |
| 286 | ** (Th_Interp.paCmd). Delete the Th_Command structure that the |
| 287 | ** entry points to. |
| 288 | ** |
| 289 | ** Argument pContext is a pointer to the interpreter structure. |
| 290 | ** |
| 291 | ** Always returns non-zero. |
| 292 | */ |
| 293 | static int thFreeCommand(Th_HashEntry *pEntry, void *pContext){ |
| 294 | Th_Command *pCommand = (Th_Command *)pEntry->pData; |
| 295 | if( pCommand->xDel ){ |
| 296 | pCommand->xDel((Th_Interp *)pContext, pCommand->pContext); |
| 297 | } |
| 298 | Th_Free((Th_Interp *)pContext, pEntry->pData); |
| 299 | pEntry->pData = 0; |
| 300 | return 1; |
| 301 | } |
| 302 | |
| 303 | /* |
| 304 | ** Push a new frame onto the stack. |
| 305 | */ |
| @@ -1042,10 +1050,25 @@ | |
| 1050 | *pnInner = nInner; |
| 1051 | *pisGlobal = isGlobal; |
| 1052 | return TH_OK; |
| 1053 | } |
| 1054 | |
| 1055 | /* |
| 1056 | ** The Find structure is used to return extra information to callers of the |
| 1057 | ** thFindValue function. The fields within it are populated by thFindValue |
| 1058 | ** as soon as the necessary information is available. Callers should check |
| 1059 | ** each field of interest upon return. |
| 1060 | */ |
| 1061 | |
| 1062 | struct Find { |
| 1063 | Th_HashEntry *pValueEntry; /* Pointer to the scalar or array hash entry */ |
| 1064 | Th_HashEntry *pElemEntry; /* Pointer to the element hash entry, if any */ |
| 1065 | const char *zElem; /* Name of array element, if applicable */ |
| 1066 | int nElem; /* Length of array element name, if applicable */ |
| 1067 | }; |
| 1068 | typedef struct Find Find; |
| 1069 | |
| 1070 | /* |
| 1071 | ** Input string (zVar, nVar) contains a variable name. This function locates |
| 1072 | ** the Th_Variable structure associated with the named variable. The |
| 1073 | ** variable name may be a global or local scalar or array variable |
| 1074 | ** |
| @@ -1055,17 +1078,19 @@ | |
| 1078 | ** |
| 1079 | ** If the arrayok argument is false and the named variable is an array, |
| 1080 | ** an error is left in the interpreter result and NULL returned. If |
| 1081 | ** arrayok is true an array name is Ok. |
| 1082 | */ |
| 1083 | |
| 1084 | static Th_Variable *thFindValue( |
| 1085 | Th_Interp *interp, |
| 1086 | const char *zVar, /* Pointer to variable name */ |
| 1087 | int nVar, /* Number of bytes at nVar */ |
| 1088 | int create, /* If true, create the variable if not found */ |
| 1089 | int arrayok, /* If true, an array is Ok. Otherwise array==error */ |
| 1090 | int noerror, /* If false, set interpreter result to error */ |
| 1091 | Find *pFind /* If non-zero, place output here */ |
| 1092 | ){ |
| 1093 | const char *zOuter; |
| 1094 | int nOuter; |
| 1095 | const char *zInner; |
| 1096 | int nInner; |
| @@ -1074,16 +1099,24 @@ | |
| 1099 | Th_HashEntry *pEntry; |
| 1100 | Th_Frame *pFrame = interp->pFrame; |
| 1101 | Th_Variable *pValue; |
| 1102 | |
| 1103 | thAnalyseVarname(zVar, nVar, &zOuter, &nOuter, &zInner, &nInner, &isGlobal); |
| 1104 | if( pFind ){ |
| 1105 | memset(pFind, 0, sizeof(Find)); |
| 1106 | pFind->zElem = zInner; |
| 1107 | pFind->nElem = nInner; |
| 1108 | } |
| 1109 | if( isGlobal ){ |
| 1110 | while( pFrame->pCaller ) pFrame = pFrame->pCaller; |
| 1111 | } |
| 1112 | |
| 1113 | pEntry = Th_HashFind(interp, pFrame->paVar, zOuter, nOuter, create); |
| 1114 | assert(pEntry || create<=0); |
| 1115 | if( pFind ){ |
| 1116 | pFind->pValueEntry = pEntry; |
| 1117 | } |
| 1118 | if( !pEntry ){ |
| 1119 | goto no_such_var; |
| 1120 | } |
| 1121 | |
| 1122 | pValue = (Th_Variable *)pEntry->pData; |
| @@ -1106,10 +1139,14 @@ | |
| 1139 | goto no_such_var; |
| 1140 | } |
| 1141 | pValue->pHash = Th_HashNew(interp); |
| 1142 | } |
| 1143 | pEntry = Th_HashFind(interp, pValue->pHash, zInner, nInner, create); |
| 1144 | assert(pEntry || create<=0); |
| 1145 | if( pFind ){ |
| 1146 | pFind->pElemEntry = pEntry; |
| 1147 | } |
| 1148 | if( !pEntry ){ |
| 1149 | goto no_such_var; |
| 1150 | } |
| 1151 | pValue = (Th_Variable *)pEntry->pData; |
| 1152 | if( !pValue ){ |
| @@ -1145,11 +1182,11 @@ | |
| 1182 | ** an error message in the interpreter result. |
| 1183 | */ |
| 1184 | int Th_GetVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1185 | Th_Variable *pValue; |
| 1186 | |
| 1187 | pValue = thFindValue(interp, zVar, nVar, 0, 0, 0, 0); |
| 1188 | if( !pValue ){ |
| 1189 | return TH_ERROR; |
| 1190 | } |
| 1191 | if( !pValue->zData ){ |
| 1192 | Th_ErrorMessage(interp, "no such variable:", zVar, nVar); |
| @@ -1161,12 +1198,12 @@ | |
| 1198 | |
| 1199 | /* |
| 1200 | ** Return true if variable (zVar, nVar) exists. |
| 1201 | */ |
| 1202 | int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1203 | Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0); |
| 1204 | return pValue && (pValue->zData || pValue->pHash); |
| 1205 | } |
| 1206 | |
| 1207 | /* |
| 1208 | ** String (zVar, nVar) must contain the name of a scalar variable or |
| 1209 | ** array member. If the variable does not exist it is created. The |
| @@ -1182,11 +1219,11 @@ | |
| 1219 | const char *zValue, |
| 1220 | int nValue |
| 1221 | ){ |
| 1222 | Th_Variable *pValue; |
| 1223 | |
| 1224 | pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0); |
| 1225 | if( !pValue ){ |
| 1226 | return TH_ERROR; |
| 1227 | } |
| 1228 | |
| 1229 | if( nValue<0 ){ |
| @@ -1225,11 +1262,11 @@ | |
| 1262 | if( !pFrame ){ |
| 1263 | return TH_ERROR; |
| 1264 | } |
| 1265 | pSavedFrame = interp->pFrame; |
| 1266 | interp->pFrame = pFrame; |
| 1267 | pValue = thFindValue(interp, zLink, nLink, 1, 1, 0, 0); |
| 1268 | interp->pFrame = pSavedFrame; |
| 1269 | |
| 1270 | pEntry = Th_HashFind(interp, interp->pFrame->paVar, zLocal, nLocal, 1); |
| 1271 | if( pEntry->pData ){ |
| 1272 | Th_ErrorMessage(interp, "variable exists:", zLocal, nLocal); |
| @@ -1246,29 +1283,68 @@ | |
| 1283 | ** an array, or an array member. If the identified variable exists, it |
| 1284 | ** is deleted and TH_OK returned. Otherwise, an error message is left |
| 1285 | ** in the interpreter result and TH_ERROR is returned. |
| 1286 | */ |
| 1287 | int Th_UnsetVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1288 | Find find; |
| 1289 | Th_Variable *pValue; |
| 1290 | Th_HashEntry *pEntry; |
| 1291 | int rc = TH_ERROR; |
| 1292 | |
| 1293 | pValue = thFindValue(interp, zVar, nVar, 0, 1, 0, &find); |
| 1294 | if( !pValue ){ |
| 1295 | return rc; |
| 1296 | } |
| 1297 | |
| 1298 | if( pValue->zData || pValue->pHash ){ |
| 1299 | rc = TH_OK; |
| 1300 | }else { |
| 1301 | Th_ErrorMessage(interp, "no such variable:", zVar, nVar); |
| 1302 | } |
| 1303 | |
| 1304 | /* |
| 1305 | ** The variable may be shared by more than one frame; therefore, make sure |
| 1306 | ** it is actually freed prior to freeing the parent structure. The values |
| 1307 | ** for the variable must be freed now so the variable appears undefined in |
| 1308 | ** all frames. The hash entry in the current frame must also be deleted |
| 1309 | ** now; otherwise, if the current stack frame is later popped, it will try |
| 1310 | ** to delete a variable which has already been freed. |
| 1311 | */ |
| 1312 | if( find.zElem ){ |
| 1313 | pEntry = find.pElemEntry; |
| 1314 | }else{ |
| 1315 | pEntry = find.pValueEntry; |
| 1316 | } |
| 1317 | assert( pEntry ); |
| 1318 | assert( pValue ); |
| 1319 | if( thFreeVariable(pEntry, (void *)interp) ){ |
| 1320 | if( find.zElem ){ |
| 1321 | Th_Variable *pValue2 = find.pValueEntry->pData; |
| 1322 | Th_HashFind(interp, pValue2->pHash, find.zElem, find.nElem, -1); |
| 1323 | }else if( pEntry->pData ){ |
| 1324 | Th_Free(interp, pEntry->pData); |
| 1325 | pEntry->pData = 0; |
| 1326 | } |
| 1327 | }else{ |
| 1328 | if( pValue->zData ){ |
| 1329 | Th_Free(interp, pValue->zData); |
| 1330 | pValue->zData = 0; |
| 1331 | } |
| 1332 | if( pValue->pHash ){ |
| 1333 | Th_HashIterate(interp, pValue->pHash, thFreeVariable, (void *)interp); |
| 1334 | Th_HashDelete(interp, pValue->pHash); |
| 1335 | pValue->pHash = 0; |
| 1336 | } |
| 1337 | if( find.zElem ){ |
| 1338 | Th_Variable *pValue2 = find.pValueEntry->pData; |
| 1339 | Th_HashFind(interp, pValue2->pHash, find.zElem, find.nElem, -1); |
| 1340 | } |
| 1341 | } |
| 1342 | if( !find.zElem ){ |
| 1343 | thFindValue(interp, zVar, nVar, -1, 1, 1, 0); |
| 1344 | } |
| 1345 | return rc; |
| 1346 | } |
| 1347 | |
| 1348 | /* |
| 1349 | ** Return an allocated buffer containing a copy of string (z, n). The |
| 1350 | ** caller is responsible for eventually calling Th_Free() to free |
| @@ -2211,16 +2287,17 @@ | |
| 2287 | |
| 2288 | /* |
| 2289 | ** Iterate through all values currently stored in the hash table. Invoke |
| 2290 | ** the callback function xCallback for each entry. The second argument |
| 2291 | ** passed to xCallback is a copy of the fourth argument passed to this |
| 2292 | ** function. The return value from the callback function xCallback is |
| 2293 | ** ignored. |
| 2294 | */ |
| 2295 | void Th_HashIterate( |
| 2296 | Th_Interp *interp, |
| 2297 | Th_Hash *pHash, |
| 2298 | int (*xCallback)(Th_HashEntry *pEntry, void *pContext), |
| 2299 | void *pContext |
| 2300 | ){ |
| 2301 | int i; |
| 2302 | for(i=0; i<TH_HASHSIZE; i++){ |
| 2303 | Th_HashEntry *pEntry; |
| @@ -2231,14 +2308,15 @@ | |
| 2308 | } |
| 2309 | } |
| 2310 | } |
| 2311 | |
| 2312 | /* |
| 2313 | ** Helper function for Th_HashDelete(). Always returns non-zero. |
| 2314 | */ |
| 2315 | static int xFreeHashEntry(Th_HashEntry *pEntry, void *pContext){ |
| 2316 | Th_Free((Th_Interp *)pContext, (void *)pEntry); |
| 2317 | return 1; |
| 2318 | } |
| 2319 | |
| 2320 | /* |
| 2321 | ** Free a hash-table previously allocated by Th_HashNew(). |
| 2322 | */ |
| 2323 |
M
src/th.h
+1
-1
| --- src/th.h | ||
| +++ src/th.h | ||
| @@ -174,11 +174,11 @@ | ||
| 174 | 174 | int nKey; |
| 175 | 175 | Th_HashEntry *pNext; /* Internal use only */ |
| 176 | 176 | }; |
| 177 | 177 | Th_Hash *Th_HashNew(Th_Interp *); |
| 178 | 178 | void Th_HashDelete(Th_Interp *, Th_Hash *); |
| 179 | -void Th_HashIterate(Th_Interp*,Th_Hash*,void (*x)(Th_HashEntry*, void*),void*); | |
| 179 | +void Th_HashIterate(Th_Interp*,Th_Hash*,int (*x)(Th_HashEntry*, void*),void*); | |
| 180 | 180 | Th_HashEntry *Th_HashFind(Th_Interp*, Th_Hash*, const char*, int, int); |
| 181 | 181 | |
| 182 | 182 | /* |
| 183 | 183 | ** Useful functions from th_lang.c. |
| 184 | 184 | */ |
| 185 | 185 |
| --- src/th.h | |
| +++ src/th.h | |
| @@ -174,11 +174,11 @@ | |
| 174 | int nKey; |
| 175 | Th_HashEntry *pNext; /* Internal use only */ |
| 176 | }; |
| 177 | Th_Hash *Th_HashNew(Th_Interp *); |
| 178 | void Th_HashDelete(Th_Interp *, Th_Hash *); |
| 179 | void Th_HashIterate(Th_Interp*,Th_Hash*,void (*x)(Th_HashEntry*, void*),void*); |
| 180 | Th_HashEntry *Th_HashFind(Th_Interp*, Th_Hash*, const char*, int, int); |
| 181 | |
| 182 | /* |
| 183 | ** Useful functions from th_lang.c. |
| 184 | */ |
| 185 |
| --- src/th.h | |
| +++ src/th.h | |
| @@ -174,11 +174,11 @@ | |
| 174 | int nKey; |
| 175 | Th_HashEntry *pNext; /* Internal use only */ |
| 176 | }; |
| 177 | Th_Hash *Th_HashNew(Th_Interp *); |
| 178 | void Th_HashDelete(Th_Interp *, Th_Hash *); |
| 179 | void Th_HashIterate(Th_Interp*,Th_Hash*,int (*x)(Th_HashEntry*, void*),void*); |
| 180 | Th_HashEntry *Th_HashFind(Th_Interp*, Th_Hash*, const char*, int, int); |
| 181 | |
| 182 | /* |
| 183 | ** Useful functions from th_lang.c. |
| 184 | */ |
| 185 |
+55
| --- test/th1.test | ||
| +++ test/th1.test | ||
| @@ -140,10 +140,40 @@ | ||
| 140 | 140 | fossil test-th-eval "catch {bad}; info exists var; set th_stack_trace" |
| 141 | 141 | test th1-info-exists-6 {$RESULT eq {bad}} |
| 142 | 142 | |
| 143 | 143 | ############################################################################### |
| 144 | 144 | |
| 145 | +fossil test-th-eval "set var(1) 1; info exists var" | |
| 146 | +test th1-info-exists-7 {$RESULT eq {1}} | |
| 147 | + | |
| 148 | +############################################################################### | |
| 149 | + | |
| 150 | +fossil test-th-eval "set var(1) 1; unset var(1); info exists var" | |
| 151 | +test th1-info-exists-8 {$RESULT eq {1}} | |
| 152 | + | |
| 153 | +############################################################################### | |
| 154 | + | |
| 155 | +fossil test-th-eval "set var(1) 1; unset var; info exists var" | |
| 156 | +test th1-info-exists-9 {$RESULT eq {0}} | |
| 157 | + | |
| 158 | +############################################################################### | |
| 159 | + | |
| 160 | +fossil test-th-eval "set var(1) 1; info exists var(1)" | |
| 161 | +test th1-info-exists-10 {$RESULT eq {1}} | |
| 162 | + | |
| 163 | +############################################################################### | |
| 164 | + | |
| 165 | +fossil test-th-eval "set var(1) 1; unset var(1); info exists var(1)" | |
| 166 | +test th1-info-exists-11 {$RESULT eq {0}} | |
| 167 | + | |
| 168 | +############################################################################### | |
| 169 | + | |
| 170 | +fossil test-th-eval "set var(1) 1; unset var; info exists var(1)" | |
| 171 | +test th1-info-exists-12 {$RESULT eq {0}} | |
| 172 | + | |
| 173 | +############################################################################### | |
| 174 | + | |
| 145 | 175 | fossil test-th-eval "set var 1; unset var" |
| 146 | 176 | test th1-unset-1 {$RESULT eq {var}} |
| 147 | 177 | |
| 148 | 178 | ############################################################################### |
| 149 | 179 | |
| @@ -152,5 +182,30 @@ | ||
| 152 | 182 | |
| 153 | 183 | ############################################################################### |
| 154 | 184 | |
| 155 | 185 | fossil test-th-eval "set var 1; unset var; unset var" |
| 156 | 186 | test th1-unset-3 {$RESULT eq {TH_ERROR: no such variable: var}} |
| 187 | + | |
| 188 | +############################################################################### | |
| 189 | + | |
| 190 | +fossil test-th-eval "set gv 1; proc p {} {upvar 1 gv lv; unset lv}; p; unset gv" | |
| 191 | +test th1-unset-4 {$RESULT eq {TH_ERROR: no such variable: gv}} | |
| 192 | + | |
| 193 | +############################################################################### | |
| 194 | + | |
| 195 | +fossil test-th-eval "set gv 1; upvar 0 gv gv2; info exists gv2" | |
| 196 | +test th1-unset-5 {$RESULT eq {1}} | |
| 197 | + | |
| 198 | +############################################################################### | |
| 199 | + | |
| 200 | +fossil test-th-eval "set gv 1; upvar 0 gv gv2; unset gv; unset gv2" | |
| 201 | +test th1-unset-6 {$RESULT eq {TH_ERROR: no such variable: gv2}} | |
| 202 | + | |
| 203 | +############################################################################### | |
| 204 | + | |
| 205 | +fossil test-th-eval "set gv 1; upvar 0 gv gv2(1); unset gv; unset gv2(1)" | |
| 206 | +test th1-unset-7 {$RESULT eq {TH_ERROR: no such variable: gv2(1)}} | |
| 207 | + | |
| 208 | +############################################################################### | |
| 209 | + | |
| 210 | +fossil test-th-eval "set gv(1) 1; upvar 0 gv(1) gv2; unset gv(1); unset gv2" | |
| 211 | +test th1-unset-8 {$RESULT eq {TH_ERROR: no such variable: gv2}} | |
| 157 | 212 |
| --- test/th1.test | |
| +++ test/th1.test | |
| @@ -140,10 +140,40 @@ | |
| 140 | fossil test-th-eval "catch {bad}; info exists var; set th_stack_trace" |
| 141 | test th1-info-exists-6 {$RESULT eq {bad}} |
| 142 | |
| 143 | ############################################################################### |
| 144 | |
| 145 | fossil test-th-eval "set var 1; unset var" |
| 146 | test th1-unset-1 {$RESULT eq {var}} |
| 147 | |
| 148 | ############################################################################### |
| 149 | |
| @@ -152,5 +182,30 @@ | |
| 152 | |
| 153 | ############################################################################### |
| 154 | |
| 155 | fossil test-th-eval "set var 1; unset var; unset var" |
| 156 | test th1-unset-3 {$RESULT eq {TH_ERROR: no such variable: var}} |
| 157 |
| --- test/th1.test | |
| +++ test/th1.test | |
| @@ -140,10 +140,40 @@ | |
| 140 | fossil test-th-eval "catch {bad}; info exists var; set th_stack_trace" |
| 141 | test th1-info-exists-6 {$RESULT eq {bad}} |
| 142 | |
| 143 | ############################################################################### |
| 144 | |
| 145 | fossil test-th-eval "set var(1) 1; info exists var" |
| 146 | test th1-info-exists-7 {$RESULT eq {1}} |
| 147 | |
| 148 | ############################################################################### |
| 149 | |
| 150 | fossil test-th-eval "set var(1) 1; unset var(1); info exists var" |
| 151 | test th1-info-exists-8 {$RESULT eq {1}} |
| 152 | |
| 153 | ############################################################################### |
| 154 | |
| 155 | fossil test-th-eval "set var(1) 1; unset var; info exists var" |
| 156 | test th1-info-exists-9 {$RESULT eq {0}} |
| 157 | |
| 158 | ############################################################################### |
| 159 | |
| 160 | fossil test-th-eval "set var(1) 1; info exists var(1)" |
| 161 | test th1-info-exists-10 {$RESULT eq {1}} |
| 162 | |
| 163 | ############################################################################### |
| 164 | |
| 165 | fossil test-th-eval "set var(1) 1; unset var(1); info exists var(1)" |
| 166 | test th1-info-exists-11 {$RESULT eq {0}} |
| 167 | |
| 168 | ############################################################################### |
| 169 | |
| 170 | fossil test-th-eval "set var(1) 1; unset var; info exists var(1)" |
| 171 | test th1-info-exists-12 {$RESULT eq {0}} |
| 172 | |
| 173 | ############################################################################### |
| 174 | |
| 175 | fossil test-th-eval "set var 1; unset var" |
| 176 | test th1-unset-1 {$RESULT eq {var}} |
| 177 | |
| 178 | ############################################################################### |
| 179 | |
| @@ -152,5 +182,30 @@ | |
| 182 | |
| 183 | ############################################################################### |
| 184 | |
| 185 | fossil test-th-eval "set var 1; unset var; unset var" |
| 186 | test th1-unset-3 {$RESULT eq {TH_ERROR: no such variable: var}} |
| 187 | |
| 188 | ############################################################################### |
| 189 | |
| 190 | fossil test-th-eval "set gv 1; proc p {} {upvar 1 gv lv; unset lv}; p; unset gv" |
| 191 | test th1-unset-4 {$RESULT eq {TH_ERROR: no such variable: gv}} |
| 192 | |
| 193 | ############################################################################### |
| 194 | |
| 195 | fossil test-th-eval "set gv 1; upvar 0 gv gv2; info exists gv2" |
| 196 | test th1-unset-5 {$RESULT eq {1}} |
| 197 | |
| 198 | ############################################################################### |
| 199 | |
| 200 | fossil test-th-eval "set gv 1; upvar 0 gv gv2; unset gv; unset gv2" |
| 201 | test th1-unset-6 {$RESULT eq {TH_ERROR: no such variable: gv2}} |
| 202 | |
| 203 | ############################################################################### |
| 204 | |
| 205 | fossil test-th-eval "set gv 1; upvar 0 gv gv2(1); unset gv; unset gv2(1)" |
| 206 | test th1-unset-7 {$RESULT eq {TH_ERROR: no such variable: gv2(1)}} |
| 207 | |
| 208 | ############################################################################### |
| 209 | |
| 210 | fossil test-th-eval "set gv(1) 1; upvar 0 gv(1) gv2; unset gv(1); unset gv2" |
| 211 | test th1-unset-8 {$RESULT eq {TH_ERROR: no such variable: gv2}} |
| 212 |