Newer
Older
// --------------------------------------------------------------------------
// --- AST Source Code
// --------------------------------------------------------------------------
import React from 'react';
import * as Server from 'frama-c/server';
import * as States from 'frama-c/states';
import * as Dome from 'dome';
import { RichTextBuffer } from 'dome/text/buffers';
import { Text } from 'dome/text/editors';
import { IconButton } from 'dome/controls/buttons';
import { Component, TitleBar } from 'frama-c/LabViews';
import 'codemirror/theme/ambiance.css';
import 'codemirror/theme/solarized.css';
{ id: 'default', label: 'Default' },
{ id: 'ambiance', label: 'Ambiance' },
{ id: 'solarized light', label: 'Solarized Light' },
{ id: 'solarized dark', label: 'Solarized Dark' },
// --------------------------------------------------------------------------
// --- Pretty Printing (Browser Console)
// --------------------------------------------------------------------------
const PP = new Dome.PP('AST View');
// --------------------------------------------------------------------------
// --- Rich Text Printer
// --------------------------------------------------------------------------
async function loadAST(
buffer: RichTextBuffer, theFunction?: string, theMarker?: string,
) {
buffer.clear();
if (theFunction) {
buffer.log('// Loading', theFunction, '…');
(async () => {
try {
const data = await Server.GET({
endpoint: 'kernel.ast.printFunction',
params: theFunction,
});
buffer.operation(() => {
buffer.clear();

Michele Alberti
committed
if (!data) {
buffer.log('// No code for function', theFunction);

Michele Alberti
committed
}
buffer.printTextWithTags(data);
if (theMarker)
buffer.scroll(theMarker, undefined);
});
'Fail to retrieve the AST of function', theFunction,
'marker:', theMarker, err,
async function functionCallers(
updateSelection: (a: States.SelectionActions) => void,
kf: string,
) {
David Bühler
committed
try {
const data = await Server.GET({
endpoint: 'eva.callers',
params: kf,
});
const locations = data.map((d: string[2]) => (
{ function: d[0], marker: d[1] }
));
updateSelection({ locations });
} catch (err) {
PP.error(`Fail to retrieve callers of function ${kf}`, err);
David Bühler
committed
}
}
// --------------------------------------------------------------------------
// --- AST Printer
// --------------------------------------------------------------------------
const ASTview = () => {
const buffer = React.useMemo(() => new RichTextBuffer(), []);

Michele Alberti
committed
const printed: React.MutableRefObject<string | undefined> = React.useRef();
const [selection, updateSelection] = States.useSelection();
const multipleSelections = selection?.multiple.allSelections;
const [theme, setTheme] = Dome.useGlobalSetting('ASTview.theme', 'default');
const [fontSize, setFontSize] = Dome.useGlobalSetting('ASTview.fontSize', 12);
const [wrapText, setWrapText] = Dome.useSwitch('ASTview.wrapText', false);
const markers = States.useSyncArray('kernel.ast.markerKind');

Michele Alberti
committed
const theFunction = selection?.current?.function;
const theMarker = selection?.current?.marker;
React.useEffect(() => {
if (printed.current !== theFunction) {
printed.current = theFunction;
loadAST(buffer, theFunction, theMarker);
React.useEffect(() => {
const decorator = (marker: string) => {
if (multipleSelections?.some((location) => location?.marker === marker))
return 'highlighted-marker';
return undefined;
};
buffer.setDecorator(decorator);
}, [buffer, multipleSelections]);
React.useEffect(() => {
if (theMarker) buffer.scroll(theMarker, undefined);
const zoomIn = () => fontSize < 48 && setFontSize(fontSize + 2);
const zoomOut = () => fontSize > 4 && setFontSize(fontSize - 2);

Michele Alberti
committed
function onTextSelection(id: string) {
if (selection.current) {
const location = { ...selection.current, marker: id };
updateSelection({ location });

Michele Alberti
committed
}
}
function onContextMenu(id: string) {
David Bühler
committed
const items = [];
const marker = markers[id];
David Bühler
committed
if (marker?.kind === 'lvalue' && marker?.var === 'function') {
items.push({
label: `Go to definition of ${marker.name}`,

Michele Alberti
committed
onClick: () => {
const location = { function: marker.name };
updateSelection({ location });

Michele Alberti
committed
},
David Bühler
committed
});
}
if (marker?.kind === 'declaration'
&& marker?.var === 'function'
&& marker?.name) {
David Bühler
committed
items.push({
label: 'Go to callers',
onClick: () => functionCallers(updateSelection, marker.name),
David Bühler
committed
});
David Bühler
committed
if (items.length > 0)
Dome.popupMenu(items);
}
const selectTheme = (id?: string) => id && setTheme(id);
const checkTheme =
(th: { id: string }) => ({ checked: th.id === theme, ...th });
const themePopup =
() => Dome.popupMenu(THEMES.map(checkTheme), selectTheme);
<>
<IconButton
icon="ZOOM.OUT"
onClick={zoomOut}
/>
<IconButton
icon="ZOOM.IN"
onClick={zoomIn}
/>
<IconButton
icon="WRAPTEXT"
selected={wrapText}
onClick={setWrapText}
</TitleBar>
<Text
buffer={buffer}
mode="text/x-csrc"
theme={theme}
fontSize={fontSize}

Michele Alberti
committed
onSelection={onTextSelection}
onContextMenu={onContextMenu}
</>
};
// --------------------------------------------------------------------------
// --- Export Component
// --------------------------------------------------------------------------
export default () => (
<Component
id="frama-c.astview"
label="AST"
>
<ASTview />
</Component>
);
// --------------------------------------------------------------------------