diff --git a/README.md b/README.md index 4048583..a6c90f0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,23 @@ # Java Java version of SimplePEG + +Using example: + + String grammar = "GRAMMAR url\n" + + "\n" + + "url -> scheme \"://\" host pathname search hash?;\n" + + "scheme -> \"http\" \"s\"?;\n" + + "host -> hostname port?;\n" + + "hostname -> segment (\".\" segment)*;\n" + + "segment -> [a-z0-9-]+;\n" + + "port -> \":\" [0-9]+;\n" + + "pathname -> \"/\" [^ ?]*;\n" + + "search -> (\"?\" [^ #]*)?;\n" + + "hash -> \"#\" [^ ]*;"; + + RuleProcessor rp = new RuleProcessor(SpegParser.createAndExec(grammar)); + + System.out.println(rp.check("https://simplepeg.github.io/")); + System.out.println(rp.check("https://google.com/")); + System.out.println(rp.check("https://abcdssss.....com/")); + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..804eda0 --- /dev/null +++ b/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + ru.dude + SimplePEG-Java + 1.0-SNAPSHOT + + + + junit + junit + RELEASE + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.7 + 1.7 + + + + + \ No newline at end of file diff --git a/src/main/java/ru/dude/simplepeg/Executable.java b/src/main/java/ru/dude/simplepeg/Executable.java new file mode 100644 index 0000000..ab15b2e --- /dev/null +++ b/src/main/java/ru/dude/simplepeg/Executable.java @@ -0,0 +1,18 @@ +package ru.dude.simplepeg; + +import ru.dude.simplepeg.entity.PegNode; +import ru.dude.simplepeg.entity.State; + +/** + * For implements in PEG expression + * Created by dude on 29.10.2017. + */ +public interface Executable { + + /** + * Execute PEG expression + * + * @return + */ + PegNode exec(State state); +} diff --git a/src/main/java/ru/dude/simplepeg/HelloWorld.java b/src/main/java/ru/dude/simplepeg/HelloWorld.java deleted file mode 100644 index cdf57c0..0000000 --- a/src/main/java/ru/dude/simplepeg/HelloWorld.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.dude.simplepeg; - -/** - * Created by dude on 29.10.2017. - */ -public class HelloWorld { - public static void main(String[]args){ - System.out.println("HelloWorld"); - } -} diff --git a/src/main/java/ru/dude/simplepeg/RdExecutor.java b/src/main/java/ru/dude/simplepeg/RdExecutor.java new file mode 100644 index 0000000..2db81a4 --- /dev/null +++ b/src/main/java/ru/dude/simplepeg/RdExecutor.java @@ -0,0 +1,360 @@ +package ru.dude.simplepeg; + +import ru.dude.simplepeg.entity.PegNode; +import ru.dude.simplepeg.entity.ResultType; +import ru.dude.simplepeg.entity.SpegTypes; +import ru.dude.simplepeg.entity.State; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Parser for PEG business expressions + *

+ * Created by dude on 29.10.2017. + */ +public class RdExecutor { + + + public RdExecutor() { + + } + + public Executable parseString(final String str) { + + return new Executable() { + @Override + public PegNode exec(State state) { + PegNode res = new PegNode(); + res.setType(SpegTypes.STRING); + res.setExecName(str); + + int endPos = state.getPosition() + str.length(); + if (endPos <= state.getTextData().length() && + state.getTextData().substring(state.getPosition(), endPos).equals(str)) { + + res.addMatch(str); + res.setStartPosition(state.getPosition()); + res.setEndPosition(state.appendPosition(str.length())); + res.setResultType(ResultType.OK); + } else { + res.setResultType(ResultType.ERROR); + res.setError(" parseString " + str + " lastPos = " + state.getPosition() + " unexpected " + state.atPos()); + } + return res; + } + }; + } + + public Executable parseRegexp(final String regexp) { + return new Executable() { + @Override + public PegNode exec(State state) { + + PegNode res = new PegNode(); + res.setType(SpegTypes.REGEXP); + + Pattern pattern = Pattern.compile(regexp); + Matcher matcher = pattern.matcher(state.getTextData()); + if (matcher.find(state.getPosition()) && state.getPosition() == matcher.start()) { + //нашли и у места , где каретка + String founded = matcher.group(); + + res.addMatch(founded); + res.setStartPosition(matcher.start()); + res.setEndPosition(state.appendPosition(founded.length())); + res.setResultType(ResultType.OK); + } else { + res.setResultType(ResultType.ERROR); + res.setError(" parseRegexp " + regexp + " lastPos = " + state.getPosition() + " unexpected " + state.atPos()); + } + return res; + } + }; + } + + /** + * Последовательно выполняет все exec. + * Возвращает ERROR, если вернулся хотя бы один ERROR + * EMPTY - если все exec вернули EMPTY + * OK - если все exec вернули OK(минимум один) или EMPTY + * + * @param execName + * @param execs + * @return + */ + public Executable sequence(final String execName, final Executable... execs) { + return new Executable() { + + @Override + public PegNode exec(State state) { + PegNode res = new PegNode(); + res.setType(SpegTypes.SEQUENCE); + res.setExecName(execName); + + for (Executable exec : execs) { + State stateCp = state.copy(); + PegNode pegNode = exec.exec(stateCp); + + switch (pegNode.getResultType()) { + case OK: + res.appendChild(pegNode); + res.setResultType(ResultType.OK); + state.setPosition(stateCp.getPosition()); + break; + case EMPTY: + + break; + case ERROR: + res.setResultType(ResultType.ERROR); + res.setError(" sequence " + execName + " lastPos = " + stateCp.getPosition() + " unexpected " + stateCp.atPos()); + state.setPosition(stateCp.getPosition()); + return res; + } + } + + if (res.getChildrens().size() == 0) { + res.setResultType(ResultType.EMPTY); + } + return res; + } + }; + + } + + /** + * Последовательное выполнение. Первая, полнившаяся OK возвращается как результат. + * Если вернулись все пустые: возвращает EMPTY + * Если в результатах exec были ERROR или EMPTY - возвращает ERROR + * + * @param execs + * @return + */ + public Executable orderedChoice(final String execName, final Executable... execs) { + return new Executable() { + + @Override + public PegNode exec(State state) { + PegNode res = new PegNode(); + res.setType(SpegTypes.ORDERED_CHOICE); + res.setExecName(execName); + + boolean hasEmpty = false; + boolean hasError = false; + + for (Executable exec : execs) { + State statecp = state.copy(); + PegNode pegNode = exec.exec(statecp); + + switch (pegNode.getResultType()) { + case OK: + res.appendChild(pegNode); + res.setResultType(ResultType.OK); + res.setError(""); + state.setPosition(statecp.getPosition()); + return res; + case EMPTY: + res.setResultType(ResultType.EMPTY); + hasEmpty = true; + //return res; + break; + case ERROR: + res.setResultType(ResultType.ERROR); + res.setError(" orderedChoice " + " lastPos = " + state.getPosition() + " unexpected " + state.atPos()); + hasError = true; + break; + + } + } + + if (hasError) { + res.setResultType(ResultType.ERROR); + } else { + if (hasEmpty) { + res.setResultType(ResultType.EMPTY); + } + } + return res; + } + }; + + } + + /** + * Выплняет exec, добавляя OK руезльутаты в child + * Возвращает OK если добавленых child > 0 иначе ERROR + * + * @param execName + * @param exec + * @return + */ + public Executable oneOrMore(final String execName, final Executable exec) { + return new Executable() { + + @Override + public PegNode exec(State state) { + PegNode res = new PegNode(); + res.setType(SpegTypes.ONE_OR_MORE); + res.setExecName(execName); + + PegNode pegNode; + while ((pegNode = exec.exec(state)).getResultType().equals(ResultType.OK)) { + res.appendChild(pegNode); + } + + if (res.getChildrens().size() > 0) { + res.setResultType(ResultType.OK); + } else { + res.setResultType(ResultType.ERROR); + res.setError(" oneOrMore " + " lastPos = " + state.getPosition() + " unexpected " + state.atPos()); + } + return res; + } + }; + } + + /** + * Выплняет exec, добавляя OK руезльутаты в child + * Возвращает OK если добавленых child > 0, EMPTY если child = 0 + * + * @param execName + * @param exec + * @return + */ + public Executable zeroOrMore(final String execName, final Executable exec) { + return new Executable() { + + @Override + public PegNode exec(State state) { + PegNode res = new PegNode(); + res.setType(SpegTypes.ZERO_OR_MORE); + res.setExecName(execName); + + PegNode pegNode; + while ((pegNode = exec.exec(state)).getResultType().equals(ResultType.OK)) { + res.appendChild(pegNode); + } + + if (res.getChildrens().size() == 0) { + res.setResultType(ResultType.EMPTY); + } else { + res.setResultType(ResultType.OK); + } + return res; + } + }; + + } + + /** + * Возвращает OK если результат exec, иначе EMPTY + * + * @param exec + * @return + */ + public Executable optional(final Executable exec) { + return new Executable() { + + @Override + public PegNode exec(State state) { + PegNode res = new PegNode(); + res.setType(SpegTypes.OPTIONAL); + + State statecp = state.copy(); + PegNode pegNode = exec.exec(statecp); + if (pegNode.getResultType().equals(ResultType.OK)) { + res.setResultType(ResultType.OK); + res.appendChild(pegNode); + state.setPosition(statecp.getPosition()); + } else { + res.setResultType(ResultType.EMPTY); + } + + return res; + } + }; + + } + + /** + * Предикат not. + * По хорошему, внутри он должен двигать position... оставлю пока так + * + * @param exec + * @return + */ + public Executable not(final Executable exec) { + return new Executable() { + + @Override + public PegNode exec(State state) { + PegNode res = new PegNode(); + res.setType(SpegTypes.NOT); + + State statecp = state.copy(); + PegNode pegNode = exec.exec(statecp); + + switch (pegNode.getResultType()) { + case OK: + + res.setResultType(ResultType.ERROR); + res.setError(" not " + " lastPos = " + statecp.getPosition() + " unexpected " + statecp.atPos()); + + break; + case ERROR: + default: + res.setResultType(ResultType.OK); + res.appendChild(pegNode); + state.setPosition(statecp.getPosition()); + break; + } + return res; + } + }; + + } + + /** + * OK если достигнут конец строки данных + * + * @return + */ + public Executable parseEndOfFile() { + return new Executable() { + + @Override + public PegNode exec(State state) { + + PegNode res = new PegNode(); + res.setType(SpegTypes.END_OF_FILE); + if (state.getPosition() >= state.getTextData().length()) { + + res.setResultType(ResultType.OK); + res.setStartPosition(state.getPosition()); + res.setEndPosition(state.getPosition()); + } else { + res.setResultType(ResultType.ERROR); + res.setError(" parseEndOfFile " + " lastPos = " + state.getPosition() + " unexpected " + state.atPos()); + } + return res; + } + }; + } + + /** + * @param spParser + * @return + */ + public Executable rec(final SpegParser spParser) { + return new Executable() { + + @Override + public PegNode exec(State state) { + Executable recExec = spParser.ruleExpression(); + return recExec.exec(state); + } + }; + } + + +} diff --git a/src/main/java/ru/dude/simplepeg/RuleProcessor.java b/src/main/java/ru/dude/simplepeg/RuleProcessor.java new file mode 100644 index 0000000..bc0aa6d --- /dev/null +++ b/src/main/java/ru/dude/simplepeg/RuleProcessor.java @@ -0,0 +1,146 @@ +package ru.dude.simplepeg; + +import ru.dude.simplepeg.entity.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * For text checking + */ +public class RuleProcessor { + + /** + * Разобранные правила + */ + Map rules; + + /** + * Первое правило + */ + PegNode firstRule; + + RdExecutor rdExecutor; + + public RuleProcessor(PegNode grammarTree) { + rdExecutor = new RdExecutor(); + selectRules(grammarTree); + } + + private void selectRules(PegNode grammarTree) { + rules = new HashMap<>(); + firstRule = null; + + PegNode lines = grammarTree.child("body"); + for (PegNode ch : lines.getChildrens()) { + if (ch.getExecName().equals("rule_lines")) { + PegNode rule = ch.child("rule"); + String ruleName = rule.child("rule_name").getMatch().toString(); + PegNode ruleExpression = rule.child("rule_expression"); + if (firstRule == null) { + firstRule = ruleExpression; + } + rules.put(ruleName, ruleExpression); + } + } + } + + public CheckResult check(String text) { + if (firstRule == null || rules == null || rules.size() == 0) { + return CheckResult.error("rules init failure"); + } + + State textState = new State(text); + + PegNode resultExec = executeRule(firstRule, textState); + + if (resultExec.getResultType() == ResultType.OK) { + return CheckResult.ok(); + } + + return CheckResult.error(resultExec.getError()); + } + + private PegNode executeRule(PegNode rule, State state) { + + Executable exec = applyRule(rule); + PegNode res = exec.exec(state); + return res; + } + + + public Executable applyRule(final PegNode rule) { + return new Executable() { + + @Override + public PegNode exec(State state) { + + Executable[] childExecs = null; + + if (rule.getChildrens() != null) { + List childExecsList = new ArrayList<>(); + for (PegNode childNode : rule.getChildrens()) { + childExecsList.add(applyRule(childNode)); + } + childExecs = childExecsList.toArray(new Executable[0]); + } + + String fullStr = rule.getMatch().toString(); + String unQuotedStr = fullStr; + if (fullStr.length() > 1) { + unQuotedStr = fullStr.substring(1, fullStr.length() - 1); + } + + PegNode emptyRes = new PegNode(); + emptyRes.setResultType(ResultType.EMPTY); + + if (rule.getExecName() == null) { + return emptyRes; + } + + SpegNames spegName = SpegNames.bySpegName(rule.getExecName()); + + switch (spegName) { + case NAME_STRING: + return rdExecutor.parseString(unQuotedStr).exec(state); + case NAME_REGEX: + return rdExecutor.parseRegexp(fullStr).exec(state); + case NAME_SEQUENCE: + return rdExecutor.sequence("applySequence", childExecs).exec(state); + case NAME_ORDERED_CHOCE: + return rdExecutor.orderedChoice("applyOrderedChoice", childExecs).exec(state); + case NAME_ONE_OR_MORE: + return rdExecutor.oneOrMore("applyOneOrMore", childExecs[0]).exec(state); + case NAME_ZERO_OR_MORE: + return rdExecutor.zeroOrMore("applyZeroOrMore", childExecs[0]).exec(state); + case NAME_NOT: + return rdExecutor.not(childExecs[1]).exec(state); + //case NAME_AND: //return rdExecutor.(childExecs[1]).exec(state); + case NAME_OPTIONAL: + return rdExecutor.optional(childExecs[0]).exec(state); + case NAME_RULE_EXPRESSION: + default: + if (rules.containsKey(fullStr)) { + return applyRule(rules.get(fullStr)).exec(state); + } + + + if (childExecs.length == 0) { + return emptyRes; + } + + if (childExecs.length == 1) { + return childExecs[0].exec(state); + } + + return rdExecutor.sequence("deafultSequence", childExecs).exec(state); + } + } + }; + } + + +} diff --git a/src/main/java/ru/dude/simplepeg/SpegParser.java b/src/main/java/ru/dude/simplepeg/SpegParser.java new file mode 100644 index 0000000..4eb0daa --- /dev/null +++ b/src/main/java/ru/dude/simplepeg/SpegParser.java @@ -0,0 +1,329 @@ +package ru.dude.simplepeg; + +import ru.dude.simplepeg.entity.PegNode; +import ru.dude.simplepeg.entity.SpegNames; +import ru.dude.simplepeg.entity.State; + +import java.io.InputStream; +import java.util.Map; + +/** + * Parser for SimplePEG constructions + *

+ * Created by dude on 29.10.2017. + */ +public class SpegParser { + + private Map rules; + State state; + RdExecutor rdExecutor; + + + public SpegParser(State state) { + this.state = state; + this.rdExecutor = new RdExecutor(); + } + + public SpegParser(State state, Map rules) { + this(state); + this.rules = rules; + } + + + public static PegNode createAndExec(String grammar) { + State state = new State(grammar); + + SpegParser spegParser = new SpegParser(state); + return spegParser.peg().exec(state); + } + + public static PegNode createAndExec(InputStream grammarIS) { + State state = new State(grammarIS); + + SpegParser spegParser = new SpegParser(state); + return spegParser.peg().exec(state); + } + + /** + * All SPEG + * + * @return + */ + public Executable peg() { + + return rdExecutor.sequence("peg_parser", + rdExecutor.zeroOrMore("spaces", spacesBreaks()), + parsingHeader(), + rdExecutor.oneOrMore("spaces", spacesBreaks()), + parsingBody(), + rdExecutor.parseEndOfFile() + ); + + + } + + /** + * HEADER only + * + * @return + */ + public Executable parsingHeader() { + return rdExecutor.sequence("header", + rdExecutor.parseString("GRAMMAR"), + rdExecutor.oneOrMore("spaces", spacesBreaks()), + rdExecutor.oneOrMore("rulenames", ruleName()) + ); + } + + + /** + * BODy only + * + * @return + */ + public Executable parsingBody() { + return rdExecutor.oneOrMore("body", + rdExecutor.orderedChoice("rule_lines", + parsingRule(), + rdExecutor.oneOrMore("spaces", spacesBreaks()) + ) + ); + } + + /** + * Space symbols filter + * + * @return + */ + private Executable spacesBreaks() { + return rdExecutor.parseRegexp("[\\s]"); + } + + + /** + * Parce rule + *

+ * + * @return + */ + private Executable parsingRule() { + return rdExecutor.sequence("rule", + + ruleName(), + rdExecutor.zeroOrMore("spaces", spacesBreaks()), + rdExecutor.parseString("->"), + rdExecutor.zeroOrMore("spaces", spacesBreaks()), + ruleExpression(), + rdExecutor.zeroOrMore("spaces", spacesBreaks()), + rdExecutor.parseString(";"), + rdExecutor.zeroOrMore("spaces", spacesBreaks()) + + ); + } + + /** + * Parse rule name + *

+ * js parsing_rule_name + * + * @return + */ + private Executable ruleName() { + return rdExecutor.sequence("rule_name", + rdExecutor.parseRegexp("[a-zA-Z_]"), + rdExecutor.zeroOrMore("", rdExecutor.parseRegexp("[a-zA-Z0-9_]")) + ); + } + + /** + * Parse rule Expression + *

+ * js parsing_expression + * + * @return + */ + public Executable ruleExpression() { + return rdExecutor.orderedChoice("rule_expression", + parsingSequence(), + parsingOrderedChoice(), + parsingSubExpression() + ); + } + + + private Executable parsingSequence() { + + return rdExecutor.sequence("sequence", + rdExecutor.orderedChoice( + "", parsingOrderedChoice(), + parsingSubExpression() + ), + rdExecutor.oneOrMore("sequence_args", + rdExecutor.sequence("sequence_arg", + rdExecutor.oneOrMore("spaces", spacesBreaks()), + rdExecutor.orderedChoice( + "", parsingOrderedChoice(), + parsingSubExpression() + ) + ) + ) + ); + } + + private Executable parsingOrderedChoice() { + return rdExecutor.sequence("ordered_choice", + parsingSubExpression(), + rdExecutor.oneOrMore("ordered_choice_args", + rdExecutor.sequence("ordered_choice_arg", + rdExecutor.zeroOrMore("spaces", spacesBreaks()), + rdExecutor.parseString("/"), + rdExecutor.zeroOrMore("spaces", spacesBreaks()), + parsingSubExpression() + ) + ) + ); + } + + private Executable parsingSubExpression() { + return rdExecutor.sequence("sub_expression", + + rdExecutor.zeroOrMore("", rdExecutor.sequence("tags", + tag(), + rdExecutor.parseString(":") + + )), + rdExecutor.orderedChoice( + "", parsingNot(), + parsingAnd(), + parsingOptional(), + parsingOneOrMore(), + parsingZeroOrMore(), + parsingGroup(), + parsingAtomicExpression() + ) + + ); + + } + + + private Executable tag() { + return rdExecutor.sequence("tag_name", + rdExecutor.parseRegexp("[a-zA-Z_]"), + rdExecutor.zeroOrMore("", rdExecutor.parseRegexp("[a-zA-Z0-9_]")) + ); + } + + + private Executable parsingGroup() { + return rdExecutor.sequence("group", + rdExecutor.parseString("("), + rdExecutor.zeroOrMore("spaces", spacesBreaks()), + rdExecutor.rec(this), + rdExecutor.zeroOrMore("spaces", spacesBreaks()), + rdExecutor.parseString(")") + ); + } + + private Executable parsingAtomicExpression() { + return rdExecutor.orderedChoice( + "", parseString(), + parseRegex(), + rdExecutor.parseEndOfFile(), + parsingRuleCall() + ); + } + + + /** + * js parsing_rule_call() + * + * @return + */ + private Executable parsingRuleCall() { + return ruleName(); + } + + private Executable parseString() { + return rdExecutor.sequence(SpegNames.NAME_STRING.getSpegName(), + rdExecutor.parseString("\""), + rdExecutor.oneOrMore(SpegNames.NAME_STRING.getSpegName(), + rdExecutor.orderedChoice( + "", rdExecutor.parseString("\\\\"), + rdExecutor.parseString("\\\""), + rdExecutor.parseRegexp("[^\"]") + )), + rdExecutor.parseString("\"") + ); + } + + private Executable parseRegex() { + return rdExecutor.orderedChoice( + "", rdExecutor.sequence(SpegNames.NAME_REGEX.getSpegName(), + rdExecutor.parseString("["), + rdExecutor.optional(rdExecutor.parseString("^")), + rdExecutor.oneOrMore("regex[]", rdExecutor.orderedChoice( + "", rdExecutor.parseString("\\]"), + rdExecutor.parseString("\\["), + rdExecutor.parseRegexp("[^\\]]") + )), + rdExecutor.parseString("]") + ), + rdExecutor.parseString(".") + ); + } + + + private Executable parsingNot() { + return rdExecutor.sequence(SpegNames.NAME_NOT.getSpegName(), + rdExecutor.parseString("!"), + rdExecutor.orderedChoice( + "", parsingGroup(), + parsingAtomicExpression() + ) + ); + } + + private Executable parsingAnd() { + return rdExecutor.sequence(SpegNames.NAME_AND.getSpegName(), + rdExecutor.parseString("&"), + rdExecutor.orderedChoice( + "", parsingGroup(), + parsingAtomicExpression() + ) + ); + } + + private Executable parsingOneOrMore() { + return rdExecutor.sequence(SpegNames.NAME_ONE_OR_MORE.getSpegName(), + rdExecutor.orderedChoice( + "", parsingGroup(), + parsingAtomicExpression() + ), + rdExecutor.parseString("+") + ); + } + + + private Executable parsingZeroOrMore() { + return rdExecutor.sequence(SpegNames.NAME_ZERO_OR_MORE.getSpegName(), + rdExecutor.orderedChoice( + "", parsingGroup(), + parsingAtomicExpression() + ), + rdExecutor.parseString("*") + ); + } + + private Executable parsingOptional() { + return rdExecutor.sequence(SpegNames.NAME_OPTIONAL.getSpegName(), + rdExecutor.orderedChoice( + "", parsingGroup(), + parsingAtomicExpression() + ), + rdExecutor.parseString("?") + ); + } + + +} diff --git a/src/main/java/ru/dude/simplepeg/Starter.java b/src/main/java/ru/dude/simplepeg/Starter.java new file mode 100644 index 0000000..a29977c --- /dev/null +++ b/src/main/java/ru/dude/simplepeg/Starter.java @@ -0,0 +1,47 @@ +package ru.dude.simplepeg; + +import ru.dude.simplepeg.entity.PegNode; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; + +/** + * Created by dude on 29.10.2017. + */ +public class Starter { + + public static void main(String[] args) throws Exception { + + String grammar = "GRAMMAR url\n" + + "\n" + + "url -> scheme \"://\" host pathname search hash?;\n" + + "scheme -> \"http\" \"s\"?;\n" + + "host -> hostname port?;\n" + + "hostname -> segment (\".\" segment)*;\n" + + "segment -> [a-z0-9-]+;\n" + + "port -> \":\" [0-9]+;\n" + + "pathname -> \"/\" [^ ?]*;\n" + + "search -> (\"?\" [^ #]*)?;\n" + + "hash -> \"#\" [^ ]*;"; + + RuleProcessor rp = new RuleProcessor(SpegParser.createAndExec(grammar)); + + System.out.println(rp.check("https://simplepeg.github.io/")); + System.out.println(rp.check("https://google.com/")); + System.out.println(rp.check("https://abcdssss.....com/")); + + } + + public static void printTree(PegNode node) throws IOException { + System.out.println(node.getResultType()); + System.out.println(node.getError()); + + StringBuilder sb = new StringBuilder(); + node.toJson(sb, 0); + BufferedWriter bw = new BufferedWriter(new FileWriter("tree.json")); + + bw.write(sb.toString()); + bw.close(); + } +} diff --git a/src/main/java/ru/dude/simplepeg/entity/CheckResult.java b/src/main/java/ru/dude/simplepeg/entity/CheckResult.java new file mode 100644 index 0000000..6f67b5a --- /dev/null +++ b/src/main/java/ru/dude/simplepeg/entity/CheckResult.java @@ -0,0 +1,45 @@ +package ru.dude.simplepeg.entity; + +/** + * Результат проверки + */ +public class CheckResult { + + private ResultType resultType; + private String errorText; + + private CheckResult() { + + } + + public static CheckResult error(String errorText) { + CheckResult r = new CheckResult(); + r.resultType = ResultType.ERROR; + r.errorText = errorText; + return r; + } + + public static CheckResult ok() { + CheckResult r = new CheckResult(); + r.resultType = ResultType.OK; + r.errorText = ""; + return r; + } + + public ResultType getResultType() { + return resultType; + } + + public String getErrorText() { + return errorText; + } + + @Override + public String toString() { + if (ResultType.OK.equals(resultType)) { + return ResultType.OK.toString(); + } else { + return resultType + "[" + errorText + ']'; + } + } +} diff --git a/src/main/java/ru/dude/simplepeg/entity/PegNode.java b/src/main/java/ru/dude/simplepeg/entity/PegNode.java new file mode 100644 index 0000000..95df1c6 --- /dev/null +++ b/src/main/java/ru/dude/simplepeg/entity/PegNode.java @@ -0,0 +1,157 @@ +package ru.dude.simplepeg.entity; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tree object with result data + *

+ * Created by dude on 29.10.2017. + */ +public class PegNode { + + private static Integer nextId = 0; + + Integer id; + SpegTypes type; + StringBuilder match = new StringBuilder(); + Integer startPosition; + Integer endPosition; + + List childrens = new ArrayList<>(); + + ResultType resultType = ResultType.NONE; + String error; + private String execName; + + public PegNode() { + id = nextId++; + } + + public void appendChild(PegNode child) { + childrens.add(child); + match.append(child.match); + + if (startPosition == null || getStartPosition() > child.getStartPosition()) { + startPosition = child.getStartPosition(); + } + + if (endPosition == null || endPosition < child.getEndPosition()) { + endPosition = child.getEndPosition(); + } + } + + + public void toJson(StringBuilder sb, int level) { + + addtabs(sb, level).append("{\n"); + addtabs(sb, level + 1).append("\"id\" :\"").append(id).append("\",\n"); + addtabs(sb, level + 1).append("\"execName\" :\"").append(execName).append("\",\n"); + addtabs(sb, level + 1).append("\"type\" :\"").append(type).append("\",\n"); + addtabs(sb, level + 1).append("\"match\" :\"").append(match).append("\",\n"); + addtabs(sb, level + 1).append("\"startPosition\" :").append(startPosition).append(",\n"); + addtabs(sb, level + 1).append("\"endPosition\" :").append(endPosition).append(",\n"); + + if (childrens.size() > 0) { + addtabs(sb, level + 1).append("\"childrens\":[\n"); + for (PegNode children : childrens) { + children.toJson(sb, level + 2); + } + addtabs(sb, level + 1).append("]\n"); + } + addtabs(sb, level).append("},\n"); + } + + private StringBuilder addtabs(StringBuilder sb, int level) { + for (int i = 0; i < level; ++i) { + sb.append("\t"); + } + return sb; + } + + public PegNode child(String execName) { + + for (PegNode ch : childrens) { + if (ch.getExecName().equals(execName)) { + return ch; + } + } + return null; + } + + + public SpegTypes getType() { + return type; + } + + public PegNode setType(SpegTypes type) { + this.type = type; + return this; + } + + public StringBuilder getMatch() { + return match; + } + + public PegNode addMatch(String match) { + this.match.append(match); + return this; + } + + public PegNode setMatch(StringBuilder match) { + this.match = match; + return this; + } + + public Integer getStartPosition() { + return startPosition; + + } + + public PegNode setStartPosition(Integer startPosition) { + this.startPosition = startPosition; + return this; + } + + public Integer getEndPosition() { + return endPosition; + } + + public PegNode setEndPosition(Integer endPosition) { + this.endPosition = endPosition; + return this; + } + + public List getChildrens() { + return childrens; + } + + public void setChildrens(List childrens) { + this.childrens = childrens; + } + + + public ResultType getResultType() { + return resultType; + } + + public void setResultType(ResultType resultType) { + this.resultType = resultType; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public void setExecName(String execName) { + this.execName = execName; + } + + public String getExecName() { + return execName; + } +} diff --git a/src/main/java/ru/dude/simplepeg/entity/ResultType.java b/src/main/java/ru/dude/simplepeg/entity/ResultType.java new file mode 100644 index 0000000..785e8c4 --- /dev/null +++ b/src/main/java/ru/dude/simplepeg/entity/ResultType.java @@ -0,0 +1,12 @@ +package ru.dude.simplepeg.entity; + +/** + * Тип результата + * Created by dude on 30.10.2017. + */ +public enum ResultType { + NONE, + OK, + ERROR, + EMPTY +} diff --git a/src/main/java/ru/dude/simplepeg/entity/SpegNames.java b/src/main/java/ru/dude/simplepeg/entity/SpegNames.java new file mode 100644 index 0000000..85fe28e --- /dev/null +++ b/src/main/java/ru/dude/simplepeg/entity/SpegNames.java @@ -0,0 +1,42 @@ +package ru.dude.simplepeg.entity; + +/** + * SPEG names. + * + * Имя узла может нести семантическое значение узла (например header или only_letter_rule). + * Здесь собраны имена узлов, определяемых при парсинге грамматики, и используемые для валидации по грамматике. + */ +public enum SpegNames { + NAME_OTHER(""), + NAME_STRING("string"), + NAME_REGEX("regex"), + NAME_SEQUENCE("sequence"), + NAME_ORDERED_CHOCE("ordered_choice"), + NAME_ONE_OR_MORE("one_or_more"), + NAME_ZERO_OR_MORE("zero_or_more"), + NAME_NOT("not"), + NAME_AND("and"), + NAME_OPTIONAL("optional"), + NAME_RULE_EXPRESSION("rule_expression"), + ; + + private String spegName; + + SpegNames(String spegName) { + + this.spegName = spegName; + } + + public String getSpegName() { + return spegName; + } + + public static SpegNames bySpegName(String spegName) { + for (SpegNames sn : values()) { + if (sn.getSpegName().equals(spegName)) { + return sn; + } + } + return NAME_OTHER; + } +} diff --git a/src/main/java/ru/dude/simplepeg/entity/SpegTypes.java b/src/main/java/ru/dude/simplepeg/entity/SpegTypes.java new file mode 100644 index 0000000..1a49fd9 --- /dev/null +++ b/src/main/java/ru/dude/simplepeg/entity/SpegTypes.java @@ -0,0 +1,19 @@ +package ru.dude.simplepeg.entity; + +/** + * PEG operation types + * + * Типы операций в peg-грамматике. Фиксированный набор. + * Created by dude on 29.10.2017. + */ +public enum SpegTypes { + SEQUENCE, + ORDERED_CHOICE, + ONE_OR_MORE, + ZERO_OR_MORE, + REGEXP, + STRING, + OPTIONAL, + END_OF_FILE, + NOT, +} diff --git a/src/main/java/ru/dude/simplepeg/entity/State.java b/src/main/java/ru/dude/simplepeg/entity/State.java new file mode 100644 index 0000000..8b8df42 --- /dev/null +++ b/src/main/java/ru/dude/simplepeg/entity/State.java @@ -0,0 +1,99 @@ +package ru.dude.simplepeg.entity; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; + +/** + * Store and processing input data + * Created by dude on 29.10.2017. + */ +public class State { + + /** + * Input data + */ + StringBuilder textData; + + /** + * current position (slide) + */ + int position; + + private State() { + textData = new StringBuilder(); + position = 0; + } + + public State(InputStream is) { + textData = new StringBuilder(); + position = 0; + loadByStream(is); + } + + public State(String grammar) { + textData = new StringBuilder(grammar); + position = 0; + } + + /** + * Load data from input stream + * + * @param is + */ + private void loadByStream(InputStream is) { + textData = new StringBuilder(); + + try (Reader r = new InputStreamReader(is, "UTF-8")) { + int c = 0; + while ((c = r.read()) != -1) { + textData.append((char) c); + } + r.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + position = 0; + } + + + public int getPosition() { + return position; + } + + public StringBuilder getTextData() { + return textData; + } + + /** + * increase slide + * + * @param len + * @return + */ + public int appendPosition(int len) { + position += len; + return position; + } + + public String atPos() { + if (position < textData.length()) { + return textData.charAt(position) + ""; + } + return null; + } + + public State copy() { + State state = new State(); + state.textData = textData; + state.position = position; + return state; + } + + + public void setPosition(int position) { + this.position = position; + } +} diff --git a/src/test/java/RdComplexTest.java b/src/test/java/RdComplexTest.java new file mode 100644 index 0000000..329014d --- /dev/null +++ b/src/test/java/RdComplexTest.java @@ -0,0 +1,87 @@ +import org.junit.Assert; +import org.junit.Test; +import ru.dude.simplepeg.Executable; +import ru.dude.simplepeg.RdExecutor; +import ru.dude.simplepeg.entity.PegNode; +import ru.dude.simplepeg.entity.ResultType; +import ru.dude.simplepeg.entity.State; + +/** + * Created by dude on 27.05.2018. + *

+ * Сложные тесты для RdExecutor + */ +public class RdComplexTest extends Assert { + + private void assertProcess(String grammar, String input, Executable executable, ResultType resultType) { + PegNode result = executable.exec(new State(grammar)); + String message = "GRAMMAR:\n" + grammar + "\nINPUT:\n" + input + "\nERROR:" + result.getError(); + assertEquals(message, resultType, result.getResultType()); + } + + + @Test + public void sequencesAndOneZeroMore() { + RdExecutor rd = new RdExecutor(); + Executable executable = rd.sequence("test_sequence", + rd.oneOrMore("", rd.parseString("a")), + rd.oneOrMore("", rd.parseString("b")), + rd.zeroOrMore("", rd.parseString("cc")), + rd.zeroOrMore("", rd.parseString("X")), + rd.zeroOrMore("", rd.parseString("d")), + rd.oneOrMore("", rd.parseString("e")) + ); + assertProcess("aabbccddee", "abcdXde", executable, ResultType.OK); + assertProcess("aabbccccddee", "abcdXde", executable, ResultType.OK); + assertProcess("aabbcccddee", "abcdXde", executable, ResultType.ERROR); + } + + @Test + public void sequencesAndOptional() { + RdExecutor rd = new RdExecutor(); + Executable executable = rd.sequence("test_sequence", + rd.parseString("aaaa"), + rd.optional(rd.parseString("bbbb")), + rd.optional(rd.parseString("bbbX")), + rd.parseString("cccc") + ); + assertProcess("aaaabbbbcccc", "aaaabbbbXcccc", executable, ResultType.OK); + assertProcess("aaaacccc", "aaaabbbbXcccc", executable, ResultType.OK); + assertProcess("aaaabbbXcccc", "aaaabbbbXcccc", executable, ResultType.OK); + } + + @Test + public void oneOrManyAndOptional() { + RdExecutor rd = new RdExecutor(); + Executable executable = rd.oneOrMore("test_oneOrMany", + rd.orderedChoice( + "", rd.optional(rd.parseString("bxy")), + rd.optional(rd.parseString("bxz")) + ) + ); + assertProcess("bxz", "bxy/bxz", executable, ResultType.OK); + + } + + @Test + public void sequenceAndOneOrManyAndOptional() { + RdExecutor rd = new RdExecutor(); + Executable executable = + rd.sequence("", + rd.oneOrMore("test_oneOrMany", + rd.optional( + rd.sequence("", + rd.parseString("a"), + rd.parseString("b"), + rd.parseString("c") + ) + ) + ), + rd.parseString("abx") + ); + assertProcess("abcabcabx", "abcabcabx", executable, ResultType.OK); + + } + + +} diff --git a/src/test/java/RdExecutorTest.java b/src/test/java/RdExecutorTest.java new file mode 100644 index 0000000..be0edb0 --- /dev/null +++ b/src/test/java/RdExecutorTest.java @@ -0,0 +1,197 @@ +import org.junit.Assert; +import org.junit.Test; +import ru.dude.simplepeg.Executable; +import ru.dude.simplepeg.RdExecutor; +import ru.dude.simplepeg.entity.PegNode; +import ru.dude.simplepeg.entity.ResultType; +import ru.dude.simplepeg.entity.State; + + +/** + * Created by dude on 27.05.2018. + * + * Тесты конструкций PEG + */ +public class RdExecutorTest extends Assert { + + + private void assertProcess(String grammar,String input,Executable executable,ResultType resultType){ + PegNode result = executable.exec(new State(grammar)); + String message = "GRAMMAR:\n" + grammar + "\nINPUT:\n"+input+"\nERROR:"+result.getError(); + assertEquals(message, resultType,result.getResultType()); + } + + @Test + public void simpleString(){ + String grammar = "aaaazz;"; + String input = "aaaazz"; + Executable executable = new RdExecutor().parseString(input); + assertProcess(grammar,input,executable,ResultType.OK); + } + + + @Test + public void simpleRegexp(){ + String grammar = "aaaazz;"; + String input = "[a-z]+"; + Executable executable = new RdExecutor().parseRegexp(input); + assertProcess(grammar,input,executable,ResultType.OK); + } + + @Test + public void eofTest(){ + String grammar = ""; + String input = "[___ANY___]"; + Executable executable = new RdExecutor().parseEndOfFile(); + assertProcess(grammar,input,executable,ResultType.OK); + } + + @Test + public void simpleSequenceOk(){ + String grammar = "aaaazzxxXx"; + String s1 = "aaaa"; + String s2 = "zz"; + String r3 = "[xX]+"; + RdExecutor rd = new RdExecutor(); + Executable executable = rd.sequence("test_sequence", + rd.parseString(s1), + rd.parseString(s2), + rd.parseRegexp(r3) + ); + assertProcess(grammar,s1+","+s2+","+r3,executable,ResultType.OK); + } + + @Test + public void simpleSequenceEmpty(){ + String grammar = "_____XXXX___"; + String s1 = "aaaa"; + String s2 = "zz"; + String r3 = "[0-9]+"; + RdExecutor rd = new RdExecutor(); + Executable executable = rd.sequence("test_sequence", + rd.optional(rd.parseString(s1)), + rd.optional(rd.parseString(s2)), + rd.optional(rd.parseRegexp(r3)) + ); + assertProcess(grammar,s1+","+s2+","+r3,executable,ResultType.EMPTY); + } + + @Test + public void simpleSequenceError(){ + String grammar = "aaaazzxxXx"; + String s1 = "aaaa"; + String s2 = "zz"; + String r3 = "[0-9]+"; + RdExecutor rd = new RdExecutor(); + Executable executable = rd.sequence("test_sequence", + rd.parseString(s1), + rd.parseString(s2), + rd.parseRegexp(r3) + ); + assertProcess(grammar,s1+","+s2+","+r3,executable,ResultType.ERROR); + } + + @Test + public void simpleOrderedChoiceOk(){ + String s1 = "aaaa"; + String s2 = "zz"; + String r3 = "[xX]+"; + RdExecutor rd = new RdExecutor(); + Executable executable = rd.orderedChoice( + "", rd.parseString(s1), + rd.parseString(s2), + rd.parseRegexp(r3) + ); + assertProcess("aaaazzxxXx_",s1+","+s2+","+r3,executable,ResultType.OK); + assertProcess("zz____xxXx_",s1+","+s2+","+r3,executable,ResultType.OK); + assertProcess("xxXx_______",s1+","+s2+","+r3,executable,ResultType.OK); + } + + @Test + public void simpleOrderedChoiceEmpty(){ + String s1 = "aaaa"; + String s2 = "zz"; + String r3 = "[0-9]+"; + RdExecutor rd = new RdExecutor(); + Executable executable = rd.orderedChoice( + "", rd.optional(rd.parseString(s1)), + rd.optional(rd.parseString(s2)), + rd.optional(rd.parseRegexp(r3)) + ); + assertProcess("xxXx_______",s1+","+s2+","+r3,executable,ResultType.EMPTY); + } + + @Test + public void simpleOrderedChoiceError(){ + String s1 = "aaaa"; + String s2 = "zz"; + String r3 = "[0-9]+"; + RdExecutor rd = new RdExecutor(); + Executable executable = rd.orderedChoice( + "", rd.parseString(s1), + rd.parseString(s2), + rd.parseRegexp(r3) + ); + assertProcess("xxXx_______",s1+","+s2+","+r3,executable,ResultType.ERROR); + } + + @Test + public void simpleOneOrMoreOk(){ + String s1 = "a"; + RdExecutor rd = new RdExecutor(); + Executable executable = rd.oneOrMore("simpleOneOrMoreOK_test", + rd.parseString(s1) + ); + assertProcess("aaaazzxxXx_",s1,executable,ResultType.OK); + } + + @Test + public void simpleOneOrMoreError(){ + String s1 = "a"; + RdExecutor rd = new RdExecutor(); + Executable executable = rd.oneOrMore("simpleOneOrMoreError_test", + rd.parseString(s1) + ); + assertProcess("bbaaaazzxxXx_",s1,executable,ResultType.ERROR); + } + + @Test + public void simplZeroOrMoreOk(){ + String s1 = "a"; + RdExecutor rd = new RdExecutor(); + Executable executable = rd.zeroOrMore( + "", rd.parseString(s1) + ); + assertProcess("aaaazzxxXx_",s1,executable,ResultType.OK); + } + + @Test + public void simpleZeroOrMoreEmpty(){ + String s1 = "a"; + RdExecutor rd = new RdExecutor(); + Executable executable = rd.zeroOrMore( + "", rd.parseString(s1) + ); + assertProcess("bbaaaazzxxXx_",s1,executable,ResultType.EMPTY); + } + + @Test + public void simpleOptionalOk(){ + String s1 = "a"; + RdExecutor rd = new RdExecutor(); + Executable executable = rd.optional( + rd.parseString(s1) + ); + assertProcess("aaaazzxxXx_",s1,executable,ResultType.OK); + } + + @Test + public void simpleOptionalEmpty(){ + String s1 = "a"; + RdExecutor rd = new RdExecutor(); + Executable executable = rd.optional( + rd.parseString(s1) + ); + assertProcess("bbaaaazzxxXx_",s1,executable,ResultType.EMPTY); + } +} diff --git a/src/test/java/RuleTest.java b/src/test/java/RuleTest.java new file mode 100644 index 0000000..a2e4f2f --- /dev/null +++ b/src/test/java/RuleTest.java @@ -0,0 +1,385 @@ +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import ru.dude.simplepeg.RuleProcessor; +import ru.dude.simplepeg.SpegParser; +import ru.dude.simplepeg.entity.CheckResult; +import ru.dude.simplepeg.entity.PegNode; +import ru.dude.simplepeg.entity.ResultType; + +/** + * Created by dude on 27.05.2018. + * + * Тесты выполнения rule + */ +public class RuleTest extends Assert { + + @Test + public void ruleString(){ + String grammar = "GRAMMAR simple url-> \"aaaa\";"; + String text = "aaaa"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + + @Test + public void ruleStringError(){ + String grammar = "GRAMMAR simple url-> \"aaaa\";"; + String text = "aabb"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.ERROR); + } + + @Test + public void ruleStringMany(){ + String grammar = "GRAMMAR simple url-> \"aaaa\" \"bb\" \"cc\" \"dd\" \"ee\";"; + String text = "aaaabbccddee"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + + @Test + public void ruleRegex(){ + String grammar = "GRAMMAR simple url-> [a-z];"; + String text = "a"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + + @Test + public void ruleRegexMany(){ + String grammar = "GRAMMAR simple url-> [a-z] [A-Z] [0-9];"; + String text = "aZ2"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + + @Test + public void ruleRegexError(){ + String grammar = "GRAMMAR simple url-> [a-z];"; + String text = "X"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.ERROR); + } + + + @Test + public void ruleOneOrMore(){ + String grammar = "GRAMMAR simple url-> \"a\"+;"; + String text = "aaa"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + @Test + public void ruleOneOrMore2(){ + String grammar = "GRAMMAR simple url-> \"ab\"+;"; + String text = "ababab"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + @Test + public void ruleOneOrMoreError(){ + String grammar = "GRAMMAR simple url-> \"abc\"+;"; + String text = "ababab"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.ERROR); + } + + @Test + public void ruleZeroOrMore(){ + String grammar = "GRAMMAR simple url-> \"a\"*;"; + String text = "aaa"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + @Test + public void ruleZeroOrMore2(){ + String grammar = "GRAMMAR simple url-> \"ab\"*;"; + String text = "ababX"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + @Test + public void ruleOrderedChoise(){ + String grammar = "GRAMMAR simple url-> \"a\"/\"b\";"; + String text = "b"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + @Test + public void ruleOrderedChoiseError(){ + String grammar = "GRAMMAR simple url-> \"a\"/\"b\";"; + String text = "c"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.ERROR); + } + + + @Test + public void ruleGroup(){ + String grammar = "GRAMMAR simple url-> (\"a\"/\"b\")+;"; + String text = "ab"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + @Test + public void ruleGroupError(){ + String grammar = "GRAMMAR simple url-> (\"a\"/\"b\");"; + String text = "c"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.ERROR); + } + + + @Test + public void ruleNot(){ + String grammar = "GRAMMAR simple url-> !\"a\";"; + String text = "b"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + @Test + public void ruleNotError(){ + String grammar = "GRAMMAR simple url-> !\"a\";"; + String text = "a"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.ERROR); + } + + /** + * осатвлю пока также как в js версии. + * по моему то неправильное поведение предиката not + */ + @Test + public void ruleNot2(){ + String grammar = "GRAMMAR simple url-> (!\"a\") \"v\";"; + String text = "v"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + + @Test + @Ignore + public void ruleAnd(){ + + /* + ??????? + + не понятно что это + + String grammar = "GRAMMAR simple url-> (!\"a\") \"v\";"; + String text = "v"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + */ + } + + @Test + public void ruleOptional(){ + String grammar = "GRAMMAR simple url-> \"a\"? \"b\";"; + String text = "ab"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + @Test + public void ruleOptional2(){ + String grammar = "GRAMMAR simple url-> \"a\"? \"b\";"; + String text = "b"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + @Test + public void ruleOptionalError(){ + String grammar = "GRAMMAR simple url-> \"a\"? \"b\";"; + String text = "xb"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.ERROR); + } + + + @Test + public void ruleSubExpression(){ + String grammar = "GRAMMAR url url -> shema; shema -> \"ab\";"; + String text = "ab"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + @Test + public void ruleSubExpressionError(){ + String grammar = "GRAMMAR url url -> shema; shema -> \"ab\";"; + String text = "ac"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.ERROR); + } + + @Test + public void ruleCError(){ + String grammar = "GRAMMAR url url -> shema; shema -> \"ab\";"; + String text = "ac"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.ERROR); + } + + + @Test + public void complicateTest_URL(){ + String grammar = "GRAMMAR url\n" + + "\n" + + "url -> scheme \"://\" host pathname search hash?;\n" + + "scheme -> \"http\" \"s\"?;\n" + + "host -> hostname port?;\n" + + "hostname -> segment (\".\" segment)*;\n" + + "segment -> [a-z0-9-]+;\n" + + "port -> \":\" [0-9]+;\n" + + "pathname -> \"/\" [^ ?]*;\n" + + "search -> (\"?\" [^ #]*)?;\n" + + "hash -> \"#\" [^ ]*;"; + + String text = "https://simplepeg.github.io/"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.OK); + } + + @Test + public void complicateTest_URL_Error(){ + String grammar = "GRAMMAR url\n" + + "\n" + + "url -> scheme \"://\" host pathname search hash?;\n" + + "scheme -> \"http\" \"s\"?;\n" + + "host -> hostname port?;\n" + + "hostname -> segment (\".\" segment)*;\n" + + "segment -> [a-z0-9-]+;\n" + + "port -> \":\" [0-9]+;\n" + + "pathname -> \"/\" [^ ?]*;\n" + + "search -> (\"?\" [^ #]*)?;\n" + + "hash -> \"#\" [^ ]*;"; + + String text = "https://////simplepeg.github.io/"; + + PegNode grammarTree = SpegParser.createAndExec(grammar); + RuleProcessor rp = new RuleProcessor(grammarTree); + CheckResult cr = rp.check(text); + + assertEquals(cr.getErrorText(), cr.getResultType(), ResultType.ERROR); + } + +} diff --git a/src/test/java/Tests.java b/src/test/java/Tests.java index a695a8c..4681d81 100644 --- a/src/test/java/Tests.java +++ b/src/test/java/Tests.java @@ -1,5 +1,116 @@ +import org.junit.Assert; +import org.junit.Test; +import ru.dude.simplepeg.SpegParser; +import ru.dude.simplepeg.entity.PegNode; +import ru.dude.simplepeg.entity.ResultType; + +import java.io.ByteArrayInputStream; + /** * Created by dude on 29.10.2017. */ -public class Tests { +public class Tests extends Assert{ + + private void assertProcess(String input,ResultType expected){ + PegNode result = SpegParser.createAndExec(input); + String message = "INPUT:\n"+input+"\nERROR:"+result.getError(); + assertEquals(message,expected,result.getResultType()); + } + + // @Test + public void all(){ + headerAndSimpleRule(); + noRuleError(); + noLastSemicolonError(); + + oneRuleStrings(); + oneRuleRegexp(); + twoRulesSimple(); + twoRulesRegexp(); + manyRulesRegexp(); + + complicateTest_URL(); + } + + @Test + public void headerAndSimpleRule(){ + String input = "GRAMMAR simple rule-> \"aaaa\";"; + assertProcess(input,ResultType.OK); + } + + @Test + public void orderedGrammar(){ + String input = "GRAMMAR simple rule-> \"a\"/\"b\";"; + assertProcess(input,ResultType.OK); + } + + @Test + public void groupGrammar(){ + String input = "GRAMMAR simple rule-> (\"a\"/\"b\")+;"; + assertProcess(input,ResultType.OK); + } + + @Test + public void noRuleError(){ + String input = "GRAMMAR simple;"; + assertProcess(input,ResultType.ERROR); + } + + @Test + public void noLastSemicolonError(){ + String input = "GRAMMAR simple rule-> \"aaaa\""; + assertProcess(input,ResultType.ERROR); + } + + @Test + public void oneRuleStrings(){ + String input = "GRAMMAR one rule-> \"aaaa\" \"bb\" \"cccc\" \"zz\";"; + assertProcess(input,ResultType.OK); + } + + @Test + public void oneRuleRegexp(){ + String input = "GRAMMAR one rule-> \"XX\" [a-zA-Z0-9-]+ [^ #]*;"; + assertProcess(input,ResultType.OK); + } + + + @Test + public void twoRulesSimple(){ + String input = "GRAMMAR duo rule_one-> \"XX\" rule_two; rule_two-> \"YY\";"; + assertProcess(input,ResultType.OK); + } + + @Test + public void twoRulesRegexp(){ + String input = "GRAMMAR duo rule_one-> \"XX\" rule_two [^ #]*; rule_two-> \"YY\" [a-zA-Z0-9-]+;"; + assertProcess(input,ResultType.OK); + } + + @Test + public void manyRulesRegexp(){ + String input = "GRAMMAR many\n" + + "rule-> r_one r_two r_three r_four; \n" + + "r_one-> \"XX\";\n" + + "r_two-> [^ #]*;\n" + + "r_three-> \"YY\";\n" + + "r_four-> \"ZZ\" [a-zA-Z0-9-]+;"; + assertProcess(input,ResultType.OK); + } + + @Test + public void complicateTest_URL(){ + String input = "GRAMMAR url\n" + + "\n" + + "url -> scheme \"://\" host pathname search hash?;\n" + + "scheme -> \"http\" \"s\"?;\n" + + "host -> hostname port?;\n" + + "hostname -> segment (\".\" segment)*;\n" + + "segment -> [a-z0-9-]+;\n" + + "port -> \":\" [0-9]+;\n" + + "pathname -> \"/\" [^ ?]*;\n" + + "search -> (\"?\" [^ #]*)?;\n" + + "hash -> \"#\" [^ ]*;"; + assertProcess(input,ResultType.OK); + } }