123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239(*---------------------------------------------------------------------------
Copyright (c) 2020 The b0 programmers. All rights reserved.
SPDX-License-Identifier: ISC
---------------------------------------------------------------------------*)openB0_stdletexit_b0_file_error=121(* See B0_driver.Exit.b0_file_error *)(* Names *)typename=stringtypequalified_name=stringletpp_name=Fmt.codeletsep="."(* Note this has to be one byte. *)letlib_root=sepletis_name_validname=name<>""&&matchString.index_optnamesep.[0]with|None->true|Some_->false(*
let check_scope_name name = match String.index_opt name sep.[0] with
| None -> name
| Some _ -> Fmt.invalid_arg "Illegal scope name %S: cannot contain %s" name sep
*)(* Errors *)exceptionErrorofstringletraise_errorfmt=Fmt.kstr(funs->raise(Errors))fmtletraise_no_scope_error~kind~name=raise_error"No open file or library scope to define %s %a"kindpp_namename(* Scopes *)typefile={qname:qualified_name;(* The fully qualified name scope. *)file:Fpath.t;dir:Fpath.t}typelib={module':string;qname:qualified_name;}typet=(* XXX add a case for Root ? *)|Liboflib|Fileoffile*filelist(* parents, from nearest to root *)letequal=Stdlib.(=)letfile=functionFile(file,_)->Somefile.file|Lib_->Noneletdir=functionFile(file,_)->Somefile.dir|Lib_->Noneletis_root=functionFile({qname=""},_)->true|_->falseletpath=function|Liblib->String.split_on_char'.'lib.qname|File({qname},_)->ifqname=""then[]elseString.split_on_char'.'qnameletraise_invalid_name_error~kind~name=matchnamewith|""->raise_error"The empty string is not a valid %s name"kind|name->raise_error"%a is not a valid %s name, dots are not allowed"pp_namenamekindletraise_duplicate_error~kind~name=raise_error"%s %a already defined in scope."(String.Ascii.capitalizekind)pp_namenameletqualify_namescope~kindname=matchscopewith|File({qname;_},_)->ifnot(is_name_validname)thenraise_invalid_name_error~kind~nameelseifqname<>""thenString.concatsep[qname;name]elsename|Liblib->(* Allow to use lib scope name without [sep] as a name *)ifname=""thenlib.qnameelseifnot(is_name_validname)thenraise_invalid_name_error~kind~nameelseString.concatsep[lib.qname;name]letmake_unique_qualified_namescope~defs~kindname=letqname=qualify_namescope~kindnameinifString.Map.memqnamedefsthenraise_duplicate_error~kind~nameelseqname(* Scope state *)letcurrent=refNoneletsealed=reffalseletlist=ref[]letcheck_no_scope()=match!currentwith|None->()|SomeLiblib->Fmt.invalid_arg"Unclosed library scope %s"lib.qname|SomeFile(file,_)->Fmt.invalid_arg"Unclosed file scope %s %a"file.qnameFpath.ppfile.file(* Sealing *)exceptionAfter_sealofstringletseal()=(* XXX it would be nice to remove the set_uncaught_exception_handler
added by [open_root]. Doable once we require OCaml 4.11 see
https://github.com/ocaml/ocaml/issues/9248 *)check_no_scope();sealed:=trueletsealed()=!sealedletraise_after_sealfmt=Fmt.kstr(funs->raise(After_seals))fmtletraise_create_after_seal~kind~name=raise_after_seal"%s %a illegaly created after b0 file initialization."(String.Ascii.capitalizekind)pp_namename(* Scoping *)letcurrent_scope_prefix()=match!currentwith|None->""|SomeLiblib->lib.qname|SomeFile(file,_)->file.qnameletqualify_name_in_current~kindname=match!currentwith|None->name|Somescope->qualify_namescope~kindnameletclose()=match!currentwith|None->invalid_arg"No scope to close"|SomeLib_->current:=None|SomeFile(file,parents)->list:=(file.qname,file.file)::!list;letparent=matchparentswith|[]->None|file::parents->Some(File(file,parents))incurrent:=parentletopen_lib~module'lib=(* XXX why don't we check for dots here ? *)iflib=""thenFmt.invalid_arg"Library scope name cannot be empty"elseletqname=String.concat""[lib_root;lib]incheck_no_scope();current:=Some(Lib{module';qname})(* File scopes *)letroot_scopefile=File({qname="";file;dir=Fpath.parentfile},[])letcurrent_is_root()=match!currentwith|Somescope->is_rootscope|_->falseletname_list()=ifsealed()thenList.rev!listelseinvalid_arg"B0_scope.seal () has not been called yet."letpp_uncaught_exnppf(exn,bt)=letcurrent_locationuse_bt=letlocation_in_backtracefilebt=matchPrintexc.backtrace_slotsbtwith|None->None|Someslots->(* find earliest slot that has [file] *)letrecloopfilefoundslotsimax=matchi>maxwith|true->found|false->matchPrintexc.Slot.locationslots.(i)with|None->loopfilefoundslots(i+1)max|Someloc->matchString.equalloc.Printexc.filenamefilewith|false->loopfilefoundslots(i+1)max|true->loopfile(Someloc)slots(i+1)maxinloop(Fpath.to_stringfile)Noneslots0(Array.lengthslots-1)inmatch!currentwith|None->invalid_arg"no current scope"|SomeLiblib->Fmt.str"Library %a:"pp_namelib.qname|SomeFile(file,_)->letloc=matchuse_btwith|None->"line 1"|Somebt->matchlocation_in_backtracefile.filebtwith|None->"line 1"|Someloc->Fmt.str"line %d, characters %d-%d"loc.Printexc.line_numberloc.Printexc.start_charloc.Printexc.end_charinFmt.str"File %S, %s:"(Fpath.to_stringfile.file)locinletpp_error_labelppf()=Fmt.st[`Fg`Red;`Bold]ppf"Error"inletpp_errorppf(err,bt)=Fmt.pfppf"@[<v>%s@,%a: %s@]"(current_location(Somebt))pp_error_label()errinletpp_uncaughtppf(exn,bt)=Fmt.pfppf"@[<v>%s@,%a: b0 file raised an uncaught exception.@, @[<v>%a@]@]"(current_locationNone)pp_error_label()Fmt.exn_backtrace(exn,bt)inmatchexnwith|Errorerr->pp_errorppf(err,bt)|exn->pp_uncaughtppf(exn,bt)letopen_rootfile=letcatch_exnexnbt=Fmt.epr"@[%a@]@."pp_uncaught_exn(exn,bt);Stdlib.exitexit_b0_file_errorincurrent:=Some(root_scopefile);Printexc.record_backtracetrue;Printexc.set_uncaught_exception_handlercatch_exnletopen_filenamefile=match!currentwith|SomeFile({qname;_}asparent,parents)->letqname=ifqname<>""thenString.concatsep[qname;name]elsenameinletnew'={qname;file;dir=Fpath.parentfile}incurrent:=Some(File(new',parent::parents))|_->Fmt.invalid_arg"No root scope found to open scope %s for file %a"nameFpath.ppfile(* Current *)letcurrent()=!currentletis_currentscope=matchcurrent()with|None->false|Somecurrent->equalscopecurrentletcurrent_make_unique_qualified_name~defs~kindname=matchcurrent()with|Somescope->letqname=make_unique_qualified_namescope~defs~kindnameinqname,scope|Nonewhensealed()->raise_create_after_seal~kind~name|None->raise_no_scope_error~kind~name