001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 the ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| You may obtain a copy of the License at | 008| | 009| http://www.apache.org/licenses/LICENSE-2.0 | 010| | 011| Unless required by applicable law or agreed to in writing, software | 012| distributed under the License is distributed on an "AS IS" BASIS, | 013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 014| See the License for the specific language governing permissions and | 015| limitations under the License. | 016+-------------------------------------------------------------------------*/ 017package eu.cqse.check.framework.util.tokens; 018 019/** 020 * Matches a nested structure (e.g. nested parentheses). 021 */ 022public class SkipNestedPattern extends TokenPatternBase { 023 024 /** Matches the token that increases the nesting level. */ 025 private final TokenPatternBase openMatcher; 026 027 /** Matches the token that decreases the nesting level. */ 028 private final TokenPatternBase closeMatcher; 029 030 /** 031 * If this is <code>true</code>, this pattern matches, even if no nested 032 * structure is found at the current position. In this case, an empty match is 033 * returned. 034 */ 035 private final boolean optional; 036 037 /** Constructor. */ 038 public SkipNestedPattern(TokenPatternBase openMatcher, TokenPatternBase closeMatcher, boolean optional) { 039 this.openMatcher = openMatcher; 040 this.closeMatcher = closeMatcher; 041 this.optional = optional; 042 } 043 044 /** {@inheritDoc} */ 045 @Override 046 protected TokenPatternMatch matchesLocally(TokenStream stream) { 047 int beforeMatch = stream.getPosition(); 048 TokenPatternMatch parentMatch = createMatch(stream); 049 050 TokenPatternMatch openMatch = openMatcher.matches(stream); 051 if (openMatch == null) { 052 if (optional) { 053 stream.setPosition(beforeMatch); 054 return createMatch(stream); 055 } 056 return null; 057 } 058 parentMatch.mergeFrom(openMatch); 059 060 int level = 1; 061 while (level > 0) { 062 if (stream.isExhausted()) { 063 if (optional) { 064 stream.setPosition(beforeMatch); 065 return createMatch(stream); 066 } 067 return null; 068 } 069 070 int beforeAlternative = stream.getPosition(); 071 TokenPatternMatch alternativeSubMatch = closeMatcher.matches(stream); 072 if (alternativeSubMatch != null) { 073 parentMatch.mergeFrom(alternativeSubMatch); 074 level -= 1; 075 continue; 076 } 077 stream.setPosition(beforeAlternative); 078 079 alternativeSubMatch = openMatcher.matches(stream); 080 if (alternativeSubMatch != null) { 081 parentMatch.mergeFrom(alternativeSubMatch); 082 level += 1; 083 continue; 084 } 085 stream.setPosition(beforeAlternative); 086 087 stream.next(); 088 } 089 090 return parentMatch; 091 } 092 093 /** {@inheritDoc} */ 094 @Override 095 public String toString() { 096 return "SkipNested (OPEN: " + openMatcher.toString() + " CLOSE: " + closeMatcher.toString() + " optional: " 097 + openMatcher + ")"; 098 } 099 100}