Skip to main content

LSP.pmod/Intelligence.pmod/ModuleResolution.pike

Overview

ModuleResolution.pike - Import/include/inherit/require directive handling

This file provides handlers for parsing and resolving Pike module imports

and dependencies with waterfall symbol loading and circular dependency detection.

Design pattern:

  • create(object ctx) constructor for context injection

  • Handlers wrap errors in catch blocks with LSPError responses

  • Uses trim_whites() for string operations

  • Uses Parser.Pike.split() for tokenization (NOT regex)

@module ModuleResolution

@summary Module resolution API for Pike LSP analyzer

This module provides functionality for:

  • Parsing import/include/inherit/require directives from Pike code

  • Resolving directive targets to file paths

  • Detecting circular dependencies in import graphs

  • Waterfall symbol loading across dependencies

@const string INCLUDE - Preprocessor include directive type

@const string IMPORT - Import statement directive type

@const string INHERIT - Inheritance statement directive type

@const string REQUIRE - Preprocessor require directive type

Create a new ModuleResolution instance

@param ctx Context object with parser, intelligence, analysis references

Trim leading and trailing whitespace from a string

@param s The string to trim

@returns Trimmed string

Check if a file exists (Pike 8.0 compatible)

@param path The file path to check

@returns 1 if file exists and is a regular file, 0 otherwise

Parse a #require directive using limited subset approach.

Supported patterns:

  • String literals: #require "module.pike";

  • Constant identifiers: #require constant(ModuleName);

  • Complex expressions: Marked as skip

@param line

The line containing the #require directive

@param line_num

Line number for error reporting (unused but reserved for future)

@returns

Mapping with type, path, resolution_type, identifier (for constant), skip (for complex)

@example

@code

mapping result = parse_require_directive("#require "my_module.pike";", 1);

// result: ([

// "type": "require",

// "path": "my_module.pike",

// "resolution_type": "string_literal",

// "skip": 0

// ])

mapping result2 = parse_require_directive("#require constant(MyModule);", 2);

// result2: ([

// "type": "require",

// "path": "MyModule",

// "resolution_type": "constant_identifier",

// "identifier": "MyModule",

// "skip": 0

// ])

mapping result3 = parse_require_directive("#require some_func() + ".pike";", 3);

// result3: ([

// "type": "require",

// "path": "some_func() + ".pike";",

// "resolution_type": "complex_require",

// "skip": 1

// ])

@endcode

Extract import/include/inherit/require directives from Pike code

Parses a Pike source code string and extracts all import-related directives.

Supports both preprocessor directives (#include, #require) and keyword-based

statements (import, inherit).

@param params Mapping with "code" key (Pike source code string)

@returns Mapping with "result" containing array of import_entry mappings

@returns_mapping result

@returns_array result.imports Array of import_entry structures

@example

@code

mapping params = ([

"code": "import Stdio;\n#include <unistd.h>\ninherit Thread.Thread;"

]);

mapping result = handle_extract_imports(params);

// result->result->imports contains:

// ({ (["type": "import", "target": "Stdio", "raw": "import Stdio"]),

// (["type": "include", "target": "unistd.h", "raw": "#include <unistd.h>"]),

// (["type": "inherit", "target": "Thread.Thread", "raw": "inherit Thread.Thread"]) })

@endcode

Resolve an import/include/inherit/require directive to its file path

Given an import directive type and target, attempts to resolve it to a file path.

Resolution logic varies by import type:

  • INCLUDE: Resolves "local.h" relative to current file, <system.h> via include paths

  • IMPORT: Uses master()->resolv() to find the program, then Program.defined()

  • INHERIT: Multi-strategy resolution (introspection, qualified names, workspace search, stdlib)

  • REQUIRE: Tries as module via master()->resolv(), then as file path

@param params Mapping with "import_type" key (type constant), "target" key (module/path name),

and optional "current_file" key (file path for relative resolution)

@returns_mapping result

@returns_string result.path Resolved absolute file path (empty if not found)

@returns_int result.exists 1 if file exists, 0 otherwise

@returns_string result.type Import type (include/import/inherit/require)

@returns_int result.mtime File modification time (0 if not found)

@returns_string result.error Error message if resolution failed (empty string if success)

@example

@code

mapping params = (["import_type": "import", "target": "Stdio"]);

mapping result = handle_resolve_import(params);

// result->result->path: "/usr/local/pike/lib/modules/Stdio.so"

// result->result->exists: 1

// result->result->type: "import"

mapping params2 = ([

"import_type": "inherit",

"target": "Thread.Thread",

"current_file": "/path/to/current.pike"

]);

mapping result2 = handle_resolve_import(params2);

// Multi-strategy resolution: introspection → qualified → workspace → stdlib

@endcode

Resolve #include directive to file path

@param target Include target (with or without quotes/angle brackets)

@param current_file Current file path for relative includes

@returns Resolved file path or 0

Resolve import Module.Name via master()->resolv()

@param target Module name to resolve

@returns Resolved file path or 0

Resolve #require directive to file path

@param target Require target (string literal or identifier)

@param current_file Current file path for relative paths

@returns Resolved file path or 0

Resolve inherit ClassName using multi-strategy approach

@param class_name Name of class to resolve

@param current_file Current file path for workspace search context

@returns Resolved file path or 0

Strategy 1: Resolve inherit via introspection data

@param class_name Name of class to resolve

@returns Resolved file path or 0

Strategy 2: Resolve inherit via qualified module names

@param class_name Name of class to resolve

@returns Resolved file path or 0

Strategy 3: Resolve inherit via direct workspace search

@param class_name Name of class to resolve

@param current_file Current file path for context

@returns Resolved file path or 0

Strategy 4: Resolve inherit via stdlib master()->resolv()

@param class_name Name of class to resolve

@returns Resolved file path or 0

Get directory name from file path

@param path File path

@returns Directory path

Check for circular dependencies in a dependency graph

Performs cycle detection on a dependency graph structure using depth-first search.

Uses three-color DFS (white=unvisited, gray=visiting, black=visited).

@param params Mapping with optional "graph" key (pre-built dependency graph)

or "file" key to build graph from a file

@returns_mapping result

@returns_int result.has_circular 1 if cycles detected, 0 otherwise

@returns_array result.cycle Array of file paths forming a cycle (if found)

@returns_array result.dependencies All dependencies found

@example

@code

// Check imports from code

mapping params = ([

"code": "import A; import B;",

"filename": "test.pike"

]);

mapping result = handle_check_circular(params);

@endcode

Build dependency graph from source code

@param code Pike source code

@param filename Filename for the code

@returns Graph mapping (file -> array of dependencies)

Detect cycles in dependency graph using DFS

@param graph Dependency graph (file -> array of dependencies)

@returns Array of file paths forming a cycle, or empty array if no cycle

DFS helper for cycle detection

@param node Current node being visited

@param graph Dependency graph

@param color Color mapping (0=white, 1=gray, 2=black)

@param path Current DFS path

@returns Cycle path if found, empty array otherwise

Get symbols with waterfall loading (transitive dependency resolution)

Performs transitive symbol loading by recursively resolving all dependencies

of the specified file. Implements waterfall pattern where symbols from

dependencies are loaded with depth tracking for proper prioritization.

@param params Mapping with "code" key (Pike source code) and optional

"filename" key (for resolution context), "max_depth" (default: 5)

@returns_mapping result

@returns_array result.symbols All symbols from file and transitive dependencies

@returns_array result.imports Direct imports from the file

@returns_array result.transitive Transitive imports (waterfall)

@returns_mapping result.provenance Provenance information for each symbol

@example

@code

mapping params = ([

"code": "import Stdio;\n#include "header.h";",

"filename": "test.pike",

"max_depth": 3

]);

mapping result = handle_get_waterfall_symbols(params);

@endcode

Load symbols from a file and its transitive dependencies (waterfall)

@param file File path or identifier

@param depth Current depth (0 for direct imports, incremented for transitive)

@param max_depth Maximum depth to traverse

@param visited Mapping of visited files (to prevent cycles)

@param visit_order Array tracking visit order

@param symbols Array to accumulate symbols

@param transitive Array to accumulate transitive imports

@param provenance Mapping of provenance information

Merge symbols with "most specific wins" precedence

Prioritizes symbols based on their depth:

  • Current file symbols (depth -1) have highest priority

  • Direct imports (depth 0) have medium priority

  • Transitive imports (depth 1+) have lowest priority

@param symbols_by_file Array of [symbols, file, depth] entries

@returns Merged symbol array with precedence applied

@example

@code

array result = merge_symbols_with_precedence(({

({ current_file_syms, "main.pike", -1 }),

({ import_syms, "Stdio", 0 }),

({ transitive_syms, "header.h", 1 })

}));

@endcode

Symbols

SymbolTypeLine
INCLUDEconstant22
IMPORTconstant25
INHERITconstant28
REQUIREconstant31
createfunction36
trim_whitesfunction44
is_filefunction62
parse_require_directivefunction108
handle_extract_importsfunction177
handle_resolve_importfunction376
resolve_includefunction496
resolve_import_modulefunction542
iffunction545
resolve_requirefunction566
resolve_inheritfunction587
resolve_inherit_strategy_introspectionfunction612
resolve_inherit_strategy_qualifiedfunction640
resolve_inherit_strategy_workspacefunction675
resolve_inherit_strategy_stdlibfunction716
get_dirnamefunction732
handle_check_circularfunction760
build_dependency_graph_from_codefunction818
handle_get_waterfall_symbolsfunction915
load_waterfall_symbolsfunction982
merge_symbols_with_precedencefunction1081