diff --git a/build.sbt b/build.sbt index 2c6b443..8fc8d00 100644 --- a/build.sbt +++ b/build.sbt @@ -24,10 +24,14 @@ libraryDependencies += "org.projectlombok" % "lombok" % "1.18.2" libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" libraryDependencies += "com.fasterxml.jackson.core" % "jackson-databind" % "2.9.6" % "test" -libraryDependencies += "org.antlr" % "antlr4" % "4.7.1" % "compile" +libraryDependencies += "org.antlr" % "antlr4" % "4.7.1" % "test" libraryDependencies += "org.antlr" % "antlr4-runtime" % "4.7.1" % "test" libraryDependencies += "org.tukaani" % "xz" % "1.8" % "test" artifactName := { (sv: ScalaVersion, module: ModuleID, artifact: Artifact) => artifact.name + "-" + module.revision + "." + artifact.extension } + +enablePlugins(Antlr4Plugin) +antlr4Version in Antlr4 := "4.7.1" +antlr4PackageName in Antlr4 := Some("org.oggio88.worth.antlr") \ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt index f67cbbe..3906da3 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -7,3 +7,4 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.9.3") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "4.1.0") addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.3") addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.6") +addSbtPlugin("com.simplytyped" % "sbt-antlr4" % "0.8.1") \ No newline at end of file diff --git a/src/test/resources/JSON.g4 b/src/main/antlr4/JSON.g4 similarity index 89% rename from src/test/resources/JSON.g4 rename to src/main/antlr4/JSON.g4 index b15a1f7..8388d72 100644 --- a/src/test/resources/JSON.g4 +++ b/src/main/antlr4/JSON.g4 @@ -27,17 +27,27 @@ value | NUMBER | obj | array - | 'true' - | 'false' - | 'null' + | TRUE + | FALSE + | NULL ; +TRUE + : 'true' + ; + +FALSE + : 'false' + ; + +NULL + : 'null' + ; STRING : '"' (ESC | SAFECODEPOINT)* '"' ; - fragment ESC : '\\' (["\\/bfnrt] | UNICODE) ; diff --git a/src/main/java/org/oggio88/worth/buffer/LookAheadInputStream.java b/src/main/java/org/oggio88/worth/buffer/LookAheadInputStream.java new file mode 100644 index 0000000..25827e5 --- /dev/null +++ b/src/main/java/org/oggio88/worth/buffer/LookAheadInputStream.java @@ -0,0 +1,25 @@ +package org.oggio88.worth.buffer; + +import java.io.IOException; +import java.io.InputStream; + +public class LookAheadInputStream extends InputStream { + + private final InputStream stream; + private int currentByte; + + LookAheadInputStream(InputStream stream) { + this.stream = stream; + } + + @Override + public int read() throws IOException { + int result = currentByte; + currentByte = stream.read(); + return result; + } + + public int getCurrentByte(){ + return currentByte; + } +} diff --git a/src/main/java/org/oggio88/worth/serialization/json/JSONDumper.java b/src/main/java/org/oggio88/worth/serialization/json/JSONDumper.java index fd31650..9cf83be 100644 --- a/src/main/java/org/oggio88/worth/serialization/json/JSONDumper.java +++ b/src/main/java/org/oggio88/worth/serialization/json/JSONDumper.java @@ -23,6 +23,37 @@ public class JSONDumper extends ValueDumper { protected Writer writer; + private String escapeString(String value){ + StringBuilder sb = new StringBuilder(); + for (char c : value.toCharArray()) { + switch (c) { + case '"': + sb.append("\\\""); + break; + case '\r': + sb.append("\\r"); + break; + case '\n': + sb.append("\\n"); + break; + case '\t': + sb.append("\\t"); + break; + case '\\': + sb.append("\\\\"); + break; + default: { + if (c < 128) + sb.append(c); + else { + sb.append("\\u").append(String.format("%04X", (int) c)); + } + } + } + } + return sb.toString(); + } + @Override public void dump(Value value, OutputStream stream) { dump(value, new OutputStreamWriter(stream)); @@ -121,40 +152,13 @@ public class JSONDumper extends ValueDumper { @Override @SneakyThrows protected void objectKey(String key) { - this.writer.write("\"" + key + "\""); + this.writer.write("\"" + escapeString(key) + "\""); } @Override @SneakyThrows protected void stringValue(String value) { - StringBuilder sb = new StringBuilder(); - for (char c : value.toCharArray()) { - switch (c) { - case '"': - sb.append("\\\""); - break; - case '\r': - sb.append("\\r"); - break; - case '\n': - sb.append("\\n"); - break; - case '\t': - sb.append("\\t"); - break; - case '\\': - sb.append("\\\\"); - break; - default: { - if (c < 128) - sb.append(c); - else { - sb.append("\\u").append(String.format("%04X", (int) c)); - } - } - } - } - this.writer.write("\"" + sb.toString() + "\""); + this.writer.write("\"" + escapeString(value) + "\""); } @Override diff --git a/src/main/java/org/oggio88/worth/utils/WorthUtils.java b/src/main/java/org/oggio88/worth/utils/WorthUtils.java index d63ab77..555a12a 100644 --- a/src/main/java/org/oggio88/worth/utils/WorthUtils.java +++ b/src/main/java/org/oggio88/worth/utils/WorthUtils.java @@ -1,7 +1,10 @@ package org.oggio88.worth.utils; import lombok.SneakyThrows; +import org.oggio88.worth.buffer.CircularBuffer; +import org.oggio88.worth.exception.ParseException; +import java.io.InputStream; import java.util.concurrent.Callable; public class WorthUtils { diff --git a/src/test/java/org/oggio88/worth/antlr/JSONListenerImpl.java b/src/test/java/org/oggio88/worth/antlr/JSONListenerImpl.java new file mode 100644 index 0000000..792f14f --- /dev/null +++ b/src/test/java/org/oggio88/worth/antlr/JSONListenerImpl.java @@ -0,0 +1,102 @@ +package org.oggio88.worth.antlr; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.oggio88.worth.serialization.ValueParser; +import org.oggio88.worth.xface.Value; + +public class JSONListenerImpl extends ValueParser implements JSONListener { + + public Value result = null; + + private String unquote(String quoted) { + return quoted.substring(1, quoted.length() - 1); + } + + @Override + public void enterJson(JSONParser.JsonContext ctx) { + } + + @Override + public void exitJson(JSONParser.JsonContext ctx) { + result = stack.get(0).value; + stack.clear(); + } + + @Override + public void enterObj(JSONParser.ObjContext ctx) { + beginObject(); + } + + @Override + public void exitObj(JSONParser.ObjContext ctx) { + endObject(); + } + + @Override + public void enterPair(JSONParser.PairContext ctx) { + objectKey(unquote(ctx.STRING().getText())); + } + + @Override + public void exitPair(JSONParser.PairContext ctx) { + + } + + @Override + public void enterArray(JSONParser.ArrayContext ctx) { + beginArray(); + } + + @Override + public void exitArray(JSONParser.ArrayContext ctx) { + endArray(); + } + + @Override + public void enterValue(JSONParser.ValueContext ctx) { + if (ctx.obj() != null) { + } else if (ctx.array() != null) { + } else if (ctx.STRING() != null) { + stringValue(unquote(ctx.STRING().getText())); + } else if (ctx.TRUE() != null) { + booleanValue(true); + } else if (ctx.FALSE() != null) { + booleanValue(false); + } else if (ctx.NULL() != null) { + nullValue(); + } else if (ctx.NUMBER() != null) { + String text = ctx.NUMBER().getText(); + if (text.indexOf('.') < 0) + integerValue(Long.valueOf(text)); + else + floatValue(Float.valueOf(text)); + } + } + + @Override + public void exitValue(JSONParser.ValueContext ctx) { + + } + + @Override + public void visitTerminal(TerminalNode node) { + + } + + @Override + public void visitErrorNode(ErrorNode node) { + + } + + @Override + public void enterEveryRule(ParserRuleContext ctx) { + + } + + @Override + public void exitEveryRule(ParserRuleContext ctx) { + + } +} diff --git a/src/test/java/org/oggio88/worth/antlr/ParseTest.java b/src/test/java/org/oggio88/worth/antlr/ParseTest.java new file mode 100644 index 0000000..9a6aed1 --- /dev/null +++ b/src/test/java/org/oggio88/worth/antlr/ParseTest.java @@ -0,0 +1,28 @@ +package org.oggio88.worth.antlr; + +import lombok.SneakyThrows; +import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTreeWalker; +import org.junit.Test; +import org.oggio88.worth.serialization.json.JSONDumper; +import org.oggio88.worth.xface.Value; + +public class ParseTest { + + @Test + @SneakyThrows + public void test(){ + + ANTLRInputStream inputStream = new ANTLRInputStream(getClass().getResourceAsStream("/test.json")); + JSONLexer lexer = new JSONLexer(inputStream); + CommonTokenStream commonTokenStream = new CommonTokenStream(lexer); + JSONParser parser = new JSONParser(commonTokenStream); + JSONListenerImpl listener = new JSONListenerImpl(); + ParseTreeWalker walker = new ParseTreeWalker(); + walker.walk(listener, parser.json()); + Value result = listener.result; + new JSONDumper().dump(result, System.out); +// TestRig.main(new String[] {"org.oggio88.worth.antlr.JSON", "json", "-ps", "tree.ps", "src/test/resources/test.json"}); + } +} diff --git a/src/test/java/org/oggio88/worth/serialization/json/PerformanceTest.java b/src/test/java/org/oggio88/worth/serialization/json/PerformanceTest.java index 089201c..dd17a80 100644 --- a/src/test/java/org/oggio88/worth/serialization/json/PerformanceTest.java +++ b/src/test/java/org/oggio88/worth/serialization/json/PerformanceTest.java @@ -3,8 +3,13 @@ package org.oggio88.worth.serialization.json; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; +import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.junit.Ignore; import org.junit.Test; +import org.oggio88.worth.antlr.JSONLexer; +import org.oggio88.worth.antlr.JSONListenerImpl; import org.oggio88.worth.xface.Value; import org.tukaani.xz.XZInputStream; @@ -65,7 +70,7 @@ public class PerformanceTest { @Test @SneakyThrows public void loopTest() { - double jacksonTime, worthTime; + double jacksonTime, worthTime, antlrTime; final int loops = 100; Chronometer chr = new Chronometer(); { @@ -85,6 +90,21 @@ public class PerformanceTest { worthTime = chr.stop(Chronometer.TimeUnit.MILLISECOND); System.out.printf("Worth time: %8s msec\n", String.format("%.3f", worthTime)); } + { + chr.reset(); + for (int i = 0; i < loops; i++) { + ANTLRInputStream inputStream = new ANTLRInputStream( + getClass().getResourceAsStream("/wordpress.json")); + JSONLexer lexer = new JSONLexer(inputStream); + CommonTokenStream commonTokenStream = new CommonTokenStream(lexer); + org.oggio88.worth.antlr.JSONParser parser = new org.oggio88.worth.antlr.JSONParser(commonTokenStream); + JSONListenerImpl listener = new JSONListenerImpl(); + ParseTreeWalker walker = new ParseTreeWalker(); + walker.walk(listener, parser.json()); + } + antlrTime = chr.stop(Chronometer.TimeUnit.MILLISECOND); + System.out.printf("Antlr time: %8s msec\n", String.format("%.3f", antlrTime)); + } } @Test @@ -92,7 +112,7 @@ public class PerformanceTest { @SneakyThrows public void hugeJSONTest() { byte[] testData = extractTestData(); - double jacksonTime, worthTime; + double jacksonTime, worthTime, antlrTime; Chronometer chr = new Chronometer(); { chr.reset(); @@ -107,5 +127,17 @@ public class PerformanceTest { worthTime = chr.stop(Chronometer.TimeUnit.SECOND); System.out.printf("Worth time: %8s sec\n", String.format("%.3f", worthTime)); } + { + chr.reset(); + ANTLRInputStream inputStream = new ANTLRInputStream(new ByteArrayInputStream(testData)); + JSONLexer lexer = new JSONLexer(inputStream); + CommonTokenStream commonTokenStream = new CommonTokenStream(lexer); + org.oggio88.worth.antlr.JSONParser parser = new org.oggio88.worth.antlr.JSONParser(commonTokenStream); + JSONListenerImpl listener = new JSONListenerImpl(); + ParseTreeWalker walker = new ParseTreeWalker(); + walker.walk(listener, parser.json()); + antlrTime = chr.stop(Chronometer.TimeUnit.SECOND); + System.out.printf("Antlr time: %8s sec\n", String.format("%.3f", antlrTime)); + } } }