package net.liftweb.json
/*
* Copyright 2009 WorldWide Conferencing, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions
* and limitations under the License.
*/
/** Fast imperative parser.
*/
object JsonParser {
import JsonAST._
class ParseException(message: String) extends Exception
sealed abstract class Token
case object OpenObj extends Token
case object CloseObj extends Token
case class FieldStart(name: String) extends Token
case object End extends Token
case class StringVal(value: String) extends Token
case class IntVal(value: BigInt) extends Token
case class DoubleVal(value: Double) extends Token
case class BoolVal(value: Boolean) extends Token
case object NullVal extends Token
case object OpenArr extends Token
case object CloseArr extends Token
trait MValue {
def toJValue: JValue
}
case class MField(name: String, var value: MValue) extends MValue {
def toJValue = JField(name, value.toJValue)
}
case object MNull extends MValue {
def toJValue = JNull
}
case class MString(value: String) extends MValue {
def toJValue = JString(value)
}
case class MInt(value: BigInt) extends MValue {
def toJValue = JInt(value)
}
case class MDouble(value: Double) extends MValue {
def toJValue = JDouble(value)
}
case class MBool(value: Boolean) extends MValue {
def toJValue = JBool(value)
}
trait MBlock[A <: MValue] {
protected var elems = List[A]()
def +=(f: A) = elems = f :: elems
}
case class MObject() extends MValue with MBlock[MField] {
def toJValue = JObject(elems.map(_.toJValue).reverse)
}
case class MArray() extends MValue with MBlock[MValue] {
def toJValue = JArray(elems.map(_.toJValue).reverse)
}
def parse(s: String): JValue =
try {
parse0(s)
} catch {
case e: Exception => throw new ParseException(e.getMessage)
}
private def parse0(s: String): JValue = {
val p = new Parser(s)
val vals = new ValStack
var token: Token = null
var root: Option[JValue] = None
def closeBlock(v: MValue) {
vals.peekOption match {
case Some(f: MField) =>
f.value = v
val field = vals.pop[MField]
vals.peek[MObject] += field
case Some(o: MObject) => o += v.asInstanceOf[MField]
case Some(a: MArray) => a += v
case None => root = Some(v.toJValue)
}
}
def newValue(v: MValue) {
vals.peek[MValue] match {
case f: MField =>
vals.pop[MField]
f.value = v
vals.peek[MObject] += f
case a: MArray =>
a += v
}
}
do {
token = p.nextToken
token match {
case OpenObj => vals.push(MObject())
case FieldStart(name) => vals.push(MField(name, null))
case StringVal(x) => newValue(MString(x))
case IntVal(x) => newValue(MInt(x))
case DoubleVal(x) => newValue(MDouble(x))
case BoolVal(x) => newValue(MBool(x))
case NullVal => newValue(MNull)
case CloseObj => closeBlock(vals.pop[MValue])
case OpenArr => vals.push(MArray())
case CloseArr => closeBlock(vals.pop[MArray])
case End =>
}
} while (token != End)
root.get
}
private class ValStack {
import java.util.LinkedList
val stack = new LinkedList[MValue]()
def pop[A <: MValue] = stack.poll match {
case x: A => x
case x => error("unexpected " + x)
}
def push(v: MValue) = stack.addFirst(v)
def peek[A <: MValue] = stack.peek match {
case x: A => x
case x => error("unexpected " + x)
}
def peekOption = if (stack isEmpty) None else Some(stack.peek)
}
private class Parser(buf: String) {
import java.util.LinkedList
val blocks = new LinkedList[BlockMode]()
var fieldNameMode = true
var cur = 0 // Pointer which points current parsing location
def nextToken: Token = {
def isDelimiter(c: Char) = c == ' ' || c == '\n' || c == ',' || c == '\r' || c == '\t' || c == '}' || c == ']'
def parseFieldName: String = {
cur = cur+1
val start = cur
while (true) {
val c = buf.charAt(cur)
if (c == '"') {
cur = cur+1
return buf.substring(start, cur-1)
}
cur = cur+1
}
error("can't happen")
}
def parseString: String = {
cur = cur+1
val s = new java.lang.StringBuilder
def append(c: Char) = s.append(c)
while (true) {
val c = buf.charAt(cur)
if (c == '"') {
cur = cur+1
return s.toString
}
if (c == '\\') {
cur = cur+1
buf.charAt(cur) match {
case '"' => append('"')
case 'n' => append('\n')
case 't' => append('\t')
case 'r' => append('\r')
case 'b' => append('\b')
case 'u' =>
val codePoint = Integer.parseInt(buf.substring(cur+1, cur+5), 16)
cur = cur+4
s.appendCodePoint(codePoint)
case _ => append('\\')
}
} else append(c)
cur = cur+1
}
error("can't happen")
}
def parseValue = {
var i = cur+1
var wasInt = true
var doubleVal = false
while (wasInt) {
val c = buf.charAt(i)
if (c == '.' || c == 'e' || c == 'E') {
doubleVal = true
i = i+1
} else if (!(Character.isDigit(c) || c == '.' || c == 'e' || c == 'E' || c == '-')) {
wasInt = false
} else {
i = i+1
}
}
val value = buf.substring(cur, i)
cur = i
if (doubleVal) DoubleVal(value.toDouble) else IntVal(BigInt(value))
}
val len = buf.length
while (cur < len) {
buf.charAt(cur) match {
case '{' =>
blocks.addFirst(OBJECT)
cur = cur+1
fieldNameMode = true
return OpenObj
case '}' =>
blocks.poll
cur = cur+1
return CloseObj
case '"' =>
if (fieldNameMode && blocks.peek == OBJECT) return FieldStart(parseFieldName)
else {
fieldNameMode = true
return StringVal(parseString)
}
case 't' =>
fieldNameMode = true
if (buf.charAt(cur+1) == 'r' && buf.charAt(cur+2) == 'u' && buf.charAt(cur+3) == 'e' && isDelimiter(buf.charAt(cur+4))) {
cur = cur+4
return BoolVal(true)
}
error("expected boolean")
case 'f' =>
fieldNameMode = true
if (buf.charAt(cur+1) == 'a' && buf.charAt(cur+2) == 'l' && buf.charAt(cur+3) == 's' && buf.charAt(cur+4) == 'e' && isDelimiter(buf.charAt(cur+5))) {
cur = cur+5
return BoolVal(false)
}
error("expected boolean")
case 'n' =>
fieldNameMode = true
if (buf.charAt(cur+1) == 'u' && buf.charAt(cur+2) == 'l' && buf.charAt(cur+3) == 'l' && isDelimiter(buf.charAt(cur+4))) {
cur = cur+4
return NullVal
}
error("expected null")
case ':' =>
fieldNameMode = false
cur = cur+1
case '[' =>
blocks.addFirst(ARRAY)
cur = cur+1
return OpenArr
case ']' =>
fieldNameMode = true
blocks.poll
cur = cur+1
return CloseArr
case c if Character.isDigit(c) || c == '-' =>
fieldNameMode = true
return parseValue
case c if isDelimiter(c) => cur = cur+1
case c => error("unknown token " + c)
}
}
End
}
sealed abstract class BlockMode
case object ARRAY extends BlockMode
case object OBJECT extends BlockMode
}
}