use crate::{
compile::names::{self, Name},
node,
};
use std::ops::Range;
#[derive(Clone, Debug, Default)]
pub struct SourceMap {
defs: Vec<Def>,
occs: Vec<Occ>,
}
#[derive(Clone, Debug)]
pub struct Def {
pub range: Range<usize>,
pub name: Option<Name>,
pub name_range: Range<usize>,
}
#[derive(Clone, Debug)]
pub struct Occ {
pub range: Range<usize>,
pub name: Name,
pub def_ix: usize,
}
#[derive(Clone, Debug, Default)]
pub struct NodeSpans {
pub defs: Vec<Range<usize>>,
pub refs: Vec<Range<usize>>,
}
impl SourceMap {
pub fn parse(src: &str) -> Self {
scan(src)
}
pub fn defs(&self) -> &[Def] {
&self.defs
}
pub fn occs(&self) -> &[Occ] {
&self.occs
}
pub fn node_spans(&self, path: &[node::Id]) -> NodeSpans {
let mut spans = NodeSpans::default();
for def in &self.defs {
if def_path(def) == Some(path) {
spans.defs.push(def.range.clone());
}
}
for occ in &self.occs {
let def = &self.defs[occ.def_ix];
if occ_path(occ, def).as_deref() == Some(path) {
spans.refs.push(occ.range.clone());
}
}
spans
}
pub fn node_at(&self, range: Range<usize>) -> Option<Vec<node::Id>> {
let range = range.start..range.end.max(range.start + 1);
let ix = self
.defs
.partition_point(|d| d.range.start <= range.start)
.checked_sub(1)?;
let def = &self.defs[ix];
if range.start >= def.range.end {
return None;
}
let intersects = |r: &Range<usize>| r.start < range.end && range.start < r.end;
if intersects(&def.name_range) {
return Some(def_path(def).map(<[_]>::to_vec).unwrap_or_default());
}
let mut first_var: Option<&Occ> = None;
for occ in self.occs.iter().filter(|o| o.def_ix == ix) {
if !intersects(&occ.range) {
continue;
}
match occ.name {
Name::NodeFn { .. } | Name::GraphFn { .. } | Name::LvlFn { .. } => {
return occ_path(occ, def);
}
_ => first_var = first_var.or(Some(occ)),
}
}
if let Some(path) = first_var.and_then(|occ| occ_path(occ, def)) {
return Some(path);
}
Some(def_path(def).map(<[_]>::to_vec).unwrap_or_default())
}
}
fn def_path(def: &Def) -> Option<&[node::Id]> {
match def.name.as_ref()? {
Name::NodeFn { path, .. } | Name::GraphFn { path, .. } | Name::LvlFn { path, .. } => {
Some(path)
}
_ => None,
}
}
fn level_path(def: &Def) -> Option<&[node::Id]> {
match def.name.as_ref()? {
Name::NodeFn { path, .. } => path.split_last().map(|(_, parent)| parent),
Name::GraphFn { path, .. } | Name::LvlFn { path, .. } => Some(path),
Name::EntryFn { .. } => Some(&[]),
_ => None,
}
}
fn occ_path(occ: &Occ, def: &Def) -> Option<Vec<node::Id>> {
match &occ.name {
Name::NodeFn { path, .. } | Name::GraphFn { path, .. } | Name::LvlFn { path, .. } => {
Some(path.clone())
}
Name::EntryFn { .. } => Some(vec![]),
Name::Output { node, .. }
| Name::Input { node, .. }
| Name::Result { node }
| Name::Join { id: node } => {
let mut path = level_path(def)?.to_vec();
path.push(*node);
Some(path)
}
}
}
fn scan(src: &str) -> SourceMap {
struct Form {
start: usize,
head_seen: bool,
is_define: bool,
name: Option<(Range<usize>, Option<Name>)>,
}
fn close(form: Form, end: usize) -> Def {
let (name_range, name) = match form.name {
Some((range, name)) => (range, name),
None => (form.start..form.start, None),
};
Def {
range: form.start..end,
name,
name_range,
}
}
let bytes = src.as_bytes();
let mut map = SourceMap::default();
let mut form: Option<Form> = None;
let mut depth = 0usize;
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
b'(' | b'[' => {
if depth == 0 {
form = Some(Form {
start: i,
head_seen: false,
is_define: false,
name: None,
});
}
depth += 1;
i += 1;
}
b')' | b']' => {
depth = depth.saturating_sub(1);
if depth == 0 {
if let Some(form) = form.take() {
map.defs.push(close(form, i + 1));
}
}
i += 1;
}
b'"' => i = skip_string(bytes, i),
b';' => i = skip_comment(bytes, i),
b'\'' | b'`' | b',' => i += 1,
b if b.is_ascii_whitespace() => i += 1,
_ => {
let start = i;
i = scan_atom(bytes, i);
let Some(form) = form.as_mut() else {
continue;
};
let token = &src[start..i];
if !form.head_seen {
form.head_seen = true;
form.is_define = token == "define";
} else if form.is_define && form.name.is_none() {
form.name = Some((start..i, names::parse(token)));
} else if let Some(name) = names::parse(token) {
map.occs.push(Occ {
range: start..i,
name,
def_ix: map.defs.len(),
});
}
}
}
}
if let Some(form) = form.take() {
map.defs.push(close(form, bytes.len()));
}
map
}
fn skip_string(bytes: &[u8], start: usize) -> usize {
let mut i = start + 1;
while i < bytes.len() {
match bytes[i] {
b'\\' => i += 2,
b'"' => return i + 1,
_ => i += 1,
}
}
bytes.len()
}
fn skip_comment(bytes: &[u8], start: usize) -> usize {
let mut i = start;
while i < bytes.len() && bytes[i] != b'\n' {
i += 1;
}
i
}
fn scan_atom(bytes: &[u8], start: usize) -> usize {
let mut i = start;
while i < bytes.len() {
if i == start + 2 && &bytes[start..i] == br"#\" {
i += 1;
continue;
}
match bytes[i] {
b'(' | b')' | b'[' | b']' | b'"' | b';' | b'\'' | b'`' | b',' => break,
b if b.is_ascii_whitespace() => break,
_ => i += 1,
}
}
i
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scan_defs_and_occs() {
let src = "(define (node-fn-0:1-i1-o1 input0) (+ input0 1))\n\n\
(define (graph-fn-0) (node-fn-0:1-i1-o1 node-1-o0))";
let map = SourceMap::parse(src);
assert_eq!(map.defs().len(), 2);
assert_eq!(
map.defs()[0].name,
Some(Name::NodeFn {
path: vec![0, 1],
inputs: "1".parse().unwrap(),
outputs: "1".parse().unwrap(),
})
);
assert_eq!(&src[map.defs()[0].name_range.clone()], "node-fn-0:1-i1-o1");
assert_eq!(
map.defs()[1].name,
Some(Name::GraphFn {
path: vec![0],
inputs: node::Conns::empty(),
})
);
let occs: Vec<_> = map
.occs()
.iter()
.map(|o| (&src[o.range.clone()], o.def_ix))
.collect();
assert_eq!(occs, vec![("node-fn-0:1-i1-o1", 1), ("node-1-o0", 1)]);
}
#[test]
fn scanner_skips_strings_comments_and_char_literals() {
let src = "(define (node-fn-0-o1) (foo \"a ) b \\\" (\" #\\( #\\) ; node-1-o0 )\n 'node-2-o0 sym))";
let map = SourceMap::parse(src);
assert_eq!(map.defs().len(), 1);
assert_eq!(&src[map.defs()[0].range.clone()], src);
let occs: Vec<_> = map.occs().iter().map(|o| &src[o.range.clone()]).collect();
assert_eq!(occs, vec!["node-2-o0"]);
}
#[test]
fn node_at_resolution() {
let src = "(define (lvl-fn-abababab-3) (define node-0-o0 (node-fn-3:0-o1)) node-0-o0)";
let map = SourceMap::parse(src);
let call = src.find("(node-fn-3:0-o1)").unwrap();
assert_eq!(map.node_at(call..call + 16), Some(vec![3, 0]));
let var = src.rfind("node-0-o0").unwrap();
assert_eq!(map.node_at(var..var + 9), Some(vec![3, 0]));
let kw = src.find("define").unwrap();
assert_eq!(map.node_at(kw..kw + 6), Some(vec![3]));
assert_eq!(map.node_at(src.len()..src.len() + 1), None);
}
}