"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "src/codegen.cc" between
ragel-7.0.0.11.tar.gz and ragel-7.0.0.12.tar.gz

About: Ragel compiles executable finite state machines from regular languages (C, C++, Obj-C, C#, D, Java, Go and Ruby). Development version.

codegen.cc  (ragel-7.0.0.11):codegen.cc  (ragel-7.0.0.12)
skipping to change at line 45 skipping to change at line 45
using std::ostringstream; using std::ostringstream;
using std::string; using std::string;
using std::endl; using std::endl;
using std::istream; using std::istream;
using std::ifstream; using std::ifstream;
using std::ostream; using std::ostream;
using std::ios; using std::ios;
using std::cin; using std::cin;
using std::endl; using std::endl;
std::ostream &operator<<( std::ostream &out, Variable &v )
{
out << v.name;
v.isReferenced = true;
return out;
}
std::ostream &operator<<( std::ostream &out, GotoLabel &l )
{
out << l.name;
l.isReferenced = true;
return out;
}
TableArray::TableArray( const char *name, CodeGen &codeGen ) TableArray::TableArray( const char *name, CodeGen &codeGen )
: :
state(InitialState), state(InitialState),
name(name), name(name),
width(0), width(0),
isSigned(true), isSigned(true),
isChar(false), isChar(false),
stringTables( codeGen.stringTables ), stringTables( codeGen.stringTables ),
iall( codeGen.stringTables ? IALL_STRING : IALL_INTEGRAL ), iall( codeGen.stringTables ? IALL_STRING : IALL_INTEGRAL ),
values(0), values(0),
/* /*
* Use zero for min and max because * Use zero for min and max because
* we we null terminate every array. * we we null terminate every array.
*/ */
min(0), min(0),
max(0), max(0),
codeGen(codeGen), codeGen(codeGen),
out(codeGen.out), out(codeGen.out),
ln(0) ln(0),
isReferenced(false),
started(false)
{ {
codeGen.arrayVector.append( this ); codeGen.arrayVector.append( this );
} }
std::string TableArray::ref() const std::string TableArray::ref()
{ {
isReferenced = true;
return string("_") + codeGen.DATA_PREFIX() + name; return string("_") + codeGen.DATA_PREFIX() + name;
} }
long long TableArray::size() long long TableArray::size()
{ {
return width * values; return width * values;
} }
void TableArray::startAnalyze() void TableArray::startAnalyze()
{ {
skipping to change at line 99 skipping to change at line 116
if ( v > max ) if ( v > max )
max = v; max = v;
} }
void TableArray::finishAnalyze() void TableArray::finishAnalyze()
{ {
if ( codeGen.backend == Direct ) { if ( codeGen.backend == Direct ) {
/* Calculate the type if it is not already set. */ /* Calculate the type if it is not already set. */
if ( type.empty() ) { if ( type.empty() ) {
if ( min >= S8BIT_MIN && max <= S8BIT_MAX ) { if ( min >= S8BIT_MIN && max <= S8BIT_MAX ) {
type = "char"; type = "signed char";
width = sizeof(char); width = sizeof(char);
} }
else if ( min >= S16BIT_MIN && max <= S16BIT_MAX ) { else if ( min >= S16BIT_MIN && max <= S16BIT_MAX ) {
type = "short"; type = "short";
width = sizeof(short); width = sizeof(short);
} }
else if ( min >= S32BIT_MIN && max <= S32BIT_MAX ) { else if ( min >= S32BIT_MIN && max <= S32BIT_MAX ) {
type = "int"; type = "int";
width = sizeof(int); width = sizeof(int);
} }
skipping to change at line 288 skipping to change at line 305
if ( codeGen.red->id->printStatistics ) { if ( codeGen.red->id->printStatistics ) {
codeGen.red->id->stats() << name << "\t" << values << "\t" << codeGen.red->id->stats() << name << "\t" << values << "\t" <<
size() << "\t" << endl; size() << "\t" << endl;
} }
codeGen.tableData += size(); codeGen.tableData += size();
} }
void TableArray::start() void TableArray::start()
{ {
assert( !started );
started = true;
switch ( state ) { switch ( state ) {
case InitialState: case InitialState:
break; break;
case AnalyzePass: case AnalyzePass:
startAnalyze(); startAnalyze();
break; break;
case GeneratePass: case GeneratePass:
startGenerate(); if ( isReferenced )
startGenerate();
break; break;
} }
} }
void TableArray::value( long long v ) void TableArray::value( long long v )
{ {
assert( started );
switch ( state ) { switch ( state ) {
case InitialState: case InitialState:
break; break;
case AnalyzePass: case AnalyzePass:
valueAnalyze( v ); valueAnalyze( v );
break; break;
case GeneratePass: case GeneratePass:
valueGenerate( v ); if ( isReferenced )
valueGenerate( v );
break; break;
} }
} }
void TableArray::finish() void TableArray::finish()
{ {
assert( started );
started = false;
switch ( state ) { switch ( state ) {
case InitialState: case InitialState:
break; break;
case AnalyzePass: case AnalyzePass:
finishAnalyze(); finishAnalyze();
break; break;
case GeneratePass: case GeneratePass:
finishGenerate(); if ( isReferenced )
finishGenerate();
break; break;
} }
} }
/* Init code gen with in parameters. */ /* Init code gen with in parameters. */
CodeGen::CodeGen( const CodeGenArgs &args ) CodeGen::CodeGen( const CodeGenArgs &args )
: :
CodeGenData( args ), CodeGenData( args ),
cpc( "_cpc" ), cpc( "_cpc" ),
pop_test( "_pop_test" ),
new_recs( "new_recs" ),
alt( "_alt" ),
tableData( 0 ), tableData( 0 ),
backend( args.id->hostLang->backend ), backend( args.id->hostLang->backend ),
stringTables( args.id->stringTables ), stringTables( args.id->stringTables ),
nfaTargs( "nfa_targs", *this ), nfaTargs( "nfa_targs", *this ),
nfaOffsets( "nfa_offsets", *this ), nfaOffsets( "nfa_offsets", *this ),
nfaPushActions( "nfa_push_actions", *this ), nfaPushActions( "nfa_push_actions", *this ),
nfaPopTrans( "nfa_pop_trans", *this ) nfaPopTrans( "nfa_pop_trans", *this )
{ {
} }
skipping to change at line 518 skipping to change at line 546
INLINE_LIST( ret, red->getKeyExpr, 0, false, false ); INLINE_LIST( ret, red->getKeyExpr, 0, false, false );
ret << CLOSE_HOST_EXPR(); ret << CLOSE_HOST_EXPR();
} }
else { else {
/* Expression for retrieving the key, use simple dereference. */ /* Expression for retrieving the key, use simple dereference. */
ret << "( " << DEREF( "data", P() ) << ")"; ret << "( " << DEREF( "data", P() ) << ")";
} }
return ret.str(); return ret.str();
} }
/* Write out level number of tabs. Makes the nested binary search nice
* looking. */
string CodeGen::TABS( int level )
{
string result;
while ( level-- > 0 )
result += "\t";
return result;
}
/* Write out a key from the fsm code gen. Depends on wether or not the key is /* Write out a key from the fsm code gen. Depends on wether or not the key is
* signed. */ * signed. */
string CodeGen::KEY( Key key ) string CodeGen::KEY( Key key )
{ {
if ( backend == Direct ) { if ( backend == Direct ) {
ostringstream ret; ostringstream ret;
if ( alphType->isChar ) if ( alphType->isChar )
ret << "c(" << (unsigned long) key.getVal() << ")"; ret << "c(" << (unsigned long) key.getVal() << ")";
else if ( keyOps->isSigned || !keyOps->explicitUnsigned ) else if ( keyOps->isSigned || !keyOps->explicitUnsigned )
ret << key.getVal(); ret << key.getVal();
skipping to change at line 562 skipping to change at line 580
} }
bool CodeGen::isAlphTypeSigned() bool CodeGen::isAlphTypeSigned()
{ {
return keyOps->isSigned; return keyOps->isSigned;
} }
void CodeGen::DECLARE( std::string type, Variable &var, std::string init ) void CodeGen::DECLARE( std::string type, Variable &var, std::string init )
{ {
if ( var.isReferenced ) if ( var.isReferenced )
out << type << " " << var.name << init << ';'; out << type << " " << var.name << init << ";\n";
} }
void CodeGen::EXEC( ostream &ret, GenInlineItem *item, int targState, int inFini sh ) void CodeGen::EXEC( ostream &ret, GenInlineItem *item, int targState, int inFini sh )
{ {
/* The parser gives fexec two children. The double brackets are for D /* The parser gives fexec two children. The double brackets are for D
* code. If the inline list is a single word it will get interpreted as a * code. If the inline list is a single word it will get interpreted as a
* C-style cast by the D compiler. */ * C-style cast by the D compiler. */
ret << OPEN_GEN_BLOCK() << P() << " = (("; ret << OPEN_GEN_BLOCK() << P() << " = ((";
INLINE_LIST( ret, item->children, targState, inFinish, false ); INLINE_LIST( ret, item->children, targState, inFinish, false );
ret << "))-1;" << CLOSE_GEN_BLOCK() << "\n"; ret << "))-1;" << CLOSE_GEN_BLOCK() << "\n";
skipping to change at line 591 skipping to change at line 609
for ( GenInlineList::Iter lma = *item->children; lma.lte(); lma++ ) { for ( GenInlineList::Iter lma = *item->children; lma.lte(); lma++ ) {
/* Write the case label, the action and the case break. */ /* Write the case label, the action and the case break. */
if ( lma->lmId < 0 ) if ( lma->lmId < 0 )
ret << " " << DEFAULT() << " {\n"; ret << " " << DEFAULT() << " {\n";
else else
ret << " " << CASE( STR(lma->lmId) ) << " {\n"; ret << " " << CASE( STR(lma->lmId) ) << " {\n";
/* Write the block and close it off. */ /* Write the block and close it off. */
INLINE_LIST( ret, lma->children, targState, inFinish, csForced ); INLINE_LIST( ret, lma->children, targState, inFinish, csForced );
ret << CEND() << "}\n"; ret << CEND() << "\n}\n";
} }
ret << ret <<
" }" << CLOSE_GEN_BLOCK() << "\n" " }" << CLOSE_GEN_BLOCK() << "\n"
"\t"; "\t";
} }
void CodeGen::LM_EXEC( ostream &ret, GenInlineItem *item, int targState, int inF inish ) void CodeGen::LM_EXEC( ostream &ret, GenInlineItem *item, int targState, int inF inish )
{ {
/* The parser gives fexec two children. The double brackets are for D /* The parser gives fexec two children. The double brackets are for D
skipping to change at line 736 skipping to change at line 754
} }
/* Write out an inline tree structure. Walks the list and possibly calls out /* Write out an inline tree structure. Walks the list and possibly calls out
* to virtual functions than handle language specific items in the tree. */ * to virtual functions than handle language specific items in the tree. */
void CodeGen::INLINE_LIST( ostream &ret, GenInlineList *inlineList, void CodeGen::INLINE_LIST( ostream &ret, GenInlineList *inlineList,
int targState, bool inFinish, bool csForced ) int targState, bool inFinish, bool csForced )
{ {
for ( GenInlineList::Iter item = *inlineList; item.lte(); item++ ) { for ( GenInlineList::Iter item = *inlineList; item.lte(); item++ ) {
switch ( item->type ) { switch ( item->type ) {
case GenInlineItem::Text: case GenInlineItem::Text:
translatedHostData( ret, item->data ); if ( backend == Direct )
ret << item->data;
else
translatedHostData( ret, item->data );
break; break;
case GenInlineItem::Goto: case GenInlineItem::Goto:
GOTO( ret, item->targState->id, inFinish ); GOTO( ret, item->targState->id, inFinish );
break; break;
case GenInlineItem::Call: case GenInlineItem::Call:
CALL( ret, item->targState->id, targState, inFinish ); CALL( ret, item->targState->id, targState, inFinish );
break; break;
case GenInlineItem::Ncall: case GenInlineItem::Ncall:
NCALL( ret, item->targState->id, targState, inFinish ); NCALL( ret, item->targState->id, targState, inFinish );
break; break;
skipping to change at line 768 skipping to change at line 789
break; break;
case GenInlineItem::Char: case GenInlineItem::Char:
ret << OPEN_GEN_EXPR() << GET_KEY() << CLOSE_GEN_EXPR(); ret << OPEN_GEN_EXPR() << GET_KEY() << CLOSE_GEN_EXPR();
break; break;
case GenInlineItem::Hold: case GenInlineItem::Hold:
ret << OPEN_GEN_BLOCK() << P() << " = " << P() << " - 1; " << CLOSE_GEN_BLOCK(); ret << OPEN_GEN_BLOCK() << P() << " = " << P() << " - 1; " << CLOSE_GEN_BLOCK();
break; break;
case GenInlineItem::LmHold: case GenInlineItem::LmHold:
ret << P() << " = " << P() << " - 1;"; ret << P() << " = " << P() << " - 1;";
break; break;
case GenInlineItem::NfaClear:
ret << "nfa_len = 0; ";
break;
case GenInlineItem::Exec: case GenInlineItem::Exec:
EXEC( ret, item, targState, inFinish ); EXEC( ret, item, targState, inFinish );
break; break;
case GenInlineItem::Curs: case GenInlineItem::Curs:
CURS( ret, inFinish ); CURS( ret, inFinish );
break; break;
case GenInlineItem::Targs: case GenInlineItem::Targs:
TARGS( ret, inFinish, targState ); TARGS( ret, inFinish, targState );
break; break;
case GenInlineItem::Entry: case GenInlineItem::Entry:
skipping to change at line 861 skipping to change at line 885
ostringstream ret; ostringstream ret;
for ( char *pc = path; *pc != 0; pc++ ) { for ( char *pc = path; *pc != 0; pc++ ) {
if ( *pc == '\\' ) if ( *pc == '\\' )
ret << "\\\\"; ret << "\\\\";
else else
ret << *pc; ret << *pc;
} }
return ret.str(); return ret.str();
} }
void CodeGen::EOF_CHECK( ostream &ret )
{
ret <<
" if ( " << P() << " == " << PE() << " )\n"
" goto _test_eof;\n";
testEofUsed = true;
}
void CodeGen::ACTION( ostream &ret, GenAction *action, IlOpts opts ) void CodeGen::ACTION( ostream &ret, GenAction *action, IlOpts opts )
{ {
ret << '\t'; ret << '\t';
ret << OPEN_HOST_BLOCK( action->loc.fileName, action->loc.line ); ret << OPEN_HOST_BLOCK( action->loc.fileName, action->loc.line );
INLINE_LIST( ret, action->inlineList, opts.targState, opts.inFinish, opts .csForced ); INLINE_LIST( ret, action->inlineList, opts.targState, opts.inFinish, opts .csForced );
ret << CLOSE_HOST_BLOCK(); ret << CLOSE_HOST_BLOCK();
ret << "\n";
genOutputLineDirective( ret );
} }
void CodeGen::CONDITION( ostream &ret, GenAction *condition ) void CodeGen::CONDITION( ostream &ret, GenAction *condition )
{ {
ret << OPEN_HOST_EXPR( condition->loc.fileName, condition->loc.line ); ret << OPEN_HOST_EXPR( condition->loc.fileName, condition->loc.line );
INLINE_LIST( ret, condition->inlineList, 0, false, false ); INLINE_LIST( ret, condition->inlineList, 0, false, false );
ret << CLOSE_HOST_EXPR(); ret << CLOSE_HOST_EXPR();
ret << "\n";
genOutputLineDirective( ret );
} }
void CodeGen::NFA_CONDITION( ostream &ret, GenAction *condition, bool last ) void CodeGen::NFA_CONDITION( ostream &ret, GenAction *condition, bool last )
{ {
if ( condition->inlineList->length() == 1 && if ( condition->inlineList->length() == 1 &&
condition->inlineList->head->type == condition->inlineList->head->type ==
GenInlineItem::NfaWrapAction ) GenInlineItem::NfaWrapAction )
{ {
GenAction *action = condition->inlineList->head->wrappedAction; GenAction *action = condition->inlineList->head->wrappedAction;
ACTION( out, action, IlOpts( 0, false, false ) ); ACTION( out, action, IlOpts( 0, false, false ) );
ret << "\n";
} }
else if ( condition->inlineList->length() == 1 && else if ( condition->inlineList->length() == 1 &&
condition->inlineList->head->type == condition->inlineList->head->type ==
GenInlineItem::NfaWrapConds ) GenInlineItem::NfaWrapConds )
{ {
ret << ret <<
" " << cpc << " = 0;\n"; " " << cpc << " = 0;\n";
GenCondSpace *condSpace = condition->inlineList->head->condSpace; GenCondSpace *condSpace = condition->inlineList->head->condSpace;
for ( GenCondSet::Iter csi = condSpace->condSet; csi.lte(); csi++ ) { for ( GenCondSet::Iter csi = condSpace->condSet; csi.lte(); csi++ ) {
ret << ret <<
" if ( "; " if ( ";
CONDITION( out, *csi ); CONDITION( out, *csi );
Size condValOffset = (1 << csi.pos()); Size condValOffset = (1 << csi.pos());
ret << " ) " << cpc << " += " << condValOffset << ";\n"; ret << " ) " << cpc << " += " << condValOffset << ";\n";
} }
const CondKeySet &keys = condition->inlineList->head->condKeySet; const CondKeySet &keys = condition->inlineList->head->condKeySet;
if ( keys.length() > 0 ) { if ( keys.length() > 0 ) {
ret << "_pop_test = "; ret << pop_test << " = ";
for ( CondKeySet::Iter cki = keys; cki.lte(); cki++ ) { for ( CondKeySet::Iter cki = keys; cki.lte(); cki++ ) {
ret << "" << cpc << " == " << *cki; ret << "" << cpc << " == " << *cki;
if ( !cki.last() ) if ( !cki.last() )
ret << " || "; ret << " || ";
} }
ret << ";\n"; ret << ";\n";
} }
else { else {
ret << "_pop_test = 0;\n"; ret << pop_test << " = 0;\n";
} }
if ( !last ) if ( !last ) {
ret << "if ( !_pop_test ) break;\n"; ret <<
"if ( !" << pop_test << " )\n"
" break;\n";
}
} }
else { else {
ret << "_pop_test = "; ret << pop_test << " = ";
CONDITION( ret, condition ); CONDITION( ret, condition );
ret << ";\n"; ret << ";\n";
if ( !last )
ret << "if ( !_pop_test ) break;\n"; if ( !last ) {
ret <<
"if ( !" << pop_test << " )\n"
" break;\n";
}
}
}
void CodeGen::NFA_POP_TEST_EXEC()
{
out <<
" " << pop_test << " = 1;\n"
" switch ( nfa_bp[nfa_len].popTrans ) {\n";
/* Loop the actions. */
for ( GenActionTableMap::Iter redAct = redFsm->actionMap;
redAct.lte(); redAct++ )
{
if ( redAct->numNfaPopTestRefs > 0 ) {
/* Write the entry label. */
out << "\t " << CASE( STR( redAct->actListId+1 ) ) << " {
\n";
/* Write each action in the list of action items. */
for ( GenActionTable::Iter item = redAct->key; item.lte()
; item++ )
NFA_CONDITION( out, item->value, item.last() );
out << CEND() << "\n}\n";
}
} }
out <<
" }\n"
"\n";
} }
string CodeGen::ERROR_STATE() string CodeGen::ERROR_STATE()
{ {
ostringstream ret; ostringstream ret;
if ( redFsm->errState != 0 ) if ( redFsm->errState != 0 )
ret << redFsm->errState->id; ret << redFsm->errState->id;
else else
ret << "-1"; ret << "-1";
return ret.str(); return ret.str();
skipping to change at line 1073 skipping to change at line 1125
} }
out << "\n"; out << "\n";
} }
} }
void CodeGen::NFA_PUSH( std::string state ) void CodeGen::NFA_PUSH( std::string state )
{ {
if ( redFsm->anyNfaStates() ) { if ( redFsm->anyNfaStates() ) {
out << out <<
" if ( " << ARR_REF( nfaOffsets ) << "[" << state < < "] ) {\n" " if ( " << ARR_REF( nfaOffsets ) << "[" << state < < "] ) {\n"
" int alt = 0; \n" " " << alt << " = 0; \n"
" int new_recs = " << ARR_REF( nfaTargs ) < " " << new_recs << " = " << ARR_REF( nfaTar
< "[" << CAST("int") << gs ) << "[" << CAST("int") <<
ARR_REF( nfaOffsets ) << "[" << s tate << "]];\n"; ARR_REF( nfaOffsets ) << "[" << s tate << "]];\n";
if ( red->nfaPrePushExpr != 0 ) { if ( red->nfaPrePushExpr != 0 ) {
out << OPEN_HOST_BLOCK( red->nfaPrePushExpr ); out << OPEN_HOST_BLOCK( red->nfaPrePushExpr );
INLINE_LIST( out, red->nfaPrePushExpr->inlineList, 0, fal se, false ); INLINE_LIST( out, red->nfaPrePushExpr->inlineList, 0, fal se, false );
out << CLOSE_HOST_BLOCK(); out << CLOSE_HOST_BLOCK();
out << "\n";
genOutputLineDirective( out );
} }
out << out <<
" while ( alt < new_recs ) { \n"; " while ( " << alt << " < " << new_recs << " ) { \n";
out << out <<
" nfa_bp[nfa_len].state = " << ARR_ REF( nfaTargs ) << "[" << CAST("int") << " nfa_bp[nfa_len].state = " << ARR_ REF( nfaTargs ) << "[" << CAST("int") <<
ARR_REF( nfaOffsets ) << "[" << state << "] + 1 + alt];\n" ARR_REF( nfaOffsets ) << "[" << state << "] + 1 + " << alt << "];\n"
" nfa_bp[nfa_len].p = " << P() << " ;\n"; " nfa_bp[nfa_len].p = " << P() << " ;\n";
if ( redFsm->bAnyNfaPops ) { if ( redFsm->bAnyNfaPops ) {
out << out <<
" nfa_bp[nfa_len].popTrans " nfa_bp[nfa_len].popTrans
= " << CAST("long") << = " << ARR_REF( nfaPopTrans ) << "[" << CAST("long") <<
ARR_REF( nfaOffse ARR_REF( nfaOffse
ts ) << "[" << state << "] + 1 + alt;\n" ts ) << "[" << state << "] + 1 + " << alt << "];\n"
"\n" "\n"
; ;
} }
if ( redFsm->bAnyNfaPushes ) { if ( redFsm->bAnyNfaPushes ) {
out << out <<
" switch ( " << ARR_REF( nf aPushActions ) << "[" << CAST("int") << " switch ( " << ARR_REF( nf aPushActions ) << "[" << CAST("int") <<
ARR_REF( nfaOffse ts ) << "[" << state << "] + 1 + alt] ) {\n"; ARR_REF( nfaOffse ts ) << "[" << state << "] + 1 + " << alt << "] ) {\n";
/* Loop the actions. */ /* Loop the actions. */
for ( GenActionTableMap::Iter redAct = redFsm->actionMap; for ( GenActionTableMap::Iter redAct = redFsm->actionMap;
redAct.lte(); redAct++ ) redAct.lte(); redAct++ )
{ {
if ( redAct->numNfaPushRefs > 0 ) { if ( redAct->numNfaPushRefs > 0 ) {
/* Write the entry label. */ /* Write the entry label. */
out << "\t " << CASE( STR( redAct->actLis tId+1 ) ) << " {\n"; out << "\t " << CASE( STR( redAct->actLis tId+1 ) ) << " {\n";
/* Write each action in the list of actio n items. */ /* Write each action in the list of actio n items. */
for ( GenActionTable::Iter item = redAct- >key; item.lte(); item++ ) for ( GenActionTable::Iter item = redAct- >key; item.lte(); item++ )
ACTION( out, item->value, IlOpts( 0, false, false ) ); ACTION( out, item->value, IlOpts( 0, false, false ) );
out << "\n\t" << CEND() << "}\n"; out << "\n\t" << CEND() << "\n}\n";
} }
} }
out << out <<
" }\n"; " }\n";
} }
out << out <<
" nfa_len += 1;\n" " nfa_len += 1;\n"
" alt += 1;\n" " " << alt << " += 1;\n"
" }\n" " }\n"
" }\n" " }\n"
; ;
} }
} }
void CodeGen::NFA_POST_POP()
{
if ( red->nfaPostPopExpr != 0 ) {
out << OPEN_HOST_BLOCK( red->nfaPostPopExpr );
INLINE_LIST( out, red->nfaPostPopExpr->inlineList, 0, false, fals
e );
out << CLOSE_HOST_BLOCK();
}
}
 End of changes. 36 change blocks. 
48 lines changed or deleted 104 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)