/** We here treat the property values as expressions: padding: {expression} {expression} {expression}; margin: {expression}; */ func (parser *Parser) ParseExpr(inParenthesis bool) ast.Expr { var pos = parser.Pos // plus or minus. This creates an unary expression that holds the later term. // this is for: +3 or -4 var expr ast.Expr = nil if tok := parser.acceptAnyOf2(ast.T_PLUS, ast.T_MINUS); tok != nil { if term := parser.ParseTerm(); term != nil { expr = ast.NewUnaryExpr(ast.NewOpWithToken(tok), term) if uexpr, ok := expr.(*ast.UnaryExpr); ok { // if it's evaluatable just return the evaluated value. if val, ok := runtime.ReduceExpr(uexpr, parser.GlobalContext); ok { expr = ast.Expr(val) } } } else { parser.restore(pos) return nil } } else { expr = parser.ParseTerm() } if expr == nil { debug("ParseExpr failed, got %+v, restoring to %d", expr, pos) parser.restore(pos) return nil } var rightTok = parser.peek() for rightTok.Type == ast.T_PLUS || rightTok.Type == ast.T_MINUS || rightTok.Type == ast.T_LITERAL_CONCAT { // accept plus or minus parser.advance() if rightTerm := parser.ParseTerm(); rightTerm != nil { // XXX: check parenthesis var bexpr = ast.NewBinaryExpr(ast.NewOpWithToken(rightTok), expr, rightTerm, inParenthesis) if val, ok := runtime.ReduceExpr(bexpr, parser.GlobalContext); ok { expr = ast.Expr(val) } else { // wrap the existing expression with the new binary expression object expr = ast.Expr(bexpr) } } else { panic(SyntaxError{ Reason: "Expecting term on the right side", ActualToken: parser.peek(), File: parser.File, }) } rightTok = parser.peek() } return expr }
/* Parse string literal expression (literal concat with interpolation) */ func (parser *Parser) ParseLiteralExpr() ast.Expr { if expr := parser.ParseExpr(false); expr != nil { for tok := parser.accept(ast.T_LITERAL_CONCAT); tok != nil; tok = parser.accept(ast.T_LITERAL_CONCAT) { var rightExpr = parser.ParseExpr(false) if rightExpr == nil { panic(SyntaxError{ Reason: "Expecting expression or ident after the literal concat operator.", ActualToken: parser.peek(), File: parser.File, }) } expr = ast.NewLiteralConcat(expr, rightExpr) } // Check if the expression is reduce-able // For now, division looks like CSS slash at the first level, should be string. if runtime.CanReduceExpr(expr) { if reducedExpr, ok := runtime.ReduceExpr(expr, parser.GlobalContext); ok { return reducedExpr } } else { // Return expression as css slash syntax string // TODO: re-visit here later return runtime.EvaluateExpr(expr, parser.GlobalContext) } // if we can't evaluate the value, just return the expression tree return expr } return nil }
func (parser *Parser) ParseAssignStmt() ast.Stmt { var variable = parser.ParseVariable() // skip ":", T_COLON token parser.expect(ast.T_COLON) // Expecting semicolon at the end of the statement var valExpr = parser.ParseValue(ast.T_SEMICOLON) if valExpr == nil { panic(SyntaxError{ Reason: "Expecting value after variable assignment.", ActualToken: parser.peek(), File: parser.File, }) } // Optimize the expression only when it's an expression // TODO: for expression inside a map or list we should also optmise them too if bexpr, ok := valExpr.(ast.BinaryExpr); ok { if reducedExpr, ok := runtime.ReduceExpr(bexpr, parser.GlobalContext); ok { valExpr = reducedExpr } } else if uexpr, ok := valExpr.(ast.UnaryExpr); ok { if reducedExpr, ok := runtime.ReduceExpr(uexpr, parser.GlobalContext); ok { valExpr = reducedExpr } } // FIXME // Even we can visit the variable assignment in the AST visitors but if we // could save the information, we can reduce the effort for the visitors. /* if currentBlock := parser.GlobalContext.CurrentBlock(); currentBlock != nil { currentBlock.GetSymTable().Set(variable.Name, valExpr) } else { panic("nil block") } */ var stm = ast.NewAssignStmt(variable, valExpr) parser.ParseFlags(stm) parser.accept(ast.T_SEMICOLON) return stm }
/* Parse the SASS @for statement. @for $var from <start> to <end> { } @for $var from <start> through <end> { } @see http://sass-lang.com/documentation/file.SASS_REFERENCE.html#_10 */ func (parser *Parser) ParseForStmt() ast.Stmt { parser.expect(ast.T_FOR) // get the variable token var variable = parser.ParseVariable() var stm = ast.NewForStmt(variable) if parser.accept(ast.T_FOR_FROM) != nil { var fromExpr = parser.ParseExpr(true) if reducedExpr, ok := runtime.ReduceExpr(fromExpr, parser.GlobalContext); ok { fromExpr = reducedExpr } stm.From = fromExpr // "through" or "to" var tok = parser.next() if tok.Type != ast.T_FOR_THROUGH && tok.Type != ast.T_FOR_TO { panic(SyntaxError{ Reason: "Expecting 'through' or 'to' of range syntax.", ActualToken: tok, File: parser.File, }) } var endExpr = parser.ParseExpr(true) if reducedExpr, ok := runtime.ReduceExpr(endExpr, parser.GlobalContext); ok { endExpr = reducedExpr } if tok.Type == ast.T_FOR_THROUGH { stm.Through = endExpr } else if tok.Type == ast.T_FOR_TO { stm.To = endExpr } } else if parser.accept(ast.T_FOR_IN) != nil { var fromExpr = parser.ParseExpr(true) if reducedExpr, ok := runtime.ReduceExpr(fromExpr, parser.GlobalContext); ok { fromExpr = reducedExpr } stm.From = fromExpr parser.expect(ast.T_RANGE) var endExpr = parser.ParseExpr(true) if reducedExpr, ok := runtime.ReduceExpr(endExpr, parser.GlobalContext); ok { endExpr = reducedExpr } stm.To = endExpr } if b := parser.ParseDeclBlock(); b != nil { stm.Block = b } else { panic("The @for statement expecting block after the range syntax") } return stm }