01658d04bb
This is the first change of many to merge the MuPack/MuIL formats into the heart of the "compiler". In fact, the entire meaning of the compiler has changed, from something that took metadata and produced CloudFormation, into something that takes MuPack/MuIL as input, and produces a MuGL graph as output. Although this process is distinctly different, there are several aspects we can reuse, like workspace management, dependency resolution, and some amount of name binding and symbol resolution, just as a few examples. An overview of the compilation process is available as a comment inside of the compiler.Compile function, although it is currently unimplemented. The relationship between Workspace and Compiler has been semi- inverted, such that all Compiler instances require a Workspace object. This is more natural anyway and moves some of the detection logic "outside" of the Compiler. Similarly, Options has moved to a top-level package, so that Workspace and Compiler may share access to it without causing package import cycles. Finally, all that templating crap is gone. This alone is cause for mass celebration!
392 lines
10 KiB
Go
392 lines
10 KiB
Go
// Copyright 2016 Marapongo, Inc. All rights reserved.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/marapongo/mu/pkg/cmdutil"
|
|
"github.com/marapongo/mu/pkg/pack"
|
|
"github.com/marapongo/mu/pkg/pack/ast"
|
|
"github.com/marapongo/mu/pkg/symbols"
|
|
"github.com/marapongo/mu/pkg/util/contract"
|
|
)
|
|
|
|
func newDescribeCmd() *cobra.Command {
|
|
var printAll bool
|
|
var printExports bool
|
|
var printIL bool
|
|
var printSymbols bool
|
|
var cmd = &cobra.Command{
|
|
Use: "describe [packages...]",
|
|
Short: "Describe a MuPackage",
|
|
Long: "Describe prints package, symbol, and IL information from one or more MuPackages.",
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
// If printAll is true, flip all the flags.
|
|
if printAll {
|
|
printExports = true
|
|
printIL = true
|
|
printSymbols = true
|
|
}
|
|
|
|
// Enumerate the list of packages, deserialize them, and print information.
|
|
for _, arg := range args {
|
|
pkg := cmdutil.ReadPackageFromArg(arg)
|
|
if pkg == nil {
|
|
break
|
|
}
|
|
printPackage(pkg, printSymbols, printExports, printIL)
|
|
}
|
|
},
|
|
}
|
|
|
|
cmd.PersistentFlags().BoolVarP(
|
|
&printSymbols, "all", "a", false,
|
|
"Print everything: the package, symbols, and MuIL")
|
|
cmd.PersistentFlags().BoolVarP(
|
|
&printExports, "exports", "e", false,
|
|
"Print just the exported symbols")
|
|
cmd.PersistentFlags().BoolVarP(
|
|
&printIL, "il", "i", false,
|
|
"Pretty-print the MuIL")
|
|
cmd.PersistentFlags().BoolVarP(
|
|
&printSymbols, "symbols", "s", false,
|
|
"Print a complete listing of all symbols, exported or otherwise")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func printComment(pc *string, indent string) {
|
|
// Prints a comment header using the given indentation, wrapping at 100 lines.
|
|
if pc != nil {
|
|
prefix := "// "
|
|
maxlen := 100 - len(indent)
|
|
|
|
// For every tab, chew up 3 more chars (so each one is 4 chars wide).
|
|
for _, i := range indent {
|
|
if i == '\t' {
|
|
maxlen -= 3
|
|
}
|
|
}
|
|
maxlen -= len(prefix)
|
|
if maxlen < 40 {
|
|
maxlen = 40
|
|
}
|
|
|
|
c := make([]rune, 0)
|
|
for _, r := range *pc {
|
|
c = append(c, r)
|
|
}
|
|
|
|
for len(c) > 0 {
|
|
fmt.Print(indent + prefix)
|
|
// Now, try to split the comment as close to maxlen-3 chars as possible (taking into account indent+"// "),
|
|
// but don't split words -- only split at whitespace characters if we can help it.
|
|
six := maxlen
|
|
for {
|
|
if len(c) <= six {
|
|
six = len(c)
|
|
break
|
|
} else if unicode.IsSpace(c[six]) {
|
|
// It's a space, set six to the first non-space character beforehand, and eix to the first
|
|
// non-space character afterwards.
|
|
for six > 0 && unicode.IsSpace(c[six-1]) {
|
|
six--
|
|
}
|
|
break
|
|
} else if six == 0 {
|
|
// We hit the start of the string and didn't find any spaces. Start over and try to find the
|
|
// first space *beyond* the start point (instead of *before*) and use that.
|
|
six = maxlen + 1
|
|
for six < len(c) && !unicode.IsSpace(c[six]) {
|
|
six++
|
|
}
|
|
break
|
|
}
|
|
|
|
// We need to keep searching, back up one and try again.
|
|
six--
|
|
}
|
|
|
|
// Print what we've got thus far, plus a newline.
|
|
fmt.Printf("%v\n", string(c[:six]))
|
|
|
|
// Now find the first non-space character beyond the split point and use that for the remainder.
|
|
eix := six
|
|
for eix < len(c) && unicode.IsSpace(c[eix]) {
|
|
eix++
|
|
}
|
|
c = c[eix:]
|
|
}
|
|
}
|
|
}
|
|
|
|
// printPackage pretty-prints the package metadata.
|
|
func printPackage(pkg *pack.Package, printSymbols bool, printExports bool, printIL bool) {
|
|
printComment(pkg.Description, "")
|
|
fmt.Printf("package \"%v\" {\n", pkg.Name)
|
|
|
|
if pkg.Author != nil {
|
|
fmt.Printf("%vauthor \"%v\"\n", tab, *pkg.Author)
|
|
}
|
|
if pkg.Website != nil {
|
|
fmt.Printf("%vwebsite \"%v\"\n", tab, *pkg.Website)
|
|
}
|
|
if pkg.License != nil {
|
|
fmt.Printf("%vlicense \"%v\"\n", tab, *pkg.License)
|
|
}
|
|
|
|
// Print the dependencies:
|
|
fmt.Printf("%vdependencies [", tab)
|
|
if pkg.Dependencies != nil && len(*pkg.Dependencies) > 0 {
|
|
fmt.Printf("\n")
|
|
for _, dep := range *pkg.Dependencies {
|
|
fmt.Printf("%v\"%v\"\n", tab+tab, dep)
|
|
}
|
|
fmt.Printf("%v", tab)
|
|
}
|
|
fmt.Printf("]\n")
|
|
|
|
// Print the modules (just names by default, or full symbols and/or IL if requested).
|
|
printModules(pkg, printSymbols, printExports, printIL, tab)
|
|
|
|
fmt.Printf("}\n")
|
|
}
|
|
|
|
func printModules(pkg *pack.Package, printSymbols bool, printExports bool, printIL bool, indent string) {
|
|
for _, name := range ast.StableModules(*pkg.Modules) {
|
|
mod := (*pkg.Modules)[name]
|
|
|
|
// Print the name.
|
|
fmt.Printf("%vmodule \"%v\" {", indent, name)
|
|
|
|
// Now, if requested, print the symbols.
|
|
if printSymbols || printExports {
|
|
if mod.Imports != nil || mod.Members != nil {
|
|
fmt.Printf("\n")
|
|
|
|
if mod.Imports != nil {
|
|
// Print the imports.
|
|
fmt.Printf("%vimports [", indent+tab)
|
|
if mod.Imports != nil && len(*mod.Imports) > 0 {
|
|
fmt.Printf("\n")
|
|
for _, imp := range *mod.Imports {
|
|
fmt.Printf("%v\"%v\"\n", indent+tab+tab, imp)
|
|
}
|
|
fmt.Printf("%v", indent+tab)
|
|
}
|
|
fmt.Printf("]\n")
|
|
}
|
|
|
|
if mod.Members != nil {
|
|
// Print the members.
|
|
for _, member := range ast.StableModuleMembers(*mod.Members) {
|
|
printModuleMember(member, (*mod.Members)[member], printExports, indent+tab)
|
|
}
|
|
fmt.Printf("%v", indent)
|
|
}
|
|
}
|
|
} else {
|
|
// Print a "..." so that it's clear we're omitting information, versus the module being empty.
|
|
fmt.Printf("...")
|
|
}
|
|
fmt.Printf("}\n")
|
|
}
|
|
}
|
|
|
|
func printModuleMember(name symbols.Token, member ast.ModuleMember, exportOnly bool, indent string) {
|
|
printComment(member.GetDescription(), indent)
|
|
|
|
acc := member.GetAccess()
|
|
if !exportOnly || (acc != nil && *acc == symbols.PublicAccessibility) {
|
|
switch member.GetKind() {
|
|
case ast.ExportKind:
|
|
printExport(name, member.(*ast.Export), indent)
|
|
case ast.ClassKind:
|
|
printClass(name, member.(*ast.Class), exportOnly, indent)
|
|
case ast.ModulePropertyKind:
|
|
printModuleProperty(name, member.(*ast.ModuleProperty), indent)
|
|
case ast.ModuleMethodKind:
|
|
printModuleMethod(name, member.(*ast.ModuleMethod), indent)
|
|
default:
|
|
contract.Failf("Unexpected ModuleMember kind: %v\n", member.GetKind())
|
|
}
|
|
}
|
|
}
|
|
|
|
func printExport(name symbols.Token, export *ast.Export, indent string) {
|
|
var mods []string
|
|
if export.Access != nil {
|
|
mods = append(mods, string(*export.Access))
|
|
}
|
|
fmt.Printf("%vexport \"%v\"%v %v\n", indent, name, modString(mods), export.Token)
|
|
}
|
|
|
|
func printClass(name symbols.Token, class *ast.Class, exportOnly bool, indent string) {
|
|
fmt.Printf("%vclass \"%v\"", indent, name)
|
|
|
|
var mods []string
|
|
if class.Access != nil {
|
|
mods = append(mods, string(*class.Access))
|
|
}
|
|
if class.Sealed != nil && *class.Sealed {
|
|
mods = append(mods, "sealed")
|
|
}
|
|
if class.Abstract != nil && *class.Abstract {
|
|
mods = append(mods, "abstract")
|
|
}
|
|
if class.Record != nil && *class.Record {
|
|
mods = append(mods, "record")
|
|
}
|
|
if class.Interface != nil && *class.Interface {
|
|
mods = append(mods, "interface")
|
|
}
|
|
fmt.Printf(modString(mods))
|
|
|
|
if class.Extends != nil {
|
|
fmt.Printf("\n%vextends %v", indent+tab+tab, string(*class.Extends))
|
|
}
|
|
if class.Implements != nil {
|
|
for _, impl := range *class.Implements {
|
|
fmt.Printf("\n%vimplements %v", indent+tab+tab, string(impl))
|
|
}
|
|
}
|
|
|
|
fmt.Printf(" {")
|
|
if class.Members != nil {
|
|
fmt.Printf("\n")
|
|
for _, member := range ast.StableClassMembers(*class.Members) {
|
|
printClassMember(member, (*class.Members)[member], exportOnly, indent+tab)
|
|
}
|
|
fmt.Printf(indent)
|
|
}
|
|
fmt.Printf("}\n")
|
|
}
|
|
|
|
func printClassMember(name symbols.Token, member ast.ClassMember, exportOnly bool, indent string) {
|
|
printComment(member.GetDescription(), indent)
|
|
|
|
acc := member.GetAccess()
|
|
if !exportOnly || (acc != nil && *acc == symbols.PublicClassAccessibility) {
|
|
switch member.GetKind() {
|
|
case ast.ClassPropertyKind:
|
|
printClassProperty(name, member.(*ast.ClassProperty), indent)
|
|
case ast.ClassMethodKind:
|
|
printClassMethod(name, member.(*ast.ClassMethod), indent)
|
|
default:
|
|
contract.Failf("Unexpected ClassMember kind: %v\n", member.GetKind())
|
|
}
|
|
}
|
|
}
|
|
|
|
func printClassProperty(name symbols.Token, prop *ast.ClassProperty, indent string) {
|
|
var mods []string
|
|
if prop.Access != nil {
|
|
mods = append(mods, string(*prop.Access))
|
|
}
|
|
if prop.Static != nil && *prop.Static {
|
|
mods = append(mods, "static")
|
|
}
|
|
if prop.Readonly != nil && *prop.Readonly {
|
|
mods = append(mods, "readonly")
|
|
}
|
|
fmt.Printf("%vproperty \"%v\"%v", indent, name, modString(mods))
|
|
if prop.Type != nil {
|
|
fmt.Printf(": %v", *prop.Type)
|
|
}
|
|
fmt.Printf("\n")
|
|
}
|
|
|
|
func printClassMethod(name symbols.Token, meth *ast.ClassMethod, indent string) {
|
|
var mods []string
|
|
if meth.Access != nil {
|
|
mods = append(mods, string(*meth.Access))
|
|
}
|
|
if meth.Static != nil && *meth.Static {
|
|
mods = append(mods, "static")
|
|
}
|
|
if meth.Sealed != nil && *meth.Sealed {
|
|
mods = append(mods, "sealed")
|
|
}
|
|
if meth.Abstract != nil && *meth.Abstract {
|
|
mods = append(mods, "abstract")
|
|
}
|
|
fmt.Printf("%vmethod \"%v\"%v: %v\n", indent, name, modString(mods), funcSig(meth))
|
|
}
|
|
|
|
func printModuleMethod(name symbols.Token, meth *ast.ModuleMethod, indent string) {
|
|
var mods []string
|
|
if meth.Access != nil {
|
|
mods = append(mods, string(*meth.Access))
|
|
}
|
|
fmt.Printf("%vmethod \"%v\"%v: %v\n", indent, name, modString(mods), funcSig(meth))
|
|
}
|
|
|
|
func printModuleProperty(name symbols.Token, prop *ast.ModuleProperty, indent string) {
|
|
var mods []string
|
|
if prop.Access != nil {
|
|
mods = append(mods, string(*prop.Access))
|
|
}
|
|
if prop.Readonly != nil && *prop.Readonly {
|
|
mods = append(mods, "readonly")
|
|
}
|
|
fmt.Printf("%vproperty \"%v\"%v", indent, name, modString(mods))
|
|
if prop.Type != nil {
|
|
fmt.Printf(": %v", *prop.Type)
|
|
}
|
|
fmt.Printf("\n")
|
|
}
|
|
|
|
func modString(mods []string) string {
|
|
if len(mods) == 0 {
|
|
return ""
|
|
}
|
|
s := " ["
|
|
for i, mod := range mods {
|
|
if i > 0 {
|
|
s += ", "
|
|
}
|
|
s += mod
|
|
}
|
|
s += "]"
|
|
return s
|
|
}
|
|
|
|
// spaces returns a string with the given number of spaces.
|
|
func spaces(num int) string {
|
|
return strings.Repeat(" ", num)
|
|
}
|
|
|
|
// tab is a tab represented as spaces, since some consoles have ridiculously wide true tabs.
|
|
var tab = spaces(4)
|
|
|
|
func funcSig(fun ast.Function) string {
|
|
sig := "("
|
|
|
|
// To create a signature, first concatenate the parameters.
|
|
params := fun.GetParameters()
|
|
if params != nil {
|
|
for i, param := range *params {
|
|
if i > 0 {
|
|
sig += ", "
|
|
}
|
|
sig += string(param.Name.Ident)
|
|
if param.Type != nil {
|
|
sig += ": " + string(*param.Type)
|
|
}
|
|
}
|
|
}
|
|
sig += ")"
|
|
|
|
// And then the return type, if present.
|
|
ret := fun.GetReturnType()
|
|
if ret != nil {
|
|
sig += ": " + string(*ret)
|
|
}
|
|
|
|
return sig
|
|
}
|