logtab.h 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224
  1. ////////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2016 RWS Inc, All Rights Reserved
  4. //
  5. // This program is free software; you can redistribute it and/or modify
  6. // it under the terms of version 2 of the GNU General Public License as published by
  7. // the Free Software Foundation
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License along
  15. // with this program; if not, write to the Free Software Foundation, Inc.,
  16. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  17. //
  18. // logtab.h
  19. // Project: Nostril (aka Postal)
  20. //
  21. // History:
  22. // 05/17/97 MJR Started.
  23. //
  24. // 05/19/97 JMI Added forward declaration of CLogTab. Note that normally
  25. // you don't need to do this but, in VC 5.0, the friend
  26. // directive seems to need to know whether or not the class
  27. // being befriended is templated or not so that it'll parse it
  28. // correctly. I'm not sure if this is a parser bug or new
  29. // stricter ANSI requirements. /shrug.
  30. //
  31. // 05/27/97 MJR Added logging feature.
  32. //
  33. // 05/27/97 MJR Added comment token.
  34. // Added operators (=, <, >, !, *).
  35. //
  36. // 07/21/97 BRH/MJR Fixed table matching bug on last column in logic table.
  37. //
  38. ////////////////////////////////////////////////////////////////////////////////
  39. #ifndef LOGTAB_H
  40. #define LOGTAB_H
  41. #include <stdio.h>
  42. #include <ctype.h>
  43. #include "RSPiX.h"
  44. class RFile;
  45. // Name of log file (if there is one)
  46. #define LOGTAB_LOGFILE "logtablog.log"
  47. // Error codes
  48. #define LOGTAB_ERR_NO_SUCH_VAR -10
  49. #define LOGTAB_ERR_DUP_VAR -11
  50. #define LOGTAB_ERR_INVALID_VAR_TEXT -12
  51. #define LOGTAB_ERR_BAD_RANGE_VAR_TEXT -13
  52. #define LOGTAB_ERR_NOT_OUTPUT_VAR -14
  53. #define LOGTAB_ERR_VAR_NOT_FOUND -15
  54. #define LOGTAB_ERR_TOO_MANY_VARS -16
  55. #define LOGTAB_ERR_NO_ROWS_AFTER_HEADINGS -17
  56. #define LOGTAB_ERR_NO_INPUTS -18
  57. #define LOGTAB_ERR_NO_OUTPUT_SEPARATOR -19
  58. #define LOGTAB_ERR_NO_OUTPUTS -20
  59. #define LOGTAB_ERR_EXTRA_COLUMNS -21
  60. #define LOGTAB_ERR_TOO_FEW_COLUMNS -22
  61. #define LOGTAB_ERR_TOO_MANY_ROWS -23
  62. #define LOGTAB_ERR_UNEXPECTED_EOF -24
  63. #define LOGTAB_ERR_READ_ERROR -25
  64. #define LOGTAB_ERR_TEXT_TOO_LONG -26
  65. // Token for "comment" (everything after it is ignored up to the end of the row)
  66. #define LOGTAB_COMMENT_TOKEN "//"
  67. // Symbol in logic table files used to separate input columns from output columns.
  68. #define LOGTAB_OUTPUT_SEPARATOR "->"
  69. // Symbol in logic table files for "don't care"
  70. #define LOGTAB_DONT_CARE "*"
  71. // The default name given to all vars, just in case someone forgets to assign
  72. // a real name in their derived var class.
  73. #define LOGTABVAR_NONAME "<NO NAME!>"
  74. // For "internal" use only when calling rspMsgBox(). This whole approach will
  75. // change once I switch over to an error callback.
  76. #define LOGTAB_MSG RSP_MB_BUT_OK | RSP_MB_ICN_STOP, "Logic Table Loader"
  77. // Forward declaration of CLogTab. Note that normally you don't need to do this
  78. // but, in VC 5.0, the friend directive seems to need to know whether or not
  79. // the class being befriended is templated or not so that it'll parse
  80. // it correctly. I'm not sure if this is a parser bug or new stricter
  81. // ANSI requirements. /shrug.
  82. template <class usertype>
  83. class CLogTab;
  84. ////////////////////////////////////////////////////////////////////////////////
  85. // This is the base-class implimentation for Logic Table Variables.
  86. ////////////////////////////////////////////////////////////////////////////////
  87. template <class usertype>
  88. class CLogTabVar
  89. {
  90. // Make logic table a friend
  91. friend class CLogTab<usertype>;
  92. //------------------------------------------------------------------------------
  93. // Types, enums, etc.
  94. //------------------------------------------------------------------------------
  95. public:
  96. //------------------------------------------------------------------------------
  97. // Variables
  98. //------------------------------------------------------------------------------
  99. protected:
  100. static CLogTabVar* ms_pHead; // Head of linked list of variables
  101. char* m_pszName; // Variable name
  102. short m_sMaxVal; // Maximum value
  103. bool m_bSettable; // Whether val is settable (true) or not (false)
  104. short m_sNumStrings; // Number of strings (0 if number based)
  105. char** m_papszStrings; // Pointer to array of pointers to strings
  106. short m_sOutputWidth; // Width of output in characters
  107. short m_sRefCount; // Reference count (how many logic tables)
  108. CLogTabVar* m_pNext; // Pointer to next variable
  109. CLogTabVar* m_pPrev; // Pointer to prev variable
  110. //------------------------------------------------------------------------------
  111. // Functions
  112. //------------------------------------------------------------------------------
  113. public:
  114. ////////////////////////////////////////////////////////////////////////////////
  115. // Constructor
  116. ////////////////////////////////////////////////////////////////////////////////
  117. CLogTabVar(void)
  118. {
  119. // Point name to default value so we can detect errors and so we can use
  120. // this pointer without worrying about whether it's valid.
  121. m_pszName = LOGTABVAR_NONAME;
  122. // Set maximum value to default of 0
  123. m_sMaxVal = 0;
  124. // Default to value not being settable
  125. m_bSettable = false;
  126. // Init string stuff
  127. m_sNumStrings = 0;
  128. m_papszStrings = 0;
  129. // Set default output width
  130. m_sOutputWidth = strlen(LOGTABVAR_NONAME);
  131. // Clear reference count
  132. m_sRefCount = 0;
  133. // Insert at front of linked list
  134. m_pNext = ms_pHead;
  135. m_pPrev = 0;
  136. if (ms_pHead)
  137. ms_pHead->m_pPrev = this;
  138. ms_pHead = this;
  139. }
  140. ////////////////////////////////////////////////////////////////////////////////
  141. // Destructor
  142. ////////////////////////////////////////////////////////////////////////////////
  143. ~CLogTabVar()
  144. {
  145. // Check reference count and issue warning if necessary
  146. if (m_sRefCount > 0)
  147. {
  148. TRACE("CLogTabVar::~CLogTabVar(): Destroying '%s', which is still being used by CLogTab(s) (refcount = %hd)!\n", m_pszName, (short)m_sRefCount);
  149. ASSERT("Destroying CLogTabVar that's being used by one or more CLogTab's (see trace for details)" == 0);
  150. }
  151. // Remove from linked list
  152. if (m_pNext)
  153. m_pNext->m_pPrev = m_pPrev;
  154. if (m_pPrev)
  155. m_pPrev->m_pNext = m_pNext;
  156. else
  157. ms_pHead = m_pNext;
  158. }
  159. ////////////////////////////////////////////////////////////////////////////////
  160. // Find the variable with the specified name
  161. ////////////////////////////////////////////////////////////////////////////////
  162. static
  163. short FindVar( // Returns 0 if successfull, non-zero otherwise
  164. char* pszName, // In: Variable name to find
  165. CLogTabVar** ppVar); // Out: Pointer to variable (if found)
  166. ////////////////////////////////////////////////////////////////////////////////
  167. // Increment reference count
  168. ////////////////////////////////////////////////////////////////////////////////
  169. void IncRefCount(void)
  170. {
  171. m_sRefCount++;
  172. }
  173. ////////////////////////////////////////////////////////////////////////////////
  174. // Decrement reference count
  175. ////////////////////////////////////////////////////////////////////////////////
  176. void DecRefCount(void)
  177. {
  178. m_sRefCount--;
  179. }
  180. ////////////////////////////////////////////////////////////////////////////////
  181. // Convert text into value
  182. ////////////////////////////////////////////////////////////////////////////////
  183. short TextToVal( // Returns 0 if successfull, non-zero otherwise
  184. char* pszText, // In: Text to convert
  185. short* psVal); // Out: Value (only if successfull)
  186. ////////////////////////////////////////////////////////////////////////////////
  187. // Convert value into text. Resulting text will be truncated if necessary to
  188. // fit specified maximum text length. If the text is truncated, the final
  189. // character will be a '#' to indicate the truncation.
  190. //
  191. // NOTE: Specified text length MUST BE AT LEAST 10! This helps avoid all sorts
  192. // of niggling little problems.
  193. ////////////////////////////////////////////////////////////////////////////////
  194. short ValToText( // Returns 0 if successfull, non-zero otherwise
  195. short sVal, // In: Value to convert
  196. char* pszText, // Out: Text (only if successfull)
  197. short sMaxText); // In: Maximum text length (must be >= 10)
  198. ////////////////////////////////////////////////////////////////////////////////
  199. // Get maximum value (range is 0 to N, where this returns N)
  200. ////////////////////////////////////////////////////////////////////////////////
  201. short GetMax(void) // Returns maximum value
  202. {
  203. return m_sMaxVal;
  204. }
  205. ////////////////////////////////////////////////////////////////////////////////
  206. // Get name
  207. ////////////////////////////////////////////////////////////////////////////////
  208. char* GetName(void) // Returns name
  209. {
  210. return m_pszName;
  211. }
  212. ////////////////////////////////////////////////////////////////////////////////
  213. // Get current value (range is 0 to N, where N is returned by GetMax())
  214. ////////////////////////////////////////////////////////////////////////////////
  215. virtual short GetVal( // Returns current value
  216. usertype user) // In: User type passed here
  217. {
  218. TRACE("CLogTabVar::GetVal(): '%s' is likely missing a derived-class GetVal()!\n", m_pszName);
  219. return 0;
  220. }
  221. ////////////////////////////////////////////////////////////////////////////////
  222. // Set current value (range is 0 to N, where N is returned by GetMax())
  223. ////////////////////////////////////////////////////////////////////////////////
  224. virtual void SetVal(
  225. usertype user, // In: User type passed here
  226. short sVal) // In: Value to set
  227. {
  228. if (m_bSettable)
  229. TRACE("CLogTabVar::SetVal(): '%s' is likely missing a derived-class SetVal()!\n", m_pszName);
  230. else
  231. TRACE("CLogTabVar::SetVal(): '%s' is not settable!\n", m_pszName);
  232. }
  233. };
  234. ////////////////////////////////////////////////////////////////////////////////
  235. // Static's for CLogTabVar
  236. ////////////////////////////////////////////////////////////////////////////////
  237. template <class usertype>
  238. CLogTabVar<usertype>* CLogTabVar<usertype>::ms_pHead = 0;
  239. ////////////////////////////////////////////////////////////////////////////////
  240. // Find the variable with the specified name
  241. ////////////////////////////////////////////////////////////////////////////////
  242. //static
  243. template <class usertype>
  244. short CLogTabVar<usertype>::FindVar( // Returns 0 if successfull, non-zero otherwise
  245. char* pszName, // In: Variable name to find
  246. CLogTabVar** ppVar) // Out: Pointer to variable (if found)
  247. {
  248. short sResult = 0;
  249. // Scan through linked list looking for specified name
  250. short sFound = 0;
  251. CLogTabVar* p = ms_pHead;
  252. while (p)
  253. {
  254. if (rspStricmp(pszName, p->m_pszName) == 0)
  255. {
  256. *ppVar = p;
  257. sFound++;
  258. // In debug mode we don't want to stop (we're looking for duplicates)
  259. #ifndef _DEBUG
  260. break; // Only stop in release mode
  261. #endif
  262. }
  263. p = p->m_pNext;
  264. }
  265. // If not found, it's an error
  266. if (sFound == 0)
  267. {
  268. sResult = LOGTAB_ERR_NO_SUCH_VAR;
  269. TRACE("CLogTabVar::FindName(): Cannot find variable named '%s'\n", pszName);
  270. }
  271. // In debug mode, we check for duplicate names
  272. #ifdef _DEBUG
  273. if (sFound > 1)
  274. {
  275. sResult = LOGTAB_ERR_DUP_VAR;
  276. TRACE("CLogTabVar::FindName(): Found %hd variables named '%s'!\n", (short)sFound, pszName);
  277. }
  278. #endif
  279. return sResult;
  280. }
  281. ////////////////////////////////////////////////////////////////////////////////
  282. // Convert text into value
  283. ////////////////////////////////////////////////////////////////////////////////
  284. template <class usertype>
  285. short CLogTabVar<usertype>::TextToVal( // Returns 0 if successfull, non-zero otherwise
  286. char* pszText, // In: Text to convert
  287. short* psVal) // Out: Value (only if successfull)
  288. {
  289. short sResult = 0;
  290. // Check if this var is string-based or number-based
  291. if (m_sNumStrings > 0)
  292. {
  293. // Scan through array of strings looking for a match
  294. short s;
  295. for (s = 0; s < m_sNumStrings; s++)
  296. {
  297. if (rspStricmp(pszText, m_papszStrings[s]) == 0)
  298. {
  299. *psVal = s;
  300. break;
  301. }
  302. }
  303. // If no matches were found, it's an error
  304. if (s == m_sNumStrings)
  305. {
  306. sResult = LOGTAB_ERR_INVALID_VAR_TEXT;
  307. TRACE("CLogTabVar::TextToVal(): '%s' is not a valid setting for '%s'!\n", pszText, m_pszName);
  308. }
  309. }
  310. else
  311. {
  312. // Make sure text consists only of digits
  313. short sLen = strlen(pszText);
  314. for (short s = 0; s < sLen; s++)
  315. {
  316. if (!isdigit(pszText[s]))
  317. {
  318. sResult = LOGTAB_ERR_INVALID_VAR_TEXT;
  319. TRACE("CLogTabVar::TextToVal(): '%s' is not a valid setting for '%s'!\n", pszText, m_pszName);
  320. break;
  321. }
  322. }
  323. if (sResult == 0)
  324. {
  325. // Convert to value, then validate range
  326. short sTmp = (short)atoi(pszText);
  327. if ((sTmp >= 0) && (sTmp <= m_sMaxVal))
  328. *psVal = sTmp;
  329. else
  330. {
  331. sResult = LOGTAB_ERR_BAD_RANGE_VAR_TEXT;
  332. TRACE("CLogTabVar::TextToVal(): '%hd' is out of range for '%s'!\n", (short)sTmp, m_pszName);
  333. }
  334. }
  335. }
  336. return sResult;
  337. }
  338. ////////////////////////////////////////////////////////////////////////////////
  339. // Convert value into text. Resulting text will be truncated if necessary to
  340. // fit specified maximum text length. If the text is truncated, the final
  341. // character will be a '#' to indicate the truncation.
  342. //
  343. // NOTE: Specified text length MUST BE AT LEAST 10! This helps avoid all sorts
  344. // of niggling little problems.
  345. ////////////////////////////////////////////////////////////////////////////////
  346. template <class usertype>
  347. short CLogTabVar<usertype>::ValToText( // Returns 0 if successfull, non-zero otherwise
  348. short sVal, // In: Value to convert
  349. char* pszText, // Out: Text (only if successfull)
  350. short sMaxText) // In: Maximum text length (must be >= 10)
  351. {
  352. ASSERT(sMaxText >= 10);
  353. short sResult = 0;
  354. // Check if this var is string-based or number-based
  355. if (m_sNumStrings > 0)
  356. {
  357. // If value is in range, lookup its string equivalent
  358. if ((sVal >= 0) && (sVal < m_sNumStrings))
  359. {
  360. if (strlen(m_papszStrings[sVal]) < sMaxText)
  361. strcpy(pszText, m_papszStrings[sVal]);
  362. else
  363. {
  364. strncpy(pszText, m_papszStrings[sVal], sMaxText - 2);
  365. pszText[sMaxText - 2] = '#';
  366. pszText[sMaxText - 1] = 0;
  367. }
  368. }
  369. else
  370. {
  371. sResult = LOGTAB_ERR_BAD_RANGE_VAR_TEXT;
  372. TRACE("CLogTabVar::ValToText(): Value (%hd) is out of range for '%s'!\n", (short)sVal, m_pszName);
  373. }
  374. }
  375. else
  376. {
  377. // Convert value to text equivalent (we know that a short can't take up
  378. // any more than 6 characters, and that's with a negative sign in front).
  379. sprintf(pszText, "%hd", (short)sVal);
  380. }
  381. return sResult;
  382. }
  383. ////////////////////////////////////////////////////////////////////////////////
  384. // Logic Table Class
  385. ////////////////////////////////////////////////////////////////////////////////
  386. template <class usertype>
  387. class CLogTab
  388. {
  389. //------------------------------------------------------------------------------
  390. // Types, enums, etc.
  391. //------------------------------------------------------------------------------
  392. public:
  393. typedef enum
  394. {
  395. MaxVars = 32, // Limited only by memory considerations
  396. MaxRows = 100, // Limited only by memory considerations
  397. MaxTextLen = 32 // A reasonable value
  398. };
  399. typedef enum
  400. {
  401. Equal,
  402. Not,
  403. Less,
  404. Greater,
  405. DontCare
  406. } Operand;
  407. typedef struct
  408. {
  409. Operand operand;
  410. short sEntry;
  411. } Cell;
  412. //------------------------------------------------------------------------------
  413. // Variables
  414. //------------------------------------------------------------------------------
  415. protected:
  416. short m_sRows; // Number of rows in table
  417. short m_sInVars; // Number of input vars
  418. short m_sOutVars; // Number of output vars
  419. short m_sTotalVars; // Number of vars
  420. CLogTabVar<usertype>* m_apVars[MaxVars]; // Array of pointers to vars
  421. Cell m_cellTable[MaxRows][MaxVars]; // Table of input and output cells
  422. bool m_bLogFileError;
  423. FILE* m_fpLog;
  424. short m_sLogLastRow;
  425. //------------------------------------------------------------------------------
  426. // Functions
  427. //------------------------------------------------------------------------------
  428. public:
  429. ////////////////////////////////////////////////////////////////////////////////
  430. // Constructor
  431. ////////////////////////////////////////////////////////////////////////////////
  432. CLogTab()
  433. {
  434. m_sRows = 0;
  435. m_sInVars = 0;
  436. m_sOutVars = 0;
  437. m_sTotalVars = 0;
  438. m_bLogFileError = false;
  439. m_fpLog = NULL;
  440. m_sLogLastRow = -1;
  441. }
  442. ////////////////////////////////////////////////////////////////////////////////
  443. // Destructor
  444. ////////////////////////////////////////////////////////////////////////////////
  445. ~CLogTab()
  446. {
  447. // Close log file
  448. if (m_fpLog != NULL)
  449. fclose(m_fpLog);
  450. FreeVars();
  451. }
  452. ////////////////////////////////////////////////////////////////////////////////
  453. // Evaluate table
  454. ////////////////////////////////////////////////////////////////////////////////
  455. short Evaluate( // Returns 0 if successfull, non-zero otherwise
  456. usertype user, // In: User type passed here
  457. bool bLog); // In: Whether to log (true) or not (false)
  458. ////////////////////////////////////////////////////////////////////////////////
  459. // Dump contents of logic table to file.
  460. ////////////////////////////////////////////////////////////////////////////////
  461. void DumpToLog(void);
  462. ////////////////////////////////////////////////////////////////////////////////
  463. // Load from current position of already-open file
  464. ////////////////////////////////////////////////////////////////////////////////
  465. short Load( // Returns 0 if successfull, non-zero otherwise
  466. RFile* pFile); // In: RFile to load from
  467. ////////////////////////////////////////////////////////////////////////////////
  468. // Save to current position of already-open file
  469. ////////////////////////////////////////////////////////////////////////////////
  470. short Save( // Returns 0 if successfull, non-zero otherwise
  471. RFile* pFile); // In: RFile to save to
  472. private:
  473. ////////////////////////////////////////////////////////////////////////////////
  474. // Free vars associated with this table
  475. ////////////////////////////////////////////////////////////////////////////////
  476. void FreeVars(void)
  477. {
  478. // Decrement vars' reference counts since we're no longer using them
  479. for (short s = 0; s < m_sTotalVars; s++)
  480. m_apVars[s]->DecRefCount();
  481. }
  482. ////////////////////////////////////////////////////////////////////////////////
  483. // (See .cpp for details)
  484. ////////////////////////////////////////////////////////////////////////////////
  485. short ReadEntry(
  486. RFile* pFile, // In: RFile to read from
  487. char* pszEntry, // Out: Entry is returned in here
  488. short sMaxEntrySize, // In: Maximum size of entry
  489. bool* pbEndOfTable, // Out: true if this is last entry in table
  490. bool* pbEndOfRow, // Out: true if this is last entry in row
  491. char* pcSave); // I/O: Save char from last call (or 0 if none)
  492. };
  493. ////////////////////////////////////////////////////////////////////////////////
  494. // Evaluate table
  495. ////////////////////////////////////////////////////////////////////////////////
  496. template <class usertype>
  497. short CLogTab<usertype>::Evaluate(
  498. usertype user, // In: User type passed here
  499. bool bLog) // In: Whether to log (true) or not (false)
  500. {
  501. short sResult = 0;
  502. // If we're asked to log but a log error occurred previously, then don't log
  503. if (bLog && m_bLogFileError)
  504. bLog = false;
  505. // If we're logging but we haven't started the log file yet, then do it now.
  506. if (bLog && (m_fpLog == NULL))
  507. {
  508. m_fpLog = fopen(LOGTAB_LOGFILE, "wt");
  509. if (m_fpLog != NULL)
  510. DumpToLog();
  511. else
  512. m_bLogFileError = true;
  513. }
  514. // Clear array of "cached" vals. By caching these values, we avoid having
  515. // to re-evaluate the vars each time we need them, thereby saving lots of time.
  516. short sVals[MaxVars];
  517. for (short s = 0; s < m_sInVars; s++)
  518. sVals[s] = -1;
  519. // Scan table looking for a row that matches all the way across
  520. char acBuf[256];
  521. bool bRowMatched = false;
  522. for (short sRow = 0; sRow < m_sRows; sRow++)
  523. {
  524. // Optimistically default to matching cell (it's faster)
  525. bool bCellMatched = true;
  526. short sVar;
  527. for (sVar = 0; bCellMatched && (sVar < m_sInVars); sVar++)
  528. {
  529. // Get the entry from the table
  530. short sEntry = m_cellTable[sRow][sVar].sEntry;
  531. // Evaluate entry as per operand type
  532. switch(m_cellTable[sRow][sVar].operand)
  533. {
  534. case Equal:
  535. // If value isn't cached, then ask var for it now
  536. if (sVals[sVar] == -1)
  537. sVals[sVar] = m_apVars[sVar]->GetVal(user);
  538. // If value doesn't match entry, it fails
  539. if (sVals[sVar] != sEntry)
  540. bCellMatched = false;
  541. break;
  542. case Not:
  543. // If value isn't cached, then ask var for it now
  544. if (sVals[sVar] == -1)
  545. sVals[sVar] = m_apVars[sVar]->GetVal(user);
  546. // If value doesn't match entry, it fails
  547. if (sVals[sVar] == sEntry)
  548. bCellMatched = false;
  549. break;
  550. case Less:
  551. // If value isn't cached, then ask var for it now
  552. if (sVals[sVar] == -1)
  553. sVals[sVar] = m_apVars[sVar]->GetVal(user);
  554. // If value is not less than entry, it fails
  555. if (sVals[sVar] >= sEntry)
  556. bCellMatched = false;
  557. break;
  558. case Greater:
  559. // If value isn't cached, then ask var for it now
  560. if (sVals[sVar] == -1)
  561. sVals[sVar] = m_apVars[sVar]->GetVal(user);
  562. // If value is not greater than entry, it fails
  563. if (sVals[sVar] <= sEntry)
  564. bCellMatched = false;
  565. break;
  566. case DontCare:
  567. // Don't need to do anything
  568. break;
  569. default:
  570. TRACE("CLogTab::Evaluate(): Unknown operand!\n");
  571. break;
  572. }
  573. }
  574. // If we reached the last input var, then this is row is a complete match!
  575. if (sVar == m_sInVars && bCellMatched)
  576. {
  577. // Set flag
  578. bRowMatched = true;
  579. // Set output vars as indicated by table entries
  580. for (short sVar = 0; sVar < m_sOutVars; sVar++)
  581. {
  582. // If operand is NOT don't care, then set var to specified entry
  583. if (m_cellTable[sRow][m_sInVars + sVar].operand != DontCare)
  584. m_apVars[m_sInVars + sVar]->SetVal(user, m_cellTable[sRow][m_sInVars + sVar].sEntry);
  585. }
  586. // If we're logging and this isn't the same row as we logged last time...
  587. if (bLog && (sRow != m_sLogLastRow))
  588. {
  589. // Save new row for next time
  590. m_sLogLastRow = sRow;
  591. // Write out the values of all vars
  592. fprintf(m_fpLog, "Matched row #%hd: ", (short)sRow);
  593. for (short sVar = 0; sVar < m_sTotalVars; sVar++)
  594. {
  595. fprintf(m_fpLog, "%s=", m_apVars[sVar]->GetName());
  596. if (sVar < m_sInVars)
  597. {
  598. short sEntry = sVals[sVar];
  599. if (sEntry != -1)
  600. {
  601. m_apVars[sVar]->ValToText(sEntry, acBuf, sizeof(acBuf));
  602. fprintf(m_fpLog, "%s, ", acBuf);
  603. }
  604. else
  605. fprintf(m_fpLog, "<ignored>, ");
  606. }
  607. else
  608. {
  609. if (m_cellTable[sRow][sVar].operand != DontCare)
  610. {
  611. m_apVars[sVar]->ValToText(m_cellTable[sRow][sVar].sEntry, acBuf, sizeof(acBuf));
  612. fprintf(m_fpLog, "%s", acBuf);
  613. }
  614. else
  615. fprintf(m_fpLog, "<dontcare>");
  616. m_apVars[sVar]->ValToText(m_apVars[sVar]->GetVal(user), acBuf, sizeof(acBuf));
  617. fprintf(m_fpLog, "(%s), ", acBuf);
  618. }
  619. // Put a separator between the input and output vars
  620. if ((sVar + 1) == m_sInVars)
  621. fprintf(m_fpLog, " => ");
  622. }
  623. // Write out the row number we matched
  624. fprintf(m_fpLog, "\n");
  625. }
  626. // Stop row loop
  627. break;
  628. }
  629. }
  630. // If we're logging and no rows matched...
  631. if (bLog && !bRowMatched)
  632. {
  633. // Reset last row so next matching row will get logged
  634. m_sLogLastRow = -1;
  635. // Show input vars
  636. fprintf(m_fpLog, "No rows matched! ");
  637. for (short sVar = 0; sVar < m_sInVars; sVar++)
  638. {
  639. fprintf(m_fpLog, "%s=", m_apVars[sVar]->GetName());
  640. short sEntry = sVals[sVar];
  641. if (sEntry != -1)
  642. {
  643. m_apVars[sVar]->ValToText(sEntry, acBuf, sizeof(acBuf));
  644. fprintf(m_fpLog, "%s, ", acBuf);
  645. }
  646. else
  647. fprintf(m_fpLog, "<ignored>, ");
  648. }
  649. fprintf(m_fpLog, "\n");
  650. }
  651. return sResult;
  652. }
  653. ////////////////////////////////////////////////////////////////////////////////
  654. // Dump contents of logic table to file.
  655. ////////////////////////////////////////////////////////////////////////////////
  656. template <class usertype>
  657. void CLogTab<usertype>::DumpToLog(void)
  658. {
  659. fprintf(m_fpLog, "This is a LogTab logging file.\n\n");
  660. fprintf(m_fpLog, "Contents of the logic table:\n");
  661. // Write out first row (names of vars)
  662. for (short sVar = 0; sVar < m_sTotalVars; sVar++)
  663. {
  664. fprintf(m_fpLog, "%s ", m_apVars[sVar]->GetName());
  665. // Put a separator between the input and output vars
  666. if ((sVar + 1) == m_sInVars)
  667. fprintf(m_fpLog, " => ");
  668. }
  669. fprintf(m_fpLog, "\n");
  670. // Write out rest of table
  671. for (short sRow = 0; sRow < m_sRows; sRow++)
  672. {
  673. fprintf(m_fpLog, "Row #%hd: ", (short)sRow);
  674. for (short sVar = 0; sVar < m_sTotalVars; sVar++)
  675. {
  676. // Get the entry from the table
  677. Operand op = m_cellTable[sRow][sVar].operand;
  678. switch(op)
  679. {
  680. case Equal:
  681. fprintf(m_fpLog, "=");
  682. break;
  683. case Not:
  684. fprintf(m_fpLog, "!");
  685. break;
  686. case Less:
  687. fprintf(m_fpLog, "<");
  688. break;
  689. case Greater:
  690. fprintf(m_fpLog, ">");
  691. break;
  692. case DontCare:
  693. fprintf(m_fpLog, "* ");
  694. break;
  695. default:
  696. TRACE("CLogTab::Evaluate(): Unknown operand!\n");
  697. break;
  698. }
  699. if (op != DontCare)
  700. {
  701. char acBuf[256];
  702. m_apVars[sVar]->ValToText(m_cellTable[sRow][sVar].sEntry, acBuf, sizeof(acBuf));
  703. fprintf(m_fpLog, "%s ", acBuf);
  704. }
  705. }
  706. fprintf(m_fpLog, "\n");
  707. }
  708. fprintf(m_fpLog, "\n");
  709. }
  710. ////////////////////////////////////////////////////////////////////////////////
  711. // Load from current position of already-open file
  712. ////////////////////////////////////////////////////////////////////////////////
  713. template <class usertype>
  714. short CLogTab<usertype>::Load( // Returns 0 if successfull, non-zero otherwise
  715. RFile* pFile) // In: RFile to load from
  716. {
  717. short sResult = 0;
  718. // Free any vars currently associated with this table
  719. FreeVars();
  720. // Reset counts
  721. m_sInVars = 0;
  722. m_sOutVars = 0;
  723. m_sTotalVars = 0;
  724. m_sRows = 0;
  725. // Read first row, which contains variable names to be associated with each column
  726. char acText[MaxTextLen];
  727. char cSave = 0;
  728. bool bEndOfTable;
  729. bool bEndOfRow;
  730. bool bOutput = false;
  731. do {
  732. // Get next entry
  733. sResult = ReadEntry(pFile, acText, sizeof(acText), &bEndOfTable, &bEndOfRow, &cSave);
  734. if (sResult == 0)
  735. {
  736. // Output separator is special case -- it separates output vars from input vars
  737. if (rspStricmp(acText, LOGTAB_OUTPUT_SEPARATOR) == 0)
  738. {
  739. // All subsequent columns are output vars
  740. bOutput = true;
  741. }
  742. else if (rspStricmp(acText, LOGTAB_COMMENT_TOKEN) == 0)
  743. {
  744. // If we get a comment token, ignore everything that follows until the end of the row
  745. while ((sResult == 0) && !bEndOfRow)
  746. sResult = ReadEntry(pFile, acText, sizeof(acText), &bEndOfTable, &bEndOfRow, &cSave);
  747. bEndOfRow = false;
  748. }
  749. else
  750. {
  751. // Make sure total vars doesn't exceed max allowable
  752. if (m_sTotalVars < MaxVars)
  753. {
  754. // Find the var with the specified name
  755. CLogTabVar<usertype>* pVar;
  756. if (CLogTabVar<usertype>::FindVar(acText, &pVar) == 0)
  757. {
  758. // If this is an output, it must settable or it's an error
  759. if (bOutput && !pVar->m_bSettable)
  760. {
  761. sResult = LOGTAB_ERR_NOT_OUTPUT_VAR;
  762. TRACE("CLogTab::Load(): '%s' cannot be used as an output!\n", pVar->m_pszName);
  763. rspMsgBox(LOGTAB_MSG, "'%s' cannot be used as an output.", pVar->m_pszName);
  764. }
  765. if (sResult == 0)
  766. {
  767. // Add var to array and increment total number of vars
  768. m_apVars[m_sTotalVars++] = pVar;
  769. // Increment var's reference count since we're using it
  770. pVar->IncRefCount();
  771. // Adjust count of input or output vars, as appropriate
  772. if (bOutput)
  773. m_sOutVars++;
  774. else
  775. m_sInVars++;
  776. }
  777. }
  778. else
  779. {
  780. sResult = LOGTAB_ERR_VAR_NOT_FOUND;
  781. TRACE("CLogTab::Load(): Cannot resolve variable named '%s'!\n", acText);
  782. rspMsgBox(LOGTAB_MSG, "Variable named '%s' does not exist or is defined multiple times. Either way, you are hosed.", acText);
  783. }
  784. }
  785. else
  786. {
  787. sResult = LOGTAB_ERR_TOO_MANY_VARS;
  788. TRACE("CLogTab::Load(): Table exceeds maximum number of vars, which is currently set to %hd!\n", (short)MaxVars);
  789. rspMsgBox(LOGTAB_MSG, "Table exceeds maximum number of vars, which is currently limited to %hd.", (short)MaxVars);
  790. }
  791. }
  792. }
  793. } while ((sResult == 0) && !bEndOfRow);
  794. if (sResult == 0)
  795. {
  796. // Make sure we didn't hit the end of the table
  797. if (bEndOfTable)
  798. {
  799. sResult = LOGTAB_ERR_NO_ROWS_AFTER_HEADINGS;
  800. TRACE("CLogTab::Load(): Unexpected end of table following initial row of column headings!\n");
  801. rspMsgBox(LOGTAB_MSG, "Unexpected end of table following initial row of column headings.");
  802. }
  803. else
  804. {
  805. // Make sure table contains at least one input column
  806. if (m_sInVars < 1)
  807. {
  808. sResult = LOGTAB_ERR_NO_INPUTS;
  809. TRACE("CLogTab::Load(): No inputs were found in initial row of column headings!\n");
  810. rspMsgBox(LOGTAB_MSG, "No inputs were found in initial row of column headings.");
  811. }
  812. // Make sure table contains output separator and at least one output column
  813. if (!bOutput)
  814. {
  815. sResult = LOGTAB_ERR_NO_OUTPUT_SEPARATOR;
  816. TRACE("CLogTab::Load(): Output separator '%s' was not found in initial row of column headings!\n", LOGTAB_OUTPUT_SEPARATOR);
  817. rspMsgBox(LOGTAB_MSG, "Output separator '%s' was not found in initial row of column headings.", LOGTAB_OUTPUT_SEPARATOR);
  818. }
  819. else if (m_sOutVars < 1)
  820. {
  821. sResult = LOGTAB_ERR_NO_OUTPUTS;
  822. TRACE("CLogTab::Load(): No outputs were found in initial row of column headings!\n");
  823. rspMsgBox(LOGTAB_MSG, "No outputs were found in initial row of column headings.");
  824. }
  825. }
  826. if (sResult == 0)
  827. {
  828. // Read the remaining rows
  829. do {
  830. // Each row must contain the same number of columns as the total number of vars
  831. short sColumn = 0;
  832. do {
  833. // Get next entry
  834. sResult = ReadEntry(pFile, acText, sizeof(acText), &bEndOfTable, &bEndOfRow, &cSave);
  835. if (sResult == 0)
  836. {
  837. // Check for comment token
  838. if (rspStricmp(acText, LOGTAB_COMMENT_TOKEN) == 0)
  839. {
  840. // If we get a comment token, ignore everything that follows until the end of the row
  841. while ((sResult == 0) && !bEndOfRow)
  842. sResult = ReadEntry(pFile, acText, sizeof(acText), &bEndOfTable, &bEndOfRow, &cSave);
  843. bEndOfRow = false;
  844. }
  845. else
  846. {
  847. // Make sure number of columns doesn't exceed number of vars
  848. if (sColumn < m_sTotalVars)
  849. {
  850. // Leading character determines type of operand
  851. short sOffset = 0;
  852. switch(acText[0])
  853. {
  854. case '=':
  855. sOffset = 1;
  856. default: // If there is no special prefix, then we assume "="
  857. m_cellTable[m_sRows][sColumn].operand = Equal;
  858. break;
  859. case '!':
  860. sOffset = 1;
  861. m_cellTable[m_sRows][sColumn].operand = Not;
  862. break;
  863. case '<':
  864. sOffset = 1;
  865. m_cellTable[m_sRows][sColumn].operand = Less;
  866. break;
  867. case '>':
  868. sOffset = 1;
  869. m_cellTable[m_sRows][sColumn].operand = Greater;
  870. break;
  871. case '*':
  872. sOffset = 1;
  873. m_cellTable[m_sRows][sColumn].operand = DontCare;
  874. break;
  875. }
  876. if (m_cellTable[m_sRows][sColumn].operand != DontCare)
  877. {
  878. // Use this column's var to convert text into value
  879. short sVal;
  880. CLogTabVar<usertype>* pVar = m_apVars[sColumn];
  881. sResult = pVar->TextToVal(&(acText[sOffset]), &sVal);
  882. if (sResult == 0)
  883. m_cellTable[m_sRows][sColumn].sEntry = sVal;
  884. else
  885. {
  886. // sResult was properly set by TextToVal()
  887. TRACE("CLogTab::Load(): '%s' is an invalid setting for '%s'!\n", acText, pVar->m_pszName);
  888. rspMsgBox(LOGTAB_MSG, "'%s' is an invalid setting for '%s'.", acText, pVar->m_pszName);
  889. }
  890. }
  891. // Increment column count
  892. if (sResult == 0)
  893. sColumn++;
  894. }
  895. else
  896. {
  897. sResult = LOGTAB_ERR_EXTRA_COLUMNS;
  898. TRACE("CLogTab::Load(): Row #%hd contains extra columns!\n", (short)(m_sRows + 2));
  899. rspMsgBox(LOGTAB_MSG, "Row #%hd has more columns than the first row.", (short)(m_sRows + 2));
  900. }
  901. }
  902. }
  903. } while ((sResult == 0) && !bEndOfRow);
  904. // Make sure there weren't too few columns
  905. if ((sResult == 0) && (sColumn < m_sTotalVars))
  906. {
  907. sResult = LOGTAB_ERR_TOO_FEW_COLUMNS;
  908. TRACE("CLogTab::Load(): Row #%hd does not contain enough columns!\n", (short)(m_sRows + 2));
  909. rspMsgBox(LOGTAB_MSG, "Row #%hd contains less columns than the first row.\n", (short)(m_sRows + 2));
  910. }
  911. // Increment row count
  912. if (sResult == 0)
  913. {
  914. m_sRows++;
  915. // If we're at the max number of rows but NOT at the end of the table,
  916. // then the table obviously contains too many rows.
  917. if ((m_sRows == MaxRows) && !bEndOfTable)
  918. {
  919. sResult = LOGTAB_ERR_TOO_MANY_ROWS;
  920. TRACE("CLogTab::Load(): Table exceeds maximum number of rows, which is currently set to %hd\n", (short)MaxRows);
  921. rspMsgBox(LOGTAB_MSG, "Table exceeds maximum number of rows, which is currently limited to %hd.", (short)MaxRows);
  922. }
  923. }
  924. } while ((sResult == 0) && !bEndOfTable);
  925. // Make sure there was at least one row
  926. if ((sResult == 0) && (m_sRows < 1))
  927. {
  928. sResult = LOGTAB_ERR_NO_ROWS_AFTER_HEADINGS;
  929. TRACE("CLogTab::Load(): Table has no rows following initial column headings!\n");
  930. rspMsgBox(LOGTAB_MSG, "Table has no rows following initial column headings.\n");
  931. }
  932. }
  933. }
  934. return sResult;
  935. }
  936. ////////////////////////////////////////////////////////////////////////////////
  937. // Read next table entry.
  938. //
  939. // A table consists of one or more rows, each of which contains one or more
  940. // entry.
  941. //
  942. // The rows in a table are delimited by carriage returns. The last row is an
  943. // exception, as it may be terminated by the EOF.
  944. //
  945. // The entries within a row are delimited by whitespace. The last entry in a
  946. // row is an exception, as it may be terminated by the carriage return that
  947. // ends the row or by the EOF.
  948. //
  949. // After reading an entry, this function continues reading beyond it in order
  950. // to determine whether the entry was the last one in the row. If so,
  951. // pbEndOfRow is set to true. Otherwise, it is set to false.
  952. //
  953. // Likewise, if it is determined that the entry was the last one in the table,
  954. // then pbEndOfTable is set to true. Otherwise, it is set to false.
  955. //
  956. // Note that if pbEndOfTable is true, then pbEndOfRow will also be true.
  957. //
  958. // Reaching the end of the table is not considered an error condition.
  959. // However, once this function indicates that the end of the table has been
  960. // reached, then any subsequent calls to this function will return an error
  961. // because no additional entries are available.
  962. //
  963. // Note: Since this function "looks ahead" to determine what follows an entry,
  964. // it may read the first character of the next entry. If this happens, it will
  965. // save this character in the location pointed at by pcSave, and use that
  966. // character the next time this function is called. The location pointed at
  967. // by pcSave MUST be set to 0 before the first time this function is called.
  968. // After that, just leave it alone and it will take care of itself.
  969. ////////////////////////////////////////////////////////////////////////////////
  970. template <class usertype>
  971. short CLogTab<usertype>::ReadEntry( // Returns 0 if successfull, non-zero otherwise
  972. RFile* pFile, // In: RFile to read from
  973. char* pszEntry, // Out: Entry is returned in here
  974. short sMaxEntrySize, // In: Maximum size of entry
  975. bool* pbEndOfTable, // Out: true if this is last entry in table
  976. bool* pbEndOfRow, // Out: true if this is last entry in row
  977. char* pcSave) // I/O: Save char from last call (or 0 if none)
  978. {
  979. short sResult = 0;
  980. // If the saved char is non-zero, then we use it as the first character.
  981. // Otherwise, we need to read a new character from the file. We then
  982. // clear the saved char to 0 for next time.
  983. char ch = *pcSave;
  984. if (ch == 0)
  985. pFile->Read(&ch, 1);
  986. *pcSave = 0;
  987. // Keep processing chars until we're done. Being done basically entails
  988. // throwing out any leading whitespace characters, then adding all subsequent
  989. // non-whitespace characters to the entry string, and then going through
  990. // all following characters until we hit another non-whitespace character
  991. // or the EOF. If we hit a CR along the way, we set the end-of-row flag.
  992. // If we hit the EOF, we set the end-of-table flag. It's kind of tricky,
  993. // but remember that the idea is that we want to know what follows the
  994. // current entry, so we need to go beyond it, which usually means going
  995. // up to the next entry.
  996. *pbEndOfTable = false; // Not at end of table
  997. *pbEndOfRow = false; // Not at end of row
  998. short sEntryIndex = 0; // Start index at 0
  999. bool bFillingEntry = false; // Not filling entry yet
  1000. do {
  1001. // Check for EOF
  1002. if (pFile->IsEOF())
  1003. {
  1004. // If we've added something to the entry, then EOF is okay
  1005. if (sEntryIndex > 0)
  1006. {
  1007. // Set flags for end of row and end of table
  1008. *pbEndOfRow = true;
  1009. *pbEndOfTable = true;
  1010. }
  1011. else
  1012. {
  1013. sResult = LOGTAB_ERR_UNEXPECTED_EOF;
  1014. TRACE("CLogTab::ReadEntry(): Unexpected end of file!\n");
  1015. rspMsgBox(LOGTAB_MSG, "Unexpected end of file.");
  1016. }
  1017. break;
  1018. }
  1019. // Check for file error
  1020. if (pFile->Error())
  1021. {
  1022. sResult = LOGTAB_ERR_READ_ERROR;
  1023. TRACE("CLogTab::ReadEntry(): Error reading from file!\n");
  1024. rspMsgBox(LOGTAB_MSG, "Error reading from file.");
  1025. break;
  1026. }
  1027. // Check if char is whitespace or not
  1028. if (isspace(ch))
  1029. {
  1030. // If we haven't started filling entry yet, then flag is already clear.
  1031. // If we have started, then whitespace ends it.
  1032. bFillingEntry = false;
  1033. // CR is special-case whitespace. If we've already added to the entry,
  1034. // then CR indicates end of row. Otherwise, it's just an empty row before
  1035. // the entry, so we ignore it.
  1036. if ((ch == '\r') && (sEntryIndex > 0))
  1037. *pbEndOfRow = true;
  1038. }
  1039. else
  1040. {
  1041. // If we haven't added anything to entry, then start entry now
  1042. if (sEntryIndex == 0)
  1043. bFillingEntry = true;
  1044. // If we're filling the entry, add this new char to it
  1045. if (bFillingEntry)
  1046. {
  1047. // Make sure we don't exceed max entry size (leave room for null!)
  1048. if (sEntryIndex < (sMaxEntrySize - 1))
  1049. {
  1050. // Add to entry, increment index, and tack on a null
  1051. pszEntry[sEntryIndex++] = ch;
  1052. pszEntry[sEntryIndex] = 0;
  1053. }
  1054. else
  1055. {
  1056. sResult = LOGTAB_ERR_TEXT_TOO_LONG;
  1057. TRACE("CLogTab::ReadEntry(): Text too long! Text so far: '%s'\n", pszEntry);
  1058. rspMsgBox(LOGTAB_MSG, "Text entry is too long. The text read so far is '%s'.", pszEntry);
  1059. break;
  1060. }
  1061. }
  1062. else
  1063. {
  1064. // This char belongs to the next entry, so save it for next time.
  1065. *pcSave = ch;
  1066. break;
  1067. }
  1068. }
  1069. // Read next char
  1070. pFile->Read(&ch, 1);
  1071. } while (sResult == 0);
  1072. return sResult;
  1073. }
  1074. #endif //LOGTAB_H
  1075. ////////////////////////////////////////////////////////////////////////////////
  1076. // EOF
  1077. ////////////////////////////////////////////////////////////////////////////////