///|
priv struct TraceNode {
offset : Int
line : Int
column : Int
children : Array[TraceNode]
}
///|
pub(all) struct SourceLocation {
source : String
offset : Int
line : Int
column : Int
} derive(Eq, Debug)
///|
pub(all) struct ResolvedOrigin {
path : Array[Selector]
location : SourceLocation
} derive(Eq, Debug)
///|
pub(all) struct ReferenceEdge {
source : String
target : String
kind : String
location : SourceLocation
} derive(Eq, Debug)
///|
pub(all) struct ResolvedProject {
document : Document
origins : Array[ResolvedOrigin]
references : Array[ReferenceEdge]
} derive(Eq, Debug)
///|
pub extend SourceLocation with Eq::{equal, not_equal}
///|
pub extend SourceLocation with @debug.Debug::{to_repr}
///|
pub extend ResolvedOrigin with Eq::{equal, not_equal}
///|
pub extend ResolvedOrigin with @debug.Debug::{to_repr}
///|
pub extend ReferenceEdge with Eq::{equal, not_equal}
///|
pub extend ReferenceEdge with @debug.Debug::{to_repr}
///|
pub extend ResolvedProject with Eq::{equal, not_equal}
///|
pub extend ResolvedProject with @debug.Debug::{to_repr}
///|
priv struct ResolvedNode {
entry : Entry
location : SourceLocation
children : Array[ResolvedNode]
}
///|
fn traced_nodes(
entries : Array[Entry],
traces : Array[TraceNode],
source : String,
) -> Array[ResolvedNode] {
entries.mapi((i, entry) => {
let trace = traces[i]
let children = match entry.value {
Object(entries) => traced_nodes(entries, trace.children, source)
_ => []
}
{
entry,
location: {
source,
offset: trace.offset,
line: trace.line,
column: trace.column,
},
children,
}
})
}
///|
fn collect_origins(
nodes : Array[ResolvedNode],
path : Array[Selector],
output : Array[ResolvedOrigin],
) -> Unit {
let occurrences : Map[String, Int] = Map([])
for node in nodes {
let occurrence = occurrences.get(node.entry.key).unwrap_or(0)
occurrences[node.entry.key] = occurrence + 1
let next = path.map(p => p)
next.push({ key: node.entry.key, occurrence, })
output.push({ path: next, location: node.location, })
collect_origins(node.children, next, output)
}
}
///|
/// The resolver and provenance use one merge operation. Locations identify key
/// starts in the original files, before filtering or formatting, in scalar units.
pub fn load_project(
source : String,
loader : (String) -> String raise,
options? : ParseOptions = ParseOptions::default(),
max_files? : Int = 64,
) -> ResolvedProject raise {
if max_files < 1 || max_files > 256 {
raise InvalidReference("max_files must be between 1 and 256")
}
let state = ResolveState::{
options,
max_files,
files: 0,
chars: 0,
entries: 0,
references: [],
}
let nodes = resolve_file(normalize_reference(source), loader, [], state)
let origins : Array[ResolvedOrigin] = []
collect_origins(nodes, [], origins)
{
document: { entries: nodes.map(n => n.entry), },
origins,
references: state.references,
}
}
///|
fn location_json(location : SourceLocation) -> Json {
Json::object({
"source": Json::string(location.source),
"offset": Json::number(location.offset.to_double()),
"line": Json::number(location.line.to_double()),
"column": Json::number(location.column.to_double()),
})
}
///|
pub fn ResolvedProject::to_json(self : ResolvedProject) -> Json {
Json::object({
"tree": self.document.to_json(),
"origins": Json::array(
self.origins.map(origin => {
Json::object({
"path": Json::array(
origin.path.map(p => {
Json::object({
"key": Json::string(p.key),
"occurrence": Json::number(p.occurrence.to_double()),
})
}),
),
"location": location_json(origin.location),
})
}),
),
"references": Json::array(
self.references.map(edge => {
Json::object({
"source": Json::string(edge.source),
"target": Json::string(edge.target),
"kind": Json::string(edge.kind),
"location": location_json(edge.location),
})
}),
),
})
}
///|
/// Raw source locations include branches and directives without evaluation.
pub fn SourceDocument::locations(
self : SourceDocument,
source : String,
) -> Array[ResolvedOrigin] {
let spans : Map[String, SourceSpan] = Map([])
for span in self.spans {
spans[span.path.map(i => i.to_string()).join("/")] = span
}
let starts : Map[Int, Array[Selector]] = Map([])
source_paths(self.doc.entries, "", [], spans, starts)
let result : Array[ResolvedOrigin] = []
let mut line = 1
let mut column = 1
let mut cr = false
for i in 0.. Unit {
let occurrences : Map[String, Int] = Map([])
for i in 0.. p)
next.push({ key: entry.key, occurrence, })
let key = prefix + i.to_string()
if spans.get(key) is Some(span) {
starts[span.key_start] = next
}
if entry.value is Object(children) {
source_paths(children, key + "/", next, spans, starts)
}
}
}