split casing and fill onto separate levels
[potlatch2.git] / org / as3yaml / Scanner.as
1 /*
2  * Copyright (c) 2007 Derek Wischusen
3  * 
4  * Permission is hereby granted, free of charge, to any person obtaining a copy of 
5  * this software and associated documentation files (the "Software"), to deal in 
6  * the Software without restriction, including without limitation the rights to 
7  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
8  * of the Software, and to permit persons to whom the Software is furnished to do
9  * so, subject to the following conditions:
10  * 
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13  * 
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
20  * SOFTWARE.
21  */
22
23
24 package org.as3yaml {
25         import org.as3yaml.tokens.*;
26         import org.as3yaml.util.StringUtils;
27         import org.idmedia.as3commons.util.Iterator;
28         import org.rxr.actionscript.io.StringReader;
29
30         
31
32
33 public class Scanner {
34     private const LINEBR : String = "\n\u0085\u2028\u2029";
35     private const NULL_BL_LINEBR : String = "\x00 \r\n\u0085";
36     private const NULL_BL_T_LINEBR : String = "\x00 \t\r\n\u0085";
37     private const NULL_OR_OTHER : String = NULL_BL_T_LINEBR;
38     private const NULL_OR_LINEBR : String = "\x00\r\n\u0085";
39     private const FULL_LINEBR : String = "\r\n\u0085";
40     private const BLANK_OR_LINEBR : String = " \r\n\u0085";
41     private const S4 : String = "\0 \t\r\n\u0028[]{}";    
42     private const ALPHA : String = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
43     private const STRANGE_CHAR : String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789][-';/?:@&=+$,.!~*()%";
44     private const RN : String = "\r\n";
45     private const BLANK_T : String = " \t";
46     private const SPACES_AND_STUFF : String = "'\"\\\x00 \t\r\n\u0085";
47     private const DOUBLE_ESC : String = "\"\\";
48     private const NON_ALPHA_OR_NUM : String = "\x00 \t\r\n\u0085?:,]}%@`";
49     private const NON_PRINTABLE : RegExp = new RegExp("[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD]");
50     private const NOT_HEXA : RegExp = new RegExp("[^0-9A-Fa-f]");
51     private const NON_ALPHA : RegExp = new RegExp("[^-0-9A-Za-z_]");
52     private const R_FLOWZERO : RegExp = new RegExp(/[\x00 \t\r\n\u0085]|(:[\x00 \t\r\n\u0028])/);
53     private const R_FLOWNONZERO : RegExp = new RegExp(/[\x00 \t\r\n\u0085\\\[\\\]{},:?]/);
54     private const LINE_BR_REG : RegExp = new RegExp("[\n\u0085]|(?:\r[^\n])");
55     private const END_OR_START : RegExp = new RegExp("^(---|\\.\\.\\.)[\0 \t\r\n\u0085]$");
56     private const ENDING : RegExp = new RegExp("^---[\0 \t\r\n\u0085]$");
57     private const START : RegExp = new RegExp("^\\.\\.\\.[\x00 \t\r\n\u0085]$");
58     private const BEG : RegExp = new RegExp(/^([^\x00 \t\r\n\u0085\\\-?:,\\[\\\]{}#&*!|>'\"%@]|([\\\-?:][^\x00 \t\r\n\u0085]))/);
59
60     private var ESCAPE_REPLACEMENTS : Object = new Object();
61     private var ESCAPE_CODES : Object = new Object();
62
63         private function initEscapes(): void { 
64         ESCAPE_REPLACEMENTS['0'] = "\x00";
65         ESCAPE_REPLACEMENTS['a'] = "\u0007";
66         ESCAPE_REPLACEMENTS['b'] = "\u0008";
67         ESCAPE_REPLACEMENTS['t'] = "\u0009";
68         ESCAPE_REPLACEMENTS['\t'] = "\u0009";
69         ESCAPE_REPLACEMENTS['n'] = "\n";
70         ESCAPE_REPLACEMENTS['v'] = "\u000B";
71         ESCAPE_REPLACEMENTS['f'] = "\u000C";
72         ESCAPE_REPLACEMENTS['r'] = "\r";
73         ESCAPE_REPLACEMENTS['e'] = "\u001B";
74         ESCAPE_REPLACEMENTS[' '] = "\u0020";
75         ESCAPE_REPLACEMENTS['"'] = "\"";
76         ESCAPE_REPLACEMENTS['\\'] = "\\";
77         ESCAPE_REPLACEMENTS['N'] = "\u0085";
78         ESCAPE_REPLACEMENTS['_'] = "\u00A0";
79         ESCAPE_REPLACEMENTS['L'] = "\u2028";
80         ESCAPE_REPLACEMENTS['P'] = "\u2029";
81
82         ESCAPE_CODES['x'] = 2;
83         ESCAPE_CODES['u'] = 4;
84         ESCAPE_CODES['U'] = 8;
85     }
86
87     private var done : Boolean = false;
88     private var flowLevel : int = 0;
89     private var tokensTaken : int = 0;
90     private var indent : int = -1;
91     private var allowSimpleKey : Boolean = true;
92     private var eof : Boolean = true;
93     private var column : int = 0;
94     private var buffer : StringReader;
95     private var tokens : Array;
96     private var indents : Array;
97     private var possibleSimpleKeys : Object;
98
99     private var docStart : Boolean = false;
100
101     public function Scanner(stream : String) {
102         initEscapes();
103         this.buffer = new StringReader(stream);
104         this.tokens = new Array();
105         this.indents = new Array();
106         this.possibleSimpleKeys = new Object();
107         checkPrintable(stream);
108         buffer.writeChar('\x00');
109         fetchStreamStart();
110     }
111
112     public function checkToken(choices : Array) : Boolean {
113 //        while(needMoreTokens()) {
114 //            fetchMoreTokens();
115 //        }
116         if(this.tokens.length > 0) {
117             if(choices.length == 0) {
118                 return true;
119             }
120            var first : Class = this.tokens.get(0) as Class;
121            var len: int = choices.length;
122             for (var i : int = 0; i < len; i++) {
123                 if(choices[i] is first) {
124                     return true;
125                 }
126             }
127         }
128         return false;
129     }
130
131     public function peekToken() : Token {
132         while(needMoreTokens()) {
133             fetchMoreTokens();
134         }
135         return Token(this.tokens.length == 0 ? null : this.tokens[0]);
136     }
137
138     public function getToken() : Token {
139
140         if(this.tokens.length > 0) {
141             this.tokensTaken++;
142             return this.tokens.shift() as Token;
143             
144         }
145         return null;
146     }
147
148     public function eachToken(scanner : Scanner) : Iterator  {
149         return new TokenIterator(scanner);
150     }
151
152     public function iterator(scanner : Scanner) : Iterator{
153         return eachToken(scanner);
154     }
155
156     private function peek(offset : int = 0) : String {
157         return buffer.peek(offset);
158     }
159
160     private function prefix(length : int, offset: int = 0) : String {
161         if(length > buffer.charsAvailable) {
162             return buffer.peekRemaining()
163         } else {
164             return buffer.peekFor(length, offset);
165         }
166     }
167
168     private function prefixForward(length : int) : String {
169         var buff : String = null;
170         if(length > buffer.charsAvailable) {
171             buff = buffer.readRemaining()();
172         } else {
173             buff = buffer.readFor(length);
174         }
175         var ch : String;
176         var j:int = buff.length;
177         for(var i:int=0; i<j; i++) {
178             ch = buff.charAt(i);
179             if(LINEBR.indexOf(ch) != -1 || (ch == '\r' && buff.charAt(i+1) != '\n')) {
180                 this.column = 0;
181             } else if(ch != '\uFEFF') {
182                 this.column++;
183             }
184         }
185         return buff;
186     }
187
188     private function forward(char: String=null) : void {
189         const ch1 : String =  char ? char : buffer.peek();
190         buffer.forward();
191         if(ch1 == '\n' || ch1 == '\u0085' || (ch1 == '\r' && buffer.peek() != '\n')) {
192             this.column = 0;
193         } else {
194             this.column++;
195         }
196     }
197     
198     private function forwardBy(length : int) : void {
199         var ch : String;
200         for(var i:int=0;i<length;i++) {
201             ch = buffer.read();
202             if(LINEBR.indexOf(ch) != -1 || (ch == '\r' && buffer.peek() != '\n')) {
203                 this.possibleSimpleKeys = new Object();
204                 this.column = 0;
205             } else if(ch != '\uFEFF') {
206                 this.column++;
207             }
208         }
209     }
210
211     private function checkPrintable(data : String) : void {
212         var match : Object = NON_PRINTABLE.exec(data);
213         if(match) {
214             throw new YAMLException("At " + match.index + " we found: " + match + ". Special characters are not allowed");
215         }
216     }
217
218     private function needMoreTokens() : Boolean {
219         
220         if(this.tokens.length == 0)
221                 return true;
222         else if(nextPossibleSimpleKey() == this.tokensTaken)
223                         return true;
224                 
225                 return false;
226     }
227         
228     private function fetchMoreTokens() : Token {
229         scanToNextToken();
230         unwindIndent(this.column);
231         var ch : String =  buffer.peek();
232         var colz :Boolean = this.column == 0;
233         switch(ch) {
234         case ':': if(this.flowLevel != 0 || NULL_OR_OTHER.indexOf(buffer.peek(1)) != -1) { return fetchValue(); } break;        
235         case '\'': return fetchSingle();
236         case '"': return fetchDouble();
237         case '?': if(this.flowLevel != 0 || NULL_OR_OTHER.indexOf(buffer.peek(1)) != -1) { return fetchKey(); } break;
238         case '%': if(colz) {return fetchDirective(); } break;
239         case '-': 
240             if((colz || docStart) && (ENDING.exec(prefix(4)))) {
241                 return fetchDocumentStart(); 
242             } else if(NULL_OR_OTHER.indexOf(buffer.peek(1)) != -1) {
243                 return fetchBlockEntry(); 
244             }
245             break;
246         case '.': 
247             if(colz && START.exec(prefix(4))) {
248                 return fetchDocumentEnd(); 
249             }
250             break;
251         case '[': return fetchFlowSequenceStart();
252         case '{': return fetchFlowMappingStart();
253         case ']': return fetchFlowSequenceEnd();
254         case '}': return fetchFlowMappingEnd();
255         case ',': return fetchFlowEntry();
256         case '*': return fetchAlias();
257         case '&': return fetchAnchor();
258         case '!': return fetchTag();
259         case '|': if(this.flowLevel == 0) { return fetchLiteral(); } break;
260         case '>': if(this.flowLevel == 0) { return fetchFolded(); } break;
261         case '\x00': return fetchStreamEnd();
262         }
263         if(BEG.exec(prefix(2))) {
264             return fetchPlain();
265         }
266         throw new ScannerException("while scanning for the next token","found character " + ch + "(" + (ch) + " that cannot start any token",null);
267     }
268
269     private function nextPossibleSimpleKey() : int {
270         for(var keyObj : Object in this.possibleSimpleKeys) {
271             var sk : SimpleKey = this.possibleSimpleKeys[keyObj] as SimpleKey;
272             var tokNum : int = sk.tokenNumber;         
273             if(tokNum > 0) {
274                 return tokNum;
275             }
276         }
277         return -1;
278     }
279
280     private function removePossibleSimpleKey() : void {
281         var key : SimpleKey = SimpleKey(this.possibleSimpleKeys[this.flowLevel]);
282         if(key != null) {
283                 delete this.possibleSimpleKeys[this.flowLevel];
284             if(key.isRequired()) {
285                 throw new ScannerException("while scanning a simple key","could not find expected ':'",null);
286             }
287         }
288     }
289     
290     private function savePossibleSimpleKey() : void {
291         if(this.allowSimpleKey) {
292                 this.removePossibleSimpleKey();
293             this.possibleSimpleKeys[this.flowLevel] = new SimpleKey(this.tokensTaken+this.tokens.length,(this.flowLevel == 0) && this.indent == this.column,-1,-1,this.column);
294         }
295     }
296     
297     private function unwindIndent(col : int) : void {
298         if(this.flowLevel != 0) {
299             return;
300         }
301
302         while(this.indent > col) {
303             this.indent = this.indents.shift();
304             this.tokens.push(Tokens.BLOCK_END);
305         }
306     }
307     
308     private function addIndent(col : int) : Boolean {
309         if(this.indent < col) {
310             this.indents.unshift(this.indent);
311             this.indent = col;
312             return true;
313         }
314         return false;
315     }
316
317     private function fetchStreamStart() : Token {
318         this.docStart = true;
319         this.tokens.push(Tokens.STREAM_START);
320         return Tokens.STREAM_START;
321     }
322
323     private function fetchStreamEnd() : Token {
324         unwindIndent(-1);
325         this.allowSimpleKey = false;
326         this.possibleSimpleKeys = new Object();
327         this.tokens.push(Tokens.STREAM_END);
328         this.done = true;
329         return Tokens.STREAM_END;
330     }
331
332     private function fetchDirective() : Token {
333         unwindIndent(-1);
334         this.allowSimpleKey = false;
335         var tok : Token = scanDirective();
336         this.tokens.push(tok);
337         return tok;
338     }
339     
340     private function fetchDocumentStart() : Token {
341         this.docStart = false;
342         return fetchDocumentIndicator(Tokens.DOCUMENT_START);
343     }
344
345     private function fetchDocumentEnd() : Token {
346         return fetchDocumentIndicator(Tokens.DOCUMENT_END);
347     }
348
349     private function fetchDocumentIndicator(tok : Token) : Token {
350         unwindIndent(-1);
351         removePossibleSimpleKey();
352         this.allowSimpleKey = false;
353         forwardBy(3);
354         this.tokens.push(tok);
355         return tok;
356     }
357     
358     private function fetchFlowSequenceStart() : Token {
359         return fetchFlowCollectionStart(Tokens.FLOW_SEQUENCE_START);
360     }
361
362     private function fetchFlowMappingStart() : Token {
363         return fetchFlowCollectionStart(Tokens.FLOW_MAPPING_START);
364     }
365
366     private function fetchFlowCollectionStart(tok : Token) : Token {
367         savePossibleSimpleKey();
368         this.flowLevel++;
369         this.allowSimpleKey = true;
370         forwardBy(1);
371         this.tokens.push(tok);
372         return tok;
373     }
374
375     private function fetchFlowSequenceEnd() : Token {
376         return fetchFlowCollectionEnd(Tokens.FLOW_SEQUENCE_END);
377     }
378     
379     private function fetchFlowMappingEnd() : Token {
380         return fetchFlowCollectionEnd(Tokens.FLOW_MAPPING_END);
381     }
382     
383     private function fetchFlowCollectionEnd(tok : Token) : Token {
384         removePossibleSimpleKey();
385         this.flowLevel--;
386         this.allowSimpleKey = false;
387         forwardBy(1);
388         this.tokens.push(tok);
389         return tok;
390     }
391     
392     private function fetchFlowEntry() : Token {
393         this.allowSimpleKey = true;
394         removePossibleSimpleKey();
395         forwardBy(1);
396         this.tokens.push(Tokens.FLOW_ENTRY);
397         return Tokens.FLOW_ENTRY;
398     }
399
400     private function fetchBlockEntry() : Token {
401
402         if(this.flowLevel == 0) {
403             if(!this.allowSimpleKey) {
404                 throw new ScannerException(null,"sequence entries are not allowed here",null);
405             }
406             if(addIndent(this.column)) {
407                 this.tokens.push(Tokens.BLOCK_SEQUENCE_START);
408             }
409         }
410         this.allowSimpleKey = true;
411         removePossibleSimpleKey();
412         forward();
413         this.tokens.push(Tokens.BLOCK_ENTRY);
414
415         return Tokens.BLOCK_ENTRY;
416     }        
417
418     private function fetchKey() : Token {
419         if(this.flowLevel == 0) {
420             if(!this.allowSimpleKey) {
421                 throw new ScannerException(null,"mapping keys are not allowed here",null);
422             }
423             if(addIndent(this.column)) {
424                 this.tokens.push(Tokens.BLOCK_MAPPING_START);
425             }
426         }
427         this.allowSimpleKey = this.flowLevel == 0;
428         removePossibleSimpleKey();
429         forward();
430         this.tokens.push(Tokens.KEY);
431         return Tokens.KEY;
432     }
433
434     private function fetchValue() : Token {
435         this.docStart = false;
436         var key : SimpleKey = this.possibleSimpleKeys[this.flowLevel];
437         if(null == key) {
438             if(this.flowLevel == 0 && !this.allowSimpleKey) {
439                 throw new ScannerException(null,"mapping values are not allowed here",null);
440             }
441             this.allowSimpleKey = (this.flowLevel == 0);
442             removePossibleSimpleKey();
443         } else {
444             delete this.possibleSimpleKeys[this.flowLevel];
445             var sIndex: int = key.tokenNumber-this.tokensTaken;
446                     var s:Array = tokens.slice(0, sIndex);
447                     var e:Array = tokens.slice(sIndex);
448                     tokens = s.concat(Tokens.KEY).concat(e);
449             if(this.flowLevel == 0 && addIndent(key.getColumn())) {
450                     var sIndex2: int = key.tokenNumber-this.tokensTaken;
451                             var s2:Array = tokens.slice(0, sIndex2);
452                             var e2:Array = tokens.slice(sIndex2);
453                             tokens = s2.concat(Tokens.BLOCK_MAPPING_START).concat(e2);
454             }
455             this.allowSimpleKey = false;
456         }
457         forward();
458         this.tokens.push(Tokens.VALUE);
459         return Tokens.VALUE;
460     }
461
462     private function fetchAlias() : Token {
463         savePossibleSimpleKey();
464         this.allowSimpleKey = false;
465         var tok : Token = scanAnchor(new AliasToken());
466         this.tokens.push(tok);
467         return tok;
468     }
469
470     private function fetchAnchor() : Token {
471         savePossibleSimpleKey();
472         this.allowSimpleKey = false;
473         var tok : Token = scanAnchor(new AnchorToken());
474         this.tokens.push(tok);
475         return tok;
476     }
477
478     private function fetchTag() : Token {
479         this.docStart = false;
480         savePossibleSimpleKey();
481         this.allowSimpleKey = false;
482         var tok : Token = scanTag();
483         this.tokens.push(tok);
484         return tok;
485     }
486     
487     private function fetchLiteral() : Token {
488         return fetchBlockScalar('|');
489     }
490     
491     private function fetchFolded() : Token {
492         return fetchBlockScalar('>');
493     }
494     
495     private function fetchBlockScalar(style : String) : Token {
496         this.allowSimpleKey = true;
497         this.removePossibleSimpleKey();
498         var tok : Token = scanBlockScalar(style);
499         this.tokens.push(tok);
500         return tok;
501     }
502     
503     private function fetchSingle() : Token {
504         return fetchFlowScalar('\'');
505     }
506     
507     private function fetchDouble() : Token {
508         return fetchFlowScalar('"');
509     }
510     
511     private function fetchFlowScalar(style : String) : Token {
512         savePossibleSimpleKey();
513         this.allowSimpleKey = false;
514         var tok : Token = scanFlowScalar(style);
515         this.tokens.push(tok);
516         return tok;
517     }
518     
519     private function fetchPlain() : Token {
520         savePossibleSimpleKey();
521         this.allowSimpleKey = false;
522         var tok : Token = scanPlain();
523         this.tokens.push(tok);
524         return tok;
525     }
526     
527     private function scanToNextToken() : void {
528         while(1) {
529                 
530                         var pk : String = buffer.peek();
531             while(pk == ' ') {
532                 forward(pk);
533                 pk = buffer.peek();
534             }                   
535             if(pk == '#') {
536                 while(NULL_OR_LINEBR.indexOf(buffer.peek()) == -1) {
537                     forward();
538                 }
539             }
540             if(scanLineBreak().length != 0 ) {
541                 if(this.flowLevel == 0) {
542                     this.allowSimpleKey = true;
543                 }
544             } else {
545                 break;
546             }
547         }
548     }
549     
550     private function scanDirective() : Token {
551         forward();
552         var name : String = scanDirectiveName();
553         var value : Array = null;
554         if(name == ("YAML")) {
555             value = scanYamlDirectiveValue();
556         } else if(name == ("TAG")) {
557             value = scanTagDirectiveValue();
558         } else {
559             while(NULL_OR_LINEBR.indexOf(buffer.peek()) == -1) {
560                 forward();
561             }
562         }
563         scanDirectiveIgnoredLine();
564         return new DirectiveToken(name,value);
565     }
566     
567     private function scanDirectiveName() : String {
568         var length : int = 0;
569         var ch : String = buffer.peek(length);
570         var zlen : Boolean = true;
571         while(ALPHA.indexOf(ch) != -1) {
572             zlen = false;
573             length++;
574             ch = buffer.peek(length);
575         }
576         if(zlen) {
577             throw new ScannerException("while scanning a directive","expected alphabetic or numeric character, but found " + ch + "(" + (ch) + ")",null);
578         }
579         var value : String = prefixForward(length);
580         //        forward(length);
581         if(NULL_BL_LINEBR.indexOf(buffer.peek()) == -1) {
582             throw new ScannerException("while scanning a directive","expected alphabetic or numeric character, but found " + ch + "(" + (ch) + ")",null);
583         }
584         return value;
585     }
586
587     private function scanYamlDirectiveValue() : Array {
588         while(buffer.peek() == ' ') {
589             forward();
590         }
591         var major : String = scanYamlDirectiveNumber();
592         if(buffer.peek() != '.') {
593             throw new ScannerException("while scanning a directive","expected a digit or '.', but found " + buffer.peek() + "(" + (buffer.peek()) + ")",null);
594         }
595         forward();
596         var minor : String = scanYamlDirectiveNumber();
597         if(NULL_BL_LINEBR.indexOf(buffer.peek()) == -1) {
598             throw new ScannerException("while scanning a directive","expected a digit or ' ', but found " + buffer.peek() + "(" + (buffer.peek()) + ")",null);
599         }
600         return [major,minor];
601     }
602
603     private function scanYamlDirectiveNumber() : String {
604         var ch : String = buffer.peek();
605         if(!StringUtils.isDigit(ch)) {
606             throw new ScannerException("while scanning a directive","expected a digit, but found " + ch + "(" + (ch) + ")",null);
607         }
608         var length : int = 0;
609         while(StringUtils.isDigit(buffer.peek(length))) {
610             length++;
611         }
612         var value : String = prefixForward(length);
613         //        forward(length);
614         return value;
615     }
616
617     private function scanTagDirectiveValue() : Array  {
618         while(buffer.peek() == ' ') {
619             forward();
620         }
621         var handle : String = scanTagDirectiveHandle();
622         while(buffer.peek() == ' ') {
623             forward();
624         }
625         var prefix : String = scanTagDirectivePrefix();
626         return [handle,prefix];
627     }
628
629     private function scanTagDirectiveHandle() : String {
630         var value : String = scanTagHandle("directive");
631         if(buffer.peek() != ' ') {
632             throw new ScannerException("while scanning a directive","expected ' ', but found " + buffer.peek() + "(" + (buffer.peek()) + ")",null);
633         }
634         return value;
635     }
636     
637     private function scanTagDirectivePrefix() : String {
638         var value : String = scanTagUri("directive");
639         if(NULL_BL_LINEBR.indexOf(buffer.peek()) == -1) {
640             throw new ScannerException("while scanning a directive","expected ' ', but found " + buffer.peek() + "(" + (buffer.peek()) + ")",null);
641         }
642         return value;
643     }
644
645     private function scanDirectiveIgnoredLine() : String {
646         while(buffer.peek() == ' ') {
647             forward();
648         }
649         if(buffer.peek() == '"') {
650             while(NULL_OR_LINEBR.indexOf(buffer.peek()) == -1) {
651                 forward();
652             }
653         }
654         var ch : String = buffer.peek();
655         if(NULL_OR_LINEBR.indexOf(ch) == -1) {
656             throw new ScannerException("while scanning a directive","expected a comment or a line break, but found " + buffer.peek() + "(" + (buffer.peek()) + ")",null);
657         }
658         return scanLineBreak();
659     }
660
661     private function scanAnchor(tok : Token) : Token {
662         var indicator : String = buffer.peek();
663         var name : String = indicator == '*' ? "alias" : "anchor";
664         forward();
665         var length : int = 0;
666         var chunk_size : int = 16;
667         var match : Object;
668         for(;;) {
669             var chunk : String = prefix(chunk_size);
670             if((match = NON_ALPHA.exec(chunk))) {
671                 break;
672             }
673             chunk_size+=16;
674         }
675         length = match.index;
676         if(length == 0) {
677             throw new ScannerException("while scanning an " + name,"expected alphabetic or numeric character, but found something else...",null);
678         }
679         var value : String = prefixForward(length);
680         //        forward(length);
681         if(NON_ALPHA_OR_NUM.indexOf(buffer.peek()) == -1) {
682             throw new ScannerException("while scanning an " + name,"expected alphabetic or numeric character, but found "+ buffer.peek() + "(" + (buffer.peek()) + ")",null);
683
684         }
685         tok.setValue(value);
686         return tok;
687     }
688
689     private function scanTag() : Token {
690         var ch : String = buffer.peek(1);
691         var handle : String = null;
692         var suffix : String = null;
693         if(ch == '<') {
694             forwardBy(2);
695             suffix = scanTagUri("tag");
696             if(buffer.peek() != '>') {
697                 throw new ScannerException("while scanning a tag","expected '>', but found "+ buffer.peek() + "(" + (buffer.peek()) + ")",null);
698             }
699             forward();
700         } else if(NULL_BL_T_LINEBR.indexOf(ch) != -1) {
701             suffix = "!";
702             forward();
703         } else {
704             var length : int = 1;
705             var useHandle : Boolean = false;
706             while(NULL_BL_T_LINEBR.indexOf(ch) == -1) {
707                 if(ch == '!') {
708                     useHandle = true;
709                     break;
710                 }
711                 length++;
712                 ch = buffer.peek(length);
713             }
714             handle = "!";
715             if(useHandle) {
716                 handle = scanTagHandle("tag");
717             } else {
718                 handle = "!";
719                 forward();
720             }
721             suffix = scanTagUri("tag");
722         }
723         if(NULL_BL_LINEBR.indexOf(buffer.peek()) == -1) {
724             throw new ScannerException("while scanning a tag","expected ' ', but found " + buffer.peek() + "(" + (buffer.peek()) + ")",null);
725         }
726         return new TagToken([handle,suffix]);
727     }
728
729     private function scanBlockScalar(style : String) : ScalarToken {
730         var folded : Boolean = style == '>';
731         var chunks : String = new String();
732         forward();
733         var chompi : Array = scanBlockScalarIndicators();
734         var chomping : Boolean = Boolean(chompi[0])
735         var increment : int = (int(chompi[1]));
736         scanBlockScalarIgnoredLine();
737         var minIndent : int = this.indent+1;
738         if(minIndent < 1) {
739             minIndent = 1;
740         }
741         var breaks : String = null;
742         var maxIndent : int = 0;
743         var ind : int = 0;
744         if(increment == -1) {
745             var brme : Array = scanBlockScalarIndentation();
746             breaks = String(brme[0]);
747             maxIndent = (int(brme[1]))
748             if(minIndent > maxIndent) {
749                 ind = minIndent;
750             } else {
751                 ind = maxIndent;
752             }
753         } else {
754             ind = minIndent + increment - 1;
755             breaks = scanBlockScalarBreaks(ind);
756         }
757                 var pk :String = buffer.peek();
758         var lineBreak : String = "";
759         while(this.column == ind && pk != '\x00') {
760             chunks += breaks;
761             var leadingNonSpace : Boolean = BLANK_T.indexOf(pk) == -1;
762             var length : int = 0;
763             while(NULL_OR_LINEBR.indexOf(buffer.peek(length))==-1) {
764                 length++;
765             }
766             chunks += prefixForward(length);
767             //            forward(length);
768             lineBreak = scanLineBreak();
769             breaks = scanBlockScalarBreaks(ind);
770             pk = buffer.peek();
771             if(this.column == ind && pk != '\x00') {
772                 if(folded && lineBreak == ("\n") && leadingNonSpace && BLANK_T.indexOf(pk) == -1) {
773                     if(breaks.length == 0) {
774                         chunks += " ";
775                     }
776                 } else {
777                     chunks += lineBreak;
778                 }
779             } else {
780                 break;
781             }
782         }
783
784         if(chomping) {
785             chunks += lineBreak;
786             chunks += breaks;
787         }
788
789         return new ScalarToken(chunks,false,style);
790     }
791
792     private function scanBlockScalarIndicators() : Array {
793         var chomping : Boolean = false;
794         var increment : int = -1;
795         var ch : String = buffer.peek();
796         if(ch == '-' || ch == '+') {
797             chomping = ch == '+';
798             forward(ch);
799             ch = buffer.peek();
800             if(StringUtils.isDigit(ch)) {
801                 increment = int(ch);
802                 if(increment == 0) {
803                     throw new ScannerException("while scanning a block scalar","expected indentation indicator in the range 1-9, but found 0",null);
804                 }
805                 forward(ch);
806             }
807         } else if(StringUtils.isDigit(ch)) {
808             increment = int(ch);
809             if(increment == 0) {
810                 throw new ScannerException("while scanning a block scalar","expected indentation indicator in the range 1-9, but found 0",null);
811             }
812             forward();
813             ch = buffer.peek();
814             if(ch == '-' || ch == '+') {
815                 chomping = ch == '+';
816                 forward();
817             }
818         }
819         if(NULL_BL_LINEBR.indexOf(buffer.peek()) == -1) {
820             throw new ScannerException("while scanning a block scalar","expected chomping or indentation indicators, but found " + buffer.peek() + "(" + (buffer.peek()) + ")",null);
821         }
822         return [chomping, increment];
823 }
824
825     private function scanBlockScalarIgnoredLine() : String {
826         while(buffer.peek() == ' ') {
827             forward();
828         }
829         if(buffer.peek() == '#') {
830             while(NULL_OR_LINEBR.indexOf(buffer.peek()) == -1) {
831                 forward();
832             }
833         }
834         if(NULL_OR_LINEBR.indexOf(buffer.peek()) == -1) {
835             throw new ScannerException("while scanning a block scalar","expected a comment or a line break, but found " + buffer.peek() + "(" + (buffer.peek()) + ")",null);
836         }
837         return scanLineBreak();
838     }
839
840     private function scanBlockScalarIndentation() : Array {
841         var chunks : String = new String();
842         var maxIndent : int = 0;
843         while(BLANK_OR_LINEBR.indexOf(buffer.peek()) != -1) {
844             if(buffer.peek() != ' ') {
845                 chunks += scanLineBreak();
846             } else {
847                 forward();
848                 if(this.column > maxIndent) {
849                     maxIndent = column;
850                 }
851             }
852         }
853         return [chunks, maxIndent];
854     }
855
856     private function scanBlockScalarBreaks(indent : int) : String {
857         var chunks : String = new String();
858         while(this.column < indent && buffer.peek() == ' ') {
859             forward();
860         }
861         while(FULL_LINEBR.indexOf(buffer.peek()) != -1) {
862             chunks += scanLineBreak();
863             while(this.column < indent && buffer.peek() == ' ') {
864                 forward();
865             }
866         }
867         return chunks;
868     }
869
870     private function scanFlowScalar(style : String) : Token {
871         var dbl : Boolean = style == '"';
872         var chunks : String = new String();
873         var quote : String = buffer.peek();
874         forward();
875         chunks += scanFlowScalarNonSpaces(dbl);
876         while(buffer.peek() != quote) {
877             chunks += scanFlowScalarSpaces();
878             chunks += scanFlowScalarNonSpaces(dbl);
879         }
880         forward();
881                
882         return new ScalarToken(chunks,false,style);
883     }
884
885     private function scanFlowScalarNonSpaces(dbl : Boolean) : String {
886         var chunks : String = new String();
887         while(1) {
888             var length : int = 0;
889             while(SPACES_AND_STUFF.indexOf(buffer.peek(length)) == -1) {
890                 length++;
891             }
892             if(length != 0) {
893                 chunks += (prefixForward(length));
894                 //                forward(length);
895             }
896             var ch : String = buffer.peek();
897             if(!dbl && ch == '\'' && buffer.peek(1) == '\'') {
898                 chunks += ("'");
899                 forwardBy(2);
900             } else if((dbl && ch == '\'') || (!dbl && DOUBLE_ESC.indexOf(ch) != -1)) {
901                 chunks += ch;
902                 forward();
903             } else if(dbl && ch == '\\') {
904                 forward();
905                 ch = buffer.peek();
906                 if(ESCAPE_REPLACEMENTS[ch]) {
907                     chunks += ESCAPE_REPLACEMENTS[ch];
908                     forward(); 
909                 } else if(ESCAPE_CODES[ch]) {
910                     length = (ESCAPE_CODES[ch]);
911                     forward();
912                     var val : String = prefix(length);
913                     if(NOT_HEXA.exec(val)) {
914                         throw new ScannerException("while scanning a double-quoted scalar","expected escape sequence of " + length + " hexadecimal numbers, but found something else: " + val,null);
915                     }
916                     var charCode : int = parseInt(val, 16);
917                     var char : String = String.fromCharCode(charCode);
918                     chunks += char;
919                     forwardBy(length);
920                 } else if(FULL_LINEBR.indexOf(ch) != -1) {
921                     scanLineBreak();
922                     chunks += scanFlowScalarBreaks();
923                 } else {
924                     throw new ScannerException("while scanning a double-quoted scalar","found unknown escape character " + ch + "(" + (ch) + ")",null);
925                 }
926             } else {
927                 return chunks;
928             }
929         }
930         return "";
931     }
932
933     private function scanFlowScalarSpaces() : String {
934         var chunks : String = new String();
935         var length : int = 1;
936         while(BLANK_T.indexOf(buffer.peek(length)) != -1) {
937             length++;
938         }
939         var whitespaces : String = prefixForward(length);
940         //        forward(length);
941         var ch : String = buffer.peek();
942         if(ch == '\x00') {
943             throw new ScannerException("while scanning a quoted scalar","found unexpected end of stream",null);
944         } else if(FULL_LINEBR.indexOf(ch) != -1) {
945             var lineBreak : String = scanLineBreak();
946             var breaks : String = scanFlowScalarBreaks();
947             if(!lineBreak == ("\n")) {
948                 chunks += lineBreak;
949             } else if(breaks.length == 0) {
950                 chunks += " ";
951             }
952             chunks += breaks;
953         } else {
954             chunks += whitespaces;
955         }
956         return chunks;
957     }
958
959     private function scanFlowScalarBreaks() : String {
960         var chunks : String = "";
961         var pre : String = null;
962         while(1) {
963             pre = prefix(3);
964             if((pre == ("---") || pre == ("...")) && NULL_BL_T_LINEBR.indexOf(buffer.peek(3)) != -1) {
965                 throw new ScannerException("while scanning a quoted scalar","found unexpected document separator",null);
966             }
967             while(BLANK_T.indexOf(buffer.peek()) != -1) {
968                 forward();
969             }
970             if(FULL_LINEBR.indexOf(buffer.peek()) != -1) {
971                 chunks += scanLineBreak();
972             } else {
973                 return chunks;
974             }            
975         }
976         return "";
977     }
978
979
980     private function scanPlain() : Token {
981         /*
982        See the specification for details.
983        We add an additional restriction for the flow context:
984          plain scalars in the flow context cannot contain ',', ':' and '?'.
985        We also keep track of the `allow_simple_key` flag here.
986        Indentation rules are loosed for the flow context.
987          */
988         
989         
990         var chunks : String = new String();
991         var ind : int = this.indent+1;
992         var spaces : String = "";
993         var f_nzero : Boolean = true;
994         var r_check : RegExp = R_FLOWNONZERO;
995         if(this.flowLevel == 0) {
996             f_nzero = false;
997             r_check = R_FLOWZERO;
998         }
999         while(buffer.peek() != '#') {
1000                 
1001             var chunkSize : int = 256;
1002             var startAt: int = 0;
1003             var match: Object;
1004  
1005             while(!(match = r_check.exec(prefix(chunkSize, startAt)))) {
1006                 startAt += chunkSize;
1007             }
1008             
1009             const length: int = startAt + int(match.index);
1010             var ch : String = buffer.peek(length);
1011             if(f_nzero && ch == ':' && S4.indexOf(buffer.peek(length+1)) == -1) {
1012                 forwardBy(length);
1013                 throw new ScannerException("while scanning a plain scalar","found unexpected ':'","Please check http://pyyaml.org/wiki/YAMLColonInFlowContext for details.");
1014             }
1015                         
1016             if(length == 0) {
1017                 break;
1018             }
1019             this.allowSimpleKey = false;
1020             chunks += spaces;
1021             chunks += prefixForward(length);
1022
1023             spaces = scanPlainSpaces(ind);
1024             if(spaces == null || (this.flowLevel == 0 && this.column < ind)) {
1025                 break;
1026             }
1027         }
1028                        
1029         return new ScalarToken(chunks,true);
1030     }
1031
1032     private function scanPlainSpaces(indent : int) : String {
1033         var chunks : String = new String();
1034         var length : int = 0;
1035         while(buffer.peek(length) == ' ') {
1036             length++;
1037         }
1038         var whitespaces : String = prefixForward(length);
1039         //        forward(length);
1040         var ch : String  = buffer.peek();
1041         if(FULL_LINEBR.indexOf(ch) != -1) {
1042             var lineBreak : String = scanLineBreak();
1043             this.allowSimpleKey = true;
1044             if(END_OR_START.exec(prefix(4))) {
1045                 return "";
1046             }
1047             var breaks : String = new String();
1048             while(BLANK_OR_LINEBR.indexOf(buffer.peek()) != -1) {
1049                 if(' ' == buffer.peek()) {
1050                     forward();
1051                 } else {
1052                     breaks += scanLineBreak();
1053                     if(END_OR_START.exec(prefix(4))) {
1054                         return "";
1055                     }
1056                 }
1057             }            
1058             if(!lineBreak == ("\n")) {
1059                 chunks += lineBreak;
1060             } else if(breaks == null || breaks.toString() == ("")) {
1061                 chunks += " ";
1062             }
1063             chunks += breaks;
1064         } else {
1065             chunks += whitespaces;
1066         }
1067         return chunks;
1068     }
1069
1070     private function scanTagHandle(name : String) : String {
1071         var ch : String =  buffer.peek();
1072         if(ch != '!') {
1073             throw new ScannerException("while scanning a " + name,"expected '!', but found " + ch + "(" + (ch) + ")",null);
1074         }
1075         var length : int = 1;
1076         ch = buffer.peek(length);
1077         if(ch != ' ') {
1078             while(ALPHA.indexOf(ch) != -1) {
1079                 length++;
1080                 ch = buffer.peek(length);
1081             }
1082             if('!' != ch) {
1083                 forwardBy(length);
1084                 throw new ScannerException("while scanning a " + name,"expected '!', but found " + ch + "(" + (ch) + ")",null);
1085             }
1086             length++;
1087         }
1088         var value :String = prefixForward(length);
1089
1090         return value;
1091     }
1092
1093     private function scanTagUri(name : String) : String {
1094         var chunks : String = new String();
1095         var length : int = 0;
1096         var ch : String = buffer.peek(length);
1097         while(STRANGE_CHAR.indexOf(ch) != -1) {
1098             if('%' == ch) {
1099                 chunks += prefixForward(length);
1100                 length = 0;
1101                 chunks += scanUriEscapes(name);
1102             } else {
1103                 length++;
1104             }
1105             ch = buffer.peek(length);
1106         }
1107         if(length != 0) {
1108             chunks += (prefixForward(length));
1109         }
1110
1111         if(chunks.length == 0) {
1112             throw new ScannerException("while scanning a " + name,"expected URI, but found " + ch + "(" + (ch) + ")",null);
1113         }
1114         return chunks;
1115     }
1116
1117     private function scanUriEscapes(name : String) : String {
1118         var bytes : String = new String();
1119         while(buffer.peek() == '%') {
1120             forward();
1121             try {
1122                 bytes += int(prefix(2)).toString(16);
1123             } catch(nfe : Error) {
1124                 throw new ScannerException("while scanning a " + name,"expected URI escape sequence of 2 hexadecimal numbers, but found " + buffer.peek(1) + "(" + (buffer.peek(1)) + ") and "+ buffer.peek(2) + "(" + (buffer.peek(2)) + ")",null);
1125             }
1126             forwardBy(2);
1127         }
1128         return bytes
1129     }
1130
1131     private function scanLineBreak() : String {
1132         // Transforms:
1133         //   '\r\n'      :   '\n'
1134         //   '\r'        :   '\n'
1135         //   '\n'        :   '\n'
1136         //   '\x85'      :   '\n'
1137         //   default     :   ''
1138         var val : String = buffer.peek();
1139         if(FULL_LINEBR.indexOf(val) != -1) {
1140             if(RN == (prefix(2))) {
1141                 forwardBy(2);
1142             } else {
1143                 forward(val);
1144             }
1145             return "\n";
1146         } else {
1147             return "";
1148         }
1149     }
1150
1151 }
1152 }
1153
1154 import org.idmedia.as3commons.util.Iterator;
1155 import org.as3yaml.Scanner;
1156         
1157 internal class TokenIterator implements Iterator {
1158         
1159         private var scanner : Scanner;
1160         
1161         public function TokenIterator(scanner : Scanner) : void
1162         {
1163                 this.scanner = scanner;
1164         }
1165         
1166     public function hasNext() : Boolean {
1167         return null != scanner.peekToken();
1168     }
1169
1170     public function next() : * {
1171         return scanner.getToken();
1172     }
1173
1174     public function remove() : void {
1175     }
1176 }