Konstantin8105/f4go

View on GitHub
fortran/expression.go

Summary

Maintainability
C
1 day
Test Coverage
package fortran

import (
    "fmt"
    goast "go/ast"
    goparser "go/parser"
    "go/token"
    "strconv"
    "strings"
)

func nodesToString(nodes []node) (str string) {
    for _, n := range nodes {
        switch n.tok {
        case
            token.LSS,    // <
            token.GTR,    // >
            token.LEQ,    // <=
            token.GEQ,    // >=
            token.NOT,    // !
            token.NEQ,    // !=
            token.EQL,    // ==
            token.LAND,   // &&
            token.LOR,    // ||
            token.ASSIGN: // =
            str += " " + view(n.tok)
        default:
            str += " " + string(n.b)
        }
    }
    return strings.TrimSpace(str)
}

func (p *parser) parseExpr(start, end int) (expr goast.Expr) {
    return p.parseExprNodes(p.ns[start:end])
}

func (p *parser) parseExprNodes(in []node) (expr goast.Expr) {

    for i := 0; i < len(in); i++ {
        if in[i].tok == ftNewLine {
            p.addError("NEW_LINE is not acceptable inside expression : " +
                nodesToString(in))
        }
    }

    base := make([]node, len(in))
    copy(base, in)

    nodes := make([]node, len(in))
    copy(nodes, in)

    p.fixFakeParen(&nodes)
    p.fixArrayVariables(&nodes)
    if m, ok := p.fixVectorExplode(&nodes); ok {
        nodes = m
    }
    p.fixDoubleStar(&nodes)
    p.fixString(&nodes)
    p.fixComplexValue(&nodes)
    p.fixIdent(&nodes)
    p.fixConcatString(&nodes)

    str := nodesToString(nodes)

    //use std package go/parser for change to parse expression
    ast, err := goparser.ParseExpr(str)
    if err != nil {
        out := "Cannot parse Expression : "
        if len(base) > 0 {
            out += fmt.Sprintf("pos {%v} ", base[0].pos)
        }
        out += fmt.Sprintf("`%s`\t`%s`\t`%s`",
            nodesToString(base), str, err)
        return goast.NewIdent(str)
    }

    p.fixComplexRealOperation(ast)

    return ast
}

func (p *parser) isVariable(name string) bool {
    _, ok := p.initVars.get(name)
    return ok
}

func (p *parser) isArrayVariable(name string) bool {
    if v, ok := p.initVars.get(name); ok {
        if v.typ.isArray() || v.typ.baseType == "string" {
            return true
        }
    }
    return false
}

// change  `(/` and `/)` to `((` and `))`
func (p *parser) fixFakeParen(nodes *[]node) {
    var foundBegin bool
    var begin int
    for i := 1; i < len(*nodes); i++ {
        if !foundBegin {
            if (*nodes)[i-1].tok == token.LPAREN && (*nodes)[i].tok == token.QUO {
                begin = i
                foundBegin = !foundBegin
            }
            continue
        }
        if (*nodes)[i-1].tok == token.QUO && (*nodes)[i].tok == token.RPAREN {
            foundBegin = false
            (*nodes)[begin].tok, (*nodes)[begin].b = token.LPAREN, []byte("(")
            (*nodes)[i-1].tok, (*nodes)[i-1].b = token.RPAREN, []byte(")")
            continue
        }
    }
}

// fixArrayVariables - change tokens of array
// From : ... | NAME | ( | I |   ,   | J | ) | ...
// To   : ... | NAME | [ | I | ] | [ | J | ] | ...
func (p *parser) fixArrayVariables(nodes *[]node) {
    var positions []int
    // find all arrays
    for i, n := range *nodes {
        if n.tok == token.IDENT && p.isArrayVariable(string(n.b)) {
            positions = append(positions, i)
        }
    }

    if len(positions) == 0 {
        return
    }

    // modify tokens
    pos := 0
    for {
        var v varInitialization
        for ; pos < len(*nodes); pos++ {
            if (*nodes)[pos].tok == token.IDENT {
                var ok bool
                if v, ok = p.initVars.get(string((*nodes)[pos].b)); ok &&
                    (p.getArrayLen(v.name) > 0 || v.typ.baseType == "string") {
                    break
                }
            }
        }
        if pos >= len(*nodes) {
            break
        }
        if pos+1 >= len(*nodes) || (*nodes)[pos+1].tok != token.LPAREN {
            // Example:
            //  ingeter c(10)
            //  call func(c) ! in function no LPAREN
            pos += 1
            continue
        }
        pos += 1
        args, end := separateArgsParen((*nodes)[pos:])

        // inject nodes
        var inject []node
        for i, a := range args {
            begin := p.getArrayBegin(v.name, i)
            for j := range a {
                if a[j].tok == token.COLON {
                    // from : S[ I:I ]
                    // to   : S[ I ]
                    if j == 1 && len(a) == 3 {
                        a[j].tok, a[j].b = token.STRING, []byte(" ")
                        a[j+1].tok, a[j+1].b = token.STRING, []byte(" ")
                        // a = append(a[:j], append([]node{
                        //     {tok: token.SUB, b: []byte("-")},
                        //     {tok: token.LPAREN, b: []byte("(")},
                        //     {
                        //         tok: token.INT,
                        //         b:   []byte(strconv.Itoa(begin)),
                        //     },
                        //     {tok: token.RPAREN, b: []byte(")")},
                        // }, a[j:]...)...)
                    }
                }
            }
            inject = append(inject, node{tok: token.LBRACK, b: []byte("[")})
            inject = append(inject, a...)
            inject = append(inject, []node{
                {tok: token.SUB, b: []byte("-")},
                {tok: token.LPAREN, b: []byte("(")},
                {
                    tok: token.INT,
                    b:   []byte(strconv.Itoa(begin)),
                },
                {tok: token.RPAREN, b: []byte(")")},
            }...)
            inject = append(inject, node{tok: token.RBRACK, b: []byte("]")})
        }

        (*nodes) = append((*nodes)[:pos], append(inject, (*nodes)[pos+end:]...)...)
        pos += end
    }
}

// Example:
// ( ( D ( I , J ) , J = 1 , 4 ) , I = 1 , 4 )
// =                             = = = = = = =
//
// Sign is change behavior ( KFIN ( 1 , J ) , J = - 40 , 40 )
func (p *parser) fixVectorExplode(nodes *[]node) (merge []node, ok bool) {
    ns, ok := explodeFor(*nodes)
    if !ok {
        return
    }
    for i := range ns {
        merge = append(merge, ns[i]...)
        if i != len(ns)-1 {
            merge = append(merge, node{
                tok: token.COMMA,
                b:   []byte{','},
            })
        }
    }
    return merge, true
}

// Examples:
//   SD2 / GAM ** 2
//   DSQRT ( ( DA / SCALE ) ** 2 + ( DB / SCALE ) ** 2 )
func (p *parser) fixDoubleStar(nodes *[]node) {
    var haveDoubleStar bool
    var pos int // saving last position of DOUBLE_STAR
    for i, n := range *nodes {
        switch n.tok {
        case ftDoubleStar: // **
            haveDoubleStar = true
            pos = i
        }
    }

    if !haveDoubleStar {
        return
    }

    // add package in source
    p.addImport("math")

    leftOther, leftVariable, rightVariable, rightOther := p.split(nodes, pos)

    // combine expression by next formula:
    // leftOther math.Pow(leftVariable , rightVariable) rightOther
    var comb []node
    comb = append(comb, leftOther...)
    comb = append(comb, []node{
        {tok: token.IDENT, b: []byte("math.Pow")},
        {tok: token.LPAREN, b: []byte("(")},
    }...)
    comb = append(comb, leftVariable...)
    comb = append(comb, node{tok: token.COMMA, b: []byte(",")})
    comb = append(comb, rightVariable...)
    comb = append(comb, node{tok: token.RPAREN, b: []byte(")")})
    comb = append(comb, rightOther...)

    *nodes = comb

    // again checking, because we can have a few DOUBLE_STAR
    p.fixDoubleStar(nodes)
}

func (p *parser) fixString(nodes *[]node) {
    for i := range *nodes {
        if (*nodes)[i].tok == token.STRING {
            if (*nodes)[i].b[0] == '\'' {
                (*nodes)[i].b[0] = '"'
            }
            if (*nodes)[i].b[len((*nodes)[i].b)-1] == '\'' {
                (*nodes)[i].b[len((*nodes)[i].b)-1] = '"'
            }
        }
    }
}

// parse complex init
// From :
// ( 1.0E+0 , 0.0E+0 )
// To :
//  1.0E+0 + 0.0E+0i
//
// Common pattern is:
//  ( .leftNodes. , .rightNodes. )
// Example of .nodes. :
//  IDENT
//  INT
//  FLOAT
//  ARRAY[...][...]
//  ( EXPRESSION )
//  EXPRESSION
//  FUNCTION (...)
func (p *parser) fixComplexValue(nodes *[]node) {
    var start, comma, end int
    var find bool
    for i := range *nodes {
        if i != 0 {
            if (*nodes)[i-1].tok == token.IDENT {
                // this is function
                continue
            }
        }
        // find (
        start = i
        if (*nodes)[i].tok != token.LPAREN {
            continue
        }
        i++
        // create function
        getNodes := func() bool {
            var j int
            for j = i; j < len(*nodes); j++ {
                var exit bool
                switch (*nodes)[j].tok {
                case token.LPAREN:
                    counter := 0
                    for ; j < len(*nodes); j++ {
                        switch (*nodes)[j].tok {
                        case token.LPAREN:
                            counter++
                        case token.RPAREN:
                            counter--
                        }
                        if counter == 0 {
                            break
                        }
                    }
                case ftNewLine, token.EOF:
                    return false
                case token.COMMA, token.RPAREN:
                    exit = true
                }
                if exit {
                    break
                }
            }
            i = j - 1
            return true
        }
        // find leftNodes
        if !getNodes() {
            continue
        }
        i++
        // is comma?
        comma = i
        if (*nodes)[i].tok != token.COMMA {
            continue
        }
        i++
        // find rightNodes
        if !getNodes() {
            continue
        }
        i++
        // if )
        end = i
        if (*nodes)[i].tok != token.RPAREN {
            continue
        }
        // all part of complex value if found
        find = true
        break
    }
    if !find {
        return
    }
    // combine new complex value interpretation
    var comb []node
    comb = append(comb, (*nodes)[:start]...)

    comb = append(comb, node{tok: token.LPAREN, b: []byte("(")})
    comb = append(comb, (*nodes)[start:comma]...)
    comb = append(comb, node{tok: token.ADD, b: []byte("+")})
    comb = append(comb, node{tok: token.LPAREN, b: []byte("(")})
    comb = append(comb, (*nodes)[comma+1:end]...)
    comb = append(comb, node{tok: token.RPAREN, b: []byte(")")})
    comb = append(comb, node{tok: token.MUL, b: []byte("*")})
    comb = append(comb, node{tok: token.FLOAT, b: []byte("1i")})
    comb = append(comb, node{tok: token.RPAREN, b: []byte(")")})
    comb = append(comb, (*nodes)[end:]...)

    *nodes = comb

    p.fixComplexValue(nodes)
}

func (p *parser) fixConcatString(nodes *[]node) {
    for {
        var pos int
        var found bool
        for i, n := range *nodes {
            if n.tok == ftStringConcat {
                pos = i
                found = true
            }
        }

        if !found {
            return
        }

        leftOther, leftVariable, rightVariable, rightOther := p.split(nodes, pos)

        // combine expression by next formula:
        // leftOther append(append([]byte,leftVariable),rightVariable) rightOther
        //           ---------------------            --             -
        var comb []node
        comb = append(comb, leftOther...)
        comb = append(comb, []node{
            {tok: token.IDENT, b: []byte("append")},
            {tok: token.LPAREN, b: []byte("(")},
            {tok: token.IDENT, b: []byte("append")},
            {tok: token.LPAREN, b: []byte("(")},
            {tok: token.LBRACK, b: []byte("[")},
            {tok: token.RBRACK, b: []byte("]")},
            {tok: token.IDENT, b: []byte("byte")},
            {tok: token.LBRACE, b: []byte("{")},
            {tok: token.RBRACE, b: []byte("}")},
            {tok: token.COMMA, b: []byte(",")},
        }...)
        comb = append(comb, leftVariable...)
        comb = append(comb, node{tok: token.RPAREN, b: []byte(")")})
        comb = append(comb, node{tok: token.COMMA, b: []byte(",")})
        comb = append(comb, rightVariable...)
        comb = append(comb, node{tok: token.RPAREN, b: []byte(")")})
        comb = append(comb, rightOther...)

        *nodes = comb
    }
}

func (p *parser) fixIdent(nodes *[]node) {
    for i := len(*nodes) - 1; i >= 0; i-- {
        if (*nodes)[i].tok != token.IDENT {
            continue
        }
        if i+1 < len(*nodes) && (*nodes)[i+1].tok == token.LPAREN {
            // for function
            continue
        }
        if s := string((*nodes)[i].b); s == "false" || s == "true" {
            continue
        }

        // from | IDENT  |
        // to   | LPAREN | STAR | IDENT | RPAREN |
        var comb []node
        comb = append(comb, (*nodes)[:i]...)
        comb = append(comb, node{tok: token.LPAREN, b: []byte("(")})
        comb = append(comb, node{tok: token.MUL, b: []byte("*")})
        comb = append(comb, (*nodes)[i])
        comb = append(comb, node{tok: token.RPAREN, b: []byte(")")})
        comb = append(comb, (*nodes)[i+1:]...)
        *nodes = comb
        i += 1
    }
}

// Example:
//
//  0  *ast.BinaryExpr {
//  1  .  X: *ast.Ident {
//  3  .  .  Name: "R"
//  8  .  }
//  9  .  OpPos: -
// 10  .  Op: *
// 11  .  Y: *ast.Ident {
// 13  .  .  Name: "CR"
// 15  .  }
// 16  }
//
//  0  *ast.BinaryExpr {
//  1  .  X: *ast.ParenExpr {
//  3  .  .  X: *ast.StarExpr {
//  5  .  .  .  X: *ast.Ident {
//  7  .  .  .  .  Name: "R"
// 12  .  .  .  }
// 13  .  .  }
// 15  .  }
// 16  .  OpPos: -
// 17  .  Op: *
// 18  .  Y: *ast.ParenExpr {
// 20  .  .  X: *ast.StarExpr {
// 22  .  .  .  X: *ast.Ident {
// 24  .  .  .  .  Name: "CR"
// 26  .  .  .  }
// 27  .  .  }
// 29  .  }
// 30  }
//
func (p *parser) fixComplexRealOperation(ast goast.Expr) {
    if be, ok := ast.(*goast.BinaryExpr); ok {
        xIsComplex, xOk := p.isComplex(be.X)
        yIsComplex, yOk := p.isComplex(be.Y)
        if xOk && yOk {
            if xIsComplex && !yIsComplex {
                be.Y = &goast.CallExpr{
                    Fun: goast.NewIdent("complex"),
                    Args: []goast.Expr{
                        be.Y,
                        goast.NewIdent("0"),
                    },
                }
            }
            if !xIsComplex && yIsComplex {
                be.X = &goast.CallExpr{
                    Fun: goast.NewIdent("complex"),
                    Args: []goast.Expr{
                        be.X,
                        goast.NewIdent("0"),
                    },
                }
            }
        }
    }
}

// Example:
//
//  0  *ast.CallExpr {
//  3  .  .  Name: "DBLE"
//  8  .  }
// 10  .  Args: []ast.Expr (len = 1) {
// 11  .  .  0: *ast.Ident {
// 13  .  .  .  Name: "CR"
// 15  .  .  }
// 16  .  }
// 19  }
//
// 0  *ast.Ident {
// 2  .  Name: "A"
// 7  }
//
// 18 *ast.ParenExpr {
// 20 X: *ast.StarExpr {
// 22 .  X: *ast.Ident {
// 24 .  .  Name: "CR"
// 26 .  }
// 27 }
//
func (p *parser) isComplex(e goast.Expr) (isComplex, ok bool) {
    if par, ok := e.(*goast.ParenExpr); ok {
        if st, ok := par.X.(*goast.StarExpr); ok {
            if id, ok := st.X.(*goast.Ident); ok {
                if v, ok := p.initVars.get(id.Name); ok {
                    if strings.Contains(v.typ.getBaseType(), "complex") {
                        return true, true
                    } else {
                        return false, true
                    }
                }
            }
        }
    }
    if id, ok := e.(*goast.Ident); ok {
        if v, ok := p.initVars.get(id.Name); ok {
            if strings.Contains(v.typ.getBaseType(), "complex") {
                return true, true
            } else {
                return false, true
            }
        }
    }
    if ce, ok := e.(*goast.CallExpr); ok {
        if id, ok := ce.Fun.(*goast.Ident); ok {
            switch id.Name {
            case "DBLE":
                return false, true
            }
        }
    }
    return false, false
}