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

/cfg.php

https://github.com/mtpro/chomsky
PHP | 741 lines | 517 code | 109 blank | 115 comment | 54 complexity | 4ec2001aa765715a8a824085c8844f36 MD5 | raw file
  1. <?php
  2. # Copyright (C) 2010 by Sam Hughes
  3. # Permission is hereby granted, free of charge, to any person obtaining a copy
  4. # of this software and associated documentation files (the "Software"), to deal
  5. # in the Software without restriction, including without limitation the rights
  6. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. # copies of the Software, and to permit persons to whom the Software is
  8. # furnished to do so, subject to the following conditions:
  9. # The above copyright notice and this permission notice shall be included in
  10. # all copies or substantial portions of the Software.
  11. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  12. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  13. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  14. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  15. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  16. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  17. # THE SOFTWARE.
  18. # http://qntm.org/chomsky
  19. # A nonterminal can be ANYTHING.
  20. # A terminal has to be a single-character string at the moment (multi-character strings are forthcoming).
  21. class Rule {
  22. # attributes
  23. private $left = null;
  24. private $rights = null;
  25. # setters
  26. public function __construct($left, $rights) {
  27. $this->setLeft($left);
  28. $this->setRights($rights);
  29. }
  30. private function setLeft($left) {
  31. $this->left = $left;
  32. }
  33. private function setRights($rights) {
  34. if(!is_array($rights)) {
  35. throw new Exception("Right-hand string '".print_r($rights, true)."' is not an array.");
  36. }
  37. $this->rights = $rights;
  38. }
  39. # getters
  40. public function getLeft() {
  41. return $this->left;
  42. }
  43. public function getRights() {
  44. return $this->rights;
  45. }
  46. public function equals($other) {
  47. if(!is_a($other, "Rule")) {
  48. return false;
  49. }
  50. if($this->getLeft() !== $other->getLeft()) {
  51. return false;
  52. }
  53. if($this->getRights() !== $other->getRights()) {
  54. return false;
  55. }
  56. return true;
  57. }
  58. }
  59. class ContextFreeGrammar {
  60. # attributes
  61. private $alphabet = array();
  62. private $nonterminals = array();
  63. private $rules = array();
  64. private $startSymbol = null;
  65. private $flaggedEpsilons = array();
  66. # setters
  67. public function __construct($startSymbol, $terminals=array(), $otherNonterminals=array(), $rules=array()) {
  68. $this->addNonterminal($startSymbol);
  69. $this->setStartSymbol($startSymbol);
  70. $this->addTerminals($terminals);
  71. $this->addNonterminals($otherNonterminals);
  72. $this->addRules($rules);
  73. }
  74. public function flagEpsilon($oldRule) {
  75. $this->flaggedEpsilons[] = $oldRule;
  76. }
  77. public function addTerminal($terminal) {
  78. if(!is_string($terminal)) {
  79. throw new Exception("Can't add terminal '".print_r($terminal, true)."', not a string.");
  80. }
  81. if(in_array($terminal, $this->getTerminals(), true)) {
  82. throw new Exception("Can't add terminal '".$terminal."', already in A.");
  83. }
  84. if(in_array($terminal, $this->getNonterminals(), true)) {
  85. throw new Exception("Can't add terminal '".$terminal."', already in N.");
  86. }
  87. $this->alphabet[] = $terminal;
  88. }
  89. public function addTerminals($terminals) {
  90. if(!is_array($terminals)) {
  91. throw new Exception("Can't add terminals '".print_r($terminals, true)."', not an array.");
  92. }
  93. foreach($terminals as $terminal) {
  94. $this->addTerminal($terminal);
  95. }
  96. }
  97. public function deleteTerminal($terminalToDelete) {
  98. if(!in_array($terminalToDelete, $this->getTerminals(), true)) {
  99. throw new Exception("Can't delete terminal '".print_r($terminalToDelete, true)."', not in A.");
  100. }
  101. if($this->referencesTerminal($terminalToDelete)) {
  102. throw new Exception("Can't delete terminal '".print_r($terminalToDelete, true)."', there is a rule which references it.");
  103. }
  104. $newTerminals = array();
  105. foreach($this->getTerminals() as $terminal) {
  106. if($terminal === $terminalToDelete) {
  107. continue;
  108. }
  109. $newTerminals[] = $terminal;
  110. }
  111. $this->terminals = $newTerminals;
  112. }
  113. public function deleteTerminals($terminals) {
  114. if(!is_array($terminals)) {
  115. throw new Exception("Can't delete terminals '".print_r($terminals, true)."', not an array.");
  116. }
  117. foreach($terminals as $terminal) {
  118. $this->deleteTerminal($terminal);
  119. }
  120. }
  121. public function addNonterminal($nonterminal) {
  122. if(in_array($nonterminal, $this->getTerminals(), true)) {
  123. throw new Exception("Can't add nonterminal '".print_r($nonterminal, true)."', already in A.");
  124. }
  125. if(in_array($nonterminal, $this->getNonterminals(), true)) {
  126. throw new Exception("Can't add nonterminal '".print_r($nonterminal, true)."', already in N.");
  127. }
  128. $this->nonterminals[] = $nonterminal;
  129. }
  130. public function addNonterminals($nonterminals) {
  131. if(!is_array($nonterminals)) {
  132. throw new Exception("Can't add nonterminals '".print_r($nonterminals, true)."', not an array.");
  133. }
  134. foreach($nonterminals as $nonterminal) {
  135. $this->addNonterminal($nonterminal);
  136. }
  137. }
  138. public function deleteNonterminal($nonterminalToDelete) {
  139. if(!in_array($nonterminalToDelete, $this->getNonterminals(), true)) {
  140. throw new Exception("Can't delete nonterminal '".print_r($nonterminalToDelete, true)."', doesn't exist in N.");
  141. }
  142. if($this->referencesNonterminal($nonterminalToDelete)) {
  143. throw new Exception("Can't delete nonterminal '".print_r($nonterminalToDelete, true)."', there is a rule which references it.");
  144. }
  145. $newNonterminals = array();
  146. foreach($this->getNonterminals() as $nonterminal) {
  147. if($nonterminal === $nonterminalToDelete) {
  148. continue;
  149. }
  150. $newNonterminals[] = $nonterminal;
  151. }
  152. $this->nonterminals = $newNonterminals;
  153. }
  154. public function deleteNonterminals($nonterminals) {
  155. if(!is_array($nonterminals)) {
  156. throw new Exception("Can't delete nonterminals '".print_r($nonterminals, true)."', not an array.");
  157. }
  158. foreach($nonterminals as $nonterminal) {
  159. $this->deleteNonterminal($nonterminal);
  160. }
  161. }
  162. public function addRule($rule) {
  163. if(!is_a($rule, "Rule")) {
  164. throw new Exception("Can't add rule '".print_r($rule, true)."', not a rule.");
  165. }
  166. $left = $rule->getLeft();
  167. if(!in_array($left, $this->getNonterminals(), true)) {
  168. throw new Exception("Can't add rule with left symbol '".print_r($left, true)."', not in N.");
  169. }
  170. $rights = $rule->getRights();
  171. if(!is_array($rights)) {
  172. throw new Exception("Can't add rule with right-hand side '".print_r($rights, true)."', not an array.");
  173. }
  174. foreach($rights as $right) {
  175. if(!in_array($right, $this->getAllSymbols(), true)) {
  176. throw new Exception("Can't add rule with right symbol '".print_r($right, true)."', not in A or N.");
  177. }
  178. }
  179. if($this->ruleExists($rule)) {
  180. throw new Exception("Can't add rule '".print_r($rule, true)."', already exists.");
  181. }
  182. $this->rules[] = $rule;
  183. }
  184. public function addRules($rules) {
  185. if(!is_array($rules)) {
  186. throw new Exception("Can't add rules '".print_r($rules, true)."', not an array.");
  187. }
  188. foreach($rules as $rule) {
  189. $this->addRule($rule);
  190. }
  191. }
  192. public function deleteRule($ruleToDelete) {
  193. if(!$this->ruleExists($ruleToDelete)) {
  194. throw new Exception("Can't delete rule '".print_r($ruleToDelete, true)."', doesn't exist.");
  195. }
  196. $newRules = array();
  197. foreach($this->getRules() as $rule) {
  198. if($rule->equals($ruleToDelete)) {
  199. continue;
  200. }
  201. $newRules[] = $rule;
  202. }
  203. $this->rules = $newRules;
  204. }
  205. public function deleteRules($rulesToDelete) {
  206. if(!is_array($rulesToDelete)) {
  207. throw new Exception("Can't delete rules '".print_r($rulesToDelete, true)."', not an array.");
  208. }
  209. foreach($rulesToDelete as $ruleToDelete) {
  210. $this->deleteRule($ruleToDelete);
  211. }
  212. }
  213. public function setStartSymbol($startSymbol) {
  214. if(!in_array($startSymbol, $this->getNonterminals(), true)) {
  215. throw new Exception("Can't set starting symbol to '".print_r($startSymbol, true)."', is not in N.");
  216. }
  217. $this->startSymbol = $startSymbol;
  218. }
  219. # getters
  220. public function getTerminals() {
  221. return $this->alphabet;
  222. }
  223. public function getReferencedTerminals() {
  224. $referenced = array();
  225. foreach($this->getTerminals() as $terminal) {
  226. if($this->referencesTerminal($terminal)) {
  227. $referenced[] = $terminal;
  228. }
  229. }
  230. return $referenced;
  231. }
  232. public function getUnreferencedTerminals() {
  233. return array_diff($this->getTerminals(), $this->getReferencedTerminals());
  234. }
  235. public function referencesTerminal($terminal) {
  236. foreach($this->getRules() as $rule) {
  237. if(in_array($terminal, $rule->getRights(), true)) {
  238. return true;
  239. }
  240. }
  241. return false;
  242. }
  243. public function getNonterminals() {
  244. return $this->nonterminals;
  245. }
  246. public function getRulelessNonterminals() {
  247. $ruleless = array();
  248. foreach($this->getNonterminals() as $nonterminal) {
  249. if(count($this->getRulesFor($nonterminal)) === 0) {
  250. $ruleless[] = $nonterminal;
  251. }
  252. }
  253. return $ruleless;
  254. }
  255. public function getReachableNonterminals() {
  256. // fixed point calculation
  257. // crawl the grammar making a list of reachable nonterminals
  258. $reachable = array($this->getStartSymbol());
  259. for($i = 0; $i < count($reachable); $i++) {
  260. $nonterminal = $reachable[$i];
  261. foreach($this->getRulesFor($nonterminal) as $rule) {
  262. foreach($rule->getRights() as $right) {
  263. // ignore everything except more nonterminals
  264. if(!in_array($right, $this->getNonterminals(), true)) {
  265. continue;
  266. }
  267. // no dupes!
  268. if(in_array($right, $reachable, true)) {
  269. continue;
  270. }
  271. // add one
  272. $reachable[] = $right;
  273. }
  274. }
  275. }
  276. return $reachable;
  277. }
  278. public function getUnreachableNonterminals() {
  279. return array_diff($this->getNonterminals(), $this->getReachableNonterminals());
  280. }
  281. public function referencesNonterminal($nonterminal) {
  282. foreach($this->getRules() as $rule) {
  283. if($rule->getLeft() === $nonterminal) {
  284. return true;
  285. }
  286. if(in_array($nonterminal, $rule->getRights(), true)) {
  287. return true;
  288. }
  289. }
  290. return false;
  291. }
  292. public function getAllSymbols() {
  293. return array_merge($this->getTerminals(), $this->getNonterminals());
  294. }
  295. public function getRules() {
  296. return $this->rules;
  297. }
  298. public function getRulesFor($nonterminal) {
  299. if(!in_array($nonterminal, $this->getNonterminals(), true)) {
  300. throw new Exception("Can't get rules for symbol '".print_r($nonterminal, true)."', is not in N.");
  301. }
  302. $rules = array();
  303. foreach($this->getRules() as $rule) {
  304. if($rule->getLeft() === $nonterminal) {
  305. $rules[] = $rule;
  306. }
  307. }
  308. return $rules;
  309. }
  310. public function getRulesReferencing($symbol) {
  311. if(!in_array($symbol, $this->getAllSymbols(), true)) {
  312. throw new Exception("Can't get rules referencing symbol '".print_r($symbol, true)."', not in A or N.");
  313. }
  314. $rules = array();
  315. foreach($this->getRules() as $rule) {
  316. if(in_array($symbol, $rule->getRights(), true)) {
  317. $rules[] = $rule;
  318. }
  319. }
  320. return $rules;
  321. }
  322. public function ruleExists($needle) {
  323. foreach($this->getRules() as $straw) {
  324. if($straw->equals($needle)) {
  325. return true;
  326. }
  327. }
  328. return false;
  329. }
  330. public function getStartSymbol() {
  331. return $this->startSymbol;
  332. }
  333. public function printRules() {
  334. foreach($this->getRules() as $rule) {
  335. print($rule->getLeft()." ->");
  336. if(count($rule->getRights()) === 0) {
  337. print(" epsilon");
  338. } else {
  339. foreach($rule->getRights() as $right) {
  340. print(" ");
  341. if(in_array($right, $this->getTerminals(), true)) {
  342. print("'".$right."'");
  343. } elseif(in_array($right, $this->getNonterminals(), true)) {
  344. print($right);
  345. } else {
  346. throw new Exception("Malformed rule.");
  347. }
  348. }
  349. }
  350. print("\n");
  351. }
  352. print("\n");
  353. }
  354. // Eliminate cruft. This step is technically unnecessary but the results
  355. // are frequently cluttered otherwise.
  356. public function eliminateUselesses() {
  357. // Some nonterminals may have no rules. That means they cannot be developed
  358. // further, so there is no point in creating one, so any rule referring to
  359. // such nonterminals can be deleted along with the nonterminals themselves
  360. // Loop until done
  361. do {
  362. $rulelessNonterminals = $this->getRulelessNonterminals();
  363. foreach($rulelessNonterminals as $rulelessNonterminal) {
  364. $this->deleteRules($this->getRulesReferencing($rulelessNonterminal));
  365. $this->deleteNonterminal($rulelessNonterminal);
  366. }
  367. } while(count($rulelessNonterminals) > 0);
  368. // any unreachable nonterminal can be destroyed along with all its rules
  369. $unreachableNonterminals = $this->getUnreachableNonterminals();
  370. foreach($unreachableNonterminals as $nonterminal) {
  371. $this->deleteRules($this->getRulesFor($nonterminal));
  372. }
  373. $this->deleteNonterminals($unreachableNonterminals);
  374. // likewise any unreferenced terminal
  375. $this->deleteTerminals($this->getUnreferencedTerminals());
  376. }
  377. // eliminate all other rules depending on the specified unit rule,
  378. // "<A> => <B>"
  379. private function eliminateUnit($A, $B) {
  380. foreach($this->getRules() as $rule) {
  381. if($rule->getLeft() === $B) {
  382. $newRule = new Rule($A, $rule->getRights());
  383. // when e.g. eliminating "<A> => <B>" from "<B> => <A>",
  384. // we may get the useless "<B> => <B>" result which should be ignored
  385. if(array($newRule->getLeft()) === $newRule->getRights()) {
  386. continue;
  387. }
  388. if(!$this->ruleExists($newRule)) {
  389. $this->addRule($newRule);
  390. }
  391. }
  392. }
  393. }
  394. // eliminate rules of the form "<A> => <B>"
  395. private function eliminateUnits() {
  396. while(1) {
  397. foreach($this->getRules() as $rule) {
  398. $rights = $rule->getRights();
  399. // Not a singleton, no problem
  400. if(count($rights) !== 1) {
  401. continue;
  402. }
  403. // must be a non-terminal symbol, or no problem
  404. $right = $rights[0];
  405. if(!in_array($right, $this->getNonterminals(), true)) {
  406. continue;
  407. }
  408. // that's a unit! Must eliminate it!
  409. $this->eliminateUnit($rule->getLeft(), $right);
  410. $this->deleteRule($rule);
  411. // and START OVER
  412. continue 2;
  413. }
  414. // all rules passed, no units found: exit
  415. break;
  416. }
  417. }
  418. // e.g. if $right = array("<A>", "b", "<A>"), $left = "<A>", returns
  419. // array(
  420. // array("<A>", "b", "<A>"),
  421. // array("<A>", "b"),
  422. // array("b", "<A>"),
  423. // array("b")
  424. // )
  425. private function getCombos($rights, $left) {
  426. // find the "pivot point" in the listing
  427. $firstLeft = array_search($left, $rights, true);
  428. // e.g. 0
  429. // If not found, just return verbatim
  430. if($firstLeft === false) {
  431. return array($rights);
  432. }
  433. $combos = array();
  434. // pivot around this location
  435. $prefix = array_slice($rights, 0, $firstLeft);
  436. // e.g. array()
  437. $suffix = array_slice($rights, $firstLeft + 1, count($rights) - ($firstLeft + 1));
  438. // e.g. array("b", "<A>")
  439. $subCombos = $this->getCombos($suffix, $left);
  440. // e.g. array( array("b"), array("b", "<A>") )
  441. foreach($subCombos as $subCombo) {
  442. $combo = array_merge($prefix, array($left), $subCombo);
  443. // e.g. array("<A>", "b")
  444. // e.g. array("<A>", "b", "<A>")
  445. if(!in_array($combo, $combos, true)) {
  446. $combos[] = $combo;
  447. }
  448. $combo = array_merge($prefix, $subCombo);
  449. // e.g. array("b")
  450. // e.g. array("b", "<A>")
  451. if(!in_array($combo, $combos, true)) {
  452. $combos[] = $combo;
  453. }
  454. }
  455. return $combos;
  456. }
  457. // eliminate all dependence on the rule "<A> => epsilon"
  458. private function eliminateEpsilon($A) {
  459. foreach($this->getRules() as $rule) {
  460. // eliminate "<A> => epsilon" from e.g. "<S> => <A> b <A>"
  461. // gives four rules: "<S> => <A> b <A>", "<S> => <A> b", "<S> => b <A>", "<S> => b"
  462. // (the original "<A> => epsilon" will be removed later...)
  463. $combos = $this->getCombos($rule->getRights(), $A);
  464. foreach($combos as $combo) {
  465. $newRule = new Rule($rule->getLeft(), $combo);
  466. // when e.g. eliminating "<A> => epsilon" from "<S> => <S> <A>"
  467. // we may get the useless "<S> => <S>" result which should be ignored
  468. if(array($newRule->getLeft()) === $newRule->getRights()) {
  469. continue;
  470. }
  471. if(in_array($newRule, $this->flaggedEpsilons, false)) {
  472. continue;
  473. }
  474. if(!$this->ruleExists($newRule)) {
  475. $this->addRule($newRule);
  476. }
  477. }
  478. }
  479. }
  480. // eliminate rules of the form <A> => epsilon where <A> is not <S>
  481. private function eliminateEpsilons() {
  482. while(1) {
  483. foreach($this->getRules() as $rule) {
  484. // not an epsilon, no problem, next rule
  485. if($rule->getRights() !== array()) {
  486. continue;
  487. }
  488. // "<S0> => epsilon" is still okay
  489. if($rule->getLeft() === $this->getStartSymbol()) {
  490. continue;
  491. }
  492. // an epsilon was found!!
  493. // get rid of it
  494. $this->eliminateEpsilon($rule->getLeft());
  495. $this->deleteRule($rule);
  496. // Now we need to ensure that this rule is not added
  497. // by eliminateEpsilon() again, as this throws us into
  498. // an infinite loop
  499. $this->flagEpsilon($rule);
  500. // and START OVER
  501. continue 2;
  502. }
  503. // all rules passed,
  504. // no epsilons found: exit
  505. break;
  506. }
  507. }
  508. // return a new, unused nonterminal symbol e.g. <Z0>
  509. private function getNewNonterminal() {
  510. $i = 0;
  511. while(1) {
  512. $nonterminal = "<Z".$i.">";
  513. if(!in_array($nonterminal, $this->getAllSymbols(), true)) {
  514. return $nonterminal;
  515. }
  516. $i++;
  517. }
  518. }
  519. // create a new unit of the form "<Z0> => <S>" where <S> is the old start symbol
  520. // and <Z0> is the new one
  521. private function introduceS0() {
  522. // get <Z0>
  523. $newStartSymbol = $this->getNewNonterminal();
  524. // add <Z0>
  525. $this->addNonterminal($newStartSymbol);
  526. // "<Z0> => <S>"
  527. $this->addRule(new Rule($newStartSymbol, array($this->getStartSymbol())));
  528. // <Z0> becomes start symbol
  529. $this->setStartSymbol($newStartSymbol);
  530. }
  531. // remove all rules of the form <A> => <B> <C> <D> ...
  532. private function eliminateMultiples() {
  533. while(1) {
  534. foreach($this->getRules() as $oldRule) {
  535. // no problem, next rule
  536. if(count($oldRule->getRights()) <= 2) {
  537. continue;
  538. }
  539. // problem, eliminate rule
  540. // delete <A> => <B> <C> <D> ...
  541. $this->deleteRule($oldRule);
  542. // add new terminal <Z0>,
  543. $newNonterminal = $this->getNewNonterminal();
  544. $this->addNonterminal($newNonterminal);
  545. // add new rules "<A> => <B> <Z0>" and "<Z0> => <C> <D> ..."
  546. $oldRights = $oldRule->getRights();
  547. $this->addRule(new Rule($oldRule->getLeft(), array($oldRights[0], $newNonterminal)));
  548. $this->addRule(new Rule($newNonterminal, array_slice($oldRights, 1)));
  549. // and START OVER
  550. continue(2);
  551. }
  552. // all rules successfully passed with no problem
  553. break;
  554. }
  555. }
  556. // eliminate all rules which contain the specified terminal symbol on the
  557. // right, but not JUST the specified terminal.
  558. private function eliminateTerminal($oldTerminal) {
  559. // new nonterminal to stand in for the old terminal, and new rule
  560. $newNonterminal = $this->getNewNonterminal();
  561. $this->addNonterminal($newNonterminal);
  562. $this->addRule(new Rule($newNonterminal, array($oldTerminal)));
  563. // and rewrite any applicable rules
  564. foreach($this->getRules() as $oldRule) {
  565. // no rewrite
  566. if(count($oldRule->getRights()) < 2) {
  567. continue;
  568. }
  569. // no ref: no problem
  570. $oldRights = $oldRule->getRights();
  571. if(!in_array($oldTerminal, $oldRights, true)) {
  572. continue;
  573. }
  574. // yes rewrite
  575. $newRights = array();
  576. foreach($oldRights as $oldRight) {
  577. if($oldRight === $oldTerminal) {
  578. $newRights[] = $newNonterminal;
  579. } else {
  580. $newRights[] = $oldRight;
  581. }
  582. }
  583. $newRule = new Rule($oldRule->getLeft(), $newRights);
  584. $this->addRule($newRule);
  585. $this->deleteRule($oldRule);
  586. }
  587. }
  588. // eliminate all rules which contain a terminal symbol on the right side
  589. // but that don't contain JUST a terminal symbol on the right side
  590. private function eliminateTerminals() {
  591. // compile a list of all the offending terminals
  592. while(1) {
  593. foreach($this->getRules() as $rule) {
  594. // no problem, next rule
  595. if(count($rule->getRights()) < 2) {
  596. continue;
  597. }
  598. foreach($rule->getRights() as $right) {
  599. // no problem, next Right
  600. if(!in_array($right, $this->getTerminals(), true)) {
  601. continue;
  602. }
  603. // problem! Eliminate terminal...
  604. $this->eliminateTerminal($right);
  605. // and START OVER
  606. continue(3);
  607. }
  608. }
  609. // all rules successfully consumed and
  610. // no terminals found: exit
  611. break;
  612. }
  613. }
  614. // Convert a CFG to Chomsky Normal Form
  615. public function toCnf() {
  616. $this->eliminateTerminals();
  617. $this->eliminateMultiples();
  618. $this->introduceS0(); // introduces a unit
  619. $this->eliminateEpsilons(); // may introduce units
  620. $this->eliminateUnits();
  621. $this->eliminateUselesses();
  622. }
  623. }
  624. ?>