/src/EBNF/visitor/TextSyntaxTree.php

https://github.com/Weltraumschaf/ebnf · PHP · 252 lines · 84 code · 26 blank · 142 comment · 16 complexity · c0abf96f6478b7473a40b29c6650d0f6 MD5 · raw file

  1. <?php
  2. /**
  3. * This program is free software: you can redistribute it and/or modify
  4. * it under the terms of the GNU General Public License as published by
  5. * the Free Software Foundation, either version 3 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. * @license http://www.gnu.org/licenses/ GNU General Public License
  17. * @author Sven Strittmatter <ich@weltraumschaf.de>
  18. * @package visitor
  19. */
  20. namespace de\weltraumschaf\ebnf\visitor;
  21. /**
  22. * @see Visitor
  23. */
  24. require_once __DIR__ . DIRECTORY_SEPARATOR . 'Visitor.php';
  25. use de\weltraumschaf\ebnf\ast\Node;
  26. use de\weltraumschaf\ebnf\ast\Composite;
  27. use de\weltraumschaf\ebnf\ast\Identifier;
  28. use de\weltraumschaf\ebnf\ast\Rule;
  29. use de\weltraumschaf\ebnf\ast\Syntax;
  30. use de\weltraumschaf\ebnf\ast\Terminal;
  31. /**
  32. * Generates an ASCII formatted tree of the visited AST {@link Syntax} node.
  33. *
  34. * Example:
  35. *
  36. * The file <kbd>tests/fixtures/rules_with_literals.ebnf</kbd> will produce
  37. *
  38. * <pre>
  39. * [syntax]
  40. * +--[rule='literal']
  41. * +--[choice]
  42. * +--[sequence]
  43. * | +--[terminal=''']
  44. * | +--[identifier='character']
  45. * | +--[loop]
  46. * | | +--[identifier='character']
  47. * | +--[terminal=''']
  48. * +--[sequence]
  49. * +--[terminal='"']
  50. * +--[identifier='character']
  51. * +--[loop]
  52. * | +--[identifier='character']
  53. * +--[terminal='"']
  54. * </pre>
  55. *
  56. * For generating the tree lines a two dimensional array is used as
  57. * "render" matrix. The text is lazy computed by computing the array field
  58. * column by column and row by row.
  59. *
  60. * @package visitor
  61. * @version @@version@@
  62. */
  63. class TextSyntaxTree implements Visitor {
  64. /**
  65. * The formatted ASCII text.
  66. *
  67. * Lazy computed.
  68. *
  69. * @var string
  70. */
  71. private $text;
  72. /**
  73. * Depth of the visited tree. Asked in before visit from {@link Syntax} node.
  74. *
  75. * @var int
  76. */
  77. private $depth;
  78. /**
  79. * The indention level in the matrix.
  80. *
  81. * @var int
  82. */
  83. private $level = 0;
  84. /**
  85. * The matrix.
  86. *
  87. * Two dimenaionals array. Initialized on visiting a {@link Syntax} node.
  88. * So it is important that the syntax node is the root node of the tree.
  89. * The matrix grows row by row by visiting each child node. A child node
  90. * represents a row.
  91. *
  92. * @var array
  93. */
  94. private $matrix = array();
  95. /**
  96. * Returns the two dimensional matrix.
  97. *
  98. * @return array
  99. */
  100. public function getMatrix() {
  101. return $this->matrix;
  102. }
  103. /**
  104. * Returns the depth.
  105. *
  106. * @return int
  107. */
  108. public function getDepth() {
  109. return $this->depth;
  110. }
  111. /**
  112. * Formats nodes two strings.
  113. *
  114. * {@link Rule}, {@link Terminal} ans {@link Identifier} nodes will be
  115. * rendered with their attributes name or value.
  116. *
  117. * @param Node $n Formatted node.
  118. *
  119. * @return string
  120. */
  121. public static function formatNode(Node $n) {
  122. $text = "[{$n->getNodeName()}";
  123. if ($n instanceof Rule && ! empty($n->name)) {
  124. $text .= "='{$n->name}'";
  125. } else if ($n instanceof Terminal || $n instanceof Identifier) {
  126. if ( ! empty($n->value)) {
  127. $text .= "='{$n->value}'";
  128. }
  129. }
  130. $text .= "]";
  131. return $text;
  132. }
  133. /**
  134. * Returns an array of $colCount empty strings as elements.
  135. *
  136. * @param int $colCount Count of colums with empty strings.
  137. *
  138. * @return array
  139. */
  140. public static function createRow($colCount) {
  141. $colCount = max(0, (int) $colCount);
  142. $row = array();
  143. for ($i = 0; $i < $colCount; $i++) {
  144. $row[] = "";
  145. }
  146. return $row;
  147. }
  148. /**
  149. * If as {@link Syntax} node comes around the visitor will be initializez.
  150. * Which means that the depth property is read, the matrix and level properties
  151. * will be initialized. All other {@link Node} types increment the level property.
  152. *
  153. * @param Node $visitable Visited node.
  154. *
  155. * @return void
  156. */
  157. public function beforeVisit(Node $visitable) {
  158. if ($visitable instanceof Syntax) {
  159. $this->depth = $visitable->depth();
  160. $this->matrix = array();
  161. $this->level = 0;
  162. } else {
  163. $this->level++;
  164. }
  165. // While we're visiting the output will change anyway.
  166. $this->text = null;
  167. }
  168. /**
  169. * Genertates the string contents in the row of the visited node.
  170. *
  171. * @param Node $visitable Visited node.
  172. *
  173. * @return void
  174. */
  175. public function visit(Node $visitable) {
  176. $row = self::createRow($this->depth);
  177. if ($this->level > 0) {
  178. for ($i = 0 ; $i < $this->level - 1; $i++) {
  179. $row[$i] = " ";
  180. }
  181. $row[$this->level - 1] = " +--";
  182. $row[$this->level] = self::formatNode($visitable);
  183. }
  184. $row[$this->level] = self::formatNode($visitable);
  185. $this->matrix[] = $row;
  186. }
  187. /**
  188. * Also "climbs" all rows in the current level and sets a "|" to parent nodes
  189. * id appropriate.
  190. *
  191. * Ans decrements the level until it reaches 0.
  192. *
  193. * @param Node $visitable visited node.
  194. *
  195. * @return void
  196. */
  197. public function afterVisit(Node $visitable) {
  198. $rowCnt = count($this->matrix);
  199. for ($i = $rowCnt - 1; $i > -1; $i--) {
  200. if ($this->matrix[$i][$this->level] === " +--" || $this->matrix[$i][$this->level] === " | ") {
  201. if ($this->matrix[$i - 1][$this->level] === " ") {
  202. $this->matrix[$i - 1][$this->level] = " | ";
  203. }
  204. }
  205. }
  206. $this->level = max(0, $this->level - 1);
  207. }
  208. /**
  209. * Concatenates the matrix columns and rows adn returns the ASCII formatted text.
  210. *
  211. * After all visiting is done this method only generates the string once and memizes
  212. * the result.
  213. *
  214. * @return string
  215. */
  216. public function getText() {
  217. if (null === $this->text) {
  218. $buffer = "";
  219. foreach ($this->matrix as $row) {
  220. $buffer .= implode("", $row) . PHP_EOL;
  221. }
  222. $this->text = $buffer;
  223. }
  224. return $this->text;
  225. }
  226. }