123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424openOdoc_compatmoduleLocation=Odoc_model.Location_moduleError=Odoc_model.ErrormoduleComment=Odoc_model.Commenttype'awith_location='aLocation.with_locationtypeast_leaf_inline_element=[|`Spaceofstring|`Wordofstring|`Code_spanofstring|`Raw_markupofstringoption*string]typestatus={warnings:Error.warning_accumulator;sections_allowed:Ast.sections_allowed;parent_of_sections:Odoc_model.Paths.Identifier.LabelParent.t;}(* TODO This and Token.describe probably belong in Parse_error. *)letdescribe_element=function|`Reference(`Simple,_,_)->Token.describe(`Simple_reference"")|`Reference(`With_text,_,_)->Token.describe(`Begin_reference_with_replacement_text"")|`Link_->Token.describe(`Begin_link_with_replacement_text"")|`Heading(level,_,_)->Token.describe(`Begin_section_heading(level,None))letleaf_inline_element:status->ast_leaf_inline_elementwith_location->Comment.leaf_inline_elementwith_location=funstatuselement->matchelementwith|{value=(`Word_|`Code_span_);_}aselement->element|{value=`Space_;_}->Location.sameelement`Space|{value=`Raw_markup(Some"html",s);_}->Location.sameelement(`Raw_markup(`Html,s))|{value=`Raw_markup(target,s);location}->leterror=matchtargetwith|Someinvalid_target->Parse_error.invalid_raw_markup_targetinvalid_targetlocation|None->Parse_error.default_raw_markup_target_not_supportedlocationinError.warningstatus.warningserror;Location.sameelement(`Code_spans)letrecnon_link_inline_element:status->surrounding:_->Ast.inline_elementwith_location->Comment.non_link_inline_elementwith_location=funstatus~surroundingelement->matchelementwith|{value=#ast_leaf_inline_element;_}aselement->(leaf_inline_elementstatuselement:>Comment.non_link_inline_elementwith_location)|{value=`Styled(style,content);_}->`Styled(style,non_link_inline_elementsstatus~surroundingcontent)|>Location.sameelement|{value=`Reference(_,_,content);_}|{value=`Link(_,content);_}aselement->Parse_error.not_allowed~what:(describe_elementelement.value)~in_what:(describe_elementsurrounding)element.location|>Error.warningstatus.warnings;`Styled(`Emphasis,non_link_inline_elementsstatus~surroundingcontent)|>Location.sameelementandnon_link_inline_elementsstatus~surroundingelements=List.map(non_link_inline_elementstatus~surrounding)elementsletrecinline_element:status->Ast.inline_elementwith_location->Comment.inline_elementwith_location=funstatuselement->matchelementwith|{value=#ast_leaf_inline_element;_}aselement->(leaf_inline_elementstatuselement:>Comment.inline_elementwith_location)|{value=`Styled(style,content);location}->`Styled(style,inline_elementsstatuscontent)|>Location.atlocation|{value=`Reference(kind,target,content)asvalue;location}->let{Location.value=target;location=target_location}=targetinbeginmatchReference.parsestatus.warningstarget_locationtargetwith|Result.Oktarget->letcontent=non_link_inline_elementsstatus~surrounding:valuecontentinLocation.atlocation(`Reference(target,content))|Result.Errorerror->Error.warningstatus.warningserror;letplaceholder=matchkindwith|`Simple->`Code_spantarget|`With_text->`Styled(`Emphasis,content)ininline_elementstatus(Location.atlocationplaceholder)end|{value=`Link(target,content)asvalue;location}->`Link(target,non_link_inline_elementsstatus~surrounding:valuecontent)|>Location.atlocationandinline_elementsstatuselements=List.map(inline_elementstatus)elementsletrecnestable_block_element:status->Ast.nestable_block_elementwith_location->Comment.nestable_block_elementwith_location=funstatuselement->matchelementwith|{value=`Paragraphcontent;location}->Location.atlocation(`Paragraph(inline_elementsstatuscontent))|{value=`Code_block_;_}|{value=`Verbatim_;_}aselement->element|{value=`Modulesmodules;location}->letmodules=List.fold_left(funacc{Location.value;location}->matchReference.read_mod_longidentstatus.warningslocationvaluewith|Result.Okr->r::acc|Result.Errorerror->Error.warningstatus.warningserror;acc)[]modules|>List.revinLocation.atlocation(`Modulesmodules)|{value=`List(kind,_syntax,items);location}->`List(kind,List.map(nestable_block_elementsstatus)items)|>Location.atlocationandnestable_block_elementsstatuselements=List.map(nestable_block_elementstatus)elementslettag:location:Location.span->status->Ast.tag->(Comment.block_elementwith_location,Ast.block_elementwith_location)Result.result=fun~locationstatustag->letokt=Result.Ok(Location.atlocation(`Tagt))inmatchtagwith|`Author_|`Since_|`Version_|`Inline|`Open|`Closedastag->oktag|`Canonical{value=s;location=r_location}->letpath=Reference.read_path_longidentr_locationsinletmodule_=Reference.read_mod_longidentstatus.warningsr_locationsinbeginmatchpath,module_with|Result.Okpath,Result.Okmodule_->ok(`Canonical(path,module_))|Result.Errore,_|Result.Ok_,Result.Errore->Error.warningstatus.warningse;letplaceholder=[`Word"@canonical";`Space" ";`Code_spans]inletplaceholder=List.map(Location.atlocation)placeholderinError(Location.atlocation(`Paragraphplaceholder))end|`Deprecatedcontent->ok(`Deprecated(nestable_block_elementsstatuscontent))|`Param(name,content)->ok(`Param(name,nestable_block_elementsstatuscontent))|`Raise(name,content)->ok(`Raise(name,nestable_block_elementsstatuscontent))|`Returncontent->ok(`Return(nestable_block_elementsstatuscontent))|`See(kind,target,content)->ok(`See(kind,target,nestable_block_elementsstatuscontent))|`Before(version,content)->ok(`Before(version,nestable_block_elementsstatuscontent))(* When the user does not give a section heading a label (anchor), we generate
one from the text in the heading. This is the common case. This involves
simply scanning the AST for words, lowercasing them, and joining them with
hyphens.
This must be done in the parser (i.e. early, not at HTML/other output
generation time), so that the cross-referencer can see these anchors. *)letgenerate_heading_label:Comment.link_content->string=funcontent->(* Code spans can contain spaces, so we need to replace them with hyphens. We
also lowercase all the letters, for consistency with the rest of this
procedure. *)letreplace_spaces_with_hyphens_and_lowercases=letresult=Bytes.create(String.lengths)ins|>String.iteribeginfunindexc->letc=matchcwith|' '|'\t'|'\r'|'\n'->'-'|_->Char.lowercase_asciicinBytes.setresultindexcend;Bytes.unsafe_to_stringresultin(* Perhaps this should be done using a [Buffer.t]; we can switch to that as
needed. *)letrecscan_inline_elementsanchor=function|[]->anchor|element::more->letanchor=matchelement.Location.valuewith|`Space->anchor^"-"|`Wordw->anchor^(String.lowercase_asciiw)|`Code_spanc->anchor^(replace_spaces_with_hyphens_and_lowercasec)|`Raw_markup_->(* TODO Perhaps having raw markup in a section heading should be an
error? *)anchor|`Styled(_,content)->scan_inline_elementsanchorcontentinscan_inline_elementsanchormoreinscan_inline_elements""contentletsection_heading:status->top_heading_level:intoption->Location.span->[`Headingof_]->intoption*(Comment.block_elementwith_location)=funstatus~top_heading_levellocationheading->let`Heading(level,label,content)=headinginletcontent=non_link_inline_elementsstatus~surrounding:headingcontentinletlabel=matchlabelwith|Somelabel->label|None->generate_heading_labelcontentinletlabel=`Label(status.parent_of_sections,Odoc_model.Names.LabelName.of_stringlabel)inmatchstatus.sections_allowed,levelwith|`None,_any_level->Error.warningstatus.warnings(Parse_error.headings_not_allowedlocation);letcontent=(content:>(Comment.inline_elementwith_location)list)inletelement=Location.atlocation(`Paragraph[Location.atlocation(`Styled(`Bold,content))])intop_heading_level,element|`No_titles,0->Error.warningstatus.warnings(Parse_error.titles_not_allowedlocation);letelement=`Heading(`Title,label,content)inletelement=Location.atlocationelementinlettop_heading_level=matchtop_heading_levelwith|None->Somelevel|some->someintop_heading_level,element|_,level->letlevel'=matchlevelwith|0->`Title|1->`Section|2->`Subsection|3->`Subsubsection|4->`Paragraph|5->`Subparagraph|_->Error.warningstatus.warnings(Parse_error.bad_heading_levellevellocation);(* Implicitly promote to level-5. *)`Subparagraphinbeginmatchtop_heading_levelwith|Sometop_levelwhenstatus.sections_allowed=`All&&level<=top_level&&level<=5->Error.warningstatus.warnings(Parse_error.heading_level_should_be_lower_than_top_levelleveltop_levellocation)|_->()end;letelement=`Heading(level',label,content)inletelement=Location.atlocationelementinlettop_heading_level=matchtop_heading_levelwith|None->Somelevel|some->someintop_heading_level,elementletvalidate_first_page_headingstatusast_element=matchstatus.parent_of_sectionswith|`Page({file;_},_)->beginmatchast_elementwith|{Location.value=`Heading(_,_,_);_}->()|_invalid_ast_element->letfilename=Odoc_model.Root.Odoc_file.namefile^".mld"inError.warningstatus.warnings(Parse_error.page_heading_requiredfilename)end|_not_a_page->()lettop_level_block_elements:status->(Ast.block_elementwith_location)list->(Comment.block_elementwith_location)list=funstatusast_elements->letrectraverse:top_heading_level:intoption->(Comment.block_elementwith_location)list->(Ast.block_elementwith_location)list->(Comment.block_elementwith_location)list=fun~top_heading_levelcomment_elements_accast_elements->matchast_elementswith|[]->List.revcomment_elements_acc|ast_element::ast_elements->(* The first [ast_element] in pages must be a title or section heading. *)ifstatus.sections_allowed=`All&&top_heading_level=Nonethenbeginvalidate_first_page_headingstatusast_elementend;matchast_elementwith|{value=#Ast.nestable_block_element;_}aselement->letelement=nestable_block_elementstatuselementinletelement=(element:>Comment.block_elementwith_location)intraverse~top_heading_level(element::comment_elements_acc)ast_elements|{value=`Tagthe_tag;location}->beginmatchtag~locationstatusthe_tagwith|Result.Okelement->traverse~top_heading_level(element::comment_elements_acc)ast_elements|Result.Errorplaceholder->traverse~top_heading_levelcomment_elements_acc(placeholder::ast_elements)end|{value=`Heading_asheading;_}->lettop_heading_level,element=section_headingstatus~top_heading_levelast_element.Location.locationheadingintraverse~top_heading_level(element::comment_elements_acc)ast_elementsinlettop_heading_level=(* Non-page documents have a generated title. *)matchstatus.parent_of_sectionswith|`Page_->None|_parent_with_generated_title->Some0intraverse~top_heading_level[]ast_elementsletast_to_commentwarnings~sections_allowed~parent_of_sectionsast=letstatus={warnings;sections_allowed;parent_of_sections;}intop_level_block_elementsstatusast