generate.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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. "devt.de/krotik/common/errorutil"
  28. "devt.de/krotik/common/stringutil"
  29. )
  30. //go:generate echo Generating ECAL stdlib from Go functions ...
  31. //go:generate go run devt.de/krotik/ecal/stdlib/generate $PWD/stdlib
  32. /*
  33. Stdlib candidates modules:
  34. go list std | grep -v internal | grep -v '\.' | grep -v unsafe | grep -v syscall
  35. */
  36. // List underneath the Go symbols which should be available in ECAL's stdlib
  37. // e.g. var pkgNames = map[string][]string{ "fmt": {"Println", "Sprint"} }
  38. // Turn the generateDoc switch on or off to extract documentation from the
  39. // Go source.
  40. // =============EDIT HERE START=============
  41. var pkgNames = map[string][]string{
  42. // "math": {"Pi", "E", "Phi", "Pow"},
  43. // "fmt": {"Println", "Sprint"},
  44. }
  45. var generateDoc = false
  46. // ==============EDIT HERE END==============
  47. var filename = filepath.Join(os.Args[1], "stdlib_gen.go")
  48. var stderrPrint = fmt.Println
  49. var stdoutPrint = fmt.Println
  50. func main() {
  51. var err error
  52. var outbuf bytes.Buffer
  53. synopsis := make(map[string]string)
  54. pkgDocs := make(map[string]*doc.Package)
  55. flag.Parse()
  56. // Make sure we have at least an empty pkgName
  57. if len(pkgNames) == 0 {
  58. pkgNames["math"] = []string{"Pi", "E", "Phi"}
  59. }
  60. // Make sure pkgNames is sorted
  61. var importList []string
  62. for pkgName, names := range pkgNames {
  63. sort.Strings(names)
  64. importList = append(importList, pkgName)
  65. synopsis["math"] = "Mathematics-related constants and functions"
  66. }
  67. sort.Strings(importList)
  68. outbuf.WriteString(`
  69. // Code generated by ecal/stdlib/generate; DO NOT EDIT.
  70. package stdlib
  71. `)
  72. outbuf.WriteString("import (\n")
  73. for _, pkgName := range importList {
  74. if generateDoc {
  75. syn, pkgDoc, err := getPackageDocs(pkgName)
  76. errorutil.AssertOk(err) // If this throws try not generating the docs!
  77. synopsis[pkgName] = syn
  78. pkgDocs[pkgName] = pkgDoc
  79. } else if _, ok := synopsis[pkgName]; !ok {
  80. synopsis[pkgName] = fmt.Sprintf("Package %v", pkgName)
  81. }
  82. outbuf.WriteString(fmt.Sprintf("\t\"%v\"\n", pkgName))
  83. }
  84. if stringutil.IndexOf("fmt", importList) == -1 {
  85. outbuf.WriteString(` "fmt"
  86. `)
  87. }
  88. if stringutil.IndexOf("reflect", importList) == -1 {
  89. outbuf.WriteString(` "reflect"
  90. )
  91. `)
  92. }
  93. outbuf.WriteString(`/*
  94. genStdlib contains all generated stdlib constructs.
  95. */
  96. `)
  97. outbuf.WriteString("var genStdlib = map[interface{}]interface{}{\n")
  98. for _, pkgName := range importList {
  99. if s, ok := synopsis[pkgName]; ok {
  100. outbuf.WriteString(fmt.Sprintf("\t\"%v-synopsis\" : %#v,\n", pkgName, s))
  101. }
  102. outbuf.WriteString(fmt.Sprintf("\t\"%v-const\" : %vConstMap,\n", pkgName, pkgName))
  103. outbuf.WriteString(fmt.Sprintf("\t\"%v-func\" : %vFuncMap,\n", pkgName, pkgName))
  104. outbuf.WriteString(fmt.Sprintf("\t\"%v-func-doc\" : %vFuncDocMap,\n", pkgName, pkgName))
  105. }
  106. outbuf.WriteString("}\n\n")
  107. for _, pkgName := range importList {
  108. var pkg *types.Package
  109. pkgSymbols := pkgNames[pkgName]
  110. if err == nil {
  111. pkg, err = importer.ForCompiler(fset, "source", nil).Import(pkgName)
  112. if err == nil {
  113. stdoutPrint("Generating adapter functions for", pkg)
  114. scope := pkg.Scope()
  115. // Write constants
  116. writeConstants(&outbuf, pkgName, pkgSymbols, scope)
  117. // Write function documentation
  118. writeFuncDoc(&outbuf, pkgName, pkgDocs, pkgSymbols, scope)
  119. // Write functions
  120. writeFuncs(&outbuf, pkgName, pkgSymbols, scope)
  121. }
  122. }
  123. }
  124. // Write dummy statement
  125. outbuf.WriteString("// Dummy statement to prevent declared and not used errors\n")
  126. outbuf.WriteString("var Dummy = fmt.Sprint(reflect.ValueOf(fmt.Sprint))\n\n")
  127. if err == nil {
  128. err = ioutil.WriteFile(filename, outbuf.Bytes(), 0644)
  129. }
  130. if err != nil {
  131. stderrPrint("Error:", err)
  132. }
  133. }
  134. var (
  135. fset = token.NewFileSet()
  136. ctx = &build.Default
  137. )
  138. /*
  139. writeConstants writes out all stdlib constant definitions.
  140. */
  141. func writeConstants(outbuf *bytes.Buffer, pkgName string, pkgSymbols []string, scope *types.Scope) {
  142. outbuf.WriteString(fmt.Sprintf(`/*
  143. %vConstMap contains the mapping of stdlib %v constants.
  144. */
  145. var %vConstMap = map[interface{}]interface{}{
  146. `, pkgName, pkgName, pkgName))
  147. for _, name := range scope.Names() {
  148. if !containsSymbol(pkgSymbols, name) {
  149. continue
  150. }
  151. switch obj := scope.Lookup(name).(type) {
  152. case *types.Const:
  153. if unicode.IsUpper([]rune(name)[0]) {
  154. line := fmt.Sprintf(` "%v": %v.%v,
  155. `, name, pkgName, obj.Name())
  156. if basicType, ok := obj.Type().(*types.Basic); ok {
  157. // Convert number constants so they can be used in calculations
  158. switch basicType.Kind() {
  159. case types.Int,
  160. types.Int8,
  161. types.Int16,
  162. types.Int32,
  163. types.Int64,
  164. types.Uint,
  165. types.Uint8,
  166. types.Uint16,
  167. types.Uint32,
  168. types.Uint64,
  169. types.Uintptr,
  170. types.Float32,
  171. types.UntypedInt,
  172. types.UntypedFloat:
  173. line = fmt.Sprintf(` "%v": float64(%v.%v),
  174. `, name, pkgName, obj.Name())
  175. }
  176. }
  177. outbuf.WriteString(line)
  178. }
  179. }
  180. }
  181. outbuf.WriteString("}\n\n")
  182. }
  183. /*
  184. writeFuncDoc writes out all stdlib function documentation.
  185. */
  186. func writeFuncDoc(outbuf *bytes.Buffer, pkgName string, pkgDocs map[string]*doc.Package,
  187. pkgSymbols []string, scope *types.Scope) {
  188. outbuf.WriteString(fmt.Sprintf(`/*
  189. %vFuncDocMap contains the documentation of stdlib %v functions.
  190. */
  191. var %vFuncDocMap = map[interface{}]interface{}{
  192. `, pkgName, pkgName, pkgName))
  193. if pkgDoc, ok := pkgDocs[pkgName]; ok {
  194. for _, name := range scope.Names() {
  195. if !containsSymbol(pkgSymbols, name) {
  196. continue
  197. }
  198. for _, f := range pkgDoc.Funcs {
  199. if f.Name == name {
  200. outbuf.WriteString(
  201. fmt.Sprintf(` "%v": %#v,
  202. `, name, f.Doc))
  203. }
  204. }
  205. }
  206. } else {
  207. for _, name := range pkgSymbols {
  208. switch scope.Lookup(name).(type) {
  209. case *types.Func:
  210. outbuf.WriteString(
  211. fmt.Sprintf(` "%v": "Function: %v",
  212. `, name, name))
  213. }
  214. }
  215. }
  216. outbuf.WriteString("}\n\n")
  217. }
  218. /*
  219. writeFuncs writes out all stdlib function definitions.
  220. */
  221. func writeFuncs(outbuf *bytes.Buffer, pkgName string, pkgSymbols []string, scope *types.Scope) {
  222. outbuf.WriteString(fmt.Sprintf(`/*
  223. %vFuncMap contains the mapping of stdlib %v functions.
  224. */
  225. var %vFuncMap = map[interface{}]interface{}{
  226. `, pkgName, pkgName, pkgName))
  227. for _, name := range scope.Names() {
  228. if !containsSymbol(pkgSymbols, name) {
  229. continue
  230. }
  231. switch obj := scope.Lookup(name).(type) {
  232. case *types.Func:
  233. if unicode.IsUpper([]rune(name)[0]) {
  234. outbuf.WriteString(
  235. fmt.Sprintf(` %#v: &ECALFunctionAdapter{reflect.ValueOf(%v), fmt.Sprint(%vFuncDocMap[%#v])},
  236. `, name, obj.FullName(), pkgName, name))
  237. }
  238. }
  239. }
  240. outbuf.WriteString("}\n\n")
  241. }
  242. /*
  243. getPackageDocs returns the source code documentation of as given Go package.
  244. Returns a short synopsis and a documentation object.
  245. */
  246. func getPackageDocs(pkgName string) (string, *doc.Package, error) {
  247. var synopsis string
  248. var pkgDoc *doc.Package
  249. var filenames []string
  250. bp, err := ctx.Import(pkgName, ".", 0)
  251. if err == nil {
  252. synopsis = bp.Doc
  253. // Get all go files of the package
  254. filenames = append(filenames, bp.GoFiles...)
  255. filenames = append(filenames, bp.CgoFiles...)
  256. // Build the ast package from Go source
  257. astPkg := &ast.Package{
  258. Name: bp.Name,
  259. Files: make(map[string]*ast.File),
  260. }
  261. for _, filename := range filenames {
  262. filepath := filepath.Join(bp.Dir, filename)
  263. astFile, _ := goparser.ParseFile(fset, filepath, nil, goparser.ParseComments)
  264. astPkg.Files[filepath] = astFile
  265. }
  266. // Build the package doc object
  267. pkgDoc = doc.New(astPkg, bp.Dir, doc.AllDecls)
  268. }
  269. return synopsis, pkgDoc, err
  270. }
  271. func containsSymbol(symbols []string, item string) bool {
  272. i := sort.SearchStrings(symbols, item)
  273. return i < len(symbols) && symbols[i] == item
  274. }