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