aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2016-04-27 20:54:50 +0100
committerMichael Williamson <mike@zwobble.org>2016-04-28 21:38:41 +0100
commit5bdef288372d3a6a8134479a6497c44f3b17c4dc (patch)
treefaac4e716212c78394b7b982e13f916cb6523d7f /src/main/java
parentf8ace2ab808441e55ffc95634bc8b88575de78ca (diff)
Replace parboiled parser with hand-written parser
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/styles/parsing/DocumentMatcherParser.java94
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/styles/parsing/HtmlPathParser.java68
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/styles/parsing/ListVar.java30
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/styles/parsing/Parsing.java21
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMapParser.java37
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMappingParser.java196
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMappingTokeniser.java112
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/styles/parsing/Token.java24
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/styles/parsing/TokenIterator.java57
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/styles/parsing/TokenType.java20
10 files changed, 407 insertions, 252 deletions
diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/DocumentMatcherParser.java b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/DocumentMatcherParser.java
new file mode 100644
index 0000000..0998f8b
--- /dev/null
+++ b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/DocumentMatcherParser.java
@@ -0,0 +1,94 @@
+package org.zwobble.mammoth.internal.styles.parsing;
+
+import org.zwobble.mammoth.internal.documents.NumberingLevel;
+import org.zwobble.mammoth.internal.styles.HtmlPath;
+import org.zwobble.mammoth.internal.styles.ParagraphMatcher;
+import org.zwobble.mammoth.internal.styles.RunMatcher;
+import org.zwobble.mammoth.internal.styles.StyleMapBuilder;
+
+import java.math.BigInteger;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+
+public class DocumentMatcherParser {
+ public static BiConsumer<StyleMapBuilder, HtmlPath> parse(TokenIterator tokens) {
+ String identifier = tokens.nextValue(TokenType.IDENTIFIER);
+ switch (identifier) {
+ case "p":
+ ParagraphMatcher paragraph = parseParagraphMatcher(tokens);
+ return (builder, path) -> builder.mapParagraph(paragraph, path);
+ case "r":
+ RunMatcher run = parseRunMatcher(tokens);
+ return (builder, path) -> builder.mapRun(run, path);
+ case "b":
+ return StyleMapBuilder::bold;
+ case "i":
+ return StyleMapBuilder::italic;
+ case "u":
+ return StyleMapBuilder::underline;
+ case "strike":
+ return StyleMapBuilder::strikethrough;
+ default:
+ throw new IllegalArgumentException("Unrecognised document element: " + identifier);
+ }
+ }
+
+ public static ParagraphMatcher parseParagraphMatcher(TokenIterator tokens) {
+ Optional<String> styleId = parseStyleId(tokens);
+ Optional<String> styleName = parseStyleName(tokens);
+ Optional<NumberingLevel> numbering = parseNumbering(tokens);
+ return new ParagraphMatcher(styleId, styleName, numbering);
+ }
+
+ public static RunMatcher parseRunMatcher(TokenIterator tokens) {
+ Optional<String> styleId = parseStyleId(tokens);
+ Optional<String> styleName = parseStyleName(tokens);
+ return new RunMatcher(styleId, styleName);
+ }
+
+ private static Optional<String> parseStyleId(TokenIterator tokens) {
+ if (tokens.peekTokenType() == TokenType.DOT) {
+ tokens.skip();
+ return Optional.of(tokens.nextValue(TokenType.IDENTIFIER));
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ private static Optional<String> parseStyleName(TokenIterator tokens) {
+ if (tokens.peekTokenType() == TokenType.OPEN_SQUARE_BRACKET) {
+ tokens.skip();
+ tokens.skip(TokenType.IDENTIFIER, "style-name");
+ tokens.skip(TokenType.EQUALS);
+ String value = tokens.nextValue(TokenType.STRING);
+ tokens.skip(TokenType.CLOSE_SQUARE_BRACKET);
+ return Optional.of(value.substring(1, value.length() - 1));
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ private static Optional<NumberingLevel> parseNumbering(TokenIterator tokens) {
+ if (tokens.peekTokenType() == TokenType.COLON) {
+ tokens.skip();
+ boolean isOrdered = parseListType(tokens);
+ tokens.skip(TokenType.OPEN_PAREN);
+ String level = new BigInteger(tokens.nextValue(TokenType.INTEGER)).subtract(BigInteger.ONE).toString();
+ tokens.skip(TokenType.CLOSE_PAREN);
+ return Optional.of(new NumberingLevel(level, isOrdered));
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ private static boolean parseListType(TokenIterator tokens) {
+ String listType = tokens.nextValue(TokenType.IDENTIFIER);
+ if (listType.equals("ordered-list")) {
+ return true;
+ } else if (listType.equals("unordered-list")) {
+ return false;
+ } else {
+ throw new IllegalArgumentException("Unrecognised list type: " + listType);
+ }
+ }
+}
diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/HtmlPathParser.java b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/HtmlPathParser.java
new file mode 100644
index 0000000..105504c
--- /dev/null
+++ b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/HtmlPathParser.java
@@ -0,0 +1,68 @@
+package org.zwobble.mammoth.internal.styles.parsing;
+
+import org.zwobble.mammoth.internal.styles.HtmlPath;
+import org.zwobble.mammoth.internal.styles.HtmlPathElement;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.zwobble.mammoth.internal.util.Maps.map;
+
+public class HtmlPathParser {
+ public static HtmlPath parse(TokenIterator tokens) {
+ List<HtmlPathElement> elements = new ArrayList<>();
+
+ if (tokens.peekTokenType() == TokenType.IDENTIFIER) {
+ HtmlPathElement element = parseElement(tokens);
+ elements.add(element);
+ while (tokens.peekTokenType() == TokenType.WHITESPACE && tokens.peekTokenType(1) == TokenType.GREATER_THAN) {
+ tokens.skip(TokenType.WHITESPACE);
+ tokens.skip(TokenType.GREATER_THAN);
+ tokens.skip(TokenType.WHITESPACE);
+ elements.add(parseElement(tokens));
+ }
+ }
+
+ return new HtmlPath(elements);
+ }
+
+ private static HtmlPathElement parseElement(TokenIterator tokens) {
+ List<String> tagNames = parseTagNames(tokens);
+ List<String> classNames = parseClassNames(tokens);
+ Map<String, String> attributes = classNames.isEmpty()
+ ? map()
+ : map("class", String.join(" ", classNames));
+ boolean isFresh = parseIsFresh(tokens);
+ return new HtmlPathElement(tagNames, attributes, !isFresh);
+ }
+
+ private static List<String> parseTagNames(TokenIterator tokens) {
+ List<String> tagNames = new ArrayList<>();
+ tagNames.add(tokens.nextValue(TokenType.IDENTIFIER));
+ while (tokens.peekTokenType() == TokenType.CHOICE) {
+ tokens.skip();
+ tagNames.add(tokens.nextValue(TokenType.IDENTIFIER));
+ }
+ return tagNames;
+ }
+
+ private static List<String> parseClassNames(TokenIterator tokens) {
+ List<String> classNames = new ArrayList<>();
+ while (tokens.peekTokenType() == TokenType.DOT) {
+ tokens.skip();
+ classNames.add(tokens.nextValue(TokenType.IDENTIFIER));
+ }
+ return classNames;
+ }
+
+ private static boolean parseIsFresh(TokenIterator tokens) {
+ if (tokens.peekTokenType() == TokenType.COLON) {
+ tokens.skip();
+ tokens.skip(TokenType.IDENTIFIER, "fresh");
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/ListVar.java b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/ListVar.java
deleted file mode 100644
index 0404a3a..0000000
--- a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/ListVar.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.zwobble.mammoth.internal.styles.parsing;
-
-
-import org.parboiled.support.Var;
-
-import java.util.ArrayList;
-import java.util.List;
-
-class ListVar<T> extends Var<List<T>> {
- boolean append(Var<T> element) {
- return append(element.get());
- }
-
- boolean append(T element) {
- get().add(element);
- return set(get());
- }
-
- @Override
- public List<T> get() {
- if (isNotSet()) {
- set(new ArrayList<T>());
- }
- return super.get();
- }
-
- public boolean isEmpty() {
- return get().isEmpty();
- }
-}
diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/Parsing.java b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/Parsing.java
deleted file mode 100644
index bef3c0b..0000000
--- a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/Parsing.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.zwobble.mammoth.internal.styles.parsing;
-
-import org.parboiled.BaseParser;
-import org.parboiled.Parboiled;
-import org.parboiled.Rule;
-import org.parboiled.parserunners.ReportingParseRunner;
-import org.parboiled.support.ParsingResult;
-
-import java.util.function.Function;
-
-public class Parsing {
- public static <T, U extends BaseParser<T>, V> void parse(Class<U> parserType, Function<U, Rule> rule, String value) {
- U parser = Parboiled.createParser(parserType);
- ReportingParseRunner<T> runner = new ReportingParseRunner<>(rule.apply(parser));
- ParsingResult<T> result = runner.run(value);
- if (result.hasErrors()) {
- // TODO: wrap in result
- throw new RuntimeException("Parse error");
- }
- }
-}
diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMapParser.java b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMapParser.java
index d0bf2c7..9e8e8c9 100644
--- a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMapParser.java
+++ b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMapParser.java
@@ -1,10 +1,12 @@
package org.zwobble.mammoth.internal.styles.parsing;
-import org.parboiled.support.Var;
+import org.zwobble.mammoth.internal.styles.HtmlPath;
import org.zwobble.mammoth.internal.styles.StyleMap;
import org.zwobble.mammoth.internal.styles.StyleMapBuilder;
import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
import static java.util.Arrays.asList;
@@ -14,14 +16,14 @@ public class StyleMapParser {
}
public static StyleMap parseStyleMappings(List<String> lines) {
- Var<StyleMapBuilder> styleMap = new Var<>(StyleMap.builder());
+ StyleMapBuilder styleMap = StyleMap.builder();
for (String line : lines) {
handleLine(styleMap, line);
}
- return styleMap.get().build();
+ return styleMap.build();
}
- private static void handleLine(Var<StyleMapBuilder> styleMap, String line) {
+ private static void handleLine(StyleMapBuilder styleMap, String line) {
if (line.startsWith("#")) {
return;
}
@@ -29,6 +31,31 @@ public class StyleMapParser {
if (line.isEmpty()) {
return;
}
- Parsing.parse(StyleMappingParser.class, parser -> parser.StyleMapping(styleMap), line);
+
+ parseStyleMapping(line).accept(styleMap);
+ }
+
+ private static Consumer<StyleMapBuilder> parseStyleMapping(String line) {
+ TokenIterator tokens = new TokenIterator(StyleMappingTokeniser.tokenise(line));
+
+ BiConsumer<StyleMapBuilder, HtmlPath> documentMatcher = DocumentMatcherParser.parse(tokens);
+
+ tokens.skip(TokenType.WHITESPACE);
+ tokens.skip(TokenType.ARROW);
+
+ HtmlPath htmlPath = parseHtmlPath(tokens);
+
+ tokens.skip(TokenType.EOF);
+
+ return styleMap -> documentMatcher.accept(styleMap, htmlPath);
+ }
+
+ private static HtmlPath parseHtmlPath(TokenIterator tokens) {
+ if (tokens.peekTokenType() == TokenType.EOF) {
+ return HtmlPath.EMPTY;
+ } else {
+ tokens.skip(TokenType.WHITESPACE);
+ return HtmlPathParser.parse(tokens);
+ }
}
}
diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMappingParser.java b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMappingParser.java
deleted file mode 100644
index c006319..0000000
--- a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMappingParser.java
+++ /dev/null
@@ -1,196 +0,0 @@
-package org.zwobble.mammoth.internal.styles.parsing;
-
-import org.parboiled.Action;
-import org.parboiled.BaseParser;
-import org.parboiled.Context;
-import org.parboiled.Rule;
-import org.parboiled.support.Var;
-import org.zwobble.mammoth.internal.documents.NumberingLevel;
-import org.zwobble.mammoth.internal.styles.*;
-
-import java.math.BigInteger;
-import java.util.Optional;
-import java.util.function.BiFunction;
-
-import static org.zwobble.mammoth.internal.util.Maps.map;
-
-public class StyleMappingParser extends BaseParser<StyleMapBuilder> {
- Rule StyleMapping(Var<StyleMapBuilder> styleMap) {
- Var<BiFunction<StyleMapBuilder, HtmlPath, StyleMapBuilder>> documentElementMatcher = new Var<>();
- Var<HtmlPath> path = new Var<>();
- return Sequence(
- DocumentElementMatcher(documentElementMatcher), Whitespace(), "=>", Whitespace(), HtmlPath(path), EOI,
- styleMap.set(documentElementMatcher.get().apply(styleMap.get(), path.get())));
- }
-
- Rule DocumentElementMatcher(Var<BiFunction<StyleMapBuilder, HtmlPath, StyleMapBuilder>> updateBuilder) {
- Var<ParagraphMatcher> paragraphMatcher = new Var<>();
- Var<RunMatcher> runMatcher = new Var<>();
- return FirstOf(
- Sequence(
- ParagraphMatcher(paragraphMatcher),
- new Action() {
- @Override
- public boolean run(Context context) {
- updateBuilder.set((styleMap, htmlPath) -> styleMap.mapParagraph(paragraphMatcher.get(), htmlPath));
- return true;
- }
- }),
- Sequence(
- RunMatcher(runMatcher),
- new Action() {
- @Override
- public boolean run(Context context) {
- updateBuilder.set((styleMap, htmlPath) -> styleMap.mapRun(runMatcher.get(), htmlPath));
- return true;
- }
- }),
- Sequence(
- "b",
- new Action() {
- @Override
- public boolean run(Context context) {
- updateBuilder.set(StyleMapBuilder::bold);
- return true;
- }
- }),
- Sequence(
- "i",
- new Action() {
- @Override
- public boolean run(Context context) {
- updateBuilder.set(StyleMapBuilder::italic);
- return true;
- }
- }),
- Sequence(
- "u",
- new Action() {
- @Override
- public boolean run(Context context) {
- updateBuilder.set(StyleMapBuilder::underline);
- return true;
- }
- }),
- Sequence(
- "strike",
- new Action() {
- @Override
- public boolean run(Context context) {
- updateBuilder.set(StyleMapBuilder::strikethrough);
- return true;
- }
- }));
- }
-
- public Rule ParagraphMatcher(Var<ParagraphMatcher> matcher) {
- Var<Optional<String>> styleId = new Var<>();
- Var<Optional<String>> styleName = new Var<>();
- Var<Optional<NumberingLevel>> numbering = new Var<>();
- return Sequence(
- "p", StyleId(styleId), StyleName(styleName), Numbering(numbering),
- matcher.set(new ParagraphMatcher(styleId.get(), styleName.get(), numbering.get())));
- }
-
- public Rule RunMatcher(Var<RunMatcher> matcher) {
- Var<Optional<String>> styleId = new Var<>();
- Var<Optional<String>> styleName = new Var<>();
- return Sequence(
- "r", StyleId(styleId), StyleName(styleName),
- matcher.set(new RunMatcher(styleId.get(), styleName.get())));
- }
-
- Rule StyleId(Var<Optional<String>> styleId) {
- return Sequence(styleId.set(Optional.empty()), Optional(".", Identifier(), styleId.set(Optional.of(match()))));
- }
-
- Rule StyleName(Var<Optional<String>> styleName) {
- Var<String> value = new Var<>();
- return Sequence(
- styleName.set(Optional.empty()),
- Optional("[style-name=", SingleQuotedString(value), "]", styleName.set(Optional.of(value.get()))));
- }
-
- Rule Numbering(Var<Optional<NumberingLevel>> numbering) {
- Var<Boolean> isOrdered = new Var<>();
- Var<BigInteger> level = new Var<>();
- return Sequence(
- numbering.set(Optional.empty()),
- Optional(Sequence(
- ":", NumberingType(isOrdered), "(", Number(level), ")",
- numbering.set(Optional.of(new NumberingLevel(level.get().subtract(BigInteger.ONE).toString(), isOrdered.get()))))));
- }
-
- Rule NumberingType(Var<Boolean> isOrdered) {
- return FirstOf(
- Sequence("ordered-list", isOrdered.set(true)),
- Sequence("unordered-list", isOrdered.set(false)));
- }
-
- Rule Number(Var<BigInteger> level) {
- return Sequence(
- OneOrMore(CharRange('0', '9')),
- level.set(new BigInteger(match())));
- }
-
- public Rule HtmlPath(Var<HtmlPath> htmlPath) {
- ListVar<HtmlPathElement> elements = new ListVar<>();
- return Sequence(Optional(HtmlPathElements(elements)), htmlPath.set(new HtmlPath(elements.get())));
- }
-
- Rule HtmlPathElements(ListVar<HtmlPathElement> elements) {
- Var<HtmlPathElement> element = new Var<>();
- return Sequence(
- HtmlPathElement(element),
- elements.append(element),
- ZeroOrMore(Sequence(Whitespace(), Ch('>'), Whitespace(), HtmlPathElement(element), elements.append(element))));
- }
-
- Rule HtmlPathElement(Var<HtmlPathElement> element) {
- ListVar<String> tagNames = new ListVar<>();
- ListVar<String> classNames = new ListVar<>();
- Var<Boolean> fresh = new Var<>();
- return Sequence(
- TagNames(tagNames),
- ClassNames(classNames),
- Fresh(fresh),
- element.set(new HtmlPathElement(
- tagNames.get(),
- classNames.isEmpty() ? map() : map("class", String.join(" ", classNames.get())),
- !fresh.get())));
- }
-
- Rule TagNames(ListVar<String> tagNames) {
- return Sequence(
- Identifier(),
- tagNames.append(match()),
- ZeroOrMore(Sequence("|", Identifier(), tagNames.append(match()))));
- }
-
- Rule ClassNames(ListVar<String> classNames) {
- Var<String> className = new Var<>();
- return ZeroOrMore(Sequence(ClassName(className), classNames.append(className)));
- }
-
- Rule ClassName(Var<String> className) {
- return Sequence(".", Identifier(), className.set(match()));
- }
-
- Rule Fresh(Var<Boolean> fresh) {
- return Sequence(fresh.set(false), Optional(":fresh", fresh.set(true)));
- }
-
- Rule SingleQuotedString(Var<String> value) {
- return Sequence("'", ZeroOrMore(NoneOf("\'")), value.set(match()), "'");
- }
-
- Rule Identifier() {
- return Sequence(
- FirstOf(CharRange('a', 'z'), CharRange('A', 'Z')),
- ZeroOrMore(FirstOf(CharRange('a', 'z'), CharRange('A', 'Z'), CharRange('0', '9'), Ch('-'), Ch('_'))));
- }
-
- Rule Whitespace() {
- return ZeroOrMore(AnyOf(" \t"));
- }
-}
diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMappingTokeniser.java b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMappingTokeniser.java
new file mode 100644
index 0000000..fe36242
--- /dev/null
+++ b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMappingTokeniser.java
@@ -0,0 +1,112 @@
+package org.zwobble.mammoth.internal.styles.parsing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class StyleMappingTokeniser {
+ public static List<Token> tokenise(String line) {
+ List<Token> tokens = new ArrayList<>();
+
+ int index = 0;
+ while (index < line.length()) {
+ Token token = nextToken(line, index);
+ tokens.add(token);
+ index += token.getValue().length();
+ }
+
+ tokens.add(new Token(TokenType.EOF, ""));
+ return tokens;
+ }
+
+ private static Token nextToken(String line, int startIndex) {
+ char nextChar = line.charAt(startIndex);
+ TokenType tokenType = tokenType(nextChar);
+ int index = 0;
+ if (nextChar == '=' && startIndex + 1 < line.length() && line.charAt(startIndex + 1) == '>') {
+ tokenType = TokenType.ARROW;
+ index = startIndex + 2;
+ } else {
+ index = seekEndOfToken(line, startIndex + 1, tokenType);
+ }
+ return new Token(tokenType, line.substring(startIndex, index));
+ }
+
+ private static TokenType tokenType(char nextChar) {
+ if (isAlphabetic(nextChar)) {
+ return TokenType.IDENTIFIER;
+ } else if (isWhitespace(nextChar)) {
+ return TokenType.WHITESPACE;
+ } else if (isNumeric(nextChar)) {
+ return TokenType.INTEGER;
+ } else {
+ switch (nextChar) {
+ case '\'':
+ return TokenType.STRING;
+ case '.':
+ return TokenType.DOT;
+ case ':':
+ return TokenType.COLON;
+ case '>':
+ return TokenType.GREATER_THAN;
+ case '=':
+ return TokenType.EQUALS;
+ case '(':
+ return TokenType.OPEN_PAREN;
+ case ')':
+ return TokenType.CLOSE_PAREN;
+ case '[':
+ return TokenType.OPEN_SQUARE_BRACKET;
+ case ']':
+ return TokenType.CLOSE_SQUARE_BRACKET;
+ case '|':
+ return TokenType.CHOICE;
+ default:
+ return TokenType.UNKNOWN;
+ }
+ }
+ }
+
+ private static int seekEndOfToken(String line, int index, TokenType tokenType) {
+ switch (tokenType) {
+ case WHITESPACE:
+ while (index < line.length() && isWhitespace(line.charAt(index))) {
+ index += 1;
+ }
+ return index;
+ case IDENTIFIER:
+ while (index < line.length() && isIdentifierCharacter(line.charAt(index))) {
+ index += 1;
+ }
+ return index;
+ case INTEGER:
+ while (index < line.length() && isNumeric(line.charAt(index))) {
+ index += 1;
+ }
+ return index;
+ case STRING:
+ while (index < line.length() && line.charAt(index) != '\'') {
+ index += 1;
+ }
+ index += 1;
+ return index;
+ default:
+ return index;
+ }
+ }
+
+ private static boolean isWhitespace(char c) {
+ return c == ' ' || c == '\t';
+ }
+
+ private static boolean isIdentifierCharacter(char c) {
+ return isAlphabetic(c) || isNumeric(c) || c == '-' || c == '_';
+ }
+
+ private static boolean isAlphabetic(char nextChar) {
+ return (nextChar >= 'a' && nextChar <= 'z') || (nextChar >= 'A' && nextChar <= 'Z');
+ }
+
+ private static boolean isNumeric(char c) {
+ return c >= '0' && c <= '9';
+ }
+}
diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/Token.java b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/Token.java
new file mode 100644
index 0000000..f6b771e
--- /dev/null
+++ b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/Token.java
@@ -0,0 +1,24 @@
+package org.zwobble.mammoth.internal.styles.parsing;
+
+public class Token {
+ private final TokenType tokenType;
+ private final String value;
+
+ public Token(TokenType tokenType, String value) {
+ this.tokenType = tokenType;
+ this.value = value;
+ }
+
+ public TokenType getTokenType() {
+ return tokenType;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return "Token(tokenType=" + tokenType + ", value=" + value + ")";
+ }
+}
diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/TokenIterator.java b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/TokenIterator.java
new file mode 100644
index 0000000..df22b5d
--- /dev/null
+++ b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/TokenIterator.java
@@ -0,0 +1,57 @@
+package org.zwobble.mammoth.internal.styles.parsing;
+
+import java.util.List;
+
+public class TokenIterator {
+ private final List<Token> tokens;
+ private int index;
+
+ public TokenIterator(List<Token> tokens) {
+ this.tokens = tokens;
+ this.index = 0;
+ }
+
+ public TokenType peekTokenType() {
+ return tokens.get(index).getTokenType();
+ }
+
+ public TokenType peekTokenType(int count) {
+ return tokens.get(index + count).getTokenType();
+ }
+
+ public String nextValue(TokenType type) {
+ Token token = tokens.get(index);
+ if (token.getTokenType() == type) {
+ index += 1;
+ return token.getValue();
+ } else {
+ throw unexpectedTokenType(type, token.getTokenType());
+ }
+ }
+
+ public void skip() {
+ index += 1;
+ }
+
+ public void skip(TokenType tokenType) {
+ if (peekTokenType() != tokenType) {
+ throw unexpectedTokenType(tokenType, peekTokenType());
+ }
+ index += 1;
+ }
+
+ public void skip(TokenType tokenType, String tokenValue) {
+ if (peekTokenType() != tokenType) {
+ throw unexpectedTokenType(tokenType, peekTokenType());
+ }
+ String actualValue = tokens.get(index).getValue();
+ if (!actualValue.equals(tokenValue)) {
+ throw new IllegalArgumentException("expected " + tokenType + " token with value " + tokenValue + " but value was " + actualValue);
+ }
+ index += 1;
+ }
+
+ private IllegalArgumentException unexpectedTokenType(TokenType expected, TokenType actual) {
+ return new IllegalArgumentException("expected token of type " + expected + " but was of type " + actual);
+ }
+}
diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/TokenType.java b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/TokenType.java
new file mode 100644
index 0000000..c9cc9a4
--- /dev/null
+++ b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/TokenType.java
@@ -0,0 +1,20 @@
+package org.zwobble.mammoth.internal.styles.parsing;
+
+public enum TokenType {
+ WHITESPACE,
+ IDENTIFIER,
+ DOT,
+ COLON,
+ GREATER_THAN,
+ ARROW,
+ EQUALS,
+ OPEN_PAREN,
+ CLOSE_PAREN,
+ OPEN_SQUARE_BRACKET,
+ CLOSE_SQUARE_BRACKET,
+ STRING,
+ INTEGER,
+ CHOICE,
+ EOF,
+ UNKNOWN
+}