package org.chartsy.stockscanpro.lexer; import java.util.regex.Pattern; import org.chartsy.stockscanpro.lexer.api.ScanTokenId; import org.netbeans.api.lexer.Token; import org.netbeans.spi.lexer.Lexer; import org.netbeans.spi.lexer.LexerInput; import org.netbeans.spi.lexer.LexerRestartInfo; import org.netbeans.spi.lexer.TokenFactory; /** * * @author Viorel */ public class ScanLexer implements Lexer<ScanTokenId> { private static final int EOF = LexerInput.EOF; private final LexerInput input; private final TokenFactory<ScanTokenId> tokenFactory; private boolean tradeValueFlag = false; public ScanLexer(LexerRestartInfo<ScanTokenId> info) { this.input = info.input(); this.tokenFactory = info.tokenFactory(); assert (info.state() == null); // never set to non-null value in state() } @Override public Object state() { return null; // always in default state after token recognition } @Override public Token<ScanTokenId> nextToken() { while (true) { int c = input.read(); switch (c) { case '"': case '|': case '&': case '%': case '^': case '\'': case '!': case '~': case ';': case ':': case '?': case '{': case '}': case '@': return token(ScanTokenId.ERROR); case ',': return token(ScanTokenId.COMMA); case '=': if ((c = input.read()) == '=') // == return token(ScanTokenId.ERROR); input.backup(1); return token(ScanTokenId.EQ); case '>': switch (c = input.read()) { case '>': // >> switch (c = input.read()) { case '>': // >>> if ((c = input.read()) == '=') // >>>= return token(ScanTokenId.ERROR); input.backup(1); return token(ScanTokenId.ERROR); case '=': // >>= return token(ScanTokenId.ERROR); } input.backup(1); return token(ScanTokenId.ERROR); case '=': // >= return token(ScanTokenId.GTEQ); } input.backup(1); return token(ScanTokenId.GT); case '<': switch (c = input.read()) { case '<': // << if ((c = input.read()) == '=') // <<= return token(ScanTokenId.ERROR); input.backup(1); return token(ScanTokenId.ERROR); case '=': // <= return token(ScanTokenId.LTEQ); } input.backup(1); return token(ScanTokenId.LT); case '+': return token(ScanTokenId.PLUS); case '-': return token(ScanTokenId.MINUS); case '*': return token(ScanTokenId.STAR); case '/': return token(ScanTokenId.SLASH); case '.': if ((c = input.read()) == '.') if ((c = input.read()) == '.') // ... return token(ScanTokenId.ERROR); else input.backup(2); else if ('0' <= c && c >= '9') // float literal { return finishNumberLiteral(c = input.read(), true); } else input.backup(1); return token(ScanTokenId.ERROR); case '(': return token(ScanTokenId.LPAREN); case ')': return token(ScanTokenId.RPAREN); case '[': return token(ScanTokenId.LBRACKET); case ']': return token(ScanTokenId.RBRACKET); case '0': // is a number c = input.read(); return finishNumberLiteral(c, false); case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return finishNumberLiteral(c = input.read(), false); // Keywords and indicators lexing case 'a': switch (c = input.read()) { case 'd': // adx if ((c = input.read()) == 'x') return parseParamsIndicator(ScanTokenId.ADX, false, 1); else return token(ScanTokenId.ERROR); case 'n': // and return ((c = input.read()) == 'd') ? token(ScanTokenId.AND) : token(ScanTokenId.ERROR); case 't': // atr if ((c = input.read()) == 'r') return parseParamsIndicator(ScanTokenId.ATR, false, 1); else return token(ScanTokenId.ERROR); default: return token(ScanTokenId.ERROR); } case 'b': switch (c = input.read()) { case 'o': switch (c = input.read()) { case 'd': if ((c = input.read()) == 'y' && (c = input.read()) == '_') switch (c = input.read()) { case 'b': // body_bottom if ((c = input.read()) == 'o' && (c = input.read()) == 't' && (c = input.read()) == 't' && (c = input.read()) == 'o' && (c = input.read()) == 'm') return parseNoParamsIndicator(ScanTokenId.BODY_BOTTOM); else return token(ScanTokenId.ERROR); case 't': // body_top if ((c = input.read()) == 'o' && (c = input.read()) == 'p') return parseNoParamsIndicator(ScanTokenId.BODY_TOP); else return token(ScanTokenId.ERROR); } break; case 'l': if ((c = input.read()) == 'l' && (c = input.read()) == 'i' && (c = input.read()) == 'n' && (c = input.read()) == 'g' && (c = input.read()) == 'e' && (c = input.read()) == 'r' && (c = input.read()) == '_') switch (c = input.read()) { case 'l': // bollinger_lower if ((c = input.read()) == 'o' && (c = input.read()) == 'w' && (c = input.read()) == 'e' && (c = input.read()) == 'r') return parseParamsIndicator(ScanTokenId.BOLLINGER_LOWER, true, 3); else return token(ScanTokenId.ERROR); case 'm': // bollinger_middle if ((c = input.read()) == 'i' && (c = input.read()) == 'd' && (c = input.read()) == 'd' && (c = input.read()) == 'l' && (c = input.read()) == 'e') return parseParamsIndicator(ScanTokenId.BOLLINGER_MIDDLE, true, 2); else return token(ScanTokenId.ERROR); case 'u': // bollinger_upper if ((c = input.read()) == 'p' && (c = input.read()) == 'p' && (c = input.read()) == 'e' && (c = input.read()) == 'r') return parseParamsIndicator(ScanTokenId.BOLLINGER_UPPER, true, 3); else return token(ScanTokenId.ERROR); default: return token(ScanTokenId.ERROR); } break; default: return token(ScanTokenId.ERROR); } default: return token(ScanTokenId.ERROR); } case 'c': switch (c = input.read()) { case 'c': if ((c = input.read()) == 'i') { if ((c = input.read()) == '_') { if ((c = input.read()) == 'e' && (c = input.read()) == 'm' && (c = input.read()) == 'a') return parseParamsIndicator(ScanTokenId.CCI_EMA, false, 2); else return token(ScanTokenId.ERROR); } input.backup(1); return parseParamsIndicator(ScanTokenId.CCI, false, 1); } break; case 'l': // close if ((c = input.read()) == 'o' && (c = input.read()) == 's' && (c = input.read()) == 'e') { if (tradeValueFlag) { tradeValueFlag = false; return token(ScanTokenId.CLOSE_VAR); } return parseNoParamsIndicator(ScanTokenId.CLOSE); } else return token(ScanTokenId.ERROR); default: return token(ScanTokenId.ERROR); } case 'e': // ema if ((c = input.read()) == 'm' && (c = input.read()) == 'a') return parseParamsIndicator(ScanTokenId.EMA, true, 2); else return token(ScanTokenId.ERROR); case 'f': // force_index if ((c = input.read()) == 'o' && (c = input.read()) == 'r' && (c = input.read()) == 'c' && (c = input.read()) == 'e' && (c = input.read()) == '_' && (c = input.read()) == 'i' && (c = input.read()) == 'n' && (c = input.read()) == 'd' && (c = input.read()) == 'e' && (c = input.read()) == 'x') return parseParamsIndicator(ScanTokenId.FORCE_INDEX, false, 1); else return token(ScanTokenId.ERROR); case 'h': // high if ((c = input.read()) == 'i' && (c = input.read()) == 'g' && (c = input.read()) == 'h') { if (tradeValueFlag) { tradeValueFlag = false; return token(ScanTokenId.HIGH_VAR); } return parseNoParamsIndicator(ScanTokenId.HIGH); } else return token(ScanTokenId.ERROR); case 'l': // low if ((c = input.read()) == 'o' && (c = input.read()) == 'w') { if (tradeValueFlag) { tradeValueFlag = false; return token(ScanTokenId.LOW_VAR); } return parseNoParamsIndicator(ScanTokenId.LOW); } else return token(ScanTokenId.ERROR); case 'm': switch (c = input.read()) { case 'a': switch (c = input.read()) { case 'x': // max return parseParamsIndicator(ScanTokenId.MAX, true, 2); case 'c': // macd if ((c = input.read()) != 'd') return token(ScanTokenId.ERROR); if ((c = input.read()) == '_') { switch (c = input.read()) { case 'h': if ((c = input.read()) == 'i' && (c = input.read()) == 's' && (c = input.read()) == 't' && (c = input.read()) == 'o' && (c = input.read()) == 'g' && (c = input.read()) == 'r' && (c = input.read()) == 'a' && (c = input.read()) == 'm') return parseParamsIndicator(ScanTokenId.MACD_HISTOGRAM, false, 3); break; case 's': if ((c = input.read()) == 'i' && (c = input.read()) == 'g' && (c = input.read()) == 'n' && (c = input.read()) == 'a' && (c = input.read()) == 'l') return parseParamsIndicator(ScanTokenId.MACD_SIGNAL, false, 3); break; } } input.backup(1); return parseParamsIndicator(ScanTokenId.MACD, false, 2); } case 'd': // mdi if ((c = input.read()) == 'i') return parseParamsIndicator(ScanTokenId.MDI, false, 1); else return token(ScanTokenId.ERROR); case 'i': // min if ((c = input.read()) == 'n') return parseParamsIndicator(ScanTokenId.MIN, true, 2); else return token(ScanTokenId.ERROR); case 'o': if ((c = input.read()) == 'n' && (c = input.read()) == 'e' && (c = input.read()) == 'y' && (c = input.read()) == '_' && (c = input.read()) == 'f' && (c = input.read()) == 'l' && (c = input.read()) == 'o' && (c = input.read()) == 'w') { if ((c = input.read()) == '_') { if ((c = input.read()) == 'a' && (c = input.read()) == 'v' && (c = input.read()) == 'g') return parseParamsIndicator(ScanTokenId.MONEY_FLOW_AVG, false, 1); else return token(ScanTokenId.ERROR); } input.backup(1); return parseNoParamsIndicator(ScanTokenId.MONEY_FLOW); } break; default: return token(ScanTokenId.ERROR); } case 'o': switch (c = input.read()) { case 'p': // open if ((c = input.read()) == 'e' && (c = input.read()) == 'n') { if (tradeValueFlag) { tradeValueFlag = false; return token(ScanTokenId.OPEN_VAR); } return parseNoParamsIndicator(ScanTokenId.OPEN); } else return token(ScanTokenId.ERROR); case 'r': // or return token(ScanTokenId.OR); case 'n': // on_balance_volume if ((c = input.read()) == '_' && (c = input.read()) == 'b' && (c = input.read()) == 'a' && (c = input.read()) == 'l' && (c = input.read()) == 'a' && (c = input.read()) == 'n' && (c = input.read()) == 'c' && (c = input.read()) == 'e' && (c = input.read()) == '_' && (c = input.read()) == 'v' && (c = input.read()) == 'o' && (c = input.read()) == 'l' && (c = input.read()) == 'u' && (c = input.read()) == 'm' && (c = input.read()) == 'e') return parseParamsIndicator(ScanTokenId.ON_BALANCE_VOLUME, false, 1); else return token(ScanTokenId.ERROR); default: return token(ScanTokenId.ERROR); } case 'p': switch (c = input.read()) { case 'c': // pctr if ((c = input.read()) == 't' && (c = input.read()) == 'r') return parseParamsIndicator(ScanTokenId.PCTR, false, 1); else return token(ScanTokenId.ERROR); case 'd': // pdi if ((c = input.read()) == 'i') return parseParamsIndicator(ScanTokenId.PDI, false, 1); else return token(ScanTokenId.ERROR); default: return token(ScanTokenId.ERROR); } case 'r': // rsi if ((c = input.read()) == 's' && (c = input.read()) == 'i') return parseParamsIndicator(ScanTokenId.RSI, false, 1); else return token(ScanTokenId.ERROR); case 's': switch (c = input.read()) { case 'm': // sma if ((c = input.read()) == 'a') return parseParamsIndicator(ScanTokenId.SMA, true, 2); else return token(ScanTokenId.ERROR); case 'p': // spread if ((c = input.read()) == 'r' && (c = input.read()) == 'e' && (c = input.read()) == 'a' && (c = input.read()) == 'd') { if (tradeValueFlag) { tradeValueFlag = false; return token(ScanTokenId.SPREAD_VAR); } return parseNoParamsIndicator(ScanTokenId.SPREAD); } else return token(ScanTokenId.ERROR); case 't': if ((c = input.read()) == 'o' && (c = input.read()) == '_') switch (c = input.read()) { case 'd': // sto_d return parseParamsIndicator(ScanTokenId.STO_D, false, 3); case 'k': // sto_k return parseParamsIndicator(ScanTokenId.STO_K, false, 2); default: return token(ScanTokenId.ERROR); } break; default: return token(ScanTokenId.ERROR); } case 'u': // ultimate_oscillator if ((c = input.read()) == 'l' && (c = input.read()) == 't' && (c = input.read()) == 'i' && (c = input.read()) == 'm' && (c = input.read()) == 'a' && (c = input.read()) == 't' && (c = input.read()) == 'e' && (c = input.read()) == '_' && (c = input.read()) == 'o' && (c = input.read()) == 's' && (c = input.read()) == 'c' && (c = input.read()) == 'i' && (c = input.read()) == 'l' && (c = input.read()) == 'l' && (c = input.read()) == 'a' && (c = input.read()) == 't' && (c = input.read()) == 'o' && (c = input.read()) == 'r') return parseNoParamsIndicator(ScanTokenId.ULTIMATE_OSCILLATOR); else return token(ScanTokenId.ERROR); case 'v': // volume if ((c = input.read()) == 'o' && (c = input.read()) == 'l' && (c = input.read()) == 'u' && (c = input.read()) == 'm' && (c = input.read()) == 'e') { if (tradeValueFlag) { tradeValueFlag = false; return token(ScanTokenId.VOLUME_VAR); } return parseNoParamsIndicator(ScanTokenId.VOLUME); } else return token(ScanTokenId.ERROR); // Rest of lowercase letters starting identifiers case 'd': case 'g': case 'i': case 'j': case 'k': case 'n': case 'q': case 't': case 'w': case 'x': case 'y': case 'z': // Uppercase letters starting identifiers case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '$': //return finishIdentifier(); return token(ScanTokenId.ERROR); // All Character.isWhitespace(c) below 0x80 follow // ['\t' - '\r'] and [0x1c - ' '] case '\t': case '\n': case 0x0b: case '\f': case '\r': case 0x1c: case 0x1d: case 0x1e: case 0x1f: return finishWhitespace(); case ' ': c = input.read(); if (c == EOF || !Character.isWhitespace(c)) { input.backup(1); return tokenFactory.getFlyweightToken(ScanTokenId.WHITESPACE, " "); } return finishWhitespace(); case EOF: return null; default: if (c >= 0x80) { c = translateSurrogates(c); if (Character.isJavaIdentifierStart(c)) return finishIdentifier(); if (Character.isWhitespace(c)) return finishWhitespace(); } // Invalid char return token(ScanTokenId.ERROR); } // end of switch (c) } // end of while(true) } private int translateSurrogates(int c) { if (Character.isHighSurrogate((char)c)) { int lowSurr = input.read(); if (lowSurr != EOF && Character.isLowSurrogate((char)lowSurr)) { // c and lowSurr form the integer unicode char. c = Character.toCodePoint((char)c, (char)lowSurr); } else { // Otherwise it's error: Low surrogate does not follow the high one. // Leave the original character unchanged. // As the surrogates do not belong to any // specific unicode category the lexer should finally // categorize them as a lexical error. input.backup(1); } } return c; } private Token<ScanTokenId> finishWhitespace() { while (true) { int c = input.read(); // There should be no surrogates possible for whitespace // so do not call translateSurrogates() if (c == EOF || !Character.isWhitespace(c)) { input.backup(1); return tokenFactory.createToken(ScanTokenId.WHITESPACE); } } } private Token<ScanTokenId> finishIdentifier() { return finishIdentifier(input.read()); } private Token<ScanTokenId> finishIdentifier(int c) { while (true) { if (c == EOF || !Character.isJavaIdentifierPart(c = translateSurrogates(c))) { // For surrogate 2 chars must be backed up input.backup((c >= Character.MIN_SUPPLEMENTARY_CODE_POINT) ? 2 : 1); return tokenFactory.createToken(ScanTokenId.IDENTIFIER); } c = input.read(); } } private Token<ScanTokenId> keywordOrIdentifier(ScanTokenId keywordId) { return keywordOrIdentifier(keywordId, input.read()); } private Token<ScanTokenId> keywordOrIdentifier(ScanTokenId keywordId, int c) { // Check whether the given char is non-ident and if so then return keyword if (c == EOF || !Character.isJavaIdentifierPart(c = translateSurrogates(c))) { // For surrogate 2 chars must be backed up input.backup((c >= Character.MIN_SUPPLEMENTARY_CODE_POINT) ? 2 : 1); return token(keywordId); } else // c is identifier part return finishIdentifier(); } private Token<ScanTokenId> finishNumberLiteral(int c, boolean inFraction) { while (true) { switch (c) { case '.': if (!inFraction) { inFraction = true; } else // two dots in the literal { return token(ScanTokenId.FLOAT_LITERAL_INVALID); } break; case 'l': case 'L': // 0l or 0L return token(ScanTokenId.ERROR); case 'd': case 'D': return token(ScanTokenId.ERROR); case 'f': case 'F': return token(ScanTokenId.ERROR); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': break; case 'e': case 'E': // exponent part return token(ScanTokenId.ERROR); default: input.backup(1); return token(inFraction ? ScanTokenId.DOUBLE_LITERAL : ScanTokenId.INT_LITERAL); } c = input.read(); } } private Token<ScanTokenId> token(ScanTokenId id) { String fixedText = id.fixedText(); return (fixedText != null) ? tokenFactory.getFlyweightToken(id, fixedText) : tokenFactory.createToken(id); } @Override public void release() {} private String readUntilNextOpenParenthesis() { StringBuilder sb = new StringBuilder(); do { int c = input.read(); if (c == EOF) return sb.toString(); sb.append((char) c); if (c == '(') return sb.toString(); } while (true); } private String readUntilNextParenthesis() { StringBuilder sb = new StringBuilder(); int nParen = 1; do { int c = input.read(); if (c == EOF) return sb.toString(); sb.append((char) c); switch (c) { case '(': nParen++; break; case ')': nParen--; break; } if (nParen == 0) return sb.toString(); } while (true); } private String readUntilNextBraket() { StringBuilder sb = new StringBuilder(); int nBraket = 1; do { int c = input.read(); if (c == EOF) return sb.toString(); sb.append((char) c); switch (c) { case '[': nBraket++; break; case ']': nBraket--; break; } if (nBraket == 0) return sb.toString(); } while (true); } private Token<ScanTokenId> parseNoParamsIndicator(ScanTokenId tokenId) { if (input.read() == '(' && input.read() == ')') { if (input.read() == '[') if (!parseExtraArgument()) return token(ScanTokenId.ERROR); input.backup(3); return token(tokenId); } else { input.backup(2); return token(ScanTokenId.ERROR); } } private Token<ScanTokenId> parseParamsIndicator(ScanTokenId tokenId, boolean hasTradeValue, int nParams) { String space = readUntilNextOpenParenthesis(); if (space.endsWith("(")) { String params = readUntilNextParenthesis(); if (!parseParams(params, hasTradeValue, nParams)) return token(ScanTokenId.ERROR); if (!params.endsWith(")")) return token(ScanTokenId.ERROR); if (input.read() == '[') if (!parseExtraArgument()) return token(ScanTokenId.ERROR); input.backup(params.length() + space.length() + 1); tradeValueFlag = hasTradeValue; return token(tokenId); } else { input.backup(space.length()); return token(ScanTokenId.ERROR); } } private boolean parseParams(String string, boolean hasTradeValue, int nParams) { String params = string; if (params.endsWith(")")) params = params.substring(0, params.length() - 1); params = params.replace(" ", ""); String[] paramList = params.split(","); if (paramList.length != nParams) return false; if (hasTradeValue) if (!isTradeValue(paramList[0])) return false; for (int i = (hasTradeValue ? 1 : 0); i < nParams; i++) if (!isInteger(paramList[i])) return false; return true; } private boolean parseExtraArgument() { String string = readUntilNextBraket(); if (!string.startsWith("-")) return false; if (!isNegativeInteger(string)) return false; if (!string.endsWith("]")) return false; input.backup(string.length()); return true; } private boolean isNegativeInteger(String text) { String number = text.substring(0, text.length() - 1); if (!number.startsWith("-")) return false; number = number.replace("-", ""); if (number.equals("0")) return false; return number.matches("^\\d+$"); } private boolean isInteger(String text) { if (text.contains(".") || text.contains(",")) return false; return text.matches("^\\d+$"); } private static Pattern TRADE_VALUE = Pattern.compile("open|close|high|low|volume|spread"); private boolean isTradeValue(String string) { return TRADE_VALUE.matcher(string).matches(); } }