Konstantin8105/f4go

View on GitHub
main.go

Summary

Maintainability
A
0 mins
Test Coverage
package main

import (
    "bytes"
    "flag"
    "fmt"
    "go/format"
    "go/token"
    "io/ioutil"
    "os"
    "os/exec"
    "regexp"
    "runtime"
    "runtime/debug"
    "strings"

    "github.com/Konstantin8105/f4go/fortran"
)

var (
    packageFlag  *string
    simplifyFlag *bool
    parallelFlag *int
    verboseFlag  *int
)

func init() {
    p := 1
    parallelFlag = &p
    v := 1
    verboseFlag = &v
}

func main() {
    packageFlag = flag.String("p",
        "main", "set the name of the generated package")
    simplifyFlag = flag.Bool("s",
        false, "simplify from (*I) to I of Golang file. NOT guaranteed to work correctly")
    parallelFlag = flag.Int("m",
        1, "enable parallelism in file processing. Default is only one core")
    verboseFlag = flag.Int("v",
        1, "0: error output, 1: print log, 2: print info, 3: print debug")
    run()
}

func run() {
    if packageFlag == nil {
        packageFlag = new(string)
    }

    // free 1 CPU for other computer stuff
    runtime.GOMAXPROCS(*parallelFlag)

    flag.Parse()
    fortran.Verbose = *verboseFlag
    if flag.NArg() < 1 {
        fortran.Errorf("Please run: f4go -h")
        return
    }

    if packageFlag == nil {
        var s string
        packageFlag = &s
    }
    var es []errorRow
    if *parallelFlag > 1 {
        es = parseParallel(flag.Args(), *packageFlag)
    } else {
        for _, s := range flag.Args() {
            fortran.Logf("parsing file %s\n", s)
            er := parse(s, *packageFlag, "")
            es = append(es, er...)
        }
    }

    for _, e := range es {
        fortran.Logf("%20s : %s\n", e.filename, e.err.Error())
    }
}

type errorRow struct {
    err      error
    filename string
}

func (err errorRow) Error() string {
    return fmt.Sprintf("%s : %v", err.filename, err.err)
}

// parsing to Go code
func parse(filename, packageName, goFilename string) (errR []errorRow) {
    defer func() {
        err := recover()
        if err != nil {
            fortran.Errorf("panic parsing %s: %s\n%s", filename, err, string(debug.Stack()))
            os.Exit(1)
        }
    }()

    if packageName == "" {
        packageName = "main"
    }

    // read fortran source
    dat, err := ioutil.ReadFile(filename)
    if err != nil {
        return []errorRow{
            {
                err:      fmt.Errorf("Cannot fortran source: %v", err),
                filename: filename,
            },
        }
    }

    // remove some end line symbols
    dat = bytes.Replace(dat, []byte{'\r'}, []byte{}, -1)
    dat = bytes.Replace(dat, []byte{'\015'}, []byte{}, -1)

    // parse fortran to go/ast
    ast, errs := fortran.Parse(dat, packageName)
    if len(errs) > 0 {
        for _, er := range errs {
            errR = append(errR, errorRow{
                err:      fmt.Errorf("Parsing error : %v", er.Error()),
                filename: filename,
            })
        }
    }

    // convert ast tree to string
    var buf bytes.Buffer
    if err = format.Node(&buf, token.NewFileSet(), &ast); err != nil {
        return []errorRow{{err: fmt.Errorf("Error go/format : %v", err), filename: filename}}
    }

    // generate filename of result
    if goFilename == "" {
        index := strings.LastIndex(filename, ".")
        goFilename = filename + ".go"
        if index > 0 {
            goFilename = filename[:index] + ".go"
        }
    }

    // save go source
    if err = ioutil.WriteFile(goFilename, buf.Bytes(), 0644); err != nil {
        return []errorRow{{err: fmt.Errorf("Cannot write Go source: %v", err), filename: filename}}
    }

    // gofmt simplification
    _, _ = exec.Command("gofmt", "-s", "-w", goFilename).CombinedOutput()

    // goimports
    _, _ = exec.Command("goimport", "-w", goFilename).CombinedOutput()

    if simplifyFlag == nil {
        simplifyFlag = new(bool)
    }
    if *simplifyFlag {
        simplify(goFilename)
    }

    return
}

func parseParallel(filenames []string, packageName string) (ess []errorRow) {
    var (
        jobs    = make(chan string, len(filenames))
        results = make(chan []errorRow, len(filenames))
    )

    for w := 1; w <= 2*runtime.NumCPU(); w++ {
        go func(jobs <-chan string, results chan<- []errorRow) {
            for job := range jobs {
                results <- parse(job, packageName, "")
            }
        }(jobs, results)
    }

    for _, f := range filenames {
        jobs <- f
    }
    close(jobs)

    for range filenames {
        ess = append(ess, <-results...)
    }
    return
}

func simplify(filename string) {
    // read file
    content, err := ioutil.ReadFile(filename)
    if err != nil {
        panic(err)
    }

    {
        // from : (*(L)) -> L
        re := regexp.MustCompile(`\(\*\((?P<name>[[:word:]]*)\)\)`)
        content = re.ReplaceAll(content, []byte("$1"))
    }
    {
        // from : (*K)   -> K
        re := regexp.MustCompile(`\(\*(?P<name>[[:word:]]*)\)`)
        content = re.ReplaceAll(content, []byte("$1"))
    }
    {
        // from: A := new(int) -> var A int
        re := regexp.MustCompile(`(?P<name>[[:word:]]*) := new\((?P<typ>[[:word:]]*)\)`)
        content = re.ReplaceAll(content, []byte("var $1 $2"))
    }
    {
        // from: (123) -> 123
        re := regexp.MustCompile(`\((?P<name>[[:digit:]]*)\)`)
        content = re.ReplaceAll(content, []byte("$1"))
    }
    {
        // *int
        content = bytes.ReplaceAll(content, []byte("*int"), []byte("int"))
        content = bytes.ReplaceAll(content, []byte("*float"), []byte("float"))
    }
    {
        // *[]int
        content = bytes.ReplaceAll(content, []byte("*[]"), []byte("[]"))
    }

    // write file
    err = ioutil.WriteFile(filename, content, 0644)
    if err != nil {
        panic(err)
    }
}