1// mk4tcl.cpp --
2// $Id: mk4tcl.cpp 4452 2008-12-10 22:57:54Z patthoyts $
3// This is part of Metakit, see http://www.equi4.com/metakit.html
4
5#include "mk4tcl.h"
6#include "mk4io.h"
7
8#ifndef _WIN32_WCE
9#include <errno.h>
10#endif
11
12#include <stdlib.h>
13#include <string.h>
14#include <ctype.h>
15
16#ifndef EINVAL
17#define EINVAL 9
18#endif
19
20// stub interface code, removes the need to link with libtclstub*.a
21//#ifdef USE_TCL_STUBS
22//#include "stubtcl.h"
23//#else
24//#define MyInitStubs(x) 1
25//#endif
26
27#if 10 * TCL_MAJOR_VERSION + TCL_MINOR_VERSION < 86
28#define Tcl_GetErrorLine(interp) (interp)->errorLine
29#endif
30
31// definition of valid property name - alpha numerics, underscore, percent,
32// or any extended utf-8 character
33#define ISNAME(c)       (isalnum((c)) || (c) == '_' || (c) == '%' || (c) & 0x80)
34///////////////////////////////////////////////////////////////////////////////
35// Defined in this file:
36
37class MkPath;
38class MkWorkspace;
39class Tcl;
40class MkTcl;
41
42///////////////////////////////////////////////////////////////////////////////
43
44// inc'ed whenever a datafile is closed, forces relookup of all paths
45static int generation;
46
47#ifdef TCL_THREADS
48
49// There is a single monolithic mutex for protecting all of Mk4tcl, but it has
50// to be a bit more advanced than Tcl's one since it has to support recursion,
51// i.e. re-entering this code from the *same* thread needs to be allowed.  The
52// recursion can happen in Tcl's type callbacks, see "Tcl_ObjType mkCursorType".
53//
54// We know the current interpreter in all cases, it can be used as mutex owner.
55// So we can be multiple times inside the mutex, but ONLY from a simgle interp.
56// No deadlock is possible, locking is always in the order mkMutex -> infoMutex.
57
58TCL_DECLARE_MUTEX(mkMutex)    // use a single monolithic mutex for now
59TCL_DECLARE_MUTEX(infoMutex)  // use a second mutex to manage the info below
60
61// set to the interp holding the mutex, or to zero when not locked
62static Tcl_Interp *mutex_owner;
63
64// set to the reursion level, > 1 means we've re-entered from same interp
65static int mutex_level;
66
67static void EnterMutex(Tcl_Interp *ip_) {
68    d4_assert(ip_ != 0);
69    Tcl_MutexLock(&infoMutex);
70    if (ip_ != mutex_owner) {
71        Tcl_MutexUnlock(&infoMutex);
72        Tcl_MutexLock(&mkMutex);
73        Tcl_MutexLock(&infoMutex);
74        d4_assert(mutex_owner == 0);
75        mutex_owner = ip_;
76    }
77    ++mutex_level;
78    Tcl_MutexUnlock(&infoMutex);
79}
80
81static void LeaveMutex() {
82    Tcl_MutexLock(&infoMutex);
83    d4_assert(mutex_owner != 0 && mutex_level > 0);
84    if (--mutex_level == 0) {
85      mutex_owner = 0;
86      Tcl_MutexUnlock(&mkMutex);
87    }
88    Tcl_MutexUnlock(&infoMutex);
89}
90
91#else
92
93#define EnterMutex(x)
94#define LeaveMutex()
95
96#endif
97
98// put code in this file as a mutex is static in Windows
99int Mk_EvalObj(Tcl_Interp *ip_, Tcl_Obj *cmd_) {
100    LeaveMutex();
101    int e = Tcl_EvalObj(ip_, cmd_);
102    EnterMutex(ip_);
103    return e;
104}
105
106// moved out of member func scope to please HP-UX's aCC:
107
108static const char *getCmds[] =  {
109  "-size", 0
110};
111
112static const char *viewCmds[] =  {
113  "layout", "delete", "size", "properties", "locate", "restrict", "open", "new",
114    "info", 0
115};
116
117static const char *cursorCmds[] =  {
118  "create", "position", "incr", 0
119};
120
121static const char *channelCmds[] =  {
122  "read", "write", "append", 0
123};
124
125/////////////////////////////////////////////////////////////////////////////
126// Utility code: return next token up to char < '0', and
127// advance the string pointer past following character.
128
129c4_String f4_GetToken(const char * &str_) {
130  d4_assert(str_);
131
132  const char *p = str_;
133  while (ISNAME(*p) ||  *p == ':')
134    ++p;
135
136  c4_String result(str_, p - str_);
137
138  if (*p)
139    ++p;
140  // advance over seperator - but no check!
141  str_ = p;
142
143  return result;
144}
145
146///////////////////////////////////////////////////////////////////////////////
147// Utility code: true if value contains a word starting with the given prefix
148
149bool MatchOneKeyword(const char *value_, const c4_String &crit_) {
150  int n = crit_.GetLength();
151  if (n == 0)
152    return true;
153
154  char cu = (char)toupper(crit_[0]);
155  char cl = (char)tolower(crit_[0]);
156
157  const char *limit = value_ + strlen(value_) - n;
158  while (value_ <= limit) {
159    c4_String s(value_, n);
160    if (s.CompareNoCase(crit_) == 0)
161      return true;
162
163    while (*++value_)
164      if ((*value_ == cu ||  *value_ == cl) && !isalnum(value_[ - 1]))
165        break;
166  }
167
168  return false;
169}
170
171///////////////////////////////////////////////////////////////////////////////
172// A "storage in a storage" strategy class for Metakit
173// Adapted from MkWrap, the Python interface
174
175class SiasStrategy: public c4_Strategy {
176  public:
177    c4_Storage _storage;
178    c4_View _view;
179    c4_BytesProp _memo;
180    int _row;
181    t4_i32 _position;
182    Tcl_Channel _chan;
183    int _validMask;
184    int _watchMask;
185    Tcl_Interp *_interp;
186
187    SiasStrategy(c4_Storage &storage_, const c4_View &view_, const c4_BytesProp
188      &memo_, int row_): _storage(storage_), _view(view_), _memo(memo_), _row
189      (row_), _position(0), _interp(0) {
190        // set up mapping if the memo itself is mapped in its entirety
191        c4_Strategy &strat = storage_.Strategy();
192        if (strat._mapStart != 0) {
193            c4_RowRef r = _view[_row];
194            c4_Bytes data = _memo(r).Access(0);
195            const t4_byte *ptr = data.Contents();
196            if (data.Size() == _memo(r).GetSize() && strat._mapStart != 0 &&
197              ptr >= strat._mapStart && ptr - strat._mapStart < strat._dataSize)
198              {
199                _mapStart = ptr;
200                _dataSize = data.Size();
201            }
202        }
203    }
204
205    virtual ~SiasStrategy() {
206        _view = c4_View();
207        _mapStart = 0;
208        _dataSize = 0;
209
210        if (_chan != 0)
211          Tcl_UnregisterChannel(_interp, _chan);
212    }
213
214    virtual void DataSeek(t4_i32 position_) {
215        _position = position_;
216    }
217
218    virtual int DataRead(t4_i32 pos_, void *buffer_, int length_) {
219        if (pos_ != ~0)
220          _position = pos_;
221
222        int i = 0;
223
224        while (i < length_) {
225            c4_Bytes data = _memo(_view[_row]).Access(_position + i, length_ -
226              i);
227            int n = data.Size();
228            if (n <= 0)
229              break;
230            memcpy((char*)buffer_ + i, data.Contents(), n);
231            i += n;
232        }
233
234        _position += i;
235        return i;
236    }
237
238    virtual void DataWrite(t4_i32 pos_, const void *buffer_, int length_) {
239        if (pos_ != ~0)
240          _position = pos_;
241
242        c4_Bytes data(buffer_, length_);
243        if (_memo(_view[_row]).Modify(data, _position))
244          _position += length_;
245        else
246          ++_failure;
247    }
248
249    virtual void DataCommit(t4_i32 newSize_) {
250        if (newSize_ > 0)
251          _memo(_view[_row]).Modify(c4_Bytes(), newSize_);
252    }
253
254    virtual void ResetFileMapping() {
255        _mapStart = 0; // never called, but just in case
256    }
257};
258
259///////////////////////////////////////////////////////////////////////////////
260// New in 1.2: channel interface to memo fields
261
262typedef SiasStrategy MkChannel;
263
264typedef struct {
265  Tcl_Event header;
266  MkChannel *chan;
267} MkEvent;
268
269static int mkEventProc(Tcl_Event *evPtr, int flags) {
270  MkEvent *me = (MkEvent*)evPtr;
271
272  if (!(flags &TCL_FILE_EVENTS))
273    return 0;
274
275  Tcl_NotifyChannel(me->chan->_chan, me->chan->_watchMask);
276  return 1;
277}
278
279static int mkEventFilter(Tcl_Event *evPtr, ClientData instanceData) {
280  MkEvent *me = (MkEvent*)evPtr;
281  MkChannel *chan = (MkChannel*)instanceData;
282  return evPtr->proc == mkEventProc && me->chan == chan;
283}
284
285static int mkClose(ClientData instanceData, Tcl_Interp *interp) {
286  MkChannel *chan = (MkChannel*)instanceData;
287
288  Tcl_DeleteEvents(mkEventFilter, (ClientData)chan);
289  chan->_chan = 0;
290  delete chan;
291
292  return TCL_OK;
293}
294
295static int mkInput(ClientData instanceData, char *buf, int toRead, int
296  *errorCodePtr) {
297  MkChannel *chan = (MkChannel*)instanceData;
298  return chan->DataRead(~0, buf, toRead);
299}
300
301static int mkOutput(ClientData instanceData, const char *buf, int toWrite, int
302  *errorCodePtr) {
303  MkChannel *chan = (MkChannel*)instanceData;
304  chan->DataWrite(~0, buf, toWrite);
305  if (chan->_failure == 0)
306    return toWrite;
307
308  *errorCodePtr = EINVAL; // hm, bad choice of error code
309  return  - 1;
310}
311
312static int mkSeek(ClientData instanceData, long offset, int seekMode, int
313  *errorCodePtr) {
314  MkChannel *chan = (MkChannel*)instanceData;
315
316  switch (seekMode) {
317    default:
318       *errorCodePtr = EINVAL; // hm, bad choice of error code
319      return  - 1;
320    case 0:
321      break;
322    case 1:
323      offset += chan->_position;
324      break;
325    case 2:
326      offset += chan->_memo(chan->_view[chan->_row]).GetSize();
327      break;
328  }
329
330  chan->DataSeek(offset);
331  return offset;
332}
333
334static void mkWatchChannel(ClientData instanceData, int mask) {
335  MkChannel *chan = (MkChannel*)instanceData;
336  Tcl_Time blockTime =  {
337    0, 0
338  };
339
340  /*
341   * Since the file is always ready for events, we set the block time
342   * to zero so we will poll.
343   */
344
345  chan->_watchMask = mask &chan->_validMask;
346  if (chan->_watchMask) {
347    Tcl_SetMaxBlockTime(&blockTime);
348  }
349}
350
351static int mkGetFile(ClientData instanceData, int direction, ClientData
352  *handlePtr) {
353  return TCL_ERROR;
354}
355
356static Tcl_ChannelType mkChannelType =  {
357  "mk",  /* Type name.                  */
358  0,  /* Set blocking/nonblocking behaviour. NULL'able */
359  mkClose,  /* Close channel, clean instance data      */
360  mkInput,  /* Handle read request               */
361  (Tcl_DriverOutputProc*)mkOutput,  /* Handle write request              */
362  (Tcl_DriverSeekProc*)mkSeek,  /* Move location of access point.    NULL'able
363    */
364  0,  /* Set options.              NULL'able */
365  0,  /* Get options.              NULL'able */
366  (Tcl_DriverWatchProc*)mkWatchChannel,  /* Initialize notifier               */
367  mkGetFile /* Get OS handle from the channel.         */
368};
369
370///////////////////////////////////////////////////////////////////////////////
371// Utility code: get a Metakit item and convert it to a Tcl object
372
373Tcl_Obj *GetAsObj(const c4_RowRef &row_, const c4_Property &prop_, Tcl_Obj
374  *obj_) {
375  if (obj_ == 0)
376    obj_ = Tcl_NewObj();
377
378  switch (prop_.Type()) {
379    case 'S':
380       {
381        const char *p = ((c4_StringProp &)prop_)(row_);
382        Tcl_SetStringObj(obj_, (char*)p,  - 1);
383      }
384      break;
385
386    case 'B':
387       {
388        c4_Bytes temp;
389        prop_(row_).GetData(temp);
390        Tcl_SetByteArrayObj(obj_, (t4_byte*)temp.Contents(), temp.Size());
391      }
392      break;
393
394    case 'F':
395      Tcl_SetDoubleObj(obj_, ((c4_FloatProp &)prop_)(row_));
396      break;
397
398    case 'D':
399      Tcl_SetDoubleObj(obj_, ((c4_DoubleProp &)prop_)(row_));
400      break;
401
402#ifdef TCL_WIDE_INT_TYPE
403    case 'L':
404      Tcl_SetWideIntObj(obj_, ((c4_LongProp &)prop_)(row_));
405      break;
406#endif
407
408    case 'I':
409      Tcl_SetLongObj(obj_, ((c4_IntProp &)prop_)(row_));
410      break;
411
412    case 'V':
413       {
414        c4_View view = ((c4_ViewProp &)prop_)(row_);
415        Tcl_SetIntObj(obj_, view.GetSize());
416      }
417      break;
418
419    default:
420       {
421        KeepRef keeper(obj_); // a funny way to release the value
422      }
423      return 0;
424  }
425
426  return obj_;
427}
428
429///////////////////////////////////////////////////////////////////////////////
430// Utility code: set a Metakit item and convert it from a Tcl object
431
432int SetAsObj(Tcl_Interp *interp, const c4_RowRef &row_, const c4_Property
433  &prop_, Tcl_Obj *obj_) {
434  int e = TCL_OK;
435
436  switch (prop_.Type()) {
437    case 'S':
438       {
439        int len;
440        const char *ptr = Tcl_GetStringFromObj(obj_, &len);
441        prop_(row_).SetData(c4_Bytes(ptr, len + 1));
442      }
443      break;
444
445    case 'B':
446       {
447        int len;
448        const t4_byte *ptr = Tcl_GetByteArrayFromObj(obj_, &len);
449        prop_(row_).SetData(c4_Bytes(ptr, len));
450      }
451      break;
452
453    case 'F':
454       {
455        double value = 0;
456        e = Tcl_GetDoubleFromObj(interp, obj_, &value);
457        if (e == TCL_OK)
458          ((c4_FloatProp &)prop_)(row_) = (float)value;
459      }
460      break;
461
462    case 'D':
463       {
464        double value = 0;
465        e = Tcl_GetDoubleFromObj(interp, obj_, &value);
466        if (e == TCL_OK)
467          ((c4_DoubleProp &)prop_)(row_) = value;
468      }
469      break;
470
471#ifdef TCL_WIDE_INT_TYPE
472    case 'L':
473       {
474        Tcl_WideInt value = 0;
475        e = Tcl_GetWideIntFromObj(interp, obj_, &value);
476        if (e == TCL_OK)
477          ((c4_LongProp &)prop_)(row_) = value;
478      }
479      break;
480#endif
481
482    case 'I':
483       {
484        long value = 0;
485        e = Tcl_GetLongFromObj(interp, obj_, &value);
486        if (e == TCL_OK)
487          ((c4_IntProp &)prop_)(row_) = value;
488      }
489      break;
490
491    default:
492      Tcl_SetResult(interp, (char*)"unsupported property type", TCL_STATIC);
493      e = TCL_ERROR;
494  }
495
496  return e;
497}
498
499///////////////////////////////////////////////////////////////////////////////
500// In Tcl, streaming I/O uses the Tcl channel interface for loading/saving.
501
502class c4_TclStream: public c4_Stream {
503    Tcl_Channel _stream;
504
505  public:
506    c4_TclStream(Tcl_Channel stream_);
507    virtual ~c4_TclStream();
508
509    virtual int Read(void *buffer_, int length_);
510    virtual bool Write(const void *buffer_, int length_);
511};
512
513c4_TclStream::c4_TclStream(Tcl_Channel stream_): _stream(stream_){}
514
515c4_TclStream::~c4_TclStream(){}
516
517int c4_TclStream::Read(void *buffer_, int length_) {
518  return Tcl_Read(_stream, (char*)buffer_, length_);
519}
520
521bool c4_TclStream::Write(const void *buffer_, int length_) {
522  return Tcl_Write(_stream, (char*)buffer_, length_) >= 0;
523}
524
525///////////////////////////////////////////////////////////////////////////////
526
527MkPath::MkPath(MkWorkspace &ws_, const char * &path_, Tcl_Interp *interp):
528  _refs(1), _ws(&ws_), _path(path_), _currGen(generation) {
529  // if this view is not part of any storage, make a new temporary row
530  if (_path.IsEmpty()) {
531    ws_.AllocTempRow(_path);
532    AttachView(interp);
533  } else {
534    int n = AttachView(interp);
535    path_ += n; // move past all processed characters
536
537    // but trim white space and unprocessed tail from stored path
538    while (n > 0 && _path[n - 1] < '0')
539      --n;
540    if (n < _path.GetLength())
541      _path = _path.Left(n);
542  }
543}
544
545MkPath::~MkPath() {
546  // 24-01-2003: paths should not clean up workspaces once exiting
547  if (_currGen != -1)
548    _ws->ForgetPath(this);
549}
550
551#if 0
552static c4_View OpenMapped(c4_View v_, int col_, int row_) {
553  if (col_ < 0)
554    return c4_View();
555
556  const c4_Property &prop = v_.NthProperty(col_);
557  d4_assert(prop.Type() == 'V');
558  if (prop.Type() != 'V')
559    return c4_View();
560
561  c4_View vw = ((c4_ViewProp &)prop)(v_[row_]);
562
563  c4_String name = prop.Name();
564  int h = v_.FindPropIndexByName(name + "_H1");
565  if (h >= 0) {
566    const c4_Property &proph = v_.NthProperty(h);
567    if (proph.Type() == 'V') {
568      c4_View vwh = ((c4_ViewProp &)proph)(v_[row_]);
569      vw = vw.Hash(vwh, 1);
570    }
571  }
572
573  return vw;
574}
575
576#endif
577
578int MkPath::AttachView(Tcl_Interp * /*interp*/) {
579  const char *base = _path;
580  const char *p = base;
581
582  //  The format of a path description is:
583  //
584  //    storage '.' viewname [ '!' row# '.' viewprop ]*
585  //  or
586  //    storage '.' viewname [ '!' row# '.' viewprop ]* '!' row#
587  //
588  //  In the second case, the trailing row# is ignored.
589
590  MkWorkspace::Item *ip = _ws != 0 ? _ws->Find(f4_GetToken(p)): 0;
591  if (ip != 0) {
592    // 16-1-2003: allow path reference to root view (i.e. storage itself)
593    if (*p == 0) {
594      _view = ip->_storage;
595      return p - base;
596    }
597#if 0
598    c4_View root =  *ip->_storage;
599    int col = root.FindPropIndexByName(f4_GetToken(p));
600    _view = OpenMapped(root, col, 0);
601#else
602    _view = ip->_storage.View(f4_GetToken(p));
603#endif
604    while (*p) {
605      if (!isdigit(*p)) {
606        _view = c4_View(); // bad stuff, bail out with an empty view
607        break;
608      }
609
610      const char *q = p;
611
612      int r = atoi(f4_GetToken(p));
613
614      if (! *p)
615        return q - base;
616      // return partial number of chars processed
617
618      //  A future version could parse derived view expressions here.
619      //  Perhaps this could be done as Metakit property expressions.
620
621      int n = _view.FindPropIndexByName(f4_GetToken(p));
622      if (n < 0)
623        return q - base;
624      // make sure the property exists
625
626      const c4_Property &prop = _view.NthProperty(n);
627      if (prop.Type() != 'V')
628        return q - base;
629      // make sure it's a subview
630
631#if 0
632      _view = OpenMapped(_view, n, r);
633#else
634      _view = ((c4_ViewProp &)prop)(_view[r]);
635      ;
636#endif
637    }
638  } else
639    _view = c4_View();
640
641  return p - base; // return pointer to ending null byte
642}
643
644int MkPath::Refs(int diff_) {
645  d4_assert( - 1 <= diff_ && diff_ <=  + 1);
646
647  _refs += diff_;
648
649  d4_assert(_refs >= 0);
650
651  if (_refs == 0 && diff_ < 0) {
652    delete this;
653    return 0;
654  }
655
656  return _refs;
657}
658
659///////////////////////////////////////////////////////////////////////////////
660
661c4_PtrArray *MkWorkspace::Item::_shared = 0;
662
663MkWorkspace::Item::Item(const char *name_, const char *fileName_, int mode_,
664  c4_PtrArray &items_, int index_, bool share_): _name(name_), _fileName
665  (fileName_), _items(items_), _index(index_) {
666  ++generation; // make sure all cached paths refresh on next access
667
668  if (*fileName_) {
669    c4_Storage s(fileName_, mode_);
670    if (!s.Strategy().IsValid())
671      return ;
672    _storage = s;
673  }
674
675  if (_index >= _items.GetSize())
676    _items.SetSize(_index + 1);
677
678  _items.SetAt(_index, this);
679
680  if (share_) {
681    if (_shared == 0)
682      _shared = new c4_PtrArray;
683    _shared->Add(this);
684  }
685}
686
687MkWorkspace::Item::~Item() {
688  //! ForceRefresh();
689  // all views referring to this datafile are made invalid
690  for (int i = 0; i < _paths.GetSize(); ++i) {
691    MkPath *path = (MkPath*)_paths.GetAt(i);
692    if (_index > 0)
693      path->_view = c4_View();
694    path->_path = "?"; // make sure it never matches
695    path->_currGen = -1; // make sure lookup is retried on next use
696    // TODO: get rid of generations, use a "_valid" flag instead
697  }
698  ++generation; // make sure all cached paths refresh on next access
699
700  if (_index < _items.GetSize()) {
701    d4_assert(_items.GetAt(_index) == this || _items.GetAt(_index) == 0);
702    _items.SetAt(_index, 0);
703  }
704
705  if (_shared != 0) {
706    for (int i = 0; i < _shared->GetSize(); ++i)
707    if (_shared->GetAt(i) == this) {
708      _shared->RemoveAt(i);
709      break;
710    }
711
712    if (_shared->GetSize() == 0) {
713      delete _shared;
714      _shared = 0;
715    }
716  }
717}
718
719void MkWorkspace::Item::ForceRefresh() {
720  // all views referring to this datafile are cleared
721  for (int i = 0; i < _paths.GetSize(); ++i) {
722    MkPath *path = (MkPath*)_paths.GetAt(i);
723    path->_view = c4_View();
724  }
725
726  ++generation; // make sure all cached paths refresh on next access
727}
728
729MkWorkspace::MkWorkspace(Tcl_Interp *ip_): _interp(ip_) {
730  new Item("", "", 0, _items, 0);
731
732  // never uses entry zero (so atoi failure in ForgetPath is harmless)
733  _usedRows = _usedBuffer.SetBufferClear(16);
734    // no realloc for first 16 temp rows
735}
736
737MkWorkspace::~MkWorkspace() {
738  CleanupCommands();
739
740  for (int i = _items.GetSize(); --i >= 0;)
741    delete Nth(i);
742
743  // need this to prevent recursion in Tcl_DeleteAssocData in 8.2 (not 8.0!)
744  Tcl_SetAssocData(_interp, "mk4tcl", 0, 0);
745  Tcl_DeleteAssocData(_interp, "mk4tcl");
746}
747
748void MkWorkspace::DefCmd(MkTcl *cmd_) {
749  _commands.Add(cmd_);
750}
751
752MkWorkspace::Item *MkWorkspace::Define(const char *name_, const char *fileName_,
753  int mode_, bool share_) {
754  Item *ip = Find(name_);
755
756  if (ip == 0) {
757    int n =  - 1;
758    while (++n < _items.GetSize())
759      if (Nth(n) == 0)
760        break;
761
762    ip = new Item(name_, fileName_, mode_, _items, n, share_);
763    if (*fileName_ != 0 && !ip->_storage.Strategy().IsValid()) {
764      delete ip;
765      return 0;
766    }
767  }
768
769  return ip;
770}
771
772MkWorkspace::Item *MkWorkspace::Find(const char *name_)const {
773  for (int i = 0; i < _items.GetSize(); ++i) {
774    Item *ip = Nth(i);
775    if (ip && ip->_name.Compare(name_) == 0)
776      return ip;
777  }
778
779  if (Item::_shared != 0)
780   { // look in the shared pool, if there is one
781    for (int j = 0; j < Item::_shared->GetSize(); ++j) {
782      Item *ip = (Item*)Item::_shared->GetAt(j);
783      if (ip && ip->_name == name_)
784        return ip;
785    }
786  }
787
788  return 0;
789}
790
791int MkWorkspace::NumItems()const {
792  return _items.GetSize();
793}
794
795MkWorkspace::Item *MkWorkspace::Nth(int index_)const {
796  return (Item*)_items.GetAt(index_);
797}
798
799MkPath *MkWorkspace::AddPath(const char * &name_, Tcl_Interp *interp) {
800  const char *p = name_;
801
802  Item *ip = Find(f4_GetToken(p));
803  if (ip == 0) {
804    ip = Nth(0);
805    d4_assert(ip != 0);
806    name_ = ""; // no such tag, assign a temporary one instead
807  } else
808  for (int i = 0; i < ip->_paths.GetSize(); ++i) {
809    MkPath *path = (MkPath*)ip->_paths.GetAt(i);
810    d4_assert(path != 0);
811
812    if (path->_path.CompareNoCase(name_) == 0 && path->_currGen == generation) {
813      path->Refs( + 1);
814      return path;
815    }
816  }
817
818  MkPath *newPath = new MkPath(*this, name_, interp);
819  ip->_paths.Add(newPath);
820
821  return newPath;
822}
823
824void MkWorkspace::AllocTempRow(c4_String &result_) {
825  int i;
826
827  // find an unused row
828  for (i = 1; i < _usedBuffer.Size(); ++i)
829    if (_usedRows[i] == 0)
830      break;
831
832  // allocate new vec if old one is too small, doubling it in size
833  if (i >= _usedBuffer.Size()) {
834    c4_Bytes temp;
835    t4_byte *tempPtr = temp.SetBufferClear(2 *i + 1);
836    memcpy(tempPtr, _usedRows, _usedBuffer.Size());
837
838    _usedBuffer.Swap(temp);
839    _usedRows = tempPtr;
840
841    c4_View v = Nth(0)->_storage.View("");
842    v.SetSize(_usedBuffer.Size());
843  }
844
845  // flag it as being in use
846  _usedRows[i] = 1;
847
848  // temporary rows have special names
849  char buf[20];
850  sprintf(buf, "._!%d._", i);
851  result_ = buf;
852}
853
854void MkWorkspace::ForgetPath(const MkPath *path_) {
855  const char *p = path_->_path;
856
857  Item *ip = Find(f4_GetToken(p));
858  if (ip != 0) {
859    for (int j = 0; j < ip->_paths.GetSize(); ++j)
860    if ((MkPath*)ip->_paths.GetAt(j) == path_) {
861      ip->_paths.RemoveAt(j);
862      break;
863    }
864
865    // last ref to a temporary row determines when to release it
866    if (ip == Nth(0)) {
867      int n = atoi(((const char*)path_->_path) + 3);
868      d4_assert(_usedRows[n] != 0);
869      _usedRows[n] = 0;
870    }
871  }
872}
873
874void MkWorkspace::Invalidate(const MkPath &path_) {
875  const char *p = path_._path;
876
877  c4_String prefix = path_._path + "!";
878  int n = prefix.GetLength();
879
880  Item *ip = Find(f4_GetToken(p));
881  if (ip != 0) {
882    for (int j = 0; j < ip->_paths.GetSize(); ++j) {
883      MkPath *entry = (MkPath*)ip->_paths.GetAt(j);
884      if (strncmp(entry->_path, prefix, n) == 0)
885        entry->_currGen =  - 1;
886      // the next use will reattach
887    }
888  }
889}
890
891///////////////////////////////////////////////////////////////////////////////
892// Translate between the Metakit and Tcl-style datafile structure descriptions
893
894static c4_String KitToTclDesc(const char *desc_) {
895  c4_Bytes temp;
896  char *p = (char*)temp.SetBuffer(3 *strlen(desc_) + 100);
897
898  while (*desc_) {
899    char *q = p;
900
901    // assume normal property
902    while (ISNAME(*desc_) ||  *desc_ == ':')
903      *p++ =  *desc_++;
904
905    // strip a trailing ':S'
906    if (p[ - 2] == ':' && p[ - 1] == 'S')
907      p -= 2;
908
909    // at end of property, process commas and brackets
910    switch (*desc_++) {
911      // defensive coding, this cannot happen
912      case 0:
913        --desc_;
914        break;
915
916        // opening bracket "xxx[" --> "{xxx {"
917      case '[':
918         {
919          c4_String name(q, p - q);
920          *q++ = '{';
921          strcpy(q, name);
922          ++p;
923
924          *p++ = ' ';
925          *p++ = '{';
926        }
927        break;
928
929        // opening bracket "]" --> "}}"
930      case ']':
931         {
932          *p++ = '}';
933          *p++ = '}';
934        }
935        break;
936
937        // comma separator "," --> " "
938      case ',':
939        *p++ = ' ';
940    }
941  }
942
943  *p++ = 0;
944  return (const char*)temp.Contents();
945}
946
947///////////////////////////////////////////////////////////////////////////////
948//
949//  Interface to Tcl 8.0 type mechanism, defines a new "mkProperty" datatype
950//
951//  Since properties are immutable, we don't need most of the calls.
952
953static void FreePropertyInternalRep(Tcl_Obj *propPtr);
954
955static void DupPropertyInternalRep(Tcl_Obj *, Tcl_Obj*) {
956  d4_assert(false);
957}
958
959static void UpdateStringOfProperty(Tcl_Obj*) {
960  d4_assert(false);
961}
962
963static int SetPropertyFromAny(Tcl_Interp *, Tcl_Obj*) {
964  d4_assert(false);
965  return TCL_OK;
966}
967
968static Tcl_ObjType mkPropertyType =  {
969  (char*)"mkProperty",  // name
970  FreePropertyInternalRep,  // freeIntRepProc
971  DupPropertyInternalRep,  // dupIntRepProc
972  UpdateStringOfProperty,  // updateStringProc
973  SetPropertyFromAny  // setFromAnyProc
974};
975
976///////////////////////////////////////////////////////////////////////////////
977
978const c4_Property &AsProperty(Tcl_Obj *objPtr, const c4_View &view_) {
979  void *tag = (&view_[0])._seq; // horrific hack to get at c4_Sequence pointer
980  if (objPtr->typePtr !=  &mkPropertyType || objPtr
981    ->internalRep.twoPtrValue.ptr1 != tag) {
982    CONST86 Tcl_ObjType *oldTypePtr = objPtr->typePtr;
983
984    char type = 'S';
985
986    int length;
987    char *string = Tcl_GetStringFromObj(objPtr, &length);
988    c4_Property *prop;
989
990    if (length > 2 && string[length - 2] == ':') {
991      type = string[length - 1];
992      prop = new c4_Property(type, c4_String(string, length - 2));
993    } else
994     { // look into the view to try to determine the type
995      int n = view_.FindPropIndexByName(string);
996      if (n >= 0)
997        type = view_.NthProperty(n).Type();
998      prop = new c4_Property(type, string);
999    }
1000
1001    if (oldTypePtr && oldTypePtr->freeIntRepProc)
1002      oldTypePtr->freeIntRepProc(objPtr);
1003
1004    objPtr->typePtr = &mkPropertyType;
1005    // use a (char*), because the Mac wants it, others use (void*)
1006    objPtr->internalRep.twoPtrValue.ptr1 = tag;
1007    objPtr->internalRep.twoPtrValue.ptr2 = (char*)prop;
1008  }
1009
1010  return *(c4_Property*)objPtr->internalRep.twoPtrValue.ptr2;
1011}
1012
1013static void FreePropertyInternalRep(Tcl_Obj *propPtr) {
1014  // no mutex protection needed here, MK's own C++ locking is sufficient
1015  delete (c4_Property*)propPtr->internalRep.twoPtrValue.ptr2;
1016}
1017
1018///////////////////////////////////////////////////////////////////////////////
1019//
1020//  Interface to Tcl 8.0 type mechanism, defines a new "mkCursor" datatype
1021
1022static void FreeCursorInternalRep(Tcl_Obj *propPtr);
1023static void DupCursorInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr);
1024//static int SetCursorFromAny(Tcl_Interp* interp, Tcl_Obj* objPtr);
1025static void UpdateStringOfCursor(Tcl_Obj *propPtr);
1026
1027static Tcl_ObjType mkCursorType =  {
1028  (char*)"mkCursor",  // name
1029  FreeCursorInternalRep,  // freeIntRepProc
1030  DupCursorInternalRep,  // dupIntRepProc
1031  UpdateStringOfCursor,  // updateStringProc
1032  SetCursorFromAny  // setFromAnyProc
1033};
1034
1035///////////////////////////////////////////////////////////////////////////////
1036//
1037//  Cursors in Tcl are implemented as a pointer to an MkPath plus an index.
1038
1039MkPath &AsPath(Tcl_Obj *obj_) {
1040  d4_assert(obj_->typePtr ==  &mkCursorType);
1041  d4_assert(obj_->internalRep.twoPtrValue.ptr2 != 0);
1042
1043  return *(MkPath*)obj_->internalRep.twoPtrValue.ptr2;
1044}
1045
1046int &AsIndex(Tcl_Obj *obj_) {
1047  d4_assert(obj_->typePtr ==  &mkCursorType);
1048  d4_assert(obj_->internalRep.twoPtrValue.ptr2 != 0);
1049
1050  return (int &)obj_->internalRep.twoPtrValue.ptr1;
1051}
1052
1053static void FreeCursorInternalRep(Tcl_Obj *cursorPtr) {
1054  MkPath &path = AsPath(cursorPtr);
1055  EnterMutex(path._ws->_interp);
1056  path.Refs( - 1);
1057  LeaveMutex();
1058}
1059
1060static void DupCursorInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr) {
1061  MkPath &path = AsPath(srcPtr);
1062  EnterMutex(path._ws->_interp);
1063  path.Refs( + 1);
1064  copyPtr->internalRep = srcPtr->internalRep;
1065  copyPtr->typePtr = &mkCursorType;
1066  LeaveMutex();
1067}
1068
1069int SetCursorFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr) {
1070  d4_assert(interp != 0);
1071  EnterMutex(interp);
1072
1073  // force a relookup if the this object is of the wrong generation
1074  if (objPtr->typePtr == &mkCursorType && AsPath(objPtr)._currGen !=
1075    generation) {
1076    // make sure we have a string representation around
1077    if (objPtr->bytes == 0)
1078      UpdateStringOfCursor(objPtr);
1079
1080    // get rid of the object form
1081    FreeCursorInternalRep(objPtr);
1082    objPtr->typePtr = 0;
1083  }
1084
1085  if (objPtr->typePtr !=  &mkCursorType) {
1086    CONST86 Tcl_ObjType *oldTypePtr = objPtr->typePtr;
1087
1088    const char *string = Tcl_GetStringFromObj(objPtr, 0);
1089
1090    // dig up the workspace used in this interpreter
1091    MkWorkspace *work = (MkWorkspace*)Tcl_GetAssocData(interp, "mk4tcl", 0);
1092    // cast required for Mac
1093    char *s = (char*)(void*)work->AddPath(string, interp);
1094    int i = isdigit(*string) ? atoi(string):  - 1;
1095
1096    if (oldTypePtr && oldTypePtr->freeIntRepProc)
1097      oldTypePtr->freeIntRepProc(objPtr);
1098
1099    objPtr->typePtr = &mkCursorType;
1100    objPtr->internalRep.twoPtrValue.ptr1 = (void*)i;
1101    objPtr->internalRep.twoPtrValue.ptr2 = s;
1102  }
1103
1104  LeaveMutex();
1105  return TCL_OK;
1106}
1107
1108static void UpdateStringOfCursor(Tcl_Obj *cursorPtr) {
1109  MkPath &path = AsPath(cursorPtr);
1110  EnterMutex(path._ws->_interp);
1111  c4_String s = path._path;
1112
1113  int index = AsIndex(cursorPtr);
1114  if (index >= 0) {
1115    char buf[20];
1116    sprintf(buf, "%s%d", s.IsEmpty() ? "" : "!", index);
1117    s += buf;
1118  }
1119
1120  cursorPtr->length = s.GetLength();
1121  cursorPtr->bytes = strcpy(Tcl_Alloc(cursorPtr->length + 1), s);
1122  LeaveMutex();
1123}
1124
1125static Tcl_Obj *AllocateNewTempRow(MkWorkspace &work_) {
1126  Tcl_Obj *result = Tcl_NewObj();
1127
1128  const char *empty = "";
1129  MkPath *path = work_.AddPath(empty, 0);
1130  //  path->_view.SetSize(1);
1131
1132  result->typePtr = &mkCursorType;
1133  result->internalRep.twoPtrValue.ptr2 = (char*)(void*)path;
1134  AsIndex(result) = 0;
1135
1136  Tcl_InvalidateStringRep(result);
1137
1138  return result;
1139}
1140
1141///////////////////////////////////////////////////////////////////////////////
1142// Helper class for the mk::select command, stores params and performs select
1143
1144TclSelector::TclSelector(Tcl_Interp *interp_, const c4_View &view_): _interp
1145  (interp_), _view(view_), _temp(0), _first(0), _count( - 1){}
1146
1147TclSelector::~TclSelector() {
1148  for (int i = 0; i < _conditions.GetSize(); ++i)
1149    delete (Condition*)_conditions.GetAt(i);
1150}
1151
1152// convert a property (or list of properties) to an empty view
1153c4_View TclSelector::GetAsProps(Tcl_Obj *obj_) {
1154  c4_View result;
1155
1156  Tcl_Obj *o;
1157
1158  for (int i = 0; Tcl_ListObjIndex(_interp, obj_, i, &o) == TCL_OK && o != 0;
1159    ++i)
1160    result.AddProperty(AsProperty(o, _view));
1161
1162  return result;
1163}
1164
1165int TclSelector::AddCondition(int id_, Tcl_Obj *props_, Tcl_Obj *value_) {
1166  c4_View props = GetAsProps(props_);
1167  if (props.NumProperties() > 0)
1168    _conditions.Add(new Condition(id_, props, value_));
1169
1170  return TCL_OK;
1171}
1172
1173bool TclSelector::MatchOneString(int id_, const char *value_, const char *crit_)
1174  {
1175  switch (id_) {
1176    case 2:
1177      // -exact prop value : exact case-sensitive match
1178      return strcmp(value_, crit_) == 0;
1179
1180    case 3:
1181      // -glob prop pattern : match "glob" expression wildcard
1182      return Tcl_StringMatch(value_, crit_) > 0;
1183
1184    case 4:
1185      // -regexp prop pattern : match specified regular expression
1186      return Tcl_RegExpMatch(_interp, (CONST84 char*)value_, (CONST84 char*)
1187        crit_) > 0;
1188    case 5:
1189      // -keyword prop prefix : match keyword in given property
1190      return MatchOneKeyword(value_, crit_);
1191
1192    case 10:
1193      // -globnc prop pattern : match "glob", but not case sensitive
1194      return Tcl_StringCaseMatch(value_, crit_, 1) > 0;
1195  }
1196
1197  return false;
1198}
1199
1200bool TclSelector::Match(const c4_RowRef &row_) {
1201  // go through each condition and make sure they all match
1202  for (int i = 0; i < _conditions.GetSize(); ++i) {
1203    const Condition &cond = *(const Condition*)_conditions.GetAt(i);
1204
1205    bool matched = false;
1206
1207    // go through each property until one matches
1208    for (int j = 0; j < cond._view.NumProperties(); ++j) {
1209      const c4_Property &prop = cond._view.NthProperty(j);
1210
1211      if (cond._id < 2)
1212       { // use typed comparison as defined by Metakit
1213        c4_Row data; // this is *very* slow in Metakit 1.8
1214        if (SetAsObj(_interp, data, prop, cond._crit) != TCL_OK)
1215          return false;
1216
1217        // data is now a row with the criterium as single property
1218        matched = cond._id < 0 && data == row_ || cond._id == 0 && data <= row_
1219          || cond._id > 0 && data >= row_;
1220      } else
1221       { // use item value as a string
1222        GetAsObj(row_, prop, _temp);
1223        matched = MatchOneString(cond._id, Tcl_GetStringFromObj(_temp, NULL),
1224          Tcl_GetStringFromObj(cond._crit, NULL));
1225        if (matched)
1226          break;
1227      }
1228    }
1229
1230    if (!matched)
1231      return false;
1232  }
1233
1234  return true;
1235}
1236
1237// pick out criteria which specify an exact match
1238void TclSelector::ExactKeyProps(const c4_RowRef &row_) {
1239  for (int i = 0; i < _conditions.GetSize(); ++i) {
1240    const Condition &cond = *(const Condition*)_conditions.GetAt(i);
1241    if (cond._id ==  - 1 || cond._id == 2) {
1242      for (int j = 0; j < cond._view.NumProperties(); ++j) {
1243        const c4_Property &prop = cond._view.NthProperty(j);
1244        SetAsObj(_interp, row_, prop, cond._crit);
1245      }
1246    }
1247  }
1248}
1249
1250int TclSelector::DoSelect(Tcl_Obj *list_, c4_View *result_) {
1251  c4_IntProp pIndex("index");
1252
1253  // normalize _first and _count to be in allowable range
1254  int n = _view.GetSize();
1255  if (_first < 0)
1256    _first = 0;
1257  if (_first > n)
1258    _first = n;
1259  if (_count < 0)
1260    _count = n;
1261  if (_first + _count > n)
1262    _count = n - _first;
1263
1264  c4_View result;
1265  result.SetSize(_count); // upper bound
1266
1267  // keep a temporary around during the comparison loop
1268  _temp = Tcl_NewObj();
1269  KeepRef keeper(_temp);
1270
1271  // try to take advantage of key lookup structures
1272  c4_Row exact;
1273  ExactKeyProps(exact);
1274  if (exact.Container().NumProperties() > 0)
1275    _view.RestrictSearch(exact, _first, _count);
1276
1277  // the matching loop where all the hard work is done
1278  for (n = 0; _first < _view.GetSize() && n < _count; ++_first)
1279    if (Match(_view[_first]))
1280      pIndex(result[n++]) = _first;
1281
1282  result.SetSize(n);
1283
1284  // set up sorting, this references/loads a lot of extra Metakit code
1285  const bool sorted = n > 0 && _sortProps.NumProperties() > 0;
1286
1287  c4_View mapView;
1288  c4_View sortResult;
1289  if (sorted) {
1290    mapView = _view.RemapWith(result);
1291    sortResult = mapView.SortOnReverse(_sortProps, _sortRevProps);
1292  }
1293
1294  // convert result to a Tcl list of ints
1295  if (list_ != 0)
1296  for (int i = 0; i < n; ++i) {
1297    // sorting means we have to lookup the index of the original again
1298    int pos = i;
1299    if (sorted)
1300      pos = mapView.GetIndexOf(sortResult[i]);
1301
1302    // set up a Tcl integer which holds the selected row index
1303    KeepRef o = Tcl_NewIntObj(pIndex(result[pos]));
1304
1305    if (Tcl_ListObjAppendElement(_interp, list_, o) != TCL_OK)
1306      return TCL_ERROR;
1307  }
1308
1309  // added 2003/02/14: return intermediate view, if requested
1310  if (result_ != 0)
1311    *result_ = sorted ? sortResult : result;
1312
1313  return TCL_OK;
1314}
1315
1316///////////////////////////////////////////////////////////////////////////////
1317// The Tcl class is a generic interface to Tcl, providing some C++ wrapping
1318
1319Tcl::Tcl(Tcl_Interp *ip_): interp(ip_){}
1320
1321int Tcl::Fail(const char *msg_, int err_) {
1322  if (!_error) {
1323    if (msg_)
1324      Tcl_SetResult(interp, (char*)msg_, TCL_VOLATILE);
1325    _error = err_;
1326  }
1327
1328  return _error;
1329}
1330
1331Tcl_Obj *Tcl::tcl_GetObjResult() {
1332  return Tcl_GetObjResult(interp);
1333}
1334
1335int Tcl::tcl_SetObjResult(Tcl_Obj *obj_) {
1336  Tcl_SetObjResult(interp, obj_);
1337  return _error;
1338}
1339
1340int Tcl::tcl_ListObjLength(Tcl_Obj *obj_) {
1341  int result;
1342  _error = Tcl_ListObjLength(interp, obj_, &result);
1343  return _error ?  - 1: result;
1344}
1345
1346void Tcl::tcl_ListObjAppendElement(Tcl_Obj *obj_, Tcl_Obj *value_) {
1347  if (!_error)
1348    if (value_ == 0)
1349      Fail();
1350    else
1351      _error = Tcl_ListObjAppendElement(interp, obj_, value_);
1352}
1353
1354bool Tcl::tcl_GetBooleanFromObj(Tcl_Obj *obj_) {
1355  int value = 0;
1356  if (!_error)
1357    _error = Tcl_GetBooleanFromObj(interp, obj_, &value);
1358  return value != 0;
1359}
1360
1361int Tcl::tcl_GetIntFromObj(Tcl_Obj *obj_) {
1362  int value = 0;
1363  if (!_error)
1364    _error = Tcl_GetIntFromObj(interp, obj_, &value);
1365  return value;
1366}
1367
1368long Tcl::tcl_GetLongFromObj(Tcl_Obj *obj_) {
1369  long value = 0;
1370  if (!_error)
1371    _error = Tcl_GetLongFromObj(interp, obj_, &value);
1372  return value;
1373}
1374
1375double Tcl::tcl_GetDoubleFromObj(Tcl_Obj *obj_) {
1376  double value = 0;
1377  if (!_error)
1378    _error = Tcl_GetDoubleFromObj(interp, obj_, &value);
1379  return value;
1380}
1381
1382int Tcl::tcl_GetIndexFromObj(Tcl_Obj *obj_, const char **table_, const char
1383  *msg_) {
1384  int index =  - 1;
1385  if (!_error)
1386    _error = Tcl_GetIndexFromObj(interp, obj_, (CONST84 char **)table_, msg_, 0,
1387      &index);
1388  return _error == TCL_OK ? index :  - 1;
1389}
1390
1391long Tcl::tcl_ExprLongObj(Tcl_Obj *obj_) {
1392  long result = 0;
1393  if (!_error)
1394    _error = Tcl_ExprLongObj(interp, obj_, &result);
1395  return result;
1396}
1397
1398Tcl_Obj *Tcl::GetValue(const c4_RowRef &row_, const c4_Property &prop_, Tcl_Obj
1399  *obj_) {
1400  obj_ = GetAsObj(row_, prop_, obj_);
1401
1402  if (!obj_)
1403    Fail("unsupported property type");
1404
1405  return obj_;
1406}
1407
1408Tcl_Obj *Tcl::tcl_NewStringObj(const char *str_, int len_) {
1409  return Tcl_NewStringObj((char*)str_, len_);
1410}
1411
1412void Tcl::list2desc(Tcl_Obj *in_, Tcl_Obj *out_) {
1413  Tcl_Obj *o,  **ov;
1414  int oc;
1415  if (Tcl_ListObjGetElements(0, in_, &oc, &ov) == TCL_OK && oc > 0) {
1416    char sep = '[';
1417    for (int i = 0; i < oc; ++i) {
1418      Tcl_AppendToObj(out_, &sep, 1);
1419      sep = ',';
1420      Tcl_ListObjIndex(0, ov[i], 0, &o);
1421      if (o != 0)
1422        Tcl_AppendObjToObj(out_, o);
1423      Tcl_ListObjIndex(0, ov[i], 1, &o);
1424      if (o != 0)
1425        list2desc(o, out_);
1426    }
1427    Tcl_AppendToObj(out_, "]", 1);
1428  }
1429}
1430
1431///////////////////////////////////////////////////////////////////////////////
1432// The MkTcl class adds Metakit-specific utilities and all the command procs.
1433
1434int MkTcl::Dispatcher(ClientData cd, Tcl_Interp *ip, int oc, Tcl_Obj *const *
1435  ov) {
1436  MkTcl *self = (MkTcl*)cd;
1437
1438  if (self == 0 || self->interp != ip) {
1439    Tcl_SetResult(ip, (char*)"Initialization error in dispatcher", TCL_STATIC);
1440    return TCL_ERROR;
1441  }
1442
1443  return self->Execute(oc, ov);
1444}
1445
1446MkTcl::MkTcl(MkWorkspace *ws_, Tcl_Interp *ip_, int id_, const char *cmd_): Tcl
1447  (ip_), id(id_), work(*ws_) {
1448  Tcl_CreateObjCommand(ip_, (char*)cmd_, Dispatcher, this, 0);
1449}
1450
1451MkTcl::~MkTcl(){}
1452
1453c4_View MkTcl::asView(Tcl_Obj *obj_) {
1454  SetCursorFromAny(interp, obj_);
1455  return AsPath(obj_)._view;
1456}
1457
1458int &MkTcl::changeIndex(Tcl_Obj *obj_) {
1459  SetCursorFromAny(interp, obj_);
1460  Tcl_InvalidateStringRep(obj_);
1461  return AsIndex(obj_);
1462}
1463
1464c4_RowRef MkTcl::asRowRef(Tcl_Obj *obj_, int type_) {
1465  c4_View view = asView(obj_);
1466  int index = AsIndex(obj_);
1467  int size = view.GetSize();
1468
1469  switch (type_) {
1470    case kExtendRow:
1471      if (index >= size)
1472        view.SetSize(size = index + 1);
1473    case kLimitRow:
1474      if (index > size)
1475        Fail("view index is too large");
1476      else if (index < 0)
1477        Fail("view index is negative");
1478      break;
1479
1480    case kExistingRow:
1481      if (index < 0 || index >= size) {
1482        Fail("view index is out of range");
1483        break;
1484      }
1485    case kAnyRow:
1486      ;
1487  }
1488
1489  return view[index];
1490}
1491
1492int MkTcl::GetCmd() {
1493  c4_RowRef row = asRowRef(objv[1], kExistingRow);
1494
1495  if (!_error) {
1496    const bool returnSize = objc > 2 &&  // fixed 1999-11-19
1497    tcl_GetIndexFromObj(objv[2], getCmds) >= 0;
1498    if (returnSize) {
1499      --objc;
1500      ++objv;
1501    } else {
1502      _error = TCL_OK; // ignore missing option
1503      KeepRef o = Tcl_NewObj();
1504      tcl_SetObjResult(o);
1505    }
1506
1507    Tcl_Obj *result = tcl_GetObjResult();
1508
1509    if (objc < 3) {
1510      c4_View view = row.Container();
1511      for (int i = 0; i < view.NumProperties() && !_error; ++i) {
1512        const c4_Property &prop = view.NthProperty(i);
1513        if (prop.Type() == 'V')
1514          continue;
1515        // omit subviews
1516
1517        tcl_ListObjAppendElement(result, tcl_NewStringObj(prop.Name()));
1518        tcl_ListObjAppendElement(result, returnSize ? Tcl_NewIntObj(prop(row)
1519          .GetSize()): GetValue(row, prop));
1520      }
1521    } else if (objc == 3) {
1522      const c4_Property &prop = AsProperty(objv[2], row.Container());
1523      if (returnSize)
1524        Tcl_SetIntObj(result, prop(row).GetSize());
1525      else
1526        GetValue(row, prop, result);
1527    } else {
1528      for (int i = 2; i < objc && !_error; ++i) {
1529        const c4_Property &prop = AsProperty(objv[i], row.Container());
1530        tcl_ListObjAppendElement(result, returnSize ? Tcl_NewIntObj(prop(row)
1531          .GetSize()): GetValue(row, prop));
1532      }
1533    }
1534  }
1535
1536  return _error;
1537}
1538
1539int MkTcl::SetValues(const c4_RowRef &row_, int objc, Tcl_Obj *const * objv) {
1540  while (objc >= 2 && !_error) {
1541    _error = SetAsObj(interp, row_, AsProperty(objv[0], row_.Container()),
1542      objv[1]);
1543
1544    objc -= 2;
1545    objv += 2;
1546  }
1547
1548  return _error;
1549}
1550
1551int MkTcl::SetCmd() {
1552  if (objc < 4)
1553    return GetCmd();
1554
1555  int size = asView(objv[1]).GetSize();
1556  c4_RowRef row = asRowRef(objv[1], kExtendRow);
1557
1558  int e = SetValues(row, objc - 2, objv + 2);
1559  if (e != TCL_OK)
1560    asView(objv[1]).SetSize(size);
1561  // 1.1: restore old size on errors
1562
1563  if (_error)
1564    return _error;
1565
1566  return tcl_SetObjResult(objv[1]);
1567}
1568
1569int MkTcl::RowCmd() {
1570  static const char *cmds[] =  {
1571    "create", "append", "delete", "insert", "replace", 0
1572  };
1573
1574  // "create" is optional if there are no further args
1575  int id = objc <= 1 ? 0 : tcl_GetIndexFromObj(objv[1], cmds);
1576  if (id < 0)
1577    return _error;
1578
1579  switch (id) {
1580    case 0:
1581       {
1582        Tcl_Obj *var = AllocateNewTempRow(work);
1583        KeepRef keeper(var);
1584
1585        SetValues(asRowRef(var, kExtendRow), objc - 2, objv + 2);
1586        return tcl_SetObjResult(var); // different result
1587      }
1588
1589    case 1:
1590       {
1591        Tcl_Obj *var = Tcl_DuplicateObj(objv[2]);
1592        tcl_SetObjResult(var);
1593
1594        // used to be a single stmt, avoids bug in gcc 2.7.2 on Linux?
1595        int size = asView(var).GetSize();
1596        changeIndex(var) = size;
1597
1598        int oc = objc - 3;
1599        Tcl_Obj **ov = (Tcl_Obj **)objv + 3;
1600
1601        // 2003-03-16, allow giving all pairs as list
1602        if (oc == 1 && Tcl_ListObjGetElements(interp, objv[3], &oc, &ov) !=
1603          TCL_OK)
1604          return TCL_ERROR;
1605
1606        // 2000-06-15: this will not work with custom viewers which
1607        // take over ordering or uniqueness, because such views can
1608        // not be resized to create emtpy rows, which get filled in
1609        int e = SetValues(asRowRef(var, kExtendRow), oc, ov);
1610        if (e != TCL_OK)
1611          asView(var).SetSize(size);
1612        // 1.1: restore old size on errors
1613
1614        return e;
1615      }
1616
1617    case 2:
1618       {
1619        c4_RowRef row = asRowRef(objv[2]);
1620        if (_error)
1621          return _error;
1622
1623        c4_View view = row.Container();
1624        int index = AsIndex(objv[2]);
1625
1626        int count = objc > 3 ? tcl_GetIntFromObj(objv[3]): 1;
1627        if (count > view.GetSize() - index)
1628          count = view.GetSize() - index;
1629
1630        if (count >= 1) {
1631          view.RemoveAt(index, count);
1632          work.Invalidate(AsPath(objv[2]));
1633        }
1634      }
1635      break;
1636
1637    case 3:
1638       {
1639        c4_RowRef toRow = asRowRef(objv[2], kLimitRow);
1640        if (_error)
1641          return _error;
1642
1643        c4_View view = toRow.Container();
1644        int n = AsIndex(objv[2]);
1645
1646        int count = objc > 3 ? tcl_GetIntFromObj(objv[3]): 1;
1647        if (count >= 1) {
1648          c4_Row temp;
1649          view.InsertAt(n, temp, count);
1650
1651          if (objc > 4) {
1652            c4_RowRef fromRow = asRowRef(objv[4]);
1653            if (_error)
1654              return _error;
1655
1656            while (--count >= 0)
1657              view[n++] = fromRow;
1658          }
1659          work.Invalidate(AsPath(objv[2]));
1660        }
1661      }
1662      break;
1663
1664    case 4:
1665       {
1666        c4_RowRef row = asRowRef(objv[2]);
1667        if (_error)
1668          return _error;
1669
1670        if (objc > 3)
1671          row = asRowRef(objv[3]);
1672        else
1673          row = c4_Row();
1674      }
1675      break;
1676  }
1677
1678  if (_error)
1679    return _error;
1680
1681  return tcl_SetObjResult(objv[2]);
1682}
1683
1684int MkTcl::FileCmd() {
1685  static const char *cmds[] =  {
1686    "open", "end", "close", "commit", "rollback", "load", "save", "views",
1687      "aside", "autocommit", "space", 0
1688  };
1689
1690  int id = tcl_GetIndexFromObj(objv[1], cmds);
1691  if (id < 0)
1692    return _error;
1693
1694  if (id == 0 && objc == 2)
1695   { // new in 1.1: return list of db's
1696    Tcl_Obj *result = tcl_GetObjResult();
1697
1698    // skip first entry, which is for temp rows
1699    for (int i = 1; i < work.NumItems() && !_error; ++i) {
1700      MkWorkspace::Item *ip = work.Nth(i);
1701
1702      if (ip != 0) {
1703        tcl_ListObjAppendElement(result, tcl_NewStringObj(ip->_name));
1704        tcl_ListObjAppendElement(result, tcl_NewStringObj(ip->_fileName));
1705      }
1706    }
1707
1708    return _error;
1709  }
1710
1711  const char *string = Tcl_GetStringFromObj(objv[2], 0);
1712
1713  MkWorkspace::Item *np = work.Find(f4_GetToken(string));
1714  if (np == 0 && id > 1)
1715    return Fail("no storage with this name");
1716
1717  switch (id) {
1718    case 0:
1719       { // open
1720        if (np != 0)
1721          return Fail("file already open");
1722
1723        int mode = 1;
1724        bool nocommit = false, shared = false;
1725        static const char *options[] =  {
1726          "-readonly", "-extend", "-nocommit", "-shared", 0
1727        }
1728        ;
1729
1730        while (objc > 2 &&  *Tcl_GetStringFromObj(objv[objc - 1], 0) == '-')
1731        switch (tcl_GetIndexFromObj(objv[--objc], options)) {
1732        case 0:
1733          mode = 0;
1734          break;
1735        case 1:
1736          mode = 2;
1737          break;
1738        case 2:
1739          nocommit = true;
1740          break;
1741        case 3:
1742          shared = true;
1743          break;
1744        default:
1745          return _error;
1746        }
1747
1748        const char *name = Tcl_GetStringFromObj(objv[2], 0);
1749        int len = 0;
1750        const char *file = objc < 4 ? "": Tcl_GetStringFromObj(objv[3], &len);
1751#ifdef WIN32
1752        np = work.Define(name, file, mode, shared);
1753#else
1754        Tcl_DString ds;
1755        const char *native = Tcl_UtfToExternalDString(NULL, file, len, &ds);
1756        np = work.Define(name, native, mode, shared);
1757        Tcl_DStringFree(&ds);
1758#endif
1759        if (np == 0)
1760          return Fail("file open failed");
1761
1762        if (*file && mode != 0 && !nocommit)
1763          np->_storage.AutoCommit();
1764      }
1765      break;
1766
1767    case 1:
1768       { // end
1769        int len;
1770        const char *name = Tcl_GetStringFromObj(objv[2], &len);
1771        c4_FileStrategy strat;
1772#ifdef WIN32
1773        int err = strat.DataOpen(name, false);
1774#else
1775        Tcl_DString ds;
1776        const char *native = Tcl_UtfToExternalDString(NULL, name, len, &ds);
1777        int err = strat.DataOpen(native, false);
1778        Tcl_DStringFree(&ds);
1779#endif
1780        if (!err || !strat.IsValid())
1781          return Fail("no such file");
1782        t4_i32 end = strat.EndOfData();
1783        if (end < 0)
1784          return Fail("not a Metakit datafile");
1785
1786        Tcl_SetIntObj(tcl_GetObjResult(), end);
1787        return _error;
1788      }
1789      break;
1790
1791    case 2:
1792       { // close
1793        delete np;
1794      }
1795      break;
1796
1797    case 3:
1798       { // commit
1799        if (!np->_storage.Strategy().IsValid())
1800          return Fail("cannot commit temporary dataset");
1801
1802        np->ForceRefresh(); // detach first
1803
1804        // 1-Mar-1999: check commit success
1805        bool full = objc > 3 && strcmp(Tcl_GetStringFromObj(objv[3], 0),
1806          "-full") == 0;
1807        if (!np->_storage.Commit(full))
1808          return Fail("I/O error during commit");
1809      }
1810      break;
1811
1812    case 4:
1813       { // rollback
1814        if (!np->_storage.Strategy().IsValid())
1815          return Fail("cannot rollback temporary dataset");
1816
1817        np->ForceRefresh(); // detach first
1818
1819        bool full = objc > 3 && strcmp(Tcl_GetStringFromObj(objv[3], 0),
1820          "-full") == 0;
1821        np->_storage.Rollback(full);
1822      }
1823      break;
1824
1825    case 5:
1826       { // load
1827        char *channel = Tcl_GetStringFromObj(objv[3], 0);
1828
1829        int mode;
1830        Tcl_Channel cp = Tcl_GetChannel(interp, channel, &mode);
1831        if (cp == 0 || !(mode &TCL_READABLE))
1832          return Fail("load from channel failed");
1833
1834        if (Tcl_SetChannelOption(interp, cp, "-translation", "binary"))
1835          return Fail();
1836
1837        np->ForceRefresh(); // detach first
1838
1839        c4_TclStream stream(cp);
1840        if (!np->_storage.LoadFrom(stream))
1841          return Fail("load error");
1842      }
1843      break;
1844
1845    case 6:
1846       { // save
1847        char *channel = Tcl_GetStringFromObj(objv[3], 0);
1848
1849        int mode;
1850        Tcl_Channel cp = Tcl_GetChannel(interp, channel, &mode);
1851        if (cp == 0 || !(mode &TCL_WRITABLE))
1852          return Fail("save to channel failed");
1853
1854        if (Tcl_SetChannelOption(interp, cp, "-translation", "binary"))
1855          return Fail();
1856
1857        c4_TclStream stream(cp);
1858        np->_storage.SaveTo(stream);
1859      }
1860      break;
1861
1862    case 7:
1863       { // views
1864        c4_View view = np->_storage;
1865        Tcl_Obj *result = tcl_GetObjResult();
1866
1867        for (int i = 0; i < view.NumProperties() && !_error; ++i) {
1868          const c4_Property &prop = view.NthProperty(i);
1869          tcl_ListObjAppendElement(result, tcl_NewStringObj(prop.Name()));
1870        }
1871
1872        return _error; // different result
1873      }
1874
1875    case 8:
1876       { // aside
1877        if (objc != 4)
1878          return Fail("mk::file aside: needs 2 storage args");
1879
1880        const char *as = Tcl_GetStringFromObj(objv[3], 0);
1881        MkWorkspace::Item *np2 = work.Find(f4_GetToken(as));
1882        if (np2 == 0)
1883          return Fail("no storage with this name");
1884
1885        np->_storage.SetAside(np2->_storage);
1886      }
1887      break;
1888
1889    case 9:
1890       { // autocommit
1891        if (objc != 3)
1892          return Fail("mk::file autocommit: too many args");
1893
1894        np->_storage.AutoCommit();
1895      }
1896      break;
1897
1898    case 10:
1899       { // space, new on 30-11-2001:  returns allocator used space pairs
1900        // nasty hack to obtain the storage's sequence pointer
1901        c4_View v = np->_storage;
1902        c4_Cursor c = &v[0];
1903        c4_Sequence *s = c._seq;
1904
1905        // even more horrible (i.e. brittle) hack to get the space vector
1906        c4_Persist *p = s->Persist();
1907        c4_PtrArray *a = p != 0 ? *(c4_PtrArray **)p: 0; // first field
1908        if (a == 0)
1909          return Fail("storage is not persistent");
1910
1911        // now return the values as a list
1912        Tcl_Obj *r = tcl_GetObjResult();
1913        for (int i = 1; i < a->GetSize() - 1 && !_error; ++i)
1914          tcl_ListObjAppendElement(r, Tcl_NewLongObj((long)a->GetAt(i)));
1915        return _error;
1916      }
1917  }
1918
1919  if (_error)
1920    return _error;
1921
1922  return tcl_SetObjResult(objv[2]);
1923}
1924
1925int MkTcl::ViewCmd() {
1926  int id = tcl_GetIndexFromObj(objv[1], viewCmds);
1927  if (id < 0)
1928    return _error;
1929
1930  switch (id) {
1931    case 0:
1932      // layout
1933      if (objc == 3) {
1934        const char *string = Tcl_GetStringFromObj(objv[2], 0);
1935
1936        MkWorkspace::Item *np = work.Find(f4_GetToken(string));
1937        if (np == 0)
1938          return Fail("no storage with this name");
1939
1940        c4_Storage &s = np->_storage;
1941
1942        const char *p = s.Description(f4_GetToken(string));
1943        if (p == 0)
1944          return Fail("no view with this name");
1945
1946        c4_String desc = KitToTclDesc(p);
1947        KeepRef o = tcl_NewStringObj(desc);
1948        return tcl_SetObjResult(o); // different result
1949      }
1950      // else fall through
1951    case 1:
1952       { // delete
1953        const char *string = Tcl_GetStringFromObj(objv[2], 0);
1954
1955        MkWorkspace::Item *np = work.Find(f4_GetToken(string));
1956        if (np == 0 && id != 4)
1957          return Fail("no storage with this name");
1958
1959        c4_String s = f4_GetToken(string);
1960        if (s.IsEmpty() ||  *string != 0)
1961          return Fail("unrecognized view name");
1962
1963        if (id == 0) {
1964          KeepRef o = tcl_NewStringObj(s);
1965          list2desc(objv[3], o);
1966          const char *desc = Tcl_GetStringFromObj(o, 0);
1967          if (desc &&  *desc)
1968            np->_storage.GetAs(desc);
1969        }
1970         else {
1971          c4_View v = np->_storage;
1972          if (v.FindPropIndexByName(s) < 0)
1973            return Fail("no view with this name");
1974
1975          np->_storage.GetAs(s);
1976        }
1977
1978        np->ForceRefresh(); // make sure views are re-attached
1979      }
1980      break;
1981
1982    case 2:
1983       { // size
1984        c4_View view = asView(objv[2]);
1985
1986        if (objc > 3) {
1987          int i = tcl_GetIntFromObj(objv[3]);
1988          if (_error)
1989            return _error;
1990          view.SetSize(i);
1991        }
1992
1993        Tcl_SetIntObj(tcl_GetObjResult(), view.GetSize());
1994        return _error; // different result
1995      }
1996      break;
1997
1998    case 3:
1999      // properties
2000    case 8:
2001       { // info (will be deprecated)
2002        c4_View view = asView(objv[2]);
2003        Tcl_Obj *result = tcl_GetObjResult();
2004
2005        for (int i = 0; i < view.NumProperties() && !_error; ++i) {
2006          const c4_Property &prop = view.NthProperty(i);
2007
2008          c4_String s = prop.Name();
2009          if (prop.Type() != 'S') {
2010            s += ":";
2011            s += prop.Type();
2012          }
2013
2014          tcl_ListObjAppendElement(result, tcl_NewStringObj(s));
2015        }
2016
2017        return _error;
2018      }
2019
2020    case 4:
2021       { // locate
2022        c4_View view = asView(objv[2]);
2023
2024        bool force = strcmp(Tcl_GetStringFromObj(objv[3], 0), "-force") == 0;
2025        int k = force ? 4 : 3;
2026
2027        if (k >= objc)
2028          return Fail("no key specified");
2029
2030        c4_Row key;
2031
2032        for (int i = 0; k + i < objc; ++i) {
2033          const c4_Property &prop = view.NthProperty(i);
2034          _error = SetAsObj(interp, key, prop, objv[k + i]);
2035          if (_error)
2036            return _error;
2037        }
2038
2039        int pos;
2040        if (view.Locate(key, &pos) == 0)
2041          if (force)
2042            view.InsertAt(pos, key);
2043          else
2044            return Fail("key not found");
2045
2046        Tcl_SetIntObj(tcl_GetObjResult(), pos);
2047        return _error;
2048      }
2049
2050    case 5:
2051       { // restrict
2052        if (objc <= 5)
2053          return Fail("too few args");
2054
2055        c4_View view = asView(objv[2]);
2056        c4_View hview = asView(objv[3]);
2057        int nkeys = tcl_GetIntFromObj(objv[4]);
2058        view = view.Hash(hview, nkeys);
2059
2060        c4_Row key;
2061
2062        for (int i = 0; i + 5 < objc; ++i) {
2063          const c4_Property &prop = view.NthProperty(i);
2064          _error = SetAsObj(interp, key, prop, objv[i + 5]);
2065          if (_error)
2066            return _error;
2067        }
2068
2069        int pos = 0;
2070        int count = view.GetSize();
2071        int result = view.RestrictSearch(key, pos, count);
2072
2073        Tcl_Obj *r = tcl_GetObjResult();
2074        tcl_ListObjAppendElement(r, Tcl_NewIntObj(result));
2075        tcl_ListObjAppendElement(r, Tcl_NewIntObj(pos));
2076        tcl_ListObjAppendElement(r, Tcl_NewIntObj(count));
2077        return _error;
2078      }
2079
2080    case 6:
2081       { // open
2082        if (objc < 3 || objc > 4)
2083          return Fail("wrong number of args");
2084
2085        c4_View view = asView(objv[2]);
2086        const char *name = objc > 3 ? Tcl_GetStringFromObj(objv[3], 0): "";
2087
2088        MkView *cmd = new MkView(interp, view, name);
2089        Tcl_SetStringObj(tcl_GetObjResult(), (char*)(const char*)cmd->CmdName(),
2090          - 1);
2091        return _error;
2092      }
2093
2094    case 7:
2095       { // new ?name?
2096        if (objc < 2 || objc > 3)
2097          return Fail("wrong number of args");
2098
2099        c4_View view;
2100        const char *name = objc > 3 ? Tcl_GetStringFromObj(objv[2], 0): "";
2101
2102        MkView *cmd = new MkView(interp, view, name);
2103        Tcl_SetStringObj(tcl_GetObjResult(), (char*)(const char*)cmd->CmdName(),
2104          - 1);
2105        return _error;
2106      }
2107  }
2108
2109  if (_error)
2110    return _error;
2111
2112  return tcl_SetObjResult(objv[2]);
2113}
2114
2115int MkTcl::LoopCmd() {
2116  Tcl_Obj *value = objc >= 4 ? Tcl_ObjSetVar2(interp, objv[1], 0, objv[2],
2117    TCL_LEAVE_ERR_MSG): Tcl_ObjGetVar2(interp, objv[1], 0, TCL_LEAVE_ERR_MSG);
2118  if (value == 0)
2119    return Fail();
2120  // has to exist, can't be valid otherwise
2121
2122  long first = objc >= 5 ? tcl_ExprLongObj(objv[3]): 0;
2123  long limit = objc >= 6 ? tcl_ExprLongObj(objv[4]): asView(value).GetSize();
2124  long incr = objc >= 7 ? tcl_ExprLongObj(objv[5]): 1;
2125
2126  if (incr == 0)
2127    Fail("increment must be nonzero");
2128
2129  if (_error)
2130    return _error;
2131
2132  Tcl_Obj *var = objv[1];
2133  Tcl_Obj *cmd = objv[objc - 1];
2134
2135  for (int i = first;; i += incr) {
2136    if (Tcl_IsShared(value))
2137      value = Tcl_DuplicateObj(value);
2138
2139    changeIndex(value) = i;
2140
2141    if (Tcl_ObjSetVar2(interp, var, 0, value, TCL_LEAVE_ERR_MSG) == 0)
2142      return Fail();
2143
2144    if (!(i < limit && incr > 0 || i > limit && incr < 0))
2145      break;
2146
2147    LeaveMutex();
2148    _error = Tcl_EvalObj(interp, cmd);
2149    EnterMutex(interp);
2150
2151    if (_error == TCL_CONTINUE)
2152      _error = TCL_OK;
2153
2154    if (_error) {
2155      if (_error == TCL_BREAK)
2156        _error = TCL_OK;
2157      else if (_error == TCL_ERROR) {
2158        char msg[100];
2159        sprintf(msg, "\n  (\"mk::loop\" body line %d)", Tcl_GetErrorLine(interp));
2160        Tcl_AddObjErrorInfo(interp, msg,  - 1);
2161      }
2162      break;
2163    }
2164  }
2165
2166  if (_error == TCL_OK)
2167    Tcl_ResetResult(interp);
2168
2169  return _error;
2170}
2171
2172int MkTcl::CursorCmd() {
2173  int id = tcl_GetIndexFromObj(objv[1], cursorCmds);
2174  if (id < 0)
2175    return _error;
2176
2177  Tcl_Obj *name = objv[2];
2178
2179  Tcl_Obj *var = 0;
2180
2181  if (id == 0) {
2182    var = objc < 4 ? AllocateNewTempRow(work): objv[3]; // create expects a path
2183
2184    --objc; // shift so the index will be picked up if present
2185    ++objv;
2186  } else
2187   { // alter an existing cursor
2188    var = Tcl_ObjGetVar2(interp, name, 0, TCL_LEAVE_ERR_MSG);
2189    if (var == 0)
2190      return Fail();
2191    // has to exist, can't be valid otherwise
2192  }
2193
2194  // about to modify, so make sure we are sole owners
2195  Tcl_Obj *original = 0;
2196  if (Tcl_IsShared(var)) {
2197    original = var;
2198    var = Tcl_DuplicateObj(var);
2199  }
2200
2201  KeepRef keeper(var);
2202
2203  c4_View view = asView(var);
2204
2205  int value;
2206  if (objc <= 3) {
2207    if (id == 1)
2208     { // position without value returns current value
2209      Tcl_SetIntObj(tcl_GetObjResult(), AsIndex(var));
2210      return _error;
2211    }
2212
2213    value = id == 0 ? 0 : 1; // create defaults to 0, incr defaults to 1
2214  } else if (Tcl_GetIntFromObj(interp, objv[3], &value) != TCL_OK) {
2215    const char *step = Tcl_GetStringFromObj(objv[3], 0);
2216    if (strcmp(step, "end") == 0)
2217      value = view.GetSize() - 1;
2218    else {
2219      if (original)
2220        Tcl_DecrRefCount(original);
2221      return Fail();
2222    }
2223  }
2224
2225  if (id < 2)
2226    changeIndex(var) = value;
2227  else
2228    changeIndex(var) += value;
2229
2230  Tcl_Obj *result = Tcl_ObjSetVar2(interp, name, 0, var, TCL_LEAVE_ERR_MSG);
2231  if (result == 0)
2232    return Fail();
2233
2234  return tcl_SetObjResult(result);
2235}
2236
2237int MkTcl::SelectCmd() {
2238  TclSelector sel(interp, asView(objv[1]));
2239
2240  static const char *opts[] =  {
2241    "-min",  // 0
2242    "-max",  // 1
2243    "-exact",  // 2
2244    "-glob",  // 3
2245    "-regexp",  // 4
2246    "-keyword",  // 5
2247    "-first",  // 6
2248    "-count",  // 7
2249    "-sort",  // 8
2250    "-rsort",  // 9
2251    "-globnc",  // 10
2252    0
2253  };
2254
2255  while (objc >= 4) {
2256    objc -= 2; // gobble next two arguments
2257    objv += 2;
2258
2259    // at this point, *objv is the next option, and objc >= 2
2260
2261    int id =  - 1;
2262
2263    const char *p = Tcl_GetStringFromObj(*objv, 0);
2264    if (p &&  *p == '-') {
2265      id = tcl_GetIndexFromObj(*objv, opts);
2266      if (id < 0)
2267        return _error;
2268    }
2269
2270    switch (id) {
2271      case  - 1:  { // prop value : case-insensitive match
2272        _error = sel.AddCondition( - 1, objv[0], objv[1]);
2273      }
2274      break;
2275
2276      case 0:
2277        // -min prop value : property must be greater or equal to value
2278      case 1:
2279        // -max prop value : property must be less or equal to value
2280      case 2:
2281        // -exact prop value : exact case-sensitive match
2282      case 3:
2283        // -glob prop pattern : match "glob" expression wildcard
2284      case 4:
2285        // -regexp prop pattern : match specified regular expression
2286      case 5:
2287        // -keyword prop prefix : match keyword in given property
2288      case 10:
2289         { // -globnc prop pattern : match "glob", but ignore case
2290          if (objc < 3)
2291            return Fail("not enough arguments");
2292
2293          _error = sel.AddCondition(id, objv[1], objv[2]);
2294
2295          --objc; // gobble a third argument
2296          ++objv;
2297        }
2298        break;
2299
2300      case 6:
2301        // -first pos : searching starts at specified row index
2302      case 7:
2303         { // -count num : return no more than this many results
2304          int n = tcl_GetIntFromObj(objv[1]);
2305          if (_error)
2306            return _error;
2307
2308          if (id == 6)
2309            sel._first = n;
2310          else
2311            sel._count = n;
2312        }
2313        break;
2314
2315      case 8:
2316        // -sort prop : sort on one or more properties, ascending
2317      case 9:
2318         { // -rsort prop : sort on one or more properties, descending
2319          c4_View props = sel.GetAsProps(objv[1]);
2320          for (int i = 0; i < props.NumProperties(); ++i) {
2321            const c4_Property &prop = props.NthProperty(i);
2322
2323            sel._sortProps.AddProperty(prop);
2324            if (id == 9)
2325              sel._sortRevProps.AddProperty(prop);
2326          }
2327        }
2328        break;
2329    }
2330  }
2331
2332  if (_error)
2333    return _error;
2334
2335  return sel.DoSelect(tcl_GetObjResult());
2336}
2337
2338int MkTcl::ChannelCmd() {
2339  c4_RowRef row = asRowRef(objv[1]);
2340  MkPath &path = AsPath(objv[1]);
2341  int index = AsIndex(objv[1]);
2342
2343  if (_error)
2344    return _error;
2345
2346  const c4_BytesProp &memo = (const c4_BytesProp &)AsProperty(objv[2],
2347    path._view);
2348
2349  int id = objc < 4 ? 0 : tcl_GetIndexFromObj(objv[3], channelCmds);
2350  if (id < 0)
2351    return _error;
2352
2353  const char *p = path._path;
2354  MkWorkspace::Item *ip = work.Find(f4_GetToken(p));
2355  if (ip == 0)
2356    return Fail("no storage with this name");
2357
2358  if (id == 1)
2359    memo(row).SetData(c4_Bytes());
2360  // truncate the existing contents
2361
2362  int mode = id == 0 ? TCL_READABLE : id == 1 ? TCL_WRITABLE : TCL_READABLE |
2363    TCL_WRITABLE;
2364
2365  MkChannel *mkChan = new MkChannel(ip->_storage, path._view, memo, index);
2366  d4_assert(mkChan != 0);
2367
2368  static int mkChanSeq = 0;
2369  char buffer[10];
2370  sprintf(buffer, "mk%d", ++mkChanSeq);
2371
2372  mkChan->_watchMask = 0;
2373  mkChan->_validMask = mode;
2374  mkChan->_interp = interp;
2375  mkChan->_chan = Tcl_CreateChannel(&mkChannelType, buffer, (ClientData)mkChan,
2376    mode);
2377
2378  if (id == 2)
2379    Tcl_Seek(mkChan->_chan, 0, SEEK_END);
2380
2381  Tcl_RegisterChannel(interp, mkChan->_chan);
2382
2383  if (_error)
2384    return _error;
2385
2386  KeepRef o = tcl_NewStringObj(buffer);
2387  return tcl_SetObjResult(o);
2388}
2389
2390int MkTcl::Execute(int oc, Tcl_Obj *const * ov) {
2391  struct CmdDef {
2392    int min;
2393    int max;
2394    const char *desc;
2395  };
2396
2397  static CmdDef defTab[] =  {
2398     {
2399      2, 0, "get cursor ?prop ...?"
2400    }
2401    ,  {
2402      3, 0, "set cursor prop ?value prop value ...?"
2403    }
2404    ,  {
2405      3, 5, "cursor option cursorname ?...?"
2406    }
2407    ,  {
2408      2, 0, "row option ?cursor ...?"
2409    }
2410    ,  {
2411      2, 0, "view option view ?arg?"
2412    }
2413    ,  {
2414      2, 6, "file option ?tag ...?"
2415    }
2416    ,  {
2417      3, 7, "loop cursor ?path first limit incr? {cmds}"
2418    }
2419    ,  {
2420      2, 0, "select path ?...?"
2421    }
2422    ,  {
2423      3, 4, "channel path prop ?mode?"
2424    }
2425    ,
2426    {
2427      0, 0, 0
2428    }
2429    ,
2430  };
2431
2432  _error = TCL_OK;
2433
2434  CmdDef &cd = defTab[id];
2435
2436  objc = oc;
2437  objv = ov;
2438
2439  if (oc < cd.min || (cd.max > 0 && oc > cd.max)) {
2440    msg = "wrong # args: should be \"mk::";
2441    msg += cd.desc;
2442    msg += "\"";
2443
2444    return Fail(msg);
2445  }
2446
2447  EnterMutex(interp);
2448  int result = 0;
2449  switch (id) {
2450    case 0:
2451      result = GetCmd();
2452      break;
2453    case 1:
2454      result = SetCmd();
2455      break;
2456    case 2:
2457      result = CursorCmd();
2458      break;
2459    case 3:
2460      result = RowCmd();
2461      break;
2462    case 4:
2463      result = ViewCmd();
2464      break;
2465    case 5:
2466      result = FileCmd();
2467      break;
2468    case 6:
2469      result = LoopCmd();
2470      break;
2471    case 7:
2472      result = SelectCmd();
2473      break;
2474    case 8:
2475      result = ChannelCmd();
2476      break;
2477  }
2478  LeaveMutex();
2479  return result;
2480}
2481
2482///////////////////////////////////////////////////////////////////////////////
2483
2484void MkWorkspace::CleanupCommands() {
2485  for (int i = 0; i < _commands.GetSize(); ++i)
2486    delete (MkTcl*)_commands.GetAt(i);
2487  _commands.SetSize(0);
2488}
2489
2490static void ExitProc(ClientData cd_) {
2491  delete (MkWorkspace*)cd_;
2492}
2493
2494static void DelProc(ClientData cd_, Tcl_Interp *ip_) {
2495  // got here through assoc's delproc, don't trigger again on exit
2496  Tcl_DeleteExitHandler(ExitProc, cd_);
2497  ExitProc(cd_);
2498}
2499
2500static int Mktcl_Cmds(Tcl_Interp *interp, bool /*safe*/) {
2501  if (Tcl_InitStubs(interp, "8.1", 0) == 0)
2502    return TCL_ERROR;
2503
2504  // Create workspace if not present.
2505  MkWorkspace *ws = (MkWorkspace*)Tcl_GetAssocData(interp, "mk4tcl", 0);
2506  if (ws == 0) {
2507    Tcl_RegisterObjType(&mkPropertyType);
2508    Tcl_RegisterObjType(&mkCursorType);
2509
2510    ws = new MkWorkspace(interp);
2511    // add an association with delproc to catch "interp delete",
2512    // since that does not seem to trigger exitproc handling (!)
2513    Tcl_SetAssocData(interp, "mk4tcl", DelProc, ws);
2514    Tcl_CreateExitHandler(ExitProc, ws);
2515  }
2516
2517  // this list must match the "CmdDef defTab []" above.
2518  static const char *cmds[] =  {
2519    "get", "set", "cursor", "row", "view", "file", "loop", "select", "channel",
2520    0
2521  };
2522
2523  c4_String prefix = "mk::";
2524
2525  for (int i = 0; cmds[i]; ++i)
2526    ws->DefCmd(new MkTcl(ws, interp, i, prefix + cmds[i]));
2527
2528  return Tcl_PkgProvide(interp, "Mk4tcl", "2.4.9.7");
2529}
2530
2531///////////////////////////////////////////////////////////////////////////////
2532// The proper way to load this extension is with "load mk4tcl.{so,dll} mk4tcl",
2533// but 8.0.2 load guesses module "mk" instead of "mk4tcl" (it stops at digits)
2534// when the third argument is omitted, allow that too: "load mk4tcl.{so,dll}".
2535
2536EXTERN int Mk4tcl_Init(Tcl_Interp *interp) {
2537  return Mktcl_Cmds(interp, false);
2538}
2539
2540EXTERN int Mk_Init(Tcl_Interp *interp) {
2541  return Mktcl_Cmds(interp, false);
2542}
2543
2544EXTERN int Mk4tcl_SafeInit(Tcl_Interp *interp) {
2545  return Mktcl_Cmds(interp, true);
2546}
2547
2548EXTERN int Mk_SafeInit(Tcl_Interp *interp) {
2549  return Mktcl_Cmds(interp, true);
2550}
2551
2552///////////////////////////////////////////////////////////////////////////////
2553