From eb05e7ac47e50dde1cf7eec62c93f421cfb28cc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr> Date: Wed, 23 Feb 2022 14:36:58 +0100 Subject: [PATCH] [ivette] AST informations --- ivette/src/frama-c/index.tsx | 12 +- ivette/src/frama-c/kernel/ASTinfo.tsx | 335 ++++++++++++++++-- ivette/src/frama-c/kernel/ASTview.tsx | 4 +- ivette/src/frama-c/kernel/api/ast/index.ts | 31 +- ivette/src/frama-c/kernel/style.css | 86 ++++- ivette/src/frama-c/{utils.ts => richtext.tsx} | 84 ++++- src/plugins/server/kernel_ast.ml | 189 ++++++---- src/plugins/server/kernel_ast.mli | 26 ++ 8 files changed, 648 insertions(+), 119 deletions(-) rename ivette/src/frama-c/{utils.ts => richtext.tsx} (57%) diff --git a/ivette/src/frama-c/index.tsx b/ivette/src/frama-c/index.tsx index 9c3e42d30e1..cca6df663d7 100644 --- a/ivette/src/frama-c/index.tsx +++ b/ivette/src/frama-c/index.tsx @@ -55,18 +55,18 @@ Ivette.registerGroup({ Ivette.registerSidebar({ id: 'frama-c.globals', children: <Globals /> }); Ivette.registerToolbar({ id: 'frama-c.history', children: <History /> }); Ivette.registerStatusbar({ id: 'frama-c.message', children: <Status /> }); + Ivette.registerComponent({ + id: 'frama-c.astinfo', + label: 'Informations', + title: 'Contextual informations on current selection', + children: <ASTinfo /> + }); Ivette.registerComponent({ id: 'frama-c.astview', label: 'AST', title: 'Normalized C/ACSL Source Code', children: <ASTview />, }); - Ivette.registerComponent({ - id: 'frama-c.astinfo', - label: 'Informations', - title: 'Informations on currently selected item', - children: <ASTinfo />, - }); Ivette.registerComponent({ id: 'frama-c.sourcecode', label: 'Source Code', diff --git a/ivette/src/frama-c/kernel/ASTinfo.tsx b/ivette/src/frama-c/kernel/ASTinfo.tsx index 928411368bd..abf6d96d7c7 100644 --- a/ivette/src/frama-c/kernel/ASTinfo.tsx +++ b/ivette/src/frama-c/kernel/ASTinfo.tsx @@ -25,49 +25,322 @@ // -------------------------------------------------------------------------- import React from 'react'; +import * as Dome from 'dome'; +import { classes } from 'dome/misc/utils'; import * as States from 'frama-c/states'; -import * as Utils from 'frama-c/utils'; +import * as DATA from 'frama-c/kernel/api/data'; +import * as AST from 'frama-c/kernel/api/ast'; +import { Text } from 'frama-c/richtext'; +import { Icon } from 'dome/controls/icons'; +import { Code } from 'dome/controls/labels'; +import { IconButton } from 'dome/controls/buttons'; +import * as Boxes from 'dome/layout/boxes'; +import { TitleBar } from 'ivette'; -import { Vfill } from 'dome/layout/boxes'; -import { RichTextBuffer } from 'dome/text/buffers'; -import { Text } from 'dome/text/editors'; -import { getInfo } from 'frama-c/kernel/api/ast'; +// -------------------------------------------------------------------------- +// --- Marker Kinds +// -------------------------------------------------------------------------- + +interface MarkerKindProps { label: string; title: string } + +function MarkerKind(props: MarkerKindProps): JSX.Element { + const { label, title } = props; + return <span className="astinfo-markerkind" title={title}>{label}</span>; +} + +const GMARKER = + <MarkerKind label="M" title="Generic Marker" />; + +const MARKERS = new Map<AST.markerKind, JSX.Element>(); +[ + { + kind: AST.markerKind.declaration, + elt: <MarkerKind label="D" title="Declaration" />, + }, + { + kind: AST.markerKind.global, + elt: <MarkerKind label="G" title="Global" />, + }, + { + kind: AST.markerKind.lvalue, + elt: <MarkerKind label="L" title="L-value" />, + }, + { + kind: AST.markerKind.expression, + elt: <MarkerKind label="E" title="Expression" />, + }, + { + kind: AST.markerKind.statement, + elt: <MarkerKind label="S" title="Statement" />, + }, + { + kind: AST.markerKind.property, + elt: <MarkerKind label="P" title="Property" />, + }, + { + kind: AST.markerKind.term, + elt: <MarkerKind label="T" title="Term" />, + }, +].forEach(({ kind, elt }) => MARKERS.set(kind, elt)); // -------------------------------------------------------------------------- -// --- Information Panel +// --- Information Details // -------------------------------------------------------------------------- -export default function ASTinfo(): JSX.Element { +interface InfoItemProps { + label: string; + title: string; + descr: DATA.text; +} + +function InfoItem(props: InfoItemProps): JSX.Element { + return ( + <div className="astinfo-infos"> + <div + className="dome-text-label astinfo-kind" + title={props.title} + > + {props.label} + </div> + <div className="dome-text-cell astinfo-data"> + <Text text={props.descr} /> + </div> + </div> + ); +} + +interface ASTinfos { + id: string; + label: string; + title: string; + descr: DATA.text; +} + +interface InfoSectionProps { + marker: AST.marker; + markerInfo: AST.markerInfoData; + filter: string; + selected: boolean; + hovered: boolean; + pinned: boolean; + onPin: () => void; + onHover: (hover: boolean) => void; + onSelect: () => void; + onRemove: () => void; +} + +function MarkInfos(props: InfoSectionProps): JSX.Element { + const [unfold, setUnfold] = React.useState(true); + const [more, setMore] = React.useState(false); + const { marker, markerInfo } = props; + const allInfos: ASTinfos[] = + States.useRequest(AST.getInformations, marker) ?? []; + const highlight = classes( + props.selected && !props.pinned && 'transient', + props.hovered && 'hovered', + ); + const descr = markerInfo.descr ?? markerInfo.name; + const kind = MARKERS.get(markerInfo.kind) ?? GMARKER; + const fs = props.filter.split(':'); + const filtered = allInfos.filter((info) => !fs.some((m) => m === info.id)); + const infos = more ? allInfos : filtered; + const hasMore = filtered.length < allInfos.length; + return ( + <div + className={`astinfo-section ${highlight}`} + onMouseEnter={() => props.onHover(true)} + onMouseLeave={() => props.onHover(false)} + onDoubleClick={props.onSelect} + > + <div + key="MARKER" + className={`astinfo-markerbar ${highlight}`} + title={descr} + > + <Icon + className="astinfo-folderbutton" + style={{ visibility: infos.length ? 'visible' : 'hidden' }} + size={9} + offset={-2} + id={unfold ? 'TRIANGLE.DOWN' : 'TRIANGLE.RIGHT'} + onClick={() => setUnfold(!unfold)} + /> + <Code className="astinfo-markercode"> + {kind}{descr} + </Code> + <IconButton + className="astinfo-markerbutton" + title="Pin/unpin information in sidebar" + size={9} + offset={0} + icon="PIN" + selected={props.pinned} + onClick={props.onPin} + /> + <IconButton + style={{ display: hasMore ? undefined : 'none' }} + className="astinfo-markerbutton" + title="Show all available informations" + size={9} + offset={0} + icon="CIRC.PLUS" + selected={more} + onClick={() => setMore(!more)} + /> + <IconButton + className="astinfo-markerbutton" + title="Remove informations" + size={9} + offset={0} + icon="CIRC.CLOSE" + onClick={props.onRemove} + /> + </div> + {unfold && infos.map((info) => <InfoItem key={info.id} {...info} />)} + </div> + ); +} - const buffer = React.useMemo(() => new RichTextBuffer(), []); - const [selection, updateSelection] = States.useSelection(); - const marker = selection?.current?.marker; - const data = States.useRequest(getInfo, marker); - - React.useEffect(() => { - buffer.clear(); - const style = { css: 'color: var(--text-highlighted)' }; - if (data) Utils.printTextWithTags(buffer, data, style); - }, [buffer, data]); - - // Callbacks - function onTextSelection(id: string): void { - // For now, the only markers are functions. - const location = { fct: id }; - updateSelection({ location }); +// -------------------------------------------------------------------------- +// --- Information Selection State +// -------------------------------------------------------------------------- + +type Mark = { fct: string; marker: AST.marker }; + +const reload = new Dome.Event('frama-c.astinfo'); + +class InfoMarkers { + + private selection: Mark[] = []; + private pinned = new Map<string, boolean>(); + + setSelected(location?: States.Location, pinned = false): void { + const fct = location?.fct; + const marker = location?.marker; + const keep = + (m: Mark): boolean => m.marker === marker || this.isPinned(m.marker); + const self = + (m: Mark): boolean => m.marker === marker; + this.selection = this.selection.filter(keep); + if (fct && marker && !this.selection.some(self)) { + this.selection.push({ fct, marker }); + this.pinned.set(marker, pinned); + } + reload.emit(); + } + + isPinned(marker: AST.marker | undefined): boolean { + return (marker !== undefined) && (this.pinned.get(marker) ?? false); + } + + setPinned(marker: AST.marker, pinned: boolean): void { + this.pinned.set(marker, pinned); + reload.emit(); + } + + removeMarker(marker: AST.marker): void { + this.selection = this.selection.filter((m) => m.marker !== marker); + this.pinned.delete(marker); + reload.emit(); } - // Component + getSelected(): Mark[] { return this.selection; } + +} + +// -------------------------------------------------------------------------- +// --- Context Menu Filter +// -------------------------------------------------------------------------- + +function openFilter( + infos: ASTinfos[], + filter: string, + onChange: (f: string) => void, +): void { + const menuItems = infos.map((info) => { + const fs = filter.split(':'); + const checked = !fs.some((m) => m === info.id); + const onClick = (): void => { + const newFs = + checked + ? fs.concat(info.id) + : fs.filter((m) => m !== info.id); + onChange(newFs.join(':')); + }; + return { + id: info.id, + label: `${info.title} (${info.label})`, + checked, + onClick, + }; + }); + Dome.popupMenu(menuItems); + return; +} + +// -------------------------------------------------------------------------- +// --- Information Panel +// -------------------------------------------------------------------------- + +export default function ASTinfo(): JSX.Element { + // Hooks + Dome.useUpdate(reload); + const markers = React.useMemo(() => new InfoMarkers(), []); + const markerInfos = States.useSyncArray(AST.markerInfo, false); + const [selection] = States.useSelection(); + const [hoveredLoc] = States.useHovered(); + const informations = States.useRequest(AST.getInformations, null) ?? []; + const [filter, setFilter] = + Dome.useStringSettings('frama-c.sidebar.astinfo.filter', ''); + const location = selection?.current; + const selected = location?.marker; + const hovered = hoveredLoc?.marker; + React.useEffect(() => markers.setSelected(location), [markers, location]); + Dome.useEvent(States.MetaSelection, (loc) => { + if (selected) markers.setPinned(selected, true); + markers.setSelected(loc, true); + }); + // Rendering + const renderMark = (mark: Mark): JSX.Element | null => { + const { marker } = mark; + const markerInfo = markerInfos.getData(marker); + if (!markerInfo) return null; + const pinned = markers.isPinned(marker); + const isSelected = selected === marker; + const isHovered = hovered === marker; + const onPin = () => void markers.setPinned(marker, !pinned); + const onRemove = () => void markers.removeMarker(marker); + const onSelect = () => void States.setSelection(mark); + const onHover = + (h: boolean): void => States.setHovered(h ? mark : undefined); + return ( + <MarkInfos + key={marker} + marker={marker} + markerInfo={markerInfo} + pinned={pinned} + selected={isSelected} + filter={filter} + hovered={isHovered} + onPin={onPin} + onRemove={onRemove} + onHover={onHover} + onSelect={onSelect} + /> + ); + }; return ( <> - <Vfill> - <Text - buffer={buffer} - mode="text" - onSelection={onTextSelection} - readOnly + <TitleBar> + <IconButton + icon="CLIPBOARD" + onClick={() => openFilter(informations, filter, setFilter)} + title="Information Filters" /> - </Vfill> + </TitleBar> + <Boxes.Vfill> + {markers.getSelected().map(renderMark)} + </Boxes.Vfill> </> ); } diff --git a/ivette/src/frama-c/kernel/ASTview.tsx b/ivette/src/frama-c/kernel/ASTview.tsx index d54fb085d2f..53650f233aa 100644 --- a/ivette/src/frama-c/kernel/ASTview.tsx +++ b/ivette/src/frama-c/kernel/ASTview.tsx @@ -30,7 +30,7 @@ import React from 'react'; import _ from 'lodash'; import * as Server from 'frama-c/server'; import * as States from 'frama-c/states'; -import * as Utils from 'frama-c/utils'; +import * as RichText from 'frama-c/richtext'; import * as Dome from 'dome'; import { RichTextBuffer } from 'dome/text/buffers'; @@ -66,7 +66,7 @@ async function loadAST( if (!data) { buffer.log('// No code for function', theFunction); } - Utils.printTextWithTags(buffer, data); + RichText.printTextWithTags(buffer, data); if (theMarker) buffer.scroll(theMarker); } catch (err) { diff --git a/ivette/src/frama-c/kernel/api/ast/index.ts b/ivette/src/frama-c/kernel/api/ast/index.ts index 73dad1c7216..8e2a100ffc9 100644 --- a/ivette/src/frama-c/kernel/api/ast/index.ts +++ b/ivette/src/frama-c/kernel/api/ast/index.ts @@ -458,15 +458,34 @@ const functions_internal: State.Array<Json.key<'#functions'>,functionsData> = { /** AST Functions */ export const functions: State.Array<Json.key<'#functions'>,functionsData> = functions_internal; -const getInfo_internal: Server.GetRequest<marker,text> = { +/** Updated AST informations */ +export const getInformationsUpdate: Server.Signal = { + name: 'kernel.ast.getInformationsUpdate', +}; + +const getInformations_internal: Server.GetRequest< + marker | + undefined, + { id: string, label: string, title: string, descr: text }[] + > = { kind: Server.RqKind.GET, - name: 'kernel.ast.getInfo', + name: 'kernel.ast.getInformations', input: jMarker, - output: jText, - signals: [], + output: Json.jList( + Json.jObject({ + id: Json.jFail(Json.jString,'String expected'), + label: Json.jFail(Json.jString,'String expected'), + title: Json.jFail(Json.jString,'String expected'), + descr: jTextSafe, + })), + signals: [[ { name: 'kernel.ast.getInformationsUpdate' } ]], }; -/** Get textual information about a marker */ -export const getInfo: Server.GetRequest<marker,text>= getInfo_internal; +/** Get available informations about markers. When no marker is given, returns all kinds of informations (with empty `descr` field). */ +export const getInformations: Server.GetRequest< + marker | + undefined, + { id: string, label: string, title: string, descr: text }[] + >= getInformations_internal; const getMarkerAt_internal: Server.GetRequest< [ string, number, number ], diff --git a/ivette/src/frama-c/kernel/style.css b/ivette/src/frama-c/kernel/style.css index be30b0d1581..52e6d44c63d 100644 --- a/ivette/src/frama-c/kernel/style.css +++ b/ivette/src/frama-c/kernel/style.css @@ -9,6 +9,18 @@ text-overflow: ellipsis; } +/* -------------------------------------------------------------------------- */ +/* --- Lightweight Text Markers --- */ +/* -------------------------------------------------------------------------- */ + +.kernel-text { + white-space: pre; +} + +.kernel-text-marker:hover { + background: lightgreen; +} + /* -------------------------------------------------------------------------- */ /* --- AST View --- */ /* -------------------------------------------------------------------------- */ @@ -17,6 +29,10 @@ background-color: var(--highlighted-marker); } +.hovered-marker { + background-color: lightblue; +} + .dead-code { background-color: var(--dead-code); border-bottom: solid 0.1em var(--dead-code); @@ -54,7 +70,75 @@ } /* -------------------------------------------------------------------------- */ -/* --- Globals --- */ +/* --- Informations --- */ +/* -------------------------------------------------------------------------- */ + +.astinfo-section { + position: relative; + overflow: hidden; + padding-top: 1px; + padding-bottom: 2px; +} + +.astinfo-section.hovered { + background: #ddd; +} + +.astinfo-section.transient { + background: #f5d69e; +} + +.astinfo-folderbutton { + flex: 0 0 auto; +} + +.astinfo-markerbar { + display: flex; + overflow: hidden; + padding-left: 4px; +} + +.astinfo-markerkind { + background: #b1c6dc; + position: relative; + top: -1px; + border-radius: 2px; + font-size: 7pt; + padding: 2px; + color: black; + user-select: none; + margin-right: 3pt; +} + +.astinfo-markercode { + flex: 1 1 auto; + text-overflow: ellipsis; + cursor: default; +} + +.astinfo-markerbutton { + flex: 0 1 auto; + margin: 0px; + position: relative; +} + +.astinfo-infos { + display: flex ; + margin-top: 1px ; + margin-bottom: 1px ; +} + +.astinfo-kind { + flex: 0 1 50px; + text-align: center; + margin-top: 1px; + margin-left: 8px; + color: #999; + font-size: smaller; +} + +/* -------------------------------------------------------------------------- */ +/* --- Messages --- */ /* -------------------------------------------------------------------------- */ .message-search { diff --git a/ivette/src/frama-c/utils.ts b/ivette/src/frama-c/richtext.tsx similarity index 57% rename from ivette/src/frama-c/utils.ts rename to ivette/src/frama-c/richtext.tsx index 86df5df5f3d..3ce352526b6 100644 --- a/ivette/src/frama-c/utils.ts +++ b/ivette/src/frama-c/richtext.tsx @@ -26,12 +26,14 @@ /** * @packageDocumentation - * @module frama-c/utils -*/ + * @module frama-c/richtext + */ +import React from 'react'; import * as Dome from 'dome'; import * as DomeBuffers from 'dome/text/buffers'; import * as KernelData from 'frama-c/kernel/api/data'; +import { classes } from 'dome/misc/utils'; const D = new Dome.Debug('Utils'); @@ -42,33 +44,93 @@ const D = new Dome.Debug('Utils'); /** * Print text containing tags into buffer. * @param buffer Rich text buffer to print into. - * @param contents Actual text containing tags. + * @param text Actual text containing tags. * @param options Specify particular marker options. */ export function printTextWithTags( buffer: DomeBuffers.RichTextBuffer, - contents: KernelData.text, + text: KernelData.text, options?: DomeBuffers.MarkerProps, ): void { - if (Array.isArray(contents)) { + if (Array.isArray(text)) { let marker = false; - const tag = contents.shift(); + const tag = text.shift(); if (tag) { if (Array.isArray(tag)) { - contents.unshift(tag); + text.unshift(tag); } else { buffer.openTextMarker({ id: tag, ...options ?? {} }); marker = true; } } - contents.forEach((txt) => printTextWithTags(buffer, txt, options)); + text.forEach((txt) => printTextWithTags(buffer, txt, options)); if (marker) { marker = false; buffer.closeTextMarker(); } - } else if (typeof contents === 'string') { - buffer.append(contents); + } else if (typeof text === 'string') { + buffer.append(text); } else { - D.error('Unexpected text', contents); + D.error('Unexpected text', text); + } +} + +// -------------------------------------------------------------------------- +// --- Lightweight Text Renderer +// -------------------------------------------------------------------------- + +interface MarkerProps { + marker: string; + onMarker?: (marker: string) => void; + children?: React.ReactNode; +} + +function Marker(props: MarkerProps): JSX.Element { + const { marker, onMarker, children } = props; + const onClick = (): void => { if (onMarker) onMarker(marker); }; + return ( + <span + className="kernel-text-marker" + onClick={onClick} + > + {children} + </span> + ); +} + +function makeContents(text: KernelData.text): React.ReactNode { + if (Array.isArray(text)) { + const tag = text.shift(); + let marker: string | undefined; + if (tag) { + if (Array.isArray(tag)) { + text.unshift(tag); + } else if (typeof tag === 'string') { + marker = tag; + } + // otherwize, ignore tag + } + const contents = React.Children.map(text, makeContents); + if (marker) + return <Marker marker={marker}>{contents}</Marker>; + return <>{contents}</>; + } if (typeof text === 'string') { + return text; } + D.error('Unexpected text', text); + return null; + } + +export interface TextProps { + text: KernelData.text; + onMarker?: (marker: string) => void; + className?: string; +} + +export function Text(props: TextProps): JSX.Element { + const className = classes('kernel-text', 'dome-text-code', props.className); + return <div className={className}>{makeContents(props.text)}</div>; +} + +// -------------------------------------------------------------------------- diff --git a/src/plugins/server/kernel_ast.ml b/src/plugins/server/kernel_ast.ml index d8cf92f5501..bc13a83fd26 100644 --- a/src/plugins/server/kernel_ast.ml +++ b/src/plugins/server/kernel_ast.ml @@ -543,74 +543,139 @@ struct end (* -------------------------------------------------------------------------- *) -(* --- Information --- *) +(* --- Marker Informations --- *) (* -------------------------------------------------------------------------- *) -module Info = struct - open Printer_tag +module Informations = +struct + + type info = { + id: string; + rank: int; + label: string; + title: string; + pretty: Format.formatter -> Printer_tag.localizable -> unit + } + + (* Info markers serialization *) + + module S = + struct + type t = (info * Jtext.t) + let jtype = Package.(Jrecord[ + "id", Jstring ; + "label", Jstring ; + "title", Jstring ; + "descr", Jtext.jtype ; + ]) + let of_json _ = failwith "Informations.Info" + let to_json (info,text) = `Assoc [ + "id", `String info.id ; + "label", `String info.label ; + "title", `String info.title ; + "descr", text ; + ] + end + + (* Info markers registry *) + + let rankId = ref 0 + let registry : (string,info) Hashtbl.t = Hashtbl.create 0 + + let jtext pp marker = + try + let buffer = Jbuffer.create () in + let fmt = Jbuffer.formatter buffer in + pp fmt marker; + Format.pp_print_flush fmt (); + Jbuffer.contents buffer + with Not_found -> + `Null + + let rank ({rank},_) = rank + let by_rank a b = Stdlib.compare (rank a) (rank b) + + let get_informations tgt = + let infos = ref [] in + Hashtbl.iter + (fun _ info -> + match tgt with + | None -> infos := (info, `Null) :: !infos + | Some marker -> + let text = jtext info.pretty marker in + if not (Jbuffer.is_empty text) then + infos := (info, text) :: !infos + ) registry ; + List.sort by_rank !infos + + let signal = Request.signal ~package + ~name:"getInformationsUpdate" + ~descr:(Md.plain "Updated AST informations") + + let update () = Request.emit signal + + let register ~id ~label ~title pretty = + let rank = incr rankId ; !rankId in + let info = { id ; rank ; label ; title ; pretty } in + if Hashtbl.mem registry id then + ( let msg = Format.sprintf + "Server.Kernel_ast.register_info: duplicate %S" id in + raise (Invalid_argument msg) ); + Hashtbl.add registry id info - let print_function fmt name = - let stag = Format.String_tag name in - Format.pp_open_stag fmt stag; - Format.pp_print_string fmt name; - Format.pp_close_stag fmt () - - let print_kf fmt kf = print_function fmt (Kernel_function.get_name kf) - - let print_variable fmt vi = - Format.fprintf fmt "Variable %s has type %a.@." - vi.vname Printer.pp_typ vi.vtype; - let kf = Kernel_function.find_defining_kf vi in - let pp_kf fmt kf = Format.fprintf fmt " of function %a" print_kf kf in - Format.fprintf fmt "It is a %s variable%a.@." - (if vi.vglob then "global" else if vi.vformal then "formal" else "local") - (Format.pp_print_option pp_kf) kf; - if vi.vtemp then - Format.fprintf fmt "This is a temporary variable%s.@." - (match vi.vdescr with None -> "" | Some descr -> " for " ^ descr); - Format.fprintf fmt "It is %sreferenced and its address is %staken." - (if vi.vreferenced then "" else "not ") - (if vi.vaddrof then "" else "not ") - - let print_varinfo fmt vi = - if Cil.isFunctionType vi.vtype - then - Format.fprintf fmt "%a is a C function of type '%a'." - print_function vi.vname Printer.pp_typ vi.vtype - else print_variable fmt vi - - let print_lvalue fmt _loc = function - | Var vi, NoOffset -> print_varinfo fmt vi - | lval -> - Format.fprintf fmt "This is an lvalue of type %a." - Printer.pp_typ (Cil.typeOfLval lval) - - let print_localizable fmt = function - | PExp (_, _, e) -> - Format.fprintf fmt "This is a pure C expression of type %a." - Printer.pp_typ (Cil.typeOf e) - | PLval (_, _, lval) as loc -> print_lvalue fmt loc lval - | PVDecl (_, _, vi) -> - Format.fprintf fmt "This is the declaration of variable %a.@.@." - Printer.pp_varinfo vi; - print_varinfo fmt vi - | PStmt (kf, _) | PStmtStart (kf, _) -> - Format.fprintf fmt "This is a statement of function %a." print_kf kf - | _ -> () - - let get_marker_info loc = - let buffer = Jbuffer.create () in - let fmt = Jbuffer.formatter buffer in - print_localizable fmt loc; - Format.pp_print_flush fmt (); - Jbuffer.contents buffer end let () = Request.register ~package - ~kind:`GET ~name:"getInfo" - ~descr:(Md.plain "Get textual information about a marker") - ~input:(module Marker) ~output:(module Jtext) - Info.get_marker_info + ~kind:`GET ~name:"getInformations" + ~descr:(Md.plain + "Get available informations about markers. \ + When no marker is given, returns all kinds \ + of informations (with empty `descr` field).") + ~input:(module Joption(Marker)) + ~output:(module Jlist(Informations.S)) + ~signals:[Informations.signal] + Informations.get_informations + +(* -------------------------------------------------------------------------- *) +(* --- Default Kernel Informations --- *) +(* -------------------------------------------------------------------------- *) + +let () = Informations.register + ~id:"kernel.ast.varinfo" + ~label:"Var" + ~title:"Variable Informations" + begin fun fmt loc -> + match loc with + | PLval (_ , _, (Var x,NoOffset)) | PVDecl(_,_,x) -> + if not x.vreferenced then Format.pp_print_string fmt "unused " ; + begin + match x.vstorage with + | NoStorage -> () + | Extern -> Format.pp_print_string fmt "extern " + | Static -> Format.pp_print_string fmt "static " + | Register -> Format.pp_print_string fmt "register " + end ; + if x.vghost then Format.pp_print_string fmt "ghost " ; + if x.vaddrof then Format.pp_print_string fmt "aliased " ; + if x.vformal then Format.pp_print_string fmt "formal" else + if x.vglob then Format.pp_print_string fmt "global" else + if x.vtemp then Format.pp_print_string fmt "temporary" else + Format.pp_print_string fmt "local" ; + | _ -> raise Not_found + end + +let () = Informations.register + ~id:"kernel.ast.typeinfo" + ~label:"Type" + ~title:"Type of C/ASCL expression" + begin fun fmt loc -> + let open Printer in + match loc with + | PExp (_, _, e) -> pp_typ fmt (Cil.typeOf e) + | PLval (_, _, lval) -> pp_typ fmt (Cil.typeOfLval lval) + | PTermLval(_,_,_,lv) -> pp_logic_type fmt (Cil.typeOfTermLval lv) + | _ -> raise Not_found + end (* -------------------------------------------------------------------------- *) (* --- Marker at a position --- *) diff --git a/src/plugins/server/kernel_ast.mli b/src/plugins/server/kernel_ast.mli index 448d6f373de..3dd3868e276 100644 --- a/src/plugins/server/kernel_ast.mli +++ b/src/plugins/server/kernel_ast.mli @@ -62,6 +62,32 @@ module KfMarker : Data.S with type t = kernel_function * Printer_tag.localizable module Printer : Printer_tag.S_pp +(* -------------------------------------------------------------------------- *) +(** Ast Informations *) +(* -------------------------------------------------------------------------- *) + +module Informations : +sig + (** + Registers a marker information printer. + Identifier [id] shall be unique. + Label [label] shall be very short. + Description shall succintly describe the kind of informations. + The printer is allowed to raise [Not_found] exception, + which is interpreted as there is no information of this kind for + the localizable. + *) + val register : + id:string -> label:string -> title:string -> + (Format.formatter -> Printer_tag.localizable -> unit) -> unit + + (** Updated informations signal *) + val signal : Request.signal + + (** Emits a signal to server clients to reload AST marker informations. *) + val update : unit -> unit +end + (* -------------------------------------------------------------------------- *) (** Globals *) (* -------------------------------------------------------------------------- *) -- GitLab