kicad/eeschema/sch_sexpr_parser.cpp

2705 lines
70 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 CERN
*
* @author Wayne Stambaugh <stambaughw@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
/**
* @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 <wx/base64.h>
#include <wx/mstream.h>
#include <wx/tokenzr.h>
#include <common.h>
#include <lib_id.h>
#include <class_libentry.h>
#include <lib_arc.h>
#include <lib_bezier.h>
#include <lib_circle.h>
#include <lib_pin.h>
#include <lib_polyline.h>
#include <lib_rectangle.h>
#include <lib_text.h>
#include <sch_bitmap.h>
#include <sch_bus_entry.h>
#include <sch_component.h>
#include <sch_edit_frame.h> // CMP_ORIENT_XXX
#include <sch_field.h>
#include <sch_line.h>
#include <sch_junction.h>
#include <sch_no_connect.h>
#include <sch_screen.h>
#include <sch_sexpr_parser.h>
#include <template_fieldnames.h>
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<LIB_PART> 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<int>( 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<int>( 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<LIB_ITEM*>( parseArc() );
break;
case T_bezier:
return static_cast<LIB_ITEM*>( parseBezier() );
break;
case T_circle:
return static_cast<LIB_ITEM*>( parseCircle() );
break;
case T_pin:
return static_cast<LIB_ITEM*>( parsePin() );
break;
case T_polyline:
return static_cast<LIB_ITEM*>( parsePolyLine() );
break;
case T_rectangle:
return static_cast<LIB_ITEM*>( parseRectangle() );
break;
case T_text:
return static_cast<LIB_TEXT*>( 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();
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<LIB_PART>& 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<LIB_PART>& 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<LIB_FIELD> 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<int>( parseDouble( "text angle" ) * 10.0 ) );
NeedRIGHT();
break;
case T_effects:
parseEDA_TEXT( static_cast<EDA_TEXT*>( 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<LIB_ARC> 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<LIB_BEZIER> 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<LIB_CIRCLE> 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()
{
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<LIB_PIN> pin( new LIB_PIN( nullptr ) );
pin->SetUnit( m_unit );
pin->SetConvert( m_convert );
// Pin electrical type.
token = NextTok();
switch( token )
{
case T_input:
pin->SetType( ELECTRICAL_PINTYPE::PT_INPUT );
break;
case T_output:
pin->SetType( ELECTRICAL_PINTYPE::PT_OUTPUT );
break;
case T_bidirectional:
pin->SetType( ELECTRICAL_PINTYPE::PT_BIDI );
break;
case T_tri_state:
pin->SetType( ELECTRICAL_PINTYPE::PT_TRISTATE );
break;
case T_passive:
pin->SetType( ELECTRICAL_PINTYPE::PT_PASSIVE );
break;
case T_unspecified:
pin->SetType( ELECTRICAL_PINTYPE::PT_UNSPECIFIED );
break;
case T_power_in:
pin->SetType( ELECTRICAL_PINTYPE::PT_POWER_IN );
break;
case T_power_out:
pin->SetType( ELECTRICAL_PINTYPE::PT_POWER_OUT );
break;
case T_open_collector:
pin->SetType( ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR );
break;
case T_open_emitter:
pin->SetType( ELECTRICAL_PINTYPE::PT_OPENEMITTER );
break;
case T_unconnected:
pin->SetType( ELECTRICAL_PINTYPE::PT_NC );
break;
default:
Expecting( "input, output, bidirectional, tri_state, passive, unspecified, "
"power_in, power_out, open_collector, open_emitter, or unconnected" );
}
// Pin shape.
token = NextTok();
switch( token )
{
case T_line:
pin->SetShape( GRAPHIC_PINSHAPE::LINE );
break;
case T_inverted:
pin->SetShape( GRAPHIC_PINSHAPE::INVERTED );
break;
case T_clock:
pin->SetShape( GRAPHIC_PINSHAPE::CLOCK );
break;
case T_inverted_clock:
pin->SetShape( GRAPHIC_PINSHAPE::INVERTED_CLOCK );
break;
case T_input_low:
pin->SetShape( GRAPHIC_PINSHAPE::INPUT_LOW );
break;
case T_clock_low:
pin->SetShape( GRAPHIC_PINSHAPE::CLOCK_LOW );
break;
case T_output_low:
pin->SetShape( GRAPHIC_PINSHAPE::OUTPUT_LOW );
break;
case T_edge_clock_high:
pin->SetShape( GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK );
break;
case T_non_logic:
pin->SetShape( GRAPHIC_PINSHAPE::NONLOGIC );
break;
default:
Expecting( "line, inverted, clock, inverted_clock, input_low, clock_low, "
"output_low, edge_clock_high, non_logic" );
}
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(), false );
NeedRIGHT();
}
else
{
Expecting( "effects" );
}
}
break;
default:
Expecting( "at, name, number, or length" );
}
}
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<LIB_POLYLINE> 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<LIB_RECTANGLE> 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<LIB_TEXT> 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<EDA_TEXT*>( 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<SCH_FIELD> 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<int>( parseDouble( "text angle" ) * 10.0 ) );
NeedRIGHT();
break;
case T_effects:
parseEDA_TEXT( static_cast<EDA_TEXT*>( 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<SCH_SHEET_PIN> 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<EDA_TEXT*>( 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<SCH_ITEM*>( parseSchematicSymbol() ) );
break;
case T_image:
screen->Append( static_cast<SCH_ITEM*>( 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<SCH_ITEM*>( sheet ) );
break;
}
case T_junction:
screen->Append( static_cast<SCH_ITEM*>( parseJunction() ) );
break;
case T_no_connect:
screen->Append( static_cast<SCH_ITEM*>( parseNoConnect() ) );
break;
case T_bus_entry:
screen->Append( static_cast<SCH_ITEM*>( parseBusEntry() ) );
break;
case T_polyline:
case T_bus:
case T_wire:
screen->Append( static_cast<SCH_ITEM*>( parseLine() ) );
break;
case T_text:
case T_label:
case T_global_label:
case T_hierarchical_label:
screen->Append( static_cast<SCH_ITEM*>( 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<SCH_COMPONENT> symbol( new SCH_COMPONENT() );
TRANSFORM transform;
std::set<int> 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<int>( 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<KIID&>( 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;
}
default:
Expecting( "lib_id, lib_name, at, mirror, uuid, property, 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<SCH_BITMAP> 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<SCH_FIELD> fields;
std::unique_ptr<SCH_SHEET> sheet( new SCH_SHEET() );
std::set<int> 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<KIID&>( 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<SCH_JUNCTION> 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<SCH_NO_CONNECT> 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<SCH_BUS_WIRE_ENTRY> 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<SCH_LINE> 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<SCH_TEXT> 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<int>( 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<EDA_TEXT*>( 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 );
}