|
|
|
Jimmy Johnson
|
I am trying to make a wrapper of the Fantom API for the Lego Mindstorms robot and then use the `sendDirectCommand' method to send commands to the NXT. But I am having trouble getting the cpp wrappers to compile. (I flushed my brain cells of C/C++ when ISE released 3.1 for the PC; now I am paying for that.) I hope I am not asking too much, but I don't know where else to turn.
Thanks, Jimmy J. Johnson Below is the sendDirectCommand method for reference: virtual ViUInt32 sendDirectCommand( ViBoolean requireResponse, const ViByte commandBufferPtr[], ViUInt32 commandBufferSizeInBytes, ViPBuf responseBufferPtr, ViUInt32 responseBufferSizeInBytes, tStatus& status ) = 0; I can follow an example wrapper if someone could provide examples for creating an iNXT, iNXTIterator, and a tStatus object. In the example program they create an iNXTIterator and then use that to get an iNXT object. But the createNXTIterator feature is in iNXT class. It seems circular to me; I need an iNXT to make the iterator but I need an iterator to make the nxt? Here is [most of] the first part of the example program: int _VI_FUNCC main( int argc, char** argv ) { nFANTOM100::tStatus status; nFANTOM100::iNXTIterator* nxtIteratorPtr = NULL; nFANTOM100::iNXT* nxtPtr = NULL; // Create an NXT iterator object to find all accessible NXT devices. nxtIteratorPtr = nFANTOM100::iNXT::createNXTIterator( false /* search for NXTs using USB only */, 0 /* timeout for Bluetooth discovery ignored */, status ); if( status.isNotFatal()) { nxtPtr = nxtIteratorPtr->getNXT( status ); // Destroy the NXT iterator object which we no longer need nFANTOM100::iNXT::destroyNXTIterator( nxtIteratorPtr ); } Here is the feature from iNXT.h: nFANTOM100_kExport static iNXTIterator* _VI_FUNCC createNXTIterator( ViBoolean searchBluetooth, ViUInt32 bluetoothSearchTimeoutInSeconds, tStatus& status ); from iNXTIterator.h: virtual iNXT* getNXT( tStatus& status ) = 0; and from tStatus.h: namespace nFANTOM100 { // classes... const ViInt32 kStatusOffset = -142000; // 0xFFFDD550 const ViStatus kStatusSuccess = VI_SUCCESS; /*! \brief Enumeration of Fantom-specific status codes. NI-VISA status codes may also be returned. These are documented in the NI-VISA Programmer Reference Manual which is available from <http://ni.com/>. */ enum tFANTOMStatus { kStatusFirst = (kStatusOffset + 0), //! Error: Bluetooth pairing operation failed. //! Warning: You have already paired with that Bluetooth device. kStatusPairingFailed = (kStatusOffset + -5), // 0x54B //! Error: Bluetooth search failed. kStatusBluetoothSearchFailed = (kStatusOffset + -6), // 0x54A -- {snip} a whole bunch of other enumerartions. kStatusLast = (kStatusOffset + -999) }; /*! \brief Class that contains a status code and the file name and line number where that status code was generated. */ class tStatus { public: // methods //! constructor /*! Creates a tStatus object intialized to success. \post The status code is set to VI_SUCCESS. */ inline tStatus( void ) : _code( VI_SUCCESS ), _lineNumber( 0 ) { _fileName[0] = '\0'; } -- {snip} other features removed. private: // declared private to prevent assignment tStatus& operator=(const tStatus& rhs); enum { kMaxFileNameLength = 101 }; ViStatus _code; ViChar _fileName[ kMaxFileNameLength ]; ViUInt32 _lineNumber; }; |
||||||||||||||||
|
Colin LeMahieu
|
One thing to remember is that :: is doing both namespace resolution and a static method call; it's not a member or feature call on an object. So when it's asking the iNXT 'class' for an iterator, it's really like asking a global function for an iterator, they just happened to tie it to the iNXT class for some reason. I agree that wasn't a good choice of placement on their part.
A member call on an object of the iNXT class would look like: nxtPtr->method_call(); A static call to a function attached to the iNXT class would look like: iNXT::static_method_call(); Notice how iNXT isn't an instance, it's the class. --- In [hidden email], "Jimmy J. Johnson" <boxer41a@...> wrote: > > I am trying to make a wrapper of the Fantom API for the Lego Mindstorms robot and then use the `sendDirectCommand' method to send commands to the NXT. But I am having trouble getting the cpp wrappers to compile. (I flushed my brain cells of C/C++ when ISE released 3.1 for the PC; now I am paying for that.) I hope I am not asking too much, but I don't know where else to turn. > > Thanks, > > Jimmy J. Johnson > > > Below is the sendDirectCommand method for reference: > > virtual ViUInt32 sendDirectCommand( ViBoolean requireResponse, const ViByte commandBufferPtr[], ViUInt32 commandBufferSizeInBytes, ViPBuf responseBufferPtr, ViUInt32 responseBufferSizeInBytes, tStatus& status ) = 0; > > I can follow an example wrapper if someone could provide examples for creating an iNXT, iNXTIterator, and a tStatus object. In the example program they create an iNXTIterator and then use that to get an iNXT object. But the createNXTIterator feature is in iNXT class. It seems circular to me; I need an iNXT to make the iterator but I need an iterator to make the nxt? Here is [most of] the first part of the example program: > > int _VI_FUNCC main( int argc, char** argv ) > { > nFANTOM100::tStatus status; > nFANTOM100::iNXTIterator* nxtIteratorPtr = NULL; > nFANTOM100::iNXT* nxtPtr = NULL; > // Create an NXT iterator object to find all accessible NXT devices. > nxtIteratorPtr = nFANTOM100::iNXT::createNXTIterator( > false /* search for NXTs using USB only */, > 0 /* timeout for Bluetooth discovery ignored */, status ); > if( status.isNotFatal()) > { nxtPtr = nxtIteratorPtr->getNXT( status ); > // Destroy the NXT iterator object which we no longer need > nFANTOM100::iNXT::destroyNXTIterator( nxtIteratorPtr ); > } > > > > Here is the feature from iNXT.h: > > nFANTOM100_kExport static iNXTIterator* _VI_FUNCC createNXTIterator( > ViBoolean searchBluetooth, ViUInt32 bluetoothSearchTimeoutInSeconds, > tStatus& status ); > > > from iNXTIterator.h: > > virtual iNXT* getNXT( tStatus& status ) = 0; > > > and from tStatus.h: > > namespace nFANTOM100 > { > // classes... > const ViInt32 kStatusOffset = -142000; // 0xFFFDD550 > const ViStatus kStatusSuccess = VI_SUCCESS; > > /*! > \brief Enumeration of Fantom-specific status codes. NI-VISA status codes may also be > returned. These are documented in the NI-VISA Programmer Reference Manual which is > available from <http://ni.com/>. > */ > enum tFANTOMStatus > { > kStatusFirst = (kStatusOffset + 0), > > //! Error: Bluetooth pairing operation failed. > //! Warning: You have already paired with that Bluetooth device. > kStatusPairingFailed = (kStatusOffset + -5), // 0x54B > > //! Error: Bluetooth search failed. > kStatusBluetoothSearchFailed = (kStatusOffset + -6), // 0x54A > > -- {snip} a whole bunch of other enumerartions. > > kStatusLast = (kStatusOffset + -999) > }; > > /*! > \brief Class that contains a status code and the file name and line number where that > status code was generated. > */ > class tStatus > { > public: > > // methods > > //! constructor > /*! > Creates a tStatus object intialized to success. > > \post The status code is set to VI_SUCCESS. > */ > inline tStatus( void ) : > _code( VI_SUCCESS ), > _lineNumber( 0 ) > { > _fileName[0] = '\0'; > } > > -- {snip} other features removed. > > private: > // declared private to prevent assignment > tStatus& operator=(const tStatus& rhs); > enum > { > kMaxFileNameLength = 101 > }; > ViStatus _code; > ViChar _fileName[ kMaxFileNameLength ]; > ViUInt32 _lineNumber; > }; > |
||||||||||||||||
|
Jimmy Johnson
|
I think I see. So when I make a wrapper for that "feature" I can put it in any class?
--- In [hidden email], "colinlema" <clemahieu@...> wrote: > > One thing to remember is that :: is doing both namespace resolution and a static method call; it's not a member or feature call on an object. So when it's asking the iNXT 'class' for an iterator, it's really like asking a global function for an iterator, they just happened to tie it to the iNXT class for some reason. I agree that wasn't a good choice of placement on their part. > > A member call on an object of the iNXT class would look like: > nxtPtr->method_call(); > > A static call to a function attached to the iNXT class would look like: > iNXT::static_method_call(); > > Notice how iNXT isn't an instance, it's the class. > > --- In [hidden email], "Jimmy J. Johnson" <boxer41a@> wrote: > > > > I am trying to make a wrapper of the Fantom API for the Lego Mindstorms robot and then use the `sendDirectCommand' method to send commands to the NXT. But I am having trouble getting the cpp wrappers to compile. (I flushed my brain cells of C/C++ when ISE released 3.1 for the PC; now I am paying for that.) I hope I am not asking too much, but I don't know where else to turn. > > > > Thanks, > > > > Jimmy J. Johnson > > > > > > Below is the sendDirectCommand method for reference: > > > > virtual ViUInt32 sendDirectCommand( ViBoolean requireResponse, const ViByte commandBufferPtr[], ViUInt32 commandBufferSizeInBytes, ViPBuf responseBufferPtr, ViUInt32 responseBufferSizeInBytes, tStatus& status ) = 0; > > > > I can follow an example wrapper if someone could provide examples for creating an iNXT, iNXTIterator, and a tStatus object. In the example program they create an iNXTIterator and then use that to get an iNXT object. But the createNXTIterator feature is in iNXT class. It seems circular to me; I need an iNXT to make the iterator but I need an iterator to make the nxt? Here is [most of] the first part of the example program: > > > > int _VI_FUNCC main( int argc, char** argv ) > > { > > nFANTOM100::tStatus status; > > nFANTOM100::iNXTIterator* nxtIteratorPtr = NULL; > > nFANTOM100::iNXT* nxtPtr = NULL; > > // Create an NXT iterator object to find all accessible NXT devices. > > nxtIteratorPtr = nFANTOM100::iNXT::createNXTIterator( > > false /* search for NXTs using USB only */, > > 0 /* timeout for Bluetooth discovery ignored */, status ); > > if( status.isNotFatal()) > > { nxtPtr = nxtIteratorPtr->getNXT( status ); > > // Destroy the NXT iterator object which we no longer need > > nFANTOM100::iNXT::destroyNXTIterator( nxtIteratorPtr ); > > } > > > > > > > > Here is the feature from iNXT.h: > > > > nFANTOM100_kExport static iNXTIterator* _VI_FUNCC createNXTIterator( > > ViBoolean searchBluetooth, ViUInt32 bluetoothSearchTimeoutInSeconds, > > tStatus& status ); > > > > > > from iNXTIterator.h: > > > > virtual iNXT* getNXT( tStatus& status ) = 0; > > > > > > and from tStatus.h: > > > > namespace nFANTOM100 > > { > > // classes... > > const ViInt32 kStatusOffset = -142000; // 0xFFFDD550 > > const ViStatus kStatusSuccess = VI_SUCCESS; > > > > /*! > > \brief Enumeration of Fantom-specific status codes. NI-VISA status codes may also be > > returned. These are documented in the NI-VISA Programmer Reference Manual which is > > available from <http://ni.com/>. > > */ > > enum tFANTOMStatus > > { > > kStatusFirst = (kStatusOffset + 0), > > > > //! Error: Bluetooth pairing operation failed. > > //! Warning: You have already paired with that Bluetooth device. > > kStatusPairingFailed = (kStatusOffset + -5), // 0x54B > > > > //! Error: Bluetooth search failed. > > kStatusBluetoothSearchFailed = (kStatusOffset + -6), // 0x54A > > > > -- {snip} a whole bunch of other enumerartions. > > > > kStatusLast = (kStatusOffset + -999) > > }; > > > > /*! > > \brief Class that contains a status code and the file name and line number where that > > status code was generated. > > */ > > class tStatus > > { > > public: > > > > // methods > > > > //! constructor > > /*! > > Creates a tStatus object intialized to success. > > > > \post The status code is set to VI_SUCCESS. > > */ > > inline tStatus( void ) : > > _code( VI_SUCCESS ), > > _lineNumber( 0 ) > > { > > _fileName[0] = '\0'; > > } > > > > -- {snip} other features removed. > > > > private: > > // declared private to prevent assignment > > tStatus& operator=(const tStatus& rhs); > > enum > > { > > kMaxFileNameLength = 101 > > }; > > ViStatus _code; > > ViChar _fileName[ kMaxFileNameLength ]; > > ViUInt32 _lineNumber; > > }; > > > |
||||||||||||||||
|
Colin LeMahieu
|
Strictly speaking you don't have to follow their abstractions at all. It would be a design decision, do you want your Eiffel code to look exactly like their code, or do you want to make better abstractions.
The pattern I've seen many people used and I find very useful, is to make a couple Eiffel classes that simply wrap the C++ functions. On top of these wrapper classes you can make some better abstractions. Maybe one Eiffel class per C++ class or one Eiffel class per C++ file, depending on how the C++ is divided up. --- In [hidden email], "Jimmy J. Johnson" <boxer41a@...> wrote: > > I think I see. So when I make a wrapper for that "feature" I can put it in any class? > > > --- In [hidden email], "colinlema" <clemahieu@> wrote: > > > > One thing to remember is that :: is doing both namespace resolution and a static method call; it's not a member or feature call on an object. So when it's asking the iNXT 'class' for an iterator, it's really like asking a global function for an iterator, they just happened to tie it to the iNXT class for some reason. I agree that wasn't a good choice of placement on their part. > > > > A member call on an object of the iNXT class would look like: > > nxtPtr->method_call(); > > > > A static call to a function attached to the iNXT class would look like: > > iNXT::static_method_call(); > > > > Notice how iNXT isn't an instance, it's the class. > > > > --- In [hidden email], "Jimmy J. Johnson" <boxer41a@> wrote: > > > > > > I am trying to make a wrapper of the Fantom API for the Lego Mindstorms robot and then use the `sendDirectCommand' method to send commands to the NXT. But I am having trouble getting the cpp wrappers to compile. (I flushed my brain cells of C/C++ when ISE released 3.1 for the PC; now I am paying for that.) I hope I am not asking too much, but I don't know where else to turn. > > > > > > Thanks, > > > > > > Jimmy J. Johnson > > > > > > > > > Below is the sendDirectCommand method for reference: > > > > > > virtual ViUInt32 sendDirectCommand( ViBoolean requireResponse, const ViByte commandBufferPtr[], ViUInt32 commandBufferSizeInBytes, ViPBuf responseBufferPtr, ViUInt32 responseBufferSizeInBytes, tStatus& status ) = 0; > > > > > > I can follow an example wrapper if someone could provide examples for creating an iNXT, iNXTIterator, and a tStatus object. In the example program they create an iNXTIterator and then use that to get an iNXT object. But the createNXTIterator feature is in iNXT class. It seems circular to me; I need an iNXT to make the iterator but I need an iterator to make the nxt? Here is [most of] the first part of the example program: > > > > > > int _VI_FUNCC main( int argc, char** argv ) > > > { > > > nFANTOM100::tStatus status; > > > nFANTOM100::iNXTIterator* nxtIteratorPtr = NULL; > > > nFANTOM100::iNXT* nxtPtr = NULL; > > > // Create an NXT iterator object to find all accessible NXT devices. > > > nxtIteratorPtr = nFANTOM100::iNXT::createNXTIterator( > > > false /* search for NXTs using USB only */, > > > 0 /* timeout for Bluetooth discovery ignored */, status ); > > > if( status.isNotFatal()) > > > { nxtPtr = nxtIteratorPtr->getNXT( status ); > > > // Destroy the NXT iterator object which we no longer need > > > nFANTOM100::iNXT::destroyNXTIterator( nxtIteratorPtr ); > > > } > > > > > > > > > > > > Here is the feature from iNXT.h: > > > > > > nFANTOM100_kExport static iNXTIterator* _VI_FUNCC createNXTIterator( > > > ViBoolean searchBluetooth, ViUInt32 bluetoothSearchTimeoutInSeconds, > > > tStatus& status ); > > > > > > > > > from iNXTIterator.h: > > > > > > virtual iNXT* getNXT( tStatus& status ) = 0; > > > > > > > > > and from tStatus.h: > > > > > > namespace nFANTOM100 > > > { > > > // classes... > > > const ViInt32 kStatusOffset = -142000; // 0xFFFDD550 > > > const ViStatus kStatusSuccess = VI_SUCCESS; > > > > > > /*! > > > \brief Enumeration of Fantom-specific status codes. NI-VISA status codes may also be > > > returned. These are documented in the NI-VISA Programmer Reference Manual which is > > > available from <http://ni.com/>. > > > */ > > > enum tFANTOMStatus > > > { > > > kStatusFirst = (kStatusOffset + 0), > > > > > > //! Error: Bluetooth pairing operation failed. > > > //! Warning: You have already paired with that Bluetooth device. > > > kStatusPairingFailed = (kStatusOffset + -5), // 0x54B > > > > > > //! Error: Bluetooth search failed. > > > kStatusBluetoothSearchFailed = (kStatusOffset + -6), // 0x54A > > > > > > -- {snip} a whole bunch of other enumerartions. > > > > > > kStatusLast = (kStatusOffset + -999) > > > }; > > > > > > /*! > > > \brief Class that contains a status code and the file name and line number where that > > > status code was generated. > > > */ > > > class tStatus > > > { > > > public: > > > > > > // methods > > > > > > //! constructor > > > /*! > > > Creates a tStatus object intialized to success. > > > > > > \post The status code is set to VI_SUCCESS. > > > */ > > > inline tStatus( void ) : > > > _code( VI_SUCCESS ), > > > _lineNumber( 0 ) > > > { > > > _fileName[0] = '\0'; > > > } > > > > > > -- {snip} other features removed. > > > > > > private: > > > // declared private to prevent assignment > > > tStatus& operator=(const tStatus& rhs); > > > enum > > > { > > > kMaxFileNameLength = 101 > > > }; > > > ViStatus _code; > > > ViChar _fileName[ kMaxFileNameLength ]; > > > ViUInt32 _lineNumber; > > > }; > > > > > > |
||||||||||||||||
|
Jimmy Johnson
|
No, I don't want to follow a bad design if I don't have to. I also don't need to wrap every routine. I just can't get the c++ syntax correct.
Is the code below the correct approach? If so, can someone tell me the syntax for feature `c_create'? Thanks, Jimmy J. Johnson class NXT_ITERATOR inherit ANY redefine default_create end create default_create feature {NONE} -- Initialization default_create is -- Initialize an iterator do cpp_object := c_create end feature {NONE} -- Implementation c_create: POINTER is -- Call the cpp constructor external "C++ inline use %"iNXTIterator.h%"" alias "[ iNXTIterator *it; return new it; ]" end cpp_object: POINTER -- cpp handle to the iterator end --- In [hidden email], "colinlema" <clemahieu@...> wrote: > > Strictly speaking you don't have to follow their abstractions at all. It would be a design decision, do you want your Eiffel code to look exactly like their code, or do you want to make better abstractions. > > The pattern I've seen many people used and I find very useful, is to make a couple Eiffel classes that simply wrap the C++ functions. On top of these wrapper classes you can make some better abstractions. Maybe one Eiffel class per C++ class or one Eiffel class per C++ file, depending on how the C++ is divided up. > > --- In [hidden email], "Jimmy J. Johnson" <boxer41a@> wrote: > > > > I think I see. So when I make a wrapper for that "feature" I can put it in any class? > > > > > > --- In [hidden email], "colinlema" <clemahieu@> wrote: > > > > > > One thing to remember is that :: is doing both namespace resolution and a static method call; it's not a member or feature call on an object. So when it's asking the iNXT 'class' for an iterator, it's really like asking a global function for an iterator, they just happened to tie it to the iNXT class for some reason. I agree that wasn't a good choice of placement on their part. > > > > > > A member call on an object of the iNXT class would look like: > > > nxtPtr->method_call(); > > > > > > A static call to a function attached to the iNXT class would look like: > > > iNXT::static_method_call(); > > > > > > Notice how iNXT isn't an instance, it's the class. > > > > > > --- In [hidden email], "Jimmy J. Johnson" <boxer41a@> wrote: > > > > > > > > I am trying to make a wrapper of the Fantom API for the Lego Mindstorms robot and then use the `sendDirectCommand' method to send commands to the NXT. But I am having trouble getting the cpp wrappers to compile. (I flushed my brain cells of C/C++ when ISE released 3.1 for the PC; now I am paying for that.) I hope I am not asking too much, but I don't know where else to turn. > > > > > > > > Thanks, > > > > > > > > Jimmy J. Johnson > > > > > > > > > > > > Below is the sendDirectCommand method for reference: > > > > > > > > virtual ViUInt32 sendDirectCommand( ViBoolean requireResponse, const ViByte commandBufferPtr[], ViUInt32 commandBufferSizeInBytes, ViPBuf responseBufferPtr, ViUInt32 responseBufferSizeInBytes, tStatus& status ) = 0; > > > > > > > > I can follow an example wrapper if someone could provide examples for creating an iNXT, iNXTIterator, and a tStatus object. In the example program they create an iNXTIterator and then use that to get an iNXT object. But the createNXTIterator feature is in iNXT class. It seems circular to me; I need an iNXT to make the iterator but I need an iterator to make the nxt? Here is [most of] the first part of the example program: > > > > > > > > int _VI_FUNCC main( int argc, char** argv ) > > > > { > > > > nFANTOM100::tStatus status; > > > > nFANTOM100::iNXTIterator* nxtIteratorPtr = NULL; > > > > nFANTOM100::iNXT* nxtPtr = NULL; > > > > // Create an NXT iterator object to find all accessible NXT devices. > > > > nxtIteratorPtr = nFANTOM100::iNXT::createNXTIterator( > > > > false /* search for NXTs using USB only */, > > > > 0 /* timeout for Bluetooth discovery ignored */, status ); > > > > if( status.isNotFatal()) > > > > { nxtPtr = nxtIteratorPtr->getNXT( status ); > > > > // Destroy the NXT iterator object which we no longer need > > > > nFANTOM100::iNXT::destroyNXTIterator( nxtIteratorPtr ); > > > > } > > > > > > > > > > > > > > > > Here is the feature from iNXT.h: > > > > > > > > nFANTOM100_kExport static iNXTIterator* _VI_FUNCC createNXTIterator( > > > > ViBoolean searchBluetooth, ViUInt32 bluetoothSearchTimeoutInSeconds, > > > > tStatus& status ); > > > > > > > > > > > > from iNXTIterator.h: > > > > > > > > virtual iNXT* getNXT( tStatus& status ) = 0; > > > > > > > > > > > > and from tStatus.h: > > > > > > > > namespace nFANTOM100 > > > > { > > > > // classes... > > > > const ViInt32 kStatusOffset = -142000; // 0xFFFDD550 > > > > const ViStatus kStatusSuccess = VI_SUCCESS; > > > > > > > > /*! > > > > \brief Enumeration of Fantom-specific status codes. NI-VISA status codes may also be > > > > returned. These are documented in the NI-VISA Programmer Reference Manual which is > > > > available from <http://ni.com/>. > > > > */ > > > > enum tFANTOMStatus > > > > { > > > > kStatusFirst = (kStatusOffset + 0), > > > > > > > > //! Error: Bluetooth pairing operation failed. > > > > //! Warning: You have already paired with that Bluetooth device. > > > > kStatusPairingFailed = (kStatusOffset + -5), // 0x54B > > > > > > > > //! Error: Bluetooth search failed. > > > > kStatusBluetoothSearchFailed = (kStatusOffset + -6), // 0x54A > > > > > > > > -- {snip} a whole bunch of other enumerartions. > > > > > > > > kStatusLast = (kStatusOffset + -999) > > > > }; > > > > > > > > /*! > > > > \brief Class that contains a status code and the file name and line number where that > > > > status code was generated. > > > > */ > > > > class tStatus > > > > { > > > > public: > > > > > > > > // methods > > > > > > > > //! constructor > > > > /*! > > > > Creates a tStatus object intialized to success. > > > > > > > > \post The status code is set to VI_SUCCESS. > > > > */ > > > > inline tStatus( void ) : > > > > _code( VI_SUCCESS ), > > > > _lineNumber( 0 ) > > > > { > > > > _fileName[0] = '\0'; > > > > } > > > > > > > > -- {snip} other features removed. > > > > > > > > private: > > > > // declared private to prevent assignment > > > > tStatus& operator=(const tStatus& rhs); > > > > enum > > > > { > > > > kMaxFileNameLength = 101 > > > > }; > > > > ViStatus _code; > > > > ViChar _fileName[ kMaxFileNameLength ]; > > > > ViUInt32 _lineNumber; > > > > }; > > > > > > > > > > |
||||||||||||||||
| Free Embeddable Forum Powered by Nabble | Help |