source: src/Parser/StatementNode.cc @ e9ed2a1

ADTast-experimental
Last change on this file since e9ed2a1 was bb7422a, checked in by Andrew Beach <ajbeach@…>, 15 months ago

Translated parser to the new ast. This incuded a small fix in the resolver so larger expressions can be used in with statements and some updated tests. errors/declaration just is a formatting update. attributes now actually preserves more attributes (unknown if all versions work).

  • Property mode set to 100644
File size: 17.0 KB
RevLine 
[b87a5ed]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//
[4e7171f]7// StatementNode.cc -- Transform from parse data-structures to AST data-structures, usually deleting the parse
8//     data-structure after the transformation.
[b87a5ed]9//
10// Author           : Rodolfo G. Esteves
11// Created On       : Sat May 16 14:59:41 2015
[bb7422a]12// Last Modified By : Andrew Beach
13// Last Modified On : Tue Apr  4 11:40:00 2023
14// Update Count     : 427
[b87a5ed]15//
16
[e3e16bc]17#include <cassert>                 // for assert, strict_dynamic_cast, assertf
[d180746]18#include <memory>                  // for unique_ptr
19#include <string>                  // for string
[51b7345]20
[bb7422a]21#include "AST/Label.hpp"           // for Label
22#include "AST/Stmt.hpp"            // for Stmt, AsmStmt, BranchStmt, CaseCla...
[d180746]23#include "Common/SemanticError.h"  // for SemanticError
24#include "Common/utility.h"        // for maybeMoveBuild, maybeBuild
25#include "ParseNode.h"             // for StatementNode, ExpressionNode, bui...
26#include "parserutility.h"         // for notZeroExpr
27
28class Declaration;
[51b7345]29
30using namespace std;
31
[61fc4f6]32StatementNode::StatementNode( DeclarationNode * decl ) {
[c0aa336]33        assert( decl );
[61fc4f6]34        DeclarationNode * agg = decl->extractAggregate();
[c0aa336]35        if ( agg ) {
[bb7422a]36                StatementNode * nextStmt = new StatementNode(
37                        new ast::DeclStmt( decl->location, maybeBuild( decl ) ) );
[c0aa336]38                set_next( nextStmt );
39                if ( decl->get_next() ) {
40                        get_next()->set_next( new StatementNode( dynamic_cast< DeclarationNode * >(decl->get_next()) ) );
41                        decl->set_next( 0 );
[1d4580a]42                } // if
43        } else {
[c0aa336]44                if ( decl->get_next() ) {
45                        set_next( new StatementNode( dynamic_cast< DeclarationNode * >( decl->get_next() ) ) );
46                        decl->set_next( 0 );
47                } // if
48                agg = decl;
[1d4580a]49        } // if
[bb7422a]50        // Local copy to avoid accessing the pointer after it is moved from.
51        CodeLocation declLocation = agg->location;
52        stmt.reset( new ast::DeclStmt( declLocation, maybeMoveBuild( agg ) ) );
[c0aa336]53} // StatementNode::StatementNode
[1d4580a]54
[61fc4f6]55StatementNode * StatementNode::append_last_case( StatementNode * stmt ) {
56        StatementNode * prev = this;
[1d4580a]57        // find end of list and maintain previous pointer
58        for ( StatementNode * curr = prev; curr != nullptr; curr = (StatementNode *)curr->get_next() ) {
[61fc4f6]59                StatementNode * node = strict_dynamic_cast< StatementNode * >(curr);
[bb7422a]60                assert( nullptr == node->stmt.get() );
61                assert( dynamic_cast<ast::CaseClause *>( node->clause.get() ) );
[8cc5cb0]62                prev = curr;
63        } // for
[e82aa9df]64        // convert from StatementNode list to Statement list
[61fc4f6]65        StatementNode * node = dynamic_cast< StatementNode * >(prev);
[bb7422a]66        std::vector<ast::ptr<ast::Stmt>> stmts;
[7ecbb7e]67        buildMoveList( stmt, stmts );
[e82aa9df]68        // splice any new Statements to end of current Statements
[bb7422a]69        auto caseStmt = strict_dynamic_cast<ast::CaseClause *>( node->clause.get() );
70        for ( auto const & newStmt : stmts ) {
71                caseStmt->stmts.emplace_back( newStmt );
72        }
73        stmts.clear();
[8cc5cb0]74        return this;
[401e61f]75} // StatementNode::append_last_case
[8cc5cb0]76
[bb7422a]77ast::Stmt * build_expr( CodeLocation const & location, ExpressionNode * ctl ) {
78        if ( ast::Expr * e = maybeMoveBuild( ctl ) ) {
79                return new ast::ExprStmt( location, e );
80        } else {
81                return new ast::NullStmt( location );
82        }
[401e61f]83} // build_expr
[8cc5cb0]84
[bb7422a]85static ast::Expr * build_if_control( CondCtl * ctl,
86                std::vector<ast::ptr<ast::Stmt>> & inits ) {
87        assert( inits.empty() );
88        if ( nullptr != ctl->init ) {
89                buildMoveList( ctl->init, inits );
[936e9f4]90        } // if
91
[bb7422a]92        ast::Expr * cond = nullptr;
[a01f7c94]93        if ( ctl->condition ) {
94                // compare the provided condition against 0
[702e826]95                cond = notZeroExpr( maybeMoveBuild( ctl->condition ) );
[a01f7c94]96        } else {
[bb7422a]97                for ( ast::ptr<ast::Stmt> & stmt : inits ) {
[a01f7c94]98                        // build the && of all of the declared variables compared against 0
[bb7422a]99                        //auto declStmt = strict_dynamic_cast<ast::DeclStmt *>( stmt );
100                        auto declStmt = stmt.strict_as<ast::DeclStmt>();
101                        //ast::DeclWithType * dwt = strict_dynamic_cast<ast::DeclWithType *>( declStmt->decl );
102                        auto dwt = declStmt->decl.strict_as<ast::DeclWithType>();
103                        ast::Expr * nze = notZeroExpr( new ast::VariableExpr( dwt->location, dwt ) );
104                        cond = cond ? new ast::LogicalExpr( dwt->location, cond, nze, ast::AndExpr ) : nze;
[a01f7c94]105                }
106        }
[6d49ea3]107        delete ctl;
[ee3c93d]108        return cond;
[401e61f]109} // build_if_control
[ee3c93d]110
[bb7422a]111ast::Stmt * build_if( const CodeLocation & location, CondCtl * ctl, StatementNode * then, StatementNode * else_ ) {
112        std::vector<ast::ptr<ast::Stmt>> astinit;                                               // maybe empty
113        ast::Expr * astcond = build_if_control( ctl, astinit ); // ctl deleted, cond/init set
[436bbe5]114
[bb7422a]115        std::vector<ast::ptr<ast::Stmt>> aststmt;
116        buildMoveList( then, aststmt );
[436bbe5]117        assert( aststmt.size() == 1 );
[bb7422a]118        ast::Stmt const * astthen = aststmt.front().release();
[436bbe5]119
[bb7422a]120        ast::Stmt const * astelse = nullptr;
[436bbe5]121        if ( else_ ) {
[bb7422a]122                std::vector<ast::ptr<ast::Stmt>> aststmt;
123                buildMoveList( else_, aststmt );
[436bbe5]124                assert( aststmt.size() == 1 );
[bb7422a]125                astelse = aststmt.front().release();
[ee3c93d]126        } // if
127
[bb7422a]128        return new ast::IfStmt( location, astcond, astthen, astelse,
129                std::move( astinit )
130        );
[401e61f]131} // build_if
[2f22cc4]132
[bb7422a]133// Temporary work around. Split StmtClause off from StatementNode.
134template<typename clause_t>
135static void buildMoveClauseList( StatementNode * firstNode,
136                std::vector<ast::ptr<clause_t>> & output ) {
137        SemanticErrorException errors;
138        std::back_insert_iterator<std::vector<ast::ptr<clause_t>>>
139                out( output );
140        StatementNode * cur = firstNode;
141
142        while ( cur ) {
143                try {
144                        auto clause = cur->clause.release();
145                        if ( auto result = dynamic_cast<clause_t *>( clause ) ) {
146                                *out++ = result;
147                        } else {
148                                assertf(false, __PRETTY_FUNCTION__ );
149                                SemanticError( cur->location, "type specifier declaration in forall clause is currently unimplemented." );
150                        } // if
151                } catch( SemanticErrorException & e ) {
152                        errors.append( e );
153                } // try
154                ParseNode * temp = cur->get_next();
155                // Should not return nullptr, then it is non-homogeneous:
156                cur = dynamic_cast<StatementNode *>( temp );
157                if ( !cur && temp ) {
158                        SemanticError( temp->location, "internal error, non-homogeneous nodes founds in buildList processing." );
159                } // if
160        } // while
161        if ( ! errors.isEmpty() ) {
162                throw errors;
163        } // if
164        // Usually in the wrapper.
165        delete firstNode;
166}
167
168ast::Stmt * build_switch( const CodeLocation & location, bool isSwitch, ExpressionNode * ctl, StatementNode * stmt ) {
169        std::vector<ast::ptr<ast::CaseClause>> aststmt;
170        buildMoveClauseList( stmt, aststmt );
171        // If it is not a switch it is a choose statement.
172        if ( ! isSwitch ) {
173                for ( ast::ptr<ast::CaseClause> & stmt : aststmt ) {
174                        // Code after "case" is the end of case list.
175                        if ( !stmt->stmts.empty() ) {
176                                auto mutStmt = ast::mutate( stmt.get() );
177                                // I believe the stmts are actually always one block.
178                                auto stmts = mutStmt->stmts.front().get_and_mutate();
179                                auto block = strict_dynamic_cast<ast::CompoundStmt *>( stmts );
180                                block->kids.push_back( new ast::BranchStmt( block->location,
181                                        ast::BranchStmt::Break,
182                                        ast::Label( block->location ) ) );
183                                stmt = mutStmt;
[6a276a0]184                        } // if
185                } // for
186        } // if
[436bbe5]187        // aststmt.size() == 0 for switch (...) {}, i.e., no declaration or statements
[bb7422a]188        return new ast::SwitchStmt( location,
189                maybeMoveBuild( ctl ), std::move( aststmt ) );
[401e61f]190} // build_switch
191
[bb7422a]192ast::CaseClause * build_case( ExpressionNode * ctl ) {
193        // stmt starts empty and then added to
194        auto expr = maybeMoveBuild( ctl );
195        return new ast::CaseClause( expr->location, expr, {} );
[401e61f]196} // build_case
197
[bb7422a]198ast::CaseClause * build_default( const CodeLocation & location ) {
199        // stmt starts empty and then added to
200        return new ast::CaseClause( location, nullptr, {} );
[401e61f]201} // build_default
202
[bb7422a]203ast::Stmt * build_while( const CodeLocation & location, CondCtl * ctl, StatementNode * stmt, StatementNode * else_ ) {
204        std::vector<ast::ptr<ast::Stmt>> astinit;                                               // maybe empty
205        ast::Expr * astcond = build_if_control( ctl, astinit ); // ctl deleted, cond/init set
[3b0bc16]206
[bb7422a]207        std::vector<ast::ptr<ast::Stmt>> aststmt;                                               // loop body, compound created if empty
208        buildMoveList( stmt, aststmt );
[3b0bc16]209        assert( aststmt.size() == 1 );
210
[bb7422a]211        std::vector<ast::ptr<ast::Stmt>> astelse;                                               // else clause, maybe empty
212        buildMoveList( else_, astelse );
213        assert( astelse.size() <= 1 );
214
215        return new ast::WhileDoStmt( location,
216                astcond,
217                aststmt.front(),
218                astelse.empty() ? nullptr : astelse.front().release(),
219                std::move( astinit ),
220                false
221        );
[401e61f]222} // build_while
[2f22cc4]223
[bb7422a]224ast::Stmt * build_do_while( const CodeLocation & location, ExpressionNode * ctl, StatementNode * stmt, StatementNode * else_ ) {
225        std::vector<ast::ptr<ast::Stmt>> aststmt;                                               // loop body, compound created if empty
226        buildMoveList( stmt, aststmt );
[436bbe5]227        assert( aststmt.size() == 1 );                                          // compound created if empty
[3b0bc16]228
[bb7422a]229        std::vector<ast::ptr<ast::Stmt>> astelse;                                               // else clause, maybe empty
230        buildMoveList( else_, astelse );
231        assert( astelse.size() <= 1 );
[ee3c93d]232
[4e7171f]233        // do-while cannot have declarations in the contitional, so init is always empty
[bb7422a]234        return new ast::WhileDoStmt( location,
235                notZeroExpr( maybeMoveBuild( ctl ) ),
236                aststmt.front(),
237                astelse.empty() ? nullptr : astelse.front().release(),
238                {},
239                true
240        );
[401e61f]241} // build_do_while
[2f22cc4]242
[bb7422a]243ast::Stmt * build_for( const CodeLocation & location, ForCtrl * forctl, StatementNode * stmt, StatementNode * else_ ) {
244        std::vector<ast::ptr<ast::Stmt>> astinit;                                               // maybe empty
[436bbe5]245        buildMoveList( forctl->init, astinit );
[2f22cc4]246
[bb7422a]247        ast::Expr * astcond = nullptr;                                          // maybe empty
[702e826]248        astcond = notZeroExpr( maybeMoveBuild( forctl->condition ) );
[2f22cc4]249
[bb7422a]250        ast::Expr * astincr = nullptr;                                          // maybe empty
[702e826]251        astincr = maybeMoveBuild( forctl->change );
[436bbe5]252        delete forctl;
[2f22cc4]253
[bb7422a]254        std::vector<ast::ptr<ast::Stmt>> aststmt;                                               // loop body, compound created if empty
255        buildMoveList( stmt, aststmt );
[3b0bc16]256        assert( aststmt.size() == 1 );
257
[bb7422a]258        std::vector<ast::ptr<ast::Stmt>> astelse;                                               // else clause, maybe empty
259        buildMoveList( else_, astelse );
260        assert( astelse.size() <= 1 );
261
262        return new ast::ForStmt( location,
263                std::move( astinit ),
264                astcond,
265                astincr,
266                aststmt.front(),
267                astelse.empty() ? nullptr : astelse.front().release()
268        );
[401e61f]269} // build_for
[2f22cc4]270
[bb7422a]271ast::Stmt * build_branch( const CodeLocation & location, ast::BranchStmt::Kind kind ) {
272        return new ast::BranchStmt( location,
273                kind,
274                ast::Label( location )
275        );
[401e61f]276} // build_branch
277
[bb7422a]278ast::Stmt * build_branch( const CodeLocation & location, string * identifier, ast::BranchStmt::Kind kind ) {
279        ast::Stmt * ret = new ast::BranchStmt( location,
280                kind,
281                ast::Label( location, *identifier )
282        );
283        delete identifier;                                                                      // allocated by lexer
[ab57786]284        return ret;
[401e61f]285} // build_branch
286
[bb7422a]287ast::Stmt * build_computedgoto( ExpressionNode * ctl ) {
288        ast::Expr * expr = maybeMoveBuild( ctl );
289        return new ast::BranchStmt( expr->location, expr );
[401e61f]290} // build_computedgoto
[8cc5cb0]291
[bb7422a]292ast::Stmt * build_return( const CodeLocation & location, ExpressionNode * ctl ) {
293        std::vector<ast::ptr<ast::Expr>> exps;
[7ecbb7e]294        buildMoveList( ctl, exps );
[bb7422a]295        return new ast::ReturnStmt( location,
296                exps.size() > 0 ? exps.back().release() : nullptr
297        );
[401e61f]298} // build_return
[daf1af8]299
[bb7422a]300static ast::Stmt * build_throw_stmt(
301                const CodeLocation & location,
302                ExpressionNode * ctl,
303                ast::ExceptionKind kind ) {
304        std::vector<ast::ptr<ast::Expr>> exps;
[7ecbb7e]305        buildMoveList( ctl, exps );
[436bbe5]306        assertf( exps.size() < 2, "CFA internal error: leaking memory" );
[bb7422a]307        return new ast::ThrowStmt( location,
308                kind,
309                !exps.empty() ? exps.back().release() : nullptr,
310                (ast::Expr *)nullptr
311        );
312}
313
314ast::Stmt * build_throw( const CodeLocation & loc, ExpressionNode * ctl ) {
315        return build_throw_stmt( loc, ctl, ast::Terminate );
[401e61f]316} // build_throw
[daf1af8]317
[bb7422a]318ast::Stmt * build_resume( const CodeLocation & loc, ExpressionNode * ctl ) {
319        return build_throw_stmt( loc, ctl, ast::Resume );
[401e61f]320} // build_resume
[daf1af8]321
[bb7422a]322ast::Stmt * build_resume_at( ExpressionNode * ctl, ExpressionNode * target ) {
[794c15b]323        (void)ctl;
324        (void)target;
325        assertf( false, "resume at (non-local throw) is not yet supported," );
[401e61f]326} // build_resume_at
[321f55d]327
[bb7422a]328ast::Stmt * build_try( const CodeLocation & location, StatementNode * try_, StatementNode * catch_, StatementNode * finally_ ) {
329        std::vector<ast::ptr<ast::CatchClause>> aststmt;
330        buildMoveClauseList( catch_, aststmt );
331        ast::CompoundStmt * tryBlock = strict_dynamic_cast<ast::CompoundStmt *>( maybeMoveBuild( try_ ) );
332        ast::FinallyClause * finallyBlock = nullptr;
333        if ( finally_ ) {
334                finallyBlock = dynamic_cast<ast::FinallyClause *>( finally_->clause.release() );
335        }
336        return new ast::TryStmt( location,
337                tryBlock,
338                std::move( aststmt ),
339                finallyBlock
340        );
[401e61f]341} // build_try
342
[bb7422a]343ast::CatchClause * build_catch( const CodeLocation & location, ast::ExceptionKind kind, DeclarationNode * decl, ExpressionNode * cond, StatementNode * body ) {
344        std::vector<ast::ptr<ast::Stmt>> aststmt;
345        buildMoveList( body, aststmt );
[436bbe5]346        assert( aststmt.size() == 1 );
[bb7422a]347        return new ast::CatchClause( location,
348                kind,
349                maybeMoveBuild( decl ),
350                maybeMoveBuild( cond ),
351                aststmt.front().release()
352        );
[401e61f]353} // build_catch
354
[bb7422a]355ast::FinallyClause * build_finally( const CodeLocation & location, StatementNode * stmt ) {
356        std::vector<ast::ptr<ast::Stmt>> aststmt;
357        buildMoveList( stmt, aststmt );
[436bbe5]358        assert( aststmt.size() == 1 );
[bb7422a]359        return new ast::FinallyClause( location,
360                aststmt.front().strict_as<ast::CompoundStmt>()
361        );
[401e61f]362} // build_finally
[1d4580a]363
[bb7422a]364ast::SuspendStmt * build_suspend( const CodeLocation & location, StatementNode * then, ast::SuspendStmt::Type type ) {
365        std::vector<ast::ptr<ast::Stmt>> stmts;
366        buildMoveList( then, stmts );
367        ast::CompoundStmt const * then2 = nullptr;
[427854b]368        if(!stmts.empty()) {
369                assert( stmts.size() == 1 );
[bb7422a]370                then2 = stmts.front().strict_as<ast::CompoundStmt>();
[427854b]371        }
[bb7422a]372        auto node = new ast::SuspendStmt( location, then2, ast::SuspendStmt::None );
373        node->type = type;
[427854b]374        return node;
[9fd9d015]375} // build_suspend
[427854b]376
[bb7422a]377ast::WaitForStmt * build_waitfor( const CodeLocation & location, ast::WaitForStmt * existing, ExpressionNode * when, ExpressionNode * targetExpr, StatementNode * stmt ) {
378        auto clause = new ast::WaitForClause( location );
379        clause->target_func = maybeBuild( targetExpr );
380        clause->stmt = maybeMoveBuild( stmt );
381        clause->cond = notZeroExpr( maybeMoveBuild( when ) );
[2065609]382
383        ExpressionNode * next = dynamic_cast<ExpressionNode *>( targetExpr->get_next() );
384        targetExpr->set_next( nullptr );
[bb7422a]385        buildMoveList( next, clause->target_args );
[2065609]386
[135b431]387        delete targetExpr;
388
[bb7422a]389        existing->clauses.insert( existing->clauses.begin(), clause );
[135b431]390
[9fd9d015]391        return existing;
[401e61f]392} // build_waitfor
[135b431]393
[bb7422a]394ast::WaitForStmt * build_waitfor_else( const CodeLocation & location, ast::WaitForStmt * existing, ExpressionNode * when, StatementNode * stmt ) {
395        existing->else_stmt = maybeMoveBuild( stmt );
396        existing->else_cond = notZeroExpr( maybeMoveBuild( when ) );
[135b431]397
[bb7422a]398        (void)location;
[9fd9d015]399        return existing;
400} // build_waitfor_else
[135b431]401
[bb7422a]402ast::WaitForStmt * build_waitfor_timeout( const CodeLocation & location, ast::WaitForStmt * existing, ExpressionNode * when, ExpressionNode * timeout, StatementNode * stmt ) {
403        existing->timeout_time = maybeMoveBuild( timeout );
404        existing->timeout_stmt = maybeMoveBuild( stmt );
405        existing->timeout_cond = notZeroExpr( maybeMoveBuild( when ) );
[135b431]406
[bb7422a]407        (void)location;
[9fd9d015]408        return existing;
[401e61f]409} // build_waitfor_timeout
[135b431]410
[bb7422a]411ast::Stmt * build_with( const CodeLocation & location, ExpressionNode * exprs, StatementNode * stmt ) {
412        std::vector<ast::ptr<ast::Expr>> e;
[a378ca7]413        buildMoveList( exprs, e );
[bb7422a]414        ast::Stmt * s = maybeMoveBuild( stmt );
415        return new ast::DeclStmt( location, new ast::WithStmt( location, std::move( e ), s ) );
[401e61f]416} // build_with
[a378ca7]417
[bb7422a]418ast::Stmt * build_compound( const CodeLocation & location, StatementNode * first ) {
419        auto cs = new ast::CompoundStmt( location );
420        buildMoveList( first, cs->kids );
[b87a5ed]421        return cs;
[401e61f]422} // build_compound
[51b7345]423
[a025ea8]424// A single statement in a control structure is always converted to a compound statement so subsequent generated code
425// can be placed within this compound statement. Otherwise, code generation has to constantly check for a single
426// statement and wrap it into a compound statement to insert additional code. Hence, all control structures have a
427// conical form for code generation.
[bb7422a]428StatementNode * maybe_build_compound( const CodeLocation & location, StatementNode * first ) {
[a025ea8]429        // Optimization: if the control-structure statement is a compound statement, do not wrap it.
430        // e.g., if (...) {...} do not wrap the existing compound statement.
[bb7422a]431        if ( !dynamic_cast<ast::CompoundStmt *>( first->stmt.get() ) ) { // unique_ptr
432                return new StatementNode( build_compound( location, first ) );
[a025ea8]433        } // if
434        return first;
435} // maybe_build_compound
436
[f135b50]437// Question
[bb7422a]438ast::Stmt * build_asm( const CodeLocation & location, bool voltile, ast::Expr * instruction, ExpressionNode * output, ExpressionNode * input, ExpressionNode * clobber, LabelNode * gotolabels ) {
439        std::vector<ast::ptr<ast::Expr>> out, in;
440        std::vector<ast::ptr<ast::ConstantExpr>> clob;
[e82aa9df]441
[7ecbb7e]442        buildMoveList( output, out );
443        buildMoveList( input, in );
444        buildMoveList( clobber, clob );
[bb7422a]445        return new ast::AsmStmt( location,
446                voltile,
447                instruction,
448                std::move( out ),
449                std::move( in ),
450                std::move( clob ),
451                gotolabels ? gotolabels->labels : std::vector<ast::Label>()
452        );
[401e61f]453} // build_asm
[7f5566b]454
[bb7422a]455ast::Stmt * build_directive( const CodeLocation & location, string * directive ) {
456        auto stmt = new ast::DirectiveStmt( location, *directive );
457        delete directive;
458        return stmt;
[401e61f]459} // build_directive
[61fc4f6]460
[bb7422a]461ast::Stmt * build_mutex( const CodeLocation & location, ExpressionNode * exprs, StatementNode * stmt ) {
462        std::vector<ast::ptr<ast::Expr>> expList;
[de52331]463        buildMoveList( exprs, expList );
[bb7422a]464        ast::Stmt * body = maybeMoveBuild( stmt );
465        return new ast::MutexStmt( location, body, std::move( expList ) );
[de52331]466} // build_mutex
467
[51b7345]468// Local Variables: //
[b87a5ed]469// tab-width: 4 //
470// mode: c++ //
471// compile-command: "make install" //
[51b7345]472// End: //
Note: See TracBrowser for help on using the repository browser.