1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Nette;
13:
14: use Nette;
15:
16:
17:
18: 19: 20: 21: 22:
23: class NeonParser extends Object
24: {
25:
26: private static $patterns = array(
27: '\'[^\'\n]*\'|"(?:\\\\.|[^"\\\\\n])*"', 28: '@[a-zA-Z_0-9\\\\]+', 29: '[:-](?=\s|$)|[,=[\]{}()]', 30: '?:#.*', 31: '\n *', 32: '[^#"\',:=@[\]{}()<>\s](?:[^#,:=\]})>\n]+|:(?!\s)|(?<!\s)#)*(?<!\s)', 33: '?: +', 34: );
35:
36:
37: private static $tokenizer;
38:
39: private static $brackets = array(
40: '[' => ']',
41: '{' => '}',
42: '(' => ')',
43: );
44:
45:
46: private $n;
47:
48:
49:
50: 51: 52: 53: 54:
55: public function parse($input)
56: {
57: if (!self::$tokenizer) { 58: self::$tokenizer = new Tokenizer(self::$patterns, 'mi');
59: }
60: $input = str_replace("\r", '', $input);
61: $input = strtr($input, "\t", ' ');
62: $input = "\n" . $input . "\n"; 63: self::$tokenizer->tokenize($input);
64:
65: $this->n = 0;
66: $res = $this->_parse();
67:
68: while (isset(self::$tokenizer->tokens[$this->n])) {
69: if (self::$tokenizer->tokens[$this->n][0] === "\n") {
70: $this->n++;
71: } else {
72: $this->error();
73: }
74: }
75: return $res;
76: }
77:
78:
79:
80: 81: 82: 83: 84: 85:
86: private function _parse($indent = NULL, $endBracket = NULL)
87: {
88: $inlineParser = $endBracket !== NULL; 89:
90: $result = $inlineParser || $indent ? array() : NULL;
91: $value = $key = $object = NULL;
92: $hasValue = $hasKey = FALSE;
93: $tokens = self::$tokenizer->tokens;
94: $n = & $this->n;
95: $count = count($tokens);
96:
97: for (; $n < $count; $n++) {
98: $t = $tokens[$n];
99:
100: if ($t === ',') { 101: if (!$hasValue || !$inlineParser) {
102: $this->error();
103: }
104: if ($hasKey) $result[$key] = $value; else $result[] = $value;
105: $hasKey = $hasValue = FALSE;
106:
107: } elseif ($t === ':' || $t === '=') { 108: if ($hasKey || !$hasValue) {
109: $this->error();
110: }
111: $key = (string) $value;
112: $hasKey = TRUE;
113: $hasValue = FALSE;
114:
115: } elseif ($t === '-') { 116: if ($hasKey || $hasValue || $inlineParser) {
117: $this->error();
118: }
119: $key = NULL;
120: $hasKey = TRUE;
121:
122: } elseif (isset(self::$brackets[$t])) { 123: if ($hasValue) {
124: $this->error();
125: }
126: $hasValue = TRUE;
127: $value = $this->_parse(NULL, self::$brackets[$tokens[$n++]]);
128:
129: } elseif ($t === ']' || $t === '}' || $t === ')') { 130: if ($t !== $endBracket) { 131: $this->error();
132: }
133: if ($hasValue) {
134: if ($hasKey) $result[$key] = $value; else $result[] = $value;
135: } elseif ($hasKey) {
136: $this->error();
137: }
138: return $result; 139:
140: } elseif ($t[0] === '@') { 141: $object = $t; 142:
143: } elseif ($t[0] === "\n") { 144: if ($inlineParser) {
145: if ($hasValue) {
146: if ($hasKey) $result[$key] = $value; else $result[] = $value;
147: $hasKey = $hasValue = FALSE;
148: }
149:
150: } else {
151: while (isset($tokens[$n+1]) && $tokens[$n+1][0] === "\n") $n++; 152:
153: $newIndent = strlen($tokens[$n]) - 1;
154: if ($indent === NULL) { 155: $indent = $newIndent;
156: }
157:
158: if ($newIndent > $indent) { 159: if ($hasValue || !$hasKey) {
160: $this->error();
161: } elseif ($key === NULL) {
162: $result[] = $this->_parse($newIndent);
163: } else {
164: $result[$key] = $this->_parse($newIndent);
165: }
166: $newIndent = strlen($tokens[$n]) - 1;
167: $hasKey = FALSE;
168:
169: } else {
170: if ($hasValue && !$hasKey) { 171: if ($result === NULL) return $value; 172: $this->error();
173:
174: } elseif ($hasKey) {
175: $value = $hasValue ? $value : NULL;
176: if ($key === NULL) $result[] = $value; else $result[$key] = $value;
177: $hasKey = $hasValue = FALSE;
178: }
179: }
180:
181: if ($newIndent < $indent || !isset($tokens[$n+1])) { 182: return $result; 183: }
184: }
185:
186: } else { 187: if ($hasValue) {
188: $this->error();
189: }
190: if ($t[0] === '"') {
191: $value = json_decode($t);
192: if ($value === NULL) {
193: $this->error();
194: }
195: } elseif ($t[0] === "'") {
196: $value = substr($t, 1, -1);
197: } elseif ($t === 'true' || $t === 'yes' || $t === 'TRUE' || $t === 'YES') {
198: $value = TRUE;
199: } elseif ($t === 'false' || $t === 'no' || $t === 'FALSE' || $t === 'NO') {
200: $value = FALSE;
201: } elseif ($t === 'null' || $t === 'NULL') {
202: $value = NULL;
203: } elseif (is_numeric($t)) {
204: $value = $t * 1;
205: } else { 206: $value = $t;
207: }
208: $hasValue = TRUE;
209: }
210: }
211:
212: throw new NeonException('Unexpected end of file.');
213: }
214:
215:
216:
217: private function error()
218: {
219: list(, $line, $col) = self::$tokenizer->getOffset($this->n);
220: throw new NeonException("Unexpected '" . str_replace("\n", '\n', substr(self::$tokenizer->tokens[$this->n], 0, 10))
221: . "' on line " . ($line - 1) . ", column $col.");
222: }
223:
224: }
225:
226:
227:
228: 229: 230:
231: class NeonException extends \Exception
232: {
233: }
234: