1 #include <string.h>
2 #include <ctype.h>
3 #include <new>
4 #include <mk4.h>
5 
6 #include "jim.h"
7 #include "jimautoconf.h"
8 #include "jim-subcmd.h"
9 
10 extern "C" { /* The whole file is essentially C */
11 
12 #define MK_PROPERTY_BINARY  'B'
13 #define MK_PROPERTY_INT     'I'
14 #define MK_PROPERTY_LONG    'L'
15 #define MK_PROPERTY_FLOAT   'F'
16 #define MK_PROPERTY_DOUBLE  'D'
17 #define MK_PROPERTY_STRING  'S'
18 #define MK_PROPERTY_VIEW    'V'
19 
20 #define MK_MODE_ORIGINAL    -1
21 #define MK_MODE_READONLY    0
22 #define MK_MODE_READWRITE   1
23 #define MK_MODE_EXTEND      2
24 
25 #define MK_CMD_LEN 32
26 #define JIM_CURSOR_SPACE (35+JIM_REFERENCE_TAGLEN + 1 + 20)
27 #define JIM_POSITION_SPACE 32
28 #define MK_VERSION_SPACE 16
29 #define JIM_MK_DESCR_LEN 64 /* Default, will be reallocated if needed */
30 
31 #define isnamech(c) ( (c) && !strchr(":,[^]!", (c)) )
32 
33 #ifndef max
34 #define max(x, y) ((x) >= (y) ? (x) : (y))
35 #endif
36 
37 /* utilities */
38 static int JimCheckMkName(Jim_Interp *interp, Jim_Obj *name, const char *type);
39 static const char *JimMkTypeName(char type);
40 static Jim_Obj *JimFromMkDescription(Jim_Interp *interp, const char *descr, const char **endPtr);
41 static int JimToMkDescription(Jim_Interp *interp, Jim_Obj *obj, char **descrPtr);
42 static Jim_Obj *JimGetMkValue(Jim_Interp *interp, c4_Cursor cur, const c4_Property &prop);
43 static int JimSetMkValue(Jim_Interp *interp, c4_Cursor cur, const c4_Property &prop, Jim_Obj *obj);
44 
45 static int JimPipelineBoundary(int argc, Jim_Obj *const *argv);
46 
47 /* property object */
48 static Jim_Obj *JimNewPropertyObj (Jim_Interp *interp, c4_Property prop);
49 static int JimGetProperty (Jim_Interp *interp, Jim_Obj *obj,
50     c4_View view, const char *what, const c4_Property **propPtr);
51 static int JimGetPropertyTyped (Jim_Interp *interp, Jim_Obj *obj,
52     char type, const c4_Property **propPtr);
53 static int JimGetNewProperty (Jim_Interp *interp, Jim_Obj *obj,
54     c4_View view, char type, const c4_Property **propPtr);
55 static int JimGetProperties (Jim_Interp *interp, int objc, Jim_Obj *const *objv,
56     c4_View view, c4_View *propsPtr);
57 static Jim_Obj *JimViewPropertiesList (Jim_Interp *interp, c4_View view);
58 
59 /* cursor object */
60 static int JimGetPosition (Jim_Interp *interp, Jim_Obj *obj, c4_View view, int *indexPtr);
61 static int JimGetCursor (Jim_Interp *interp, Jim_Obj *obj, c4_Cursor *curPtr);
62 static int JimGetCursorView (Jim_Interp *interp, Jim_Obj *obj,
63     Jim_Obj **viewObjPtr);
64 static int JimCursorPos (Jim_Interp *interp, Jim_Obj *obj, Jim_Obj **posObjPtr);
65 static int JimIncrCursor (Jim_Interp *interp, Jim_Obj *obj, int offset);
66 static int JimSeekCursor (Jim_Interp *interp, Jim_Obj *obj, Jim_Obj *posObj);
67 
68 /* Also accepts JIM_ERRMSG */
69 #define JIM_CURSOR_GET      (1 << JIM_PRIV_FLAG_SHIFT)
70 #define JIM_CURSOR_SET      (2 << JIM_PRIV_FLAG_SHIFT)
71 #define JIM_CURSOR_INSERT   (4 << JIM_PRIV_FLAG_SHIFT)
72 
73 static int JimCheckCursor (Jim_Interp *interp, Jim_Obj *curObj, int flags);
74 
75 /* view handle */
76 static Jim_Obj *JimNewViewObj (Jim_Interp *interp, c4_View view);
77 static int JimGetView (Jim_Interp *interp, Jim_Obj *obj, c4_View *viewPtr);
78 static void JimPinView (Jim_Interp *interp, Jim_Obj *obj);
79 
80 /* -------------------------------------------------------------------------
81  * Utilities
82  * ------------------------------------------------------------------------- */
83 
JimCheckMkName(Jim_Interp * interp,Jim_Obj * name,const char * type)84 static int JimCheckMkName(Jim_Interp *interp, Jim_Obj *name, const char *type)
85 {
86     const char *s;
87     int i, len;
88 
89     s = Jim_GetString(name, &len);
90 
91     if (len > 0 && s[0] == '-')
92         goto err;
93     for (i = 0; i < len; i++) {
94         if (!isnamech(s[i]))
95             goto err;
96     }
97 
98     return JIM_OK;
99 
100   err:
101     Jim_SetResultFormatted(interp, "expected %s name but got \"%#s\"", type ? type : "property", name);
102     return JIM_ERR;
103 }
104 
105 static const char *const jim_mktype_options[] = {
106     "-integer",
107     "-long",
108     "-float",
109     "-double",
110     "-string",
111     "-subview",
112     /* FIXME "-binary", */
113     0
114 };
115 
116 static const char *const jim_mktype_names[] = {
117     "integer",
118     "long",
119     "float",
120     "double",
121     "string",
122     "subview",
123     /* FIXME "binary", */
124     0
125 };
126 
127 static const char jim_mktype_types[] = {
128     MK_PROPERTY_INT,
129     MK_PROPERTY_LONG,
130     MK_PROPERTY_FLOAT,
131     MK_PROPERTY_DOUBLE,
132     MK_PROPERTY_STRING,
133     MK_PROPERTY_VIEW,
134     /* MK_PROPERTY_BINARY, */
135 };
136 
137 #define JIM_MKTYPES ((int)(sizeof(jim_mktype_types) / sizeof(jim_mktype_types[0])))
138 
JimMkTypeName(char type)139 static const char *JimMkTypeName(char type)
140 {
141     int i;
142 
143     for (i = 0; i < JIM_MKTYPES; i++) {
144         if (type == jim_mktype_types[i])
145             return jim_mktype_names[i];
146     }
147     return "(unknown type)";
148 }
149 
JimFromMkDescription(Jim_Interp * interp,const char * descr,const char ** endPtr)150 static Jim_Obj *JimFromMkDescription(Jim_Interp *interp, const char *descr, const char **endPtr)
151 {
152     Jim_Obj *result;
153     const char *delim;
154 
155     result = Jim_NewListObj(interp, NULL, 0);
156     for (;;) {
157         if (*descr == ']') {
158             descr++;
159             break;
160         }
161         else if (*descr == '\0')
162             break;
163         else if (*descr == ',')
164             descr++;
165 
166         delim = strpbrk(descr, ",:[]");
167         /* JimPanic((!delim, "Invalid Metakit description string")); */
168 
169         Jim_ListAppendElement(interp, result,
170             Jim_NewStringObj(interp, descr, delim - descr));
171 
172         if (delim[0] == '[') {
173             Jim_ListAppendElement(interp, result,
174                 JimFromMkDescription(interp, delim + 1, &descr));
175         }
176         else if (delim[0] == ':') {
177             Jim_ListAppendElement(interp, result,
178                 Jim_NewStringObj(interp, JimMkTypeName(delim[1]), -1));
179             descr = delim + 2;
180         }
181         else {
182             /* Seems that Metakit never generates descriptions without type
183              * tags, but let's handle this just to be safe
184              */
185 
186             Jim_ListAppendElement(interp, result,
187                 Jim_NewStringObj(interp, JimMkTypeName(MK_PROPERTY_STRING), -1));
188         }
189     }
190 
191     if (endPtr)
192         *endPtr = descr;
193     return result;
194 }
195 
196 /* This allocates the buffer once per user call and stores it in a static
197  * variable. Recursive calls are distinguished by descrPtr == NULL.
198  */
JimToMkDescription(Jim_Interp * interp,Jim_Obj * descrObj,char ** descrPtr)199 static int JimToMkDescription(Jim_Interp *interp, Jim_Obj *descrObj, char **descrPtr)
200 {
201     static char *descr, *outPtr;
202     static int bufSize;
203 
204     #define ENLARGE(size) do {                                   \
205         if ((descr - outPtr) + (size) > bufSize) {               \
206             bufSize = max(2*bufSize, (descr - outPtr) + (size)); \
207             descr = (char *)Jim_Realloc(descr, bufSize);         \
208         }                                                        \
209     } while(0)
210 
211     int i, count;
212     Jim_Obj *name, *struc;
213 
214     const char *rep;
215     int len;
216 
217     count = Jim_ListLength(interp, descrObj);
218     if (count % 2) {
219         Jim_SetResultString(interp,
220             "view description must have an even number of elements", -1);
221         return JIM_ERR;
222     }
223 
224     if (descrPtr) {
225         descr = (char *)Jim_Alloc(bufSize = JIM_MK_DESCR_LEN);
226         outPtr = descr;
227     }
228 
229     for (i = 0; i < count; i += 2) {
230         Jim_ListIndex(interp, descrObj, i, &name, 0);
231         Jim_ListIndex(interp, descrObj, i + 1, &struc, 0);
232 
233         if (JimCheckMkName(interp, name, NULL) != JIM_OK)
234             goto err;
235 
236         rep = Jim_GetString(name, &len);
237         ENLARGE(len + 3); /* At least :T, or [], */
238         memcpy(outPtr, rep, len);
239         outPtr += len;
240 
241         if (Jim_ListLength(interp, struc) == 1) {
242             int idx;
243 
244             if (Jim_GetEnum(interp, struc, jim_mktype_names, &idx,
245                     "property type", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK)
246                 goto err;
247 
248             *outPtr++ = ':';
249             *outPtr++ = jim_mktype_types[idx];
250         }
251         else {
252             *outPtr++ = '[';
253 
254             if (JimToMkDescription(interp, struc, NULL) != JIM_OK)
255                 goto err;
256 
257             ENLARGE(2); /* bracket, comma */
258             *outPtr++ = ']';
259         }
260 
261         *outPtr++ = ',';
262     }
263     *(--outPtr) = '\0';
264 
265     #undef ENLARGE
266 
267     if (descrPtr) {
268         *descrPtr = (char *)Jim_Realloc(descr, strlen(descr) + 1);
269         descr = NULL; /* Safety measure */
270     }
271 
272     return JIM_OK;
273 
274   err:
275 
276     if (descrPtr)
277         Jim_Free(descr);
278 
279     return JIM_ERR;
280 }
281 
JimGetMkValue(Jim_Interp * interp,c4_Cursor cur,const c4_Property & prop)282 static Jim_Obj *JimGetMkValue(Jim_Interp *interp, c4_Cursor cur, const c4_Property &prop)
283 {
284     switch (prop.Type()) {
285         case MK_PROPERTY_INT:
286             return Jim_NewIntObj(interp, ((c4_IntProp &)prop).Get(*cur));
287         case MK_PROPERTY_LONG:
288             return Jim_NewIntObj(interp, ((c4_LongProp &)prop).Get(*cur));
289         case MK_PROPERTY_FLOAT:
290             return Jim_NewDoubleObj(interp, ((c4_FloatProp &)prop).Get(*cur));
291         case MK_PROPERTY_DOUBLE:
292             return Jim_NewDoubleObj(interp, ((c4_DoubleProp &)prop).Get(*cur));
293         case MK_PROPERTY_STRING:
294             return Jim_NewStringObj(interp, ((c4_StringProp &)prop).Get(*cur), -1);
295         case MK_PROPERTY_VIEW:
296             return JimNewViewObj(interp, ((c4_ViewProp &)prop).Get(*cur));
297 
298         case MK_PROPERTY_BINARY:
299             /* FIXME */
300         default:
301             /* FIXME Something more meaningful here? */
302             return Jim_NewEmptyStringObj(interp);
303     }
304 }
305 
JimSetMkValue(Jim_Interp * interp,c4_Cursor cur,const c4_Property & prop,Jim_Obj * obj)306 static int JimSetMkValue(Jim_Interp *interp, c4_Cursor cur, const c4_Property &prop, Jim_Obj *obj)
307 {
308     switch (prop.Type()) {
309         case MK_PROPERTY_INT: {
310             jim_wide value;
311 
312             if (Jim_GetWide(interp, obj, &value) != JIM_OK)
313                 return JIM_ERR;
314 
315             ((c4_IntProp &)prop).Set(*cur, value);
316             return JIM_OK;
317         }
318         case MK_PROPERTY_LONG: {
319             jim_wide value;
320 
321             if (Jim_GetWide(interp, obj, &value) != JIM_OK)
322                 return JIM_ERR;
323 
324             ((c4_LongProp &)prop).Set(*cur, value);
325             return JIM_OK;
326         }
327         case MK_PROPERTY_FLOAT: {
328             double value;
329 
330             if (Jim_GetDouble(interp, obj, &value) != JIM_OK)
331                 return JIM_ERR;
332 
333             ((c4_FloatProp &)prop).Set(*cur, value);
334             return JIM_OK;
335         }
336         case MK_PROPERTY_DOUBLE: {
337             double value;
338 
339             if (Jim_GetDouble(interp, obj, &value) != JIM_OK)
340                 return JIM_ERR;
341 
342             ((c4_DoubleProp &)prop).Set(*cur, value);
343             return JIM_OK;
344         }
345         case MK_PROPERTY_STRING: {
346             int len;
347             const char *rep;
348 
349             rep = Jim_GetString(obj, &len);
350             if (len != (int)strlen(rep)) {
351                 Jim_SetResultString(interp, "null characters are not allowed in Metakit strings", -1);
352                 return JIM_ERR;
353             }
354 
355             ((c4_StringProp &)prop).Set(*cur, rep);
356             return JIM_OK;
357         }
358         case MK_PROPERTY_VIEW: {
359             c4_View value;
360 
361             if (JimGetView(interp, obj, &value) != JIM_OK)
362                 return JIM_ERR;
363 
364             ((c4_ViewProp &)prop).Set(*cur, value);
365         }
366         case MK_PROPERTY_BINARY:
367             /* FIXME */
368         default:
369             Jim_SetResultString(interp, "unsupported Metakit type", -1);
370             return JIM_ERR;
371     }
372 }
373 
JimPipelineBoundary(int argc,Jim_Obj * const * argv)374 static int JimPipelineBoundary(int argc, Jim_Obj *const *argv) {
375     const char *rep;
376     int pipe, len;
377 
378     for (pipe = 0; pipe < argc; pipe++) {
379         rep = Jim_GetString(argv[pipe], &len);
380         if (len == 1 && rep[0] == '|')
381             break;
382     }
383     return pipe;
384 }
385 
386 /* -------------------------------------------------------------------------
387  * Property object
388  * ------------------------------------------------------------------------- */
389 
390 #define JimPropertyValue(o) ((c4_Property *)((o)->internalRep.ptr))
391 
FreePropertyInternalRep(Jim_Interp * interp,Jim_Obj * obj)392 static void FreePropertyInternalRep(Jim_Interp *interp, Jim_Obj *obj)
393 {
394     delete JimPropertyValue(obj);
395 }
396 
DupPropertyInternalRep(Jim_Interp * interp,Jim_Obj * oldObj,Jim_Obj * newObj)397 static void DupPropertyInternalRep(Jim_Interp *interp, Jim_Obj *oldObj, Jim_Obj *newObj)
398 {
399     newObj->internalRep.ptr = new c4_Property(*JimPropertyValue(oldObj));
400     newObj->typePtr = oldObj->typePtr;
401 }
402 
UpdateStringOfProperty(Jim_Obj * obj)403 static void UpdateStringOfProperty(Jim_Obj* obj)
404 {
405     const char *name = JimPropertyValue(obj)->Name();
406     int len = strlen(name);
407 
408     obj->bytes = (char *) Jim_Alloc(len + 1);
409     memcpy(obj->bytes, name, len + 1);
410     obj->length = len;
411 }
412 
413 static Jim_ObjType propertyObjType = {
414     "mk.property",
415     FreePropertyInternalRep,
416     DupPropertyInternalRep,
417     UpdateStringOfProperty,
418     JIM_TYPE_NONE
419 };
420 
JimGetProperty(Jim_Interp * interp,Jim_Obj * obj,c4_View view,const char * name,const c4_Property ** propPtr)421 static int JimGetProperty(Jim_Interp *interp, Jim_Obj *obj, c4_View view, const char *name, const c4_Property **propPtr)
422 {
423     int index;
424 
425     if (obj->typePtr == &propertyObjType) {
426         index = view.FindProperty(JimPropertyValue(obj)->GetId());
427     }
428     else {
429         if (JimCheckMkName(interp, obj, name) != JIM_OK)
430             return JIM_ERR;
431         index = view.FindPropIndexByName(Jim_String(obj));
432     }
433 
434     if (index != -1) {
435         *propPtr = &view.NthProperty(index);
436         return JIM_OK;
437     }
438     else {
439         Jim_SetResultFormatted(interp, "%s \"%#s\" does not exist",
440             name ? name : "property", obj);
441         return JIM_ERR;
442     }
443 }
444 
JimGetPropertyTyped(Jim_Interp * interp,Jim_Obj * obj,char type,const c4_Property ** propPtr)445 static int JimGetPropertyTyped(Jim_Interp *interp, Jim_Obj *obj, char type, const c4_Property **propPtr)
446 {
447     c4_Property *prop;
448 
449     if (obj->typePtr == &propertyObjType) {
450         if (JimPropertyValue(obj)->Type() != type) {
451             /* coerce the property type */
452 
453             prop = new c4_Property(type, JimPropertyValue(obj)->Name());
454             delete JimPropertyValue(obj);
455             obj->internalRep.ptr = prop;
456         }
457     }
458     else {
459         if (JimCheckMkName(interp, obj, NULL) != JIM_OK)
460             return JIM_ERR;
461 
462         prop = new c4_Property(type, Jim_String(obj));
463 
464         Jim_FreeIntRep(interp, obj);
465         obj->typePtr = &propertyObjType;
466         obj->internalRep.ptr = (void *)prop;
467     }
468 
469     *propPtr = JimPropertyValue(obj);
470     return JIM_OK;
471 }
472 
JimGetNewProperty(Jim_Interp * interp,Jim_Obj * obj,c4_View view,char type,const c4_Property ** propPtr)473 static int JimGetNewProperty(Jim_Interp *interp, Jim_Obj *obj, c4_View view, char type, const c4_Property **propPtr)
474 {
475     const c4_Property *newp, *prop;
476 
477     if (JimGetPropertyTyped(interp, obj, type, &newp) != JIM_OK)
478         return JIM_ERR;
479 
480     prop = &view.NthProperty(view.AddProperty(*newp));
481 
482     if (prop->Type() != newp->Type()) {
483         Jim_SetResultFormatted(interp, "property \"%#s\" is %s, not %s",
484             obj, JimMkTypeName(prop->Type()), JimMkTypeName(newp->Type()));
485         return JIM_ERR;
486     }
487 
488     *propPtr = prop;
489     return JIM_OK;
490 }
491 
JimGetProperties(Jim_Interp * interp,int objc,Jim_Obj * const * objv,c4_View view,c4_View * propsPtr)492 static int JimGetProperties(Jim_Interp *interp, int objc, Jim_Obj *const *objv, c4_View view, c4_View *propsPtr)
493 {
494     int i;
495     const c4_Property *prop;
496     c4_View props;
497 
498     for (i = 0; i < objc; i++) {
499         if (JimGetProperty(interp, objv[i], view, NULL, &prop) != JIM_OK)
500             return JIM_ERR;
501 
502         props.AddProperty(*prop);
503     }
504 
505     *propsPtr = props;
506     return JIM_OK;
507 }
508 
JimNewPropertyObj(Jim_Interp * interp,c4_Property prop)509 static Jim_Obj *JimNewPropertyObj(Jim_Interp *interp, c4_Property prop)
510 {
511     Jim_Obj *obj;
512 
513     obj = Jim_NewObj(interp);
514     obj->typePtr = &propertyObjType;
515     obj->bytes = NULL;
516     obj->internalRep.ptr = new c4_Property(prop);
517     return obj;
518 }
519 
520 /* -------------------------------------------------------------------------
521  * Cursor object
522  * ------------------------------------------------------------------------- */
523 
524 /* Position ---------------------------------------------------------------- */
525 
526 /* A normal position if endFlag == 0; otherwise an offset from end+1 (!) */
527 typedef struct MkPosition {
528     int index;
529     int endFlag;
530 } MkPosition;
531 
532 /* This is mostly the same as SetIndexFromAny, but preserves more information
533  * and allows multiple [+-]integer parts.
534  */
GetPosition(Jim_Interp * interp,Jim_Obj * obj,MkPosition * posPtr)535 static int GetPosition(Jim_Interp *interp, Jim_Obj *obj, MkPosition *posPtr)
536 {
537     MkPosition pos;
538     const char *rep;
539     char *end;
540     int sign, offset;
541 
542     rep = Jim_String(obj);
543 
544     if (strncmp(rep, "end", 3) == 0) {
545         pos.endFlag = 1;
546         pos.index = -1;
547 
548         rep += 3;
549     }
550     else {
551         pos.endFlag = 0;
552         pos.index = strtol(rep, &end, 10);
553         if (end == rep)
554             goto err;
555 
556         rep = end;
557     }
558 
559     while ((rep[0] == '+') || (rep[0] == '-')) {
560         sign = (rep[0] == '+' ? 1 : -1);
561         rep++;
562 
563         offset = strtol(rep, &end, 10);
564         if (end == rep)
565             goto err;
566 
567         pos.index += sign * offset;
568         rep = end;
569     }
570 
571     while (isspace(UCHAR(*rep)))
572         rep++;
573     if (*rep != '\0')
574         goto err;
575 
576     *posPtr = pos;
577     return JIM_OK;
578 
579   err:
580     Jim_SetResultFormatted(interp, "expected cursor position but got \"%#s\"", obj);
581     return JIM_ERR;
582 }
583 
PositionIndex(const MkPosition * posPtr,c4_View view)584 static int PositionIndex(const MkPosition *posPtr, c4_View view)
585 {
586     if (posPtr->endFlag)
587         return view.GetSize() + posPtr->index;
588     else
589         return posPtr->index;
590 }
591 
JimGetPosition(Jim_Interp * interp,Jim_Obj * obj,c4_View view,int * indexPtr)592 static int JimGetPosition(Jim_Interp *interp, Jim_Obj *obj, c4_View view, int *indexPtr)
593 {
594     MkPosition pos;
595 
596     if (GetPosition(interp, obj, &pos) != JIM_OK)
597         return JIM_ERR;
598 
599     *indexPtr = PositionIndex(&pos, view);
600     return JIM_OK;
601 }
602 
603 /* Cursor type ------------------------------------------------------------- */
604 
605 typedef struct MkCursor {
606     MkPosition pos;
607     Jim_Obj *viewObj;
608 } MkCursor;
609 
610 #define JimCursorValue(obj) ((MkCursor *)(obj->internalRep.ptr))
FreeCursorInternalRep(Jim_Interp * interp,Jim_Obj * obj)611 static void FreeCursorInternalRep(Jim_Interp *interp, Jim_Obj *obj)
612 {
613     Jim_DecrRefCount(interp, JimCursorValue(obj)->viewObj);
614     Jim_Free(obj->internalRep.ptr);
615 }
616 
DupCursorInternalRep(Jim_Interp * interp,Jim_Obj * oldObj,Jim_Obj * newObj)617 static void DupCursorInternalRep(Jim_Interp *interp, Jim_Obj *oldObj, Jim_Obj *newObj)
618 {
619     newObj->internalRep.ptr = Jim_Alloc(sizeof(MkCursor));
620     *JimCursorValue(newObj) = *JimCursorValue(oldObj);
621     Jim_IncrRefCount(JimCursorValue(oldObj)->viewObj);
622 
623     newObj->typePtr = oldObj->typePtr;
624 }
625 
UpdateStringOfCursor(Jim_Obj * obj)626 static void UpdateStringOfCursor(Jim_Obj *obj)
627 {
628     char buf[JIM_CURSOR_SPACE + 1];
629     MkCursor *curPtr = JimCursorValue(obj);
630     int idx, len;
631 
632     len = snprintf(buf, JIM_CURSOR_SPACE + 1, "%s!", Jim_String(curPtr->viewObj));
633 
634     if (curPtr->pos.endFlag) {
635         idx = curPtr->pos.index + 1;
636         if (idx == 0)
637             len += snprintf(buf + len, JIM_CURSOR_SPACE + 1 - len, "end");
638         else
639             len += snprintf(buf + len, JIM_CURSOR_SPACE + 1 - len, "end%+d", idx);
640     }
641     else {
642         len += snprintf(buf + len, JIM_CURSOR_SPACE + 1 - len, "%d",
643             curPtr->pos.index);
644     }
645 
646     obj->bytes = (char *)Jim_Alloc(len + 1);
647     memcpy(obj->bytes, buf, len + 1);
648     obj->length = len;
649 }
650 
651 static Jim_ObjType cursorObjType = {
652     "mk.cursor",
653     FreeCursorInternalRep,
654     DupCursorInternalRep,
655     UpdateStringOfCursor,
656     JIM_TYPE_REFERENCES
657 };
658 
SetCursorFromAny(Jim_Interp * interp,Jim_Obj * obj)659 static int SetCursorFromAny(Jim_Interp *interp, Jim_Obj *obj)
660 {
661     const char *rep, *delim;
662     int len;
663     Jim_Obj *posObj;
664     MkCursor cur;
665 
666     rep = Jim_GetString(obj, &len);
667     delim = strrchr(rep, '!');
668 
669     if (!delim) {
670         Jim_SetResultFormatted(interp, "expected cursor but got \"%#s\"", obj);
671         return JIM_ERR;
672     }
673 
674     cur.viewObj = Jim_NewStringObj(interp, rep, delim - rep);
675     posObj = Jim_NewStringObj(interp, delim + 1, len - (delim - rep) - 1);
676 
677     if (GetPosition(interp, posObj, &cur.pos) != JIM_OK) {
678         Jim_FreeNewObj(interp, posObj);
679         Jim_FreeNewObj(interp, cur.viewObj);
680         return JIM_ERR;
681     }
682 
683     Jim_FreeIntRep(interp, obj);
684     Jim_FreeNewObj(interp, posObj);
685     Jim_IncrRefCount(cur.viewObj);
686 
687     obj->typePtr = &cursorObjType;
688     obj->internalRep.ptr = Jim_Alloc(sizeof(MkCursor));
689     *JimCursorValue(obj) = cur;
690 
691     return JIM_OK;
692 }
693 
694 /* Functions --------------------------------------------------------------- */
695 
JimCursorPos(Jim_Interp * interp,Jim_Obj * obj,Jim_Obj ** posObjPtr)696 static int JimCursorPos(Jim_Interp *interp, Jim_Obj *obj, Jim_Obj **posObjPtr)
697 {
698     if (obj->typePtr != &cursorObjType && SetCursorFromAny(interp, obj) != JIM_OK)
699         return JIM_ERR;
700 
701     *posObjPtr = Jim_NewStringObj(interp, strrchr(Jim_String(obj), '!') + 1, -1);
702     return JIM_OK;
703 }
704 
JimGetCursorView(Jim_Interp * interp,Jim_Obj * obj,Jim_Obj ** viewObjPtr)705 static int JimGetCursorView(Jim_Interp *interp, Jim_Obj *obj, Jim_Obj **viewObjPtr)
706 {
707     if (obj->typePtr != &cursorObjType && SetCursorFromAny(interp, obj) != JIM_OK)
708         return JIM_ERR;
709 
710     *viewObjPtr = JimCursorValue(obj)->viewObj;
711     return JIM_OK;
712 }
713 
JimGetCursor(Jim_Interp * interp,Jim_Obj * obj,c4_Cursor * curPtr)714 static int JimGetCursor(Jim_Interp *interp, Jim_Obj *obj, c4_Cursor *curPtr)
715 {
716     c4_View view;
717 
718     if (obj->typePtr != &cursorObjType && SetCursorFromAny(interp, obj) != JIM_OK)
719         return JIM_ERR;
720     if (JimGetView(interp, JimCursorValue(obj)->viewObj, &view) != JIM_OK)
721         return JIM_ERR;
722 
723     if (curPtr)
724         *curPtr = &view[PositionIndex(&JimCursorValue(obj)->pos, view)];
725     return JIM_OK;
726 }
727 
JimIncrCursor(Jim_Interp * interp,Jim_Obj * obj,int offset)728 static int JimIncrCursor(Jim_Interp *interp, Jim_Obj *obj, int offset)
729 {
730     /* JimPanic((Jim_IsShared(obj), "JimIncrCursor called with shared object")) */
731 
732     if (obj->typePtr != &cursorObjType && SetCursorFromAny(interp, obj) != JIM_OK)
733         return JIM_ERR;
734 
735     Jim_InvalidateStringRep(obj);
736     JimCursorValue(obj)->pos.index += offset;
737     return JIM_OK;
738 }
739 
JimSeekCursor(Jim_Interp * interp,Jim_Obj * obj,Jim_Obj * posObj)740 static int JimSeekCursor(Jim_Interp *interp, Jim_Obj *obj, Jim_Obj *posObj)
741 {
742     /* JimPanic((Jim_IsShared(obj), "JimSeekCursor called with shared object")) */
743 
744     if (obj->typePtr != &cursorObjType && SetCursorFromAny(interp, obj) != JIM_OK)
745         return JIM_ERR;
746 
747     Jim_InvalidateStringRep(obj);
748     return GetPosition(interp, posObj, &JimCursorValue(obj)->pos);
749 }
750 
JimCheckCursor(Jim_Interp * interp,Jim_Obj * curObj,int flags)751 static int JimCheckCursor(Jim_Interp *interp, Jim_Obj *curObj, int flags)
752 {
753     static c4_View nullView;
754 
755     c4_Cursor cur = &nullView[0];
756     int size;
757 
758     if (JimGetCursor(interp, curObj, &cur) != JIM_OK)
759         return JIM_ERR;
760     size = (*cur).Container().GetSize();
761 
762     if ((flags & JIM_CURSOR_GET) && (cur._index < 0 || cur._index >= size)) {
763         if (flags & JIM_ERRMSG) {
764             Jim_SetResultFormatted(interp,
765                 "cursor \"%#s\" does not point to an existing row", curObj);
766         }
767         return JIM_ERR;
768     }
769     else if ((flags & JIM_CURSOR_SET) && cur._index < 0) {
770         if (flags & JIM_ERRMSG) {
771             Jim_SetResultFormatted(interp,
772                 "cursor \"%#s\" points before start of view", curObj);
773         }
774         return JIM_ERR;
775     }
776     else if ((flags & JIM_CURSOR_INSERT) && (cur._index < 0 || cur._index > size)) {
777         if (flags & JIM_ERRMSG) {
778             Jim_SetResultFormatted(interp,
779                 "cursor \"%#s\" does not point to a valid insert position", curObj);
780         }
781         return JIM_ERR;
782     }
783 
784     return JIM_OK;
785 }
786 
787 /* Records ----------------------------------------------------------------- */
788 
cursor_cmd_get(Jim_Interp * interp,int argc,Jim_Obj * const * argv)789 static int cursor_cmd_get(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
790 {
791     c4_View view;
792     c4_Cursor cur = &view[0];
793 
794     if (JimGetCursor(interp, argv[0], &cur) != JIM_OK)
795         return JIM_ERR;
796     if (JimCheckCursor(interp, argv[0], JIM_ERRMSG | JIM_CURSOR_GET) != JIM_OK)
797         return JIM_ERR;
798 
799     view = (*cur).Container();
800 
801     if (argc == 1) { /* Return all properties */
802         int i, count;
803         Jim_Obj *result;
804 
805         result = Jim_NewListObj(interp, NULL, 0);
806         count = view.NumProperties();
807 
808         for (i = 0; i < count; i++) {
809             c4_Property prop = view.NthProperty(i);
810 
811             Jim_ListAppendElement(interp, result, JimNewPropertyObj(interp, prop));
812             Jim_ListAppendElement(interp, result, JimGetMkValue(interp, cur, prop));
813         }
814 
815         Jim_SetResult(interp, result);
816         return JIM_OK;
817     }
818     else { /* Return a single property */
819         const c4_Property *propPtr;
820         int pipe;
821 
822         pipe = JimPipelineBoundary(argc, argv);
823         if (pipe == 2) {
824             /* No type annotation, existing property */
825             if (JimGetProperty(interp, argv[1], view, NULL, &propPtr) != JIM_OK)
826                 return JIM_ERR;
827         }
828         else if (pipe == 3) {
829             /* Explicit type annotation; the property may be new */
830             int idx;
831 
832             if (Jim_GetEnum(interp, argv[1], jim_mktype_options, &idx,
833                     "property type", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK)
834                 return JIM_ERR;
835             if (JimGetNewProperty(interp, argv[2], view, jim_mktype_types[idx], &propPtr) != JIM_OK)
836                 return JIM_ERR;
837         }
838         else {
839             Jim_WrongNumArgs(interp, 0, NULL, "cursor get ?-type? ?prop?");
840             return JIM_ERR;
841         }
842 
843         Jim_SetResult(interp, JimGetMkValue(interp, cur, *propPtr));
844 
845         if (pipe == argc)
846             return JIM_OK;
847         else
848             return Jim_EvalObjPrefix(interp, Jim_GetResult(interp), argc - pipe - 1, argv + pipe + 1);
849     }
850 }
851 
cursor_cmd_set(Jim_Interp * interp,int argc,Jim_Obj * const * argv)852 static int cursor_cmd_set(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
853 {
854     c4_View view;
855     c4_Cursor cur = &view[0];
856     const c4_Property *propPtr;
857     int i, oldSize;
858 
859     if (JimGetCursor(interp, argv[0], &cur) != JIM_OK)
860         return JIM_ERR;
861     if (JimCheckCursor(interp, argv[0], JIM_ERRMSG | JIM_CURSOR_SET) != JIM_OK)
862         return JIM_ERR;
863 
864     view = (*cur).Container();
865     oldSize = view.GetSize();
866 
867     if (cur._index >= oldSize)
868         view.SetSize(cur._index + 1);
869 
870     if (argc == 2) {
871         /* Update everything except subviews from a dictionary in argv[1].
872          * No new properties are permitted.
873          */
874 
875         int objc;
876         Jim_Obj **objv;
877 
878         if (Jim_DictPairs(interp, argv[1], &objv, &objc) != JIM_OK)
879             goto err;
880 
881         for (i = 0; i < objc; i += 2) {
882             if (JimGetProperty(interp, objv[i], view, NULL, &propPtr) != JIM_OK ||
883                 JimSetMkValue(interp, cur, *propPtr, objv[i+1]) != JIM_OK)
884             {
885                 Jim_Free(objv);
886                 goto err;
887             }
888         }
889     }
890     else {
891         /* Update everything from argv[1..]. New properties are permitted if
892          * explicitly typed.
893          */
894 
895         for (i = 1; i < argc; i += 2) {
896             if (Jim_String(argv[i])[0] == '-') {
897                 int idx;
898 
899                 if (i + 2 >= argc) {
900                     Jim_WrongNumArgs(interp, 2, argv, "?-type? prop value ?...?");
901                     goto err;
902                 }
903 
904                 if (Jim_GetEnum(interp, argv[i], jim_mktype_options, &idx,
905                         "property type", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK)
906                     goto err;
907                 if (JimGetNewProperty(interp, argv[i+1], view, jim_mktype_types[idx], &propPtr) != JIM_OK)
908                     goto err;
909                 i++;
910             }
911             else {
912                 if (i + 1 >= argc) {
913                     Jim_WrongNumArgs(interp, 2, argv, "?-type? prop value ?...?");
914                     goto err;
915                 }
916 
917                 if (JimGetProperty(interp, argv[i], view, NULL, &propPtr) != JIM_OK)
918                     goto err;
919             }
920 
921             if (JimSetMkValue(interp, cur, *propPtr, argv[i+1]) != JIM_OK)
922                 goto err;
923         }
924     }
925 
926     return JIM_OK;
927 
928   err:
929     view.SetSize(oldSize);
930     return JIM_ERR;
931 }
932 
cursor_cmd_insert(Jim_Interp * interp,int argc,Jim_Obj * const * argv)933 static int cursor_cmd_insert(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
934 {
935     c4_View view;
936     c4_Cursor cur = &view[0];
937     jim_wide count;
938 
939     if (JimGetCursor(interp, argv[0], &cur) != JIM_OK)
940         return JIM_ERR;
941     if (JimCheckCursor(interp, argv[0], JIM_ERRMSG | JIM_CURSOR_INSERT) != JIM_OK)
942         return JIM_ERR;
943 
944     view = (*cur).Container();
945 
946     if (argc == 1)
947         count = 1;
948     else {
949         if (Jim_GetWide(interp, argv[1], &count) != JIM_OK)
950             return JIM_ERR;
951     }
952 
953     if (count > 0) {
954         c4_Row empty;
955         view.InsertAt(cur._index, empty, (int)count);
956     }
957 
958     Jim_SetEmptyResult(interp);
959     return JIM_OK;
960 }
961 
cursor_cmd_remove(Jim_Interp * interp,int argc,Jim_Obj * const * argv)962 static int cursor_cmd_remove(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
963 {
964     c4_View view;
965     c4_Cursor cur = &view[0];
966     int pos;
967     jim_wide count;
968 
969     if (JimGetCursor(interp, argv[0], &cur) != JIM_OK)
970         return JIM_ERR;
971     if (JimCheckCursor(interp, argv[0], JIM_ERRMSG | JIM_CURSOR_SET) != JIM_OK)
972         return JIM_ERR;
973 
974     view = (*cur).Container();
975     pos = cur._index;
976 
977     if (argc == 1)
978         count = 1;
979     else {
980         if (Jim_GetWide(interp, argv[1], &count) != JIM_OK)
981             return JIM_ERR;
982     }
983 
984     if (pos + count < view.GetSize())
985         count = view.GetSize() - pos;
986 
987     if (pos < view.GetSize())
988         view.RemoveAt(pos, (int)count);
989 
990     return JIM_OK;
991 }
992 
993 /* Attributes -------------------------------------------------------------- */
994 
cursor_cmd_view(Jim_Interp * interp,int argc,Jim_Obj * const * argv)995 static int cursor_cmd_view(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
996 {
997     Jim_Obj *viewObj;
998 
999     if (JimGetCursorView(interp, argv[0], &viewObj) != JIM_OK)
1000         return JIM_ERR;
1001 
1002     JimPinView(interp, viewObj);
1003     Jim_SetResult(interp, viewObj);
1004     return JIM_OK;
1005 }
1006 
1007 /* Positioning ------------------------------------------------------------- */
1008 
cursor_cmd_tell(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1009 static int cursor_cmd_tell(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1010 {
1011     if (argc == 1) {
1012         Jim_Obj *result;
1013 
1014         if (JimCursorPos(interp, argv[0], &result) != JIM_OK)
1015             return JIM_ERR;
1016         Jim_SetResult(interp, result);
1017     }
1018     else {
1019         static c4_View nullView;
1020         c4_Cursor cur = &nullView[0];
1021 
1022         if (!Jim_CompareStringImmediate(interp, argv[0], "-absolute")) {
1023             Jim_SetResultFormatted(interp,
1024                 "bad option \"%#s\": must be -absolute", argv[0]);
1025             return JIM_ERR;
1026         }
1027 
1028         if (JimGetCursor(interp, argv[1], &cur) != JIM_OK)
1029             return JIM_ERR;
1030 
1031         Jim_SetResultInt(interp, cur._index);
1032     }
1033 
1034     return JIM_OK;
1035 }
1036 
cursor_cmd_validfor(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1037 static int cursor_cmd_validfor(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1038 {
1039     static const char *options[] = {
1040         "get", "set", "insert", "remove", 0
1041     };
1042     static int optflags[] = {
1043         JIM_CURSOR_GET,
1044         JIM_CURSOR_SET,
1045         JIM_CURSOR_INSERT,
1046         JIM_CURSOR_SET
1047     };
1048 
1049     int idx;
1050 
1051     if (argc == 1)
1052         idx = 0;
1053     else {
1054         if (Jim_GetEnum(interp, argv[0], options, &idx, NULL,
1055                 JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK)
1056             return JIM_ERR;
1057     }
1058 
1059     if (JimGetCursor(interp, argv[argc-1], NULL) != JIM_OK)
1060         return JIM_ERR;
1061 
1062     Jim_SetResultBool(interp, JimCheckCursor(interp, argv[argc-1], optflags[idx]) == JIM_OK);
1063     return JIM_OK;
1064 }
1065 
cursor_cmd_seek(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1066 static int cursor_cmd_seek(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1067 {
1068     Jim_Obj *curObj;
1069 
1070     curObj = Jim_GetVariable(interp, argv[0], JIM_ERRMSG | JIM_UNSHARED);
1071     if (curObj == NULL)
1072         return JIM_ERR;
1073 
1074     if (JimSeekCursor(interp, curObj, argv[1]) != JIM_OK)
1075         return JIM_ERR;
1076 
1077     Jim_SetResult(interp, curObj);
1078     return JIM_OK;
1079 }
1080 
cursor_cmd_incr(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1081 static int cursor_cmd_incr(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1082 {
1083     Jim_Obj *curObj;
1084     jim_wide offset;
1085 
1086     if (argc == 1)
1087         offset = 1;
1088     else {
1089         if (Jim_GetWide(interp, argv[1], &offset) != JIM_OK)
1090             return JIM_ERR;
1091     }
1092 
1093     curObj = Jim_GetVariable(interp, argv[0], JIM_ERRMSG | JIM_UNSHARED);
1094     if (curObj == NULL)
1095         return JIM_ERR;
1096 
1097     if (JimIncrCursor(interp, curObj, (int)offset) != JIM_OK)
1098         return JIM_ERR;
1099 
1100     Jim_SetResult(interp, curObj);
1101     return JIM_OK;
1102 }
1103 
1104 /* Command table ----------------------------------------------------------- */
1105 
1106 static const jim_subcmd_type cursor_command_table[] = {
1107 
1108     /* Records */
1109 
1110     {   "get", "cur ?-type? ?prop?",
1111         cursor_cmd_get,
1112         1, -1,
1113         0,
1114         /*"Get the whole record or a specific property at the cursor"*/
1115     },
1116     {   "set", "cur [dict | ?-type? field value ?...?]",
1117         cursor_cmd_set,
1118         1, -1,
1119         0,
1120         /*"Update the record at the cursor"*/
1121     },
1122     {   "insert", "cur ?count?",
1123         cursor_cmd_insert,
1124         1, 2,
1125         0,
1126         /*"Insert a specified number of empty rows at the cursor (default 1)"*/
1127     },
1128     {   "remove", "cur ?count?",
1129         cursor_cmd_remove,
1130         1, 2,
1131         0,
1132         /*"Remove a specified number of rows at the cursor (default 1)"*/
1133     },
1134 
1135     /* Attributes */
1136 
1137     {   "view", "cur",
1138         cursor_cmd_view,
1139         1, 1,
1140         0,
1141         /*"Get the view the cursor points into"*/
1142     },
1143 
1144     /* Positioning */
1145 
1146     {   "tell", "?-absolute? cur",
1147         cursor_cmd_tell,
1148         1, 2,
1149         0,
1150         /*"Get the position of the cursor"*/
1151     },
1152     {   "validfor", "?command? cur",
1153         cursor_cmd_validfor,
1154         1, 2,
1155         0,
1156         /*"Checks if the cursor is valid for get (default), set or insert commands"*/
1157     },
1158     {   "seek", "curVar index",
1159         cursor_cmd_seek,
1160         2, 2,
1161         0,
1162         /*"Seek to the specified index in the view"*/
1163     },
1164     {   "incr", "curVar ?offset?",
1165         cursor_cmd_incr,
1166         1, 2,
1167         0,
1168         /*"Move the cursor offset records from its current position (default 1)"*/
1169     },
1170 
1171     { 0 }
1172 };
1173 
JimCursorCommand(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1174 static int JimCursorCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1175 {
1176     Jim_Obj *cmdObj;
1177 
1178     if (argc < 2) {
1179         Jim_WrongNumArgs(interp, 1, argv, "command ...");
1180         return JIM_ERR;
1181     }
1182 
1183     cmdObj = Jim_NewStringObj(interp, "cursor ", -1);
1184     Jim_AppendObj(interp, cmdObj, argv[1]);
1185 
1186     if (Jim_GetCommand(interp, cmdObj, 0) != NULL)
1187         return Jim_EvalObjPrefix(interp, cmdObj, argc - 2, argv + 2);
1188     else {
1189         Jim_FreeNewObj(interp, cmdObj);
1190         return Jim_CallSubCmd(interp,
1191             Jim_ParseSubCmd(interp, cursor_command_table, argc, argv), argc, argv);
1192     }
1193 }
1194 
1195 /* -------------------------------------------------------------------------
1196  * View handle
1197  * ------------------------------------------------------------------------- */
1198 
1199 /* Views aren't really Jim objects; instead, they are Tk-style commands with
1200  * oo.tcl-like lifetime management. Additionally, all views are initially
1201  * created as one-shot, meaning that they die after one command. Call
1202  * JimPinView to make a view object persistent.
1203  *
1204  * It is valid to rename a view in the Tcl land, but by doing this you take
1205  * the responsibility of destroying the object when it's no longer needed.
1206  * Any cursors that pointed into the view become invalid.
1207  */
1208 
1209 static int JimViewSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
1210 static int JimOneShotViewSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
1211 
1212 /* Unary operations -------------------------------------------------------- */
1213 
1214 #define UNOP(name, Method) \
1215 static int view_cmd_##name(Jim_Interp *interp, int argc, Jim_Obj *const *argv)  \
1216 {   \
1217     const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp);          \
1218     \
1219     Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->Method()));            \
1220     return JIM_OK;                                                              \
1221 }
1222 
UNOP(copy,Duplicate)1223 UNOP(copy, Duplicate)
1224 UNOP(clone, Clone)
1225 UNOP(unique, Unique)
1226 
1227 #undef UNOP
1228 
1229 static int view_cmd_blocked(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1230 {
1231     const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp);
1232 
1233     if (viewPtr->GetSize() != 1 ||
1234         strcmp(viewPtr->NthProperty(0).Name(), "_B") != 0 ||
1235         viewPtr->NthProperty(0).Type() != MK_PROPERTY_VIEW)
1236     {
1237         Jim_SetResultString(interp,
1238             "blocked view must have exactly one subview property called _B", -1);
1239         return JIM_ERR;
1240     }
1241 
1242     Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->Blocked()));
1243     return JIM_OK;
1244 }
1245 
1246 /* Binary operations ------------------------------------------------------- */
1247 
1248 #define BINOP(name, Method) \
1249 static int view_cmd_##name(Jim_Interp *interp, int argc, Jim_Obj *const *argv)  \
1250 {   \
1251     const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp);          \
1252     c4_View otherView;                                                          \
1253     \
1254     if (JimGetView(interp, argv[0], &otherView) != JIM_OK)                      \
1255         return JIM_ERR;                                                         \
1256     \
1257     Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->Method(otherView)));   \
1258     return JIM_OK;                                                              \
1259 }
1260 
BINOP(pair,Pair)1261 BINOP(pair, Pair)
1262 BINOP(concat, Concat)
1263 BINOP(product, Product)
1264 
1265 BINOP(union, Union)
1266 BINOP(intersect, Intersect)
1267 BINOP(minus, Minus)
1268 BINOP(different, Different)
1269 
1270 #undef BINOP
1271 
1272 /* Projections ------------------------------------------------------------- */
1273 
1274 static int view_cmd_project(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1275 {
1276     const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp);
1277     c4_View props;
1278 
1279     if (JimGetProperties(interp, argc, argv, *viewPtr, &props) != JIM_OK)
1280         return JIM_ERR;
1281 
1282     Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->Project(props)));
1283     return JIM_OK;
1284 }
1285 
view_cmd_without(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1286 static int view_cmd_without(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1287 {
1288     const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp);
1289     c4_View props;
1290 
1291     if (JimGetProperties(interp, argc, argv, *viewPtr, &props) != JIM_OK)
1292         return JIM_ERR;
1293 
1294     Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->ProjectWithout(props)));
1295     return JIM_OK;
1296 }
1297 
view_cmd_range(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1298 static int view_cmd_range(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1299 {
1300     const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp);
1301     int start, end;
1302     jim_wide step;
1303 
1304     if (JimGetPosition(interp, argv[0], *viewPtr, &start) != JIM_OK ||
1305         JimGetPosition(interp, argv[1], *viewPtr, &end) != JIM_OK)
1306     {
1307         return JIM_ERR;
1308     }
1309 
1310     if (argc == 2)
1311         step = 1;
1312     else if (Jim_GetWide(interp, argv[2], &step) != JIM_OK)
1313         return JIM_ERR;
1314 
1315     Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->Slice(start, end + 1, (int)step)));
1316     return JIM_OK;
1317 }
1318 
1319 /* Ordering ---------------------------------------------------------------- */
1320 
view_cmd_sort(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1321 static int view_cmd_sort(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1322 {
1323     const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp);
1324     c4_View sortProps, revProps;
1325     const c4_Property *propPtr;
1326     int i, len;
1327 
1328     const char *rep;
1329     int reverse;
1330     Jim_Obj *propObj;
1331 
1332     /* Special case: property names may be preceded with a dash. Use
1333      * a temporary object in this case.
1334      */
1335 
1336     for (i = 0; i < argc; i++) {
1337         propObj = argv[i];
1338 
1339         rep = Jim_GetString(argv[i], &len);
1340         reverse = (len > 0 && rep[0] == '-');
1341 
1342         if (reverse)
1343             propObj = Jim_NewStringObj(interp, rep + 1, len - 1);
1344 
1345         if (JimGetProperty(interp, propObj, *viewPtr, NULL, &propPtr) != JIM_OK) {
1346             if (reverse)
1347                 Jim_FreeNewObj(interp, propObj);
1348             return JIM_ERR;
1349         }
1350 
1351         sortProps.AddProperty(*propPtr);
1352         if (reverse) {
1353             revProps.AddProperty(*propPtr);
1354             Jim_FreeNewObj(interp, propObj);
1355         }
1356     }
1357 
1358     if (sortProps.GetSize() == 0)
1359         Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->Sort()));
1360     else if (revProps.GetSize() == 0)
1361         Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->SortOn(sortProps)));
1362     else
1363         Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->SortOnReverse(sortProps, revProps)));
1364 
1365     return JIM_OK;
1366 }
1367 
1368 /* Metakit core seems to be doing something similar for SortOn, but neither
1369  * Ordered nor Hash use it, for unknown reason.
1370  */
1371 
BubbleProperties(Jim_Interp * interp,c4_View orig,int objc,Jim_Obj * const * objv,c4_View * projPtr)1372 static int BubbleProperties(Jim_Interp *interp, c4_View orig, int objc, Jim_Obj *const *objv, c4_View *projPtr)
1373 {
1374     c4_View proj;
1375     const c4_Property *propPtr;
1376     int i, count;
1377 
1378     for (i = 0; i < objc; i++) {
1379         if (JimGetProperty(interp, objv[i], orig, NULL, &propPtr) != JIM_OK)
1380             return JIM_ERR;
1381         proj.AddProperty(*propPtr);
1382     }
1383 
1384     count = orig.NumProperties();
1385     for (i = 0; i < count; i++)
1386         proj.AddProperty(orig.NthProperty(i));
1387 
1388     *projPtr = proj;
1389     return JIM_OK;
1390 }
1391 
view_cmd_ordered(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1392 static int view_cmd_ordered(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1393 {
1394     const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp);
1395     c4_View proj;
1396 
1397     if (BubbleProperties(interp, *viewPtr, argc, argv, &proj) != JIM_OK)
1398         return JIM_ERR;
1399 
1400     Jim_SetResult(interp, JimNewViewObj(interp, proj.Ordered(argc)));
1401     return JIM_OK;
1402 }
1403 
view_cmd_hash(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1404 static int view_cmd_hash(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1405 {
1406     const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp);
1407     c4_View hash, proj;
1408 
1409     if (JimGetView(interp, argv[0], &hash) != JIM_OK)
1410         return JIM_ERR;
1411 
1412     if (hash.GetSize() != 2 ||
1413         strcmp(hash.NthProperty(0).Name(), "_H") != 0 ||
1414         hash.NthProperty(0).Type() != MK_PROPERTY_INT ||
1415         strcmp(hash.NthProperty(1).Name(), "_R") != 0 ||
1416         hash.NthProperty(1).Type() != MK_PROPERTY_INT) /* Ouch. */
1417     {
1418         Jim_SetResultString(interp,
1419             "hash view must be laid out as {_H integer _R integer}", -1);
1420         return JIM_ERR;
1421     }
1422 
1423     if (BubbleProperties(interp, *viewPtr, argc - 1, argv + 1, &proj) != JIM_OK)
1424         return JIM_ERR;
1425 
1426     Jim_SetResult(interp, JimNewViewObj(interp, proj.Hash(hash, argc - 1)));
1427     return JIM_OK;
1428 }
1429 
1430 /* Relational operations --------------------------------------------------- */
1431 
view_cmd_join(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1432 static int view_cmd_join(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1433 {
1434     const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp);
1435     c4_View other, props;
1436     int outer, off;
1437 
1438     if (JimGetView(interp, argv[0], &other) != JIM_OK)
1439         return JIM_ERR;
1440 
1441     off = 1; outer = 0;
1442     if (Jim_CompareStringImmediate(interp, argv[1], "-outer")) {
1443         off++; outer = 1;
1444     }
1445 
1446     if (JimGetProperties(interp, argc - off, argv + off, *viewPtr, &props) != JIM_OK)
1447         return JIM_ERR;
1448 
1449     Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->Join(props, other, outer)));
1450     return JIM_OK;
1451 }
1452 
view_cmd_group(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1453 static int view_cmd_group(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1454 {
1455     const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp);
1456     const c4_Property *subviewPtr;
1457     c4_View props;
1458 
1459     if (JimGetPropertyTyped(interp, argv[0], MK_PROPERTY_VIEW, &subviewPtr) != JIM_OK)
1460         return JIM_ERR;
1461 
1462     if (JimGetProperties(interp, argc - 1, argv + 1, *viewPtr, &props) != JIM_OK)
1463         return JIM_ERR;
1464 
1465     Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->GroupBy(props, *(c4_ViewProp *)subviewPtr)));
1466     return JIM_OK;
1467 }
1468 
view_cmd_flatten(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1469 static int view_cmd_flatten(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1470 {
1471     const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp);
1472     const c4_Property *subviewPtr;
1473 
1474     if (JimGetProperty(interp, argv[0], *viewPtr, NULL, &subviewPtr) != JIM_OK)
1475         return JIM_ERR;
1476 
1477     if (subviewPtr->Type() != MK_PROPERTY_VIEW) {
1478         Jim_SetResultFormatted(interp, "expected a subview property but got %s one",
1479             JimMkTypeName(subviewPtr->Type()));
1480         return JIM_ERR;
1481     }
1482 
1483     Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->JoinProp(*(c4_ViewProp *)subviewPtr)));
1484     return JIM_OK;
1485 }
1486 
1487 /* View queries ------------------------------------------------------------ */
1488 
view_cmd_properties(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1489 static int view_cmd_properties(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1490 {
1491     const c4_View *viewPtr = (const c4_View *) Jim_CmdPrivData(interp);
1492     Jim_SetResult(interp, JimViewPropertiesList(interp, *viewPtr));
1493     return JIM_OK;
1494 }
1495 
view_cmd_size(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1496 static int view_cmd_size(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1497 {
1498     const c4_View *viewPtr = (const c4_View *) Jim_CmdPrivData(interp);
1499     Jim_SetResultInt(interp, viewPtr->GetSize());
1500     return JIM_OK;
1501 }
1502 
view_cmd_resize(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1503 static int view_cmd_resize(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1504 {
1505     c4_View *view = (c4_View *) Jim_CmdPrivData(interp);
1506     jim_wide size;
1507 
1508     if (Jim_GetWide(interp, argv[0], &size) != JIM_OK)
1509         return JIM_ERR;
1510     if (size < 0 || size > INT_MAX) {
1511         Jim_SetResultFormatted(interp,
1512             "view size \"%#s\" is out of range", argv[0]);
1513         return JIM_ERR;
1514     }
1515 
1516     view->SetSize((int)size);
1517     Jim_SetResult(interp, argv[0]);
1518     return JIM_OK;
1519 }
1520 
view_cmd_type(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1521 static int view_cmd_type(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1522 {
1523     const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp);
1524 
1525     if (argc == 1) {
1526         const c4_Property *propPtr;
1527 
1528         if (JimGetProperty(interp, argv[0], *viewPtr, NULL, &propPtr) != JIM_OK)
1529             return JIM_ERR;
1530 
1531         Jim_SetResultString(interp, JimMkTypeName(propPtr->Type()), -1);
1532     }
1533     else {
1534         Jim_Obj *result;
1535         int i, count;
1536 
1537         result = Jim_NewListObj(interp, NULL, 0);
1538         count = viewPtr->NumProperties();
1539 
1540         for (i = 0; i < count; i++) {
1541             c4_Property prop = viewPtr->NthProperty(i);
1542             Jim_ListAppendElement(interp, result, JimNewPropertyObj(interp, prop));
1543             Jim_ListAppendElement(interp, result,
1544                 Jim_NewStringObj(interp, JimMkTypeName(prop.Type()), -1));
1545         }
1546 
1547         Jim_SetResult(interp, result);
1548     }
1549 
1550     return JIM_OK;
1551 }
1552 
1553 /* View lifetime ----------------------------------------------------------- */
1554 
view_cmd_pin(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1555 static int view_cmd_pin(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1556 {
1557     JimPinView(interp, argv[0]);
1558     Jim_SetResult(interp, argv[0]);
1559     return JIM_OK;
1560 }
1561 
view_cmd_as(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1562 static int view_cmd_as(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1563 {
1564     JimPinView(interp, argv[0]);
1565     Jim_SetVariable(interp, argv[2], argv[0]);
1566     Jim_SetResult(interp, argv[0]);
1567     return JIM_OK;
1568 }
1569 
view_cmd_destroy(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1570 static int view_cmd_destroy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1571 {
1572     Jim_DeleteCommand(interp, argv[0]);
1573     return JIM_OK;
1574 }
1575 
1576 /* Command table ----------------------------------------------------------- */
1577 
1578 static const jim_subcmd_type view_command_table[] = {
1579 
1580     /* Unary operations */
1581 
1582     {   "copy", "",
1583         view_cmd_copy,
1584         0, 0,
1585         0,
1586         /*"Create a copy of the view with exactly the same data"*/
1587     },
1588     {   "clone", "",
1589         view_cmd_clone,
1590         0, 0,
1591         0,
1592         /*"Create an empty view with the same properties as this one"*/
1593     },
1594     {   "unique", "",
1595         view_cmd_unique,
1596         0, 0,
1597         0,
1598         /*"Derived view without any duplicate rows (read-only, no change notifications)"*/
1599     },
1600     {   "blocked", "",
1601         view_cmd_blocked,
1602         0, 0,
1603         0,
1604         /*"Build a scalable \"blocked\" out of a view with a single subview property called _B"*/
1605     },
1606 
1607     /* Binary operations */
1608 
1609     #define BINOP(name, descr)  \
1610     {   #name, "otherView",     \
1611         view_cmd_##name,        \
1612         1, 1, 0,                \
1613     }
1614 
1615     BINOP(pair, "Pairwise concatenation of two views"),
1616     BINOP(concat, "Concatenation of two views; unlike union, doesn't remove duplicates"),
1617     BINOP(product, "Cartesian product of two views, i.e. every row in view paired with every row in otherView"),
1618 
1619     /* Set operations */
1620 
1621     #define SETOP(name, descr) BINOP(name, descr "; works only if all the rows are unique")
1622 
1623     SETOP(union, "Set union of two views (read-only, no change notifications)"),
1624     SETOP(intersect, "Set intersection of two views"),
1625     SETOP(different, "Symmetric difference of two views"),
1626     SETOP(minus, "Set minus, i.e. all rows from view not in otherView"),
1627 
1628     #undef SETOP
1629 
1630     #undef BINOP
1631 
1632     /* Projections and selections */
1633 
1634     {   "project", "prop ?prop ...?",
1635         view_cmd_project,
1636         1, -1,
1637         0,
1638         /*"View projection: only the specified properties, in the specified order"*/
1639     },
1640     {   "without", "prop ?prop ...?",
1641         view_cmd_without,
1642         1, -1,
1643         0,
1644         /*"View projection: remove the specified properties"*/
1645     },
1646     {   "range", "first last ?step?",
1647         view_cmd_range,
1648         2, 3,
1649         0,
1650         /*"Range or slice of the view (read-write, no change notifications)"*/
1651     },
1652 
1653     /* Ordering */
1654 
1655     {   "sort", "?[prop|-prop] ...?",
1656         view_cmd_sort,
1657         0, -1,
1658         0,
1659         /*"Derived view sorted on the specified properties (in order), or on all properties"*/
1660     },
1661     {   "ordered", "prop ?prop ...?",
1662         view_cmd_ordered,
1663         1, -1,
1664         0,
1665         /*"Consider the underlying view ordered on the specified properties"*/
1666     },
1667     {   "hash", "hashView prop ?prop ...?",
1668         view_cmd_hash,
1669         2, -1,
1670         0,
1671         /*"Mapped view maintaining a hash table on the key consisting of the specified properties"*/
1672     },
1673 
1674     /* Relational operations */
1675 
1676     {   "join", "view ?-outer? prop ?prop ...?",
1677         view_cmd_join,
1678         2, -1,
1679         0,
1680         /*"Relational join with view on the specified properties"*/
1681     },
1682     {   "group", "subviewName prop ?prop ...?",
1683         view_cmd_group,
1684         1, -1,
1685         0,
1686         /*"Group rows with equal specified properties, move all other properties into subview"*/
1687     },
1688     {   "flatten", "subviewProp",
1689         view_cmd_flatten,
1690         1, 1,
1691         0,
1692         /*"Flatten the specified subview; the inverse of group"*/
1693     },
1694 
1695     /* Attributes */
1696 
1697     {   "properties", "",
1698         view_cmd_properties,
1699         0, 0,
1700         0,
1701         /*"List the properties in this view"*/
1702     },
1703     {   "size", "",
1704         view_cmd_size,
1705         0, 0,
1706         0,
1707         /*"Return the number of records in the view"*/
1708     },
1709     {   "resize", "newSize",
1710         view_cmd_resize,
1711         1, 1,
1712         0,
1713         /*"Set the number of records in the view"*/
1714     },
1715     {   "type", "?prop?",
1716         view_cmd_type,
1717         0, 1,
1718         0,
1719         /*"Return the type of an existing property, or of all properties"*/
1720     },
1721 
1722     /* Lifetime management */
1723 
1724     {   "pin", "",
1725         view_cmd_pin,
1726         0, 0,
1727         JIM_MODFLAG_FULLARGV,
1728         /*"Marks the view as persistent"*/
1729     },
1730     {   "as", "varName",
1731         view_cmd_as,
1732         1, 1,
1733         JIM_MODFLAG_FULLARGV,
1734         /*"Marks the view as persistent and assigns it to the given variable"*/
1735     },
1736     {   "destroy", "",
1737         view_cmd_destroy,
1738         0, 0,
1739         JIM_MODFLAG_FULLARGV,
1740         /*"Destroys the view explicitly"*/
1741     },
1742 
1743     { 0 }
1744 };
1745 
JimViewDelProc(Jim_Interp * interp,void * privData)1746 static void JimViewDelProc(Jim_Interp *interp, void *privData)
1747 {
1748     delete (c4_View *)privData;
1749 }
1750 
JimViewSubCmdProc(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1751 static int JimViewSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1752 {
1753     int pipe, result;
1754     Jim_Obj *cmdObj;
1755 
1756     pipe = JimPipelineBoundary(argc, argv);
1757 
1758     if (pipe < 1) {
1759         Jim_WrongNumArgs(interp, 1, argv, "command ...");
1760         return JIM_ERR;
1761     }
1762 
1763     /* Check for a Tcl command first, and try builtins afterwards.
1764      * We have to do it in this order so that Jim_ParseSubCmd isn't too greedy
1765      * about abbreviations, and still it can't now detect ambigous abbrevs
1766      * properly :( Tcl commands cannot be abbreviated at all.
1767      */
1768 
1769     cmdObj = Jim_NewStringObj(interp, "mk.view ", -1);
1770     Jim_AppendObj(interp, cmdObj, argv[1]);
1771 
1772     /* The command will be cached even though we discard the result */
1773     if (Jim_GetCommand(interp, cmdObj, 0) != NULL) {
1774         /* Shuffle the arguments: $view cmd args... => {mk.view cmd} $view args... */
1775 
1776         Jim_Obj **objv = (Jim_Obj **)Jim_Alloc(pipe * sizeof(Jim_Obj *));
1777         objv[0] = cmdObj;
1778         objv[1] = argv[0];
1779         memcpy(objv + 2, argv + 2, (pipe - 2) * sizeof(Jim_Obj *));
1780 
1781         result = Jim_EvalObjVector(interp, pipe, objv);
1782 
1783         Jim_Free(objv);
1784     } else {
1785         Jim_FreeNewObj(interp, cmdObj);
1786         result = Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, view_command_table, pipe, argv), pipe, argv);
1787     }
1788 
1789     if (result != JIM_OK || pipe == argc)
1790         return result;
1791     else
1792         return Jim_EvalObjPrefix(interp, Jim_GetResult(interp), argc - pipe - 1, argv + pipe + 1);
1793 }
1794 
JimOneShotViewSubCmdProc(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1795 static int JimOneShotViewSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1796 {
1797     int result;
1798     Jim_Cmd *cmd;
1799 
1800     result = JimViewSubCmdProc(interp, argc, argv);
1801 
1802     cmd = Jim_GetCommand(interp, argv[0], 0);
1803     if (cmd && !cmd->isproc && cmd->u.native.cmdProc == JimOneShotViewSubCmdProc)
1804         Jim_DeleteCommand(interp, argv[0]);
1805 
1806     return result;
1807 }
1808 
JimViewFinalizerProc(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1809 static int JimViewFinalizerProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1810 {
1811     /* We won't succeed here if the user renamed the command, and this is right */
1812     Jim_DeleteCommand(interp, argv[1]);
1813     return JIM_OK;
1814 }
1815 
JimNewViewObj(Jim_Interp * interp,c4_View view)1816 static Jim_Obj *JimNewViewObj(Jim_Interp *interp, c4_View view) {
1817     Jim_Obj *tag, *ref;
1818 
1819     tag = Jim_NewStringObj(interp, "mk.view", -1);
1820     ref = Jim_NewReference(interp, tag, tag, Jim_NewStringObj(interp, "mk.view.finalizer", -1));
1821     Jim_CreateCommand(interp, Jim_String(ref),
1822         JimOneShotViewSubCmdProc, new c4_View(view), JimViewDelProc);
1823 
1824     return ref;
1825 }
1826 
JimGetView(Jim_Interp * interp,Jim_Obj * obj,c4_View * viewPtr)1827 static int JimGetView(Jim_Interp *interp, Jim_Obj *obj, c4_View *viewPtr)
1828 {
1829     Jim_Cmd *cmd = Jim_GetCommand(interp, obj, 0);
1830 
1831     if (cmd == NULL || cmd->isproc || cmd->u.native.delProc != JimViewDelProc) {
1832         Jim_SetResultFormatted(interp, "invalid view object \"%#s\"", obj);
1833         return JIM_ERR;
1834     }
1835 
1836     *viewPtr = *(c4_View *)cmd->u.native.privData;
1837     return JIM_OK;
1838 }
1839 
1840 /* Only call this against known view objects. */
JimPinView(Jim_Interp * interp,Jim_Obj * obj)1841 static void JimPinView(Jim_Interp *interp, Jim_Obj *obj)
1842 {
1843     Jim_Cmd *cmd = Jim_GetCommand(interp, obj, 0);
1844     /* JimPanic((cmd == NULL, "JimPinView called against non-view"))
1845        JimPanic((cmd->u.native.delProc != JimViewDelProc, "JimPinView called against non-view")) */
1846     cmd->u.native.cmdProc = JimViewSubCmdProc;
1847 }
1848 
JimViewPropertiesList(Jim_Interp * interp,c4_View view)1849 static Jim_Obj *JimViewPropertiesList(Jim_Interp *interp, c4_View view)
1850 {
1851     int i, count;
1852     Jim_Obj *result;
1853 
1854     result = Jim_NewListObj(interp, NULL, 0);
1855     count = view.NumProperties();
1856 
1857     for (i = 0; i < count; i++) {
1858         Jim_ListAppendElement(interp, result, Jim_NewStringObj(interp,
1859             view.NthProperty(i).Name(), -1));
1860     }
1861 
1862     return result;
1863 }
1864 
1865 /* ----------------------------------------------------------------------------
1866  * Storage handle
1867  * ---------------------------------------------------------------------------- */
1868 
1869 /* These are also commands, like views, but must be managed explicitly by the
1870  * user. Quite like file handles, actually.
1871  */
1872 
1873 typedef struct MkStorage {
1874     unsigned flags;
1875     Jim_Obj *filename;
1876     c4_Storage storage;
1877     c4_Cursor content;
1878 } MkStorage;
1879 
1880 #define JIM_MKFLAG_INMEMORY     0x0001
1881 #define JIM_MKFLAG_READONLY     0x0002
1882 #define JIM_MKFLAG_EXTEND       0x0004
1883 #define JIM_MKFLAG_AUTOCOMMIT   0x0008
1884 
1885 /* Attributes -------------------------------------------------------------- */
1886 
storage_cmd_autocommit(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1887 static int storage_cmd_autocommit(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1888 {
1889     MkStorage *mk = (MkStorage *)Jim_CmdPrivData(interp);
1890 
1891     if (argc == 1) {
1892         jim_wide flag;
1893 
1894         if (Jim_GetWide(interp, argv[0], &flag) != JIM_OK)
1895             return JIM_ERR;
1896 
1897         if (flag)
1898             mk->flags |= JIM_MKFLAG_AUTOCOMMIT;
1899         else
1900             mk->flags &= ~JIM_MKFLAG_AUTOCOMMIT;
1901         mk->storage.AutoCommit(flag);
1902     }
1903 
1904     Jim_SetResultBool(interp, (mk->flags & JIM_MKFLAG_AUTOCOMMIT) != 0);
1905     return JIM_OK;
1906 }
1907 
storage_cmd_readonly(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1908 static int storage_cmd_readonly(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1909 {
1910     MkStorage *mk = (MkStorage *)Jim_CmdPrivData(interp);
1911 
1912     Jim_SetResultBool(interp, (mk->flags & JIM_MKFLAG_READONLY) != 0);
1913     return JIM_OK;
1914 }
1915 
1916 /* Views ------------------------------------------------------------------- */
1917 
storage_cmd_views(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1918 static int storage_cmd_views(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1919 {
1920     MkStorage *mk = (MkStorage *)Jim_CmdPrivData(interp);
1921 
1922     Jim_SetResult(interp, JimViewPropertiesList(interp, mk->storage));
1923     return JIM_OK;
1924 }
1925 
storage_cmd_view(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1926 static int storage_cmd_view(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1927 {
1928     MkStorage *mk = (MkStorage *)Jim_CmdPrivData(interp);
1929     const c4_Property *propPtr;
1930 
1931     if (JimGetProperty(interp, argv[0], mk->storage, "view", &propPtr) != JIM_OK)
1932         return JIM_ERR;
1933     Jim_SetResult(interp, JimGetMkValue(interp, mk->content, *propPtr));
1934 
1935     if (argc == 1)
1936         return JIM_OK;
1937     else {
1938         if (!Jim_CompareStringImmediate(interp, argv[1], "|")) {
1939             Jim_SetResultFormatted(interp,
1940                 "expected start of a pipeline but got \"%#s\"", argv[1]);
1941             return JIM_ERR;
1942         }
1943         return Jim_EvalObjPrefix(interp, Jim_GetResult(interp), argc - 2, argv + 2);
1944     }
1945 }
1946 
storage_cmd_structure(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1947 static int storage_cmd_structure(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1948 {
1949     MkStorage *mk = (MkStorage *)Jim_CmdPrivData(interp);
1950 
1951     if (argc < 2) { /* Query */
1952         const char *name;
1953 
1954         if (argc == 0)
1955             name = NULL;
1956         else {
1957             const c4_Property *propPtr;
1958 
1959             if (JimGetProperty(interp, argv[0], mk->storage, "view", &propPtr) != JIM_OK)
1960                 return JIM_ERR;
1961             name = propPtr->Name();
1962         }
1963 
1964         Jim_SetResult(interp, JimFromMkDescription(interp,
1965             mk->storage.Description(name), NULL));
1966     }
1967     else { /* Modify */
1968         char *descr;
1969         const char *name;
1970         int len, dlen;
1971 
1972         if (JimCheckMkName(interp, argv[0], "view") != JIM_OK)
1973             return JIM_ERR;
1974         name = Jim_GetString(argv[0], &len);
1975 
1976         if (JimToMkDescription(interp, argv[1], &descr) != JIM_OK)
1977             return JIM_ERR;
1978         dlen = strlen(descr);
1979 
1980         descr = (char *)Jim_Realloc(descr, dlen + len + 2);
1981         memmove(descr + len + 1, descr, dlen);
1982         memcpy(descr, name, len);
1983         descr[len] = '[';
1984         descr[len + 1 + dlen] = ']';
1985         descr[len + 1 + dlen + 1] = '\0';
1986 
1987         mk->storage.GetAs(descr);
1988 
1989         Jim_Free(descr);
1990     }
1991 
1992     return JIM_OK;
1993 }
1994 
1995 /* Store operations -------------------------------------------------------- */
1996 
storage_cmd_commit(Jim_Interp * interp,int argc,Jim_Obj * const * argv)1997 static int storage_cmd_commit(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1998 {
1999     MkStorage *mk = (MkStorage *)Jim_CmdPrivData(interp);
2000 
2001     if (mk->flags & JIM_MKFLAG_INMEMORY) {
2002         Jim_SetResultString(interp, "cannot commit an in-memory storage", -1);
2003         return JIM_ERR;
2004     }
2005     else if (mk->flags & JIM_MKFLAG_READONLY) {
2006         Jim_SetResultString(interp, "cannot commit a read-only storage", -1);
2007         return JIM_ERR;
2008     }
2009 
2010     if (mk->storage.Commit(0)) {
2011         Jim_SetEmptyResult(interp);
2012         return JIM_OK;
2013     }
2014     else {
2015         Jim_SetResultString(interp, "I/O error during commit", -1);
2016         return JIM_ERR;
2017     }
2018 }
2019 
storage_cmd_rollback(Jim_Interp * interp,int argc,Jim_Obj * const * argv)2020 static int storage_cmd_rollback(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
2021 {
2022     MkStorage *mk = (MkStorage *)Jim_CmdPrivData(interp);
2023 
2024     if (mk->flags & JIM_MKFLAG_INMEMORY) {
2025         Jim_SetResultString(interp, "cannot rollback an in-memory storage", -1);
2026         return JIM_ERR;
2027     }
2028 
2029     if (mk->storage.Rollback(0)) {
2030         Jim_SetEmptyResult(interp);
2031         return JIM_OK;
2032     }
2033     else {
2034         Jim_SetResultString(interp, "I/O error during rollback", -1);
2035         return JIM_ERR;
2036     }
2037 }
2038 
storage_cmd_close(Jim_Interp * interp,int argc,Jim_Obj * const * argv)2039 static int storage_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
2040 {
2041     return Jim_DeleteCommand(interp, argv[0]);
2042 }
2043 
2044 /* Command table ----------------------------------------------------------- */
2045 
2046 static const jim_subcmd_type storage_command_table[] = {
2047 
2048     /* Options */
2049 
2050     {   "autocommit", "?value?",
2051         storage_cmd_autocommit,
2052         0, 1,
2053         0,
2054         /*"Query or modify the auto-commit option of this storage"*/
2055     },
2056     {   "readonly", "",
2057         storage_cmd_readonly,
2058         0, 0,
2059         0,
2060         /*"Returns the read-only status of this storage"*/
2061     },
2062 
2063     /* Views */
2064 
2065     {   "views", "",
2066         storage_cmd_views,
2067         0, 0,
2068         0,
2069         /*"Returns the list of views stored here"*/
2070     },
2071     {   "view", "viewName",
2072         storage_cmd_view,
2073         1, -1,
2074         0,
2075         /*"Retrieve the view specified by viewName"*/
2076     },
2077     {   "structure", "?viewName? ?description?",
2078         storage_cmd_structure,
2079         0, 2,
2080         0,
2081         /*"Query or modify the structure of this storage"*/
2082     },
2083 
2084     /* Store operations */
2085 
2086     {   "commit", "",
2087         storage_cmd_commit,
2088         0, 0,
2089         0,
2090         /*"Commit the changes to disk"*/
2091     },
2092     {   "rollback", "",
2093         storage_cmd_rollback,
2094         0, 0,
2095         0,
2096         /*"Revert to the saved state"*/
2097     },
2098     {   "close", "",
2099         storage_cmd_close,
2100         0, 0,
2101         JIM_MODFLAG_FULLARGV,
2102         /*"Close this storage"*/
2103     },
2104 
2105     { 0 }
2106 };
2107 
JimStorageDelProc(Jim_Interp * interp,void * privData)2108 static void JimStorageDelProc(Jim_Interp *interp, void *privData)
2109 {
2110     MkStorage *mk = (MkStorage *)privData;
2111 
2112     mk->storage.~c4_Storage();
2113     mk->content.~c4_Cursor();
2114     Jim_DecrRefCount(interp, mk->filename);
2115     Jim_Free(mk);
2116 }
2117 
JimStorageSubCmdProc(Jim_Interp * interp,int argc,Jim_Obj * const * argv)2118 static int JimStorageSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
2119 {
2120     Jim_Obj *cmdObj;
2121 
2122     cmdObj = Jim_NewStringObj(interp, "mk.storage ", -1);
2123     Jim_AppendObj(interp, cmdObj, argv[1]);
2124 
2125     if (Jim_GetCommand(interp, cmdObj, 0) != NULL) {
2126         int result;
2127 
2128         Jim_Obj **objv = (Jim_Obj **)Jim_Alloc(argc * sizeof(Jim_Obj *));
2129         objv[0] = cmdObj;
2130         objv[1] = argv[0];
2131         memcpy(objv + 2, argv + 2, (argc - 2) * sizeof(Jim_Obj *));
2132 
2133         result = Jim_EvalObjVector(interp, argc, objv);
2134 
2135         Jim_Free(objv);
2136         return result;
2137     } else {
2138         Jim_FreeNewObj(interp, cmdObj);
2139         return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp,
2140             storage_command_table, argc, argv), argc, argv);
2141     }
2142 }
2143 
2144 /* -------------------------------------------------------------------------
2145  * storage ?options? ?filename?
2146  *
2147  * Creates a new metakit storage object, optionally backed by a file.
2148  *
2149  * Options apply only when filename is given; these include:
2150  *
2151  *   -readonly   Open the file in read-only mode
2152  *   -original   Open the file in read-only mode, discarding possible extends
2153  *   -extend     Open the file in extend mode
2154  *   -nocommit   Do not commit the changes when the storage is closed
2155  * ------------------------------------------------------------------------- */
2156 
JimStorageCommand(Jim_Interp * interp,int argc,Jim_Obj * const * argv)2157 static int JimStorageCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
2158 {
2159     MkStorage *mk;
2160     char buf[MK_CMD_LEN];
2161     int i, mode;
2162 
2163     static const char *const options[] = {
2164         "-readonly",
2165         "-original",
2166         "-extend",
2167         "-nocommit",
2168         0
2169     };
2170     enum {
2171         OPT_READONLY,
2172         OPT_ORIGINAL,
2173         OPT_EXTEND,
2174         OPT_NOCOMMIT
2175     };
2176     int option;
2177 
2178     mk = (MkStorage *)Jim_Alloc(sizeof(MkStorage));
2179     mk->flags = JIM_MKFLAG_AUTOCOMMIT;
2180     mode = MK_MODE_READWRITE;
2181     for (i = 1; i < argc - 1; i++ ) {
2182         if (Jim_GetEnum(interp, argv[i], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
2183             Jim_Free(mk);
2184             return JIM_ERR;
2185         }
2186 
2187         switch (option) {
2188             case OPT_READONLY:
2189                 if (mode != MK_MODE_READWRITE)
2190                     goto modeconflict;
2191 
2192                 mode = MK_MODE_READONLY;
2193                 mk->flags |= JIM_MKFLAG_READONLY;
2194                 break;
2195 
2196             case OPT_ORIGINAL:
2197                 if (mode != MK_MODE_READWRITE)
2198                     goto modeconflict;
2199 
2200                 mode = MK_MODE_ORIGINAL;
2201                 mk->flags |= JIM_MKFLAG_READONLY;
2202                 break;
2203 
2204             case OPT_EXTEND:
2205                 if (mode != MK_MODE_READWRITE)
2206                     goto modeconflict;
2207 
2208                 mode = MK_MODE_EXTEND;
2209                 mk->flags |= JIM_MKFLAG_EXTEND;
2210                 break;
2211 
2212             case OPT_NOCOMMIT:
2213                 mk->flags &= ~JIM_MKFLAG_AUTOCOMMIT;
2214                 break;
2215         }
2216     }
2217 
2218     if (argc > 1) {
2219         new(&mk->storage) c4_Storage(Jim_String(argv[argc-1]), mode);
2220 
2221         if (!mk->storage.Strategy().IsValid()) {
2222             mk->storage.~c4_Storage();
2223             Jim_Free(mk);
2224             Jim_SetResultFormatted(interp, "could not open storage \"%#s\"", argv[argc-1]);
2225             return JIM_ERR;
2226         }
2227 
2228         mk->filename = argv[argc-1];
2229 
2230         if ((mk->flags & JIM_MKFLAG_AUTOCOMMIT) && !(mk->flags & JIM_MKFLAG_READONLY))
2231             mk->storage.AutoCommit(1);
2232     }
2233     else {
2234         mk->flags |= JIM_MKFLAG_INMEMORY;
2235 
2236         new(&mk->storage) c4_Storage();
2237         mk->filename = Jim_NewEmptyStringObj(interp);
2238     }
2239     new(&mk->content) c4_Cursor(&mk->storage[0]);
2240     Jim_IncrRefCount(mk->filename);
2241 
2242     snprintf(buf, sizeof(buf), "mk.handle%ld", Jim_GetId(interp));
2243     Jim_CreateCommand(interp, buf, JimStorageSubCmdProc, mk, JimStorageDelProc);
2244     Jim_SetResultString(interp, buf, -1);
2245     return JIM_OK;
2246 
2247   modeconflict:
2248     Jim_Free(mk);
2249     Jim_SetResultString(interp, "only one of -readonly, -original and -extend may be specified", -1);
2250     return JIM_ERR;
2251 }
2252 
2253 /* -------------------------------------------------------------------------
2254  * Initialization code
2255  * ------------------------------------------------------------------------- */
2256 
Jim_mkInit(Jim_Interp * interp)2257 int Jim_mkInit(Jim_Interp *interp)
2258 {
2259     char version[MK_VERSION_SPACE];
2260 
2261     snprintf(version, MK_VERSION_SPACE, "%d.%d.%d",
2262         d4_MetakitLibraryVersion / 100,
2263         d4_MetakitLibraryVersion % 100 / 10,
2264         d4_MetakitLibraryVersion % 10);
2265 
2266     if (Jim_PackageProvide(interp, "mk", version, JIM_ERRMSG))
2267         return JIM_ERR;
2268 
2269     Jim_CreateCommand(interp, "storage", JimStorageCommand, NULL, NULL);
2270     Jim_CreateCommand(interp, "cursor", JimCursorCommand, NULL, NULL);
2271     Jim_CreateCommand(interp, "mk.view.finalizer", JimViewFinalizerProc, NULL, NULL);
2272 
2273     return JIM_OK;
2274 }
2275 
2276 } /* extern "C" */
2277