generate.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. /*
  2. * ECAL
  3. *
  4. * Copyright 2020 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the MIT
  7. * License, If a copy of the MIT License was not distributed with this
  8. * file, You can obtain one at https://opensource.org/licenses/MIT.
  9. */
  10. package main
  11. import (
  12. "bytes"
  13. "flag"
  14. "fmt"
  15. "go/ast"
  16. "go/build"
  17. "go/doc"
  18. "go/importer"
  19. goparser "go/parser"
  20. "go/token"
  21. "go/types"
  22. "io/ioutil"
  23. "os"
  24. "path/filepath"
  25. "sort"
  26. "unicode"
  27. )
  28. //go:generate echo Generating ECAL stdlib from Go functions ...
  29. //go:generate go run devt.de/krotik/ecal/stdlib/generate $PWD/stdlib
  30. /*
  31. Stdlib candidates modules:
  32. go list std | grep -v internal | grep -v '\.' | grep -v unsafe | grep -v syscall
  33. */
  34. // List underneath the Go symbols which should be available in ECAL's stdlib
  35. // e.g. var pkgNames = map[string][]string{ "fmt": {"Println", "Sprint"} }
  36. // =============EDIT HERE START=============
  37. var pkgNames = map[string][]string{
  38. "math": {"Pi"},
  39. "fmt": {"Println", "Sprint"},
  40. }
  41. // ==============EDIT HERE END==============
  42. var filename = filepath.Join(os.Args[1], "stdlib_gen.go")
  43. var stderrPrint = fmt.Println
  44. var stdoutPrint = fmt.Println
  45. var generateDoc = true
  46. func main() {
  47. var err error
  48. var outbuf bytes.Buffer
  49. synopsis := make(map[string]string)
  50. pkgDocs := make(map[string]*doc.Package)
  51. flag.Parse()
  52. // Make sure pkgNames is sorted
  53. var importList []string
  54. for pkgName, names := range pkgNames {
  55. sort.Strings(names)
  56. importList = append(importList, pkgName)
  57. }
  58. sort.Strings(importList)
  59. outbuf.WriteString(`
  60. // Code generated by ecal/stdlib/generate; DO NOT EDIT.
  61. package stdlib
  62. `)
  63. outbuf.WriteString("import (\n")
  64. for _, pkgName := range importList {
  65. if generateDoc {
  66. syn, pkgDoc, err := getPackageDocs(pkgName)
  67. if err != nil {
  68. stderrPrint("Warning:", err)
  69. } else {
  70. synopsis[pkgName] = syn
  71. pkgDocs[pkgName] = pkgDoc
  72. }
  73. }
  74. outbuf.WriteString(fmt.Sprintf("\t\"%v\"\n", pkgName))
  75. }
  76. outbuf.WriteString(` "reflect"
  77. )
  78. `)
  79. outbuf.WriteString(`/*
  80. genStdlib contains all generated stdlib constructs.
  81. */
  82. `)
  83. outbuf.WriteString("var genStdlib = map[interface{}]interface{}{\n")
  84. for _, pkgName := range importList {
  85. if s, ok := synopsis[pkgName]; ok {
  86. outbuf.WriteString(fmt.Sprintf("\t\"%v-synopsis\" : %#v,\n", pkgName, s))
  87. }
  88. outbuf.WriteString(fmt.Sprintf("\t\"%v-const\" : %vConstMap,\n", pkgName, pkgName))
  89. outbuf.WriteString(fmt.Sprintf("\t\"%v-func\" : %vFuncMap,\n", pkgName, pkgName))
  90. outbuf.WriteString(fmt.Sprintf("\t\"%v-func-doc\" : %vFuncDocMap,\n", pkgName, pkgName))
  91. }
  92. outbuf.WriteString("}\n\n")
  93. for _, pkgName := range importList {
  94. var pkg *types.Package
  95. pkgSymbols := pkgNames[pkgName]
  96. if err == nil {
  97. pkg, err = importer.ForCompiler(fset, "source", nil).Import(pkgName)
  98. if err == nil {
  99. stdoutPrint("Generating adapter functions for", pkg)
  100. scope := pkg.Scope()
  101. // Write constants
  102. outbuf.WriteString(fmt.Sprintf(`/*
  103. %vConstMap contains the mapping of stdlib %v constants.
  104. */
  105. var %vConstMap = map[interface{}]interface{}{
  106. `, pkgName, pkgName, pkgName))
  107. for _, name := range scope.Names() {
  108. if !containsSymbol(pkgSymbols, name) {
  109. continue
  110. }
  111. switch obj := scope.Lookup(name).(type) {
  112. case *types.Const:
  113. if unicode.IsUpper([]rune(name)[0]) {
  114. line := fmt.Sprintf(` "%v": %v.%v,
  115. `, name, pkgName, obj.Name())
  116. if basicType, ok := obj.Type().(*types.Basic); ok {
  117. // Convert number constants so they can be used in calculations
  118. switch basicType.Kind() {
  119. case types.Int,
  120. types.Int8,
  121. types.Int16,
  122. types.Int32,
  123. types.Int64,
  124. types.Uint,
  125. types.Uint8,
  126. types.Uint16,
  127. types.Uint32,
  128. types.Uint64,
  129. types.Uintptr,
  130. types.Float32,
  131. types.UntypedInt,
  132. types.UntypedFloat:
  133. line = fmt.Sprintf(` "%v": float64(%v.%v),
  134. `, name, pkgName, obj.Name())
  135. }
  136. }
  137. outbuf.WriteString(line)
  138. }
  139. }
  140. }
  141. outbuf.WriteString("}\n\n")
  142. // Write functions
  143. outbuf.WriteString(fmt.Sprintf(`/*
  144. %vFuncMap contains the mapping of stdlib %v functions.
  145. */
  146. var %vFuncMap = map[interface{}]interface{}{
  147. `, pkgName, pkgName, pkgName))
  148. for _, name := range scope.Names() {
  149. if !containsSymbol(pkgSymbols, name) {
  150. continue
  151. }
  152. switch obj := scope.Lookup(name).(type) {
  153. case *types.Func:
  154. if unicode.IsUpper([]rune(name)[0]) {
  155. outbuf.WriteString(
  156. fmt.Sprintf(` "%v": &ECALFunctionAdapter{reflect.ValueOf(%v)},
  157. `, name, obj.FullName()))
  158. }
  159. }
  160. }
  161. outbuf.WriteString("}\n\n")
  162. // Write function documentation
  163. outbuf.WriteString(fmt.Sprintf(`/*
  164. %vFuncDocMap contains the documentation of stdlib %v functions.
  165. */
  166. var %vFuncDocMap = map[interface{}]interface{}{
  167. `, pkgName, pkgName, pkgName))
  168. if pkgDoc, ok := pkgDocs[pkgName]; ok {
  169. for _, name := range scope.Names() {
  170. if !containsSymbol(pkgSymbols, name) {
  171. continue
  172. }
  173. for _, f := range pkgDoc.Funcs {
  174. if f.Name == name {
  175. outbuf.WriteString(
  176. fmt.Sprintf(` "%v": %#v,
  177. `, name, f.Doc))
  178. }
  179. }
  180. }
  181. }
  182. outbuf.WriteString("}\n\n")
  183. }
  184. }
  185. }
  186. if err == nil {
  187. err = ioutil.WriteFile(filename, outbuf.Bytes(), 0644)
  188. }
  189. if err != nil {
  190. stderrPrint("Error:", err)
  191. }
  192. }
  193. var (
  194. fset = token.NewFileSet()
  195. ctx = &build.Default
  196. )
  197. /*
  198. getPackageDocs returns the source code documentation of as given Go package.
  199. Returns a short synopsis and a documentation object.
  200. */
  201. func getPackageDocs(pkgName string) (string, *doc.Package, error) {
  202. var synopsis string
  203. var pkgDoc *doc.Package
  204. var filenames []string
  205. bp, err := ctx.Import(pkgName, ".", 0)
  206. if err == nil {
  207. synopsis = bp.Doc
  208. // Get all go files of the package
  209. filenames = append(filenames, bp.GoFiles...)
  210. filenames = append(filenames, bp.CgoFiles...)
  211. // Build the ast package from Go source
  212. astPkg := &ast.Package{
  213. Name: bp.Name,
  214. Files: make(map[string]*ast.File),
  215. }
  216. for _, filename := range filenames {
  217. filepath := filepath.Join(bp.Dir, filename)
  218. astFile, _ := goparser.ParseFile(fset, filepath, nil, goparser.ParseComments)
  219. astPkg.Files[filepath] = astFile
  220. }
  221. // Build the package doc object
  222. pkgDoc = doc.New(astPkg, bp.Dir, doc.AllDecls)
  223. }
  224. return synopsis, pkgDoc, err
  225. }
  226. func containsSymbol(symbols []string, item string) bool {
  227. i := sort.SearchStrings(symbols, item)
  228. return i < len(symbols) && symbols[i] == item
  229. }