Hi! I'm trying to understand when/how clickable references to formal entities are created. Here's a minimal example:
theory Scratch
imports Main
keywords "dummy0" "dummy1" :: thy_goal
begin
ML‹
fun do_something tyname ctxt =
let val _ = Proof_Context.read_type_name {proper = true, strict = false} ctxt tyname in
ctxt
end
fun do_nothing _ ctxt = ctxt
val _ = Outer_Syntax.local_theory @{command_keyword "dummy0"} "" (Parse.const >> do_something)
val _ = Outer_Syntax.local_theory @{command_keyword "dummy1"} "" (Parse.const >> do_nothing)
›
dummy0 nat ―‹Clickable reference›
dummy1 nat ―‹No reference›
end
How does Isabelle achieve the clickable reference to nat
in case of dummy0 nat
? Parse.const
is a mere string parser, so where and how is the information about the position of the string retained that would later allow read_type_name
to add markup?
A pide report is generated (see context_position.ML
) and sent to Isabelle/Scala. Parse.const
will not do that but Proof_Context.read_type_name
will.
Thanks Fabian! But read_type_name
takes a mere string as an argument, without positioning information? How/where is the position information about the original argument retained after Parse.term
?
I assume that is stored in the context.
I'm sorry, I don't follow yet -- can you elaborate?
There is the ctxt
argument in your do_*
function -- this is a context where such arbitrary contextual information can be stored, and judging from the signatures, this is where the position comes from.
If you take a look at the definitions you should be able to follow how exactly.
Yes I saw that Parse.typ
stores some information about the input being parsed 'as a type' somewhere. But even so, it only returns a plain string, so even with the context being passed around, I don't understand how read_type_name
could know which source code position its input is coming from.
I'm not sure what you mean with 'only returns a plain string' - No method involved here does that ;)
There is read_type_name
which takes a context
and Parse.const
which is of type string parser
.
Yes. Parse.const
is a string parser
, so the parser's output is a string -- ISTM that unless position information is somehow hidden in the string, read_type_name
would not be able to add markup for it in the original source.
Ok... that does indeed seem to be the case. String.explode
shows various positioning information in the string that are not displayed when printing it e.g. via tracing
ML‹
fun do_something tyname ctxt =
let
val _ = tyname |> String.explode |> List.map Char.toString |> String.concatWith "" |> tracing
val _ = Proof_Context.read_type_name {proper = true, strict = false} ctxt tyname in
ctxt
end
val _ = Outer_Syntax.local_theory @{command_keyword "dummy0"} "" (Parse.typ >> do_something)
›
dummy0 nat ―‹Clickable reference›
―‹\^E\^Finput\^Fdelimited=false\^Foffset=8\^Fend_offset=11\^Fid=-15172\^Enat\^E\^F\^E›
Also the string parser
is not a function to string - it is T list -> 'a * T list
, and the whole parser for the command is of type (local_theory -> local_theory) parser
.
Yes, that's understood
But, ultimately, the position information is not passed through context here, but through the string itself -- which is difficult to notice because it's elided upon normal printing.
Yup. That is not obvious from the signature but when you read through the code it becomes clear:
fun read_type_name flags ctxt text =
let
val source = Syntax.read_input text;
val (T, reports) = check_type_name flags ctxt (Input.source_content source);
val _ = Context_Position.reports ctxt reports;
Do you happen to know if there are helper functions for removing such position annotations to get "plain string"?
Not by memory but there must be, look at the module where the type of source
here is defined.
Syntax.read_input #> Input.string_of
seems to work
Great, I think I can make progress now. Thanks for the help! :pray:
Last updated: Dec 21 2024 at 16:20 UTC