Browse Source

feat: Stdlib generation functions with documentation

Matthias Ladkau 3 years ago
parent
commit
fdb20e0c91
6 changed files with 150 additions and 104 deletions
  1. 3 37
      stdlib/adapter.go
  2. 2 2
      stdlib/adapter_test.go
  3. 70 37
      stdlib/generate/generate.go
  4. 32 9
      stdlib/generate/generate_test.go
  5. 24 16
      stdlib/stdlib.go
  6. 19 3
      stdlib/stdlib_test.go

+ 3 - 37
stdlib/adapter.go

@@ -12,14 +12,7 @@ package stdlib
 
 import (
 	"fmt"
-	"go/ast"
-	"go/doc"
-	goparser "go/parser"
-	"go/token"
-	"path/filepath"
 	"reflect"
-	"runtime"
-	"strings"
 
 	"devt.de/krotik/ecal/parser"
 )
@@ -28,7 +21,8 @@ import (
 ECALFunctionAdapter models a bridge adapter between an ECAL function to a Go function.
 */
 type ECALFunctionAdapter struct {
-	funcval reflect.Value
+	funcval   reflect.Value
+	docstring string
 }
 
 /*
@@ -160,33 +154,5 @@ func (ea *ECALFunctionAdapter) Run(instanceID string, vs parser.Scope,
 DocString returns the docstring of the wrapped function.
 */
 func (ea *ECALFunctionAdapter) DocString() (string, error) {
-	ffunc := runtime.FuncForPC(ea.funcval.Pointer())
-	fileName, _ := ffunc.FileLine(0)
-	funcNameSplit := strings.Split(ffunc.Name(), ".")
-	funcName := funcNameSplit[len(funcNameSplit)-1]
-	fset := token.NewFileSet()
-	res := ""
-
-	parsedAst, err := goparser.ParseFile(fset, fileName, nil, goparser.ParseComments)
-
-	if err == nil {
-
-		pkg := &ast.Package{
-			Name:  "Any",
-			Files: make(map[string]*ast.File),
-		}
-		pkg.Files[fileName] = parsedAst
-
-		importPath, _ := filepath.Abs("/")
-		myDoc := doc.New(pkg, importPath, doc.AllDecls)
-
-		for _, theFunc := range myDoc.Funcs {
-			if theFunc.Name == funcName {
-				res = theFunc.Doc
-				break
-			}
-		}
-	}
-
-	return res, err
+	return ea.docstring, nil
 }

+ 2 - 2
stdlib/adapter_test.go

@@ -239,7 +239,7 @@ func TestECALFunctionAdapter(t *testing.T) {
 
 	// Get documentation
 
-	afuncEcal := &ECALFunctionAdapter{reflect.ValueOf(fmt.Sprint)}
+	afuncEcal := &ECALFunctionAdapter{reflect.ValueOf(fmt.Sprint), "test123"}
 
 	if s, err := afuncEcal.DocString(); s == "" || err != nil {
 		t.Error("Docstring should return something")
@@ -248,7 +248,7 @@ func TestECALFunctionAdapter(t *testing.T) {
 }
 
 func runAdapterTest(afunc reflect.Value, args []interface{}) (interface{}, error) {
-	afuncEcal := &ECALFunctionAdapter{afunc}
+	afuncEcal := &ECALFunctionAdapter{afunc, ""}
 	return afuncEcal.Run("test", scope.NewScope(""), make(map[string]interface{}), args)
 
 }

+ 70 - 37
stdlib/generate/generate.go

@@ -26,6 +26,9 @@ import (
 	"path/filepath"
 	"sort"
 	"unicode"
+
+	"devt.de/krotik/common/errorutil"
+	"devt.de/krotik/common/stringutil"
 )
 
 //go:generate echo Generating ECAL stdlib from Go functions ...
@@ -40,13 +43,18 @@ 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"},
-	"fmt":  {"Println", "Sprint"},
+	//	"math": {"Pi"},
+	//	"fmt":  {"Println", "Sprint"},
 }
 
+var generateDoc = true
+
 // ==============EDIT HERE END==============
 
 var filename = filepath.Join(os.Args[1], "stdlib_gen.go")
@@ -54,8 +62,6 @@ var filename = filepath.Join(os.Args[1], "stdlib_gen.go")
 var stderrPrint = fmt.Println
 var stdoutPrint = fmt.Println
 
-var generateDoc = true
-
 func main() {
 	var err error
 	var outbuf bytes.Buffer
@@ -65,6 +71,12 @@ func main() {
 
 	flag.Parse()
 
+	// Make sure we have at least an empty pkgName
+
+	if len(pkgNames) == 0 {
+		pkgNames["math"] = []string{"Pi"}
+	}
+
 	// Make sure pkgNames is sorted
 
 	var importList []string
@@ -86,21 +98,27 @@ package stdlib
 
 		if generateDoc {
 			syn, pkgDoc, err := getPackageDocs(pkgName)
-			if err != nil {
-				stderrPrint("Warning:", err)
-			} else {
-				synopsis[pkgName] = syn
-				pkgDocs[pkgName] = pkgDoc
-			}
+			errorutil.AssertOk(err) // If this throws try not generating the docs!
+			synopsis[pkgName] = syn
+			pkgDocs[pkgName] = pkgDoc
+		} else {
+			synopsis[pkgName] = fmt.Sprintf("Package %v", pkgName)
 		}
 
 		outbuf.WriteString(fmt.Sprintf("\t\"%v\"\n", pkgName))
 	}
 
-	outbuf.WriteString(`	"reflect"
+	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.
@@ -185,32 +203,6 @@ var %vConstMap = map[interface{}]interface{}{
 
 				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)},
-`, name, obj.FullName()))
-						}
-					}
-				}
-
-				outbuf.WriteString("}\n\n")
-
 				// Write function documentation
 
 				outbuf.WriteString(fmt.Sprintf(`/*
@@ -235,6 +227,43 @@ var %vFuncDocMap = map[interface{}]interface{}{
 							}
 						}
 					}
+
+				} 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")
@@ -242,6 +271,10 @@ var %vFuncDocMap = map[interface{}]interface{}{
 		}
 	}
 
+	// 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)
 	}

+ 32 - 9
stdlib/generate/generate_test.go

@@ -15,6 +15,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
+	"strings"
 	"testing"
 )
 
@@ -75,9 +76,11 @@ import (
 genStdlib contains all generated stdlib constructs.
 */
 var genStdlib = map[interface{}]interface{}{
+	"fmt-synopsis" : "Package fmt",
 	"fmt-const" : fmtConstMap,
 	"fmt-func" : fmtFuncMap,
 	"fmt-func-doc" : fmtFuncDocMap,
+	"math-synopsis" : "Package math",
 	"math-const" : mathConstMap,
 	"math-func" : mathFuncMap,
 	"math-func-doc" : mathFuncDocMap,
@@ -90,16 +93,17 @@ var fmtConstMap = map[interface{}]interface{}{
 }
 
 /*
-fmtFuncMap contains the mapping of stdlib fmt functions.
+fmtFuncDocMap contains the documentation of stdlib fmt functions.
 */
-var fmtFuncMap = map[interface{}]interface{}{
-	"Println": &ECALFunctionAdapter{reflect.ValueOf(fmt.Println)},
+var fmtFuncDocMap = map[interface{}]interface{}{
+	"Println": "Function: Println",
 }
 
 /*
-fmtFuncDocMap contains the documentation of stdlib fmt functions.
+fmtFuncMap contains the mapping of stdlib fmt functions.
 */
-var fmtFuncDocMap = map[interface{}]interface{}{
+var fmtFuncMap = map[interface{}]interface{}{
+	"Println": &ECALFunctionAdapter{reflect.ValueOf(fmt.Println), fmt.Sprint(fmtFuncDocMap["Println"])},
 }
 
 /*
@@ -110,19 +114,38 @@ var mathConstMap = map[interface{}]interface{}{
 }
 
 /*
-mathFuncMap contains the mapping of stdlib math functions.
+mathFuncDocMap contains the documentation of stdlib math functions.
 */
-var mathFuncMap = map[interface{}]interface{}{
+var mathFuncDocMap = map[interface{}]interface{}{
 }
 
 /*
-mathFuncDocMap contains the documentation of stdlib math functions.
+mathFuncMap contains the mapping of stdlib math functions.
 */
-var mathFuncDocMap = map[interface{}]interface{}{
+var mathFuncMap = map[interface{}]interface{}{
 }
 
+// Dummy statement to prevent declared and not used errors
+var Dummy = fmt.Sprint(reflect.ValueOf(fmt.Sprint))
+
 ` {
 		t.Errorf("Unexpected result: Go string: %#v\nNormal output: %v", string(out), string(out))
 		return
 	}
+
+	generateDoc = true
+
+	main()
+
+	out, err = ioutil.ReadFile(filename)
+
+	if err != nil {
+		t.Error("Could not read file:", filename, " ", err)
+		return
+	}
+
+	if !strings.Contains(string(out), "formats using the default formats") {
+		t.Error("Unexpected result:", string(out))
+		return
+	}
 }

+ 24 - 16
stdlib/stdlib.go

@@ -43,10 +43,11 @@ func GetStdlibSymbols() ([]string, []string, []string) {
 
 	for k, v := range genStdlib {
 		sym := fmt.Sprint(k)
-		symMap := v.(map[interface{}]interface{})
 
-		constSymbols = addSym(sym, "-const", symMap, constSymbols)
-		funcSymbols = addSym(sym, "-func", symMap, funcSymbols)
+		if symMap, ok := v.(map[interface{}]interface{}); ok {
+			constSymbols = addSym(sym, "-const", symMap, constSymbols)
+			funcSymbols = addSym(sym, "-func", symMap, funcSymbols)
+		}
 	}
 	for k := range packageSet {
 		packageNames = append(packageNames, k)
@@ -59,46 +60,53 @@ func GetStdlibSymbols() ([]string, []string, []string) {
 GetStdlibConst looks up a constant from stdlib.
 */
 func GetStdlibConst(name string) (interface{}, bool) {
-	m, n := splitModuleAndName(name)
+	var res interface{}
+	var resok bool
 
-	if n != "" {
+	if m, n := splitModuleAndName(name); n != "" {
 		if cmap, ok := genStdlib[fmt.Sprintf("%v-const", m)]; ok {
-			if cv, ok := cmap.(map[interface{}]interface{})[n]; ok {
-				return cv.(interface{}), true
-			}
+			res, resok = cmap.(map[interface{}]interface{})[n]
 		}
 	}
 
-	return nil, false
+	return res, resok
 }
 
 /*
 GetStdlibFunc looks up a function from stdlib.
 */
 func GetStdlibFunc(name string) (util.ECALFunction, bool) {
-	m, n := splitModuleAndName(name)
+	var res util.ECALFunction
+	var resok bool
 
-	if n != "" {
+	if m, n := splitModuleAndName(name); n != "" {
 		if fmap, ok := genStdlib[fmt.Sprintf("%v-func", m)]; ok {
 			if fn, ok := fmap.(map[interface{}]interface{})[n]; ok {
-				return fn.(util.ECALFunction), true
+				res = fn.(util.ECALFunction)
+				resok = true
 			}
 		}
 	}
 
-	return nil, false
+	return res, resok
 }
 
 /*
 GetPkgDocString returns the docstring of a stdlib package.
 */
 func GetPkgDocString(name string) (string, bool) {
+	var res = ""
+	s, ok := genStdlib[fmt.Sprintf("%v-synopsis", name)]
+	if ok {
+		res = fmt.Sprint(s)
+	}
 
-	// TODO Implement
-
-	return "", false
+	return res, ok
 }
 
+/*
+splitModuleAndName splits up a given full function name in module and function name part.
+*/
 func splitModuleAndName(fullname string) (string, string) {
 	var module, name string
 

+ 19 - 3
stdlib/stdlib_test.go

@@ -13,13 +13,27 @@ package stdlib
 import (
 	"fmt"
 	"math"
+	"reflect"
 	"testing"
 )
 
 func TestGetPkgDocString(t *testing.T) {
-	doc, ok := GetPkgDocString("math")
 
-	fmt.Println(doc, ok)
+	mathFuncMap["Println"] = &ECALFunctionAdapter{reflect.ValueOf(fmt.Println), "foo"}
+
+	f, _ := GetStdlibFunc("math.Println")
+
+	if s, _ := f.DocString(); s != "foo" {
+		t.Error("Unexpected result:", s)
+		return
+	}
+
+	doc, _ := GetPkgDocString("math")
+
+	if doc == "" {
+		t.Error("Unexpected result:", doc)
+		return
+	}
 }
 
 func TestSymbols(t *testing.T) {
@@ -55,7 +69,9 @@ func TestSplitModuleAndName(t *testing.T) {
 
 func TestGetStdLibItems(t *testing.T) {
 
-	if f, _ := GetStdlibFunc("fmt.Println"); f != fmtFuncMap["Println"] {
+	mathFuncMap["Println"] = &ECALFunctionAdapter{reflect.ValueOf(fmt.Println), "foo"}
+
+	if f, _ := GetStdlibFunc("math.Println"); f != mathFuncMap["Println"] {
 		t.Error("Unexpected resutl: functions should lookup correctly")
 		return
 	}