source: src/CodeTools/ResolvProtoDump.cc@ 72b0573

ADT aaron-thesis arm-eh ast-experimental cleanup-dtors deferred_resn enum forall-pointer-decay jacob/cs343-translation jenkins-sandbox new-ast new-ast-unique-expr no_list persistent-indexer pthread-emulation qualifiedEnum
Last change on this file since 72b0573 was a4a000d, checked in by Aaron Moss <a3moss@…>, 7 years ago

support assertions in RPDump

  • Property mode set to 100644
File size: 20.7 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// ResolvProtoDump.cc -- Translates CFA resolver instances into resolv-proto instances
8//
9// Author : Aaron Moss
10// Created On : Tue Sep 11 09:04:00 2018
11// Last Modified By : Aaron Moss
12// Last Modified On : Tue Sep 11 09:04:00 2018
13// Update Count : 1
14//
15
16#include <algorithm>
17#include <cctype>
18#include <iostream>
19#include <memory>
20#include <list>
21#include <set>
22#include <sstream>
23#include <string>
24#include <unordered_set>
25#include <utility>
26#include <vector>
27
28#include "Common/PassVisitor.h"
29#include "Common/utility.h"
30#include "CodeGen/OperatorTable.h"
31#include "SynTree/Declaration.h"
32#include "SynTree/Expression.h"
33#include "SynTree/Initializer.h"
34#include "SynTree/Statement.h"
35#include "SynTree/Type.h"
36
37namespace CodeTools {
38
39 /// Visitor for dumping resolver prototype output
40 class ProtoDump : public WithShortCircuiting, public WithVisitorRef<ProtoDump> {
41 std::set<std::string> decls; ///< Declarations in this scope
42 std::vector<std::string> exprs; ///< Expressions in this scope
43 std::vector<ProtoDump> subs; ///< Sub-scopes
44 std::unordered_set<std::string> closed; ///< Closed type variables
45 const ProtoDump* parent; ///< Outer lexical scope
46 std::unique_ptr<Type> rtnType; ///< Return type for this scope
47
48 public:
49 /// Default constructor for root ProtoDump
50 ProtoDump() : decls(), exprs(), subs(), closed(), parent(nullptr), rtnType(nullptr) {}
51
52 /// Child constructor
53 ProtoDump(const ProtoDump* p, Type* r)
54 : decls(), exprs(), subs(), closed(p->closed), parent(p), rtnType(r) {}
55
56 // Fix copy issues
57 ProtoDump(const ProtoDump& o)
58 : decls(o.decls), exprs(o.exprs), subs(o.subs), closed(o.closed), parent(o.parent),
59 rtnType(maybeClone(o.rtnType.get())) {}
60 ProtoDump( ProtoDump&& ) = default;
61
62 ProtoDump& operator= (const ProtoDump& o) {
63 if ( this == &o ) return *this;
64
65 decls = o.decls;
66 exprs = o.exprs;
67 subs = o.subs;
68 closed = o.closed;
69 parent = o.parent;
70 rtnType.reset( maybeClone(o.rtnType.get()) );
71
72 return *this;
73 }
74 ProtoDump& operator= (ProtoDump&&) = default;
75
76 private:
77 /// checks if this declaration is contained in the scope or one of its parents
78 bool hasDecl( const std::string& s ) const {
79 if ( decls.count( s ) ) return true;
80 if ( parent ) return parent->hasDecl( s );
81 return false;
82 }
83
84 /// adds a new declaration to this scope, providing it does not already exist
85 void addDecl( const std::string& s ) {
86 if ( ! hasDecl( s ) ) decls.insert( s );
87 }
88
89 /// adds a new expression to this scope
90 void addExpr( const std::string& s ) {
91 if ( ! s.empty() ) { exprs.emplace_back( s ); }
92 }
93
94 /// adds a new subscope to this scope, returning a reference
95 void addSub( PassVisitor<ProtoDump>&& sub ) {
96 subs.emplace_back( std::move(sub.pass) );
97 }
98
99 /// Whether lists should be separated, terminated, or preceded by their separator
100 enum septype { separated, terminated, preceded };
101
102 /// builds space-separated list of types
103 template<typename V>
104 static void build( V& visitor, const std::list< Type* >& tys, std::stringstream& ss,
105 septype mode = separated ) {
106 if ( tys.empty() ) return;
107
108 if ( mode == preceded ) { ss << ' '; }
109
110 auto it = tys.begin();
111 (*it)->accept( visitor );
112
113 while ( ++it != tys.end() ) {
114 ss << ' ';
115 (*it)->accept( visitor );
116 }
117
118 if ( mode == terminated ) { ss << ' '; }
119 }
120
121 /// builds list of types wrapped as tuple type
122 template<typename V>
123 static void buildAsTuple( V& visitor, const std::list< Type* >& tys,
124 std::stringstream& ss ) {
125 switch ( tys.size() ) {
126 case 0: ss << "#void"; break;
127 case 1: tys.front()->accept( visitor ); break;
128 default:
129 ss << "#$" << tys.size() << '<';
130 build( visitor, tys, ss );
131 ss << '>';
132 break;
133 }
134 }
135
136 /// gets types from DWT list
137 static std::list< Type* > from_decls( const std::list< DeclarationWithType* >& decls ) {
138 std::list< Type* > tys;
139 for ( auto decl : decls ) { tys.emplace_back( decl->get_type() ); }
140 return tys;
141 }
142
143 /// gets types from TypeExpr list
144 static std::list< Type* > from_exprs( const std::list< Expression* >& exprs ) {
145 std::list< Type* > tys;
146 for ( auto expr : exprs ) {
147 if ( TypeExpr* tyExpr = dynamic_cast<TypeExpr*>(expr) ) {
148 tys.emplace_back( tyExpr->type );
149 }
150 }
151 return tys;
152 }
153
154 /// builds prefixes for rp_name
155 static std::string new_prefix( const std::string& old, const char* added ) {
156 if ( old.empty() ) return std::string{"$"} + added;
157 return old + added;
158 }
159
160 /// shortens operator names
161 static void op_name( const std::string& name, std::stringstream& ss ) {
162 if ( name.compare( 0, 10, "_operator_" ) == 0 ) {
163 ss << name.substr(10);
164 } else if ( name.compare( "_constructor" ) == 0
165 || name.compare( "_destructor" ) == 0 ) {
166 ss << name.substr(1);
167 } else if ( name.compare( 0, 11, "__operator_" ) == 0 ) {
168 ss << name.substr(11);
169 } else {
170 ss << name;
171 }
172 }
173
174 /// replaces operators with resolv-proto names
175 static void rp_name( const std::string& name, std::stringstream& ss,
176 std::string&& pre = "" ) {
177 // safety check for anonymous names
178 if ( name.empty() ) {
179 ss << new_prefix(pre, "anon");
180 return;
181 }
182
183 // replace operator names
184 CodeGen::OperatorInfo info;
185 if ( CodeGen::operatorLookup( name, info ) ) {
186 ss << new_prefix(pre, "");
187 op_name( info.outputName, ss );
188 return;
189 }
190
191 // replace retval names
192 if ( name.compare( 0, 8, "_retval_" ) == 0 ) {
193 ss << new_prefix(pre, "rtn_");
194 op_name( name.substr(8), ss );
195 return;
196 }
197
198 // default to just name
199 ss << pre << name;
200 }
201
202 /// ensures type inst names are uppercase
203 static void ti_name( const std::string& name, std::stringstream& ss ) {
204 ss << (char)std::toupper( static_cast<unsigned char>(name[0]) )
205 << (name.c_str() + 1);
206 }
207
208 /// Visitor for printing types
209 struct TypePrinter : public WithShortCircuiting, WithVisitorRef<TypePrinter>, WithGuards {
210 std::stringstream& ss; ///< Output to print to
211 const std::unordered_set<std::string>& closed; ///< Closed type variables
212 unsigned depth; ///< Depth of nesting from root type
213
214 TypePrinter( const std::unordered_set<std::string>& closed, std::stringstream& ss )
215 : ss(ss), closed(closed), depth(0) {}
216
217 // basic type represented as integer type
218 // TODO maybe hard-code conversion graph and make named type
219 void previsit( BasicType* bt ) { ss << (int)bt->get_kind(); }
220
221 // pointers represented as generic type
222 // TODO except pointer to function
223 void previsit( PointerType* ) { ss << "#$ptr<"; ++depth; }
224 void postvisit( PointerType* ) { --depth; ss << '>'; }
225
226 // arrays represented as generic type
227 void previsit( ArrayType* at ) {
228 ss << "#$arr<";
229 ++depth;
230 at->base->accept( *visitor );
231 --depth;
232 ss << '>';
233 visit_children = false;
234 }
235
236 // ignore top-level reference types, they're mostly transparent to resolution
237 void previsit( ReferenceType* ) {
238 if ( depth > 0 ) { ss << "#$ref<"; }
239 ++depth;
240 }
241 void postvisit( ReferenceType* ) {
242 --depth;
243 if ( depth > 0 ) { ss << '>'; }
244 }
245
246 // encode function type as a 2-param generic type
247 // TODO handle forall functions
248 void previsit( FunctionType* ft ) {
249 ss << "#$fn<";
250 ++depth;
251 buildAsTuple( *visitor, from_decls( ft->returnVals ), ss );
252 ss << ' ';
253 buildAsTuple( *visitor, from_decls( ft->parameters ), ss );
254 --depth;
255 ss << '>';
256 visit_children = false;
257 }
258
259 private:
260 // prints aggregate type name as NamedType with optional paramters
261 void handleAggregate( ReferenceToType* at ) {
262 ss << '#' << at->name;
263 if ( ! at->parameters.empty() ) {
264 ss << '<';
265 ++depth;
266 build( *visitor, from_exprs( at->parameters ), ss );
267 --depth;
268 ss << '>';
269 }
270 visit_children = false;
271 }
272
273 public:
274 // handle aggregate types using NamedType
275 void previsit( StructInstType* st ) { handleAggregate( st ); }
276 void previsit( UnionInstType* ut ) { handleAggregate( ut ); }
277
278 // replace enums with int
279 void previsit( EnumInstType* ) { ss << (int)BasicType::SignedInt; }
280
281 void previsit( TypeInstType* vt ) {
282 // print closed variables as named types
283 if ( closed.count( vt->name ) ) { ss << '#' << vt->name; }
284 // otherwise make sure first letter is capitalized
285 else { ti_name( vt->name, ss ); }
286 }
287
288 // flattens empty and singleton tuples
289 void previsit( TupleType* tt ) {
290 ++depth;
291 buildAsTuple( *visitor, tt->types, ss );
292 --depth;
293 visit_children = false;
294 }
295
296 // TODO support VarArgsType
297
298 // replace 0 and 1 with int
299 // TODO support 0 and 1 with their proper type names and conversions
300 void previsit( ZeroType* ) { ss << (int)BasicType::SignedInt; }
301 void previsit( OneType* ) { ss << (int)BasicType::SignedInt; }
302
303 // only print void type if not at top level
304 void previsit( VoidType* ) {
305 if ( depth > 0 ) { ss << "#void"; }
306 }
307 };
308
309 /// builds description of function
310 void build( const std::string& name, FunctionType* fnTy, std::stringstream& ss ) {
311 PassVisitor<TypePrinter> printTy{ closed, ss };
312 // print return values
313 build( printTy, from_decls( fnTy->returnVals ), ss, terminated );
314 // print name
315 rp_name( name, ss );
316 // print parameters
317 build( printTy, from_decls( fnTy->parameters ), ss, preceded );
318 // print assertions
319 for ( TypeDecl* tyvar : fnTy->forall ) {
320 for ( DeclarationWithType* assn : tyvar->assertions ) {
321 ss << " | ";
322 build( assn->name, assn->get_type(), ss );
323 }
324 }
325 }
326
327 /// builds description of a variable (falls back to function if function type)
328 void build( const std::string& name, Type* ty, std::stringstream& ss ) {
329 // ignore top-level references
330 Type *norefs = ty->stripReferences();
331
332 // fall back to function declaration if function type
333 if ( PointerType* pTy = dynamic_cast< PointerType* >(norefs) ) {
334 if ( FunctionType* fnTy = dynamic_cast< FunctionType* >(pTy->base) ) {
335 build( name, fnTy, ss );
336 return;
337 }
338 } else if ( FunctionType* fnTy = dynamic_cast< FunctionType* >(norefs) ) {
339 build( name, fnTy, ss );
340 return;
341 }
342
343 // print variable declaration as zero-arg function
344 PassVisitor<TypePrinter> printTy{ closed, ss };
345 norefs->accept( printTy );
346 ss << ' ';
347 rp_name( name, ss );
348 }
349
350 /// builds description of a field access
351 void build( const std::string& name, AggregateDecl* agg, Type* ty, std::stringstream& ss ) {
352 // ignore top-level references
353 Type *norefs = ty->stripReferences();
354
355 // print access as new field name
356 PassVisitor<TypePrinter> printTy{ closed, ss };
357 norefs->accept( printTy );
358 ss << ' ';
359 rp_name( name, ss, "$field_" );
360 ss << " #" << agg->name;
361 // handle type parameters
362 if ( ! agg->parameters.empty() ) {
363 ss << '<';
364 auto it = agg->parameters.begin();
365 while (true) {
366 ti_name( (*it)->name, ss );
367 if ( ++it == agg->parameters.end() ) break;
368 ss << ' ';
369 }
370 ss << '>';
371 }
372 }
373
374 /// Visitor for printing expressions
375 struct ExprPrinter : WithShortCircuiting, WithVisitorRef<ExprPrinter> {
376 // TODO change interface to generate multiple expression candidates
377 const std::unordered_set<std::string>& closed; ///< set of closed type vars
378 std::stringstream& ss; ///< Output to print to
379
380 ExprPrinter( const std::unordered_set<std::string>& closed, std::stringstream& ss )
381 : closed(closed), ss(ss) {}
382
383 /// Names handled as nullary function calls
384 void previsit( NameExpr* expr ) {
385 rp_name( expr->name, ss );
386 ss << "()";
387 }
388
389 /// Calls handled as calls
390 void previsit( UntypedExpr* expr ) {
391 // TODO handle name extraction more generally
392 NameExpr* name = dynamic_cast<NameExpr*>(expr->function);
393
394 // fall back on just resolving call to function name
395 // TODO incorporate function type into resolv-proto
396 if ( ! name ) {
397 expr->function->accept( *visitor );
398 visit_children = false;
399 return;
400 }
401
402 rp_name( name->name, ss );
403 if ( expr->args.empty() ) {
404 ss << "()";
405 } else {
406 ss << "( ";
407 auto it = expr->args.begin();
408 while (true) {
409 (*it)->accept( *visitor );
410 if ( ++it == expr->args.end() ) break;
411 ss << ' ';
412 }
413 ss << " )";
414 }
415 visit_children = false;
416 }
417
418 /// Address-of handled as operator
419 void previsit( AddressExpr* expr ) {
420 ss << "$addr( ";
421 expr->arg->accept( *visitor );
422 ss << " )";
423 visit_children = false;
424 }
425
426 /// Casts replaced with result type
427 /// TODO put cast target functions in, and add second expression for target
428 void previsit( CastExpr* cast ) {
429 PassVisitor<TypePrinter> tyPrinter{ closed, ss };
430 cast->result->accept( tyPrinter );
431 visit_children = false;
432 }
433
434 /// Member access handled as function from aggregate to member
435 void previsit( UntypedMemberExpr* expr ) {
436 // TODO handle name extraction more generally
437 NameExpr* name = dynamic_cast<NameExpr*>(expr->member);
438
439 // fall back on just resolving call to member name
440 // TODO incorporate function type into resolv-proto
441 if ( ! name ) {
442 expr->member->accept( *visitor );
443 visit_children = false;
444 return;
445 }
446
447 rp_name( name->name, ss, "$field_" );
448 ss << "( ";
449 expr->aggregate->accept( *visitor );
450 ss << " )";
451 visit_children = false;
452 }
453
454 /// Constant expression replaced by its type
455 void previsit( ConstantExpr* expr ) {
456 PassVisitor<TypePrinter> tyPrinter{ closed, ss };
457 expr->constant.get_type()->accept( tyPrinter );
458 visit_children = false;
459 }
460
461 /// sizeof( ... ), alignof( ... ), offsetof( ... ) replaced by unsigned long constant
462 /// TODO extra expression to resolve argument
463 void previsit( SizeofExpr* ) {
464 ss << (int)BasicType::LongUnsignedInt;
465 visit_children = false;
466 }
467 void previsit( AlignofExpr* ) {
468 ss << (int)BasicType::LongUnsignedInt;
469 visit_children = false;
470 }
471 void previsit( UntypedOffsetofExpr* ) {
472 ss << (int)BasicType::LongUnsignedInt;
473 visit_children = false;
474 }
475
476 /// Logical expressions represented as operators
477 void previsit( LogicalExpr* expr ) {
478 ss << '$' << ( expr->get_isAnd() ? "and" : "or" ) << "( ";
479 expr->arg1->accept( *visitor );
480 ss << ' ';
481 expr->arg2->accept( *visitor );
482 ss << " )";
483 visit_children = false;
484 }
485
486 /// Conditional expression represented as operator
487 void previsit( ConditionalExpr* expr ) {
488 ss << "$if( ";
489 expr->arg1->accept( *visitor );
490 ss << ' ';
491 expr->arg2->accept( *visitor );
492 ss << ' ';
493 expr->arg3->accept( *visitor );
494 ss << " )";
495 visit_children = false;
496 }
497
498 /// Comma expression represented as operator
499 void previsit( CommaExpr* expr ) {
500 ss << "$seq( ";
501 expr->arg1->accept( *visitor );
502 ss << ' ';
503 expr->arg2->accept( *visitor );
504 ss << " )";
505 visit_children = false;
506 }
507
508 // TODO handle ignored ImplicitCopyCtorExpr and below
509 };
510
511 void build( Initializer* init, std::stringstream& ss ) {
512 if ( SingleInit* si = dynamic_cast<SingleInit*>(init) ) {
513 PassVisitor<ExprPrinter> exprPrinter{ closed, ss };
514 si->value->accept( exprPrinter );
515 ss << ' ';
516 } else if ( ListInit* li = dynamic_cast<ListInit*>(init) ) {
517 for ( Initializer* it : li->initializers ) {
518 build( it, ss );
519 ss << ' ';
520 }
521 }
522 }
523
524 /// Adds an object initializer to the list of expressions
525 void build( const std::string& name, Initializer* init, std::stringstream& ss ) {
526 ss << "$constructor( ";
527 rp_name( name, ss );
528 ss << "() ";
529 build( init, ss );
530 ss << ')';
531 }
532
533 /// Adds a return expression to the list of expressions
534 void build( Type* rtnType, Expression* expr, std::stringstream& ss ) {
535 ss << "$constructor( ";
536 PassVisitor<TypePrinter> tyPrinter{ closed, ss };
537 rtnType->accept( tyPrinter );
538 ss << ' ';
539 PassVisitor<ExprPrinter> exprPrinter{ closed, ss };
540 expr->accept( exprPrinter );
541 ss << " )";
542 }
543
544 /// Adds all named declarations in a list to the local scope
545 void addAll( const std::list<DeclarationWithType*>& decls ) {
546 for ( auto decl : decls ) {
547 // skip anonymous decls
548 if ( decl->name.empty() ) continue;
549
550 // handle objects
551 if ( ObjectDecl* obj = dynamic_cast< ObjectDecl* >( decl ) ) {
552 previsit( obj );
553 }
554 }
555 }
556
557 /// encode field access as function
558 void addAggregateFields( AggregateDecl* agg ) {
559 // make field names functions
560 for ( Declaration* member : agg->members ) {
561 if ( ObjectDecl* obj = dynamic_cast< ObjectDecl* >(member) ) {
562 std::stringstream ss;
563 build( obj->name, agg, obj->type, ss );
564 addDecl( ss.str() );
565 }
566 }
567
568 visit_children = false;
569 }
570
571 public:
572 void previsit( ObjectDecl *obj ) {
573 // add variable as declaration
574 std::stringstream ss;
575 build( obj->name, obj->type, ss );
576 addDecl( ss.str() );
577
578 // add initializer as expression if applicable
579 if ( obj->init ) {
580 std::stringstream ss;
581 build( obj->name, obj->init, ss );
582 addExpr( ss.str() );
583 }
584 }
585
586 void previsit( FunctionDecl *decl ) {
587 // add function as declaration
588 std::stringstream ss;
589 build( decl->name, decl->type, ss );
590 addDecl( ss.str() );
591
592 // add body if available
593 if ( decl->statements ) {
594 std::list<Type*> rtns = from_decls( decl->type->returnVals );
595 Type* rtn = nullptr;
596 if ( rtns.size() == 1 ) {
597 if ( ! dynamic_cast<VoidType*>(rtns.front()) ) rtn = rtns.front()->clone();
598 } else if ( rtns.size() > 1 ) {
599 rtn = new TupleType{ Type::Qualifiers{}, rtns };
600 }
601 PassVisitor<ProtoDump> body{ this, rtn };
602
603 for ( TypeDecl* tyvar : decl->type->forall ) {
604 // add set of "closed" types to body so that it can print them as NamedType
605 body.pass.closed.insert( tyvar->name );
606
607 // add assertions to local scope as declarations as well
608 for ( DeclarationWithType* assn : tyvar->assertions ) {
609 assn->accept( body );
610 }
611 }
612
613 // add named parameters and returns to local scope
614 body.pass.addAll( decl->type->returnVals );
615 body.pass.addAll( decl->type->parameters );
616
617 // add contents of function to new scope
618 decl->statements->accept( body );
619
620 // store sub-scope
621 addSub( std::move(body) );
622 }
623
624 visit_children = false;
625 }
626
627 void previsit( StructDecl* sd ) { addAggregateFields(sd); }
628 void previsit( UnionDecl* ud ) { addAggregateFields(ud); }
629
630 void previsit( EnumDecl* ed ) {
631 std::unique_ptr<Type> eType =
632 std::make_unique<BasicType>( Type::Qualifiers{}, BasicType::SignedInt );
633
634 // add field names directly to enclosing scope
635 for ( Declaration* member : ed->members ) {
636 if ( ObjectDecl* obj = dynamic_cast< ObjectDecl* >(member) ) {
637 previsit(obj);
638 }
639 }
640
641 visit_children = false;
642 }
643
644 void previsit( ReturnStmt* stmt ) {
645 // do nothing for void-returning functions or statements returning nothing
646 if ( ! rtnType || ! stmt->expr ) return;
647
648 // otherwise construct the return type from the expression
649 std::stringstream ss;
650 build( rtnType.get(), stmt->expr, ss );
651 addExpr( ss.str() );
652 visit_children = false;
653 }
654
655 void previsit( Expression* expr ) {
656 std::stringstream ss;
657 PassVisitor<ExprPrinter> exPrinter{ closed, ss };
658 expr->accept( exPrinter );
659 addExpr( ss.str() );
660 visit_children = false;
661 }
662
663 /// Print non-prelude global declarations for resolv proto
664 void printGlobals() const {
665 std::cout << "#ptr<T> $addr T" << std::endl; // &?
666 int i = (int)BasicType::SignedInt;
667 std::cout << i << " $and " << i << ' ' << i << std::endl; // ?&&?
668 std::cout << i << " $or " << i << ' ' << i << std::endl; // ?||?
669 std::cout << "T $if " << i << " T T" << std::endl; // ternary operator
670 std::cout << "T $seq X T" << std::endl; // ?,?
671 }
672
673 public:
674 /// Prints this ProtoDump instance
675 void print(unsigned indent = 0) const {
676 // print globals at root level
677 if ( ! parent ) printGlobals();
678 // print decls
679 std::string tab( indent, '\t' );
680 for ( const std::string& d : decls ) {
681 std::cout << tab << d << std::endl;
682 }
683 // print divider
684 std::cout << '\n' << tab << "%%\n" << std::endl;
685 // print top-level expressions
686 for ( const std::string& e : exprs ) {
687 std::cout << tab << e << std::endl;
688 }
689 // print child scopes
690 ++indent;
691 for ( const PassVisitor<ProtoDump>& s : subs ) {
692 std::cout << tab << '{' << std::endl;
693 s.pass.print( indent );
694 std::cout << tab << '}' << std::endl;
695 }
696 }
697 };
698
699 void dumpAsResolvProto( std::list< Declaration * > &translationUnit ) {
700 PassVisitor<ProtoDump> dump;
701 acceptAll( translationUnit, dump );
702 dump.pass.print();
703 }
704
705} // namespace CodeTools
706
707// Local Variables: //
708// tab-width: 4 //
709// mode: c++ //
710// compile-command: "make install" //
711// End: //
Note: See TracBrowser for help on using the repository browser.