generate.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  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. // "fmt": {"Println", "Sprint"},
  43. }
  44. var generateDoc = false
  45. // ==============EDIT HERE END==============
  46. var filename = filepath.Join(os.Args[1], "stdlib_gen.go")
  47. var stderrPrint = fmt.Println
  48. var stdoutPrint = fmt.Println
  49. func main() {
  50. var err error
  51. var outbuf bytes.Buffer
  52. synopsis := make(map[string]string)
  53. pkgDocs := make(map[string]*doc.Package)
  54. flag.Parse()
  55. // Make sure we have at least an empty pkgName
  56. if len(pkgNames) == 0 {
  57. pkgNames["math"] = []string{
  58. "E",
  59. "Pi",
  60. "Phi",
  61. "Sqrt2",
  62. "SqrtE",
  63. "SqrtPi",
  64. "SqrtPhi",
  65. "Ln2",
  66. "Log2E",
  67. "Ln10",
  68. "Log10E",
  69. "Abs",
  70. "Acos",
  71. "Acosh",
  72. "Asin",
  73. "Asinh",
  74. "Atan",
  75. "Atan2",
  76. "Atanh",
  77. "Cbrt",
  78. "Ceil",
  79. "Copysign",
  80. "Cos",
  81. "Cosh",
  82. "Dim",
  83. "Erf",
  84. "Erfc",
  85. "Erfcinv",
  86. "Erfinv",
  87. "Exp",
  88. "Exp2",
  89. "Expm1",
  90. "Floor",
  91. "Frexp",
  92. "Gamma",
  93. "Hypot",
  94. "Ilogb",
  95. "Inf",
  96. "IsInf",
  97. "IsNaN",
  98. "J0",
  99. "J1",
  100. "Jn",
  101. "Ldexp",
  102. "Lgamma",
  103. "Log",
  104. "Log10",
  105. "Log1p",
  106. "Log2",
  107. "Logb",
  108. "Max",
  109. "Min",
  110. "Mod",
  111. "Modf",
  112. "NaN",
  113. "Nextafter",
  114. "Nextafter32",
  115. "Pow",
  116. "Pow10",
  117. "Remainder",
  118. "Round",
  119. "RoundToEven",
  120. "Signbit",
  121. "Sin",
  122. "Sincos",
  123. "Sinh",
  124. "Sqrt",
  125. "Tan",
  126. "Tanh",
  127. "Trunc",
  128. "Y0",
  129. "Y1",
  130. "Yn",
  131. }
  132. }
  133. // Make sure pkgNames is sorted
  134. var importList []string
  135. for pkgName, names := range pkgNames {
  136. sort.Strings(names)
  137. importList = append(importList, pkgName)
  138. synopsis["math"] = "Mathematics-related constants and functions"
  139. }
  140. sort.Strings(importList)
  141. outbuf.WriteString(`
  142. // Code generated by ecal/stdlib/generate; DO NOT EDIT.
  143. package stdlib
  144. `)
  145. outbuf.WriteString("import (\n")
  146. for _, pkgName := range importList {
  147. if generateDoc {
  148. syn, pkgDoc, err := getPackageDocs(pkgName)
  149. errorutil.AssertOk(err) // If this throws try not generating the docs!
  150. synopsis[pkgName] = syn
  151. pkgDocs[pkgName] = pkgDoc
  152. } else if _, ok := synopsis[pkgName]; !ok {
  153. synopsis[pkgName] = fmt.Sprintf("Package %v", pkgName)
  154. }
  155. outbuf.WriteString(fmt.Sprintf("\t\"%v\"\n", pkgName))
  156. }
  157. if stringutil.IndexOf("fmt", importList) == -1 {
  158. outbuf.WriteString(` "fmt"
  159. `)
  160. }
  161. if stringutil.IndexOf("reflect", importList) == -1 {
  162. outbuf.WriteString(` "reflect"
  163. )
  164. `)
  165. }
  166. outbuf.WriteString(`/*
  167. genStdlib contains all generated stdlib constructs.
  168. */
  169. `)
  170. outbuf.WriteString("var genStdlib = map[interface{}]interface{}{\n")
  171. for _, pkgName := range importList {
  172. if s, ok := synopsis[pkgName]; ok {
  173. outbuf.WriteString(fmt.Sprintf("\t\"%v-synopsis\" : %#v,\n", pkgName, s))
  174. }
  175. outbuf.WriteString(fmt.Sprintf("\t\"%v-const\" : %vConstMap,\n", pkgName, pkgName))
  176. outbuf.WriteString(fmt.Sprintf("\t\"%v-func\" : %vFuncMap,\n", pkgName, pkgName))
  177. outbuf.WriteString(fmt.Sprintf("\t\"%v-func-doc\" : %vFuncDocMap,\n", pkgName, pkgName))
  178. }
  179. outbuf.WriteString("}\n\n")
  180. for _, pkgName := range importList {
  181. var pkg *types.Package
  182. pkgSymbols := pkgNames[pkgName]
  183. if err == nil {
  184. pkg, err = importer.ForCompiler(fset, "source", nil).Import(pkgName)
  185. if err == nil {
  186. stdoutPrint("Generating adapter functions for", pkg)
  187. scope := pkg.Scope()
  188. // Write constants
  189. writeConstants(&outbuf, pkgName, pkgSymbols, scope)
  190. // Write function documentation
  191. writeFuncDoc(&outbuf, pkgName, pkgDocs, pkgSymbols, scope)
  192. // Write functions
  193. writeFuncs(&outbuf, pkgName, pkgSymbols, scope)
  194. }
  195. }
  196. }
  197. // Write dummy statement
  198. outbuf.WriteString("// Dummy statement to prevent declared and not used errors\n")
  199. outbuf.WriteString("var Dummy = fmt.Sprint(reflect.ValueOf(fmt.Sprint))\n\n")
  200. if err == nil {
  201. err = ioutil.WriteFile(filename, outbuf.Bytes(), 0644)
  202. }
  203. if err != nil {
  204. stderrPrint("Error:", err)
  205. }
  206. }
  207. var (
  208. fset = token.NewFileSet()
  209. ctx = &build.Default
  210. )
  211. /*
  212. writeConstants writes out all stdlib constant definitions.
  213. */
  214. func writeConstants(outbuf *bytes.Buffer, pkgName string, pkgSymbols []string, scope *types.Scope) {
  215. outbuf.WriteString(fmt.Sprintf(`/*
  216. %vConstMap contains the mapping of stdlib %v constants.
  217. */
  218. var %vConstMap = map[interface{}]interface{}{
  219. `, pkgName, pkgName, pkgName))
  220. for _, name := range scope.Names() {
  221. if !containsSymbol(pkgSymbols, name) {
  222. continue
  223. }
  224. switch obj := scope.Lookup(name).(type) {
  225. case *types.Const:
  226. if unicode.IsUpper([]rune(name)[0]) {
  227. line := fmt.Sprintf(` "%v": %v.%v,
  228. `, name, pkgName, obj.Name())
  229. if basicType, ok := obj.Type().(*types.Basic); ok {
  230. // Convert number constants so they can be used in calculations
  231. switch basicType.Kind() {
  232. case types.Int,
  233. types.Int8,
  234. types.Int16,
  235. types.Int32,
  236. types.Int64,
  237. types.Uint,
  238. types.Uint8,
  239. types.Uint16,
  240. types.Uint32,
  241. types.Uint64,
  242. types.Uintptr,
  243. types.Float32,
  244. types.UntypedInt,
  245. types.UntypedFloat:
  246. line = fmt.Sprintf(` "%v": float64(%v.%v),
  247. `, name, pkgName, obj.Name())
  248. }
  249. }
  250. outbuf.WriteString(line)
  251. }
  252. }
  253. }
  254. outbuf.WriteString("}\n\n")
  255. }
  256. /*
  257. writeFuncDoc writes out all stdlib function documentation.
  258. */
  259. func writeFuncDoc(outbuf *bytes.Buffer, pkgName string, pkgDocs map[string]*doc.Package,
  260. pkgSymbols []string, scope *types.Scope) {
  261. outbuf.WriteString(fmt.Sprintf(`/*
  262. %vFuncDocMap contains the documentation of stdlib %v functions.
  263. */
  264. var %vFuncDocMap = map[interface{}]interface{}{
  265. `, pkgName, pkgName, pkgName))
  266. if pkgDoc, ok := pkgDocs[pkgName]; ok {
  267. for _, name := range scope.Names() {
  268. if !containsSymbol(pkgSymbols, name) {
  269. continue
  270. }
  271. for _, f := range pkgDoc.Funcs {
  272. if f.Name == name {
  273. outbuf.WriteString(
  274. fmt.Sprintf(` "%v": %#v,
  275. `, name, f.Doc))
  276. }
  277. }
  278. }
  279. } else {
  280. for _, name := range pkgSymbols {
  281. switch scope.Lookup(name).(type) {
  282. case *types.Func:
  283. outbuf.WriteString(
  284. fmt.Sprintf(` "%v": "Function: %v",
  285. `, lcFirst(name), lcFirst(name)))
  286. }
  287. }
  288. }
  289. outbuf.WriteString("}\n\n")
  290. }
  291. /*
  292. writeFuncs writes out all stdlib function definitions.
  293. */
  294. func writeFuncs(outbuf *bytes.Buffer, pkgName string, pkgSymbols []string, scope *types.Scope) {
  295. outbuf.WriteString(fmt.Sprintf(`/*
  296. %vFuncMap contains the mapping of stdlib %v functions.
  297. */
  298. var %vFuncMap = map[interface{}]interface{}{
  299. `, pkgName, pkgName, pkgName))
  300. for _, name := range scope.Names() {
  301. if !containsSymbol(pkgSymbols, name) {
  302. continue
  303. }
  304. switch obj := scope.Lookup(name).(type) {
  305. case *types.Func:
  306. if unicode.IsUpper([]rune(name)[0]) {
  307. outbuf.WriteString(
  308. fmt.Sprintf(` %#v: &ECALFunctionAdapter{reflect.ValueOf(%v), fmt.Sprint(%vFuncDocMap[%#v])},
  309. `, lcFirst(name), obj.FullName(), pkgName, lcFirst(name)))
  310. }
  311. }
  312. }
  313. outbuf.WriteString("}\n\n")
  314. }
  315. /*
  316. getPackageDocs returns the source code documentation of as given Go package.
  317. Returns a short synopsis and a documentation object.
  318. */
  319. func getPackageDocs(pkgName string) (string, *doc.Package, error) {
  320. var synopsis string
  321. var pkgDoc *doc.Package
  322. var filenames []string
  323. bp, err := ctx.Import(pkgName, ".", 0)
  324. if err == nil {
  325. synopsis = bp.Doc
  326. // Get all go files of the package
  327. filenames = append(filenames, bp.GoFiles...)
  328. filenames = append(filenames, bp.CgoFiles...)
  329. // Build the ast package from Go source
  330. astPkg := &ast.Package{
  331. Name: bp.Name,
  332. Files: make(map[string]*ast.File),
  333. }
  334. for _, filename := range filenames {
  335. filepath := filepath.Join(bp.Dir, filename)
  336. astFile, _ := goparser.ParseFile(fset, filepath, nil, goparser.ParseComments)
  337. astPkg.Files[filepath] = astFile
  338. }
  339. // Build the package doc object
  340. pkgDoc = doc.New(astPkg, bp.Dir, doc.AllDecls)
  341. }
  342. return synopsis, pkgDoc, err
  343. }
  344. /*
  345. containsSymbol checks if a list of strings contains a given item.
  346. */
  347. func containsSymbol(symbols []string, item string) bool {
  348. i := sort.SearchStrings(symbols, item)
  349. return i < len(symbols) && symbols[i] == item
  350. }
  351. /*
  352. lcFirst lower cases the first rune of a given string
  353. */
  354. func lcFirst(s string) string {
  355. var ret = ""
  356. for i, v := range s {
  357. ret = string(unicode.ToLower(v)) + s[i+len(string(v)):]
  358. break
  359. }
  360. return ret
  361. }