package cn.liutils.ripple.impl.compiler; import java.io.IOException; import java.io.LineNumberReader; import java.io.PushbackReader; import java.io.Reader; import java.util.ArrayList; import java.util.List; import cn.liutils.ripple.IFunction; import cn.liutils.ripple.Path; import cn.liutils.ripple.RippleException.RippleCompilerException; import cn.liutils.ripple.ScriptProgram; import cn.liutils.ripple.impl.compiler.Token.MultiCharSymbol; public class Parser { public static class ScriptObject { public Object value; public String path; public IFunction func; public int funcArgNum; } public ScriptProgram program; private Reader inputReader; private LineNumberReader lineNumberReader; private PushbackReader reader; private Token currentToken; // Used for debug private final String scriptName; private int lineNumber = 0; private ArrayList<ScriptObject> parsedObject = new ArrayList(); private Path currentPath = new Path(null); private Parser(ScriptProgram program, Reader input, String scriptName) { this.program = program; this.scriptName = scriptName; this.inputReader = input; this.lineNumberReader = new LineNumberReader(input); this.reader = new PushbackReader(lineNumberReader); this.currentToken = new Token(); } //parse private void parseProgram() throws IOException { this.readToken(); this.parseNamespace(); if (!this.currentToken.isEOS()) { throw new RippleCompilerException("Invalid token. Should be end of stream but got" + currentToken, this); } this.reader.close(); this.lineNumberReader.close(); this.inputReader.close(); } private Path parsePath(String first) throws IOException { StringBuilder sb = new StringBuilder(); sb.append(first); //first has been skipped while (currentToken.isSingleChar('.')) { sb.append('.'); this.readToken(); if (!currentToken.isIdentifier()) { throw new RippleCompilerException("Invalid token. Should be identifier", this); } sb.append(currentToken.str); this.readToken(); } return new Path(sb.toString()); } private void parseNamespace() throws IOException { while (currentToken.isIdentifier()) { String name = currentToken.str; this.readToken(); Path path = this.parsePath(name); if (currentToken.isSingleChar('{')) { this.readToken(); Path parentPath = currentPath; currentPath = new Path(parentPath, path); this.parseNamespace(); this.currentPath = parentPath; if(currentToken.isInteger() || currentToken.isDouble()) { this.parseValue(new Path(currentPath, path)); } if (!currentToken.isSingleChar('}')) { throw new RippleCompilerException("Invalid token. Should be '}' but got " + currentToken, this); } this.readToken(); } else if (currentToken.isSingleChar('(')) { this.parseFunction(new Path(currentPath, path)); } else { throw new RippleCompilerException("Invalid token. Should be function or namespace", this); } } } private void parseFunction(Path functionPath) throws IOException { CodeGenerator gen = new CodeGenerator(this, functionPath); //param list this.readToken(); int nargs = 0; if (!currentToken.isSingleChar(')')) { //has params while (true) { if (!currentToken.isIdentifier()) { throw new RippleCompilerException("Invalid token. Should be parameter name", this); } gen.addParameter(currentToken.str); ++nargs; this.readToken(); if (currentToken.isSingleChar(')')) { break; } if (!currentToken.isSingleChar(',')) { throw new RippleCompilerException("Invalid token. Should be ','", this); } this.readToken(); } } this.readToken(); //skip ')' if (!currentToken.isSingleChar('{')) { throw new RippleCompilerException("Invalid token. Should be '{'", this); } this.readToken(); //skip '{' gen.functionBodyBegin(); this.parseExpression(gen); IFunction f = gen.functionBodyEnd(); if (!currentToken.isSingleChar('}')) { throw new RippleCompilerException("Invalid token. Should be '}'", this); } this.readToken(); ScriptObject obj = new ScriptObject(); obj.path = functionPath.path; obj.func = f; obj.funcArgNum = nargs; // System.out.println("Parsed function " + functionPath.path); this.parsedObject.add(obj); } private void parseValue(Path valuePath) throws IOException { ScriptObject obj = new ScriptObject(); obj.path = valuePath.path; if(currentToken.isDouble()) obj.value = currentToken.doubleValue; else if(currentToken.isInteger()) obj.value = currentToken.integerValue; else throw new RippleCompilerException("Invalid value when parsing value: " + currentToken, this); this.readToken(); // System.out.println("Parsed value " + valuePath.path); this.parsedObject.add(obj); } private void parseExpression(CodeGenerator gen) throws IOException { this.parseSubExpr(gen, 0); } private void parseSubExpr(CodeGenerator gen, int priority) throws IOException { UnaryOperator unop = currentToken.toUnOp(); if (unop != UnaryOperator.UNKNOWN) { this.readToken(); this.parseSubExpr(gen, BinaryOperator.MAX_PRIORITY); gen.calcUnary(unop); } else { this.parseSimpleExpr(gen); } BinaryOperator binop = currentToken.toBinOp(); while (binop.priority > priority) { this.readToken(); this.parseSubExpr(gen, binop.priority); gen.calcBinary(binop); binop = currentToken.toBinOp(); } } private void parseSimpleExpr(CodeGenerator gen) throws IOException { if (currentToken.isKeyword("true")) { gen.pushBooleanConst(true); this.readToken(); } else if (currentToken.isKeyword("false")) { gen.pushBooleanConst(false); this.readToken(); } else if (currentToken.isInteger()) { gen.pushIntegerConst(currentToken.integerValue); this.readToken(); } else if (currentToken.isDouble()) { gen.pushDoubleConst(currentToken.doubleValue); this.readToken(); } else if (currentToken.isSingleChar('(')) { this.readToken(); this.parseExpression(gen); if (!currentToken.isSingleChar(')')) { throw new RippleCompilerException("Invalid token. Should be ')'", this); } this.readToken(); } else if (currentToken.isKeyword("switch")) { this.readToken(); if (!currentToken.isSingleChar('(')) { throw new RippleCompilerException("Invalid token. Should be '('", this); } this.readToken(); this.parseExpression(gen); if (!currentToken.isSingleChar(')')) { throw new RippleCompilerException("Invalid token. Should be ')'", this); } this.readToken(); gen.pushSwitchBlock(); if (!currentToken.isSingleChar('{')) { throw new RippleCompilerException("Invalid token. Should be '{'", this); } do { this.readToken(); if (currentToken.isKeyword("default")) { this.readToken(); gen.switchCaseDefault(); } else if (currentToken.isKeyword("when")) { this.readToken(); this.parseExpression(gen); gen.switchCase(true); } else { this.parseExpression(gen); gen.switchCase(false); } if (!currentToken.isSingleChar(':')) { throw new RippleCompilerException("Invalid token. Should be ':'", this); } this.readToken(); this.parseExpression(gen); gen.switchCaseEnd(); } while (currentToken.isSingleChar(';')); if (!currentToken.isSingleChar('}')) { throw new RippleCompilerException("Invalid token. Should be '}'", this); } this.readToken(); gen.popSwitchBlock(); } else if (currentToken.isIdentifier()) { String name = currentToken.str; this.readToken(); if (currentToken.isSingleChar('.') || currentToken.isSingleChar('(')) { Path path = this.parsePath(name); if (currentToken.isSingleChar('(')) { gen.beforeCallFunction(Path.concatenate(this.currentPath, path)); this.readToken(); int nargs = 0; if (!currentToken.isSingleChar(')')) { ++nargs; this.parseExpression(gen); while (currentToken.isSingleChar(',')) { this.readToken(); ++nargs; this.parseExpression(gen); } if (!currentToken.isSingleChar(')')) { throw new RippleCompilerException("Invalid token. Should be ')'", this); } } this.readToken(); //skip ')' gen.afterCallFunction(nargs); } else { //namespace value throw new RippleCompilerException("Value reference is not supported", this); } } else { //param gen.pushParameter(name); } } else { throw new RippleCompilerException("Invalid token. Should be expression", this); } } //lex private char peekChar() throws IOException { char c = (char) reader.read(); reader.unread(c); return c; } private void readNumber(char first) throws IOException { StringBuilder sb = new StringBuilder(); sb.append(first); char c; while (Character.isDigit(c = peekChar())) { reader.read(); sb.append(c); } if (peekChar() == '.') { reader.read(); sb.append('.'); while (Character.isDigit(c = peekChar())) { reader.read(); sb.append(c); } String resultStr = sb.toString(); if (resultStr.endsWith(".")) { throw new RippleCompilerException("Invalid number format", this); } //double currentToken.setDouble(Double.parseDouble(resultStr)); return; } //integer currentToken.setInteger(Integer.parseInt(sb.toString())); } private void readIdentifier(char first) throws IOException { StringBuilder sb = new StringBuilder(); sb.append(first); char c = peekChar(); while (Character.isLetterOrDigit(c) || c == '_') { reader.read(); sb.append(c); c = peekChar(); } currentToken.setString(sb.toString()); } private void skipLine() throws IOException { char c = peekChar(); while (c != '\r' && c != '\n') { reader.read(); c = peekChar(); } } private void readToken() throws IOException { int charEnd = reader.read(); char c; if (charEnd == -1) { currentToken.setEOS(); return; } c = (char) charEnd; switch (c) { //single char case '+': case '-': case '*': case '/': case '(': case ')': case '{': case '}': case '.': case ',': case ';': case ':': case '=': //currently no == case '&': case '|': currentToken.setSingleChar(c); break; //followed by '=' case '>': if (peekChar() == '=') { currentToken.setMultiChar(MultiCharSymbol.S_GE); reader.read(); } else { currentToken.setSingleChar(c); } break; case '<': if (peekChar() == '=') { currentToken.setMultiChar(MultiCharSymbol.S_LE); reader.read(); } else { currentToken.setSingleChar(c); } break; case '!': if (peekChar() == '=') { currentToken.setMultiChar(MultiCharSymbol.S_NE); reader.read(); } else { currentToken.setSingleChar(c); } break; //number case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': readNumber(c); break; case '#': skipLine(); readToken(); //read again return; default: if (Character.isWhitespace(c)) { readToken(); //read again if(c == '\n' || c == '\r') lineNumber++; return; } else if (Character.isLetter(c) || c == '_') { readIdentifier(c); return; } else { throw new RippleCompilerException("Unknown token", this); } } } //api public static List<ScriptObject> parse(ScriptProgram program, Reader input, String scriptName) { Parser p = new Parser(program, input, scriptName); try { p.parseProgram(); } catch (IOException e) { throw new RippleCompilerException(e, p); } return p.parsedObject; } //debug public int getLineNumber() { return lineNumber; } public String getScriptName() { return scriptName; } }