/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2022 Mikolaj Wielgus * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. * * 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, you may find one here: * https://www.gnu.org/licenses/gpl-3.0.html * or you may search the http://www.gnu.org website for the version 3 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include namespace SIM_MODEL_SPICE_PARSER { using namespace SPICE_GRAMMAR; template struct spiceUnitSelector : std::false_type {}; template <> struct spiceUnitSelector : std::true_type {}; template <> struct spiceUnitSelector : std::true_type {}; template <> struct spiceUnitSelector : std::true_type {}; template <> struct spiceUnitSelector : std::true_type {}; template <> struct spiceUnitSelector : std::true_type {}; template <> struct spiceUnitSelector : std::true_type {}; } wxString SPICE_GENERATOR_SPICE::Preview( const wxString& aModelName ) const { wxString spiceCode = ModelLine( aModelName ); if( spiceCode == "" ) spiceCode = static_cast( m_model ).m_spiceCode; if( spiceCode == "" && m_model.GetBaseModel() ) spiceCode = static_cast( m_model.GetBaseModel() )->m_spiceCode; wxString itemLine = ItemLine( "", aModelName ); if( spiceCode != "" ) spiceCode << "\n"; spiceCode << itemLine; return spiceCode.Trim(); } SIM_MODEL::TYPE SIM_MODEL_SPICE::ReadTypeFromSpiceCode( const wxString& aSpiceCode ) { tao::pegtl::string_input<> in( aSpiceCode.ToUTF8(), "Spice_Code" ); std::unique_ptr root; try { root = tao::pegtl::parse_tree::parse ( in ); } catch( const tao::pegtl::parse_error& e ) { wxLogDebug( "%s", e.what() ); return TYPE::NONE; } for( const auto& node : root->children ) { if( node->is_type() ) { wxString paramName; wxString typeString; wxString level; wxString version; for( const auto& subnode : node->children ) { if( subnode->is_type() ) { // Do nothing. } else if( subnode->is_type() ) { typeString = subnode->string(); TYPE type = readTypeFromSpiceStrings( typeString ); if( type != TYPE::RAWSPICE ) return type; } else if( subnode->is_type() ) { paramName = subnode->string(); } else if( subnode->is_type() ) { wxASSERT( paramName != "" ); if( paramName == "level" ) level = subnode->string(); else if( paramName == "version" ) version = subnode->string(); } else { wxFAIL_MSG( "Unhandled parse tree subnode" ); return TYPE::NONE; } } // Type was not determined from Spice type string alone, so now we take `level` and // `version` variables into account too. This is suboptimal since we read the model // twice this way, and moreover the code is now somewhat duplicated. return readTypeFromSpiceStrings( typeString, level, version, false ); } else if( node->is_type() ) return TYPE::SUBCKT; else { wxFAIL_MSG( "Unhandled parse tree node" ); return TYPE::NONE; } } wxFAIL_MSG( "Could not derive type from Spice code" ); return TYPE::NONE; } std::unique_ptr SIM_MODEL_SPICE::Create( const wxString& aSpiceCode ) { auto model = static_cast( SIM_MODEL::Create( ReadTypeFromSpiceCode( aSpiceCode ) ).release() ); try { model->ReadSpiceCode( aSpiceCode ); } catch( const IO_ERROR& e ) { DisplayErrorMessage( nullptr, e.What() ); } return std::unique_ptr( model ); } void SIM_MODEL_SPICE::ReadSpiceCode( const wxString& aSpiceCode ) { // The default behavior is to treat the Spice param=value pairs as the model parameters and // values (for many models the correspondence is not exact, so this function is overridden). tao::pegtl::string_input<> in( aSpiceCode.ToUTF8(), "Spice_Code" ); std::unique_ptr root; try { root = tao::pegtl::parse_tree::parse ( in ); } catch( tao::pegtl::parse_error& e ) { THROW_IO_ERROR( e.what() ); } for( const auto& node : root->children ) { if( node->is_type() ) { wxString paramName = ""; for( const auto& subnode : node->children ) { if( subnode->is_type() ) { // Do nothing. } else if( subnode->is_type() ) { // Do nothing. } else if( subnode->is_type() ) { paramName = subnode->string(); } else if( subnode->is_type() ) { wxASSERT( !paramName.IsEmpty() ); if( !SetParamFromSpiceCode( paramName, subnode->string() ) ) { THROW_IO_ERROR( wxString::Format( _( "Failed to set parameter '%s' to '%s'" ), paramName, subnode->string() ) ); } } else { wxFAIL_MSG( "Unhandled parse tree subnode" ); } } } else { wxFAIL_MSG( "Unhandled parse tree node" ); } } m_spiceCode = aSpiceCode; } bool SIM_MODEL_SPICE::SetParamValue( unsigned aParamIndex, const wxString& aParamValue, SIM_VALUE_GRAMMAR::NOTATION aNotation ) { // Models sourced from a library are immutable. if( m_spiceCode != "" ) return false; return SIM_MODEL::SetParamValue( aParamIndex, aParamValue, aNotation ); } bool SIM_MODEL_SPICE::SetParamFromSpiceCode( const wxString& aParamName, const wxString& aParamValue, SIM_VALUE_GRAMMAR::NOTATION aNotation ) { return SIM_MODEL::SetParamValue( aParamName, aParamValue, aNotation ); } SIM_MODEL::TYPE SIM_MODEL_SPICE::readTypeFromSpiceStrings( const wxString& aTypeString, const wxString& aLevel, const wxString& aVersion, bool aSkipDefaultLevel ) { std::unique_ptr readLevel = SIM_VALUE::Create( SIM_VALUE::TYPE_INT, aLevel.ToStdString() ); for( TYPE type : TYPE_ITERATOR() ) { wxString typePrefix = SpiceInfo( type ).modelType; wxString level = SpiceInfo( type ).level; wxString version = SpiceInfo( type ).version; bool isDefaultLevel = SpiceInfo( type ).isDefaultLevel; if( typePrefix == "" ) continue; // Check if `aTypeString` starts with `typePrefix`. if( aTypeString.Upper().StartsWith( typePrefix ) && ( level == readLevel->ToString() || ( !aSkipDefaultLevel && isDefaultLevel && aLevel == "" ) ) && version == aVersion ) { return type; } } // If the type string is not recognized, demote to a raw Spice element. This way the user won't // have an error if there is a type KiCad does not recognize. return TYPE::RAWSPICE; }