Changeset 72781 in webkit for trunk/JavaScriptCore/yarr/RegexInterpreter.cpp
- Timestamp:
- Nov 28, 2010, 4:45:16 PM (15 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/JavaScriptCore/yarr/RegexInterpreter.cpp
r72489 r72781 61 61 }; 62 62 struct BackTrackInfoParenthesesOnce { 63 uintptr_t inParentheses; 63 uintptr_t begin; 64 }; 65 struct BackTrackInfoParenthesesTerminal { 66 uintptr_t begin; 64 67 }; 65 68 struct BackTrackInfoParentheses { … … 632 635 case QuantifierGreedy: { 633 636 // set this speculatively; if we get to the parens end this will be true. 634 backTrack-> inParentheses = 1;637 backTrack->begin = input.getPos(); 635 638 break; 636 639 } 637 640 case QuantifierNonGreedy: { 638 backTrack-> inParentheses = 0;641 backTrack->begin = notFound; 639 642 context->term += term.atom.parenthesesWidth; 640 643 return true; … … 652 655 } 653 656 654 bool matchParenthesesOnceEnd(ByteTerm& term, DisjunctionContext* )657 bool matchParenthesesOnceEnd(ByteTerm& term, DisjunctionContext* context) 655 658 { 656 659 ASSERT(term.type == ByteTerm::TypeParenthesesSubpatternOnceEnd); … … 661 664 output[(subpatternId << 1) + 1] = input.getPos() + term.inputPosition; 662 665 } 663 return true; 666 667 if (term.atom.quantityType == QuantifierFixedCount) 668 return true; 669 670 BackTrackInfoParenthesesOnce* backTrack = reinterpret_cast<BackTrackInfoParenthesesOnce*>(context->frame + term.frameLocation); 671 return backTrack->begin != input.getPos(); 664 672 } 665 673 … … 680 688 case QuantifierGreedy: 681 689 // if we backtrack to this point, there is another chance - try matching nothing. 682 ASSERT(backTrack-> inParentheses);683 backTrack-> inParentheses = 0;690 ASSERT(backTrack->begin != notFound); 691 backTrack->begin = notFound; 684 692 context->term += term.atom.parenthesesWidth; 685 693 return true; 686 694 case QuantifierNonGreedy: 687 ASSERT(backTrack-> inParentheses);695 ASSERT(backTrack->begin != notFound); 688 696 case QuantifierFixedCount: 689 697 break; … … 702 710 switch (term.atom.quantityType) { 703 711 case QuantifierGreedy: 704 if ( !backTrack->inParentheses) {712 if (backTrack->begin == notFound) { 705 713 context->term -= term.atom.parenthesesWidth; 706 714 return false; 707 715 } 708 716 case QuantifierNonGreedy: 709 if (!backTrack->inParentheses) { 710 // now try to match the parens; set this speculatively. 711 backTrack->inParentheses = 1; 717 if (backTrack->begin == notFound) { 718 backTrack->begin = input.getPos(); 712 719 if (term.capture()) { 720 // Technically this access to inputPosition should be accessing the begin term's 721 // inputPosition, but for repeats other than fixed these values should be 722 // the same anyway! (we don't pre-check for greedy or non-greedy matches.) 723 ASSERT((&term - term.atom.parenthesesWidth)->type == ByteTerm::TypeParenthesesSubpatternOnceBegin); 724 ASSERT((&term - term.atom.parenthesesWidth)->inputPosition == term.inputPosition); 713 725 unsigned subpatternId = term.atom.subpatternId; 714 726 output[subpatternId << 1] = input.getPos() + term.inputPosition; … … 721 733 } 722 734 735 return false; 736 } 737 738 bool matchParenthesesTerminalBegin(ByteTerm& term, DisjunctionContext* context) 739 { 740 ASSERT(term.type == ByteTerm::TypeParenthesesSubpatternTerminalBegin); 741 ASSERT(term.atom.quantityType == QuantifierGreedy); 742 ASSERT(term.atom.quantityCount == UINT_MAX); 743 ASSERT(!term.capture()); 744 745 BackTrackInfoParenthesesTerminal* backTrack = reinterpret_cast<BackTrackInfoParenthesesTerminal*>(context->frame + term.frameLocation); 746 backTrack->begin = input.getPos(); 747 return true; 748 } 749 750 bool matchParenthesesTerminalEnd(ByteTerm& term, DisjunctionContext* context) 751 { 752 ASSERT(term.type == ByteTerm::TypeParenthesesSubpatternTerminalEnd); 753 754 BackTrackInfoParenthesesTerminal* backTrack = reinterpret_cast<BackTrackInfoParenthesesTerminal*>(context->frame + term.frameLocation); 755 // Empty match is a failed match. 756 if (backTrack->begin == input.getPos()) 757 return false; 758 759 // Successful match! Okay, what's next? - loop around and try to match moar! 760 context->term -= (term.atom.parenthesesWidth + 1); 761 return true; 762 } 763 764 bool backtrackParenthesesTerminalBegin(ByteTerm& term, DisjunctionContext* context) 765 { 766 ASSERT(term.type == ByteTerm::TypeParenthesesSubpatternTerminalBegin); 767 ASSERT(term.atom.quantityType == QuantifierGreedy); 768 ASSERT(term.atom.quantityCount == UINT_MAX); 769 ASSERT(!term.capture()); 770 771 // If we backtrack to this point, we have failed to match this iteration of the parens. 772 // Since this is greedy / zero minimum a failed is also accepted as a match! 773 context->term += term.atom.parenthesesWidth; 774 return true; 775 } 776 777 bool backtrackParenthesesTerminalEnd(ByteTerm&, DisjunctionContext*) 778 { 779 // 'Terminal' parentheses are at the end of the regex, and as such a match past end 780 // should always be returned as a successful match - we should never becktrack to here. 781 ASSERT_NOT_REACHED(); 723 782 return false; 724 783 } … … 1170 1229 case ByteTerm::TypeParenthesesSubpatternOnceEnd: 1171 1230 if (matchParenthesesOnceEnd(currentTerm(), context)) 1231 MATCH_NEXT(); 1232 BACKTRACK(); 1233 case ByteTerm::TypeParenthesesSubpatternTerminalBegin: 1234 if (matchParenthesesTerminalBegin(currentTerm(), context)) 1235 MATCH_NEXT(); 1236 BACKTRACK(); 1237 case ByteTerm::TypeParenthesesSubpatternTerminalEnd: 1238 if (matchParenthesesTerminalEnd(currentTerm(), context)) 1172 1239 MATCH_NEXT(); 1173 1240 BACKTRACK(); … … 1285 1352 MATCH_NEXT(); 1286 1353 BACKTRACK(); 1354 case ByteTerm::TypeParenthesesSubpatternTerminalBegin: 1355 if (backtrackParenthesesTerminalBegin(currentTerm(), context)) 1356 MATCH_NEXT(); 1357 BACKTRACK(); 1358 case ByteTerm::TypeParenthesesSubpatternTerminalEnd: 1359 if (backtrackParenthesesTerminalEnd(currentTerm(), context)) 1360 MATCH_NEXT(); 1361 BACKTRACK(); 1287 1362 case ByteTerm::TypeParentheticalAssertionBegin: 1288 1363 if (backtrackParentheticalAssertionBegin(currentTerm(), context)) … … 1446 1521 } 1447 1522 1448 void atomParentheses SubpatternBegin(unsigned subpatternId, bool capture, int inputPosition, unsigned frameLocation, unsigned alternativeFrameLocation)1523 void atomParenthesesOnceBegin(unsigned subpatternId, bool capture, int inputPosition, unsigned frameLocation, unsigned alternativeFrameLocation) 1449 1524 { 1450 1525 int beginTerm = m_bodyDisjunction->terms.size(); … … 1459 1534 } 1460 1535 1536 void atomParenthesesTerminalBegin(unsigned subpatternId, bool capture, int inputPosition, unsigned frameLocation, unsigned alternativeFrameLocation) 1537 { 1538 int beginTerm = m_bodyDisjunction->terms.size(); 1539 1540 m_bodyDisjunction->terms.append(ByteTerm(ByteTerm::TypeParenthesesSubpatternTerminalBegin, subpatternId, capture, inputPosition)); 1541 m_bodyDisjunction->terms[m_bodyDisjunction->terms.size() - 1].frameLocation = frameLocation; 1542 m_bodyDisjunction->terms.append(ByteTerm::AlternativeBegin()); 1543 m_bodyDisjunction->terms[m_bodyDisjunction->terms.size() - 1].frameLocation = alternativeFrameLocation; 1544 1545 m_parenthesesStack.append(ParenthesesStackEntry(beginTerm, m_currentAlternativeIndex)); 1546 m_currentAlternativeIndex = beginTerm + 1; 1547 } 1548 1549 void atomParenthesesSubpatternBegin(unsigned subpatternId, bool capture, int inputPosition, unsigned frameLocation, unsigned alternativeFrameLocation) 1550 { 1551 // Errrk! - this is a little crazy, we initially generate as a TypeParenthesesSubpatternOnceBegin, 1552 // then fix this up at the end! - simplifying this should make it much clearer. 1553 // https://bugs.webkit.org/show_bug.cgi?id=50136 1554 1555 int beginTerm = m_bodyDisjunction->terms.size(); 1556 1557 m_bodyDisjunction->terms.append(ByteTerm(ByteTerm::TypeParenthesesSubpatternOnceBegin, subpatternId, capture, inputPosition)); 1558 m_bodyDisjunction->terms[m_bodyDisjunction->terms.size() - 1].frameLocation = frameLocation; 1559 m_bodyDisjunction->terms.append(ByteTerm::AlternativeBegin()); 1560 m_bodyDisjunction->terms[m_bodyDisjunction->terms.size() - 1].frameLocation = alternativeFrameLocation; 1561 1562 m_parenthesesStack.append(ParenthesesStackEntry(beginTerm, m_currentAlternativeIndex)); 1563 m_currentAlternativeIndex = beginTerm + 1; 1564 } 1565 1461 1566 void atomParentheticalAssertionBegin(unsigned subpatternId, bool invert, unsigned frameLocation, unsigned alternativeFrameLocation) 1462 1567 { … … 1470 1575 m_parenthesesStack.append(ParenthesesStackEntry(beginTerm, m_currentAlternativeIndex)); 1471 1576 m_currentAlternativeIndex = beginTerm + 1; 1577 } 1578 1579 void atomParentheticalAssertionEnd(int inputPosition, unsigned frameLocation, unsigned quantityCount, QuantifierType quantityType) 1580 { 1581 unsigned beginTerm = popParenthesesStack(); 1582 closeAlternative(beginTerm + 1); 1583 unsigned endTerm = m_bodyDisjunction->terms.size(); 1584 1585 ASSERT(m_bodyDisjunction->terms[beginTerm].type == ByteTerm::TypeParentheticalAssertionBegin); 1586 1587 bool invertOrCapture = m_bodyDisjunction->terms[beginTerm].invertOrCapture; 1588 unsigned subpatternId = m_bodyDisjunction->terms[beginTerm].atom.subpatternId; 1589 1590 m_bodyDisjunction->terms.append(ByteTerm(ByteTerm::TypeParentheticalAssertionEnd, subpatternId, invertOrCapture, inputPosition)); 1591 m_bodyDisjunction->terms[beginTerm].atom.parenthesesWidth = endTerm - beginTerm; 1592 m_bodyDisjunction->terms[endTerm].atom.parenthesesWidth = endTerm - beginTerm; 1593 m_bodyDisjunction->terms[endTerm].frameLocation = frameLocation; 1594 1595 m_bodyDisjunction->terms[beginTerm].atom.quantityCount = quantityCount; 1596 m_bodyDisjunction->terms[beginTerm].atom.quantityType = quantityType; 1597 m_bodyDisjunction->terms[endTerm].atom.quantityCount = quantityCount; 1598 m_bodyDisjunction->terms[endTerm].atom.quantityType = quantityType; 1472 1599 } 1473 1600 … … 1543 1670 } 1544 1671 1545 void atomParentheses End(bool doInline,unsigned lastSubpatternId, int inputPosition, unsigned frameLocation, unsigned quantityCount, QuantifierType quantityType, unsigned callFrameSize = 0)1672 void atomParenthesesSubpatternEnd(unsigned lastSubpatternId, int inputPosition, unsigned frameLocation, unsigned quantityCount, QuantifierType quantityType, unsigned callFrameSize = 0) 1546 1673 { 1547 1674 unsigned beginTerm = popParenthesesStack(); … … 1549 1676 unsigned endTerm = m_bodyDisjunction->terms.size(); 1550 1677 1551 bool isAssertion = m_bodyDisjunction->terms[beginTerm].type == ByteTerm::TypeParentheticalAssertionBegin; 1678 ASSERT(m_bodyDisjunction->terms[beginTerm].type == ByteTerm::TypeParenthesesSubpatternOnceBegin); 1679 1680 ByteTerm& parenthesesBegin = m_bodyDisjunction->terms[beginTerm]; 1681 1682 bool invertOrCapture = parenthesesBegin.invertOrCapture; 1683 unsigned subpatternId = parenthesesBegin.atom.subpatternId; 1684 1685 unsigned numSubpatterns = lastSubpatternId - subpatternId + 1; 1686 ByteDisjunction* parenthesesDisjunction = new ByteDisjunction(numSubpatterns, callFrameSize); 1687 1688 parenthesesDisjunction->terms.append(ByteTerm::SubpatternBegin()); 1689 for (unsigned termInParentheses = beginTerm + 1; termInParentheses < endTerm; ++termInParentheses) 1690 parenthesesDisjunction->terms.append(m_bodyDisjunction->terms[termInParentheses]); 1691 parenthesesDisjunction->terms.append(ByteTerm::SubpatternEnd()); 1692 1693 m_bodyDisjunction->terms.shrink(beginTerm); 1694 1695 m_allParenthesesInfo.append(parenthesesDisjunction); 1696 m_bodyDisjunction->terms.append(ByteTerm(ByteTerm::TypeParenthesesSubpattern, subpatternId, parenthesesDisjunction, invertOrCapture, inputPosition)); 1697 1698 m_bodyDisjunction->terms[beginTerm].atom.quantityCount = quantityCount; 1699 m_bodyDisjunction->terms[beginTerm].atom.quantityType = quantityType; 1700 m_bodyDisjunction->terms[beginTerm].frameLocation = frameLocation; 1701 } 1702 1703 void atomParenthesesOnceEnd(int inputPosition, unsigned frameLocation, unsigned quantityCount, QuantifierType quantityType) 1704 { 1705 unsigned beginTerm = popParenthesesStack(); 1706 closeAlternative(beginTerm + 1); 1707 unsigned endTerm = m_bodyDisjunction->terms.size(); 1708 1709 ASSERT(m_bodyDisjunction->terms[beginTerm].type == ByteTerm::TypeParenthesesSubpatternOnceBegin); 1710 1552 1711 bool invertOrCapture = m_bodyDisjunction->terms[beginTerm].invertOrCapture; 1553 1712 unsigned subpatternId = m_bodyDisjunction->terms[beginTerm].atom.subpatternId; 1554 1713 1555 m_bodyDisjunction->terms.append(ByteTerm( isAssertion ? ByteTerm::TypeParentheticalAssertionEnd :ByteTerm::TypeParenthesesSubpatternOnceEnd, subpatternId, invertOrCapture, inputPosition));1714 m_bodyDisjunction->terms.append(ByteTerm(ByteTerm::TypeParenthesesSubpatternOnceEnd, subpatternId, invertOrCapture, inputPosition)); 1556 1715 m_bodyDisjunction->terms[beginTerm].atom.parenthesesWidth = endTerm - beginTerm; 1557 1716 m_bodyDisjunction->terms[endTerm].atom.parenthesesWidth = endTerm - beginTerm; 1558 1717 m_bodyDisjunction->terms[endTerm].frameLocation = frameLocation; 1559 1718 1560 if (doInline) { 1561 m_bodyDisjunction->terms[beginTerm].atom.quantityCount = quantityCount; 1562 m_bodyDisjunction->terms[beginTerm].atom.quantityType = quantityType; 1563 m_bodyDisjunction->terms[endTerm].atom.quantityCount = quantityCount; 1564 m_bodyDisjunction->terms[endTerm].atom.quantityType = quantityType; 1565 } else { 1566 ByteTerm& parenthesesBegin = m_bodyDisjunction->terms[beginTerm]; 1567 ASSERT(parenthesesBegin.type == ByteTerm::TypeParenthesesSubpatternOnceBegin); 1568 1569 bool invertOrCapture = parenthesesBegin.invertOrCapture; 1570 unsigned subpatternId = parenthesesBegin.atom.subpatternId; 1571 1572 unsigned numSubpatterns = lastSubpatternId - subpatternId + 1; 1573 ByteDisjunction* parenthesesDisjunction = new ByteDisjunction(numSubpatterns, callFrameSize); 1574 1575 parenthesesDisjunction->terms.append(ByteTerm::SubpatternBegin()); 1576 for (unsigned termInParentheses = beginTerm + 1; termInParentheses < endTerm; ++termInParentheses) 1577 parenthesesDisjunction->terms.append(m_bodyDisjunction->terms[termInParentheses]); 1578 parenthesesDisjunction->terms.append(ByteTerm::SubpatternEnd()); 1579 1580 m_bodyDisjunction->terms.shrink(beginTerm); 1581 1582 m_allParenthesesInfo.append(parenthesesDisjunction); 1583 m_bodyDisjunction->terms.append(ByteTerm(ByteTerm::TypeParenthesesSubpattern, subpatternId, parenthesesDisjunction, invertOrCapture, inputPosition)); 1584 1585 m_bodyDisjunction->terms[beginTerm].atom.quantityCount = quantityCount; 1586 m_bodyDisjunction->terms[beginTerm].atom.quantityType = quantityType; 1587 m_bodyDisjunction->terms[beginTerm].frameLocation = frameLocation; 1588 } 1719 m_bodyDisjunction->terms[beginTerm].atom.quantityCount = quantityCount; 1720 m_bodyDisjunction->terms[beginTerm].atom.quantityType = quantityType; 1721 m_bodyDisjunction->terms[endTerm].atom.quantityCount = quantityCount; 1722 m_bodyDisjunction->terms[endTerm].atom.quantityType = quantityType; 1723 } 1724 1725 void atomParenthesesTerminalEnd(int inputPosition, unsigned frameLocation, unsigned quantityCount, QuantifierType quantityType) 1726 { 1727 unsigned beginTerm = popParenthesesStack(); 1728 closeAlternative(beginTerm + 1); 1729 unsigned endTerm = m_bodyDisjunction->terms.size(); 1730 1731 ASSERT(m_bodyDisjunction->terms[beginTerm].type == ByteTerm::TypeParenthesesSubpatternTerminalBegin); 1732 1733 bool invertOrCapture = m_bodyDisjunction->terms[beginTerm].invertOrCapture; 1734 unsigned subpatternId = m_bodyDisjunction->terms[beginTerm].atom.subpatternId; 1735 1736 m_bodyDisjunction->terms.append(ByteTerm(ByteTerm::TypeParenthesesSubpatternTerminalEnd, subpatternId, invertOrCapture, inputPosition)); 1737 m_bodyDisjunction->terms[beginTerm].atom.parenthesesWidth = endTerm - beginTerm; 1738 m_bodyDisjunction->terms[endTerm].atom.parenthesesWidth = endTerm - beginTerm; 1739 m_bodyDisjunction->terms[endTerm].frameLocation = frameLocation; 1740 1741 m_bodyDisjunction->terms[beginTerm].atom.quantityCount = quantityCount; 1742 m_bodyDisjunction->terms[beginTerm].atom.quantityType = quantityType; 1743 m_bodyDisjunction->terms[endTerm].atom.quantityCount = quantityCount; 1744 m_bodyDisjunction->terms[endTerm].atom.quantityType = quantityType; 1589 1745 } 1590 1746 … … 1681 1837 case PatternTerm::TypeParenthesesSubpattern: { 1682 1838 unsigned disjunctionAlreadyCheckedCount = 0; 1683 if ((term.quantityCount == 1) && !term.parentheses.isCopy) { 1684 if (term.quantityType == QuantifierFixedCount) { 1839 if (term.quantityCount == 1 && !term.parentheses.isCopy) { 1840 unsigned alternativeFrameLocation = term.frameLocation; 1841 // For QuantifierFixedCount we pre-check the minimum size; for greedy/non-greedy we reserve a slot in the frame. 1842 if (term.quantityType == QuantifierFixedCount) 1685 1843 disjunctionAlreadyCheckedCount = term.parentheses.disjunction->m_minimumSize; 1686 unsigned delegateEndInputOffset = term.inputPosition - currentCountAlreadyChecked; 1687 atomParenthesesSubpatternBegin(term.parentheses.subpatternId, term.invertOrCapture, delegateEndInputOffset - disjunctionAlreadyCheckedCount, term.frameLocation, term.frameLocation); 1688 emitDisjunction(term.parentheses.disjunction, currentCountAlreadyChecked, term.parentheses.disjunction->m_minimumSize); 1689 atomParenthesesEnd(true, term.parentheses.lastSubpatternId, delegateEndInputOffset, term.frameLocation, term.quantityCount, term.quantityType, term.parentheses.disjunction->m_callFrameSize); 1690 } else { 1691 unsigned delegateEndInputOffset = term.inputPosition - currentCountAlreadyChecked; 1692 atomParenthesesSubpatternBegin(term.parentheses.subpatternId, term.invertOrCapture, delegateEndInputOffset - disjunctionAlreadyCheckedCount, term.frameLocation, term.frameLocation + RegexStackSpaceForBackTrackInfoParenthesesOnce); 1693 emitDisjunction(term.parentheses.disjunction, currentCountAlreadyChecked, 0); 1694 atomParenthesesEnd(true, term.parentheses.lastSubpatternId, delegateEndInputOffset, term.frameLocation, term.quantityCount, term.quantityType, term.parentheses.disjunction->m_callFrameSize); 1695 } 1844 else 1845 alternativeFrameLocation += RegexStackSpaceForBackTrackInfoParenthesesOnce; 1846 unsigned delegateEndInputOffset = term.inputPosition - currentCountAlreadyChecked; 1847 atomParenthesesOnceBegin(term.parentheses.subpatternId, term.invertOrCapture, delegateEndInputOffset - disjunctionAlreadyCheckedCount, term.frameLocation, alternativeFrameLocation); 1848 emitDisjunction(term.parentheses.disjunction, currentCountAlreadyChecked, disjunctionAlreadyCheckedCount); 1849 atomParenthesesOnceEnd(delegateEndInputOffset, term.frameLocation, term.quantityCount, term.quantityType); 1850 } else if (term.parentheses.isTerminal) { 1851 unsigned delegateEndInputOffset = term.inputPosition - currentCountAlreadyChecked; 1852 atomParenthesesTerminalBegin(term.parentheses.subpatternId, term.invertOrCapture, delegateEndInputOffset - disjunctionAlreadyCheckedCount, term.frameLocation, term.frameLocation + RegexStackSpaceForBackTrackInfoParenthesesOnce); 1853 emitDisjunction(term.parentheses.disjunction, currentCountAlreadyChecked, disjunctionAlreadyCheckedCount); 1854 atomParenthesesTerminalEnd(delegateEndInputOffset, term.frameLocation, term.quantityCount, term.quantityType); 1696 1855 } else { 1697 1856 unsigned delegateEndInputOffset = term.inputPosition - currentCountAlreadyChecked; 1698 1857 atomParenthesesSubpatternBegin(term.parentheses.subpatternId, term.invertOrCapture, delegateEndInputOffset - disjunctionAlreadyCheckedCount, term.frameLocation, 0); 1699 1858 emitDisjunction(term.parentheses.disjunction, currentCountAlreadyChecked, 0); 1700 atomParentheses End(false,term.parentheses.lastSubpatternId, delegateEndInputOffset, term.frameLocation, term.quantityCount, term.quantityType, term.parentheses.disjunction->m_callFrameSize);1859 atomParenthesesSubpatternEnd(term.parentheses.lastSubpatternId, delegateEndInputOffset, term.frameLocation, term.quantityCount, term.quantityType, term.parentheses.disjunction->m_callFrameSize); 1701 1860 } 1702 1861 break; … … 1711 1870 atomParentheticalAssertionBegin(term.parentheses.subpatternId, term.invertOrCapture, term.frameLocation, alternativeFrameLocation); 1712 1871 emitDisjunction(term.parentheses.disjunction, currentCountAlreadyChecked, positiveInputOffset, true); 1713 atomParenthe sesEnd(true, term.parentheses.lastSubpatternId,0, term.frameLocation, term.quantityCount, term.quantityType);1872 atomParentheticalAssertionEnd(0, term.frameLocation, term.quantityCount, term.quantityType); 1714 1873 break; 1715 1874 }
Note:
See TracChangeset
for help on using the changeset viewer.