123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- /*
- * ECAL
- *
- * Copyright 2020 Matthias Ladkau. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the MIT
- * License, If a copy of the MIT License was not distributed with this
- * file, You can obtain one at https://opensource.org/licenses/MIT.
- */
- package main
- import (
- "bytes"
- "flag"
- "fmt"
- "go/ast"
- "go/build"
- "go/doc"
- "go/importer"
- goparser "go/parser"
- "go/token"
- "go/types"
- "io/ioutil"
- "os"
- "path/filepath"
- "sort"
- "unicode"
- "devt.de/krotik/common/errorutil"
- "devt.de/krotik/common/stringutil"
- )
- //go:generate echo Generating ECAL stdlib from Go functions ...
- //go:generate go run devt.de/krotik/ecal/stdlib/generate $PWD/stdlib
- /*
- Stdlib candidates modules:
- go list std | grep -v internal | grep -v '\.' | grep -v unsafe | grep -v syscall
- */
- // List underneath the Go symbols which should be available in ECAL's stdlib
- // e.g. var pkgNames = map[string][]string{ "fmt": {"Println", "Sprint"} }
- // Turn the generateDoc switch on or off to extract documentation from the
- // Go source.
- // =============EDIT HERE START=============
- var pkgNames = map[string][]string{
- // "math": {"Pi", "E", "Phi", "Pow"},
- // "fmt": {"Println", "Sprint"},
- }
- var generateDoc = false
- // ==============EDIT HERE END==============
- var filename = filepath.Join(os.Args[1], "stdlib_gen.go")
- var stderrPrint = fmt.Println
- var stdoutPrint = fmt.Println
- func main() {
- var err error
- var outbuf bytes.Buffer
- synopsis := make(map[string]string)
- pkgDocs := make(map[string]*doc.Package)
- flag.Parse()
- // Make sure we have at least an empty pkgName
- if len(pkgNames) == 0 {
- pkgNames["math"] = []string{"Pi", "E", "Phi"}
- }
- // Make sure pkgNames is sorted
- var importList []string
- for pkgName, names := range pkgNames {
- sort.Strings(names)
- importList = append(importList, pkgName)
- synopsis["math"] = "Mathematics-related constants and functions"
- }
- sort.Strings(importList)
- outbuf.WriteString(`
- // Code generated by ecal/stdlib/generate; DO NOT EDIT.
- package stdlib
- `)
- outbuf.WriteString("import (\n")
- for _, pkgName := range importList {
- if generateDoc {
- syn, pkgDoc, err := getPackageDocs(pkgName)
- errorutil.AssertOk(err) // If this throws try not generating the docs!
- synopsis[pkgName] = syn
- pkgDocs[pkgName] = pkgDoc
- } else if _, ok := synopsis[pkgName]; !ok {
- synopsis[pkgName] = fmt.Sprintf("Package %v", pkgName)
- }
- outbuf.WriteString(fmt.Sprintf("\t\"%v\"\n", pkgName))
- }
- if stringutil.IndexOf("fmt", importList) == -1 {
- outbuf.WriteString(` "fmt"
- `)
- }
- if stringutil.IndexOf("reflect", importList) == -1 {
- outbuf.WriteString(` "reflect"
- )
- `)
- }
- outbuf.WriteString(`/*
- genStdlib contains all generated stdlib constructs.
- */
- `)
- outbuf.WriteString("var genStdlib = map[interface{}]interface{}{\n")
- for _, pkgName := range importList {
- if s, ok := synopsis[pkgName]; ok {
- outbuf.WriteString(fmt.Sprintf("\t\"%v-synopsis\" : %#v,\n", pkgName, s))
- }
- outbuf.WriteString(fmt.Sprintf("\t\"%v-const\" : %vConstMap,\n", pkgName, pkgName))
- outbuf.WriteString(fmt.Sprintf("\t\"%v-func\" : %vFuncMap,\n", pkgName, pkgName))
- outbuf.WriteString(fmt.Sprintf("\t\"%v-func-doc\" : %vFuncDocMap,\n", pkgName, pkgName))
- }
- outbuf.WriteString("}\n\n")
- for _, pkgName := range importList {
- var pkg *types.Package
- pkgSymbols := pkgNames[pkgName]
- if err == nil {
- pkg, err = importer.ForCompiler(fset, "source", nil).Import(pkgName)
- if err == nil {
- stdoutPrint("Generating adapter functions for", pkg)
- scope := pkg.Scope()
- // Write constants
- outbuf.WriteString(fmt.Sprintf(`/*
- %vConstMap contains the mapping of stdlib %v constants.
- */
- var %vConstMap = map[interface{}]interface{}{
- `, pkgName, pkgName, pkgName))
- for _, name := range scope.Names() {
- if !containsSymbol(pkgSymbols, name) {
- continue
- }
- switch obj := scope.Lookup(name).(type) {
- case *types.Const:
- if unicode.IsUpper([]rune(name)[0]) {
- line := fmt.Sprintf(` "%v": %v.%v,
- `, name, pkgName, obj.Name())
- if basicType, ok := obj.Type().(*types.Basic); ok {
- // Convert number constants so they can be used in calculations
- switch basicType.Kind() {
- case types.Int,
- types.Int8,
- types.Int16,
- types.Int32,
- types.Int64,
- types.Uint,
- types.Uint8,
- types.Uint16,
- types.Uint32,
- types.Uint64,
- types.Uintptr,
- types.Float32,
- types.UntypedInt,
- types.UntypedFloat:
- line = fmt.Sprintf(` "%v": float64(%v.%v),
- `, name, pkgName, obj.Name())
- }
- }
- outbuf.WriteString(line)
- }
- }
- }
- outbuf.WriteString("}\n\n")
- // Write function documentation
- outbuf.WriteString(fmt.Sprintf(`/*
- %vFuncDocMap contains the documentation of stdlib %v functions.
- */
- var %vFuncDocMap = map[interface{}]interface{}{
- `, pkgName, pkgName, pkgName))
- if pkgDoc, ok := pkgDocs[pkgName]; ok {
- for _, name := range scope.Names() {
- if !containsSymbol(pkgSymbols, name) {
- continue
- }
- for _, f := range pkgDoc.Funcs {
- if f.Name == name {
- outbuf.WriteString(
- fmt.Sprintf(` "%v": %#v,
- `, name, f.Doc))
- }
- }
- }
- } else {
- for _, name := range pkgSymbols {
- switch scope.Lookup(name).(type) {
- case *types.Func:
- outbuf.WriteString(
- fmt.Sprintf(` "%v": "Function: %v",
- `, name, name))
- }
- }
- }
- outbuf.WriteString("}\n\n")
- // Write functions
- outbuf.WriteString(fmt.Sprintf(`/*
- %vFuncMap contains the mapping of stdlib %v functions.
- */
- var %vFuncMap = map[interface{}]interface{}{
- `, pkgName, pkgName, pkgName))
- for _, name := range scope.Names() {
- if !containsSymbol(pkgSymbols, name) {
- continue
- }
- switch obj := scope.Lookup(name).(type) {
- case *types.Func:
- if unicode.IsUpper([]rune(name)[0]) {
- outbuf.WriteString(
- fmt.Sprintf(` %#v: &ECALFunctionAdapter{reflect.ValueOf(%v), fmt.Sprint(%vFuncDocMap[%#v])},
- `, name, obj.FullName(), pkgName, name))
- }
- }
- }
- outbuf.WriteString("}\n\n")
- }
- }
- }
- // Write dummy statement
- outbuf.WriteString("// Dummy statement to prevent declared and not used errors\n")
- outbuf.WriteString("var Dummy = fmt.Sprint(reflect.ValueOf(fmt.Sprint))\n\n")
- if err == nil {
- err = ioutil.WriteFile(filename, outbuf.Bytes(), 0644)
- }
- if err != nil {
- stderrPrint("Error:", err)
- }
- }
- var (
- fset = token.NewFileSet()
- ctx = &build.Default
- )
- /*
- getPackageDocs returns the source code documentation of as given Go package.
- Returns a short synopsis and a documentation object.
- */
- func getPackageDocs(pkgName string) (string, *doc.Package, error) {
- var synopsis string
- var pkgDoc *doc.Package
- var filenames []string
- bp, err := ctx.Import(pkgName, ".", 0)
- if err == nil {
- synopsis = bp.Doc
- // Get all go files of the package
- filenames = append(filenames, bp.GoFiles...)
- filenames = append(filenames, bp.CgoFiles...)
- // Build the ast package from Go source
- astPkg := &ast.Package{
- Name: bp.Name,
- Files: make(map[string]*ast.File),
- }
- for _, filename := range filenames {
- filepath := filepath.Join(bp.Dir, filename)
- astFile, _ := goparser.ParseFile(fset, filepath, nil, goparser.ParseComments)
- astPkg.Files[filepath] = astFile
- }
- // Build the package doc object
- pkgDoc = doc.New(astPkg, bp.Dir, doc.AllDecls)
- }
- return synopsis, pkgDoc, err
- }
- func containsSymbol(symbols []string, item string) bool {
- i := sort.SearchStrings(symbols, item)
- return i < len(symbols) && symbols[i] == item
- }
|