KiCad PCB EDA Suite
Loading...
Searching...
No Matches
kicad_netlist_reader.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 (C) 1992-2011 Jean-Pierre Charras.
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include <netlist_lexer.h> // netlist_lexer is common to Eeschema and Pcbnew
26#include <string_utils.h>
27#include <json_common.h>
28
29#include "pcb_netlist.h"
30#include "netlist_reader.h"
32
33using namespace NL_T;
34
35
37{
39
40 parser.Parse();
41
43 {
45
46 // Sort the component pins so they are in the same order as the legacy format. This
47 // is useful for comparing legacy and s-expression netlist dumps.
48 for( unsigned i = 0; i < m_netlist->GetCount(); i++ )
50 }
51}
52
53
54// KICAD_NETLIST_PARSER
56 NETLIST_LEXER( aReader )
57{
58 m_lineReader = aReader;
59 m_netlist = aNetlist;
60 token = T_NONE;
61}
62
63
65{
66 int curr_level = 0;
67
68 while( ( token = NextTok() ) != T_EOF )
69 {
70 if( token == T_LEFT )
71 curr_level--;
72
73 if( token == T_RIGHT )
74 {
75 curr_level++;
76
77 if( curr_level > 0 )
78 return;
79 }
80 }
81}
82
83
85{
86 int plevel = 0; // the count of ')' to read at end of file after parsing all sections
87
88 while( ( token = NextTok() ) != T_EOF )
89 {
90 if( token == T_LEFT )
91 token = NextTok();
92
93 switch( token )
94 {
95 case T_export: // The netlist starts here.
96 // nothing to do here, just increment the count of ')' to read at end of file
97 plevel++;
98 break;
99
100 case T_version: // The netlist starts here.
101 // version id not yet used: read it but does not use it
102 NextTok();
103 NeedRIGHT();
104 break;
105
106 case T_components: // The section comp starts here.
107 while( ( token = NextTok() ) != T_EOF )
108 {
109 if( token == T_RIGHT )
110 break;
111 else if( token == T_LEFT )
112 token = NextTok();
113
114 if( token == T_comp ) // A component section found. Read it
116 }
117
118 break;
119
120 case T_groups: // The section groups starts here.
121 while( ( token = NextTok() ) != T_EOF )
122 {
123 if( token == T_RIGHT )
124 break;
125 else if( token == T_LEFT )
126 token = NextTok();
127
128 if( token == T_group ) // A group section found. Read it
129 parseGroup();
130 }
131
132 break;
133
134 case T_nets: // The section nets starts here.
135 while( ( token = NextTok() ) != T_EOF )
136 {
137 if( token == T_RIGHT )
138 break;
139 else if( token == T_LEFT )
140 token = NextTok();
141
142 if( token == T_net ) // A net section if found. Read it
143 parseNet();
144 }
145
146 break;
147
148 case T_libparts: // The section libparts starts here.
149 while( ( token = NextTok() ) != T_EOF )
150 {
151 if( token == T_RIGHT )
152 break;
153 else if( token == T_LEFT )
154 token = NextTok();
155
156 if( token == T_libpart ) // A libpart section if found. Read it
158 }
159
160 break;
161
162 case T_libraries: // The section libraries starts here.
163 // List of libraries in use.
164 // Not used here, just skip it
165 skipCurrent();
166 break;
167
168 case T_design: // The section design starts here.
169 // Not used (mainly they are comments), just skip it
170 skipCurrent();
171 break;
172
173 case T_RIGHT: // The closing parenthesis of the file.
174 plevel--;
175 break;
176
177 default:
178 skipCurrent();
179 break;
180 }
181 }
182
183 // Go back and apply group information to the components
185
186 if( plevel != 0 )
187 {
188 wxFAIL_MSG( wxString::Format( wxT( "KICAD_NETLIST_PARSER::Parse(): bad parenthesis "
189 "count (count = %d" ),
190 plevel ) );
191 }
192}
193
194
196{
197 /* Parses a section like
198 * (net (code 20) (name /PC-A0)
199 * (node (ref "BUS1") (pin "62)")
200 * (node (ref "U3") ("pin 3") (pin_function "clock"))
201 * (node (ref "U9") (pin "M6") (pin_function "reset")))
202 */
203
204 wxString code;
205 wxString name;
206 wxString reference;
207 wxString pin_number;
208 wxString pin_function;
209 wxString pin_type;
210
211 // The token net was read, so the next data is (code <number>)
212 while( (token = NextTok() ) != T_EOF )
213 {
214 if( token == T_RIGHT )
215 break;
216 else if( token == T_LEFT )
217 token = NextTok();
218
219 switch( token )
220 {
221 case T_code:
222 NeedSYMBOLorNUMBER();
223 code = From_UTF8( CurText() );
224 NeedRIGHT();
225 break;
226
227 case T_name:
228 NeedSYMBOLorNUMBER();
229 name = From_UTF8( CurText() );
230 NeedRIGHT();
231 break;
232
233 case T_node:
234 // By default: no pin function or type.
235 pin_function.Clear();
236 pin_type.Clear();
237
238 while( (token = NextTok() ) != T_EOF )
239 {
240 if( token == T_RIGHT )
241 break;
242 else if( token == T_LEFT )
243 token = NextTok();
244
245 switch( token )
246 {
247 case T_ref:
248 NeedSYMBOLorNUMBER();
249 reference = From_UTF8( CurText() );
250 NeedRIGHT();
251 break;
252
253 case T_pin:
254 NeedSYMBOLorNUMBER();
255 pin_number = From_UTF8( CurText() );
256 NeedRIGHT();
257 break;
258
259 case T_pinfunction:
260 NeedSYMBOLorNUMBER();
261 pin_function = From_UTF8( CurText() );
262 NeedRIGHT();
263 break;
264
265 case T_pintype:
266 NeedSYMBOLorNUMBER();
267 pin_type = From_UTF8( CurText() );
268 NeedRIGHT();
269 break;
270
271 default:
272 skipCurrent();
273 break;
274 }
275 }
276
277 // Don't assume component will be found; it might be "DNP" or "Exclude from board".
278 if( COMPONENT* component = m_netlist->GetComponentByReference( reference ) )
279 {
280 if( strtol( code.c_str(), nullptr, 10 ) >= 1 )
281 {
282 if( name.IsEmpty() ) // Give a dummy net name like N-000009
283 name = wxT("N-00000") + code;
284
285 component->AddNet( pin_number, name, pin_function, pin_type );
286 }
287 }
288
289 break;
290
291 default:
292 skipCurrent();
293 break;
294 }
295 }
296}
297
298
300{
301 /* Parses a section like
302 * (comp (ref P1)
303 * (value DB25FEMALE)
304 * (footprint DB25FC)
305 * (libsource (lib conn) (part DB25))
306 * (property (name PINCOUNT) (value 25))
307 * (sheetpath (names /) (tstamps /))
308 * (component_classes (class (name "CLASS")))
309 * (tstamp 68183921-93a5-49ac-91b0-49d05a0e1647))
310 *
311 * other fields (unused) are skipped
312 * A component need a reference, value, footprint name and a full time stamp
313 * The full time stamp is the sheetpath time stamp + the component time stamp
314 */
315 LIB_ID fpid;
316 wxString footprint;
317 wxString ref;
318 wxString value;
319 wxString library;
320 wxString name;
321 wxString humanSheetPath;
323
324 std::vector<KIID> uuids;
325 std::map<wxString, wxString> properties;
326 nlohmann::ordered_map<wxString, wxString> fields;
327 std::unordered_set<wxString> componentClasses;
328
329 bool duplicatePinsAreJumpers = false;
330 std::vector<std::set<wxString>> jumperPinGroups;
331
332 // The token comp was read, so the next data is (ref P1)
333 while( (token = NextTok() ) != T_RIGHT )
334 {
335 if( token == T_LEFT )
336 token = NextTok();
337
338 switch( token )
339 {
340 case T_ref:
341 NeedSYMBOLorNUMBER();
342 ref = From_UTF8( CurText() );
343 NeedRIGHT();
344 break;
345
346 case T_value:
347 NeedSYMBOLorNUMBER();
348 value = From_UTF8( CurText() );
349 NeedRIGHT();
350 break;
351
352 case T_footprint:
353 NeedSYMBOLorNUMBER();
354 footprint = FromUTF8();
355 NeedRIGHT();
356 break;
357
358 case T_libsource:
359 // Read libsource
360 while( ( token = NextTok() ) != T_RIGHT )
361 {
362 if( token == T_LEFT )
363 token = NextTok();
364
365 if( token == T_lib )
366 {
367 NeedSYMBOLorNUMBER();
368 library = From_UTF8( CurText() );
369 NeedRIGHT();
370 }
371 else if( token == T_part )
372 {
373 NeedSYMBOLorNUMBER();
374 name = From_UTF8( CurText() );
375 NeedRIGHT();
376 }
377 else if( token == T_description )
378 {
379 NeedSYMBOLorNUMBER();
380 NeedRIGHT();
381 }
382 else
383 {
384 Expecting( "part, lib or description" );
385 }
386 }
387 break;
388
389 case T_property:
390 {
391 wxString propName;
392 wxString propValue;
393
394 while( (token = NextTok() ) != T_RIGHT )
395 {
396 if( token == T_LEFT )
397 token = NextTok();
398
399 if( token == T_name )
400 {
401 NeedSYMBOLorNUMBER();
402 propName = From_UTF8( CurText() );
403 NeedRIGHT();
404 }
405 else if( token == T_value )
406 {
407 NeedSYMBOLorNUMBER();
408 propValue = From_UTF8( CurText() );
409 NeedRIGHT();
410 }
411 else
412 {
413 Expecting( "name or value" );
414 }
415 }
416
417 if( !propName.IsEmpty() )
418 properties[propName] = std::move( propValue );
419 }
420 break;
421
422 case T_fields:
423 while( ( token = NextTok() ) != T_RIGHT )
424 {
425 if( token == T_LEFT )
426 token = NextTok();
427
428 if( token == T_field )
429 {
430 wxString fieldName;
431 wxString fieldValue;
432
433 while( ( token = NextTok() ) != T_RIGHT )
434 {
435 if( token == T_LEFT )
436 token = NextTok();
437
438 if( token == T_name )
439 {
440 NeedSYMBOLorNUMBER();
441 fieldName = From_UTF8( CurText() );
442 NeedRIGHT();
443 }
444 else if( token == T_STRING )
445 {
446 fieldValue = From_UTF8( CurText() );
447 }
448 }
449
450 if( !fieldName.IsEmpty() )
451 fields[fieldName] = std::move( fieldValue );
452 }
453 else
454 {
455 Expecting( "field" );
456 }
457 }
458 break;
459
460 case T_sheetpath:
461 while( ( token = NextTok() ) != T_EOF )
462 {
463 if( token == T_names )
464 {
465 NeedSYMBOLorNUMBER();
466 humanSheetPath = From_UTF8( CurText() );
467 NeedRIGHT();
468 }
469
470 if( token == T_tstamps )
471 {
472 NeedSYMBOLorNUMBER();
473 path = KIID_PATH( From_UTF8( CurText() ) );
474 NeedRIGHT();
475 break;
476 }
477 }
478
479 NeedRIGHT();
480
481 break;
482
483 case T_tstamps:
484 while( ( token = NextTok() ) != T_EOF )
485 {
486 if( token == T_RIGHT )
487 break;
488
489 uuids.emplace_back( From_UTF8( CurText() ) );
490 }
491
492 break;
493
494 case T_component_classes:
495 while( ( token = NextTok() ) != T_RIGHT )
496 {
497 if( token != T_LEFT )
498 Expecting( T_LEFT );
499
500 if( ( token = NextTok() ) != T_class )
501 Expecting( T_class );
502
503 NeedSYMBOLorNUMBER();
504 componentClasses.insert( From_UTF8( CurText() ) );
505 NeedRIGHT();
506 }
507
508 break;
509
510 case T_duplicate_pin_numbers_are_jumpers:
511 {
512 NeedSYMBOLorNUMBER();
513 duplicatePinsAreJumpers = From_UTF8( CurText() ) == wxT( "1" );
514 NeedRIGHT();
515 break;
516 }
517
518 case T_jumper_pin_groups:
519 {
520 std::set<wxString>* currentGroup = nullptr;
521
522 for( token = NextTok(); currentGroup || token != T_RIGHT; token = NextTok() )
523 {
524 if( token == T_LEFT )
525 token = NextTok();
526
527 switch( token )
528 {
529 case T_group:
530 currentGroup = &jumperPinGroups.emplace_back();
531 break;
532
533 case T_pin:
534 {
535 NeedSYMBOLorNUMBER();
536 wxString padName = From_UTF8( CurText() );
537 NeedRIGHT();
538 wxCHECK2( currentGroup, continue );
539 currentGroup->insert( padName );
540 break;
541 }
542
543 case T_RIGHT:
544 currentGroup = nullptr;
545 break;
546
547 default:
548 Expecting( "group or pin" );
549 }
550 }
551
552 break;
553 }
554
555 default:
556 // Skip not used data (i.e all other tokens)
557 skipCurrent();
558 break;
559 }
560 }
561
562 if( !footprint.IsEmpty() && fpid.Parse( footprint, true ) >= 0 )
563 {
564 wxString error;
565 error.Printf( _( "Invalid footprint ID in\nfile: '%s'\nline: %d\nofff: %d" ),
566 CurSource(), CurLineNumber(), CurOffset() );
567
568 THROW_IO_ERROR( error );
569 }
570
571 COMPONENT* component = new COMPONENT( fpid, ref, value, path, uuids );
572 component->SetName( name );
573 component->SetLibrary( library );
574 component->SetProperties( properties );
575 component->SetFields( fields );
576 component->SetHumanReadablePath( humanSheetPath );
577 component->SetComponentClassNames( componentClasses );
578 component->SetDuplicatePadNumbersAreJumpers( duplicatePinsAreJumpers );
579 std::ranges::copy( jumperPinGroups, std::inserter( component->JumperPadGroups(),
580 component->JumperPadGroups().end() ) );
581 m_netlist->AddComponent( component );
582}
583
584
586{
587 /* Parses a section like
588 * (groups
589 * (group (name "") (lib_id "DesignBlock:Block") (uuid "7b1488be-4c43-4004-94fc-e4a26dda8f5b")
590 * (members
591 * (member (uuid "dfef752d-e203-4feb-91de-483b44bc4062"))
592 */
593
594 wxString name;
595 KIID uuid;
596 wxString libId; // Design block library link
597 std::vector<KIID> members;
598
599 // The token net was read, so the next data is (code <number>)
600 while( (token = NextTok() ) != T_EOF )
601 {
602 if( token == T_RIGHT )
603 break;
604 else if( token == T_LEFT )
605 token = NextTok();
606
607 switch( token )
608 {
609 case T_name:
610 NeedSYMBOLorNUMBER();
611 name = From_UTF8( CurText() );
612 NeedRIGHT();
613 break;
614
615 case T_uuid:
616 NeedSYMBOLorNUMBER();
617 uuid = From_UTF8( CurText() );
618 NeedRIGHT();
619 break;
620
621 case T_lib_id:
622 NeedSYMBOLorNUMBER();
623 libId = FromUTF8();
624 NeedRIGHT();
625 break;
626
627 case T_members:
628 while( ( token = NextTok() ) != T_RIGHT )
629 {
630 if( token == T_LEFT )
631 token = NextTok();
632
633 if( token == T_member )
634 {
635 wxString memberUuid;
636
637 while( ( token = NextTok() ) != T_RIGHT )
638 {
639 if( token == T_LEFT )
640 token = NextTok();
641
642 if( token == T_uuid )
643 {
644 NeedSYMBOLorNUMBER();
645 memberUuid = From_UTF8( CurText() );
646 NeedRIGHT();
647 }
648 else
649 {
650 Expecting( "uuid" );
651 }
652 }
653
654 members.emplace_back( memberUuid );
655 }
656 else
657 {
658 Expecting( "member" );
659 }
660
661 }
662 break;
663
664 default:
665 skipCurrent();
666 break;
667 }
668 }
669
670 LIB_ID groupLibId;
671
672 if( !libId.IsEmpty() && groupLibId.Parse( libId, true ) >= 0 )
673 {
674 wxString error;
675 error.Printf( _( "Invalid lib_id ID in\nfile: '%s'\nline: %d\nofff: %d" ), CurSource(), CurLineNumber(),
676 CurOffset() );
677
678 THROW_IO_ERROR( error );
679 }
680
681 NETLIST_GROUP* group = new NETLIST_GROUP{ std::move( name ), std::move( uuid ), std::move( groupLibId ),
682 std::move( members ) };
684}
685
686
688{
689 /* Parses a section like
690 * (libpart (lib device) (part C)
691 * (aliases
692 * (alias Cxx)
693 * (alias Cyy))
694 * (description "Condensateur non polarise")
695 * (footprints
696 * (fp SM*)
697 * (fp C?)
698 * (fp C1-1))
699 * (fields
700 * (field (name Reference) C)
701 * (field (name Value) C))
702 * (pins
703 * (pin (num 1) (name ~) (type passive))
704 * (pin (num 2) (name ~) (type passive))))
705 *
706 * Currently footprints section/fp are read and data stored
707 * other fields (unused) are skipped
708 */
709 COMPONENT* component = NULL;
710 wxString libName;
711 wxString libPartName;
712 wxArrayString footprintFilters;
713 wxArrayString aliases;
714 int pinCount = 0;
715
716 // The last token read was libpart, so read the next token
717 while( (token = NextTok() ) != T_RIGHT )
718 {
719 if( token == T_LEFT )
720 token = NextTok();
721
722 switch( token )
723 {
724 case T_lib:
725 NeedSYMBOLorNUMBER();
726 libName = From_UTF8( CurText() );
727 NeedRIGHT();
728 break;
729
730 case T_part:
731 NeedSYMBOLorNUMBER();
732 libPartName = From_UTF8( CurText() );
733 NeedRIGHT();
734 break;
735
736 case T_footprints:
737 // Read all fp elements (footprint filter item)
738 while( (token = NextTok() ) != T_RIGHT )
739 {
740 if( token == T_LEFT )
741 token = NextTok();
742
743 if( token != T_fp )
744 Expecting( T_fp );
745
746 token = NextTok();
747
748 // Accept an empty (fp) sexpr. We do write them out.
749 if( token == T_RIGHT )
750 continue;
751
752 if( !IsSymbol( token ) && !IsNumber( token ) )
753 Expecting( "footprint ID" );
754
755 footprintFilters.Add( From_UTF8( CurText() ) );
756 NeedRIGHT();
757 }
758 break;
759
760 case T_aliases:
761 while( (token = NextTok() ) != T_RIGHT )
762 {
763 if( token == T_LEFT )
764 token = NextTok();
765
766 if( token != T_alias )
767 Expecting( T_alias );
768
769 NeedSYMBOLorNUMBER();
770 aliases.Add( From_UTF8( CurText() ) );
771 NeedRIGHT();
772 }
773 break;
774
775 case T_pins:
776 while( (token = NextTok() ) != T_RIGHT )
777 {
778 if( token == T_LEFT )
779 token = NextTok();
780
781 if( token != T_pin )
782 Expecting( T_pin );
783
784 pinCount++;
785
786 skipCurrent();
787 }
788 break;
789
790 default:
791 // Skip not used data (i.e all other tokens)
792 skipCurrent();
793 break;
794 }
795 }
796
797 // Find all of the components that reference this component library part definition.
798 for( unsigned i = 0; i < m_netlist->GetCount(); i++ )
799 {
800 component = m_netlist->GetComponent( i );
801
802 if( component->IsLibSource( libName, libPartName ) )
803 {
804 component->SetFootprintFilters( footprintFilters );
805 component->SetPinCount( pinCount );
806 }
807
808 for( unsigned jj = 0; jj < aliases.GetCount(); jj++ )
809 {
810 if( component->IsLibSource( libName, aliases[jj] ) )
811 {
812 component->SetFootprintFilters( footprintFilters );
813 component->SetPinCount( pinCount );
814 }
815 }
816
817 }
818}
const char * name
Definition: DXF_plotter.cpp:62
bool Load(NETLIST *aNetlist)
Read the *.cmp file format contains the component footprint assignments created by CvPcb into aNetlis...
Store all of the related footprint information found in a netlist.
Definition: pcb_netlist.h:100
void SetFields(nlohmann::ordered_map< wxString, wxString > &aFields)
Definition: pcb_netlist.h:148
void SetLibrary(const wxString &aLibrary)
Definition: pcb_netlist.h:139
void SetProperties(std::map< wxString, wxString > &aProps)
Definition: pcb_netlist.h:154
void SortPins()
Definition: pcb_netlist.h:134
void SetPinCount(int aPinCount)
Definition: pcb_netlist.h:173
bool IsLibSource(const wxString &aLibrary, const wxString &aName) const
Definition: pcb_netlist.h:183
void SetFootprintFilters(const wxArrayString &aFilters)
Definition: pcb_netlist.h:170
void SetHumanReadablePath(const wxString &aPath)
Definition: pcb_netlist.h:190
void SetDuplicatePadNumbersAreJumpers(bool aEnabled)
Definition: pcb_netlist.h:201
void SetComponentClassNames(const std::unordered_set< wxString > &aClassNames)
Definition: pcb_netlist.h:193
std::vector< std::set< wxString > > & JumperPadGroups()
Definition: pcb_netlist.h:203
void SetName(const wxString &aName)
Definition: pcb_netlist.h:136
The parser for reading the KiCad s-expression netlist format.
KICAD_NETLIST_PARSER(LINE_READER *aReader, NETLIST *aNetlist)
void parseComponent()
Parse a component description: (comp (ref P1) (value DB25FEMELLE) (footprint DB25FC) (libsource (lib ...
void Parse()
Function Parse parse the full netlist.
void parseNet()
Parse a net section (net (code 20) (name /PC-A0) (node (ref BUS1) (pin 62)) (node (ref U3) (pin 3)) (...
NETLIST * m_netlist
The netlist to parse into. Not owned.
void parseLibPartList()
Read the section "libparts" in the netlist: (libparts (libpart (lib device) (part C) (description "Co...
void parseGroup()
Parse a group section (group (name "GroupName") (member (uuid "..."))))
LINE_READER * m_lineReader
The line reader used to parse the netlist. Not owned.
void skipCurrent()
Skip the current token level, i.e search for the RIGHT parenthesis which closes the current descripti...
virtual void LoadNetlist() override
Load the contents of the netlist file into aNetlist.
Definition: kiid.h:49
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:49
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition: lib_id.cpp:52
An abstract class from which implementation specific LINE_READERs may be derived to read single lines...
Definition: richio.h:93
NETLIST * m_netlist
The net list to read the file(s) into.
LINE_READER * m_lineReader
The line reader of the netlist.
CMP_READER * m_footprintReader
The reader used to load the footprint links. If NULL, footprint links are not read.
Store information read from a netlist along with the flags used to update the NETLIST in the BOARD.
Definition: pcb_netlist.h:274
unsigned GetCount() const
Definition: pcb_netlist.h:295
void AddGroup(NETLIST_GROUP *aGroup)
void AddComponent(COMPONENT *aComponent)
Add aComponent to the NETLIST.
COMPONENT * GetComponentByReference(const wxString &aReference)
Return a COMPONENT by aReference.
COMPONENT * GetComponent(unsigned aIndex)
Return the COMPONENT at aIndex.
Definition: pcb_netlist.h:303
void ApplyGroupMembership()
After groups and components are parsed, apply the group memberships to the internal components based ...
#define _(s)
#define THROW_IO_ERROR(msg)
Definition: ki_exception.h:39
static bool IsNumber(char x)
wxString From_UTF8(const char *cstring)