123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809(** YAML 1.2 parser. Transforms the token stream from the Scanner into an event
stream. The parser implements a recursive-descent grammar matching the YAML
1.2.2 specification.
Grammar summary (tokens → events) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ stream
::= STREAM_START doc* STREAM_END doc ::= DIRECTIVE* DOCUMENT_START node?
DOCUMENT_END? node ::= ALIAS | properties? (block_content | flow_content)
properties ::= TAG ANCHOR? | ANCHOR TAG? block_content ::= block_collection
| scalar flow_content ::= flow_collection | scalar block_collection ::=
block_sequence | block_mapping flow_collection ::= flow_sequence |
flow_mapping block_sequence ::= BLOCK_SEQ_START (BLOCK_ENTRY node?)*
BLOCK_END block_mapping ::= BLOCK_MAP_START ((KEY node?)? (VALUE node?)?)*
BLOCK_END flow_sequence ::= FLOW_SEQ_START (flow_seq_entry FLOW_ENTRY?)*
FLOW_SEQ_END flow_mapping ::= FLOW_MAP_START (flow_map_entry FLOW_ENTRY?)*
FLOW_MAP_END
The parser is implemented as a state machine. [next_event] drives it one
step at a time, returning one event per call. *)openTypes(* ------------------------------------------------------------------ *)(* Directive table *)(* ------------------------------------------------------------------ *)typedirectives={mutableversion:(int*int)option;mutabletags:(string*string)list;(** (handle, prefix) pairs *)}letdefault_directives()={version=None;tags=[("!","!");("!!","tag:yaml.org,2002:")]}(* ------------------------------------------------------------------ *)(* Parser state *)(* ------------------------------------------------------------------ *)(** The parser is a pushdown automaton. [state] encodes the current production
being processed and [states] is the return stack. *)typeparse_state=|Parse_stream_start|Parse_document_start_implicit(** first document without '---' *)|Parse_document_start(** subsequent or explicit documents *)|Parse_document_end|Parse_document_content|Parse_block_node|Parse_block_node_or_indentless_sequence|Parse_flow_node|Parse_block_sequence_first_entry|Parse_block_sequence_entry|Parse_indentless_sequence_entry|Parse_block_mapping_first_key|Parse_block_mapping_key|Parse_block_mapping_value|Parse_flow_sequence_first_entry|Parse_flow_sequence_entry|Parse_flow_sequence_need_separator(** after an entry: requires [','] or ['\]'] *)|Parse_flow_sequence_entry_mapping_key|Parse_flow_sequence_entry_mapping_value|Parse_flow_sequence_entry_mapping_end|Parse_flow_mapping_first_key|Parse_flow_mapping_key|Parse_flow_mapping_need_separator(** after a key-value pair: requires [','] or ['\}'] *)|Parse_flow_mapping_value|Parse_endtypet={scanner:Scanner.state;mutablestate:parse_state;mutablestates:parse_statelist;(** return stack *)mutabledirectives:directives;mutableevents:eventlist;(** buffered events *)}letcreate(scanner:Scanner.state):t={scanner;state=Parse_stream_start;states=[];directives=default_directives();events=[];}(* ------------------------------------------------------------------ *)(* Helpers *)(* ------------------------------------------------------------------ *)letmk_eventkindspep={kind;start_pos=sp;end_pos=ep}(** Push a continuation state onto the return stack. *)letpush_statepstate=p.states<-state::p.states(** Pop a continuation state. *)letpop_statep=matchp.stateswith|s::rest->p.states<-rest;s|[]->Parse_end(** True if the scanner's next token has the given kind. *)letcheckpkinds=Scanner.check_tokenp.scannerkindsletpeek_kindp=Scanner.peek_kindp.scannerletget_tokp=Scanner.get_tokenp.scannerletpeek_tokp=Scanner.peek_tokenp.scanner(** Percent-decode a tag suffix (e.g. [%21] → [!]). *)letpct_decodes=letn=String.lengthsinletbuf=Buffer.createninleti=ref0inwhile!i<ndoifs.[!i]='%'&&!i+2<nthenbeginlethi=s.[!i+1]inletlo=s.[!i+2]inlethex_valc=ifc>='0'&&c<='9'thenChar.codec-Char.code'0'elseifc>='a'&&c<='f'thenChar.codec-Char.code'a'+10elseifc>='A'&&c<='F'thenChar.codec-Char.code'A'+10else-1inleth=hex_valhiandl=hex_valloinifh>=0&&l>=0thenbeginBuffer.add_charbuf(Char.chr((hlsl4)lorl));i:=!i+3endelsebeginBuffer.add_charbufs.[!i];incriendendelsebeginBuffer.add_charbufs.[!i];incrienddone;Buffer.contentsbuf(** Resolve a tag using the directive table. [(handle, suffix)] → full URI
string. *)letresolve_tagdirectivesposhandlesuffix=letsuffix=pct_decodesuffixinmatchhandlewith|""->suffix(* verbatim tag *)|_->(matchList.assoc_opthandledirectives.tagswith|Someprefix->prefix^suffix|None->Types.parse_errorpos"tag handle '%s' is not defined in this document"handle)(* ------------------------------------------------------------------ *)(* Anchor / tag / alias collection *)(* ------------------------------------------------------------------ *)(** Collect consecutive ANCHOR and TAG tokens (in any order) and return them
together with the position of the first. *)letcollect_node_propertiesp=letanchor=refNoneinlettag=refNoneinletstart=refNoneinletcollecting=reftrueinwhile!collectingdomatchpeek_kindpwith|Anchorname->lettok_pos=(peek_tokp).tok_start_posinif!start=Nonethenstart:=Sometok_pos;if!anchor<>NonethenTypes.parse_errortok_pos"a node cannot have two anchors";anchor:=Somename;ignore(get_tokp)|Tag(handle,suffix)->lettok=get_tokpinif!start=Nonethenstart:=Sometok.tok_start_pos;tag:=Some(resolve_tagp.directivestok.tok_start_poshandlesuffix)|_->collecting:=falsedone;(!anchor,!tag,!start)(* ------------------------------------------------------------------ *)(* Directive processing *)(* ------------------------------------------------------------------ *)letprocess_directivesp=(* Reset directives to defaults for each new document *)p.directives<-default_directives();lethad_directives=reffalseinletcollecting=reftrueinwhile!collectingdomatchpeek_kindpwith|Directive(name,value)->(lettok=get_tokpinhad_directives:=true;matchnamewith|"YAML"->(ifp.directives.version<>NonethenTypes.parse_errortok.tok_start_pos"duplicate YAML directive (only one allowed per document)";letparts=String.split_on_char'.'valueinmatchpartswith|[maj;min]->(tryp.directives.version<-Some(int_of_stringmaj,int_of_stringmin)with|Failure_->())|_->())|handlewhenString.lengthhandle>=1&&handle.[0]='!'->(* TAG directive: name = handle, value = prefix *)p.directives.tags<-(handle,value)::List_ext.filter(fun(h,_)->h<>handle)p.directives.tags|_->())|_->collecting:=falsedone;!had_directives(* ------------------------------------------------------------------ *)(* Empty scalar helper *)(* ------------------------------------------------------------------ *)letempty_scalarpos=mk_event(Scalar{anchor=None;tag=None;value="";style=Plain})pospos(* ------------------------------------------------------------------ *)(* Core state machine *)(* ------------------------------------------------------------------ *)(** Produce one event from the current state. Updates [p.state] to the next
state. *)letrecproducep=matchp.statewith(* ---- Stream ---- *)|Parse_stream_start->lettok=get_tokpin(* STREAM_START *)p.state<-Parse_document_start_implicit;mk_eventStream_starttok.tok_start_postok.tok_end_pos|Parse_end->lettok=peek_tokpinmk_eventStream_endtok.tok_start_postok.tok_end_pos(* ---- Document start ---- *)|Parse_document_start_implicit->(lethad_dir=process_directivespinletsp=(peek_tokp).tok_start_posinmatchpeek_kindpwith|Directive_->assertfalse(* already processed *)|Document_start->(* Explicit document start *)ignore(get_tokp);letep=(peek_tokp).tok_start_posinpush_statepParse_document_end;p.state<-Parse_document_content;mk_event(Document_start{explicit=true;version=p.directives.version;tag_directives=p.directives.tags;})spep|Stream_end->ifhad_dirthenTypes.parse_errorsp"directive(s) not followed by a document";ignore(get_tokp);p.state<-Parse_end;mk_eventStream_endspsp|Document_end->ifhad_dirthenTypes.parse_errorsp"directive(s) not followed by a document (unexpected '...')";(* Bare '...' at start of stream (no document open): skip it *)ignore(get_tokp);p.state<-Parse_document_start_implicit;producep|_->(* Implicit document *)push_statepParse_document_end;p.state<-Parse_block_node;mk_event(Document_start{explicit=false;version=None;tag_directives=p.directives.tags;})spsp)|Parse_document_start->(lethad_dir=process_directivespinletsp=(peek_tokp).tok_start_posinmatchpeek_kindpwith|Stream_end->ifhad_dirthenTypes.parse_errorsp"directive(s) not followed by a document";ignore(get_tokp);p.state<-Parse_end;mk_eventStream_endspsp|Document_end->(* Bare '...' between documents (no new doc started yet): skip it *)ignore(get_tokp);p.state<-Parse_document_start;producep|Document_start->lettok=get_tokpinpush_statepParse_document_end;p.state<-Parse_document_content;mk_event(Document_start{explicit=true;version=p.directives.version;tag_directives=p.directives.tags;})tok.tok_start_postok.tok_end_pos|_->(* Implicit document (content without '---') *)push_statepParse_document_end;p.state<-Parse_block_node;mk_event(Document_start{explicit=false;version=p.directives.version;tag_directives=p.directives.tags;})spsp)(* ---- Document end ---- *)|Parse_document_end->(matchpeek_kindpwith|Document_end->lettok=get_tokpinp.state<-Parse_document_start;mk_event(Document_end{explicit=true})tok.tok_start_postok.tok_end_pos|Directive_->(* A directive after a document requires an explicit '...' marker first *)lettok=peek_tokpinTypes.parse_errortok.tok_start_pos"a directive must be preceded by a document-end marker ('...')"|Document_start|Stream_end->letsp=(peek_tokp).tok_start_posinp.state<-Parse_document_start;mk_event(Document_end{explicit=false})spsp|_->(* Anything else is unexpected extra content after the document's root node *)lettok=peek_tokpinTypes.parse_errortok.tok_start_pos"unexpected content after document root node (missing '---' or \
'...'?)")(* ---- Document content ---- *)|Parse_document_content->(matchpeek_kindpwith|Document_end|Stream_end->p.state<-pop_statep;letsp=(peek_tokp).tok_start_posin(* Empty document: emit an empty plain scalar as the document's node *)empty_scalarsp|_->p.state<-Parse_block_node;producep)(* ---- Nodes ---- *)|Parse_block_node|Parse_block_node_or_indentless_sequence|Parse_flow_node->(* NOTE: do NOT pop the state here. parse_node pops it internally once
it knows what the node is (scalar, sequence, mapping). Popping early
would discard the continuation state (e.g. Parse_document_end) before
parse_node has a chance to use it. *)letallow_indentless=p.state=Parse_block_node_or_indentless_sequenceinletin_flow=p.state=Parse_flow_nodeinparse_nodep~allow_indentless~in_flow(* ---- Block sequences ---- *)|Parse_block_sequence_first_entry->ignore(get_tokp);(* BLOCK_SEQUENCE_START *)p.state<-Parse_block_sequence_entry;producep|Parse_block_sequence_entry->(matchpeek_kindpwith|Block_entry->(lettok=get_tokpin(* BLOCK_ENTRY *)matchpeek_kindpwith|Block_entry|Block_end->p.state<-Parse_block_sequence_entry;(* Empty item *)empty_scalartok.tok_end_pos|_->push_statepParse_block_sequence_entry;p.state<-Parse_block_node;producep)|Block_end->lettok=get_tokpinp.state<-pop_statep;mk_eventSequence_endtok.tok_start_postok.tok_end_pos|kind->Types.parse_error(peek_tokp).tok_start_pos"expected block sequence entry or BLOCK_END, got %s"(show_kindkind))(* ---- Indentless sequence (mapping value is an implicit sequence) ---- *)|Parse_indentless_sequence_entry->(matchpeek_kindpwith|Block_entry->(lettok=get_tokpinmatchpeek_kindpwith|Block_entry|Key|Value|Block_end->p.state<-Parse_indentless_sequence_entry;empty_scalartok.tok_end_pos|_->push_statepParse_indentless_sequence_entry;p.state<-Parse_block_node;producep)|_->letsp=(peek_tokp).tok_start_posinp.state<-pop_statep;mk_eventSequence_endspsp)(* ---- Block mappings ---- *)|Parse_block_mapping_first_key->ignore(get_tokp);(* BLOCK_MAPPING_START *)p.state<-Parse_block_mapping_key;producep|Parse_block_mapping_key->(matchpeek_kindpwith|Key->(lettok=get_tokpinmatchpeek_kindpwith|Key|Value|Block_end->p.state<-Parse_block_mapping_value;empty_scalartok.tok_end_pos|_->push_statepParse_block_mapping_value;p.state<-Parse_block_node_or_indentless_sequence;producep)|Block_end->lettok=get_tokpinp.state<-pop_statep;mk_eventMapping_endtok.tok_start_postok.tok_end_pos|Value->(* Implicit empty key: ': value' without a preceding '?' *)p.state<-Parse_block_mapping_value;letsp=(peek_tokp).tok_start_posinempty_scalarsp|kind->Types.parse_error(peek_tokp).tok_start_pos"expected block mapping key or BLOCK_END, got %s"(show_kindkind))|Parse_block_mapping_value->(matchpeek_kindpwith|Value->(lettok=get_tokpinmatchpeek_kindpwith|Key|Value|Block_end->p.state<-Parse_block_mapping_key;empty_scalartok.tok_end_pos|Block_entry->push_statepParse_block_mapping_key;p.state<-Parse_indentless_sequence_entry;letsp=tok.tok_end_posinmk_event(Sequence_start{anchor=None;tag=None;implicit=true;flow=false})spsp|_->push_statepParse_block_mapping_key;p.state<-Parse_block_node_or_indentless_sequence;producep)|_->(* Missing value: emit empty scalar *)p.state<-Parse_block_mapping_key;letsp=(peek_tokp).tok_start_posinempty_scalarsp)(* ---- Flow sequences ---- *)|Parse_flow_sequence_first_entry->letfs_tok=get_tokpin(* FLOW_SEQUENCE_START *)(* Leading comma: a comma immediately after '[' is invalid in YAML 1.2 *)(matchpeek_kindpwith|Flow_entry->Types.parse_errorfs_tok.tok_end_pos"empty entry in flow sequence (unexpected leading comma)"|_->());p.state<-Parse_flow_sequence_entry;producep|Parse_flow_sequence_entry->((* Parses one entry; this state is entered at start or after consuming a comma.
After the entry, Parse_flow_sequence_need_separator requires the next comma. *)matchpeek_kindpwith|Flow_sequence_end->lettok=get_tokpinp.state<-pop_statep;mk_eventSequence_endtok.tok_start_postok.tok_end_pos|Key->(* Inline mapping inside a flow sequence: [key: val] style.
Set state to need_separator so that after the mapping, a comma is required. *)lettok=get_tokpinp.state<-Parse_flow_sequence_entry_mapping_key;mk_event(Mapping_start{anchor=None;tag=None;implicit=true;flow=true})tok.tok_start_postok.tok_end_pos|Value->(* Bare ':' with no preceding key → empty implicit key in flow sequence *)letsp=(peek_tokp).tok_start_posinp.state<-Parse_flow_sequence_entry_mapping_key;mk_event(Mapping_start{anchor=None;tag=None;implicit=true;flow=true})spsp|_->(* Parse one node; after it, a comma (or ']') is required *)push_statepParse_flow_sequence_need_separator;p.state<-Parse_flow_node;producep)|Parse_flow_sequence_need_separator->((* After an entry, require ',' or ']'; anything else is a missing-comma error. *)matchpeek_kindpwith|Flow_sequence_end->lettok=get_tokpinp.state<-pop_statep;mk_eventSequence_endtok.tok_start_postok.tok_end_pos|Flow_entry->letcomma_tok=get_tokpin(matchpeek_kindpwith|Flow_entry->Types.parse_errorcomma_tok.tok_end_pos"empty entry in flow sequence (unexpected consecutive comma)"|_->());p.state<-Parse_flow_sequence_entry;producep|_->lettok=peek_tokpinTypes.parse_errortok.tok_start_pos"missing comma between flow sequence entries")|Parse_flow_sequence_entry_mapping_key->(matchpeek_kindpwith|Value|Flow_entry|Flow_sequence_end->p.state<-Parse_flow_sequence_entry_mapping_value;letsp=(peek_tokp).tok_start_posinempty_scalarsp|_->push_statepParse_flow_sequence_entry_mapping_value;p.state<-Parse_flow_node;producep)|Parse_flow_sequence_entry_mapping_value->(matchpeek_kindpwith|Value->(ignore(get_tokp);matchpeek_kindpwith|Flow_entry|Flow_sequence_end->p.state<-Parse_flow_sequence_entry_mapping_end;letsp=(peek_tokp).tok_start_posinempty_scalarsp|_->push_statepParse_flow_sequence_entry_mapping_end;p.state<-Parse_flow_node;producep)|_->p.state<-Parse_flow_sequence_entry_mapping_end;letsp=(peek_tokp).tok_start_posinempty_scalarsp)|Parse_flow_sequence_entry_mapping_end->p.state<-Parse_flow_sequence_need_separator;letsp=(peek_tokp).tok_start_posinmk_eventMapping_endspsp(* ---- Flow mappings ---- *)|Parse_flow_mapping_first_key->ignore(get_tokp);(* FLOW_MAPPING_START *)p.state<-Parse_flow_mapping_key;producep|Parse_flow_mapping_key->((* Parses one key; entered after '{' (first key) or after consuming a comma.
After the key-value pair, Parse_flow_mapping_need_separator requires a comma. *)matchpeek_kindpwith|Flow_mapping_end->lettok=get_tokpinp.state<-pop_statep;mk_eventMapping_endtok.tok_start_postok.tok_end_pos|Key->(lettok=get_tokpinmatchpeek_kindpwith|Value|Flow_entry|Flow_mapping_end->p.state<-Parse_flow_mapping_value;empty_scalartok.tok_end_pos|_->push_statepParse_flow_mapping_value;p.state<-Parse_flow_node;producep)|_->(* Implicit key in flow mapping (e.g., {foo: bar}) *)push_statepParse_flow_mapping_value;p.state<-Parse_flow_node;producep)|Parse_flow_mapping_need_separator->((* After a key-value pair: require ',' or '}'. *)matchpeek_kindpwith|Flow_mapping_end->lettok=get_tokpinp.state<-pop_statep;mk_eventMapping_endtok.tok_start_postok.tok_end_pos|Flow_entry->ignore(get_tokp);p.state<-Parse_flow_mapping_key;producep|_->lettok=peek_tokpinTypes.parse_errortok.tok_start_pos"missing comma between flow mapping entries")|Parse_flow_mapping_value->(matchpeek_kindpwith|Value->(ignore(get_tokp);matchpeek_kindpwith|Flow_entry|Flow_mapping_end->p.state<-Parse_flow_mapping_need_separator;letsp=(peek_tokp).tok_start_posinempty_scalarsp|_->push_statepParse_flow_mapping_need_separator;p.state<-Parse_flow_node;producep)|_->p.state<-Parse_flow_mapping_need_separator;letsp=(peek_tokp).tok_start_posinempty_scalarsp)(** Parse a node (scalar, collection, or alias). Handles properties (anchor /
tag) and dispatches to the right collection or scalar production. *)andparse_nodep~allow_indentless~in_flow:_=letsp=(peek_tokp).tok_start_posin(* Handle alias *)ifcheckp[Alias""]|>fun_->matchpeek_kindpwith|Alias_->true|_->falsethenbeginlettok=get_tokpinletname=matchtok.tok_kindwith|Aliasn->n|_->assertfalseinp.state<-pop_statep;mk_event(Aliasname)tok.tok_start_postok.tok_end_posendelsebeginletanchor,tag,prop_start=collect_node_propertiespinletnode_start=matchprop_startwith|Somes->s|None->spinmatchpeek_kindpwith|Block_sequence_start->p.state<-Parse_block_sequence_first_entry;letev_sp=(peek_tokp).tok_start_posinmk_event(Sequence_start{anchor;tag;implicit=tag=None;flow=false})node_startev_sp|Block_mapping_start->p.state<-Parse_block_mapping_first_key;letev_sp=(peek_tokp).tok_start_posinmk_event(Mapping_start{anchor;tag;implicit=tag=None;flow=false})node_startev_sp|Flow_sequence_start->letev_sp=(peek_tokp).tok_start_posinp.state<-Parse_flow_sequence_first_entry;mk_event(Sequence_start{anchor;tag;implicit=tag=None;flow=true})node_startev_sp|Flow_mapping_start->letev_sp=(peek_tokp).tok_start_posinp.state<-Parse_flow_mapping_first_key;mk_event(Mapping_start{anchor;tag;implicit=tag=None;flow=true})node_startev_sp|Block_entrywhenallow_indentless->(* Indentless sequence: a sequence that starts at the current indent.
The continuation (e.g. Parse_block_mapping_key) is already on the
state stack from the caller; do NOT push again. *)letev_sp=(peek_tokp).tok_start_posinp.state<-Parse_indentless_sequence_entry;mk_event(Sequence_start{anchor;tag;implicit=tag=None;flow=false})node_startev_sp|Scalar(value,style)->lettok=get_tokpinp.state<-pop_statep;mk_event(Scalar{anchor;tag;value;style})tok.tok_start_postok.tok_end_pos|_->((* Empty node: emit empty plain scalar *)p.state<-pop_statep;match(anchor,tag)with|None,None->empty_scalarnode_start|_->(* Anchor/tag with no value: empty scalar *)mk_event(Scalar{anchor;tag;value="";style=Plain})node_startnode_start)endandshow_kind=function|Stream_start->"STREAM_START"|Stream_end->"STREAM_END"|Directive_->"DIRECTIVE"|Document_start->"DOCUMENT_START"|Document_end->"DOCUMENT_END"|Block_sequence_start->"BLOCK_SEQUENCE_START"|Block_mapping_start->"BLOCK_MAPPING_START"|Block_end->"BLOCK_END"|Flow_sequence_start->"FLOW_SEQUENCE_START"|Flow_sequence_end->"FLOW_SEQUENCE_END"|Flow_mapping_start->"FLOW_MAPPING_START"|Flow_mapping_end->"FLOW_MAPPING_END"|Block_entry->"BLOCK_ENTRY"|Flow_entry->"FLOW_ENTRY"|Key->"KEY"|Value->"VALUE"|Alias_->"ALIAS"|Anchor_->"ANCHOR"|Tag_->"TAG"|Scalar_->"SCALAR"(* ------------------------------------------------------------------ *)(* Public interface *)(* ------------------------------------------------------------------ *)(** Return (without consuming) the next event. Produces a new event if the
buffer is empty. *)letpeek_event(p:t):event=matchp.eventswith|ev::_->ev|[]->letev=producepinp.events<-[ev];ev(** Consume and return the next event. *)letget_event(p:t):event=matchp.eventswith|ev::rest->p.events<-rest;ev|[]->producep(** Check whether the next event's kind is in the list. Uses structural equality
on the constructors only (ignores fields). *)letcheck_event(p:t)(kinds:event_kindlist):bool=letev=peek_eventpinList.exists(funk->match(ev.kind,k)with|Stream_start,Stream_start->true|Stream_end,Stream_end->true|Document_start_,Document_start_->true|Document_end_,Document_end_->true|Mapping_start_,Mapping_start_->true|Mapping_end,Mapping_end->true|Sequence_start_,Sequence_start_->true|Sequence_end,Sequence_end->true|Scalar_,Scalar_->true|Alias_,Alias_->true|_->false)kinds(** Expose the underlying scanner, e.g. to drain accumulated comments after
parsing is complete. *)letget_scanner(p:t):Scanner.state=p.scanner(** Collect all events into a list. This is a convenience for tests. *)letto_event_list(p:t):eventlist=letresult=ref[]inletstop=reffalseinwhilenot!stopdoletev=get_eventpinresult:=ev::!result;matchev.kindwith|Stream_end->stop:=true|_->()done;List.rev!result