7.10.5. PEG-05 — JSON Parser

This tutorial builds a complete JSON parser that produces daslib/json JsonValue? trees. You will learn:

  • MB() for optional matching

  • Complex recursive grammars (arrays in arrays, objects in objects)

  • Tuple and table return types

  • Escaped characters in literals ("\{" and "\}")

  • The comma-separated list pattern in practice

Prerequisites: PEG-03 — CSV Parser.

7.10.5.1. Recursive Grammars

JSON is inherently recursive — arrays contain values, values can be arrays or objects, objects contain values. In PEG, rules reference each other freely:

element  ->  array | object | value
array    ->  "[" element_list "]"
object   ->  "{" mapping_list "}"
value    ->  true | false | null | number | string

dasPEG’s packrat memoization handles the recursion efficiently.

7.10.5.2. Escaping Braces

Since { and } have special meaning in daslang string builders, literal braces in grammar strings must be escaped with a backslash: "\{" and "\}".

7.10.5.3. The Grammar (Simplified)

def parse_json(input : string;
               blk : block<(var val : JsonValue?# implicit;
                            err : array<ParsingError>) : void>) {
    parse(input) {
        var json_value : JsonValue?
        rule(WS, element as e, EOF) {
            return <- e
        }

        var element : JsonValue?
        rule(array_ as a) { return <- JV(a) }
        rule(object_ as o) { return <- JV(o) }
        rule(value as v) { return <- v }

        // Array: [ elements ]
        var array_ : array<JsonValue?>
        rule("[", WS, "]", WS) {
            return <- array<JsonValue?>()
        }
        rule("[", WS, element_list as list, "]", WS) {
            return <- list
        }

        // Comma-separated element list
        var element_list : array<JsonValue?>
        rule(*comma_element as els, element as last) {
            els |> push <| last
            return <- els
        }

        var comma_element : JsonValue?
        rule(element as e, ",", WS) { return <- e }

        // Object: { mappings }
        var object_ : table<string; JsonValue?>
        rule("\{", WS, "\}", WS) {
            var empty : table<string; JsonValue?>
            return <- empty
        }
        rule("\{", WS, mapping_list as list, "\}", WS) {
            return <- list |> into_table
        }

        // ... mapping rules, value rules (true/false/null/number/string)
    }
}

7.10.5.4. Tuple Return Types

Key-value pairs use tuple<string; JsonValue?> as the return type:

var mapping : tuple<string; JsonValue?>
rule(string_ as s, WS, ":", WS, element as e) {
    return <- (s, e)
}

Tuples are constructed with parenthesized comma expressions.

7.10.5.5. Verification

The tutorial verifies its output matches daslib/json’s built-in parser:

var discard_error : string
var stdlib_json <- read_json(test_input, discard_error)
parse_json(test_input) $(var peg_json; err) {
    let match_ = write_json(stdlib_json) == write_json(peg_json)
    print("stdlib vs PEG match: {match_}\n")   // true
}

See also

Full source: tutorials/dasPEG/05_json_parser.das

Next tutorial: PEG-06 — Debugging and Options

Related: JSON (daslib/json module tutorial)