/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * Free SoftwareFoundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.es.parser; import com.caucho.es.*; import com.caucho.java.LineMap; import com.caucho.util.CharBuffer; import com.caucho.util.L10N; import com.caucho.vfs.ReadStream; import java.io.CharConversionException; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.regex.Pattern; /** * JavaScript lexer. */ class Lexer { private static final L10N L = new L10N(Lexer.class); final static int ERROR = -3; final static int START = -2; final static int EOF = -1; final static int RESERVED = 256; final static int LITERAL = RESERVED + 1; final static int REGEXP = LITERAL + 1; final static int IDENTIFIER = REGEXP + 1; final static int THIS = IDENTIFIER + 1; final static int HASH_DEF = THIS + 1; final static int HASH_REF = HASH_DEF + 1; final static int BIN_OP = HASH_REF + 1; final static int UNARY_OP = BIN_OP + 1; final static int BANDU_OP = UNARY_OP + 1; final static int RSHIFT = BANDU_OP + 1; final static int URSHIFT = RSHIFT + 1; final static int LSHIFT = URSHIFT + 1; final static int BITAND = LSHIFT + 1; final static int BITOR = BITAND + 1; final static int GEQ = BITOR + 1; final static int LEQ = GEQ + 1; final static int EQ = LEQ + 1; final static int NEQ = EQ + 1; final static int STRICT_EQ = NEQ + 1; final static int STRICT_NEQ = STRICT_EQ + 1; final static int AND = STRICT_NEQ + 1; final static int OR = AND + 1; final static int ASSIGN_OP = OR + 1; final static int PREFIX = ASSIGN_OP + 1; final static int POSTFIX = PREFIX + 1; final static int DELETE = POSTFIX + 1; final static int VOID = DELETE + 1; final static int TYPEOF = VOID + 1; final static int IF = TYPEOF + 1; final static int ELSE = IF + 1; final static int SWITCH = ELSE + 1; final static int CASE = SWITCH + 1; final static int DEFAULT = CASE + 1; final static int WHILE = DEFAULT + 1; final static int DO = WHILE + 1; final static int FOR = DO + 1; final static int IN = FOR + 1; final static int BREAK = IN + 1; final static int CONTINUE = BREAK + 1; final static int FUNCTION = CONTINUE + 1; final static int CONSTRUCTOR = FUNCTION; final static int RETURN = CONSTRUCTOR + 1; final static int NEW = RETURN + 1; final static int VAR = NEW + 1; final static int WITH = VAR + 1; final static int NULL = WITH + 1; final static int UNDEFINED = NULL + 1; final static int TRUE = UNDEFINED + 1; final static int FALSE = TRUE + 1; final static int EVAL = FALSE + 1; final static int CLASS = EVAL + 1; final static int EXTENDS = CLASS + 1; final static int SYNCHRONIZED = EXTENDS + 1; final static int TRY = SYNCHRONIZED + 1; final static int CATCH = TRY + 1; final static int FINALLY = CATCH + 1; final static int THROW = FINALLY + 1; final static int IMPORT = THROW + 1; final static int STATIC = IMPORT + 1; final static int LAST_LEXEME = STATIC; static HashMap ops; static HashMap reserved; Global resin; ReadStream is; int peek = -1; int peek2 = -1; ArrayList macros = new ArrayList(); CharBuffer macroText; int macroIndex; int macroOldLine; int _flags; int state; int lbrace; int stringClose; boolean isRegexp; LineMap lineMap; String filename; String lastFilename; String beginFilename; int lastLine; int beginLine; int beginLineCh; int line; int lineCh; Op op; int lexeme; int lastLexeme; CharBuffer text; CharBuffer lineText = new CharBuffer(); boolean isEof = false; ESId id; ESBase literal; int intValue; boolean hasLf; boolean regexpOk; String writeln; CharBuffer temp = new CharBuffer(); Lexer(ReadStream is, String filename, int line, LineMap lineMap) { this.filename = filename; this.line = line; this.lastFilename = filename; this.lastLine = line; this.lineMap = lineMap; this.is = is; peek = -1; peek2 = -1; text = new CharBuffer(); lexeme = START; lastLexeme = START; regexpOk = true; macroText = null; macroIndex = 0; // Initialize the operator table if (ops == null) { ops = new HashMap(); opsPut(".", '.', '.', Parser.PREC_DOT, false); opsPut("++", '+', POSTFIX, Parser.PREC_DOT, false); opsPut("--", '-', POSTFIX, Parser.PREC_DOT, false); opsPut("@", '@', '@', Parser.PREC_DOT, false); opsPut("~", '~', UNARY_OP, Parser.PREC_UMINUS, false); opsPut("!", '!', UNARY_OP, Parser.PREC_UMINUS, false); opsPut("*", '*', BIN_OP, Parser.PREC_TIMES, false); opsPut("/", '/', BIN_OP, Parser.PREC_TIMES, false); opsPut("%", '%', BIN_OP, Parser.PREC_TIMES, false); opsPut("+", '+', BANDU_OP, Parser.PREC_PLUS, false); opsPut("-", '-', BANDU_OP, Parser.PREC_PLUS, false); opsPut(">>", RSHIFT, BIN_OP, Parser.PREC_SHIFT, false); opsPut(">>>", URSHIFT, BIN_OP, Parser.PREC_SHIFT, false); opsPut("<<", LSHIFT, BIN_OP, Parser.PREC_SHIFT, false); opsPut(">", '>', BIN_OP, Parser.PREC_CMP, false); opsPut(">=", GEQ, BIN_OP, Parser.PREC_CMP, false); opsPut("<", '<', BIN_OP, Parser.PREC_CMP, false); opsPut("<=", LEQ, BIN_OP, Parser.PREC_CMP, false); opsPut("==", EQ, BIN_OP, Parser.PREC_CMP, false); opsPut("!=", NEQ, BIN_OP, Parser.PREC_CMP, false); opsPut("===", STRICT_EQ, BIN_OP, Parser.PREC_CMP, false); opsPut("!==", STRICT_NEQ, BIN_OP, Parser.PREC_CMP, false); opsPut("&", '&', BIN_OP, Parser.PREC_BITAND, false); opsPut("^", '^', BIN_OP, Parser.PREC_BITXOR, false); opsPut("|", '|', BIN_OP, Parser.PREC_BITOR, false); opsPut("&&", AND, BIN_OP, Parser.PREC_AND, false); opsPut("||", OR, BIN_OP, Parser.PREC_OR, false); opsPut("?", '?', '?', Parser.PREC_COND, false); opsPut("=", '=', '=', Parser.PREC_ASSIGN, true); opsPut("*=", '*', '=', Parser.PREC_ASSIGN, true); opsPut("/=", '/', '=', Parser.PREC_ASSIGN, true); opsPut("%=", '%', '=', Parser.PREC_ASSIGN, true); opsPut("+=", '+', '=', Parser.PREC_ASSIGN, true); opsPut("-=", '-', '=', Parser.PREC_ASSIGN, true); opsPut(">>=", RSHIFT, '=', Parser.PREC_ASSIGN, true); opsPut(">>>=", URSHIFT, '=', Parser.PREC_ASSIGN, true); opsPut("<<=", LSHIFT, '=', Parser.PREC_ASSIGN, true); opsPut("&=", '&', '=', Parser.PREC_ASSIGN, true); opsPut("^=", '^', '=', Parser.PREC_ASSIGN, true); opsPut("|=", '|', '=', Parser.PREC_ASSIGN, true); opsPut(",", ',', ',', Parser.PREC_COMMA, false); reserved = new HashMap(); resPut("new", NEW); resPut("var", VAR); resPut("delete", DELETE); resPut("void", VOID); resPut("typeof", TYPEOF); resPut("if", IF); resPut("else", ELSE); resPut("switch", SWITCH); resPut("case", CASE); resPut("default", DEFAULT); resPut("while", WHILE); resPut("do", DO); resPut("for", FOR); resPut("in", IN); resPut("break", BREAK); resPut("continue", CONTINUE); resPut("null", NULL); resPut("undefined", UNDEFINED); resPut("true", TRUE); resPut("false", FALSE); resPut("this", THIS); resPut("eval", EVAL); resPut("function", FUNCTION); //resPut("constructor", CONSTRUCTOR); resPut("return", RETURN); resPut("with", WITH); resPut("class", CLASS); resPut("extends", EXTENDS); resPut("synchronized", SYNCHRONIZED); resPut("try", TRY); resPut("catch", CATCH); resPut("finally", FINALLY); resPut("throw", THROW); resPut("import", IMPORT); resPut("static", STATIC); resPut("const", RESERVED); resPut("debugger", RESERVED); resPut("enum", RESERVED); resPut("export", RESERVED); resPut("super", RESERVED); /* resPut("boolean", RESERVED); resPut("byte", RESERVED); resPut("char", RESERVED); resPut("double", RESERVED); resPut("float", RESERVED); resPut("int", RESERVED); resPut("long", RESERVED); resPut("short", RESERVED); */ resPut("public", RESERVED); resPut("private", RESERVED); resPut("protected", RESERVED); resPut("throws", RESERVED); } } Lexer(ReadStream is, String filename, int line) { this(is, filename, line, null); } Lexer(ReadStream is, LineMap lineMap) { this(is, null, 1, lineMap); } void setLineMap(LineMap lineMap) { this.lineMap = lineMap; } private void opsPut(String name, int code, int lex, int prec, boolean flag) { ops.put(new CharBuffer(name), new Op(code, lex, prec, flag)); } private void resPut(String name, int code) { reserved.put(new CharBuffer(name), new Integer(code)); } int peek() throws ESParseException { try { if (lexeme == START) { lexeme = lex(); } lastLexeme = lexeme; return lexeme; } catch (ESParseException e) { throw e; } catch (Exception e) { e.printStackTrace(); throw error(e.toString()); } } int next() throws ESParseException { try { int value = lexeme; if (value == START) { value = lex(); } lastLexeme = value; lexeme = START; lastFilename = beginFilename; lastLine = beginLine; return value; } catch (ESParseException e) { throw e; } catch (Exception e) { e.printStackTrace(); throw error(e == null ? "" : e.toString()); } } int prev() { if (lastLexeme == START) throw new RuntimeException(); lexeme = lastLexeme; lastLexeme = START; return lexeme; } int last() { if (lastLexeme == START) throw new RuntimeException(); return lastLexeme; } private int peekCh() throws ESParseException { try { int ch = read(); ungetc(ch); return (ch); } catch (Exception e) { return -1; } } /** * Returns the next lexeme */ private int lex() throws ESParseException { lastFilename = beginFilename; lastLine = beginLine; hasLf = false; while (true) { beginFilename = filename; beginLine = line; beginLineCh = lineCh; int ch = read(); switch (ch) { case -1: isEof = true; return EOF; case ' ': case '\t': case '\f': case 0x0b: /* vertical tab */ break; case '\n': newline(); hasLf = true; break; case '+': case '-': case '*': case '!': case ',': case '^': case '<': case '>': case '&': case '|': case '=': case '~': case '?': regexpOk = true; // exception ++/-- return lexOp(ch); case ')': case ']': regexpOk = false; return ch; case ':': case ';': case '(': case '[': case '{': case '}': regexpOk = true; return ch; case '.': { int ch2 = read(); if (ch2 >= '0' && ch2 <= '9') { regexpOk = false; return lexFloat(0, ch2); } else { regexpOk = true; ungetc(ch2); return lexOp(ch); } } case '/': { int ch2 = read(); if (ch2 == '/') { for (ch2 = read(); ch2 > 0 && ch2 != '\n'; ch2 = read()) { } ungetc(ch2); break; } else if (ch2 == '*') { boolean seenStar = false; for (ch2 = read(); ch2 > 0 && (! seenStar || ch2 != '/'); ch2 = read()) { if (ch2 == '/') { ch2 = read(); if (ch2 == '*') throw error(L.l("comments can't nest")); } seenStar = ch2 == '*'; if (ch2 == '\n') { newline(); hasLf = true; } } break; } else if (regexpOk) { regexpOk = false; ungetc(ch2); lexString('/', null, true, false); readRegexpFlags(); try { Pattern regexp = Pattern.compile(literal.toString(), _flags); // checking for errors } catch (Exception e) { // e.printStackTrace(); throw error(String.valueOf(e)); } return REGEXP; } else { ungetc(ch2); return lexOp(ch); } } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': regexpOk = false; return lexNumber(ch); case '"': case '\'': regexpOk = false; return lexString((char) ch, null, false, false); case '@': { int ch2 = read(); switch (ch2) { case '"': CharBuffer macro = new CharBuffer(); macro.append('('); interpolate(macro, '"', null, "\"", "\"", false, false); macro.append(')'); pushMacro(macro); break; case '\'': macro = new CharBuffer(); macro.append('('); interpolate(macro, '\'', null, "\'", "\'", false, false); macro.append(')'); pushMacro(macro); break; case '@': if ((ch2 = read()) < 0) throw error(L.l("unexpected end of file")); switch (ch2) { case '{': ch2 = '}'; break; case '<': ch2 = '>'; break; case '(': ch2 = ')'; break; case '[': ch2 = ']'; break; } return lexString((char) ch2, null, true, false); case '<': if ((ch2 = read()) != '<') throw error(L.l("illegal character at `@'")); if (scanMultiline()) return LITERAL; break; case '/': macro = new CharBuffer(); macro.append("new RegExp("); interpolate(macro, '/', null, "@@/", "/", true, false); macro.append(","); macro.append(readRegexpFlags()); macro.append(")"); pushMacro(macro); break; default: return lexOp('@'); } break; } case '%': { int ch2 = read(); regexpOk = true; ungetc(ch2); return lexOp(ch); } case '#': { int ch2 = read(); if (line == 1 && lineCh == 2 && ch2 == '!') { for (; ch2 > 0 && ch2 != '\n'; ch2 = read()) { } ungetc(ch2); break; } if (ch2 >= 'a' && ch2 <= 'z' || ch2 >= 'A' && ch2 <= 'Z') { temp.clear(); for (; ch2 >= 'a' && ch2 <= 'z' || ch2 >= 'A' && ch2 <= 'Z'; ch2 = read()) { temp.append((char) ch2); } if (temp.toString().equals("line")) scanLine(ch2); else if (temp.toString().equals("file")) scanFile(ch2); else throw error(L.l("expected pragma at `{0}'", temp)); break; } if (ch2 < '0' || ch2 > '9') throw error(L.l("expected digit at {0}", badChar(ch2))); intValue = 0; for (; ch2 >= '0' && ch2 <= '9'; ch2 = read()) intValue = 10 * intValue + ch2 - '0'; if (ch2 == '=') return HASH_DEF; else if (ch2 == '#') return HASH_REF; else throw error(L.l("expected sharp variable at {0}", badChar(ch))); } default: if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_' || ch == '$') { regexpOk = false; return lexId(ch); } else { throw error(L.l("illegal character at {0}", badChar(ch))); } } } } /** * Returns the text object for the lexeme. */ CharBuffer getText() { return text; } boolean isEof() { return isEof; } /** * Used for error messages. */ String getToken() { return lineText.substring(beginLineCh, lineCh); } /** * Returns the Id */ ESId getId() { return id; } /** * Returns true if seen linefeed since the last. */ boolean seenLineFeed() { return hasLf; } ESParseException error(String text) { return new ESParseException(filename, beginLine, beginLineCh, line, lineCh, text); } private String hex(int value) { CharBuffer cb = new CharBuffer(); for (int b = 3; b >= 0; b--) { int v = (value >> (4 * b)) & 0xf; if (v < 10) cb.append((char) (v + '0')); else cb.append((char) (v - 10 + 'a')); } return cb.toString(); } private String badChar(int ch) { if (ch >= 0x20 && ch <= 0x7f) return "`" + (char) ch + "'"; else if (ch == '\n') return L.l("end of line"); else if (ch == -1) return L.l("end of file"); else return "`" + (char) ch + "' (\\u" + hex(ch) + ")"; } String getFilename() { if (lineMap != null) { LineMap.Line map = lineMap.getLine(line); if (map != null) return map.getSourceFilename(); } return filename; } long getLastModified() { if (is.getPath() == null) return 0; else return is.getPath().getLastModified(); } int getLine() { if (lineMap != null) { LineMap.Line map = lineMap.getLine(line); if (map != null) { return map.getSourceLine(line); } } return line; } String getLastFilename() { if (lineMap != null) { LineMap.Line map = lineMap.getLine(lastLine); if (map != null) return map.getSourceFilename(); } return lastFilename; } int getLastLine() { if (lineMap != null) { LineMap.Line map = lineMap.getLine(lastLine); if (map != null) { return map.getSourceLine(lastLine); } } return lastLine; } private void pushMacro(CharBuffer cb) throws ESParseException { if (peek >= 0) cb.append((char) read()); // Because of peek if (peek >= 0) cb.append((char) read()); // Because of peek if (macroText != null) macros.add(new Macro(macroText, macroIndex, macroOldLine)); macroText = cb; macroIndex = 0; macroOldLine = line; } /** * Update variables to handle a newline. */ private void newline() { line++; lineCh = 0; lineText.clear(); } /** * Handles all the goodies for a floating point number after the * dot or 'e' */ private int lexFloat(double value, int ch) throws ESParseException { int expt = 0; for (; ch >= '0' && ch <= '9'; ch = read()) { value = 10 * value + ch - '0'; expt--; } if (ch == 'e' || ch == 'E') { ch = read(); int sign = 1; if (ch == '-') { sign = -1; ch = read(); } else if (ch == '+') { ch = read(); } if (ch < '0' || ch > '9') throw error(L.l("expected exponent at {0}", badChar(ch))); int userExpt = 0; for (; ch >= '0' && ch <= '9'; ch = read()) { userExpt = 10 * userExpt + ch - '0'; } expt += sign * userExpt; } ungetc(ch); if (expt >= 0) literal = ESNumber.create(value * Math.pow(10, expt)); else literal = ESNumber.create(value / Math.pow(10, -expt)); return LITERAL; } /** * Lexeme for a number */ private int lexNumber(int ch) throws ESParseException { int radix = 10; double value = 0; boolean hasChar = true; if (ch == '0') { ch = read(); if (ch >= '0' && ch <= '9') radix = 8; else if (ch == 'x' || ch == 'X') { hasChar = false; radix = 16; ch = read(); } } for (; ch >= 0; ch = read()) { if (ch >= '0' && ch <= '9') { value = radix * value + ch - '0'; hasChar = true; if (radix == 8 && ch >= '8') throw error(L.l("expected octal digit at {0}", badChar(ch))); } else if (radix == 16 && ch >= 'a' && ch <= 'f') { hasChar = true; value = radix * value + ch - 'a' + 10; } else if (radix == 16 && ch >= 'A' && ch <= 'F') { hasChar = true; value = radix * value + ch - 'A' + 10; } else break; } if (! hasChar) throw error(L.l("expected hex digit at {0}", badChar(ch))); if (radix == 10 && ch == '.') { ch = read(); if (ch >= '0' && ch <= '9') return lexFloat(value, ch); else { ungetc(ch); literal = ESNumber.create(value); return LITERAL; } } else if (radix == 10 && (ch == 'e' || ch == 'E')) return lexFloat(value, ch); else { ungetc(ch); literal = ESNumber.create(value); return LITERAL; } } /** * Returns the number for a hex digit. */ private int hexDigit(int ch) throws ESParseException { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; else throw error(L.l("expected hex digit at {0}", badChar(ch))); } /** * Lexeme for a string. */ private int lexString(char endCh, String endTail, boolean isRegexp, boolean isMultiline) throws ESParseException { text.setLength(0); int ch = read(); for (; ch >= 0; ch = read()) { if (ch == '\n') { if (isMultiline) { } else if (isRegexp) throw error(L.l("unexpected end of line in regular expression")); else throw error(L.l("unexpected end of line in string")); newline(); } if (ch != endCh) { } else if (endTail == null) { literal = ESString.create(text.toString()); return LITERAL; } else if (! text.endsWith(endTail)) { } else if (text.length() == endTail.length()) { literal = ESString.create(""); return LITERAL; } else { char tailCh = text.charAt(text.length() - endTail.length() - 1); if (tailCh == '\n') { text.setLength(text.length() - endTail.length() - 1); literal = ESString.create(text.toString()); return LITERAL; } } if (ch == '\\') { ch = read(); switch (ch) { case -1: if (isRegexp) throw error(L.l("unexpected end of file in regular expression")); else throw error(L.l("unexpected end of file in string")); case '\n': if (isRegexp) throw error(L.l("unexpected end of line in regular expression")); else throw error(L.l("unexpected end of line in string")); case 'b': if (isRegexp) text.append("\\b"); else text.append('\b'); break; case 'e': text.append((char) 0x1b); break; case 'f': text.append('\f'); break; case 'n': text.append('\n'); break; case 'r': text.append('\r'); break; case 't': text.append('\t'); break; case 'v': text.append((char) 0xb); break; case 'c': { ch = read(); if (ch >= 'a' && ch <= 'z') text.append((char) (ch - 'a' + 1)); else if (ch >= 'A' && ch <= 'Z') text.append((char) (ch - 'A' + 1)); else if (ch - '@' >= 0 && ch - '@' < ' ') text.append((char) (ch - '@')); else throw error(L.l("expected control character at {0}", badChar(ch))); } break; case 'o': { int value = 0; while ((ch = read()) >= '0' && ch <= '8') { value = 8 * value + ch - '0'; } ungetc(ch); text.append((char) value); } break; case 'x': { int value = 16 * hexDigit(read()); value += hexDigit(read()); text.append((char) value); } break; case 'u': { int value = 4096 * hexDigit(read()); value += 256 * hexDigit(read()); value += 16 * hexDigit(read()); value += hexDigit(read()); text.append((char) value); } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { int value = ch - '0'; if (ch != '0' && isRegexp) { text.append('\\'); text.append((char) ch); break; } if ((ch = read()) >= '0' && ch <= '7') { value = 8 * value + ch - '0'; if (value >= 040) { } else if ((ch = read()) >= '0' && ch <= '7') value = 8 * value + ch - '0'; else ungetc(ch); } else ungetc(ch); text.append((char) value); } break; default: if (isRegexp) text.append('\\'); text.append((char) ch); break; } } else { text.append((char) ch); } } if (ch != -1) { } else if (isRegexp) throw error(L.l("unexpected end of file in regular expression")); else throw error(L.l("unexpected end of file in string")); literal = ESString.create(text.toString()); return LITERAL; } private void scanMacroStatement(CharBuffer macro, int end, boolean isRegexp, boolean multiline) throws ESParseException { int ch; while ((ch = read()) >= 0 && ch != end) { macro.append((char) ch); switch (ch) { case '\\': ch = read(); macro.append((char) ch); break; case '\'': case '"': int testch = ch; while ((ch = read()) >= 0) { if (ch == '\\') { macro.append((char) ch); ch = read(); } else if (ch == testch) { macro.append((char) ch); break; } else if (ch == '\n') { if (! multiline) throw error("unexpected end of line in " + (isRegexp ? "regular expression" : "string")); newline(); } macro.append((char) ch); } break; case '(': scanMacroStatement(macro, ')', isRegexp, multiline); macro.append(')'); break; case '{': scanMacroStatement(macro, '}', isRegexp, multiline); macro.append('}'); break; case '\n': if (! multiline) throw error("unexpected end of line in " + (isRegexp ? "regular expression" : "string")); newline(); break; default: break; } } } private void interpolate(CharBuffer macro, int tail, String matchText, String beginStr, String endStr, boolean isRegexp, boolean multiline) throws ESParseException { int ch = read(); int ch1; macro.append(beginStr); int start = macro.length(); loop: for (; ch >= 0; ch = read()) { switch (ch) { case '\\': macro.append((char) ch); ch = read(); if (ch != -1) macro.append((char) ch); break; case '$': if ((ch = read()) == -1) break; if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_' || ch == '$') { macro.append(endStr); macro.append("+("); macro.append((char) ch); while ((ch = read()) >= 0 && (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '$') { macro.append((char) ch); } ungetc(ch); macro.append(")+"); macro.append(beginStr); } else if (ch == '{') { macro.append(endStr); macro.append("+("); scanMacroStatement(macro, '}', isRegexp, multiline); macro.append(")+"); macro.append(beginStr); } else if (ch == '(') { macro.append(endStr); macro.append("+("); scanMacroStatement(macro, ')', isRegexp, multiline); macro.append(")+"); macro.append(beginStr); } else { ungetc(ch); macro.append('$'); } break; default: if (ch == '\n') { newline(); if (! multiline) throw error("unexpected end of line in " + (isRegexp ? "regular expression" : "string")); } if (ch != tail) { } else if (matchText == null) { break loop; } else if (! macro.endsWith(matchText)) { } else if (macro.length() - start == matchText.length()) { macro.setLength(start); break loop; } else if (macro.charAt(macro.length() - matchText.length() - 1) == '\n') { macro.setLength(macro.length() - matchText.length() - 1); break loop; } macro.append((char) ch); break; } } macro.append(endStr); } private boolean scanMultiline() throws ESParseException { int ch; CharBuffer end = new CharBuffer(); boolean interpolate = true; boolean endNewline = true; if ((ch = read()) >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_' || ch == '$') { for (; ch >= 0 && ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_' || ch == '$' || ch >= '0' && ch <= '9'; ch = read()) { end.append((char) ch); } } else if (ch == '\'') { interpolate = false; for (ch = read(); ch >= 0 && ch != '\'' && ch != '\n'; ch = read()) { end.append((char) ch); } if (ch != '\'') throw error(L.l("multiline escape error at {0}", badChar(ch))); ch = read(); } else if (ch == '`') { interpolate = false; for (ch = read(); ch >= 0 && ch != '`' && ch != '\n'; ch = read()) { end.append((char) ch); } if (ch != '`') throw error(L.l("multiline escape error at {0}", badChar(ch))); endNewline = false; } else if (ch == '\"') { for (ch = read(); ch >= 0 && ch != '\"' && ch != '\n'; ch = read()) { end.append((char) ch); } if (ch != '\"') throw error(L.l("multiline escape error at {0}", badChar(ch))); ch = read(); } int oldLine = line; CharBuffer lineTail = null; if (endNewline) { lineTail = new CharBuffer(); for (; ch >= 0 && ch != '\n'; ch = read()) { lineTail.append((char) ch); } if (ch == '\r') { lineTail.append((char) ch); ch = read(); } if (ch == '\n') { newline(); lineTail.append((char) ch); } } CharBuffer macro = null; String endString = end.toString(); if (interpolate) { macro = new CharBuffer(); macro.append('('); interpolate(macro, '\n', endString, "@<<`" + endString + "`", "\n" + endString + '\n', false, true); macro.append("+'\\n')"); } else { if (endNewline) { lexString('\n', endString, false, true); text.append('\n'); literal = ESString.create(text); } else { lexString('\n', endString, false, true); line -= 2; } } if (endNewline) { pushMacro(lineTail); line = oldLine; } if (interpolate) { pushMacro(macro); line++; return false; } else return true; } private int readRegexpFlags() throws ESParseException { int ch; while (true) { switch ((ch = read())) { case 'x': _flags |= Pattern.COMMENTS; break; case 'i': _flags |= Pattern.CASE_INSENSITIVE; break; case 'g': break; case 'm': _flags |= Pattern.MULTILINE; break; case 's': break; default: ungetc(ch); return _flags; } } } /** * Lexeme for an Id. Reserved words are looked up in a * HashMap. */ private int lexId(int ch) throws ESParseException { text.setLength(0); text.append((char) ch); while (true) { ch = read(); if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_' || ch == '$' || ch >= '0' && ch <= '9') { text.append((char) ch); } else { ungetc(ch); break; } } Integer value = (Integer) reserved.get(text); if (value == null) { id = ESId.intern(text.toString()); return IDENTIFIER; } else { int intValue = value.intValue(); switch (intValue) { case NULL: literal = ESBase.esNull; return LITERAL; case UNDEFINED: literal = ESBase.esUndefined; return LITERAL; case FALSE: literal = ESBoolean.create(false); return LITERAL; case TRUE: literal = ESBoolean.create(true); return LITERAL; default: return value.intValue(); } } } /** * Lexeme for an operation */ private int lexOp(int ch) throws ESParseException { text.setLength(0); text.append((char) ch); loop: while ((ch = read()) >= 0) { switch (ch) { case '+': case '-': case '*': case '/': case '%': case '!': case '<': case '.': case '>': case '&': case '|': case '=': case '^': case '?': text.append((char) ch); op = (Op) ops.get(text); if (op == null) { text.setLength(text.length() - 1); ungetc(ch); break loop; } break; default: ungetc(ch); break loop; } } op = (Op) ops.get(text); // XXX: non-reachable if (op == null) throw error(L.l("expected operator at `{0}'", text.toString())); return op.lexeme; } /** * Return the operation for a lexeme. Binary operations like '*' will * return BIN_OP as the lexeme. Calling getOp() will get the actual * operation. */ int getOp() { return op.op; } int getPrecedence() { return op.precedence; } boolean isRightAssoc() { return op.isRightAssoc; } ESBase getLiteral() { return literal; } int getFlags() { return _flags; } private void scanLine(int ch) throws ESParseException { for (; ch == ' ' || ch == '\t'; ch = read()) { } if (ch < '0' || ch > '9') throw error(L.l("expected digit at {0}", badChar(ch))); line = 0; for (; ch >= '0' && ch <= '9'; ch = read()) line = 10 * line + ch - '0'; for (; ch == ' ' || ch == '\t'; ch = read()) { } if (ch != '#') throw error(L.l("expected `#' at {0}", badChar(ch))); } private void scanFile(int ch) throws ESParseException { for (; ch == ' ' || ch == '\t'; ch = read()) { } temp.clear(); for (; ch >= 0 && ch != ' ' && ch != '\t' && ch != '#'; ch = read()) temp.append((char) ch); if (temp.length() == 0) throw error(L.l("expected filename at {0}", badChar(ch))); filename = temp.toString(); for (; ch == ' ' || ch == '\t'; ch = read()) { } line = 0; for (; ch >= '0' && ch <= '9'; ch = read()) line = 10 * line + ch - '0'; if (line == 0) line = 1; for (; ch == ' ' || ch == '\t'; ch = read()) { } if (ch != '#') throw error(L.l("expected `#' at {0}", badChar(ch))); } /** * Reads the next character. */ private int read() throws ESParseException { lineCh++; if (peek >= 0) { int ch = peek; peek = peek2; peek2 = -1; return ch; } while (macroText != null) { if (macroIndex < macroText.length()) { int ch = macroText.charAt(macroIndex++); lineText.append((char) ch); return ch; } line = macroOldLine; if (macros.size() == 0) macroText = null; else { Macro macro = (Macro) macros.remove(macros.size() - 1); macroText = macro.text; macroIndex = macro.index; macroOldLine = macro.oldLine; } } try { int ch = is.readChar(); if (ch == '\r') { ch = is.readChar(); if (ch != '\n') { if (ch == '\r') peek = '\n'; else peek = ch; } ch = '\n'; } lineText.append((char) ch); return ch; } catch (CharConversionException e1) { throw error(L.l("expected {0} encoded character", is.getEncoding())); } catch (IOException e1) { throw new ESParseException(e1); } } private void ungetc(int ch) { peek2 = peek; peek = ch; if (lineCh > 0) lineCh--; /* if (ch == '\n') line--; */ } static class Op { int op; int lexeme; int precedence; boolean isRightAssoc; Op(int op, int lexeme, int precedence, boolean isRightAssoc) { this.op = op; this.lexeme = lexeme; this.precedence = precedence; this.isRightAssoc = isRightAssoc; } }; class Macro { CharBuffer text; int index; int oldLine; void clear() { text.clear(); index = 0; } Macro(CharBuffer cb, int index, int oldLine) { this.text = cb; this.index = index; this.oldLine = oldLine; } } }