#include #include #include #include #include "jim.h" #include "jimautoconf.h" #include "jim-subcmd.h" extern "C" { /* The whole file is essentially C */ #define MK_PROPERTY_BINARY 'B' #define MK_PROPERTY_INT 'I' #define MK_PROPERTY_LONG 'L' #define MK_PROPERTY_FLOAT 'F' #define MK_PROPERTY_DOUBLE 'D' #define MK_PROPERTY_STRING 'S' #define MK_PROPERTY_VIEW 'V' #define MK_MODE_ORIGINAL -1 #define MK_MODE_READONLY 0 #define MK_MODE_READWRITE 1 #define MK_MODE_EXTEND 2 #define MK_CMD_LEN 32 #define JIM_CURSOR_SPACE (35+JIM_REFERENCE_TAGLEN + 1 + 20) #define JIM_POSITION_SPACE 32 #define MK_VERSION_SPACE 16 #define JIM_MK_DESCR_LEN 64 /* Default, will be reallocated if needed */ #define isnamech(c) ( (c) && !strchr(":,[^]!", (c)) ) #ifndef max #define max(x, y) ((x) >= (y) ? (x) : (y)) #endif /* utilities */ static int JimCheckMkName(Jim_Interp *interp, Jim_Obj *name, const char *type); static const char *JimMkTypeName(char type); static Jim_Obj *JimFromMkDescription(Jim_Interp *interp, const char *descr, const char **endPtr); static int JimToMkDescription(Jim_Interp *interp, Jim_Obj *obj, char **descrPtr); static Jim_Obj *JimGetMkValue(Jim_Interp *interp, c4_Cursor cur, const c4_Property &prop); static int JimSetMkValue(Jim_Interp *interp, c4_Cursor cur, const c4_Property &prop, Jim_Obj *obj); static int JimPipelineBoundary(int argc, Jim_Obj *const *argv); /* property object */ static Jim_Obj *JimNewPropertyObj (Jim_Interp *interp, c4_Property prop); static int JimGetProperty (Jim_Interp *interp, Jim_Obj *obj, c4_View view, const char *what, const c4_Property **propPtr); static int JimGetPropertyTyped (Jim_Interp *interp, Jim_Obj *obj, char type, const c4_Property **propPtr); static int JimGetNewProperty (Jim_Interp *interp, Jim_Obj *obj, c4_View view, char type, const c4_Property **propPtr); static int JimGetProperties (Jim_Interp *interp, int objc, Jim_Obj *const *objv, c4_View view, c4_View *propsPtr); static Jim_Obj *JimViewPropertiesList (Jim_Interp *interp, c4_View view); /* cursor object */ static int JimGetPosition (Jim_Interp *interp, Jim_Obj *obj, c4_View view, int *indexPtr); static int JimGetCursor (Jim_Interp *interp, Jim_Obj *obj, c4_Cursor *curPtr); static int JimGetCursorView (Jim_Interp *interp, Jim_Obj *obj, Jim_Obj **viewObjPtr); static int JimCursorPos (Jim_Interp *interp, Jim_Obj *obj, Jim_Obj **posObjPtr); static int JimIncrCursor (Jim_Interp *interp, Jim_Obj *obj, int offset); static int JimSeekCursor (Jim_Interp *interp, Jim_Obj *obj, Jim_Obj *posObj); /* Also accepts JIM_ERRMSG */ #define JIM_CURSOR_GET (1 << JIM_PRIV_FLAG_SHIFT) #define JIM_CURSOR_SET (2 << JIM_PRIV_FLAG_SHIFT) #define JIM_CURSOR_INSERT (4 << JIM_PRIV_FLAG_SHIFT) static int JimCheckCursor (Jim_Interp *interp, Jim_Obj *curObj, int flags); /* view handle */ static Jim_Obj *JimNewViewObj (Jim_Interp *interp, c4_View view); static int JimGetView (Jim_Interp *interp, Jim_Obj *obj, c4_View *viewPtr); static void JimPinView (Jim_Interp *interp, Jim_Obj *obj); /* ------------------------------------------------------------------------- * Utilities * ------------------------------------------------------------------------- */ static int JimCheckMkName(Jim_Interp *interp, Jim_Obj *name, const char *type) { const char *s; int i, len; s = Jim_GetString(name, &len); if (len > 0 && s[0] == '-') goto err; for (i = 0; i < len; i++) { if (!isnamech(s[i])) goto err; } return JIM_OK; err: Jim_SetResultFormatted(interp, "expected %s name but got \"%#s\"", type ? type : "property", name); return JIM_ERR; } static const char *const jim_mktype_options[] = { "-integer", "-long", "-float", "-double", "-string", "-subview", /* FIXME "-binary", */ 0 }; static const char *const jim_mktype_names[] = { "integer", "long", "float", "double", "string", "subview", /* FIXME "binary", */ 0 }; static const char jim_mktype_types[] = { MK_PROPERTY_INT, MK_PROPERTY_LONG, MK_PROPERTY_FLOAT, MK_PROPERTY_DOUBLE, MK_PROPERTY_STRING, MK_PROPERTY_VIEW, /* MK_PROPERTY_BINARY, */ }; #define JIM_MKTYPES ((int)(sizeof(jim_mktype_types) / sizeof(jim_mktype_types[0]))) static const char *JimMkTypeName(char type) { int i; for (i = 0; i < JIM_MKTYPES; i++) { if (type == jim_mktype_types[i]) return jim_mktype_names[i]; } return "(unknown type)"; } static Jim_Obj *JimFromMkDescription(Jim_Interp *interp, const char *descr, const char **endPtr) { Jim_Obj *result; const char *delim; result = Jim_NewListObj(interp, NULL, 0); for (;;) { if (*descr == ']') { descr++; break; } else if (*descr == '\0') break; else if (*descr == ',') descr++; delim = strpbrk(descr, ",:[]"); /* JimPanic((!delim, "Invalid Metakit description string")); */ Jim_ListAppendElement(interp, result, Jim_NewStringObj(interp, descr, delim - descr)); if (delim[0] == '[') { Jim_ListAppendElement(interp, result, JimFromMkDescription(interp, delim + 1, &descr)); } else if (delim[0] == ':') { Jim_ListAppendElement(interp, result, Jim_NewStringObj(interp, JimMkTypeName(delim[1]), -1)); descr = delim + 2; } else { /* Seems that Metakit never generates descriptions without type * tags, but let's handle this just to be safe */ Jim_ListAppendElement(interp, result, Jim_NewStringObj(interp, JimMkTypeName(MK_PROPERTY_STRING), -1)); } } if (endPtr) *endPtr = descr; return result; } /* This allocates the buffer once per user call and stores it in a static * variable. Recursive calls are distinguished by descrPtr == NULL. */ static int JimToMkDescription(Jim_Interp *interp, Jim_Obj *descrObj, char **descrPtr) { static char *descr, *outPtr; static int bufSize; #define ENLARGE(size) do { \ if ((descr - outPtr) + (size) > bufSize) { \ bufSize = max(2*bufSize, (descr - outPtr) + (size)); \ descr = (char *)Jim_Realloc(descr, bufSize); \ } \ } while(0) int i, count; Jim_Obj *name, *struc; const char *rep; int len; count = Jim_ListLength(interp, descrObj); if (count % 2) { Jim_SetResultString(interp, "view description must have an even number of elements", -1); return JIM_ERR; } if (descrPtr) { descr = (char *)Jim_Alloc(bufSize = JIM_MK_DESCR_LEN); outPtr = descr; } for (i = 0; i < count; i += 2) { Jim_ListIndex(interp, descrObj, i, &name, 0); Jim_ListIndex(interp, descrObj, i + 1, &struc, 0); if (JimCheckMkName(interp, name, NULL) != JIM_OK) goto err; rep = Jim_GetString(name, &len); ENLARGE(len + 3); /* At least :T, or [], */ memcpy(outPtr, rep, len); outPtr += len; if (Jim_ListLength(interp, struc) == 1) { int idx; if (Jim_GetEnum(interp, struc, jim_mktype_names, &idx, "property type", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) goto err; *outPtr++ = ':'; *outPtr++ = jim_mktype_types[idx]; } else { *outPtr++ = '['; if (JimToMkDescription(interp, struc, NULL) != JIM_OK) goto err; ENLARGE(2); /* bracket, comma */ *outPtr++ = ']'; } *outPtr++ = ','; } *(--outPtr) = '\0'; #undef ENLARGE if (descrPtr) { *descrPtr = (char *)Jim_Realloc(descr, strlen(descr) + 1); descr = NULL; /* Safety measure */ } return JIM_OK; err: if (descrPtr) Jim_Free(descr); return JIM_ERR; } static Jim_Obj *JimGetMkValue(Jim_Interp *interp, c4_Cursor cur, const c4_Property &prop) { switch (prop.Type()) { case MK_PROPERTY_INT: return Jim_NewIntObj(interp, ((c4_IntProp &)prop).Get(*cur)); case MK_PROPERTY_LONG: return Jim_NewIntObj(interp, ((c4_LongProp &)prop).Get(*cur)); case MK_PROPERTY_FLOAT: return Jim_NewDoubleObj(interp, ((c4_FloatProp &)prop).Get(*cur)); case MK_PROPERTY_DOUBLE: return Jim_NewDoubleObj(interp, ((c4_DoubleProp &)prop).Get(*cur)); case MK_PROPERTY_STRING: return Jim_NewStringObj(interp, ((c4_StringProp &)prop).Get(*cur), -1); case MK_PROPERTY_VIEW: return JimNewViewObj(interp, ((c4_ViewProp &)prop).Get(*cur)); case MK_PROPERTY_BINARY: /* FIXME */ default: /* FIXME Something more meaningful here? */ return Jim_NewEmptyStringObj(interp); } } static int JimSetMkValue(Jim_Interp *interp, c4_Cursor cur, const c4_Property &prop, Jim_Obj *obj) { switch (prop.Type()) { case MK_PROPERTY_INT: { jim_wide value; if (Jim_GetWide(interp, obj, &value) != JIM_OK) return JIM_ERR; ((c4_IntProp &)prop).Set(*cur, value); return JIM_OK; } case MK_PROPERTY_LONG: { jim_wide value; if (Jim_GetWide(interp, obj, &value) != JIM_OK) return JIM_ERR; ((c4_LongProp &)prop).Set(*cur, value); return JIM_OK; } case MK_PROPERTY_FLOAT: { double value; if (Jim_GetDouble(interp, obj, &value) != JIM_OK) return JIM_ERR; ((c4_FloatProp &)prop).Set(*cur, value); return JIM_OK; } case MK_PROPERTY_DOUBLE: { double value; if (Jim_GetDouble(interp, obj, &value) != JIM_OK) return JIM_ERR; ((c4_DoubleProp &)prop).Set(*cur, value); return JIM_OK; } case MK_PROPERTY_STRING: { int len; const char *rep; rep = Jim_GetString(obj, &len); if (len != (int)strlen(rep)) { Jim_SetResultString(interp, "null characters are not allowed in Metakit strings", -1); return JIM_ERR; } ((c4_StringProp &)prop).Set(*cur, rep); return JIM_OK; } case MK_PROPERTY_VIEW: { c4_View value; if (JimGetView(interp, obj, &value) != JIM_OK) return JIM_ERR; ((c4_ViewProp &)prop).Set(*cur, value); } case MK_PROPERTY_BINARY: /* FIXME */ default: Jim_SetResultString(interp, "unsupported Metakit type", -1); return JIM_ERR; } } static int JimPipelineBoundary(int argc, Jim_Obj *const *argv) { const char *rep; int pipe, len; for (pipe = 0; pipe < argc; pipe++) { rep = Jim_GetString(argv[pipe], &len); if (len == 1 && rep[0] == '|') break; } return pipe; } /* ------------------------------------------------------------------------- * Property object * ------------------------------------------------------------------------- */ #define JimPropertyValue(o) ((c4_Property *)((o)->internalRep.ptr)) static void FreePropertyInternalRep(Jim_Interp *interp, Jim_Obj *obj) { delete JimPropertyValue(obj); } static void DupPropertyInternalRep(Jim_Interp *interp, Jim_Obj *oldObj, Jim_Obj *newObj) { newObj->internalRep.ptr = new c4_Property(*JimPropertyValue(oldObj)); newObj->typePtr = oldObj->typePtr; } static void UpdateStringOfProperty(Jim_Obj* obj) { const char *name = JimPropertyValue(obj)->Name(); int len = strlen(name); obj->bytes = (char *) Jim_Alloc(len + 1); memcpy(obj->bytes, name, len + 1); obj->length = len; } static Jim_ObjType propertyObjType = { "mk.property", FreePropertyInternalRep, DupPropertyInternalRep, UpdateStringOfProperty, JIM_TYPE_NONE }; static int JimGetProperty(Jim_Interp *interp, Jim_Obj *obj, c4_View view, const char *name, const c4_Property **propPtr) { int index; if (obj->typePtr == &propertyObjType) { index = view.FindProperty(JimPropertyValue(obj)->GetId()); } else { if (JimCheckMkName(interp, obj, name) != JIM_OK) return JIM_ERR; index = view.FindPropIndexByName(Jim_String(obj)); } if (index != -1) { *propPtr = &view.NthProperty(index); return JIM_OK; } else { Jim_SetResultFormatted(interp, "%s \"%#s\" does not exist", name ? name : "property", obj); return JIM_ERR; } } static int JimGetPropertyTyped(Jim_Interp *interp, Jim_Obj *obj, char type, const c4_Property **propPtr) { c4_Property *prop; if (obj->typePtr == &propertyObjType) { if (JimPropertyValue(obj)->Type() != type) { /* coerce the property type */ prop = new c4_Property(type, JimPropertyValue(obj)->Name()); delete JimPropertyValue(obj); obj->internalRep.ptr = prop; } } else { if (JimCheckMkName(interp, obj, NULL) != JIM_OK) return JIM_ERR; prop = new c4_Property(type, Jim_String(obj)); Jim_FreeIntRep(interp, obj); obj->typePtr = &propertyObjType; obj->internalRep.ptr = (void *)prop; } *propPtr = JimPropertyValue(obj); return JIM_OK; } static int JimGetNewProperty(Jim_Interp *interp, Jim_Obj *obj, c4_View view, char type, const c4_Property **propPtr) { const c4_Property *newp, *prop; if (JimGetPropertyTyped(interp, obj, type, &newp) != JIM_OK) return JIM_ERR; prop = &view.NthProperty(view.AddProperty(*newp)); if (prop->Type() != newp->Type()) { Jim_SetResultFormatted(interp, "property \"%#s\" is %s, not %s", obj, JimMkTypeName(prop->Type()), JimMkTypeName(newp->Type())); return JIM_ERR; } *propPtr = prop; return JIM_OK; } static int JimGetProperties(Jim_Interp *interp, int objc, Jim_Obj *const *objv, c4_View view, c4_View *propsPtr) { int i; const c4_Property *prop; c4_View props; for (i = 0; i < objc; i++) { if (JimGetProperty(interp, objv[i], view, NULL, &prop) != JIM_OK) return JIM_ERR; props.AddProperty(*prop); } *propsPtr = props; return JIM_OK; } static Jim_Obj *JimNewPropertyObj(Jim_Interp *interp, c4_Property prop) { Jim_Obj *obj; obj = Jim_NewObj(interp); obj->typePtr = &propertyObjType; obj->bytes = NULL; obj->internalRep.ptr = new c4_Property(prop); return obj; } /* ------------------------------------------------------------------------- * Cursor object * ------------------------------------------------------------------------- */ /* Position ---------------------------------------------------------------- */ /* A normal position if endFlag == 0; otherwise an offset from end+1 (!) */ typedef struct MkPosition { int index; int endFlag; } MkPosition; /* This is mostly the same as SetIndexFromAny, but preserves more information * and allows multiple [+-]integer parts. */ static int GetPosition(Jim_Interp *interp, Jim_Obj *obj, MkPosition *posPtr) { MkPosition pos; const char *rep; char *end; int sign, offset; rep = Jim_String(obj); if (strncmp(rep, "end", 3) == 0) { pos.endFlag = 1; pos.index = -1; rep += 3; } else { pos.endFlag = 0; pos.index = strtol(rep, &end, 10); if (end == rep) goto err; rep = end; } while ((rep[0] == '+') || (rep[0] == '-')) { sign = (rep[0] == '+' ? 1 : -1); rep++; offset = strtol(rep, &end, 10); if (end == rep) goto err; pos.index += sign * offset; rep = end; } while (isspace(UCHAR(*rep))) rep++; if (*rep != '\0') goto err; *posPtr = pos; return JIM_OK; err: Jim_SetResultFormatted(interp, "expected cursor position but got \"%#s\"", obj); return JIM_ERR; } static int PositionIndex(const MkPosition *posPtr, c4_View view) { if (posPtr->endFlag) return view.GetSize() + posPtr->index; else return posPtr->index; } static int JimGetPosition(Jim_Interp *interp, Jim_Obj *obj, c4_View view, int *indexPtr) { MkPosition pos; if (GetPosition(interp, obj, &pos) != JIM_OK) return JIM_ERR; *indexPtr = PositionIndex(&pos, view); return JIM_OK; } /* Cursor type ------------------------------------------------------------- */ typedef struct MkCursor { MkPosition pos; Jim_Obj *viewObj; } MkCursor; #define JimCursorValue(obj) ((MkCursor *)(obj->internalRep.ptr)) static void FreeCursorInternalRep(Jim_Interp *interp, Jim_Obj *obj) { Jim_DecrRefCount(interp, JimCursorValue(obj)->viewObj); Jim_Free(obj->internalRep.ptr); } static void DupCursorInternalRep(Jim_Interp *interp, Jim_Obj *oldObj, Jim_Obj *newObj) { newObj->internalRep.ptr = Jim_Alloc(sizeof(MkCursor)); *JimCursorValue(newObj) = *JimCursorValue(oldObj); Jim_IncrRefCount(JimCursorValue(oldObj)->viewObj); newObj->typePtr = oldObj->typePtr; } static void UpdateStringOfCursor(Jim_Obj *obj) { char buf[JIM_CURSOR_SPACE + 1]; MkCursor *curPtr = JimCursorValue(obj); int idx, len; len = snprintf(buf, JIM_CURSOR_SPACE + 1, "%s!", Jim_String(curPtr->viewObj)); if (curPtr->pos.endFlag) { idx = curPtr->pos.index + 1; if (idx == 0) len += snprintf(buf + len, JIM_CURSOR_SPACE + 1 - len, "end"); else len += snprintf(buf + len, JIM_CURSOR_SPACE + 1 - len, "end%+d", idx); } else { len += snprintf(buf + len, JIM_CURSOR_SPACE + 1 - len, "%d", curPtr->pos.index); } obj->bytes = (char *)Jim_Alloc(len + 1); memcpy(obj->bytes, buf, len + 1); obj->length = len; } static Jim_ObjType cursorObjType = { "mk.cursor", FreeCursorInternalRep, DupCursorInternalRep, UpdateStringOfCursor, JIM_TYPE_REFERENCES }; static int SetCursorFromAny(Jim_Interp *interp, Jim_Obj *obj) { const char *rep, *delim; int len; Jim_Obj *posObj; MkCursor cur; rep = Jim_GetString(obj, &len); delim = strrchr(rep, '!'); if (!delim) { Jim_SetResultFormatted(interp, "expected cursor but got \"%#s\"", obj); return JIM_ERR; } cur.viewObj = Jim_NewStringObj(interp, rep, delim - rep); posObj = Jim_NewStringObj(interp, delim + 1, len - (delim - rep) - 1); if (GetPosition(interp, posObj, &cur.pos) != JIM_OK) { Jim_FreeNewObj(interp, posObj); Jim_FreeNewObj(interp, cur.viewObj); return JIM_ERR; } Jim_FreeIntRep(interp, obj); Jim_FreeNewObj(interp, posObj); Jim_IncrRefCount(cur.viewObj); obj->typePtr = &cursorObjType; obj->internalRep.ptr = Jim_Alloc(sizeof(MkCursor)); *JimCursorValue(obj) = cur; return JIM_OK; } /* Functions --------------------------------------------------------------- */ static int JimCursorPos(Jim_Interp *interp, Jim_Obj *obj, Jim_Obj **posObjPtr) { if (obj->typePtr != &cursorObjType && SetCursorFromAny(interp, obj) != JIM_OK) return JIM_ERR; *posObjPtr = Jim_NewStringObj(interp, strrchr(Jim_String(obj), '!') + 1, -1); return JIM_OK; } static int JimGetCursorView(Jim_Interp *interp, Jim_Obj *obj, Jim_Obj **viewObjPtr) { if (obj->typePtr != &cursorObjType && SetCursorFromAny(interp, obj) != JIM_OK) return JIM_ERR; *viewObjPtr = JimCursorValue(obj)->viewObj; return JIM_OK; } static int JimGetCursor(Jim_Interp *interp, Jim_Obj *obj, c4_Cursor *curPtr) { c4_View view; if (obj->typePtr != &cursorObjType && SetCursorFromAny(interp, obj) != JIM_OK) return JIM_ERR; if (JimGetView(interp, JimCursorValue(obj)->viewObj, &view) != JIM_OK) return JIM_ERR; if (curPtr) *curPtr = &view[PositionIndex(&JimCursorValue(obj)->pos, view)]; return JIM_OK; } static int JimIncrCursor(Jim_Interp *interp, Jim_Obj *obj, int offset) { /* JimPanic((Jim_IsShared(obj), "JimIncrCursor called with shared object")) */ if (obj->typePtr != &cursorObjType && SetCursorFromAny(interp, obj) != JIM_OK) return JIM_ERR; Jim_InvalidateStringRep(obj); JimCursorValue(obj)->pos.index += offset; return JIM_OK; } static int JimSeekCursor(Jim_Interp *interp, Jim_Obj *obj, Jim_Obj *posObj) { /* JimPanic((Jim_IsShared(obj), "JimSeekCursor called with shared object")) */ if (obj->typePtr != &cursorObjType && SetCursorFromAny(interp, obj) != JIM_OK) return JIM_ERR; Jim_InvalidateStringRep(obj); return GetPosition(interp, posObj, &JimCursorValue(obj)->pos); } static int JimCheckCursor(Jim_Interp *interp, Jim_Obj *curObj, int flags) { static c4_View nullView; c4_Cursor cur = &nullView[0]; int size; if (JimGetCursor(interp, curObj, &cur) != JIM_OK) return JIM_ERR; size = (*cur).Container().GetSize(); if ((flags & JIM_CURSOR_GET) && (cur._index < 0 || cur._index >= size)) { if (flags & JIM_ERRMSG) { Jim_SetResultFormatted(interp, "cursor \"%#s\" does not point to an existing row", curObj); } return JIM_ERR; } else if ((flags & JIM_CURSOR_SET) && cur._index < 0) { if (flags & JIM_ERRMSG) { Jim_SetResultFormatted(interp, "cursor \"%#s\" points before start of view", curObj); } return JIM_ERR; } else if ((flags & JIM_CURSOR_INSERT) && (cur._index < 0 || cur._index > size)) { if (flags & JIM_ERRMSG) { Jim_SetResultFormatted(interp, "cursor \"%#s\" does not point to a valid insert position", curObj); } return JIM_ERR; } return JIM_OK; } /* Records ----------------------------------------------------------------- */ static int cursor_cmd_get(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { c4_View view; c4_Cursor cur = &view[0]; if (JimGetCursor(interp, argv[0], &cur) != JIM_OK) return JIM_ERR; if (JimCheckCursor(interp, argv[0], JIM_ERRMSG | JIM_CURSOR_GET) != JIM_OK) return JIM_ERR; view = (*cur).Container(); if (argc == 1) { /* Return all properties */ int i, count; Jim_Obj *result; result = Jim_NewListObj(interp, NULL, 0); count = view.NumProperties(); for (i = 0; i < count; i++) { c4_Property prop = view.NthProperty(i); Jim_ListAppendElement(interp, result, JimNewPropertyObj(interp, prop)); Jim_ListAppendElement(interp, result, JimGetMkValue(interp, cur, prop)); } Jim_SetResult(interp, result); return JIM_OK; } else { /* Return a single property */ const c4_Property *propPtr; int pipe; pipe = JimPipelineBoundary(argc, argv); if (pipe == 2) { /* No type annotation, existing property */ if (JimGetProperty(interp, argv[1], view, NULL, &propPtr) != JIM_OK) return JIM_ERR; } else if (pipe == 3) { /* Explicit type annotation; the property may be new */ int idx; if (Jim_GetEnum(interp, argv[1], jim_mktype_options, &idx, "property type", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) return JIM_ERR; if (JimGetNewProperty(interp, argv[2], view, jim_mktype_types[idx], &propPtr) != JIM_OK) return JIM_ERR; } else { Jim_WrongNumArgs(interp, 0, NULL, "cursor get ?-type? ?prop?"); return JIM_ERR; } Jim_SetResult(interp, JimGetMkValue(interp, cur, *propPtr)); if (pipe == argc) return JIM_OK; else return Jim_EvalObjPrefix(interp, Jim_GetResult(interp), argc - pipe - 1, argv + pipe + 1); } } static int cursor_cmd_set(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { c4_View view; c4_Cursor cur = &view[0]; const c4_Property *propPtr; int i, oldSize; if (JimGetCursor(interp, argv[0], &cur) != JIM_OK) return JIM_ERR; if (JimCheckCursor(interp, argv[0], JIM_ERRMSG | JIM_CURSOR_SET) != JIM_OK) return JIM_ERR; view = (*cur).Container(); oldSize = view.GetSize(); if (cur._index >= oldSize) view.SetSize(cur._index + 1); if (argc == 2) { /* Update everything except subviews from a dictionary in argv[1]. * No new properties are permitted. */ int objc; Jim_Obj **objv; if (Jim_DictPairs(interp, argv[1], &objv, &objc) != JIM_OK) goto err; for (i = 0; i < objc; i += 2) { if (JimGetProperty(interp, objv[i], view, NULL, &propPtr) != JIM_OK || JimSetMkValue(interp, cur, *propPtr, objv[i+1]) != JIM_OK) { Jim_Free(objv); goto err; } } } else { /* Update everything from argv[1..]. New properties are permitted if * explicitly typed. */ for (i = 1; i < argc; i += 2) { if (Jim_String(argv[i])[0] == '-') { int idx; if (i + 2 >= argc) { Jim_WrongNumArgs(interp, 2, argv, "?-type? prop value ?...?"); goto err; } if (Jim_GetEnum(interp, argv[i], jim_mktype_options, &idx, "property type", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) goto err; if (JimGetNewProperty(interp, argv[i+1], view, jim_mktype_types[idx], &propPtr) != JIM_OK) goto err; i++; } else { if (i + 1 >= argc) { Jim_WrongNumArgs(interp, 2, argv, "?-type? prop value ?...?"); goto err; } if (JimGetProperty(interp, argv[i], view, NULL, &propPtr) != JIM_OK) goto err; } if (JimSetMkValue(interp, cur, *propPtr, argv[i+1]) != JIM_OK) goto err; } } return JIM_OK; err: view.SetSize(oldSize); return JIM_ERR; } static int cursor_cmd_insert(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { c4_View view; c4_Cursor cur = &view[0]; jim_wide count; if (JimGetCursor(interp, argv[0], &cur) != JIM_OK) return JIM_ERR; if (JimCheckCursor(interp, argv[0], JIM_ERRMSG | JIM_CURSOR_INSERT) != JIM_OK) return JIM_ERR; view = (*cur).Container(); if (argc == 1) count = 1; else { if (Jim_GetWide(interp, argv[1], &count) != JIM_OK) return JIM_ERR; } if (count > 0) { c4_Row empty; view.InsertAt(cur._index, empty, (int)count); } Jim_SetEmptyResult(interp); return JIM_OK; } static int cursor_cmd_remove(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { c4_View view; c4_Cursor cur = &view[0]; int pos; jim_wide count; if (JimGetCursor(interp, argv[0], &cur) != JIM_OK) return JIM_ERR; if (JimCheckCursor(interp, argv[0], JIM_ERRMSG | JIM_CURSOR_SET) != JIM_OK) return JIM_ERR; view = (*cur).Container(); pos = cur._index; if (argc == 1) count = 1; else { if (Jim_GetWide(interp, argv[1], &count) != JIM_OK) return JIM_ERR; } if (pos + count < view.GetSize()) count = view.GetSize() - pos; if (pos < view.GetSize()) view.RemoveAt(pos, (int)count); return JIM_OK; } /* Attributes -------------------------------------------------------------- */ static int cursor_cmd_view(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_Obj *viewObj; if (JimGetCursorView(interp, argv[0], &viewObj) != JIM_OK) return JIM_ERR; JimPinView(interp, viewObj); Jim_SetResult(interp, viewObj); return JIM_OK; } /* Positioning ------------------------------------------------------------- */ static int cursor_cmd_tell(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { if (argc == 1) { Jim_Obj *result; if (JimCursorPos(interp, argv[0], &result) != JIM_OK) return JIM_ERR; Jim_SetResult(interp, result); } else { static c4_View nullView; c4_Cursor cur = &nullView[0]; if (!Jim_CompareStringImmediate(interp, argv[0], "-absolute")) { Jim_SetResultFormatted(interp, "bad option \"%#s\": must be -absolute", argv[0]); return JIM_ERR; } if (JimGetCursor(interp, argv[1], &cur) != JIM_OK) return JIM_ERR; Jim_SetResultInt(interp, cur._index); } return JIM_OK; } static int cursor_cmd_validfor(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { static const char *options[] = { "get", "set", "insert", "remove", 0 }; static int optflags[] = { JIM_CURSOR_GET, JIM_CURSOR_SET, JIM_CURSOR_INSERT, JIM_CURSOR_SET }; int idx; if (argc == 1) idx = 0; else { if (Jim_GetEnum(interp, argv[0], options, &idx, NULL, JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) return JIM_ERR; } if (JimGetCursor(interp, argv[argc-1], NULL) != JIM_OK) return JIM_ERR; Jim_SetResultBool(interp, JimCheckCursor(interp, argv[argc-1], optflags[idx]) == JIM_OK); return JIM_OK; } static int cursor_cmd_seek(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_Obj *curObj; curObj = Jim_GetVariable(interp, argv[0], JIM_ERRMSG | JIM_UNSHARED); if (curObj == NULL) return JIM_ERR; if (JimSeekCursor(interp, curObj, argv[1]) != JIM_OK) return JIM_ERR; Jim_SetResult(interp, curObj); return JIM_OK; } static int cursor_cmd_incr(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_Obj *curObj; jim_wide offset; if (argc == 1) offset = 1; else { if (Jim_GetWide(interp, argv[1], &offset) != JIM_OK) return JIM_ERR; } curObj = Jim_GetVariable(interp, argv[0], JIM_ERRMSG | JIM_UNSHARED); if (curObj == NULL) return JIM_ERR; if (JimIncrCursor(interp, curObj, (int)offset) != JIM_OK) return JIM_ERR; Jim_SetResult(interp, curObj); return JIM_OK; } /* Command table ----------------------------------------------------------- */ static const jim_subcmd_type cursor_command_table[] = { /* Records */ { "get", "cur ?-type? ?prop?", cursor_cmd_get, 1, -1, 0, /*"Get the whole record or a specific property at the cursor"*/ }, { "set", "cur [dict | ?-type? field value ?...?]", cursor_cmd_set, 1, -1, 0, /*"Update the record at the cursor"*/ }, { "insert", "cur ?count?", cursor_cmd_insert, 1, 2, 0, /*"Insert a specified number of empty rows at the cursor (default 1)"*/ }, { "remove", "cur ?count?", cursor_cmd_remove, 1, 2, 0, /*"Remove a specified number of rows at the cursor (default 1)"*/ }, /* Attributes */ { "view", "cur", cursor_cmd_view, 1, 1, 0, /*"Get the view the cursor points into"*/ }, /* Positioning */ { "tell", "?-absolute? cur", cursor_cmd_tell, 1, 2, 0, /*"Get the position of the cursor"*/ }, { "validfor", "?command? cur", cursor_cmd_validfor, 1, 2, 0, /*"Checks if the cursor is valid for get (default), set or insert commands"*/ }, { "seek", "curVar index", cursor_cmd_seek, 2, 2, 0, /*"Seek to the specified index in the view"*/ }, { "incr", "curVar ?offset?", cursor_cmd_incr, 1, 2, 0, /*"Move the cursor offset records from its current position (default 1)"*/ }, { 0 } }; static int JimCursorCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_Obj *cmdObj; if (argc < 2) { Jim_WrongNumArgs(interp, 1, argv, "command ..."); return JIM_ERR; } cmdObj = Jim_NewStringObj(interp, "cursor ", -1); Jim_AppendObj(interp, cmdObj, argv[1]); if (Jim_GetCommand(interp, cmdObj, 0) != NULL) return Jim_EvalObjPrefix(interp, cmdObj, argc - 2, argv + 2); else { Jim_FreeNewObj(interp, cmdObj); return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, cursor_command_table, argc, argv), argc, argv); } } /* ------------------------------------------------------------------------- * View handle * ------------------------------------------------------------------------- */ /* Views aren't really Jim objects; instead, they are Tk-style commands with * oo.tcl-like lifetime management. Additionally, all views are initially * created as one-shot, meaning that they die after one command. Call * JimPinView to make a view object persistent. * * It is valid to rename a view in the Tcl land, but by doing this you take * the responsibility of destroying the object when it's no longer needed. * Any cursors that pointed into the view become invalid. */ static int JimViewSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv); static int JimOneShotViewSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv); /* Unary operations -------------------------------------------------------- */ #define UNOP(name, Method) \ static int view_cmd_##name(Jim_Interp *interp, int argc, Jim_Obj *const *argv) \ { \ const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp); \ \ Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->Method())); \ return JIM_OK; \ } UNOP(copy, Duplicate) UNOP(clone, Clone) UNOP(unique, Unique) #undef UNOP static int view_cmd_blocked(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp); if (viewPtr->GetSize() != 1 || strcmp(viewPtr->NthProperty(0).Name(), "_B") != 0 || viewPtr->NthProperty(0).Type() != MK_PROPERTY_VIEW) { Jim_SetResultString(interp, "blocked view must have exactly one subview property called _B", -1); return JIM_ERR; } Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->Blocked())); return JIM_OK; } /* Binary operations ------------------------------------------------------- */ #define BINOP(name, Method) \ static int view_cmd_##name(Jim_Interp *interp, int argc, Jim_Obj *const *argv) \ { \ const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp); \ c4_View otherView; \ \ if (JimGetView(interp, argv[0], &otherView) != JIM_OK) \ return JIM_ERR; \ \ Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->Method(otherView))); \ return JIM_OK; \ } BINOP(pair, Pair) BINOP(concat, Concat) BINOP(product, Product) BINOP(union, Union) BINOP(intersect, Intersect) BINOP(minus, Minus) BINOP(different, Different) #undef BINOP /* Projections ------------------------------------------------------------- */ static int view_cmd_project(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp); c4_View props; if (JimGetProperties(interp, argc, argv, *viewPtr, &props) != JIM_OK) return JIM_ERR; Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->Project(props))); return JIM_OK; } static int view_cmd_without(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp); c4_View props; if (JimGetProperties(interp, argc, argv, *viewPtr, &props) != JIM_OK) return JIM_ERR; Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->ProjectWithout(props))); return JIM_OK; } static int view_cmd_range(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp); int start, end; jim_wide step; if (JimGetPosition(interp, argv[0], *viewPtr, &start) != JIM_OK || JimGetPosition(interp, argv[1], *viewPtr, &end) != JIM_OK) { return JIM_ERR; } if (argc == 2) step = 1; else if (Jim_GetWide(interp, argv[2], &step) != JIM_OK) return JIM_ERR; Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->Slice(start, end + 1, (int)step))); return JIM_OK; } /* Ordering ---------------------------------------------------------------- */ static int view_cmd_sort(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp); c4_View sortProps, revProps; const c4_Property *propPtr; int i, len; const char *rep; int reverse; Jim_Obj *propObj; /* Special case: property names may be preceded with a dash. Use * a temporary object in this case. */ for (i = 0; i < argc; i++) { propObj = argv[i]; rep = Jim_GetString(argv[i], &len); reverse = (len > 0 && rep[0] == '-'); if (reverse) propObj = Jim_NewStringObj(interp, rep + 1, len - 1); if (JimGetProperty(interp, propObj, *viewPtr, NULL, &propPtr) != JIM_OK) { if (reverse) Jim_FreeNewObj(interp, propObj); return JIM_ERR; } sortProps.AddProperty(*propPtr); if (reverse) { revProps.AddProperty(*propPtr); Jim_FreeNewObj(interp, propObj); } } if (sortProps.GetSize() == 0) Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->Sort())); else if (revProps.GetSize() == 0) Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->SortOn(sortProps))); else Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->SortOnReverse(sortProps, revProps))); return JIM_OK; } /* Metakit core seems to be doing something similar for SortOn, but neither * Ordered nor Hash use it, for unknown reason. */ static int BubbleProperties(Jim_Interp *interp, c4_View orig, int objc, Jim_Obj *const *objv, c4_View *projPtr) { c4_View proj; const c4_Property *propPtr; int i, count; for (i = 0; i < objc; i++) { if (JimGetProperty(interp, objv[i], orig, NULL, &propPtr) != JIM_OK) return JIM_ERR; proj.AddProperty(*propPtr); } count = orig.NumProperties(); for (i = 0; i < count; i++) proj.AddProperty(orig.NthProperty(i)); *projPtr = proj; return JIM_OK; } static int view_cmd_ordered(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp); c4_View proj; if (BubbleProperties(interp, *viewPtr, argc, argv, &proj) != JIM_OK) return JIM_ERR; Jim_SetResult(interp, JimNewViewObj(interp, proj.Ordered(argc))); return JIM_OK; } static int view_cmd_hash(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp); c4_View hash, proj; if (JimGetView(interp, argv[0], &hash) != JIM_OK) return JIM_ERR; if (hash.GetSize() != 2 || strcmp(hash.NthProperty(0).Name(), "_H") != 0 || hash.NthProperty(0).Type() != MK_PROPERTY_INT || strcmp(hash.NthProperty(1).Name(), "_R") != 0 || hash.NthProperty(1).Type() != MK_PROPERTY_INT) /* Ouch. */ { Jim_SetResultString(interp, "hash view must be laid out as {_H integer _R integer}", -1); return JIM_ERR; } if (BubbleProperties(interp, *viewPtr, argc - 1, argv + 1, &proj) != JIM_OK) return JIM_ERR; Jim_SetResult(interp, JimNewViewObj(interp, proj.Hash(hash, argc - 1))); return JIM_OK; } /* Relational operations --------------------------------------------------- */ static int view_cmd_join(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp); c4_View other, props; int outer, off; if (JimGetView(interp, argv[0], &other) != JIM_OK) return JIM_ERR; off = 1; outer = 0; if (Jim_CompareStringImmediate(interp, argv[1], "-outer")) { off++; outer = 1; } if (JimGetProperties(interp, argc - off, argv + off, *viewPtr, &props) != JIM_OK) return JIM_ERR; Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->Join(props, other, outer))); return JIM_OK; } static int view_cmd_group(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp); const c4_Property *subviewPtr; c4_View props; if (JimGetPropertyTyped(interp, argv[0], MK_PROPERTY_VIEW, &subviewPtr) != JIM_OK) return JIM_ERR; if (JimGetProperties(interp, argc - 1, argv + 1, *viewPtr, &props) != JIM_OK) return JIM_ERR; Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->GroupBy(props, *(c4_ViewProp *)subviewPtr))); return JIM_OK; } static int view_cmd_flatten(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp); const c4_Property *subviewPtr; if (JimGetProperty(interp, argv[0], *viewPtr, NULL, &subviewPtr) != JIM_OK) return JIM_ERR; if (subviewPtr->Type() != MK_PROPERTY_VIEW) { Jim_SetResultFormatted(interp, "expected a subview property but got %s one", JimMkTypeName(subviewPtr->Type())); return JIM_ERR; } Jim_SetResult(interp, JimNewViewObj(interp, viewPtr->JoinProp(*(c4_ViewProp *)subviewPtr))); return JIM_OK; } /* View queries ------------------------------------------------------------ */ static int view_cmd_properties(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const c4_View *viewPtr = (const c4_View *) Jim_CmdPrivData(interp); Jim_SetResult(interp, JimViewPropertiesList(interp, *viewPtr)); return JIM_OK; } static int view_cmd_size(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const c4_View *viewPtr = (const c4_View *) Jim_CmdPrivData(interp); Jim_SetResultInt(interp, viewPtr->GetSize()); return JIM_OK; } static int view_cmd_resize(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { c4_View *view = (c4_View *) Jim_CmdPrivData(interp); jim_wide size; if (Jim_GetWide(interp, argv[0], &size) != JIM_OK) return JIM_ERR; if (size < 0 || size > INT_MAX) { Jim_SetResultFormatted(interp, "view size \"%#s\" is out of range", argv[0]); return JIM_ERR; } view->SetSize((int)size); Jim_SetResult(interp, argv[0]); return JIM_OK; } static int view_cmd_type(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { const c4_View *viewPtr = (const c4_View *)Jim_CmdPrivData(interp); if (argc == 1) { const c4_Property *propPtr; if (JimGetProperty(interp, argv[0], *viewPtr, NULL, &propPtr) != JIM_OK) return JIM_ERR; Jim_SetResultString(interp, JimMkTypeName(propPtr->Type()), -1); } else { Jim_Obj *result; int i, count; result = Jim_NewListObj(interp, NULL, 0); count = viewPtr->NumProperties(); for (i = 0; i < count; i++) { c4_Property prop = viewPtr->NthProperty(i); Jim_ListAppendElement(interp, result, JimNewPropertyObj(interp, prop)); Jim_ListAppendElement(interp, result, Jim_NewStringObj(interp, JimMkTypeName(prop.Type()), -1)); } Jim_SetResult(interp, result); } return JIM_OK; } /* View lifetime ----------------------------------------------------------- */ static int view_cmd_pin(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { JimPinView(interp, argv[0]); Jim_SetResult(interp, argv[0]); return JIM_OK; } static int view_cmd_as(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { JimPinView(interp, argv[0]); Jim_SetVariable(interp, argv[2], argv[0]); Jim_SetResult(interp, argv[0]); return JIM_OK; } static int view_cmd_destroy(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_DeleteCommand(interp, argv[0]); return JIM_OK; } /* Command table ----------------------------------------------------------- */ static const jim_subcmd_type view_command_table[] = { /* Unary operations */ { "copy", "", view_cmd_copy, 0, 0, 0, /*"Create a copy of the view with exactly the same data"*/ }, { "clone", "", view_cmd_clone, 0, 0, 0, /*"Create an empty view with the same properties as this one"*/ }, { "unique", "", view_cmd_unique, 0, 0, 0, /*"Derived view without any duplicate rows (read-only, no change notifications)"*/ }, { "blocked", "", view_cmd_blocked, 0, 0, 0, /*"Build a scalable \"blocked\" out of a view with a single subview property called _B"*/ }, /* Binary operations */ #define BINOP(name, descr) \ { #name, "otherView", \ view_cmd_##name, \ 1, 1, 0, \ } BINOP(pair, "Pairwise concatenation of two views"), BINOP(concat, "Concatenation of two views; unlike union, doesn't remove duplicates"), BINOP(product, "Cartesian product of two views, i.e. every row in view paired with every row in otherView"), /* Set operations */ #define SETOP(name, descr) BINOP(name, descr "; works only if all the rows are unique") SETOP(union, "Set union of two views (read-only, no change notifications)"), SETOP(intersect, "Set intersection of two views"), SETOP(different, "Symmetric difference of two views"), SETOP(minus, "Set minus, i.e. all rows from view not in otherView"), #undef SETOP #undef BINOP /* Projections and selections */ { "project", "prop ?prop ...?", view_cmd_project, 1, -1, 0, /*"View projection: only the specified properties, in the specified order"*/ }, { "without", "prop ?prop ...?", view_cmd_without, 1, -1, 0, /*"View projection: remove the specified properties"*/ }, { "range", "first last ?step?", view_cmd_range, 2, 3, 0, /*"Range or slice of the view (read-write, no change notifications)"*/ }, /* Ordering */ { "sort", "?[prop|-prop] ...?", view_cmd_sort, 0, -1, 0, /*"Derived view sorted on the specified properties (in order), or on all properties"*/ }, { "ordered", "prop ?prop ...?", view_cmd_ordered, 1, -1, 0, /*"Consider the underlying view ordered on the specified properties"*/ }, { "hash", "hashView prop ?prop ...?", view_cmd_hash, 2, -1, 0, /*"Mapped view maintaining a hash table on the key consisting of the specified properties"*/ }, /* Relational operations */ { "join", "view ?-outer? prop ?prop ...?", view_cmd_join, 2, -1, 0, /*"Relational join with view on the specified properties"*/ }, { "group", "subviewName prop ?prop ...?", view_cmd_group, 1, -1, 0, /*"Group rows with equal specified properties, move all other properties into subview"*/ }, { "flatten", "subviewProp", view_cmd_flatten, 1, 1, 0, /*"Flatten the specified subview; the inverse of group"*/ }, /* Attributes */ { "properties", "", view_cmd_properties, 0, 0, 0, /*"List the properties in this view"*/ }, { "size", "", view_cmd_size, 0, 0, 0, /*"Return the number of records in the view"*/ }, { "resize", "newSize", view_cmd_resize, 1, 1, 0, /*"Set the number of records in the view"*/ }, { "type", "?prop?", view_cmd_type, 0, 1, 0, /*"Return the type of an existing property, or of all properties"*/ }, /* Lifetime management */ { "pin", "", view_cmd_pin, 0, 0, JIM_MODFLAG_FULLARGV, /*"Marks the view as persistent"*/ }, { "as", "varName", view_cmd_as, 1, 1, JIM_MODFLAG_FULLARGV, /*"Marks the view as persistent and assigns it to the given variable"*/ }, { "destroy", "", view_cmd_destroy, 0, 0, JIM_MODFLAG_FULLARGV, /*"Destroys the view explicitly"*/ }, { 0 } }; static void JimViewDelProc(Jim_Interp *interp, void *privData) { delete (c4_View *)privData; } static int JimViewSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { int pipe, result; Jim_Obj *cmdObj; pipe = JimPipelineBoundary(argc, argv); if (pipe < 1) { Jim_WrongNumArgs(interp, 1, argv, "command ..."); return JIM_ERR; } /* Check for a Tcl command first, and try builtins afterwards. * We have to do it in this order so that Jim_ParseSubCmd isn't too greedy * about abbreviations, and still it can't now detect ambigous abbrevs * properly :( Tcl commands cannot be abbreviated at all. */ cmdObj = Jim_NewStringObj(interp, "mk.view ", -1); Jim_AppendObj(interp, cmdObj, argv[1]); /* The command will be cached even though we discard the result */ if (Jim_GetCommand(interp, cmdObj, 0) != NULL) { /* Shuffle the arguments: $view cmd args... => {mk.view cmd} $view args... */ Jim_Obj **objv = (Jim_Obj **)Jim_Alloc(pipe * sizeof(Jim_Obj *)); objv[0] = cmdObj; objv[1] = argv[0]; memcpy(objv + 2, argv + 2, (pipe - 2) * sizeof(Jim_Obj *)); result = Jim_EvalObjVector(interp, pipe, objv); Jim_Free(objv); } else { Jim_FreeNewObj(interp, cmdObj); result = Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, view_command_table, pipe, argv), pipe, argv); } if (result != JIM_OK || pipe == argc) return result; else return Jim_EvalObjPrefix(interp, Jim_GetResult(interp), argc - pipe - 1, argv + pipe + 1); } static int JimOneShotViewSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { int result; Jim_Cmd *cmd; result = JimViewSubCmdProc(interp, argc, argv); cmd = Jim_GetCommand(interp, argv[0], 0); if (cmd && !cmd->isproc && cmd->u.native.cmdProc == JimOneShotViewSubCmdProc) Jim_DeleteCommand(interp, argv[0]); return result; } static int JimViewFinalizerProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { /* We won't succeed here if the user renamed the command, and this is right */ Jim_DeleteCommand(interp, argv[1]); return JIM_OK; } static Jim_Obj *JimNewViewObj(Jim_Interp *interp, c4_View view) { Jim_Obj *tag, *ref; tag = Jim_NewStringObj(interp, "mk.view", -1); ref = Jim_NewReference(interp, tag, tag, Jim_NewStringObj(interp, "mk.view.finalizer", -1)); Jim_CreateCommand(interp, Jim_String(ref), JimOneShotViewSubCmdProc, new c4_View(view), JimViewDelProc); return ref; } static int JimGetView(Jim_Interp *interp, Jim_Obj *obj, c4_View *viewPtr) { Jim_Cmd *cmd = Jim_GetCommand(interp, obj, 0); if (cmd == NULL || cmd->isproc || cmd->u.native.delProc != JimViewDelProc) { Jim_SetResultFormatted(interp, "invalid view object \"%#s\"", obj); return JIM_ERR; } *viewPtr = *(c4_View *)cmd->u.native.privData; return JIM_OK; } /* Only call this against known view objects. */ static void JimPinView(Jim_Interp *interp, Jim_Obj *obj) { Jim_Cmd *cmd = Jim_GetCommand(interp, obj, 0); /* JimPanic((cmd == NULL, "JimPinView called against non-view")) JimPanic((cmd->u.native.delProc != JimViewDelProc, "JimPinView called against non-view")) */ cmd->u.native.cmdProc = JimViewSubCmdProc; } static Jim_Obj *JimViewPropertiesList(Jim_Interp *interp, c4_View view) { int i, count; Jim_Obj *result; result = Jim_NewListObj(interp, NULL, 0); count = view.NumProperties(); for (i = 0; i < count; i++) { Jim_ListAppendElement(interp, result, Jim_NewStringObj(interp, view.NthProperty(i).Name(), -1)); } return result; } /* ---------------------------------------------------------------------------- * Storage handle * ---------------------------------------------------------------------------- */ /* These are also commands, like views, but must be managed explicitly by the * user. Quite like file handles, actually. */ typedef struct MkStorage { unsigned flags; Jim_Obj *filename; c4_Storage storage; c4_Cursor content; } MkStorage; #define JIM_MKFLAG_INMEMORY 0x0001 #define JIM_MKFLAG_READONLY 0x0002 #define JIM_MKFLAG_EXTEND 0x0004 #define JIM_MKFLAG_AUTOCOMMIT 0x0008 /* Attributes -------------------------------------------------------------- */ static int storage_cmd_autocommit(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { MkStorage *mk = (MkStorage *)Jim_CmdPrivData(interp); if (argc == 1) { jim_wide flag; if (Jim_GetWide(interp, argv[0], &flag) != JIM_OK) return JIM_ERR; if (flag) mk->flags |= JIM_MKFLAG_AUTOCOMMIT; else mk->flags &= ~JIM_MKFLAG_AUTOCOMMIT; mk->storage.AutoCommit(flag); } Jim_SetResultBool(interp, (mk->flags & JIM_MKFLAG_AUTOCOMMIT) != 0); return JIM_OK; } static int storage_cmd_readonly(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { MkStorage *mk = (MkStorage *)Jim_CmdPrivData(interp); Jim_SetResultBool(interp, (mk->flags & JIM_MKFLAG_READONLY) != 0); return JIM_OK; } /* Views ------------------------------------------------------------------- */ static int storage_cmd_views(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { MkStorage *mk = (MkStorage *)Jim_CmdPrivData(interp); Jim_SetResult(interp, JimViewPropertiesList(interp, mk->storage)); return JIM_OK; } static int storage_cmd_view(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { MkStorage *mk = (MkStorage *)Jim_CmdPrivData(interp); const c4_Property *propPtr; if (JimGetProperty(interp, argv[0], mk->storage, "view", &propPtr) != JIM_OK) return JIM_ERR; Jim_SetResult(interp, JimGetMkValue(interp, mk->content, *propPtr)); if (argc == 1) return JIM_OK; else { if (!Jim_CompareStringImmediate(interp, argv[1], "|")) { Jim_SetResultFormatted(interp, "expected start of a pipeline but got \"%#s\"", argv[1]); return JIM_ERR; } return Jim_EvalObjPrefix(interp, Jim_GetResult(interp), argc - 2, argv + 2); } } static int storage_cmd_structure(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { MkStorage *mk = (MkStorage *)Jim_CmdPrivData(interp); if (argc < 2) { /* Query */ const char *name; if (argc == 0) name = NULL; else { const c4_Property *propPtr; if (JimGetProperty(interp, argv[0], mk->storage, "view", &propPtr) != JIM_OK) return JIM_ERR; name = propPtr->Name(); } Jim_SetResult(interp, JimFromMkDescription(interp, mk->storage.Description(name), NULL)); } else { /* Modify */ char *descr; const char *name; int len, dlen; if (JimCheckMkName(interp, argv[0], "view") != JIM_OK) return JIM_ERR; name = Jim_GetString(argv[0], &len); if (JimToMkDescription(interp, argv[1], &descr) != JIM_OK) return JIM_ERR; dlen = strlen(descr); descr = (char *)Jim_Realloc(descr, dlen + len + 2); memmove(descr + len + 1, descr, dlen); memcpy(descr, name, len); descr[len] = '['; descr[len + 1 + dlen] = ']'; descr[len + 1 + dlen + 1] = '\0'; mk->storage.GetAs(descr); Jim_Free(descr); } return JIM_OK; } /* Store operations -------------------------------------------------------- */ static int storage_cmd_commit(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { MkStorage *mk = (MkStorage *)Jim_CmdPrivData(interp); if (mk->flags & JIM_MKFLAG_INMEMORY) { Jim_SetResultString(interp, "cannot commit an in-memory storage", -1); return JIM_ERR; } else if (mk->flags & JIM_MKFLAG_READONLY) { Jim_SetResultString(interp, "cannot commit a read-only storage", -1); return JIM_ERR; } if (mk->storage.Commit(0)) { Jim_SetEmptyResult(interp); return JIM_OK; } else { Jim_SetResultString(interp, "I/O error during commit", -1); return JIM_ERR; } } static int storage_cmd_rollback(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { MkStorage *mk = (MkStorage *)Jim_CmdPrivData(interp); if (mk->flags & JIM_MKFLAG_INMEMORY) { Jim_SetResultString(interp, "cannot rollback an in-memory storage", -1); return JIM_ERR; } if (mk->storage.Rollback(0)) { Jim_SetEmptyResult(interp); return JIM_OK; } else { Jim_SetResultString(interp, "I/O error during rollback", -1); return JIM_ERR; } } static int storage_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { return Jim_DeleteCommand(interp, argv[0]); } /* Command table ----------------------------------------------------------- */ static const jim_subcmd_type storage_command_table[] = { /* Options */ { "autocommit", "?value?", storage_cmd_autocommit, 0, 1, 0, /*"Query or modify the auto-commit option of this storage"*/ }, { "readonly", "", storage_cmd_readonly, 0, 0, 0, /*"Returns the read-only status of this storage"*/ }, /* Views */ { "views", "", storage_cmd_views, 0, 0, 0, /*"Returns the list of views stored here"*/ }, { "view", "viewName", storage_cmd_view, 1, -1, 0, /*"Retrieve the view specified by viewName"*/ }, { "structure", "?viewName? ?description?", storage_cmd_structure, 0, 2, 0, /*"Query or modify the structure of this storage"*/ }, /* Store operations */ { "commit", "", storage_cmd_commit, 0, 0, 0, /*"Commit the changes to disk"*/ }, { "rollback", "", storage_cmd_rollback, 0, 0, 0, /*"Revert to the saved state"*/ }, { "close", "", storage_cmd_close, 0, 0, JIM_MODFLAG_FULLARGV, /*"Close this storage"*/ }, { 0 } }; static void JimStorageDelProc(Jim_Interp *interp, void *privData) { MkStorage *mk = (MkStorage *)privData; mk->storage.~c4_Storage(); mk->content.~c4_Cursor(); Jim_DecrRefCount(interp, mk->filename); Jim_Free(mk); } static int JimStorageSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_Obj *cmdObj; cmdObj = Jim_NewStringObj(interp, "mk.storage ", -1); Jim_AppendObj(interp, cmdObj, argv[1]); if (Jim_GetCommand(interp, cmdObj, 0) != NULL) { int result; Jim_Obj **objv = (Jim_Obj **)Jim_Alloc(argc * sizeof(Jim_Obj *)); objv[0] = cmdObj; objv[1] = argv[0]; memcpy(objv + 2, argv + 2, (argc - 2) * sizeof(Jim_Obj *)); result = Jim_EvalObjVector(interp, argc, objv); Jim_Free(objv); return result; } else { Jim_FreeNewObj(interp, cmdObj); return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, storage_command_table, argc, argv), argc, argv); } } /* ------------------------------------------------------------------------- * storage ?options? ?filename? * * Creates a new metakit storage object, optionally backed by a file. * * Options apply only when filename is given; these include: * * -readonly Open the file in read-only mode * -original Open the file in read-only mode, discarding possible extends * -extend Open the file in extend mode * -nocommit Do not commit the changes when the storage is closed * ------------------------------------------------------------------------- */ static int JimStorageCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { MkStorage *mk; char buf[MK_CMD_LEN]; int i, mode; static const char *const options[] = { "-readonly", "-original", "-extend", "-nocommit", 0 }; enum { OPT_READONLY, OPT_ORIGINAL, OPT_EXTEND, OPT_NOCOMMIT }; int option; mk = (MkStorage *)Jim_Alloc(sizeof(MkStorage)); mk->flags = JIM_MKFLAG_AUTOCOMMIT; mode = MK_MODE_READWRITE; for (i = 1; i < argc - 1; i++ ) { if (Jim_GetEnum(interp, argv[i], options, &option, NULL, JIM_ERRMSG) != JIM_OK) { Jim_Free(mk); return JIM_ERR; } switch (option) { case OPT_READONLY: if (mode != MK_MODE_READWRITE) goto modeconflict; mode = MK_MODE_READONLY; mk->flags |= JIM_MKFLAG_READONLY; break; case OPT_ORIGINAL: if (mode != MK_MODE_READWRITE) goto modeconflict; mode = MK_MODE_ORIGINAL; mk->flags |= JIM_MKFLAG_READONLY; break; case OPT_EXTEND: if (mode != MK_MODE_READWRITE) goto modeconflict; mode = MK_MODE_EXTEND; mk->flags |= JIM_MKFLAG_EXTEND; break; case OPT_NOCOMMIT: mk->flags &= ~JIM_MKFLAG_AUTOCOMMIT; break; } } if (argc > 1) { new(&mk->storage) c4_Storage(Jim_String(argv[argc-1]), mode); if (!mk->storage.Strategy().IsValid()) { mk->storage.~c4_Storage(); Jim_Free(mk); Jim_SetResultFormatted(interp, "could not open storage \"%#s\"", argv[argc-1]); return JIM_ERR; } mk->filename = argv[argc-1]; if ((mk->flags & JIM_MKFLAG_AUTOCOMMIT) && !(mk->flags & JIM_MKFLAG_READONLY)) mk->storage.AutoCommit(1); } else { mk->flags |= JIM_MKFLAG_INMEMORY; new(&mk->storage) c4_Storage(); mk->filename = Jim_NewEmptyStringObj(interp); } new(&mk->content) c4_Cursor(&mk->storage[0]); Jim_IncrRefCount(mk->filename); snprintf(buf, sizeof(buf), "mk.handle%ld", Jim_GetId(interp)); Jim_CreateCommand(interp, buf, JimStorageSubCmdProc, mk, JimStorageDelProc); Jim_SetResultString(interp, buf, -1); return JIM_OK; modeconflict: Jim_Free(mk); Jim_SetResultString(interp, "only one of -readonly, -original and -extend may be specified", -1); return JIM_ERR; } /* ------------------------------------------------------------------------- * Initialization code * ------------------------------------------------------------------------- */ int Jim_mkInit(Jim_Interp *interp) { char version[MK_VERSION_SPACE]; snprintf(version, MK_VERSION_SPACE, "%d.%d.%d", d4_MetakitLibraryVersion / 100, d4_MetakitLibraryVersion % 100 / 10, d4_MetakitLibraryVersion % 10); if (Jim_PackageProvide(interp, "mk", version, JIM_ERRMSG)) return JIM_ERR; Jim_CreateCommand(interp, "storage", JimStorageCommand, NULL, NULL); Jim_CreateCommand(interp, "cursor", JimCursorCommand, NULL, NULL); Jim_CreateCommand(interp, "mk.view.finalizer", JimViewFinalizerProc, NULL, NULL); return JIM_OK; } } /* extern "C" */