/*
* Copyright (C) 2009-2011 Mathias Doenitz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.fge.grappa.parsers;
import com.github.fge.grappa.annotations.Cached;
import com.github.fge.grappa.annotations.DontExtend;
import com.github.fge.grappa.annotations.DontLabel;
import com.github.fge.grappa.annotations.SkipActionsInPredicates;
import com.github.fge.grappa.exceptions.InvalidGrammarException;
import com.github.fge.grappa.matchers.ActionMatcher;
import com.github.fge.grappa.matchers.AnyMatcher;
import com.github.fge.grappa.matchers.AnyOfMatcher;
import com.github.fge.grappa.matchers.CharIgnoreCaseMatcher;
import com.github.fge.grappa.matchers.CharMatcher;
import com.github.fge.grappa.matchers.CharRangeMatcher;
import com.github.fge.grappa.matchers.EmptyMatcher;
import com.github.fge.grappa.matchers.EndOfInputMatcher;
import com.github.fge.grappa.matchers.NothingMatcher;
import com.github.fge.grappa.matchers.RegexMatcher;
import com.github.fge.grappa.matchers.StringIgnoreCaseMatcher;
import com.github.fge.grappa.matchers.StringMatcher;
import com.github.fge.grappa.matchers.delegate.FirstOfMatcher;
import com.github.fge.grappa.matchers.delegate.OptionalMatcher;
import com.github.fge.grappa.matchers.delegate.SequenceMatcher;
import com.github.fge.grappa.matchers.join.JoinMatcherBootstrap;
import com.github.fge.grappa.matchers.join.JoinMatcherBuilder;
import com.github.fge.grappa.matchers.predicates.TestMatcher;
import com.github.fge.grappa.matchers.predicates.TestNotMatcher;
import com.github.fge.grappa.matchers.repeat.RepeatMatcherBuilder;
import com.github.fge.grappa.matchers.trie.CaseInsensitiveTrieMatcher;
import com.github.fge.grappa.matchers.trie.Trie;
import com.github.fge.grappa.matchers.trie.TrieBuilder;
import com.github.fge.grappa.matchers.trie.TrieMatcher;
import com.github.fge.grappa.matchers.trie.TrieNode;
import com.github.fge.grappa.matchers.unicode.CodePointMatcher;
import com.github.fge.grappa.matchers.unicode.CodePointRangeMatcher;
import com.github.fge.grappa.rules.Action;
import com.github.fge.grappa.rules.Rule;
import com.github.fge.grappa.support.Characters;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Base class of all parboiled parsers. Defines the basic rule creation methods.
*
* @param <V> the type of the parser values
*/
@ParametersAreNonnullByDefault
public abstract class BaseParser<V>
extends BaseActions<V>
{
/**
* Match only at the end of the input
*/
protected static final Rule EOI = new EndOfInputMatcher();
/**
* Matches any character.
*
* <p>Note that is is a Java {@code char} (ie, a UTF-16 code unit), not a
* Unicode code point. If the latter is what you want, use {@link
* #unicodeRange(int, int)}.</p>
*/
protected static final Rule ANY = new AnyMatcher();
/**
* Matches nothing and always succeeds.
*/
protected static final Rule EMPTY = new EmptyMatcher();
/**
* Matches nothing and always fails.
*/
protected static final Rule NOTHING = new NothingMatcher();
/*
* CORE RULES
*/
/**
* Match an empty string
*
* @return a rule
*/
@DontExtend
public Rule empty()
{
return EMPTY;
}
/**
* Match only at the end of input
*
* @return a rule
*/
@DontExtend
public Rule eof()
{
return EOI;
}
/**
* Match one given character
*
* @param c the character to match
* @return a rule
*/
@Cached
@DontLabel
public Rule ch(final char c)
{
return new CharMatcher(c);
}
/**
* Match a given character in a case-insensitive manner
*
* @param c the character to match
* @return a rule
*/
@Cached
@DontLabel
public Rule ignoreCase(final char c)
{
return Character.isLowerCase(c) == Character.isUpperCase(c)
? ch(c) : new CharIgnoreCaseMatcher(c);
}
/**
* Match one Unicode character
*
* @param codePoint the code point
* @return a rule
*/
@Cached
@DontLabel
public Rule unicodeChar(final int codePoint)
{
if (!Character.isValidCodePoint(codePoint))
throw new InvalidGrammarException("invalid code point "
+ codePoint);
return new CodePointMatcher(codePoint);
}
/**
* Match a Unicode character range
*
* <p>Note that this method will delegate to "regular" character matchers if
* part of, or all of, the specified range is into the basic multilingual
* plane.</p>
*
* @param low the lower code point (inclusive)
* @param high the upper code point (inclusive)
* @return a rule
*/
@Cached
@DontLabel
public Rule unicodeRange(final int low, final int high)
{
if (!Character.isValidCodePoint(low))
throw new InvalidGrammarException("invalid code point " + low);
if (!Character.isValidCodePoint(high))
throw new InvalidGrammarException("invalid code point " + high);
if (low > high)
throw new InvalidGrammarException("invalid code point range: "
+ low + " > " + high);
return low == high ? new CodePointMatcher(low)
: new CodePointRangeMatcher(low, high);
}
/**
* Match an inclusive range of {@code char}s
*
* @param cLow the start char of the range (inclusively)
* @param cHigh the end char of the range (inclusively)
* @return a rule
*/
@Cached
@DontLabel
public Rule charRange(final char cLow, final char cHigh)
{
return cLow == cHigh ? ch(cLow) : new CharRangeMatcher(cLow, cHigh);
}
/**
* Match any of the characters in the given string
*
* <p>This method delegates to {@link #anyOf(Characters)}.</p>
*
* @param characters the characters
* @return a rule
*
* @see #anyOf(Characters)
*/
@DontLabel
public Rule anyOf(final String characters)
{
Objects.requireNonNull(characters);
// TODO: see in this Characters class whether it is possible to wrap
return anyOf(characters.toCharArray());
}
/**
* Match any character in the given {@code char} array
*
* <p>This method delegates to {@link #anyOf(Characters)}.</p>
*
* @param characters the characters
* @return a rule
*
* @see #anyOf(Characters)
*/
@DontLabel
public Rule anyOf(final char[] characters)
{
Objects.requireNonNull(characters);
switch (characters.length) {
case 0:
throw new IllegalArgumentException("empty character array");
case 1:
return ch(characters[0]);
default:
return anyOf(Characters.of(characters));
}
}
/**
* Match any given character among a set of characters
*
* <p>Both {@link #anyOf(char[])} and {@link #anyOf(String)} ultimately
* delegate to this method, which caches its resuls.</p>
*
* @param characters the characters
* @return a new rule
*/
@Cached
@DontLabel
public Rule anyOf(final Characters characters)
{
Objects.requireNonNull(characters);
if (!characters.isSubtractive() && characters.getChars().length == 1)
return ch(characters.getChars()[0]);
if (characters.equals(Characters.NONE))
return NOTHING;
return new AnyOfMatcher(characters);
}
/**
* Match any characters <em>except</em> the ones contained in the strings
*
* @param characters the characters
* @return a rule
*/
@DontLabel
public Rule noneOf(final String characters)
{
Objects.requireNonNull(characters);
return noneOf(characters.toCharArray());
}
/**
* Match all characters <em>except</em> the ones in the {@code char} array
* given as an argument
*
* @param characters the characters
* @return a new rule
*/
@DontLabel
public Rule noneOf(final char[] characters)
{
Objects.requireNonNull(characters);
Preconditions.checkArgument(characters.length > 0);
return anyOf(Characters.allBut(characters));
}
/**
* Match a string literal
*
* @param string the string to match
* @return a rule
*/
@DontLabel
public Rule string(final String string)
{
Objects.requireNonNull(string);
return string(string.toCharArray());
}
/**
* Match a given set of characters as a string literal
*
* @param characters the characters of the string to match
* @return a rule
*/
@Cached
@DontLabel
public Rule string(final char... characters)
{
if (characters.length == 1)
return ch(characters[0]); // optimize one-char strings
return new StringMatcher(new String(characters));
}
/**
* Match a string literal in a case insensitive manner
*
* @param string the string to match
* @return a rule
*/
@DontLabel
public Rule ignoreCase(final String string)
{
Objects.requireNonNull(string);
return ignoreCase(string.toCharArray());
}
/**
* Match a sequence of characters as a string literal (case insensitive)
*
* @param characters the characters of the string to match
* @return a rule
*/
@Cached
@DontLabel
public Rule ignoreCase(final char... characters)
{
if (characters.length == 1)
return ignoreCase(characters[0]); // optimize one-char strings
return new StringIgnoreCaseMatcher(new String(characters));
}
/**
* Match the input text using a Java regular expression
*
* <p>A few important considerations:</p>
*
* <ul>
* <li>the method used to match text is {@link Matcher#lookingAt()}; it
* means that the match will always be attempted at the current index in
* the input;</li>
* <li>no flags are passed to {@link Pattern#compile(String)}, therefore
* if you want case insensitive matches etc, you have to use the
* appropriate regex modifiers in the regex itself (for instance, {@code
* (?i)} for case insensitivity).</li>
* </ul>
*
* @param regex the regex
* @return a rule
*
* @see Pattern
* @see Matcher#lookingAt()
* @see Matcher#end()
*/
@Cached
@DontLabel
public Rule regex(final String regex)
{
return new RegexMatcher(regex);
}
/**
* Match the longest possible string among a series of strings (case
* sensitive)
*
* <p>As its name says, this rule will always try and match the longest
* possible string among its arguments. It means that, for instance, if you
* write:</p>
*
* <pre>
* longestString("do", "double")
* </pre>
*
* <p>then with an input of {@code doubles}, then {@code double} will be
* matched; but if the input is {@code doubling} then {@code do} is matched.
* </p>
*
* <p>This method builds a {@link Collection} and delegates to {@link
* #longestString(Collection)}.</p>
*
* @param first the first string to match
* @param second the second string to match
* @param others the other strings to match, if any
* @return a rule
*/
public Rule longestString(final String first, final String second,
final String... others)
{
return trie(first, second, others);
}
/**
* Match the longest possible string among a series of strings (case
* sensitive)
*
* <p>Duplicate elements will be silently eliminated.</p>
*
* <p>As its name says, this rule will always try and match the longest
* possible string among its arguments. It means that, for instance, if you
* write:</p>
*
* <pre>
* longestString("do", "double")
* </pre>
*
* <p>then with an input of {@code doubles}, then {@code double} will be
* matched; but if the input is {@code doubling} then {@code do} is matched.
* </p>
*
* <p>This method ultimately delegates to {@link #trie(Collection)}.</p>
*
* @param strings the list of strings for this trie
* @return a rule
*/
public Rule longestString(final Collection<String> strings)
{
return trie(strings);
}
/**
* Match the longest possible string among a series of strings (case
* insensitive)
*
* <p>As its name says, this rule will always try and match the longest
* possible string among its arguments. It means that, for instance, if you
* write:</p>
*
* <pre>
* longestStringIgnoreCase("do", "double")
* </pre>
*
* <p>then with an input of {@code doubles}, then {@code double} will be
* matched; but if the input is {@code doubling} then {@code do} is matched.
* </p>
*
* <p>This method builds a {@link Collection}, and delegates to
* {@link #longestString(Collection)}.</p>
*
* @param first the first string to match
* @param second the second string to match
* @param others the other strings to match, if any
* @return a rule
*/
public Rule longestStringIgnoreCase(final String first, final String second,
final String... others)
{
return trieIgnoreCase(first, second, others);
}
/**
* Match the longest possible string among a series of strings (case
* insensitive)
*
* <p>Duplicate elements will be silently eliminated.</p>
*
* <p>As its name says, this rule will always try and match the longest
* possible string among its arguments. It means that, for instance, if you
* write:</p>
*
* <pre>
* longestStringIgnoreCase("do", "double")
* </pre>
*
* <p>then with an input of {@code doubles}, then {@code double} will be
* matched; but if the input is {@code doubling} then {@code do} is matched.
* </p>
*
* <p>This method ultimately delegates to {@link
* #trieIgnoreCase(Collection)}.</p>
*
* @param strings the list of strings for this trie
* @return a rule
*/
public Rule longestStringIgnoreCase(final Collection<String> strings)
{
return trieIgnoreCase(strings);
}
/**
* Match one string among many using a <a
* href="http://en.wikipedia.org/wiki/Trie" target="_blank">trie</a>
*
* <p>Duplicate elements will be silently eliminated.</p>
*
* <p>Note that order of elements does not matter, and that this rule will
* always trie (err, try) and match the <em>longest possible sequence</em>.
* That is, if you build a rule with inputs "do" and "double" in this order
* and the input text is "doubles", then "double" will be matched. However,
* if the input text is "doubling" then "do" is matched instead.</p>
*
* <p>Note also that the minimum length of strings in a trie is 1.</p>
*
* @param strings the list of strings for this trie
* @return a rule
*
* @see TrieMatcher
* @see TrieNode
*/
/*
* TODO:
*
* - caching may not be that good of an idea...
* - we may end up with only one string and that is a waste
* - non BMP!
*/
@Cached
public Rule trie(final Collection<String> strings)
{
final List<String> list = ImmutableList.copyOf(strings);
final TrieBuilder builder = Trie.newBuilder();
list.forEach(builder::addWord);
return new TrieMatcher(builder.build());
}
/**
* Match one string among many using a <a
* href="http://en.wikipedia.org/wiki/Trie" target="_blank">trie</a>
*
* <p>This method delegates to {@link #trie(Collection)}.</p>
*
* @param first the first string
* @param second the second string
* @param others other strings
* @return a rule
*
* @see TrieMatcher
* @see TrieNode
*/
public Rule trie(final String first, final String second,
final String... others)
{
final List<String> words = ImmutableList.<String>builder().add(first)
.add(second).add(others).build();
return trie(words);
}
/**
* Match one string among many using a <a
* href="http://en.wikipedia.org/wiki/Trie" target="_blank">trie</a>, case
* insensitive version
*
* <p>Duplicate elements will be silently eliminated.</p>
*
* <p>Note that order of elements does not matter, and that this rule will
* always trie (err, try) and match the <em>longest possible sequence</em>.
* That is, if you build a rule with inputs "do" and "double" in this order
* and the input text is "doubles", then "double" will be matched. However,
* if the input text is "doubling" then "do" is matched instead.</p>
*
* <p>Note also that the minimum length of strings in a trie is 1.</p>
*
* @param strings the list of strings for this trie
* @return a rule
*
* @see CaseInsensitiveTrieMatcher
*/
@Cached
public Rule trieIgnoreCase(final Collection<String> strings)
{
final List<String> list = ImmutableList.copyOf(strings);
final TrieBuilder builder = Trie.newBuilder();
list.forEach(builder::addWord);
return new CaseInsensitiveTrieMatcher(builder.build());
}
/**
* Match one string among many using a <a
* href="http://en.wikipedia.org/wiki/Trie" target="_blank">trie</a>, case
* insensitive
*
* <p>This method delegates to {@link #trieIgnoreCase(Collection)}.</p>
*
* @param first the first string
* @param second the second string
* @param others other strings
* @return a rule
*
* @see CaseInsensitiveTrieMatcher
*/
public Rule trieIgnoreCase(final String first, final String second,
final String... others)
{
final List<String> words = ImmutableList.<String>builder().add(first)
.add(second).add(others).build();
return trieIgnoreCase(words);
}
/*
* "DELEGATING" RULES
*
* All rules below delegate to one or more other rules
*/
/**
* Match the first rule of a series of rules
*
* <p>When one rule matches, all others are ignored.</p>
*
* <p>Note: if you are considering matching one string among many, consider
* using {@link #trie(Collection)}/{@link #trie(String, String, String...)}
* instead.</p>
*
* @param rule the first subrule
* @param rule2 the second subrule
* @param moreRules the other subrules
* @return a rule
*/
@DontLabel
public Rule firstOf(final Object rule, final Object rule2,
final Object... moreRules)
{
Objects.requireNonNull(moreRules);
final Object[] rules = ImmutableList.builder().add(rule).add(rule2)
.add(moreRules).build().toArray();
return firstOf(rules);
}
/**
* Match the first rule of a series of rules
*
* <p>When one rule matches, all others are ignored.</p>
*
* @param rules the subrules
* @return a rule
*/
@Cached
@DontLabel
public Rule firstOf(final Object[] rules)
{
Objects.requireNonNull(rules, "rules");
if (rules.length == 1)
return toRule(rules[0]);
final Collection<String> strings = new ArrayList<>();
for (final Object object: rules) {
if (!(object instanceof String))
return new FirstOfMatcher(toRules(rules));
strings.add((String) object);
}
return trie(strings);
}
/**
* Try and match a rule repeatedly, at least once
*
* <p>This is in fact an alias for {@code repeat(rule).min(1)}.</p>
*
* @param rule the subrule
* @return a rule
*
* @see #repeat(Object)
*/
@DontLabel
public Rule oneOrMore(final Object rule)
{
return repeat(rule).min(1);
}
/**
* Try and repeatedly match a set of rules, at least once
*
* <p>This is in fact an alias for {@code repeat(rule, rule2, ...).min(1)}.
* </p>
*
* @param rule the first subrule
* @param rule2 the second subrule
* @param moreRules the other subrules
* @return a rule
*
* @see #repeat(Object, Object, Object...)
*/
@DontLabel
public Rule oneOrMore(final Object rule, final Object rule2,
final Object... moreRules)
{
return repeat(rule, rule2, moreRules).min(1);
}
/**
* Try and match a rule zero or one time
*
* <p>This rule therefore always succeeds.</p>
*
* @param rule the subrule
* @return a rule
*/
@Cached
@DontLabel
public Rule optional(final Object rule)
{
Objects.requireNonNull(rule);
return new OptionalMatcher(toRule(rule));
}
/**
* Try and match a given set of rules once
*
* <p>This rule will therefore never fail.</p>
*
* @param rule the first subrule
* @param rule2 the second subrule
* @param moreRules the other subrules
* @return a rule
*/
@DontLabel
public Rule optional(final Object rule, final Object rule2,
final Object... moreRules)
{
Objects.requireNonNull(moreRules);
return optional(sequence(rule, rule2, moreRules));
}
/**
* Match a given set of rules, exactly once
*
* @param rule the first subrule
* @param rule2 the second subrule
* @param moreRules the other subrules
* @return a rule
*/
@DontLabel
public Rule sequence(final Object rule, final Object rule2,
final Object... moreRules)
{
final Object[] rules = ImmutableList.builder().add(rule).add(rule2)
.add(moreRules).build().toArray();
return sequence(rules);
}
/**
* Match a given set of rules, exactly once
*
* @param rules the rules
* @return a rule
*/
@Cached
@DontLabel
public Rule sequence(final Object[] rules)
{
Objects.requireNonNull(rules);
return rules.length == 1 ? toRule(rules[0])
: new SequenceMatcher(toRules(rules));
}
/**
* Kickstart a {@code join} rule
*
* <p>Usages:</p>
*
* <pre>
* return join(rule()).using(otherRule()).times(n);
* return join(rule()).using(otherRule()).min(n);
* </pre>
*
* <p>etc. See {@link JoinMatcherBuilder} for more possible constructs.</p>
*
* @param rule the joined rule (must not match an empty sequence!)
* @return a {@link JoinMatcherBootstrap}
*
* @see JoinMatcherBootstrap#using(Object)
*/
@DontLabel
public final JoinMatcherBootstrap<V, BaseParser<V>> join(final Object rule)
{
final Rule matcher = toRule(rule);
return new JoinMatcherBootstrap<>(this, matcher);
}
/**
* Kickstart a {@code join} rule
*
* <p>Like {@link #join(Object)}, except that several rules are accepted as
* arguments.</p>
*
* @param rule first rule
* @param rule2 second rule
* @param moreRules other rules
* @return a {@link JoinMatcherBootstrap}
*
* @see #sequence(Object, Object, Object...)
*/
@DontLabel
public final JoinMatcherBootstrap<V, BaseParser<V>> join(final Object rule,
final Object rule2, final Object... moreRules)
{
Objects.requireNonNull(moreRules);
return join(sequence(rule, rule2, moreRules));
}
/**
* Kickstart a repeat rule
*
* <p>Usages:</p>
*
* <pre>
* return repeat("Yes we can!").times(3);
* return repeat(someRule).min(2);
* // etc
* </pre>
*
* @param rule the rule to be repeated
* @return a builder
*/
@DontLabel
public final RepeatMatcherBuilder<V> repeat(final Object rule)
{
return new RepeatMatcherBuilder<>(this, toRule(rule));
}
/**
* Kickstart a repeat rule
*
* <p>Like {@link #repeat(Object)}, except several rules are accepted as
* arguments.</p>
*
* @param rule first rule
* @param rule2 second rule
* @param moreRules other rules
* @return a rule
*
* @see #sequence(Object, Object, Object...)
*/
@DontLabel
public final RepeatMatcherBuilder<V> repeat(final Object rule,
final Object rule2, final Object... moreRules)
{
Objects.requireNonNull(moreRules);
return repeat(sequence(rule, rule2, moreRules));
}
/*
* PREDICATES
*/
/**
* Test a rule, but do not consume any input (predicate)
*
* <p>Its success conditions are the same as the rule. Note that this rule
* will never consume any input, nor will it create a parse tree node.</p>
*
* <p>Note that the embedded rule can be arbitrarily complex, and this
* includes potential {@link Action}s which can act on the stack for
* instance; these <em>will</em> be executed here, unless you have chosen to
* annotate your rule, or parser class, with {@link
* SkipActionsInPredicates}.</p>
*
* @param rule the subrule
* @return a new rule
*/
@Cached
@DontLabel
public Rule test(final Object rule)
{
final Rule subMatcher = toRule(rule);
return new TestMatcher(subMatcher);
}
/**
* Test a set of rules, but do not consume any input
*
* @param rule the first subrule
* @param rule2 the second subrule
* @param moreRules the other subrules
* @return a new rule
*
* @see #test(Object)
*/
@DontLabel
public Rule test(final Object rule, final Object rule2,
final Object... moreRules)
{
Objects.requireNonNull(moreRules);
return test(sequence(rule, rule2, moreRules));
}
/**
* Test, without consuming an input, that a rule does not match
*
* <p>The same warnings given in the description of {@link #test(Object)}
* apply here.</p>
*
* @param rule the subrule
* @return a rule
*/
@Cached
@DontLabel
public Rule testNot(final Object rule)
{
return new TestNotMatcher(toRule(rule));
}
/**
* Test that a set of rules do not apply at this position
*
* @param rule the first subrule
* @param rule2 the second subrule
* @param moreRules the other subrules
* @return a new rule
*
* @see #test(Object)
* @see #testNot(Object)
*/
@DontLabel
public Rule testNot(final Object rule, final Object rule2,
final Object... moreRules)
{
Objects.requireNonNull(moreRules);
return testNot(sequence(rule, rule2, moreRules));
}
/**
* Try and match a rule zero or more times
*
* <p>This is an alias for {@code repeat(rule).min(0)}.</p>
*
* @param rule the subrule
* @return a rule
*
* @see #repeat(Object)
*/
@DontLabel
public Rule zeroOrMore(final Object rule)
{
return repeat(rule).min(0);
}
/**
* Try and match a set of rules zero or more times
*
* <p>This is an alias for {@code repeat(rule, rule2, ...).min(0)}.</p>
*
* @param rule the first subrule
* @param rule2 the second subrule
* @param moreRules the other subrules
* @return a rule
*/
@DontLabel
public Rule zeroOrMore(final Object rule, final Object rule2,
final Object... moreRules)
{
return repeat(rule, rule2, moreRules).min(0);
}
/*
* UTILITY RULES
*
* All rules defined by RFC 5234, appendix B, section 1
*/
/**
* ALPHA as defined by RFC 5234, appendix B, section 1: ASCII letters
*
* <p>Therefore a-z, A-Z.</p>
*
* @return a rule
*/
public Rule alpha()
{
return firstOf(charRange('a', 'z'), charRange('A', 'Z'));
}
/**
* BIT as defined by RFC 5234, appendix B, section 1: {@code 0} or {@code 1}
*
* @return a rule
*/
public Rule bit()
{
return anyOf(Characters.of('0', '1'));
}
/**
* CHAR as defined by RFC 5234, appendix B, section 1: ASCII, except NUL
*
* <p>That is, 0x01 to 0x7f.</p>
*
* @return a rule
*/
public Rule asciiChars()
{
return charRange((char) 0x01, (char) 0x7f);
}
/**
* CR as defined by RFC 5234, appendix B, section 1 ({@code \r})
*
* @return a rule
*/
public Rule cr()
{
return ch('\r');
}
/**
* CRLF as defined by RFC 5234, appendix B, section 1 ({@code \r\n}
*
* @return a rule
*/
public Rule crlf()
{
return string("\r\n");
}
/**
* CTL as defined by RFC 5234, appendix B, section 1: control characters
*
* <p>0x00-0x1f, plus 0x7f.</p>
*
* @return a rule
*/
public Rule ctl()
{
return firstOf(charRange((char) 0x00, (char) 0x1f), ch((char) 0x7f));
}
/**
* DIGIT as defined by RFC 5234, appendix B, section 1 (0 to 9)
*
* @return a rule
*/
public Rule digit()
{
return charRange('0', '9');
}
/**
* DQUOTE as defined by RFC 5234, appendix B, section 1 {@code "}
*
* @return a rule
*/
public Rule dquote()
{
return ch('"');
}
/**
* Hexadecimal digits, case insensitive
*
* <p><b>Note:</b> RFC 5234 only defines {@code HEXDIG} for uppercase
* letters ({@code A} to {@code F}). Use {@link #hexDigitUpperCase()} for
* this definition. Use {@link #hexDigitLowerCase()} for lowercase letters
* only.</p>
*
* @return a rule
*/
public Rule hexDigit()
{
return anyOf("ABCDEFabcdef0123456789");
}
/**
* Hexadecimal digits, uppercase
*
* @return a rule
* @see #hexDigit()
*/
public Rule hexDigitUpperCase()
{
return anyOf("ABCDEF0123456789");
}
/**
* Hexadecimal digits, lowercase
*
* @return a rule
* @see #hexDigit()
*/
public Rule hexDigitLowerCase()
{
return anyOf("abcdef0123456789");
}
/**
* HTAB as defined by RFC 5234, appendix B, section 1 ({@code \t})
*
* @return a rule
*/
public Rule hTab()
{
return ch('\t');
}
/**
* LF as defined by RFC 5234, appendix B, section 1 ({@code \n})
*
* @return a rule
*/
public Rule lf()
{
return ch('\n');
}
/**
* OCTET as defined by RFC 5234, appendix B, section 1 (0x00 to 0xff)
*
* @return a rule
*/
public Rule octet()
{
return charRange((char) 0x00, (char) 0xff);
}
/**
* SP as defined by RFC 5234, appendix B, section 1 (one space, 0x20)
*
* @return a rule
*/
public Rule sp()
{
return ch(' ');
}
/**
* VCHAR as defined by RFC 5234, appendix B, section 1: ASCII "visible"
*
* <p>Letters, {@code @}, etc etc. Note that this <strong>excludes</strong>
* whitespace characters!</p>
*
* @return a rule
*/
public Rule vchar()
{
return charRange((char) 0x21, (char) 0x7e);
}
/**
* WSP as defined by RFC 5234, appendix B, section 1: space or tab
*
* @return a rule
*/
public Rule wsp()
{
return anyOf(" \t");
}
///************************* "MAGIC" METHODS ***************************///
/**
* Explicitly marks the wrapped expression as an action expression.
*
* <p>Grappa transforms the wrapped expression into an {@link Action}
* instance during parser construction</p>
*
* @param expression the expression to turn into an Action
* @return the Action wrapping the given expression
*/
public static <T> Action<T> ACTION(final boolean expression)
{
throw new UnsupportedOperationException("ACTION(...) calls can only be"
+ " used in Rule creating parser methods");
}
///************************* HELPER METHODS ***************************///
/**
* Convert a character literal to a parser rule
*
* @param c the character
* @return the rule
*/
@DontExtend
protected Rule fromCharLiteral(final char c)
{
return ch(c);
}
/**
* Convert a string literal to a parser rule
*
* @param string the string
* @return the rule
*/
@DontExtend
protected Rule fromStringLiteral(final String string)
{
Objects.requireNonNull(string);
return fromCharArray(string.toCharArray());
}
/**
* Convert a char array to a parser rule
*
* @param array the char array
* @return the rule
*/
@DontExtend
protected Rule fromCharArray(final char[] array)
{
Objects.requireNonNull(array);
return string(array);
}
/**
* Convert the given object array to an array of rules
*
* @param objects the objects to convert
* @return the rules corresponding to the given objects
*/
@DontExtend
public Rule[] toRules(final Object... objects)
{
return Arrays.stream(objects).map(this::toRule).toArray(Rule[]::new);
}
/**
* Converts the given object to a rule
*
* @param obj the object to convert
* @return the rule corresponding to the given object
*/
@DontExtend
public Rule toRule(final Object obj)
{
Objects.requireNonNull(obj);
if (obj instanceof Rule)
return (Rule) obj;
if (obj instanceof Character)
return fromCharLiteral((Character) obj);
if (obj instanceof String)
return fromStringLiteral((String) obj);
if (obj instanceof char[])
return fromCharArray((char[]) obj);
if (obj instanceof Action) {
final Action<?> action = (Action<?>) obj;
return new ActionMatcher(action);
}
final String errmsg = obj instanceof Boolean
? "unwrapped Boolean value in rule (wrap it with ACTION())"
: "'" + obj + "' cannot be automatically converted to a rule";
throw new InvalidGrammarException(errmsg);
}
}