Fossil SCM
Fix several issues with the TH1 unset command, including a memory leak. Add more tests.
Commit
e4047acb76d90c643fd36c6a13377cc938d30179
Parent
e0f22dda7b25cd9…
3 files changed
+100
-34
+1
-1
+35
M
src/th.c
+100
-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 | */ |
| @@ -1055,17 +1063,26 @@ | ||
| 1055 | 1063 | ** |
| 1056 | 1064 | ** If the arrayok argument is false and the named variable is an array, |
| 1057 | 1065 | ** an error is left in the interpreter result and NULL returned. If |
| 1058 | 1066 | ** arrayok is true an array name is Ok. |
| 1059 | 1067 | */ |
| 1068 | +struct Find { | |
| 1069 | + Th_HashEntry *pValueEntry; | |
| 1070 | + Th_HashEntry *pElemEntry; | |
| 1071 | + const char *zElem; | |
| 1072 | + int nElem; | |
| 1073 | +}; | |
| 1074 | +typedef struct Find Find; | |
| 1075 | + | |
| 1060 | 1076 | static Th_Variable *thFindValue( |
| 1061 | 1077 | 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 */ | |
| 1078 | + const char *zVar, /* Pointer to variable name */ | |
| 1079 | + int nVar, /* Number of bytes at nVar */ | |
| 1080 | + int create, /* If true, create the variable if not found */ | |
| 1081 | + int arrayok, /* If true, an array is Ok. Otherwise array==error */ | |
| 1082 | + int noerror, /* If false, set interpreter result to error */ | |
| 1083 | + Find *pFind /* If non-zero, place output here */ | |
| 1067 | 1084 | ){ |
| 1068 | 1085 | const char *zOuter; |
| 1069 | 1086 | int nOuter; |
| 1070 | 1087 | const char *zInner; |
| 1071 | 1088 | int nInner; |
| @@ -1074,16 +1091,23 @@ | ||
| 1074 | 1091 | Th_HashEntry *pEntry; |
| 1075 | 1092 | Th_Frame *pFrame = interp->pFrame; |
| 1076 | 1093 | Th_Variable *pValue; |
| 1077 | 1094 | |
| 1078 | 1095 | thAnalyseVarname(zVar, nVar, &zOuter, &nOuter, &zInner, &nInner, &isGlobal); |
| 1096 | + if( pFind ){ | |
| 1097 | + pFind->zElem = zInner; | |
| 1098 | + pFind->nElem = nInner; | |
| 1099 | + } | |
| 1079 | 1100 | if( isGlobal ){ |
| 1080 | 1101 | while( pFrame->pCaller ) pFrame = pFrame->pCaller; |
| 1081 | 1102 | } |
| 1082 | 1103 | |
| 1083 | 1104 | pEntry = Th_HashFind(interp, pFrame->paVar, zOuter, nOuter, create); |
| 1084 | 1105 | assert(pEntry || create<=0); |
| 1106 | + if( pFind ){ | |
| 1107 | + pFind->pValueEntry = pEntry; | |
| 1108 | + } | |
| 1085 | 1109 | if( !pEntry ){ |
| 1086 | 1110 | goto no_such_var; |
| 1087 | 1111 | } |
| 1088 | 1112 | |
| 1089 | 1113 | pValue = (Th_Variable *)pEntry->pData; |
| @@ -1106,10 +1130,14 @@ | ||
| 1106 | 1130 | goto no_such_var; |
| 1107 | 1131 | } |
| 1108 | 1132 | pValue->pHash = Th_HashNew(interp); |
| 1109 | 1133 | } |
| 1110 | 1134 | pEntry = Th_HashFind(interp, pValue->pHash, zInner, nInner, create); |
| 1135 | + assert(pEntry || create<=0); | |
| 1136 | + if( pFind ){ | |
| 1137 | + pFind->pElemEntry = pEntry; | |
| 1138 | + } | |
| 1111 | 1139 | if( !pEntry ){ |
| 1112 | 1140 | goto no_such_var; |
| 1113 | 1141 | } |
| 1114 | 1142 | pValue = (Th_Variable *)pEntry->pData; |
| 1115 | 1143 | if( !pValue ){ |
| @@ -1145,11 +1173,11 @@ | ||
| 1145 | 1173 | ** an error message in the interpreter result. |
| 1146 | 1174 | */ |
| 1147 | 1175 | int Th_GetVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1148 | 1176 | Th_Variable *pValue; |
| 1149 | 1177 | |
| 1150 | - pValue = thFindValue(interp, zVar, nVar, 0, 0, 0); | |
| 1178 | + pValue = thFindValue(interp, zVar, nVar, 0, 0, 0, 0); | |
| 1151 | 1179 | if( !pValue ){ |
| 1152 | 1180 | return TH_ERROR; |
| 1153 | 1181 | } |
| 1154 | 1182 | if( !pValue->zData ){ |
| 1155 | 1183 | Th_ErrorMessage(interp, "no such variable:", zVar, nVar); |
| @@ -1161,12 +1189,12 @@ | ||
| 1161 | 1189 | |
| 1162 | 1190 | /* |
| 1163 | 1191 | ** Return true if variable (zVar, nVar) exists. |
| 1164 | 1192 | */ |
| 1165 | 1193 | 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; | |
| 1194 | + Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0); | |
| 1195 | + return pValue && (pValue->zData || pValue->pHash); | |
| 1168 | 1196 | } |
| 1169 | 1197 | |
| 1170 | 1198 | /* |
| 1171 | 1199 | ** String (zVar, nVar) must contain the name of a scalar variable or |
| 1172 | 1200 | ** array member. If the variable does not exist it is created. The |
| @@ -1182,11 +1210,11 @@ | ||
| 1182 | 1210 | const char *zValue, |
| 1183 | 1211 | int nValue |
| 1184 | 1212 | ){ |
| 1185 | 1213 | Th_Variable *pValue; |
| 1186 | 1214 | |
| 1187 | - pValue = thFindValue(interp, zVar, nVar, 1, 0, 0); | |
| 1215 | + pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0); | |
| 1188 | 1216 | if( !pValue ){ |
| 1189 | 1217 | return TH_ERROR; |
| 1190 | 1218 | } |
| 1191 | 1219 | |
| 1192 | 1220 | if( nValue<0 ){ |
| @@ -1225,11 +1253,11 @@ | ||
| 1225 | 1253 | if( !pFrame ){ |
| 1226 | 1254 | return TH_ERROR; |
| 1227 | 1255 | } |
| 1228 | 1256 | pSavedFrame = interp->pFrame; |
| 1229 | 1257 | interp->pFrame = pFrame; |
| 1230 | - pValue = thFindValue(interp, zLink, nLink, 1, 1, 0); | |
| 1258 | + pValue = thFindValue(interp, zLink, nLink, 1, 1, 0, 0); | |
| 1231 | 1259 | interp->pFrame = pSavedFrame; |
| 1232 | 1260 | |
| 1233 | 1261 | pEntry = Th_HashFind(interp, interp->pFrame->paVar, zLocal, nLocal, 1); |
| 1234 | 1262 | if( pEntry->pData ){ |
| 1235 | 1263 | Th_ErrorMessage(interp, "variable exists:", zLocal, nLocal); |
| @@ -1246,29 +1274,65 @@ | ||
| 1246 | 1274 | ** an array, or an array member. If the identified variable exists, it |
| 1247 | 1275 | ** is deleted and TH_OK returned. Otherwise, an error message is left |
| 1248 | 1276 | ** in the interpreter result and TH_ERROR is returned. |
| 1249 | 1277 | */ |
| 1250 | 1278 | int Th_UnsetVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1279 | + Find find; | |
| 1251 | 1280 | Th_Variable *pValue; |
| 1281 | + Th_HashEntry *pEntry; | |
| 1282 | + int rc = TH_ERROR; | |
| 1252 | 1283 | |
| 1253 | - pValue = thFindValue(interp, zVar, nVar, 0, 1, 0); | |
| 1284 | + memset(&find, 0, sizeof(Find)); | |
| 1285 | + pValue = thFindValue(interp, zVar, nVar, 0, 1, 0, &find); | |
| 1254 | 1286 | 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; | |
| 1287 | + return rc; | |
| 1288 | + } | |
| 1289 | + | |
| 1290 | + if( pValue->zData || pValue->pHash ){ | |
| 1291 | + rc = TH_OK; | |
| 1292 | + }else { | |
| 1293 | + Th_ErrorMessage(interp, "no such variable:", zVar, nVar); | |
| 1294 | + } | |
| 1295 | + | |
| 1296 | + /* | |
| 1297 | + ** The variable may be shared by more than one frame; therefore, make sure | |
| 1298 | + ** it is actually freed prior to freeing the parent structure. The values | |
| 1299 | + ** for the variable must be freed now so the variable appears undefined in | |
| 1300 | + ** all frames. The hash entry in the current frame must also be deleted | |
| 1301 | + ** now; otherwise, if the current stack frame is later popped, it will try | |
| 1302 | + ** to delete a variable which has already been freed. | |
| 1303 | + */ | |
| 1304 | + if( find.zElem ){ | |
| 1305 | + pEntry = find.pElemEntry; | |
| 1306 | + }else{ | |
| 1307 | + pEntry = find.pValueEntry; | |
| 1308 | + } | |
| 1309 | + assert( pEntry ); | |
| 1310 | + assert( pValue ); | |
| 1311 | + if( thFreeVariable(pEntry, (void *)interp) ){ | |
| 1312 | + if( find.zElem ){ | |
| 1313 | + Th_Variable *pValue2 = find.pValueEntry->pData; | |
| 1314 | + Th_HashFind(interp, pValue2->pHash, find.zElem, find.nElem, -1); | |
| 1315 | + }else{ | |
| 1316 | + Th_Free(interp, pEntry->pData); | |
| 1317 | + pEntry->pData = 0; | |
| 1318 | + } | |
| 1319 | + }else{ | |
| 1320 | + if( pValue->zData ){ | |
| 1321 | + Th_Free(interp, pValue->zData); | |
| 1322 | + pValue->zData = 0; | |
| 1323 | + } | |
| 1324 | + if( pValue->pHash ){ | |
| 1325 | + Th_HashIterate(interp, pValue->pHash, thFreeVariable, (void *)interp); | |
| 1326 | + Th_HashDelete(interp, pValue->pHash); | |
| 1327 | + pValue->pHash = 0; | |
| 1328 | + } | |
| 1329 | + } | |
| 1330 | + if( !find.zElem ){ | |
| 1331 | + thFindValue(interp, zVar, nVar, -1, 1, 1, 0); | |
| 1332 | + } | |
| 1333 | + return rc; | |
| 1270 | 1334 | } |
| 1271 | 1335 | |
| 1272 | 1336 | /* |
| 1273 | 1337 | ** Return an allocated buffer containing a copy of string (z, n). The |
| 1274 | 1338 | ** caller is responsible for eventually calling Th_Free() to free |
| @@ -2211,16 +2275,17 @@ | ||
| 2211 | 2275 | |
| 2212 | 2276 | /* |
| 2213 | 2277 | ** Iterate through all values currently stored in the hash table. Invoke |
| 2214 | 2278 | ** the callback function xCallback for each entry. The second argument |
| 2215 | 2279 | ** passed to xCallback is a copy of the fourth argument passed to this |
| 2216 | -** function. | |
| 2280 | +** function. The return value from the callback function xCallback is | |
| 2281 | +** ignored. | |
| 2217 | 2282 | */ |
| 2218 | 2283 | void Th_HashIterate( |
| 2219 | 2284 | Th_Interp *interp, |
| 2220 | 2285 | Th_Hash *pHash, |
| 2221 | - void (*xCallback)(Th_HashEntry *pEntry, void *pContext), | |
| 2286 | + int (*xCallback)(Th_HashEntry *pEntry, void *pContext), | |
| 2222 | 2287 | void *pContext |
| 2223 | 2288 | ){ |
| 2224 | 2289 | int i; |
| 2225 | 2290 | for(i=0; i<TH_HASHSIZE; i++){ |
| 2226 | 2291 | Th_HashEntry *pEntry; |
| @@ -2231,14 +2296,15 @@ | ||
| 2231 | 2296 | } |
| 2232 | 2297 | } |
| 2233 | 2298 | } |
| 2234 | 2299 | |
| 2235 | 2300 | /* |
| 2236 | -** Helper function for Th_HashDelete(). | |
| 2301 | +** Helper function for Th_HashDelete(). Always returns non-zero. | |
| 2237 | 2302 | */ |
| 2238 | -static void xFreeHashEntry(Th_HashEntry *pEntry, void *pContext){ | |
| 2303 | +static int xFreeHashEntry(Th_HashEntry *pEntry, void *pContext){ | |
| 2239 | 2304 | Th_Free((Th_Interp *)pContext, (void *)pEntry); |
| 2305 | + return 1; | |
| 2240 | 2306 | } |
| 2241 | 2307 | |
| 2242 | 2308 | /* |
| 2243 | 2309 | ** Free a hash-table previously allocated by Th_HashNew(). |
| 2244 | 2310 | */ |
| 2245 | 2311 |
| --- 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 | */ |
| @@ -1055,17 +1063,26 @@ | |
| 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 +1091,23 @@ | |
| 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 +1130,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 +1173,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 +1189,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 +1210,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 +1253,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 +1274,65 @@ | |
| 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 +2275,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 +2296,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 | */ |
| @@ -1055,17 +1063,26 @@ | |
| 1063 | ** |
| 1064 | ** If the arrayok argument is false and the named variable is an array, |
| 1065 | ** an error is left in the interpreter result and NULL returned. If |
| 1066 | ** arrayok is true an array name is Ok. |
| 1067 | */ |
| 1068 | struct Find { |
| 1069 | Th_HashEntry *pValueEntry; |
| 1070 | Th_HashEntry *pElemEntry; |
| 1071 | const char *zElem; |
| 1072 | int nElem; |
| 1073 | }; |
| 1074 | typedef struct Find Find; |
| 1075 | |
| 1076 | static Th_Variable *thFindValue( |
| 1077 | Th_Interp *interp, |
| 1078 | const char *zVar, /* Pointer to variable name */ |
| 1079 | int nVar, /* Number of bytes at nVar */ |
| 1080 | int create, /* If true, create the variable if not found */ |
| 1081 | int arrayok, /* If true, an array is Ok. Otherwise array==error */ |
| 1082 | int noerror, /* If false, set interpreter result to error */ |
| 1083 | Find *pFind /* If non-zero, place output here */ |
| 1084 | ){ |
| 1085 | const char *zOuter; |
| 1086 | int nOuter; |
| 1087 | const char *zInner; |
| 1088 | int nInner; |
| @@ -1074,16 +1091,23 @@ | |
| 1091 | Th_HashEntry *pEntry; |
| 1092 | Th_Frame *pFrame = interp->pFrame; |
| 1093 | Th_Variable *pValue; |
| 1094 | |
| 1095 | thAnalyseVarname(zVar, nVar, &zOuter, &nOuter, &zInner, &nInner, &isGlobal); |
| 1096 | if( pFind ){ |
| 1097 | pFind->zElem = zInner; |
| 1098 | pFind->nElem = nInner; |
| 1099 | } |
| 1100 | if( isGlobal ){ |
| 1101 | while( pFrame->pCaller ) pFrame = pFrame->pCaller; |
| 1102 | } |
| 1103 | |
| 1104 | pEntry = Th_HashFind(interp, pFrame->paVar, zOuter, nOuter, create); |
| 1105 | assert(pEntry || create<=0); |
| 1106 | if( pFind ){ |
| 1107 | pFind->pValueEntry = pEntry; |
| 1108 | } |
| 1109 | if( !pEntry ){ |
| 1110 | goto no_such_var; |
| 1111 | } |
| 1112 | |
| 1113 | pValue = (Th_Variable *)pEntry->pData; |
| @@ -1106,10 +1130,14 @@ | |
| 1130 | goto no_such_var; |
| 1131 | } |
| 1132 | pValue->pHash = Th_HashNew(interp); |
| 1133 | } |
| 1134 | pEntry = Th_HashFind(interp, pValue->pHash, zInner, nInner, create); |
| 1135 | assert(pEntry || create<=0); |
| 1136 | if( pFind ){ |
| 1137 | pFind->pElemEntry = pEntry; |
| 1138 | } |
| 1139 | if( !pEntry ){ |
| 1140 | goto no_such_var; |
| 1141 | } |
| 1142 | pValue = (Th_Variable *)pEntry->pData; |
| 1143 | if( !pValue ){ |
| @@ -1145,11 +1173,11 @@ | |
| 1173 | ** an error message in the interpreter result. |
| 1174 | */ |
| 1175 | int Th_GetVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1176 | Th_Variable *pValue; |
| 1177 | |
| 1178 | pValue = thFindValue(interp, zVar, nVar, 0, 0, 0, 0); |
| 1179 | if( !pValue ){ |
| 1180 | return TH_ERROR; |
| 1181 | } |
| 1182 | if( !pValue->zData ){ |
| 1183 | Th_ErrorMessage(interp, "no such variable:", zVar, nVar); |
| @@ -1161,12 +1189,12 @@ | |
| 1189 | |
| 1190 | /* |
| 1191 | ** Return true if variable (zVar, nVar) exists. |
| 1192 | */ |
| 1193 | int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1194 | Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0); |
| 1195 | return pValue && (pValue->zData || pValue->pHash); |
| 1196 | } |
| 1197 | |
| 1198 | /* |
| 1199 | ** String (zVar, nVar) must contain the name of a scalar variable or |
| 1200 | ** array member. If the variable does not exist it is created. The |
| @@ -1182,11 +1210,11 @@ | |
| 1210 | const char *zValue, |
| 1211 | int nValue |
| 1212 | ){ |
| 1213 | Th_Variable *pValue; |
| 1214 | |
| 1215 | pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0); |
| 1216 | if( !pValue ){ |
| 1217 | return TH_ERROR; |
| 1218 | } |
| 1219 | |
| 1220 | if( nValue<0 ){ |
| @@ -1225,11 +1253,11 @@ | |
| 1253 | if( !pFrame ){ |
| 1254 | return TH_ERROR; |
| 1255 | } |
| 1256 | pSavedFrame = interp->pFrame; |
| 1257 | interp->pFrame = pFrame; |
| 1258 | pValue = thFindValue(interp, zLink, nLink, 1, 1, 0, 0); |
| 1259 | interp->pFrame = pSavedFrame; |
| 1260 | |
| 1261 | pEntry = Th_HashFind(interp, interp->pFrame->paVar, zLocal, nLocal, 1); |
| 1262 | if( pEntry->pData ){ |
| 1263 | Th_ErrorMessage(interp, "variable exists:", zLocal, nLocal); |
| @@ -1246,29 +1274,65 @@ | |
| 1274 | ** an array, or an array member. If the identified variable exists, it |
| 1275 | ** is deleted and TH_OK returned. Otherwise, an error message is left |
| 1276 | ** in the interpreter result and TH_ERROR is returned. |
| 1277 | */ |
| 1278 | int Th_UnsetVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1279 | Find find; |
| 1280 | Th_Variable *pValue; |
| 1281 | Th_HashEntry *pEntry; |
| 1282 | int rc = TH_ERROR; |
| 1283 | |
| 1284 | memset(&find, 0, sizeof(Find)); |
| 1285 | pValue = thFindValue(interp, zVar, nVar, 0, 1, 0, &find); |
| 1286 | if( !pValue ){ |
| 1287 | return rc; |
| 1288 | } |
| 1289 | |
| 1290 | if( pValue->zData || pValue->pHash ){ |
| 1291 | rc = TH_OK; |
| 1292 | }else { |
| 1293 | Th_ErrorMessage(interp, "no such variable:", zVar, nVar); |
| 1294 | } |
| 1295 | |
| 1296 | /* |
| 1297 | ** The variable may be shared by more than one frame; therefore, make sure |
| 1298 | ** it is actually freed prior to freeing the parent structure. The values |
| 1299 | ** for the variable must be freed now so the variable appears undefined in |
| 1300 | ** all frames. The hash entry in the current frame must also be deleted |
| 1301 | ** now; otherwise, if the current stack frame is later popped, it will try |
| 1302 | ** to delete a variable which has already been freed. |
| 1303 | */ |
| 1304 | if( find.zElem ){ |
| 1305 | pEntry = find.pElemEntry; |
| 1306 | }else{ |
| 1307 | pEntry = find.pValueEntry; |
| 1308 | } |
| 1309 | assert( pEntry ); |
| 1310 | assert( pValue ); |
| 1311 | if( thFreeVariable(pEntry, (void *)interp) ){ |
| 1312 | if( find.zElem ){ |
| 1313 | Th_Variable *pValue2 = find.pValueEntry->pData; |
| 1314 | Th_HashFind(interp, pValue2->pHash, find.zElem, find.nElem, -1); |
| 1315 | }else{ |
| 1316 | Th_Free(interp, pEntry->pData); |
| 1317 | pEntry->pData = 0; |
| 1318 | } |
| 1319 | }else{ |
| 1320 | if( pValue->zData ){ |
| 1321 | Th_Free(interp, pValue->zData); |
| 1322 | pValue->zData = 0; |
| 1323 | } |
| 1324 | if( pValue->pHash ){ |
| 1325 | Th_HashIterate(interp, pValue->pHash, thFreeVariable, (void *)interp); |
| 1326 | Th_HashDelete(interp, pValue->pHash); |
| 1327 | pValue->pHash = 0; |
| 1328 | } |
| 1329 | } |
| 1330 | if( !find.zElem ){ |
| 1331 | thFindValue(interp, zVar, nVar, -1, 1, 1, 0); |
| 1332 | } |
| 1333 | return rc; |
| 1334 | } |
| 1335 | |
| 1336 | /* |
| 1337 | ** Return an allocated buffer containing a copy of string (z, n). The |
| 1338 | ** caller is responsible for eventually calling Th_Free() to free |
| @@ -2211,16 +2275,17 @@ | |
| 2275 | |
| 2276 | /* |
| 2277 | ** Iterate through all values currently stored in the hash table. Invoke |
| 2278 | ** the callback function xCallback for each entry. The second argument |
| 2279 | ** passed to xCallback is a copy of the fourth argument passed to this |
| 2280 | ** function. The return value from the callback function xCallback is |
| 2281 | ** ignored. |
| 2282 | */ |
| 2283 | void Th_HashIterate( |
| 2284 | Th_Interp *interp, |
| 2285 | Th_Hash *pHash, |
| 2286 | int (*xCallback)(Th_HashEntry *pEntry, void *pContext), |
| 2287 | void *pContext |
| 2288 | ){ |
| 2289 | int i; |
| 2290 | for(i=0; i<TH_HASHSIZE; i++){ |
| 2291 | Th_HashEntry *pEntry; |
| @@ -2231,14 +2296,15 @@ | |
| 2296 | } |
| 2297 | } |
| 2298 | } |
| 2299 | |
| 2300 | /* |
| 2301 | ** Helper function for Th_HashDelete(). Always returns non-zero. |
| 2302 | */ |
| 2303 | static int xFreeHashEntry(Th_HashEntry *pEntry, void *pContext){ |
| 2304 | Th_Free((Th_Interp *)pContext, (void *)pEntry); |
| 2305 | return 1; |
| 2306 | } |
| 2307 | |
| 2308 | /* |
| 2309 | ** Free a hash-table previously allocated by Th_HashNew(). |
| 2310 | */ |
| 2311 |
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 |
+35
| --- 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,10 @@ | ||
| 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}} | |
| 157 | 192 |
| --- 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,10 @@ | |
| 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,10 @@ | |
| 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 |