use crate::{
compile::{
EntrypointId,
ir::{JoinId, Var},
},
node,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Name {
NodeFn {
path: Vec<node::Id>,
inputs: node::Conns,
outputs: node::Conns,
},
GraphFn {
path: Vec<node::Id>,
inputs: node::Conns,
},
LvlFn { ep: String, path: Vec<node::Id> },
EntryFn { ep: String },
Output { node: node::Id, output: usize },
Input { node: node::Id, input: usize },
Result { node: node::Id },
Join { id: node::Id },
}
pub(crate) fn path_string(path: &[node::Id]) -> String {
path.iter()
.map(|id| id.to_string())
.collect::<Vec<_>>()
.join(":")
}
pub(crate) fn node_fn_name(
node_path: &[node::Id],
inputs: &node::Conns,
outputs: &node::Conns,
) -> String {
let path_string = path_string(node_path);
let inputs_prefix = if inputs.is_empty() { "" } else { "-i" };
let outputs_prefix = if outputs.is_empty() { "" } else { "-o" };
format!("node-fn-{path_string}{inputs_prefix}{inputs}{outputs_prefix}{outputs}")
}
pub(crate) fn graph_fn_name(path: &[node::Id], inputs: &node::Conns) -> String {
let path_string = path_string(path);
let inputs_prefix = if inputs.is_empty() { "" } else { "-i" };
format!("graph-fn-{path_string}{inputs_prefix}{inputs}")
}
pub(crate) fn lvl_fn_name(ep: &EntrypointId, path: &[node::Id]) -> String {
format!("lvl-fn-{}-{}", ep.0.display_short(), path_string(path))
}
pub fn entry_fn_name(id: &EntrypointId) -> String {
format!("entry-fn-{}", id.0.display_short())
}
pub(crate) fn var_name(var: &Var) -> String {
match var {
Var::Output { node, output } => format!("node-{node}-o{output}"),
Var::Input { node, input } => format!("node-{node}-i{input}"),
Var::Result { node } => pair_name(*node),
}
}
pub(crate) fn pair_name(node: node::Id) -> String {
format!("node-{node}")
}
pub(crate) fn join_name(join: JoinId) -> String {
format!("join-{join}")
}
pub fn parse(name: &str) -> Option<Name> {
if let Some(rest) = name.strip_prefix("node-fn-") {
let mut segs = rest.split('-');
let path = parse_path(segs.next()?)?;
let (inputs, outputs) = parse_io_conns(segs)?;
Some(Name::NodeFn {
path,
inputs,
outputs,
})
} else if let Some(rest) = name.strip_prefix("graph-fn-") {
let mut segs = rest.split('-');
let path = parse_path(segs.next()?)?;
let inputs = match (segs.next(), segs.next()) {
(None, _) => node::Conns::empty(),
(Some(i), None) => parse_conns(i, 'i')?,
_ => return None,
};
Some(Name::GraphFn { path, inputs })
} else if let Some(rest) = name.strip_prefix("lvl-fn-") {
let (ep, path) = rest.split_once('-')?;
is_short_hash(ep).then_some(())?;
let path = parse_path(path)?;
Some(Name::LvlFn {
ep: ep.to_string(),
path,
})
} else if let Some(rest) = name.strip_prefix("entry-fn-") {
is_short_hash(rest).then(|| Name::EntryFn {
ep: rest.to_string(),
})
} else if let Some(rest) = name.strip_prefix("join-") {
Some(Name::Join {
id: parse_id(rest)?,
})
} else if let Some(rest) = name.strip_prefix("node-") {
let Some((id, suffix)) = rest.split_once('-') else {
return Some(Name::Result {
node: parse_id(rest)?,
});
};
let node = parse_id(id)?;
if let Some(output) = suffix.strip_prefix('o') {
Some(Name::Output {
node,
output: parse_id(output)?,
})
} else if let Some(input) = suffix.strip_prefix('i') {
Some(Name::Input {
node,
input: parse_id(input)?,
})
} else {
None
}
} else {
None
}
}
fn parse_path(s: &str) -> Option<Vec<node::Id>> {
s.split(':').map(parse_id).collect()
}
fn parse_id(s: &str) -> Option<node::Id> {
if s.is_empty() || !s.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
s.parse().ok()
}
fn parse_conns(s: &str, prefix: char) -> Option<node::Conns> {
let bits = s.strip_prefix(prefix)?;
if bits.is_empty() {
return None;
}
bits.parse().ok()
}
fn parse_io_conns(mut segs: std::str::Split<'_, char>) -> Option<(node::Conns, node::Conns)> {
let empty = node::Conns::empty;
match (segs.next(), segs.next(), segs.next()) {
(None, _, _) => Some((empty(), empty())),
(Some(i), None, _) if i.starts_with('i') => Some((parse_conns(i, 'i')?, empty())),
(Some(o), None, _) if o.starts_with('o') => Some((empty(), parse_conns(o, 'o')?)),
(Some(i), Some(o), None) => Some((parse_conns(i, 'i')?, parse_conns(o, 'o')?)),
_ => None,
}
}
fn is_short_hash(s: &str) -> bool {
s.len() == 8
&& s.bytes()
.all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
}
#[cfg(test)]
mod tests {
use super::*;
fn ep(byte: u8) -> EntrypointId {
EntrypointId(gantz_ca::ContentAddr::from([byte; 32]))
}
fn conns(s: &str) -> node::Conns {
s.parse().unwrap()
}
#[test]
fn node_fn_roundtrip() {
let cases = [
(vec![0], conns(""), conns("")),
(vec![0], conns("1"), conns("")),
(vec![3], conns(""), conns("11")),
(vec![0, 1, 2], conns("0101"), conns("1100")),
(vec![10, 22, 333], conns("1"), conns("1")),
];
for (path, inputs, outputs) in cases {
let name = node_fn_name(&path, &inputs, &outputs);
assert_eq!(
parse(&name),
Some(Name::NodeFn {
path,
inputs,
outputs
}),
"{name}"
);
}
}
#[test]
fn graph_fn_roundtrip() {
let cases = [
(vec![0], conns("")),
(vec![1, 4], conns("101")),
(vec![12, 0], conns("1")),
];
for (path, inputs) in cases {
let name = graph_fn_name(&path, &inputs);
assert_eq!(parse(&name), Some(Name::GraphFn { path, inputs }), "{name}");
}
}
#[test]
fn lvl_fn_roundtrip() {
let id = ep(0xab);
let path = vec![0, 7];
let name = lvl_fn_name(&id, &path);
assert_eq!(
parse(&name),
Some(Name::LvlFn {
ep: "abababab".to_string(),
path
}),
"{name}"
);
}
#[test]
fn entry_fn_roundtrip() {
let id = ep(0x1f);
let name = entry_fn_name(&id);
assert_eq!(
parse(&name),
Some(Name::EntryFn {
ep: "1f1f1f1f".to_string()
}),
"{name}"
);
}
#[test]
fn var_roundtrip() {
let cases = [
(
Var::Output { node: 0, output: 0 },
Name::Output { node: 0, output: 0 },
),
(
Var::Output {
node: 12,
output: 3,
},
Name::Output {
node: 12,
output: 3,
},
),
(
Var::Input { node: 4, input: 1 },
Name::Input { node: 4, input: 1 },
),
(Var::Result { node: 9 }, Name::Result { node: 9 }),
];
for (var, expected) in cases {
let name = var_name(&var);
assert_eq!(parse(&name), Some(expected), "{name}");
}
}
#[test]
fn join_roundtrip() {
let name = join_name(42);
assert_eq!(parse(&name), Some(Name::Join { id: 42 }), "{name}");
}
#[test]
fn non_names_rejected() {
let cases = [
"",
"state",
"input0",
"output",
"results",
"graph-state",
"%root-state",
"%lvl-state",
"%lvl-r-3",
"%gantz-sig",
"%vals-node-0-o0",
"'%gantz-unfired",
"node-",
"node-fn-",
"node-fn-x",
"node-fn-0:",
"node-fn-0-z1",
"node-fn-0-i",
"node-0-",
"node-0-x1",
"node-1:2",
"graph-fn-",
"graph-fn-0-o1",
"lvl-fn-0",
"lvl-fn-zzzzzzzz-0",
"lvl-fn-abcd-0",
"entry-fn-xyz",
"entry-fn-ABABABAB",
"join-",
"join-x",
];
for name in cases {
assert_eq!(parse(name), None, "{name}");
}
}
}