Support Markdown lists
This commit is contained in:
		
							parent
							
								
									301eaaf98d
								
							
						
					
					
						commit
						90f84950d2
					
				
					 24 changed files with 307 additions and 88 deletions
				
			
		|  | @ -46,9 +46,9 @@ public class GrammarGraphMLParser implements GraphMLParser { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private Node processNodeElem(final ParseTree nodeElem) { |     private Node processNodeElem(final ParseTree nodeElem) { | ||||||
|         final String id = extractAttribute(nodeElem.child("tag:node"), "id"); |         final String id = extractAttribute(nodeElem.getChild("tag:node"), "id"); | ||||||
| 
 | 
 | ||||||
|         final Map<String, String> data = nodeElem.child("content").getChildren() |         final Map<String, String> data = nodeElem.getChild("content").getChildren() | ||||||
|                 .stream() |                 .stream() | ||||||
|                 .map(this::extractDataElemContent) |                 .map(this::extractDataElemContent) | ||||||
|                 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); |                 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); | ||||||
|  | @ -57,11 +57,11 @@ public class GrammarGraphMLParser implements GraphMLParser { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private Edge processEdgeElem(final ParseTree edgeElem) { |     private Edge processEdgeElem(final ParseTree edgeElem) { | ||||||
|         final String id = extractAttribute(edgeElem.child("tag:edge"), "id"); |         final String id = extractAttribute(edgeElem.getChild("tag:edge"), "id"); | ||||||
|         final String source = extractAttribute(edgeElem.child("tag:edge"), "source"); |         final String source = extractAttribute(edgeElem.getChild("tag:edge"), "source"); | ||||||
|         final String target = extractAttribute(edgeElem.child("tag:edge"), "target"); |         final String target = extractAttribute(edgeElem.getChild("tag:edge"), "target"); | ||||||
| 
 | 
 | ||||||
|         final Map<String, String> data = edgeElem.child("content").getChildren() |         final Map<String, String> data = edgeElem.getChild("content").getChildren() | ||||||
|                 .stream() |                 .stream() | ||||||
|                 .map(this::extractDataElemContent) |                 .map(this::extractDataElemContent) | ||||||
|                 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); |                 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); | ||||||
|  | @ -70,16 +70,16 @@ public class GrammarGraphMLParser implements GraphMLParser { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private Map.Entry<String, String> extractDataElemContent(final ParseTree dataElem) { |     private Map.Entry<String, String> extractDataElemContent(final ParseTree dataElem) { | ||||||
|         final var attribute = extractAttribute(dataElem.child("tag:data"), "key"); |         final var attribute = extractAttribute(dataElem.getChild("tag:data"), "key"); | ||||||
|         final var content = dataElem.child(1).getText(); |         final var content = dataElem.getChild(1).getText(); | ||||||
|         return Map.entry(attribute, content); |         return Map.entry(attribute, content); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private String extractAttribute(final ParseTree tag, final String attributeKey) { |     private String extractAttribute(final ParseTree tag, final String attributeKey) { | ||||||
|         return tag.child("attributes").getChildren() |         return tag.getChild("attributes").getChildren() | ||||||
|                 .stream() |                 .stream() | ||||||
|                 .filter(a -> a.child(0).getText().equals(attributeKey)) |                 .filter(a -> a.getChild(0).getText().equals(attributeKey)) | ||||||
|                 .map(a -> a.child(1).getText()) |                 .map(a -> a.getChild(1).getText()) | ||||||
|                 .findFirst() |                 .findFirst() | ||||||
|                 .orElseThrow(); |                 .orElseThrow(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -7,21 +7,68 @@ import static com.albertoventurini.parsley.grammar.rules.Rules.*; | ||||||
| 
 | 
 | ||||||
| public class MarkdownGrammar extends Grammar { | public class MarkdownGrammar extends Grammar { | ||||||
| 
 | 
 | ||||||
|  |     private final char NEWLINE = '\n'; | ||||||
|  | 
 | ||||||
|  |     private final char BOLD_DELIMITER = '*'; | ||||||
|  | 
 | ||||||
|  |     private final char ITALIC_DELIMITER = '_'; | ||||||
|  | 
 | ||||||
|     private final Rule h1 = sequence( |     private final Rule h1 = sequence( | ||||||
|             character('#'), |             character('#'), | ||||||
|             takeWhile(c -> c != '\n') |             takeWhile(c -> c != NEWLINE).as("text") | ||||||
|     ); |     ).as("h1"); | ||||||
|  | 
 | ||||||
|  |     private final Rule h2 = sequence( | ||||||
|  |             string("##"), | ||||||
|  |             takeWhile(c -> c != NEWLINE).as("text") | ||||||
|  |     ).as("h2"); | ||||||
|  | 
 | ||||||
|  |     private final Rule h3 = sequence( | ||||||
|  |             string("###"), | ||||||
|  |             takeWhile(c -> c != NEWLINE).as("text") | ||||||
|  |     ).as("h3"); | ||||||
|  | 
 | ||||||
|  |     private final Rule headers = sequence(oneOf(h3, h2, h1), zeroOrMore(character(NEWLINE))); | ||||||
|  | 
 | ||||||
|  |     private final Rule plainText = takeWhile(c -> | ||||||
|  |             c != NEWLINE | ||||||
|  |             && c != BOLD_DELIMITER | ||||||
|  |             && c != ITALIC_DELIMITER | ||||||
|  |     ).as("text"); | ||||||
|  | 
 | ||||||
|  |     private final Rule bold = sequence( | ||||||
|  |             character(BOLD_DELIMITER), | ||||||
|  |             takeWhile(c -> c != NEWLINE && c != BOLD_DELIMITER).as("bold"), | ||||||
|  |             character(BOLD_DELIMITER) | ||||||
|  |     ).as("boldWrapper"); | ||||||
|  | 
 | ||||||
|  |     private final Rule italic = sequence( | ||||||
|  |             character(ITALIC_DELIMITER), | ||||||
|  |             takeWhile(c -> c != NEWLINE && c != ITALIC_DELIMITER).as("italic"), | ||||||
|  |             character(ITALIC_DELIMITER) | ||||||
|  |     ).as("italicWrapper"); | ||||||
|  | 
 | ||||||
|  |     private final Rule text = oneOf( | ||||||
|  |             bold, | ||||||
|  |             italic, | ||||||
|  |             plainText); | ||||||
| 
 | 
 | ||||||
|     private final Rule paragraph = sequence( |     private final Rule paragraph = sequence( | ||||||
|             character('\n'), |             oneOrMore(text).as("paragraph"), | ||||||
|             takeWhile(c -> c != '\n') |             zeroOrMore(character(NEWLINE)) | ||||||
|     ); |     ).as("paragraphWrapper"); | ||||||
| 
 | 
 | ||||||
|     private final Rule element = oneOf(h1, paragraph); |     private final Rule listItem = sequence( | ||||||
|  |             oneOf(string("* "), string("- ")), | ||||||
|  |             takeWhile(c -> c != NEWLINE).as("listItem"), | ||||||
|  |             character(NEWLINE) | ||||||
|  |     ).as("listItemWrapper"); | ||||||
| 
 | 
 | ||||||
|     private final Rule elements = sequence(element); |     private final Rule list = sequence(oneOrMore(listItem).as("list"), zeroOrMore(character(NEWLINE))); | ||||||
| 
 | 
 | ||||||
|     private final Rule document = zeroOrMore(elements); |     private final Rule element = oneOf(headers, list, paragraph); | ||||||
|  | 
 | ||||||
|  |     private final Rule document = zeroOrMore(element); | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Rule startRule() { |     public Rule startRule() { | ||||||
|  | @ -32,4 +79,9 @@ public class MarkdownGrammar extends Grammar { | ||||||
|     protected Rule commentRule() { |     protected Rule commentRule() { | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected boolean whitespace(final char c) { | ||||||
|  |         return c != '\n' && Character.isWhitespace(c); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,13 +0,0 @@ | ||||||
| package com.albertoventurini.parsley.examples.graphml; |  | ||||||
| 
 |  | ||||||
| import com.albertoventurini.parsley.examples.markdown.MarkdownGrammar; |  | ||||||
| import org.junit.jupiter.api.Test; |  | ||||||
| 
 |  | ||||||
| public class MarkdownGrammarTest { |  | ||||||
| 
 |  | ||||||
|     @Test |  | ||||||
|     public void markdownGrammar_withH1AndParagraph_shouldParse() { |  | ||||||
|         final MarkdownGrammar grammar = new MarkdownGrammar(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,73 @@ | ||||||
|  | package com.albertoventurini.parsley.examples.markdown; | ||||||
|  | 
 | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  | 
 | ||||||
|  | import static org.junit.jupiter.api.Assertions.*; | ||||||
|  | 
 | ||||||
|  | public class MarkdownGrammarTest { | ||||||
|  | 
 | ||||||
|  |     private final MarkdownGrammar grammar = new MarkdownGrammar(); | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void shouldParseH1() { | ||||||
|  |         final var parseResult = grammar.parse("# hello world"); | ||||||
|  | 
 | ||||||
|  |         assertTrue(parseResult.isPresent()); | ||||||
|  |         assertEquals("# hello world", parseResult.get().getText()); | ||||||
|  |         assertTrue(parseResult.get().getFirstDescendantByName("h1").isPresent()); | ||||||
|  |         assertEquals("# hello world", parseResult.get().getFirstDescendantByName("h1").get().getText()); | ||||||
|  |         assertTrue(parseResult.get().getFirstDescendantByName("text").isPresent()); | ||||||
|  |         assertEquals("hello world", parseResult.get().getFirstDescendantByName("text").get().getText()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void shouldParseH2() { | ||||||
|  |         final var parseResult = grammar.parse("## hello world"); | ||||||
|  | 
 | ||||||
|  |         assertTrue(parseResult.isPresent()); | ||||||
|  |         assertEquals("## hello world", parseResult.get().getText()); | ||||||
|  |         assertTrue(parseResult.get().getFirstDescendantByName("h2").isPresent()); | ||||||
|  |         assertEquals("## hello world", parseResult.get().getFirstDescendantByName("h2").get().getText()); | ||||||
|  |         assertNotNull(parseResult.get().getFirstDescendantByName("text")); | ||||||
|  |         assertTrue(parseResult.get().getFirstDescendantByName("text").isPresent()); | ||||||
|  |         assertEquals("hello world", parseResult.get().getFirstDescendantByName("text").get().getText()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void shouldParseParagraph() { | ||||||
|  |         final var parseResult = grammar.parse("hello world"); | ||||||
|  | 
 | ||||||
|  |         assertTrue(parseResult.isPresent()); | ||||||
|  |         assertEquals("hello world", parseResult.get().getText()); | ||||||
|  |         assertTrue(parseResult.get().getFirstDescendantByName("paragraph").isPresent()); | ||||||
|  |         assertEquals("hello world", parseResult.get().getFirstDescendantByName("paragraph").get().getText()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void shouldParseParagraphWithBoldText() { | ||||||
|  |         final var parseResult = grammar.parse("hello world   *bold text*  normal text"); | ||||||
|  | 
 | ||||||
|  |         assertTrue(parseResult.isPresent()); | ||||||
|  |         assertTrue(parseResult.get().getFirstDescendantByName("paragraph").isPresent()); | ||||||
|  |         assertEquals(3, parseResult.get().getFirstDescendantByName("paragraph").get().getChildren().size()); | ||||||
|  |         assertEquals("bold text", parseResult.get().getFirstDescendantByName("bold").get().getText()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void shouldParseList() { | ||||||
|  |         final var parseResult = grammar.parse("hello world\n" + | ||||||
|  |                 "\n" + | ||||||
|  |                 "* first item\n" + | ||||||
|  |                 "* second item\n" + | ||||||
|  |                 ""); | ||||||
|  | 
 | ||||||
|  |         assertTrue(parseResult.isPresent()); | ||||||
|  |         assertTrue(parseResult.get().getFirstDescendantByName("paragraph").isPresent()); | ||||||
|  |         assertEquals("hello world", parseResult.get().getFirstDescendantByName("paragraph").get().getText()); | ||||||
|  |         assertTrue(parseResult.get().getFirstDescendantByName("list").isPresent()); | ||||||
|  |         assertEquals(2, parseResult.get().getFirstDescendantByName("list").get().getChildren().size()); | ||||||
|  |         assertEquals("first item", parseResult.get().getFirstDescendantByName("list").get().getChildren().get(0).getFirstDescendantByName("listItem").get().getText()); | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -27,6 +27,11 @@ | ||||||
|   </properties> |   </properties> | ||||||
| 
 | 
 | ||||||
|   <dependencies> |   <dependencies> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>com.google.code.findbugs</groupId> | ||||||
|  |       <artifactId>jsr305</artifactId> | ||||||
|  |     </dependency> | ||||||
|  | 
 | ||||||
|     <dependency> |     <dependency> | ||||||
|       <groupId>org.junit.jupiter</groupId> |       <groupId>org.junit.jupiter</groupId> | ||||||
|       <artifactId>junit-jupiter</artifactId> |       <artifactId>junit-jupiter</artifactId> | ||||||
|  |  | ||||||
|  | @ -10,8 +10,12 @@ public abstract class Grammar { | ||||||
| 
 | 
 | ||||||
|     protected abstract Rule commentRule(); |     protected abstract Rule commentRule(); | ||||||
| 
 | 
 | ||||||
|  |     protected boolean whitespace(final char c) { | ||||||
|  |         return Character.isWhitespace(c); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public Optional<ParseTree> parse(final String text) { |     public Optional<ParseTree> parse(final String text) { | ||||||
|         final var ctx = new GrammarContext(text, commentRule()); |         final var ctx = new GrammarContext(text, commentRule(), this::whitespace); | ||||||
|         return startRule().apply(ctx); |         return startRule().apply(ctx); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,15 +2,20 @@ package com.albertoventurini.parsley.grammar; | ||||||
| 
 | 
 | ||||||
| import com.albertoventurini.parsley.grammar.rules.Rule; | import com.albertoventurini.parsley.grammar.rules.Rule; | ||||||
| 
 | 
 | ||||||
|  | import java.util.function.Predicate; | ||||||
|  | 
 | ||||||
| public class GrammarContext extends ParseContext { | public class GrammarContext extends ParseContext { | ||||||
|     private final Rule commentRule; |     private final Rule commentRule; | ||||||
|  |     private final Predicate<Character> whitespacePredicate; | ||||||
|     private boolean inComment; |     private boolean inComment; | ||||||
| 
 | 
 | ||||||
|     GrammarContext( |     GrammarContext( | ||||||
|             final String string, |             final String string, | ||||||
|             final Rule commentRule) { |             final Rule commentRule, | ||||||
|  |             final Predicate<Character> whitespacePredicate) { | ||||||
|         super(string); |         super(string); | ||||||
|         this.commentRule = commentRule; |         this.commentRule = commentRule; | ||||||
|  |         this.whitespacePredicate = whitespacePredicate; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void advanceToNextToken() { |     public void advanceToNextToken() { | ||||||
|  | @ -21,6 +26,19 @@ public class GrammarContext extends ParseContext { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public void advanceToEndOfToken() { | ||||||
|  |         while (hasNext()) { | ||||||
|  |             if (commentRule.apply(this).isPresent()) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             final char c = peek(); | ||||||
|  |             if (isWhitespace(c)) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public boolean isInComment() { |     public boolean isInComment() { | ||||||
|         return inComment; |         return inComment; | ||||||
|     } |     } | ||||||
|  | @ -38,10 +56,16 @@ public class GrammarContext extends ParseContext { | ||||||
|     private void discardWhitespaces() { |     private void discardWhitespaces() { | ||||||
|         while (hasNext()) { |         while (hasNext()) { | ||||||
|             final char c = peek(); |             final char c = peek(); | ||||||
|             if (!(Character.isWhitespace(c) || c == '\n')) { |             if (!isWhitespace(c)) { | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             advance(); |             advance(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     private boolean isWhitespace(final char c) { | ||||||
|  |         return whitespacePredicate.test(c); | ||||||
|  | //        return Character.isWhitespace(c) || c == '\n'; | ||||||
|  | //        return c != '\n' && Character.isWhitespace(c); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,38 +1,55 @@ | ||||||
| package com.albertoventurini.parsley.grammar; | package com.albertoventurini.parsley.grammar; | ||||||
| 
 | 
 | ||||||
|  | import javax.annotation.Nonnull; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Optional; | import java.util.Optional; | ||||||
| 
 | 
 | ||||||
| public abstract class ParseTree { | public abstract class ParseTree { | ||||||
| 
 | 
 | ||||||
|  |     private final String name; | ||||||
|  | 
 | ||||||
|     private final String text; |     private final String text; | ||||||
| 
 | 
 | ||||||
|     private final List<ParseTree> children; |     private final List<ParseTree> children; | ||||||
| 
 | 
 | ||||||
|     public static final class Leaf extends ParseTree { |     public static final class Leaf extends ParseTree { | ||||||
|         public Leaf(final String text) { |         public Leaf( | ||||||
|             super(text, Collections.emptyList()); |                 @Nonnull final String name, | ||||||
|  |                 @Nonnull final String text) { | ||||||
|  |             super(name, text, Collections.emptyList()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static final class Node extends ParseTree { |     public static final class Node extends ParseTree { | ||||||
|         public Node(final String text, final List<ParseTree> children) { |         public Node( | ||||||
|             super(text, children); |                 @Nonnull final String name, | ||||||
|  |                 @Nonnull final String text, | ||||||
|  |                 @Nonnull final List<ParseTree> children) { | ||||||
|  |             super(name, text, children); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public ParseTree(final String text, final List<ParseTree> children) { |     public ParseTree( | ||||||
|  |             @Nonnull final String name, | ||||||
|  |             @Nonnull final String text, | ||||||
|  |             @Nonnull final List<ParseTree> children) { | ||||||
|  |         this.name = name; | ||||||
|         this.text = text; |         this.text = text; | ||||||
|         this.children = children; |         this.children = children; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static Leaf leaf(final String text) { |     public static Leaf leaf( | ||||||
|         return new Leaf(text); |             @Nonnull final String name, | ||||||
|  |             @Nonnull final String text) { | ||||||
|  |         return new Leaf(name, text); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static Node node(final String text, final List<ParseTree> children) { |     public static Node node( | ||||||
|         return new Node(text, children); |             @Nonnull final String name, | ||||||
|  |             @Nonnull final String text, | ||||||
|  |             @Nonnull final List<ParseTree> children) { | ||||||
|  |         return new Node(name, text, children); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public String getText() { |     public String getText() { | ||||||
|  | @ -43,12 +60,12 @@ public abstract class ParseTree { | ||||||
|         return children; |         return children; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public ParseTree child(final int i) { |     public ParseTree getChild(final int i) { | ||||||
|         return children.get(i); |         return children.get(i); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public Optional<ParseTree> getFirstDescendantByName(final String name) { |     public Optional<ParseTree> getFirstDescendantByName(@Nonnull final String name) { | ||||||
|         if (name.equals(text)) { |         if (name.equals(this.name)) { | ||||||
|             return Optional.of(this); |             return Optional.of(this); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -62,7 +79,7 @@ public abstract class ParseTree { | ||||||
|         return Optional.empty(); |         return Optional.empty(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public ParseTree child(final String name) { |     public ParseTree getChild(@Nonnull final String name) { | ||||||
|         return children.stream().filter(c -> name.equals(c.text)) |         return children.stream().filter(c -> name.equals(c.text)) | ||||||
|                 .findFirst() |                 .findFirst() | ||||||
|                 .orElseThrow(() -> new RuntimeException("Child not found: " + name)); |                 .orElseThrow(() -> new RuntimeException("Child not found: " + name)); | ||||||
|  | @ -70,6 +87,9 @@ public abstract class ParseTree { | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public String toString() { |     public String toString() { | ||||||
|         return text; |         return "ParseTree{" + | ||||||
|  |                 "name='" + name + '\'' + | ||||||
|  |                 ", text='" + text + '\'' + | ||||||
|  |                 '}'; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -6,9 +6,14 @@ import com.albertoventurini.parsley.grammar.ParseTree; | ||||||
| import java.util.Optional; | import java.util.Optional; | ||||||
| 
 | 
 | ||||||
| public final class AnyCharacter extends Rule { | public final class AnyCharacter extends Rule { | ||||||
|  | 
 | ||||||
|  |     public AnyCharacter() { | ||||||
|  |         this.name = "AnyCharacter"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Optional<ParseTree> tryApply(final GrammarContext ctx) { |     public Optional<ParseTree> tryApply(final GrammarContext ctx) { | ||||||
|         return Optional.of(ParseTree.leaf(Character.toString(ctx.next()))); |         return Optional.of(ParseTree.leaf(name, Character.toString(ctx.next()))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  |  | ||||||
|  | @ -0,0 +1,27 @@ | ||||||
|  | package com.albertoventurini.parsley.grammar.rules; | ||||||
|  | 
 | ||||||
|  | import com.albertoventurini.parsley.grammar.GrammarContext; | ||||||
|  | import com.albertoventurini.parsley.grammar.ParseTree; | ||||||
|  | 
 | ||||||
|  | import java.util.Optional; | ||||||
|  | 
 | ||||||
|  | public class AnyToken extends Rule { | ||||||
|  | 
 | ||||||
|  |     public AnyToken() { | ||||||
|  |         this.name = "AnyToken"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Optional<ParseTree> tryApply(final GrammarContext ctx) { | ||||||
|  |         ctx.advanceToNextToken(); | ||||||
|  | 
 | ||||||
|  |         if (!ctx.hasNext()) { | ||||||
|  |             return Optional.empty(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         final int start = ctx.getCursor(); | ||||||
|  |         ctx.advanceToEndOfToken(); | ||||||
|  | 
 | ||||||
|  |         return Optional.of(ParseTree.leaf(name, ctx.substring(start))); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -9,6 +9,7 @@ public final class MatchCharacter extends Rule { | ||||||
|     private final char c; |     private final char c; | ||||||
| 
 | 
 | ||||||
|     public MatchCharacter(final char c) { |     public MatchCharacter(final char c) { | ||||||
|  |         this.name = "MatchCharacter"; | ||||||
|         this.c = c; |         this.c = c; | ||||||
|         discard = true; |         discard = true; | ||||||
|     } |     } | ||||||
|  | @ -17,8 +18,8 @@ public final class MatchCharacter extends Rule { | ||||||
|     public Optional<ParseTree> tryApply(final GrammarContext ctx) { |     public Optional<ParseTree> tryApply(final GrammarContext ctx) { | ||||||
|         ctx.advanceToNextToken(); |         ctx.advanceToNextToken(); | ||||||
| 
 | 
 | ||||||
|         if (ctx.peek() == c) { |         if (ctx.hasNext() && ctx.peek() == c) { | ||||||
|             return Optional.of(ParseTree.leaf(Character.toString(ctx.next()))); |             return Optional.of(ParseTree.leaf(name, Character.toString(ctx.next()))); | ||||||
|         } else { |         } else { | ||||||
|             return Optional.empty(); |             return Optional.empty(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ public final class MatchString extends Rule { | ||||||
|     private final String s; |     private final String s; | ||||||
| 
 | 
 | ||||||
|     public MatchString(final String s) { |     public MatchString(final String s) { | ||||||
|  |         this.name = "MatchString"; | ||||||
|         this.s = s; |         this.s = s; | ||||||
| 
 | 
 | ||||||
|         discard = true; |         discard = true; | ||||||
|  | @ -18,9 +19,9 @@ public final class MatchString extends Rule { | ||||||
|     public Optional<ParseTree> tryApply(final GrammarContext ctx) { |     public Optional<ParseTree> tryApply(final GrammarContext ctx) { | ||||||
|         ctx.advanceToNextToken(); |         ctx.advanceToNextToken(); | ||||||
| 
 | 
 | ||||||
|         if (ctx.matches(s)) { |         if (ctx.hasNext() && ctx.matches(s)) { | ||||||
|             ctx.advance(s.length()); |             ctx.advance(s.length()); | ||||||
|             return Optional.of(ParseTree.leaf(s)); |             return Optional.of(ParseTree.leaf(name, s)); | ||||||
|         } else { |         } else { | ||||||
|             return Optional.empty(); |             return Optional.empty(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ public final class OneOf extends Rule { | ||||||
|     private final Rule[] rules; |     private final Rule[] rules; | ||||||
| 
 | 
 | ||||||
|     public OneOf(final Rule... rules) { |     public OneOf(final Rule... rules) { | ||||||
|  |         this.name = "OneOf"; | ||||||
|         this.rules = rules; |         this.rules = rules; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -11,11 +11,13 @@ public class OneOrMore extends Rule { | ||||||
|     private final Rule childRule; |     private final Rule childRule; | ||||||
| 
 | 
 | ||||||
|     public OneOrMore(final Rule childRule) { |     public OneOrMore(final Rule childRule) { | ||||||
|  |         this.name = "OneOrMore"; | ||||||
|         this.childRule = childRule; |         this.childRule = childRule; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Optional<ParseTree> tryApply(final GrammarContext ctx) { |     public Optional<ParseTree> tryApply(final GrammarContext ctx) { | ||||||
|  |         final int start = ctx.getCursor(); | ||||||
|         final List<ParseTree> children = new ArrayList<>(); |         final List<ParseTree> children = new ArrayList<>(); | ||||||
| 
 | 
 | ||||||
|         final Optional<ParseTree> firstChild = childRule.apply(ctx); |         final Optional<ParseTree> firstChild = childRule.apply(ctx); | ||||||
|  | @ -34,7 +36,7 @@ public class OneOrMore extends Rule { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return Optional.of(ParseTree.node(name, children)); |         return Optional.of(ParseTree.node(name, ctx.substring(start), children)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  |  | ||||||
|  | @ -8,6 +8,8 @@ import java.util.Optional; | ||||||
| public abstract class Rule { | public abstract class Rule { | ||||||
|     protected String name; |     protected String name; | ||||||
| 
 | 
 | ||||||
|  |     protected String text; | ||||||
|  | 
 | ||||||
|     public boolean isComment = false; |     public boolean isComment = false; | ||||||
| 
 | 
 | ||||||
|     protected boolean discard = false; |     protected boolean discard = false; | ||||||
|  |  | ||||||
|  | @ -12,6 +12,10 @@ public class Rules { | ||||||
|         return new AnyCharacter(); |         return new AnyCharacter(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public static AnyToken anyToken() { | ||||||
|  |         return new AnyToken(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public static MatchString string(final String s) { |     public static MatchString string(final String s) { | ||||||
|         return new MatchString(s); |         return new MatchString(s); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ public final class Sequence extends Rule { | ||||||
|     private final Rule[] rules; |     private final Rule[] rules; | ||||||
| 
 | 
 | ||||||
|     public Sequence(final Rule... rules) { |     public Sequence(final Rule... rules) { | ||||||
|  |         this.name = "Sequence"; | ||||||
|         this.rules = rules; |         this.rules = rules; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -21,11 +22,6 @@ public final class Sequence extends Rule { | ||||||
|         final List<ParseTree> children = new ArrayList<>(rules.length); |         final List<ParseTree> children = new ArrayList<>(rules.length); | ||||||
| 
 | 
 | ||||||
|         for (final Rule rule : rules) { |         for (final Rule rule : rules) { | ||||||
|             if (!ctx.hasNext()) { |  | ||||||
|                 ctx.setCursor(start); |  | ||||||
|                 return Optional.empty(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             final Optional<ParseTree> child = rule.apply(ctx); |             final Optional<ParseTree> child = rule.apply(ctx); | ||||||
| 
 | 
 | ||||||
|             if (child.isEmpty()) { |             if (child.isEmpty()) { | ||||||
|  | @ -36,7 +32,7 @@ public final class Sequence extends Rule { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return Optional.of(ParseTree.node(name, children)); |         return Optional.of(ParseTree.node(name, ctx.substring(start), children)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ public final class TakeWhileCharacter extends Rule { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public TakeWhileCharacter(final Predicate<Character> characterPredicate) { |     public TakeWhileCharacter(final Predicate<Character> characterPredicate) { | ||||||
|  |         this.name = "TakeWhileCharacter"; | ||||||
|         this.characterPredicate = characterPredicate; |         this.characterPredicate = characterPredicate; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -33,7 +34,7 @@ public final class TakeWhileCharacter extends Rule { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (ctx.getCursor() > start) { |         if (ctx.getCursor() > start) { | ||||||
|             return Optional.of(ParseTree.leaf(ctx.substring(start))); |             return Optional.of(ParseTree.leaf(name, ctx.substring(start))); | ||||||
|         } else { |         } else { | ||||||
|             return Optional.empty(); |             return Optional.empty(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ public final class UntilString extends Rule { | ||||||
|     private final char[] charArr; |     private final char[] charArr; | ||||||
| 
 | 
 | ||||||
|     public UntilString(final String s) { |     public UntilString(final String s) { | ||||||
|  |         this.name = "UntilString"; | ||||||
|         this.charArr = s.toCharArray(); |         this.charArr = s.toCharArray(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -29,9 +30,9 @@ public final class UntilString extends Rule { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (!ctx.hasNext()) { |         if (!ctx.hasNext()) { | ||||||
|             return Optional.of(ParseTree.leaf(ctx.substring(start))); |             return Optional.of(ParseTree.leaf(name, ctx.substring(start))); | ||||||
|         } else { |         } else { | ||||||
|             return Optional.of(ParseTree.leaf(ctx.substring(start, ctx.getCursor()))); |             return Optional.of(ParseTree.leaf(name, ctx.substring(start, ctx.getCursor()))); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -8,6 +8,10 @@ import java.util.Optional; | ||||||
| public final class Wrapper extends Rule { | public final class Wrapper extends Rule { | ||||||
|     private Rule childRule; |     private Rule childRule; | ||||||
| 
 | 
 | ||||||
|  |     public Wrapper() { | ||||||
|  |         this.name = "Wrapper"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public Rule getChildRule() { |     public Rule getChildRule() { | ||||||
|         return childRule; |         return childRule; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -11,11 +11,13 @@ public final class ZeroOrMore extends Rule { | ||||||
|     private final Rule childRule; |     private final Rule childRule; | ||||||
| 
 | 
 | ||||||
|     public ZeroOrMore(final Rule childRule) { |     public ZeroOrMore(final Rule childRule) { | ||||||
|  |         this.name = "ZeroOrMore"; | ||||||
|         this.childRule = childRule; |         this.childRule = childRule; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Optional<ParseTree> tryApply(final GrammarContext ctx) { |     public Optional<ParseTree> tryApply(final GrammarContext ctx) { | ||||||
|  |         final int start = ctx.getCursor(); | ||||||
|         final List<ParseTree> children = new ArrayList<>(); |         final List<ParseTree> children = new ArrayList<>(); | ||||||
| 
 | 
 | ||||||
|         while (ctx.hasNext()) { |         while (ctx.hasNext()) { | ||||||
|  | @ -27,7 +29,7 @@ public final class ZeroOrMore extends Rule { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return Optional.of(ParseTree.node(name, children)); |         return Optional.of(ParseTree.node(name, ctx.substring(start), children)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ public final class ZeroOrOne extends Rule { | ||||||
|     private final Rule childRule; |     private final Rule childRule; | ||||||
| 
 | 
 | ||||||
|     public ZeroOrOne(final Rule childRule) { |     public ZeroOrOne(final Rule childRule) { | ||||||
|  |         this.name = "ZeroOrOne"; | ||||||
|         this.childRule = childRule; |         this.childRule = childRule; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -18,7 +19,7 @@ public final class ZeroOrOne extends Rule { | ||||||
|         if (child.isPresent()) { |         if (child.isPresent()) { | ||||||
|             return child; |             return child; | ||||||
|         } else { |         } else { | ||||||
|             return Optional.of(ParseTree.leaf("")); |             return Optional.of(ParseTree.leaf(name,"")); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -23,12 +23,12 @@ public class GrammarTest { | ||||||
|         final var parseTree1 = grammar.parse("<tag1>"); |         final var parseTree1 = grammar.parse("<tag1>"); | ||||||
| 
 | 
 | ||||||
|         assertTrue(parseTree1.isPresent()); |         assertTrue(parseTree1.isPresent()); | ||||||
|         assertEquals("tag1", parseTree1.get().child(0).getText()); |         assertEquals("tag1", parseTree1.get().getChild(0).getText()); | ||||||
| 
 | 
 | ||||||
|         final var parseTree2 = grammar.parse("<tag1 />"); |         final var parseTree2 = grammar.parse("<tag1 />"); | ||||||
| 
 | 
 | ||||||
|         assertTrue(parseTree2.isPresent()); |         assertTrue(parseTree2.isPresent()); | ||||||
|         assertEquals("tag1", parseTree2.get().child(0).getText()); |         assertEquals("tag1", parseTree2.get().getChild(0).getText()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|  | @ -50,42 +50,42 @@ public class GrammarTest { | ||||||
|         final var parseTree1 = grammar.parse("<tag1 id=123>"); |         final var parseTree1 = grammar.parse("<tag1 id=123>"); | ||||||
| 
 | 
 | ||||||
|         assertTrue(parseTree1.isPresent()); |         assertTrue(parseTree1.isPresent()); | ||||||
|         assertEquals("tag1", parseTree1.get().child(0).getText()); |         assertEquals("tag1", parseTree1.get().getChild(0).getText()); | ||||||
|         assertEquals("id", parseTree1.get().child(1) // get the zeroOrMore rule |         assertEquals("id", parseTree1.get().getChild(1) // get the zeroOrMore rule | ||||||
|                 .child(0) // get the first attribute |                 .getChild(0) // get the first attribute | ||||||
|                 .child(0) // get the first element in the sequence rule |                 .getChild(0) // get the first element in the sequence rule | ||||||
|                 .getText() |                 .getText() | ||||||
|         ); |         ); | ||||||
|         assertEquals("123", parseTree1.get().child(1) // get the zeroOrMore rule |         assertEquals("123", parseTree1.get().getChild(1) // get the zeroOrMore rule | ||||||
|                 .child(0) // get the first attribute |                 .getChild(0) // get the first attribute | ||||||
|                 .child(1) // get the second child in the sequence |                 .getChild(1) // get the second child in the sequence | ||||||
|                 .getText() |                 .getText() | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         final var parseTree2 = grammar.parse("<tag1 hello=world something=else/>"); |         final var parseTree2 = grammar.parse("<tag1 hello=world something=else/>"); | ||||||
| 
 | 
 | ||||||
|         assertTrue(parseTree2.isPresent()); |         assertTrue(parseTree2.isPresent()); | ||||||
|         assertEquals("tag1", parseTree2.get().child(0).getText()); |         assertEquals("tag1", parseTree2.get().getChild(0).getText()); | ||||||
| 
 | 
 | ||||||
|         assertEquals("hello", parseTree2.get().child(1) // get the zeroOrMore rule |         assertEquals("hello", parseTree2.get().getChild(1) // get the zeroOrMore rule | ||||||
|                 .child(0) // get the first attribute |                 .getChild(0) // get the first attribute | ||||||
|                 .child(0) // get the first child in the sequence |                 .getChild(0) // get the first child in the sequence | ||||||
|                 .getText() |                 .getText() | ||||||
|         ); |         ); | ||||||
|         assertEquals("world", parseTree2.get().child(1) // get the zeroOrMore rule |         assertEquals("world", parseTree2.get().getChild(1) // get the zeroOrMore rule | ||||||
|                 .child(0) // get the first attribute |                 .getChild(0) // get the first attribute | ||||||
|                 .child(1) // get the second child in the sequence |                 .getChild(1) // get the second child in the sequence | ||||||
|                 .getText() |                 .getText() | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         assertEquals("something", parseTree2.get().child(1) // get the zeroOrMore rule |         assertEquals("something", parseTree2.get().getChild(1) // get the zeroOrMore rule | ||||||
|                 .child(1) // get the second attribute |                 .getChild(1) // get the second attribute | ||||||
|                 .child(0) // get the first child in the sequence |                 .getChild(0) // get the first child in the sequence | ||||||
|                 .getText() |                 .getText() | ||||||
|         ); |         ); | ||||||
|         assertEquals("else", parseTree2.get().child(1) // get the zeroOrMore rule |         assertEquals("else", parseTree2.get().getChild(1) // get the zeroOrMore rule | ||||||
|                 .child(1) // get the second attribute |                 .getChild(1) // get the second attribute | ||||||
|                 .child(1) // get the second child in the sequence |                 .getChild(1) // get the second child in the sequence | ||||||
|                 .getText() |                 .getText() | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								pom.xml
									
										
									
									
									
								
							
							
						
						
									
										6
									
								
								pom.xml
									
										
									
									
									
								
							|  | @ -23,12 +23,18 @@ | ||||||
|     <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version> |     <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version> | ||||||
|     <maven-jar-plugin.version>3.2.0</maven-jar-plugin.version> |     <maven-jar-plugin.version>3.2.0</maven-jar-plugin.version> | ||||||
|     <maven-shade.plugin.version>3.2.2</maven-shade.plugin.version> |     <maven-shade.plugin.version>3.2.2</maven-shade.plugin.version> | ||||||
|  |     <jsr305.version>3.0.2</jsr305.version> | ||||||
|     <mainClass>com.albertoventurini.parsley.Main</mainClass> |     <mainClass>com.albertoventurini.parsley.Main</mainClass> | ||||||
|   </properties> |   </properties> | ||||||
| 
 | 
 | ||||||
|   <dependencyManagement> |   <dependencyManagement> | ||||||
| 
 | 
 | ||||||
|     <dependencies> |     <dependencies> | ||||||
|  |       <dependency> | ||||||
|  |         <groupId>com.google.code.findbugs</groupId> | ||||||
|  |         <artifactId>jsr305</artifactId> | ||||||
|  |         <version>3.0.2</version> | ||||||
|  |       </dependency> | ||||||
| 
 | 
 | ||||||
|       <dependency> |       <dependency> | ||||||
|         <groupId>org.junit.jupiter</groupId> |         <groupId>org.junit.jupiter</groupId> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue