Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
K
kicad-source-mirror
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Commits
Open sidebar
Elphel
kicad-source-mirror
Commits
d2cc77ac
Commit
d2cc77ac
authored
Dec 30, 2010
by
Dick Hollenbeck
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
see CHANGELOG.txt
parent
7bcb4ff3
Changes
21
Show whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
458 additions
and
313 deletions
+458
-313
.bzrignore
.bzrignore
+5
-3
CHANGELOG.txt
CHANGELOG.txt
+13
-0
dsnlexer.cpp
common/dsnlexer.cpp
+3
-1
richio.cpp
common/richio.cpp
+4
-4
sch_component.h
eeschema/sch_component.h
+0
-19
kicad_exceptions.h
include/kicad_exceptions.h
+0
-92
richio.h
include/richio.h
+128
-1
CMakeLists.txt
new/CMakeLists.txt
+9
-0
sch_dir_lib_source.cpp
new/sch_dir_lib_source.cpp
+50
-33
sch_dir_lib_source.h
new/sch_dir_lib_source.h
+9
-2
sch_lib.cpp
new/sch_lib.cpp
+40
-4
sch_lib.h
new/sch_lib.h
+11
-8
sch_lib_table.cpp
new/sch_lib_table.cpp
+65
-57
sch_lib_table.h
new/sch_lib_table.h
+6
-8
sch_lpid.cpp
new/sch_lpid.cpp
+24
-43
sch_lpid.h
new/sch_lpid.h
+22
-13
sch_part.cpp
new/sch_part.cpp
+8
-2
sch_part.h
new/sch_part.h
+45
-10
sweet.keywords
new/sweet.keywords
+2
-0
utf8.h
new/utf8.h
+13
-12
specctra.cpp
pcbnew/specctra.cpp
+1
-1
No files found.
.bzrignore
View file @
d2cc77ac
...
...
@@ -5,9 +5,6 @@ eeschema/cmp_library_lexer.h
eeschema/cmp_library_keywords.*
eeschema/template_fieldnames_keywords.*
eeschema/template_fieldnames_lexer.h
new/html
new/sch_lib_table_keywords.cpp
new/sch_lib_table_lexer.h
pcbnew/dialogs/dialog_freeroute_exchange_help_html.h
Makefile
CMakeFiles
...
...
@@ -20,3 +17,8 @@ install_manifest.txt
Documentation/doxygen
*.cmake
*.bak
new/html
new/sch_lib_table_keywords.cpp
new/sch_lib_table_lexer.h
new/sweet_keywords.cpp
new/sweet_lexer.h
CHANGELOG.txt
View file @
d2cc77ac
...
...
@@ -4,6 +4,19 @@ KiCad ChangeLog 2010
Please add newer entries at the top, list the date and your name with
email address.
2010-Dec-28 UPDATE Dick Hollenbeck <dick@softplc.com>
================================================================================
++richio:
* Deleted kicad_exceptions, because it required the big #include <wx/wx.h> and
that was slowing down compiling. Moved that stuff back into richio.h where
it came from.
* Enhanced IO_ERROR to format an errorText.
* Added THROW_IO_ERROR() and THROW_PARSE_ERROR() macros to capture the
the call site of the thrower. If you have problems compiling, it is probably
due to the definition of __LOC__ in richio.h. Some compilers may not support
__func__ in C++ yet. Find a macro that identifies your compiler, and we can
work out something in the #define of __LOC__.
2010-Dec-28 UPDATE Dick Hollenbeck <dick@softplc.com>
================================================================================
++new:
...
...
common/dsnlexer.cpp
View file @
d2cc77ac
...
...
@@ -251,12 +251,14 @@ bool DSNLEXER::IsSymbol( int aTok )
void
DSNLEXER
::
ThrowIOError
(
wxString
aText
,
int
charOffset
)
throw
(
IO_ERROR
)
{
// @todo convert this to THROW_PARSE_ERROR()
// append to aText, do not overwrite
aText
<<
wxT
(
" "
)
<<
_
(
"in"
)
<<
wxT
(
"
\"
"
)
<<
CurSource
()
<<
wxT
(
"
\"
"
)
<<
_
(
"on line"
)
<<
wxT
(
" "
)
<<
reader
->
LineNumber
()
<<
wxT
(
" "
)
<<
_
(
"at offset"
)
<<
wxT
(
" "
)
<<
charOffset
;
throw
IO_ERROR
(
aText
);
THROW_
IO_ERROR
(
aText
);
}
...
...
common/richio.cpp
View file @
d2cc77ac
...
...
@@ -113,7 +113,7 @@ unsigned FILE_LINE_READER::ReadLine() throw( IO_ERROR )
length
+=
strlen
(
line
+
length
);
if
(
length
==
maxLineLength
)
throw
IO_ERROR
(
_
(
"Line length exceeded"
)
);
THROW_
IO_ERROR
(
_
(
"Line length exceeded"
)
);
// a normal line breaks here, once through while loop
if
(
length
+
1
<
capacity
||
line
[
length
-
1
]
==
'\n'
)
...
...
@@ -164,7 +164,7 @@ unsigned STRING_LINE_READER::ReadLine() throw( IO_ERROR )
if
(
length
)
{
if
(
length
>=
maxLineLength
)
throw
IO_ERROR
(
_
(
"Line length exceeded"
)
);
THROW_
IO_ERROR
(
_
(
"Line length exceeded"
)
);
if
(
length
+
1
>
capacity
)
// +1 for terminating nul
expandCapacity
(
length
+
1
);
...
...
@@ -305,7 +305,7 @@ std::string OUTPUTFORMATTER::Quoted( const std::string& aWrapee ) throw( IO_ERRO
// a decision was made to make all S-expression strings be on a single
// line. You can embed \n (human readable) in the text but not
// '\n' which is 0x0a.
throw
IO_ERROR
(
_
(
"S-expression string has newline"
)
);
THROW_
IO_ERROR
(
_
(
"S-expression string has newline"
)
);
}
}
...
...
@@ -363,7 +363,7 @@ void STREAM_OUTPUTFORMATTER::write( const char* aOutBuf, int aCount ) throw( IO_
if
(
!
os
.
IsOk
()
)
{
throw
IO_ERROR
(
_
(
"OUTPUTSTREAM_OUTPUTFORMATTER write error"
)
);
THROW_
IO_ERROR
(
_
(
"OUTPUTSTREAM_OUTPUTFORMATTER write error"
)
);
}
}
}
...
...
eeschema/sch_component.h
View file @
d2cc77ac
...
...
@@ -17,25 +17,6 @@ class LIB_PIN;
class
LIB_COMPONENT
;
/**
* Holder of an error message and may be thrown from functions.
*/
struct
Error
{
wxString
errorText
;
Error
(
const
wxChar
*
aMsg
)
:
errorText
(
aMsg
)
{
}
Error
(
const
wxString
&
aMsg
)
:
errorText
(
aMsg
)
{
}
};
/// A container for several SCH_FIELD items
typedef
std
::
vector
<
SCH_FIELD
>
SCH_FIELDS
;
...
...
include/kicad_exceptions.h
deleted
100644 → 0
View file @
7bcb4ff3
/*
* This program source code file is part of KICAD, a free EDA CAD application.
*
* Copyright (C) 2010 SoftPLC Corporation, <dick@softplc.com>
* Copyright (C) 2010 Kicad Developers, see change_log.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 2
* 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:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifndef KICAD_EXCEPTIONS_H_
#define KICAD_EXCEPTIONS_H_
/**
* @ingroup exception_types
* @{
*/
#include <wx/string.h>
#include <string>
/**
* Struct IO_ERROR
* is a class used to hold an error message and may be used to throw exceptions
* containing meaningful error messages.
* @author Dick Hollenbeck
*/
struct
IO_ERROR
{
wxString
errorText
;
/**
* Constructor ( const wxChar* )
* handles the case where _() is passed as aMsg.
*/
IO_ERROR
(
const
wxChar
*
aMsg
)
:
errorText
(
aMsg
)
{
}
IO_ERROR
(
const
wxString
&
aMsg
)
:
errorText
(
aMsg
)
{
}
IO_ERROR
(
const
std
::
string
&
aMsg
)
:
errorText
(
wxConvertMB2WX
(
aMsg
.
c_str
()
)
)
{
}
};
/**
* Class PARSE_ERROR
* contains a filename or source description, a line number, a character offset,
* and an error message.
* @author Dick Hollenbeck
*/
struct
PARSE_ERROR
:
public
IO_ERROR
{
wxString
source
;
///< filename typically, or other source
int
lineNumber
;
int
byteIndex
;
///< char offset, starting from 1, into the problem line.
PARSE_ERROR
(
const
wxString
&
aMsg
,
const
wxString
&
aSource
,
int
aLineNumber
,
int
aByteIndex
)
:
IO_ERROR
(
aMsg
),
source
(
aSource
),
lineNumber
(
aLineNumber
)
{
}
};
/** @} exception_types */
#endif // KICAD_EXCEPTIONS_H_
include/richio.h
View file @
d2cc77ac
...
...
@@ -38,7 +38,134 @@
// but the errorText needs to be wide char so wxString rules.
#include <wx/wx.h>
#include <cstdio>
#include <kicad_exceptions.h>
/**
* @ingroup exception_types
* @{
*/
#define IO_FORMAT _( "IO_ERROR: '%s'\n from %s : %s" )
#define PARSE_FORMAT _( "PARSE_ERROR: '%s' in input/source '%s', line %d, offset %d\n from %s : %s" )
// references:
// http://stackoverflow.com/questions/2670816/how-can-i-use-the-compile-time-constant-line-in-a-string
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
// use one of the following __LOC__ defs, depending on whether your
// compiler supports __func__ or not, and how it handles __LINE__
#define __LOC__ ((std::string(__func__) + " : ") + TOSTRING(__LINE__)).c_str()
//#define __LOC__ TOSTRING(__LINE__)
/// macro which captures the "call site" values of __FILE_ & __LOC__
#define THROW_IO_ERROR( msg ) throw IO_ERROR( __FILE__, __LOC__, msg )
/**
* Struct IO_ERROR
* is a class used to hold an error message and may be used to throw exceptions
* containing meaningful error messages.
* @author Dick Hollenbeck
*/
struct
IO_ERROR
// : std::exception
{
wxString
errorText
;
/**
* Constructor
*
* @param aThrowersFile is the __FILE__ preprocessor macro but generated
* at the source file of thrower.
*
* @param aThrowersLoc can be either a function name, such as __func__
* or a stringified __LINE__ preprocessor macro but generated
* at the source function of the thrower, or concatonation. Use macro
* THROW_IO_ERROR() to wrap a call to this constructor at the call site.
*
* @param aMsg is error text that will be streamed through wxString.Printf()
* using the format string IO_FORMAT above.
*/
IO_ERROR
(
const
char
*
aThrowersFile
,
const
char
*
aThrowersLoc
,
const
wxString
&
aMsg
)
{
init
(
aThrowersFile
,
aThrowersLoc
,
aMsg
);
}
IO_ERROR
(
const
char
*
aThrowersFile
,
const
char
*
aThrowersLoc
,
const
std
::
string
&
aMsg
)
{
init
(
aThrowersFile
,
aThrowersLoc
,
wxString
::
FromUTF8
(
aMsg
.
c_str
()
)
);
}
/**
* handles the case where _() is passed as aMsg.
*/
IO_ERROR
(
const
char
*
aThrowersFile
,
const
char
*
aThrowersLoc
,
const
wxChar
*
aMsg
)
{
init
(
aThrowersFile
,
aThrowersLoc
,
wxString
(
aMsg
)
);
}
void
init
(
const
char
*
aThrowersFile
,
const
char
*
aThrowersLoc
,
const
wxString
&
aMsg
)
{
errorText
.
Printf
(
IO_FORMAT
,
aMsg
.
GetData
(),
wxString
::
FromUTF8
(
aThrowersFile
).
GetData
(),
wxString
::
FromUTF8
(
aThrowersLoc
).
GetData
()
);
}
IO_ERROR
()
{}
~
IO_ERROR
()
throw
(
/*none*/
){}
};
#define THROW_PARSE_ERROR( msg, input, line, offset ) throw PARSE_ERROR( __FILE__, __LOC__, msg, input, line, offset )
/**
* Class PARSE_ERROR
* contains a filename or source description, a line number, a character offset,
* and an error message.
* @author Dick Hollenbeck
*/
struct
PARSE_ERROR
:
public
IO_ERROR
{
// wxString errorText is still public from IO_ERROR
int
lineNumber
;
///< at which line number, 1 based index.
int
byteIndex
;
///< at which character position within the line, 1 based index
PARSE_ERROR
(
const
char
*
aThrowersFile
,
const
char
*
aThrowersLoc
,
const
wxString
&
aMsg
,
const
wxString
&
aSource
,
int
aLineNumber
,
int
aByteIndex
)
:
IO_ERROR
()
{
init
(
aThrowersFile
,
aThrowersLoc
,
aMsg
,
aSource
,
aLineNumber
,
aByteIndex
);
}
void
init
(
const
char
*
aThrowersFile
,
const
char
*
aThrowersLoc
,
const
wxString
&
aMsg
,
const
wxString
&
aSource
,
int
aLineNumber
,
int
aByteIndex
)
{
// save line and offset in binary for Sweet text editor, which will catch exceptions
lineNumber
=
aLineNumber
;
byteIndex
=
aByteIndex
;
// #define PARSE_FORMAT _( "PARSE_ERROR: %s in source %s, line %d, offset %d\nfrom cpp:%s func:%s" )
errorText
.
Printf
(
PARSE_FORMAT
,
aMsg
.
GetData
(),
aSource
.
GetData
(),
aLineNumber
,
aByteIndex
,
wxString
::
FromUTF8
(
aThrowersFile
).
GetData
(),
wxString
::
FromUTF8
(
aThrowersLoc
).
GetData
()
);
}
~
PARSE_ERROR
()
throw
(
/*none*/
){}
};
/** @} exception_types */
#define LINE_READER_LINE_DEFAULT_MAX 100000
...
...
new/CMakeLists.txt
View file @
d2cc77ac
...
...
@@ -86,6 +86,8 @@ add_executable( test_sch_lib_table
sch_lib.cpp
sch_lpid.cpp
sch_dir_lib_source.cpp
sch_part.cpp
sweet_keywords.cpp
${
PROJECT_SOURCE_DIR
}
/common/richio.cpp
${
PROJECT_SOURCE_DIR
}
/common/dsnlexer.cpp
)
...
...
@@ -98,3 +100,10 @@ make_lexer(
${
CMAKE_CURRENT_SOURCE_DIR
}
/sch_lib_table_keywords.cpp
ELT_T
)
make_lexer
(
${
CMAKE_CURRENT_SOURCE_DIR
}
/sweet.keywords
${
CMAKE_CURRENT_SOURCE_DIR
}
/sweet_lexer.h
${
CMAKE_CURRENT_SOURCE_DIR
}
/sweet_keywords.cpp
PART_T
)
new/sch_dir_lib_source.cpp
View file @
d2cc77ac
...
...
@@ -41,16 +41,10 @@
*/
#include <sch_dir_lib_source.h>
using
namespace
SCH
;
#include <kicad_exceptions.h>
#include <dirent.h>
#include <sys/stat.h>
#include <cstring>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
...
...
@@ -61,11 +55,8 @@ using namespace SCH;
#include <vector>
using
namespace
std
;
/// This file extension is an implementation detail specific to this LIB_SOURCE
/// implementation, and to a corresponding LIB_SINK.
/// Core EESCHEMA should never have to see this.
#define SWEET_EXT ".part"
#define SWEET_EXTZ (sizeof(SWEET_EXT)-1)
#include <sch_dir_lib_source.h>
using
namespace
SCH
;
/* __func__ is C99 prescribed, but just in case:
...
...
@@ -157,7 +148,7 @@ static inline bool isDigit( char c )
* segment, i.e. the string segment of interest is [start,tail)
* @param separator is the separating byte, expected: '.' or '/', depending on context.
*/
static
const
char
*
endsWithRev
(
const
char
*
start
,
const
char
*
tail
,
char
separator
)
static
const
char
*
endsWithRev
(
const
char
*
start
,
const
char
*
tail
,
char
separator
=
'/'
)
{
bool
sawDigit
=
false
;
...
...
@@ -181,7 +172,7 @@ static const char* endsWithRev( const char* start, const char* tail, char separa
return
0
;
}
static
inline
const
char
*
endsWithRev
(
const
STRING
&
aPartName
,
char
separator
)
static
inline
const
char
*
endsWithRev
(
const
STRING
&
aPartName
,
char
separator
=
'/'
)
{
return
endsWithRev
(
aPartName
.
c_str
(),
aPartName
.
c_str
()
+
aPartName
.
size
(),
separator
);
}
...
...
@@ -194,8 +185,8 @@ bool BY_REV::operator() ( const STRING& s1, const STRING& s2 ) const
{
// avoid instantiating new STRINGs, and thank goodness that c_str() is const.
const
char
*
rev1
=
endsWithRev
(
s1
,
'/'
);
const
char
*
rev2
=
endsWithRev
(
s2
,
'/'
);
const
char
*
rev1
=
endsWithRev
(
s1
);
const
char
*
rev2
=
endsWithRev
(
s2
);
int
rootLen1
=
rev1
?
rev1
-
s1
.
c_str
()
:
s1
.
size
();
int
rootLen2
=
rev2
?
rev2
-
s2
.
c_str
()
:
s2
.
size
();
...
...
@@ -290,7 +281,7 @@ STRING DIR_LIB_SOURCE::makeFileName( const STRING& aPartName )
STRING
fileName
=
sourceURI
+
"/"
;
const
char
*
rev
=
endsWithRev
(
aPartName
,
'/'
);
const
char
*
rev
=
endsWithRev
(
aPartName
);
if
(
rev
)
{
...
...
@@ -318,7 +309,7 @@ void DIR_LIB_SOURCE::readString( STRING* aResult, const STRING& aFileName ) thro
{
STRING
msg
=
strerror
(
errno
);
msg
+=
"; cannot open(O_RDONLY) file "
+
aFileName
;
throw
(
IO_ERROR
(
msg
)
);
THROW_IO_ERROR
(
msg
);
}
struct
stat
fs
;
...
...
@@ -330,9 +321,14 @@ void DIR_LIB_SOURCE::readString( STRING* aResult, const STRING& aFileName ) thro
{
STRING
msg
=
aFileName
;
msg
+=
" seems too big. ( > 1 mbyte )"
;
throw
IO_ERROR
(
msg
);
THROW_
IO_ERROR
(
msg
);
}
#if 0
// I read somewhere on the Internet that std::string chars are not guaranteed
// (over time) to be contiguous in future implementations of C++, so this
// strategy is here for that eventuality. We buffer through readBuffer here.
// reuse same readBuffer, which is not thread safe, but the API
// is not advertising thread safe (yet, if ever).
if( (int) fs.st_size > (int) readBuffer.size() )
...
...
@@ -343,13 +339,27 @@ void DIR_LIB_SOURCE::readString( STRING* aResult, const STRING& aFileName ) thro
{
STRING msg = strerror( errno );
msg += "; cannot read file " + aFileName;
throw
(
IO_ERROR
(
msg
)
);
THROW_IO_ERROR( msg
);
}
// std::string chars are not guaranteed to be contiguous in
// future implementations of C++, so this is why we did not read into
// aResult directly.
aResult->assign( &readBuffer[0], count );
#else
// read into the string directly
aResult
->
resize
(
fs
.
st_size
);
int
count
=
read
(
fw
,
&
(
*
aResult
)[
0
],
fs
.
st_size
);
if
(
count
!=
(
int
)
fs
.
st_size
)
{
STRING
msg
=
strerror
(
errno
);
msg
+=
"; cannot read file "
+
aFileName
;
THROW_IO_ERROR
(
msg
);
}
// test trailing nul is there, which should have been put there with resize() above
// printf( "'%s'\n", aResult->c_str() ); // checked OK.
#endif
}
...
...
@@ -371,7 +381,7 @@ DIR_LIB_SOURCE::DIR_LIB_SOURCE( const STRING& aDirectoryPath,
if
(
sourceURI
.
size
()
==
0
)
{
throw
(
IO_ERROR
(
STRING
(
"aDirectoryPath cannot be empty"
)
)
);
THROW_IO_ERROR
(
STRING
(
"aDirectoryPath cannot be empty"
)
);
}
// remove any trailing separator, so we can add it back later without ambiguity
...
...
@@ -406,7 +416,7 @@ void DIR_LIB_SOURCE::GetCategoricalPartNames( STRINGS* aResults, const STRING& a
while
(
it
!=
limit
)
{
const
char
*
rev
=
endsWithRev
(
*
it
,
'/'
);
const
char
*
rev
=
endsWithRev
(
*
it
);
// all cached partnames have a rev string in useVersioning mode
assert
(
rev
);
...
...
@@ -434,7 +444,13 @@ void DIR_LIB_SOURCE::ReadPart( STRING* aResult, const STRING& aPartName, const S
throw
(
IO_ERROR
)
{
STRING
partName
=
aPartName
;
// appended with aRev too if not empty
const
char
*
rev
=
endsWithRev
(
partName
,
'/'
);
const
char
*
rev
=
endsWithRev
(
partName
);
if
(
!
useVersioning
&&
(
aRev
.
size
()
||
rev
)
)
{
STRING
msg
=
"this type 'dir' LIB_SOURCE not using 'useVersioning' option, cannot ask for a revision"
;
THROW_IO_ERROR
(
msg
);
}
if
(
aRev
.
size
()
)
{
...
...
@@ -443,7 +459,7 @@ void DIR_LIB_SOURCE::ReadPart( STRING* aResult, const STRING& aPartName, const S
partName
+=
"/"
+
aRev
;
rev
=
endsWithRev
(
partName
,
'/'
);
rev
=
endsWithRev
(
partName
);
}
// partName is the exact part name we need here, or if rev is NULL,
...
...
@@ -456,7 +472,7 @@ void DIR_LIB_SOURCE::ReadPart( STRING* aResult, const STRING& aPartName, const S
if
(
it
==
partnames
.
end
()
)
// part not found
{
partName
+=
" not found."
;
throw
IO_ERROR
(
partName
);
THROW_
IO_ERROR
(
partName
);
}
readString
(
aResult
,
makeFileName
(
partName
)
);
...
...
@@ -474,8 +490,9 @@ void DIR_LIB_SOURCE::ReadPart( STRING* aResult, const STRING& aPartName, const S
// some unrelated name that is larger.
if
(
it
==
partnames
.
end
()
||
it
->
compare
(
0
,
search
.
size
(),
search
)
!=
0
)
{
partName
+=
" rev not found."
;
throw
IO_ERROR
(
partName
);
partName
.
insert
(
partName
.
begin
(),
'\''
);
partName
+=
"' is not present without a revision."
;
THROW_IO_ERROR
(
partName
);
}
readString
(
aResult
,
makeFileName
(
*
it
)
);
...
...
@@ -540,7 +557,7 @@ void DIR_LIB_SOURCE::cacheOneDir( const STRING& aCategory ) throw( IO_ERROR )
{
STRING
msg
=
strerror
(
errno
);
msg
+=
"; scanning directory "
+
curDir
;
throw
(
IO_ERROR
(
msg
)
);
THROW_IO_ERROR
(
msg
);
}
struct
stat
fs
;
...
...
@@ -566,7 +583,7 @@ void DIR_LIB_SOURCE::cacheOneDir( const STRING& aCategory ) throw( IO_ERROR )
{
STRING
msg
=
partName
;
msg
+=
" has already been encountered"
;
throw
IO_ERROR
(
msg
);
THROW_
IO_ERROR
(
msg
);
}
}
...
...
@@ -649,9 +666,9 @@ void DIR_LIB_SOURCE::Test( int argc, char** argv )
printf( "std::exception\n" );
}
catch( IO_ERROR ioe )
catch( IO_ERROR
&
ioe )
{
printf( "exception: %s\n", (const char*)
wxConvertWX2MB( ioe.errorText
) );
printf( "exception: %s\n", (const char*)
ioe.errorText.ToUTF8()
) );
}
}
...
...
new/sch_dir_lib_source.h
View file @
d2cc77ac
...
...
@@ -26,11 +26,18 @@
#define DIR_LIB_SOURCE_H_
#include <sch_lib.h>
#include <set>
#include <vector>
#include <sch_lib.h>
/// This file extension is an implementation detail specific to this LIB_SOURCE
/// and to a corresponding LIB_SINK.
/// Core EESCHEMA should never have to see this.
#define SWEET_EXT ".part"
#define SWEET_EXTZ (sizeof(SWEET_EXT)-1)
/**
* struct BY_REV
...
...
new/sch_lib.cpp
View file @
d2cc77ac
...
...
@@ -22,9 +22,14 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <
sch_lib.h>
#include <
memory> // std::auto_ptr
#include <wx/string.h>
#include <sch_lib.h>
#include <sch_lpid.h>
#include <sch_part.h>
#include <sweet_lexer.h>
#include <sch_lib_table.h>
using
namespace
SCH
;
...
...
@@ -44,9 +49,40 @@ LIB::~LIB()
}
PART
*
LIB
::
LookupPart
(
const
LPID
&
aLPID
)
throw
(
IO_ERROR
)
PART
*
LIB
::
LookupPart
(
const
LPID
&
aLPID
,
LIB_TABLE
*
aLibTable
)
throw
(
IO_ERROR
)
{
return
0
;
PART
*
part
;
// If part not already cached
if
(
1
/* @todo test cache */
)
{
// load it.
part
=
new
PART
(
this
,
aLPID
.
GetPartName
(),
aLPID
.
GetRevision
()
);
std
::
auto_ptr
<
PART
>
wrapped
(
part
);
source
->
ReadPart
(
&
part
->
body
,
aLPID
.
GetPartName
(),
aLPID
.
GetRevision
()
);
#if defined(DEBUG)
const
STRING
&
body
=
part
->
body
;
printf
(
"body: %s"
,
body
.
c_str
()
);
if
(
!
body
.
size
()
||
body
[
body
.
size
()
-
1
]
!=
'\n'
)
printf
(
"
\n
"
);
#endif
SWEET_LEXER
sw
(
part
->
body
,
wxString
::
FromUTF8
(
"body"
)
/* @todo have ReadPart give better source */
);
part
->
Parse
(
&
sw
,
aLibTable
);
// stuff the part into this LIBs cache:
// @todo
wrapped
.
release
();
}
return
part
;
}
...
...
new/sch_lib.h
View file @
d2cc77ac
...
...
@@ -26,14 +26,14 @@
#define SCH_LIB_H_
#include <utf8.h>
#include <
kicad_exceptions
.h>
#include <
richio
.h>
namespace
SCH
{
class
LPID
;
class
PART
;
class
LIB_TABLE
;
/**
* Class LIB_SOURCE
...
...
@@ -69,7 +69,7 @@ protected: ///< derived classes must implement
* Function ReadPart
* fetches @a aPartName's s-expression into @a aResult after clear()ing aResult.
*/
virtual
void
ReadPart
(
STR
ING
*
aResult
,
const
STRING
&
aPartName
,
const
STRING
&
aRev
=
""
)
virtual
void
ReadPart
(
STR
_UTF
*
aResult
,
const
STRING
&
aPartName
,
const
STRING
&
aRev
=
""
)
throw
(
IO_ERROR
)
=
0
;
/**
...
...
@@ -80,7 +80,7 @@ protected: ///< derived classes must implement
* does not have a version string, then the most recent version is fetched.
* @param aResults receives the s-expressions
*/
virtual
void
ReadParts
(
STR
ING
S
*
aResults
,
const
STRINGS
&
aPartNames
)
virtual
void
ReadParts
(
STR
_UTF
S
*
aResults
,
const
STRINGS
&
aPartNames
)
throw
(
IO_ERROR
)
=
0
;
/**
...
...
@@ -239,12 +239,15 @@ public:
* @param aLPID is the part to lookup. The logicalLibName can be empty in it
* since yes, we know which LIB is in play.
*
* @param aLibTable is the LIB_TABLE view that is in effect for inheritance,
* and comes from the big containing SCHEMATIC object.
*
* @return PART* - The desired PART and will never be NULL. No ownership is
* given to caller. PARTs always reside in the cache that is a LIB.
*
* @throw IO_ERROR if the part cannot be found or loaded.
*/
PART
*
LookupPart
(
const
LPID
&
aLPID
)
PART
*
LookupPart
(
const
LPID
&
aLPID
,
LIB_TABLE
*
aLibTable
)
throw
(
IO_ERROR
);
/**
...
...
@@ -315,13 +318,13 @@ public:
protected
:
STR
ING
fetch
;
// scratch, used to fetch things, grows to worst case size.
STR
INGS
vfetch
;
// scratch, used to fetch things.
STR
_UTF
fetch
;
// scratch, used to fetch things, grows to worst case size.
STR
_UTFS
vfetch
;
// scratch, used to fetch things.
STRING
name
;
LIB_SOURCE
*
source
;
LIB_SINK
*
sink
;
STRING
libraryURI
;
//
STRING libraryURI;
STRINGS
categories
;
...
...
new/sch_lib_table.cpp
View file @
d2cc77ac
...
...
@@ -132,7 +132,7 @@ void LIB_TABLE::Parse( SCH_LIB_TABLE_LEXER* in ) throw( IO_ERROR )
msg
+=
row
->
logicalName
;
msg
+=
'\''
;
msg
+=
" is a duplicate logical lib name"
;
throw
IO_ERROR
(
msg
);
THROW_
IO_ERROR
(
msg
);
}
}
}
...
...
@@ -187,34 +187,27 @@ STRINGS LIB_TABLE::GetLogicalLibs()
}
PART
*
LIB_TABLE
::
LookupPart
(
const
LPID
&
aL
ogicalPart
ID
,
LIB
*
aLocalLib
)
PART
*
LIB_TABLE
::
LookupPart
(
const
LPID
&
aL
P
ID
,
LIB
*
aLocalLib
)
throw
(
IO_ERROR
)
{
LIB
*
lib
=
lookupLib
(
aL
ogicalPart
ID
,
aLocalLib
);
LIB
*
lib
=
lookupLib
(
aL
P
ID
,
aLocalLib
);
return
lib
->
LookupPart
(
aL
ogicalPartID
);
return
lib
->
LookupPart
(
aL
PID
,
this
);
}
LIB
*
LIB_TABLE
::
lookupLib
(
const
LPID
&
aL
ogicalPartID
,
LIB
*
aLocal
Lib
)
LIB
*
LIB_TABLE
::
lookupLib
(
const
LPID
&
aL
PID
,
LIB
*
aFallBack
Lib
)
throw
(
IO_ERROR
)
{
if
(
aL
ocalLib
)
if
(
aL
PID
.
GetLogicalLib
().
size
()
)
{
return
aLocalLib
;
}
else
{
const
STRING
&
logName
=
aLogicalPartID
.
GetLogicalLib
();
if
(
logName
.
size
()
)
{
ROW
*
row
=
FindRow
(
logName
);
ROW
*
row
=
FindRow
(
aLPID
.
GetLogicalLib
()
);
if
(
!
row
)
{
STRING
msg
=
"Unable to find logical lib "
;
msg
+=
logName
;
throw
IO_ERROR
(
msg
);
STRING
msg
=
"lib table contains no logical lib '"
;
msg
+=
aLPID
.
GetLogicalLib
();
msg
+=
'\''
;
THROW_IO_ERROR
(
msg
);
}
if
(
!
row
->
lib
)
...
...
@@ -222,18 +215,18 @@ LIB* LIB_TABLE::lookupLib( const LPID& aLogicalPartID, LIB* aLocalLib )
loadLib
(
row
);
}
assert
(
row
->
lib
);
// loadLib() throws
if cannot load
assert
(
row
->
lib
);
// fix loadLib() to throw
if cannot load
return
row
->
lib
;
}
else
if
(
aFallBackLib
)
{
STRING
msg
=
"No logicalLibName in LPID and no localLib"
;
throw
IO_ERROR
(
msg
);
}
return
aFallBackLib
;
}
// return NULL; never get here
STRING
msg
=
"lookupLib() requires logicalLibName or a fallback lib"
;
THROW_IO_ERROR
(
msg
);
}
...
...
@@ -262,6 +255,7 @@ void LIB_TABLE::loadLib( ROW* aRow ) throw( IO_ERROR )
aRow
->
lib
=
new
LIB
(
aRow
->
GetLogicalName
(),
source
.
release
(),
NULL
);
}
/*
else if( !libType.compare( "schematic" ) )
{
// @todo code and load SCHEMATIC_LIB_SOURCE
...
...
@@ -276,6 +270,14 @@ void LIB_TABLE::loadLib( ROW* aRow ) throw( IO_ERROR )
{
// @todo code and load HTTP_LIB_SOURCE
}
*/
else
{
STRING
msg
=
"cannot load unknown libType: '"
;
msg
+=
libType
;
msg
+=
'\''
;
THROW_IO_ERROR
(
msg
);
}
}
...
...
@@ -334,44 +336,34 @@ bool LIB_TABLE::InsertRow( std::auto_ptr<ROW>& aRow, bool doReplace )
}
#if
0
&& defined(DEBUG)
#if
1
&& defined(DEBUG)
// build this with a Debug CMAKE_BUILD_TYPE
void
LIB_TABLE
::
Test
()
{
// the null string is not really a legal DSN token since any duplicated
// double quote ("") is assumed to be a single double quote (").
// To pass an empty string, we can pass " " to (options " ")
// A pair of double quotes alone, is not really a legal DSN token since
// any duplicated double quote ("") is assumed to be a single double quote (")
// in any DSN lexer.
// To pass an empty string, we can pass " " to (options " "), or if you passed
// """" this would show up as "" with quotes present in the parser. The parser
// probably doesn't want a pair of double quotes, strlen() = 2.
SCH_LIB_TABLE_LEXER
slr
(
"(lib_table
\n
"
" (lib (logical www) (type http) (full_uri http://kicad.org/libs) (options
\"
\"
))
\n
"
" (lib (logical meparts) (type dir) (full_uri /tmp/eeschema-lib) (options
\" \"
))\n"
" (lib (logical meparts) (type dir) (full_uri /tmp/eeschema-lib) (options
useVersioning
))
\n
"
" (lib (logical old-project) (type schematic)(full_uri /tmp/old-schematic.sch) (options
\"
\"
))
\n
"
,
wxT
(
"inline text"
)
// source
);
try
{
// read the "( lib_table" pair of tokens
slr
.
NextTok
();
slr
.
NextTok
();
// parse the rest of input to slr
Parse
(
&
slr
);
}
catch( std::exception& ex )
{
printf( "std::exception\n" );
}
catch( IO_ERROR ioe )
{
printf( "exception: %s\n", (const char*) wxConvertWX2MB( ioe.errorText ) );
}
STRING_FORMATTER
sf
;
...
...
@@ -401,6 +393,11 @@ void LIB_TABLE::Test()
{
printf
(
"logicalName: %s
\n
"
,
it
->
c_str
()
);
}
// find a part
LPID
lpid
(
"meparts:tigers/ears/rev10"
);
LookupPart
(
lpid
);
}
...
...
@@ -408,7 +405,18 @@ int main( int argc, char** argv )
{
LIB_TABLE
lib_table
;
try
{
// test exceptions:
// THROW_PARSE_ERROR( wxT("test problem"), wxT("input"), 23, 46 );
//THROW_IO_ERROR( wxT("io test") );
lib_table
.
Test
();
}
catch
(
IO_ERROR
&
ioe
)
{
printf
(
"%s
\n
"
,
(
const
char
*
)
ioe
.
errorText
.
ToUTF8
()
);
}
return
0
;
}
...
...
new/sch_lib_table.h
View file @
d2cc77ac
...
...
@@ -350,14 +350,12 @@ private:
* finds or loads a LIB based on @a aLogicalPartID or @a aFallBackLib.
* If the LIB is already loaded then it is returned as is, else it is loaded.
*
* @param aLogicalPartID
may
hold the logicalLibName. If
* logicalLibName is empty, then @a a
Local
Lib should not be NULL.
* @param aLogicalPartID
holds the partName and may also
hold the logicalLibName. If
* logicalLibName is empty, then @a a
FallBack
Lib should not be NULL.
*
* @param aLocalLib is used if not NULL, and should be supplied especiallly if
* aLogicalPartID has an empty logicalLibName. This is for the case when
* a partName must come from the same LIB as the referring content.
* For example, a PART extends another PART in the same LIB and the extends
* LPID has no logical lib name.
* @param aFallBackLib is used only if aLogicalPartID has an empty logicalLibName.
* This is for the case when an LPID has no logicalLibName because the LPID is using
* a partName from the same LIB as was the referring content.
*
* @return LIB* - this will never be NULL, and no ownership is transfered because
* all LIBs live in the LIB_TABLEs. You only get to point to them in some LIB_TABLE.
...
...
@@ -365,7 +363,7 @@ private:
*
* @throw IO_ERROR if any problem occurs or if the LIB cannot be found or cannot be loaded.
*/
LIB
*
lookupLib
(
const
LPID
&
aLogicalPartID
,
LIB
*
a
Local
Lib
=
NULL
)
throw
(
IO_ERROR
);
LIB
*
lookupLib
(
const
LPID
&
aLogicalPartID
,
LIB
*
a
FallBack
Lib
=
NULL
)
throw
(
IO_ERROR
);
/**
* Function loadLib
...
...
new/sch_lpid.cpp
View file @
d2cc77ac
...
...
@@ -114,6 +114,7 @@ int LPID::Parse( const STRING& aLPID )
logical
.
clear
();
category
.
clear
();
baseName
.
clear
();
partName
.
clear
();
revision
.
clear
();
const
char
*
rev
=
EndsWithRev
(
aLPID
);
...
...
@@ -183,7 +184,7 @@ LPID::LPID( const STRING& aLPID ) throw( PARSE_ERROR )
if
(
offset
!=
-
1
)
{
throw
PARSE_ERROR
(
THROW_
PARSE_ERROR
(
_
(
"Illegal character found in LPID string"
),
wxConvertMB2WX
(
aLPID
.
c_str
()
),
0
,
...
...
@@ -193,12 +194,6 @@ LPID::LPID( const STRING& aLPID ) throw( PARSE_ERROR )
}
const
STRING
&
LPID
::
GetLogicalLib
()
const
{
return
logical
;
}
int
LPID
::
SetLogicalLib
(
const
STRING
&
aLogical
)
{
int
offset
=
okLogical
(
aLogical
);
...
...
@@ -210,26 +205,25 @@ int LPID::SetLogicalLib( const STRING& aLogical )
}
const
STRING
&
LPID
::
GetCategory
()
const
{
return
category
;
}
int
LPID
::
SetCategory
(
const
STRING
&
aCategory
)
{
int
offset
=
okCategory
(
aCategory
);
if
(
offset
==
-
1
)
{
category
=
aCategory
;
}
return
offset
;
}
// set the partName too
if
(
category
.
size
()
)
{
partName
=
category
;
partName
+=
'/'
;
partName
+=
baseName
;
}
else
partName
=
baseName
;
const
STRING
&
LPID
::
GetBaseName
()
const
{
return
baseName
;
}
return
offset
;
}
...
...
@@ -239,31 +233,18 @@ int LPID::SetBaseName( const STRING& aBaseName )
if
(
offset
==
-
1
)
{
baseName
=
aBaseName
;
}
return
offset
;
}
STRING
LPID
::
GetPartName
()
const
{
STRING
ret
;
// return [category/]baseName
// set the partName too
if
(
category
.
size
()
)
{
ret
+=
category
;
ret
+=
'/'
;
partName
=
category
;
partName
+=
'/'
;
partName
+=
baseName
;
}
ret
+=
baseName
;
return
ret
;
}
const
STRING
&
LPID
::
GetRevision
()
const
{
return
revision
;
else
partName
=
baseName
;
}
return
offset
;
}
...
...
@@ -306,7 +287,7 @@ STRING LPID::Format() const
}
#if
1
&& defined(DEBUG)
#if
0
&& defined(DEBUG)
// build this with Debug CMAKE_BUILD_TYPE
...
...
new/sch_lpid.h
View file @
d2cc77ac
...
...
@@ -26,7 +26,7 @@
#define SCH_LPID_H_
#include <utf8.h>
#include <
kicad_exceptions
.h>
#include <
richio
.h>
namespace
SCH
{
...
...
@@ -84,7 +84,10 @@ public:
* for this portion since it comes from the library table and is considered
* read only here.
*/
const
STRING
&
GetLogicalLib
()
const
;
const
STRING
&
GetLogicalLib
()
const
{
return
logical
;
}
/**
* Function SetCategory
...
...
@@ -100,7 +103,10 @@ public:
* returns the category of this part id, "passives" in the example at the
* top of the class description.
*/
const
STRING
&
GetCategory
()
const
;
const
STRING
&
GetCategory
()
const
{
return
category
;
}
/**
* Function SetCategory
...
...
@@ -116,7 +122,10 @@ public:
* Function GetBaseName
* returns the part name without the category.
*/
const
STRING
&
GetBaseName
()
const
;
const
STRING
&
GetBaseName
()
const
{
return
baseName
;
}
/**
* Function SetBaseName
...
...
@@ -131,20 +140,19 @@ public:
* Function GetPartName
* returns the part name, i.e. category/baseName without revision.
*/
STRING
GetPartName
()
const
;
/**
* Function SetBaseName
* overrides the part name portion of the LPID to @a aPartName
not really needed, partname is an agreggate anyway, just parse a new one.
void SetPartName( const STRING& aPartName );
*/
const
STRING
&
GetPartName
()
const
{
return
partName
;
}
/**
* Function GetRevision
* returns the revision portion of the LPID.
*/
const
STRING
&
GetRevision
()
const
;
const
STRING
&
GetRevision
()
const
{
return
revision
;
}
/**
* Function SetRevision
...
...
@@ -171,6 +179,7 @@ protected:
STRING
category
;
///< or empty
STRING
baseName
;
///< without category
STRING
revision
;
///< "revN[N..]" or empty
STRING
partName
;
///< cannot be set directory, set via SetBaseName() & SetCategory()
};
}
// namespace SCH
...
...
new/sch_part.cpp
View file @
d2cc77ac
...
...
@@ -22,12 +22,18 @@
*/
#include "sch_part.h"
#include <sch_part.h>
#include <sweet_lexer.h>
#include <wx/wx.h> // _()
using
namespace
SCH
;
void
PART
::
Parse
(
LIB
*
aLexer
)
throw
(
PARSE_ERROR
)
void
PART
::
Parse
(
SWEET_LEXER
*
aLexer
,
LIB_TABLE
*
aTable
)
throw
(
PARSE_ERROR
)
{
// Wayne's world, if he still wants it.
}
...
...
new/sch_part.h
View file @
d2cc77ac
...
...
@@ -27,9 +27,31 @@
#include <sch_lib.h>
class
SWEET_LEXER
;
namespace
SCH
{
/**
* Enum PartBit
* is a set of bit positions that can be used to create flag bits within
* PART::contains to indicate what state the PART is in and what it contains, i.e.
* whether the PART has been parsed, and what the PART contains, categorically.
*/
enum
PartBit
{
PARSED
,
///< have parsed this part already, otherwise 'body' text must be parsed
EXTENDS
,
///< saw and "extends" keyword, inheriting from another PART
};
/// Function PB
/// is a PartBit shifter for PART::contains field.
static
inline
const
int
PB
(
PartBit
oneBitOnly
)
{
return
(
1
<<
oneBitOnly
);
}
/**
* Class PART
* will have to be unified with what Wayne is doing. I want a separate copy
...
...
@@ -47,19 +69,23 @@ class PART
/// be done by PARTS_LIST, a class derived from LIB.
friend
class
LIB
;
/// a private constructor, only a LIB can instantiate a PART.
PART
()
{}
PART
(
LIB
*
aOwner
,
const
STRING
&
aPartName
,
const
STRING
&
aRevision
)
:
owner
(
aOwner
),
contains
(
0
),
partName
(
aPartName
),
revision
(
aRevision
)
{}
protected
:
// not likely to have C++ descendants, but protected none-the-less.
bool
parsed
;
///< true if the body as been parsed already.
LIB
*
owner
;
///< which LIB am I a part of (pun if you want)
int
contains
;
///< has bits from Enum PartParts
STRING
extends
;
///< LPID of base part
STRING
name
;
///< example "passives/R", immutable.
STRING
partName
;
///< example "passives/R", immutable.
STRING
revision
;
// @todo need a single search key, this won't do.
/// s-expression text for the part, initially empty, and read in as this part
/// actually becomes cached in RAM.
...
...
@@ -81,7 +107,6 @@ protected: // not likely to have C++ descendants, but protected none-the-le
// lots of other stuff, like the mandatory properties.
public
:
/**
...
...
@@ -91,7 +116,6 @@ public:
*/
void
Inherit
(
const
PART
&
aBasePart
);
/**
* Function Owner
* returns the LIB* owner of this part.
...
...
@@ -100,12 +124,23 @@ public:
/**
* Function Parse
* translates
the \a body
string into a binary form that is represented
* translates
a Sweet
string into a binary form that is represented
* by the normal fields of this class. Parse is expected to call Inherit()
* if this part extends any other.
*
* @param aLexer is an instance of SWEET_LEXER, rewound at the first line.
*
* @param aLibTable is the LIB_TABLE view that is in effect for inheritance,
* and comes from the big containing SCHEMATIC object.
*/
void
Parse
(
LIB
*
aLexer
)
throw
(
PARSE_ERROR
);
void
Parse
(
SWEET_LEXER
*
aLexer
,
LIB_TABLE
*
aTable
)
throw
(
PARSE_ERROR
);
/*
void SetBody( const STR_UTF& aSExpression )
{
body = aSExpression;
}
*/
};
}
// namespace PART
...
...
new/sweet.keywords
0 → 100644
View file @
d2cc77ac
part
extends
new/utf8.h
View file @
d2cc77ac
...
...
@@ -13,14 +13,19 @@
/**
* Type STRING
* holds a sequence of 8 bit bytes that represent a sequence
*
of variable
multi-byte international characters, with unspecified encoding.
* holds a sequence of 8 bit bytes that represent a sequence
of variable
*
length
multi-byte international characters, with unspecified encoding.
*/
typedef
std
::
string
STRING
;
/**
* Type STRINGS
* is an "array like" list of STRINGs
*/
typedef
std
::
deque
<
STRING
>
STRINGS
;
/**
* Type STR
ING_UTF8
* Type STR
_UTF
* holds a UTF8 encoded sequence of 8 bit bytes that represent a sequence
* of variable multi-byte international characters. UTF8 is the chosen encoding
* for all Kicad data files so that they can be transported from one nation to another
...
...
@@ -29,19 +34,15 @@ typedef std::string STRING;
* Filenames may be encoded on disk using an encoding chosen by the host operating
* system. Nonetheless, Kicad data file _content_ is always UTF8 encoded, regardless
* of host operating system.
* STR
ING
_UTF is UTF8 encoded, by definition.
* STR_UTF is UTF8 encoded, by definition.
*/
typedef
STRING
STRING_UTF8
;
typedef
STRING
STR_UTF
;
/**
* Type STR
INGS_UTF8
* is an "array like" list of STR
ING_UTF8
s
* Type STR
_UTFS
* is an "array like" list of STR
_UTF
s
*/
typedef
std
::
deque
<
STRING
>
STRINGS_UTF8
;
typedef
std
::
deque
<
STRING
>
STRINGS
;
typedef
std
::
deque
<
STRING
>
STR_UTFS
;
/** @} string_types */
...
...
pcbnew/specctra.cpp
View file @
d2cc77ac
...
...
@@ -542,7 +542,7 @@ void SPECCTRA_DB::ThrowIOError( const wxChar* fmt, ... ) throw( IO_ERROR )
errText
.
PrintfV
(
fmt
,
args
);
va_end
(
args
);
throw
IO_ERROR
(
errText
);
THROW_
IO_ERROR
(
errText
);
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment