• R/O
  • SSH
  • HTTPS

eircompile: Commit


Commit MetaInfo

Revision68 (tree)
Time2021-12-24 22:34:05
Authorquiret

Log Message

- added support for namespaces and enums for COS
- added basic COS declaration unit tests

Change Summary

Incremental Difference

--- testcompiler/src/cos_tests.cpp (revision 67)
+++ testcompiler/src/cos_tests.cpp (revision 68)
@@ -800,6 +800,66 @@
800800 depVector <COSNode*> params;
801801 };
802802
803+struct NamespaceStatement : public COSNode
804+{
805+ inline NamespaceStatement( COSNode *name = nullptr, COSNode *toinclude = nullptr ) noexcept : name( name ), toinclude( toinclude )
806+ {
807+ return;
808+ }
809+ inline NamespaceStatement( const NamespaceStatement& ) = default;
810+ inline NamespaceStatement( NamespaceStatement&& right ) noexcept
811+ {
812+ this->name = right.name;
813+ this->toinclude = right.toinclude;
814+
815+ right.name = nullptr;
816+ right.toinclude = nullptr;
817+ }
818+
819+ COSNode *name;
820+ COSNode *toinclude;
821+};
822+struct UsingNamespaceStatement : public COSNode
823+{
824+ inline UsingNamespaceStatement( COSNode *nsname = nullptr ) noexcept : nsname( nsname )
825+ {
826+ return;
827+ }
828+ inline UsingNamespaceStatement( const UsingNamespaceStatement& ) = default;
829+ inline UsingNamespaceStatement( UsingNamespaceStatement&& right ) noexcept
830+ {
831+ this->nsname = right.nsname;
832+
833+ right.nsname = nullptr;
834+ }
835+
836+ COSNode *nsname;
837+};
838+
839+struct EnumDefinition : public COSNode
840+{
841+ struct item
842+ {
843+ COSNode *key;
844+ COSNode *value;
845+ };
846+
847+ inline EnumDefinition( COSNode *name = nullptr, depVector <item> items = {} ) noexcept : name( name ), items( std::move( items ) )
848+ {
849+ return;
850+ }
851+ inline EnumDefinition( const EnumDefinition& ) = default;
852+ inline EnumDefinition( EnumDefinition&& right ) noexcept : items( std::move( right.items ) )
853+ {
854+ this->name = right.name;
855+
856+ right.name = nullptr;
857+ }
858+
859+ COSNode *name;
860+ depVector <item> items;
861+};
862+
803863 }; // namespace COS
804864
805865 static inline bool is_num_nonzero( char c )
@@ -897,7 +957,7 @@
897957 };
898958 lexer.GetNamedProduction( "S" ).setSelector <mintwo_specialized_selector <char, Program, COSNode, program_dispatcher, decltype(lexer)::RuntimeEnv>> ( &lexer.GetRuntimeEnvironment() );
899959 }
900- assert( lexer.CompileProduction( compiler, "statement", "typedef | block | loop | if | except | objmod | multdecl | takefirst operation spaces ';'" ) == true );
960+ assert( lexer.CompileProduction( compiler, "statement", "typedef | block | loop | if | except | objmod | namespace | enum | multdecl | takefirst operation spaces ';'" ) == true );
901961 assert( lexer.CompileProduction( compiler, "funcbody", "^(statement, <b> funcstatement)" ) == true );
902962 assert( lexer.CompileProduction( compiler, "funcstatement", "\"return\" spaces (op:operation spaces)^0:1 ';'" ) == true );
903963 {
@@ -1084,6 +1144,7 @@
10841144 }
10851145 assert( lexer.CompileProduction( compiler, "dtypedef", "\"typedef\" spaces tmultdecl spaces ';'" ) == true );
10861146 assert( lexer.CompileProduction( compiler, "block", "'{' spaces [<0>statement:extended statement, spaces] spaces '}'" ) == true );
1147+ assert( lexer.CompileProduction( compiler, "nblock", "'{' spaces [<0>statement:statement, spaces] spaces '}'" ) == true );
10871148 {
10881149 struct block_dispatcher
10891150 {
@@ -1098,6 +1159,7 @@
10981159 }
10991160 };
11001161 lexer.GetNamedProduction( "block" ).setSelector <direct_obj_build_selector <char, BlockStatement, COSNode, block_dispatcher, decltype(lexer)::RuntimeEnv>> ( &lexer.GetRuntimeEnvironment() );
1162+ lexer.GetNamedProduction( "nblock" ).setSelector <direct_obj_build_selector <char, BlockStatement, COSNode, block_dispatcher, decltype(lexer)::RuntimeEnv>> ( &lexer.GetRuntimeEnvironment() );
11011163 }
11021164
11031165 // Create the special "operation" step.
@@ -2292,7 +2354,7 @@
22922354 lexer.GetNamedProduction( "forloop" ).setSelector <direct_obj_build_selector <char, ForLoopStatement, COSNode, for_dispatcher, decltype(lexer)::RuntimeEnv>> ( &lexer.GetRuntimeEnvironment() );
22932355 }
22942356 assert( lexer.CompileProduction( compiler, "loop", "whileloop | forloop" ) == true );
2295- assert( lexer.CompileProduction( compiler, "structdef", "\"struct\" spaces name:spec spaces ^!(body:block, <b> statement+typestatement) spaces ';'" ) == true );
2357+ assert( lexer.CompileProduction( compiler, "structdef", "\"struct\" spaces name:spec spaces ^!(body:nblock, <b> statement+typestatement) spaces ';'" ) == true );
22962358 {
22972359 struct struct_dispatcher
22982360 {
@@ -3045,7 +3107,102 @@
30453107 lexer.GetNamedProduction( "type" ).setSelector <type_selector> ();
30463108 }
30473109 assert( lexer.CompileProduction( compiler, "paramlist_opt", "[<0>param:decl_opt, spaces ',' spaces]" ) == true );
3110+ assert( lexer.CompileProduction( compiler, "namespace", "\"namespace\" spaces name:spec spaces toinclude:nblock" ) == true );
3111+ {
3112+ struct ns_stmt_dispatcher
3113+ {
3114+ static inline bool AssignNodeTo( NamespaceStatement *assign_to, const eir::FixedString <char>& attrib, COSNode *node )
3115+ {
3116+ if ( attrib == "name" )
3117+ {
3118+ assign_to->name = node;
3119+ return true;
3120+ }
3121+ if ( attrib == "toinclude" )
3122+ {
3123+ assign_to->toinclude = node;
3124+ return true;
3125+ }
30483126
3127+ return false;
3128+ }
3129+ };
3130+ lexer.GetNamedProduction( "namespace" ).GetLastStep()->setSelector <direct_obj_build_selector <char, NamespaceStatement, COSNode, ns_stmt_dispatcher, decltype(lexer)::RuntimeEnv>> ( &lexer.GetRuntimeEnvironment() );
3131+ }
3132+ assert( lexer.CompileProduction( compiler, "namespace", "\"using\" spaces \"namespace\" spaces nsname:spec spaces ';'" ) == true );
3133+ {
3134+ struct ns_using_dispatcher
3135+ {
3136+ static inline bool AssignNodeTo( UsingNamespaceStatement *assign_to, const eir::FixedString <char>& attrib, COSNode *node )
3137+ {
3138+ if ( attrib == "nsname" )
3139+ {
3140+ assign_to->nsname = node;
3141+ return true;
3142+ }
3143+
3144+ return false;
3145+ }
3146+ };
3147+ lexer.GetNamedProduction( "namespace" ).GetLastStep()->setSelector <direct_obj_build_selector <char, UsingNamespaceStatement, COSNode, ns_using_dispatcher, decltype(lexer)::RuntimeEnv>> ( &lexer.GetRuntimeEnvironment() );
3148+ }
3149+ assert( lexer.CompileProduction( compiler, "enum", "\"enum\" spaces name:spec spaces '{' spaces [item:name (spaces '=' spaces inum:number)^0:1, spaces ',' spaces] spaces '}' spaces ';'" ) == true );
3150+ {
3151+ struct enum_selector
3152+ {
3153+ inline void PushItem( COSNode *val_node )
3154+ {
3155+ EnumDefinition::item entry;
3156+ entry.key = this->curKeyNode;
3157+ entry.value = val_node;
3158+
3159+ this->enumobj.items.AddToBack( std::move( entry ) );
3160+
3161+ this->curKeyNode = nullptr;
3162+ }
3163+
3164+ inline bool AssignNode( const eir::FixedString <char>& attrib, COSNode *node )
3165+ {
3166+ if ( attrib == "name" )
3167+ {
3168+ this->enumobj.name = node;
3169+ return true;
3170+ }
3171+ if ( attrib == "item" )
3172+ {
3173+ if ( this->curKeyNode != nullptr )
3174+ {
3175+ this->PushItem( nullptr );
3176+ }
3177+
3178+ this->curKeyNode = node;
3179+ return true;
3180+ }
3181+ if ( attrib == "inum" )
3182+ {
3183+ this->PushItem( node );
3184+ return true;
3185+ }
3186+
3187+ return false;
3188+ }
3189+
3190+ inline COSNode* DetachFinishedNode( void )
3191+ {
3192+ if ( this->curKeyNode != nullptr )
3193+ {
3194+ this->PushItem( nullptr );
3195+ }
3196+
3197+ return new EnumDefinition( std::move( this->enumobj ) );
3198+ }
3199+
3200+ EnumDefinition enumobj;
3201+ COSNode *curKeyNode = nullptr;
3202+ };
3203+ lexer.GetNamedProduction( "enum" ).GetLastStep()->setSelector <enum_selector> ();
3204+ }
3205+
30493206 printf( "testing COS operations (non-deep)..." );
30503207 {
30513208 assert( lexer.TestProduction( "1 + 1;" ) == true );
@@ -6074,7 +6231,424 @@
60746231
60756232 printf( "testing COS declarations..." );
60766233 {
6077- // TODO.
6234+ // 1)
6235+ {
6236+ COSNode *_root = lexer.StartProduction( "int a=1;" );
6237+
6238+ DeclarationStatement *root = dynamic_cast <DeclarationStatement*> ( _root );
6239+
6240+ assert( root != nullptr );
6241+
6242+ SpecifierString *type = dynamic_cast <SpecifierString*> ( root->type );
6243+
6244+ assert( type != nullptr );
6245+ assert( type->string == "int" );
6246+
6247+ type->Delete();
6248+
6249+ SpecifierString *name = dynamic_cast <SpecifierString*> ( root->name );
6250+
6251+ assert( name != nullptr );
6252+ assert( name->string == "a" );
6253+
6254+ name->Delete();
6255+
6256+ NumberString *init = dynamic_cast <NumberString*> ( root->initializer );
6257+
6258+ assert( init != nullptr );
6259+ assert( init->numeric_string == "1" );
6260+
6261+ init->Delete();
6262+ root->Delete();
6263+ }
6264+
6265+ // 2)
6266+ {
6267+ COSNode *_root = lexer.StartProduction( "int *a = &b;" );
6268+
6269+ DeclarationStatement *root = dynamic_cast <DeclarationStatement*> ( _root );
6270+
6271+ assert( root != nullptr );
6272+
6273+ PointerTypeSpecifier *type = dynamic_cast <PointerTypeSpecifier*> ( root->type );
6274+
6275+ assert( type != nullptr );
6276+
6277+ SpecifierString *type_spec = dynamic_cast <SpecifierString*> ( type->spec );
6278+
6279+ assert( type_spec != nullptr );
6280+ assert( type_spec->string == "int" );
6281+
6282+ type_spec->Delete();
6283+ type->Delete();
6284+
6285+ SpecifierString *name = dynamic_cast <SpecifierString*> ( root->name );
6286+
6287+ assert( name != nullptr );
6288+ assert( name->string == "a" );
6289+
6290+ name->Delete();
6291+
6292+ AddressOfOperation *init = dynamic_cast <AddressOfOperation*> ( root->initializer );
6293+
6294+ assert( init != nullptr );
6295+
6296+ SpecifierString *init_op = dynamic_cast <SpecifierString*> ( init->op );
6297+
6298+ assert( init_op != nullptr );
6299+ assert( init_op->string == "b" );
6300+
6301+ init_op->Delete();
6302+ init->Delete();
6303+ root->Delete();
6304+ }
6305+
6306+ // 3)
6307+ {
6308+ COSNode *_root = lexer.StartProduction( "int **a;" );
6309+
6310+ DeclarationStatement *root = dynamic_cast <DeclarationStatement*> ( _root );
6311+
6312+ assert( root != nullptr );
6313+ assert( root->initializer == nullptr );
6314+
6315+ PointerTypeSpecifier *type = dynamic_cast <PointerTypeSpecifier*> ( root->type );
6316+
6317+ assert( type != nullptr );
6318+
6319+ PointerTypeSpecifier *type_spec = dynamic_cast <PointerTypeSpecifier*> ( type->spec );
6320+
6321+ assert( type_spec != nullptr );
6322+
6323+ SpecifierString *type_spec_spec = dynamic_cast <SpecifierString*> ( type_spec->spec );
6324+
6325+ assert( type_spec_spec != nullptr );
6326+ assert( type_spec_spec->string == "int" );
6327+
6328+ type_spec_spec->Delete();
6329+ type_spec->Delete();
6330+ type->Delete();
6331+
6332+ SpecifierString *name = dynamic_cast <SpecifierString*> ( root->name );
6333+
6334+ assert( name != nullptr );
6335+ assert( name->string == "a" );
6336+
6337+ name->Delete();
6338+ root->Delete();
6339+ }
6340+
6341+ // 4)
6342+ {
6343+ COSNode *_root = lexer.StartProduction( "int arr[]={1, 2, 3};" );
6344+
6345+ DeclarationStatement *root = dynamic_cast <DeclarationStatement*> ( _root );
6346+
6347+ assert( root != nullptr );
6348+
6349+ SpecifierString *name = dynamic_cast <SpecifierString*> ( root->name );
6350+
6351+ assert( name != nullptr );
6352+ assert( name->string == "arr" );
6353+
6354+ name->Delete();
6355+
6356+ ArrayTypeSpecifier *type = dynamic_cast <ArrayTypeSpecifier*> ( root->type );
6357+
6358+ assert( type != nullptr );
6359+ assert( type->array_size_op == nullptr );
6360+
6361+ SpecifierString *type_spec = dynamic_cast <SpecifierString*> ( type->spec );
6362+
6363+ assert( type_spec != nullptr );
6364+ assert( type_spec->string == "int" );
6365+
6366+ type_spec->Delete();
6367+ type->Delete();
6368+
6369+ ArrayDefinition *init = dynamic_cast <ArrayDefinition*> ( root->initializer );
6370+
6371+ assert( init != nullptr );
6372+ assert( init->items.GetCount() == 3 );
6373+
6374+ NumberString *init_0 = dynamic_cast <NumberString*> ( init->items[0] );
6375+
6376+ assert( init_0 != nullptr );
6377+ assert( init_0->numeric_string == "1" );
6378+
6379+ init_0->Delete();
6380+
6381+ NumberString *init_1 = dynamic_cast <NumberString*> ( init->items[1] );
6382+
6383+ assert( init_1 != nullptr );
6384+ assert( init_1->numeric_string == "2" );
6385+
6386+ init_1->Delete();
6387+
6388+ NumberString *init_2 = dynamic_cast <NumberString*> ( init->items[2] );
6389+
6390+ assert( init_2 != nullptr );
6391+ assert( init_2->numeric_string == "3" );
6392+
6393+ init_2->Delete();
6394+ init->Delete();
6395+ root->Delete();
6396+ }
6397+
6398+ // 5)
6399+ {
6400+ COSNode *_root = lexer.StartProduction( "int arr[][] = {{1,2,3},{4,5,6}};" );
6401+
6402+ DeclarationStatement *root = dynamic_cast <DeclarationStatement*> ( _root );
6403+
6404+ assert( root != nullptr );
6405+
6406+ ArrayTypeSpecifier *type = dynamic_cast <ArrayTypeSpecifier*> ( root->type );
6407+
6408+ assert( type != nullptr );
6409+ assert( type->array_size_op == nullptr );
6410+
6411+ ArrayTypeSpecifier *type_spec = dynamic_cast <ArrayTypeSpecifier*> ( type->spec );
6412+
6413+ assert( type_spec != nullptr );
6414+ assert( type_spec->array_size_op == nullptr );
6415+
6416+ SpecifierString *type_spec_spec = dynamic_cast <SpecifierString*> ( type_spec->spec );
6417+
6418+ assert( type_spec_spec != nullptr );
6419+ assert( type_spec_spec->string == "int" );
6420+
6421+ type_spec_spec->Delete();
6422+ type_spec->Delete();
6423+ type->Delete();
6424+
6425+ SpecifierString *name = dynamic_cast <SpecifierString*> ( root->name );
6426+
6427+ assert( name != nullptr );
6428+ assert( name->string == "arr" );
6429+
6430+ name->Delete();
6431+
6432+ ArrayDefinition *init = dynamic_cast <ArrayDefinition*> ( root->initializer );
6433+
6434+ assert( init != nullptr );
6435+ assert( init->items.GetCount() == 2 );
6436+
6437+ ArrayDefinition *init_0 = dynamic_cast <ArrayDefinition*> ( init->items[0] );
6438+
6439+ assert( init_0 != nullptr );
6440+ assert( init_0->items.GetCount() == 3 );
6441+
6442+ NumberString *init_0_0 = dynamic_cast <NumberString*> ( init_0->items[0] );
6443+
6444+ assert( init_0_0 != nullptr );
6445+ assert( init_0_0->numeric_string == "1" );
6446+
6447+ init_0_0->Delete();
6448+
6449+ NumberString *init_0_1 = dynamic_cast <NumberString*> ( init_0->items[1] );
6450+
6451+ assert( init_0_1 != nullptr );
6452+ assert( init_0_1->numeric_string == "2" );
6453+
6454+ init_0_1->Delete();
6455+
6456+ NumberString *init_0_2 = dynamic_cast <NumberString*> ( init_0->items[2] );
6457+
6458+ assert( init_0_2 != nullptr );
6459+ assert( init_0_2->numeric_string == "3" );
6460+
6461+ init_0_2->Delete();
6462+ init_0->Delete();
6463+
6464+ ArrayDefinition *init_1 = dynamic_cast <ArrayDefinition*> ( init->items[1] );
6465+
6466+ assert( init_1 != nullptr );
6467+ assert( init_1->items.GetCount() == 3 );
6468+
6469+ NumberString *init_1_0 = dynamic_cast <NumberString*> ( init_1->items[0] );
6470+
6471+ assert( init_1_0 != nullptr );
6472+ assert( init_1_0->numeric_string == "4" );
6473+
6474+ init_1_0->Delete();
6475+
6476+ NumberString *init_1_1 = dynamic_cast <NumberString*> ( init_1->items[1] );
6477+
6478+ assert( init_1_1 != nullptr );
6479+ assert( init_1_1->numeric_string == "5" );
6480+
6481+ init_1_1->Delete();
6482+
6483+ NumberString *init_1_2 = dynamic_cast <NumberString*> ( init_1->items[2] );
6484+
6485+ assert( init_1_2 != nullptr );
6486+ assert( init_1_2->numeric_string == "6" );
6487+
6488+ init_1_2->Delete();
6489+ init_1->Delete();
6490+ init->Delete();
6491+ root->Delete();
6492+ }
6493+
6494+ // 6)
6495+ {
6496+ COSNode *_root = lexer.StartProduction( "int value(42);" );
6497+
6498+ DeclarationStatement *root = dynamic_cast <DeclarationStatement*> ( _root );
6499+
6500+ assert( root != nullptr );
6501+
6502+ SpecifierString *type = dynamic_cast <SpecifierString*> ( root->type );
6503+
6504+ assert( type != nullptr );
6505+ assert( type->string == "int" );
6506+
6507+ type->Delete();
6508+
6509+ SpecifierString *name = dynamic_cast <SpecifierString*> ( root->name );
6510+
6511+ assert( name != nullptr );
6512+ assert( name->string == "value" );
6513+
6514+ name->Delete();
6515+
6516+ CurlyPack *init = dynamic_cast <CurlyPack*> ( root->initializer );
6517+
6518+ assert( init != nullptr );
6519+ assert( init->params.GetCount() == 1 );
6520+
6521+ NumberString *init_1 = dynamic_cast <NumberString*> ( init->params[0] );
6522+
6523+ assert( init_1 != nullptr );
6524+ assert( init_1->numeric_string == "42" );
6525+
6526+ init_1->Delete();
6527+ init->Delete();
6528+ root->Delete();
6529+ }
6530+
6531+ // 7)
6532+ {
6533+ COSNode *_root = lexer.StartProduction( "namespace lang{int impl = 0;}" );
6534+
6535+ NamespaceStatement *root = dynamic_cast <NamespaceStatement*> ( _root );
6536+
6537+ assert( root != nullptr );
6538+
6539+ SpecifierString *name = dynamic_cast <SpecifierString*> ( root->name );
6540+
6541+ assert( name != nullptr );
6542+ assert( name->string == "lang" );
6543+
6544+ name->Delete();
6545+
6546+ BlockStatement *include = dynamic_cast <BlockStatement*> ( root->toinclude );
6547+
6548+ assert( include != nullptr );
6549+ assert( include->statements.GetCount() == 1 );
6550+
6551+ DeclarationStatement *include_1 = dynamic_cast <DeclarationStatement*> ( include->statements[0] );
6552+
6553+ assert( include_1 != nullptr );
6554+
6555+ SpecifierString *include_1_type = dynamic_cast <SpecifierString*> ( include_1->type );
6556+
6557+ assert( include_1_type != nullptr );
6558+ assert( include_1_type->string == "int" );
6559+
6560+ include_1_type->Delete();
6561+
6562+ SpecifierString *include_1_name = dynamic_cast <SpecifierString*> ( include_1->name );
6563+
6564+ assert( include_1_name != nullptr );
6565+ assert( include_1_name->string == "impl" );
6566+
6567+ include_1_name->Delete();
6568+
6569+ NumberString *include_1_init = dynamic_cast <NumberString*> ( include_1->initializer );
6570+
6571+ assert( include_1_init != nullptr );
6572+ assert( include_1_init->numeric_string == "0" );
6573+
6574+ include_1_init->Delete();
6575+ include_1->Delete();
6576+ include->Delete();
6577+ root->Delete();
6578+ }
6579+
6580+ // 8) left out on purpose.
6581+
6582+ // 9)
6583+ {
6584+ COSNode *_root = lexer.StartProduction( "using namespace ruby;" );
6585+
6586+ UsingNamespaceStatement *root = dynamic_cast <UsingNamespaceStatement*> ( _root );
6587+
6588+ assert( root != nullptr );
6589+
6590+ SpecifierString *nsname = dynamic_cast <SpecifierString*> ( root->nsname );
6591+
6592+ assert( nsname != nullptr );
6593+ assert( nsname->string == "ruby" );
6594+
6595+ nsname->Delete();
6596+ root->Delete();
6597+ }
6598+
6599+ // 10)
6600+ {
6601+ COSNode *_root = lexer.StartProduction( "float3 vec3(12,-7,3);" );
6602+
6603+ DeclarationStatement *root = dynamic_cast <DeclarationStatement*> ( _root );
6604+
6605+ SpecifierString *type = dynamic_cast <SpecifierString*> ( root->type );
6606+
6607+ assert( type != nullptr );
6608+ assert( type->string == "float3" );
6609+
6610+ type->Delete();
6611+
6612+ SpecifierString *name = dynamic_cast <SpecifierString*> ( root->name );
6613+
6614+ assert( name != nullptr );
6615+ assert( name->string == "vec3" );
6616+
6617+ name->Delete();
6618+
6619+ CurlyPack *init = dynamic_cast <CurlyPack*> ( root->initializer );
6620+
6621+ assert( init != nullptr );
6622+ assert( init->params.GetCount() == 3 );
6623+
6624+ NumberString *init_1 = dynamic_cast <NumberString*> ( init->params[0] );
6625+
6626+ assert( init_1 != nullptr );
6627+ assert( init_1->numeric_string == "12" );
6628+
6629+ init_1->Delete();
6630+
6631+ NumericNegationOperation *init_2 = dynamic_cast <NumericNegationOperation*> ( init->params[1] );
6632+
6633+ assert( init_2 != nullptr );
6634+
6635+ NumberString *init_2_op = dynamic_cast <NumberString*> ( init_2->op );
6636+
6637+ assert( init_2_op != nullptr );
6638+ assert( init_2_op->numeric_string == "7" );
6639+
6640+ init_2_op->Delete();
6641+ init_2->Delete();
6642+
6643+ NumberString *init_3 = dynamic_cast <NumberString*> ( init->params[2] );
6644+
6645+ assert( init_3 != nullptr );
6646+ assert( init_3->numeric_string == "3" );
6647+
6648+ init_3->Delete();
6649+ init->Delete();
6650+ root->Delete();
6651+ }
60786652 }
60796653 printf( "ok.\n" );
60806654
Show on old repository browser