PageRenderTime 53ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/Lisphp/Parser.php

http://github.com/lunant/lisphp
PHP | 143 lines | 133 code | 10 blank | 0 comment | 35 complexity | 527fa47b9a169af6a57c2b3048d0652c MD5 | raw file
  1. <?php
  2. require_once 'Lisphp/List.php';
  3. require_once 'Lisphp/Quote.php';
  4. require_once 'Lisphp/Symbol.php';
  5. require_once 'Lisphp/Literal.php';
  6. require_once 'Lisphp/Program.php';
  7. final class Lisphp_Parser {
  8. const PARENTHESES = '(){}[]';
  9. const QUOTE_PREFIX = ':';
  10. const WHITESPACES = " \t\n\r\f\v\0";
  11. const REAL_PATTERN = '{^
  12. [+-]? ((\d+ | (\d* \. \d+ | \d+ \. \d*)) e [+-]? \d+
  13. | \d* \. \d+ | \d+ \. \d*)
  14. }ix';
  15. const INTEGER_PATTERN = '/^([+-]?)(0x([0-9a-f]+)|0([0-7]+)|[1-9]\d*|0)/i';
  16. const STRING_PATTERN = '/^"([^"\\\\]|\\\\.)*"|\'([^\'\\\\]|\\\\.)\'/';
  17. const STRING_ESCAPE_PATTERN = '/\\\\(([0-7]{1,3})|x([0-9A-Fa-f]{1,2})|.)/';
  18. const SYMBOL_PATTERN = '{^
  19. [^ \s \d () {} \[\] : +-] [^\s () {} \[\] :]*
  20. | [+-] ([^ \s \d () {} \[\] :] [^ \s () {} \[\]]*)?
  21. }x';
  22. static function parse($program, $asArray = false) {
  23. if (!$asArray) return new Lisphp_Program($program);
  24. $i = 0;
  25. $len = strlen($program);
  26. $forms = array();
  27. while ($i < $len) {
  28. if (strpos(self::WHITESPACES, $program[$i]) === false) {
  29. try {
  30. $forms[] = self::parseForm(substr($program, $i), $offset);
  31. } catch (Lisphp_ParsingException $e) {
  32. throw new Lisphp_ParsingException($program,
  33. $e->offset + $i);
  34. }
  35. $i += $offset;
  36. } else {
  37. ++$i;
  38. }
  39. }
  40. return $forms;
  41. }
  42. static function parseForm($form, &$offset) {
  43. static $parentheses = null;
  44. if (is_null($parentheses)) {
  45. $_parentheses = self::PARENTHESES;
  46. $parentheses = array();
  47. for ($i = 0, $len = strlen($_parentheses); $i < $len; $i += 2) {
  48. $parentheses[$_parentheses[$i]] = $_parentheses[$i + 1];
  49. }
  50. unset($_parentheses);
  51. }
  52. if (isset($form[0], $parentheses[$form[0]])) {
  53. $end = $parentheses[$form[0]];
  54. $values = array();
  55. $i = 1;
  56. $len = strlen($form);
  57. while ($i < $len && $form[$i] != $end) {
  58. if (strpos(self::WHITESPACES, $form[$i]) !== false) {
  59. ++$i;
  60. continue;
  61. }
  62. try {
  63. $values[] = self::parseForm(substr($form, $i), $_offset);
  64. $i += $_offset;
  65. } catch (Lisphp_ParsingException $e) {
  66. throw new Lisphp_ParsingException($form, $i + $e->offset);
  67. }
  68. }
  69. if (isset($form[$i]) && $form[$i] == $end) {
  70. $offset = $i + 1;
  71. return new Lisphp_List($values);
  72. }
  73. throw new Lisphp_ParsingException($form, $i);
  74. } else if (isset($form[0]) && $form[0] == self::QUOTE_PREFIX) {
  75. $parsed = self::parseForm(substr($form, 1), $_offset);
  76. $offset = $_offset + 1;
  77. return new Lisphp_Quote($parsed);
  78. } else if (preg_match(self::REAL_PATTERN, $form, $matches)) {
  79. $offset = strlen($matches[0]);
  80. return new Lisphp_Literal((float) $matches[0]);
  81. } else if (preg_match(self::INTEGER_PATTERN, $form, $matches)) {
  82. $offset = strlen($matches[0]);
  83. $sign = $matches[1] == '-' ? -1 : 1;
  84. $value = !empty($matches[3]) ? hexdec($matches[3])
  85. : (!empty($matches[4]) ? octdec($matches[4]) : $matches[2]);
  86. return new Lisphp_Literal($sign * $value);
  87. } else if (preg_match(self::STRING_PATTERN, $form, $matches)) {
  88. list($parsed) = $matches;
  89. $offset = strlen($parsed);
  90. return new Lisphp_Literal(
  91. preg_replace_callback(self::STRING_ESCAPE_PATTERN,
  92. array(__CLASS__, '_unescapeString'),
  93. substr($parsed, 1, -1))
  94. );
  95. } else if (preg_match(self::SYMBOL_PATTERN, $form, $matches)) {
  96. $offset = strlen($matches[0]);
  97. return Lisphp_Symbol::get($matches[0]);
  98. } else {
  99. throw new Lisphp_ParsingException($form, 0);
  100. }
  101. }
  102. protected static function _unescapeString($matches) {
  103. static $map = array('n' => "\n", 'r' => "\r", 't' => "\t", 'v' => "\v",
  104. 'f' => "\f");
  105. if (!empty($matches[2])) return chr(octdec($matches[2]));
  106. else if (!empty($matches[3])) return chr(hexdec($matches[3]));
  107. else if (isset($map[$matches[1]])) return $map[$matches[1]];
  108. else return $matches[1];
  109. }
  110. }
  111. class Lisphp_ParsingException extends Exception {
  112. public $code, $offset, $lisphpFile;
  113. function __construct($code, $offset, $file = '') {
  114. $this->code = $code;
  115. $this->offset = $offset;
  116. $this->lisphpFile = $file;
  117. $on = ($file ? "$file:" : '')
  118. . $this->getLisphpLine() . ':'
  119. . $this->getLisphpColumn();
  120. $this->message = "parsing error on $on";
  121. }
  122. function getLisphpFile() {
  123. return $this->lisphpFile;
  124. }
  125. function getLisphpLine() {
  126. if ($this->offset <= 0) return 1;
  127. return substr_count($this->code, "\n", 0, $this->offset) + 1;
  128. }
  129. function getLisphpColumn() {
  130. $pos = strrpos(substr($this->code, 0, $this->offset), "\n");
  131. return $this->offset - ($pos === false ? -1 : $pos);
  132. }
  133. }