source: src/CodeGen/GenType.cc @ 934fa0f

Last change on this file since 934fa0f was 934fa0f, checked in by Andrew Beach <ajbeach@…>, 9 months ago

Translated the demangling code from the old ast to the new ast.

  • Property mode set to 100644
File size: 20.9 KB
Line 
1//
2// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
3//
4// The contents of this file are covered under the licence agreement in the
5// file "LICENCE" distributed with Cforall.
6//
7// GenType.cc --
8//
9// Author           : Richard C. Bilson
10// Created On       : Mon May 18 07:44:20 2015
11// Last Modified By : Andrew Beach
12// Last Modified On : Fri May 20 11:18:00 2022
13// Update Count     : 24
14//
15#include "GenType.h"
16
17#include <cassert>                // for assert, assertf
18#include <list>                   // for _List_iterator, _List_const_iterator
19#include <sstream>                // for operator<<, ostringstream, basic_os...
20
21#include "AST/Print.hpp"          // for print
22#include "AST/Vector.hpp"         // for vector
23#include "CodeGenerator.h"        // for CodeGenerator
24#include "CodeGeneratorNew.hpp"   // for CodeGenerator_new
25#include "Common/UniqueName.h"    // for UniqueName
26#include "SynTree/Declaration.h"  // for DeclarationWithType
27#include "SynTree/Expression.h"   // for Expression
28#include "SynTree/Type.h"         // for PointerType, Type, FunctionType
29#include "SynTree/Visitor.h"      // for Visitor
30
31namespace CodeGen {
32        struct GenType : public WithVisitorRef<GenType>, public WithShortCircuiting {
33                std::string typeString;
34                GenType( const std::string &typeString, const Options &options );
35
36                void previsit( BaseSyntaxNode * );
37                void postvisit( BaseSyntaxNode * );
38
39                void postvisit( FunctionType * funcType );
40                void postvisit( VoidType * voidType );
41                void postvisit( BasicType * basicType );
42                void postvisit( PointerType * pointerType );
43                void postvisit( ArrayType * arrayType );
44                void postvisit( ReferenceType * refType );
45                void postvisit( StructInstType * structInst );
46                void postvisit( UnionInstType * unionInst );
47                void postvisit( EnumInstType * enumInst );
48                void postvisit( TypeInstType * typeInst );
49                void postvisit( TupleType  * tupleType );
50                void postvisit( VarArgsType * varArgsType );
51                void postvisit( ZeroType * zeroType );
52                void postvisit( OneType * oneType );
53                void postvisit( GlobalScopeType * globalType );
54                void postvisit( TraitInstType * inst );
55                void postvisit( TypeofType * typeof );
56                void postvisit( VTableType * vtable );
57                void postvisit( QualifiedType * qualType );
58
59          private:
60                void handleQualifiers( Type *type );
61                std::string handleGeneric( ReferenceToType * refType );
62                void genArray( const Type::Qualifiers &qualifiers, Type *base, Expression *dimension, bool isVarLen, bool isStatic );
63
64                Options options;
65        };
66
67        std::string genType( Type *type, const std::string &baseString, const Options &options ) {
68                PassVisitor<GenType> gt( baseString, options );
69                std::ostringstream os;
70
71                if ( ! type->get_attributes().empty() ) {
72                        PassVisitor<CodeGenerator> cg( os, options );
73                        cg.pass.genAttributes( type->get_attributes() );
74                } // if
75
76                type->accept( gt );
77                return os.str() + gt.pass.typeString;
78        }
79
80        std::string genType( Type *type, const std::string &baseString, bool pretty, bool genC , bool lineMarks ) {
81                return genType( type, baseString, Options(pretty, genC, lineMarks, false ) );
82        }
83
84        std::string genPrettyType( Type * type, const std::string & baseString ) {
85                return genType( type, baseString, true, false );
86        }
87
88        GenType::GenType( const std::string &typeString, const Options &options ) : typeString( typeString ), options( options ) {}
89
90        // *** BaseSyntaxNode
91        void GenType::previsit( BaseSyntaxNode * ) {
92                // turn off automatic recursion for all nodes, to allow each visitor to
93                // precisely control the order in which its children are visited.
94                visit_children = false;
95        }
96
97        void GenType::postvisit( BaseSyntaxNode * node ) {
98                std::stringstream ss;
99                node->print( ss );
100                assertf( false, "Unhandled node reached in GenType: %s", ss.str().c_str() );
101        }
102
103        void GenType::postvisit( VoidType * voidType ) {
104                typeString = "void " + typeString;
105                handleQualifiers( voidType );
106        }
107
108        void GenType::postvisit( BasicType * basicType ) {
109                BasicType::Kind kind = basicType->kind;
110                assert( 0 <= kind && kind < BasicType::NUMBER_OF_BASIC_TYPES );
111                typeString = std::string( BasicType::typeNames[kind] ) + " " + typeString;
112                handleQualifiers( basicType );
113        }
114
115        void GenType::genArray( const Type::Qualifiers & qualifiers, Type * base, Expression *dimension, bool isVarLen, bool isStatic ) {
116                std::ostringstream os;
117                if ( typeString != "" ) {
118                        if ( typeString[ 0 ] == '*' ) {
119                                os << "(" << typeString << ")";
120                        } else {
121                                os << typeString;
122                        } // if
123                } // if
124                os << "[";
125
126                if ( isStatic ) {
127                        os << "static ";
128                } // if
129                if ( qualifiers.is_const ) {
130                        os << "const ";
131                } // if
132                if ( qualifiers.is_volatile ) {
133                        os << "volatile ";
134                } // if
135                if ( qualifiers.is_restrict ) {
136                        os << "__restrict ";
137                } // if
138                if ( qualifiers.is_atomic ) {
139                        os << "_Atomic ";
140                } // if
141                if ( dimension != 0 ) {
142                        PassVisitor<CodeGenerator> cg( os, options );
143                        dimension->accept( cg );
144                } else if ( isVarLen ) {
145                        // no dimension expression on a VLA means it came in with the * token
146                        os << "*";
147                } // if
148                os << "]";
149
150                typeString = os.str();
151
152                base->accept( *visitor );
153        }
154
155        void GenType::postvisit( PointerType * pointerType ) {
156                assert( pointerType->base != 0);
157                if ( pointerType->get_isStatic() || pointerType->get_isVarLen() || pointerType->dimension ) {
158                        genArray( pointerType->get_qualifiers(), pointerType->base, pointerType->dimension, pointerType->get_isVarLen(), pointerType->get_isStatic() );
159                } else {
160                        handleQualifiers( pointerType );
161                        if ( typeString[ 0 ] == '?' ) {
162                                typeString = "* " + typeString;
163                        } else {
164                                typeString = "*" + typeString;
165                        } // if
166                        pointerType->base->accept( *visitor );
167                } // if
168        }
169
170        void GenType::postvisit( ArrayType * arrayType ) {
171                genArray( arrayType->get_qualifiers(), arrayType->base, arrayType->dimension, arrayType->get_isVarLen(), arrayType->get_isStatic() );
172        }
173
174        void GenType::postvisit( ReferenceType * refType ) {
175                assert( 0 != refType->base );
176                assertf( ! options.genC, "Reference types should not reach code generation." );
177                handleQualifiers( refType );
178                typeString = "&" + typeString;
179                refType->base->accept( *visitor );
180        }
181
182        void GenType::postvisit( FunctionType * funcType ) {
183                std::ostringstream os;
184
185                if ( typeString != "" ) {
186                        if ( typeString[ 0 ] == '*' ) {
187                                os << "(" << typeString << ")";
188                        } else {
189                                os << typeString;
190                        } // if
191                } // if
192
193                /************* parameters ***************/
194
195                const std::list<DeclarationWithType *> &pars = funcType->parameters;
196
197                if ( pars.empty() ) {
198                        if ( funcType->get_isVarArgs() ) {
199                                os << "()";
200                        } else {
201                                os << "(void)";
202                        } // if
203                } else {
204                        PassVisitor<CodeGenerator> cg( os, options );
205                        os << "(" ;
206
207                        cg.pass.genCommaList( pars.begin(), pars.end() );
208
209                        if ( funcType->get_isVarArgs() ) {
210                                os << ", ...";
211                        } // if
212                        os << ")";
213                } // if
214
215                typeString = os.str();
216
217                if ( funcType->returnVals.size() == 0 ) {
218                        typeString = "void " + typeString;
219                } else {
220                        funcType->returnVals.front()->get_type()->accept( *visitor );
221                } // if
222
223                // add forall
224                if( ! funcType->forall.empty() && ! options.genC ) {
225                        // assertf( ! genC, "Aggregate type parameters should not reach code generation." );
226                        std::ostringstream os;
227                        PassVisitor<CodeGenerator> cg( os, options );
228                        os << "forall(";
229                        cg.pass.genCommaList( funcType->forall.begin(), funcType->forall.end() );
230                        os << ")" << std::endl;
231                        typeString = os.str() + typeString;
232                }
233        }
234
235        std::string GenType::handleGeneric( ReferenceToType * refType ) {
236                if ( ! refType->parameters.empty() ) {
237                        std::ostringstream os;
238                        PassVisitor<CodeGenerator> cg( os, options );
239                        os << "(";
240                        cg.pass.genCommaList( refType->parameters.begin(), refType->parameters.end() );
241                        os << ") ";
242                        return os.str();
243                }
244                return "";
245        }
246
247        void GenType::postvisit( StructInstType * structInst )  {
248                typeString = structInst->name + handleGeneric( structInst ) + " " + typeString;
249                if ( options.genC ) typeString = "struct " + typeString;
250                handleQualifiers( structInst );
251        }
252
253        void GenType::postvisit( UnionInstType * unionInst ) {
254                typeString = unionInst->name + handleGeneric( unionInst ) + " " + typeString;
255                if ( options.genC ) typeString = "union " + typeString;
256                handleQualifiers( unionInst );
257        }
258
259        void GenType::postvisit( EnumInstType * enumInst ) {
260                if ( enumInst->baseEnum && enumInst->baseEnum->base ) {
261                        typeString = genType(enumInst->baseEnum->base, typeString, options);
262                } else {
263                        typeString = enumInst->name + " " + typeString;
264                        if ( options.genC ) {
265                                typeString = "enum " + typeString;
266                        }
267                }
268                handleQualifiers( enumInst );
269        }
270
271        void GenType::postvisit( TypeInstType * typeInst ) {
272                assertf( ! options.genC, "Type instance types should not reach code generation." );
273                typeString = typeInst->name + " " + typeString;
274                handleQualifiers( typeInst );
275        }
276
277        void GenType::postvisit( TupleType * tupleType ) {
278                assertf( ! options.genC, "Tuple types should not reach code generation." );
279                unsigned int i = 0;
280                std::ostringstream os;
281                os << "[";
282                for ( Type * t : *tupleType ) {
283                        i++;
284                        os << genType( t, "", options ) << (i == tupleType->size() ? "" : ", ");
285                }
286                os << "] ";
287                typeString = os.str() + typeString;
288        }
289
290        void GenType::postvisit( VarArgsType * varArgsType ) {
291                typeString = "__builtin_va_list " + typeString;
292                handleQualifiers( varArgsType );
293        }
294
295        void GenType::postvisit( ZeroType * zeroType ) {
296                // ideally these wouldn't hit codegen at all, but should be safe to make them ints
297                typeString = (options.pretty ? "zero_t " : "long int ") + typeString;
298                handleQualifiers( zeroType );
299        }
300
301        void GenType::postvisit( OneType * oneType ) {
302                // ideally these wouldn't hit codegen at all, but should be safe to make them ints
303                typeString = (options.pretty ? "one_t " : "long int ") + typeString;
304                handleQualifiers( oneType );
305        }
306
307        void GenType::postvisit( GlobalScopeType * globalType ) {
308                assertf( ! options.genC, "Global scope type should not reach code generation." );
309                handleQualifiers( globalType );
310        }
311
312        void GenType::postvisit( TraitInstType * inst ) {
313                assertf( ! options.genC, "Trait types should not reach code generation." );
314                typeString = inst->name + " " + typeString;
315                handleQualifiers( inst );
316        }
317
318        void GenType::postvisit( TypeofType * typeof ) {
319                std::ostringstream os;
320                PassVisitor<CodeGenerator> cg( os, options );
321                os << "typeof(";
322                typeof->expr->accept( cg );
323                os << ") " << typeString;
324                typeString = os.str();
325                handleQualifiers( typeof );
326        }
327
328        void GenType::postvisit( VTableType * vtable ) {
329                assertf( ! options.genC, "Virtual table types should not reach code generation." );
330                std::ostringstream os;
331                os << "vtable(" << genType( vtable->base, "", options ) << ") " << typeString;
332                typeString = os.str();
333                handleQualifiers( vtable );
334        }
335
336        void GenType::postvisit( QualifiedType * qualType ) {
337                assertf( ! options.genC, "Qualified types should not reach code generation." );
338                std::ostringstream os;
339                os << genType( qualType->parent, "", options ) << "." << genType( qualType->child, "", options ) << typeString;
340                typeString = os.str();
341                handleQualifiers( qualType );
342        }
343
344        void GenType::handleQualifiers( Type * type ) {
345                if ( type->get_const() ) {
346                        typeString = "const " + typeString;
347                } // if
348                if ( type->get_volatile() ) {
349                        typeString = "volatile " + typeString;
350                } // if
351                if ( type->get_restrict() ) {
352                        typeString = "__restrict " + typeString;
353                } // if
354                if ( type->get_atomic() ) {
355                        typeString = "_Atomic " + typeString;
356                } // if
357        }
358
359namespace {
360
361#warning Remove the _new when old version is removed.
362struct GenType_new :
363                public ast::WithShortCircuiting,
364                public ast::WithVisitorRef<GenType_new> {
365        std::string result;
366        GenType_new( const std::string &typeString, const Options &options );
367
368        void previsit( ast::Node const * );
369        void postvisit( ast::Node const * );
370
371        void postvisit( ast::FunctionType const * type );
372        void postvisit( ast::VoidType const * type );
373        void postvisit( ast::BasicType const * type );
374        void postvisit( ast::PointerType const * type );
375        void postvisit( ast::ArrayType const * type );
376        void postvisit( ast::ReferenceType const * type );
377        void postvisit( ast::StructInstType const * type );
378        void postvisit( ast::UnionInstType const * type );
379        void postvisit( ast::EnumInstType const * type );
380        void postvisit( ast::TypeInstType const * type );
381        void postvisit( ast::TupleType const * type );
382        void postvisit( ast::VarArgsType const * type );
383        void postvisit( ast::ZeroType const * type );
384        void postvisit( ast::OneType const * type );
385        void postvisit( ast::GlobalScopeType const * type );
386        void postvisit( ast::TraitInstType const * type );
387        void postvisit( ast::TypeofType const * type );
388        void postvisit( ast::VTableType const * type );
389        void postvisit( ast::QualifiedType const * type );
390
391private:
392        void handleQualifiers( ast::Type const *type );
393        std::string handleGeneric( ast::BaseInstType const * type );
394        void genArray( const ast::CV::Qualifiers &qualifiers, ast::Type const *base, ast::Expr const *dimension, bool isVarLen, bool isStatic );
395        std::string genParamList( const ast::vector<ast::Type> & );
396
397        Options options;
398};
399
400GenType_new::GenType_new( const std::string &typeString, const Options &options ) : result( typeString ), options( options ) {}
401
402void GenType_new::previsit( ast::Node const * ) {
403        // Turn off automatic recursion for all nodes, to allow each visitor to
404        // precisely control the order in which its children are visited.
405        visit_children = false;
406}
407
408void GenType_new::postvisit( ast::Node const * node ) {
409        std::stringstream ss;
410        ast::print( ss, node );
411        assertf( false, "Unhandled node reached in GenType: %s", ss.str().c_str() );
412}
413
414void GenType_new::postvisit( ast::VoidType const * type ) {
415        result = "void " + result;
416        handleQualifiers( type );
417}
418
419void GenType_new::postvisit( ast::BasicType const * type ) {
420        ast::BasicType::Kind kind = type->kind;
421        assert( 0 <= kind && kind < ast::BasicType::NUMBER_OF_BASIC_TYPES );
422        result = std::string( ast::BasicType::typeNames[kind] ) + " " + result;
423        handleQualifiers( type );
424}
425
426void GenType_new::genArray( const ast::CV::Qualifiers & qualifiers, ast::Type const * base, ast::Expr const *dimension, bool isVarLen, bool isStatic ) {
427        std::ostringstream os;
428        if ( result != "" ) {
429                if ( result[ 0 ] == '*' ) {
430                        os << "(" << result << ")";
431                } else {
432                        os << result;
433                }
434        }
435        os << "[";
436        if ( isStatic ) {
437                os << "static ";
438        }
439        if ( qualifiers.is_const ) {
440                os << "const ";
441        }
442        if ( qualifiers.is_volatile ) {
443                os << "volatile ";
444        }
445        if ( qualifiers.is_restrict ) {
446                os << "__restrict ";
447        }
448        if ( qualifiers.is_atomic ) {
449                os << "_Atomic ";
450        }
451        if ( dimension != 0 ) {
452                ast::Pass<CodeGenerator_new>::read( dimension, os, options );
453        } else if ( isVarLen ) {
454                // no dimension expression on a VLA means it came in with the * token
455                os << "*";
456        }
457        os << "]";
458
459        result = os.str();
460
461        base->accept( *visitor );
462}
463
464void GenType_new::postvisit( ast::PointerType const * type ) {
465        if ( type->isStatic || type->isVarLen || type->dimension ) {
466                genArray( type->qualifiers, type->base, type->dimension, type->isVarLen, type->isStatic );
467        } else {
468                handleQualifiers( type );
469                if ( result[ 0 ] == '?' ) {
470                        result = "* " + result;
471                } else {
472                        result = "*" + result;
473                }
474                type->base->accept( *visitor );
475        }
476}
477
478void GenType_new::postvisit( ast::ArrayType const * type ) {
479        genArray( type->qualifiers, type->base, type->dimension, type->isVarLen, type->isStatic );
480}
481
482void GenType_new::postvisit( ast::ReferenceType const * type ) {
483        assertf( !options.genC, "Reference types should not reach code generation." );
484        handleQualifiers( type );
485        result = "&" + result;
486        type->base->accept( *visitor );
487}
488
489void GenType_new::postvisit( ast::FunctionType const * type ) {
490        std::ostringstream os;
491
492        if ( result != "" ) {
493                if ( result[ 0 ] == '*' ) {
494                        os << "(" << result << ")";
495                } else {
496                        os << result;
497                }
498        }
499
500        if ( type->params.empty() ) {
501                if ( type->isVarArgs ) {
502                        os << "()";
503                } else {
504                        os << "(void)";
505                }
506        } else {
507                os << "(" ;
508
509                os << genParamList( type->params );
510
511                if ( type->isVarArgs ) {
512                        os << ", ...";
513                }
514                os << ")";
515        }
516
517        result = os.str();
518
519        if ( type->returns.size() == 0 ) {
520                result = "void " + result;
521        } else {
522                type->returns.front()->accept( *visitor );
523        }
524
525        // Add forall clause.
526        if( !type->forall.empty() && !options.genC ) {
527                //assertf( !options.genC, "FunctionDecl type parameters should not reach code generation." );
528                std::ostringstream os;
529                ast::Pass<CodeGenerator_new> cg( os, options );
530                os << "forall(";
531                cg.core.genCommaList( type->forall );
532                os << ")" << std::endl;
533                result = os.str() + result;
534        }
535}
536
537std::string GenType_new::handleGeneric( ast::BaseInstType const * type ) {
538        if ( !type->params.empty() ) {
539                std::ostringstream os;
540                ast::Pass<CodeGenerator_new> cg( os, options );
541                os << "(";
542                cg.core.genCommaList( type->params );
543                os << ") ";
544                return os.str();
545        }
546        return "";
547}
548
549void GenType_new::postvisit( ast::StructInstType const * type )  {
550        result = type->name + handleGeneric( type ) + " " + result;
551        if ( options.genC ) result = "struct " + result;
552        handleQualifiers( type );
553}
554
555void GenType_new::postvisit( ast::UnionInstType const * type ) {
556        result = type->name + handleGeneric( type ) + " " + result;
557        if ( options.genC ) result = "union " + result;
558        handleQualifiers( type );
559}
560
561void GenType_new::postvisit( ast::EnumInstType const * type ) {
562        if ( type->base && type->base->base ) {
563                result = genType( type->base->base, result, options );
564        } else {
565                result = type->name + " " + result;
566                if ( options.genC ) {
567                        result = "enum " + result;
568                }
569        }
570        handleQualifiers( type );
571}
572
573void GenType_new::postvisit( ast::TypeInstType const * type ) {
574        assertf( !options.genC, "TypeInstType should not reach code generation." );
575        result = type->name + " " + result;
576        handleQualifiers( type );
577}
578
579void GenType_new::postvisit( ast::TupleType const * type ) {
580        assertf( !options.genC, "TupleType should not reach code generation." );
581        unsigned int i = 0;
582        std::ostringstream os;
583        os << "[";
584        for ( ast::ptr<ast::Type> const & t : type->types ) {
585                i++;
586                os << genType( t, "", options ) << (i == type->size() ? "" : ", ");
587        }
588        os << "] ";
589        result = os.str() + result;
590}
591
592void GenType_new::postvisit( ast::VarArgsType const * type ) {
593        result = "__builtin_va_list " + result;
594        handleQualifiers( type );
595}
596
597void GenType_new::postvisit( ast::ZeroType const * type ) {
598        // Ideally these wouldn't hit codegen at all, but should be safe to make them ints.
599        result = (options.pretty ? "zero_t " : "long int ") + result;
600        handleQualifiers( type );
601}
602
603void GenType_new::postvisit( ast::OneType const * type ) {
604        // Ideally these wouldn't hit codegen at all, but should be safe to make them ints.
605        result = (options.pretty ? "one_t " : "long int ") + result;
606        handleQualifiers( type );
607}
608
609void GenType_new::postvisit( ast::GlobalScopeType const * type ) {
610        assertf( !options.genC, "GlobalScopeType should not reach code generation." );
611        handleQualifiers( type );
612}
613
614void GenType_new::postvisit( ast::TraitInstType const * type ) {
615        assertf( !options.genC, "TraitInstType should not reach code generation." );
616        result = type->name + " " + result;
617        handleQualifiers( type );
618}
619
620void GenType_new::postvisit( ast::TypeofType const * type ) {
621        std::ostringstream os;
622        os << "typeof(";
623        ast::Pass<CodeGenerator_new>::read( type, os, options );
624        os << ") " << result;
625        result = os.str();
626        handleQualifiers( type );
627}
628
629void GenType_new::postvisit( ast::VTableType const * type ) {
630        assertf( !options.genC, "Virtual table types should not reach code generation." );
631        std::ostringstream os;
632        os << "vtable(" << genType( type->base, "", options ) << ") " << result;
633        result = os.str();
634        handleQualifiers( type );
635}
636
637void GenType_new::postvisit( ast::QualifiedType const * type ) {
638        assertf( !options.genC, "QualifiedType should not reach code generation." );
639        std::ostringstream os;
640        os << genType( type->parent, "", options ) << "." << genType( type->child, "", options ) << result;
641        result = os.str();
642        handleQualifiers( type );
643}
644
645void GenType_new::handleQualifiers( ast::Type const * type ) {
646        if ( type->is_const() ) {
647                result = "const " + result;
648        }
649        if ( type->is_volatile() ) {
650                result = "volatile " + result;
651        }
652        if ( type->is_restrict() ) {
653                result = "__restrict " + result;
654        }
655        if ( type->is_atomic() ) {
656                result = "_Atomic " + result;
657        }
658}
659
660std::string GenType_new::genParamList( const ast::vector<ast::Type> & range ) {
661        auto cur = range.begin();
662        auto end = range.end();
663        if ( cur == end ) return "";
664        std::ostringstream oss;
665        UniqueName param( "__param_" );
666        while ( true ) {
667                oss << genType( *cur++, options.genC ? param.newName() : "", options );
668                if ( cur == end ) break;
669                oss << ", ";
670        }
671        return oss.str();
672}
673
674} // namespace
675
676std::string genType( ast::Type const * type, const std::string & base, const Options & options ) {
677        std::ostringstream os;
678        if ( !type->attributes.empty() ) {
679                ast::Pass<CodeGenerator_new> cg( os, options );
680                cg.core.genAttributes( type->attributes );
681        }
682
683        return os.str() + ast::Pass<GenType_new>::read( type, base, options );
684}
685
686std::string genTypeNoAttr( ast::Type const * type, const std::string & base, const Options & options ) {
687        return ast::Pass<GenType_new>::read( type, base, options );
688}
689
690} // namespace CodeGen
691
692// Local Variables: //
693// tab-width: 4 //
694// mode: c++ //
695// compile-command: "make install" //
696// End: //
Note: See TracBrowser for help on using the repository browser.