KiCad PCB EDA Suite
Loading...
Searching...
No Matches
conn_bus.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "conn_bus.h"
21
23#include <string_utils.h>
24
25#include <algorithm>
26#include <functional>
27
28namespace
29{
30using namespace SCH_CONNECTIVITY;
31
32class SCHEMA_PARSER
33{
34public:
35 explicit SCHEMA_PARSER( const BUS_ALIASES& aAliases )
36 {
37 for( const auto& [name, members] : aAliases )
38 m_aliases.insert_or_assign( name.Strip( wxString::both ), std::cref( members ) );
39 }
40
41 std::optional<BUS_SCHEMA> Parse( const wxString& aText, std::set<wxString>* aAliasesUsed = nullptr )
42 {
43 const bool parsed = expand( aText, {}, {}, m_schema.root, true );
44
45 if( aAliasesUsed )
46 *aAliasesUsed = m_schema.aliasesUsed;
47
48 if( !parsed )
49 return std::nullopt;
50
51 return std::move( m_schema );
52 }
53
54private:
55 bool expandMember( const wxString& aText, const wxString& aPrefix, const std::vector<wxString>& aPath,
56 std::vector<BUS_SCHEMA::NODE>& aMembers )
57 {
58 wxString key = UnescapeString( aText );
59 key.Replace( "\\ ", " " );
60 key = key.Strip( wxString::both );
61 m_schema.aliasesUsed.insert( key );
62 const auto alias = m_aliases.find( key );
63
64 if( alias == m_aliases.end() )
65 {
66 aMembers.emplace_back();
67 return expand( aText, aPrefix, aPath, aMembers.back(), false );
68 }
69
70 if( !m_activeAliases.insert( key ).second )
71 return false;
72
73 for( const wxString& text : alias->second.get() )
74 {
75 const wxString member = text.Strip( wxString::both );
76
77 if( !member.IsEmpty()
78 && !expandMember( EscapeString( member, CTX_NETNAME ), aPrefix, aPath, aMembers ) )
79 return false;
80 }
81
82 m_activeAliases.erase( key );
83 return true;
84 }
85
86 bool expand( const wxString& aText, wxString aPrefix, std::vector<wxString> aPath,
87 BUS_SCHEMA::NODE& aNode, bool aRoot )
88 {
89 const wxString text = UnescapeString( aText );
90 wxString prefix;
91 std::vector<wxString> members;
92
93 if( NET_SETTINGS::ParseBusVector( text, &prefix, &members ) )
94 {
95 aNode.kind = BUS_SCHEMA::NODE::KIND::VECTOR;
96 aNode.text = aText;
97 aNode.prefix = EscapeString( prefix, CTX_NETNAME );
98
99 if( aRoot )
100 {
101 m_schema.shape = BUS_SCHEMA::SHAPE::VECTOR;
102 m_schema.prefix = EscapeString( prefix, CTX_NETNAME );
103 }
104
105 for( const wxString& member : members )
106 {
107 aNode.members.emplace_back();
108 addLeaf( member, aPrefix, aPath, aNode.members.back() );
109 }
110
111 return true;
112 }
113
114 size_t prefixEnd = 0;
115
116 if( NET_SETTINGS::ParseBusGroup( text, &prefix, &members, &prefixEnd ) )
117 {
118 aNode.kind = BUS_SCHEMA::NODE::KIND::GROUP;
119 aNode.text = aText;
120 aNode.prefix = EscapeString( prefix, CTX_NETNAME );
121
122 if( aRoot )
123 {
124 m_schema.shape = BUS_SCHEMA::SHAPE::GROUP;
125 m_schema.prefix = EscapeString( prefix, CTX_NETNAME );
126 const wxString sourcePrefix = text.Left( prefixEnd );
127 const auto matches = [&]( size_t offset )
128 {
129 return offset < aText.length() && aText[offset] == '{'
130 && UnescapeString( aText.Left( offset ) ) == sourcePrefix;
131 };
132 m_schema.prefixEnd = EscapeString( sourcePrefix, CTX_NETNAME ).length();
133
134 // Other escape contexts have tokens that CTX_NETNAME does not re-emit
135 if( !matches( m_schema.prefixEnd ) )
136 {
137 m_schema.prefixEnd = aText.find( '{' );
138
139 while( m_schema.prefixEnd != wxString::npos && !matches( m_schema.prefixEnd ) )
140 m_schema.prefixEnd = aText.find( '{', m_schema.prefixEnd + 1 );
141
142 if( m_schema.prefixEnd == wxString::npos )
143 return false;
144 }
145 }
146 else if( !prefix.IsEmpty() )
147 {
148 aPath.push_back( EscapeString( prefix, CTX_NETNAME ) );
149 }
150
151 if( !prefix.IsEmpty() )
152 aPrefix += prefix + ".";
153
154 for( const wxString& member : members )
155 {
156 if( !expandMember( member, aPrefix, aPath, aNode.members ) )
157 return false;
158 }
159
160 return true;
161 }
162
163 if( aRoot )
164 return false;
165
166 wxString local = text;
167 local.Replace( "\\ ", " " );
168 addLeaf( local, aPrefix, aPath, aNode );
169 return true;
170 }
171
172 void addLeaf( const wxString& aLocal, const wxString& aPrefix, const std::vector<wxString>& aPath,
173 BUS_SCHEMA::NODE& aNode )
174 {
175 aNode.leaf = m_schema.leaves.size();
176 m_schema.leaves.push_back(
177 { EscapeString( aPrefix + aLocal, CTX_NETNAME ), EscapeString( aLocal, CTX_NETNAME ), aPath } );
178 }
179
180 std::map<wxString, std::reference_wrapper<const std::vector<wxString>>> m_aliases;
181 std::set<wxString> m_activeAliases;
182 BUS_SCHEMA m_schema;
183};
184} // namespace
185
186std::optional<SCH_CONNECTIVITY::BUS_SCHEMA> SCH_CONNECTIVITY::BUS_SCHEMA::Parse( const wxString& aText,
187 const BUS_ALIASES& aAliases )
188{
189 return SCHEMA_PARSER( aAliases ).Parse( aText );
190}
191
193{
194 BUS_ALIASES normalized;
195
196 for( const auto& [name, members] : aAliases )
197 {
198 std::vector<wxString> values;
199 values.reserve( members.size() );
200
201 for( const wxString& member : members )
202 values.push_back( member.Strip( wxString::both ) );
203
204 normalized.insert_or_assign( name.Strip( wxString::both ), std::move( values ) );
205 }
206
207 if( normalized == m_aliases )
208 return false;
209
210 // Stage invalidations so allocation failure leaves the previous alias environment usable
211 auto dirty = m_dirty;
212
213 for( const auto& [text, entry] : m_cache.Entries() )
214 {
215 for( const wxString& name : entry->value.aliasesUsed )
216 {
217 const auto before = m_aliases.find( name );
218 const auto after = normalized.find( name );
219
220 if( ( before == m_aliases.end() ) != ( after == normalized.end() )
221 || ( before != m_aliases.end() && after != normalized.end() && before->second != after->second ) )
222 {
223 dirty.insert( text );
224 break;
225 }
226 }
227 }
228
229 m_dirty.swap( dirty );
230 m_aliases.swap( normalized );
231 return true;
232}
233
235{
236 const ENTRY* cached = m_cache.Find( aText );
237
238 if( cached && !m_dirty.contains( aText ) )
239 return *cached;
240
242
243 if( auto schema = SCHEMA_PARSER( m_aliases ).Parse( aText, &result.aliasesUsed ) )
244 {
245 result.tree = std::make_shared<const BUS_SCHEMA::NODE>( std::exchange( schema->root, {} ) );
246
247 if( cached && cached->value.schema && *cached->value.schema == *schema )
248 result.schema = cached->value.schema;
249 else
250 result.schema = std::make_shared<const BUS_SCHEMA>( std::move( *schema ) );
251 }
252
253 const ENTRY& entry = m_cache.Set( aText, std::move( result ) );
254 m_dirty.erase( aText );
255 return entry;
256}
257
258void SCH_CONNECTIVITY::BUS_PARSE_CACHE::Retain( const std::set<wxString>& aLive )
259{
260 for( auto it = m_cache.Entries().begin(); it != m_cache.Entries().end(); )
261 {
262 const auto current = it++;
263
264 if( !aLive.contains( current->first ) )
265 {
266 m_dirty.erase( current->first );
267 m_cache.Erase( current->first );
268 }
269 }
270}
271
272std::shared_ptr<const SCH_CONNECTIVITY::BUS_SCHEMA::NODE>
274{
275 if( m_dirty.contains( aText ) )
276 return {};
277
278 const ENTRY* entry = m_cache.Find( aText );
279 return entry ? entry->value.tree : nullptr;
280}
281
283{
285 const bool byName = aLeft.shape == BUS_SCHEMA::SHAPE::GROUP && aRight.shape == BUS_SCHEMA::SHAPE::GROUP;
286 using KEY = std::pair<std::vector<wxString>, wxString>;
287 std::map<KEY, std::vector<size_t>> candidates;
288
289 if( byName )
290 {
291 for( size_t right = aRight.leaves.size(); right-- > 0; )
292 {
293 const auto& leaf = aRight.leaves[right];
294 candidates[{ leaf.groupPath, leaf.localName }].push_back( right );
295 }
296 }
297
298 for( size_t left = 0; left < aLeft.leaves.size(); ++left )
299 {
300 size_t right = left;
301
302 if( byName )
303 {
304 const auto& leaf = aLeft.leaves[left];
305 const auto found = candidates.find( { leaf.groupPath, leaf.localName } );
306 right = aRight.leaves.size();
307
308 if( found != candidates.end() && !found->second.empty() )
309 {
310 right = found->second.back();
311 found->second.pop_back();
312 }
313 }
314
315 if( right < aRight.leaves.size() )
316 result.matched.emplace_back( left, right );
317 else
318 result.unmappedLeft.push_back( left );
319 }
320
321 return result;
322}
const char * name
static bool ParseBusGroup(const wxString &aGroup, wxString *name, std::vector< wxString > *aMemberList, size_t *aPrefixEnd=nullptr)
Parse a bus group label into the name and a list of components.
static bool ParseBusVector(const wxString &aBus, wxString *aName, std::vector< wxString > *aMemberList)
Parse a bus vector (e.g.
CACHE_TABLE< wxString, RESULT >::ENTRY ENTRY
Definition conn_bus.h:146
CACHE_TABLE< wxString, RESULT > m_cache
Definition conn_bus.h:174
std::set< wxString > m_dirty
Definition conn_bus.h:173
void Retain(const std::set< wxString > &aLive)
Erase the entries whose text is not in aLive.
Definition conn_bus.cpp:258
bool SetAliases(const BUS_ALIASES &aAliases)
Replace the alias table.
Definition conn_bus.cpp:192
std::shared_ptr< const BUS_SCHEMA::NODE > FindTree(const wxString &aText) const
Current tree without cache writes.
Definition conn_bus.cpp:273
const ENTRY & Parse(const wxString &aText)
Return the cached entry, and parse again if the entry is missing or dirty.
Definition conn_bus.cpp:234
Value keys and the key session of the schematic connectivity engine.
std::map< wxString, std::vector< wxString > > BUS_ALIASES
Bus alias table, from alias name to member texts.
Definition conn_bus.h:40
BUS_ALIGNMENT Align(const BUS_SCHEMA &aLeft, const BUS_SCHEMA &aRight)
Match the leaves of aLeft to the leaves of aRight.
Definition conn_bus.cpp:282
PARSE_RESULT Parse(const std::string &aString, NOTATION aNotation=NOTATION::SI, SIM_VALUE::TYPE aValueType=SIM_VALUE::TYPE_FLOAT)
wxString UnescapeString(const wxString &aSource)
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
@ CTX_NETNAME
Member correspondence between two bus schemas.
Definition conn_bus.h:181
wxString text
Container label text.
Definition conn_bus.h:82
std::optional< size_t > leaf
Leaf ordinal of a NET node.
Definition conn_bus.h:86
std::vector< NODE > members
Definition conn_bus.h:84
The parsed form of one bus text.
Definition conn_bus.h:48
std::vector< LEAF > leaves
Member nets in declaration order, with nested buses flattened.
Definition conn_bus.h:98
@ GROUP
A list such as I2C{SDA SCL}. Members align by name with another group.
Definition conn_bus.h:52
static std::optional< BUS_SCHEMA > Parse(const wxString &aText, const BUS_ALIASES &aAliases={})
Parse a vector or group bus text.
Definition conn_bus.cpp:186
wxString result
Test unit parsing edge cases and error handling.