diff --git a/asai-examples/Syslib/Logger/Code/index.html b/asai-examples/Syslib/Logger/Code/index.html index 437fe2b..4e24583 100644 --- a/asai-examples/Syslib/Logger/Code/index.html +++ b/asai-examples/Syslib/Logger/Code/index.html @@ -1,2 +1,2 @@ -
Logger.Code
val default_severity : t -> Asai.Diagnostic.severity
val to_string : t -> string
Logger.Code
val default_severity : t -> Asai.Diagnostic.severity
val to_string : t -> string
Syslib.Logger
module Code : sig ... end
include sig ... end
val emit :
- ?severity:Asai.Diagnostic.severity ->
- ?loc:Asai.Span.t ->
- ?backtrace:Asai.Diagnostic.backtrace ->
- ?additional_messages:Asai.Diagnostic.message list ->
+Logger (asai-examples.Syslib.Logger) Module Syslib.Logger
module Code : sig ... end
include sig ... end
val emit :
+ ?severity:Asai.Diagnostic.severity ->
+ ?loc:Asai.Span.t ->
+ ?backtrace:Asai.Diagnostic.backtrace ->
+ ?additional_messages:Asai.Diagnostic.message list ->
Code.t ->
string ->
unit
val emitf :
- ?severity:Asai.Diagnostic.severity ->
- ?loc:Asai.Span.t ->
- ?backtrace:Asai.Diagnostic.backtrace ->
- ?additional_messages:Asai.Diagnostic.message list ->
+ ?severity:Asai.Diagnostic.severity ->
+ ?loc:Asai.Span.t ->
+ ?backtrace:Asai.Diagnostic.backtrace ->
+ ?additional_messages:Asai.Diagnostic.message list ->
Code.t ->
('a, Stdlib.Format.formatter, unit, unit) Stdlib.format4 ->
'a
val emit_diagnostic : Code.t Asai.Diagnostic.t -> unit
val fatal :
- ?severity:Asai.Diagnostic.severity ->
- ?loc:Asai.Span.t ->
- ?backtrace:Asai.Diagnostic.backtrace ->
- ?additional_messages:Asai.Diagnostic.message list ->
+ ?severity:Asai.Diagnostic.severity ->
+ ?loc:Asai.Span.t ->
+ ?backtrace:Asai.Diagnostic.backtrace ->
+ ?additional_messages:Asai.Diagnostic.message list ->
Code.t ->
string ->
'a
val fatalf :
- ?severity:Asai.Diagnostic.severity ->
- ?loc:Asai.Span.t ->
- ?backtrace:Asai.Diagnostic.backtrace ->
- ?additional_messages:Asai.Diagnostic.message list ->
+ ?severity:Asai.Diagnostic.severity ->
+ ?loc:Asai.Span.t ->
+ ?backtrace:Asai.Diagnostic.backtrace ->
+ ?additional_messages:Asai.Diagnostic.message list ->
Code.t ->
('a, Stdlib.Format.formatter, unit, 'b) Stdlib.format4 ->
- 'a
val fatal_diagnostic : Code.t Asai.Diagnostic.t -> 'a
val get_backtrace : unit -> Asai.Diagnostic.backtrace
val with_backtrace : Asai.Diagnostic.backtrace -> (unit -> 'a) -> 'a
val trace : ?loc:Asai.Span.t -> string -> (unit -> 'a) -> 'a
val tracef :
- ?loc:Asai.Span.t ->
+ 'a
val fatal_diagnostic : Code.t Asai.Diagnostic.t -> 'a
val get_backtrace : unit -> Asai.Diagnostic.backtrace
val with_backtrace : Asai.Diagnostic.backtrace -> (unit -> 'a) -> 'a
val trace : ?loc:Asai.Span.t -> string -> (unit -> 'a) -> 'a
val tracef :
+ ?loc:Asai.Span.t ->
('a, Stdlib.Format.formatter, unit, (unit -> 'b) -> 'b) Stdlib.format4 ->
- 'a
val trace_text : ?loc:Asai.Span.t -> Asai.Diagnostic.text -> (unit -> 'a) -> 'a
val trace_message : Asai.Diagnostic.message -> (unit -> 'a) -> 'a
val get_loc : unit -> Asai.Span.t option
val with_loc : Asai.Span.t option -> (unit -> 'a) -> 'a
val merge_loc : Asai.Span.t option -> (unit -> 'a) -> 'a
val diagnostic :
- ?severity:Asai.Diagnostic.severity ->
- ?loc:Asai.Span.t ->
- ?backtrace:Asai.Diagnostic.backtrace ->
- ?additional_messages:Asai.Diagnostic.message list ->
+ 'a
val trace_text : ?loc:Asai.Span.t -> Asai.Diagnostic.text -> (unit -> 'a) -> 'a
val trace_message : Asai.Diagnostic.message -> (unit -> 'a) -> 'a
val get_loc : unit -> Asai.Span.t option
val with_loc : Asai.Span.t option -> (unit -> 'a) -> 'a
val merge_loc : Asai.Span.t option -> (unit -> 'a) -> 'a
val diagnostic :
+ ?severity:Asai.Diagnostic.severity ->
+ ?loc:Asai.Span.t ->
+ ?backtrace:Asai.Diagnostic.backtrace ->
+ ?additional_messages:Asai.Diagnostic.message list ->
Code.t ->
string ->
Code.t Asai.Diagnostic.t
val diagnosticf :
- ?severity:Asai.Diagnostic.severity ->
- ?loc:Asai.Span.t ->
- ?backtrace:Asai.Diagnostic.backtrace ->
- ?additional_messages:Asai.Diagnostic.message list ->
+ ?severity:Asai.Diagnostic.severity ->
+ ?loc:Asai.Span.t ->
+ ?backtrace:Asai.Diagnostic.backtrace ->
+ ?additional_messages:Asai.Diagnostic.message list ->
Code.t ->
('a, Stdlib.Format.formatter, unit, Code.t Asai.Diagnostic.t) Stdlib.format4 ->
'a
val kdiagnosticf :
- ?severity:Asai.Diagnostic.severity ->
- ?loc:Asai.Span.t ->
- ?backtrace:Asai.Diagnostic.backtrace ->
- ?additional_messages:Asai.Diagnostic.message list ->
+ ?severity:Asai.Diagnostic.severity ->
+ ?loc:Asai.Span.t ->
+ ?backtrace:Asai.Diagnostic.backtrace ->
+ ?additional_messages:Asai.Diagnostic.message list ->
(Code.t Asai.Diagnostic.t -> 'b) ->
Code.t ->
('a, Stdlib.Format.formatter, unit, 'b) Stdlib.format4 ->
'a
val run :
- ?init_loc:Asai.Span.t ->
- ?init_backtrace:Asai.Diagnostic.backtrace ->
- emit:(Code.t Asai.Diagnostic.t -> unit) ->
- fatal:(Code.t Asai.Diagnostic.t -> 'a) ->
+ ?init_loc:Asai.Span.t ->
+ ?init_backtrace:Asai.Diagnostic.backtrace ->
+ emit:(Code.t Asai.Diagnostic.t -> unit) ->
+ fatal:(Code.t Asai.Diagnostic.t -> 'a) ->
(unit -> 'a) ->
'a
val adopt :
('code Asai.Diagnostic.t -> Code.t Asai.Diagnostic.t) ->
- (?init_loc:Asai.Span.t ->
- ?init_backtrace:Asai.Diagnostic.backtrace ->
- emit:('code Asai.Diagnostic.t -> unit) ->
- fatal:('code Asai.Diagnostic.t -> 'a) ->
+ (?init_loc:Asai.Span.t ->
+ ?init_backtrace:Asai.Diagnostic.backtrace ->
+ emit:('code Asai.Diagnostic.t -> unit) ->
+ fatal:('code Asai.Diagnostic.t -> 'a) ->
(unit -> 'a) ->
'a) ->
(unit -> 'a) ->
'a
val try_with :
- ?emit:(Code.t Asai.Diagnostic.t -> unit) ->
- ?fatal:(Code.t Asai.Diagnostic.t -> 'a) ->
+ ?emit:(Code.t Asai.Diagnostic.t -> unit) ->
+ ?fatal:(Code.t Asai.Diagnostic.t -> 'a) ->
(unit -> 'a) ->
'a
val register_printer :
([ `Emit of Code.t Asai.Diagnostic.t
| `Fatal of Code.t Asai.Diagnostic.t
| `Trace ] ->
string option) ->
- unit
\ No newline at end of file
+ unit
Syslib.Operations
Syslib.Operations
Syslib
module Logger : sig ... end
module Operations : sig ... end
Syslib
module Logger : sig ... end
module Operations : sig ... end
The entry point of this library is the module: Syslib
.
The entry point of this library is the module: Syslib
.
Asai.Diagnostic
The definition of diagnostics and some utility functions.
module type Code = sig ... end
The signature of message code. An implementer should specify the message code used in their library or application.
The type of text.
When we render a diagnostic, the layout engine of the rendering backend should be the one making layout choices. Therefore, we cannot pass already formatted strings. Instead, a text is defined to be a function that takes a formatter and uses it to render the content. The following two conditions must be satisfied:
\n
). It is okay to have break hints (such as @,
and @
) but not literal control characters. This means you should avoid pre-formatted strings, and if you must use them, use text
to convert newline characters. Control characters include `U+0000-001F` (C0 controls), `U+007F` (backspace) and `U+0080-009F` (C1 controls). These characters are banned because they would mess up the cursor position.type message = text Span.located
A message is a located text
.
type backtrace = message Bwd.bwd
A backtrace is a (backward) list of messages.
type 'code t = {
severity : severity;
Severity of the diagnostic.
*)code : 'code;
The message code.
*)message : message;
The main message.
*)backtrace : backtrace;
The backtrace leading to this diagnostic.
*)additional_messages : message list;
Additional messages relevant to the main message that are not part of the backtrace.
*)}
The type of diagnostics.
val text : string -> text
text str
converts the string str
into a text, converting each '\n'
into a call to Format
.pp_force_newline.
val textf : ('a, Stdlib.Format.formatter, unit, text) Stdlib.format4 -> 'a
textf format ...
constructs a text. It is an alias of Format
.dprintf. Note that there should not be any literal control characters (e.g., literal newline characters).
val ktextf :
+Diagnostic (asai.Asai.Diagnostic) Module Asai.Diagnostic
The definition of diagnostics and some utility functions.
Types
module type Code = sig ... end
The signature of message code. An implementer should specify the message code used in their library or application.
The type of text.
When we render a diagnostic, the layout engine of the rendering backend should be the one making layout choices. Therefore, we cannot pass already formatted strings. Instead, a text is defined to be a function that takes a formatter and uses it to render the content. The following two conditions must be satisfied:
- All string (and character) literals must be encoded using UTF-8.
- All string (and character) literals must not contain control characters (such as the newline character
\n
). It is okay to have break hints (such as @,
and @
) but not literal control characters. This means you should avoid pre-formatted strings, and if you must use them, use text
to convert newline characters. Control characters include `U+0000-001F` (C0 controls), `U+007F` (backspace) and `U+0080-009F` (C1 controls). These characters are banned because they would mess up the cursor position.
type message = text Span.located
A message is a located text
.
type backtrace = message Bwd.bwd
A backtrace is a (backward) list of messages.
type 'code t = {
severity : severity;
(*Severity of the diagnostic.
*)code : 'code;
(*The message code.
*)message : message;
(*The main message.
*)backtrace : backtrace;
(*The backtrace leading to this diagnostic.
*)additional_messages : message list;
(*Additional messages relevant to the main message that are not part of the backtrace.
*)
}
The type of diagnostics.
Constructing Messages
val text : string -> text
text str
converts the string str
into a text, converting each '\n'
into a call to Format.pp_force_newline
.
val textf : ('a, Stdlib.Format.formatter, unit, text) Stdlib.format4 -> 'a
textf format ...
constructs a text. It is an alias of Format.dprintf
. Note that there should not be any literal control characters (e.g., literal newline characters).
val ktextf :
(text -> 'b) ->
('a, Stdlib.Format.formatter, unit, 'b) Stdlib.format4 ->
- 'a
ktextf kont format ...
is kont (textf code format ...)
. It is an alias of Format
.kdprintf.
val messagef :
- ?loc:Span.t ->
+ 'a
ktextf kont format ...
is kont (textf code format ...)
. It is an alias of Format.kdprintf
.
messagef format ...
constructs a message. Note that there should not be any literal control characters (e.g., literal newline characters).
val kmessagef :
- ?loc:Span.t ->
+ ?loc:Span.t ->
(message -> 'b) ->
('a, Stdlib.Format.formatter, unit, 'b) Stdlib.format4 ->
'a
kmessagef kont format ...
is kont (messagef code format ...)
.
Constructing Diagnostics
val make :
- ?loc:Span.t ->
- ?backtrace:backtrace ->
- ?additional_messages:message list ->
+ ?loc:Span.t ->
+ ?backtrace:backtrace ->
+ ?additional_messages:message list ->
severity ->
'code ->
string ->
'code t
make severity code str
constructs a diagnostic with the message str
.
Example:
make Warning `ChiError "Your Ch'i is critically low"
val makef :
- ?loc:Span.t ->
- ?backtrace:backtrace ->
- ?additional_messages:message list ->
+ ?loc:Span.t ->
+ ?backtrace:backtrace ->
+ ?additional_messages:message list ->
severity ->
'code ->
('a, Stdlib.Format.formatter, unit, 'code t) Stdlib.format4 ->
'a
makef severity code format ...
is make severity code (messagef format ...)
. It formats the message and constructs a diagnostic out of it.
Example:
makef Warning `ChiError "Your %s is critically low" "Ch'i"
val kmakef :
- ?loc:Span.t ->
- ?backtrace:backtrace ->
- ?additional_messages:message list ->
+ ?loc:Span.t ->
+ ?backtrace:backtrace ->
+ ?additional_messages:message list ->
('code t -> 'b) ->
severity ->
'code ->
('a, Stdlib.Format.formatter, unit, 'b) Stdlib.format4 ->
'a
kmakef kont severity code format ...
is kont (makef severity code format ...)
.
val of_message :
- ?backtrace:backtrace ->
- ?additional_messages:message list ->
+ ?backtrace:backtrace ->
+ ?additional_messages:message list ->
severity ->
'code ->
message ->
- 'code t
of_message severity code message
constructs a diagnostic with the message
.
Example:
make Warning `ChiError @@ message "Your Ch'i is critically low"
Other Helper Functions
A convenience function that maps the message code. This is helpful when using Logger.S.adopt
.
val string_of_severity : severity -> string
\ No newline at end of file
+ 'code t
of_message severity code message
constructs a diagnostic with the message
.
Example:
make Warning `ChiError @@ message "Your Ch'i is critically low"
A convenience function that maps the message code. This is helpful when using Logger.S.adopt
.
val string_of_severity : severity -> string
Diagnostic.Code
The signature of message code. An implementer should specify the message code used in their library or application.
The default severity of the code. The severity of a message is about whether the message is an error or a warning, etc. To clarify, it is about how serious the message is to the end user, not whether the program should stop or continue. The severity may be overwritten at the time of issuing a message to the end user.
val to_string : t -> string
A concise, ideally Google-able string representation of each message code. Detailed or long descriptions of code should be avoided. For example, E001
works better than type-checking error
. The shorter, the better.
Diagnostic.Code
The signature of message code. An implementer should specify the message code used in their library or application.
The default severity of the code. The severity of a message is about whether the message is an error or a warning, etc. To clarify, it is about how serious the message is to the end user, not whether the program should stop or continue. The severity may be overwritten at the time of issuing a message to the end user.
val to_string : t -> string
A concise, ideally Google-able string representation of each message code. Detailed or long descriptions of code should be avoided. For example, E001
works better than type-checking error
. The shorter, the better.
Asai.Explication
The definition of highlighted text suitable for rendering. You probably do not need this module unless you want to create your own backend.
type 'style segment = (string, 'style) styled
A segment is a styled string from the user content.
type 'style block = {
start_line_num : int;
The starting 1-indexed line number of a block.
*)lines : 'style line list;
}
A block is a collection of consecutive lines.
type 'style part = {
file_path : string;
The file path of a part.
*)blocks : 'style block list;
The blocks within a part.
*)}
A part consists of multiple blocks from the same file. These blocks should be non-overlapping and sorted by importance or the textual order.
type 'style t = 'style part list
Highlighted texts instead of spans.
val style : 'style -> 'value -> ('value, 'style) styled
val dump :
+Explication (asai.Asai.Explication) Module Asai.Explication
The definition of highlighted text suitable for rendering. You probably do not need this module unless you want to create your own backend.
Types
type 'style segment = (string, 'style) styled
A segment is a styled string from the user content.
type 'style block = {
start_line_num : int;
(*The starting 1-indexed line number of a block.
*)lines : 'style line list;
}
A block is a collection of consecutive lines.
type 'style part = {
file_path : string;
(*The file path of a part.
*)blocks : 'style block list;
(*The blocks within a part.
*)
}
A part consists of multiple blocks from the same file. These blocks should be non-overlapping and sorted by importance or the textual order.
type 'style t = 'style part list
Highlighted texts instead of spans.
Helper Functions
val style : 'style -> 'value -> ('value, 'style) styled
Debugging
val dump :
(Stdlib.Format.formatter -> 'style -> unit) ->
Stdlib.Format.formatter ->
'style t ->
- unit
Ugly printer for debugging
\ No newline at end of file
+ unit
Ugly printer for debugging
Make._
val load : string -> file
load file_path
loads the resource at file_path
.
val length : file -> int
length file_path
gets the size of the file.
val unsafe_get : file -> int -> char
unsafe_get file_path i
reads the ith byte of the file without checking the file size.
Make._
val load : string -> file
load file_path
loads the resource at file_path
.
val length : file -> int
length file_path
gets the size of the file.
val unsafe_get : file -> int -> char
unsafe_get file_path i
reads the ith byte of the file without checking the file size.
Make.Style
val default : t
default
is the default style, meaning no highlighting is applied. It should be the unit of compose
.
val is_default : t -> bool
is_default s
checks if the style is the default style. It should be equivalent to equal default s
.
Compose two styles into one. The operator should form a commutative group with default
being its unit.
val dump : Stdlib.Format.formatter -> t -> unit
Ugly printer for debugging
Make.Style
val default : t
default
is the default style, meaning no highlighting is applied. It should be the unit of compose
.
val is_default : t -> bool
is_default s
checks if the style is the default style. It should be equivalent to equal default s
.
Compose two styles into one. The operator should form a commutative group with default
being its unit.
val dump : Stdlib.Format.formatter -> t -> unit
Ugly printer for debugging
Explicator.Make
Making an explicator.
exception Unexpected_end_of_file of Span.position
Unexpected_end_of_file pos
means the pos
lies beyond the end of file. This usually means the file has been truncated after the parsing.
exception Unexpected_line_num_increment of Span.position
Unexpected_line_num_increment pos
means the line number of pos
is larger than than that of its preceding position during explication, but the explicator did not encounter a newline in between. This usually indicates that there's something wrong with the lexer, or that the file has changed since the parsing.
exception Unexpected_newline of Span.position
Unexpected_newline pos
means the line number of pos
is the same as its preceding position during explication, but the explicator encountered a newline in between. This usually indicates that there's something wrong with the lexer, or that the file has changed since the parsing.
exception Unexpected_position_in_newline of Span.position
Unexpected_position_in_newline pos
means the position pos
is in the middle of a newline. This can happen when the newline consists of multiple bytes, for example 0x0D 0x0A
. It usually indicates that there's something wrong with the lexer, or that the file has changed since the parsing.
val explicate :
- ?line_breaking:[ `Unicode | `Traditional ] ->
- ?block_splitting_threshold:int ->
+Make (asai.Asai.Explicator.Make) Module Explicator.Make
Making an explicator.
Parameters
Signature
exception Unexpected_end_of_file of Span.position
Unexpected_end_of_file pos
means the pos
lies beyond the end of file. This usually means the file has been truncated after the parsing.
exception Unexpected_line_num_increment of Span.position
Unexpected_line_num_increment pos
means the line number of pos
is larger than than that of its preceding position during explication, but the explicator did not encounter a newline in between. This usually indicates that there's something wrong with the lexer, or that the file has changed since the parsing.
exception Unexpected_newline of Span.position
Unexpected_newline pos
means the line number of pos
is the same as its preceding position during explication, but the explicator encountered a newline in between. This usually indicates that there's something wrong with the lexer, or that the file has changed since the parsing.
exception Unexpected_position_in_newline of Span.position
Unexpected_position_in_newline pos
means the position pos
is in the middle of a newline. This can happen when the newline consists of multiple bytes, for example 0x0D 0x0A
. It usually indicates that there's something wrong with the lexer, or that the file has changed since the parsing.
val explicate :
+ ?line_breaking:[ `Unicode | `Traditional ] ->
+ ?block_splitting_threshold:int ->
(Span.t, Style.t) Explication.styled list ->
- Style.t Explication.t
Explicate a list of spans using content from a data reader.
\ No newline at end of file
+ Style.t Explication.t
Explicate a list of spans using content from a data reader.
Asai.Explicator
Turning location information into highlighted text suitable for rendering. You probably do not need this module unless you want to create your own backend.
module type Reader = sig ... end
The signature of data readers.
module type Style = sig ... end
The signature of highlighting styles
module type S = sig ... end
The signature of explicators.
Asai.Explicator
Turning location information into highlighted text suitable for rendering. You probably do not need this module unless you want to create your own backend.
module type Reader = sig ... end
The signature of data readers.
module type Style = sig ... end
The signature of highlighting styles
module type S = sig ... end
The signature of explicators.
Explicator.Reader
The signature of data readers.
val load : string -> file
load file_path
loads the resource at file_path
.
val length : file -> int
length file_path
gets the size of the file.
val unsafe_get : file -> int -> char
unsafe_get file_path i
reads the ith byte of the file without checking the file size.
Explicator.Reader
The signature of data readers.
val load : string -> file
load file_path
loads the resource at file_path
.
val length : file -> int
length file_path
gets the size of the file.
val unsafe_get : file -> int -> char
unsafe_get file_path i
reads the ith byte of the file without checking the file size.
S.Style
val default : t
default
is the default style, meaning no highlighting is applied. It should be the unit of compose
.
val is_default : t -> bool
is_default s
checks if the style is the default style. It should be equivalent to equal default s
.
Compose two styles into one. The operator should form a commutative group with default
being its unit.
val dump : Stdlib.Format.formatter -> t -> unit
Ugly printer for debugging
S.Style
val default : t
default
is the default style, meaning no highlighting is applied. It should be the unit of compose
.
val is_default : t -> bool
is_default s
checks if the style is the default style. It should be equivalent to equal default s
.
Compose two styles into one. The operator should form a commutative group with default
being its unit.
val dump : Stdlib.Format.formatter -> t -> unit
Ugly printer for debugging
Explicator.S
The signature of explicators.
exception Unexpected_end_of_file of Span.position
Unexpected_end_of_file pos
means the pos
lies beyond the end of file. This usually means the file has been truncated after the parsing.
exception Unexpected_line_num_increment of Span.position
Unexpected_line_num_increment pos
means the line number of pos
is larger than than that of its preceding position during explication, but the explicator did not encounter a newline in between. This usually indicates that there's something wrong with the lexer, or that the file has changed since the parsing.
exception Unexpected_newline of Span.position
Unexpected_newline pos
means the line number of pos
is the same as its preceding position during explication, but the explicator encountered a newline in between. This usually indicates that there's something wrong with the lexer, or that the file has changed since the parsing.
exception Unexpected_position_in_newline of Span.position
Unexpected_position_in_newline pos
means the position pos
is in the middle of a newline. This can happen when the newline consists of multiple bytes, for example 0x0D 0x0A
. It usually indicates that there's something wrong with the lexer, or that the file has changed since the parsing.
val explicate :
- ?line_breaking:[ `Unicode | `Traditional ] ->
- ?block_splitting_threshold:int ->
+S (asai.Asai.Explicator.S) Module type Explicator.S
The signature of explicators.
exception Unexpected_end_of_file of Span.position
Unexpected_end_of_file pos
means the pos
lies beyond the end of file. This usually means the file has been truncated after the parsing.
exception Unexpected_line_num_increment of Span.position
Unexpected_line_num_increment pos
means the line number of pos
is larger than than that of its preceding position during explication, but the explicator did not encounter a newline in between. This usually indicates that there's something wrong with the lexer, or that the file has changed since the parsing.
exception Unexpected_newline of Span.position
Unexpected_newline pos
means the line number of pos
is the same as its preceding position during explication, but the explicator encountered a newline in between. This usually indicates that there's something wrong with the lexer, or that the file has changed since the parsing.
exception Unexpected_position_in_newline of Span.position
Unexpected_position_in_newline pos
means the position pos
is in the middle of a newline. This can happen when the newline consists of multiple bytes, for example 0x0D 0x0A
. It usually indicates that there's something wrong with the lexer, or that the file has changed since the parsing.
val explicate :
+ ?line_breaking:[ `Unicode | `Traditional ] ->
+ ?block_splitting_threshold:int ->
(Span.t, Style.t) Explication.styled list ->
- Style.t Explication.t
Explicate a list of spans using content from a data reader.
\ No newline at end of file
+ Style.t Explication.t
Explicate a list of spans using content from a data reader.
Explicator.Style
The signature of highlighting styles
val default : t
default
is the default style, meaning no highlighting is applied. It should be the unit of compose
.
val is_default : t -> bool
is_default s
checks if the style is the default style. It should be equivalent to equal default s
.
Compose two styles into one. The operator should form a commutative group with default
being its unit.
val dump : Stdlib.Format.formatter -> t -> unit
Ugly printer for debugging
Explicator.Style
The signature of highlighting styles
val default : t
default
is the default style, meaning no highlighting is applied. It should be the unit of compose
.
val is_default : t -> bool
is_default s
checks if the style is the default style. It should be equivalent to equal default s
.
Compose two styles into one. The operator should form a commutative group with default
being its unit.
val dump : Stdlib.Format.formatter -> t -> unit
Ugly printer for debugging
Asai.FileReader
An implementation of Explicator.Reader
using memory-mapped file I/O. You probably do not need this module unless you want to create your own backend.
include Explicator.Reader
val load : string -> file
load file_path
loads the resource at file_path
.
val length : file -> int
length file_path
gets the size of the file.
val unsafe_get : file -> int -> char
unsafe_get file_path i
reads the ith byte of the file without checking the file size.
Asai.FileReader
An implementation of Explicator.Reader
using memory-mapped file I/O. You probably do not need this module unless you want to create your own backend.
include Explicator.Reader
val load : string -> file
load file_path
loads the resource at file_path
.
val length : file -> int
length file_path
gets the size of the file.
val unsafe_get : file -> int -> char
unsafe_get file_path i
reads the ith byte of the file without checking the file size.
Make.Code
val default_severity : t -> Diagnostic.severity
The default severity of the code. The severity of a message is about whether the message is an error or a warning, etc. To clarify, it is about how serious the message is to the end user, not whether the program should stop or continue. The severity may be overwritten at the time of issuing a message to the end user.
val to_string : t -> string
A concise, ideally Google-able string representation of each message code. Detailed or long descriptions of code should be avoided. For example, E001
works better than type-checking error
. The shorter, the better.
Make.Code
val default_severity : t -> Diagnostic.severity
The default severity of the code. The severity of a message is about whether the message is an error or a warning, etc. To clarify, it is about how serious the message is to the end user, not whether the program should stop or continue. The severity may be overwritten at the time of issuing a message to the end user.
val to_string : t -> string
A concise, ideally Google-able string representation of each message code. Detailed or long descriptions of code should be avoided. For example, E001
works better than type-checking error
. The shorter, the better.
GitHub.Make
The functor to create a printer for GitHub Actions workflow commands.
module Code : Diagnostic.Code
val print : Code.t Diagnostic.t -> unit
Print a diagnostic as a GitHub Actions workflow command.
GitHub.Make
The functor to create a printer for GitHub Actions workflow commands.
module Code : Diagnostic.Code
val print : Code.t Diagnostic.t -> unit
Print a diagnostic as a GitHub Actions workflow command.
Asai.GitHub
GitHub Actions workflow commands.
module Make (Code : Diagnostic.Code) : sig ... end
The functor to create a printer for GitHub Actions workflow commands.
Asai.GitHub
GitHub Actions workflow commands.
module Make (Code : Diagnostic.Code) : sig ... end
The functor to create a printer for GitHub Actions workflow commands.
Make.Code
val default_severity : t -> Diagnostic.severity
The default severity of the code. The severity of a message is about whether the message is an error or a warning, etc. To clarify, it is about how serious the message is to the end user, not whether the program should stop or continue. The severity may be overwritten at the time of issuing a message to the end user.
val to_string : t -> string
A concise, ideally Google-able string representation of each message code. Detailed or long descriptions of code should be avoided. For example, E001
works better than type-checking error
. The shorter, the better.
Make.Code
val default_severity : t -> Diagnostic.severity
The default severity of the code. The severity of a message is about whether the message is an error or a warning, etc. To clarify, it is about how serious the message is to the end user, not whether the program should stop or continue. The severity may be overwritten at the time of issuing a message to the end user.
val to_string : t -> string
A concise, ideally Google-able string representation of each message code. Detailed or long descriptions of code should be avoided. For example, E001
works better than type-checking error
. The shorter, the better.
Logger.Make
The functor to generate a logger.
module Code : Diagnostic.Code
val emit :
- ?severity:Diagnostic.severity ->
- ?loc:Span.t ->
- ?backtrace:Diagnostic.backtrace ->
- ?additional_messages:Diagnostic.message list ->
+Make (asai.Asai.Logger.Make) Module Logger.Make
The functor to generate a logger.
Parameters
module Code : Diagnostic.Code
Signature
Sending Messages
val emit :
+ ?severity:Diagnostic.severity ->
+ ?loc:Span.t ->
+ ?backtrace:Diagnostic.backtrace ->
+ ?additional_messages:Diagnostic.message list ->
Code.t ->
string ->
unit
emit code str
emits a string and continues the computation.
Example:
Logger.emit `TypeError "This type is extremely unnatural:\nNat"
val emitf :
- ?severity:Diagnostic.severity ->
- ?loc:Span.t ->
- ?backtrace:Diagnostic.backtrace ->
- ?additional_messages:Diagnostic.message list ->
+ ?severity:Diagnostic.severity ->
+ ?loc:Span.t ->
+ ?backtrace:Diagnostic.backtrace ->
+ ?additional_messages:Diagnostic.message list ->
Code.t ->
('a, Stdlib.Format.formatter, unit, unit) Stdlib.format4 ->
'a
emitf code format ...
formats and emits a message, and then continues the computation. Note that there should not be any literal control characters. See Diagnostic.text
.
Example:
Logger.emitf `TypeError "Type %a is too ugly" Syntax.pp tp
val emit_diagnostic : Code.t Diagnostic.t -> unit
Emit a diagnostic and continue the computation.
val fatal :
- ?severity:Diagnostic.severity ->
- ?loc:Span.t ->
- ?backtrace:Diagnostic.backtrace ->
- ?additional_messages:Diagnostic.message list ->
+ ?severity:Diagnostic.severity ->
+ ?loc:Span.t ->
+ ?backtrace:Diagnostic.backtrace ->
+ ?additional_messages:Diagnostic.message list ->
Code.t ->
string ->
'a
fatal code str
aborts the current computation with the string str
.
Example:
Logger.fatal `FileError "Forgot to feed the cat"
val fatalf :
- ?severity:Diagnostic.severity ->
- ?loc:Span.t ->
- ?backtrace:Diagnostic.backtrace ->
- ?additional_messages:Diagnostic.message list ->
+ ?severity:Diagnostic.severity ->
+ ?loc:Span.t ->
+ ?backtrace:Diagnostic.backtrace ->
+ ?additional_messages:Diagnostic.message list ->
Code.t ->
('a, Stdlib.Format.formatter, unit, 'b) Stdlib.format4 ->
'a
fatalf code format ...
constructs a diagnostic and aborts the current computation with the diagnostic. Note that there should not be any literal control characters. See Diagnostic.text
.
Example:
Logger.fatalf `FileError "Failed to write the password to %s" file_path
val fatal_diagnostic : Code.t Diagnostic.t -> 'a
Abort the computation with a diagnostic.
Backtraces
val get_backtrace : unit -> Diagnostic.backtrace
get_backtrace()
returns the current backtrace.
val with_backtrace : Diagnostic.backtrace -> (unit -> 'a) -> 'a
with_backtrace bt f
runs the thunk f
with bt
as the initial backtrace.
Example:
(* running code with a fresh backtrace *)
-with_backtrace Emp @@ fun () -> ...
val trace : ?loc:Span.t -> string -> (unit -> 'a) -> 'a
trace str f
records the string str
and runs the thunk f
with the new backtrace.
val tracef :
- ?loc:Span.t ->
+with_backtrace Emp @@ fun () -> ...
val trace : ?loc:Span.t -> string -> (unit -> 'a) -> 'a
trace str f
records the string str
and runs the thunk f
with the new backtrace.
val tracef :
+ ?loc:Span.t ->
('a, Stdlib.Format.formatter, unit, (unit -> 'b) -> 'b) Stdlib.format4 ->
- 'a
tracef format ... f
formats and records a message as a frame in the backtrace, and runs the thunk f
with the new backtrace. Note that there should not be any literal control characters. See Diagnostic.text
.
val trace_text : ?loc:Span.t -> Diagnostic.text -> (unit -> 'a) -> 'a
trace_text text f
records the message text
and runs the thunk f
with the new backtrace.
val trace_message : Diagnostic.message -> (unit -> 'a) -> 'a
trace_message msg f
records the message msg
and runs the thunk f
with the new backtrace.
Locations
val get_loc : unit -> Span.t option
get_loc()
returns the current location.
val with_loc : Span.t option -> (unit -> 'a) -> 'a
with_loc loc f
runs the thunk f
with loc
as the initial location loc
. Note that with_loc None
will clear the current location, while merge_loc None
will keep it. See merge_loc
.
val merge_loc : Span.t option -> (unit -> 'a) -> 'a
merge_loc loc f
"merges" loc
into the current location and runs the thunk f
. By "merge", it means that if loc
is None
, then the current location is kept; otherwise, it is overwritten. Note that with_loc None
will clear the current location, while merge_loc None
will keep it. See with_loc
.
Constructing Diagnostics
Functions in this section differ from the ones in Diagnostic
(for example, Diagnostic.make
) in that they fill out the current location, the current backtrace, and the severity automatically. (One can still overwrite them with optional arguments.)
val diagnostic :
- ?severity:Diagnostic.severity ->
- ?loc:Span.t ->
- ?backtrace:Diagnostic.backtrace ->
- ?additional_messages:Diagnostic.message list ->
+ 'a
tracef format ... f
formats and records a message as a frame in the backtrace, and runs the thunk f
with the new backtrace. Note that there should not be any literal control characters. See Diagnostic.text
.
val trace_text : ?loc:Span.t -> Diagnostic.text -> (unit -> 'a) -> 'a
trace_text text f
records the message text
and runs the thunk f
with the new backtrace.
val trace_message : Diagnostic.message -> (unit -> 'a) -> 'a
trace_message msg f
records the message msg
and runs the thunk f
with the new backtrace.
Locations
val get_loc : unit -> Span.t option
get_loc()
returns the current location.
val with_loc : Span.t option -> (unit -> 'a) -> 'a
with_loc loc f
runs the thunk f
with loc
as the initial location loc
. Note that with_loc None
will clear the current location, while merge_loc None
will keep it. See merge_loc
.
val merge_loc : Span.t option -> (unit -> 'a) -> 'a
merge_loc loc f
"merges" loc
into the current location and runs the thunk f
. By "merge", it means that if loc
is None
, then the current location is kept; otherwise, it is overwritten. Note that with_loc None
will clear the current location, while merge_loc None
will keep it. See with_loc
.
Constructing Diagnostics
Functions in this section differ from the ones in Diagnostic
(for example, Diagnostic.make
) in that they fill out the current location, the current backtrace, and the severity automatically. (One can still overwrite them with optional arguments.)
val diagnostic :
+ ?severity:Diagnostic.severity ->
+ ?loc:Span.t ->
+ ?backtrace:Diagnostic.backtrace ->
+ ?additional_messages:Diagnostic.message list ->
Code.t ->
string ->
Code.t Diagnostic.t
diagnostic code str
constructs a diagnostic with the message str
along with the backtrace frames recorded via tracef
.
Example:
Logger.diagnostic `TypeError "This\nis\ntoo\nmuch."
val diagnosticf :
- ?severity:Diagnostic.severity ->
- ?loc:Span.t ->
- ?backtrace:Diagnostic.backtrace ->
- ?additional_messages:Diagnostic.message list ->
+ ?severity:Diagnostic.severity ->
+ ?loc:Span.t ->
+ ?backtrace:Diagnostic.backtrace ->
+ ?additional_messages:Diagnostic.message list ->
Code.t ->
('a, Stdlib.Format.formatter, unit, Code.t Diagnostic.t) Stdlib.format4 ->
'a
diagnosticf code format ...
constructs a diagnostic along with the backtrace frames recorded via trace
. Note that there should not be any literal control characters. See Diagnostic.text
.
Example:
Logger.diagnosticf `TypeError "Term %a does not type check, or does it?" Syntax.pp tm
val kdiagnosticf :
- ?severity:Diagnostic.severity ->
- ?loc:Span.t ->
- ?backtrace:Diagnostic.backtrace ->
- ?additional_messages:Diagnostic.message list ->
+ ?severity:Diagnostic.severity ->
+ ?loc:Span.t ->
+ ?backtrace:Diagnostic.backtrace ->
+ ?additional_messages:Diagnostic.message list ->
(Code.t Diagnostic.t -> 'b) ->
Code.t ->
('a, Stdlib.Format.formatter, unit, 'b) Stdlib.format4 ->
'a
kdiagnosticf kont code format ...
is kont (diagnosticf code format ...)
. Note that there should not be any literal control characters. See Diagnostic.text
.
Algebraic Effects
val run :
- ?init_loc:Span.t ->
- ?init_backtrace:Diagnostic.backtrace ->
- emit:(Code.t Diagnostic.t -> unit) ->
- fatal:(Code.t Diagnostic.t -> 'a) ->
+ ?init_loc:Span.t ->
+ ?init_backtrace:Diagnostic.backtrace ->
+ emit:(Code.t Diagnostic.t -> unit) ->
+ fatal:(Code.t Diagnostic.t -> 'a) ->
(unit -> 'a) ->
'a
val adopt :
('code Diagnostic.t -> Code.t Diagnostic.t) ->
- (?init_loc:Span.t ->
- ?init_backtrace:Diagnostic.backtrace ->
- emit:('code Diagnostic.t -> unit) ->
- fatal:('code Diagnostic.t -> 'a) ->
+ (?init_loc:Span.t ->
+ ?init_backtrace:Diagnostic.backtrace ->
+ emit:('code Diagnostic.t -> unit) ->
+ fatal:('code Diagnostic.t -> 'a) ->
(unit -> 'a) ->
'a) ->
(unit -> 'a) ->
@@ -78,10 +78,10 @@
module LibLogger = Lib.Logger
let _ = MainLogger.adopt (Diagnostic.map code_mapper) LibLogger.run @@ fun () -> ...
val try_with :
- ?emit:(Code.t Diagnostic.t -> unit) ->
- ?fatal:(Code.t Diagnostic.t -> 'a) ->
+ ?emit:(Code.t Diagnostic.t -> unit) ->
+ ?fatal:(Code.t Diagnostic.t -> 'a) ->
(unit -> 'a) ->
'a
try_with ~emit ~fatal f
runs the thunk f
, using emit
to intercept non-fatal diagnostics before continuing the computation (see emit
and emitf
), and fatal
to intercept fatal diagnostics that have aborted the computation (see fatal
and fatalf
). The default interceptors re-emit or re-raise the intercepted diagnostics.
val register_printer :
([ `Trace | `Emit of Code.t Diagnostic.t | `Fatal of Code.t Diagnostic.t ] ->
string option) ->
- unit
register_printer p
registers a printer p
via Printexc
.register_printer to convert unhandled internal effects and exceptions into strings for the OCaml runtime system to display. Ideally, all internal effects and exceptions should have been handled by run
and there is no need to use this function, but when it is not the case, this function can be helpful for debugging. The functor Logger.Make
always registers a simple printer to suggest using run
, but you can register new ones to override it. The return type of the printer p
should return Some s
where s
is the resulting string, or None
if it chooses not to convert a particular effect or exception. The registered printers are tried in reverse order until one of them returns Some s
for some s
; that is, the last registered printer is tried first. Note that this function is a wrapper of Printexc
.register_printer and all the registered printers (via this function or Printexc
.register_printer) are put into the same list.
The input type of the printer p
is a variant representation of all internal effects and exceptions used in this module:
`Trace
corresponds to the effect triggered by trace
; and`Emit diag
corresponds to the effect triggered by emit
; and`Fatal diag
corresponds to the exception triggered by fatal
.
Note: Diagnostic.string_of_text
can be handy for converting a message into a string.
\ No newline at end of file
+ unit
register_printer p
registers a printer p
via Printexc.register_printer
to convert unhandled internal effects and exceptions into strings for the OCaml runtime system to display. Ideally, all internal effects and exceptions should have been handled by run
and there is no need to use this function, but when it is not the case, this function can be helpful for debugging. The functor Logger.Make
always registers a simple printer to suggest using run
, but you can register new ones to override it. The return type of the printer p
should return Some s
where s
is the resulting string, or None
if it chooses not to convert a particular effect or exception. The registered printers are tried in reverse order until one of them returns Some s
for some s
; that is, the last registered printer is tried first. Note that this function is a wrapper of Printexc.register_printer
and all the registered printers (via this function or Printexc.register_printer
) are put into the same list.
The input type of the printer p
is a variant representation of all internal effects and exceptions used in this module:
`Trace
corresponds to the effect triggered by trace
; and`Emit diag
corresponds to the effect triggered by emit
; and`Fatal diag
corresponds to the exception triggered by fatal
.Note: Diagnostic.string_of_text
can be handy for converting a message into a string.
Asai.Logger
Generating and handling diagnostics using algebraic effects.
module type S = sig ... end
The signature of a logger.
Asai.Logger
Generating and handling diagnostics using algebraic effects.
module type S = sig ... end
The signature of a logger.
S.Code
val default_severity : t -> Diagnostic.severity
The default severity of the code. The severity of a message is about whether the message is an error or a warning, etc. To clarify, it is about how serious the message is to the end user, not whether the program should stop or continue. The severity may be overwritten at the time of issuing a message to the end user.
val to_string : t -> string
A concise, ideally Google-able string representation of each message code. Detailed or long descriptions of code should be avoided. For example, E001
works better than type-checking error
. The shorter, the better.
S.Code
val default_severity : t -> Diagnostic.severity
The default severity of the code. The severity of a message is about whether the message is an error or a warning, etc. To clarify, it is about how serious the message is to the end user, not whether the program should stop or continue. The severity may be overwritten at the time of issuing a message to the end user.
val to_string : t -> string
A concise, ideally Google-able string representation of each message code. Detailed or long descriptions of code should be avoided. For example, E001
works better than type-checking error
. The shorter, the better.
Logger.S
The signature of a logger.
module Code : Diagnostic.Code
val emit :
- ?severity:Diagnostic.severity ->
- ?loc:Span.t ->
- ?backtrace:Diagnostic.backtrace ->
- ?additional_messages:Diagnostic.message list ->
+S (asai.Asai.Logger.S) Module type Logger.S
The signature of a logger.
module Code : Diagnostic.Code
Sending Messages
val emit :
+ ?severity:Diagnostic.severity ->
+ ?loc:Span.t ->
+ ?backtrace:Diagnostic.backtrace ->
+ ?additional_messages:Diagnostic.message list ->
Code.t ->
string ->
unit
emit code str
emits a string and continues the computation.
Example:
Logger.emit `TypeError "This type is extremely unnatural:\nNat"
val emitf :
- ?severity:Diagnostic.severity ->
- ?loc:Span.t ->
- ?backtrace:Diagnostic.backtrace ->
- ?additional_messages:Diagnostic.message list ->
+ ?severity:Diagnostic.severity ->
+ ?loc:Span.t ->
+ ?backtrace:Diagnostic.backtrace ->
+ ?additional_messages:Diagnostic.message list ->
Code.t ->
('a, Stdlib.Format.formatter, unit, unit) Stdlib.format4 ->
'a
emitf code format ...
formats and emits a message, and then continues the computation. Note that there should not be any literal control characters. See Diagnostic.text
.
Example:
Logger.emitf `TypeError "Type %a is too ugly" Syntax.pp tp
val emit_diagnostic : Code.t Diagnostic.t -> unit
Emit a diagnostic and continue the computation.
val fatal :
- ?severity:Diagnostic.severity ->
- ?loc:Span.t ->
- ?backtrace:Diagnostic.backtrace ->
- ?additional_messages:Diagnostic.message list ->
+ ?severity:Diagnostic.severity ->
+ ?loc:Span.t ->
+ ?backtrace:Diagnostic.backtrace ->
+ ?additional_messages:Diagnostic.message list ->
Code.t ->
string ->
'a
fatal code str
aborts the current computation with the string str
.
Example:
Logger.fatal `FileError "Forgot to feed the cat"
val fatalf :
- ?severity:Diagnostic.severity ->
- ?loc:Span.t ->
- ?backtrace:Diagnostic.backtrace ->
- ?additional_messages:Diagnostic.message list ->
+ ?severity:Diagnostic.severity ->
+ ?loc:Span.t ->
+ ?backtrace:Diagnostic.backtrace ->
+ ?additional_messages:Diagnostic.message list ->
Code.t ->
('a, Stdlib.Format.formatter, unit, 'b) Stdlib.format4 ->
'a
fatalf code format ...
constructs a diagnostic and aborts the current computation with the diagnostic. Note that there should not be any literal control characters. See Diagnostic.text
.
Example:
Logger.fatalf `FileError "Failed to write the password to %s" file_path
val fatal_diagnostic : Code.t Diagnostic.t -> 'a
Abort the computation with a diagnostic.
Backtraces
val get_backtrace : unit -> Diagnostic.backtrace
get_backtrace()
returns the current backtrace.
val with_backtrace : Diagnostic.backtrace -> (unit -> 'a) -> 'a
with_backtrace bt f
runs the thunk f
with bt
as the initial backtrace.
Example:
(* running code with a fresh backtrace *)
-with_backtrace Emp @@ fun () -> ...
val trace : ?loc:Span.t -> string -> (unit -> 'a) -> 'a
trace str f
records the string str
and runs the thunk f
with the new backtrace.
val tracef :
- ?loc:Span.t ->
+with_backtrace Emp @@ fun () -> ...
val trace : ?loc:Span.t -> string -> (unit -> 'a) -> 'a
trace str f
records the string str
and runs the thunk f
with the new backtrace.
val tracef :
+ ?loc:Span.t ->
('a, Stdlib.Format.formatter, unit, (unit -> 'b) -> 'b) Stdlib.format4 ->
- 'a
tracef format ... f
formats and records a message as a frame in the backtrace, and runs the thunk f
with the new backtrace. Note that there should not be any literal control characters. See Diagnostic.text
.
val trace_text : ?loc:Span.t -> Diagnostic.text -> (unit -> 'a) -> 'a
trace_text text f
records the message text
and runs the thunk f
with the new backtrace.
val trace_message : Diagnostic.message -> (unit -> 'a) -> 'a
trace_message msg f
records the message msg
and runs the thunk f
with the new backtrace.
Locations
val get_loc : unit -> Span.t option
get_loc()
returns the current location.
val with_loc : Span.t option -> (unit -> 'a) -> 'a
with_loc loc f
runs the thunk f
with loc
as the initial location loc
. Note that with_loc None
will clear the current location, while merge_loc None
will keep it. See merge_loc
.
val merge_loc : Span.t option -> (unit -> 'a) -> 'a
merge_loc loc f
"merges" loc
into the current location and runs the thunk f
. By "merge", it means that if loc
is None
, then the current location is kept; otherwise, it is overwritten. Note that with_loc None
will clear the current location, while merge_loc None
will keep it. See with_loc
.
Constructing Diagnostics
Functions in this section differ from the ones in Diagnostic
(for example, Diagnostic.make
) in that they fill out the current location, the current backtrace, and the severity automatically. (One can still overwrite them with optional arguments.)
val diagnostic :
- ?severity:Diagnostic.severity ->
- ?loc:Span.t ->
- ?backtrace:Diagnostic.backtrace ->
- ?additional_messages:Diagnostic.message list ->
+ 'a
tracef format ... f
formats and records a message as a frame in the backtrace, and runs the thunk f
with the new backtrace. Note that there should not be any literal control characters. See Diagnostic.text
.
val trace_text : ?loc:Span.t -> Diagnostic.text -> (unit -> 'a) -> 'a
trace_text text f
records the message text
and runs the thunk f
with the new backtrace.
val trace_message : Diagnostic.message -> (unit -> 'a) -> 'a
trace_message msg f
records the message msg
and runs the thunk f
with the new backtrace.
Locations
val get_loc : unit -> Span.t option
get_loc()
returns the current location.
val with_loc : Span.t option -> (unit -> 'a) -> 'a
with_loc loc f
runs the thunk f
with loc
as the initial location loc
. Note that with_loc None
will clear the current location, while merge_loc None
will keep it. See merge_loc
.
val merge_loc : Span.t option -> (unit -> 'a) -> 'a
merge_loc loc f
"merges" loc
into the current location and runs the thunk f
. By "merge", it means that if loc
is None
, then the current location is kept; otherwise, it is overwritten. Note that with_loc None
will clear the current location, while merge_loc None
will keep it. See with_loc
.
Constructing Diagnostics
Functions in this section differ from the ones in Diagnostic
(for example, Diagnostic.make
) in that they fill out the current location, the current backtrace, and the severity automatically. (One can still overwrite them with optional arguments.)
val diagnostic :
+ ?severity:Diagnostic.severity ->
+ ?loc:Span.t ->
+ ?backtrace:Diagnostic.backtrace ->
+ ?additional_messages:Diagnostic.message list ->
Code.t ->
string ->
Code.t Diagnostic.t
diagnostic code str
constructs a diagnostic with the message str
along with the backtrace frames recorded via tracef
.
Example:
Logger.diagnostic `TypeError "This\nis\ntoo\nmuch."
val diagnosticf :
- ?severity:Diagnostic.severity ->
- ?loc:Span.t ->
- ?backtrace:Diagnostic.backtrace ->
- ?additional_messages:Diagnostic.message list ->
+ ?severity:Diagnostic.severity ->
+ ?loc:Span.t ->
+ ?backtrace:Diagnostic.backtrace ->
+ ?additional_messages:Diagnostic.message list ->
Code.t ->
('a, Stdlib.Format.formatter, unit, Code.t Diagnostic.t) Stdlib.format4 ->
'a
diagnosticf code format ...
constructs a diagnostic along with the backtrace frames recorded via trace
. Note that there should not be any literal control characters. See Diagnostic.text
.
Example:
Logger.diagnosticf `TypeError "Term %a does not type check, or does it?" Syntax.pp tm
val kdiagnosticf :
- ?severity:Diagnostic.severity ->
- ?loc:Span.t ->
- ?backtrace:Diagnostic.backtrace ->
- ?additional_messages:Diagnostic.message list ->
+ ?severity:Diagnostic.severity ->
+ ?loc:Span.t ->
+ ?backtrace:Diagnostic.backtrace ->
+ ?additional_messages:Diagnostic.message list ->
(Code.t Diagnostic.t -> 'b) ->
Code.t ->
('a, Stdlib.Format.formatter, unit, 'b) Stdlib.format4 ->
'a
kdiagnosticf kont code format ...
is kont (diagnosticf code format ...)
. Note that there should not be any literal control characters. See Diagnostic.text
.
Algebraic Effects
val run :
- ?init_loc:Span.t ->
- ?init_backtrace:Diagnostic.backtrace ->
- emit:(Code.t Diagnostic.t -> unit) ->
- fatal:(Code.t Diagnostic.t -> 'a) ->
+ ?init_loc:Span.t ->
+ ?init_backtrace:Diagnostic.backtrace ->
+ emit:(Code.t Diagnostic.t -> unit) ->
+ fatal:(Code.t Diagnostic.t -> 'a) ->
(unit -> 'a) ->
'a
val adopt :
('code Diagnostic.t -> Code.t Diagnostic.t) ->
- (?init_loc:Span.t ->
- ?init_backtrace:Diagnostic.backtrace ->
- emit:('code Diagnostic.t -> unit) ->
- fatal:('code Diagnostic.t -> 'a) ->
+ (?init_loc:Span.t ->
+ ?init_backtrace:Diagnostic.backtrace ->
+ emit:('code Diagnostic.t -> unit) ->
+ fatal:('code Diagnostic.t -> 'a) ->
(unit -> 'a) ->
'a) ->
(unit -> 'a) ->
@@ -78,10 +78,10 @@
module LibLogger = Lib.Logger
let _ = MainLogger.adopt (Diagnostic.map code_mapper) LibLogger.run @@ fun () -> ...
val try_with :
- ?emit:(Code.t Diagnostic.t -> unit) ->
- ?fatal:(Code.t Diagnostic.t -> 'a) ->
+ ?emit:(Code.t Diagnostic.t -> unit) ->
+ ?fatal:(Code.t Diagnostic.t -> 'a) ->
(unit -> 'a) ->
'a
try_with ~emit ~fatal f
runs the thunk f
, using emit
to intercept non-fatal diagnostics before continuing the computation (see emit
and emitf
), and fatal
to intercept fatal diagnostics that have aborted the computation (see fatal
and fatalf
). The default interceptors re-emit or re-raise the intercepted diagnostics.
val register_printer :
([ `Trace | `Emit of Code.t Diagnostic.t | `Fatal of Code.t Diagnostic.t ] ->
string option) ->
- unit
register_printer p
registers a printer p
via Printexc
.register_printer to convert unhandled internal effects and exceptions into strings for the OCaml runtime system to display. Ideally, all internal effects and exceptions should have been handled by run
and there is no need to use this function, but when it is not the case, this function can be helpful for debugging. The functor Logger.Make
always registers a simple printer to suggest using run
, but you can register new ones to override it. The return type of the printer p
should return Some s
where s
is the resulting string, or None
if it chooses not to convert a particular effect or exception. The registered printers are tried in reverse order until one of them returns Some s
for some s
; that is, the last registered printer is tried first. Note that this function is a wrapper of Printexc
.register_printer and all the registered printers (via this function or Printexc
.register_printer) are put into the same list.
The input type of the printer p
is a variant representation of all internal effects and exceptions used in this module:
`Trace
corresponds to the effect triggered by trace
; and`Emit diag
corresponds to the effect triggered by emit
; and`Fatal diag
corresponds to the exception triggered by fatal
.
Note: Diagnostic.string_of_text
can be handy for converting a message into a string.
\ No newline at end of file
+ unit
register_printer p
registers a printer p
via Printexc.register_printer
to convert unhandled internal effects and exceptions into strings for the OCaml runtime system to display. Ideally, all internal effects and exceptions should have been handled by run
and there is no need to use this function, but when it is not the case, this function can be helpful for debugging. The functor Logger.Make
always registers a simple printer to suggest using run
, but you can register new ones to override it. The return type of the printer p
should return Some s
where s
is the resulting string, or None
if it chooses not to convert a particular effect or exception. The registered printers are tried in reverse order until one of them returns Some s
for some s
; that is, the last registered printer is tried first. Note that this function is a wrapper of Printexc.register_printer
and all the registered printers (via this function or Printexc.register_printer
) are put into the same list.
The input type of the printer p
is a variant representation of all internal effects and exceptions used in this module:
`Trace
corresponds to the effect triggered by trace
; and`Emit diag
corresponds to the effect triggered by emit
; and`Fatal diag
corresponds to the exception triggered by fatal
.Note: Diagnostic.string_of_text
can be handy for converting a message into a string.
Make.Code
val default_severity : t -> Diagnostic.severity
The default severity of the code. The severity of a message is about whether the message is an error or a warning, etc. To clarify, it is about how serious the message is to the end user, not whether the program should stop or continue. The severity may be overwritten at the time of issuing a message to the end user.
val to_string : t -> string
A concise, ideally Google-able string representation of each message code. Detailed or long descriptions of code should be avoided. For example, E001
works better than type-checking error
. The shorter, the better.
Make.Code
val default_severity : t -> Diagnostic.severity
The default severity of the code. The severity of a message is about whether the message is an error or a warning, etc. To clarify, it is about how serious the message is to the end user, not whether the program should stop or continue. The severity may be overwritten at the time of issuing a message to the end user.
val to_string : t -> string
A concise, ideally Google-able string representation of each message code. Detailed or long descriptions of code should be avoided. For example, E001
works better than type-checking error
. The shorter, the better.
Lsp.Make
This module provides a rudimentary and incomplete implementation of the LSP protocol.
Note: many features are missing and it does not handle positionEncoding
.
module Code : Diagnostic.Code
val start :
- source:string option ->
- init:(root:string option -> unit) ->
- load_file:(display:(Code.t Diagnostic.t -> unit) -> string -> unit) ->
- unit
run ~init ~load_file
starts the LSP server with the two callbacks init
and load_file
.
Lsp.Make
This module provides a rudimentary and incomplete implementation of the LSP protocol.
Note: many features are missing and it does not handle positionEncoding
.
module Code : Diagnostic.Code
val start :
+ source:string option ->
+ init:(root:string option -> unit) ->
+ load_file:(display:(Code.t Diagnostic.t -> unit) -> string -> unit) ->
+ unit
run ~init ~load_file
starts the LSP server with the two callbacks init
and load_file
.
Asai.Lsp
An LSP (Language Service Protocol) server for asai
module Make (Code : Diagnostic.Code) : sig ... end
This module provides a rudimentary and incomplete implementation of the LSP protocol.
Asai.Lsp
An LSP (Language Service Protocol) server for asai
module Make (Code : Diagnostic.Code) : sig ... end
This module provides a rudimentary and incomplete implementation of the LSP protocol.
Asai.Span
Locations and spans.
type position = {
file_path : string;
The absolute file path of the file that contains the position.
*)offset : int;
The 0-indexed byte offset of the position relative to the beginning of the file.
*)start_of_line : int;
The 0-indexed byte offset pointing to the start of the line that contains the position.
*)line_num : int;
The 1-indexed line number of the line that contains the position.
*)}
The type of positions; this is isomorphic to Lexing
.position, but with arguably better field names.
An auxiliary type to package data with an optional span.
make (beginning, ending)
builds the span [begining, ending)
(not including the byte at the ending position) from a pair of positions beginning
and ending
.
split span
returning the pair of the beginning and ending positions of span
. It is the right inverse of make
.
val file_path : t -> string
file_path span
returns the file path associated with span
.
val begin_line_num : t -> int
begin_line_num span
returns the 1-indexed line number of the beginning position.
val end_line_num : t -> int
end_line_num span
returns the 1-indexed line number of the ending position.
val begin_offset : t -> int
begin_offset span
returns the 0-indexed offset of the (inclusive) beginning position.
val end_offset : t -> int
end_offset span
returns the 0-indexed offset of the (exclusive) ending position.
val of_lex_position : Stdlib.Lexing.position -> position
of_lex_position pos
converts an OCaml lexer position pos
of type Lexing
.position into a position
. The input pos
must be byte-indexed. (Therefore, the OCaml tool ocamllex
is compatible, but the OCaml library sedlex
is not because it uses Unicode code points.)
val of_lex_span : (Stdlib.Lexing.position * Stdlib.Lexing.position) -> t
of_lex_span (begining, ending)
takes a pair of OCaml lexer positions and creates a span. It is make (of_lex_position begining, of_lex_position ending)
.
val of_lexbuf : Stdlib.Lexing.lexbuf -> t
of_lexbuf lexbuf
constructs a span from the current lexeme that lexbuf
points to. It is of_lex_span (Lexing.lexeme_start_p lexbuf, Lexing.lexeme_end_p lexbuf)
.
val locate_lex :
+Span (asai.Asai.Span) Module Asai.Span
Locations and spans.
Types
type position = {
file_path : string;
(*The absolute file path of the file that contains the position.
*)offset : int;
(*The 0-indexed byte offset of the position relative to the beginning of the file.
*)start_of_line : int;
(*The 0-indexed byte offset pointing to the start of the line that contains the position.
*)line_num : int;
(*The 1-indexed line number of the line that contains the position.
*)
}
The type of positions; this is isomorphic to Lexing.position
, but with arguably better field names.
An auxiliary type to package data with an optional span.
Spans
make (beginning, ending)
builds the span [begining, ending)
(not including the byte at the ending position) from a pair of positions beginning
and ending
.
split span
returning the pair of the beginning and ending positions of span
. It is the right inverse of make
.
val file_path : t -> string
file_path span
returns the file path associated with span
.
val begin_line_num : t -> int
begin_line_num span
returns the 1-indexed line number of the beginning position.
val end_line_num : t -> int
end_line_num span
returns the 1-indexed line number of the ending position.
val begin_offset : t -> int
begin_offset span
returns the 0-indexed offset of the (inclusive) beginning position.
val end_offset : t -> int
end_offset span
returns the 0-indexed offset of the (exclusive) ending position.
Support of Lexing
val of_lex_position : Stdlib.Lexing.position -> position
of_lex_position pos
converts an OCaml lexer position pos
of type Lexing.position
into a position
. The input pos
must be byte-indexed. (Therefore, the OCaml tool ocamllex
is compatible, but the OCaml library sedlex
is not because it uses Unicode code points.)
val of_lex_span : (Stdlib.Lexing.position * Stdlib.Lexing.position) -> t
of_lex_span (begining, ending)
takes a pair of OCaml lexer positions and creates a span. It is make (of_lex_position begining, of_lex_position ending)
.
val of_lexbuf : Stdlib.Lexing.lexbuf -> t
of_lexbuf lexbuf
constructs a span from the current lexeme that lexbuf
points to. It is of_lex_span (Lexing.lexeme_start_p lexbuf, Lexing.lexeme_end_p lexbuf)
.
val locate_lex :
(Stdlib.Lexing.position * Stdlib.Lexing.position) ->
'a ->
'a located
locate_lex ps v
is a helper function to create a value annotated with a span. It is locate (Some (of_lex_span ps)) v
and is designed to work with the OCaml parser generator Menhir. You can add the following code to your Menhir grammar to generate annotated data:
%inline
locate(X):
| e = X
- { Asai.Span.locate_lex $loc e }
\ No newline at end of file
+ { Asai.Span.locate_lex $loc e }
Make.Code
val default_severity : t -> Diagnostic.severity
The default severity of the code. The severity of a message is about whether the message is an error or a warning, etc. To clarify, it is about how serious the message is to the end user, not whether the program should stop or continue. The severity may be overwritten at the time of issuing a message to the end user.
val to_string : t -> string
A concise, ideally Google-able string representation of each message code. Detailed or long descriptions of code should be avoided. For example, E001
works better than type-checking error
. The shorter, the better.
Make.Code
val default_severity : t -> Diagnostic.severity
The default severity of the code. The severity of a message is about whether the message is an error or a warning, etc. To clarify, it is about how serious the message is to the end user, not whether the program should stop or continue. The severity may be overwritten at the time of issuing a message to the end user.
val to_string : t -> string
A concise, ideally Google-able string representation of each message code. Detailed or long descriptions of code should be avoided. For example, E001
works better than type-checking error
. The shorter, the better.
Tty.Make
This module provides functions to display or interact with diagnostics in UNIX terminals. A message will look like this:
🭁 examples/stlc/example.lambda +Make (asai.Asai.Tty.Make) Module
Tty.Make
This module provides functions to display or interact with diagnostics in UNIX terminals. A message will look like this:
🭁 examples/stlc/example.lambda │ 1 │ (check (λ ä (λ 123 2 │ sdaf)) (→ ℕ (→ ℕ ℕ))) @@ -9,10 +9,10 @@ ┷ [E002] Why am I checking the term (→ ℕ (→ ℕ ℕ)), which looks amazing?\ No newline at end of file + unitParameters
module Code : Diagnostic.Code
Signature
val display : - ?show_backtrace:bool -> - ?line_breaking:[ `Unicode | `Traditional ] -> - ?block_splitting_threshold:int -> - ?tab_size:int -> + ?show_backtrace:bool -> + ?line_breaking:[ `Unicode | `Traditional ] -> + ?block_splitting_threshold:int -> + ?tab_size:int -> Code.t Diagnostic.t -> unit
display d
prints the diagnosticd
to the standard output, using terminal control characters for formatting. A message will look like this:🭁 examples/stlc/example1.lambda │ @@ -31,9 +31,9 @@ │ 8 │ assert (asai is cool) ┷ - [E002] Why am I checking the term (→ ℕ (→ ℕ ℕ))?val interactive_trace : + ?line_breaking:[ `Unicode | `Traditional ] -> + ?block_splitting_threshold:int -> + ?tab_size:int -> Code.t Diagnostic.t -> - unit
interactive_trace d
drops the user in a small interactive terminal app where they can cycle through the message provided ind
and its backtrace.diff --git a/asai/Asai/Tty/index.html b/asai/Asai/Tty/index.html index 61428d4..f607129 100644 --- a/asai/Asai/Tty/index.html +++ b/asai/Asai/Tty/index.html @@ -1,2 +1,2 @@ -
interactive_trace d
drops the user in a small interactive terminal app where they can cycle through the message provided ind
and its backtrace.Tty (asai.Asai.Tty) Module
Asai.Tty
Diagnostic display for UNIX terminals.
\ No newline at end of file +Display
module Make (Code : Diagnostic.Code) : sig ... end
This module provides functions to display or interact with diagnostics in UNIX terminals. A message will look like this:
Tty (asai.Asai.Tty) Module
Asai.Tty
Diagnostic display for UNIX terminals.
diff --git a/asai/Asai/index.html b/asai/Asai/index.html index 2527f56..d4c9e19 100644 --- a/asai/Asai/index.html +++ b/asai/Asai/index.html @@ -1,2 +1,2 @@ -Display
module Make (Code : Diagnostic.Code) : sig ... end
This module provides functions to display or interact with diagnostics in UNIX terminals. A message will look like this:
Asai (asai.Asai) Module
Asai
Compiler diagnostics
\ No newline at end of file +A diagnostic is a message for the end user, for example a compiler warning or error.
Core API
module Span : sig ... end
Locations and spans.
module Diagnostic : sig ... end
The definition of diagnostics and some utility functions.
module Logger : sig ... end
Generating and handling diagnostics using algebraic effects.
Experimental Backends
module Tty : sig ... end
Diagnostic display for UNIX terminals.
module Lsp : sig ... end
An LSP (Language Service Protocol) server for asai
module GitHub : sig ... end
GitHub Actions workflow commands.
Internals
module Explication : sig ... end
The definition of highlighted text suitable for rendering. You probably do not need this module unless you want to create your own backend.
module Explicator : sig ... end
Turning location information into highlighted text suitable for rendering. You probably do not need this module unless you want to create your own backend.
module FileReader : sig ... end
An implementation of
Explicator.Reader
using memory-mapped file I/O. You probably do not need this module unless you want to create your own backend.Asai (asai.Asai) Module
Asai
Compiler diagnostics
diff --git a/asai/design.html b/asai/design.html index ae9f645..7571e98 100644 --- a/asai/design.html +++ b/asai/design.html @@ -1,2 +1,2 @@ -A diagnostic is a message for the end user, for example a compiler warning or error.
Core API
module Span : sig ... end
Locations and spans.
module Diagnostic : sig ... end
The definition of diagnostics and some utility functions.
module Logger : sig ... end
Generating and handling diagnostics using algebraic effects.
Experimental Backends
module Tty : sig ... end
Diagnostic display for UNIX terminals.
module Lsp : sig ... end
An LSP (Language Service Protocol) server for asai
module GitHub : sig ... end
GitHub Actions workflow commands.
Internals
module Explication : sig ... end
The definition of highlighted text suitable for rendering. You probably do not need this module unless you want to create your own backend.
module Explicator : sig ... end
Turning location information into highlighted text suitable for rendering. You probably do not need this module unless you want to create your own backend.
module FileReader : sig ... end
An implementation of
Explicator.Reader
using memory-mapped file I/O. You probably do not need this module unless you want to create your own backend.design (asai.design) Design Principles
\ No newline at end of file +Five Independent Parameters of a Diagnostic
In addition to the main message, the API should allow implementers to easily specify the following five factors of a diagnostic, and it should be possible to specify them independently.
- Whether the program terminates after sending the message. This is indicated by the choice between emit (for non-fatal messages) and fatal (for fatal ones).
- A message code with a succinct Google-able representation, for example
V0003
. A succinct representation is useful for an end user to report a bug or ask for help.- How seriously end users should take the message. Is it a warning, an error, or just a hint? See the type severity for available classifications. In practice, messages with the same message code tend to have the same severity, and thus our API requires an implementer to specify a default severity for each message code. While this seems to violate the independence constraint, our API allows overriding the default severity at each call of emit or fatal.
- A stack backtrace. There should be a straightforward way to push new stack frames. Our implementation is trace.
- Additional messages. It should be possible to attach any numbers of additional related messages. Currently, emit and fatal are taking .
Compositionality: Using Libraries that Use
asai
It should be easy for an application to use other libraries who themselves use
asai
. Our current implementation allows an application to adopt messages from a library.Unicode Art
There is a long history of using ASCII printable characters and ANSI escape sequences, and recently also non-ASCII Unicode characters, to draw pictures on terminals. To display compiler diagnostics, this technique has been used to assemble line numbers, code from end users, code highlighting, and other pieces of information in a visually pleasing way. Non-ASCII Unicode characters (from implementers or from end users) greatly expand the vocabulary of ASCII art, and we will call the new art form Unicode art to signify the use of non-ASCII characters.
No Column Numbers, Ever
The arrival of non-ASCII Unicode characters imposes new challenges as their visual widths are unpredictable without knowing the exact terminal (or terminal emulator), the exact font, etc. Unicode emoji sequences might be one of the most challenging cases: a pirate flag (🏴☠️) may be shown as a single emoji flag on supported platforms but as a sequence with a black flag (🏴) and a skull (☠️) on other platforms. This means the visual width of the pirate flag is unpredictable. (See Unicode Emoji Section 2.2.) The rainbow flag (🏳️🌈), skin tones, and many other emoji sequences have the same issue. Other less chaotic but still challenging cases include characters whose East Asian width is Ambiguous. These challenges bear some similarity with the unpredictability of the visual width of horizontal tabulations, but in a much wilder way.
Note: "Unicode characters" are not really defined in the Unicode standard, and here they mean Unicode scalar values, that is, all Unicode code points except the surrogate code points for UTF-16 to represent all scalar values. Although the word "character" has many incompatible meanings and usages, we decided to call scalar values "Unicode characters" anyway because (1) most people are not familiar with the official term "scalar values" and (2) scalar values are the only stable primitive unit one can work with in a programming language.
It is thus wise to think twice before using emoji sequences and other tricky characters in Unicode art. To quantify the degree to which a Unicode art can remain visually pleasing on different platforms, we specify the following four levels of stability. Note that if implementers decide to integrate content from end users into their Unicode art, the end users should have the freedom to include arbitrary emoji sequences and tricky characters in their content. The final Unicode art must remain visually pleasing as defined by the stability levels for any reasonable user content.
- Level 0 (the least stable): Stability under the assumption that every Unicode character occupies exactly the same visual width. Thankfully, programs meeting only this level are mostly considered outdated.
- Level 1: Stability under the assumption each Unicode string visually occupies a multiple of some fixed width, where the multiplier is determined by heuristics (such as various implementations of
wcwidth
andwcswidth
). These heuristics are created to help programmers handle more characters, in particular CJK characters, without dramatically changing the code. They however do not solve the core problem (that is, visual width is fundamentally ill-defined) and they often could not handle tricky cases such as emoji sequences. Many compilers are at this level.
- Level 2a: Stability under very limited assumptions on which characters should have the same widths. For example, if a Unicode art only assumes Unicode box-drawing characters are of the same visual width (which is the case in all conceivable situations), then its stability is at this level. However, the phrase "very limited" is somewhat subjective, and thus we present a more precise version below.
Level 2b: Stability under only theses assumptions:
- All characters whose East Asian width is either Halfwidth or Narrow have the same visual width. This class includes all ASCII printable characters and thus an ASCII art very likely satisfies Level 2b.
- All characters whose East Asian width is either Fullwidth or Wide have the same visual width (as long as they are not used as part of an emoji sequence). Note that we do not assume the visual width of these characters is exactly double the visual width of the characters in the previous class.
- All box-drawing characters have the same visual width.
- Equivalent (extended) grapheme clusters have the same visual width (regardless of the context). Note that an application can and maybe should customize grapheme clusters, but we believe it is okay to leave out the detail here.
Level 2b is making explicit what Level 2a means; we might update the details of Level 2b later to better match our understanding of Level 2a. Collectively, Levels 2a and 2b are called "Level 2".
- Level 3 (the most stable): Stability under only one assumption that equivalent (extended) grapheme clusters have the same visual width (the last assumption of Level 2b). This means that the Unicode art will remain visually pleasing in almost all situations. It can even be rendered with a variable-width font.
Unlike most implementations, which are at Level 1, our terminal backend strives to achieve Level 2. That means we must not make any assumption about the visual width of end users' code and must abandon the idea of column numbers. Our terminal backend never uses column numbers and we consider that as a significant improvement. On the other hand, Level 3 seems to be too restricted for compiler diagnostics because we cannot show line numbers along with the end users' code. (We cannot assume the numbers "10" and "99" will have the same visual width at Level 3.)
Note: a fixed-width font with enough glyphs that covers many Unicode characters is often technically duospaced, not monospaced, because many CJK characters would occupy a double character visual width. Thus, we do not use the terminology "monospaced".
No Support of Bidirectional Text Yet
Proper support of bidirectional text will benefit many potential end users, but unfortunately, we currently do not have the capacity to implement it. The general support of bidirectional text in most system libraries and tools is poor, and without dedicated effort, it is hard to verify whether we manage to avoid common pitfalls.
On a related note, Unicode Source Code Handling suggests that source code should be segmented into atoms and their display order should remain the same to maintain the lexical structure. (This deviation is allowed by the Unicode Bidirectional Algorithm.) Our current implementation cannot handle this because it has no access to such structural information of the content from end users.
Raw Bytes as Positions
All positions should be byte-oriented. We believe other popular alternatives proposals are worse:
- Unicode characters (Unicode scalar values): This is a reasonable and technically well-defined choice. The problem is that it may take linear time to count the number of characters from raw bytes without a clever data structure (unless we are using UTF-32), and they often do not match what end users perceive as "characters". In other words, it takes more time to compute and may invite misconceptions about Unicode characters.
- Code units used in UTF-16: This is somewhat similar to Unicode characters, but with quirks from UTF-16: a Unicode scalar value above
U+FFFF
(such as😎
) will require two code units to form a surrogate pair. Therefore, it is arguably worse than just using Unicode characters. This scheme was unfortunately chosen by the Language Service Protocol (LSP) as the default unit, and until LSP version 3.17 was the only choice. The developers of the protocol made this decision probably because Visual Studio Code was written in JavaScript (and TypeScript), whose strings use UTF-16 encoding.- (Extended) grapheme clusters or user-perceived characters. The notion of grapheme clusters can help segment a Unicode text for end users to edit or select part of it in an "intuitive" way. It is not trivial to implement the segmentation algorithm (see the OCaml library uuseg) and the default rules can (and maybe should) be overriden for each application. The complexity and external dependency of grapheme clusters make it an unreliable unit for specifying positions. It also takes at least linear time to count the number of grapheme clusters from raw bytes.
- Column numbers, the visual width of a string in display. As analyzed in the above section, this is the most ill-defined unit of all, and a heuristic that can give passable results in most cases still takes linear time.
Know Bug: Our LSP prototype does not handle
positionEncoding
yet, and because the default unit in LSP is based on UTF-16 (see above), an LSP client may be confused about the byte-oriented ranges returned by this library. A proper LSP implementation should negotiate with the client to determine how to represent column positions (and our current prototype does not). On the other hand, it can be tricky to negotiate with the client to use raw bytes because there is not an official predefined encoding scheme for raw bytes yet.design (asai.design) Design Principles
diff --git a/asai/index.html b/asai/index.html index 8e0eb15..bc1e8bc 100644 --- a/asai/index.html +++ b/asai/index.html @@ -1,2 +1,2 @@ -Five Independent Parameters of a Diagnostic
In addition to the main message, the API should allow implementers to easily specify the following five factors of a diagnostic, and it should be possible to specify them independently.
- Whether the program terminates after sending the message. This is indicated by the choice between emit (for non-fatal messages) and fatal (for fatal ones).
- A message code with a succinct Google-able representation, for example
V0003
. A succinct representation is useful for an end user to report a bug or ask for help.- How seriously end users should take the message. Is it a warning, an error, or just a hint? See the type severity for available classifications. In practice, messages with the same message code tend to have the same severity, and thus our API requires an implementer to specify a default severity for each message code. While this seems to violate the independence constraint, our API allows overriding the default severity at each call of emit or fatal.
- A stack backtrace. There should be a straightforward way to push new stack frames. Our implementation is trace.
- Additional messages. It should be possible to attach any numbers of additional related messages. Currently, emit and fatal are taking .
Compositionality: Using Libraries that Use
asai
It should be easy for an application to use other libraries who themselves use
asai
. Our current implementation allows an application to adopt messages from a library.Unicode Art
There is a long history of using ASCII printable characters and ANSI escape sequences, and recently also non-ASCII Unicode characters, to draw pictures on terminals. To display compiler diagnostics, this technique has been used to assemble line numbers, code from end users, code highlighting, and other pieces of information in a visually pleasing way. Non-ASCII Unicode characters (from implementers or from end users) greatly expand the vocabulary of ASCII art, and we will call the new art form Unicode art to signify the use of non-ASCII characters.
No Column Numbers, Ever
The arrival of non-ASCII Unicode characters imposes new challenges as their visual widths are unpredictable without knowing the exact terminal (or terminal emulator), the exact font, etc. Unicode emoji sequences might be one of the most challenging cases: a pirate flag (🏴☠️) may be shown as a single emoji flag on supported platforms but as a sequence with a black flag (🏴) and a skull (☠️) on other platforms. This means the visual width of the pirate flag is unpredictable. (See Unicode Emoji Section 2.2.) The rainbow flag (🏳️🌈), skin tones, and many other emoji sequences have the same issue. Other less chaotic but still challenging cases include characters whose East Asian width is Ambiguous. These challenges bear some similarity with the unpredictability of the visual width of horizontal tabulations, but in a much wilder way.
Note: "Unicode characters" are not really defined in the Unicode standard, and here they mean Unicode scalar values, that is, all Unicode code points except the surrogate code points for UTF-16 to represent all scalar values. Although the word "character" has many incompatible meanings and usages, we decided to call scalar values "Unicode characters" anyway because (1) most people are not familiar with the official term "scalar values" and (2) scalar values are the only stable primitive unit one can work with in a programming language.
It is thus wise to think twice before using emoji sequences and other tricky characters in Unicode art. To quantify the degree to which a Unicode art can remain visually pleasing on different platforms, we specify the following four levels of stability. Note that if implementers decide to integrate content from end users into their Unicode art, the end users should have the freedom to include arbitrary emoji sequences and tricky characters in their content. The final Unicode art must remain visually pleasing as defined by the stability levels for any reasonable user content.
- Level 0 (the least stable): Stability under the assumption that every Unicode character occupies exactly the same visual width. Thankfully, programs meeting only this level are mostly considered outdated.
- Level 1: Stability under the assumption each Unicode string visually occupies a multiple of some fixed width, where the multiplier is determined by heuristics (such as various implementations of
wcwidth
andwcswidth
). These heuristics are created to help programmers handle more characters, in particular CJK characters, without dramatically changing the code. They however do not solve the core problem (that is, visual width is fundamentally ill-defined) and they often could not handle tricky cases such as emoji sequences. Many compilers are at this level.
- Level 2a: Stability under very limited assumptions on which characters should have the same widths. For example, if a Unicode art only assumes Unicode box-drawing characters are of the same visual width (which is the case in all conceivable situations), then its stability is at this level. However, the phrase "very limited" is somewhat subjective, and thus we present a more precise version below.
Level 2b: Stability under only theses assumptions:
- All characters whose East Asian width is either Halfwidth or Narrow have the same visual width. This class includes all ASCII printable characters and thus an ASCII art very likely satisfies Level 2b.
- All characters whose East Asian width is either Fullwidth or Wide have the same visual width (as long as they are not used as part of an emoji sequence). Note that we do not assume the visual width of these characters is exactly double the visual width of the characters in the previous class.
- All box-drawing characters have the same visual width.
- Equivalent (extended) grapheme clusters have the same visual width (regardless of the context). Note that an application can and maybe should customize grapheme clusters, but we believe it is okay to leave out the detail here.
Level 2b is making explicit what Level 2a means; we might update the details of Level 2b later to better match our understanding of Level 2a. Collectively, Levels 2a and 2b are called "Level 2".
- Level 3 (the most stable): Stability under only one assumption that equivalent (extended) grapheme clusters have the same visual width (the last assumption of Level 2b). This means that the Unicode art will remain visually pleasing in almost all situations. It can even be rendered with a variable-width font.
Unlike most implementations, which are at Level 1, our terminal backend strives to achieve Level 2. That means we must not make any assumption about the visual width of end users' code and must abandon the idea of column numbers. Our terminal backend never uses column numbers and we consider that as a significant improvement. On the other hand, Level 3 seems to be too restricted for compiler diagnostics because we cannot show line numbers along with the end users' code. (We cannot assume the numbers "10" and "99" will have the same visual width at Level 3.)
Note: a fixed-width font with enough glyphs that covers many Unicode characters is often technically duospaced, not monospaced, because many CJK characters would occupy a double character visual width. Thus, we do not use the terminology "monospaced".
No Support of Bidirectional Text Yet
Proper support of bidirectional text will benefit many potential end users, but unfortunately, we currently do not have the capacity to implement it. The general support of bidirectional text in most system libraries and tools is poor, and without dedicated effort, it is hard to verify whether we manage to avoid common pitfalls.
On a related note, Unicode Source Code Handling suggests that source code should be segmented into atoms and their display order should remain the same to maintain the lexical structure. (This deviation is allowed by the Unicode Bidirectional Algorithm.) Our current implementation cannot handle this because it has no access to such structural information of the content from end users.
Raw Bytes as Positions
All positions should be byte-oriented. We believe other popular alternatives proposals are worse:
- Unicode characters (Unicode scalar values): This is a reasonable and technically well-defined choice. The problem is that it may take linear time to count the number of characters from raw bytes without a clever data structure (unless we are using UTF-32), and they often do not match what end users perceive as "characters". In other words, it takes more time to compute and may invite misconceptions about Unicode characters.
- Code units used in UTF-16: This is somewhat similar to Unicode characters, but with quirks from UTF-16: a Unicode scalar value above
U+FFFF
(such as😎
) will require two code units to form a surrogate pair. Therefore, it is arguably worse than just using Unicode characters. This scheme was unfortunately chosen by the Language Service Protocol (LSP) as the default unit, and until LSP version 3.17 was the only choice. The developers of the protocol made this decision probably because Visual Studio Code was written in JavaScript (and TypeScript), whose strings use UTF-16 encoding.- (Extended) grapheme clusters or user-perceived characters. The notion of grapheme clusters can help segment a Unicode text for end users to edit or select part of it in an "intuitive" way. It is not trivial to implement the segmentation algorithm (see the OCaml library uuseg) and the default rules can (and maybe should) be overriden for each application. The complexity and external dependency of grapheme clusters make it an unreliable unit for specifying positions. It also takes at least linear time to count the number of grapheme clusters from raw bytes.
- Column numbers, the visual width of a string in display. As analyzed in the above section, this is the most ill-defined unit of all, and a heuristic that can give passable results in most cases still takes linear time.
Know Bug: Our LSP prototype does not handle
positionEncoding
yet, and because the default unit in LSP is based on UTF-16 (see above), an LSP client may be confused about the byte-oriented ranges returned by this library. A proper LSP implementation should negotiate with the client to determine how to represent column positions (and our current prototype does not). On the other hand, it can be tricky to negotiate with the client to use raw bytes because there is not an official predefined encoding scheme for raw bytes yet.index (asai.index) asai: Compiler Diagnostics
\ No newline at end of file +Links
What is "asai"?
"asai" is the transliteration of "浅井", the family name of the character Kei Asai (浅井 ケイ) in the Japanese light novel Sagrada Reset (サクラダリセット, also known in English as Sakurada Reset). His ability is perfect photographic memory that is even immune to the "Reset" ability owned by another main character. This OCaml library should record all messages, just like the character.
index (asai.index) asai: Compiler Diagnostics
diff --git a/asai/quickstart.html b/asai/quickstart.html index 5039ef4..f725080 100644 --- a/asai/quickstart.html +++ b/asai/quickstart.html @@ -1,5 +1,5 @@ -Links
What is "asai"?
"asai" is the transliteration of "浅井", the family name of the character Kei Asai (浅井 ケイ) in the Japanese light novel Sagrada Reset (サクラダリセット, also known in English as Sakurada Reset). His ability is perfect photographic memory that is even immune to the "Reset" ability owned by another main character. This OCaml library should record all messages, just like the character.
quickstart (asai.quickstart) Quickstart Tutorial
This tutorial is for an implementer (you!) to adopt this library as quickly as possible. We will assume you are already familiar with OCaml and are using a typical OCaml package structure.
diff --git a/odoc.support/fonts/fira-mono-v14-latin-500.woff2 b/odoc.support/fonts/fira-mono-v14-latin-500.woff2 new file mode 100644 index 0000000..9d07a63 Binary files /dev/null and b/odoc.support/fonts/fira-mono-v14-latin-500.woff2 differ diff --git a/odoc.support/fonts/fira-mono-v14-latin-regular.woff2 b/odoc.support/fonts/fira-mono-v14-latin-regular.woff2 new file mode 100644 index 0000000..edc71a8 Binary files /dev/null and b/odoc.support/fonts/fira-mono-v14-latin-regular.woff2 differ diff --git a/odoc.support/fonts/fira-sans-v17-latin-500.woff2 b/odoc.support/fonts/fira-sans-v17-latin-500.woff2 new file mode 100644 index 0000000..24bb8f4 Binary files /dev/null and b/odoc.support/fonts/fira-sans-v17-latin-500.woff2 differ diff --git a/odoc.support/fonts/fira-sans-v17-latin-500italic.woff2 b/odoc.support/fonts/fira-sans-v17-latin-500italic.woff2 new file mode 100644 index 0000000..1a8b72d Binary files /dev/null and b/odoc.support/fonts/fira-sans-v17-latin-500italic.woff2 differ diff --git a/odoc.support/fonts/fira-sans-v17-latin-700.woff2 b/odoc.support/fonts/fira-sans-v17-latin-700.woff2 new file mode 100644 index 0000000..40b8a1c Binary files /dev/null and b/odoc.support/fonts/fira-sans-v17-latin-700.woff2 differ diff --git a/odoc.support/fonts/fira-sans-v17-latin-700italic.woff2 b/odoc.support/fonts/fira-sans-v17-latin-700italic.woff2 new file mode 100644 index 0000000..bdf8f5f Binary files /dev/null and b/odoc.support/fonts/fira-sans-v17-latin-700italic.woff2 differ diff --git a/odoc.support/fonts/fira-sans-v17-latin-italic.woff2 b/odoc.support/fonts/fira-sans-v17-latin-italic.woff2 new file mode 100644 index 0000000..b9619dd Binary files /dev/null and b/odoc.support/fonts/fira-sans-v17-latin-italic.woff2 differ diff --git a/odoc.support/fonts/fira-sans-v17-latin-regular.woff2 b/odoc.support/fonts/fira-sans-v17-latin-regular.woff2 new file mode 100644 index 0000000..d31eba8 Binary files /dev/null and b/odoc.support/fonts/fira-sans-v17-latin-regular.woff2 differ diff --git a/odoc.support/fonts/noticia-text-v15-latin-700.woff2 b/odoc.support/fonts/noticia-text-v15-latin-700.woff2 new file mode 100644 index 0000000..536fbe1 Binary files /dev/null and b/odoc.support/fonts/noticia-text-v15-latin-700.woff2 differ diff --git a/odoc.support/fonts/noticia-text-v15-latin-italic.woff2 b/odoc.support/fonts/noticia-text-v15-latin-italic.woff2 new file mode 100644 index 0000000..9b83b07 Binary files /dev/null and b/odoc.support/fonts/noticia-text-v15-latin-italic.woff2 differ diff --git a/odoc.support/fonts/noticia-text-v15-latin-regular.woff2 b/odoc.support/fonts/noticia-text-v15-latin-regular.woff2 new file mode 100644 index 0000000..efff29f Binary files /dev/null and b/odoc.support/fonts/noticia-text-v15-latin-regular.woff2 differ diff --git a/odoc.support/highlight.pack.js b/odoc.support/highlight.pack.js index a373159..7d1bcd0 100644 --- a/odoc.support/highlight.pack.js +++ b/odoc.support/highlight.pack.js @@ -302,7 +302,238 @@ e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),s.push(e)} },t.versionString="11.7.0",t.regex={concat:p,lookahead:d,either:f,optional:h, anyNumberOfTimes:u};for(const t in A)"object"==typeof A[t]&&e.exports(A[t]) ;return Object.assign(t,A),t})({});return te}() -;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);/*! `xml` grammar compiled for Highlight.js 11.7.0 */ +;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);/*! `reasonml` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const n="~?[a-z$_][0-9a-zA-Z$_]*",a="`?[A-Z$_][0-9a-zA-Z$_]*",s="("+["||","++","**","+.","*","/","*.","/.","..."].map((e=>e.split("").map((e=>"\\"+e)).join(""))).join("|")+"|\\|>|&&|==|===)",i="\\s+"+s+"\\s+",r={ +keyword:"and as asr assert begin class constraint do done downto else end exception external for fun function functor if in include inherit initializer land lazy let lor lsl lsr lxor match method mod module mutable new nonrec object of open or private rec sig struct then to try type val virtual when while with", +built_in:"array bool bytes char exn|5 float int int32 int64 list lazy_t|5 nativeint|5 ref string unit ", +literal:"true false" +},l="\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)",t={ +className:"number",relevance:0,variants:[{begin:l},{begin:"\\(-"+l+"\\)"}]},c={ +className:"operator",relevance:0,begin:s},o=[{className:"identifier", +relevance:0,begin:n},c,t],g=[e.QUOTE_STRING_MODE,c,{className:"module", +begin:"\\b"+a,returnBegin:!0,relevance:0,end:".",contains:[{ +className:"identifier",begin:a,relevance:0}]}],b=[{className:"module", +begin:"\\b"+a,returnBegin:!0,end:".",relevance:0,contains:[{ +className:"identifier",begin:a,relevance:0}]}],m={className:"function", +relevance:0,keywords:r,variants:[{begin:"\\s(\\(\\.?.*?\\)|"+n+")\\s*=>", +end:"\\s*=>",returnBegin:!0,relevance:0,contains:[{className:"params", +variants:[{begin:n},{ +begin:"~?[a-z$_][0-9a-zA-Z$_]*(\\s*:\\s*[a-z$_][0-9a-z$_]*(\\(\\s*('?[a-z$_][0-9a-z$_]*\\s*(,'?[a-z$_][0-9a-z$_]*\\s*)*)?\\))?){0,2}" +},{begin:/\(\s*\)/}]}]},{begin:"\\s\\(\\.?[^;\\|]*\\)\\s*=>",end:"\\s=>", +returnBegin:!0,relevance:0,contains:[{className:"params",relevance:0,variants:[{ +begin:n,end:"(,|\\n|\\))",relevance:0,contains:[c,{className:"typing",begin:":", +end:"(,|\\n)",returnBegin:!0,relevance:0,contains:b}]}]}]},{ +begin:"\\(\\.\\s"+n+"\\)\\s*=>"}]};g.push(m);const d={className:"constructor", +begin:a+"\\(",end:"\\)",illegal:"\\n",keywords:r, +contains:[e.QUOTE_STRING_MODE,c,{className:"params",begin:"\\b"+n}]},u={ +className:"pattern-match",begin:"\\|",returnBegin:!0,keywords:r,end:"=>", +relevance:0,contains:[d,c,{relevance:0,className:"constructor",begin:a}]},v={ +className:"module-access",keywords:r,returnBegin:!0,variants:[{ +begin:"\\b("+a+"\\.)+"+n},{begin:"\\b("+a+"\\.)+\\(",end:"\\)",returnBegin:!0, +contains:[m,{begin:"\\(",end:"\\)",relevance:0,skip:!0}].concat(g)},{ +begin:"\\b("+a+"\\.)+\\{",end:/\}/}],contains:g};return b.push(v),{ +name:"ReasonML",aliases:["re"],keywords:r,illegal:"(:-|:=|\\$\\{|\\+=)", +contains:[e.COMMENT("/\\*","\\*/",{illegal:"^(#,\\/\\/)"}),{ +className:"character",begin:"'(\\\\[^']+|[^'])'",illegal:"\\n",relevance:0 +},e.QUOTE_STRING_MODE,{className:"literal",begin:"\\(\\)",relevance:0},{ +className:"literal",begin:"\\[\\|",end:"\\|\\]",relevance:0,contains:o},{ +className:"literal",begin:"\\[",end:"\\]",relevance:0,contains:o},d,{ +className:"operator",begin:i,illegal:"--\x3e",relevance:0 +},t,e.C_LINE_COMMENT_MODE,u,m,{className:"module-def", +begin:"\\bmodule\\s+"+n+"\\s+"+a+"\\s+=\\s+\\{",end:/\}/,returnBegin:!0, +keywords:r,relevance:0,contains:[{className:"module",relevance:0,begin:a},{ +begin:/\{/,end:/\}/,relevance:0,skip:!0}].concat(g)},v]}}})() +;hljs.registerLanguage("reasonml",e)})();/*! `javascript` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict" +;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],r=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],c=["arguments","this","super","console","window","document","localStorage","module","global"],i=[].concat(r,t,s) +;return o=>{const l=o.regex,b=e,d={begin:/<[A-Za-z0-9\\._:-]+/, +end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ +const a=e[0].length+e.index,t=e.input[a] +;if("<"===t||","===t)return void n.ignoreMatch();let s +;">"===t&&(((e,{after:n})=>{const a=""+e[0].slice(1) +;return-1!==e.input.indexOf(a,n)})(e,{after:a})||n.ignoreMatch()) +;const r=e.input.substring(a) +;((s=r.match(/^\s*=/))||(s=r.match(/^\s+extends\s+/))&&0===s.index)&&n.ignoreMatch() +}},g={$pattern:e,keyword:n,literal:a,built_in:i,"variable.language":c +},u="\\.([0-9](_?[0-9])*)",m="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",E={ +className:"number",variants:[{ +begin:`(\\b(${m})((${u})|\\.)?|(${u}))[eE][+-]?([0-9](_?[0-9])*)\\b`},{ +begin:`\\b(${m})\\b((${u})\\b|\\.)?|(${u})\\b`},{ +begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{ +begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{ +begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{ +begin:"\\b0[0-7]+n?\\b"}],relevance:0},A={className:"subst",begin:"\\$\\{", +end:"\\}",keywords:g,contains:[]},y={begin:"html`",end:"",starts:{end:"`", +returnEnd:!1,contains:[o.BACKSLASH_ESCAPE,A],subLanguage:"xml"}},N={ +begin:"css`",end:"",starts:{end:"`",returnEnd:!1, +contains:[o.BACKSLASH_ESCAPE,A],subLanguage:"css"}},_={className:"string", +begin:"`",end:"`",contains:[o.BACKSLASH_ESCAPE,A]},h={className:"comment", +variants:[o.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{ +begin:"(?=@[A-Za-z]+)",relevance:0,contains:[{className:"doctag", +begin:"@[A-Za-z]+"},{className:"type",begin:"\\{",end:"\\}",excludeEnd:!0, +excludeBegin:!0,relevance:0},{className:"variable",begin:b+"(?=\\s*(-)|$)", +endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}] +}),o.C_BLOCK_COMMENT_MODE,o.C_LINE_COMMENT_MODE] +},f=[o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,y,N,_,{match:/\$\d+/},E] +;A.contains=f.concat({begin:/\{/,end:/\}/,keywords:g,contains:["self"].concat(f) +});const v=[].concat(h,A.contains),p=v.concat([{begin:/\(/,end:/\)/,keywords:g, +contains:["self"].concat(v)}]),S={className:"params",begin:/\(/,end:/\)/, +excludeBegin:!0,excludeEnd:!0,keywords:g,contains:p},w={variants:[{ +match:[/class/,/\s+/,b,/\s+/,/extends/,/\s+/,l.concat(b,"(",l.concat(/\./,b),")*")], +scope:{1:"keyword",3:"title.class",5:"keyword",7:"title.class.inherited"}},{ +match:[/class/,/\s+/,b],scope:{1:"keyword",3:"title.class"}}]},R={relevance:0, +match:l.either(/\bJSON/,/\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,/\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,/\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/), +className:"title.class",keywords:{_:[...t,...s]}},O={variants:[{ +match:[/function/,/\s+/,b,/(?=\s*\()/]},{match:[/function/,/\s*(?=\()/]}], +className:{1:"keyword",3:"title.function"},label:"func.def",contains:[S], +illegal:/%/},k={ +match:l.concat(/\b/,(I=[...r,"super","import"],l.concat("(?!",I.join("|"),")")),b,l.lookahead(/\(/)), +className:"title.function",relevance:0};var I;const x={ +begin:l.concat(/\./,l.lookahead(l.concat(b,/(?![0-9A-Za-z$_(])/))),end:b, +excludeBegin:!0,keywords:"prototype",className:"property",relevance:0},T={ +match:[/get|set/,/\s+/,b,/(?=\()/],className:{1:"keyword",3:"title.function"}, +contains:[{begin:/\(\)/},S] +},C="(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+o.UNDERSCORE_IDENT_RE+")\\s*=>",M={ +match:[/const|var|let/,/\s+/,b,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(C)], +keywords:"async",className:{1:"keyword",3:"title.function"},contains:[S]} +;return{name:"Javascript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{ +PARAMS_CONTAINS:p,CLASS_REFERENCE:R},illegal:/#(?![$_A-z])/, +contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{ +label:"use_strict",className:"meta",relevance:10, +begin:/^\s*['"]use (strict|asm)['"]/ +},o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,y,N,_,h,{match:/\$\d+/},E,R,{ +className:"attr",begin:b+l.lookahead(":"),relevance:0},M,{ +begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", +keywords:"return throw case",relevance:0,contains:[h,o.REGEXP_MODE,{ +className:"function",begin:C,returnBegin:!0,end:"\\s*=>",contains:[{ +className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{ +className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0, +excludeEnd:!0,keywords:g,contains:p}]}]},{begin:/,/,relevance:0},{match:/\s+/, +relevance:0},{variants:[{begin:"<>",end:">"},{ +match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:d.begin, +"on:begin":d.isTrulyOpeningTag,end:d.end}],subLanguage:"xml",contains:[{ +begin:d.begin,end:d.end,skip:!0,contains:["self"]}]}]},O,{ +beginKeywords:"while if switch catch for"},{ +begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", +returnBegin:!0,label:"func.def",contains:[S,o.inherit(o.TITLE_MODE,{begin:b, +className:"title.function"})]},{match:/\.\.\./,relevance:0},x,{match:"\\$"+b, +relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"}, +contains:[S]},k,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},w,T,{match:/\$[(.]/}]}}})() +;hljs.registerLanguage("javascript",e)})();/*! `sql` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const r=e.regex,t=e.COMMENT("--","$"),n=["true","false","unknown"],a=["bigint","binary","blob","boolean","char","character","clob","date","dec","decfloat","decimal","float","int","integer","interval","nchar","nclob","national","numeric","real","row","smallint","time","timestamp","varchar","varying","varbinary"],i=["abs","acos","array_agg","asin","atan","avg","cast","ceil","ceiling","coalesce","corr","cos","cosh","count","covar_pop","covar_samp","cume_dist","dense_rank","deref","element","exp","extract","first_value","floor","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","last_value","lead","listagg","ln","log","log10","lower","max","min","mod","nth_value","ntile","nullif","percent_rank","percentile_cont","percentile_disc","position","position_regex","power","rank","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","row_number","sin","sinh","sqrt","stddev_pop","stddev_samp","substring","substring_regex","sum","tan","tanh","translate","translate_regex","treat","trim","trim_array","unnest","upper","value_of","var_pop","var_samp","width_bucket"],s=["create table","insert into","primary key","foreign key","not null","alter table","add constraint","grouping sets","on overflow","character set","respect nulls","ignore nulls","nulls first","nulls last","depth first","breadth first"],o=i,c=["abs","acos","all","allocate","alter","and","any","are","array","array_agg","array_max_cardinality","as","asensitive","asin","asymmetric","at","atan","atomic","authorization","avg","begin","begin_frame","begin_partition","between","bigint","binary","blob","boolean","both","by","call","called","cardinality","cascaded","case","cast","ceil","ceiling","char","char_length","character","character_length","check","classifier","clob","close","coalesce","collate","collect","column","commit","condition","connect","constraint","contains","convert","copy","corr","corresponding","cos","cosh","count","covar_pop","covar_samp","create","cross","cube","cume_dist","current","current_catalog","current_date","current_default_transform_group","current_path","current_role","current_row","current_schema","current_time","current_timestamp","current_path","current_role","current_transform_group_for_type","current_user","cursor","cycle","date","day","deallocate","dec","decimal","decfloat","declare","default","define","delete","dense_rank","deref","describe","deterministic","disconnect","distinct","double","drop","dynamic","each","element","else","empty","end","end_frame","end_partition","end-exec","equals","escape","every","except","exec","execute","exists","exp","external","extract","false","fetch","filter","first_value","float","floor","for","foreign","frame_row","free","from","full","function","fusion","get","global","grant","group","grouping","groups","having","hold","hour","identity","in","indicator","initial","inner","inout","insensitive","insert","int","integer","intersect","intersection","interval","into","is","join","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","language","large","last_value","lateral","lead","leading","left","like","like_regex","listagg","ln","local","localtime","localtimestamp","log","log10","lower","match","match_number","match_recognize","matches","max","member","merge","method","min","minute","mod","modifies","module","month","multiset","national","natural","nchar","nclob","new","no","none","normalize","not","nth_value","ntile","null","nullif","numeric","octet_length","occurrences_regex","of","offset","old","omit","on","one","only","open","or","order","out","outer","over","overlaps","overlay","parameter","partition","pattern","per","percent","percent_rank","percentile_cont","percentile_disc","period","portion","position","position_regex","power","precedes","precision","prepare","primary","procedure","ptf","range","rank","reads","real","recursive","ref","references","referencing","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","release","result","return","returns","revoke","right","rollback","rollup","row","row_number","rows","running","savepoint","scope","scroll","search","second","seek","select","sensitive","session_user","set","show","similar","sin","sinh","skip","smallint","some","specific","specifictype","sql","sqlexception","sqlstate","sqlwarning","sqrt","start","static","stddev_pop","stddev_samp","submultiset","subset","substring","substring_regex","succeeds","sum","symmetric","system","system_time","system_user","table","tablesample","tan","tanh","then","time","timestamp","timezone_hour","timezone_minute","to","trailing","translate","translate_regex","translation","treat","trigger","trim","trim_array","true","truncate","uescape","union","unique","unknown","unnest","update","upper","user","using","value","values","value_of","var_pop","var_samp","varbinary","varchar","varying","versioning","when","whenever","where","width_bucket","window","with","within","without","year","add","asc","collation","desc","final","first","last","view"].filter((e=>!i.includes(e))),l={ +begin:r.concat(/\b/,r.either(...o),/\s*\(/),relevance:0,keywords:{built_in:o}} +;return{name:"SQL",case_insensitive:!0,illegal:/[{}]|<\//,keywords:{ +$pattern:/\b[\w\.]+/,keyword:((e,{exceptions:r,when:t}={})=>{const n=t +;return r=r||[],e.map((e=>e.match(/\|\d+$/)||r.includes(e)?e:n(e)?e+"|0":e)) +})(c,{when:e=>e.length<3}),literal:n,type:a, +built_in:["current_catalog","current_date","current_default_transform_group","current_path","current_role","current_schema","current_transform_group_for_type","current_user","session_user","system_time","system_user","current_time","localtime","current_timestamp","localtimestamp"] +},contains:[{begin:r.either(...s),relevance:0,keywords:{$pattern:/[\w\.]+/, +keyword:c.concat(s),literal:n,type:a}},{className:"type", +begin:r.either("double precision","large object","with timezone","without timezone") +},l,{className:"variable",begin:/@[a-z0-9]+/},{className:"string",variants:[{ +begin:/'/,end:/'/,contains:[{begin:/''/}]}]},{begin:/"/,end:/"/,contains:[{ +begin:/""/}]},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,{className:"operator", +begin:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/,relevance:0}]}}})() +;hljs.registerLanguage("sql",e)})();/*! `bash` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const s=e.regex,t={},n={begin:/\$\{/, +end:/\}/,contains:["self",{begin:/:-/,contains:[t]}]};Object.assign(t,{ +className:"variable",variants:[{ +begin:s.concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},n]});const a={ +className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},i={ +begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/, +end:/(\w+)/,className:"string"})]}},c={className:"string",begin:/"/,end:/"/, +contains:[e.BACKSLASH_ESCAPE,t,a]};a.contains.push(c);const o={begin:/\$?\(\(/, +end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,t] +},r=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10 +}),l={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0, +contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{ +name:"Bash",aliases:["sh"],keywords:{$pattern:/\b[a-z][a-z0-9._-]+\b/, +keyword:["if","then","else","elif","fi","for","while","in","do","done","case","esac","function"], +literal:["true","false"], +built_in:["break","cd","continue","eval","exec","exit","export","getopts","hash","pwd","readonly","return","shift","test","times","trap","umask","unset","alias","bind","builtin","caller","command","declare","echo","enable","help","let","local","logout","mapfile","printf","read","readarray","source","type","typeset","ulimit","unalias","set","shopt","autoload","bg","bindkey","bye","cap","chdir","clone","comparguments","compcall","compctl","compdescribe","compfiles","compgroups","compquote","comptags","comptry","compvalues","dirs","disable","disown","echotc","echoti","emulate","fc","fg","float","functions","getcap","getln","history","integer","jobs","kill","limit","log","noglob","popd","print","pushd","pushln","rehash","sched","setcap","setopt","stat","suspend","ttyctl","unfunction","unhash","unlimit","unsetopt","vared","wait","whence","where","which","zcompile","zformat","zftp","zle","zmodload","zparseopts","zprof","zpty","zregexparse","zsocket","zstyle","ztcp","chcon","chgrp","chown","chmod","cp","dd","df","dir","dircolors","ln","ls","mkdir","mkfifo","mknod","mktemp","mv","realpath","rm","rmdir","shred","sync","touch","truncate","vdir","b2sum","base32","base64","cat","cksum","comm","csplit","cut","expand","fmt","fold","head","join","md5sum","nl","numfmt","od","paste","ptx","pr","sha1sum","sha224sum","sha256sum","sha384sum","sha512sum","shuf","sort","split","sum","tac","tail","tr","tsort","unexpand","uniq","wc","arch","basename","chroot","date","dirname","du","echo","env","expr","factor","groups","hostid","id","link","logname","nice","nohup","nproc","pathchk","pinky","printenv","printf","pwd","readlink","runcon","seq","sleep","stat","stdbuf","stty","tee","test","timeout","tty","uname","unlink","uptime","users","who","whoami","yes"] +},contains:[r,e.SHEBANG(),l,o,e.HASH_COMMENT_MODE,i,{match:/(\/[a-z._-]+)+/},c,{ +className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},t]}}})() +;hljs.registerLanguage("bash",e)})();/*! `shell` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var s=(()=>{"use strict";return s=>({name:"Shell Session", +aliases:["console","shellsession"],contains:[{className:"meta.prompt", +begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#][ ]?/,starts:{end:/[^\\](?=\s*$)/, +subLanguage:"bash"}}]})})();hljs.registerLanguage("shell",s)})();/*! `plaintext` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var t=(()=>{"use strict";return t=>({name:"Plain text", +aliases:["text","txt"],disableAutodetect:!0})})() +;hljs.registerLanguage("plaintext",t)})();/*! `graphql` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const a=e.regex;return{name:"GraphQL", +aliases:["gql"],case_insensitive:!0,disableAutodetect:!1,keywords:{ +keyword:["query","mutation","subscription","type","input","schema","directive","interface","union","scalar","fragment","enum","on"], +literal:["true","false","null"]}, +contains:[e.HASH_COMMENT_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE,{ +scope:"punctuation",match:/[.]{3}/,relevance:0},{scope:"punctuation", +begin:/[\!\(\)\:\=\[\]\{\|\}]{1}/,relevance:0},{scope:"variable",begin:/\$/, +end:/\W/,excludeEnd:!0,relevance:0},{scope:"meta",match:/@\w+/,excludeEnd:!0},{ +scope:"symbol",begin:a.concat(/[_A-Za-z][_0-9A-Za-z]*/,a.lookahead(/\s*:/)), +relevance:0}],illegal:[/[;<']/,/BEGIN/]}}})();hljs.registerLanguage("graphql",e) +})();/*! `ocaml` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>({name:"OCaml",aliases:["ml"], +keywords:{$pattern:"[a-z_]\\w*!?", +keyword:"and as assert asr begin class constraint do done downto else end exception external for fun function functor if in include inherit! inherit initializer land lazy let lor lsl lsr lxor match method!|10 method mod module mutable new object of open! open or private rec sig struct then to try type val! val virtual when while with parser value", +built_in:"array bool bytes char exn|5 float int int32 int64 list lazy_t|5 nativeint|5 string unit in_channel out_channel ref", +literal:"true false"},illegal:/\/\/|>>/,contains:[{className:"literal", +begin:"\\[(\\|\\|)?\\]|\\(\\)",relevance:0},e.COMMENT("\\(\\*","\\*\\)",{ +contains:["self"]}),{className:"symbol",begin:"'[A-Za-z_](?!')[\\w']*"},{ +className:"type",begin:"`[A-Z][\\w']*"},{className:"type", +begin:"\\b[A-Z][\\w']*",relevance:0},{begin:"[a-z_]\\w*'[\\w']*",relevance:0 +},e.inherit(e.APOS_STRING_MODE,{className:"string",relevance:0 +}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null}),{className:"number", +begin:"\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)", +relevance:0},{begin:/->/}]})})();hljs.registerLanguage("ocaml",e)})();/*! `json` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const a=["true","false","null"],n={ +scope:"literal",beginKeywords:a.join(" ")};return{name:"JSON",keywords:{ +literal:a},contains:[{className:"attr",begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/, +relevance:1.01},{match:/[{}[\],:]/,className:"punctuation",relevance:0 +},e.QUOTE_STRING_MODE,n,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE], +illegal:"\\S"}}})();hljs.registerLanguage("json",e)})();/*! `python` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const n=e.regex,a=/[\p{XID_Start}_]\p{XID_Continue}*/u,i=["and","as","assert","async","await","break","case","class","continue","def","del","elif","else","except","finally","for","from","global","if","import","in","is","lambda","match","nonlocal|10","not","or","pass","raise","return","try","while","with","yield"],s={ +$pattern:/[A-Za-z]\w+|__\w+__/,keyword:i, +built_in:["__import__","abs","all","any","ascii","bin","bool","breakpoint","bytearray","bytes","callable","chr","classmethod","compile","complex","delattr","dict","dir","divmod","enumerate","eval","exec","filter","float","format","frozenset","getattr","globals","hasattr","hash","help","hex","id","input","int","isinstance","issubclass","iter","len","list","locals","map","max","memoryview","min","next","object","oct","open","ord","pow","print","property","range","repr","reversed","round","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","vars","zip"], +literal:["__debug__","Ellipsis","False","None","NotImplemented","True"], +type:["Any","Callable","Coroutine","Dict","List","Literal","Generic","Optional","Sequence","Set","Tuple","Type","Union"] +},t={className:"meta",begin:/^(>>>|\.\.\.) /},r={className:"subst",begin:/\{/, +end:/\}/,keywords:s,illegal:/#/},l={begin:/\{\{/,relevance:0},b={ +className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{ +begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/, +contains:[e.BACKSLASH_ESCAPE,t],relevance:10},{ +begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,end:/"""/, +contains:[e.BACKSLASH_ESCAPE,t],relevance:10},{ +begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/, +contains:[e.BACKSLASH_ESCAPE,t,l,r]},{begin:/([fF][rR]|[rR][fF]|[fF])"""/, +end:/"""/,contains:[e.BACKSLASH_ESCAPE,t,l,r]},{begin:/([uU]|[rR])'/,end:/'/, +relevance:10},{begin:/([uU]|[rR])"/,end:/"/,relevance:10},{ +begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])"/, +end:/"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/, +contains:[e.BACKSLASH_ESCAPE,l,r]},{begin:/([fF][rR]|[rR][fF]|[fF])"/,end:/"/, +contains:[e.BACKSLASH_ESCAPE,l,r]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE] +},o="[0-9](_?[0-9])*",c=`(\\b(${o}))?\\.(${o})|\\b(${o})\\.`,d="\\b|"+i.join("|"),g={ +className:"number",relevance:0,variants:[{ +begin:`(\\b(${o})|(${c}))[eE][+-]?(${o})[jJ]?(?=${d})`},{begin:`(${c})[jJ]?`},{ +begin:`\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${d})`},{ +begin:`\\b0[bB](_?[01])+[lL]?(?=${d})`},{begin:`\\b0[oO](_?[0-7])+[lL]?(?=${d})` +},{begin:`\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${d})`},{begin:`\\b(${o})[jJ](?=${d})` +}]},p={className:"comment",begin:n.lookahead(/# type:/),end:/$/,keywords:s, +contains:[{begin:/# type:/},{begin:/#/,end:/\b\B/,endsWithParent:!0}]},m={ +className:"params",variants:[{className:"",begin:/\(\s*\)/,skip:!0},{begin:/\(/, +end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:s, +contains:["self",t,g,b,e.HASH_COMMENT_MODE]}]};return r.contains=[b,g,t],{ +name:"Python",aliases:["py","gyp","ipython"],unicodeRegex:!0,keywords:s, +illegal:/(<\/|->|\?)|=>/,contains:[t,g,{begin:/\bself\b/},{beginKeywords:"if", +relevance:0},b,p,e.HASH_COMMENT_MODE,{match:[/\bdef/,/\s+/,a],scope:{ +1:"keyword",3:"title.function"},contains:[m]},{variants:[{ +match:[/\bclass/,/\s+/,a,/\s*/,/\(\s*/,a,/\s*\)/]},{match:[/\bclass/,/\s+/,a]}], +scope:{1:"keyword",3:"title.class",6:"title.class.inherited"}},{ +className:"meta",begin:/^[\t ]*@/,end:/(?=#)|$/,contains:[g,m,b]}]}}})() +;hljs.registerLanguage("python",e)})();/*! `xml` grammar compiled for Highlight.js 11.7.0 */ (()=>{var e=(()=>{"use strict";return e=>{ const a=e.regex,n=a.concat(/[\p{L}_]/u,a.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),s={ className:"symbol",begin:/&[a-z]+;|[0-9]+;|[a-f0-9]+;/},t={begin:/\s/, @@ -360,102 +591,44 @@ contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{ begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{ className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{ className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}})() -;hljs.registerLanguage("markdown",e)})();/*! `plaintext` grammar compiled for Highlight.js 11.7.0 */ -(()=>{var t=(()=>{"use strict";return t=>({name:"Plain text", -aliases:["text","txt"],disableAutodetect:!0})})() -;hljs.registerLanguage("plaintext",t)})();/*! `ocaml` grammar compiled for Highlight.js 11.7.0 */ -(()=>{var e=(()=>{"use strict";return e=>({name:"OCaml",aliases:["ml"], -keywords:{$pattern:"[a-z_]\\w*!?", -keyword:"and as assert asr begin class constraint do done downto else end exception external for fun function functor if in include inherit! inherit initializer land lazy let lor lsl lsr lxor match method!|10 method mod module mutable new object of open! open or private rec sig struct then to try type val! val virtual when while with parser value", -built_in:"array bool bytes char exn|5 float int int32 int64 list lazy_t|5 nativeint|5 string unit in_channel out_channel ref", -literal:"true false"},illegal:/\/\/|>>/,contains:[{className:"literal", -begin:"\\[(\\|\\|)?\\]|\\(\\)",relevance:0},e.COMMENT("\\(\\*","\\*\\)",{ -contains:["self"]}),{className:"symbol",begin:"'[A-Za-z_](?!')[\\w']*"},{ -className:"type",begin:"`[A-Z][\\w']*"},{className:"type", -begin:"\\b[A-Z][\\w']*",relevance:0},{begin:"[a-z_]\\w*'[\\w']*",relevance:0 -},e.inherit(e.APOS_STRING_MODE,{className:"string",relevance:0 -}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null}),{className:"number", -begin:"\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)", -relevance:0},{begin:/->/}]})})();hljs.registerLanguage("ocaml",e)})();/*! `python` grammar compiled for Highlight.js 11.7.0 */ -(()=>{var e=(()=>{"use strict";return e=>{ -const n=e.regex,a=/[\p{XID_Start}_]\p{XID_Continue}*/u,i=["and","as","assert","async","await","break","case","class","continue","def","del","elif","else","except","finally","for","from","global","if","import","in","is","lambda","match","nonlocal|10","not","or","pass","raise","return","try","while","with","yield"],s={ -$pattern:/[A-Za-z]\w+|__\w+__/,keyword:i, -built_in:["__import__","abs","all","any","ascii","bin","bool","breakpoint","bytearray","bytes","callable","chr","classmethod","compile","complex","delattr","dict","dir","divmod","enumerate","eval","exec","filter","float","format","frozenset","getattr","globals","hasattr","hash","help","hex","id","input","int","isinstance","issubclass","iter","len","list","locals","map","max","memoryview","min","next","object","oct","open","ord","pow","print","property","range","repr","reversed","round","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","vars","zip"], -literal:["__debug__","Ellipsis","False","None","NotImplemented","True"], -type:["Any","Callable","Coroutine","Dict","List","Literal","Generic","Optional","Sequence","Set","Tuple","Type","Union"] -},t={className:"meta",begin:/^(>>>|\.\.\.) /},r={className:"subst",begin:/\{/, -end:/\}/,keywords:s,illegal:/#/},l={begin:/\{\{/,relevance:0},b={ -className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{ -begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/, -contains:[e.BACKSLASH_ESCAPE,t],relevance:10},{ -begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,end:/"""/, -contains:[e.BACKSLASH_ESCAPE,t],relevance:10},{ -begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/, -contains:[e.BACKSLASH_ESCAPE,t,l,r]},{begin:/([fF][rR]|[rR][fF]|[fF])"""/, -end:/"""/,contains:[e.BACKSLASH_ESCAPE,t,l,r]},{begin:/([uU]|[rR])'/,end:/'/, -relevance:10},{begin:/([uU]|[rR])"/,end:/"/,relevance:10},{ -begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])"/, -end:/"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/, -contains:[e.BACKSLASH_ESCAPE,l,r]},{begin:/([fF][rR]|[rR][fF]|[fF])"/,end:/"/, -contains:[e.BACKSLASH_ESCAPE,l,r]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE] -},o="[0-9](_?[0-9])*",c=`(\\b(${o}))?\\.(${o})|\\b(${o})\\.`,d="\\b|"+i.join("|"),g={ -className:"number",relevance:0,variants:[{ -begin:`(\\b(${o})|(${c}))[eE][+-]?(${o})[jJ]?(?=${d})`},{begin:`(${c})[jJ]?`},{ -begin:`\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${d})`},{ -begin:`\\b0[bB](_?[01])+[lL]?(?=${d})`},{begin:`\\b0[oO](_?[0-7])+[lL]?(?=${d})` -},{begin:`\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${d})`},{begin:`\\b(${o})[jJ](?=${d})` -}]},p={className:"comment",begin:n.lookahead(/# type:/),end:/$/,keywords:s, -contains:[{begin:/# type:/},{begin:/#/,end:/\b\B/,endsWithParent:!0}]},m={ -className:"params",variants:[{className:"",begin:/\(\s*\)/,skip:!0},{begin:/\(/, -end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:s, -contains:["self",t,g,b,e.HASH_COMMENT_MODE]}]};return r.contains=[b,g,t],{ -name:"Python",aliases:["py","gyp","ipython"],unicodeRegex:!0,keywords:s, -illegal:/(<\/|->|\?)|=>/,contains:[t,g,{begin:/\bself\b/},{beginKeywords:"if", -relevance:0},b,p,e.HASH_COMMENT_MODE,{match:[/\bdef/,/\s+/,a],scope:{ -1:"keyword",3:"title.function"},contains:[m]},{variants:[{ -match:[/\bclass/,/\s+/,a,/\s*/,/\(\s*/,a,/\s*\)/]},{match:[/\bclass/,/\s+/,a]}], -scope:{1:"keyword",3:"title.class",6:"title.class.inherited"}},{ -className:"meta",begin:/^[\t ]*@/,end:/(?=#)|$/,contains:[g,m,b]}]}}})() -;hljs.registerLanguage("python",e)})();/*! `reasonml` grammar compiled for Highlight.js 11.7.0 */ -(()=>{var e=(()=>{"use strict";return e=>{ -const n="~?[a-z$_][0-9a-zA-Z$_]*",a="`?[A-Z$_][0-9a-zA-Z$_]*",s="("+["||","++","**","+.","*","/","*.","/.","..."].map((e=>e.split("").map((e=>"\\"+e)).join(""))).join("|")+"|\\|>|&&|==|===)",i="\\s+"+s+"\\s+",r={ -keyword:"and as asr assert begin class constraint do done downto else end exception external for fun function functor if in include inherit initializer land lazy let lor lsl lsr lxor match method mod module mutable new nonrec object of open or private rec sig struct then to try type val virtual when while with", -built_in:"array bool bytes char exn|5 float int int32 int64 list lazy_t|5 nativeint|5 ref string unit ", -literal:"true false" -},l="\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)",t={ -className:"number",relevance:0,variants:[{begin:l},{begin:"\\(-"+l+"\\)"}]},c={ -className:"operator",relevance:0,begin:s},o=[{className:"identifier", -relevance:0,begin:n},c,t],g=[e.QUOTE_STRING_MODE,c,{className:"module", -begin:"\\b"+a,returnBegin:!0,relevance:0,end:".",contains:[{ -className:"identifier",begin:a,relevance:0}]}],b=[{className:"module", -begin:"\\b"+a,returnBegin:!0,end:".",relevance:0,contains:[{ -className:"identifier",begin:a,relevance:0}]}],m={className:"function", -relevance:0,keywords:r,variants:[{begin:"\\s(\\(\\.?.*?\\)|"+n+")\\s*=>", -end:"\\s*=>",returnBegin:!0,relevance:0,contains:[{className:"params", -variants:[{begin:n},{ -begin:"~?[a-z$_][0-9a-zA-Z$_]*(\\s*:\\s*[a-z$_][0-9a-z$_]*(\\(\\s*('?[a-z$_][0-9a-z$_]*\\s*(,'?[a-z$_][0-9a-z$_]*\\s*)*)?\\))?){0,2}" -},{begin:/\(\s*\)/}]}]},{begin:"\\s\\(\\.?[^;\\|]*\\)\\s*=>",end:"\\s=>", -returnBegin:!0,relevance:0,contains:[{className:"params",relevance:0,variants:[{ -begin:n,end:"(,|\\n|\\))",relevance:0,contains:[c,{className:"typing",begin:":", -end:"(,|\\n)",returnBegin:!0,relevance:0,contains:b}]}]}]},{ -begin:"\\(\\.\\s"+n+"\\)\\s*=>"}]};g.push(m);const d={className:"constructor", -begin:a+"\\(",end:"\\)",illegal:"\\n",keywords:r, -contains:[e.QUOTE_STRING_MODE,c,{className:"params",begin:"\\b"+n}]},u={ -className:"pattern-match",begin:"\\|",returnBegin:!0,keywords:r,end:"=>", -relevance:0,contains:[d,c,{relevance:0,className:"constructor",begin:a}]},v={ -className:"module-access",keywords:r,returnBegin:!0,variants:[{ -begin:"\\b("+a+"\\.)+"+n},{begin:"\\b("+a+"\\.)+\\(",end:"\\)",returnBegin:!0, -contains:[m,{begin:"\\(",end:"\\)",relevance:0,skip:!0}].concat(g)},{ -begin:"\\b("+a+"\\.)+\\{",end:/\}/}],contains:g};return b.push(v),{ -name:"ReasonML",aliases:["re"],keywords:r,illegal:"(:-|:=|\\$\\{|\\+=)", -contains:[e.COMMENT("/\\*","\\*/",{illegal:"^(#,\\/\\/)"}),{ -className:"character",begin:"'(\\\\[^']+|[^'])'",illegal:"\\n",relevance:0 -},e.QUOTE_STRING_MODE,{className:"literal",begin:"\\(\\)",relevance:0},{ -className:"literal",begin:"\\[\\|",end:"\\|\\]",relevance:0,contains:o},{ -className:"literal",begin:"\\[",end:"\\]",relevance:0,contains:o},d,{ -className:"operator",begin:i,illegal:"--\x3e",relevance:0 -},t,e.C_LINE_COMMENT_MODE,u,m,{className:"module-def", -begin:"\\bmodule\\s+"+n+"\\s+"+a+"\\s+=\\s+\\{",end:/\}/,returnBegin:!0, -keywords:r,relevance:0,contains:[{className:"module",relevance:0,begin:a},{ -begin:/\{/,end:/\}/,relevance:0,skip:!0}].concat(g)},v]}}})() -;hljs.registerLanguage("reasonml",e)})(); \ No newline at end of file +;hljs.registerLanguage("markdown",e)})();/*! `c` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const n=e.regex,t=e.COMMENT("//","$",{ +contains:[{begin:/\\\n/}] +}),s="[a-zA-Z_]\\w*::",a="(decltype\\(auto\\)|"+n.optional(s)+"[a-zA-Z_]\\w*"+n.optional("<[^<>]+>")+")",r={ +className:"type",variants:[{begin:"\\b[a-z\\d_]*_t\\b"},{ +match:/\batomic_[a-z]{3,6}\b/}]},i={className:"string",variants:[{ +begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{ +begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)", +end:"'",illegal:"."},e.END_SAME_AS_BEGIN({ +begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},l={ +className:"number",variants:[{begin:"\\b(0b[01']+)"},{ +begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)" +},{ +begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" +}],relevance:0},o={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{ +keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include" +},contains:[{begin:/\\\n/,relevance:0},e.inherit(i,{className:"string"}),{ +className:"string",begin:/<.*?>/},t,e.C_BLOCK_COMMENT_MODE]},c={ +className:"title",begin:n.optional(s)+e.IDENT_RE,relevance:0 +},d=n.optional(s)+e.IDENT_RE+"\\s*\\(",u={ +keyword:["asm","auto","break","case","continue","default","do","else","enum","extern","for","fortran","goto","if","inline","register","restrict","return","sizeof","struct","switch","typedef","union","volatile","while","_Alignas","_Alignof","_Atomic","_Generic","_Noreturn","_Static_assert","_Thread_local","alignas","alignof","noreturn","static_assert","thread_local","_Pragma"], +type:["float","double","signed","unsigned","int","short","long","char","void","_Bool","_Complex","_Imaginary","_Decimal32","_Decimal64","_Decimal128","const","static","complex","bool","imaginary"], +literal:"true false NULL", +built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr" +},g=[o,r,t,e.C_BLOCK_COMMENT_MODE,l,i],m={variants:[{begin:/=/,end:/;/},{ +begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}], +keywords:u,contains:g.concat([{begin:/\(/,end:/\)/,keywords:u, +contains:g.concat(["self"]),relevance:0}]),relevance:0},p={ +begin:"("+a+"[\\*&\\s]+)+"+d,returnBegin:!0,end:/[{;=]/,excludeEnd:!0, +keywords:u,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:"decltype\\(auto\\)", +keywords:u,relevance:0},{begin:d,returnBegin:!0,contains:[e.inherit(c,{ +className:"title.function"})],relevance:0},{relevance:0,match:/,/},{ +className:"params",begin:/\(/,end:/\)/,keywords:u,relevance:0, +contains:[t,e.C_BLOCK_COMMENT_MODE,i,l,r,{begin:/\(/,end:/\)/,keywords:u, +relevance:0,contains:["self",t,e.C_BLOCK_COMMENT_MODE,i,l,r]}] +},r,t,e.C_BLOCK_COMMENT_MODE,o]};return{name:"C",aliases:["h"],keywords:u, +disableAutodetect:!0,illegal:"",contains:[].concat(m,p,g,[o,{ +begin:e.IDENT_RE+"::",keywords:u},{className:"class", +beginKeywords:"enum class struct union",end:/[{;:<>=]/,contains:[{ +beginKeywords:"final class struct"},e.TITLE_MODE]}]),exports:{preprocessor:o, +strings:i,keywords:u}}}})();hljs.registerLanguage("c",e)})(); diff --git a/odoc.support/odoc.css b/odoc.support/odoc.css index 8b0ed5a..7230f82 100644 --- a/odoc.support/odoc.css +++ b/odoc.support/odoc.css @@ -1,12 +1,97 @@ @charset "UTF-8"; /* Copyright (c) 2016 The odoc contributors. All rights reserved. Distributed under the ISC license, see terms at the end of the file. - odoc 2.2.0 */ + odoc 2.3.0 */ /* Fonts */ -@import url('https://fonts.googleapis.com/css?family=Fira+Mono:400,500'); -@import url('https://fonts.googleapis.com/css?family=Noticia+Text:400,400i,700'); -@import url('https://fonts.googleapis.com/css?family=Fira+Sans:400,400i,500,500i,600,600i,700,700i'); +/* noticia-text-regular - latin */ +@font-face { + font-family: 'Noticia Text'; + font-style: normal; + font-weight: 400; + src: url('fonts/noticia-text-v15-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} + +/* noticia-text-italic - latin */ +@font-face { + font-family: 'Noticia Text'; + font-style: italic; + font-weight: 400; + src: url('fonts/noticia-text-v15-latin-italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} + +/* noticia-text-700 - latin */ +@font-face { + font-family: 'Noticia Text'; + font-style: normal; + font-weight: 700; + src: url('fonts/noticia-text-v15-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} + +/* fira-mono-regular - latin */ +@font-face { + font-family: 'Fira Mono'; + font-style: normal; + font-weight: 400; + src: url('fonts/fira-mono-v14-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} + +/* fira-mono-500 - latin */ +@font-face { + font-family: 'Fira Mono'; + font-style: normal; + font-weight: 500; + src: url('fonts/fira-mono-v14-latin-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} + +/* fira-sans-regular - latin */ +@font-face { + font-family: 'Fira Sans'; + font-style: normal; + font-weight: 400; + src: url('fonts/fira-sans-v17-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} + +/* fira-sans-italic - latin */ +@font-face { + font-family: 'Fira Sans'; + font-style: italic; + font-weight: 400; + src: url('fonts/fira-sans-v17-latin-italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} + +/* fira-sans-500 - latin */ +@font-face { + font-family: 'Fira Sans'; + font-style: normal; + font-weight: 500; + src: url('fonts/fira-sans-v17-latin-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} + +/* fira-sans-500italic - latin */ +@font-face { + font-family: 'Fira Sans'; + font-style: italic; + font-weight: 500; + src: url('fonts/fira-sans-v17-latin-500italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} + +/* fira-sans-700 - latin */ +@font-face { + font-family: 'Fira Sans'; + font-style: normal; + font-weight: 700; + src: url('fonts/fira-sans-v17-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} + +/* fira-sans-700italic - latin */ +@font-face { + font-family: 'Fira Sans'; + font-style: italic; + font-weight: 700; + src: url('fonts/fira-sans-v17-latin-700italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ +} + :root, .light:root { @@ -14,6 +99,7 @@ --color: #333333; --link-color: #2C94BD; + --source-color: grey; --anchor-hover: #555; --anchor-color: #d5d5d5; --xref-shadow: #cc6666; @@ -33,6 +119,7 @@ --toc-list-border: #ccc; --spec-summary-border-color: #5c9cf5; + --spec-label-color: green; --spec-summary-background: var(--code-background); --spec-summary-hover-background: #ebeff2; --spec-details-after-background: rgba(0, 4, 15, 0.05); @@ -72,6 +159,8 @@ --hljs-type: #ac885b; --hljs-meta: #82aaff; --hljs-variable: #cf6a4c; + + --spec-label-color: lightgreen; } @media (prefers-color-scheme: dark) { @@ -118,6 +207,8 @@ --hljs-type: #ac885b; --hljs-meta: #82aaff; --hljs-variable: #cf6a4c; + + --spec-label-color: lightgreen; } } @@ -142,6 +233,7 @@ table { html { font-size: 15px; + scroll-behavior: smooth; } body { @@ -149,16 +241,23 @@ body { background: #FFFFFF; color: var(--color); background-color: var(--main-background); + font-family: "Noticia Text", Georgia, serif; + line-height: 1.5; } body { - max-width: 100ex; margin-left: calc(10vw + 20ex); margin-right: 4ex; margin-top: 20px; margin-bottom: 50px; - font-family: "Noticia Text", Georgia, serif; - line-height: 1.5; +} + +body.odoc { + max-width: 100ex; +} + +body.odoc-src { + margin-right: calc(10vw + 20ex); } header { @@ -238,6 +337,10 @@ a { color: var(--link-color); } +.odoc-src pre a { + color: inherit; +} + a:hover { box-shadow: 0 1px 0 0 var(--link-color); } @@ -289,6 +392,14 @@ a.anchor { box-shadow: 0 1px 0 0 var(--xref-shadow); } +/* Source links float inside preformated text or headings. */ +a.source_link { + float: right; + color: var(--source-color); + font-family: "Fira Sans", Helvetica, Arial, sans-serif; + font-size: initial; +} + /* Section and document divisions. Until at least 4.03 many of the modules of the stdlib start at .h7, we restart the sequence there like h2 */ @@ -372,32 +483,32 @@ tt, code, pre { font-weight: 400; } -pre { +.odoc pre { padding: 0.1em; border: 1px solid var(--pre-border-color); border-radius: 5px; overflow-x: auto; } -p code, -li code { +.odoc p code, +.odoc li code { background-color: var(--li-code-background); color: var(--li-code-color); border-radius: 3px; padding: 0 0.3ex; } -p a > code { +p a > code, li a > code { color: var(--link-color); } -code { +.odoc code { white-space: pre-wrap; } /* Code blocks (e.g. Examples) */ -pre code { +.odoc pre code { font-size: 0.893rem; } @@ -419,6 +530,10 @@ pre code { padding: 0.35em 0.5em; } +.spec .label, .spec .optlabel { + color: var(--spec-label-color); +} + li:not(:last-child) > .def-doc { margin-bottom: 15px; } @@ -536,6 +651,12 @@ td.def-doc *:first-child { .at-tags li { padding-left: 3ex; text-indent: -3ex; } .at-tags .at-tag { text-transform: capitalize } +/* Alert emoji */ + +.alert::before, .deprecated::before { + content: '⚠️ '; +} + /* Lists of modules */ .modules { list-style-type: none; margin-left: -3ex; } @@ -674,10 +795,26 @@ td.def-doc *:first-child { padding-left: 12px; } +/* Tables */ + +.odoc-table { + margin: 1em; +} + +.odoc-table td, .odoc-table th { + padding-left: 0.5em; + padding-right: 0.5em; + border: 1px solid black; +} + +.odoc-table th { + font-weight: bold; +} + /* Mobile adjustements. */ -@media only screen and (max-width: 95ex) { - body.odoc { +@media only screen and (max-width: 110ex) { + body { margin: 2em; } .odoc-toc { @@ -704,6 +841,47 @@ td.def-doc *:first-child { } } +/* Source code. */ + +.source_container { + display: flex; +} + +.source_line_column { + padding-right: 0.5em; + text-align: right; + background: #eee8d5; +} + +.source_line { + padding: 0 1em; +} + +.source_code { + flex-grow: 1; + background: #fdf6e3; + padding: 0 0.3em; + color: #657b83; +} + +/* Source directories */ + +.odoc-directory::before { + content: "📁"; + margin: 0.3em; + font-size: 1.3em; +} + +.odoc-file::before { + content: "📄"; + margin: 0.3em; + font-size: 1.3em; +} + +.odoc-folder-list { + list-style: none; +} + /* Syntax highlighting (based on github-gist) */ .hljs { @@ -777,6 +955,34 @@ td.def-doc *:first-child { text-decoration: underline; } +.VAL, .TYPE, .LET, .REC, .IN, .OPEN, .NONREC, .MODULE, .METHOD, .LETOP, .INHERIT, .INCLUDE, .FUNCTOR, .EXTERNAL, .CONSTRAINT, .ASSERT, .AND, .END, .CLASS, .STRUCT, .SIG { + color: #859900;; +} + +.WITH, .WHILE, .WHEN, .VIRTUAL, .TRY, .TO, .THEN, .PRIVATE, .OF, .NEW, .MUTABLE, .MATCH, .LAZY, .IF, .FUNCTION, .FUN, .FOR, .EXCEPTION, .ELSE, .TO, .DOWNTO, .DO, .DONE, .BEGIN, .AS { + color: #cb4b16; +} + +.TRUE, .FALSE { + color: #b58900; +} + +.failwith, .INT, .SEMISEMI, .LIDENT { + color: #2aa198; +} + +.STRING, .CHAR, .UIDENT { + color: #b58900; +} + +.DOCSTRING { + color: #268bd2; +} + +.COMMENT { + color: #93a1a1; +} + /*--------------------------------------------------------------------------- Copyright (c) 2016 The odoc contributorsDefine Error Codes
The first step is to create a file
Logger.ml
with the following template:module Code = +
quickstart (asai.quickstart) Quickstart Tutorial
This tutorial is for an implementer (you!) to adopt this library as quickly as possible. We will assume you are already familiar with OCaml and are using a typical OCaml package structure.
\ No newline at end of file + fDefine Error Codes
The first step is to create a file
Logger.ml
with the following template:module Code = struct (** All message codes used in your application. *) type t = (* ... *) @@ -19,16 +19,16 @@ end (** Include all the goodies from the asai library. *) -include Asai.Logger.Make(Code)
The most important step is to decide the message codes. It should be a meaningful classification of all the messages that could be sent to the end users. For example,
UndefinedSymbol
could be a reasonable code for a message about failing to find the definition of a symbol. Once you define the type of all message codes, you will have to define two functionsdefault_severity
andto_string
:
default_severity
: Severity means how serious an end user should take your message (is it an error or a warning?), and this can be overwritten when a message is sent. It seems messages with the same message code usually come with the same severity, so we want you to define a default severity value for each message code. You can then save some typing later when sending a message.to_string
: This function is to show the message code to the user. Ideally, it should give a short, Google-able string representation. Please do not use long descriptions such as "scope-error: undefined symbols." The library will give you plenty of opportunities to add as many details as you want to a message, but not here. The message code should be unambiguous, easily recognizable, and "machine-readable without ChatGPT."Once you have filled out the template, run
dune build
or other tools to check that everything compiles. If so, you are ready for the next step.Start Sending Messages
Now, go to the places where you want to send a message to end users, be it a warning or an error. If you want to print a message and continue the execution, you can emit a string:
Logger.emit Greeting "Hello!"; -(* continue doing other things *)
where
Greeting
is the message code of this message. The fancier version is emitf, which formats a message likeprintf
and sends it:Logger.emitf TypeError "@[<2>This term doesn't look right:@ %a@]" Syntax.pp term; -(* continue doing other things *)
There is an important limitation of emitf though: you should not include any control character (for example the newline character
\n
) anywhere when using emitf. Use break hints (such as@,
and@
) and boxes instead. SeeStdlib
.Format for more information on boxes and break hints.If you wish to terminate your program after sending a message instead of continuing the execution, use fatal instead of emit. There's also a fancier fatalf that works in the same way as emitf.
Choose a Backend
Now your program is generating lots of messages, and you have to choose a backend to handle them. We will show how to display those messages in a terminal. Suppose your entry point module looks like this:
let () = (* your application code *)
You can use the terminal backend as follows:
module Term = Asai.Tty.Make (Logger.Code) +include Asai.Logger.Make(Code)
The most important step is to decide the message codes. It should be a meaningful classification of all the messages that could be sent to the end users. For example,
UndefinedSymbol
could be a reasonable code for a message about failing to find the definition of a symbol. Once you define the type of all message codes, you will have to define two functionsdefault_severity
andto_string
:
default_severity
: Severity means how serious an end user should take your message (is it an error or a warning?), and this can be overwritten when a message is sent. It seems messages with the same message code usually come with the same severity, so we want you to define a default severity value for each message code. You can then save some typing later when sending a message.to_string
: This function is to show the message code to the user. Ideally, it should give a short, Google-able string representation. Please do not use long descriptions such as "scope-error: undefined symbols." The library will give you plenty of opportunities to add as many details as you want to a message, but not here. The message code should be unambiguous, easily recognizable, and "machine-readable without ChatGPT."Once you have filled out the template, run
dune build
or other tools to check that everything compiles. If so, you are ready for the next step.Start Sending Messages
Now, go to the places where you want to send a message to end users, be it a warning or an error. If you want to print a message and continue the execution, you can emit a string:
Logger.emit Greeting "Hello!"; +(* continue doing other things *)
where
Greeting
is the message code of this message. The fancier version is emitf, which formats a message likeprintf
and sends it:Logger.emitf TypeError "@[<2>This term doesn't look right:@ %a@]" Syntax.pp term; +(* continue doing other things *)
There is an important limitation of emitf though: you should not include any control character (for example the newline character
\n
) anywhere when using emitf. Use break hints (such as@,
and@
) and boxes instead. SeeStdlib.Format
for more information on boxes and break hints.If you wish to terminate your program after sending a message instead of continuing the execution, use fatal instead of emit. There's also a fancier fatalf that works in the same way as emitf.
Choose a Backend
Now your program is generating lots of messages, and you have to choose a backend to handle them. We will show how to display those messages in a terminal. Suppose your entry point module looks like this:
let () = (* your application code *)
You can use the terminal backend as follows:
module Term = Asai.Tty.Make (Logger.Code) let () = Logger.run ~emit:Term.display ~fatal:Term.display @@ fun () -> (* your application code *)
Add Backtraces
Great messages come with meaningful backtraces. To add backtraces, you will have to "annotate" your code to generate meaningful stack frames. Suppose this is one of the functions whose invocation should be noted in user-facing backtraces:
let f x y = - (* very important code *)
Add trace to add a frame to the current backtrace:
let f x y = + (* very important code *)
Add trace to add a frame to the current backtrace:
let f x y = Logger.trace "When calling f" @@ fun () -> - (* very important code *)
Similar to emitf, there is also tracef which allows you to format messages:
let f x y = + (* very important code *)
Similar to emitf, there is also tracef which allows you to format messages:
let f x y = Logger.tracef "When calling f on %d and %d" x y @@ fun () -> (* very important code *)
Note that, by default, the terminal backend will not show backtraces. You have to enable it as follows in your entry-point module:
module Term = Asai.Tty.Make (Logger.Code) @@ -36,11 +36,11 @@ Logger.run ~emit:(Term.display ~show_backtrace:true) ~fatal:(Term.display ~show_backtrace:true) @@ fun () -> - (* your application code *)
We do not recommend adding trace to every single function. Remember that they have to make sense to end users!
Add Location Information
Good messages also help end users locate the issues in their program or proof. Here, a location is a range of text from a file, which we call span. Many functions in your
Logger
take an optional location argumentloc
, including trace, which should be a span highlighting the most relevant text. For example, maybe the term which does not type check should be highlighted. The asai library will take the location information and draw fancy Unicode art on the screen to highlight the text. Here is one snippet showing the usage:Logger.emit ~loc Greeting "Hello again!"; -(* continue doing other things *)
You can use Span.make to create such a span manually. However, if you are using ocamllex and Menhir, you certainly want to use provided helper functions. One of them is Span.locate; you can add these lines in your Menhir grammar to generated a node annotated with its location:
%inline + (* your application code *)We do not recommend adding trace to every single function. Remember that they have to make sense to end users!
Add Location Information
Good messages also help end users locate the issues in their program or proof. Here, a location is a range of text from a file, which we call span. Many functions in your
Logger
take an optional location argumentloc
, including trace, which should be a span highlighting the most relevant text. For example, maybe the term which does not type check should be highlighted. The asai library will take the location information and draw fancy Unicode art on the screen to highlight the text. Here is one snippet showing the usage:Logger.emit ~loc Greeting "Hello again!"; +(* continue doing other things *)
You can use Span.make to create such a span manually. However, if you are using ocamllex and Menhir, you certainly want to use provided helper functions. One of them is Span.locate; you can add these lines in your Menhir grammar to generated a node annotated with its location:
%inline locate(X): | e = X - { Asai.Span.locate_lex $loc e }The annotated node will have type
data
Span.located wheredata
is the output type ofX
. Another one is Span.of_lexbuf, which comes in handy when reporting a parsing error:try Grammar.start Lex.token lexbuf with + { Asai.Span.locate_lex $loc e }
The annotated node will have type
data
Span.located wheredata
is the output type ofX
. Another one is Span.of_lexbuf, which comes in handy when reporting a parsing error:try Grammar.start Lex.token lexbuf with | Lex.SyntaxError token -> Logger.fatalf ~loc:(Span.of_lexbuf lexbuf) ParsingError {|Unrecognized token `%s'|} (String.escaped token) @@ -49,7 +49,7 @@ "Failed to parse the code"
Please take a look at
Asai.Span
to learn all kinds of ways to create a span!Note that
Logger
will remember and reuse the innermost specified location, and thus you do not have to explicitly pass it. For example, in the following codeLogger.trace ~loc "When checking this code" @@ fun () -> (* ... *) Logger.emit "Wow!" (* using the location [loc] from above *) -(* ... *)
the inner message
"Wow!"
will inherit the locationloc
from the outer trace function call! You can also use merge_loc to "remember" a location for later use, which is helpful when you want to remember a location but not to leave a trace:Logger.merge_loc (Some loc) @@ fun () -> +(* ... *)
the inner message
"Wow!"
will inherit the locationloc
from the outer trace function call! You can also use merge_loc to "remember" a location for later use, which is helpful when you want to remember a location but not to leave a trace:Logger.merge_loc (Some loc) @@ fun () -> (* ... *) Logger.emit "Wow!" (* using the location [loc] from above *) (* ... *)
Of course, you can always pass a new location to overwrite the remembered one:
Logger.merge_loc (Some loc) @@ fun () -> @@ -79,8 +79,8 @@ try_with ~emit:(fun d -> emit_diagnostic {d with severity = Error}) ~fatal:(fun d -> fatal_diagnostic {d with severity = Error}) - f
And then use
Logger.all_as_errors
to turn all messages into errors:Logger.all_as_errors @@ fun () -> (* any message sent here will be an error *)
Note that turning a message into an error does not abort the computation.
all_as_errors
only makes the message look scarier and it will not affect the control flow. If you wish to also abort the program the moment any message is sent, replace emit_diagnostic with fatal_diagnostic:let abort_at_any f = + f
And then use
Logger.all_as_errors
to turn all messages into errors:Logger.all_as_errors @@ fun () -> (* any message sent here will be an error *)
Note that turning a message into an error does not abort the computation.
all_as_errors
only makes the message look scarier and it will not affect the control flow. If you wish to also abort the program the moment any message is sent, replace emit_diagnostic with fatal_diagnostic:let abort_at_any f = try_with ~emit:(fun d -> fatal_diagnostic {d with severity = Error}) ~fatal:(fun d -> fatal_diagnostic {d with severity = Error}) - f
Within
abort_at_any
, every message will become a fatal error:Logger.abort_at_any @@ fun () -> (* any message will be an error AND abort the program *)
Recover from Fatal Messages
Just like the usual
try ... with
in OCaml, you can useLogger.try_with
to intercept fatal messages. However, unlike messages sent via emit, there is no way to resume the aborted computation (as what you can do with an OCaml exception). Therefore, you have to provide a new value as a replacement. For example, the code:Logger.try_with ~fatal:(fun _ -> 42) @@ fun () -> Logger.fatal "abort!"
will give you the number
42
in the end. It intercepts the fatal message and gives42
instead.There are More!
We are still expanding this tutorial, but in the meanwhile, you can also check out our 📔 API reference.
Within
abort_at_any
, every message will become a fatal error:Logger.abort_at_any @@ fun () -> (* any message will be an error AND abort the program *)
Recover from Fatal Messages
Just like the usual
try ... with
in OCaml, you can useLogger.try_with
to intercept fatal messages. However, unlike messages sent via emit, there is no way to resume the aborted computation (as what you can do with an OCaml exception). Therefore, you have to provide a new value as a replacement. For example, the code:Logger.try_with ~fatal:(fun _ -> 42) @@ fun () -> Logger.fatal "abort!"
will give you the number
42
in the end. It intercepts the fatal message and gives42
instead.There are More!
We are still expanding this tutorial, but in the meanwhile, you can also check out our 📔 API reference.