Browse Source

feat: Stdlib documentation support

Matthias Ladkau 3 years ago
parent
commit
35eeb88614

+ 4 - 3
cli/ecal.go

@@ -15,6 +15,7 @@ import (
 	"fmt"
 	"os"
 
+	"devt.de/krotik/ecal/cli/tool"
 	"devt.de/krotik/ecal/config"
 )
 
@@ -65,16 +66,16 @@ func main() {
 		arg := flag.Args()[0]
 
 		if arg == "console" {
-			err = interpret(true)
+			err = tool.Interpret(true)
 		} else if arg == "run" {
-			err = interpret(false)
+			err = tool.Interpret(false)
 		} else {
 			flag.Usage()
 		}
 
 	} else if err == nil {
 
-		err = interpret(true)
+		err = tool.Interpret(true)
 	}
 
 	if err != nil {

+ 20 - 7
cli/interpret.go

@@ -8,13 +8,14 @@
  * file, You can obtain one at https://opensource.org/licenses/MIT.
  */
 
-package main
+package tool
 
 import (
 	"flag"
 	"fmt"
 	"io"
 	"os"
+	"strings"
 
 	"devt.de/krotik/common/fileutil"
 	"devt.de/krotik/common/termutil"
@@ -26,9 +27,11 @@ import (
 )
 
 /*
-interpret starts the ECAL code interpreter.
+Interpret starts the ECAL code interpreter from a CLI application which
+calls the interpret function as a sub executable. Starts an interactive console
+if the interactive flag is set.
 */
-func interpret(interactive bool) error {
+func Interpret(interactive bool) error {
 	var err error
 
 	wd, _ := os.Getwd()
@@ -113,6 +116,10 @@ func interpret(interactive bool) error {
 
 		if interactive {
 
+			// Preload stdlib packages and functions
+
+			// TODO stdlibPackages, stdlibConst, stdlibFuncs := stdlib.GetStdlibSymbols()
+
 			// Drop into interactive shell
 
 			if err == nil {
@@ -139,6 +146,7 @@ func interpret(interactive bool) error {
 
 						line, err = clt.NextLine()
 						for err == nil && !isExitLine(line) {
+							trimmedLine := strings.TrimSpace(line)
 
 							// Process the entered line
 
@@ -150,12 +158,17 @@ func interpret(interactive bool) error {
 								clt.WriteString(fmt.Sprintf("\n"))
 								clt.WriteString(fmt.Sprintf("Console supports all normal ECAL statements and the following special commands:\n"))
 								clt.WriteString(fmt.Sprintf("\n"))
-								clt.WriteString(fmt.Sprintf("    !syms - List all available inbuild functions and available stdlib packages of ECAL.\n"))
-								clt.WriteString(fmt.Sprintf("    !stdl - List all available constants and functions of a stdlib package.\n"))
-								clt.WriteString(fmt.Sprintf("    !lk   - Do a full text search through all docstrings.\n"))
+								clt.WriteString(fmt.Sprintf("    @syms - List all available inbuild functions and available stdlib packages of ECAL.\n"))
+								clt.WriteString(fmt.Sprintf("    @stdl - List all available constants and functions of a stdlib package.\n"))
+								clt.WriteString(fmt.Sprintf("    @lk   - Do a full text search through all docstrings.\n"))
 								clt.WriteString(fmt.Sprintf("\n"))
 
-							} else if line == "!funcs" {
+							} else if strings.HasPrefix(trimmedLine, "@syms") {
+								args := strings.Split(trimmedLine, " ")[1:]
+
+								// TODO Implement
+
+								clt.WriteString(fmt.Sprint("syms:", args))
 
 							} else if line == "!reset" {
 

+ 3 - 3
interpreter/func_provider.go

@@ -23,9 +23,9 @@ import (
 )
 
 /*
-inbuildFuncMap contains the mapping of inbuild functions.
+InbuildFuncMap contains the mapping of inbuild functions.
 */
-var inbuildFuncMap = map[string]util.ECALFunction{
+var InbuildFuncMap = map[string]util.ECALFunction{
 	"range":           &rangeFunc{&inbuildBaseFunc{}},
 	"new":             &newFunc{&inbuildBaseFunc{}},
 	"len":             &lenFunc{&inbuildBaseFunc{}},
@@ -517,7 +517,7 @@ func (rf *docFunc) Run(instanceID string, vs parser.Scope, is map[string]interfa
 
 				// Check for inbuild function
 
-				funcObj, ok = inbuildFuncMap[astring]
+				funcObj, ok = InbuildFuncMap[astring]
 			}
 		}
 

+ 1 - 1
interpreter/func_provider_test.go

@@ -308,7 +308,7 @@ identifier: a
 }
 
 func TestDocstrings(t *testing.T) {
-	for k, v := range inbuildFuncMap {
+	for k, v := range InbuildFuncMap {
 		if res, _ := v.DocString(); res == "" {
 			t.Error("Docstring missing for ", k)
 			return

+ 1 - 1
interpreter/rt_identifier.go

@@ -155,7 +155,7 @@ func (rt *identifierRuntime) resolveFunction(astring string, vs parser.Scope, is
 
 						// Check for inbuild function
 
-						funcObj, ok = inbuildFuncMap[astring]
+						funcObj, ok = InbuildFuncMap[astring]
 					}
 				}
 			}

+ 107 - 2
stdlib/generate/generate.go

@@ -14,7 +14,11 @@ import (
 	"bytes"
 	"flag"
 	"fmt"
+	"go/ast"
+	"go/build"
+	"go/doc"
 	"go/importer"
+	goparser "go/parser"
 	"go/token"
 	"go/types"
 	"io/ioutil"
@@ -33,20 +37,32 @@ 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"} }
+
+// =============EDIT HERE START=============
+
 var pkgNames = map[string][]string{
 	"math": {"Pi"},
 	"fmt":  {"Println", "Sprint"},
 }
 
+// ==============EDIT HERE END==============
+
 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
 
+	synopsis := make(map[string]string)
+	pkgDocs := make(map[string]*doc.Package)
+
 	flag.Parse()
 
 	// Make sure pkgNames is sorted
@@ -67,6 +83,17 @@ package stdlib
 
 	outbuf.WriteString("import (\n")
 	for _, pkgName := range importList {
+
+		if generateDoc {
+			syn, pkgDoc, err := getPackageDocs(pkgName)
+			if err != nil {
+				stderrPrint("Warning:", err)
+			} else {
+				synopsis[pkgName] = syn
+				pkgDocs[pkgName] = pkgDoc
+			}
+		}
+
 		outbuf.WriteString(fmt.Sprintf("\t\"%v\"\n", pkgName))
 	}
 
@@ -81,8 +108,12 @@ 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")
 
@@ -92,7 +123,8 @@ genStdlib contains all generated stdlib constructs.
 		pkgSymbols := pkgNames[pkgName]
 
 		if err == nil {
-			pkg, err = importer.ForCompiler(token.NewFileSet(), "source", nil).Import(pkgName)
+
+			pkg, err = importer.ForCompiler(fset, "source", nil).Import(pkgName)
 
 			if err == nil {
 				stdoutPrint("Generating adapter functions for", pkg)
@@ -178,8 +210,35 @@ var %vFuncMap = map[interface{}]interface{}{
 				}
 
 				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))
+							}
+						}
+					}
+				}
+
+				outbuf.WriteString("}\n\n")
+			}
 		}
 	}
 
@@ -192,6 +251,52 @@ var %vFuncMap = map[interface{}]interface{}{
 	}
 }
 
+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

+ 15 - 0
stdlib/generate/generate_test.go

@@ -22,6 +22,7 @@ const InvalidFileName = "**" + string(0x0)
 
 func TestGenerate(t *testing.T) {
 	filename = InvalidFileName
+	generateDoc = false
 
 	var buf bytes.Buffer
 	stderrPrint = func(v ...interface{}) (int, error) {
@@ -76,8 +77,10 @@ genStdlib contains all generated stdlib constructs.
 var genStdlib = map[interface{}]interface{}{
 	"fmt-const" : fmtConstMap,
 	"fmt-func" : fmtFuncMap,
+	"fmt-func-doc" : fmtFuncDocMap,
 	"math-const" : mathConstMap,
 	"math-func" : mathFuncMap,
+	"math-func-doc" : mathFuncDocMap,
 }
 
 /*
@@ -93,6 +96,12 @@ var fmtFuncMap = map[interface{}]interface{}{
 	"Println": &ECALFunctionAdapter{reflect.ValueOf(fmt.Println)},
 }
 
+/*
+fmtFuncDocMap contains the documentation of stdlib fmt functions.
+*/
+var fmtFuncDocMap = map[interface{}]interface{}{
+}
+
 /*
 mathConstMap contains the mapping of stdlib math constants.
 */
@@ -106,6 +115,12 @@ mathFuncMap contains the mapping of stdlib math functions.
 var mathFuncMap = map[interface{}]interface{}{
 }
 
+/*
+mathFuncDocMap contains the documentation of stdlib math functions.
+*/
+var mathFuncDocMap = map[interface{}]interface{}{
+}
+
 ` {
 		t.Errorf("Unexpected result: Go string: %#v\nNormal output: %v", string(out), string(out))
 		return

+ 10 - 0
stdlib/stdlib.go

@@ -89,6 +89,16 @@ func GetStdlibFunc(name string) (util.ECALFunction, bool) {
 	return nil, false
 }
 
+/*
+GetPkgDocString returns the docstring of a stdlib package.
+*/
+func GetPkgDocString(name string) (string, bool) {
+
+	// TODO Implement
+
+	return "", false
+}
+
 func splitModuleAndName(fullname string) (string, string) {
 	var module, name string
 

+ 7 - 0
stdlib/stdlib_test.go

@@ -11,10 +11,17 @@
 package stdlib
 
 import (
+	"fmt"
 	"math"
 	"testing"
 )
 
+func TestGetPkgDocString(t *testing.T) {
+	doc, ok := GetPkgDocString("math")
+
+	fmt.Println(doc, ok)
+}
+
 func TestSymbols(t *testing.T) {
 	p, c, f := GetStdlibSymbols()
 	if len(p) == 0 || len(c) == 0 || len(f) == 0 {