PageRenderTime 49ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/src/common/util/mathscript.cpp

https://gitlab.com/GreatShift/PSExtended
C++ | 1225 lines | 955 code | 152 blank | 118 comment | 197 complexity | eca08f620b4277074cb6c52947b151de MD5 | raw file
Possible License(s): GPL-3.0, LGPL-3.0
  1. /*
  2. * mathscript.cpp by Keith Fulton <keith@planeshift.it>
  3. *
  4. * Copyright (C) 2010s Atomic Blue (info@planeshift.it, http://www.atomicblue.org)
  5. *
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation (version 2 of the License)
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program; if not, write to the Free Software
  16. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  17. *
  18. */
  19. #include <psconfig.h>
  20. #include "psconst.h"
  21. #if defined(CS_EXTENSIVE_MEMDEBUG) || defined(CS_MEMORY_TRACKER)
  22. # undef new
  23. # if defined(CS_EXTENSIVE_MEMDEBUG)
  24. # undef CS_EXTENSIVE_MEMDEBUG
  25. # endif
  26. # if defined(CS_MEMORY_TRACKER)
  27. # undef CS_MEMORY_TRACKER
  28. # endif
  29. #endif
  30. #include <new>
  31. #include "util/log.h"
  32. #include <csutil/randomgen.h>
  33. #include <csutil/xmltiny.h>
  34. #include "psdatabase.h"
  35. #include "util/mathscript.h"
  36. #include "util/consoleout.h"
  37. //support for limited compilers
  38. #if defined _MSC_VER && _MSC_VER < 1800
  39. double round(double value)
  40. {
  41. return (value >= 0) ? floor(value + 0.5) : ceil(value - 0.5);
  42. }
  43. #endif
  44. csString MathVar::ToString() const
  45. {
  46. switch (Type())
  47. {
  48. case VARTYPE_OBJ:
  49. return GetObject()->ToString();
  50. case VARTYPE_STR:
  51. return GetString();
  52. default:
  53. if (fabs(floor(value) - value) < EPSILON) // avoid .00 for whole numbers
  54. return csString().Format("%.0f", value);
  55. return csString().Format("%.2f", value);
  56. }
  57. }
  58. csString MathVar::Dump() const
  59. {
  60. switch (Type())
  61. {
  62. case VARTYPE_STR:
  63. return GetString();
  64. case VARTYPE_OBJ:
  65. return csString().Format("%p", (void*)GetObject());
  66. default:
  67. return csString().Format("%1.4f", value);
  68. }
  69. }
  70. MathType MathVar::Type() const
  71. {
  72. // initialize conversion
  73. MathScriptEngine::IDConverter ID;
  74. // assign our masked value
  75. ID.value = value;
  76. ID.ID.mask &= 0xFFF0;
  77. // check the mask
  78. if (ID.ID.mask == 0xFFF0)
  79. {
  80. // matched the string mask
  81. return VARTYPE_STR;
  82. }
  83. else if (ID.ID.mask == 0x7FF0)
  84. {
  85. // matched the object mask
  86. return VARTYPE_OBJ;
  87. }
  88. else
  89. {
  90. return VARTYPE_VALUE;
  91. }
  92. }
  93. void MathVar::SetValue(double v)
  94. {
  95. value = v;
  96. if (changedVarCallback)
  97. changedVarCallback(changedVarCallbackArg);
  98. }
  99. void MathVar::SetObject(iScriptableVar* p)
  100. {
  101. value = parent->GetValue(p);
  102. if (changedVarCallback)
  103. changedVarCallback(changedVarCallbackArg);
  104. }
  105. void MathVar::SetString(const char* p)
  106. {
  107. value = parent->GetValue(p);
  108. if (changedVarCallback)
  109. changedVarCallback(changedVarCallbackArg);
  110. }
  111. //----------------------------------------------------------------------------
  112. void MathEnvironment::Init()
  113. {
  114. // always define the environment as variable
  115. // as that's required to pass the environment to scriptable
  116. // objects in custom compound functions
  117. MathScriptEngine::IDConverter converter;
  118. converter.p = (uintptr_t)this;
  119. // don't use Define here as it'd check the parent
  120. MathVar* env = new MathVar(this);
  121. env->SetValue(converter.value);
  122. variables.Put("environment", env);
  123. }
  124. MathEnvironment::~MathEnvironment()
  125. {
  126. csHash<MathVar*, csString>::GlobalIterator it(variables.GetIterator());
  127. while (it.HasNext())
  128. {
  129. delete it.Next();
  130. }
  131. if(!parent)
  132. {
  133. delete UID;
  134. }
  135. }
  136. MathVar* MathEnvironment::Lookup(const char *name) const
  137. {
  138. MathVar *var = variables.Get(name, NULL);
  139. if (!var && parent)
  140. var = parent->Lookup(name);
  141. return var;
  142. }
  143. MathVar* MathEnvironment::GetVar(const char* name)
  144. {
  145. MathVar *var = Lookup(name);
  146. if (!var)
  147. {
  148. var = new MathVar(this);
  149. variables.Put(name,var);
  150. }
  151. return var;
  152. }
  153. void MathEnvironment::DumpAllVars() const
  154. {
  155. csString name;
  156. csHash<MathVar*, csString>::ConstGlobalIterator it(variables.GetIterator());
  157. while (it.HasNext())
  158. {
  159. MathVar *var = it.Next(name);
  160. CPrintf(CON_DEBUG, "%25s = %s\n", name.GetData(), var->Dump().GetData());
  161. }
  162. }
  163. void MathEnvironment::InterpolateString(csString & str) const
  164. {
  165. csString varName;
  166. size_t pos = (size_t)-1;
  167. while ((pos = str.Find("${", pos+1)) != SIZET_NOT_FOUND)
  168. {
  169. size_t end = str.Find("}", pos+2);
  170. if (end == SIZET_NOT_FOUND)
  171. continue; // invalid - unterminated ${
  172. if (end < pos+3)
  173. continue; // invalid - empty ${}
  174. str.SubString(varName, pos+2, end-(pos+2));
  175. MathVar *var = Lookup(varName);
  176. if (!var)
  177. continue; // invalid - required variable not in environment. leave it as is.
  178. str.DeleteAt(pos, end-pos+1);
  179. str.Insert(pos, var->ToString());
  180. }
  181. }
  182. void MathEnvironment::Define(const char *name, double value)
  183. {
  184. MathVar* var = GetVar(name);
  185. var->SetValue(value);
  186. }
  187. void MathEnvironment::Define(const char *name, iScriptableVar* obj)
  188. {
  189. MathVar* var = GetVar(name);
  190. var->SetObject(obj);
  191. }
  192. void MathEnvironment::Define(const char *name, const char* str)
  193. {
  194. MathVar* var = GetVar(name);
  195. var->SetString(str);
  196. }
  197. bool MathEnvironment::HasString(const char* p) const
  198. {
  199. bool result = false;
  200. if(parent)
  201. {
  202. result = parent->HasString(p);
  203. }
  204. result |= stringLiterals.Contains(p);
  205. return result;
  206. }
  207. csString MathEnvironment::GetString(double value) const
  208. {
  209. // initialize converter
  210. MathScriptEngine::IDConverter ID;
  211. // assign value
  212. ID.value = value;
  213. ID.ID.mask &= 0xFFF0;
  214. // check whether the ID mask matches the string one
  215. if(ID.ID.mask != 0xFFF0)
  216. {
  217. return "";
  218. }
  219. else
  220. {
  221. // obtain the string associated witht he ID and
  222. // return it - first checking in global lookup table
  223. const char* str = stringLiterals.Request(ID.ID.value);
  224. if(parent && !str)
  225. {
  226. str = parent->GetString(value);
  227. }
  228. if(!str)
  229. {
  230. str = MathScriptEngine::Request(ID.ID.value);
  231. }
  232. return str ? str : "";
  233. }
  234. return "";
  235. }
  236. double MathEnvironment::GetValue(const char* p)
  237. {
  238. // initialize id converter
  239. MathScriptEngine::IDConverter ID;
  240. // set the object mask
  241. ID.ID.value = 0xFFF0;
  242. // initialize the part we won't use
  243. ID.ID.ignored = 0;
  244. // check whether the string is present in the
  245. // global lookup table
  246. if(MathScriptEngine::HasString(p))
  247. {
  248. // obtain an ID from the global lookup table
  249. ID.ID.value = MathScriptEngine::RequestID(p);
  250. }
  251. else if(parent && parent->HasString(p))
  252. {
  253. // const cast is safe here as we verified it won't change
  254. // the parents environment but is a mere lookup
  255. ID.ID.value = const_cast<MathEnvironment*>(parent)->GetValue(p);
  256. }
  257. else
  258. {
  259. // obtain an ID from the local lookup table
  260. ID.ID.value = stringLiterals.Request(p);
  261. }
  262. // return masked value
  263. return ID.value;
  264. }
  265. bool MathEnvironment::HasObject(iScriptableVar* p) const
  266. {
  267. bool result = false;
  268. if(parent && parent->HasObject(p))
  269. {
  270. result = true;
  271. }
  272. result |= scriptableRegistry.Contains(p);
  273. return result;
  274. }
  275. iScriptableVar* MathEnvironment::GetPointer(double value) const
  276. {
  277. // initialize converter
  278. MathScriptEngine::IDConverter ID;
  279. // assign value
  280. ID.value = value;
  281. ID.ID.mask &= 0xFFF0;
  282. // check whether the ID mask matches the object one
  283. if(ID.ID.mask != 0x7FF0)
  284. {
  285. Error5("requested pointer for value %04X%04X%08X[%f] which is not a pointer", ID.ID.mask, ID.ID.ignored, ID.ID.value, ID.value);
  286. return NULL;
  287. }
  288. else
  289. {
  290. // obtain the object associated witht he ID and
  291. // return it
  292. iScriptableVar* object = scriptableVariables.Get(ID.ID.value,NULL);
  293. if(parent && !object)
  294. {
  295. object = parent->GetPointer(value);
  296. }
  297. return object;
  298. }
  299. }
  300. double MathEnvironment::GetValue(iScriptableVar* p)
  301. {
  302. // initialize id converter
  303. MathScriptEngine::IDConverter ID;
  304. // set the object mask
  305. ID.ID.mask = 0x7FF0;
  306. // initialize the part we won't use
  307. ID.ID.ignored = 0;
  308. // obtain an ID and set it as value
  309. if(scriptableRegistry.Contains(p))
  310. {
  311. // already present, retrieve ID
  312. ID.ID.value = scriptableRegistry.Get(p,(uint32)-1);
  313. }
  314. else if(parent && parent->HasObject(p))
  315. {
  316. // this const-cast is safe as we verified it won't change the parent
  317. ID.ID.value = const_cast<MathEnvironment*>(parent)->GetValue(p);
  318. }
  319. else
  320. {
  321. // not yet part of the lookup table
  322. // assign a new ID
  323. ID.ID.value = ++(*UID);
  324. // add to the lookup table
  325. scriptableVariables.Put(ID.ID.value,p);
  326. scriptableRegistry.Put(p,ID.ID.value);
  327. }
  328. // return masked value
  329. return ID.value;
  330. }
  331. //----------------------------------------------------------------------------
  332. MathStatement* MathStatement::Create(const csString & line, const char *name)
  333. {
  334. size_t assignAt = line.FindFirst('=');
  335. if (assignAt == SIZET_NOT_FOUND || assignAt == 0 || assignAt == line.Length())
  336. return NULL;
  337. MathStatement *stmt = new MathStatement;
  338. stmt->name = name;
  339. csString & assignee = stmt->assignee;
  340. line.SubString(assignee, 0, assignAt);
  341. assignee.Trim();
  342. bool validAssignee = isupper(assignee.GetAt(0)) != 0;
  343. for (size_t i = 1; validAssignee && i < assignee.Length(); i++)
  344. {
  345. if (!isalnum(assignee[i]) && assignee[i] != '_')
  346. validAssignee = false;
  347. }
  348. if (!validAssignee && assignee != "exit") // exit is special
  349. {
  350. Error3("Parse error in MathStatement >%s: %s<: Invalid assignee.", name, line.GetData());
  351. delete stmt;
  352. return NULL;
  353. }
  354. csString expression;
  355. line.SubString(expression, assignAt+1);
  356. if (!stmt->Parse(expression))
  357. {
  358. delete stmt;
  359. return NULL;
  360. }
  361. stmt->opcode |= MATH_ASSIGN;
  362. return stmt;
  363. }
  364. double MathStatement::Evaluate(MathEnvironment *env) const
  365. {
  366. double result = MathExpression::Evaluate(env);
  367. env->Define(assignee, result);
  368. return result;
  369. }
  370. //----------------------------------------------------------------------------
  371. MathScript* MathScript::Create(const char *name, const csString & script)
  372. {
  373. MathScript* s = new MathScript(name);
  374. size_t start = 0;
  375. size_t semicolonAt = 0;
  376. size_t blockStart = 0;
  377. size_t blockEnd = 0;
  378. while (start < script.Length())
  379. {
  380. csString trimmedScript = script.Slice(start).LTrim();
  381. // skip line comments
  382. if(trimmedScript.StartsWith("//"))
  383. {
  384. semicolonAt = script.FindFirst("\r\n", start);
  385. if(semicolonAt == SIZET_NOT_FOUND)
  386. start = script.Length();
  387. else
  388. start = semicolonAt+1;
  389. continue;
  390. }
  391. semicolonAt = script.FindFirst(';', start);
  392. if (semicolonAt == SIZET_NOT_FOUND)
  393. semicolonAt = script.Length();
  394. // parse code blocks
  395. blockStart = script.FindFirst('{', start);
  396. if (blockStart != SIZET_NOT_FOUND && blockStart < semicolonAt)
  397. {
  398. // check whether it's a conditional one
  399. csString line = script.Slice(start, blockStart - start);
  400. line.Trim();
  401. size_t opcode = MATH_NONE;
  402. if(line.StartsWith("if"))
  403. {
  404. opcode = MATH_IF;
  405. }
  406. else if(line.StartsWith("else"))
  407. {
  408. // validate we have a matching if
  409. size_t lineCount = s->scriptLines.GetSize();
  410. if (lineCount < 2 || s->scriptLines.Get(lineCount - 2)->GetOpcode() != MATH_IF)
  411. {
  412. Error2("Failed to create MathScript >%s<. Found else without prior if.", name);
  413. delete s;
  414. return NULL;
  415. }
  416. opcode = MATH_ELSE;
  417. }
  418. else if(line.StartsWith("while"))
  419. {
  420. opcode = MATH_WHILE;
  421. }
  422. else if(line.StartsWith("do"))
  423. {
  424. opcode = MATH_DO;
  425. }
  426. MathExpression *st = NULL;
  427. if (opcode & MATH_EXP)
  428. {
  429. // find expression
  430. size_t expStart = line.FindFirst("(");
  431. size_t expEnd = line.FindLast(")");
  432. if (expStart == SIZET_NOT_FOUND || expEnd == SIZET_NOT_FOUND || expStart > expEnd)
  433. {
  434. Error2("Failed to create MathScript >%s<. Could not find expression in block.", name);
  435. delete s;
  436. return NULL;
  437. }
  438. expStart++; // skip (
  439. csString exp = line.Slice(expStart, expEnd - expStart);
  440. exp.Collapse();
  441. if (!exp.IsEmpty())
  442. {
  443. // disable assignments here for now - we need some better
  444. // way to verify it's an assignment and not a comparison
  445. if (0/*exp.FindFirst("=") != SIZET_NOT_FOUND*/)
  446. {
  447. opcode |= MATH_ASSIGN;
  448. st = MathStatement::Create(exp, name);
  449. }
  450. else
  451. {
  452. st = MathExpression::Create(exp, name);
  453. }
  454. }
  455. }
  456. else
  457. {
  458. st = new EmptyMathStatement;
  459. }
  460. if (opcode != MATH_NONE)
  461. {
  462. if (!st)
  463. {
  464. Error2("Failed to create MathScript >%s<. Could not validate expression in block.", name);
  465. delete s;
  466. return NULL;
  467. }
  468. st->SetOpcode(opcode);
  469. s->scriptLines.Push(st);
  470. }
  471. else
  472. {
  473. delete st;
  474. }
  475. blockStart++; // skip opening { from now on
  476. size_t nextBlockStart = script.FindFirst('{', blockStart);
  477. blockEnd = script.FindFirst('}', blockStart);
  478. if (blockEnd == SIZET_NOT_FOUND)
  479. {
  480. Error2("Failed to create MathScript >%s<. Could not find matching close tag for code block", name);
  481. delete s;
  482. return NULL;
  483. }
  484. // find the real end of the block (take care of nested blocks)
  485. while (nextBlockStart != SIZET_NOT_FOUND && nextBlockStart < blockEnd)
  486. {
  487. // skip {
  488. nextBlockStart++;
  489. // find the next block end
  490. blockEnd = script.FindFirst('}', blockEnd+1);
  491. // no matching end found
  492. if (blockEnd == SIZET_NOT_FOUND)
  493. {
  494. Error2("Failed to create MathScript >%s<. Could not find matching close tag for code block.", name);
  495. delete s;
  496. return NULL;
  497. }
  498. nextBlockStart = script.FindFirst('{', nextBlockStart);
  499. }
  500. st = MathScript::Create(name, script.Slice(blockStart, blockEnd - blockStart));
  501. if (!st)
  502. {
  503. Error3("Failed to create MathScript >%s<. "
  504. "Failed to create sub-script for code block at %zu",name,blockStart);
  505. delete s;
  506. return NULL;
  507. }
  508. s->scriptLines.Push(st);
  509. start = blockEnd+1;
  510. continue;
  511. }
  512. if (semicolonAt - start > 0)
  513. {
  514. csString line = script.Slice(start, semicolonAt - start);
  515. line.Collapse();
  516. if (!line.IsEmpty())
  517. {
  518. MathExpression *st;
  519. if(line.FindFirst("=") != SIZET_NOT_FOUND)
  520. {
  521. st = MathStatement::Create(line, name);
  522. }
  523. else
  524. {
  525. st = MathExpression::Create(line, name);
  526. }
  527. if (!st)
  528. {
  529. Error3("Failed to create MathScript >%s<. Failed to parse Statement: >%s<", name, line.GetData());
  530. delete s;
  531. return NULL;
  532. }
  533. s->scriptLines.Push(st);
  534. }
  535. }
  536. start = semicolonAt+1;
  537. }
  538. return s;
  539. }
  540. void MathScript::Destroy(MathScript* &mathScript)
  541. {
  542. delete mathScript;
  543. mathScript = NULL;
  544. }
  545. MathScript::~MathScript()
  546. {
  547. while (scriptLines.GetSize())
  548. {
  549. delete scriptLines.Pop();
  550. }
  551. }
  552. void MathScript::CopyAndDestroy(MathScript* other)
  553. {
  554. other->scriptLines.TransferTo(scriptLines);
  555. delete other;
  556. }
  557. double MathScript::Evaluate(MathEnvironment *env) const
  558. {
  559. MathVar *exitsignal = env->Lookup("exit");
  560. if (exitsignal)
  561. {
  562. exitsignal->SetValue(0); // clear exit condition before running
  563. }
  564. else
  565. {
  566. // create exit signal if it doesn't exist
  567. env->Define("exit",0.f);
  568. exitsignal = env->Lookup("exit");
  569. }
  570. for (size_t i = 0; i < scriptLines.GetSize(); i++)
  571. {
  572. MathExpression* s = scriptLines[i];
  573. size_t op = s->GetOpcode();
  574. // handle "do { }" and "while { }"
  575. if(op & MATH_LOOP)
  576. {
  577. MathExpression* l = scriptLines[i+1];
  578. while(!(op & MATH_EXP) || s->Evaluate(env))
  579. {
  580. // code blocks(MathScript) shall return a value < 0 to
  581. // signal an error/break
  582. if (l->Evaluate(env) < 0)
  583. {
  584. break;
  585. }
  586. if (exitsignal && exitsignal->GetValue() != 0.0)
  587. {
  588. // exit the script
  589. return 0;
  590. }
  591. }
  592. i++; // skip next statement as it's already handled
  593. }
  594. // handle "return x;"
  595. else if(op & MATH_BREAK)
  596. {
  597. return s->Evaluate(env);
  598. }
  599. // handle "if { } [ else { } ]"
  600. else if(op == MATH_IF)
  601. {
  602. size_t nextOp = MATH_NONE;
  603. if (i + 3 < scriptLines.GetSize())
  604. {
  605. nextOp = scriptLines[i+2]->GetOpcode();
  606. }
  607. double result = 0;
  608. if (s->Evaluate(env))
  609. {
  610. result = scriptLines[i+1]->Evaluate(env);
  611. }
  612. else if (nextOp == MATH_ELSE)
  613. {
  614. result = scriptLines[i+3]->Evaluate(env);
  615. }
  616. if(result < 0)
  617. {
  618. // code blocks(MathScript) shall return a value < 0 to
  619. // signal an error/break
  620. return result;
  621. }
  622. if (nextOp == MATH_ELSE)
  623. {
  624. i += 3; // skip next 3 statements as we already handled them
  625. }
  626. else
  627. {
  628. i++; // skip next statement as we already handled it
  629. }
  630. }
  631. // handle regular expressions, e.g. assignments
  632. else if(op & MATH_EXP)
  633. {
  634. s->Evaluate(env);
  635. }
  636. if(exitsignal && exitsignal->GetValue() != 0.0)
  637. {
  638. // printf("Terminating mathscript at line %d of %d.\n",i, scriptLines.GetSize());
  639. break;
  640. }
  641. }
  642. return 0;
  643. }
  644. //----------------------------------------------------------------------------
  645. MathScriptEngine::MathScriptEngine(iDataConnection* db, const csString& mathScriptTable)
  646. :mathScriptTable(mathScriptTable)
  647. {
  648. LoadScripts(db);
  649. }
  650. MathScriptEngine::~MathScriptEngine()
  651. {
  652. UnloadScripts();
  653. }
  654. bool MathScriptEngine::LoadScripts(iDataConnection* db, bool reload)
  655. {
  656. Result result(db->Select("SELECT * from math_scripts"));
  657. if (!result.IsValid())
  658. return false;
  659. size_t old_count = scripts.GetSize();
  660. bool ok = true;
  661. for (unsigned long i = 0; i < result.Count(); i++ )
  662. {
  663. const char* name = result[i]["name"];
  664. MathScript *scr = MathScript::Create(name, result[i][mathScriptTable]);
  665. if (!scr)
  666. {
  667. Error2("Failed to load MathScript >%s<.", name);
  668. ok = false;
  669. continue;
  670. }
  671. if (reload)
  672. {
  673. MathScript* old = scripts.Get(name, NULL);
  674. if (old)
  675. {
  676. old->CopyAndDestroy(scr);
  677. old_count--;
  678. }
  679. else
  680. {
  681. scripts.Put(name, scr);
  682. }
  683. }
  684. else
  685. {
  686. scripts.Put(name, scr);
  687. }
  688. }
  689. if (reload && old_count != 0)
  690. {
  691. Error2("WARNING: %zu MathScripts were not reloaded.", old_count);
  692. }
  693. return ok;
  694. }
  695. void MathScriptEngine::UnloadScripts()
  696. {
  697. csHash<MathScript*, csString>::GlobalIterator it(scripts.GetIterator());
  698. while (it.HasNext())
  699. {
  700. delete it.Next();
  701. }
  702. scripts.DeleteAll();
  703. MathScriptEngine::customCompoundFunctions.Empty();
  704. MathScriptEngine::stringLiterals.Empty();
  705. }
  706. MathScript* MathScriptEngine::FindScript(const csString & name)
  707. {
  708. return scripts.Get(name, NULL);
  709. }
  710. void MathScriptEngine::ReloadScripts(iDataConnection* db)
  711. {
  712. LoadScripts(db, true);
  713. }
  714. csRandomGen MathScriptEngine::rng;
  715. csStringSet MathScriptEngine::customCompoundFunctions;
  716. csStringSet MathScriptEngine::stringLiterals;
  717. double MathScriptEngine::RandomGen(const double *limit)
  718. {
  719. return MathScriptEngine::rng.Get()*limit[0];
  720. }
  721. double MathScriptEngine::CustomCompoundFunc(const double * parms)
  722. {
  723. size_t funcIndex = (size_t)parms[0];
  724. csString funcName(customCompoundFunctions.Request(funcIndex));
  725. MathScriptEngine::IDConverter converter;
  726. converter.value = parms[1];
  727. MathEnvironment* env = (MathEnvironment*)converter.p;
  728. iScriptableVar* v = env->GetPointer(parms[2]);
  729. if(!v || !env)
  730. {
  731. Error5("custom compound function %s called with invalid scriptable variable %p(%f) and env %p", funcName.GetData(), (void*)v, parms[2], (void*)env);
  732. }
  733. if (funcName == "IsValid")
  734. {
  735. // check validity of the object
  736. return (v != NULL);
  737. }
  738. else if (funcName == "GetProperty")
  739. {
  740. // retrieve a property of an anonymous object
  741. return (v ? v->GetProperty(env, env->GetString(parms[3])) : 0);
  742. }
  743. else
  744. {
  745. // calculate a function on the object
  746. return (v ? v->CalcFunction(env, funcName.GetData(), &parms[3]) : 0);
  747. }
  748. }
  749. csString MathScriptEngine::FormatMessage(const csString& format, size_t arg_count, const double* parms)
  750. {
  751. if(format.IsEmpty() || arg_count == 0)
  752. {
  753. return format;
  754. }
  755. // cap argument count to max (10)
  756. arg_count = arg_count > 10 ? 10 : arg_count;
  757. // @@@RlyDontKnow:
  758. // we always pass the maximum amount of arguments
  759. // because we can't build va_list dynamically in a sane way
  760. // and a switch on argc seems unnecessary
  761. double args[10];
  762. memcpy(args,parms,sizeof(double)*arg_count);
  763. csString result;
  764. result.Format(format.GetData(),args[0],args[1],args[2],args[3],args[4],
  765. args[5],args[6],args[7],args[8],args[9]);
  766. return result;
  767. }
  768. //----------------------------------------------------------------------------
  769. MathExpression::MathExpression() : opcode(MATH_EXP)
  770. {
  771. fp.AddFunction("rnd", MathScriptEngine::RandomGen, 1);
  772. // first 3 arguments are reserved for function index, scriptable object
  773. // and MathEnvironment
  774. for (int n = 3; n < 13; n++)
  775. {
  776. csString funcName("customCompoundFunc");
  777. funcName.Append(n);
  778. fp.AddFunction(funcName.GetData(), MathScriptEngine::CustomCompoundFunc, n);
  779. }
  780. }
  781. MathExpression* MathExpression::Create(const char *expression, const char *name)
  782. {
  783. MathExpression* exp = new MathExpression;
  784. exp->name = name;
  785. if (!expression || expression[0] == '\0' || !exp->Parse(expression))
  786. {
  787. delete exp;
  788. return NULL;
  789. }
  790. return exp;
  791. }
  792. bool MathExpression::Parse(const char *exp)
  793. {
  794. CS_ASSERT(exp);
  795. // SCANNER: creates a list of tokens.
  796. csArray<csString> tokens;
  797. size_t start = SIZET_NOT_FOUND;
  798. char quote = '\0';
  799. for (size_t i = 0; exp[i] != '\0'; i++)
  800. {
  801. char c = exp[i];
  802. // are we in a string literal?
  803. if (quote)
  804. {
  805. // found a closing quote?
  806. if (c == quote && exp[i-1] != '\\')
  807. {
  808. quote = '\0';
  809. csString token(exp+start, i-start);
  810. // strip slashes.
  811. for (size_t j = 0; j < token.Length(); j++)
  812. {
  813. if (token[j] == '\\')
  814. token.DeleteAt(j++); // remove and skip what it escaped
  815. }
  816. tokens.Push(token);
  817. start = SIZET_NOT_FOUND;
  818. }
  819. // otherwise, it's part of the string...ignore it.
  820. continue;
  821. }
  822. // alpha, numeric, and underscores don't break a token
  823. if (isalnum(c) || c == '_')
  824. {
  825. if (start == SIZET_NOT_FOUND) // and they can start one
  826. start = i;
  827. continue;
  828. }
  829. // everything else breaks the token...
  830. if (start != SIZET_NOT_FOUND)
  831. {
  832. tokens.Push(csString(exp+start, i-start));
  833. start = SIZET_NOT_FOUND;
  834. }
  835. // check if it's starting a string literal
  836. if (c == '\'' || c == '"')
  837. {
  838. quote = c;
  839. start = i;
  840. continue;
  841. }
  842. // ...otherwise, if it's not whitespace, it's a token by itself.
  843. if (!isspace(c))
  844. {
  845. tokens.Push(csString(c));
  846. }
  847. }
  848. // Push the last token, too
  849. if (start != SIZET_NOT_FOUND)
  850. tokens.Push(exp+start);
  851. // return statements are treated specially
  852. if (tokens.GetSize() && tokens[0] == "return")
  853. {
  854. tokens.DeleteIndex(0);
  855. opcode |= MATH_BREAK;
  856. if (tokens.IsEmpty())
  857. {
  858. // return -1 per default
  859. tokens.Push("-1");
  860. }
  861. }
  862. //for (size_t i = 0; i < tokens.GetSize(); i++)
  863. // printf("Token[%d] = %s\n", int(i), tokens[i].GetDataSafe());
  864. // used to assign UIDs to string literals and to disable optimizations
  865. // if string literals are used to prevent them from being optimized out
  866. // due to their NaN value
  867. size_t stringCount = 0;
  868. // PARSER: (kind of)
  869. for (size_t i = 0; i < tokens.GetSize(); i++)
  870. {
  871. if (tokens[i] == ":")
  872. {
  873. if (i+1 == tokens.GetSize() || !isalpha(tokens[i+1].GetAt(0)))
  874. {
  875. Error4("Parse error in MathExpression >%s: '%s'<: Expected property or method after ':' operator; found >%s<.", name, exp, tokens[i+1].GetData());
  876. return false;
  877. }
  878. if (!isupper(tokens[i-1].GetAt(0)))
  879. {
  880. Error4("Parse error in MathExpression >%s: '%s'<: ':' Expected variable before ':' operator; found >%s<.", name, exp, tokens[i-1].GetData());
  881. return false;
  882. }
  883. // Is it a method call?
  884. if (i+2 < tokens.GetSize() && tokens[i+2] == "(")
  885. {
  886. // variables may not be objects in the same expressions
  887. if(!requiredVars.Contains(tokens[i-1]))
  888. {
  889. Error4("Parse error in MathExpression >%s: '%s'<: ':' Expected object before ':' operator; found >%s<.", name, exp, tokens[i-1].GetData());
  890. return false;
  891. }
  892. // unknown object, add it to the list
  893. requiredObjs.Add(tokens[i-1]);
  894. // Methods start as Target:WeaponAt(Slot) and turn into customCompoundFuncN(X,Env,Target,Slot)
  895. // where N-3 is the number of parameters and X is the index in a global lookup table and
  896. // Env is the environment the expression is executed in.
  897. // customCompoundFunc takes three args as boilerplate.
  898. int paramCount = 3;
  899. // Count the number of params via the number of commas. First one doesn't come with a comma.
  900. if (i+3 < tokens.GetSize() && tokens[i+3] != ")")
  901. paramCount++;
  902. for (size_t j = i+3; j < tokens.GetSize() && tokens[j] != ")"; j++)
  903. {
  904. if (tokens[j] == "(")
  905. while (tokens[++j] != ")"); // fast forward; skip over nested calls for now.
  906. if (tokens[j] == ",")
  907. paramCount++;
  908. }
  909. // Build the replacement call - replace all four tokens.
  910. csString object = tokens[i-1];
  911. csString method = tokens[i+1];
  912. tokens[i-1] = "customCompoundFunc";
  913. tokens[ i ].Format("%d", paramCount);
  914. tokens[i+1] = "(";
  915. tokens[i+2].Format("%u,environment,%s", MathScriptEngine::GetCompoundFunction(method), object.GetData());
  916. if (paramCount > 3)
  917. tokens[i+2].Append(',');
  918. i += 2; // skip method name & paren - we just dealt with them.
  919. }
  920. else
  921. {
  922. // The previous variable must be an object.
  923. requiredObjs.Add(tokens[i-1]);
  924. // Found a property reference, i.e. Actor:HP
  925. tokens[i] = "_"; // fparser can't deal with ':', so change it to '_'.
  926. PropertyRef ref = {tokens[i-1],tokens[i+1]};
  927. propertyRefs.Add(ref);
  928. i++; // skip next token - we already dealt with the property.
  929. }
  930. }
  931. else // not dealing with a colon
  932. {
  933. // Record any string literals and replace them with their table index.
  934. if (tokens[i].GetAt(0) == '"' || tokens[i].GetAt(0) == '\'')
  935. {
  936. // remove quote (scanner already omitted the closing quote)
  937. tokens[i].DeleteAt(0);
  938. // build placeholder token
  939. csString placeholder("string");
  940. placeholder.Append(++stringCount);
  941. // add the placeholder token as constant so we can have an exact value
  942. // (i.e. different NaNs/Infs)
  943. fp.AddConstant(placeholder.GetData(), MathScriptEngine::Request(tokens[i]));
  944. // put the placeholder as token to parse
  945. tokens[i] = placeholder;
  946. }
  947. else
  948. {
  949. // Jot down any variable names (tokens starting with [A-Z])
  950. if (isupper(tokens[i].GetAt(0)))
  951. requiredVars.Add(tokens[i]);
  952. }
  953. }
  954. }
  955. // make sure environment will be resolved upon execution
  956. requiredVars.Add("environment");
  957. // Parse the formula.
  958. csString fpVars;
  959. {
  960. // add all required variables
  961. csSet<csString>::GlobalIterator it(requiredVars.GetIterator());
  962. while (it.HasNext())
  963. {
  964. fpVars.Append(it.Next());
  965. fpVars.Append(',');
  966. }
  967. }
  968. {
  969. // add all property references as variables
  970. csSet<PropertyRef>::GlobalIterator it = propertyRefs.GetIterator();
  971. while (it.HasNext())
  972. {
  973. const PropertyRef& ref = it.Next();
  974. csString var;
  975. var.Format("%s_%s", ref.object.GetData(), ref.property.GetData());
  976. fpVars.Append(var);
  977. fpVars.Append(',');
  978. }
  979. }
  980. if(!fpVars.IsEmpty())
  981. {
  982. fpVars.Truncate(fpVars.Length() - 1); // remove the trailing ','
  983. }
  984. // Rebuild the expression now that method calls & properties are transformed
  985. csString expression;
  986. for (size_t i = 0; i < tokens.GetSize(); i++)
  987. expression.Append(tokens[i]);
  988. Debug3(LOG_SCRIPT, 0, "Final expression: '%s' (%s)\n", expression.GetData(), fpVars.GetDataSafe());
  989. size_t ret = fp.Parse(expression.GetData(), fpVars.GetDataSafe());
  990. if (ret != (size_t) -1)
  991. {
  992. Error5("Parse error in MathExpression >%s: '%s'< at column %zu: %s", name, expression.GetData(), ret, fp.ErrorMsg());
  993. return false;
  994. }
  995. if(!stringCount)
  996. fp.Optimize();
  997. return true;
  998. }
  999. double MathExpression::Evaluate(MathEnvironment *env) const
  1000. {
  1001. double *values = new double [requiredVars.GetSize() + propertyRefs.GetSize()];
  1002. size_t i = 0;
  1003. // retrieve the values of all required variables
  1004. csSet<csString>::GlobalIterator it(requiredVars.GetIterator());
  1005. while (it.HasNext())
  1006. {
  1007. const csString & varName = it.Next();
  1008. MathVar *var = env->Lookup(varName);
  1009. if (!var) // invalid variable
  1010. {
  1011. csString msg;
  1012. msg.Format("Error in >%s<: Required variable >%s< not supplied in environment.", name, varName.GetData());
  1013. CS_ASSERT_MSG(msg.GetData(),false);
  1014. Error2("%s",msg.GetData());
  1015. delete [] values;
  1016. return 0.0;
  1017. }
  1018. values[i++] = var->GetValue();
  1019. }
  1020. // retrieve the objects requried to retrieve
  1021. // calculated values or properties
  1022. it = requiredObjs.GetIterator();
  1023. while (it.HasNext())
  1024. {
  1025. const csString & objName = it.Next();
  1026. MathVar *var = env->Lookup(objName);
  1027. CS_ASSERT(var); // checked as part of requiredVars
  1028. if (var->Type() != VARTYPE_OBJ) // invalid type
  1029. {
  1030. csString msg;
  1031. msg.Format("Error in >%s<: Type inference requires >%s< to be an iScriptableVar, but it isn't.", name, objName.GetData());
  1032. CS_ASSERT_MSG(msg.GetData(),false);
  1033. Error2("%s",msg.GetData());
  1034. delete [] values;
  1035. return 0.0;
  1036. }
  1037. else if (!var->GetObject()) // invalid object
  1038. {
  1039. csString msg;
  1040. msg.Format("Error in >%s<: Given a NULL iScriptableVar* for >%s<.", name, objName.GetData());
  1041. CS_ASSERT_MSG(msg.GetData(),false);
  1042. Error2("%s",msg.GetData());
  1043. delete [] values;
  1044. return 0.0;
  1045. }
  1046. }
  1047. // retrieve the required properties
  1048. csSet<PropertyRef>::GlobalIterator propIt(propertyRefs.GetIterator());
  1049. while (propIt.HasNext())
  1050. {
  1051. const PropertyRef& ref = propIt.Next();
  1052. MathVar *var = env->Lookup(ref.object);
  1053. CS_ASSERT(var); // checked as part of requiredVars
  1054. iScriptableVar *obj = var->GetObject();
  1055. CS_ASSERT(obj); // checked as part of requiredObjs
  1056. values[i++] = obj->GetProperty(env,ref.property.GetData());
  1057. }
  1058. double ret = fp.Eval(values);
  1059. delete [] values;
  1060. return ret;
  1061. }