use controlled_option::ControlledOption;
use lsp_positions::SpanCalculator;
use once_cell::sync::Lazy;
use stack_graphs::arena::Handle;
use stack_graphs::graph::File;
use stack_graphs::graph::Node;
use stack_graphs::graph::NodeID;
use stack_graphs::graph::StackGraph;
use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::HashSet;
use std::mem::transmute;
use std::ops::BitOr;
use std::path::Path;
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
use std::time::Instant;
use thiserror::Error;
use tree_sitter::Parser;
use tree_sitter_graph::functions::Functions;
use tree_sitter_graph::graph::Edge;
use tree_sitter_graph::graph::Graph;
use tree_sitter_graph::graph::GraphNode;
use tree_sitter_graph::graph::GraphNodeRef;
use tree_sitter_graph::graph::Value;
use tree_sitter_graph::parse_error::ParseError;
use tree_sitter_graph::parse_error::TreeWithParseErrorVec;
use tree_sitter_graph::ExecutionConfig;
use util::DisplayParseErrorsPretty;
use util::TreeSitterCancellationFlag;
#[cfg(feature = "cli")]
pub mod ci;
#[cfg(feature = "cli")]
pub mod cli;
pub mod functions;
pub mod loader;
pub mod test;
mod util;
pub use tree_sitter_graph::VariableError;
pub use tree_sitter_graph::Variables;
pub(self) const MAX_PARSE_ERRORS: usize = 5;
static DROP_SCOPES_TYPE: &'static str = "drop_scopes";
static POP_SCOPED_SYMBOL_TYPE: &'static str = "pop_scoped_symbol";
static POP_SYMBOL_TYPE: &'static str = "pop_symbol";
static PUSH_SCOPED_SYMBOL_TYPE: &'static str = "push_scoped_symbol";
static PUSH_SYMBOL_TYPE: &'static str = "push_symbol";
static SCOPE_TYPE: &'static str = "scope";
static DEBUG_ATTR_PREFIX: &'static str = "debug_";
static DEFINIENS_NODE_ATTR: &'static str = "definiens_node";
static EMPTY_SOURCE_SPAN_ATTR: &'static str = "empty_source_span";
static IS_DEFINITION_ATTR: &'static str = "is_definition";
static IS_ENDPOINT_ATTR: &'static str = "is_endpoint";
static IS_EXPORTED_ATTR: &'static str = "is_exported";
static IS_REFERENCE_ATTR: &'static str = "is_reference";
static SCOPE_ATTR: &'static str = "scope";
static SOURCE_NODE_ATTR: &'static str = "source_node";
static SYMBOL_ATTR: &'static str = "symbol";
static SYNTAX_TYPE_ATTR: &'static str = "syntax_type";
static TYPE_ATTR: &'static str = "type";
static POP_SCOPED_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
HashSet::from([
TYPE_ATTR,
SYMBOL_ATTR,
IS_DEFINITION_ATTR,
DEFINIENS_NODE_ATTR,
SYNTAX_TYPE_ATTR,
])
});
static POP_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
HashSet::from([
TYPE_ATTR,
SYMBOL_ATTR,
IS_DEFINITION_ATTR,
DEFINIENS_NODE_ATTR,
SYNTAX_TYPE_ATTR,
])
});
static PUSH_SCOPED_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> =
Lazy::new(|| HashSet::from([TYPE_ATTR, SYMBOL_ATTR, SCOPE_ATTR, IS_REFERENCE_ATTR]));
static PUSH_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> =
Lazy::new(|| HashSet::from([TYPE_ATTR, SYMBOL_ATTR, IS_REFERENCE_ATTR]));
static SCOPE_ATTRS: Lazy<HashSet<&'static str>> =
Lazy::new(|| HashSet::from([TYPE_ATTR, IS_EXPORTED_ATTR, IS_ENDPOINT_ATTR]));
static PRECEDENCE_ATTR: &'static str = "precedence";
pub const ROOT_NODE_VAR: &'static str = "ROOT_NODE";
pub const JUMP_TO_SCOPE_NODE_VAR: &'static str = "JUMP_TO_SCOPE_NODE";
pub const FILE_PATH_VAR: &'static str = "FILE_PATH";
pub const ROOT_PATH_VAR: &'static str = "ROOT_PATH";
pub struct StackGraphLanguage {
language: tree_sitter::Language,
tsg: tree_sitter_graph::ast::File,
tsg_path: PathBuf,
tsg_source: std::borrow::Cow<'static, str>,
functions: Functions,
}
impl StackGraphLanguage {
pub fn new(
language: tree_sitter::Language,
tsg: tree_sitter_graph::ast::File,
) -> StackGraphLanguage {
debug_assert_eq!(language, tsg.language);
StackGraphLanguage {
language,
tsg,
tsg_path: PathBuf::from("<tsg>"),
tsg_source: Cow::from(String::new()),
functions: Self::default_functions(),
}
}
pub fn from_str(
language: tree_sitter::Language,
tsg_source: &str,
) -> Result<StackGraphLanguage, LanguageError> {
let tsg = tree_sitter_graph::ast::File::from_str(language.clone(), tsg_source)?;
Ok(StackGraphLanguage {
language,
tsg,
tsg_path: PathBuf::from("<missing tsg path>"),
tsg_source: Cow::from(tsg_source.to_string()),
functions: Self::default_functions(),
})
}
pub fn from_source(
language: tree_sitter::Language,
tsg_path: PathBuf,
tsg_source: &str,
) -> Result<StackGraphLanguage, LanguageError> {
let mut sgl = Self::from_str(language, tsg_source)?;
sgl.tsg_path = tsg_path;
Ok(sgl)
}
pub fn set_tsg_info(&mut self, path: PathBuf, source: Cow<'static, str>) {
self.tsg_path = path;
self.tsg_source = source;
}
fn default_functions() -> tree_sitter_graph::functions::Functions {
let mut functions = tree_sitter_graph::functions::Functions::stdlib();
crate::functions::add_path_functions(&mut functions);
functions
}
pub fn functions_mut(&mut self) -> &mut tree_sitter_graph::functions::Functions {
&mut self.functions
}
pub fn language(&self) -> &tree_sitter::Language {
&self.language
}
pub fn tsg_path(&self) -> &Path {
&self.tsg_path
}
pub fn tsg_source(&self) -> &Cow<'static, str> {
&self.tsg_source
}
}
#[derive(Debug, Error)]
pub enum LanguageError {
#[error(transparent)]
ParseError(#[from] tree_sitter_graph::ParseError),
}
impl LanguageError {
pub fn display_pretty<'a>(
&'a self,
path: &'a Path,
source: &'a str,
) -> impl std::fmt::Display + 'a {
match self {
Self::ParseError(err) => err.display_pretty(path, source),
}
}
}
impl StackGraphLanguage {
pub fn build_stack_graph_into<'a>(
&'a self,
stack_graph: &'a mut StackGraph,
file: Handle<File>,
source: &'a str,
globals: &'a Variables<'a>,
cancellation_flag: &'a dyn CancellationFlag,
) -> Result<(), BuildError> {
self.builder_into_stack_graph(stack_graph, file, source)
.build(globals, cancellation_flag)
}
pub fn builder_into_stack_graph<'a>(
&'a self,
stack_graph: &'a mut StackGraph,
file: Handle<File>,
source: &'a str,
) -> Builder<'a> {
Builder::new(self, stack_graph, file, source)
}
}
pub struct Builder<'a> {
sgl: &'a StackGraphLanguage,
stack_graph: &'a mut StackGraph,
file: Handle<File>,
source: &'a str,
graph: Graph<'a>,
remapped_nodes: HashMap<usize, NodeID>,
injected_node_count: usize,
span_calculator: SpanCalculator<'a>,
}
impl<'a> Builder<'a> {
fn new(
sgl: &'a StackGraphLanguage,
stack_graph: &'a mut StackGraph,
file: Handle<File>,
source: &'a str,
) -> Self {
let span_calculator = SpanCalculator::new(source);
Builder {
sgl,
stack_graph,
file,
source,
graph: Graph::new(),
remapped_nodes: HashMap::new(),
injected_node_count: 0,
span_calculator,
}
}
pub fn build(
mut self,
globals: &'a Variables<'a>,
cancellation_flag: &dyn CancellationFlag,
) -> Result<(), BuildError> {
let tree = {
let mut parser = Parser::new();
parser.set_language(&self.sgl.language)?;
let ts_cancellation_flag = TreeSitterCancellationFlag::from(cancellation_flag);
unsafe { parser.set_cancellation_flag(Some(ts_cancellation_flag.as_ref())) };
parser
.parse(self.source, None)
.ok_or(BuildError::ParseError)?
};
let parse_errors = ParseError::into_all(tree);
if parse_errors.errors().len() > 0 {
return Err(BuildError::ParseErrors(parse_errors));
}
let tree = parse_errors.into_tree();
let mut globals = Variables::nested(globals);
let root_node = self.inject_node(NodeID::root());
globals
.add(ROOT_NODE_VAR.into(), root_node.into())
.unwrap_or_default();
let jump_to_scope_node = self.inject_node(NodeID::jump_to());
globals
.add(JUMP_TO_SCOPE_NODE_VAR.into(), jump_to_scope_node.into())
.expect("Failed to set JUMP_TO_SCOPE_NODE");
if globals.get(&FILE_PATH_VAR.into()).is_none() {
let file_name = self.stack_graph[self.file].to_string();
globals
.add(FILE_PATH_VAR.into(), file_name.into())
.expect("Failed to set FILE_PATH");
}
let mut config = ExecutionConfig::new(&self.sgl.functions, &globals)
.lazy(true)
.debug_attributes(
[DEBUG_ATTR_PREFIX, "tsg_location"].concat().as_str().into(),
[DEBUG_ATTR_PREFIX, "tsg_variable"].concat().as_str().into(),
[DEBUG_ATTR_PREFIX, "tsg_match_node"]
.concat()
.as_str()
.into(),
);
let tree: &'a tree_sitter::Tree = unsafe { transmute(&tree) };
self.sgl.tsg.execute_into(
&mut self.graph,
tree,
self.source,
&mut config,
&(cancellation_flag as &dyn CancellationFlag),
)?;
self.load(cancellation_flag)
}
pub fn inject_node(&mut self, id: NodeID) -> GraphNodeRef {
let node = self.graph.add_graph_node();
self.remapped_nodes.insert(node.index(), id);
self.injected_node_count += 1;
node
}
}
pub trait CancellationFlag: Sync {
fn check(&self, at: &'static str) -> Result<(), CancellationError>;
}
#[derive(Clone, Debug, Error)]
#[error("Cancelled at \"{0}\"")]
pub struct CancellationError(pub &'static str);
impl stack_graphs::CancellationFlag for &dyn CancellationFlag {
fn check(&self, at: &'static str) -> Result<(), stack_graphs::CancellationError> {
CancellationFlag::check(*self, at).map_err(|err| stack_graphs::CancellationError(err.0))
}
}
impl tree_sitter_graph::CancellationFlag for &dyn CancellationFlag {
fn check(&self, at: &'static str) -> Result<(), tree_sitter_graph::CancellationError> {
CancellationFlag::check(*self, at)
.map_err(|err| tree_sitter_graph::CancellationError(err.0))
}
}
impl<'a> BitOr for &'a dyn CancellationFlag {
type Output = OrCancellationFlag<'a>;
fn bitor(self, rhs: Self) -> Self::Output {
OrCancellationFlag(self, rhs)
}
}
pub struct OrCancellationFlag<'a>(&'a dyn CancellationFlag, &'a dyn CancellationFlag);
impl CancellationFlag for OrCancellationFlag<'_> {
fn check(&self, at: &'static str) -> Result<(), CancellationError> {
self.0.check(at)?;
self.1.check(at)?;
Ok(())
}
}
pub struct NoCancellation;
impl CancellationFlag for NoCancellation {
fn check(&self, _at: &'static str) -> Result<(), CancellationError> {
Ok(())
}
}
pub struct CancelAfterDuration {
start: Instant,
limit: Duration,
}
impl CancelAfterDuration {
pub fn new(limit: Duration) -> Self {
Self {
start: Instant::now(),
limit,
}
}
pub fn from_option(limit: Option<Duration>) -> Box<dyn CancellationFlag> {
match limit {
Some(limit) => Box::new(Self::new(limit)),
None => Box::new(NoCancellation),
}
}
}
impl CancellationFlag for CancelAfterDuration {
fn check(&self, at: &'static str) -> Result<(), CancellationError> {
if self.start.elapsed().ge(&self.limit) {
return Err(CancellationError(at));
}
Ok(())
}
}
#[derive(Clone)]
pub struct AtomicCancellationFlag {
flag: Arc<AtomicBool>,
}
impl AtomicCancellationFlag {
pub fn new() -> Self {
Self {
flag: Arc::new(AtomicBool::new(false)),
}
}
pub fn cancel(&self) {
self.flag.store(true, Ordering::Relaxed)
}
}
impl CancellationFlag for AtomicCancellationFlag {
fn check(&self, at: &'static str) -> Result<(), CancellationError> {
if self.flag.load(Ordering::Relaxed) {
return Err(CancellationError(at));
}
Ok(())
}
}
#[derive(Debug, Error)]
pub enum BuildError {
#[error("{0}")]
Cancelled(&'static str),
#[error("Missing ‘type’ attribute on graph node")]
MissingNodeType(GraphNodeRef),
#[error("Missing ‘symbol’ attribute on graph node")]
MissingSymbol(GraphNodeRef),
#[error("Missing ‘scope’ attribute on graph node")]
MissingScope(GraphNodeRef),
#[error("Unknown ‘{0}’ flag type {1}")]
UnknownFlagType(String, String),
#[error("Unknown node type {0}")]
UnknownNodeType(String),
#[error("Unknown symbol type {0}")]
UnknownSymbolType(String),
#[error(transparent)]
ExecutionError(tree_sitter_graph::ExecutionError),
#[error("Error parsing source")]
ParseError,
#[error("Error parsing source")]
ParseErrors(TreeWithParseErrorVec),
#[error("Error converting shorthand ‘{0}’ on {1} with value {2}")]
ConversionError(String, String, String),
#[error(transparent)]
LanguageError(#[from] tree_sitter::LanguageError),
#[error("Expected exported symbol scope in {0}, got {1}")]
SymbolScopeError(String, String),
}
impl From<stack_graphs::CancellationError> for BuildError {
fn from(value: stack_graphs::CancellationError) -> Self {
Self::Cancelled(value.0)
}
}
impl From<tree_sitter_graph::ExecutionError> for BuildError {
fn from(value: tree_sitter_graph::ExecutionError) -> Self {
match value {
tree_sitter_graph::ExecutionError::Cancelled(err) => Self::Cancelled(err.0),
err => Self::ExecutionError(err),
}
}
}
impl BuildError {
pub fn display_pretty<'a>(
&'a self,
source_path: &'a Path,
source: &'a str,
tsg_path: &'a Path,
tsg: &'a str,
) -> impl std::fmt::Display + 'a {
DisplayBuildErrorPretty {
error: self,
source_path,
source,
tsg_path,
tsg,
}
}
}
struct DisplayBuildErrorPretty<'a> {
error: &'a BuildError,
source_path: &'a Path,
source: &'a str,
tsg_path: &'a Path,
tsg: &'a str,
}
impl std::fmt::Display for DisplayBuildErrorPretty<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.error {
BuildError::ExecutionError(err) => write!(
f,
"{}",
err.display_pretty(self.source_path, self.source, self.tsg_path, self.tsg)
),
BuildError::ParseErrors(parse_errors) => write!(
f,
"{}",
DisplayParseErrorsPretty {
parse_errors,
path: self.source_path,
source: self.source,
max_errors: crate::MAX_PARSE_ERRORS,
}
),
err => err.fmt(f),
}
}
}
impl<'a> Builder<'a> {
fn load(mut self, cancellation_flag: &dyn CancellationFlag) -> Result<(), BuildError> {
let cancellation_flag: &dyn stack_graphs::CancellationFlag = &cancellation_flag;
let mut next_local_id = (self.graph.node_count() - self.injected_node_count) as u32;
for node in self.stack_graph.nodes_for_file(self.file) {
let local_id = self.stack_graph[node].id().local_id();
let index = (local_id as usize) + self.injected_node_count;
while self
.stack_graph
.node_for_id(NodeID::new_in_file(self.file, next_local_id))
.is_some()
{
next_local_id += 1;
}
self.remapped_nodes
.insert(index, NodeID::new_in_file(self.file, next_local_id))
.map(|_| panic!("index already remapped"));
}
for node_ref in self.graph.iter_nodes().skip(self.injected_node_count) {
cancellation_flag.check("loading graph nodes")?;
let node_type = self.get_node_type(node_ref)?;
let handle = match node_type {
NodeType::DropScopes => self.load_drop_scopes(node_ref),
NodeType::PopScopedSymbol => self.load_pop_scoped_symbol(node_ref)?,
NodeType::PopSymbol => self.load_pop_symbol(node_ref)?,
NodeType::PushScopedSymbol => self.load_push_scoped_symbol(node_ref)?,
NodeType::PushSymbol => self.load_push_symbol(node_ref)?,
NodeType::Scope => self.load_scope(node_ref)?,
};
self.load_source_info(node_ref, handle)?;
self.load_node_debug_info(node_ref, handle)?;
}
for node in self.stack_graph.nodes_for_file(self.file) {
self.verify_node(node)?;
}
for source_ref in self.graph.iter_nodes() {
let source = &self.graph[source_ref];
let source_node_id = self.node_id_for_graph_node(source_ref);
let source_handle = self.stack_graph.node_for_id(source_node_id).unwrap();
for (sink_ref, edge) in source.iter_edges() {
cancellation_flag.check("loading graph edges")?;
let precedence = match edge.attributes.get(PRECEDENCE_ATTR) {
Some(precedence) => precedence.as_integer()? as i32,
None => 0,
};
let sink_node_id = self.node_id_for_graph_node(sink_ref);
let sink_handle = self.stack_graph.node_for_id(sink_node_id).unwrap();
self.stack_graph
.add_edge(source_handle, sink_handle, precedence);
Self::load_edge_debug_info(
&mut self.stack_graph,
source_handle,
sink_handle,
edge,
)?;
}
}
Ok(())
}
fn get_node_type(&self, node_ref: GraphNodeRef) -> Result<NodeType, BuildError> {
let node = &self.graph[node_ref];
let node_type = match node.attributes.get(TYPE_ATTR) {
Some(node_type) => node_type.as_str()?,
None => return Ok(NodeType::Scope),
};
if node_type == DROP_SCOPES_TYPE {
return Ok(NodeType::DropScopes);
} else if node_type == POP_SCOPED_SYMBOL_TYPE {
return Ok(NodeType::PopScopedSymbol);
} else if node_type == POP_SYMBOL_TYPE {
return Ok(NodeType::PopSymbol);
} else if node_type == PUSH_SCOPED_SYMBOL_TYPE {
return Ok(NodeType::PushScopedSymbol);
} else if node_type == PUSH_SYMBOL_TYPE {
return Ok(NodeType::PushSymbol);
} else if node_type == SCOPE_TYPE {
return Ok(NodeType::Scope);
} else {
return Err(BuildError::UnknownNodeType(format!("{}", node_type)));
}
}
fn verify_node(&self, node: Handle<Node>) -> Result<(), BuildError> {
if let Node::PushScopedSymbol(node) = &self.stack_graph[node] {
let scope = &self.stack_graph[self.stack_graph.node_for_id(node.scope).unwrap()];
if !scope.is_exported_scope() {
return Err(BuildError::SymbolScopeError(
format!("{}", node.display(self.stack_graph)),
format!("{}", scope.display(self.stack_graph)),
));
}
}
Ok(())
}
}
enum NodeType {
DropScopes,
PopSymbol,
PopScopedSymbol,
PushSymbol,
PushScopedSymbol,
Scope,
}
impl<'a> Builder<'a> {
fn node_id_for_graph_node(&self, node_ref: GraphNodeRef) -> NodeID {
let index = node_ref.index();
self.remapped_nodes.get(&index).map_or_else(
|| NodeID::new_in_file(self.file, (index - self.injected_node_count) as u32),
|id| *id,
)
}
fn load_drop_scopes(&mut self, node_ref: GraphNodeRef) -> Handle<Node> {
let id = self.node_id_for_graph_node(node_ref);
self.stack_graph.add_drop_scopes_node(id).unwrap()
}
fn load_pop_scoped_symbol(
&mut self,
node_ref: GraphNodeRef,
) -> Result<Handle<Node>, BuildError> {
let node = &self.graph[node_ref];
let symbol = match node.attributes.get(SYMBOL_ATTR) {
Some(symbol) => self.load_symbol(symbol)?,
None => return Err(BuildError::MissingSymbol(node_ref)),
};
let symbol = self.stack_graph.add_symbol(&symbol);
let id = self.node_id_for_graph_node(node_ref);
let is_definition = self.load_flag(node, IS_DEFINITION_ATTR)?;
self.verify_attributes(node, POP_SCOPED_SYMBOL_TYPE, &POP_SCOPED_SYMBOL_ATTRS);
let node_handle = self
.stack_graph
.add_pop_scoped_symbol_node(id, symbol, is_definition)
.unwrap();
if is_definition {
self.load_definiens_info(node_ref, node_handle)?;
}
Ok(node_handle)
}
fn load_pop_symbol(&mut self, node_ref: GraphNodeRef) -> Result<Handle<Node>, BuildError> {
let node = &self.graph[node_ref];
let symbol = match node.attributes.get(SYMBOL_ATTR) {
Some(symbol) => self.load_symbol(symbol)?,
None => return Err(BuildError::MissingSymbol(node_ref)),
};
let symbol = self.stack_graph.add_symbol(&symbol);
let id = self.node_id_for_graph_node(node_ref);
let is_definition = self.load_flag(node, IS_DEFINITION_ATTR)?;
self.verify_attributes(node, POP_SYMBOL_TYPE, &POP_SYMBOL_ATTRS);
let node_handle = self
.stack_graph
.add_pop_symbol_node(id, symbol, is_definition)
.unwrap();
if is_definition {
self.load_definiens_info(node_ref, node_handle)?;
}
Ok(node_handle)
}
fn load_push_scoped_symbol(
&mut self,
node_ref: GraphNodeRef,
) -> Result<Handle<Node>, BuildError> {
let node = &self.graph[node_ref];
let symbol = match node.attributes.get(SYMBOL_ATTR) {
Some(symbol) => self.load_symbol(symbol)?,
None => return Err(BuildError::MissingSymbol(node_ref)),
};
let symbol = self.stack_graph.add_symbol(&symbol);
let id = self.node_id_for_graph_node(node_ref);
let scope = match node.attributes.get(SCOPE_ATTR) {
Some(scope) => self.node_id_for_graph_node(scope.as_graph_node_ref()?),
None => return Err(BuildError::MissingScope(node_ref)),
};
let is_reference = self.load_flag(node, IS_REFERENCE_ATTR)?;
self.verify_attributes(node, PUSH_SCOPED_SYMBOL_TYPE, &PUSH_SCOPED_SYMBOL_ATTRS);
Ok(self
.stack_graph
.add_push_scoped_symbol_node(id, symbol, scope, is_reference)
.unwrap())
}
fn load_push_symbol(&mut self, node_ref: GraphNodeRef) -> Result<Handle<Node>, BuildError> {
let node = &self.graph[node_ref];
let symbol = match node.attributes.get(SYMBOL_ATTR) {
Some(symbol) => self.load_symbol(symbol)?,
None => return Err(BuildError::MissingSymbol(node_ref)),
};
let symbol = self.stack_graph.add_symbol(&symbol);
let id = self.node_id_for_graph_node(node_ref);
let is_reference = self.load_flag(node, IS_REFERENCE_ATTR)?;
self.verify_attributes(node, PUSH_SYMBOL_TYPE, &PUSH_SYMBOL_ATTRS);
Ok(self
.stack_graph
.add_push_symbol_node(id, symbol, is_reference)
.unwrap())
}
fn load_scope(&mut self, node_ref: GraphNodeRef) -> Result<Handle<Node>, BuildError> {
let node = &self.graph[node_ref];
let id = self.node_id_for_graph_node(node_ref);
let is_exported =
self.load_flag(node, IS_EXPORTED_ATTR)? || self.load_flag(node, IS_ENDPOINT_ATTR)?;
self.verify_attributes(node, SCOPE_TYPE, &SCOPE_ATTRS);
Ok(self.stack_graph.add_scope_node(id, is_exported).unwrap())
}
fn load_symbol(&self, value: &Value) -> Result<String, BuildError> {
match value {
Value::Integer(i) => Ok(i.to_string()),
Value::String(s) => Ok(s.clone()),
_ => Err(BuildError::UnknownSymbolType(format!("{}", value))),
}
}
fn load_flag(&self, node: &GraphNode, attribute: &str) -> Result<bool, BuildError> {
match node.attributes.get(attribute) {
Some(value) => value.as_boolean().map_err(|_| {
BuildError::UnknownFlagType(format!("{}", attribute), format!("{}", value))
}),
None => Ok(false),
}
}
fn load_source_info(
&mut self,
node_ref: GraphNodeRef,
node_handle: Handle<Node>,
) -> Result<(), BuildError> {
let node = &self.graph[node_ref];
if let Some(source_node) = node.attributes.get(SOURCE_NODE_ATTR) {
let source_node = &self.graph[source_node.as_syntax_node_ref()?];
let mut source_span = self.span_calculator.for_node(source_node);
if match node.attributes.get(EMPTY_SOURCE_SPAN_ATTR) {
Some(empty_source_span) => empty_source_span.as_boolean()?,
None => false,
} {
source_span.end = source_span.start.clone();
}
let containing_line = &self.source[source_span.start.containing_line.clone()];
let containing_line = self.stack_graph.add_string(containing_line);
let source_info = self.stack_graph.source_info_mut(node_handle);
source_info.span = source_span;
source_info.containing_line = ControlledOption::some(containing_line);
}
if let Some(syntax_type) = node.attributes.get(SYNTAX_TYPE_ATTR) {
let syntax_type = syntax_type.as_str()?;
let syntax_type = self.stack_graph.add_string(syntax_type);
let source_info = self.stack_graph.source_info_mut(node_handle);
source_info.syntax_type = syntax_type.into();
}
Ok(())
}
fn load_definiens_info(
&mut self,
node_ref: GraphNodeRef,
node_handle: Handle<Node>,
) -> Result<(), BuildError> {
let node = &self.graph[node_ref];
let definiens_node = match node.attributes.get(DEFINIENS_NODE_ATTR) {
Some(Value::Null) => return Ok(()),
Some(definiens_node) => &self.graph[definiens_node.as_syntax_node_ref()?],
None => return Ok(()),
};
let definiens_span = self.span_calculator.for_node(definiens_node);
let source_info = self.stack_graph.source_info_mut(node_handle);
source_info.definiens_span = definiens_span;
Ok(())
}
fn load_node_debug_info(
&mut self,
node_ref: GraphNodeRef,
node_handle: Handle<Node>,
) -> Result<(), BuildError> {
let node = &self.graph[node_ref];
for (name, value) in node.attributes.iter() {
let name = name.to_string();
if name.starts_with(DEBUG_ATTR_PREFIX) {
let value = match value {
Value::String(value) => value.clone(),
value => value.to_string(),
};
let key = self
.stack_graph
.add_string(&name[DEBUG_ATTR_PREFIX.len()..]);
let value = self.stack_graph.add_string(&value);
self.stack_graph
.node_debug_info_mut(node_handle)
.add(key, value);
}
}
Ok(())
}
fn load_edge_debug_info(
stack_graph: &mut StackGraph,
source_handle: Handle<Node>,
sink_handle: Handle<Node>,
edge: &Edge,
) -> Result<(), BuildError> {
for (name, value) in edge.attributes.iter() {
let name = name.to_string();
if name.starts_with(DEBUG_ATTR_PREFIX) {
let value = match value {
Value::String(value) => value.clone(),
value => value.to_string(),
};
let key = stack_graph.add_string(&name[DEBUG_ATTR_PREFIX.len()..]);
let value = stack_graph.add_string(&value);
stack_graph
.edge_debug_info_mut(source_handle, sink_handle)
.add(key, value);
}
}
Ok(())
}
fn verify_attributes(
&self,
node: &GraphNode,
node_type: &str,
allowed_attributes: &HashSet<&'static str>,
) {
for (id, _) in node.attributes.iter() {
let id = id.as_str();
if !allowed_attributes.contains(id)
&& id != SOURCE_NODE_ATTR
&& id != EMPTY_SOURCE_SPAN_ATTR
&& !id.starts_with(DEBUG_ATTR_PREFIX)
{
eprintln!("Unexpected attribute {} on node of type {}", id, node_type);
}
}
}
}
pub trait FileAnalyzer {
fn build_stack_graph_into<'a>(
&self,
stack_graph: &mut StackGraph,
file: Handle<File>,
path: &Path,
source: &str,
all_paths: &mut dyn Iterator<Item = &'a Path>,
globals: &HashMap<String, String>,
cancellation_flag: &dyn CancellationFlag,
) -> Result<(), BuildError>;
}