kicad-sch-api 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of kicad-sch-api might be problematic. Click here for more details.
- kicad_sch_api/__init__.py +112 -0
- kicad_sch_api/core/__init__.py +23 -0
- kicad_sch_api/core/components.py +652 -0
- kicad_sch_api/core/formatter.py +312 -0
- kicad_sch_api/core/parser.py +434 -0
- kicad_sch_api/core/schematic.py +478 -0
- kicad_sch_api/core/types.py +369 -0
- kicad_sch_api/library/__init__.py +10 -0
- kicad_sch_api/library/cache.py +548 -0
- kicad_sch_api/mcp/__init__.py +5 -0
- kicad_sch_api/mcp/server.py +500 -0
- kicad_sch_api/py.typed +1 -0
- kicad_sch_api/utils/__init__.py +15 -0
- kicad_sch_api/utils/validation.py +447 -0
- kicad_sch_api-0.0.1.dist-info/METADATA +226 -0
- kicad_sch_api-0.0.1.dist-info/RECORD +20 -0
- kicad_sch_api-0.0.1.dist-info/WHEEL +5 -0
- kicad_sch_api-0.0.1.dist-info/entry_points.txt +2 -0
- kicad_sch_api-0.0.1.dist-info/licenses/LICENSE +21 -0
- kicad_sch_api-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
"""
|
|
2
|
+
S-expression parser for KiCAD schematic files.
|
|
3
|
+
|
|
4
|
+
This module provides robust parsing and writing capabilities for KiCAD's S-expression format,
|
|
5
|
+
with exact format preservation and enhanced error handling.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import uuid
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
12
|
+
|
|
13
|
+
import sexpdata
|
|
14
|
+
|
|
15
|
+
from ..utils.validation import ValidationError, ValidationIssue
|
|
16
|
+
from .formatter import ExactFormatter
|
|
17
|
+
from .types import Junction, Label, Net, Point, SchematicSymbol, Wire
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SExpressionParser:
|
|
23
|
+
"""
|
|
24
|
+
High-performance S-expression parser for KiCAD schematic files.
|
|
25
|
+
|
|
26
|
+
Features:
|
|
27
|
+
- Exact format preservation
|
|
28
|
+
- Enhanced error handling with detailed validation
|
|
29
|
+
- Optimized for large schematics
|
|
30
|
+
- Support for KiCAD 9 format
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, preserve_format: bool = True):
|
|
34
|
+
"""
|
|
35
|
+
Initialize the parser.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
preserve_format: If True, preserve exact formatting when writing
|
|
39
|
+
"""
|
|
40
|
+
self.preserve_format = preserve_format
|
|
41
|
+
self._formatter = ExactFormatter() if preserve_format else None
|
|
42
|
+
self._validation_issues = []
|
|
43
|
+
logger.info(f"S-expression parser initialized (format preservation: {preserve_format})")
|
|
44
|
+
|
|
45
|
+
def parse_file(self, filepath: Union[str, Path]) -> Dict[str, Any]:
|
|
46
|
+
"""
|
|
47
|
+
Parse a KiCAD schematic file with comprehensive validation.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
filepath: Path to the .kicad_sch file
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Parsed schematic data structure
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
FileNotFoundError: If file doesn't exist
|
|
57
|
+
ValidationError: If parsing fails or validation issues found
|
|
58
|
+
"""
|
|
59
|
+
filepath = Path(filepath)
|
|
60
|
+
if not filepath.exists():
|
|
61
|
+
raise FileNotFoundError(f"Schematic file not found: {filepath}")
|
|
62
|
+
|
|
63
|
+
logger.info(f"Parsing schematic file: {filepath}")
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
# Read file content
|
|
67
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
68
|
+
content = f.read()
|
|
69
|
+
|
|
70
|
+
# Parse S-expression
|
|
71
|
+
sexp_data = self.parse_string(content)
|
|
72
|
+
|
|
73
|
+
# Validate structure
|
|
74
|
+
self._validate_schematic_structure(sexp_data, filepath)
|
|
75
|
+
|
|
76
|
+
# Convert to internal format
|
|
77
|
+
schematic_data = self._sexp_to_schematic_data(sexp_data)
|
|
78
|
+
schematic_data["_original_content"] = content # Store for format preservation
|
|
79
|
+
schematic_data["_file_path"] = str(filepath)
|
|
80
|
+
|
|
81
|
+
logger.info(
|
|
82
|
+
f"Successfully parsed schematic with {len(schematic_data.get('components', []))} components"
|
|
83
|
+
)
|
|
84
|
+
return schematic_data
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(f"Error parsing {filepath}: {e}")
|
|
88
|
+
raise ValidationError(f"Failed to parse schematic: {e}") from e
|
|
89
|
+
|
|
90
|
+
def parse_string(self, content: str) -> Any:
|
|
91
|
+
"""
|
|
92
|
+
Parse S-expression content from string.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
content: S-expression string content
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Parsed S-expression data structure
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
ValidationError: If parsing fails
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
return sexpdata.loads(content)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
raise ValidationError(f"Invalid S-expression format: {e}") from e
|
|
107
|
+
|
|
108
|
+
def write_file(self, schematic_data: Dict[str, Any], filepath: Union[str, Path]):
|
|
109
|
+
"""
|
|
110
|
+
Write schematic data to file with exact format preservation.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
schematic_data: Schematic data structure
|
|
114
|
+
filepath: Path to write to
|
|
115
|
+
"""
|
|
116
|
+
filepath = Path(filepath)
|
|
117
|
+
|
|
118
|
+
# Convert internal format to S-expression
|
|
119
|
+
sexp_data = self._schematic_data_to_sexp(schematic_data)
|
|
120
|
+
|
|
121
|
+
# Format content
|
|
122
|
+
if self.preserve_format and "_original_content" in schematic_data:
|
|
123
|
+
# Use format-preserving writer
|
|
124
|
+
content = self._formatter.format_preserving_write(
|
|
125
|
+
sexp_data, schematic_data["_original_content"]
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
# Standard S-expression formatting
|
|
129
|
+
content = self.dumps(sexp_data)
|
|
130
|
+
|
|
131
|
+
# Ensure directory exists
|
|
132
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
|
|
134
|
+
# Write to file
|
|
135
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
136
|
+
f.write(content)
|
|
137
|
+
|
|
138
|
+
logger.info(f"Schematic written to: {filepath}")
|
|
139
|
+
|
|
140
|
+
def dumps(self, data: Any, pretty: bool = True) -> str:
|
|
141
|
+
"""
|
|
142
|
+
Convert S-expression data to string.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
data: S-expression data structure
|
|
146
|
+
pretty: If True, format with proper indentation
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Formatted S-expression string
|
|
150
|
+
"""
|
|
151
|
+
if pretty and self._formatter:
|
|
152
|
+
return self._formatter.format(data)
|
|
153
|
+
else:
|
|
154
|
+
return sexpdata.dumps(data)
|
|
155
|
+
|
|
156
|
+
def _validate_schematic_structure(self, sexp_data: Any, filepath: Path):
|
|
157
|
+
"""Validate the basic structure of a KiCAD schematic."""
|
|
158
|
+
self._validation_issues.clear()
|
|
159
|
+
|
|
160
|
+
if not isinstance(sexp_data, list) or len(sexp_data) == 0:
|
|
161
|
+
self._validation_issues.append(
|
|
162
|
+
ValidationIssue("structure", "Invalid schematic format: not a list", "error")
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Check for kicad_sch header
|
|
166
|
+
if not (isinstance(sexp_data[0], sexpdata.Symbol) and str(sexp_data[0]) == "kicad_sch"):
|
|
167
|
+
self._validation_issues.append(
|
|
168
|
+
ValidationIssue("format", "Missing kicad_sch header", "error")
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Collect validation issues and raise if any errors found
|
|
172
|
+
errors = [issue for issue in self._validation_issues if issue.level == "error"]
|
|
173
|
+
if errors:
|
|
174
|
+
error_messages = [f"{issue.category}: {issue.message}" for issue in errors]
|
|
175
|
+
raise ValidationError(f"Validation failed: {'; '.join(error_messages)}")
|
|
176
|
+
|
|
177
|
+
def _sexp_to_schematic_data(self, sexp_data: List[Any]) -> Dict[str, Any]:
|
|
178
|
+
"""Convert S-expression data to internal schematic format."""
|
|
179
|
+
schematic_data = {
|
|
180
|
+
"version": None,
|
|
181
|
+
"generator": None,
|
|
182
|
+
"uuid": None,
|
|
183
|
+
"title_block": {},
|
|
184
|
+
"components": [],
|
|
185
|
+
"wires": [],
|
|
186
|
+
"junctions": [],
|
|
187
|
+
"labels": [],
|
|
188
|
+
"nets": [],
|
|
189
|
+
"lib_symbols": {},
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# Process top-level elements
|
|
193
|
+
for item in sexp_data[1:]: # Skip kicad_sch header
|
|
194
|
+
if not isinstance(item, list):
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
if len(item) == 0:
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
element_type = str(item[0]) if isinstance(item[0], sexpdata.Symbol) else None
|
|
201
|
+
|
|
202
|
+
if element_type == "version":
|
|
203
|
+
schematic_data["version"] = item[1] if len(item) > 1 else None
|
|
204
|
+
elif element_type == "generator":
|
|
205
|
+
schematic_data["generator"] = item[1] if len(item) > 1 else None
|
|
206
|
+
elif element_type == "uuid":
|
|
207
|
+
schematic_data["uuid"] = item[1] if len(item) > 1 else None
|
|
208
|
+
elif element_type == "title_block":
|
|
209
|
+
schematic_data["title_block"] = self._parse_title_block(item)
|
|
210
|
+
elif element_type == "symbol":
|
|
211
|
+
component = self._parse_symbol(item)
|
|
212
|
+
if component:
|
|
213
|
+
schematic_data["components"].append(component)
|
|
214
|
+
elif element_type == "wire":
|
|
215
|
+
wire = self._parse_wire(item)
|
|
216
|
+
if wire:
|
|
217
|
+
schematic_data["wires"].append(wire)
|
|
218
|
+
elif element_type == "junction":
|
|
219
|
+
junction = self._parse_junction(item)
|
|
220
|
+
if junction:
|
|
221
|
+
schematic_data["junctions"].append(junction)
|
|
222
|
+
elif element_type == "label":
|
|
223
|
+
label = self._parse_label(item)
|
|
224
|
+
if label:
|
|
225
|
+
schematic_data["labels"].append(label)
|
|
226
|
+
elif element_type == "lib_symbols":
|
|
227
|
+
schematic_data["lib_symbols"] = self._parse_lib_symbols(item)
|
|
228
|
+
|
|
229
|
+
return schematic_data
|
|
230
|
+
|
|
231
|
+
def _schematic_data_to_sexp(self, schematic_data: Dict[str, Any]) -> List[Any]:
|
|
232
|
+
"""Convert internal schematic format to S-expression data."""
|
|
233
|
+
sexp_data = [sexpdata.Symbol("kicad_sch")]
|
|
234
|
+
|
|
235
|
+
# Add version and generator
|
|
236
|
+
if schematic_data.get("version"):
|
|
237
|
+
sexp_data.append([sexpdata.Symbol("version"), schematic_data["version"]])
|
|
238
|
+
if schematic_data.get("generator"):
|
|
239
|
+
sexp_data.append([sexpdata.Symbol("generator"), schematic_data["generator"]])
|
|
240
|
+
if schematic_data.get("uuid"):
|
|
241
|
+
sexp_data.append([sexpdata.Symbol("uuid"), schematic_data["uuid"]])
|
|
242
|
+
|
|
243
|
+
# Add title block
|
|
244
|
+
if schematic_data.get("title_block"):
|
|
245
|
+
sexp_data.append(self._title_block_to_sexp(schematic_data["title_block"]))
|
|
246
|
+
|
|
247
|
+
# Add lib_symbols
|
|
248
|
+
if schematic_data.get("lib_symbols"):
|
|
249
|
+
sexp_data.append(self._lib_symbols_to_sexp(schematic_data["lib_symbols"]))
|
|
250
|
+
|
|
251
|
+
# Add components
|
|
252
|
+
for component in schematic_data.get("components", []):
|
|
253
|
+
sexp_data.append(self._symbol_to_sexp(component))
|
|
254
|
+
|
|
255
|
+
# Add wires
|
|
256
|
+
for wire in schematic_data.get("wires", []):
|
|
257
|
+
sexp_data.append(self._wire_to_sexp(wire))
|
|
258
|
+
|
|
259
|
+
# Add junctions
|
|
260
|
+
for junction in schematic_data.get("junctions", []):
|
|
261
|
+
sexp_data.append(self._junction_to_sexp(junction))
|
|
262
|
+
|
|
263
|
+
# Add labels
|
|
264
|
+
for label in schematic_data.get("labels", []):
|
|
265
|
+
sexp_data.append(self._label_to_sexp(label))
|
|
266
|
+
|
|
267
|
+
return sexp_data
|
|
268
|
+
|
|
269
|
+
def _parse_title_block(self, item: List[Any]) -> Dict[str, Any]:
|
|
270
|
+
"""Parse title block information."""
|
|
271
|
+
title_block = {}
|
|
272
|
+
for sub_item in item[1:]:
|
|
273
|
+
if isinstance(sub_item, list) and len(sub_item) >= 2:
|
|
274
|
+
key = str(sub_item[0]) if isinstance(sub_item[0], sexpdata.Symbol) else None
|
|
275
|
+
if key:
|
|
276
|
+
title_block[key] = sub_item[1] if len(sub_item) > 1 else None
|
|
277
|
+
return title_block
|
|
278
|
+
|
|
279
|
+
def _parse_symbol(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
280
|
+
"""Parse a symbol (component) definition."""
|
|
281
|
+
try:
|
|
282
|
+
symbol_data = {
|
|
283
|
+
"lib_id": None,
|
|
284
|
+
"position": Point(0, 0),
|
|
285
|
+
"rotation": 0,
|
|
286
|
+
"uuid": None,
|
|
287
|
+
"reference": None,
|
|
288
|
+
"value": None,
|
|
289
|
+
"footprint": None,
|
|
290
|
+
"properties": {},
|
|
291
|
+
"pins": [],
|
|
292
|
+
"in_bom": True,
|
|
293
|
+
"on_board": True,
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
for sub_item in item[1:]:
|
|
297
|
+
if not isinstance(sub_item, list) or len(sub_item) == 0:
|
|
298
|
+
continue
|
|
299
|
+
|
|
300
|
+
element_type = (
|
|
301
|
+
str(sub_item[0]) if isinstance(sub_item[0], sexpdata.Symbol) else None
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
if element_type == "lib_id":
|
|
305
|
+
symbol_data["lib_id"] = sub_item[1] if len(sub_item) > 1 else None
|
|
306
|
+
elif element_type == "at":
|
|
307
|
+
if len(sub_item) >= 3:
|
|
308
|
+
symbol_data["position"] = Point(float(sub_item[1]), float(sub_item[2]))
|
|
309
|
+
if len(sub_item) > 3:
|
|
310
|
+
symbol_data["rotation"] = float(sub_item[3])
|
|
311
|
+
elif element_type == "uuid":
|
|
312
|
+
symbol_data["uuid"] = sub_item[1] if len(sub_item) > 1 else None
|
|
313
|
+
elif element_type == "property":
|
|
314
|
+
prop_data = self._parse_property(sub_item)
|
|
315
|
+
if prop_data:
|
|
316
|
+
prop_name = prop_data.get("name")
|
|
317
|
+
if prop_name == "Reference":
|
|
318
|
+
symbol_data["reference"] = prop_data.get("value")
|
|
319
|
+
elif prop_name == "Value":
|
|
320
|
+
symbol_data["value"] = prop_data.get("value")
|
|
321
|
+
elif prop_name == "Footprint":
|
|
322
|
+
symbol_data["footprint"] = prop_data.get("value")
|
|
323
|
+
else:
|
|
324
|
+
symbol_data["properties"][prop_name] = prop_data.get("value")
|
|
325
|
+
elif element_type == "in_bom":
|
|
326
|
+
symbol_data["in_bom"] = sub_item[1] == "yes" if len(sub_item) > 1 else True
|
|
327
|
+
elif element_type == "on_board":
|
|
328
|
+
symbol_data["on_board"] = sub_item[1] == "yes" if len(sub_item) > 1 else True
|
|
329
|
+
|
|
330
|
+
return symbol_data
|
|
331
|
+
|
|
332
|
+
except Exception as e:
|
|
333
|
+
logger.warning(f"Error parsing symbol: {e}")
|
|
334
|
+
return None
|
|
335
|
+
|
|
336
|
+
def _parse_property(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
337
|
+
"""Parse a property definition."""
|
|
338
|
+
if len(item) < 3:
|
|
339
|
+
return None
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
"name": item[1] if len(item) > 1 else None,
|
|
343
|
+
"value": item[2] if len(item) > 2 else None,
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
def _parse_wire(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
347
|
+
"""Parse a wire definition."""
|
|
348
|
+
# Implementation for wire parsing
|
|
349
|
+
# This would parse pts, stroke, uuid elements
|
|
350
|
+
return {}
|
|
351
|
+
|
|
352
|
+
def _parse_junction(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
353
|
+
"""Parse a junction definition."""
|
|
354
|
+
# Implementation for junction parsing
|
|
355
|
+
return {}
|
|
356
|
+
|
|
357
|
+
def _parse_label(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
358
|
+
"""Parse a label definition."""
|
|
359
|
+
# Implementation for label parsing
|
|
360
|
+
return {}
|
|
361
|
+
|
|
362
|
+
def _parse_lib_symbols(self, item: List[Any]) -> Dict[str, Any]:
|
|
363
|
+
"""Parse lib_symbols section."""
|
|
364
|
+
# Implementation for lib_symbols parsing
|
|
365
|
+
return {}
|
|
366
|
+
|
|
367
|
+
# Conversion methods from internal format to S-expression
|
|
368
|
+
def _title_block_to_sexp(self, title_block: Dict[str, Any]) -> List[Any]:
|
|
369
|
+
"""Convert title block to S-expression."""
|
|
370
|
+
sexp = [sexpdata.Symbol("title_block")]
|
|
371
|
+
for key, value in title_block.items():
|
|
372
|
+
sexp.append([sexpdata.Symbol(key), value])
|
|
373
|
+
return sexp
|
|
374
|
+
|
|
375
|
+
def _symbol_to_sexp(self, symbol_data: Dict[str, Any]) -> List[Any]:
|
|
376
|
+
"""Convert symbol to S-expression."""
|
|
377
|
+
sexp = [sexpdata.Symbol("symbol")]
|
|
378
|
+
|
|
379
|
+
if symbol_data.get("lib_id"):
|
|
380
|
+
sexp.append([sexpdata.Symbol("lib_id"), symbol_data["lib_id"]])
|
|
381
|
+
|
|
382
|
+
# Add position and rotation
|
|
383
|
+
pos = symbol_data.get("position", Point(0, 0))
|
|
384
|
+
rotation = symbol_data.get("rotation", 0)
|
|
385
|
+
if rotation != 0:
|
|
386
|
+
sexp.append([sexpdata.Symbol("at"), pos.x, pos.y, rotation])
|
|
387
|
+
else:
|
|
388
|
+
sexp.append([sexpdata.Symbol("at"), pos.x, pos.y])
|
|
389
|
+
|
|
390
|
+
if symbol_data.get("uuid"):
|
|
391
|
+
sexp.append([sexpdata.Symbol("uuid"), symbol_data["uuid"]])
|
|
392
|
+
|
|
393
|
+
# Add properties
|
|
394
|
+
if symbol_data.get("reference"):
|
|
395
|
+
sexp.append([sexpdata.Symbol("property"), "Reference", symbol_data["reference"]])
|
|
396
|
+
if symbol_data.get("value"):
|
|
397
|
+
sexp.append([sexpdata.Symbol("property"), "Value", symbol_data["value"]])
|
|
398
|
+
if symbol_data.get("footprint"):
|
|
399
|
+
sexp.append([sexpdata.Symbol("property"), "Footprint", symbol_data["footprint"]])
|
|
400
|
+
|
|
401
|
+
for prop_name, prop_value in symbol_data.get("properties", {}).items():
|
|
402
|
+
sexp.append([sexpdata.Symbol("property"), prop_name, prop_value])
|
|
403
|
+
|
|
404
|
+
# Add BOM and board settings
|
|
405
|
+
sexp.append([sexpdata.Symbol("in_bom"), "yes" if symbol_data.get("in_bom", True) else "no"])
|
|
406
|
+
sexp.append(
|
|
407
|
+
[sexpdata.Symbol("on_board"), "yes" if symbol_data.get("on_board", True) else "no"]
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
return sexp
|
|
411
|
+
|
|
412
|
+
def _wire_to_sexp(self, wire_data: Dict[str, Any]) -> List[Any]:
|
|
413
|
+
"""Convert wire to S-expression."""
|
|
414
|
+
# Implementation for wire conversion
|
|
415
|
+
return [sexpdata.Symbol("wire")]
|
|
416
|
+
|
|
417
|
+
def _junction_to_sexp(self, junction_data: Dict[str, Any]) -> List[Any]:
|
|
418
|
+
"""Convert junction to S-expression."""
|
|
419
|
+
# Implementation for junction conversion
|
|
420
|
+
return [sexpdata.Symbol("junction")]
|
|
421
|
+
|
|
422
|
+
def _label_to_sexp(self, label_data: Dict[str, Any]) -> List[Any]:
|
|
423
|
+
"""Convert label to S-expression."""
|
|
424
|
+
# Implementation for label conversion
|
|
425
|
+
return [sexpdata.Symbol("label")]
|
|
426
|
+
|
|
427
|
+
def _lib_symbols_to_sexp(self, lib_symbols: Dict[str, Any]) -> List[Any]:
|
|
428
|
+
"""Convert lib_symbols to S-expression."""
|
|
429
|
+
# Implementation for lib_symbols conversion
|
|
430
|
+
return [sexpdata.Symbol("lib_symbols")]
|
|
431
|
+
|
|
432
|
+
def get_validation_issues(self) -> List[ValidationIssue]:
|
|
433
|
+
"""Get list of validation issues from last parse operation."""
|
|
434
|
+
return self._validation_issues.copy()
|