;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Modules and imports ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Modules, or packages, organize code in a static, usually hierarchic, structure.
;; These modules can then be imported, or refered to via qualified names.
;;
;; These rules implement the following name binding behavior:
;; - Hierarchical modules, with module names derived from the file name.
;; - Imports for a single definition or all definitions from a module.
;;
;; The supported Python syntax is:
;; - Top-level function definitions without parameters.
;; - Function calls without arguments.
;; - Import statements.
;; - Comments.
;;
;; The following nodes are used:
;; - @node.lexical_scope nodes form a tree connecting "upwards" to the lexical
;; scope, and are used to resolve references in.
;; - @node.lexical_defs nodes are connected "downwards" to the definitions
;; introduced by statements. Lexical scopes are connected to them to make the
;; definitions available when resolving references.
;; - @node.module_defs nodes are used to collect the exported definitions for the
;; definitions of a module, or to expose the imported definitions for a reference
;; to a module.
;;;;;;;;;;;;;;;;;;;
;; Global Variables
global FILE_PATH ; provided by tree-sitter-stack-graphs
global ROOT_NODE ; provided by tree-sitter-stack-graphs
;;;;;;;;;;;;;;;;;;;;;;;
;; Attribute Shorthands
attribute node_definition = node => type = "pop_symbol", node_symbol = node, is_definition
attribute node_reference = node => type = "push_symbol", node_symbol = node, is_reference
attribute node_symbol = node => symbol = (source-text node), source_node = node
attribute pop_symbol = symbol => type = "pop_symbol", symbol = symbol
attribute push_symbol = symbol => type = "push_symbol", symbol = symbol
attribute symbol_definition = symbol => type = "pop_symbol", symbol = symbol, is_definition
attribute symbol_reference = symbol => type = "push_symbol", symbol = symbol, is_reference
;;;;;;;;;;
;; Modules
(module)@mod {
node @mod.lexical_scope
node @mod.module_defs
}
(module)@mod {
;; decompose the path, and create a chain of definitions
var root = ROOT_NODE
scan FILE_PATH {
"([^/]+)/" {
node def
attr (def) symbol_definition = $1
edge root -> def
node module_defs
attr (module_defs) pop_symbol = "."
edge def -> module_defs
set root = module_defs
}
"([^/]+)\.py" {
node def
attr (def) symbol_definition = $1
edge root -> def
attr (@mod.module_defs) pop_symbol = "."
edge def -> @mod.module_defs
}
}
}
(module (_)@stmt)@mod {
;; Every statements in the module can reach all definitions visible in the module
edge @stmt.lexical_scope -> @mod.lexical_scope
;; All statement definitions are visible in the module
edge @mod.lexical_scope -> @stmt.lexical_defs
;; Module definitions are exported by the module
edge @mod.module_defs -> @stmt.module_defs
}
;;;;;;;;;;;;;
;; Statements
[
(expression_statement)
(function_definition)
(import_from_statement)
]@stmt {
node @stmt.lexical_scope
node @stmt.lexical_defs
node @stmt.module_defs
}
(expression_statement (_)@expr)@expr_stmt {
;; The expression can reach all definitions visible in the enclosing scope
edge @expr.lexical_scope -> @expr_stmt.lexical_scope
}
(function_definition name:(identifier)@name)@fun_def {
;; A definition with the name @name is introduced
node def
attr (def) node_definition = @name
;; The definition is exposed to the surrounding block or module
edge @fun_def.lexical_defs -> def
;; The definition is exported from the module
edge @fun_def.module_defs -> def
}
;; the behavior of import statements is specified by several rules
;; this is done to prevent repetition but still handle different variants
(import_from_statement module_name:(dotted_name (_)@name))@import {
;; all components of the module name are references
;; where they are resolved is determined by the rules below
node @name.ref
attr (@name.ref) node_reference = @name
;; definiitons of a module are access via a guard node marked with "."
node @name.module_defs
attr (@name.module_defs) push_symbol = "."
edge @name.module_defs -> @name.ref
}
(import_from_statement module_name:(dotted_name . (_)@first_name))@import {
;; the first component of the module name is resolved in the root scope
edge @first_name.ref -> ROOT_NODE
}
(import_from_statement module_name:(dotted_name (_)@left_name . (_)@right_name))@import {
;; every following component is resolved in the previous module's members
edge @right_name.ref -> @left_name.module_defs
}
(import_from_statement module_name:(dotted_name (_)@last_name .) (wildcard_import))@import {
;; the members from the last component of a wildcard import are exposed as a local definitions
edge @import.lexical_defs -> @last_name.module_defs
}
(import_from_statement module_name:(dotted_name (_)@last_name .) name:(_)@name)@import {
;; a pop node is introduced for the imported name which is exposed in the local scope
;; because this is a pop node, references will not resolve to this node, but only to the imported definition
;; this means that if the imported name does not exist in the module, references to this name will not resolve at all
;; an alternative design choice is to make `def` a proper definition, in which case references will resolve to the import
;; statement, and possible also to the imported definition
node def
attr (def) pop_symbol = (source-text @name)
edge @import.lexical_defs -> def
;; a reference for the imported name is introduced and resolved in the module definitions of the last component
node ref
attr (ref) node_reference = @name
edge ref -> @last_name.module_defs
;; the definition is an alias for the reference
edge def -> ref
}
;;;;;;;;;;;;;;
;; Expressions
[
(call)
]@expr {
node @expr.lexical_scope
}
(call function:(identifier)@name)@call_expr {
;; A reference for the name @name is introduced
node ref
attr (ref) node_reference = @name
;; The reference is resolved in the the enclosing scope
edge ref -> @call_expr.lexical_scope
}
;;;;;;;;;;;
;; Comments
(comment)@comment {
;; Because comments can appear everywhere, we define all possible nodes on
;; them to prevent undefined errors
node @comment.lexical_defs
node @comment.lexical_scope
node @comment.module_defs
}