/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2020 CERN * * @author Wayne Stambaugh * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ /** * @file sch_sexpr_parser.cpp * @brief Schematic and symbol library s-expression file format parser implementations. */ // For some reason wxWidgets is built with wxUSE_BASE64 unset so expose the wxWidgets // base64 code. #define wxUSE_BASE64 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // CMP_ORIENT_XXX #include #include #include #include #include #include #include using namespace TSCHEMATIC_T; SCH_SEXPR_PARSER::SCH_SEXPR_PARSER( LINE_READER* aLineReader ) : SCHEMATIC_LEXER( aLineReader ), m_requiredVersion( 0 ), m_fieldId( 0 ), m_unit( 1 ), m_convert( 1 ) { } bool SCH_SEXPR_PARSER::parseBool() { T token = NextTok(); if( token == T_yes ) return true; else if( token == T_no ) return false; else Expecting( "yes or no" ); return false; } bool SCH_SEXPR_PARSER::IsTooRecent() const { return m_requiredVersion && m_requiredVersion > SEXPR_SYMBOL_LIB_FILE_VERSION; } void SCH_SEXPR_PARSER::ParseLib( LIB_PART_MAP& aSymbolLibMap ) { T token; NeedLEFT(); NextTok(); parseHeader( T_kicad_symbol_lib, SEXPR_SYMBOL_LIB_FILE_VERSION ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); if( token == T_symbol ) { m_unit = 1; m_convert = 1; LIB_PART* symbol = ParseSymbol( aSymbolLibMap, m_requiredVersion ); aSymbolLibMap[symbol->GetName()] = symbol; } else { Expecting( "symbol" ); } } } LIB_PART* SCH_SEXPR_PARSER::ParseSymbol( LIB_PART_MAP& aSymbolLibMap, int aFileVersion ) { wxCHECK_MSG( CurTok() == T_symbol, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a symbol." ) ); T token; long tmp; wxString name; wxString error; LIB_ITEM* item; std::unique_ptr symbol( new LIB_PART( wxEmptyString ) ); m_requiredVersion = aFileVersion; symbol->SetUnitCount( 1 ); m_fieldId = MANDATORY_FIELDS; token = NextTok(); if( !IsSymbol( token ) ) { error.Printf( _( "Invalid symbol name in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } name = FromUTF8(); LIB_ID id; if( id.Parse( name, LIB_ID::ID_SCH ) >= 0 ) { error.Printf( _( "Invalid library identifier in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } m_symbolName = id.GetLibItemName().wx_str(); symbol->SetName( m_symbolName ); symbol->SetLibId( id ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_power: symbol->SetPower(); NeedRIGHT(); break; case T_pin_names: parsePinNames( symbol ); break; case T_pin_numbers: token = NextTok(); if( token != T_hide ) Expecting( "hide" ); symbol->SetShowPinNumbers( false ); NeedRIGHT(); break; case T_property: parseProperty( symbol ); break; case T_extends: { token = NextTok(); if( !IsSymbol( token ) ) { error.Printf( _( "Invalid symbol extends name in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } name = FromUTF8(); auto it = aSymbolLibMap.find( name ); if( it == aSymbolLibMap.end() ) { error.Printf( _( "No parent for extended symbol %s in\nfile: \"%s\"\nline: %d\noffset: %d" ), name.c_str(), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } symbol->SetParent( it->second ); NeedRIGHT(); break; } case T_symbol: { token = NextTok(); if( !IsSymbol( token ) ) { error.Printf( _( "Invalid symbol unit name in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } name = FromUTF8(); if( !name.StartsWith( m_symbolName ) ) { error.Printf( _( "Invalid symbol unit name prefix %s in\nfile: \"%s\"\n" "line: %d\noffset: %d" ), name.c_str(), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } name = name.Right( name.Length() - m_symbolName.Length() - 1 ); wxStringTokenizer tokenizer( name, "_" ); if( tokenizer.CountTokens() != 2 ) { error.Printf( _( "Invalid symbol unit name suffix %s in\nfile: \"%s\"\n" "line: %d\noffset: %d" ), name.c_str(), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } if( !tokenizer.GetNextToken().ToLong( &tmp ) ) { error.Printf( _( "Invalid symbol unit number %s in\nfile: \"%s\"\nline: %d\noffset: %d" ), name.c_str(), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } m_unit = static_cast( tmp ); if( !tokenizer.GetNextToken().ToLong( &tmp ) ) { error.Printf( _( "Invalid symbol convert number %s in\nfile: \"%s\"\nline: %d\noffset: %d" ), name.c_str(), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } m_convert = static_cast( tmp ); if( m_convert > 1 ) symbol->SetConversion( true, false ); if( m_unit > symbol->GetUnitCount() ) symbol->SetUnitCount( m_unit, false ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_arc: case T_bezier: case T_circle: case T_pin: case T_polyline: case T_rectangle: case T_text: item = ParseDrawItem(); wxCHECK_MSG( item, nullptr, "Invalid draw item pointer." ); item->SetParent( symbol.get() ); symbol->AddDrawItem( item ); break; default: Expecting( "arc, bezier, circle, pin, polyline, rectangle, or text" ); }; } m_unit = 1; m_convert = 1; break; } case T_arc: case T_bezier: case T_circle: case T_pin: case T_polyline: case T_rectangle: case T_text: item = ParseDrawItem(); wxCHECK_MSG( item, nullptr, "Invalid draw item pointer." ); item->SetParent( symbol.get() ); symbol->AddDrawItem( item ); break; default: Expecting( "pin_names, pin_numbers, arc, bezier, circle, pin, polyline, " "rectangle, or text" ); } } m_symbolName.clear(); return symbol.release(); } LIB_ITEM* SCH_SEXPR_PARSER::ParseDrawItem() { switch( CurTok() ) { case T_arc: return static_cast( parseArc() ); break; case T_bezier: return static_cast( parseBezier() ); break; case T_circle: return static_cast( parseCircle() ); break; case T_pin: return static_cast( parsePin() ); break; case T_polyline: return static_cast( parsePolyLine() ); break; case T_rectangle: return static_cast( parseRectangle() ); break; case T_text: return static_cast( parseText() ); break; default: Expecting( "arc, bezier, circle, pin, polyline, rectangle, or text" ); } return nullptr; } double SCH_SEXPR_PARSER::parseDouble() { char* tmp; errno = 0; double fval = strtod( CurText(), &tmp ); if( errno ) { wxString error; error.Printf( _( "Invalid floating point number in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } if( CurText() == tmp ) { wxString error; error.Printf( _( "Missing floating point number in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } return fval; } void SCH_SEXPR_PARSER::parseStroke( STROKE_PARAMS& aStroke ) { wxCHECK_RET( CurTok() == T_stroke, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a stroke." ) ); aStroke.SetWidth( Mils2iu( DEFAULT_LINE_THICKNESS ) ); aStroke.SetType( PLOT_DASH_TYPE::DEFAULT ); aStroke.SetColor( COLOR4D::UNSPECIFIED ); T token; for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_width: aStroke.SetWidth( parseInternalUnits( "stroke width" ) ); NeedRIGHT(); break; case T_type: { token = NextTok(); switch( token ) { case T_dash: aStroke.SetType( PLOT_DASH_TYPE::DASH ); break; case T_dot: aStroke.SetType( PLOT_DASH_TYPE::DOT ); break; case T_dash_dot: aStroke.SetType( PLOT_DASH_TYPE::DASHDOT ); break; case T_solid: aStroke.SetType( PLOT_DASH_TYPE::SOLID ); break; default: Expecting( "solid, dash, dash_dot, or dot" ); } NeedRIGHT(); break; } case T_color: { COLOR4D color; color.r = parseInt( "red" ) / 255.0; color.g = parseInt( "green" ) / 255.0; color.b = parseInt( "blue" ) / 255.0; color.a = Clamp( parseDouble( "alpha" ), 0.0, 1.0 ); aStroke.SetColor( color ); NeedRIGHT(); break; } default: Expecting( "width, type, or color" ); } } } void SCH_SEXPR_PARSER::parseFill( FILL_PARAMS& aFill ) { wxCHECK_RET( CurTok() == T_fill, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as fill." ) ); aFill.m_FillType = NO_FILL; aFill.m_Color = COLOR4D::UNSPECIFIED; T token; for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_type: { token = NextTok(); switch( token ) { case T_none: aFill.m_FillType = NO_FILL; break; case T_outline: aFill.m_FillType = FILLED_SHAPE; break; case T_background: aFill.m_FillType = FILLED_WITH_BG_BODYCOLOR; break; default: Expecting( "none, outline, or background" ); } NeedRIGHT(); break; } case T_color: { COLOR4D color; color.r = parseInt( "red" ) / 255.0; color.g = parseInt( "green" ) / 255.0; color.b = parseInt( "blue" ) / 255.0; color.a = Clamp( parseDouble( "alpha" ), 0.0, 1.0 ); aFill.m_Color = color; NeedRIGHT(); break; } default: Expecting( "type or color" ); } } } void SCH_SEXPR_PARSER::parseEDA_TEXT( EDA_TEXT* aText ) { wxCHECK_RET( aText && CurTok() == T_effects, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as EDA_TEXT." ) ); T token; for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token == T_LEFT ) token = NextTok(); switch( token ) { case T_font: for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token == T_LEFT ) token = NextTok(); switch( token ) { case T_size: { wxSize sz; sz.SetHeight( parseInternalUnits( "text height" ) ); sz.SetWidth( parseInternalUnits( "text width" ) ); aText->SetTextSize( sz ); NeedRIGHT(); break; } case T_thickness: aText->SetTextThickness( parseInternalUnits( "text thickness" ) ); NeedRIGHT(); break; case T_bold: aText->SetBold( true ); break; case T_italic: aText->SetItalic( true ); break; default: Expecting( "size, bold, or italic" ); } } break; case T_justify: for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { switch( token ) { case T_left: aText->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT ); break; case T_right: aText->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); break; case T_top: aText->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); break; case T_bottom: aText->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); break; case T_mirror: aText->SetMirrored( true ); break; default: Expecting( "left, right, top, bottom, or mirror" ); } } break; case T_hide: aText->SetVisible( false ); break; default: Expecting( "font, justify, or hide" ); } } } void SCH_SEXPR_PARSER::parseHeader( TSCHEMATIC_T::T aHeaderType, int aFileVersion ) { wxCHECK_RET( CurTok() == aHeaderType, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a header." ) ); NeedLEFT(); T tok = NextTok(); if( tok == T_version ) { m_requiredVersion = parseInt( FromUTF8().mb_str( wxConvUTF8 ) ); NeedRIGHT(); // Skip the host name and host build version information. NeedLEFT(); NeedSYMBOL(); NeedSYMBOL(); if( m_requiredVersion < 20200827 ) NeedSYMBOL(); NeedRIGHT(); } else { m_requiredVersion = aFileVersion; // Skip the host name and host build version information. NeedSYMBOL(); NeedSYMBOL(); NeedRIGHT(); } } void SCH_SEXPR_PARSER::parsePinNames( std::unique_ptr& aSymbol ) { wxCHECK_RET( CurTok() == T_pin_names, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a pin_name token." ) ); wxString error; T token = NextTok(); if( token == T_LEFT ) { token = NextTok(); if( token != T_offset ) Expecting( "offset" ); aSymbol->SetPinNameOffset( parseInternalUnits( "pin name offset" ) ); NeedRIGHT(); token = NextTok(); // Either ) or hide } if( token == T_hide ) { aSymbol->SetShowPinNames( false ); NeedRIGHT(); } else if( token != T_RIGHT ) { error.Printf( _( "Invalid symbol names definition in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } } void SCH_SEXPR_PARSER::parseProperty( std::unique_ptr& aSymbol ) { wxCHECK_RET( CurTok() == T_property, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a property token." ) ); wxCHECK( aSymbol, /* void */ ); wxString error; wxString name; wxString value; std::unique_ptr field( new LIB_FIELD( aSymbol.get(), MANDATORY_FIELDS ) ); T token = NextTok(); if( !IsSymbol( token ) ) { error.Printf( _( "Invalid property name in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } name = FromUTF8(); if( name.IsEmpty() ) { error.Printf( _( "Empty property name in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } field->SetName( name ); token = NextTok(); if( !IsSymbol( token ) ) { error.Printf( _( "Invalid property value in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } // Empty property values are valid. value = FromUTF8(); field->SetText( value ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_id: field->SetId( parseInt( "field ID" ) ); NeedRIGHT(); break; case T_at: field->SetPosition( parseXY() ); field->SetTextAngle( static_cast( parseDouble( "text angle" ) * 10.0 ) ); NeedRIGHT(); break; case T_effects: parseEDA_TEXT( static_cast( field.get() ) ); break; default: Expecting( "id, at or effects" ); } } LIB_FIELD* existingField; if( field->GetId() < MANDATORY_FIELDS ) { existingField = aSymbol->GetField( field->GetId() ); *existingField = *field; } else if( name == "ki_keywords" ) { // Not a LIB_FIELD object yet. aSymbol->SetKeyWords( value ); } else if( name == "ki_description" ) { // Not a LIB_FIELD object yet. aSymbol->SetDescription( value ); } else if( name == "ki_fp_filters" ) { // Not a LIB_FIELD object yet. wxArrayString filters; wxStringTokenizer tokenizer( value ); while( tokenizer.HasMoreTokens() ) filters.Add( tokenizer.GetNextToken() ); aSymbol->SetFootprintFilters( filters ); } else if( name == "ki_locked" ) { // This is a temporary LIB_FIELD object until interchangeable units are determined on // the fly. aSymbol->LockUnits( true ); } else { existingField = aSymbol->GetField( field->GetId() ); if( !existingField ) { aSymbol->AddDrawItem( field.release() ); } else { *existingField = *field; } } } LIB_ARC* SCH_SEXPR_PARSER::parseArc() { wxCHECK_MSG( CurTok() == T_arc, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as an arc token." ) ); T token; wxPoint startPoint; wxPoint midPoint; wxPoint endPoint; wxPoint pos; FILL_PARAMS fill; bool hasMidPoint = false; std::unique_ptr arc( new LIB_ARC( nullptr ) ); arc->SetUnit( m_unit ); arc->SetConvert( m_convert ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_start: startPoint = parseXY(); NeedRIGHT(); break; case T_mid: midPoint = parseXY(); NeedRIGHT(); hasMidPoint = true; break; case T_end: endPoint = parseXY(); NeedRIGHT(); break; case T_radius: for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_at: pos = parseXY(); NeedRIGHT(); break; case T_length: arc->SetRadius( parseInternalUnits( "radius length" ) ); NeedRIGHT(); break; case T_angles: { int angle1 = KiROUND( parseDouble( "start radius angle" ) * 10.0 ); int angle2 = KiROUND( parseDouble( "end radius angle" ) * 10.0 ); NORMALIZE_ANGLE_POS( angle1 ); NORMALIZE_ANGLE_POS( angle2 ); arc->SetFirstRadiusAngle( angle1 ); arc->SetSecondRadiusAngle( angle2 ); NeedRIGHT(); break; } default: Expecting( "at, length, or angle" ); } } break; case T_stroke: NeedLEFT(); token = NextTok(); if( token != T_width ) Expecting( "width" ); arc->SetWidth( parseInternalUnits( "stroke width" ) ); NeedRIGHT(); // Closes width token; NeedRIGHT(); // Closes stroke token; break; case T_fill: parseFill( fill ); arc->SetFillMode( fill.m_FillType ); break; default: Expecting( "start, end, radius, stroke, or fill" ); } } arc->SetPosition( pos ); arc->SetStart( startPoint ); arc->SetEnd( endPoint ); if( hasMidPoint ) { VECTOR2I center = GetArcCenter( arc->GetStart(), midPoint, arc->GetEnd() ); arc->SetPosition( wxPoint( center.x, center.y ) ); // @todo Calculate the radius. arc->CalcRadiusAngles(); } return arc.release(); } LIB_BEZIER* SCH_SEXPR_PARSER::parseBezier() { wxCHECK_MSG( CurTok() == T_bezier, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a bezier." ) ); T token; FILL_PARAMS fill; std::unique_ptr bezier( new LIB_BEZIER( nullptr ) ); bezier->SetUnit( m_unit ); bezier->SetConvert( m_convert ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_pts: for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); if( token != T_xy ) Expecting( "xy" ); bezier->AddPoint( parseXY() ); NeedRIGHT(); } break; case T_stroke: NeedLEFT(); token = NextTok(); if( token != T_width ) Expecting( "width" ); bezier->SetWidth( parseInternalUnits( "stroke width" ) ); NeedRIGHT(); // Closes width token; NeedRIGHT(); // Closes stroke token; break; case T_fill: parseFill( fill ); bezier->SetFillMode( fill.m_FillType ); break; default: Expecting( "pts, stroke, or fill" ); } } return bezier.release(); } LIB_CIRCLE* SCH_SEXPR_PARSER::parseCircle() { wxCHECK_MSG( CurTok() == T_circle, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a circle token." ) ); T token; FILL_PARAMS fill; std::unique_ptr circle( new LIB_CIRCLE( nullptr ) ); circle->SetUnit( m_unit ); circle->SetConvert( m_convert ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_center: circle->SetPosition( parseXY() ); NeedRIGHT(); break; case T_radius: circle->SetRadius( parseInternalUnits( "radius length" ) ); NeedRIGHT(); break; case T_stroke: NeedLEFT(); token = NextTok(); if( token != T_width ) Expecting( "width" ); circle->SetWidth( parseInternalUnits( "stroke width" ) ); NeedRIGHT(); // Closes width token; NeedRIGHT(); // Closes stroke token; break; case T_fill: parseFill( fill ); circle->SetFillMode( fill.m_FillType ); break; default: Expecting( "start, end, radius, stroke, or fill" ); } } return circle.release(); } LIB_PIN* SCH_SEXPR_PARSER::parsePin() { auto parseType = [&]( T token ) -> ELECTRICAL_PINTYPE { switch( token ) { case T_input: return ELECTRICAL_PINTYPE::PT_INPUT; case T_output: return ELECTRICAL_PINTYPE::PT_OUTPUT; case T_bidirectional: return ELECTRICAL_PINTYPE::PT_BIDI; case T_tri_state: return ELECTRICAL_PINTYPE::PT_TRISTATE; case T_passive: return ELECTRICAL_PINTYPE::PT_PASSIVE; case T_unspecified: return ELECTRICAL_PINTYPE::PT_UNSPECIFIED; case T_power_in: return ELECTRICAL_PINTYPE::PT_POWER_IN; case T_power_out: return ELECTRICAL_PINTYPE::PT_POWER_OUT; case T_open_collector: return ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR; case T_open_emitter: return ELECTRICAL_PINTYPE::PT_OPENEMITTER; case T_unconnected: return ELECTRICAL_PINTYPE::PT_NC; default: Expecting( "input, output, bidirectional, tri_state, passive, " "unspecified, power_in, power_out, open_collector, " "open_emitter, or unconnected" ); return ELECTRICAL_PINTYPE::PT_UNSPECIFIED; } }; auto parseShape = [&]( T token ) -> GRAPHIC_PINSHAPE { switch( token ) { case T_line: return GRAPHIC_PINSHAPE::LINE; case T_inverted: return GRAPHIC_PINSHAPE::INVERTED; case T_clock: return GRAPHIC_PINSHAPE::CLOCK; case T_inverted_clock: return GRAPHIC_PINSHAPE::INVERTED_CLOCK; case T_input_low: return GRAPHIC_PINSHAPE::INPUT_LOW; case T_clock_low: return GRAPHIC_PINSHAPE::CLOCK_LOW; case T_output_low: return GRAPHIC_PINSHAPE::OUTPUT_LOW; case T_edge_clock_high: return GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK; case T_non_logic: return GRAPHIC_PINSHAPE::NONLOGIC; default: Expecting( "line, inverted, clock, inverted_clock, input_low, " "clock_low, output_low, edge_clock_high, non_logic" ); return GRAPHIC_PINSHAPE::LINE; } }; wxCHECK_MSG( CurTok() == T_pin, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a pin token." ) ); T token; wxString tmp; wxString error; std::unique_ptr pin( new LIB_PIN( nullptr ) ); pin->SetUnit( m_unit ); pin->SetConvert( m_convert ); // Pin electrical type. token = NextTok(); pin->SetType( parseType( token ) ); // Pin shape. token = NextTok(); pin->SetShape( parseShape( token ) ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token == T_hide ) { pin->SetVisible( false ); continue; } if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_at: pin->SetPosition( parseXY() ); switch( parseInt( "pin orientation" ) ) { case 0: pin->SetOrientation( PIN_RIGHT ); break; case 90: pin->SetOrientation( PIN_UP ); break; case 180: pin->SetOrientation( PIN_LEFT ); break; case 270: pin->SetOrientation( PIN_DOWN ); break; default: Expecting( "0, 90, 180, or 270" ); } NeedRIGHT(); break; case T_length: pin->SetLength( parseInternalUnits( "pin length" ) ); NeedRIGHT(); break; case T_name: token = NextTok(); if( !IsSymbol( token ) ) { error.Printf( _( "Invalid pin name in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } pin->SetName( FromUTF8() ); token = NextTok(); if( token != T_RIGHT ) { token = NextTok(); if( token == T_effects ) { // The EDA_TEXT font effects formatting is used so use and EDA_TEXT object // so duplicate parsing is not required. EDA_TEXT text; parseEDA_TEXT( &text ); pin->SetNameTextSize( text.GetTextHeight() ); NeedRIGHT(); } else { Expecting( "effects" ); } } break; case T_number: token = NextTok(); if( !IsSymbol( token ) ) { error.Printf( _( "Invalid pin number in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } pin->SetNumber( FromUTF8() ); token = NextTok(); if( token != T_RIGHT ) { token = NextTok(); if( token == T_effects ) { // The EDA_TEXT font effects formatting is used so use and EDA_TEXT object // so duplicate parsing is not required. EDA_TEXT text; parseEDA_TEXT( &text ); pin->SetNumberTextSize( text.GetTextHeight() ); NeedRIGHT(); } else { Expecting( "effects" ); } } break; case T_alternate: { LIB_PIN::ALT alt; token = NextTok(); if( !IsSymbol( token ) ) { error.Printf( _( "Invalid alternate pin name in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } alt.m_Name = FromUTF8(); token = NextTok(); alt.m_Type = parseType( token ); token = NextTok(); alt.m_Shape = parseShape( token ); pin->GetAlternates()[ alt.m_Name ] = alt; NeedRIGHT(); } break; default: Expecting( "at, name, number, length, or alternate" ); } } return pin.release(); } LIB_POLYLINE* SCH_SEXPR_PARSER::parsePolyLine() { wxCHECK_MSG( CurTok() == T_polyline, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a polyline." ) ); T token; FILL_PARAMS fill; std::unique_ptr polyLine( new LIB_POLYLINE( nullptr ) ); polyLine->SetUnit( m_unit ); polyLine->SetConvert( m_convert ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_pts: for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); if( token != T_xy ) Expecting( "xy" ); polyLine->AddPoint( parseXY() ); NeedRIGHT(); } break; case T_stroke: NeedLEFT(); token = NextTok(); if( token != T_width ) Expecting( "width" ); polyLine->SetWidth( parseInternalUnits( "stroke width" ) ); NeedRIGHT(); // Closes width token; NeedRIGHT(); // Closes stroke token; break; case T_fill: parseFill( fill ); polyLine->SetFillMode( fill.m_FillType ); break; default: Expecting( "pts, stroke, or fill" ); } } return polyLine.release(); } LIB_RECTANGLE* SCH_SEXPR_PARSER::parseRectangle() { wxCHECK_MSG( CurTok() == T_rectangle, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a rectangle token." ) ); T token; FILL_PARAMS fill; std::unique_ptr rectangle( new LIB_RECTANGLE( nullptr ) ); rectangle->SetUnit( m_unit ); rectangle->SetConvert( m_convert ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_start: rectangle->SetPosition( parseXY() ); NeedRIGHT(); break; case T_end: rectangle->SetEnd( parseXY() ); NeedRIGHT(); break; case T_stroke: NeedLEFT(); token = NextTok(); if( token != T_width ) Expecting( "width" ); rectangle->SetWidth( parseInternalUnits( "stroke width" ) ); NeedRIGHT(); // Closes width token; NeedRIGHT(); // Closes stroke token; break; case T_fill: parseFill( fill ); rectangle->SetFillMode( fill.m_FillType ); break; default: Expecting( "start, end, stroke, or fill" ); } } return rectangle.release(); } LIB_TEXT* SCH_SEXPR_PARSER::parseText() { wxCHECK_MSG( CurTok() == T_text, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a text token." ) ); T token; wxString tmp; wxString error; std::unique_ptr text( new LIB_TEXT( nullptr ) ); text->SetUnit( m_unit ); text->SetConvert( m_convert ); token = NextTok(); if( !IsSymbol( token ) ) { error.Printf( _( "Invalid text string in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } text->SetText( FromUTF8() ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_at: text->SetPosition( parseXY() ); text->SetTextAngle( parseDouble( "text angle" ) ); NeedRIGHT(); break; case T_effects: parseEDA_TEXT( static_cast( text.get() ) ); break; default: Expecting( "at or effects" ); } } return text.release(); } void SCH_SEXPR_PARSER::parsePAGE_INFO( PAGE_INFO& aPageInfo ) { wxCHECK_RET( ( CurTok() == T_page && m_requiredVersion <= 20200506 ) || CurTok() == T_paper, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a PAGE_INFO." ) ); T token; NeedSYMBOL(); wxString pageType = FromUTF8(); if( !aPageInfo.SetType( pageType ) ) { wxString err; err.Printf( _( "Page type \"%s\" is not valid " ), GetChars( FromUTF8() ) ); THROW_PARSE_ERROR( err, CurSource(), CurLine(), CurLineNumber(), CurOffset() ); } if( pageType == PAGE_INFO::Custom ) { double width = parseDouble( "width" ); // width in mm // Perform some controls to avoid crashes if the size is edited by hands if( width < 100.0 ) width = 100.0; else if( width > 1200.0 ) width = 1200.0; double height = parseDouble( "height" ); // height in mm if( height < 100.0 ) height = 100.0; else if( height > 1200.0 ) height = 1200.0; aPageInfo.SetWidthMils( Mm2mils( width ) ); aPageInfo.SetHeightMils( Mm2mils( height ) ); } token = NextTok(); if( token == T_portrait ) { aPageInfo.SetPortrait( true ); NeedRIGHT(); } else if( token != T_RIGHT ) { Expecting( "portrait" ); } } void SCH_SEXPR_PARSER::parseTITLE_BLOCK( TITLE_BLOCK& aTitleBlock ) { wxCHECK_RET( CurTok() == T_title_block, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as TITLE_BLOCK." ) ); T token; for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_title: NextTok(); aTitleBlock.SetTitle( FromUTF8() ); break; case T_date: NextTok(); aTitleBlock.SetDate( FromUTF8() ); break; case T_rev: NextTok(); aTitleBlock.SetRevision( FromUTF8() ); break; case T_company: NextTok(); aTitleBlock.SetCompany( FromUTF8() ); break; case T_comment: { int commentNumber = parseInt( "comment" ); switch( commentNumber ) { case 1: NextTok(); aTitleBlock.SetComment( 0, FromUTF8() ); break; case 2: NextTok(); aTitleBlock.SetComment( 1, FromUTF8() ); break; case 3: NextTok(); aTitleBlock.SetComment( 2, FromUTF8() ); break; case 4: NextTok(); aTitleBlock.SetComment( 3, FromUTF8() ); break; case 5: NextTok(); aTitleBlock.SetComment( 4, FromUTF8() ); break; case 6: NextTok(); aTitleBlock.SetComment( 5, FromUTF8() ); break; case 7: NextTok(); aTitleBlock.SetComment( 6, FromUTF8() ); break; case 8: NextTok(); aTitleBlock.SetComment( 7, FromUTF8() ); break; case 9: NextTok(); aTitleBlock.SetComment( 8, FromUTF8() ); break; default: wxString err; err.Printf( wxT( "%d is not a valid title block comment number" ), commentNumber ); THROW_PARSE_ERROR( err, CurSource(), CurLine(), CurLineNumber(), CurOffset() ); } break; } default: Expecting( "title, date, rev, company, or comment" ); } NeedRIGHT(); } } SCH_FIELD* SCH_SEXPR_PARSER::parseSchField( SCH_ITEM* aParent ) { wxCHECK_MSG( CurTok() == T_property, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a property token." ) ); wxString error; wxString name; wxString value; T token = NextTok(); if( !IsSymbol( token ) ) { error.Printf( _( "Invalid property name in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } name = FromUTF8(); if( name.IsEmpty() ) { error.Printf( _( "Empty property name in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } token = NextTok(); if( !IsSymbol( token ) ) { error.Printf( _( "Invalid property value in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } // Empty property values are valid. value = FromUTF8(); std::unique_ptr field( new SCH_FIELD( wxDefaultPosition, -1, aParent, name ) ); field->SetText( value ); field->SetVisible( true ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_id: field->SetId( parseInt( "field ID" ) ); NeedRIGHT(); break; case T_at: field->SetPosition( parseXY() ); field->SetTextAngle( static_cast( parseDouble( "text angle" ) * 10.0 ) ); NeedRIGHT(); break; case T_effects: parseEDA_TEXT( static_cast( field.get() ) ); break; default: Expecting( "at or effects" ); } } return field.release(); } SCH_SHEET_PIN* SCH_SEXPR_PARSER::parseSchSheetPin( SCH_SHEET* aSheet ) { wxCHECK_MSG( aSheet != nullptr, nullptr, "" ); wxCHECK_MSG( CurTok() == T_pin, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a sheet pin token." ) ); wxString error; wxString name; wxString shape; T token = NextTok(); if( !IsSymbol( token ) ) { error.Printf( _( "Invalid sheet pin name in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } name = FromUTF8(); if( name.IsEmpty() ) { error.Printf( _( "Empty sheet pin name in\nfile: \"%s\"\nline: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } std::unique_ptr sheetPin( new SCH_SHEET_PIN( aSheet, wxPoint( 0, 0 ), name ) ); token = NextTok(); switch( token ) { case T_input: sheetPin->SetShape( PINSHEETLABEL_SHAPE::PS_INPUT ); break; case T_output: sheetPin->SetShape( PINSHEETLABEL_SHAPE::PS_OUTPUT ); break; case T_bidirectional: sheetPin->SetShape( PINSHEETLABEL_SHAPE::PS_BIDI ); break; case T_tri_state: sheetPin->SetShape( PINSHEETLABEL_SHAPE::PS_TRISTATE ); break; case T_passive: sheetPin->SetShape( PINSHEETLABEL_SHAPE::PS_UNSPECIFIED ); break; default: Expecting( "input, output, bidirectional, tri_state, or passive" ); } for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_at: { sheetPin->SetPosition( parseXY() ); double angle = parseDouble( "sheet pin angle (side)" ); if( angle == 0.0 ) sheetPin->SetEdge( SHEET_RIGHT_SIDE ); else if( angle == 90.0 ) sheetPin->SetEdge( SHEET_TOP_SIDE ); else if( angle == 180.0 ) sheetPin->SetEdge( SHEET_LEFT_SIDE ); else if( angle == 270.0 ) sheetPin->SetEdge( SHEET_BOTTOM_SIDE ); else Expecting( "0, 90, 180, or 270" ); NeedRIGHT(); break; } case T_effects: parseEDA_TEXT( static_cast( sheetPin.get() ) ); break; default: Expecting( "at or effects" ); } } return sheetPin.release(); } void SCH_SEXPR_PARSER::parseSchSymbolInstances( SCH_SCREEN* aScreen ) { wxCHECK_RET( CurTok() == T_symbol_instances, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a instances token." ) ); wxCHECK( aScreen, /* void */ ); T token; for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_path: { NeedSYMBOL(); COMPONENT_INSTANCE_REFERENCE instance; instance.m_Path = KIID_PATH( FromUTF8() ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_reference: NeedSYMBOL(); instance.m_Reference = FromUTF8(); NeedRIGHT(); break; case T_unit: instance.m_Unit = parseInt( "symbol unit" ); NeedRIGHT(); break; default: Expecting( "path or unit" ); } } aScreen->m_symbolInstances.emplace_back( instance ); break; } default: Expecting( "path" ); } } } void SCH_SEXPR_PARSER::ParseSchematic( SCH_SHEET* aSheet, bool aIsCopyableOnly, int aFileVersion ) { wxCHECK( aSheet != nullptr, /* void */ ); SCH_SCREEN* screen = aSheet->GetScreen(); wxCHECK( screen != nullptr, /* void */ ); if( aIsCopyableOnly ) m_requiredVersion = aFileVersion; T token; if( !aIsCopyableOnly ) { NeedLEFT(); NextTok(); if( CurTok() != T_kicad_sch ) Expecting( "kicad_sch" ); parseHeader( T_kicad_sch, SEXPR_SCHEMATIC_FILE_VERSION ); } for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( aIsCopyableOnly && token == T_EOF ) break; if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); if( !aIsCopyableOnly && token == T_page && m_requiredVersion <= 20200506 ) token = T_paper; switch( token ) { case T_paper: { if( aIsCopyableOnly ) Unexpected( T_paper ); PAGE_INFO pageInfo; parsePAGE_INFO( pageInfo ); screen->SetPageSettings( pageInfo ); break; } case T_page: { if( aIsCopyableOnly ) Unexpected( T_page ); // Only saved for top-level sniffing in Kicad Manager frame and other external // tool usage with flat hierarchies NeedSYMBOLorNUMBER(); NeedSYMBOLorNUMBER(); NeedRIGHT(); break; } case T_title_block: { if( aIsCopyableOnly ) Unexpected( T_title_block ); TITLE_BLOCK tb; parseTITLE_BLOCK( tb ); screen->SetTitleBlock( tb ); break; } case T_lib_symbols: { // Dummy map. No derived symbols are allowed in the library cache. LIB_PART_MAP symbolLibMap; for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_symbol: screen->AddLibSymbol( ParseSymbol( symbolLibMap ) ); break; default: Expecting( "symbol" ); } } break; } case T_symbol: screen->Append( static_cast( parseSchematicSymbol() ) ); break; case T_image: screen->Append( static_cast( parseImage() ) ); break; case T_sheet: { SCH_SHEET* sheet = parseSheet(); // Set the parent to aSheet. This effectively creates a method to find // the root sheet from any sheet so a pointer to the root sheet does not // need to be stored globally. Note: this is not the same as a hierarchy. // Complex hierarchies can have multiple copies of a sheet. This only // provides a simple tree to find the root sheet. sheet->SetParent( aSheet ); screen->Append( static_cast( sheet ) ); break; } case T_junction: screen->Append( static_cast( parseJunction() ) ); break; case T_no_connect: screen->Append( static_cast( parseNoConnect() ) ); break; case T_bus_entry: screen->Append( static_cast( parseBusEntry() ) ); break; case T_polyline: case T_bus: case T_wire: screen->Append( static_cast( parseLine() ) ); break; case T_text: case T_label: case T_global_label: case T_hierarchical_label: screen->Append( static_cast( parseSchText() ) ); break; case T_symbol_instances: if( aIsCopyableOnly ) Unexpected( T_symbol_instances ); parseSchSymbolInstances( screen ); break; case T_bus_alias: if( aIsCopyableOnly ) Unexpected( T_bus_alias ); parseBusAlias( screen ); break; default: Expecting( "symbol, paper, page, title_block, bitmap, sheet, junction, no_connect, " "bus_entry, line, bus, text, label, global_label, hierarchical_label, " "symbol_instances, or bus_alias" ); } } screen->UpdateLocalLibSymbolLinks(); } SCH_COMPONENT* SCH_SEXPR_PARSER::parseSchematicSymbol() { wxCHECK_MSG( CurTok() == T_symbol, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a symbol." ) ); T token; wxString tmp; wxString error; wxString libName; SCH_FIELD* field; std::unique_ptr symbol( new SCH_COMPONENT() ); TRANSFORM transform; std::set fieldIDsRead; m_fieldId = MANDATORY_FIELDS; for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_lib_name: { LIB_ID libId; token = NextTok(); if( !IsSymbol( token ) ) { error.Printf( _( "Invalid symbol library name in\nfile: \"%s\"\n" "line: %d\noffset: %d" ), CurSource().c_str(), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } libName = FromUTF8(); NeedRIGHT(); break; } case T_lib_id: { token = NextTok(); if( !IsSymbol( token ) && token != T_NUMBER ) Expecting( "symbol|number" ); LIB_ID libId; if( libId.Parse( FromUTF8(), LIB_ID::ID_SCH ) >= 0 ) { error.Printf( _( "Invalid symbol library ID in\nfile: \"%s\"\nline: %d\n" "offset: %d" ), GetChars( CurSource() ), CurLineNumber(), CurOffset() ); THROW_IO_ERROR( error ); } symbol->SetLibId( libId ); NeedRIGHT(); break; } case T_at: symbol->SetPosition( parseXY() ); switch( static_cast( parseDouble( "symbol orientation" ) ) ) { case 0: transform = TRANSFORM(); break; case 90: transform = TRANSFORM( 0, -1, -1, 0 ); break; case 180: transform = TRANSFORM( -1, 0, 0, 1 ); break; case 270: transform = TRANSFORM( 0, 1, 1, 0 ); break; default: Expecting( "0, 90, 180, or 270" ); } symbol->SetTransform( transform ); NeedRIGHT(); break; case T_mirror: token = NextTok(); if( token == T_x ) symbol->SetOrientation( CMP_MIRROR_X ); else if( token == T_y ) symbol->SetOrientation( CMP_MIRROR_Y ); else Expecting( "x or y" ); NeedRIGHT(); break; case T_unit: symbol->SetUnit( parseInt( "symbol unit" ) ); NeedRIGHT(); break; case T_convert: symbol->SetConvert( parseInt( "symbol convert" ) ); NeedRIGHT(); break; case T_in_bom: symbol->SetIncludeInBom( parseBool() ); NeedRIGHT(); break; case T_on_board: symbol->SetIncludeOnBoard( parseBool() ); NeedRIGHT(); break; case T_uuid: NeedSYMBOL(); const_cast( symbol->m_Uuid ) = KIID( FromUTF8() ); NeedRIGHT(); break; case T_property: { // The field parent symbol must be set and it's orientation must be set before // the field positions are set. field = parseSchField( symbol.get() ); // It would appear that at some point we allowed duplicate ids to slip through // when writing files. The easiest (and most complete) solution is to disallow // multiple instances of the same id (for all files since the source of the error // *might* in fact be hand-edited files). // // While no longer used, -1 is still a valid id for user fields and will // get written out as the next unused number on save. if( fieldIDsRead.count( field->GetId() ) ) field->SetId( -1 ); else fieldIDsRead.insert( field->GetId() ); // Set the default symbol reference prefix. if( field->GetId() == REFERENCE ) { wxString refDesignator = field->GetText(); refDesignator.Replace( "~", " " ); wxString prefix = refDesignator; while( prefix.Length() ) { if( ( prefix.Last() < '0' || prefix.Last() > '9') && prefix.Last() != '?' ) break; prefix.RemoveLast(); } // Avoid a prefix containing trailing/leading spaces prefix.Trim( true ); prefix.Trim( false ); if( prefix.IsEmpty() ) symbol->SetPrefix( wxString( "U" ) ); else symbol->SetPrefix( prefix ); } if( symbol->GetField( field->GetId() ) ) *symbol->GetField( field->GetId() ) = *field; else symbol->AddField( *field ); delete field; break; } case T_pin: { SCH_PIN* pin = new SCH_PIN( nullptr, symbol.get() ); NeedSYMBOL(); pin->SetNumber( FromUTF8() ); token = NextTok(); if( token == T_alternate ) { NeedSYMBOL(); pin->SetAlt( FromUTF8() ); NeedRIGHT(); } else { Expecting( "alternate" ); } symbol->GetPins().push_back( pin ); NeedRIGHT(); } break; default: Expecting( "lib_id, lib_name, at, mirror, uuid, property, pin, or instances" ); } } if( !libName.IsEmpty() && ( symbol->GetLibId().Format().wx_str() != libName ) ) symbol->SetSchSymbolLibraryName( libName ); // Ensure edit/status flags are cleared after these initializations: symbol->ClearFlags(); return symbol.release(); } SCH_BITMAP* SCH_SEXPR_PARSER::parseImage() { wxCHECK_MSG( CurTok() == T_image, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as an image." ) ); T token; std::unique_ptr bitmap( new SCH_BITMAP() ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_at: bitmap->SetPosition( parseXY() ); NeedRIGHT(); break; case T_scale: bitmap->GetImage()->SetScale( parseDouble( "image scale factor" ) ); if( !std::isnormal( bitmap->GetImage()->GetScale() ) ) bitmap->GetImage()->SetScale( 1.0 ); NeedRIGHT(); break; case T_data: { token = NextTok(); wxString data; // Reserve 128K because most image files are going to be larger than the default // 1K that wxString reserves. data.reserve( 1 << 17 ); while( token != T_RIGHT ) { if( !IsSymbol( token ) ) Expecting( "base64 image data" ); data += FromUTF8(); token = NextTok(); } wxMemoryBuffer buffer = wxBase64Decode( data ); wxMemoryOutputStream stream( buffer.GetData(), buffer.GetBufSize() ); wxImage* image = new wxImage(); wxMemoryInputStream istream( stream ); image->LoadFile( istream, wxBITMAP_TYPE_PNG ); bitmap->GetImage()->SetImage( image ); bitmap->GetImage()->SetBitmap( new wxBitmap( *image ) ); break; } default: Expecting( "at, scale, or data" ); } } return bitmap.release(); } SCH_SHEET* SCH_SEXPR_PARSER::parseSheet() { wxCHECK_MSG( CurTok() == T_sheet, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a sheet." ) ); T token; STROKE_PARAMS stroke; FILL_PARAMS fill; SCH_FIELD* field; std::vector fields; std::unique_ptr sheet( new SCH_SHEET() ); std::set fieldIDsRead; for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_at: sheet->SetPosition( parseXY() ); NeedRIGHT(); break; case T_size: { wxSize size; size.SetWidth( parseInternalUnits( "sheet width" ) ); size.SetHeight( parseInternalUnits( "sheet height" ) ); sheet->SetSize( size ); NeedRIGHT(); break; } case T_stroke: parseStroke( stroke ); sheet->SetBorderWidth( stroke.GetWidth() ); sheet->SetBorderColor( stroke.GetColor() ); break; case T_fill: parseFill( fill ); sheet->SetBackgroundColor( fill.m_Color ); break; case T_uuid: NeedSYMBOL(); const_cast( sheet->m_Uuid ) = KIID( FromUTF8() ); NeedRIGHT(); break; case T_property: field = parseSchField( sheet.get() ); if( m_requiredVersion <= 20200310 ) { // Earlier versions had the wrong ids (and names) saved for sheet fields. // Fortunately they only saved the sheetname and sheetfilepath (and always // in that order), so we can hack in a recovery. if( fields.empty() ) field->SetId( SHEETNAME ); else field->SetId( SHEETFILENAME ); } // It would appear the problem persists past 20200310, but this time with the // earlier ids being re-used for later (user) fields. The easiest (and most // complete) solution is to disallow multiple instances of the same id (for all // files since the source of the error *might* in fact be hand-edited files). // // While no longer used, -1 is still a valid id for user fields and will // get written out as the next unused number on save. if( fieldIDsRead.count( field->GetId() ) ) field->SetId( -1 ); else fieldIDsRead.insert( field->GetId() ); fields.emplace_back( *field ); delete field; break; case T_pin: sheet->AddPin( parseSchSheetPin( sheet.get() ) ); break; default: Expecting( "at, size, stroke, background, uuid, property, or pin" ); } } sheet->SetFields( fields ); return sheet.release(); } SCH_JUNCTION* SCH_SEXPR_PARSER::parseJunction() { wxCHECK_MSG( CurTok() == T_junction, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a junction." ) ); T token; std::unique_ptr junction( new SCH_JUNCTION() ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_at: junction->SetPosition( parseXY() ); NeedRIGHT(); break; case T_diameter: junction->SetDiameter( parseInternalUnits( "junction diameter" ) ); NeedRIGHT(); break; case T_color: { COLOR4D color; color.r = parseInt( "red" ) / 255.0; color.g = parseInt( "green" ) / 255.0; color.b = parseInt( "blue" ) / 255.0; color.a = Clamp( parseDouble( "alpha" ), 0.0, 1.0 ); junction->SetColor( color ); NeedRIGHT(); break; } default: Expecting( "at" ); } } return junction.release(); } SCH_NO_CONNECT* SCH_SEXPR_PARSER::parseNoConnect() { wxCHECK_MSG( CurTok() == T_no_connect, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a no connect." ) ); T token; std::unique_ptr no_connect( new SCH_NO_CONNECT() ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_at: no_connect->SetPosition( parseXY() ); NeedRIGHT(); break; default: Expecting( "at" ); } } return no_connect.release(); } SCH_BUS_WIRE_ENTRY* SCH_SEXPR_PARSER::parseBusEntry() { wxCHECK_MSG( CurTok() == T_bus_entry, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a bus entry." ) ); T token; STROKE_PARAMS stroke; std::unique_ptr busEntry( new SCH_BUS_WIRE_ENTRY() ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_at: busEntry->SetPosition( parseXY() ); NeedRIGHT(); break; case T_size: { wxSize size; size.SetWidth( parseInternalUnits( "bus entry height" ) ); size.SetHeight( parseInternalUnits( "bus entry width" ) ); busEntry->SetSize( size ); NeedRIGHT(); break; } case T_stroke: parseStroke( stroke ); busEntry->SetStroke( stroke ); break; default: Expecting( "at, size, or stroke" ); } } return busEntry.release(); } SCH_LINE* SCH_SEXPR_PARSER::parseLine() { T token; STROKE_PARAMS stroke; std::unique_ptr line( new SCH_LINE() ); switch( CurTok() ) { case T_polyline: line->SetLayer( LAYER_NOTES ); break; case T_wire: line->SetLayer( LAYER_WIRE ); break; case T_bus: line->SetLayer( LAYER_BUS ); break; default: wxCHECK_MSG( false, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a line." ) ); } for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_pts: NeedLEFT(); token = NextTok(); if( token != T_xy ) Expecting( "xy" ); line->SetStartPoint( parseXY() ); NeedRIGHT(); NeedLEFT(); token = NextTok(); if( token != T_xy ) Expecting( "xy" ); line->SetEndPoint( parseXY() ); NeedRIGHT(); NeedRIGHT(); break; case T_stroke: parseStroke( stroke ); line->SetStroke( stroke ); break; default: Expecting( "at or stroke" ); } } return line.release(); } SCH_TEXT* SCH_SEXPR_PARSER::parseSchText() { T token; std::unique_ptr text; switch( CurTok() ) { case T_text: text.reset( new SCH_TEXT ); break; case T_label: text.reset( new SCH_LABEL ); break; case T_global_label: text.reset( new SCH_GLOBALLABEL ); break; case T_hierarchical_label: text.reset( new SCH_HIERLABEL ); break; default: wxCHECK_MSG( false, nullptr, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as text." ) ); } NeedSYMBOL(); text->SetText( FromUTF8() ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { if( token != T_LEFT ) Expecting( T_LEFT ); token = NextTok(); switch( token ) { case T_at: { text->SetPosition( parseXY() ); switch( static_cast( parseDouble( "text angle" ) ) ) { case 0: text->SetLabelSpinStyle( LABEL_SPIN_STYLE::RIGHT ); break; case 90: text->SetLabelSpinStyle( LABEL_SPIN_STYLE::UP ); break; case 180: text->SetLabelSpinStyle( LABEL_SPIN_STYLE::LEFT ); break; case 270: text->SetLabelSpinStyle( LABEL_SPIN_STYLE::BOTTOM ); break; default: wxFAIL; text->SetLabelSpinStyle( LABEL_SPIN_STYLE::RIGHT ); break; } NeedRIGHT(); break; } case T_shape: { if( text->Type() == SCH_TEXT_T || text->Type() == SCH_LABEL_T ) Unexpected( T_shape ); token = NextTok(); switch( token ) { case T_input: text->SetShape( PINSHEETLABEL_SHAPE::PS_INPUT ); break; case T_output: text->SetShape( PINSHEETLABEL_SHAPE::PS_OUTPUT ); break; case T_bidirectional: text->SetShape( PINSHEETLABEL_SHAPE::PS_BIDI ); break; case T_tri_state: text->SetShape( PINSHEETLABEL_SHAPE::PS_TRISTATE ); break; case T_passive: text->SetShape( PINSHEETLABEL_SHAPE::PS_UNSPECIFIED ); break; default: Expecting( "input, output, bidirectional, tri_state, or passive" ); } NeedRIGHT(); break; } case T_effects: parseEDA_TEXT( static_cast( text.get() ) ); break; default: Expecting( "at, shape, or effects" ); } } return text.release(); } void SCH_SEXPR_PARSER::parseBusAlias( SCH_SCREEN* aScreen ) { wxCHECK_RET( CurTok() == T_bus_alias, wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a bus alias." ) ); wxCHECK( aScreen, /* void */ ); T token; auto busAlias = std::make_shared< BUS_ALIAS >( aScreen ); NeedSYMBOL(); busAlias->SetName( FromUTF8() ); NeedLEFT(); token = NextTok(); if( token != T_members ) Expecting( "members" ); token = NextTok(); while( token != T_RIGHT ) { if( !IsSymbol( token ) ) Expecting( "quoted string" ); busAlias->AddMember( FromUTF8() ); token = NextTok(); } NeedRIGHT(); aScreen->AddBusAlias( busAlias ); }