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