PageRenderTime 58ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/index.html

https://github.com/connec/yash
HTML | 368 lines | 356 code | 12 blank | 0 comment | 0 complexity | 4d2b5af123eb071a6bbf7993edc33b94 MD5 | raw file
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>YASH</title>
  5. <style>
  6. html, body {
  7. margin: 0;
  8. padding: 0;
  9. }
  10. </style>
  11. <link rel="stylesheet" href="jquery.yash.css">
  12. <script type="text/javascript" src="jquery.js"></script>
  13. <script type="text/javascript" src="jquery.yash.js"></script>
  14. <script type="text/javascript" src="yash.syntax.php.js"></script>
  15. <script type="text/javascript">
  16. window.onload = function() {
  17. $('#editor').yash();
  18. };
  19. </script>
  20. </head>
  21. <body>
  22. <div id="editor">&lt;?php
  23. /**
  24. * parser.php
  25. */
  26. namespace tsml;
  27. use \Exception;
  28. /**
  29. * The Parser class constructs a parse tree from a TSML source document.
  30. */
  31. class Parser {
  32. /**
  33. * A flag indicating the next line is expected to have more indentation.
  34. */
  35. const EXPECT_MORE = 1;
  36. /**
  37. * A flag indicating the next line is expected to have the same indentation.
  38. */
  39. const EXPECT_SAME = 2;
  40. /**
  41. * A flag indicating the next line is expected to have less indentation.
  42. */
  43. const EXPECT_LESS = 4;
  44. /**
  45. * A convenience flag indicating the next line can have anu indentation.
  46. */
  47. const EXPECT_ANY = 7;
  48. /**
  49. * A regular expression for matching a valid tag name.
  50. */
  51. const RE_TAG = '/^\+([a-z][_:a-z0-9]*)/i';
  52. /**
  53. * A regular expression for matching a valid id.
  54. */
  55. const RE_ID = '/^#([_a-z][-_:a-z0-9]*)/i';
  56. /**
  57. * A regular expression for matching a valid class name.
  58. */
  59. const RE_CLASS = '/^\.(-?[_a-z][-_a-z0-9]*)/i';
  60. /**
  61. * A regular expression for matching a valid attribute/value.
  62. */
  63. const RE_ATTR = '/^([_:a-z][-_:.a-z0-9]*)(?:=(["\'])(\\\\.|(?!\\\\|\\2).*)\\2)?(?:\s|$)/i';
  64. /**
  65. * A regular expression for matching start-of-line escape sequences.
  66. */
  67. const RE_ESCAPES = '/^\\\\(\s|[+])/';
  68. /**
  69. * The options this instance is using.
  70. */
  71. protected $options = [];
  72. /**
  73. * The source document. An array of lines when using parse_string, a file
  74. * handle when using parse_file.
  75. */
  76. protected $source;
  77. /**
  78. * The root of the parse tree.
  79. */
  80. protected $parse_tree;
  81. /**
  82. * The current context in the parse tree.
  83. */
  84. protected $context;
  85. /**
  86. * The line number currently being parsed.
  87. */
  88. protected $line_number;
  89. /**
  90. * The line currently being parsed.
  91. */
  92. protected $line;
  93. /**
  94. * The current level of indentation.
  95. */
  96. protected $indent_level;
  97. /**
  98. * The parser's expected about the next line's indentation level.
  99. */
  100. protected $allow_child;
  101. /**
  102. * Instantiate a Parser with given options.
  103. */
  104. public function __construct(array $options = []) {
  105. $this-&gt;options = array_merge($this-&gt;options, $options);
  106. }
  107. /**
  108. * Parses a TSML string.
  109. */
  110. public function parse_string($tsml_string) {
  111. $tsml_string = str_replace(["\r\n", "\r"], "\n", $tsml_string);
  112. $this-&gt;source = explode("\n", $tsml_string);
  113. $this-&gt;parse();
  114. $this-&gt;source = [];
  115. return $this-&gt;parse_tree;
  116. }
  117. /**
  118. * Parses a TSML file.
  119. */
  120. public function parse_file($tsml_file) {
  121. $this-&gt;source = fopen($tsml_file, 'r');
  122. $this-&gt;parse();
  123. fclose($this-&gt;source);
  124. return $this-&gt;parse_tree;
  125. }
  126. /**
  127. * Parses stored TSML source.
  128. */
  129. protected function parse() {
  130. $this-&gt;reset();
  131. while($this-&gt;get_line()) {
  132. if(!trim($this-&gt;line)) {
  133. $this-&gt;allow_child = false;
  134. continue;
  135. }
  136. $this-&gt;handle_indent();
  137. if(strpos($this-&gt;line, '+#') === 0)
  138. $this-&gt;handle_loud_comment();
  139. elseif($this-&gt;line[0] == '+')
  140. $this-&gt;handle_tag();
  141. elseif(strpos($this-&gt;line, '-#') === 0)
  142. continue;
  143. else
  144. $this-&gt;handle_text();
  145. }
  146. }
  147. /**
  148. * Resets the parser.
  149. */
  150. protected function reset() {
  151. $this-&gt;parse_tree = new RootNode(-1);
  152. $this-&gt;context = $this-&gt;parse_tree;
  153. $this-&gt;line_number = 0;
  154. $this-&gt;indent_string = '';
  155. $this-&gt;indent_level = 0;
  156. $this-&gt;allow_child = false;
  157. if(is_resource($this-&gt;source))
  158. rewind($this-&gt;source);
  159. else
  160. reset($this-&gt;source);
  161. }
  162. /**
  163. * Gets the next line from the source.
  164. */
  165. protected function get_line() {
  166. $this-&gt;line_number ++;
  167. if(is_resource($this-&gt;source)) {
  168. if($this-&gt;line = rtrim(fgets($this-&gt;source), "\r\n"))
  169. return true;
  170. } else {
  171. if(is_array($this-&gt;line = each($this-&gt;source))) {
  172. $this-&gt;line = $this-&gt;line[1];
  173. return true;
  174. }
  175. }
  176. $this-&gt;line = '';
  177. return false;
  178. }
  179. /**
  180. * Adjusts the indentation level and context for the current line.
  181. */
  182. protected function handle_indent() {
  183. preg_match('/^\s*/', $this-&gt;line, $matches);
  184. if(str_replace(' ', '', $matches[0]))
  185. $this-&gt;exception('Invalid indentation');
  186. $new_indent_level = substr_count($matches[0], ' ');
  187. if($new_indent_level &gt; $this-&gt;indent_level) {
  188. if($new_indent_level - $this-&gt;indent_level &gt; 1)
  189. $this-&gt;exception('Indentation level increased by more than one');
  190. if(!$this-&gt;allow_child)
  191. $this-&gt;exception('Unexpected indentation level increase, previous line does not allow children');
  192. if(!($this-&gt;context = $this-&gt;context-&gt;last_child()))
  193. $this-&gt;exception('No child node for context update');
  194. } elseif($new_indent_level &lt; $this-&gt;indent_level) {
  195. for($i = 0, $j = $this-&gt;indent_level - $new_indent_level; $i &lt; $j; $i ++) {
  196. if(!($this-&gt;context = $this-&gt;context-&gt;parent()))
  197. $this-&gt;exception('No parent node for context update');
  198. }
  199. }
  200. $this-&gt;line = ltrim($this-&gt;line);
  201. $this-&gt;indent_level = $new_indent_level;
  202. $this-&gt;allow_child = false;
  203. }
  204. /**
  205. * Handles a loud comment.
  206. */
  207. protected function handle_loud_comment() {
  208. $node = new LoudCommentNode($this-&gt;line_number);
  209. $node-&gt;content = trim(substr($this-&gt;line, 2));
  210. $this-&gt;context-&gt;add_child($node);
  211. $this-&gt;indent_expected = static::EXPECT_SAME | static::EXPECT_LESS;
  212. }
  213. /**
  214. * Handles a tag line.
  215. */
  216. protected function handle_tag() {
  217. $line = $this-&gt;line;
  218. $node = new TagNode($this-&gt;line_number);
  219. if(!preg_match(static::RE_TAG, $line, $matches))
  220. $this-&gt;exception('Invalid tag name');
  221. $line = (string) substr($line, strlen($matches[0]));
  222. $node-&gt;tag_name = $matches[1];
  223. if($line and $line[0] == '#') {
  224. if(!preg_match(static::RE_ID, $line, $matches))
  225. $this-&gt;exception('Invalid id');
  226. $line = (string) substr($line, strlen($matches[0]));
  227. $node-&gt;id = $matches[1];
  228. }
  229. while($line and $line[0] == '.') {
  230. if(!preg_match(static::RE_CLASS, $line, $matches))
  231. $this-&gt;exception('Invalid class name');
  232. $line = (string) substr($line, strlen($matches[0]));
  233. $node-&gt;classes[] = $matches[1];
  234. }
  235. if($line and $line[0] == '[') {
  236. if($line[strlen($line) - 1] != ']')
  237. $this-&gt;exception('No closing `]` before end-of-line');
  238. $line = substr($line, 1, -1);
  239. while($line = trim($line)) {
  240. if(!preg_match(static::RE_ATTR, $line, $matches))
  241. $this-&gt;exception('Invalid attribute');
  242. $line = (string) substr($line, strlen($matches[0]));
  243. if(!isset($matches[2]) or ! $matches[2])
  244. $node-&gt;attributes[$matches[1]] = true;
  245. else
  246. $node-&gt;attributes[$matches[1]] = str_replace("\\$matches[2]", $matches[2], $matches[3]);
  247. }
  248. }
  249. if($line)
  250. $this-&gt;exception('Expected end-of-line after tag definition');
  251. $this-&gt;context-&gt;add_child($node);
  252. $this-&gt;allow_child = true;
  253. }
  254. /**
  255. * Handles a text line.
  256. */
  257. protected function handle_text() {
  258. $node = new TextNode($this-&gt;line_number);
  259. $node-&gt;content = preg_replace(static::RE_ESCAPES, '$1', $this-&gt;line);
  260. $this-&gt;context-&gt;add_child($node);
  261. $this-&gt;indent_expected = static::EXPECT_SAME | static::EXPECT_LESS;
  262. }
  263. /**
  264. * Throws an exception, appending the line number.
  265. */
  266. protected function exception($message) {
  267. throw new Exception('ParseError: ' . $message . ' - line ' . $this-&gt;line_number);
  268. }
  269. }
  270. require 'node.php';
  271. require 'root_node.php';
  272. require 'tag_node.php';
  273. require 'text_node.php';
  274. require 'loud_comment_node.php';
  275. require 'generator.php';
  276. $tsml = &lt;&lt;&lt;TSML
  277. +# The main navigation
  278. +nav
  279. TSML;
  280. $parser = new Parser;
  281. $tree = $parser-&gt;parse_string($tsml);
  282. $tree-&gt;print_tree();
  283. $generator = new Generator;
  284. $html = $generator-&gt;generate($tree);
  285. echo htmlentities($html);
  286. ?&gt;</div>
  287. </body>
  288. </html>