Files
aego/script/langsvc/signature.go
T

273 lines
6.7 KiB
Go

package langsvc
import (
"strings"
"aego/script/abi"
"aego/script/ast"
"aego/script/checker"
"aego/script/lexer"
"aego/script/resolver"
"aego/script/source"
"aego/script/token"
"aego/script/types"
"aego/script/workspace"
)
type sigBuilder struct {
b strings.Builder
params [][2]int
}
func (s *sigBuilder) param(text string) {
if len(s.params) > 0 {
s.b.WriteString(", ")
}
start := s.b.Len()
s.b.WriteString(text)
s.params = append(s.params, [2]int{start, s.b.Len()})
}
func funcSignature(snap *workspace.Snapshot, f *checker.Func) Signature {
var sb sigBuilder
sb.b.WriteString("func ")
if f.Script != nil {
sb.b.WriteString("(" + f.Script.Name + ") ")
}
sb.b.WriteString(f.Name + "(")
for _, p := range f.Params {
sb.param(p.Name + " " + typeString(p.Type))
}
sb.b.WriteByte(')')
if f.Result != nil && f.Result != types.Void {
sb.b.WriteString(" " + f.Result.String())
}
if f.Decl != nil && f.Decl.Coroutine {
sb.b.WriteString(" coroutine")
}
doc := ""
if f.Decl != nil {
doc = f.Decl.Doc
}
if doc == "" && f.Script != nil {
doc = builtinText(snap, f.Name)
}
return Signature{Label: sb.b.String(), Params: sb.params, Doc: doc}
}
func hostSignature(prefix string, s abi.Signature, names []string, doc, example string) Signature {
var sb sigBuilder
sb.b.WriteString(prefix + "(")
for i, p := range s.Params {
t := abiType(p)
if i < len(names) {
t = names[i] + " " + t
}
sb.param(t)
}
sb.b.WriteByte(')')
if s.Result.Kind != abi.Void {
sb.b.WriteString(" " + abiType(s.Result))
}
return Signature{Label: sb.b.String(), Params: sb.params, Doc: hostDoc(doc, example)}
}
func builtinSignature(snap *workspace.Snapshot, name string) (Signature, bool) {
var sb sigBuilder
switch name {
case "len":
sb.b.WriteString("func len(")
sb.param("v string | []T")
sb.b.WriteString(") int")
case "append":
sb.b.WriteString("func append(")
sb.param("s []T")
sb.param("v T")
sb.b.WriteString(") []T")
default:
return Signature{}, false
}
return Signature{Label: sb.b.String(), Params: sb.params, Doc: builtinText(snap, name)}, true
}
func (s *Service) SignatureAt(snap *workspace.Snapshot, file source.FileID, src []byte, off int) (Signature, bool) {
if snap == nil || off < 0 || off > len(src) {
return Signature{}, false
}
toks, _, _ := lexer.TokenizeAll(file, src[:off], source.NewInterner())
open, active, depth := -1, 0, 0
scan:
for i := len(toks) - 1; i >= 0; i-- {
t := toks[i]
switch t.Kind {
case token.RParen, token.RBrack, token.RBrace:
depth++
case token.LParen:
if depth == 0 {
open = i
break scan
}
depth--
case token.LBrack, token.LBrace:
if depth == 0 {
return Signature{}, false
}
depth--
case token.Comma:
if depth == 0 {
active++
}
case token.Semicolon:
if depth == 0 && !t.IsAuto() {
return Signature{}, false
}
}
}
if open < 1 || toks[open-1].Kind != token.Ident {
return Signature{}, false
}
name, qual := toks[open-1].Text, ""
if open >= 3 && toks[open-2].Kind == token.Period && toks[open-3].Kind == token.Ident {
qual = toks[open-3].Text
}
sig, ok := resolveCallee(snap, file, qual, name)
if !ok {
return Signature{}, false
}
sig.Active = min(active, len(sig.Params)-1)
return sig, true
}
func scopeOf(snap *workspace.Snapshot, file source.FileID) (*resolver.Scope, string) {
a := snap.Analysis
pkg := snap.Packages[file]
rp := a.Symbols.Packages[pkg]
if rp == nil {
return a.Symbols.Universe, pkg
}
if f := snap.Files[file]; f != nil {
if fs := rp.FileScopes[f]; fs != nil {
return fs, pkg
}
}
return rp.Scope, pkg
}
func resolveCallee(snap *workspace.Snapshot, file source.FileID, qual, name string) (Signature, bool) {
a := snap.Analysis
scope, pkg := scopeOf(snap, file)
if qual == "" {
if o := scope.Lookup(name); o != nil {
switch o.Kind {
case resolver.ObjFunc:
if f, ok := o.Data.(*checker.Func); ok {
return funcSignature(snap, f), true
}
case resolver.ObjBuiltin:
return builtinSignature(snap, name)
}
}
for _, st := range a.Scripts {
if st.Pkg != pkg {
continue
}
if f := st.Methods[name]; f != nil {
return funcSignature(snap, f), true
}
}
return Signature{}, false
}
if o := scope.Lookup(qual); o != nil && o.Kind == resolver.ObjPkg && o.Target != "" {
if o.Host {
sig, ok := snap.Bindings.Func(o.Target, name)
if !ok {
return Signature{}, false
}
f := funcDoc(snap.Docs, o.Target, name)
return hostSignature("func "+o.Target+"."+name, sig, f.Params, f.Doc, f.Example), true
}
if tp := a.Symbols.Packages[o.Target]; tp != nil {
if x := tp.Scope.Lookup(name); x != nil && x.Kind == resolver.ObjFunc {
if f, ok := x.Data.(*checker.Func); ok {
return funcSignature(snap, f), true
}
}
}
return Signature{}, false
}
if name == "Emit" || name == "Connect" {
if sg := signalNamed(a, pkg, qual); sg != nil {
return signalSignature(snap, sg, name), true
}
}
if f := scriptMethodNamed(a, pkg, name); f != nil {
return funcSignature(snap, f), true
}
for _, recv := range [...]abi.Kind{abi.NodeRef, abi.AssetRef} {
if sig, ok := snap.Bindings.Method(recv, name); ok {
me := methodDoc(snap.Docs, recv, name)
return hostSignature("func ("+recv.String()+") "+name, sig, me.Params, me.Doc, me.Example), true
}
}
if comp, sig, ok := uniqueCompMethod(snap, name); ok {
me := compMethodDoc(snap.Docs, comp.Name, name)
return hostSignature("func ("+comp.Short+") "+name, sig, me.Params, me.Doc, me.Example), true
}
return Signature{}, false
}
func (s *Service) HintsIn(snap *workspace.Snapshot, file source.FileID) []Hint {
if snap == nil {
return nil
}
f := snap.Files[file]
if f == nil {
return nil
}
a := snap.Analysis
var out []Hint
ast.Inspect(f, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if !ok || len(call.Args) < 2 {
return true
}
k := a.Calls[call]
if k == nil {
return true
}
var names []string
switch k.Kind {
case checker.CallFunc, checker.CallMethod:
if k.Func != nil {
for _, p := range k.Func.Params {
names = append(names, p.Name)
}
}
case checker.CallHost:
names = funcDoc(snap.Docs, k.Module, k.Name).Params
case checker.CallHostMethod:
names = methodDoc(snap.Docs, types.ToABI(a.TypeOf(k.Recv)).Kind, k.Name).Params
case checker.CallComponentMethod:
names = compMethodDoc(snap.Docs, k.Component.Name, k.Name).Params
}
for i, arg := range call.Args {
if i >= len(names) || names[i] == "" {
continue
}
switch x := arg.(type) {
case *ast.Ident:
if strings.EqualFold(x.Name, names[i]) {
continue
}
case *ast.SelectorExpr:
if strings.EqualFold(x.Sel.Name, names[i]) {
continue
}
}
out = append(out, Hint{Off: arg.Span().Start, Label: names[i] + ":"})
}
return true
})
return out
}