Skip to content

Commit 372fe59

Browse files
authored
Store module tree in eval context instead of module starlark value (#251)
* Store module tree outside modules * Raise diagnostics on Module() * Fix lsp, wasm to use new API * Fix formatting, simplify * process_pending_child doesn't return frozen heap
1 parent 671d9e4 commit 372fe59

File tree

79 files changed

+2018
-1998
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+2018
-1998
lines changed

crates/pcb-zen-core/src/convert.rs

Lines changed: 50 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use crate::lang::interface::FrozenInterfaceValue;
2-
use crate::lang::module::find_moved_span;
2+
use crate::lang::module::{find_moved_span, ModulePath};
33
use crate::lang::physical::PhysicalValue;
44
use crate::lang::symbol::SymbolValue;
5-
use crate::lang::test_bench::FrozenTestBenchValue;
65
use crate::lang::type_info::TypeInfo;
76
use crate::moved::{collect_existing_paths, scoped_path, Remapper};
87
use crate::{Diagnostic, Diagnostics, WithDiagnostics};
@@ -16,10 +15,9 @@ use serde::{Deserialize, Serialize};
1615
use serde_json::{Map as JsonMap, Number as JsonNumber, Value as JsonValue};
1716
use starlark::errors::EvalSeverity;
1817
use starlark::values::list::ListRef;
19-
use starlark::values::FrozenValue;
20-
use starlark::values::{dict::DictRef, Value, ValueLike};
21-
use std::collections::HashMap;
18+
use starlark::values::{dict::DictRef, FrozenValue, Value, ValueLike};
2219
use std::collections::HashSet;
20+
use std::collections::{BTreeMap, HashMap};
2321
use std::path::{Path, PathBuf};
2422

2523
/// Convert a [`FrozenModuleValue`] to a [`Schematic`].
@@ -32,6 +30,8 @@ pub(crate) struct ModuleConverter {
3230
comp_models: Vec<(InstanceRef, FrozenSpiceModelValue)>,
3331
// Mapping <module instance ref> -> <module value> for position processing
3432
module_instances: Vec<(InstanceRef, FrozenModuleValue)>,
33+
// Module tree for looking up children
34+
module_tree: BTreeMap<ModulePath, FrozenModuleValue>,
3535
}
3636

3737
/// Module signature information to be serialized as JSON
@@ -147,27 +147,49 @@ impl ModuleConverter {
147147
net_to_properties: HashMap::new(),
148148
comp_models: Vec::new(),
149149
module_instances: Vec::new(),
150+
module_tree: BTreeMap::new(),
150151
}
151152
}
152153

153-
pub(crate) fn build(mut self, module: &FrozenModuleValue) -> crate::WithDiagnostics<Schematic> {
154+
pub(crate) fn build(
155+
mut self,
156+
module_tree: BTreeMap<ModulePath, FrozenModuleValue>,
157+
) -> crate::WithDiagnostics<Schematic> {
158+
let root_module = module_tree.get(&ModulePath::root()).unwrap();
154159
let root_instance_ref = InstanceRef::new(
155-
ModuleRef::new(module.source_path(), module.name()),
160+
ModuleRef::new(root_module.source_path(), "<root>"),
156161
Vec::new(),
157162
);
163+
self.schematic.set_root_ref(root_instance_ref);
158164

159-
if let Err(err) = self.add_module_at(module, &root_instance_ref) {
160-
let mut diagnostics = Diagnostics::default();
161-
diagnostics.push(err.into());
162-
return WithDiagnostics {
163-
output: None,
164-
diagnostics,
165-
};
165+
for (path, module) in module_tree.iter() {
166+
let instance_ref = InstanceRef::new(
167+
ModuleRef::new(root_module.source_path(), root_module.path().name()),
168+
path.segments.clone(),
169+
);
170+
if let Err(err) = self.add_module_at(module, &instance_ref) {
171+
let mut diagnostics = Diagnostics::default();
172+
diagnostics.push(err.into());
173+
return WithDiagnostics {
174+
output: None,
175+
diagnostics,
176+
};
177+
}
178+
179+
// Link child to parent module
180+
if let Some(parent_path) = path.parent() {
181+
let parent_ref = InstanceRef::new(
182+
ModuleRef::new(root_module.source_path(), root_module.path().name()),
183+
parent_path.segments.clone(),
184+
);
185+
if let Some(parent_inst) = self.schematic.instances.get_mut(&parent_ref) {
186+
parent_inst.add_child(module.path().name(), instance_ref.clone());
187+
}
188+
}
166189
}
167-
self.schematic.set_root_ref(root_instance_ref);
168190

169191
// Propagate impedance from DiffPair interfaces to P/N nets (before creating Net objects)
170-
propagate_diffpair_impedance(module, &mut self.net_to_properties);
192+
propagate_diffpair_impedance(&mut self.net_to_properties, &self.module_tree);
171193

172194
// Create Net objects directly using the names recorded per-module.
173195
// Ensure global uniqueness and stable creation order by sorting names.
@@ -193,7 +215,7 @@ impl ModuleConverter {
193215
diagnostics.push(Diagnostic::new(
194216
format!("Duplicate net name: {name}"),
195217
EvalSeverity::Error,
196-
Path::new(module.source_path()),
218+
Path::new(root_module.source_path()),
197219
));
198220
return WithDiagnostics {
199221
output: None,
@@ -273,45 +295,6 @@ impl ModuleConverter {
273295
}
274296
}
275297

276-
fn add_instance_at(
277-
&mut self,
278-
instance_ref: &InstanceRef,
279-
value: FrozenValue,
280-
) -> anyhow::Result<()> {
281-
if let Some(module_value) = value.downcast_ref::<FrozenModuleValue>() {
282-
self.add_module_at(module_value, instance_ref)
283-
} else if let Some(component_value) = value.downcast_ref::<FrozenComponentValue>() {
284-
self.add_component_at(component_value, instance_ref)
285-
} else if value.downcast_ref::<FrozenTestBenchValue>().is_some() {
286-
// Skip TestBench values - they're not part of the schematic
287-
Ok(())
288-
} else if value
289-
.downcast_ref::<crate::lang::electrical_check::FrozenElectricalCheck>()
290-
.is_some()
291-
{
292-
// Skip ElectricalCheck values - they're not part of the schematic
293-
Ok(())
294-
} else {
295-
Err(anyhow::anyhow!("Unexpected value in module: {}", value))
296-
}
297-
}
298-
299-
fn value_name(&self, value: &FrozenValue) -> anyhow::Result<String> {
300-
if let Some(module_value) = value.downcast_ref::<FrozenModuleValue>() {
301-
Ok(module_value.name().to_string())
302-
} else if let Some(component_value) = value.downcast_ref::<FrozenComponentValue>() {
303-
Ok(component_value.name().to_string())
304-
} else if let Some(testbench) = value.downcast_ref::<FrozenTestBenchValue>() {
305-
Ok(testbench.name().to_string())
306-
} else if let Some(check) =
307-
value.downcast_ref::<crate::lang::electrical_check::FrozenElectricalCheck>()
308-
{
309-
Ok(check.name.clone())
310-
} else {
311-
Err(anyhow::anyhow!("Unexpected value in module: {}", value))
312-
}
313-
}
314-
315298
fn add_module_at(
316299
&mut self,
317300
module: &FrozenModuleValue,
@@ -417,13 +400,11 @@ impl ModuleConverter {
417400
self.net_to_name.insert(*net_id, final_name);
418401
}
419402

420-
// Recurse into children, but don't pass any properties down.
421-
// Each module/component should only have its own properties.
422-
for child in module.children().iter() {
423-
let child_name = self.value_name(child)?;
424-
let child_inst_ref = instance_ref.append(child_name.clone());
425-
self.add_instance_at(&child_inst_ref, *child)?;
426-
inst.add_child(child_name.clone(), child_inst_ref.clone());
403+
// Add direct child components
404+
for component in module.components() {
405+
let child_ref = instance_ref.append(component.name().to_string());
406+
self.add_component_at(component, &child_ref)?;
407+
inst.add_child(component.name().to_string(), child_ref.clone());
427408
}
428409

429410
// Add instance to schematic.
@@ -824,20 +805,14 @@ impl ModuleConverter {
824805

825806
/// Propagate impedance from DiffPair interfaces to P/N nets
826807
fn propagate_diffpair_impedance(
827-
module: &FrozenModuleValue,
828808
net_props: &mut HashMap<NetId, HashMap<String, AttributeValue>>,
809+
tree: &BTreeMap<ModulePath, FrozenModuleValue>,
829810
) {
830-
// Check signature for interfaces
831-
for param in module.signature().iter().filter(|p| !p.is_config) {
832-
if let Some(val) = param.actual_value {
833-
propagate_from_value(val.to_value(), net_props);
834-
}
835-
}
836-
837-
// Recurse into children
838-
for child in module.children().iter() {
839-
if let Some(m) = child.downcast_ref::<FrozenModuleValue>() {
840-
propagate_diffpair_impedance(m, net_props);
811+
for module in tree.values() {
812+
for param in module.signature().iter().filter(|p| !p.is_config) {
813+
if let Some(val) = param.actual_value {
814+
propagate_from_value(val.to_value(), net_props);
815+
}
841816
}
842817
}
843818
}
@@ -884,11 +859,6 @@ fn propagate_from_value(
884859
}
885860
}
886861

887-
pub trait ToSchematic {
888-
fn to_schematic(&self) -> anyhow::Result<Schematic>;
889-
fn to_schematic_with_diagnostics(&self) -> crate::WithDiagnostics<Schematic>;
890-
}
891-
892862
fn to_attribute_value(v: starlark::values::FrozenValue) -> anyhow::Result<AttributeValue> {
893863
// Handle scalars first
894864
if let Some(s) = v.downcast_frozen_str() {
@@ -923,19 +893,3 @@ fn to_attribute_value(v: starlark::values::FrozenValue) -> anyhow::Result<Attrib
923893
// Any other type – fall back to string representation
924894
Ok(AttributeValue::String(v.to_string()))
925895
}
926-
927-
impl ToSchematic for FrozenModuleValue {
928-
fn to_schematic(&self) -> anyhow::Result<Schematic> {
929-
let result = self.to_schematic_with_diagnostics();
930-
match result.output {
931-
Some(schematic) if !result.diagnostics.has_errors() => Ok(schematic),
932-
Some(_) => Err(anyhow::anyhow!("Schematic conversion had errors")),
933-
None => Err(anyhow::anyhow!("Schematic conversion failed")),
934-
}
935-
}
936-
937-
fn to_schematic_with_diagnostics(&self) -> crate::WithDiagnostics<Schematic> {
938-
let converter = ModuleConverter::new();
939-
converter.build(self)
940-
}
941-
}

crates/pcb-zen-core/src/graph/mod.rs

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,47 +8,17 @@ use smallvec::SmallVec;
88
use std::collections::{HashMap, HashSet};
99
use thiserror::Error;
1010

11-
/// Component path wrapper type
12-
#[derive(Debug, Clone, PartialEq, Eq, Hash, allocative::Allocative)]
13-
pub struct ComponentPath(pub String);
14-
15-
impl ComponentPath {
16-
pub fn new(path: impl Into<String>) -> Self {
17-
Self(path.into())
18-
}
19-
20-
pub fn as_str(&self) -> &str {
21-
&self.0
22-
}
23-
}
24-
25-
impl From<String> for ComponentPath {
26-
fn from(s: String) -> Self {
27-
Self::new(s)
28-
}
29-
}
30-
31-
impl From<&str> for ComponentPath {
32-
fn from(s: &str) -> Self {
33-
Self::new(s)
34-
}
35-
}
36-
37-
impl std::fmt::Display for ComponentPath {
38-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39-
write!(f, "{}", self.0)
40-
}
41-
}
11+
use crate::lang::module::ModulePath;
4212

4313
/// Port path wrapper type (component path + pin name)
4414
#[derive(Debug, Clone, PartialEq, Eq, Hash, allocative::Allocative)]
4515
pub struct PortPath {
46-
pub component: ComponentPath,
16+
pub component: ModulePath,
4717
pub pin: String,
4818
}
4919

5020
impl PortPath {
51-
pub fn new(component: impl Into<ComponentPath>, pin: impl Into<String>) -> Self {
21+
pub fn new(component: impl Into<ModulePath>, pin: impl Into<String>) -> Self {
5222
Self {
5323
component: component.into(),
5424
pin: pin.into(),
@@ -334,7 +304,7 @@ impl CircuitGraph {
334304
/// Create a CircuitGraph from components data using new wrapper types
335305
pub fn new(
336306
net_to_ports: HashMap<String, Vec<PortPath>>,
337-
component_pins: HashMap<ComponentPath, Vec<String>>,
307+
component_pins: HashMap<ModulePath, Vec<String>>,
338308
public_nets: HashSet<String>,
339309
) -> Result<Self, GraphError> {
340310
let mut port_by_path = HashMap::new();
@@ -438,7 +408,7 @@ impl CircuitGraph {
438408
(Some(f0), Some(f1)) => {
439409
port_factors[port_id.0 as usize] = [f0, f1];
440410
}
441-
(Some(f0), None) if port_path.component.as_str() == "<external>" => {
411+
(Some(f0), None) if port_path.component.to_string() == "<external>" => {
442412
// External ports only have 1 factor, duplicate it for consistency
443413
port_factors[port_id.0 as usize] = [f0, f0];
444414
}

crates/pcb-zen-core/src/graph/path.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use crate::graph::{CircuitGraph, FactorId, PortId, PortPath};
2-
use starlark::collections::SmallMap;
2+
use crate::lang::module::ModulePath;
3+
use crate::FrozenComponentValue;
4+
35
use starlark::values::{tuple::TupleRef, Heap, Value};
4-
use std::collections::HashSet;
6+
use std::collections::{HashMap, HashSet};
57

68
impl CircuitGraph {
79
/// Resolve a label (net name or port tuple) to a PortId
@@ -57,7 +59,7 @@ impl CircuitGraph {
5759
&self,
5860
port_path: &[PortId],
5961
factors: &[FactorId],
60-
components: &SmallMap<String, Value<'v>>,
62+
components: &HashMap<ModulePath, FrozenComponentValue>,
6163
heap: &'v Heap,
6264
) -> starlark::Result<Value<'v>> {
6365
use crate::graph::starlark::PathValueGen;
@@ -87,13 +89,14 @@ impl CircuitGraph {
8789
for &factor_id in factors {
8890
if let crate::graph::FactorType::Component(comp_path) = self.factor_type(factor_id) {
8991
if !seen_components.contains(comp_path) {
90-
let component_value = components.get(comp_path).ok_or_else(|| {
92+
let comp_module_path = ModulePath::from(comp_path.as_str());
93+
let component_value = components.get(&comp_module_path).ok_or_else(|| {
9194
starlark::Error::new_other(anyhow::anyhow!(
9295
"Component '{}' not found",
9396
comp_path
9497
))
9598
})?;
96-
path_components.push(*component_value);
99+
path_components.push(heap.alloc_complex(component_value.clone()));
97100
seen_components.insert(comp_path.clone());
98101
}
99102
}

crates/pcb-zen-core/src/graph/starlark.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::graph::CircuitGraph;
2+
use crate::lang::evaluator_ext::EvaluatorExt;
23
use crate::{downcast_frozen_module, lang::module::FrozenModuleValue};
34
use allocative::Allocative;
45
use starlark::{
@@ -198,8 +199,7 @@ where
198199

199200
// Convert paths to PathValue objects
200201
let module_ref = downcast_frozen_module!(self.module);
201-
202-
let components = module_ref.collect_components("");
202+
let components = eval.collect_components(module_ref.path());
203203
let path_objects: Vec<Value> = paths_with_factors
204204
.into_iter()
205205
.map(|(port_path, factors)| {

crates/pcb-zen-core/src/lang/builtin.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ fn builtin_methods(methods: &mut MethodsBuilder) {
234234
let call_span = call_site.map(|cs| cs.resolve_span());
235235

236236
let check = ElectricalCheckGen::<Value> {
237-
name,
237+
name: name.clone(),
238238
inputs,
239239
check_func: check_fn,
240240
severity,
@@ -243,7 +243,8 @@ fn builtin_methods(methods: &mut MethodsBuilder) {
243243
};
244244

245245
if let Some(ctx) = eval.context_value() {
246-
ctx.add_child(eval.heap().alloc(check));
246+
let check_value = eval.heap().alloc_complex(check);
247+
ctx.module_mut().add_child(check_value);
247248
}
248249

249250
Ok(NoneType)

0 commit comments

Comments
 (0)