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:
Or
directly within a Bind
, recording the result of the Or
as a new binding. This is most suitable where the Or
alternatives are simple literals. Binding complex expressions in this way makes it more difficult to construct the bindings needed by Unparse.unparse
.Bind
names within each Or
alternative. The unparsing will choose the alternative which is most applicable given the available bindings. When using this, you should ensure that the binding names, together with the number of times they're bound, is sufficient to disambiguate alternatives. The unparse will fail if it cannot determine a unique best alternative.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=[] }"
val unparse_with_bindings :
Common.parseable ->
Common.bindings ->
Common.output * Common.bindings
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
.
val unparse : Common.parseable -> Common.bindings -> Common.output
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.