mod common;
pub use common::{
ElectricalFilter, ElectricalType, FilterOp, FilterValue as CommonFilterValue, VisibilityFilter,
compare_filter,
};
mod engine;
mod parser;
mod pattern;
mod selector;
pub use engine::{
QueryMatch as RecordQueryMatch, SelectorEngine, query_records, query_records_with_doc_name,
};
pub use parser::{SelectorParser, parse as parse_selector};
pub use pattern::Pattern;
pub use selector::{
Combinator, FilterOperator, FilterValue, NetConnectedTarget, PropertyFilter,
PseudoSelector as RecordPseudoSelector, RecordMatcher, RecordType, Selector as RecordSelector,
SelectorChain, SelectorSegment,
};
mod ast;
mod executor;
mod schql_parser;
mod view;
pub use ast::*;
pub use executor::QueryExecutor;
pub use schql_parser::{QueryError, QueryParser};
pub use view::{
ComponentView, ConnectionPoint, NetView, PinView, PortView, PowerView, SchematicView,
};
use crate::io::schdoc::SchDoc;
#[derive(Debug, Clone)]
pub struct QueryResult {
pub matches: Vec<QueryMatch>,
pub query: String,
pub execution_time_us: u64,
}
impl QueryResult {
pub fn is_empty(&self) -> bool {
self.matches.is_empty()
}
pub fn len(&self) -> usize {
self.matches.len()
}
pub fn to_text(&self) -> String {
if self.matches.is_empty() {
return format!("Query `{}`: No matches\n", self.query);
}
if let Some(QueryMatch::Count(n)) = self.matches.first() {
return format!("{}\n", n);
}
let mut output = format!(
"Query `{}`: {} match{}\n",
self.query,
self.matches.len(),
if self.matches.len() == 1 { "" } else { "es" }
);
for m in &self.matches {
output.push_str(&format!(" {}\n", m.to_short_text()));
}
output
}
pub fn to_detail_text(&self) -> String {
if self.matches.is_empty() {
return format!("Query `{}`: No matches\n", self.query);
}
let mut output = format!(
"Query `{}`: {} match{}\n\n",
self.query,
self.matches.len(),
if self.matches.len() == 1 { "" } else { "es" }
);
for m in &self.matches {
output.push_str(&m.to_detail_text());
output.push('\n');
}
output
}
}
#[derive(Debug, Clone)]
pub enum QueryMatch {
Component {
designator: String,
part: String,
description: String,
value: Option<String>,
footprint: Option<String>,
pin_count: usize,
},
Pin {
component_designator: String,
designator: String,
name: String,
electrical_type: String,
connected_net: Option<String>,
is_hidden: bool,
},
Net {
name: String,
is_power: bool,
is_ground: bool,
connection_count: usize,
connections: Vec<String>,
},
Port {
name: String,
io_type: String,
connected_net: Option<String>,
},
Wire {
index: usize,
vertex_count: usize,
start: (i32, i32),
end: (i32, i32),
},
Power {
net_name: String,
style: String,
is_ground: bool,
},
Label { text: String, location: (i32, i32) },
Junction { location: (i32, i32) },
Parameter {
component_designator: String,
name: String,
value: String,
},
Count(usize),
}
impl QueryMatch {
pub fn to_short_text(&self) -> String {
match self {
QueryMatch::Component {
designator,
part,
value,
..
} => {
if let Some(v) = value {
format!("{} ({}, {})", designator, part, v)
} else {
format!("{} ({})", designator, part)
}
}
QueryMatch::Pin {
component_designator,
designator,
name,
electrical_type,
connected_net,
..
} => {
let net_str = connected_net.as_deref().unwrap_or("NC");
format!(
"{}.{} \"{}\" [{}] -> {}",
component_designator, designator, name, electrical_type, net_str
)
}
QueryMatch::Net {
name,
connection_count,
is_power,
is_ground,
..
} => {
let suffix = if *is_power {
" [PWR]"
} else if *is_ground {
" [GND]"
} else {
""
};
format!("{}{} ({} connections)", name, suffix, connection_count)
}
QueryMatch::Port {
name,
io_type,
connected_net,
} => {
let net_str = connected_net.as_deref().unwrap_or("?");
format!("PORT {} [{}] -> {}", name, io_type, net_str)
}
QueryMatch::Wire {
index,
vertex_count,
..
} => {
format!("Wire #{} ({} vertices)", index, vertex_count)
}
QueryMatch::Power {
net_name,
style,
is_ground,
} => {
let kind = if *is_ground { "GND" } else { "PWR" };
format!("{} [{}] ({})", net_name, kind, style)
}
QueryMatch::Label { text, .. } => {
format!("Label \"{}\"", text)
}
QueryMatch::Junction { location } => {
format!(
"Junction @ ({}, {})",
location.0 / 10000,
location.1 / 10000
)
}
QueryMatch::Parameter {
component_designator,
name,
value,
} => {
format!("{}.{} = \"{}\"", component_designator, name, value)
}
QueryMatch::Count(n) => {
format!("{}", n)
}
}
}
pub fn to_detail_text(&self) -> String {
match self {
QueryMatch::Component {
designator,
part,
description,
value,
footprint,
pin_count,
} => {
let mut s = format!("Component {}\n", designator);
s.push_str(&format!(" Part: {}\n", part));
if !description.is_empty() {
s.push_str(&format!(" Description: {}\n", description));
}
if let Some(v) = value {
s.push_str(&format!(" Value: {}\n", v));
}
if let Some(fp) = footprint {
s.push_str(&format!(" Footprint: {}\n", fp));
}
s.push_str(&format!(" Pins: {}\n", pin_count));
s
}
QueryMatch::Net {
name,
connections,
is_power,
is_ground,
..
} => {
let mut s = format!("Net: {}", name);
if *is_power {
s.push_str(" [POWER]");
}
if *is_ground {
s.push_str(" [GROUND]");
}
s.push('\n');
for conn in connections {
s.push_str(&format!(" - {}\n", conn));
}
s
}
QueryMatch::Pin {
component_designator,
designator,
name,
electrical_type,
connected_net,
is_hidden,
} => {
let mut s = format!("Pin {}.{}\n", component_designator, designator);
s.push_str(&format!(" Name: {}\n", name));
s.push_str(&format!(" Type: {}\n", electrical_type));
if let Some(net) = connected_net {
s.push_str(&format!(" Net: {}\n", net));
} else {
s.push_str(" Net: (unconnected)\n");
}
if *is_hidden {
s.push_str(" Hidden: yes\n");
}
s
}
_ => self.to_short_text(),
}
}
}
pub struct SchematicQuery<'a> {
view: &'a SchematicView,
}
impl<'a> SchematicQuery<'a> {
pub fn new(view: &'a SchematicView) -> Self {
Self { view }
}
pub fn query(&self, query_str: &str) -> Result<QueryResult, QueryError> {
let start = std::time::Instant::now();
let parser = QueryParser::new();
let selector = parser.parse(query_str)?;
let executor = QueryExecutor::new(self.view);
let matches = executor.execute(&selector)?;
Ok(QueryResult {
matches,
query: query_str.to_string(),
execution_time_us: start.elapsed().as_micros() as u64,
})
}
pub fn query_batch(&self, queries: &[&str]) -> Vec<Result<QueryResult, QueryError>> {
queries.iter().map(|q| self.query(q)).collect()
}
}
pub fn query_schdoc(doc: &SchDoc, query_str: &str) -> Result<QueryResult, QueryError> {
let view = SchematicView::from_schdoc(doc);
let engine = SchematicQuery::new(&view);
engine.query(query_str)
}
#[cfg(test)]
mod tests;