Codebase list jawn / 7c16422
Update upstream source from tag 'upstream/0.11.1' Update to upstream version '0.11.1' with Debian dir 52ffc1a9b7169d6dba87837e99fb839638451e7a Emmanuel Bourg 7 years ago
42 changed file(s) with 1045 addition(s) and 387 deletion(s). Raw diff Collapse all Expand all
1616 .#*
1717 .lib
1818 .ensime_cache
19 .idea
00 language: scala
1 sudo: false
12 jdk:
23 - oraclejdk8
34 script:
1717
1818 ### Overview
1919
20 Jawn consists of three parts:
21
22 1. A fast, generic JSON parser
23 2. A small, somewhat anemic AST
20 Jawn consists of four parts:
21
22 1. A fast, generic JSON parser (`jawn-parser`)
23 2. A small, somewhat anemic AST (`jawn-ast`)
2424 3. Support packages which parse to third-party ASTs
25 4. A few helpful utilities (`jawn-util`)
2526
2627 Currently Jawn is competitive with the fastest Java JSON libraries
2728 (GSON and Jackson) and in the author's benchmarks it often wins. It
2930 2014).
3031
3132 Given the plethora of really nice JSON libraries for Scala, the
32 expectation is that you are here for (1) and (3) not (2).
33 expectation is that you're probably here for `jawn-parser` or a
34 support package.
3335
3436 ### Quick Start
3537
4244 resolvers += Resolver.sonatypeRepo("releases")
4345
4446 // use this if you just want jawn's parser, and will implement your own facade
45 libraryDependencies += "org.spire-math" %% "jawn-parser" % "0.10.3"
47 libraryDependencies += "org.spire-math" %% "jawn-parser" % "0.11.0"
4648
4749 // use this if you want jawn's parser and also jawn's ast
48 libraryDependencies += "org.spire-math" %% "jawn-ast" % "0.10.3"
50 libraryDependencies += "org.spire-math" %% "jawn-ast" % "0.11.0"
4951 ```
5052
5153 If you want to use Jawn's parser with another project's AST, see the
5355 you would say:
5456
5557 ```scala
56 libraryDependencies += "org.spire-math" %% "jawn-spray" % "0.10.3"
58 libraryDependencies += "org.spire-math" %% "jawn-spray" % "0.11.0"
5759 ```
5860
5961 There are a few reasons you might want to do this:
132134
133135 Jawn currently supports six external ASTs directly:
134136
135 | AST | 2.10 | 2.11 | 2.12 |
136 |-----------|-------|-------|-------|
137 | Argonaut | 6.1 | 6.1 | |
138 | Json4s | 3.5.0 | 3.5.0 | 3.5.0 |
139 | Play-json | 2.4.8 | 2.5.9 | |
140 | Rojoma | 2.4.3 | 2.4.3 | 2.4.3 |
141 | Rojoma-v3 | 3.7.0 | 3.7.0 | 3.7.0 |
142 | Spray | 1.3.2 | 1.3.2 | 1.3.2 |
137 | AST | 2.10 | 2.11 | 2.12 |
138 |-----------|--------|--------|-------|
139 | Argonaut | 6.2 | 6.2 | 6.2 |
140 | Json4s | 3.5.2 | 3.5.2 | 3.5.2 |
141 | Play-json | 2.4.11 | 2.5.15 | 2.6.0 |
142 | Rojoma | 2.4.3 | 2.4.3 | 2.4.3 |
143 | Rojoma-v3 | 3.7.2 | 3.7.2 | 3.7.2 |
144 | Spray | 1.3.3 | 1.3.3 | 1.3.3 |
143145
144146 Each of these subprojects provides a `Parser` object (an instance of
145147 `SupportParser[J]`) that is parameterized on the given project's
164166 ```scala
165167 resolvers += Resolver.sonatypeRepo("releases")
166168
167 libraryDependencies += "org.spire-math" %% jawn-"XYZ" % "0.10.3"
169 libraryDependencies += "org.spire-math" %% jawn-"XYZ" % "0.11.0"
168170 ```
169171
170172 This is an example of how you might use the parser into your code:
188190 ```scala
189191 resolvers += Resolver.sonatypeRepo("releases")
190192
191 libraryDependencies += "org.spire-math" %% "jawn-parser" % "0.10.3"
193 libraryDependencies += "org.spire-math" %% "jawn-parser" % "0.11.0"
192194 ```
193195
194196 To support your AST of choice, you'll want to define a `Facade[J]`
421423 All code is available to you under the MIT license, available at
422424 http://opensource.org/licenses/mit-license.php.
423425
424 Copyright Erik Osheim, 2012-2016.
426 Copyright Erik Osheim, 2012-2017.
1414 def parseFromString(s: String): Try[JValue] =
1515 Try(new StringParser[JValue](s).parse)
1616
17 def parseFromCharSequence(cs: CharSequence): Try[JValue] =
18 Try(new CharSequenceParser[JValue](cs).parse)
19
1720 def parseFromPath(path: String): Try[JValue] =
1821 parseFromFile(new File(path))
1922
203203
204204 case class DeferLong(s: String) extends JNum {
205205
206 lazy val n: Long = java.lang.Long.parseLong(s)
206 lazy val n: Long = util.parseLongUnsafe(s)
207207
208208 final override def getInt: Option[Int] = Some(n.toInt)
209209 final override def getLong: Option[Long] = Some(n)
234234 lazy val n: Double = java.lang.Double.parseDouble(s)
235235
236236 final override def getInt: Option[Int] = Some(n.toInt)
237 final override def getLong: Option[Long] = Some(n.toLong)
237 final override def getLong: Option[Long] = Some(util.parseLongUnsafe(s))
238238 final override def getDouble: Option[Double] = Some(n)
239239 final override def getBigInt: Option[BigInt] = Some(BigDecimal(s).toBigInt)
240240 final override def getBigDecimal: Option[BigDecimal] = Some(BigDecimal(s))
241241
242242 final override def asInt: Int = n.toInt
243 final override def asLong: Long = n.toLong
243 final override def asLong: Long = util.parseLongUnsafe(s)
244244 final override def asDouble: Double = n
245245 final override def asBigInt: BigInt = BigDecimal(s).toBigInt
246246 final override def asBigDecimal: BigDecimal = BigDecimal(s)
77 final val jnull = JNull
88 final val jfalse = JFalse
99 final val jtrue = JTrue
10 final def jnum(s: String) = DeferNum(s)
11 final def jint(s: String) = DeferLong(s)
12 final def jstring(s: String) = JString(s)
10
11 final def jnum(s: CharSequence, decIndex: Int, expIndex: Int): JValue =
12 if (decIndex == -1 && expIndex == -1) {
13 DeferLong(s.toString)
14 } else {
15 DeferNum(s.toString)
16 }
17
18 final def jstring(s: CharSequence): JValue =
19 JString(s.toString)
1320
1421 final def singleContext(): FContext[JValue] =
1522 new FContext[JValue] {
1623 var value: JValue = _
17 def add(s: String) { value = JString(s) }
24 def add(s: CharSequence) { value = JString(s.toString) }
1825 def add(v: JValue) { value = v }
1926 def finish: JValue = value
2027 def isObj: Boolean = false
2330 final def arrayContext(): FContext[JValue] =
2431 new FContext[JValue] {
2532 val vs = mutable.ArrayBuffer.empty[JValue]
26 def add(s: String) { vs.append(JString(s)) }
33 def add(s: CharSequence) { vs.append(JString(s.toString)) }
2734 def add(v: JValue) { vs.append(v) }
2835 def finish: JValue = JArray(vs.toArray)
2936 def isObj: Boolean = false
3340 new FContext[JValue] {
3441 var key: String = null
3542 val vs = mutable.Map.empty[String, JValue]
36 def add(s: String): Unit =
37 if (key == null) { key = s } else { vs(key) = JString(s); key = null }
43 def add(s: CharSequence): Unit =
44 if (key == null) { key = s.toString } else { vs(key.toString) = JString(s.toString); key = null }
3845 def add(v: JValue): Unit =
3946 { vs(key) = v; key = null }
4047 def finish = JObject(vs)
99
1010 import scala.collection.mutable
1111 import scala.util.{Try, Success}
12
13 import jawn.parser.TestUtil
1214
1315 import ArbitraryUtil._
1416
3234 value1 shouldBe value2
3335 value1.## shouldBe value2.##
3436
35 parser.Util.withTemp(json1) { t =>
37 TestUtil.withTemp(json1) { t =>
3638 JParser.parseFromFile(t).get shouldBe value2
3739 }
3840 }
4749 jstr2 shouldBe jstr1
4850 json2 shouldBe json1
4951 json2.## shouldBe json1.##
52 }
53 }
54
55 property("string/charSequence parsing") {
56 forAll { value: JValue =>
57 val s = CanonicalRenderer.render(value)
58 val j1 = JParser.parseFromString(s)
59 val cs = java.nio.CharBuffer.wrap(s.toCharArray)
60 val j2 = JParser.parseFromCharSequence(cs)
61 j1 shouldBe j2
62 j1.## shouldBe j2.##
5063 }
5164 }
5265
7588 } ++ checkRight(p.finish())
7689
7790 import AsyncParser.{UnwrapArray, ValueStream, SingleValue}
91
92 property("async multi") {
93 val data = "[1,2,3][4,5,6]"
94 val p = AsyncParser[JValue](ValueStream)
95 val res0 = p.absorb(data)
96 val res1 = p.finish
97 //println((res0, res1))
98 true
99 }
78100
79101 property("async parsing") {
80102 forAll { (v: JValue) =>
133155
134156 val s0 = ("x" * (40 * M))
135157 val e0 = q + s0 + q
136 parser.Util.withTemp(e0) { t =>
158 TestUtil.withTemp(e0) { t =>
137159 JParser.parseFromFile(t).filter(_ == JString(s0)).isSuccess shouldBe true
138160 }
139161
140162 val s1 = "\\" * (20 * M)
141163 val e1 = q + s1 + s1 + q
142 parser.Util.withTemp(e1) { t =>
164 TestUtil.withTemp(e1) { t =>
143165 JParser.parseFromFile(t).filter(_ == JString(s1)).isSuccess shouldBe true
144166 }
145167 }
22 javaOptions in run += "-Xmx6G"
33
44 libraryDependencies ++= Seq(
5 "io.argonaut" %% "argonaut" % "6.1-M6",
6 "org.json4s" %% "json4s-native" % "3.2.11",
7 "org.json4s" %% "json4s-jackson" % "3.2.10",
8 "com.typesafe.play" %% "play-json" % "2.3.0",
5 "io.argonaut" %% "argonaut" % "6.2",
6 "org.json4s" %% "json4s-native" % "3.5.2",
7 "org.json4s" %% "json4s-jackson" % "3.5.2",
8 "com.typesafe.play" %% "play-json" % "2.5.15",
99 "com.rojoma" %% "rojoma-json" % "2.4.3",
10 "com.rojoma" %% "rojoma-json-v3" % "3.3.0",
11 "io.spray" %% "spray-json" % "1.3.2",
12 "org.parboiled" %% "parboiled" % "2.1.0",
13 "com.fasterxml.jackson.core" % "jackson-annotations" % "2.5.3",
14 "com.fasterxml.jackson.core" % "jackson-core" % "2.5.3",
15 "com.fasterxml.jackson.core" % "jackson-databind" % "2.5.3",
16 "com.google.code.gson" % "gson" % "2.2.4"
10 "com.rojoma" %% "rojoma-json-v3" % "3.7.2",
11 "io.spray" %% "spray-json" % "1.3.3",
12 "org.parboiled" %% "parboiled" % "2.1.4",
13 "com.fasterxml.jackson.core" % "jackson-annotations" % "2.8.4",
14 "com.fasterxml.jackson.core" % "jackson-core" % "2.8.4",
15 "com.fasterxml.jackson.core" % "jackson-databind" % "2.8.4",
16 "com.google.code.gson" % "gson" % "2.8.1"
1717 )
1818
1919 // enable forking in run
99 @BenchmarkMode(Array(Mode.AverageTime))
1010 @OutputTimeUnit(TimeUnit.MILLISECONDS)
1111 abstract class JmhBenchmarks(name: String) {
12
1312 val path: String = s"src/main/resources/$name"
1413
1514 def load(path: String): String = {
2625 def buffered(path: String): BufferedReader =
2726 new BufferedReader(new FileReader(new File(path)))
2827
28 @Benchmark
29 def jawnCheckSyntax() =
30 jawn.Syntax.checkString(load(path))
31
32 @Benchmark
33 def jawnParse() =
34 jawn.ast.JParser.parseFromFile(new File(path)).get
35
36 @Benchmark
37 def jawnStringParse() =
38 jawn.ast.JParser.parseFromString(load(path)).get
39 }
40
41 trait OtherBenchmarks { self: JmhBenchmarks =>
2942 @Benchmark
3043 def json4sJacksonParse() = {
3144 import org.json4s._
6477 def gsonParse() =
6578 new com.google.gson.JsonParser().parse(buffered(path))
6679
67 @Benchmark
68 def jawnCheckSyntax() =
69 jawn.Syntax.checkString(load(path))
70
71 @Benchmark
72 def jawnParse() =
73 jawn.ast.JParser.parseFromFile(new File(path)).get
74
75 @Benchmark
76 def jawnStringParse() =
77 jawn.ast.JParser.parseFromString(load(path)).get
78
7980 // don't bother benchmarking jawn + external asts by default
8081
8182 // @Benchmark
104105 // }
105106 }
106107
107 class Qux2Bench extends JmhBenchmarks("qux2.json")
108 class Bla25Bench extends JmhBenchmarks("bla25.json")
109 class CountriesBench extends JmhBenchmarks("countries.geo.json")
110 class Ugh10kBench extends JmhBenchmarks("ugh10k.json")
108 class Qux2Bench extends JmhBenchmarks("qux2.json") with OtherBenchmarks
109 class Bla25Bench extends JmhBenchmarks("bla25.json") with OtherBenchmarks
110 class CountriesBench extends JmhBenchmarks("countries.geo.json") with OtherBenchmarks
111 class Ugh10kBench extends JmhBenchmarks("ugh10k.json") with OtherBenchmarks
112
113 class JawnOnlyQux2Bench extends JmhBenchmarks("qux2.json")
114 class JawnOnlyBla25Bench extends JmhBenchmarks("bla25.json")
115 class JawnOnlyCountriesBench extends JmhBenchmarks("countries.geo.json")
116 class JawnOnlyUgh10kBench extends JmhBenchmarks("ugh10k.json")
111117
112118 // // from https://github.com/zemirco/sf-city-lots-json
113119 // class CityLotsBench extends JmhBenchmarks("citylots.json")
0 package jawn
1 package benchmark
2
3 import java.io.{BufferedReader, File, FileInputStream, FileReader}
4 import java.util.concurrent.TimeUnit
5 import org.openjdk.jmh.annotations._
6 import scala.collection.mutable
7
8 case class Slice(s: String, begin: Int, limit: Int) extends CharSequence {
9 val length: Int = limit - begin
10 def charAt(i: Int): Char = s.charAt(begin + i)
11 def subSequence(start: Int, end: Int): Slice =
12 Slice(s, begin + start, Math.min(end + begin, limit))
13 override def toString: String =
14 s.substring(begin, limit)
15 }
16
17 @State(Scope.Benchmark)
18 @OutputTimeUnit(TimeUnit.MILLISECONDS)
19 class ParseLongBench {
20
21 val longs: Array[Long] = Array(
22 -1346837161442476189L, -4666345991836441070L, 4868830844043235709L,
23 2992690405064579158L, -2017521011608078634L, -3039682866169364757L,
24 8997687047891586260L, 5932727796276454607L, 4062739618560250554L,
25 8668950167358198490L, -8565613821858118870L, 8049785848575684314L,
26 -580831266940599830L, -3593199367295538945L, 8374322595267797482L,
27 3088261552516619129L, -6879203747403593851L, -1842900848925949857L,
28 4484592876047641351L, 5182973278356955602L, -6840392853855436945L,
29 -4176340556015032222L, -536379174926548619L, 6343722878919863216L,
30 1557757008211571405L, -334093799456298669L, 619602023052756397L,
31 6904874397154297343L, -4332034907782234995L, -8767842695446545180L,
32 -6127250063205613011L, 6902212562850963795L, 4778607575334665692L,
33 7674074815344809639L, -3834944692798167050L, 7406081418831471202L,
34 -9126886315356724563L, 8093878176633322645L, 2471547025788214028L,
35 -5018828829942988155L, -6676531171364391367L, 8189793226936659851L,
36 7150026713387306746L, -6065566098373722052L, 3281133763697608570L,
37 957103694526079944L, -3009447279791131829L, -1995600795755716697L,
38 2361055030313262510L, -4312828282749171343L, 8836216125516165138L,
39 5548785979447786253L, 8567551485822958810L, 5931896003625723150L,
40 3472058092439106147L, 4363240277904515929L, -2999484068697753019L,
41 -8285358702782547958L, -2407429647076308777L, 4411565001760018584L,
42 792384115860070648L, 3328145302561962294L, -2377559446421434356L,
43 -7837698939558960516L, -565806101451282875L, -4792610084643070650L,
44 2713520205731589923L, -6521104721472605988L, 5037187811345411645L,
45 3866939564433764178L, -3851229228204678079L, -8171137274242372558L,
46 -14713951794749384L, 2061783257002637655L, -7375571393873059570L,
47 7402007407273053723L, -5104318069025846447L, -8956415532448219980L,
48 4904595193891993401L, 5396360181536889307L, -8043917553767343384L,
49 -3666269817017255250L, -6535587792359353103L, -4553034734642385706L,
50 -7544140164897268962L, 2468330113904053484L, 5790319365381968237L,
51 -2734383156062609640L, -4831208471935595172L, 4502079643250626043L,
52 4778622151522470246L, 7233054223498326990L, 5833883346008509644L,
53 -8013495378054295093L, 2944606201054530456L, -8608231828651976245L,
54 -6957117814546267426L, -4744827311133020624L, 2640030216500286789L,
55 8343959867315747844L)
56
57 val strs: Array[CharSequence] =
58 longs.map(_.toString)
59
60 val seqs: Array[CharSequence] =
61 longs.map { n =>
62 val prefix = "x" * (n & 63).toInt
63 val suffix = "y" * ((n * 7) & 63).toInt
64 val i = prefix.length
65 val s = n.toString
66 Slice(prefix + s + suffix, i, s.length + i)
67 }
68
69 val str: CharSequence = "23948271429443"
70
71 val seq: CharSequence = Slice("weigjewigjwi23948271429443jgewigjweiwjegiwgjiewjgeiwjg", 12, 26)
72
73 def sumJava(css: Array[CharSequence]): Long = {
74 var sum: Long = 0
75 var i = 0
76 while (i < css.length) {
77 sum += java.lang.Long.parseLong(css(i).toString)
78 i += 1
79 }
80 sum
81 }
82
83 def sumStd(css: Array[CharSequence]): Long = {
84 var sum: Long = 0
85 var i = 0
86 while (i < css.length) {
87 sum += css(i).toString.toLong
88 i += 1
89 }
90 sum
91 }
92
93 def sumSafe(css: Array[CharSequence]): Long = {
94 var sum: Long = 0
95 var i = 0
96 while (i < css.length) {
97 sum += Util.parseLong(css(i))
98 i += 1
99 }
100 sum
101 }
102
103 def sumUnsafe(css: Array[CharSequence]): Long = {
104 var sum: Long = 0
105 var i = 0
106 while (i < css.length) {
107 sum += Util.parseLongUnsafe(css(i))
108 i += 1
109 }
110 sum
111 }
112
113 @Benchmark def stringArrayJava(): Long = sumJava(strs)
114 @Benchmark def seqArrayJava(): Long = sumJava(seqs)
115 @Benchmark def stringValueJava(): Long = java.lang.Long.parseLong(str.toString)
116 @Benchmark def seqValueJava(): Long = java.lang.Long.parseLong(seq.toString)
117
118 @Benchmark def stringArrayStd(): Long = sumStd(strs)
119 @Benchmark def seqArrayStd(): Long = sumStd(seqs)
120 @Benchmark def stringValueStd(): Long = str.toString.toLong
121 @Benchmark def seqValueStd(): Long = seq.toString.toLong
122
123 @Benchmark def stringArraySafe(): Long = sumSafe(strs)
124 @Benchmark def seqArraySafe(): Long = sumSafe(seqs)
125 @Benchmark def stringValueSafe(): Long = Util.parseLong(str)
126 @Benchmark def seqValueSafe(): Long = Util.parseLong(seq)
127
128 @Benchmark def stringArrayUnsafe(): Long = sumUnsafe(strs)
129 @Benchmark def seqArrayUnsafe(): Long = sumUnsafe(seqs)
130 @Benchmark def stringValueUnsafe(): Long = Util.parseLongUnsafe(str)
131 @Benchmark def seqValueUnsafe(): Long = Util.parseLongUnsafe(seq)
132 }
00 import ReleaseTransformations._
1
2 lazy val previousJawnVersion = "0.10.4"
3
4 lazy val stableCrossVersions =
5 Seq("2.10.6", "2.11.11", "2.12.2")
6
7 // we'll support 2.13.0-M1 soon but not yet
8 lazy val allCrossVersions =
9 stableCrossVersions
10
11 lazy val benchmarkVersion =
12 "2.12.2"
113
214 lazy val jawnSettings = Seq(
315 organization := "org.spire-math",
4 scalaVersion := "2.11.8",
5 crossScalaVersions := Seq("2.10.6", "2.11.8", "2.12.0"),
16 scalaVersion := "2.12.2",
17 crossScalaVersions := allCrossVersions,
18
19 mimaPreviousArtifacts := Set(organization.value %% moduleName.value % previousJawnVersion),
620
721 resolvers += Resolver.sonatypeRepo("releases"),
8 libraryDependencies ++= Seq(
9 "org.scalatest" %% "scalatest" % "3.0.0" % "test",
10 "org.scalacheck" %% "scalacheck" % "1.13.4" % "test"
11 ),
12 scalacOptions ++= Seq(
13 //"-Yinline-warnings",
14 "-deprecation",
15 "-optimize",
16 "-unchecked"
17 ),
22
23 libraryDependencies ++=
24 "org.scalatest" %% "scalatest" % "3.0.3" % Test ::
25 "org.scalacheck" %% "scalacheck" % "1.13.5" % Test ::
26 Nil,
27
28 scalacOptions ++=
29 "-deprecation" ::
30 "-optimize" ::
31 "-unchecked" ::
32 Nil,
1833
1934 licenses += ("MIT", url("http://opensource.org/licenses/MIT")),
2035 homepage := Some(url("http://github.com/non/jawn")),
2540 publishArtifact in Test := false,
2641 pomIncludeRepository := Function.const(false),
2742
28 publishTo <<= (version).apply { v =>
43 publishTo := {
2944 val nexus = "https://oss.sonatype.org/"
30 if (v.trim.endsWith("SNAPSHOT"))
45 if (isSnapshot.value) {
3146 Some("Snapshots" at nexus + "content/repositories/snapshots")
32 else
47 } else {
3348 Some("Releases" at nexus + "service/local/staging/deploy/maven2")
49 }
3450 },
3551
36 pomExtra := (
37 <scm>
38 <url>git@github.com:non/jawn.git</url>
39 <connection>scm:git:git@github.com:non/jawn.git</connection>
40 </scm>
41 <developers>
42 <developer>
43 <id>d_m</id>
44 <name>Erik Osheim</name>
45 <url>http://github.com/non/</url>
46 </developer>
47 </developers>
52 scmInfo := Some(ScmInfo(
53 browseUrl = url("https://github.com/non/jawn"),
54 connection = "scm:git:git@github.com:non/jawn.git"
55 )),
56
57 developers += Developer(
58 name = "Erik Osheim",
59 email = "erik@plastic-idolatry.com",
60 id = "d_m",
61 url = url("http://github.com/non/")
4862 ),
4963
5064 releaseProcess := Seq[ReleaseStep](
6478 lazy val noPublish = Seq(
6579 publish := {},
6680 publishLocal := {},
67 publishArtifact := false)
81 publishArtifact := false,
82 mimaPreviousArtifacts := Set())
6883
6984 lazy val root = project.in(file("."))
7085 .aggregate(all.map(Project.projectToRef): _*)
8095 .settings(jawnSettings: _*)
8196 .disablePlugins(JmhPlugin)
8297
98 lazy val util = project.in(file("util"))
99 .dependsOn(parser % "compile->compile;test->test")
100 .settings(name := "util")
101 .settings(moduleName := "jawn-util")
102 .settings(jawnSettings: _*)
103 .disablePlugins(JmhPlugin)
104
83105 lazy val ast = project.in(file("ast"))
84106 .dependsOn(parser % "compile->compile;test->test")
107 .dependsOn(util % "compile->compile;test->test")
85108 .settings(name := "ast")
86109 .settings(moduleName := "jawn-ast")
87110 .settings(jawnSettings: _*)
96119 .disablePlugins(JmhPlugin)
97120
98121 lazy val supportArgonaut = support("argonaut")
99 .settings(crossScalaVersions := Seq("2.10.6", "2.11.8"))
100 .settings(libraryDependencies += "io.argonaut" %% "argonaut" % "6.1")
122 .settings(crossScalaVersions := stableCrossVersions)
123 .settings(libraryDependencies += "io.argonaut" %% "argonaut" % "6.2")
101124
102125 lazy val supportJson4s = support("json4s")
103 .settings(libraryDependencies += "org.json4s" %% "json4s-ast" % "3.5.0")
126 .dependsOn(util)
127 .settings(crossScalaVersions := stableCrossVersions)
128 .settings(libraryDependencies += "org.json4s" %% "json4s-ast" % "3.5.2")
104129
105130 lazy val supportPlay = support("play")
106 .settings(crossScalaVersions := Seq("2.10.6", "2.11.8"))
131 .settings(crossScalaVersions := stableCrossVersions)
107132 .settings(libraryDependencies += (scalaBinaryVersion.value match {
108 case "2.10" => "com.typesafe.play" %% "play-json" % "2.4.8"
109 case _ => "com.typesafe.play" %% "play-json" % "2.5.9"
133 case "2.10" => "com.typesafe.play" %% "play-json" % "2.4.11"
134 case "2.11" => "com.typesafe.play" %% "play-json" % "2.5.15"
135 case _ => "com.typesafe.play" %% "play-json" % "2.6.0"
110136 }))
111137
112138 lazy val supportRojoma = support("rojoma")
113 .settings(crossScalaVersions := Seq("2.10.6", "2.11.8", "2.12.0"))
139 .settings(crossScalaVersions := stableCrossVersions)
114140 .settings(libraryDependencies += "com.rojoma" %% "rojoma-json" % "2.4.3")
115141
116142 lazy val supportRojomaV3 = support("rojoma-v3")
117 .settings(libraryDependencies += "com.rojoma" %% "rojoma-json-v3" % "3.7.0")
143 .settings(crossScalaVersions := stableCrossVersions)
144 .settings(libraryDependencies += "com.rojoma" %% "rojoma-json-v3" % "3.7.2")
118145
119146 lazy val supportSpray = support("spray")
147 .settings(crossScalaVersions := stableCrossVersions)
120148 .settings(resolvers += "spray" at "http://repo.spray.io/")
121 .settings(libraryDependencies += "io.spray" %% "spray-json" % "1.3.2")
149 .settings(libraryDependencies += "io.spray" %% "spray-json" % "1.3.3")
122150
123151 lazy val benchmark = project.in(file("benchmark"))
124152 .dependsOn(all.map(Project.classpathDependency[Project]): _*)
125153 .settings(name := "jawn-benchmark")
126154 .settings(jawnSettings: _*)
127 .settings(scalaVersion := "2.11.8")
155 .settings(scalaVersion := benchmarkVersion)
156 .settings(crossScalaVersions := Seq(benchmarkVersion))
128157 .settings(noPublish: _*)
129 .settings(crossScalaVersions := Seq("2.11.8"))
130158 .enablePlugins(JmhPlugin)
131159
132160 lazy val all =
133 Seq(parser, ast, supportArgonaut, supportJson4s, supportPlay, supportRojoma, supportRojomaV3, supportSpray)
161 Seq(parser, util, ast, supportArgonaut, supportJson4s, supportPlay, supportRojoma, supportRojomaV3, supportSpray)
242242 }
243243 }
244244
245 // every 1M we shift our array back by 1M.
245 // every 1M we shift our array back to the beginning.
246246 protected[this] final def reset(i: Int): Int = {
247247 if (offset >= 1048576) {
248 len -= 1048576
249 offset -= 1048576
250 pos -= 1048576
251 System.arraycopy(data, 1048576, data, 0, len)
252 i - 1048576
248 val diff = offset
249 curr -= diff
250 len -= diff
251 offset = 0
252 pos -= diff
253 System.arraycopy(data, diff, data, 0, len)
254 i - diff
253255 } else {
254256 i
255257 }
288290 * boundaries. Also, the resulting String is not guaranteed to have length
289291 * (k - i).
290292 */
291 protected[this] final def at(i: Int, k: Int): String = {
293 protected[this] final def at(i: Int, k: Int): CharSequence = {
292294 if (k > len) throw new AsyncException
293295 val size = k - i
294296 val arr = new Array[Byte](size)
1313 * update its own mutable position fields.
1414 */
1515 final class ByteBufferParser[J](src: ByteBuffer) extends SyncParser[J] with ByteBasedParser[J] {
16 final val start = src.position
17 final val limit = src.limit - start
16 private[this] final val start = src.position
17 private[this] final val limit = src.limit - start
1818
19 var line = 0
20 protected[this] final def newline(i: Int) { line += 1 }
19 private[this] var lineState = 0
20 protected[this] def line(): Int = lineState
21
22 protected[this] final def newline(i: Int) { lineState += 1 }
2123 protected[this] final def column(i: Int) = i
2224
2325 protected[this] final def close() { src.position(src.limit) }
2628 protected[this] final def byte(i: Int): Byte = src.get(i + start)
2729 protected[this] final def at(i: Int): Char = src.get(i + start).toChar
2830
29 protected[this] final def at(i: Int, k: Int): String = {
31 protected[this] final def at(i: Int, k: Int): CharSequence = {
3032 val len = k - i
3133 val arr = new Array[Byte](len)
3234 src.position(i + start)
139139 * on unicode boundaries. Also, the resulting String is not
140140 * guaranteed to have length (k - i).
141141 */
142 protected[this] final def at(i: Int, k: Int): String = {
142 protected[this] final def at(i: Int, k: Int): CharSequence = {
143143 val len = k - i
144144 if (k > Allsize) {
145145 grow()
1010 *
1111 * It is simpler than ByteBasedParser.
1212 */
13 private[jawn] trait CharBasedParser[J] extends Parser[J] {
13 trait CharBasedParser[J] extends Parser[J] {
14
15 private[this] final val charBuilder = new CharBuilder()
1416
1517 /**
1618 * See if the string has any escape sequences. If not, return the
3333 }
3434 }
3535
36 def extend(s: String): Unit = {
36 def extend(s: CharSequence): Unit = {
3737 val tlen = len + s.length
3838 resizeIfNecessary(tlen)
3939 var i = 0
0 package jawn
1
2 /**
3 * Lazy character sequence parsing.
4 *
5 * This is similar to StringParser, but acts on character sequences.
6 */
7 private[jawn] final class CharSequenceParser[J](cs: CharSequence) extends SyncParser[J] with CharBasedParser[J] {
8 var line = 0
9 final def column(i: Int) = i
10 final def newline(i: Int) { line += 1 }
11 final def reset(i: Int): Int = i
12 final def checkpoint(state: Int, i: Int, stack: List[FContext[J]]): Unit = ()
13 final def at(i: Int): Char = cs.charAt(i)
14 final def at(i: Int, j: Int): CharSequence = cs.subSequence(i, j)
15 final def atEof(i: Int) = i == cs.length
16 final def close() = ()
17 }
22 /**
33 * Facade is a type class that describes how Jawn should construct
44 * JSON AST elements of type J.
5 *
5 *
66 * Facade[J] also uses FContext[J] instances, so implementors will
77 * usually want to define both.
88 */
1414 def jnull(): J
1515 def jfalse(): J
1616 def jtrue(): J
17 def jnum(s: String): J
18 def jint(s: String): J
19 def jstring(s: String): J
17 def jnum(s: CharSequence, decIndex: Int, expIndex: Int): J
18 def jstring(s: CharSequence): J
2019 }
2120
2221 /**
2726 * cases where the entire JSON document consists of "333.33".
2827 */
2928 trait FContext[J] {
30 def add(s: String): Unit
29 def add(s: CharSequence): Unit
3130 def add(v: J): Unit
3231 def finish: J
3332 def isObj: Boolean
77
88 def singleContext() = new FContext[J] {
99 var value: J = _
10 def add(s: String) { value = jstring(s) }
10 def add(s: CharSequence) { value = jstring(s) }
1111 def add(v: J) { value = v }
1212 def finish: J = value
1313 def isObj: Boolean = false
1515
1616 def arrayContext() = new FContext[J] {
1717 val vs = mutable.ArrayBuffer.empty[J]
18 def add(s: String) { vs.append(jstring(s)) }
18 def add(s: CharSequence) { vs.append(jstring(s)) }
1919 def add(v: J) { vs.append(v) }
2020 def finish: J = jarray(vs)
2121 def isObj: Boolean = false
2424 def objectContext() = new FContext[J] {
2525 var key: String = null
2626 val vs = mutable.Map.empty[String, J]
27 def add(s: String): Unit =
28 if (key == null) { key = s } else { vs(key) = jstring(s); key = null }
27 def add(s: CharSequence): Unit =
28 if (key == null) { key = s.toString } else { vs(key) = jstring(s); key = null }
2929 def add(v: J): Unit =
3030 { vs(key) = v; key = null }
3131 def finish = jobject(vs)
1212 object NullFacade extends Facade[Unit] {
1313
1414 case class NullContext(isObj: Boolean) extends FContext[Unit] {
15 def add(s: String): Unit = ()
15 def add(s: CharSequence): Unit = ()
1616 def add(v: Unit): Unit = ()
1717 def finish: Unit = ()
1818 }
2424 def jnull(): Unit = ()
2525 def jfalse(): Unit = ()
2626 def jtrue(): Unit = ()
27 def jnum(s: String): Unit = ()
28 def jint(s: String): Unit = ()
29 def jstring(s: String): Unit = ()
27 def jnum(s: CharSequence, decIndex: Int, expIndex: Int): Unit = ()
28 def jstring(s: CharSequence): Unit = ()
3029 }
3434
3535 protected[this] final val utf8 = Charset.forName("UTF-8")
3636
37 protected[this] final val charBuilder = new CharBuilder()
38
3937 /**
4038 * Read the byte/char at 'i' as a Char.
4139 *
4745 /**
4846 * Read the bytes/chars from 'i' until 'j' as a String.
4947 */
50 protected[this] def at(i: Int, j: Int): String
48 protected[this] def at(i: Int, j: Int): CharSequence
5149
5250 /**
5351 * Return true iff 'i' is at or beyond the end of the input (EOF).
5452 */
5553 protected[this] def atEof(i: Int): Boolean
56
57 /**
58 * Return true iff the byte/char at 'i' is equal to 'c'.
59 */
60 protected[this] final def is(i: Int, c: Char): Boolean = at(i) == c
61
62 /**
63 * Return true iff the bytes/chars from 'i' until 'j' are equal to 'str'.
64 */
65 protected[this] final def is(i: Int, j: Int, str: String): Boolean = at(i, j) == str
6654
6755 /**
6856 * The reset() method is used to signal that we're working from the
140128 protected[this] final def parseNum(i: Int, ctxt: FContext[J])(implicit facade: Facade[J]): Int = {
141129 var j = i
142130 var c = at(j)
143 var dec = false
131 var decIndex = -1
132 var expIndex = -1
144133
145134 if (c == '-') {
146135 j += 1
156145 }
157146
158147 if (c == '.') {
159 dec = true
148 decIndex = j - i
160149 j += 1
161150 c = at(j)
162151 if ('0' <= c && c <= '9') {
167156 }
168157
169158 if (c == 'e' || c == 'E') {
170 dec = true
159 expIndex = j - i
171160 j += 1
172161 c = at(j)
173162 if (c == '+' || c == '-') {
181170 }
182171 }
183172
184 if (dec)
185 ctxt.add(facade.jnum(at(i, j)))
186 else
187 ctxt.add(facade.jint(at(i, j)))
173 ctxt.add(facade.jnum(at(i, j), decIndex, expIndex))
188174 j
189175 }
190176
205191 protected[this] final def parseNumSlow(i: Int, ctxt: FContext[J])(implicit facade: Facade[J]): Int = {
206192 var j = i
207193 var c = at(j)
208 var dec = false
194 var decIndex = -1
195 var expIndex = -1
209196
210197 if (c == '-') {
211198 // any valid input will require at least one digit after -
215202 if (c == '0') {
216203 j += 1
217204 if (atEof(j)) {
218 ctxt.add(facade.jint(at(i, j)))
205 ctxt.add(facade.jnum(at(i, j), decIndex, expIndex))
219206 return j
220207 }
221208 c = at(j)
223210 while ('0' <= c && c <= '9') {
224211 j += 1
225212 if (atEof(j)) {
226 ctxt.add(facade.jint(at(i, j)))
213 ctxt.add(facade.jnum(at(i, j), decIndex, expIndex))
227214 return j
228215 }
229216 c = at(j)
234221
235222 if (c == '.') {
236223 // any valid input will require at least one digit after .
237 dec = true
224 decIndex = j - i
238225 j += 1
239226 c = at(j)
240227 if ('0' <= c && c <= '9') {
241228 while ('0' <= c && c <= '9') {
242229 j += 1
243230 if (atEof(j)) {
244 ctxt.add(facade.jnum(at(i, j)))
231 ctxt.add(facade.jnum(at(i, j), decIndex, expIndex))
245232 return j
246233 }
247234 c = at(j)
253240
254241 if (c == 'e' || c == 'E') {
255242 // any valid input will require at least one digit after e, e+, etc
256 dec = true
243 expIndex = j - i
257244 j += 1
258245 c = at(j)
259246 if (c == '+' || c == '-') {
264251 while ('0' <= c && c <= '9') {
265252 j += 1
266253 if (atEof(j)) {
267 ctxt.add(facade.jnum(at(i, j)))
254 ctxt.add(facade.jnum(at(i, j), decIndex, expIndex))
268255 return j
269256 }
270257 c = at(j)
273260 die(i, "expected digit")
274261 }
275262 }
276 if (dec)
277 ctxt.add(facade.jnum(at(i, j)))
278 else
279 ctxt.add(facade.jint(at(i, j)))
263
264 ctxt.add(facade.jnum(at(i, j), decIndex, expIndex))
280265 j
281266 }
282267
286271 * NOTE: This is only capable of generating characters from the basic plane.
287272 * This is why it can only return Char instead of Int.
288273 */
289 protected[this] final def descape(s: String): Char = {
274 protected[this] final def descape(s: CharSequence): Char = {
290275 val hc = HexChars
291276 var i = 0
292277 var x = 0
304289
305290 /**
306291 * Parse the JSON constant "true".
307 */
308 protected[this] final def parseTrue(i: Int)(implicit facade: Facade[J]) =
309 if (is(i, i + 4, "true")) facade.jtrue else die(i, "expected true")
292 *
293 * Note that this method assumes that the first character has already been checked.
294 */
295 protected[this] final def parseTrue(i: Int)(implicit facade: Facade[J]): J =
296 if (at(i + 1) == 'r' && at(i + 2) == 'u' && at(i + 3) == 'e') {
297 facade.jtrue
298 } else {
299 die(i, "expected true")
300 }
310301
311302 /**
312303 * Parse the JSON constant "false".
313 */
314 protected[this] final def parseFalse(i: Int)(implicit facade: Facade[J]) =
315 if (is(i, i + 5, "false")) facade.jfalse else die(i, "expected false")
304 *
305 * Note that this method assumes that the first character has already been checked.
306 */
307 protected[this] final def parseFalse(i: Int)(implicit facade: Facade[J]): J =
308 if (at(i + 1) == 'a' && at(i + 2) == 'l' && at(i + 3) == 's' && at(i + 4) == 'e') {
309 facade.jfalse
310 } else {
311 die(i, "expected false")
312 }
316313
317314 /**
318315 * Parse the JSON constant "null".
319 */
320 protected[this] final def parseNull(i: Int)(implicit facade: Facade[J]) =
321 if (is(i, i + 4, "null")) facade.jnull else die(i, "expected null")
316 *
317 * Note that this method assumes that the first character has already been checked.
318 */
319 protected[this] final def parseNull(i: Int)(implicit facade: Facade[J]): J =
320 if (at(i + 1) == 'u' && at(i + 2) == 'l' && at(i + 3) == 'l') {
321 facade.jnull
322 } else {
323 die(i, "expected null")
324 }
322325
323326 /**
324327 * Parse and return the next JSON value and the position beyond it.
379382 protected[this] final def rparse(state: Int, j: Int, stack: List[FContext[J]])(implicit facade: Facade[J]): (J, Int) = {
380383 val i = reset(j)
381384 checkpoint(state, i, stack)
382 (state: @switch) match {
385
386 val c = at(i)
387
388 if (c == '\n') {
389 newline(i)
390 rparse(state, i + 1, stack)
391 } else if (c == ' ' || c == '\t' || c == '\r') {
392 rparse(state, i + 1, stack)
393 } else if (state == DATA) {
383394 // we are inside an object or array expecting to see data
384 case DATA =>
385 (at(i): @switch) match {
386 case '[' => rparse(ARRBEG, i + 1, facade.arrayContext() :: stack)
387 case '{' => rparse(OBJBEG, i + 1, facade.objectContext() :: stack)
388
389 case '-' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' =>
390 val ctxt = stack.head
391 val j = parseNum(i, ctxt)
392 rparse(if (ctxt.isObj) OBJEND else ARREND, j, stack)
393
394 case '"' =>
395 val ctxt = stack.head
396 val j = parseString(i, ctxt)
397 rparse(if (ctxt.isObj) OBJEND else ARREND, j, stack)
398
399 case 't' =>
400 val ctxt = stack.head
401 ctxt.add(parseTrue(i))
402 rparse(if (ctxt.isObj) OBJEND else ARREND, i + 4, stack)
403
404 case 'f' =>
405 val ctxt = stack.head
406 ctxt.add(parseFalse(i))
407 rparse(if (ctxt.isObj) OBJEND else ARREND, i + 5, stack)
408
409 case 'n' =>
410 val ctxt = stack.head
411 ctxt.add(parseNull(i))
412 rparse(if (ctxt.isObj) OBJEND else ARREND, i + 4, stack)
413
414 case ' ' => rparse(state, i + 1, stack)
415 case '\t' => rparse(state, i + 1, stack)
416 case '\r' => rparse(state, i + 1, stack)
417 case '\n' => newline(i); rparse(state, i + 1, stack)
418
419 case _ =>
420 die(i, "expected json value")
395 if (c == '[') {
396 rparse(ARRBEG, i + 1, facade.arrayContext() :: stack)
397 } else if (c == '{') {
398 rparse(OBJBEG, i + 1, facade.objectContext() :: stack)
399 } else {
400 val ctxt = stack.head
401
402 if ((c >= '0' && c <= '9') || c == '-') {
403 val j = parseNum(i, ctxt)
404 rparse(if (ctxt.isObj) OBJEND else ARREND, j, stack)
405 } else if (c == '"') {
406 val j = parseString(i, ctxt)
407 rparse(if (ctxt.isObj) OBJEND else ARREND, j, stack)
408 } else if (c == 't') {
409 ctxt.add(parseTrue(i))
410 rparse(if (ctxt.isObj) OBJEND else ARREND, i + 4, stack)
411 } else if (c == 'f') {
412 ctxt.add(parseFalse(i))
413 rparse(if (ctxt.isObj) OBJEND else ARREND, i + 5, stack)
414 } else if (c == 'n') {
415 ctxt.add(parseNull(i))
416 rparse(if (ctxt.isObj) OBJEND else ARREND, i + 4, stack)
417 } else {
418 die(i, "expected json value")
421419 }
422
423 // we are in an object expecting to see a key
424 case KEY =>
425 (at(i): @switch) match {
426 case '"' =>
427 val j = parseString(i, stack.head)
428 rparse(SEP, j, stack)
429
430 case ' ' => rparse(state, i + 1, stack)
431 case '\t' => rparse(state, i + 1, stack)
432 case '\r' => rparse(state, i + 1, stack)
433 case '\n' => newline(i); rparse(state, i + 1, stack)
434
435 case _ => die(i, "expected \"")
420 }
421 } else if (
422 (c == ']' && (state == ARREND || state == ARRBEG)) ||
423 (c == '}' && (state == OBJEND || state == OBJBEG))
424 ) {
425 // we are inside an array or object and have seen a key or a closing
426 // brace, respectively.
427 if (stack.isEmpty) {
428 error("invalid stack")
429 } else {
430 val ctxt1 = stack.head
431 val tail = stack.tail
432
433 if (tail.isEmpty) {
434 (ctxt1.finish, i + 1)
435 } else {
436 val ctxt2 = tail.head
437 ctxt2.add(ctxt1.finish)
438 rparse(if (ctxt2.isObj) OBJEND else ARREND, i + 1, tail)
436439 }
437
438 // we are starting an array, expecting to see data or a closing bracket
439 case ARRBEG =>
440 (at(i): @switch) match {
441 case ']' => stack match {
442 case ctxt1 :: Nil =>
443 (ctxt1.finish, i + 1)
444 case ctxt1 :: ctxt2 :: tail =>
445 ctxt2.add(ctxt1.finish)
446 rparse(if (ctxt2.isObj) OBJEND else ARREND, i + 1, ctxt2 :: tail)
447 case _ =>
448 error("invalid stack")
449 }
450
451 case ' ' => rparse(state, i + 1, stack)
452 case '\t' => rparse(state, i + 1, stack)
453 case '\r' => rparse(state, i + 1, stack)
454 case '\n' => newline(i); rparse(state, i + 1, stack)
455
456 case _ => rparse(DATA, i, stack)
457 }
458
459 // we are starting an object, expecting to see a key or a closing brace
460 case OBJBEG =>
461 (at(i): @switch) match {
462 case '}' => stack match {
463 case ctxt1 :: Nil =>
464 (ctxt1.finish, i + 1)
465 case ctxt1 :: ctxt2 :: tail =>
466 ctxt2.add(ctxt1.finish)
467 rparse(if (ctxt2.isObj) OBJEND else ARREND, i + 1, ctxt2 :: tail)
468 case _ =>
469 error("invalid stack")
470 }
471
472 case ' ' => rparse(state, i + 1, stack)
473 case '\t' => rparse(state, i + 1, stack)
474 case '\r' => rparse(state, i + 1, stack)
475 case '\n' => newline(i); rparse(state, i + 1, stack)
476
477 case _ => rparse(KEY, i, stack)
478 }
479
480 // we are in an object just after a key, expecting to see a colon
481 case SEP =>
482 (at(i): @switch) match {
483 case ':' => rparse(DATA, i + 1, stack)
484
485 case ' ' => rparse(state, i + 1, stack)
486 case '\t' => rparse(state, i + 1, stack)
487 case '\r' => rparse(state, i + 1, stack)
488 case '\n' => newline(i); rparse(state, i + 1, stack)
489
490 case _ => die(i, "expected :")
491 }
492
493 // we are at a possible stopping point for an array, expecting to see
494 // either a comma (before more data) or a closing bracket.
495 case ARREND =>
496 (at(i): @switch) match {
497 case ',' => rparse(DATA, i + 1, stack)
498
499 case ']' => stack match {
500 case ctxt1 :: Nil =>
501 (ctxt1.finish, i + 1)
502 case ctxt1 :: ctxt2 :: tail =>
503 ctxt2.add(ctxt1.finish)
504 rparse(if (ctxt2.isObj) OBJEND else ARREND, i + 1, ctxt2 :: tail)
505 case _ =>
506 error("invalid stack")
507 }
508
509 case ' ' => rparse(state, i + 1, stack)
510 case '\t' => rparse(state, i + 1, stack)
511 case '\r' => rparse(state, i + 1, stack)
512 case '\n' => newline(i); rparse(state, i + 1, stack)
513
514 case _ => die(i, "expected ] or ,")
515 }
516
517 // we are at a possible stopping point for an object, expecting to see
518 // either a comma (before more data) or a closing brace.
519 case OBJEND =>
520 (at(i): @switch) match {
521 case ',' => rparse(KEY, i + 1, stack)
522
523 case '}' => stack match {
524 case ctxt1 :: Nil =>
525 (ctxt1.finish, i + 1)
526 case ctxt1 :: ctxt2 :: tail =>
527 ctxt2.add(ctxt1.finish)
528 rparse(if (ctxt2.isObj) OBJEND else ARREND, i + 1, ctxt2 :: tail)
529 case _ =>
530 error("invalid stack")
531 }
532
533 case ' ' => rparse(state, i + 1, stack)
534 case '\t' => rparse(state, i + 1, stack)
535 case '\r' => rparse(state, i + 1, stack)
536 case '\n' => newline(i); rparse(state, i + 1, stack)
537
538 case _ => die(i, "expected } or ,")
539 }
440 }
441 } else if (state == KEY) {
442 // we are in an object expecting to see a key.
443 if (c == '"') {
444 val j = parseString(i, stack.head)
445 rparse(SEP, j, stack)
446 } else {
447 die(i, "expected \"")
448 }
449 } else if (state == SEP) {
450 // we are in an object just after a key, expecting to see a colon.
451 if (c == ':') {
452 rparse(DATA, i + 1, stack)
453 } else {
454 die(i, "expected :")
455 }
456 } else if (state == ARREND) {
457 // we are in an array, expecting to see a comma (before more data).
458 if (c == ',') {
459 rparse(DATA, i + 1, stack)
460 } else {
461 die(i, "expected ] or ,")
462 }
463 } else if (state == OBJEND) {
464 // we are in an object, expecting to see a comma (before more data).
465 if (c == ',') {
466 rparse(KEY, i + 1, stack)
467 } else {
468 die(i, "expected } or ,")
469 }
470 } else if (state == ARRBEG) {
471 // we are starting an array, expecting to see data or a closing bracket.
472 rparse(DATA, i, stack)
473 } else {
474 // we are starting an object, expecting to see a key or a closing brace.
475 rparse(KEY, i, stack)
540476 }
541477 }
542478 }
549485
550486 def parseFromString[J](s: String)(implicit facade: Facade[J]): Try[J] =
551487 Try(new StringParser[J](s).parse)
488
489 def parseFromCharSequence[J](cs: CharSequence)(implicit facade: Facade[J]): Try[J] =
490 Try(new CharSequenceParser[J](cs).parse)
552491
553492 def parseFromPath[J](path: String)(implicit facade: Facade[J]): Try[J] =
554493 Try(ChannelParser.fromFile[J](new File(path)).parse)
44 /**
55 * Facade is a type class that describes how Jawn should construct
66 * JSON AST elements of type J.
7 *
7 *
88 * Facade[J] also uses FContext[J] instances, so implementors will
99 * usually want to define both.
1010 */
1414
1515 def singleContext() = new FContext[J] {
1616 var value: J = _
17 def add(s: String) { value = jstring(s) }
17 def add(s: CharSequence) { value = jstring(s) }
1818 def add(v: J) { value = v }
1919 def finish: J = value
2020 def isObj: Boolean = false
2222
2323 def arrayContext() = new FContext[J] {
2424 val vs = mutable.ListBuffer.empty[J]
25 def add(s: String) { vs += jstring(s) }
25 def add(s: CharSequence) { vs += jstring(s) }
2626 def add(v: J) { vs += v }
2727 def finish: J = jarray(vs.toList)
2828 def isObj: Boolean = false
3131 def objectContext() = new FContext[J] {
3232 var key: String = null
3333 var vs = Map.empty[String, J]
34 def add(s: String): Unit =
35 if (key == null) { key = s } else { vs = vs.updated(key, jstring(s)); key = null }
34 def add(s: CharSequence): Unit =
35 if (key == null) { key = s.toString } else { vs = vs.updated(key, jstring(s)); key = null }
3636 def add(v: J): Unit =
3737 { vs = vs.updated(key, v); key = null }
3838 def finish = jobject(vs)
1616 final def column(i: Int) = i
1717 final def newline(i: Int) { line += 1 }
1818 final def reset(i: Int): Int = i
19 final def checkpoint(state: Int, i: Int, stack: List[FContext[J]]) {}
19 final def checkpoint(state: Int, i: Int, stack: List[FContext[J]]): Unit = ()
2020 final def at(i: Int): Char = s.charAt(i)
21 final def at(i: Int, j: Int): String = s.substring(i, j)
21 final def at(i: Int, j: Int): CharSequence = s.substring(i, j)
2222 final def atEof(i: Int) = i == s.length
2323 final def close() = ()
2424 }
1313 val big = q + ("x" * (40 * M)) + q
1414 val bigEscaped = q + ("\\\\" * (20 * M)) + q
1515
16 Util.withTemp(big) { t =>
16 TestUtil.withTemp(big) { t =>
1717 Parser.parseFromFile(t)(NullFacade).isSuccess shouldBe true
1818 }
1919
20 Util.withTemp(bigEscaped) { t =>
20 TestUtil.withTemp(bigEscaped) { t =>
2121 Parser.parseFromFile(t)(NullFacade).isSuccess shouldBe true
2222 }
2323 }
0 package jawn
1 package parser
2
3 import java.nio.ByteBuffer
4 import org.scalatest.{Matchers, PropSpec}
5 import org.scalatest.prop.PropertyChecks
6 import scala.util.Success
7
8 class JNumIndexCheck extends PropSpec with Matchers with PropertyChecks {
9 object JNumIndexCheckFacade extends Facade[Boolean] {
10 class JNumIndexCheckContext(val isObj: Boolean) extends FContext[Boolean] {
11 var failed = false
12 def add(s: CharSequence): Unit = ()
13 def add(v: Boolean): Unit = {
14 if (!v) failed = true
15 }
16 def finish: Boolean = !failed
17 }
18
19 val singleContext: FContext[Boolean] = new JNumIndexCheckContext(false)
20 val arrayContext: FContext[Boolean] = new JNumIndexCheckContext(false)
21 val objectContext: FContext[Boolean] = new JNumIndexCheckContext(true)
22
23 def jnull(): Boolean = true
24 def jfalse(): Boolean = true
25 def jtrue(): Boolean = true
26 def jnum(s: CharSequence, decIndex: Int, expIndex: Int): Boolean = {
27 val input = s.toString
28 val inputDecIndex = input.indexOf('.')
29 val inputExpIndex = if (input.indexOf('e') == -1) input.indexOf("E") else input.indexOf('e')
30
31 decIndex == inputDecIndex && expIndex == inputExpIndex
32 }
33 def jstring(s: CharSequence): Boolean = true
34 }
35
36 property("jnum provides the correct indices with parseFromString") {
37 forAll { (value: BigDecimal) =>
38 val json = s"""{ "num": ${value.toString} }"""
39 Parser.parseFromString(json)(JNumIndexCheckFacade) shouldBe Success(true)
40 }
41 }
42
43 property("jnum provides the correct indices with parseFromByteBuffer") {
44 forAll { (value: BigDecimal) =>
45 val json = s"""{ "num": ${value.toString} }"""
46 val bb = ByteBuffer.wrap(json.getBytes("UTF-8"))
47 Parser.parseFromByteBuffer(bb)(JNumIndexCheckFacade) shouldBe Success(true)
48 }
49 }
50
51 property("jnum provides the correct indices with parseFromFile") {
52 forAll { (value: BigDecimal) =>
53 val json = s"""{ "num": ${value.toString} }"""
54 TestUtil.withTemp(json) { t =>
55 Parser.parseFromFile(t)(JNumIndexCheckFacade) shouldBe Success(true)
56 }
57 }
58 }
59
60 property("jnum provides the correct indices at the top level with parseFromString") {
61 forAll { (value: BigDecimal) =>
62 Parser.parseFromString(value.toString)(JNumIndexCheckFacade) shouldBe Success(true)
63 }
64 }
65
66 property("jnum provides the correct indices at the top level with parseFromByteBuffer") {
67 forAll { (value: BigDecimal) =>
68 val bb = ByteBuffer.wrap(value.toString.getBytes("UTF-8"))
69 Parser.parseFromByteBuffer(bb)(JNumIndexCheckFacade) shouldBe Success(true)
70 }
71 }
72
73 property("jnum provides the correct indices at the top level with parseFromFile") {
74 forAll { (value: BigDecimal) =>
75 TestUtil.withTemp(value.toString) { t =>
76 Parser.parseFromFile(t)(JNumIndexCheckFacade) shouldBe Success(true)
77 }
78 }
79 }
80 }
6161 import java.nio.ByteBuffer
6262
6363 def isValidSyntax(s: String): Boolean = {
64 val cs = java.nio.CharBuffer.wrap(s.toCharArray)
65 val r0 = Parser.parseFromCharSequence(cs)(NullFacade).isSuccess
6466 val r1 = Parser.parseFromString(s)(NullFacade).isSuccess
6567 val bb = ByteBuffer.wrap(s.getBytes("UTF-8"))
6668 val r2 = Parser.parseFromByteBuffer(bb)(NullFacade).isSuccess
69 if (r0 == r1) r1 else sys.error(s"CharSequence/String parsing disagree($r0, $r1): $s")
6770 if (r1 == r2) r1 else sys.error(s"String/ByteBuffer parsing disagree($r1, $r2): $s")
6871
69 Util.withTemp(s) { t =>
72 TestUtil.withTemp(s) { t =>
7073 Parser.parseFromFile(t)(NullFacade).isSuccess
7174 }
7275
0 package jawn
1 package parser
2
3 import java.io._
4
5 object TestUtil {
6 def withTemp[A](s: String)(f: File => A): A = {
7 val t = File.createTempFile("jawn-syntax", ".json")
8 val pw = new PrintWriter(t)
9 pw.println(s)
10 pw.close()
11 try {
12 f(t)
13 } finally {
14 t.delete()
15 }
16 }
17 }
+0
-18
parser/src/test/scala/jawn/Util.scala less more
0 package jawn
1 package parser
2
3 import java.io._
4
5 object Util {
6 def withTemp[A](s: String)(f: File => A): A = {
7 val t = File.createTempFile("jawn-syntax", ".json")
8 val pw = new PrintWriter(t)
9 pw.println(s)
10 pw.close()
11 try {
12 f(t)
13 } finally {
14 t.delete()
15 }
16 }
17 }
0 sbt.version=0.13.8
0 sbt.version=0.13.15
0 addSbtPlugin("com.eed3si9n" % "sbt-doge" % "0.1.5")
1 addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0")
2 addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.0")
3 addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "0.5.0")
4
5 addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.1.15")
0 addSbtPlugin("com.eed3si9n" % "sbt-doge" % "0.1.5")
1 addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.25")
2 addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.14")
3 addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.1")
4 addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.5")
5 addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "1.1")
99 def jnull() = Json.jNull
1010 def jfalse() = Json.jFalse
1111 def jtrue() = Json.jTrue
12 def jnum(s: String) = Json.jNumber(JsonNumber.unsafeDecimal(s))
13 def jint(s: String) = Json.jNumber(JsonNumber.unsafeDecimal(s))
14 def jstring(s: String) = Json.jString(s)
12
13 def jnum(s: CharSequence, decIndex: Int, expIndex: Int) =
14 Json.jNumber(JsonNumber.unsafeDecimal(s.toString))
15 def jstring(s: CharSequence) = Json.jString(s.toString)
1516
1617 def singleContext() = new FContext[Json] {
1718 var value: Json = null
18 def add(s: String) { value = jstring(s) }
19 def add(s: CharSequence) { value = jstring(s) }
1920 def add(v: Json) { value = v }
2021 def finish: Json = value
2122 def isObj: Boolean = false
2324
2425 def arrayContext() = new FContext[Json] {
2526 val vs = mutable.ListBuffer.empty[Json]
26 def add(s: String) { vs += jstring(s) }
27 def add(s: CharSequence) { vs += jstring(s) }
2728 def add(v: Json) { vs += v }
2829 def finish: Json = Json.jArray(vs.toList)
2930 def isObj: Boolean = false
3233 def objectContext() = new FContext[Json] {
3334 var key: String = null
3435 var vs = JsonObject.empty
35 def add(s: String): Unit =
36 if (key == null) { key = s } else { vs = vs + (key, jstring(s)); key = null }
36 def add(s: CharSequence): Unit =
37 if (key == null) { key = s.toString } else { vs = vs + (key, jstring(s)); key = null }
3738 def add(v: Json): Unit =
3839 { vs = vs + (key, v); key = null }
3940 def finish = Json.jObject(vs)
33 import scala.collection.mutable
44 import org.json4s.JsonAST._
55
6 object Parser extends SupportParser[JValue] {
6 object Parser extends Parser(false, false)
7
8 class Parser(useBigDecimalForDouble: Boolean, useBigIntForLong: Boolean) extends SupportParser[JValue] {
79
810 implicit val facade: Facade[JValue] =
911 new Facade[JValue] {
1012 def jnull() = JNull
1113 def jfalse() = JBool(false)
1214 def jtrue() = JBool(true)
13 def jnum(s: String) = JDouble(java.lang.Double.parseDouble(s))
14 def jint(s: String) = JInt(java.lang.Integer.parseInt(s))
15 def jstring(s: String) = JString(s)
15
16 def jnum(s: CharSequence, decIndex: Int, expIndex: Int) =
17 if (decIndex == -1 && expIndex == -1) {
18 if (useBigIntForLong) JInt(BigInt(s.toString))
19 else JLong(util.parseLongUnsafe(s))
20 } else {
21 if (useBigDecimalForDouble) JDecimal(BigDecimal(s.toString))
22 else JDouble(s.toString.toDouble)
23 }
24
25 def jstring(s: CharSequence) = JString(s.toString)
1626
1727 def singleContext() =
1828 new FContext[JValue] {
1929 var value: JValue = null
20 def add(s: String) { value = jstring(s) }
30 def add(s: CharSequence) { value = jstring(s) }
2131 def add(v: JValue) { value = v }
2232 def finish: JValue = value
2333 def isObj: Boolean = false
2636 def arrayContext() =
2737 new FContext[JValue] {
2838 val vs = mutable.ListBuffer.empty[JValue]
29 def add(s: String) { vs += jstring(s) }
39 def add(s: CharSequence) { vs += jstring(s) }
3040 def add(v: JValue) { vs += v }
3141 def finish: JValue = JArray(vs.toList)
3242 def isObj: Boolean = false
3646 new FContext[JValue] {
3747 var key: String = null
3848 val vs = mutable.ListBuffer.empty[JField]
39 def add(s: String): Unit =
40 if (key == null) key = s
49 def add(s: CharSequence): Unit =
50 if (key == null) key = s.toString
4151 else { vs += JField(key, jstring(s)); key = null }
4252 def add(v: JValue): Unit =
4353 { vs += JField(key, v); key = null }
99 def jnull() = JsNull
1010 def jfalse() = JsBoolean(false)
1111 def jtrue() = JsBoolean(true)
12 def jnum(s: String) = JsNumber(BigDecimal(s))
13 def jint(s: String) = JsNumber(BigDecimal(s))
14 def jstring(s: String) = JsString(s)
12
13 def jnum(s: CharSequence, decIndex: Int, expIndex: Int) = JsNumber(BigDecimal(s.toString))
14 def jstring(s: CharSequence) = JsString(s.toString)
15
1516 def jarray(vs: List[JsValue]) = JsArray(vs)
1617 def jobject(vs: Map[String, JsValue]) = JsObject(vs)
1718 }
99 def jnull() = JNull
1010 def jfalse() = JBoolean.canonicalFalse
1111 def jtrue() = JBoolean.canonicalTrue
12 def jnum(s: String) = JNumber(BigDecimal(s))
13 def jint(s: String) = JNumber(BigDecimal(s))
14 def jstring(s: String) = JString(s)
12 def jnum(s: CharSequence, decIndex: Int, expIndex: Int) = JNumber(BigDecimal(s.toString))
13 def jstring(s: CharSequence) = JString(s.toString)
1514 def jarray(vs: mutable.ArrayBuffer[JValue]) = JArray(vs)
1615 def jobject(vs: mutable.Map[String, JValue]) = JObject(vs)
1716 }
99 def jnull() = JNull
1010 def jfalse() = JBoolean.canonicalFalse
1111 def jtrue() = JBoolean.canonicalTrue
12 def jnum(s: String) = JNumber.unsafeFromString(s)
13 def jint(s: String) = JNumber.unsafeFromString(s)
14 def jstring(s: String) = JString(s)
12 def jnum(s: CharSequence, decIndex: Int, expIndex: Int) = JNumber.unsafeFromString(s.toString)
13 def jstring(s: CharSequence) = JString(s.toString)
1514 def jarray(vs: mutable.ArrayBuffer[JValue]) = JArray(vs)
1615 def jobject(vs: mutable.Map[String, JValue]) = JObject(vs)
1716 }
88 def jnull() = JsNull
99 def jfalse() = JsFalse
1010 def jtrue() = JsTrue
11 def jnum(s: String) = JsNumber(s)
12 def jint(s: String) = JsNumber(s)
13 def jstring(s: String) = JsString(s)
11 def jnum(s: CharSequence, decIndex: Int, expIndex: Int) = JsNumber(s.toString)
12 def jstring(s: CharSequence) = JsString(s.toString)
1413 def jarray(vs: List[JsValue]) = JsArray(vs: _*)
1514 def jobject(vs: Map[String, JsValue]) = JsObject(vs)
1615 }
0 package jawn.util
1
2 class InvalidLong(s: String) extends NumberFormatException(s"For input string '$s'")
3
4 object InvalidLong {
5 def apply(s: String): InvalidLong = new InvalidLong(s)
6 }
0 package jawn.util
1
2 /**
3 * Character sequence representing a lazily-calculated substring.
4 *
5 * This class has three constructors:
6 *
7 * - Slice(s) wraps a string, ensuring that future operations
8 * (e.g. subSequence) will construct slices instead of strings.
9 *
10 * - Slice(s, start, limit) is the default, and ensures that:
11 *
12 * 1. start >= 0
13 * 2. limit >= start
14 * 3. limit <= s.length
15 *
16 * - Slice.unsafe(s, start, limit) is for situations where the above
17 * bounds-checking has already occurred. Only use this if you are
18 * absolutely sure your arguments satisfy the above invariants.
19 *
20 * Slice's subSequence returns another slice. This means that when
21 * wrapping a very large string, garbage collection on the underlying
22 * string will not occur until all slices are freed.
23 *
24 * Slice's universal equality is only defined with regard to other
25 * slices. This means comparing a Slice with other CharSequence values
26 * (including String) will always return false.
27 *
28 * Slices are serializable. However! They use the default Java
29 * serialization layout, which is not that efficient, and could be a
30 * disaster in cases where a large shared string might be serialized
31 * many times in different slices.
32 */
33 @SerialVersionUID(1L)
34 final class Slice private[jawn] (s: String, start: Int, limit: Int) extends CharSequence with Serializable {
35
36 final val length: Int =
37 limit - start
38
39 def charAt(i: Int): Char =
40 if (i < 0 || length <= i) throw new StringIndexOutOfBoundsException(s"index out of range: $i")
41 else s.charAt(start + i)
42
43 def subSequence(i: Int, j: Int): Slice =
44 Slice(s, start + i, start + j)
45
46 override def toString: String =
47 s.substring(start, limit)
48
49 override def equals(that: Any): Boolean =
50 that match {
51 case t: AnyRef if this eq t =>
52 true
53 case slice: Slice =>
54 if (length != slice.length) return false
55 var i: Int = 0
56 while (i < length) {
57 if (charAt(i) != slice.charAt(i)) return false
58 i += 1
59 }
60 true
61 case _ =>
62 false
63 }
64
65 override def hashCode: Int = {
66 var hash: Int = 0x90decade
67 var i: Int = start
68 while (i < limit) {
69 hash = s.charAt(i) + (hash * 103696301) // prime
70 i += 1
71 }
72 hash
73 }
74 }
75
76 object Slice {
77
78 val Empty: Slice = Slice("", 0, 0)
79
80 def empty: Slice = Empty
81
82 def apply(s: String): Slice =
83 new Slice(s, 0, s.length)
84
85 def apply(s: String, start: Int, limit: Int): Slice =
86 if (start < 0 || limit < start || s.length < limit) {
87 throw new IndexOutOfBoundsException(s"invalid slice: start=$start, limit=$limit, length=${s.length}")
88 } else {
89 new Slice(s, start, limit)
90 }
91
92 def unsafe(s: String, start: Int, limit: Int): Slice =
93 new Slice(s, start, limit)
94 }
0 package jawn
1
2 package object util {
3
4 /**
5 * Parse the given character sequence as a single Long value (64-bit
6 * signed integer) in decimal (base-10).
7 *
8 * Other than "0", leading zeros are not allowed, nor are leading
9 * plusses. At most one leading minus is allowed. The value "-0" is
10 * allowed, and is interpreted as 0.
11 *
12 * Stated more precisely, accepted values:
13 *
14 * - conform to the pattern: -?(0|([1-9][0-9]*))
15 * - are within [-9223372036854775808, 9223372036854775807]
16 *
17 * This method will throw an `InvalidLong` exception on invalid
18 * input.
19 */
20 def parseLong(cs: CharSequence): Long = {
21
22 // we store the inverse of the positive sum, to ensure we don't
23 // incorrectly overflow on Long.MinValue. for positive numbers
24 // this inverse sum will be inverted before being returned.
25 var inverseSum: Long = 0L
26 var inverseSign: Long = -1L
27 var i: Int = 0
28
29 if (cs.charAt(0) == '-') {
30 inverseSign = 1L
31 i = 1
32 }
33
34 val len = cs.length
35 val size = len - i
36 if (i >= len) throw InvalidLong(cs.toString)
37 if (size > 19) throw InvalidLong(cs.toString)
38 if (cs.charAt(i) == '0' && size > 1) throw InvalidLong(cs.toString)
39
40 while (i < len) {
41 val digit = cs.charAt(i).toInt - 48
42 if (digit < 0 || 9 < digit) throw InvalidLong(cs.toString)
43 inverseSum = inverseSum * 10L - digit
44 i += 1
45 }
46
47 // detect and throw on overflow
48 if (size == 19 && (inverseSum >= 0 || (inverseSum == Long.MinValue && inverseSign < 0))) {
49 throw InvalidLong(cs.toString)
50 }
51
52 inverseSum * inverseSign
53 }
54
55 /**
56 * Parse the given character sequence as a single Long value (64-bit
57 * signed integer) in decimal (base-10).
58 *
59 * For valid inputs, this method produces the same values as
60 * `parseLong`. However, by avoiding input validation it is up to
61 * 50% faster.
62 *
63 * For inputs which `parseLong` throws an error on,
64 * `parseLongUnsafe` may (or may not) throw an error, or return a
65 * bogus value. This method makes no guarantees about how it handles
66 * invalid input.
67 *
68 * This method should only be used on sequences which have already
69 * been parsed (e.g. by a Jawn parser). When in doubt, use
70 * `parseLong(cs)`, which is still significantly faster than
71 * `java.lang.Long.parseLong(cs.toString)`.
72 */
73 def parseLongUnsafe(cs: CharSequence): Long = {
74
75 // we store the inverse of the positive sum, to ensure we don't
76 // incorrectly overflow on Long.MinValue. for positive numbers
77 // this inverse sum will be inverted before being returned.
78 var inverseSum: Long = 0L
79 var inverseSign: Long = -1L
80 var i: Int = 0
81
82 if (cs.charAt(0) == '-') {
83 inverseSign = 1L
84 i = 1
85 }
86
87 val len = cs.length
88 while (i < len) {
89 inverseSum = inverseSum * 10L - (cs.charAt(i).toInt - 48)
90 i += 1
91 }
92
93 inverseSum * inverseSign
94 }
95 }
0 package jawn
1 package util
2
3 import org.scalatest._
4 import prop._
5 import org.scalacheck._
6
7 import scala.util._
8
9 class ParseLongCheck extends PropSpec with Matchers with PropertyChecks {
10
11 case class UniformLong(value: Long)
12
13 object UniformLong {
14 implicit val arbitraryUniformLong: Arbitrary[UniformLong] =
15 Arbitrary(Gen.choose(Long.MinValue, Long.MaxValue).map(UniformLong(_)))
16 }
17
18 property("both parsers accept on valid input") {
19 forAll { (n0: UniformLong, prefix: String, suffix: String) =>
20 val n = n0.value
21 val payload = n.toString
22 val s = prefix + payload + suffix
23 val i = prefix.length
24 val cs = s.subSequence(i, payload.length + i)
25 cs.toString shouldBe payload
26 parseLong(cs) shouldBe n
27 parseLongUnsafe(cs) shouldBe n
28 }
29
30 forAll { (s: String) =>
31 Try(parseLong(s)) match {
32 case Success(n) => parseLongUnsafe(s) shouldBe n
33 case Failure(_) => succeed
34 }
35 }
36 }
37
38 property("safe parser fails on invalid input") {
39 forAll { (n: Long, m: Long, suffix: String) =>
40 val s1 = n.toString + suffix
41 Try(parseLong(s1)) match {
42 case Success(n) => n shouldBe s1.toLong
43 case Failure(_) => Try(s1.toLong).isFailure
44 }
45
46 val s2 = n.toString + (m & 0x7fffffffffffffffL).toString
47 Try(parseLong(s2)) match {
48 case Success(n) => n shouldBe s2.toLong
49 case Failure(_) => Try(s2.toLong).isFailure
50 }
51 }
52
53 Try(parseLong("9223372036854775807")) shouldBe Try(Long.MaxValue)
54 Try(parseLong("-9223372036854775808")) shouldBe Try(Long.MinValue)
55 Try(parseLong("-0")) shouldBe Try(0L)
56
57 assert(Try(parseLong("")).isFailure)
58 assert(Try(parseLong("+0")).isFailure)
59 assert(Try(parseLong("00")).isFailure)
60 assert(Try(parseLong("01")).isFailure)
61 assert(Try(parseLong("+1")).isFailure)
62 assert(Try(parseLong("-")).isFailure)
63 assert(Try(parseLong("--1")).isFailure)
64 assert(Try(parseLong("9223372036854775808")).isFailure)
65 assert(Try(parseLong("-9223372036854775809")).isFailure)
66 }
67
68 // NOTE: parseLongUnsafe is not guaranteed to crash, or do anything
69 // predictable, on invalid input, so we don't test this direction.
70 // Its "unsafe" suffix is there for a reason.
71 }
0 package jawn
1 package util
2
3 import org.scalatest._
4 import prop._
5 import org.scalacheck._
6
7 import Arbitrary.arbitrary
8
9 import scala.util._
10
11 class SliceCheck extends PropSpec with Matchers with PropertyChecks {
12
13 val genSlice: Gen[Slice] = {
14 val g = arbitrary[String]
15 def c(start: Int, end: Int): Gen[Int] =
16 if (end <= start) Gen.const(start)
17 else Gen.choose(start, end)
18 Gen.oneOf(
19 g.map(Slice(_)),
20 for { s <- g; n = s.length; i <- c(0, n) } yield Slice(s, i, n),
21 for { s <- g; n = s.length; j <- c(0, n) } yield Slice(s, 0, j),
22 for { s <- g; n = s.length; i <- c(0, n); j <- c(i, n) } yield Slice(s, i, j))
23 }
24
25 implicit val arbitrarySlice: Arbitrary[Slice] =
26 Arbitrary(genSlice)
27
28 def tryEqual[A](got0: => A, expected0: => A): Unit = {
29 val got = Try(got0)
30 val expected = Try(expected0)
31 got match {
32 case Success(_) => got shouldBe expected
33 case Failure(_) => assert(expected.isFailure)
34 }
35 }
36
37 property("Slice(s, i, j) ~ s.substring(i, j)") {
38 forAll { (s: String, i: Int, j: Int) =>
39 tryEqual(
40 Slice(s, i, j).toString,
41 s.substring(i, j))
42 }
43 }
44
45 property("Slice(s, i, j).charAt(k) ~ s.substring(i, j).charAt(k)") {
46 forAll { (s: String, i: Int, j: Int, k: Int) =>
47 tryEqual(
48 Slice(s, i, j).charAt(k),
49 s.substring(i, j).charAt(k))
50 }
51 }
52
53 property("slice.length >= 0") {
54 forAll { (cs: Slice) =>
55 cs.length should be >= 0
56 }
57 }
58
59 property("slice.charAt(i) ~ slice.toString.charAt(i)") {
60 forAll { (cs: Slice, i: Int) =>
61 tryEqual(
62 cs.charAt(i),
63 cs.toString.charAt(i))
64 }
65 }
66
67 property("Slice(s, i, j).subSequence(k, l) ~ s.substring(i, j).substring(k, l)") {
68 forAll { (s: String, i: Int, j: Int, k: Int, l: Int) =>
69 tryEqual(
70 Slice(s, i, j).subSequence(k, l).toString,
71 s.substring(i, j).substring(k, l))
72 }
73 }
74
75 property("Slice(s) ~ Slice(s, 0, s.length)") {
76 forAll { (s: String) =>
77 tryEqual(
78 Slice(s).toString,
79 Slice(s, 0, s.length).toString)
80 }
81 }
82
83 property("Slice(s, i, j) => Slice.unsafe(s, i, j)") {
84 forAll { (s: String, i: Int, j: Int) =>
85 Try(Slice(s, i, j).toString) match {
86 case Success(r) => r shouldBe Slice.unsafe(s, i, j).toString
87 case Failure(_) => succeed
88 }
89 }
90 }
91
92 property("x == x") {
93 forAll { (x: Slice) => x shouldBe x }
94 }
95
96 property("(x == y) = (x.toString == y.toString)") {
97 forAll { (x: Slice, y: Slice) =>
98 (x == y) shouldBe (x.toString == y.toString)
99 }
100 }
101
102 property("(x == y) -> (x.## == y.##)") {
103 forAll { (x: Slice, y: Slice) =>
104 if (x == y) x.## shouldBe y.##
105 else (x.## == y.##) shouldBe false
106 }
107 }
108
109 property("x == Slice(x.toString)") {
110 forAll { (x: Slice) =>
111 Slice(x.toString) shouldBe x
112 }
113 }
114
115 property("slice is serializable") {
116 import java.io._
117
118 forAll { (x: Slice) =>
119 val baos = new ByteArrayOutputStream()
120 val oos = new ObjectOutputStream(baos)
121 oos.writeObject(x)
122 oos.close()
123 val bytes = baos.toByteArray
124 val bais = new ByteArrayInputStream(bytes)
125 val ois = new ObjectInputStream(bais)
126 Try(ois.readObject()) shouldBe Try(x)
127 ois.close()
128 }
129 }
130 }
0 version in ThisBuild := "0.10.4"
0 version in ThisBuild := "0.11.1"