Module Analysis.Unparse

Implementation of the "unparsing" functionality. The goal of this feature is to enable parsers to be used as printers by manually providing a Common.bindings object with a structure matching those returned by Parse.run_parse.

Fundamentally, unparsing relies on the placement of Common.parseable.Bind operators to disambiguate between Common.parseable.Or alternatives.

To make a parseable which is suitable for unparsing, you have two options when using Or operators:

Furthermore, a Common.parseable.Bind should not be nested within a Common.parseable.Bind with the same binding name. This will cause leftover bindings as the unparse will not visit the inner Bind. Repeating the same binding name in a manner which is not conceptually a repetition is discouraged.

Both approaches are demonstrated in this example:

# let spec_around_or = bind "Xd" (literals ["x1"; "x2"])
val spec_around_or : parseable =
  Bind {name = "Xd"; syntax = Or [Lit "x1"; Lit "x2"] }
# let spec_within_or = Or [bind "case1" (literals ["x1"]); bind "case2" (literals ["x2"])]
val spec_within_or : parseable =
  Or
   [Bind {name = "case1"; syntax = Lit "x1"};
    Bind {name = "case2"; syntax = Lit "x2"} ]

# let print_unparse p bs = show_parse_output @@ unparse_with_bindings p bs
val print_unparse : parseable -> Lang__.Common.bindings -> string = <fun>

# print_unparse spec_around_or @@ binding "Xd" (output_str "x1")
- : string = "tokens=[\"x1\"] bindings={ Xd=[] }"
# print_unparse spec_around_or @@ binding "Xd" (output_str "anything");
- : string = "tokens=[\"anything\"] bindings={ Xd=[] }"

# print_unparse spec_within_or @@ binding "case1" (output_str "x1");
- : string = "tokens=[\"x1\"] bindings={ case1=[] }"
# print_unparse spec_within_or @@ binding "case2" (output_str "anything");
- : string = "tokens=[\"anything\"] bindings={ case2=[] }"

Attempts to compute a list of string tokens which could have resulted in given parseable producing the given bindings. Returns the unparsing and the remaining unused bindings.

When encountering a Common.parseable.Bind operator, the bindings map is searched for a matching name. If found, the bound value is returned as the unparse and it is popped from the bindings map.

When encountering an Common.parseable.Or, the unparse explores each alternative and selects the unique alternative which results in the least leftover bindings. Here, "least" is defined by the partial order of Common.bindings_compare.

  • raises Stdlib.Not_found

    when there is no feasible parse (for example, but not limited to, a required name is missing from the given bindings).

Calls unparse_with_bindings and ensures that the final bindings map is empty. This is what you should use to unparse a top-level parser.