2025-10-15 10:12:44 +03:00

219 lines
6.6 KiB
Go

package api
import (
"errors"
"fmt"
"strconv"
"strings"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/project"
"github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
)
var (
ErrInvalidRequest = errors.New("api: invalid request")
ErrClientError = errors.New("api: client error")
)
type Method string
type Handle[T any] string
const (
handlePrefixProject = 'p'
handlePrefixSymbol = 's'
handlePrefixType = 't'
handlePrefixFile = 'f'
handlePrefixNode = 'n'
)
func ProjectHandle(p *project.Project) Handle[project.Project] {
return createHandle[project.Project](handlePrefixProject, p.Name())
}
func SymbolHandle(symbol *ast.Symbol) Handle[ast.Symbol] {
return createHandle[ast.Symbol](handlePrefixSymbol, ast.GetSymbolId(symbol))
}
func TypeHandle(t *checker.Type) Handle[checker.Type] {
return createHandle[checker.Type](handlePrefixType, t.Id())
}
func FileHandle(file *ast.SourceFile) Handle[ast.SourceFile] {
return createHandle[ast.SourceFile](handlePrefixFile, ast.GetNodeId(file.AsNode()))
}
func NodeHandle(node *ast.Node) Handle[ast.Node] {
fileHandle := FileHandle(ast.GetSourceFileOfNode(node))
return Handle[ast.Node](fmt.Sprintf("%s.%d.%d", fileHandle, node.Pos(), node.Kind))
}
func parseNodeHandle(handle Handle[ast.Node]) (Handle[ast.SourceFile], int, ast.Kind, error) {
parts := strings.SplitN(string(handle), ".", 3)
if len(parts) != 3 {
return "", 0, 0, fmt.Errorf("invalid node handle %q", handle)
}
fileHandle := Handle[ast.SourceFile](parts[0])
pos, err := strconv.ParseInt(parts[1], 10, 32)
if err != nil {
return "", 0, 0, fmt.Errorf("invalid node handle %q: %w", handle, err)
}
kind, err := strconv.ParseInt(parts[2], 10, 16)
if err != nil {
return "", 0, 0, fmt.Errorf("invalid node handle %q: %w", handle, err)
}
return fileHandle, int(pos), ast.Kind(kind), nil
}
func createHandle[T any](prefix rune, id any) Handle[T] {
return Handle[T](fmt.Sprintf("%c%016x", prefix, id))
}
const (
MethodConfigure Method = "configure"
MethodRelease Method = "release"
MethodParseConfigFile Method = "parseConfigFile"
MethodLoadProject Method = "loadProject"
MethodGetSymbolAtPosition Method = "getSymbolAtPosition"
MethodGetSymbolsAtPositions Method = "getSymbolsAtPositions"
MethodGetSymbolAtLocation Method = "getSymbolAtLocation"
MethodGetSymbolsAtLocations Method = "getSymbolsAtLocations"
MethodGetTypeOfSymbol Method = "getTypeOfSymbol"
MethodGetTypesOfSymbols Method = "getTypesOfSymbols"
MethodGetSourceFile Method = "getSourceFile"
)
var unmarshalers = map[Method]func([]byte) (any, error){
MethodRelease: unmarshallerFor[string],
MethodParseConfigFile: unmarshallerFor[ParseConfigFileParams],
MethodLoadProject: unmarshallerFor[LoadProjectParams],
MethodGetSourceFile: unmarshallerFor[GetSourceFileParams],
MethodGetSymbolAtPosition: unmarshallerFor[GetSymbolAtPositionParams],
MethodGetSymbolsAtPositions: unmarshallerFor[GetSymbolsAtPositionsParams],
MethodGetSymbolAtLocation: unmarshallerFor[GetSymbolAtLocationParams],
MethodGetSymbolsAtLocations: unmarshallerFor[GetSymbolsAtLocationsParams],
MethodGetTypeOfSymbol: unmarshallerFor[GetTypeOfSymbolParams],
MethodGetTypesOfSymbols: unmarshallerFor[GetTypesOfSymbolsParams],
}
type ConfigureParams struct {
Callbacks []string `json:"callbacks"`
LogFile string `json:"logFile"`
}
type ParseConfigFileParams struct {
FileName string `json:"fileName"`
}
type ConfigFileResponse struct {
FileNames []string `json:"fileNames"`
Options *core.CompilerOptions `json:"options"`
}
type LoadProjectParams struct {
ConfigFileName string `json:"configFileName"`
}
type ProjectResponse struct {
Id Handle[project.Project] `json:"id"`
ConfigFileName string `json:"configFileName"`
RootFiles []string `json:"rootFiles"`
CompilerOptions *core.CompilerOptions `json:"compilerOptions"`
}
func NewProjectResponse(project *project.Project) *ProjectResponse {
return &ProjectResponse{
Id: ProjectHandle(project),
ConfigFileName: project.Name(),
RootFiles: project.CommandLine.FileNames(),
CompilerOptions: project.CommandLine.CompilerOptions(),
}
}
type GetSymbolAtPositionParams struct {
Project Handle[project.Project] `json:"project"`
FileName string `json:"fileName"`
Position uint32 `json:"position"`
}
type GetSymbolsAtPositionsParams struct {
Project Handle[project.Project] `json:"project"`
FileName string `json:"fileName"`
Positions []uint32 `json:"positions"`
}
type GetSymbolAtLocationParams struct {
Project Handle[project.Project] `json:"project"`
Location Handle[ast.Node] `json:"location"`
}
type GetSymbolsAtLocationsParams struct {
Project Handle[project.Project] `json:"project"`
Locations []Handle[ast.Node] `json:"locations"`
}
type SymbolResponse struct {
Id Handle[ast.Symbol] `json:"id"`
Name string `json:"name"`
Flags uint32 `json:"flags"`
CheckFlags uint32 `json:"checkFlags"`
}
func NewSymbolResponse(symbol *ast.Symbol) *SymbolResponse {
return &SymbolResponse{
Id: SymbolHandle(symbol),
Name: symbol.Name,
Flags: uint32(symbol.Flags),
CheckFlags: uint32(symbol.CheckFlags),
}
}
type GetTypeOfSymbolParams struct {
Project Handle[project.Project] `json:"project"`
Symbol Handle[ast.Symbol] `json:"symbol"`
}
type GetTypesOfSymbolsParams struct {
Project Handle[project.Project] `json:"project"`
Symbols []Handle[ast.Symbol] `json:"symbols"`
}
type TypeResponse struct {
Id Handle[checker.Type] `json:"id"`
Flags uint32 `json:"flags"`
}
func NewTypeData(t *checker.Type) *TypeResponse {
return &TypeResponse{
Id: TypeHandle(t),
Flags: uint32(t.Flags()),
}
}
type GetSourceFileParams struct {
Project Handle[project.Project] `json:"project"`
FileName string `json:"fileName"`
}
func unmarshalPayload(method string, payload jsontext.Value) (any, error) {
unmarshaler, ok := unmarshalers[Method(method)]
if !ok {
return nil, fmt.Errorf("unknown API method %q", method)
}
return unmarshaler(payload)
}
func unmarshallerFor[T any](data []byte) (any, error) {
var v T
if err := json.Unmarshal(data, &v); err != nil {
return nil, fmt.Errorf("failed to unmarshal %T: %w", (*T)(nil), err)
}
return &v, nil
}