package com.seaofnodes.simple.parser; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.parser.Parser.ParseException; import com.seaofnodes.simple.type.*; import static com.seaofnodes.simple.util.Utils.TODO; /** Recursive-descent expression precedence, from assignment down to primaries. */ final class Expressions { private final Parser P; Expressions(Parser P) { this.P = P; } /** Parse: [name '='] expr */ Node parseAssignment() { int old = P.pos(); String name = P._lexer.matchId(); // Just a plain expression, no assignment. // Distinguish `var==expr` from `var=expr` if( name==null || Parser.KEYWORDS.contains(name) || !P.matchOpx('=','=') ) { P.pos(old); return parseExpression(); } // Find variable to update Var def = P._scope.lookup(name); if( def==null ) throw P.error("Undefined name '" + name + "'"); if( def._extern!=0 ) // Parse the C binding as a memory lvalue { P.pos(old); return parseExpression(); } P.references.checkAssignment(def); return P.references.assign(def,parseAssignment()); } /** * Parse an expression of the form: * *
     *     expression : logicalExpression ('?' assignment (':' assignment)?)?
     * 
* @return an expression {@link Node}, never {@code null} */ private Node parseExpression() { Node expr = parseLogical(); return P.match("?") ? P.controlflow.conditional(expr,":") : expr; } /** * Parse a logical expression * *
     *     logicalExpression : bitwiseExpression (('&&' | '||') logicalExpression)?
     * 
* @return a bitwise expression {@link Node}, never {@code null} */ Node parseLogical() { Node lhs = parseBitwise(); while (true) { if( P.match("&&") ) lhs = P.controlflow.conditional(lhs, "&&"); else if( P.match("||") ) lhs = P.controlflow.conditional(lhs, "||"); else break; } return lhs; } private Node parseBitwise() { Node lhs = parseEquality(); while( true ) { if( P.matchOp('&') ) lhs = new AndNode(P.loc(),lhs,null); else if( P.matchOp('|') ) lhs = new OrNode(P.loc(),lhs,null); else if( P.match ("^") ) lhs = new XorNode(P.loc(),lhs,null); else break; lhs.setDef(2,parseEquality()); lhs = P.peep(lhs); } return lhs; } /** * Parse an eq/ne expression */ private Node parseEquality() { Node lhs = parseComparison(); while( true ) { boolean eq = false; if( P.match("==") ) eq = true; else if( !P.match("!=") ) break; lhs.keep(); Node rhs = parseComparison(); lhs = P.peep(new BoolNode.EQ(lhs.unkeep(),rhs)); if( !eq ) lhs = P.peep(new NotNode(lhs)); } return lhs; } /** * Parse an expression of the form: * *
     *     expr : shiftExpr < shiftExpr <= shiftExpr...
     *     expr : shiftExpr > shiftExpr >= shiftExpr...
     * 
* @return an comparator expression {@link Node}, never {@code null} */ private Node parseComparison() { Node lhs = parseShift(); int dir = parseCompDir(); // No compare if( dir==0 ) return lhs; lhs.keep(); // Compare Node rhs = parseShift().keep(); Node cmp = makeCompBool(dir,lhs.unkeep(),rhs).keep(); // Convert to a bool // Stacked compares? int dir0 = parseCompDir(); if( dir0 == 0 ) { rhs.unkill(); return cmp.unkeep(); } // Keep the middle operand separately from the boolean result. // The next test compares adjacent operands, not that boolean. Node ifNode = new IfNode(P.ctrl(), cmp.unkeep()).peephole(); Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); // False side does nothing but capture memory & side-effects ScopeNode fail = P._scope.dup(); fail.ctrl(ifF); // Loop over stacked compares. Each loop moves the old RHS to become // the new LHS, so e.g. (a < b < c < d) compares (LHS:a < RHS:b) then // compares (LHS:b < RHS:c) then (LHS:c < RHS:d), etc. while( dir0 != 0 ) { if( Math.abs(dir) != Math.abs(dir0) ) throw P.error("Mixing relational directions in a chained relational test"); // True side parses next arm of test P.ctrl(ifT.unkeep()); lhs = rhs; // Reuse the previous RHS exactly once rhs = parseShift().keep(); cmp = makeCompBool(dir0,lhs.unkeep(),rhs); // Convert to a bool ifNode = new IfNode(P.ctrl(), cmp).peephole(); ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); // Merge result into the fail case P.ctrl(ifF); fail.mergeScopes(P._scope.dup(), P.loc()).peephole(); // Check next stacked compare dir0 = parseCompDir(); } rhs.unkill(); P.ctrl(ifT.unkeep()); RegionNode r = fail.mergeScopes(P._scope, P.loc()).init(); P._scope = fail; return new PhiNode("", r,P.con(0),P.con(1)).peephole(); } private int parseCompDir() { if( P.match("<=") ) return -1; // Negative for "equals-or-less" if( P.match(">=") ) return -2; // 1 for <, 2 for > if( P.match("<" ) ) return 1; // Positive for just "less" if( P.match(">" ) ) return 2; return 0; } private Node makeCompBool( int dir, Node lhs, Node rhs ) { if( Math.abs(dir)==2 ) { Node tmp = lhs; lhs = rhs; rhs = tmp; } Node cmp = dir < 0 ? new BoolNode.LE(lhs,rhs) : new BoolNode.LT(lhs,rhs); return P.peep(cmp); } /** * Parse an additive expression * *
     *     shiftExpr : additiveExpr (('<<' | '>>' | '>>>') additiveExpr)*
     * 
* @return a shift expression {@link Node}, never {@code null} */ private Node parseShift() { Node lhs = parseAddition(); while( true ) { if( P.match("<<") ) lhs = new ShlNode(P.loc(),lhs,null); else if( P.match(">>>")) lhs = new ShrNode(P.loc(),lhs,null); else if( P.match(">>") ) lhs = new SarNode(P.loc(),lhs,null); else break; lhs.setDef(2,parseAddition()); ParseException err; if( (err = lhs.err()) != null ) throw err; lhs = P.peep(lhs); } return lhs; } /** * Parse an additive expression * *
     *     additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
     * 
* @return an add expression {@link Node}, never {@code null} */ private Node parseAddition() { Node lhs = parseMultiplication(); while( true ) { if( P.match("+") ) lhs = new AddNode(lhs,null); else if( P.match("-") ) lhs = new SubNode(lhs,null); else break; lhs.setDef(2,parseMultiplication()); lhs = P.peep(lhs); } return lhs; } /** * Parse an multiplicativeExpr expression * *
     *     multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
     * 
* @return a multiply expression {@link Node}, never {@code null} */ private Node parseMultiplication() { var lhs = parseUnary(); while( true ) { if( P.match("*") ) lhs = new MulNode(lhs,null); else if( P.match("/") ) lhs = new DivNode(lhs,null); else break; lhs.setDef(2,parseUnary()); lhs = P.peep(lhs); } return lhs; } /** * Parse a unary minus expression. * *
     *     unaryExpr : ('-') unaryExpr | '!') unaryExpr | postfixExpr | primaryExpr | '--' Id | '++' Id
     * 
* @return a unary expression {@link Node}, never {@code null} */ private Node parseUnary() { // Pre-dec/pre-inc int old = P.pos(); if( P.match("--") || P.match("++") ) { int delta = P._lexer.peek(-1)=='+' ? 1 : -1; // Inc vs Dec int start = P.pos(); String name = P._lexer.matchId(); Var var = name==null ? null : P._scope.lookup(name); if( var!=null && !(var.type() instanceof TypeMemPtr) ) { if( var._extern==0 && var._final && !P._scope.canInit(var) ) throw P.error("Cannot reassign final '"+var._name+"'"); // Resolve fields just as a read or postfix update does. Updating // their declaration's local slot would silently lose the Store. P.pos(start); return parsePrimary(delta); } // Reset, try again P.pos(old); } if (P.match("-")) return P.peep(new MinusNode(parseUnary())); if (P.match("!")) return P.peep(new NotNode(parseUnary())); // Else be a primary expression return parsePrimary(); } /** * Parse a primary expression: * *
     *     primaryExpr : integerLiteral | "string" | 'char' | true | false | null |
     *                   new Type | '(' expression ')' | Id['++','--'] |
     *                   type['.' Id]*
     * 
* @return a primary {@link Node}, never {@code null} */ private Node parsePrimary() { return parsePrimary(0); } // pre is +/-1 for a prefix update of a known name, zero otherwise. private Node parsePrimary(int pre) { if( P._lexer.isNumber(P._lexer.peek()) ) return parseLiteral(); if( P._lexer.peek('"') ) return parsePostfix(P.allocations.newString(P._lexer.parseString())); if( P.matchx("true" ) ) return P.con(1); if( P.matchx("false") ) return P._code.ZERO; if( P.matchx("null" ) ) return P.con(Type.NIL); if( P.match ("'" ) ) return parseChar(); if( P.match ("(" ) ) return parsePostfix(P.require(parseAssignment(), ")")); if( P.matchx("new" ) ) return parsePostfix(P.allocations.parseNew()); if( P.match ("{" ) ) return parsePostfix(P.require(P.functions.parseFunction(),"}")); int pos = P.pos(); // Expect an identifier now String id = P._lexer.matchId(); if( id == null || Parser.KEYWORDS.contains(id) ) throw P.errorSyntax("an identifier or expression"); int pos2 = P.pos(); return P.references.parseName(id,pre,pos,pos2); } // Check for assign-update, "x += e0". // Returns "x + e0"; the caller assigns value. // if ch is postfix, then lhs is kept and caller will unkeep and return it. Node opAssign( char ch, Node lhs, Type t ) { // RHS of the update. lhs.keep(); // Alive across parseAssignment Node rhs = (byte)ch == 1 ? P.con( 1) : // var++ (byte)ch == -1 ? P.con(-1) : // var-- parseAssignment(); // var op= rhs if( !Parser.postfix(ch) ) lhs.unkeep(); // Allow to die in next peep // 4 cases: // int + int ==>> narrow int // int + flt ==>> error, caller must fail assigning flt into int // flt + int ==>> use float op, wrap toFloat() // flt + flt ==>> use float op Node op = switch(ch) { case 1, (char)-1, '+' -> new AddNode(lhs,rhs); case '-' -> new SubNode(lhs,rhs); case '*' -> new MulNode(lhs,rhs); case '/' -> new DivNode(lhs,rhs); case '|' -> new OrNode(null,lhs,rhs); case '&' -> new AndNode(null,lhs,rhs); case '^' -> new XorNode(P.loc(),lhs,rhs); case Lexer.SHL -> new ShlNode(P.loc(),lhs,rhs); case Lexer.SAR -> new SarNode(P.loc(),lhs,rhs); case Lexer.SHR -> new ShrNode(P.loc(),lhs,rhs); default -> throw TODO(); }; // Convert to float ops, or narrow int types; error if not declared type. // Also, if postfix LHS is still keep() return P.conversions.convertExpr(P.peep(op),t); } /** * Parse postfix expression; this can be a field expression, an array * lookup or a postfix operator like '#' * *
     *     expr ('.' FIELD)* [ = expr ]       // Field reference
     *     expr '#'                           // Postfix unary read operator
     *     expr ['++' | '--' ]                // Postfix unary write operator
     *     expr ('[' expr ']')* = [ = expr ]  // Array reference
     *     expr '(' [args,]* ')'              // Function call
     * 
*/ Node parsePostfixMethod(Node expr, Node self) { // TODO: This is too broad because the parser lacks strong types here. // Some normal functions can end up with a "self" argument. // Self method call? Self is a TMP-not-CLZ, then assume and pass self. if( P.match("(") ) { expr = parsePostfix(P.require(P.functions.functionCall(expr,self),")")); } else if( expr != self ) self.isKill(); // Not a method call, no need for self return parsePostfix(expr); } Node parsePostfix(Node expr) { P._lexer.skipWhiteSpace(); Lexer site = P.loc(); String name; if( P.match(".") ) name = P.requireId(); else if( P.match("#") ) name = "#"; else if( P.match("[") ) name = "[]"; // Can get here without an explicit receiver: "f()" or // "self.method()(next)(next)(next)". Pass the current function self. else if( P.match("(") ) return parsePostfix(P.require(P.functions.functionCall(expr,P.references.defaultSelf()),")")); else return expr; // No postfix return P.references.parseField(expr,name,0,site); } /** * Parse integer literal * *
     *     integerLiteral: [1-9][0-9]* | [0]
     *     floatLiteral: [digits].[digits]?[e [digits]]?[f]?
     * 
*/ private ConstantNode parseLiteral() { return P.con(P._lexer.parseNumber()); } // Already parsed "'" Node parseChar() { return P.require(P.con(TypeInteger.constant(P._lexer.parseChar())),"'"); } }