Support Markdown lists

This commit is contained in:
Alberto Venturini 2021-07-11 14:58:58 +02:00
parent 301eaaf98d
commit 90f84950d2
24 changed files with 307 additions and 88 deletions

View file

@ -27,6 +27,11 @@
</properties>
<dependencies>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>

View file

@ -10,8 +10,12 @@ public abstract class Grammar {
protected abstract Rule commentRule();
protected boolean whitespace(final char c) {
return Character.isWhitespace(c);
}
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);
}

View file

@ -2,15 +2,20 @@ package com.albertoventurini.parsley.grammar;
import com.albertoventurini.parsley.grammar.rules.Rule;
import java.util.function.Predicate;
public class GrammarContext extends ParseContext {
private final Rule commentRule;
private final Predicate<Character> whitespacePredicate;
private boolean inComment;
GrammarContext(
final String string,
final Rule commentRule) {
final Rule commentRule,
final Predicate<Character> whitespacePredicate) {
super(string);
this.commentRule = commentRule;
this.whitespacePredicate = whitespacePredicate;
}
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() {
return inComment;
}
@ -38,10 +56,16 @@ public class GrammarContext extends ParseContext {
private void discardWhitespaces() {
while (hasNext()) {
final char c = peek();
if (!(Character.isWhitespace(c) || c == '\n')) {
if (!isWhitespace(c)) {
break;
}
advance();
}
}
private boolean isWhitespace(final char c) {
return whitespacePredicate.test(c);
// return Character.isWhitespace(c) || c == '\n';
// return c != '\n' && Character.isWhitespace(c);
}
}

View file

@ -1,38 +1,55 @@
package com.albertoventurini.parsley.grammar;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
public abstract class ParseTree {
private final String name;
private final String text;
private final List<ParseTree> children;
public static final class Leaf extends ParseTree {
public Leaf(final String text) {
super(text, Collections.emptyList());
public Leaf(
@Nonnull final String name,
@Nonnull final String text) {
super(name, text, Collections.emptyList());
}
}
public static final class Node extends ParseTree {
public Node(final String text, final List<ParseTree> children) {
super(text, children);
public Node(
@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.children = children;
}
public static Leaf leaf(final String text) {
return new Leaf(text);
public static Leaf leaf(
@Nonnull final String name,
@Nonnull final String text) {
return new Leaf(name, text);
}
public static Node node(final String text, final List<ParseTree> children) {
return new Node(text, children);
public static Node node(
@Nonnull final String name,
@Nonnull final String text,
@Nonnull final List<ParseTree> children) {
return new Node(name, text, children);
}
public String getText() {
@ -43,12 +60,12 @@ public abstract class ParseTree {
return children;
}
public ParseTree child(final int i) {
public ParseTree getChild(final int i) {
return children.get(i);
}
public Optional<ParseTree> getFirstDescendantByName(final String name) {
if (name.equals(text)) {
public Optional<ParseTree> getFirstDescendantByName(@Nonnull final String name) {
if (name.equals(this.name)) {
return Optional.of(this);
}
@ -62,7 +79,7 @@ public abstract class ParseTree {
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))
.findFirst()
.orElseThrow(() -> new RuntimeException("Child not found: " + name));
@ -70,6 +87,9 @@ public abstract class ParseTree {
@Override
public String toString() {
return text;
return "ParseTree{" +
"name='" + name + '\'' +
", text='" + text + '\'' +
'}';
}
}

View file

@ -6,9 +6,14 @@ import com.albertoventurini.parsley.grammar.ParseTree;
import java.util.Optional;
public final class AnyCharacter extends Rule {
public AnyCharacter() {
this.name = "AnyCharacter";
}
@Override
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

View file

@ -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)));
}
}

View file

@ -9,6 +9,7 @@ public final class MatchCharacter extends Rule {
private final char c;
public MatchCharacter(final char c) {
this.name = "MatchCharacter";
this.c = c;
discard = true;
}
@ -17,8 +18,8 @@ public final class MatchCharacter extends Rule {
public Optional<ParseTree> tryApply(final GrammarContext ctx) {
ctx.advanceToNextToken();
if (ctx.peek() == c) {
return Optional.of(ParseTree.leaf(Character.toString(ctx.next())));
if (ctx.hasNext() && ctx.peek() == c) {
return Optional.of(ParseTree.leaf(name, Character.toString(ctx.next())));
} else {
return Optional.empty();
}

View file

@ -9,6 +9,7 @@ public final class MatchString extends Rule {
private final String s;
public MatchString(final String s) {
this.name = "MatchString";
this.s = s;
discard = true;
@ -18,9 +19,9 @@ public final class MatchString extends Rule {
public Optional<ParseTree> tryApply(final GrammarContext ctx) {
ctx.advanceToNextToken();
if (ctx.matches(s)) {
if (ctx.hasNext() && ctx.matches(s)) {
ctx.advance(s.length());
return Optional.of(ParseTree.leaf(s));
return Optional.of(ParseTree.leaf(name, s));
} else {
return Optional.empty();
}

View file

@ -10,6 +10,7 @@ public final class OneOf extends Rule {
private final Rule[] rules;
public OneOf(final Rule... rules) {
this.name = "OneOf";
this.rules = rules;
}

View file

@ -11,11 +11,13 @@ public class OneOrMore extends Rule {
private final Rule childRule;
public OneOrMore(final Rule childRule) {
this.name = "OneOrMore";
this.childRule = childRule;
}
@Override
public Optional<ParseTree> tryApply(final GrammarContext ctx) {
final int start = ctx.getCursor();
final List<ParseTree> children = new ArrayList<>();
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

View file

@ -8,6 +8,8 @@ import java.util.Optional;
public abstract class Rule {
protected String name;
protected String text;
public boolean isComment = false;
protected boolean discard = false;

View file

@ -12,6 +12,10 @@ public class Rules {
return new AnyCharacter();
}
public static AnyToken anyToken() {
return new AnyToken();
}
public static MatchString string(final String s) {
return new MatchString(s);
}

View file

@ -12,6 +12,7 @@ public final class Sequence extends Rule {
private final Rule[] rules;
public Sequence(final Rule... rules) {
this.name = "Sequence";
this.rules = rules;
}
@ -21,11 +22,6 @@ public final class Sequence extends Rule {
final List<ParseTree> children = new ArrayList<>(rules.length);
for (final Rule rule : rules) {
if (!ctx.hasNext()) {
ctx.setCursor(start);
return Optional.empty();
}
final Optional<ParseTree> child = rule.apply(ctx);
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

View file

@ -16,6 +16,7 @@ public final class TakeWhileCharacter extends Rule {
}
public TakeWhileCharacter(final Predicate<Character> characterPredicate) {
this.name = "TakeWhileCharacter";
this.characterPredicate = characterPredicate;
}
@ -33,7 +34,7 @@ public final class TakeWhileCharacter extends Rule {
}
if (ctx.getCursor() > start) {
return Optional.of(ParseTree.leaf(ctx.substring(start)));
return Optional.of(ParseTree.leaf(name, ctx.substring(start)));
} else {
return Optional.empty();
}

View file

@ -10,6 +10,7 @@ public final class UntilString extends Rule {
private final char[] charArr;
public UntilString(final String s) {
this.name = "UntilString";
this.charArr = s.toCharArray();
}
@ -29,9 +30,9 @@ public final class UntilString extends Rule {
}
if (!ctx.hasNext()) {
return Optional.of(ParseTree.leaf(ctx.substring(start)));
return Optional.of(ParseTree.leaf(name, ctx.substring(start)));
} else {
return Optional.of(ParseTree.leaf(ctx.substring(start, ctx.getCursor())));
return Optional.of(ParseTree.leaf(name, ctx.substring(start, ctx.getCursor())));
}
}

View file

@ -8,6 +8,10 @@ import java.util.Optional;
public final class Wrapper extends Rule {
private Rule childRule;
public Wrapper() {
this.name = "Wrapper";
}
public Rule getChildRule() {
return childRule;
}

View file

@ -11,11 +11,13 @@ public final class ZeroOrMore extends Rule {
private final Rule childRule;
public ZeroOrMore(final Rule childRule) {
this.name = "ZeroOrMore";
this.childRule = childRule;
}
@Override
public Optional<ParseTree> tryApply(final GrammarContext ctx) {
final int start = ctx.getCursor();
final List<ParseTree> children = new ArrayList<>();
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

View file

@ -9,6 +9,7 @@ public final class ZeroOrOne extends Rule {
private final Rule childRule;
public ZeroOrOne(final Rule childRule) {
this.name = "ZeroOrOne";
this.childRule = childRule;
}
@ -18,7 +19,7 @@ public final class ZeroOrOne extends Rule {
if (child.isPresent()) {
return child;
} else {
return Optional.of(ParseTree.leaf(""));
return Optional.of(ParseTree.leaf(name,""));
}
}

View file

@ -23,12 +23,12 @@ public class GrammarTest {
final var parseTree1 = grammar.parse("<tag1>");
assertTrue(parseTree1.isPresent());
assertEquals("tag1", parseTree1.get().child(0).getText());
assertEquals("tag1", parseTree1.get().getChild(0).getText());
final var parseTree2 = grammar.parse("<tag1 />");
assertTrue(parseTree2.isPresent());
assertEquals("tag1", parseTree2.get().child(0).getText());
assertEquals("tag1", parseTree2.get().getChild(0).getText());
}
@Test
@ -50,42 +50,42 @@ public class GrammarTest {
final var parseTree1 = grammar.parse("<tag1 id=123>");
assertTrue(parseTree1.isPresent());
assertEquals("tag1", parseTree1.get().child(0).getText());
assertEquals("id", parseTree1.get().child(1) // get the zeroOrMore rule
.child(0) // get the first attribute
.child(0) // get the first element in the sequence rule
assertEquals("tag1", parseTree1.get().getChild(0).getText());
assertEquals("id", parseTree1.get().getChild(1) // get the zeroOrMore rule
.getChild(0) // get the first attribute
.getChild(0) // get the first element in the sequence rule
.getText()
);
assertEquals("123", parseTree1.get().child(1) // get the zeroOrMore rule
.child(0) // get the first attribute
.child(1) // get the second child in the sequence
assertEquals("123", parseTree1.get().getChild(1) // get the zeroOrMore rule
.getChild(0) // get the first attribute
.getChild(1) // get the second child in the sequence
.getText()
);
final var parseTree2 = grammar.parse("<tag1 hello=world something=else/>");
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
.child(0) // get the first attribute
.child(0) // get the first child in the sequence
assertEquals("hello", parseTree2.get().getChild(1) // get the zeroOrMore rule
.getChild(0) // get the first attribute
.getChild(0) // get the first child in the sequence
.getText()
);
assertEquals("world", parseTree2.get().child(1) // get the zeroOrMore rule
.child(0) // get the first attribute
.child(1) // get the second child in the sequence
assertEquals("world", parseTree2.get().getChild(1) // get the zeroOrMore rule
.getChild(0) // get the first attribute
.getChild(1) // get the second child in the sequence
.getText()
);
assertEquals("something", parseTree2.get().child(1) // get the zeroOrMore rule
.child(1) // get the second attribute
.child(0) // get the first child in the sequence
assertEquals("something", parseTree2.get().getChild(1) // get the zeroOrMore rule
.getChild(1) // get the second attribute
.getChild(0) // get the first child in the sequence
.getText()
);
assertEquals("else", parseTree2.get().child(1) // get the zeroOrMore rule
.child(1) // get the second attribute
.child(1) // get the second child in the sequence
assertEquals("else", parseTree2.get().getChild(1) // get the zeroOrMore rule
.getChild(1) // get the second attribute
.getChild(1) // get the second child in the sequence
.getText()
);
}