Documentation
¶
Overview ¶
Package analysis provides scope-aware semantic analysis for ELPS lisp source.
The analyzer builds a scope tree from parsed expressions, resolves symbol references, and identifies unresolved symbols. It is designed to be used by lint analyzers for semantic checks like undefined-symbol and unused-variable.
Index ¶
- Constants
- func ExtractPackageExports(reg *lisp.PackageRegistry) map[string][]ExternalSymbol
- func MatchesExclude(path string, patterns []string) bool
- func ScanWorkspaceAll(root string) (globals []ExternalSymbol, pkgs map[string][]ExternalSymbol, ...)
- func ScanWorkspaceAllWithConfig(root string, scanCfg *ScanConfig) (globals []ExternalSymbol, pkgs map[string][]ExternalSymbol, ...)
- func ScanWorkspacePackages(root string) (map[string][]ExternalSymbol, error)
- func ScanWorkspaceRefs(root string, cfg *Config, scanCfg *ScanConfig) map[string][]FileReference
- func ShouldSkipDir(name string) bool
- func SortDefinitions(syms []ExternalSymbol)
- type Config
- type DefFormSpec
- type ExternalSymbol
- func ExtractFileDefinitions(source []byte, filename string) []ExternalSymbol
- func FindExternalSymbol(pkgMap map[string][]ExternalSymbol, pkgName, symName string) *ExternalSymbol
- func PreferredDefinition(syms []ExternalSymbol) *ExternalSymbol
- func ScanWorkspace(root string) ([]ExternalSymbol, error)
- func ScanWorkspaceDefinitions(root string) ([]ExternalSymbol, error)
- func ScanWorkspaceFull(root string) ([]ExternalSymbol, map[string][]ExternalSymbol, error)
- type FileReference
- type Reference
- type Result
- type ScanConfig
- type Scope
- func (s *Scope) Define(sym *Symbol)
- func (s *Scope) DefineImported(sym *Symbol, pkg string)
- func (s *Scope) DefineQualifiedOnly(sym *Symbol)
- func (s *Scope) Lookup(name string) *Symbol
- func (s *Scope) LookupAllLocal(name string) []*Symbol
- func (s *Scope) LookupInPackage(name, pkg string) *Symbol
- func (s *Scope) LookupLocal(name string) *Symbol
- func (s *Scope) LookupLocalInPackage(name, pkg string) *Symbol
- func (s *Scope) LookupLocalVisible(name, pkg string) *Symbol
- type ScopeKind
- type Signature
- type Symbol
- type SymbolKey
- type SymbolKind
- type UnresolvedRef
- type WorkspacePrescan
Constants ¶
const ( // DefaultMaxWorkspaceFiles limits the number of .lisp files scanned. DefaultMaxWorkspaceFiles = 5000 // DefaultMaxFileBytes limits individual file size (5 MB). DefaultMaxFileBytes = 5 * 1024 * 1024 )
Default limits for workspace scanning.
Variables ¶
This section is empty.
Functions ¶
func ExtractPackageExports ¶
func ExtractPackageExports(reg *lisp.PackageRegistry) map[string][]ExternalSymbol
ExtractPackageExports creates a map of package name to exported symbols from a loaded runtime's package registry. This is used to resolve use-package imports for packages defined in Go (stdlib) that cannot be found by workspace scanning.
func MatchesExclude ¶ added in v1.40.0
MatchesExclude returns true if the given path matches any of the exclude patterns. Each pattern is matched against the full path and each path component (directories + filename), using filepath.Match semantics.
func ScanWorkspaceAll ¶ added in v1.39.0
func ScanWorkspaceAll(root string) (globals []ExternalSymbol, pkgs map[string][]ExternalSymbol, allDefs []ExternalSymbol, err error)
ScanWorkspaceAll combines ScanWorkspaceFull and ScanWorkspaceDefinitions into a single pass: each file is parsed once and all three results are extracted from the same AST.
func ScanWorkspaceAllWithConfig ¶ added in v1.40.0
func ScanWorkspaceAllWithConfig(root string, scanCfg *ScanConfig) (globals []ExternalSymbol, pkgs map[string][]ExternalSymbol, allDefs []ExternalSymbol, truncated bool, err error)
ScanWorkspaceAllWithConfig is like ScanWorkspaceAll but accepts a ScanConfig for configurable limits. It also returns whether the file list was truncated due to hitting the MaxFiles limit.
Delegates to PrescanWorkspace internally to avoid duplicating the concurrent worker pool logic.
func ScanWorkspacePackages ¶
func ScanWorkspacePackages(root string) (map[string][]ExternalSymbol, error)
ScanWorkspacePackages is like ScanWorkspace but returns a map of package name to exported symbols, suitable for use as Config.PackageExports.
func ScanWorkspaceRefs ¶ added in v1.31.0
func ScanWorkspaceRefs(root string, cfg *Config, scanCfg *ScanConfig) map[string][]FileReference
ScanWorkspaceRefs walks the workspace directory tree, performs full analysis on each .lisp file, and extracts cross-file references. The cfg should have ExtraGlobals and PackageExports populated from a prior ScanWorkspaceFull call. Parsing is done concurrently. The optional scanCfg controls file collection limits and excludes.
Returns a map from SymbolKey.String() to FileReference slices.
func ShouldSkipDir ¶ added in v1.32.0
ShouldSkipDir returns true for directories that should not be walked. It skips hidden directories (e.g. .git, .vscode), underscore-prefixed directories (e.g. _archive, _old), and common dependency/build output directories — but not "." or ".." which represent the current/parent directory.
func SortDefinitions ¶ added in v1.40.0
func SortDefinitions(syms []ExternalSymbol)
SortDefinitions sorts external symbols in-place by deterministic priority: lexicographically smallest Source.File, then earliest Source.Line, then earliest Source.Col. Symbols with nil Source are pushed to the end.
Types ¶
type Config ¶
type Config struct {
// ExtraGlobals are symbols from other files (e.g. workspace scanning).
ExtraGlobals []ExternalSymbol
// PackageExports maps package names to their exported symbols.
// Used to resolve use-package imports from stdlib and workspace packages.
PackageExports map[string][]ExternalSymbol
// PackageSymbols maps package names to ALL symbols (exported and
// non-exported). Used by resolveQualifiedSymbol as a fallback when
// a qualified reference (pkg:sym) targets a non-exported symbol.
// ELPS runtime allows qualified access to any symbol in a package,
// not just exported ones.
PackageSymbols map[string][]ExternalSymbol
// DefForms declares additional definition-form heads for embedding programs.
// Head is matched exactly against the form head symbol. FormalsIndex must
// point at the parameter list cell. If BindsName is true, NameIndex points
// at the defined symbol cell and the analyzer registers it using NameKind.
DefForms []DefFormSpec
// PackageImports maps package name to imported package names collected
// from cross-file use-package declarations during workspace prescan.
// Per-file analysis applies these imports so symbols from packages
// imported in other files (e.g. main.lisp) are available.
PackageImports map[string][]string
// DefaultPackage overrides the default "user" package for bare files
// (no in-package declaration). Derived from main.lisp's in-package.
DefaultPackage string
// WorkspaceRefs maps SymbolKey.String() to cross-file references.
// When set, analyzers can check whether a symbol is referenced from
// other files in the workspace.
WorkspaceRefs map[string][]FileReference
// Filename is the source file being analyzed.
Filename string
}
Config controls the behavior of the analyzer.
type DefFormSpec ¶ added in v1.38.0
type DefFormSpec struct {
Head string
FormalsIndex int
BindsName bool
NameIndex int
NameKind SymbolKind
}
DefFormSpec describes a custom definition-like form.
type ExternalSymbol ¶
type ExternalSymbol struct {
Name string
Kind SymbolKind
Package string
Signature *Signature
Source *token.Location
DocString string
}
ExternalSymbol represents a symbol defined in another file.
func ExtractFileDefinitions ¶ added in v1.40.0
func ExtractFileDefinitions(source []byte, filename string) []ExternalSymbol
ExtractFileDefinitions parses source and returns all top-level definitions from a single file. This is the public counterpart of extractDefinitions, suitable for incremental workspace index updates.
func FindExternalSymbol ¶ added in v1.46.2
func FindExternalSymbol(pkgMap map[string][]ExternalSymbol, pkgName, symName string) *ExternalSymbol
FindExternalSymbol looks up a symbol by name in a package-to-symbols map.
func PreferredDefinition ¶ added in v1.40.0
func PreferredDefinition(syms []ExternalSymbol) *ExternalSymbol
PreferredDefinition returns the preferred definition from a set of symbols with the same name. The winner is chosen by: lexicographically smallest Source.File, then earliest Source.Line, then earliest Source.Col. Symbols with nil Source are ranked last. Returns nil if the slice is empty. Note: sorts the input slice in-place as a side effect.
func ScanWorkspace ¶
func ScanWorkspace(root string) ([]ExternalSymbol, error)
ScanWorkspace walks a directory tree, parsing all .lisp files and extracting exported top-level definitions. The result can be used as Config.ExtraGlobals for cross-file symbol resolution.
Files that fail to parse are silently skipped (fault tolerant).
func ScanWorkspaceDefinitions ¶ added in v1.39.0
func ScanWorkspaceDefinitions(root string) ([]ExternalSymbol, error)
ScanWorkspaceDefinitions walks a directory tree and returns all top-level definitions, including non-exported symbols. The result is intended for workspace symbol search, not cross-file resolution.
func ScanWorkspaceFull ¶ added in v1.27.0
func ScanWorkspaceFull(root string) ([]ExternalSymbol, map[string][]ExternalSymbol, error)
ScanWorkspaceFull walks a directory tree in a single pass, parsing all .lisp files and extracting both global symbols and package exports. It skips directories matched by ShouldSkipDir (hidden, underscore-prefixed, node_modules, vendor, build). Stops collecting after maxWorkspaceFiles. Parsing is done concurrently using a bounded worker pool.
Files that fail to parse are silently skipped (fault tolerant).
type FileReference ¶ added in v1.31.0
type FileReference struct {
SymbolKey SymbolKey
Source *token.Location
File string // absolute path
Enclosing string // name of enclosing function ("" if top-level)
EnclosingSource *token.Location // definition site of enclosing function
EnclosingKind SymbolKind // kind of enclosing function
}
FileReference represents a cross-file reference to a symbol.
func ExtractFileRefs ¶ added in v1.31.0
func ExtractFileRefs(result *Result, filePath string) []FileReference
ExtractFileRefs extracts cross-file-trackable references from an analysis result. Only references to global-scope, non-builtin symbols are included (builtins, special ops, parameters, and locals are skipped).
type Result ¶
type Result struct {
RootScope *Scope
Symbols []*Symbol
References []*Reference
Unresolved []*UnresolvedRef
// ExtraGlobals are the external symbols that were provided via Config.
// Stored here so downstream consumers (e.g. lint analyzers) can check
// for cross-file duplicates without relying on scope lookups that may
// have been overwritten by local definitions.
ExtraGlobals []ExternalSymbol
// WorkspaceRefs maps SymbolKey.String() to cross-file references.
// Copied from Config. Used by lint analyzers to check whether a symbol
// is referenced from other workspace files (e.g. unused-function check).
WorkspaceRefs map[string][]FileReference
}
Result holds the output of semantic analysis.
type ScanConfig ¶ added in v1.40.0
type ScanConfig struct {
// MaxFiles is the maximum number of .lisp files to collect.
// 0 means use DefaultMaxWorkspaceFiles.
MaxFiles int
// MaxFileBytes is the maximum size in bytes for a single file.
// Files exceeding this are skipped. 0 means use DefaultMaxFileBytes.
MaxFileBytes int64
// Excludes are glob patterns for files to skip during collection.
// Patterns are matched against the full path, base name, and each
// directory component using filepath.Match semantics.
Excludes []string
// IncludeDirs are directory names that override ShouldSkipDir.
// If a directory name matches any entry, it will be walked even if
// ShouldSkipDir would normally skip it (e.g. "_examples").
IncludeDirs []string
}
ScanConfig controls workspace file collection limits.
type Scope ¶
type Scope struct {
Kind ScopeKind
Parent *Scope
Children []*Scope
Symbols map[string]*Symbol
PackageSymbols map[string]*Symbol
PackageImports map[string]map[string]*Symbol
Node *lisp.LVal // the AST node that introduced this scope
// contains filtered or unexported fields
}
Scope represents a lexical scope in the source.
func ScopeAtPosition ¶ added in v1.31.0
ScopeAtPosition returns the innermost scope that contains the given 1-based ELPS line and column. It walks the scope tree depth-first.
func (*Scope) DefineImported ¶ added in v1.39.0
DefineImported adds a symbol that should be visible both by package-qualified name and as an unqualified import in the current scope.
func (*Scope) DefineQualifiedOnly ¶ added in v1.39.0
DefineQualifiedOnly adds a package-qualified symbol without adding it to the bare-name Symbols map. The symbol is still reachable through Lookup/LookupLocal via the bareNameIndex for package-agnostic callers (e.g., minifier, lint arity checker).
func (*Scope) Lookup ¶
Lookup resolves a symbol by walking the parent chain. Returns nil if the symbol is not found.
func (*Scope) LookupAllLocal ¶ added in v1.39.0
LookupAllLocal returns all symbols matching a bare name in this scope, across Symbols and all PackageSymbols entries. Used by call hierarchy to find all package variants of a function name.
func (*Scope) LookupInPackage ¶ added in v1.39.0
LookupInPackage resolves a symbol by preferring a package-qualified match in the current scope chain before falling back to a bare-name lookup. The bare-name fallback into Symbols is intentional: builtins and user-package symbols are registered with Package == "" and must be reachable when no qualified entry matches.
Unlike Lookup, LookupInPackage does not consult bareNameIndex — a symbol registered only via DefineQualifiedOnly is invisible to LookupInPackage unless the correct package is specified.
func (*Scope) LookupLocal ¶
LookupLocal resolves a symbol only in this scope (not parents).
func (*Scope) LookupLocalInPackage ¶ added in v1.39.0
LookupLocalInPackage resolves a symbol in the current scope, preferring the package-qualified key when a package is provided.
The bare-name fallback into Symbols is restricted to the default user package. This prevents builtins (Package == "") from blocking package-local definitions like (in-package 'foo) (defun set ...). Unlike LookupInPackage, this method does not consult bareNameIndex.
func (*Scope) LookupLocalVisible ¶ added in v1.39.0
LookupLocalVisible resolves a symbol as it would be seen unqualified in the current scope: first the active package, then imported/bare symbols.
type Signature ¶
Signature describes the parameter signature of a callable symbol.
type Symbol ¶
type Symbol struct {
Name string
Package string
Kind SymbolKind
Source *token.Location // nil for builtins
Node *lisp.LVal
Scope *Scope
Signature *Signature // non-nil for callables
DocString string
References int
Exported bool
External bool // true for workspace-scanned or package-imported symbols
}
Symbol represents a defined name in a scope.
func FindEnclosingFunction ¶ added in v1.31.0
FindEnclosingFunction finds the function symbol that contains the given 1-based position by walking the scope tree.
type SymbolKey ¶ added in v1.31.0
type SymbolKey struct {
Package string
Name string
Kind SymbolKind
}
SymbolKey identifies a symbol across files by name and kind.
func SymbolKeyFromNameKind ¶ added in v1.31.0
SymbolKeyFromNameKind creates a SymbolKey from raw name and kind strings. The kind string should match SymbolKind.String() output.
func SymbolToKey ¶ added in v1.31.0
SymbolToKey derives a SymbolKey from an analysis Symbol.
type SymbolKind ¶
type SymbolKind int
SymbolKind classifies a symbol definition.
const ( SymVariable SymbolKind = iota // set, let binding SymFunction // defun, flet/labels binding SymMacro // defmacro SymParameter // function/lambda parameter SymSpecialOp // special operator (if, cond, etc.) SymBuiltin // builtin function SymType // deftype )
func (SymbolKind) String ¶
func (k SymbolKind) String() string
type UnresolvedRef ¶
type UnresolvedRef struct {
Name string
Source *token.Location
Node *lisp.LVal
// InsideMacroCall is true when the unresolved reference appears inside
// a user-defined macro call body. Macros may introduce bindings at
// expansion time that are invisible to static analysis.
InsideMacroCall bool
}
UnresolvedRef records a symbol usage that could not be resolved.
type WorkspacePrescan ¶ added in v1.40.0
type WorkspacePrescan struct {
// Files is the list of .lisp file paths that were scanned.
Files []string
// Truncated is true if the file limit was reached during collection.
Truncated bool
// ExportedGlobals are exported top-level definitions only.
// Used by ScanWorkspaceAllWithConfig for backwards compatibility.
ExportedGlobals []ExternalSymbol
// PkgExports maps package name to exported symbols.
PkgExports map[string][]ExternalSymbol
// AllDefs are all definitions (exported and non-exported).
// Typically used as Config.ExtraGlobals for cross-file resolution.
AllDefs []ExternalSymbol
// PkgAllSymbols maps package name to ALL symbols (exported and
// non-exported). Used for qualified symbol resolution since ELPS
// runtime allows pkg:sym access to any symbol in a package.
PkgAllSymbols map[string][]ExternalSymbol
// DefForms are DefFormSpecs derived from defmacro definitions whose
// names start with "def". These can be injected into Config.DefForms
// for per-file analysis.
DefForms []DefFormSpec
// PackageImports maps package name to the list of packages imported
// via use-package across all workspace files. This enables per-file
// analysis to resolve symbols from cross-file use-package declarations.
PackageImports map[string][]string
// DefaultPackage is the package declared in main.lisp (if present).
// Used as the default for bare files (no in-package) so that cross-file
// resolution works in projects where files inherit the package from
// load order.
DefaultPackage string
}
WorkspacePrescan holds the results of a workspace prescan: file definitions, package exports, and DefFormSpecs extracted from defmacro definitions.
func PrescanWorkspace ¶ added in v1.40.0
func PrescanWorkspace(root string, scanCfg *ScanConfig) (*WorkspacePrescan, error)
PrescanWorkspace performs a single-pass workspace scan that collects file definitions and extracts DefFormSpecs from def*-prefixed macros. The result provides everything needed to build a Config for per-file analysis, including macro-derived definition forms.