#if DEBUG_DUMP_VERIFY bool SkPathOpsDebug::gDumpOp; // set to true to write op to file before a crash bool SkPathOpsDebug::gVerifyOp; // set to true to compare result against regions #endif
bool SkPathOpsDebug::gRunFail; // set to true to check for success on tests known to fail bool SkPathOpsDebug::gVeryVerbose; // set to true to run extensive checking tests
#define FAIL_IF_COIN(cond, coin) \ do { if (cond) log->record(SkPathOpsDebug::kFail_Glitch, coin); } while (false)
#undef FAIL_WITH_NULL_IF #define FAIL_WITH_NULL_IF(cond, span) \ do { if (cond) log->record(SkPathOpsDebug::kFail_Glitch, span); } while (false)
#define RETURN_FALSE_IF(cond, span) \ do { if (cond) log->record(SkPathOpsDebug::kReturnFalse_Glitch, span); \
} while (false)
#if DEBUG_SORT int SkPathOpsDebug::gSortCountDefault = SK_MaxS32; int SkPathOpsDebug::gSortCount; #endif
#if DEBUG_COIN // commented-out lines keep this in sync with addT() const SkOpPtT* SkOpSegment::debugAddT(double t, SkPathOpsDebug::GlitchLog* log) const {
debugValidate();
SkPoint pt = this->ptAtT(t); const SkOpSpanBase* span = &fHead; do { const SkOpPtT* result = span->ptT(); if (t == result->fT || this->match(result, this, t, pt)) { // span->bumpSpanAdds(); return result;
} if (t < result->fT) { const SkOpSpan* prev = result->span()->prev();
FAIL_WITH_NULL_IF(!prev, span); // marks in global state that new op span has been allocated
this->globalState()->setAllocatedOpSpan(); // span->init(this, prev, t, pt);
this->debugValidate(); // #if DEBUG_ADD_T // SkDebugf("%s insert t=%1.9g segID=%d spanID=%d\n", __FUNCTION__, t, // span->segment()->debugID(), span->debugID()); // #endif // span->bumpSpanAdds(); return nullptr;
}
FAIL_WITH_NULL_IF(span != &fTail, span);
} while ((span = span->upCast()->next()));
SkASSERT(0); return nullptr; // we never get here, but need this to satisfy compiler
} #endif
#if DEBUG_ANGLE void SkOpSegment::debugCheckAngleCoin() const { const SkOpSpanBase* base = &fHead; const SkOpSpan* span; do { const SkOpAngle* angle = base->fromAngle(); if (angle && angle->debugCheckCoincidence()) {
angle->debugCheckNearCoincidence();
} if (base->final()) { break;
}
span = base->upCast();
angle = span->toAngle(); if (angle && angle->debugCheckCoincidence()) {
angle->debugCheckNearCoincidence();
}
} while ((base = span->next()));
} #endif
#if DEBUG_COIN // this mimics the order of the checks in handle coincidence void SkOpSegment::debugCheckHealth(SkPathOpsDebug::GlitchLog* glitches) const {
debugMoveMultiples(glitches);
debugMoveNearby(glitches);
debugMissingCoincidence(glitches);
}
// commented-out lines keep this in sync with clearAll() void SkOpSegment::debugClearAll(SkPathOpsDebug::GlitchLog* glitches) const { const SkOpSpan* span = &fHead; do {
this->debugClearOne(span, glitches);
} while ((span = span->next()->upCastable()));
this->globalState()->coincidence()->debugRelease(glitches, this);
}
// commented-out lines keep this in sync with clearOne() void SkOpSegment::debugClearOne(const SkOpSpan* span, SkPathOpsDebug::GlitchLog* glitches) const { if (span->windValue()) glitches->record(SkPathOpsDebug::kCollapsedWindValue_Glitch, span); if (span->oppValue()) glitches->record(SkPathOpsDebug::kCollapsedOppValue_Glitch, span); if (!span->done()) glitches->record(SkPathOpsDebug::kCollapsedDone_Glitch, span);
} #endif
SkOpAngle* SkOpSegment::debugLastAngle() {
SkOpAngle* result = nullptr;
SkOpSpan* span = this->head(); do { if (span->toAngle()) {
SkASSERT(!result);
result = span->toAngle();
}
} while ((span = span->next()->upCastable()));
SkASSERT(result); return result;
}
#if DEBUG_COIN // commented-out lines keep this in sync with ClearVisited void SkOpSegment::DebugClearVisited(const SkOpSpanBase* span) { // reset visited flag back to false do { const SkOpPtT* ptT = span->ptT(), * stopPtT = ptT; while ((ptT = ptT->next()) != stopPtT) { const SkOpSegment* opp = ptT->segment();
opp->resetDebugVisited();
}
} while (!span->final() && (span = span->upCast()->next()));
} #endif
#if DEBUG_COIN // commented-out lines keep this in sync with missingCoincidence() // look for pairs of undetected coincident curves // assumes that segments going in have visited flag clear // Even though pairs of curves correct detect coincident runs, a run may be missed // if the coincidence is a product of multiple intersections. For instance, given // curves A, B, and C: // A-B intersect at a point 1; A-C and B-C intersect at point 2, so near // the end of C that the intersection is replaced with the end of C. // Even though A-B correctly do not detect an intersection at point 2, // the resulting run from point 1 to point 2 is coincident on A and B. void SkOpSegment::debugMissingCoincidence(SkPathOpsDebug::GlitchLog* log) const { if (this->done()) { return;
} const SkOpSpan* prior = nullptr; const SkOpSpanBase* spanBase = &fHead; // bool result = false; do { const SkOpPtT* ptT = spanBase->ptT(), * spanStopPtT = ptT;
SkASSERT(ptT->span() == spanBase); while ((ptT = ptT->next()) != spanStopPtT) { if (ptT->deleted()) { continue;
} const SkOpSegment* opp = ptT->span()->segment(); if (opp->done()) { continue;
} // when opp is encounted the 1st time, continue; on 2nd encounter, look for coincidence if (!opp->debugVisited()) { continue;
} if (spanBase == &fHead) { continue;
} if (ptT->segment() == this) { continue;
} const SkOpSpan* span = spanBase->upCastable(); // FIXME?: this assumes that if the opposite segment is coincident then no more // coincidence needs to be detected. This may not be true. if (span && span->segment() != opp && span->containsCoincidence(opp)) { // debug has additional condition since it may be called before inner duplicate points have been deleted continue;
} if (spanBase->segment() != opp && spanBase->containsCoinEnd(opp)) { // debug has additional condition since it may be called before inner duplicate points have been deleted continue;
} const SkOpPtT* priorPtT = nullptr, * priorStopPtT; // find prior span containing opp segment const SkOpSegment* priorOpp = nullptr; const SkOpSpan* priorTest = spanBase->prev(); while (!priorOpp && priorTest) {
priorStopPtT = priorPtT = priorTest->ptT(); while ((priorPtT = priorPtT->next()) != priorStopPtT) { if (priorPtT->deleted()) { continue;
} const SkOpSegment* segment = priorPtT->span()->segment(); if (segment == opp) {
prior = priorTest;
priorOpp = opp; break;
}
}
priorTest = priorTest->prev();
} if (!priorOpp) { continue;
} if (priorPtT == ptT) { continue;
} const SkOpPtT* oppStart = prior->ptT(); const SkOpPtT* oppEnd = spanBase->ptT(); bool swapped = priorPtT->fT > ptT->fT; if (swapped) { using std::swap;
swap(priorPtT, ptT);
swap(oppStart, oppEnd);
} const SkOpCoincidence* coincidence = this->globalState()->coincidence(); const SkOpPtT* rootPriorPtT = priorPtT->span()->ptT(); const SkOpPtT* rootPtT = ptT->span()->ptT(); const SkOpPtT* rootOppStart = oppStart->span()->ptT(); const SkOpPtT* rootOppEnd = oppEnd->span()->ptT(); if (coincidence->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)) { goto swapBack;
} if (testForCoincidence(rootPriorPtT, rootPtT, prior, spanBase, opp)) { // mark coincidence #if DEBUG_COINCIDENCE_VERBOSE // SkDebugf("%s coinSpan=%d endSpan=%d oppSpan=%d oppEndSpan=%d\n", __FUNCTION__, // rootPriorPtT->debugID(), rootPtT->debugID(), rootOppStart->debugID(), // rootOppEnd->debugID()); #endif
log->record(SkPathOpsDebug::kMissingCoin_Glitch, priorPtT, ptT, oppStart, oppEnd); // coincidences->add(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd); // } #if DEBUG_COINCIDENCE // SkASSERT(coincidences->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd); #endif // result = true;
}
swapBack: if (swapped) { using std::swap;
swap(priorPtT, ptT);
}
}
} while ((spanBase = spanBase->final() ? nullptr : spanBase->upCast()->next()));
DebugClearVisited(&fHead); return;
}
// commented-out lines keep this in sync with moveMultiples() // if a span has more than one intersection, merge the other segments' span as needed void SkOpSegment::debugMoveMultiples(SkPathOpsDebug::GlitchLog* glitches) const {
debugValidate(); const SkOpSpanBase* test = &fHead; do { int addCount = test->spanAddsCount(); // SkASSERT(addCount >= 1); if (addCount <= 1) { continue;
} const SkOpPtT* startPtT = test->ptT(); const SkOpPtT* testPtT = startPtT; do { // iterate through all spans associated with start const SkOpSpanBase* oppSpan = testPtT->span(); if (oppSpan->spanAddsCount() == addCount) { continue;
} if (oppSpan->deleted()) { continue;
} const SkOpSegment* oppSegment = oppSpan->segment(); if (oppSegment == this) { continue;
} // find range of spans to consider merging const SkOpSpanBase* oppPrev = oppSpan; const SkOpSpanBase* oppFirst = oppSpan; while ((oppPrev = oppPrev->prev())) { if (!roughly_equal(oppPrev->t(), oppSpan->t())) { break;
} if (oppPrev->spanAddsCount() == addCount) { continue;
} if (oppPrev->deleted()) { continue;
}
oppFirst = oppPrev;
} const SkOpSpanBase* oppNext = oppSpan; const SkOpSpanBase* oppLast = oppSpan; while ((oppNext = oppNext->final() ? nullptr : oppNext->upCast()->next())) { if (!roughly_equal(oppNext->t(), oppSpan->t())) { break;
} if (oppNext->spanAddsCount() == addCount) { continue;
} if (oppNext->deleted()) { continue;
}
oppLast = oppNext;
} if (oppFirst == oppLast) { continue;
} const SkOpSpanBase* oppTest = oppFirst; do { if (oppTest == oppSpan) { continue;
} // check to see if the candidate meets specific criteria: // it contains spans of segments in test's loop but not including 'this' const SkOpPtT* oppStartPtT = oppTest->ptT(); const SkOpPtT* oppPtT = oppStartPtT; while ((oppPtT = oppPtT->next()) != oppStartPtT) { const SkOpSegment* oppPtTSegment = oppPtT->segment(); if (oppPtTSegment == this) { goto tryNextSpan;
} const SkOpPtT* matchPtT = startPtT; do { if (matchPtT->segment() == oppPtTSegment) { goto foundMatch;
}
} while ((matchPtT = matchPtT->next()) != startPtT); goto tryNextSpan;
foundMatch: // merge oppTest and oppSpan
oppSegment->debugValidate();
oppTest->debugMergeMatches(glitches, oppSpan);
oppTest->debugAddOpp(glitches, oppSpan);
oppSegment->debugValidate(); goto checkNextSpan;
}
tryNextSpan:
;
} while (oppTest != oppLast && (oppTest = oppTest->upCast()->next()));
} while ((testPtT = testPtT->next()) != startPtT);
checkNextSpan:
;
} while ((test = test->final() ? nullptr : test->upCast()->next()));
debugValidate(); return;
}
// commented-out lines keep this in sync with moveNearby() // Move nearby t values and pts so they all hang off the same span. Alignment happens later. void SkOpSegment::debugMoveNearby(SkPathOpsDebug::GlitchLog* glitches) const {
debugValidate(); // release undeleted spans pointing to this seg that are linked to the primary span const SkOpSpanBase* spanBase = &fHead; do { const SkOpPtT* ptT = spanBase->ptT(); const SkOpPtT* headPtT = ptT; while ((ptT = ptT->next()) != headPtT) { const SkOpSpanBase* test = ptT->span(); if (ptT->segment() == this && !ptT->deleted() && test != spanBase
&& test->ptT() == ptT) { if (test->final()) { if (spanBase == &fHead) {
glitches->record(SkPathOpsDebug::kMoveNearbyClearAll_Glitch, this); // return;
}
glitches->record(SkPathOpsDebug::kMoveNearbyReleaseFinal_Glitch, spanBase, ptT);
} elseif (test->prev()) {
glitches->record(SkPathOpsDebug::kMoveNearbyRelease_Glitch, test, headPtT);
} // break;
}
}
spanBase = spanBase->upCast()->next();
} while (!spanBase->final());
// This loop looks for adjacent spans which are near by
spanBase = &fHead; do { // iterate through all spans associated with start const SkOpSpanBase* test = spanBase->upCast()->next(); bool found; if (!this->spansNearby(spanBase, test, &found)) {
glitches->record(SkPathOpsDebug::kMoveNearbyMergeFinal_Glitch, test);
} if (found) { if (test->final()) { if (spanBase->prev()) {
glitches->record(SkPathOpsDebug::kMoveNearbyMergeFinal_Glitch, test);
} else {
glitches->record(SkPathOpsDebug::kMoveNearbyClearAll2_Glitch, this); // return
}
} else {
glitches->record(SkPathOpsDebug::kMoveNearbyMerge_Glitch, spanBase);
}
}
spanBase = test;
} while (!spanBase->final());
debugValidate();
} #endif
#if DEBUG_SORT void SkOpAngle::debugLoop() const { const SkOpAngle* first = this; const SkOpAngle* next = this; do {
next->dumpOne(true);
SkDebugf("\n");
next = next->fNext;
} while (next && next != first);
next = first; do {
next->debugValidate();
next = next->fNext;
} while (next && next != first);
} #endif
void SkOpAngle::debugValidate() const { #if DEBUG_COINCIDENCE if (this->globalState()->debugCheckHealth()) { return;
} #endif #if DEBUG_VALIDATE const SkOpAngle* first = this; const SkOpAngle* next = this; int wind = 0; int opp = 0; int lastXor = -1; int lastOppXor = -1; do { if (next->unorderable()) { return;
} const SkOpSpan* minSpan = next->start()->starter(next->end()); if (minSpan->windValue() == SK_MinS32) { return;
} bool op = next->segment()->operand(); bool isXor = next->segment()->isXor(); bool oppXor = next->segment()->oppXor();
SkASSERT(!DEBUG_LIMIT_WIND_SUM || between(0, minSpan->windValue(), DEBUG_LIMIT_WIND_SUM));
SkASSERT(!DEBUG_LIMIT_WIND_SUM
|| between(-DEBUG_LIMIT_WIND_SUM, minSpan->oppValue(), DEBUG_LIMIT_WIND_SUM)); bool useXor = op ? oppXor : isXor;
SkASSERT(lastXor == -1 || lastXor == (int) useXor);
lastXor = (int) useXor;
wind += next->debugSign() * (op ? minSpan->oppValue() : minSpan->windValue()); if (useXor) {
wind &= 1;
}
useXor = op ? isXor : oppXor;
SkASSERT(lastOppXor == -1 || lastOppXor == (int) useXor);
lastOppXor = (int) useXor;
opp += next->debugSign() * (op ? minSpan->windValue() : minSpan->oppValue()); if (useXor) {
opp &= 1;
}
next = next->fNext;
} while (next && next != first);
SkASSERT(wind == 0 || !SkPathOpsDebug::gRunFail);
SkASSERT(opp == 0 || !SkPathOpsDebug::gRunFail); #endif
}
void SkOpAngle::debugValidateNext() const { #if !FORCE_RELEASE const SkOpAngle* first = this; const SkOpAngle* next = first;
SkTDArray<const SkOpAngle*> angles; do { // SkASSERT_RELEASE(next->fSegment->debugContains(next));
angles.push_back(next);
next = next->next(); if (next == first) { break;
}
SkASSERT_RELEASE(!angles.contains(next)); if (!next) { return;
}
} while (true); #endif
}
#if DEBUG_COIN // sets the span's end to the ptT referenced by the previous-next void SkCoincidentSpans::debugCorrectOneEnd(SkPathOpsDebug::GlitchLog* log, const SkOpPtT* (SkCoincidentSpans::* getEnd)() const, void (SkCoincidentSpans::*setEnd)(const SkOpPtT* ptT) const ) const { const SkOpPtT* origPtT = (this->*getEnd)(); const SkOpSpanBase* origSpan = origPtT->span(); const SkOpSpan* prev = origSpan->prev(); const SkOpPtT* testPtT = prev ? prev->next()->ptT()
: origSpan->upCast()->next()->prev()->ptT(); if (origPtT != testPtT) {
log->record(SkPathOpsDebug::kCorrectEnd_Glitch, this, origPtT, testPtT);
}
}
/* Commented-out lines keep this in sync with correctEnds */ // FIXME: member pointers have fallen out of favor and can be replaced with // an alternative approach. // makes all span ends agree with the segment's spans that define them void SkCoincidentSpans::debugCorrectEnds(SkPathOpsDebug::GlitchLog* log) const {
this->debugCorrectOneEnd(log, &SkCoincidentSpans::coinPtTStart, nullptr);
this->debugCorrectOneEnd(log, &SkCoincidentSpans::coinPtTEnd, nullptr);
this->debugCorrectOneEnd(log, &SkCoincidentSpans::oppPtTStart, nullptr);
this->debugCorrectOneEnd(log, &SkCoincidentSpans::oppPtTEnd, nullptr);
}
/* Commented-out lines keep this in sync with expand */ // expand the range by checking adjacent spans for coincidence bool SkCoincidentSpans::debugExpand(SkPathOpsDebug::GlitchLog* log) const { bool expanded = false; const SkOpSegment* segment = coinPtTStart()->segment(); const SkOpSegment* oppSegment = oppPtTStart()->segment(); do { const SkOpSpan* start = coinPtTStart()->span()->upCast(); const SkOpSpan* prev = start->prev(); const SkOpPtT* oppPtT; if (!prev || !(oppPtT = prev->contains(oppSegment))) { break;
} double midT = (prev->t() + start->t()) / 2; if (!segment->isClose(midT, oppSegment)) { break;
} if (log) log->record(SkPathOpsDebug::kExpandCoin_Glitch, this, prev->ptT(), oppPtT);
expanded = true;
} while (false); // actual continues while expansion is possible do { const SkOpSpanBase* end = coinPtTEnd()->span();
SkOpSpanBase* next = end->final() ? nullptr : end->upCast()->next(); if (next && next->deleted()) { break;
} const SkOpPtT* oppPtT; if (!next || !(oppPtT = next->contains(oppSegment))) { break;
} double midT = (end->t() + next->t()) / 2; if (!segment->isClose(midT, oppSegment)) { break;
} if (log) log->record(SkPathOpsDebug::kExpandCoin_Glitch, this, next->ptT(), oppPtT);
expanded = true;
} while (false); // actual continues while expansion is possible return expanded;
}
/* If A is coincident with B and B includes an endpoint, and A's matching point is not the endpoint (i.e., there's an implied line connecting B-end and A) then assume that the same implied line may intersect another curve close to B. Since we only care about coincidence that was undetected, look at the ptT list on B-segment adjacent to the B-end/A ptT loop (not in the loop, but next door) and see if the A matching point is close enough to form another coincident pair. If so, check for a new coincident span between B-end/A ptT loop and the adjacent ptT loop.
*/ void SkOpCoincidence::debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* log) const { const SkCoincidentSpans* span = fHead; if (!span) { return;
} // fTop = span; // fHead = nullptr; do { if (span->coinPtTStart()->fPt != span->oppPtTStart()->fPt) {
FAIL_IF_COIN(1 == span->coinPtTStart()->fT, span); bool onEnd = span->coinPtTStart()->fT == 0; bool oOnEnd = zero_or_one(span->oppPtTStart()->fT); if (onEnd) { if (!oOnEnd) { // if both are on end, any nearby intersect was already found
this->debugAddEndMovedSpans(log, span->oppPtTStart());
}
} elseif (oOnEnd) {
this->debugAddEndMovedSpans(log, span->coinPtTStart());
}
} if (span->coinPtTEnd()->fPt != span->oppPtTEnd()->fPt) { bool onEnd = span->coinPtTEnd()->fT == 1; bool oOnEnd = zero_or_one(span->oppPtTEnd()->fT); if (onEnd) { if (!oOnEnd) {
this->debugAddEndMovedSpans(log, span->oppPtTEnd());
}
} elseif (oOnEnd) {
this->debugAddEndMovedSpans(log, span->coinPtTEnd());
}
}
} while ((span = span->next())); // this->restoreHead(); return;
}
/* Commented-out lines keep this in sync with addExpanded */ // for each coincident pair, match the spans // if the spans don't match, add the mssing pt to the segment and loop it in the opposite span void SkOpCoincidence::debugAddExpanded(SkPathOpsDebug::GlitchLog* log) const { // DEBUG_SET_PHASE(); const SkCoincidentSpans* coin = this->fHead; if (!coin) { return;
} do { const SkOpPtT* startPtT = coin->coinPtTStart(); const SkOpPtT* oStartPtT = coin->oppPtTStart(); double priorT = startPtT->fT; double oPriorT = oStartPtT->fT;
FAIL_IF_COIN(!startPtT->contains(oStartPtT), coin);
SkOPASSERT(coin->coinPtTEnd()->contains(coin->oppPtTEnd())); const SkOpSpanBase* start = startPtT->span(); const SkOpSpanBase* oStart = oStartPtT->span(); const SkOpSpanBase* end = coin->coinPtTEnd()->span(); const SkOpSpanBase* oEnd = coin->oppPtTEnd()->span();
FAIL_IF_COIN(oEnd->deleted(), coin);
FAIL_IF_COIN(!start->upCastable(), coin); const SkOpSpanBase* test = start->upCast()->next();
FAIL_IF_COIN(!coin->flipped() && !oStart->upCastable(), coin); const SkOpSpanBase* oTest = coin->flipped() ? oStart->prev() : oStart->upCast()->next();
FAIL_IF_COIN(!oTest, coin); const SkOpSegment* seg = start->segment(); const SkOpSegment* oSeg = oStart->segment(); while (test != end || oTest != oEnd) { const SkOpPtT* containedOpp = test->ptT()->contains(oSeg); const SkOpPtT* containedThis = oTest->ptT()->contains(seg); if (!containedOpp || !containedThis) { // choose the ends, or the first common pt-t list shared by both double nextT, oNextT; if (containedOpp) {
nextT = test->t();
oNextT = containedOpp->fT;
} elseif (containedThis) {
nextT = containedThis->fT;
oNextT = oTest->t();
} else { // iterate through until a pt-t list found that contains the other const SkOpSpanBase* walk = test; const SkOpPtT* walkOpp; do {
FAIL_IF_COIN(!walk->upCastable(), coin);
walk = walk->upCast()->next();
} while (!(walkOpp = walk->ptT()->contains(oSeg))
&& walk != coin->coinPtTEnd()->span());
FAIL_IF_COIN(!walkOpp, coin);
nextT = walk->t();
oNextT = walkOpp->fT;
} // use t ranges to guess which one is missing double startRange = nextT - priorT;
FAIL_IF_COIN(!startRange, coin); double startPart = (test->t() - priorT) / startRange; double oStartRange = oNextT - oPriorT;
FAIL_IF_COIN(!oStartRange, coin); double oStartPart = (oTest->t() - oStartPtT->fT) / oStartRange;
FAIL_IF_COIN(startPart == oStartPart, coin); bool addToOpp = !containedOpp && !containedThis ? startPart < oStartPart
: !!containedThis; bool startOver = false;
addToOpp ? log->record(SkPathOpsDebug::kAddExpandedCoin_Glitch,
oPriorT + oStartRange * startPart, test)
: log->record(SkPathOpsDebug::kAddExpandedCoin_Glitch,
priorT + startRange * oStartPart, oTest); // FAIL_IF_COIN(!success, coin); if (startOver) {
test = start;
oTest = oStart;
}
end = coin->coinPtTEnd()->span();
oEnd = coin->oppPtTEnd()->span();
} if (test != end) {
FAIL_IF_COIN(!test->upCastable(), coin);
priorT = test->t();
test = test->upCast()->next();
} if (oTest != oEnd) {
oPriorT = oTest->t();
oTest = coin->flipped() ? oTest->prev() : oTest->upCast()->next();
FAIL_IF_COIN(!oTest, coin);
}
}
} while ((coin = coin->next())); return;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.