Skip to main content

dirtydata_core/
dsl.rs

1//! Surface DSL — Layer 2: Human Review Language.
2//!
3//! Authoring Language ではなく Review Language。
4//! 吸うな。その薬は強い。export-only。
5
6use std::fmt::Write;
7
8use crate::actions::node_name;
9use crate::hash;
10use crate::ir::Graph;
11use crate::types::*;
12
13/// Render the graph as Surface DSL text.
14pub fn render_dsl(graph: &Graph) -> String {
15    let mut out = String::new();
16
17    // Header
18    writeln!(out, "# DirtyData Surface DSL — revision {}", graph.revision.0).unwrap();
19    writeln!(out, "# Hash: blake3:{}", hex_short(&hash::hash_graph(graph))).unwrap();
20    writeln!(out, "# Patches: {}", graph.lineage.applied_patches.len()).unwrap();
21    writeln!(out).unwrap();
22
23    // Build name lookup for connections
24    let name_of = |id: &StableId| -> String {
25        graph.topology.nodes.get(id).map(node_name).unwrap_or_else(|| id.to_string())
26    };
27
28    // Nodes
29    for node in graph.topology.nodes.values() {
30        let kind_str = match &node.kind {
31            NodeKind::Source => "source",
32            NodeKind::Processor => "processor",
33            NodeKind::Analyzer => "analyzer",
34            NodeKind::Sink => "sink",
35            NodeKind::Junction => "junction",
36            NodeKind::Foreign(name) => {
37                writeln!(out, "foreign \"{}\" \"{}\" {{", node_name(node), name).unwrap();
38                render_node_body(&mut out, node);
39                writeln!(out, "}}").unwrap();
40                writeln!(out).unwrap();
41                continue;
42            }
43            NodeKind::Intent => "intent",
44            NodeKind::Metadata => "metadata",
45            NodeKind::Boundary => "boundary",
46            NodeKind::SubGraph => "subgraph",
47            NodeKind::InputProxy => "input_proxy",
48            NodeKind::OutputProxy => "output_proxy",
49            NodeKind::CircuitModule { .. } => "circuit_module",
50        };
51
52        writeln!(out, "{} \"{}\" {{", kind_str, node_name(node)).unwrap();
53        render_node_body(&mut out, node);
54        writeln!(out, "}}").unwrap();
55        writeln!(out).unwrap();
56    }
57
58    // Edges
59    if !graph.topology.edges.is_empty() {
60        writeln!(out, "# Connections").unwrap();
61        for edge in graph.topology.edges.values() {
62            let src_name = name_of(&edge.source.node_id);
63            let tgt_name = name_of(&edge.target.node_id);
64            
65            let kind_tag = "# normal"; 
66            
67            writeln!(
68                out,
69                "{}.{} -> {}.{}  {}",
70                src_name, edge.source.port_name,
71                tgt_name, edge.target.port_name,
72                kind_tag
73            )
74            .unwrap();
75        }
76    }
77
78    out
79}
80
81fn render_node_body(out: &mut String, node: &crate::ir::Node) {
82    // Ports
83    for port in &node.ports {
84        let dir = match port.direction {
85            PortDirection::Input => "in",
86            PortDirection::Output => "out",
87        };
88        let domain = match port.domain {
89            ExecutionDomain::Sample => "@sample",
90            ExecutionDomain::Block => "@block",
91            ExecutionDomain::Timeline => "@timeline",
92            ExecutionDomain::Background => "@background",
93        };
94        let dtype = format_data_type(&port.data_type);
95        if port.name == dir {
96            writeln!(out, "  {}: {} {}", dir, dtype, domain).unwrap();
97        } else {
98            writeln!(out, "  {} \"{}\": {} {}", dir, port.name, dtype, domain).unwrap();
99        }
100    }
101
102    // Config
103    let config_entries: Vec<_> = node
104        .config
105        .iter()
106        .filter(|(k, _)| k.as_str() != "name")
107        .collect();
108
109    if !config_entries.is_empty() {
110        writeln!(out, "  config {{").unwrap();
111        for (key, value) in config_entries {
112            writeln!(out, "    {}: {}", key, format_config_value(value)).unwrap();
113        }
114        writeln!(out, "  }}").unwrap();
115    }
116}
117
118fn format_data_type(dt: &DataType) -> String {
119    match dt {
120        DataType::Audio { channels } => format!("audio({}ch)", channels),
121        DataType::Control => "control".into(),
122        DataType::Midi => "midi".into(),
123        DataType::Spectral { bins } => format!("spectral({})", bins),
124        DataType::Blob => "blob".into(),
125        DataType::Meta => "meta".into(),
126    }
127}
128
129fn format_config_value(v: &ConfigValue) -> String {
130    match v {
131        ConfigValue::Float(f) => format!("{}", f),
132        ConfigValue::Int(i) => format!("{}", i),
133        ConfigValue::Bool(b) => format!("{}", b),
134        ConfigValue::String(s) => format!("\"{}\"", s),
135        ConfigValue::List(items) => {
136            let inner: Vec<_> = items.iter().map(format_config_value).collect();
137            format!("[{}]", inner.join(", "))
138        }
139        ConfigValue::Map(map) => {
140            let inner: Vec<_> = map
141                .iter()
142                .map(|(k, v)| format!("{}: {}", k, format_config_value(v)))
143                .collect();
144            format!("{{{}}}", inner.join(", "))
145        }
146    }
147}
148
149fn hex_short(bytes: &[u8]) -> String {
150    bytes[..8].iter().map(|b| format!("{:02x}", b)).collect()
151}