PageRenderTime 2189ms CodeModel.GetById 142ms app.highlight 1278ms RepoModel.GetById 759ms app.codeStats 1ms

/src/org/ubi/SourceReader.java

http://github.com/nddrylliog/ooc
Java | 1185 lines | 856 code | 92 blank | 237 comment | 73 complexity | 03e0ff8f01f44641aff703e27aded539 MD5 | raw file
   1package org.ubi;
   2
   3import java.io.EOFException;
   4import java.io.File;
   5import java.io.FileReader;
   6import java.io.IOException;
   7import java.util.ArrayList;
   8import java.util.List;
   9
  10/**
  11 * Utility class to read blocks of text.
  12 * Mostly useful for keeping track of line number/positions (for accurate
  13 * error messages, @see SyntaxError).
  14 * Has builtin methods for reading C-like elements/tokens, like
  15 * string literals/char literals, blocks, etc.
  16 * @author Amos Wenger
  17 */
  18public class SourceReader {
  19	
  20	/**
  21	 * The case sensibility setting, e.g. whether 'A' == 'a' or 'A' != 'a'
  22	 * @author Amos Wenger
  23	 */
  24    public enum CaseSensibility {
  25    	/** Don't make a difference between capitalized characters and others, e.g. 'A' != 'a' */
  26        SENSITIVE,
  27        /** Distinguish between capitalized characters and others, e.g. 'A' == 'a' */
  28        INSENSITIVE
  29    }
  30
  31    protected ArrayList<Integer> newlineIndices;
  32    protected String fileName;
  33    protected char[] content;
  34    protected int index;
  35    protected int mark;
  36
  37    /**
  38     * Read the content of a the file at place "path"
  39     * @param path The path of the file to be read
  40     * @return a SourceReader reading from the file content
  41     * @throws java.io.IOException if file can't be found or opened for reading
  42     * (or any other I/O exception, for that matter).
  43     */
  44    public static SourceReader getReaderFromPath(String path) throws IOException {
  45        return getReaderFromFile(new File(path));
  46    }
  47
  48    /**
  49     * Read the content of a the file pointed by "file"
  50     * @param file The file object from which to read
  51     * @return a SourceReader reading from the file content
  52     * @throws java.io.IOException if file can't be found or opened for reading
  53     * (or any other I/O exception, for that matter).
  54     */
  55    public static SourceReader getReaderFromFile(File file) throws IOException {
  56        return new SourceReader(file.getPath(), readToString(file));
  57    }
  58    
  59    /**
  60     * Read the content of a string
  61     * @param path The path this string came from. Can be an URL, a file path, etc.
  62     * anything descriptive, really, even "<system>" or "<copy-protected>" ^^
  63     * @param content
  64     * @return
  65     */
  66    public static SourceReader getReaderFromText(String path, String content) {
  67		return new SourceReader(path, content);
  68	}
  69    
  70    /**
  71     * Read the content of a the file pointed by "file"
  72     * @param file The file object from which to read
  73     * @return a SourceReader reading from the file content
  74     * @throws java.io.IOException if file can't be found or opened for reading
  75     * (or any other I/O exception, for that matter).
  76     */
  77    public static String readToString(File file) throws IOException {
  78        char[] buffer = new char[8192];
  79        FileReader fR = new FileReader(file);
  80        StringBuilder content = new StringBuilder((int) file.length());
  81        int length;
  82        while((length = fR.read(buffer)) != -1) {
  83            content.append(buffer, 0, length);
  84        }
  85        fR.close();
  86        return content.toString();
  87    }
  88
  89
  90    /**
  91     * Create a new SourceReader
  92     * @param filePath The filepath is used in locations, for accurate
  93     * error messages @see SyntaxError
  94     * @param content The content to read from.
  95     */
  96    protected SourceReader(String filePath, String content) {
  97        this.fileName = filePath;
  98        this.content = content.toCharArray();
  99        this.index = 0;
 100        this.mark = 0;
 101        this.newlineIndices = new ArrayList<Integer> ();
 102    }
 103
 104    /**
 105     * Read one character from the source at current position.
 106     * @return The character read
 107     * @throws EOFException When the end of the file is reached.
 108     */
 109    public char read() throws EOFException {
 110        if(index + 1 > content.length) {
 111            throw new EOFException("Parsing ended. Parsed"+index
 112            		+" chars, "+getLineNumber()+" lines total.");
 113        }
 114        char character = content[index++];
 115        if(character == '\n') {
 116            if(newlineIndices.isEmpty() || newlineIndices.get(newlineIndices.size() - 1).intValue() < index) {
 117                newlineIndices.add(new Integer(index));
 118            }
 119        }
 120        return character;
 121    }
 122    
 123    /**
 124     * Read one character from the source at current position, without advancing
 125     * the pointer
 126     * @return The character read
 127     * @throws EOFException When the end of the file is reached.
 128     */
 129    public char peek() {
 130
 131    	return content[index];
 132    	
 133	}
 134
 135
 136    /**
 137     * Save the current position, allowing it to be restored later with the reset()
 138     *
 139     * <i>Note : functions from SourceReader may call mark(), thus overwriting the saved
 140     * position. If you want to be safe, assign the return value from mark() to an int,
 141     * which you can later pass to reset(int).</i>
 142     *
 143     * Example :
 144     * <code>
 145     * int mark = sourceReader.mark();
 146     * sourceReader.readUntil(...);
 147     * sourceReader.reset(mark);
 148     * </code>
 149     *
 150     * @return The current position
 151     */
 152    public int mark() {
 153        this.mark = index;
 154        return mark;
 155    }
 156
 157    /**
 158     * Restore position to the last saved with mark()
 159     */
 160    public void reset() {
 161        this.index = this.mark;
 162    }
 163
 164    /**
 165     * Restore position to the given one
 166     * @param index The position to jump to
 167     */
 168    public void reset(int index) {
 169        this.index = index;
 170    }
 171
 172    /**
 173     * Rewind position from given offset.
 174     * (Subtracts offset from index)
 175     * @param index The position to jump to
 176     */
 177    public void rewind(int offset) {
 178        this.index -= offset;
 179    }
 180    
 181    /**
 182     * Advance position from given offset.
 183     * @param offset
 184     * @throws EOFException 
 185     */
 186    public void skip(int offset) throws EOFException {
 187    	if(offset < 0) {
 188    		rewind(-offset);
 189    	} else {
 190    		for(int i = 0; i < offset; i++) {
 191    			read();
 192    		}
 193    	}
 194	}
 195
 196    /**
 197     * @return the current line number
 198     */
 199    public int getLineNumber() {
 200        int lineNumber = 0;
 201        while(lineNumber < newlineIndices.size() && newlineIndices.get(lineNumber).intValue() <= index) {
 202            lineNumber++;
 203        }
 204        return lineNumber + 1;
 205    }
 206
 207    /**
 208     * @return the current position in line (e.g. number of characters since the last newline
 209     */
 210    public int getLinePos() {
 211        int lineNumber = getLineNumber();
 212        if(lineNumber == 1) {
 213            return index + 1;
 214        }
 215		return index - newlineIndices.get(getLineNumber() - 2).intValue() + 1;
 216    }
 217
 218    /**
 219     * @return false if positioned at the end of the content.
 220     */
 221    public boolean hasNext() {
 222        return (index < content.length);
 223    }
 224
 225    public String getFileName() {
 226        return fileName;
 227    }
 228
 229    /**
 230     * @return the current file location, containing the file number, line position, and index
 231     */
 232    public FileLocation getLocation() {
 233        return new FileLocation(fileName, getLineNumber(), getLinePos(), index);
 234    }
 235    
 236    public FileLocation getLocation(Locatable loc) {
 237    	return getLocation(loc.getStart(), loc.getLength());
 238    }
 239    
 240    public FileLocation getLocation(int start, int length) {
 241    	int mark = mark();
 242    	reset(0);
 243    	try {
 244    		skip(start);
 245    	} catch(EOFException e) {}
 246    	FileLocation loc = getLocation();
 247    	loc.length = length;
 248    	reset(mark);
 249		return loc;
 250	}
 251
 252    /**
 253     * @param character
 254     * @return true if the last-but-one char equals 'character'.
 255     */
 256    public boolean backMatches(char character, boolean trueIfStartpos) {
 257        if(index <= 0) {
 258            return trueIfStartpos;
 259        }
 260		return content[index - 1] == character;
 261    }
 262
 263    /**
 264     * Test if each candidate in "candidates" matches the next characters in the content.
 265     * @param candidates
 266     * @param keepEnd If false, will reset to the initial position before returning.
 267     * If true, will stay after the matched candidate.
 268     * @return -1 if no candidate matched. Otherwise, the index of the first matching candidate.
 269     * @throws java.io.EOFException
 270     * @throws java.io.IOException
 271     */
 272    public int matches(List<String> candidates, boolean keepEnd) throws EOFException {
 273        int match = -1;
 274        int count = 0;
 275        search: for(String candidate: candidates) {
 276            if(matches(candidate, keepEnd, CaseSensibility.SENSITIVE)) {
 277                match = count;
 278                break search;
 279            }
 280            ++count;
 281        }
 282        return match;
 283    }
 284    
 285    /**
 286     * Test if a "candidate" matches the next character in the content, and if there's
 287     * whitespace after it.
 288     * @param candidate
 289     * @param keepEnd
 290     * @return
 291     * @throws EOFException
 292     */
 293    public boolean matchesSpaced(String candidate, boolean keepEnd) throws EOFException {
 294    	int mark = mark();
 295    	boolean result = matches(candidate, true) && hasWhitespace(false);
 296    	if(!keepEnd) {
 297    		reset(mark);
 298    	}
 299    	return result;
 300    }
 301    
 302    /**
 303     * Test if a "candidate" matches the next character in the content, and if there's
 304     * characters other than "A-Za-z0-9_" after iti
 305     * @param candidate
 306     * @param keepEnd
 307     * @return
 308     * @throws EOFException
 309     */
 310    public boolean matchesNonident(String candidate, boolean keepEnd) throws EOFException {
 311    	int mark = mark();
 312    	boolean result = matches(candidate, true);
 313    	char c = peek();
 314    	result &= !((c == '_') || Character.isLetterOrDigit(c));
 315    	if(!keepEnd) {
 316    		reset(mark);
 317    	}
 318    	return result;
 319    }
 320
 321    /**
 322     * Test if a "candidate" matches the next characters in the content.
 323     * It is case-sensitive by default
 324     * @param candidate
 325     * @param keepEnd If false, will reset to the initial position before returning.
 326     * If true, will stay after the matched candidate.
 327     * @return true if the candidate matches, false otherwise.
 328     * 
 329     */
 330    public boolean matches(String candidate, boolean keepEnd) throws EOFException {
 331        return matches(candidate, keepEnd, CaseSensibility.SENSITIVE);
 332    }
 333
 334    /**
 335     * Test if a "candidate" matches the next characters in the content.
 336     * @param candidate
 337     * @param keepEnd If false, will reset to the initial position before returning.
 338     * If true, will stay after the matched candidate.
 339     * @param caseMode either Case.SENSITIVE or Case.INSENSITIVE
 340     * @return true if the candidate matches, false otherwise.
 341     */
 342    public boolean matches(String candidate, boolean keepEnd, CaseSensibility caseMode) throws EOFException {
 343
 344        mark();
 345        int i = 0;
 346        char c, c2;
 347        boolean result = true;
 348        while(i < candidate.length()) {
 349            c = read();
 350            c2 = candidate.charAt(i);
 351            if(c2 != c) {
 352                if((caseMode == CaseSensibility.SENSITIVE) || (Character.toLowerCase(c2) != Character.toLowerCase(c))) {
 353                    result = false;
 354                    break;
 355                }
 356            }
 357            i++;
 358        }
 359        if(!result || !keepEnd) {
 360            reset();
 361        }
 362
 363        return result;
 364
 365    }
 366
 367    /**
 368     * Read a C-style name (a string containing [A-Za-z0-9_] characters) and return it.
 369     * @return the read name
 370     */
 371    public boolean skipName() throws EOFException {
 372
 373        if(hasNext()) {
 374            char chr = read();
 375            if(!Character.isLetter(chr) && chr != '_') { 
 376                rewind(1);
 377                return false;
 378            }
 379        }
 380
 381        read : while(hasNext()) {
 382            char chr = read();
 383            if(!Character.isLetterOrDigit(chr) && chr != '_' && chr != '!') {
 384            	rewind(1);
 385                break read;
 386            }
 387        }
 388
 389        return true;
 390
 391    }
 392    
 393    /**
 394     * Read a C-style name (a string containing [A-Za-z0-9_] characters) and return it.
 395     * @return the read name
 396     */
 397    public String readName() throws EOFException {
 398
 399        StringBuilder sB = new StringBuilder();
 400
 401        mark();
 402        if(hasNext()) {
 403            char chr = read();
 404            if(Character.isLetter(chr) || chr == '_') {
 405                sB.append(chr);
 406            } else {
 407            	rewind(1);
 408                return "";
 409            }
 410        }
 411
 412        read : while(hasNext()) {
 413            mark();
 414            char chr = read();
 415            if(Character.isLetterOrDigit(chr) || chr == '_' || chr == '!') {
 416                sB.append(chr);
 417            } else {
 418            	rewind(1);
 419                break read;
 420            }
 421        }
 422
 423        return sB.toString();
 424
 425    }
 426
 427    /**
 428     * Read until a newline character and return the read input
 429     * @return the read input
 430     */
 431    public String readLine() throws EOFException {
 432
 433        return readUntil('\n', true);
 434
 435    }
 436
 437    /**
 438     * Read a C-style single-line comment (ignore a line).
 439     * C-style single-line comments are prefixed by "//"
 440     */
 441    public void readSingleComment() throws EOFException {
 442        readLine();
 443    }
 444
 445    /**
 446     * Read a C-style multi-line comment (ignore until "*\/").
 447     * C-style multi-line comments are prefixed by "/*" and "*\/"
 448     */
 449    public void readMultiComment() throws EOFException {
 450        while(!matches("*/", true, CaseSensibility.SENSITIVE)) { read(); }
 451    }
 452    
 453    /**
 454     * Read as many times candidates as we can ! Ignoring any char
 455     * in 'ignored'.
 456     * @param candidates
 457     * @param ignored
 458     * @param keepEnd
 459     * @return
 460     */
 461    public String readMany(String candidates, String ignored, boolean keepEnd) throws EOFException {
 462
 463        StringBuilder sB = new StringBuilder();
 464
 465        int myMark = mark();
 466        while(hasNext()) {
 467            char c = read();
 468            if(candidates.indexOf(c) != -1) {
 469                sB.append(c);
 470            } else if(ignored.indexOf(c) != -1) {
 471                // look up in the sky, and think of how lucky you are and others aren't.
 472            } else {
 473            	if(keepEnd) {
 474            		rewind(1); // We went one too far.
 475            	}
 476                break;
 477            }
 478        }
 479
 480        if(!keepEnd) {
 481            reset(myMark);
 482        }
 483
 484        return sB.toString();
 485
 486    }
 487
 488    /**
 489     * Read a C-style character literal, e.g. any character or an escape sequence,
 490     * and return it as a char.
 491     */
 492    @SuppressWarnings("fallthrough")
 493	public char readCharLiteral() throws EOFException, SyntaxError {
 494
 495        mark();
 496        char c = read();
 497        switch(c) {
 498            case '\'':
 499                throw new SyntaxError(getLocation(), "Empty char literal !");
 500            case '\\':
 501                char c2 = read();
 502                switch(c2) {
 503                    case '\\': // backslash
 504                        c = '\\'; break;
 505                    case '0': // null char
 506                        c = '\0'; break;
 507                    case 'n': // newline
 508                        c = '\n'; break;
 509                    case 't': // tab
 510                        c = '\t'; break;
 511                    case 'v': // vertical tab
 512                        c = '\013'; break;
 513                    case 'b': // backspace
 514                        c = '\b'; break;
 515                    case 'f': // form feed
 516                        c = '\f'; break;
 517                    case 'r': // carriage return
 518                        c = '\r'; break;
 519                    case '\'': // simple quote
 520                        c = '\''; break;
 521                    default:
 522                    	throw new SyntaxError(getLocation(), "Invalid escape sequence : "+spelled(c));
 523                }
 524            // intentional fallthrough
 525            default:
 526                c2 = read();
 527                if(c2 != '\'') {
 528                	throw new SyntaxError(getLocation(), "Char literal too long. Expected ', found "+spelled(c2));
 529                }
 530                return c;
 531        }
 532        
 533    }
 534    
 535    /**
 536     * Parse a C-style character literal from an input string, e.g. any character
 537     * or an escape sequence, and return it as a char.
 538     */
 539    @SuppressWarnings("fallthrough")
 540	public static char parseCharLiteral(String input) throws SyntaxError {
 541    	
 542        char c = input.charAt(0);
 543        int supposedLength = 1;
 544        switch(c) {
 545            case '\'':
 546                throw new SyntaxError(null, "Empty char literal !");
 547            case '\\':
 548            	supposedLength++;
 549                char c2 = input.charAt(1);
 550                switch(c2) {
 551                    case '\\': // backslash
 552                        c = '\\'; break;
 553                    case '0': // null char
 554                        c = '\0'; break;
 555                    case 'n': // newline
 556                        c = '\n'; break;
 557                    case 't': // tab
 558                        c = '\t'; break;
 559                    case 'v': // vertical tab
 560                        c = '\013'; break;
 561                    case 'b': // backspace
 562                        c = '\b'; break;
 563                    case 'f': // form feed
 564                        c = '\f'; break;
 565                    case 'r': // carriage return
 566                        c = '\r'; break;
 567                    case '\'': // simple quote
 568                        c = '\''; break;
 569                    default:
 570                    	throw new SyntaxError(null, "Invalid escape sequence : "+spelled(c));
 571                }
 572            // intentional fallthrough
 573            default:
 574                if(input.length() > supposedLength) {
 575                	throw new SyntaxError(null, "Char literal too long.");
 576                }
 577                return c;
 578        }
 579    	
 580	}
 581    
 582    public static String parseStringLiteral(String string) {
 583
 584    	int index = 0;
 585    	StringBuilder buffer = new StringBuilder();
 586        char c;
 587        while(index < string.length()) {
 588            c = string.charAt(index++);
 589            switch(c) {
 590                case '\\':
 591                    char c2 = string.charAt(index++);
 592                    switch(c2) {
 593                        case '\\': // backslash
 594                            buffer.append('\\'); break;
 595                        case '0': // null char
 596                            buffer.append('\0'); break;
 597                        case 'n': // newline
 598                            buffer.append('\n'); break;
 599                        case 't': // tab
 600                            buffer.append('\t'); break;
 601                        case 'b': // backspace
 602                            buffer.append('\b'); break;
 603                        case 'f': // form feed
 604                            buffer.append('\f'); break;
 605                        case 'r': // return
 606                            buffer.append('\r'); break;
 607                        default: // delimiter
 608                            buffer.append(c2); break;
 609                    }
 610                    break;
 611                default:
 612                	buffer.append(c);
 613            }
 614        }
 615
 616        return buffer.toString();
 617    	
 618	}
 619
 620    /**
 621     * Read a C-like string literal, e.g. enclosed by '"', and with C-like escape sequences,
 622     * and return it.
 623     * Note: eats the final '"', no need to skip it.
 624     */
 625    public String readStringLiteral() throws EOFException {
 626        return readStringLiteral('"');
 627    }
 628
 629    /**
 630     * Read a string literal, e.g. enclosed by "delimiter", and with C-like escape sequences,
 631     * and return it.
 632     * Note: eats the final '"', no need to skip it.
 633     * @param delimiter The delimitr, e.g. " (C-like), or ' (e.g. Python-like)
 634     */
 635    public String readStringLiteral(char delimiter) throws EOFException {
 636
 637        StringBuilder buffer = new StringBuilder();
 638        char c;
 639        reading : while(true) {
 640            mark();
 641            c = read();
 642            switch(c) {
 643                case '\\':
 644                    char c2 = read();
 645                    switch(c2) {
 646                        case '\\': // backslash
 647                            buffer.append('\\'); break;
 648                        case '0': // null char
 649                            buffer.append('\0'); break;
 650                        case 'n': // newline
 651                            buffer.append('\n'); break;
 652                        case 't': // tab
 653                            buffer.append('\t'); break;
 654                        case 'b': // backspace
 655                            buffer.append('\b'); break;
 656                        case 'f': // form feed
 657                            buffer.append('\f'); break;
 658                        case 'r': // return
 659                            buffer.append('\r'); break;
 660                        default: // delimiter
 661                            if(c2 == delimiter) { // freakin' java switches. *growl*
 662                                buffer.append(delimiter);
 663                            } break;
 664                    }
 665                    break;
 666                default: // TODO : wonder if newline is a syntax error in a string literal
 667                	if(c == delimiter) {
 668                		break reading;
 669                	}
 670                    buffer.append(c);
 671            }
 672        }
 673
 674        return buffer.toString();
 675
 676    }
 677
 678    /**
 679     * Return true if there's any whitespace after the current position.
 680     * @param keep If true, will have the same effect as skipWhitespace
 681     * If false, the position will be left unchanged.
 682     * @return true if there was any whitespace.
 683     * @throws java.io.IOException Go look in the closet, 3rd door left.
 684     */
 685    public boolean hasWhitespace(boolean skip) throws EOFException {
 686
 687        boolean has = false;
 688        int myMark = mark();
 689        while(hasNext()) {
 690            int c = read();
 691            if(Character.isWhitespace(c)) {
 692            	has = true;
 693            } else {
 694            	rewind(1);
 695            	break;
 696            }
 697        }
 698
 699        if(!skip) {
 700            reset(myMark);
 701        }
 702
 703        return has;
 704
 705    }
 706
 707    /**
 708     * Ignore the next characters which are whitespace (e.g. spaces, tabulations,
 709     * newlines, linefeeds, ie. anything for which Character.isWhitespace(int) is true.
 710     * @throws java.io.IOException
 711     */
 712    public boolean skipWhitespace() throws EOFException {
 713
 714        while(hasNext()) {
 715            int myMark = mark();
 716            int c = read();
 717            if(!Character.isWhitespace(c)) {
 718                reset(myMark);
 719                break;
 720            }
 721        }
 722        return true;
 723
 724    }
 725    
 726    /**
 727     * Ignore the next characters which are contained in the string 'chars'
 728     * @throws java.io.IOException
 729     */
 730    public boolean skipChars(String chars) throws EOFException {
 731
 732        while(hasNext()) {
 733            int myMark = mark();
 734            int c = read();
 735            if(chars.indexOf(c) == -1) {
 736                reset(myMark);
 737                break;
 738            }
 739        }
 740        return true;
 741
 742    }
 743
 744    /**
 745     * Skip the next characters until a newline.
 746     * @throws java.io.EOFException
 747     */
 748    public void skipLine() throws EOFException {
 749        while(read() != '\n') {
 750        	// Go on with the loop, don't look back.
 751        }
 752    }
 753
 754    /**
 755     * Read until the character "chr", and return the characters read.
 756     * Example:
 757     * <code>
 758     * String myLine = sourceReader.readUntil(';');
 759     * </code>
 760     * @param chr The end delimiter.
 761     * @throws java.io.EOFException
 762     */
 763    public String readUntil(char chr) throws EOFException {
 764        return readUntil(chr, false);
 765    }
 766
 767    /**
 768     * Read until the character "chr", and return the characters read.
 769     * @param chr The end delimiter.
 770     * @param keepEnd If false, leave the position before the end delimiter.
 771     * If true, include the delimiter in the returned String, and leave the
 772     * position after.
 773     * @throws java.io.EOFException
 774     */
 775    public String readUntil(char chr, boolean keepEnd) throws EOFException {
 776
 777        StringBuilder sB = new StringBuilder();
 778
 779        char chrRead = 0;
 780        while(hasNext() && (chrRead = read()) != chr) {
 781            sB.append(chrRead);
 782        }
 783        if(!keepEnd) {
 784            reset(index - 1); // chop off the last character
 785        } else if(chrRead != 0) {
 786            sB.append(chr);
 787        }
 788        
 789        return sB.toString();
 790
 791    }
 792
 793    /**
 794     * Read until one of the Strings in "matches" matches, and return the characters read.
 795     * By default, do not include the matching end delimiter in the resulting String, and leave
 796     * the position before the matching end delimiter.
 797     * @param readUntil The potential end delimiters
 798     * @throws java.io.EOFException
 799     */
 800    public String readUntil(String[] matches) throws EOFException {
 801        return readUntil(matches, false);
 802    }
 803
 804    /**
 805     * Read until one of the Strings in "matches" matches, and return the characters read.
 806     * @param readUntil The potential end delimiters
 807     * @param keepEnd If false, leave the position before the matching end delimiter.
 808     * If true, include the matching delimiter in the returned String, and leave the
 809     * position after.
 810     * @throws java.io.EOFException
 811     */
 812    public String readUntil(String[] matches, boolean keepEnd) throws EOFException {
 813
 814        StringBuilder sB = new StringBuilder();
 815        
 816        try { while(hasNext()) {
 817            for(String match: matches) {
 818                if(matches(match, keepEnd, CaseSensibility.SENSITIVE)) {
 819                    if(keepEnd) {
 820                        sB.append(match);
 821                    }
 822                    return sB.toString();
 823                }
 824            }
 825            sB.append(read());
 826        } } catch(EOFException e) {
 827        	// Normal operation.
 828        }
 829
 830        return sB.toString();
 831
 832    }
 833
 834    /**
 835     * Read until the end of file, and return the result.
 836     */
 837    public String readUntilEOF() {
 838
 839        StringBuilder output = new StringBuilder();
 840        
 841        try { while(hasNext()) {
 842           output.append(read());
 843        } } catch(EOFException e) {
 844        	// Well, that's the point
 845        }
 846
 847        return output.toString();
 848
 849    }
 850
 851    /**
 852     * Read a block delimited by "start" and "end". It deals with nested blocks, e.g.
 853     * with '{' and '}', it will match '{{}}' in one piece.
 854     * Note : the final end delimiter is eaten, No need to skip it.
 855     * @param startChr the start delimiter
 856     * @param endChr the end delimiter
 857     * @return the content of the block
 858     * @throws org.ubi.SyntaxError
 859     * @throws java.io.IOException
 860     */
 861    public String readBlock(char startChr, char endChr) throws SyntaxError, EOFException {
 862        return readBlock(startChr, endChr, '\0');
 863    }
 864
 865    /**
 866     * Read a block delimited by "start" and "end" delimiters. It deals with nested blocks, e.g.
 867     * with '{' and '}', it will match '{{}}' in one piece.
 868     * The escape character (escapeChar) allows to include the endDelimiter in the block,
 869     * e.g. with '"' and '"' delimiters, and '\\' escapeChar, there can be escape sequence in
 870     * what looks obviously like a String literal.
 871     * Note : the final end delimiter is eaten, No need to skip it.
 872     * @param startChr the start delimiter
 873     * @param endChr the end delimiter
 874     * @return the content of the block
 875     * @throws org.ubi.SyntaxError
 876     * @throws java.io.IOException
 877     */
 878    public String readBlock(char startChr, char endChr, char escapeChar) throws SyntaxError, EOFException {
 879
 880        skipWhitespace();
 881        mark();
 882        char c;
 883        if((c = read()) != startChr) {
 884            reset();
 885            throw new SyntaxError(getLocation(), "Trying to read block delimited by "
 886            		+spelled(startChr)+spelled(endChr)
 887            		+", but "+spelled(c)+" found instead.");
 888        }
 889
 890        StringBuilder output = new StringBuilder();
 891
 892        int count = 1;
 893        char chr;
 894
 895        try { reading: while(true) {
 896            chr = read();
 897            if(chr == escapeChar) {
 898                output.append(chr);
 899                chr = read();
 900            }
 901
 902            if(chr == endChr) {
 903                if(--count <= 0) {
 904                    break reading;
 905                }
 906            } else if(chr == startChr) {
 907                ++count;
 908            }
 909            output.append(chr);
 910        } } catch(EOFException e) {
 911        	// Normal operation
 912        }
 913
 914        return output.toString();
 915
 916    }
 917
 918    /**
 919     * Read a block delimited by "start" and "end" delimiters. It deals with nested blocks, e.g.
 920     * with '{' and '}', it will match '{{}}' in one piece.
 921     * The escape character (escapeChar) allows to include the endDelimiter in the block,
 922     * e.g. with '"' and '"' delimiters, and '\\' escapeChar, there can be escape sequence in
 923     * what looks obviously like a String literal.
 924     * Note : the final end delimiter is eaten, No need to skip it.
 925     * @param start the start delimiter
 926     * @param end the end delimiter
 927     * @return the content of the block
 928     * @throws org.ubi.SyntaxError
 929     * @throws java.io.IOException
 930     */
 931    public String readBlock(String start, String end, char escapeChar) throws SyntaxError, EOFException {
 932
 933        skipWhitespace();
 934        mark();
 935        if(!matches(start, true)) {
 936            char c = read();
 937            reset();
 938            throw new SyntaxError(getLocation(), "Trying to read block delimited by "
 939            		+spelled(start)+spelled(end)+", but "+spelled(c)+" found instead.");
 940        }
 941
 942        StringBuilder output = new StringBuilder();
 943
 944        int count = 1;
 945        char chr;
 946
 947        try { reading: while(true) {
 948
 949            if(matches(end, true)) {
 950                if(--count <= 0) {
 951                    break reading;
 952                }
 953            } else if(matches(start, true)) {
 954                ++count;
 955            } else {
 956                chr = read();
 957                if(chr == escapeChar) {
 958                    output.append(chr);
 959                    chr = read();
 960                }
 961                output.append(chr);
 962            }
 963
 964        } } catch(EOFException e) {
 965        	// Normal operation
 966        }
 967
 968        return output.toString();
 969
 970    }
 971
 972    /**
 973     * Throws a SyntaxError with the current location
 974     * @param string
 975     */
 976    public void err(String msg) throws SyntaxError {
 977        throw new SyntaxError(getLocation(), msg);
 978    }
 979
 980    /**
 981     * Return a String representation of a character, with spelled
 982     * out representations of newlines, tabs, etc.
 983     * Example: spelled(32) = " ";
 984     * Example: spelled('\n') = "\\n";
 985     */
 986    public static String spelled(char character) {
 987        switch(character) {
 988        	case '\"':
 989        		return "\\\"";
 990            case '\t':
 991                return "\\t";
 992            case '\f':
 993            	return "\\f";
 994            case '\013':
 995            	return "\\v";
 996            case '\r':
 997                return "\\r";
 998            case '\n':
 999                return "\\n";
1000            case '\0':
1001                return "\\0";
1002            case '\'':
1003            	return "\\'";
1004            case '\\':
1005            	return "\\\\";
1006            default:
1007                return Character.toString(character);
1008        }
1009    }
1010    
1011    public static void spelled(char character, Appendable output) throws IOException {
1012        switch(character) {
1013        	case '\"':
1014        		output.append("\""); return;
1015            case '\t':
1016            	output.append("\\t"); return;
1017            case '\f':
1018            	output.append("\\f"); return;
1019            case '\b':
1020            	output.append("\\b"); return;
1021            case '\013':
1022            	output.append("\\v"); return;
1023            case '\r':
1024            	output.append("\\r"); return;
1025            case '\n':
1026            	output.append("\\n"); return;
1027            case '\0':
1028            	output.append("\\0"); return;
1029            case '\'':
1030            	output.append("\\'"); return;
1031            case '\\':
1032            	output.append("\\"); return;
1033            default:
1034            	output.append(character); return;
1035        }
1036    }
1037
1038    /**
1039     * Return a String representation of a String, with spelled
1040     * out representations of newlines, tabs, etc.
1041     * Example: spelled(32) = " ";
1042     * Example: spelled('\n') = "\\n";
1043     */
1044    public static String spelled(String str) {
1045
1046		StringBuilder output = new StringBuilder();
1047		try {
1048			spelled(str, output);
1049		} catch (IOException e) {
1050			throw new Error(e);
1051		}
1052		return output.toString();
1053
1054    }
1055
1056    public static void spelled(String str, Appendable output) throws IOException {
1057    	
1058    	spelled(str, output, false);
1059    	
1060    }
1061    
1062    
1063	public static void spelled(String str, Appendable output, boolean doBackslashes) throws IOException {
1064		
1065		int length = str.length();
1066		for(int i = 0; i < length; i++) {
1067			char c = str.charAt(i);
1068			if(doBackslashes && c == '\\') {
1069				output.append("\\\\");
1070			} else {
1071				spelled(c, output); 
1072			}
1073        }
1074		
1075	}
1076
1077    /**
1078     * Return the String containing the whole content this SourceReader is reading from.
1079     */
1080    public char[] getContent() {
1081    	
1082        return content;
1083        
1084    }
1085
1086    /**
1087     * Put the current index in token.start and return true.
1088     * Intended to be used like this:
1089     * <code>
1090     * static Token token = new Token();
1091     * void parse(SourceReader read) {
1092     *   if(reader.startToken(token) && reader.matches("myKeyword", true) && reader.endToken(token)) {
1093     * 	   // Add a copy of the Token to the token list.
1094     *   }
1095     * }
1096     * </code>
1097     * @param token
1098     * @return
1099     */
1100    public boolean startToken(Token token) {
1101
1102    	token.start = index;
1103    	return true;
1104    	
1105    }
1106    
1107    
1108    /**
1109     * Put the current index in token.end and return true.
1110     * Intended to be used like this:
1111     * <code>
1112     * static Token token = new Token();
1113     * void parse(SourceReader read) {
1114     *   if(reader.startToken(token) && reader.matches("myKeyword", true) && reader.endToken(token)) {
1115     * 	   // Add a copy of the Token to the token list.
1116     *   }
1117     * }
1118     * </code>
1119     * @param token
1120     * @return
1121     */
1122    public boolean endToken(Token token) {
1123 
1124    	token.length = index - token.start;
1125    	return true;
1126    	
1127    }
1128
1129	@SuppressWarnings("boxing")
1130	public String getLine(int lineNumber) throws IOException {
1131
1132		int mark = mark();
1133		if(newlineIndices.size() > lineNumber) {
1134			reset(newlineIndices.get(lineNumber));
1135		} else {
1136			reset(0);
1137			for(int i = 1; i < lineNumber; i++) {
1138				readLine();
1139			}
1140		}
1141		
1142		String line = readLine();
1143		reset(mark);
1144		return line;
1145		
1146	}
1147
1148	/**
1149	 * Get a slice of the source, specifying the start position
1150	 * and the length of the slice.
1151	 * @param start
1152	 * @param length
1153	 * @return
1154	 */
1155	public String getSlice(int start, int length) {
1156
1157		return new String(content, start, length);
1158		
1159	}
1160
1161	/**
1162	 * Reads an exponent, such as in a number literal (for example: 8E5 or 1.5e+24).
1163	 * If no exponent is read, the position is reset
1164	 * @return true if an exponent could be read, otherwise false
1165	 */
1166	public boolean readExponent() throws IOException {
1167		if (peek() == 'e' || peek() == 'E') {
1168			int expMark = mark();
1169			skip(1);
1170			
1171			if (peek() == '+' || peek() == '-') {
1172				skip(1);
1173			}
1174			
1175			if (Character.isDigit(peek())) {
1176				skip(1);
1177				readMany("0123456789", "_", true);
1178				return true;
1179			}
1180			reset(expMark);
1181		}
1182		return false;
1183	}
1184
1185}