Packages

A JSON parser that decodes JSON into a flat (non recursive) data structure.

Current section

Files

Jump to
julienne src julienne.gleam
Raw

src/julienne.gleam

import gleam/bit_array
import gleam/bytes_tree
import gleam/list
import gleam/result.{try}
import gleam/string
pub fn parse(buffer) -> Result(List(#(Term, Int)), _) {
parse_bits(<<buffer:utf8>>)
}
pub fn parse_bits(buffer) {
value(buffer, [], [])
}
pub type Term {
Boolean(Bool)
Null
String(String)
Integer(integer: Int)
// All e numbers are floats as the exponent may be negative
Number(sign: Sign, integer: Int, decimal: #(Int, Int), exponent: Int)
Array
Object
Field(String)
}
pub type Sign {
Positive
Negative
}
type Stack {
ArrayItem
ObjectField
}
pub type Reason {
UnexpectedEnd
UnexpectedToken(BitArray)
MissingDigits
InvalidString(BitArray)
}
// tokenising is a way to drop whitespace
fn value(buffer, stack, acc) -> Result(List(#(Term, Int)), _) {
let depth = list.length(stack)
case buffer, stack {
<<"\r", rest:bits>>, _
| <<"\n", rest:bits>>, _
| <<" ", rest:bits>>, _
| <<"\t", rest:bits>>, _
-> value(rest, stack, acc)
<<"true", rest:bits>>, _ ->
continue(rest, stack, [#(Boolean(True), depth), ..acc])
<<"false", rest:bits>>, _ ->
continue(rest, stack, [#(Boolean(False), depth), ..acc])
<<"null", rest:bits>>, _ -> continue(rest, stack, [#(Null, depth), ..acc])
<<"[", rest:bits>>, _ ->
value(rest, [ArrayItem, ..stack], [#(Array, depth), ..acc])
<<"{", rest:bits>>, _ -> {
let acc = [#(Object, depth), ..acc]
let stack = [ObjectField, ..stack]
key(rest, stack, acc)
}
<<"]", rest:bits>>, [ArrayItem, ..stack] -> continue(rest, stack, acc)
<<"\"", rest:bits>>, _ -> {
use #(extracted, rest) <- try(string(rest, bytes_tree.new()))
continue(rest, stack, [#(String(extracted), depth), ..acc])
}
<<"0.", rest:bits>>, _ -> start_decimal(Positive, 0, rest, stack, acc)
<<"-0.", rest:bits>>, _ -> start_decimal(Negative, 0, rest, stack, acc)
<<"0e", rest:bits>>, _ | <<"0E", rest:bits>>, _ ->
start_exponent(Positive, 0, #(0, 0), rest, stack, acc)
<<"-0e", rest:bits>>, _ | <<"-0E", rest:bits>>, _ ->
start_exponent(Negative, 0, #(0, 0), rest, stack, acc)
<<"0", rest:bits>>, _ | <<"-0", rest:bits>>, _ ->
continue(rest, stack, [#(Integer(0), depth), ..acc])
<<"1", rest:bits>>, _ -> start_number(Positive, 1, rest, stack, acc)
<<"2", rest:bits>>, _ -> start_number(Positive, 2, rest, stack, acc)
<<"3", rest:bits>>, _ -> start_number(Positive, 3, rest, stack, acc)
<<"4", rest:bits>>, _ -> start_number(Positive, 4, rest, stack, acc)
<<"5", rest:bits>>, _ -> start_number(Positive, 5, rest, stack, acc)
<<"6", rest:bits>>, _ -> start_number(Positive, 6, rest, stack, acc)
<<"7", rest:bits>>, _ -> start_number(Positive, 7, rest, stack, acc)
<<"8", rest:bits>>, _ -> start_number(Positive, 8, rest, stack, acc)
<<"9", rest:bits>>, _ -> start_number(Positive, 9, rest, stack, acc)
<<"-1", rest:bits>>, _ -> start_number(Negative, 1, rest, stack, acc)
<<"-2", rest:bits>>, _ -> start_number(Negative, 2, rest, stack, acc)
<<"-3", rest:bits>>, _ -> start_number(Negative, 3, rest, stack, acc)
<<"-4", rest:bits>>, _ -> start_number(Negative, 4, rest, stack, acc)
<<"-5", rest:bits>>, _ -> start_number(Negative, 5, rest, stack, acc)
<<"-6", rest:bits>>, _ -> start_number(Negative, 6, rest, stack, acc)
<<"-7", rest:bits>>, _ -> start_number(Negative, 7, rest, stack, acc)
<<"-8", rest:bits>>, _ -> start_number(Negative, 8, rest, stack, acc)
<<"-9", rest:bits>>, _ -> start_number(Negative, 9, rest, stack, acc)
<<>>, _ -> Error(UnexpectedEnd)
_, _ -> Error(UnexpectedToken(buffer))
}
}
fn key(buffer, stack, acc) {
let depth = list.length(stack)
case buffer {
<<"\r", rest:bits>>
| <<"\n", rest:bits>>
| <<" ", rest:bits>>
| <<"\t", rest:bits>> -> key(rest, stack, acc)
<<"\"", rest:bits>> -> {
use #(extracted, rest) <- try(string(rest, bytes_tree.new()))
separator(rest, stack, [#(Field(extracted), depth), ..acc])
}
<<"}", rest:bits>> -> {
let assert [ObjectField, ..stack] = stack
continue(rest, stack, acc)
}
<<>> -> Error(UnexpectedEnd)
_ -> Error(UnexpectedToken(buffer))
}
}
fn separator(buffer, stack, acc) {
case buffer {
<<"\r", rest:bits>>
| <<"\n", rest:bits>>
| <<" ", rest:bits>>
| <<"\t", rest:bits>> -> separator(rest, stack, acc)
<<":", rest:bits>> -> value(rest, stack, acc)
<<>> -> Error(UnexpectedEnd)
_ -> Error(UnexpectedToken(buffer))
}
}
fn string(buffer, acc) {
case buffer {
<<"\"", rest:bits>> -> {
let bytes = bytes_tree.to_bit_array(acc)
case bit_array.to_string(bytes) {
Ok(value) -> Ok(#(value, rest))
Error(Nil) -> Error(InvalidString(bytes))
}
}
<<"\\\"", rest:bits>> -> string(rest, bytes_tree.append(acc, <<"\"">>))
<<"\\\\", rest:bits>> -> string(rest, bytes_tree.append(acc, <<"\\">>))
<<"\\/", rest:bits>> -> string(rest, bytes_tree.append(acc, <<"/">>))
<<"\\b", rest:bits>> -> string(rest, bytes_tree.append(acc, <<8>>))
<<"\\f", rest:bits>> -> string(rest, bytes_tree.append(acc, <<"\f">>))
<<"\\n", rest:bits>> -> string(rest, bytes_tree.append(acc, <<"\n">>))
<<"\\r", rest:bits>> -> string(rest, bytes_tree.append(acc, <<"\r">>))
<<"\\t", rest:bits>> -> string(rest, bytes_tree.append(acc, <<"\t">>))
<<"\\u", rest:bits>> -> {
use #(point, rest) <- try(unicode_escape(rest, buffer))
string(rest, bytes_tree.append(acc, <<point:utf8_codepoint>>))
}
<<"\\", _rest:bits>> -> Error(UnexpectedToken(buffer))
<<b, rest:bits>> -> string(rest, bytes_tree.append(acc, <<b>>))
<<>> -> Error(UnexpectedEnd)
_ -> Error(UnexpectedToken(buffer))
}
}
fn start_number(sign, first, rest, stack, acc) {
let #(n, _, rest) = digits(rest, first, 1)
case rest {
<<".", rest:bits>> -> start_decimal(sign, n, rest, stack, acc)
<<"e", rest:bits>> | <<"E", rest:bits>> ->
start_exponent(sign, n, #(0, 0), rest, stack, acc)
_ -> {
let n = case sign {
Positive -> n
Negative -> -n
}
continue(rest, stack, [#(Integer(n), list.length(stack)), ..acc])
}
}
}
fn start_decimal(sign, integer, rest, stack, acc) {
let #(decimal, size, rest) = digits(rest, 0, 0)
case size {
0 -> Error(MissingDigits)
_ ->
case rest {
<<"e", rest:bits>> | <<"E", rest:bits>> ->
start_exponent(sign, integer, #(decimal, size), rest, stack, acc)
_ -> {
let term =
Number(sign:, integer:, decimal: #(decimal, size), exponent: 0)
continue(rest, stack, [#(term, list.length(stack)), ..acc])
}
}
}
}
fn start_exponent(sign, integer, decimal, rest, stack, acc) {
let #(exp_sign, rest) = case rest {
<<"+", rest:bits>> -> #(Positive, rest)
<<"-", rest:bits>> -> #(Negative, rest)
_ -> #(Positive, rest)
}
let #(exponent, size, rest) = digits(rest, 0, 0)
case size {
0 -> Error(MissingDigits)
_ -> {
let exponent = case exp_sign {
Positive -> exponent
Negative -> -exponent
}
let term = Number(sign:, integer:, decimal:, exponent:)
continue(rest, stack, [#(term, list.length(stack)), ..acc])
}
}
}
fn digits(buffer, n, size) {
case buffer {
<<"1", rest:bits>> -> digits(rest, n * 10 + 1, size + 1)
<<"2", rest:bits>> -> digits(rest, n * 10 + 2, size + 1)
<<"3", rest:bits>> -> digits(rest, n * 10 + 3, size + 1)
<<"4", rest:bits>> -> digits(rest, n * 10 + 4, size + 1)
<<"5", rest:bits>> -> digits(rest, n * 10 + 5, size + 1)
<<"6", rest:bits>> -> digits(rest, n * 10 + 6, size + 1)
<<"7", rest:bits>> -> digits(rest, n * 10 + 7, size + 1)
<<"8", rest:bits>> -> digits(rest, n * 10 + 8, size + 1)
<<"9", rest:bits>> -> digits(rest, n * 10 + 9, size + 1)
<<"0", rest:bits>> -> digits(rest, n * 10 + 0, size + 1)
_ -> #(n, size, buffer)
}
}
fn continue(buffer, stack, acc) {
case buffer, stack {
<<"\r", rest:bits>>, _
| <<"\n", rest:bits>>, _
| <<" ", rest:bits>>, _
| <<"\t", rest:bits>>, _
-> continue(rest, stack, acc)
_, [] -> Ok(list.reverse(acc))
<<"]", rest:bits>>, [ArrayItem, ..stack] -> continue(rest, stack, acc)
<<",", rest:bits>>, [ArrayItem, ..] -> value(rest, stack, acc)
<<"}", rest:bits>>, [ObjectField, ..stack] -> continue(rest, stack, acc)
<<",", rest:bits>>, [ObjectField, ..] -> key(rest, stack, acc)
<<>>, _ -> Error(UnexpectedEnd)
_, _ -> Error(UnexpectedToken(buffer))
}
}
pub fn describe_reason(reason) {
case reason {
UnexpectedEnd -> "unexpected end of JSON"
UnexpectedToken(bytes) -> print_bit_array(bytes)
MissingDigits -> "missing digits in number"
InvalidString(bytes) -> print_bit_array(bytes)
}
}
fn print_bit_array(bit_array) {
case bit_array.to_string(bit_array) {
Ok(string) -> string
Error(Nil) -> string.inspect(bit_array)
}
}
// JSON \uXXXX handling per RFC 8259 §7 and §8.2:
// - Four hex digits form a code point in the BMP.
// - A high surrogate (U+D800..U+DBFF) must be immediately followed by
// `\u` + a low surrogate (U+DC00..U+DFFF); combined they form a code
// point in the supplementary planes.
// - Lone surrogates, truncated escapes, and bad hex digits are errors.
// `buffer` is the unconsumed input *including the leading `\u`*, kept
// around so we can return it in UnexpectedToken on failure.
fn unicode_escape(rest, buffer) {
use #(n, rest) <- try(four_hex(rest, buffer))
case n {
n if n >= 0xD800 && n <= 0xDBFF -> {
case rest {
<<"\\u", after:bits>> -> {
use #(low, rest2) <- try(four_hex(after, buffer))
case low {
low if low >= 0xDC00 && low <= 0xDFFF -> {
let point = 0x10000 + { n - 0xD800 } * 0x400 + { low - 0xDC00 }
codepoint(point, buffer)
|> result.map(fn(c) { #(c, rest2) })
}
_ -> Error(UnexpectedToken(buffer))
}
}
_ -> Error(UnexpectedToken(buffer))
}
}
n if n >= 0xDC00 && n <= 0xDFFF -> Error(UnexpectedToken(buffer))
_ ->
codepoint(n, buffer)
|> result.map(fn(c) { #(c, rest) })
}
}
fn four_hex(buffer, original) {
case buffer {
<<a, b, c, d, rest:bits>> -> {
case hex(a), hex(b), hex(c), hex(d) {
Ok(av), Ok(bv), Ok(cv), Ok(dv) ->
Ok(#(av * 4096 + bv * 256 + cv * 16 + dv, rest))
_, _, _, _ -> Error(UnexpectedToken(original))
}
}
_ -> Error(UnexpectedEnd)
}
}
fn hex(byte) {
case byte {
b if b >= 0x30 && b <= 0x39 -> Ok(b - 0x30)
b if b >= 0x41 && b <= 0x46 -> Ok(b - 0x41 + 10)
b if b >= 0x61 && b <= 0x66 -> Ok(b - 0x61 + 10)
_ -> Error(Nil)
}
}
fn codepoint(point, original) {
case string.utf_codepoint(point) {
Ok(c) -> Ok(c)
Error(Nil) -> Error(UnexpectedToken(original))
}
}