From 4ed2d2d41784d1ed03e85d4bc46e3aa5ece01c31 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 18 Jun 2026 20:45:01 +0100 Subject: [PATCH] Add VC Simplification Support --- client/src/types/derivation-nodes.ts | 35 ----- client/src/types/diagnostics.ts | 10 +- client/src/types/vc-implications.ts | 11 ++ client/src/webview/script.ts | 20 +-- client/src/webview/styles.ts | 73 +++++++--- client/src/webview/views/context/variables.ts | 7 +- .../views/diagnostics/derivation-nodes.ts | 137 ------------------ .../webview/views/diagnostics/diagnostics.ts | 29 +++- .../src/webview/views/diagnostics/errors.ts | 10 +- .../views/diagnostics/vc-implications.ts | 73 ++++++++++ server/pom.xml | 2 +- .../java/dtos/diagnostics/RefinementDTO.java | 17 +++ .../java/dtos/diagnostics/VCBinderDTO.java | 20 +++ .../dtos/diagnostics/VCImplicationDTO.java | 28 ++++ .../VCSimplificationResultDTO.java | 18 +++ .../java/dtos/errors/RefinementErrorDTO.java | 11 +- .../dtos/errors/StateRefinementErrorDTO.java | 11 +- 17 files changed, 277 insertions(+), 235 deletions(-) delete mode 100644 client/src/types/derivation-nodes.ts create mode 100644 client/src/types/vc-implications.ts delete mode 100644 client/src/webview/views/diagnostics/derivation-nodes.ts create mode 100644 client/src/webview/views/diagnostics/vc-implications.ts create mode 100644 server/src/main/java/dtos/diagnostics/RefinementDTO.java create mode 100644 server/src/main/java/dtos/diagnostics/VCBinderDTO.java create mode 100644 server/src/main/java/dtos/diagnostics/VCImplicationDTO.java create mode 100644 server/src/main/java/dtos/diagnostics/VCSimplificationResultDTO.java diff --git a/client/src/types/derivation-nodes.ts b/client/src/types/derivation-nodes.ts deleted file mode 100644 index f06386c..0000000 --- a/client/src/types/derivation-nodes.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Type definitions used in refinement errors for expanding node simplifications - -export type DerivationNode = - | ValDerivationNode - | VarDerivationNode - | BinaryDerivationNode - | UnaryDerivationNode - | IteDerivationNode; - -export type ValDerivationNode = { - value: any; - origin: DerivationNode; -} - -export type VarDerivationNode = { - var: string; - origin?: DerivationNode; -} - -export type BinaryDerivationNode = { - op: string; - left: ValDerivationNode; - right: ValDerivationNode; -} - -export type UnaryDerivationNode = { - op: string; - operand: ValDerivationNode; -} - -export type IteDerivationNode = { - condition: ValDerivationNode; - thenBranch: ValDerivationNode; - elseBranch: ValDerivationNode; -} diff --git a/client/src/types/diagnostics.ts b/client/src/types/diagnostics.ts index 02314e3..1ed3f32 100644 --- a/client/src/types/diagnostics.ts +++ b/client/src/types/diagnostics.ts @@ -1,5 +1,5 @@ -import type { ValDerivationNode } from './derivation-nodes'; import type { Range } from './context'; +import type { VCSimplificationResult } from './vc-implications'; // Type definitions used for LiquidJava diagnostics @@ -58,8 +58,8 @@ export type RefinementError = BaseDiagnostic & { category: 'error'; type: 'refinement-error'; translationTable: TranslationTable; - expected: ValDerivationNode; - found: ValDerivationNode; + expected: string; + found: VCSimplificationResult; customMessage: string; counterexample: string; } @@ -75,8 +75,8 @@ export type StateRefinementError = BaseDiagnostic & { category: 'error'; type: 'state-refinement-error'; translationTable: TranslationTable; - expected: ValDerivationNode; - found: ValDerivationNode; + expected: string; + found: VCSimplificationResult; customMessage: string; } diff --git a/client/src/types/vc-implications.ts b/client/src/types/vc-implications.ts new file mode 100644 index 0000000..2ecc53a --- /dev/null +++ b/client/src/types/vc-implications.ts @@ -0,0 +1,11 @@ +export type VCImplication = { + name: string | null; + type: string | null; + predicate: string; + next: VCImplication | null; +} + +export type VCSimplificationResult = { + implication: VCImplication; + origin: VCSimplificationResult | null; +} diff --git a/client/src/webview/script.ts b/client/src/webview/script.ts index 78762de..ca33de4 100644 --- a/client/src/webview/script.ts +++ b/client/src/webview/script.ts @@ -1,4 +1,4 @@ -import { handleDerivableNodeClick, handleDerivationResetClick } from "./views/diagnostics/derivation-nodes"; +import { handleVCImplicationStepClick } from "./views/diagnostics/vc-implications"; import { renderLoading } from "./views/loading"; import { renderStopped } from "./views/stopped"; import { renderStateMachineView } from "./views/fsm/fsm"; @@ -131,21 +131,11 @@ export function getScript(vscode: VSCodeApi, document: Document, window: Window) return; } - // derivation expansion click - const derivableNode = target.closest?.('.derivable-node'); - if (derivableNode) { + // VC implication simplification step buttons + const vcImplicationStepButton = target.closest?.('.vc-step-btn'); + if (vcImplicationStepButton) { e.stopPropagation(); - if (handleDerivableNodeClick(derivableNode)) { - updateView(); - } - return; - } - - // derivation reset button - const derivationResetButton = target.closest?.('.derivation-reset-btn'); - if (derivationResetButton) { - e.stopPropagation(); - if (handleDerivationResetClick(derivationResetButton)) { + if (handleVCImplicationStepClick(vcImplicationStepButton)) { updateView(); } return; diff --git a/client/src/webview/styles.ts b/client/src/webview/styles.ts index 29f35d3..fe9ee25 100644 --- a/client/src/webview/styles.ts +++ b/client/src/webview/styles.ts @@ -271,18 +271,6 @@ export function getStyles(): string { .link:hover { text-decoration: underline; } - .node-var { - color: var(--lj-token-identifier); - } - .node-value { - color: var(--vscode-editor-foreground); - } - .node-number { - color: var(--lj-token-number); - } - .node-boolean { - color: var(--lj-token-boolean); - } .lj-expression, .lj-expression-code { font-family: var(--vscode-editor-font-family); @@ -334,19 +322,59 @@ export function getStyles(): string { .clickable:hover { font-weight: bold; } - .derivation-container { + .vc-container { display: flex; justify-content: space-between; - align-items: center; + align-items: flex-start; gap: 1rem; + margin: 0.5rem 0; } - .reset-btn { + .vc-chain { + flex: 1; + display: flex; + flex-direction: column; + gap: 0.25rem; + min-width: 0; + } + .vc-line { + display: flex; + align-items: flex-start; + gap: 0.5rem; + min-width: 0; + } + .vc-line-content { + flex: 0 1 auto; + min-width: 0; + overflow-wrap: anywhere; + } + .vc-node { + display: inline; + padding: 0; + border: none; + background: none; + color: var(--vscode-editor-foreground); + font: inherit; + text-align: left; + } + .vc-node:hover { + background: none; + } + .vc-binder { + color: var(--vscode-descriptionForeground); + } + .vc-step-controls { + display: inline-flex; + align-items: center; + gap: 0.125rem; + flex-shrink: 0; + } + .vc-step-btn { margin: 0; display: inline-flex; align-items: center; justify-content: center; - width: 1.75rem; - height: 1.75rem; + width: 1.5rem; + height: 1.25rem; padding: 0; background-color: transparent; color: var(--vscode-button-foreground); @@ -357,12 +385,17 @@ export function getStyles(): string { flex-shrink: 0; opacity: 0.7; } - .reset-btn:hover { + .vc-step-btn .codicon { + font-size: 1.5rem; + } + .vc-step-btn:hover { font-weight: bold; background-color: transparent; + opacity: 1; } - .reset-btn:disabled { - opacity: 0.5; + .vc-step-btn:disabled { + cursor: default; + opacity: 0.35; } button { padding: 0.2rem 0.6rem; diff --git a/client/src/webview/views/context/variables.ts b/client/src/webview/views/context/variables.ts index 209bde8..ae1d763 100644 --- a/client/src/webview/views/context/variables.ts +++ b/client/src/webview/views/context/variables.ts @@ -5,7 +5,6 @@ import { escapeHtml, getSimpleName } from "../../utils"; import { renderToggleSection, renderHighlightButton, renderDiagnosticRevealButton } from "../sections"; export function renderContextVariables(variables: LJVariable[], isExpanded: boolean, errorAtCursor?: RefinementMismatchError): string { - const expected = errorAtCursor ? errorAtCursor.expected.value : undefined; const relevantNames = new Set(Object.keys(errorAtCursor?.translationTable || {})); return /*html*/`
@@ -33,7 +32,7 @@ export function renderContextVariables(variables: LJVariable[], isExpanded: bool ${renderHighlightedInlineExpression(variable.refinement)} `}).join('')} - ${errorAtCursor ? renderFailingRefinement(errorAtCursor, expected!) : ''} + ${errorAtCursor ? renderFailingRefinement(errorAtCursor) : ''} `: '

No variables declared at the cursor position

'} @@ -42,11 +41,11 @@ export function renderContextVariables(variables: LJVariable[], isExpanded: bool `; } -function renderFailingRefinement(errorAtCursor: RefinementMismatchError, expected: string): string { +function renderFailingRefinement(errorAtCursor: RefinementMismatchError): string { return /*html*/` - ${renderDiagnosticRevealButton(errorAtCursor.position!, '⊢ ' + expected)} + ${renderDiagnosticRevealButton(errorAtCursor.position!, '⊢ ' + errorAtCursor.expected)} `; diff --git a/client/src/webview/views/diagnostics/derivation-nodes.ts b/client/src/webview/views/diagnostics/derivation-nodes.ts deleted file mode 100644 index b755cb5..0000000 --- a/client/src/webview/views/diagnostics/derivation-nodes.ts +++ /dev/null @@ -1,137 +0,0 @@ -import type { LJError, RefinementMismatchError } from "../../../types/diagnostics"; -import type { DerivationNode, ValDerivationNode } from "../../../types/derivation-nodes"; -import { renderHighlightedExpression, renderHighlightedInlineExpression } from "../../highlighting"; -import { renderCodicon } from "../../icons"; -import { escapeHtml } from "../../utils"; - -// Handles rendering and interaction of derivation nodes in refinement errors - -const expansionsMap = new Map>(); - -function getExpansions(errorId: string): Set { - if (!expansionsMap.has(errorId)) { - expansionsMap.set(errorId, new Set()); - } - return expansionsMap.get(errorId)!; -} - -function renderToken(token: string): string { - return renderHighlightedInlineExpression(token); -} - -function renderJsonTree( - error: RefinementMismatchError, - node: DerivationNode | undefined, - errorId: string, - path: string, - expandedPaths: Set -): string { - if (!node) - return 'undefined'; - - const hasOrigin = Boolean("origin" in node && node.origin); - const isExpanded = expandedPaths.has(path); - if (hasOrigin && isExpanded && "origin" in node) { - return renderJsonTree(error, node.origin, errorId, `${path}.origin`, expandedPaths); - } - - // VarDerivationNode - if ("var" in node) { - const classes = `node-var ${hasOrigin ? "derivable-node clickable" : ""}`.trim(); - const attrs = hasOrigin ? ` data-node-path="${path}" data-error-id="${errorId}"` : ""; - return `${renderHighlightedInlineExpression(node.var)}`; - } - - // ValDerivationNode - if ("value" in node) { - const valueNode = node as ValDerivationNode; - const valClass = typeof valueNode.value === "number" ? "node-number" : typeof valueNode.value === "boolean" ? "node-boolean" : "node-value"; - const clickableClass = hasOrigin ? "derivable-node clickable" : ""; - const pathAttr = hasOrigin ? `data-node-path="${path}"` : ""; - const idAttr = hasOrigin ? `data-error-id="${errorId}"` : ""; - return `${renderHighlightedInlineExpression(String(valueNode.value))}`; - } - - // BinaryDerivationNode - if ("left" in node && "right" in node) { - const leftHtml = renderJsonTree(error, node.left, errorId, `${path}.left`, expandedPaths); - const rightHtml = renderJsonTree(error, node.right, errorId, `${path}.right`, expandedPaths); - return `${leftHtml} ${renderToken(node.op)} ${rightHtml}`; - } - - // UnaryDerivationNode - if ("operand" in node) { - const operandHtml = renderJsonTree(error, node.operand, errorId, `${path}.operand`, expandedPaths); - return node.op === "-" - ? `${renderToken(node.op)}${renderToken("(")}${operandHtml}${renderToken(")")}` - : `${renderToken(node.op)}${operandHtml}`; - } - - // IteDerivationNode - if ("condition" in node && "thenBranch" in node && "elseBranch" in node) { - const conditionHtml = renderJsonTree(error, node.condition, errorId, `${path}.condition`, expandedPaths); - const thenBranchHtml = renderJsonTree(error, node.thenBranch, errorId, `${path}.thenBranch`, expandedPaths); - const elseBranchHtml = renderJsonTree(error, node.elseBranch, errorId, `${path}.elseBranch`, expandedPaths); - return `${conditionHtml} ${renderToken("?")} ${thenBranchHtml} ${renderToken(":")} ${elseBranchHtml}`; - } - - // fallback - return `${escapeHtml(JSON.stringify(node))}`; -} - -function hashError(error: LJError, scope: string): string { - const content = `${error.title}|${error.message}|${error.file}|${error.position?.lineStart ?? 0}|${scope}`; - let hash = 0; - for (let i = 0; i < content.length; i++) { - const char = content.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash = hash & hash; // Convert to 32bit integer - } - return `error_${Math.abs(hash)}`; -} - -export function handleDerivableNodeClick(target?: any): boolean { - if (!target) return false; - - const nodePath = target.getAttribute("data-node-path"); - const errorId = target.getAttribute("data-error-id"); - if (nodePath && errorId !== null) { - const paths = getExpansions(errorId); - if (!paths.has(nodePath)) { - paths.add(nodePath); - } - return true; - } - return false; -} - -export function handleDerivationResetClick(target?: any): boolean { - if (!target) return false; - - const errorId = target.getAttribute("data-error-id"); - if (errorId !== null) { - expansionsMap.delete(errorId); - return true; - } - return false; -} - -export function renderDerivationNode( - error: RefinementMismatchError, - node: ValDerivationNode, - scope: "expected" | "found" -): string { - if (!node || typeof node !== "object" || !("value" in node)) return renderHighlightedExpression(String(node)); // primitive value without derivation - if (!node.origin) return renderHighlightedExpression(String(node.value)); // no derivation available - - const errorId = hashError(error, scope); - const expansions = getExpansions(errorId); - return /*html*/ ` -
-
- ${renderJsonTree(error, node, errorId, "root", expansions)} -
- -
- `; -} diff --git a/client/src/webview/views/diagnostics/diagnostics.ts b/client/src/webview/views/diagnostics/diagnostics.ts index e042d1e..5035baf 100644 --- a/client/src/webview/views/diagnostics/diagnostics.ts +++ b/client/src/webview/views/diagnostics/diagnostics.ts @@ -1,4 +1,5 @@ import { LJDiagnostic, LJError, LJWarning } from "../../../types/diagnostics"; +import type { VCImplication, VCSimplificationResult } from "../../../types/vc-implications"; import { copyToClipboard } from "../../clipboard"; import { renderCodiconButton } from "../../icons"; import { renderErrors } from "./errors"; @@ -106,13 +107,35 @@ function formatClipboardValue(value: unknown): string { return values.some(v => v.includes('\n')) ? `\n${values.join('\n')}` : values.join(', '); } - if (typeof value === 'object' && 'value' in value) { - return formatClipboardValue((value as { value: unknown }).value); - } + if (isVCSimplificationResult(value)) return formatVCImplication(value.implication); + if (isVCImplication(value)) return formatVCImplication(value); return JSON.stringify(value); } +function isVCSimplificationResult(value: unknown): value is VCSimplificationResult { + return typeof value === 'object' + && value !== null + && 'implication' in value + && 'origin' in value; +} + +function isVCImplication(value: unknown): value is VCImplication { + return typeof value === 'object' + && value !== null + && 'predicate' in value + && 'next' in value; +} + +function formatVCImplication(node: VCImplication | null): string { + if (!node) return ''; + + const binder = node.name !== null && node.type !== null ? `∀${node.name}:${node.type}, ` : ''; + const current = `${binder}${node.predicate}`; + const next = formatVCImplication(node.next); + return next ? `${current}\n=> ${next}` : current; +} + function formatDiagnosticLocation(diagnostic: LJDiagnostic): string { if (!diagnostic.file || !diagnostic.position) return ''; diff --git a/client/src/webview/views/diagnostics/errors.ts b/client/src/webview/views/diagnostics/errors.ts index b3d0704..c777a32 100644 --- a/client/src/webview/views/diagnostics/errors.ts +++ b/client/src/webview/views/diagnostics/errors.ts @@ -1,5 +1,5 @@ import { renderDiagnosticDataAttributes, renderExpressionSection, renderDiagnosticHeader, renderCustomSection, renderLocation, renderDiagnosticContextButton } from "../sections"; -import { renderDerivationNode } from "./derivation-nodes"; +import { renderVCImplication } from "./vc-implications"; import type { ArgumentMismatchError, CustomError, @@ -34,13 +34,13 @@ type ErrorRendererMap = { [E in LJError as E['type']]: (error: E) => string }; const errorContentRenderers: ErrorRendererMap = { 'refinement-error': (e: RefinementError) => /*html*/ ` - ${renderCustomSection('Expected', renderDerivationNode(e, e.expected, 'expected'))} - ${renderCustomSection('Found', renderDerivationNode(e, e.found, 'found'))} + ${renderExpressionSection('Expected', e.expected)} + ${renderCustomSection('Found', renderVCImplication(e, e.found))} ${e.counterexample ? renderExpressionSection('Counterexample', e.counterexample) : ''} `, 'state-refinement-error': (e: StateRefinementError) => /*html*/ ` - ${renderCustomSection('Expected', renderDerivationNode(e, e.expected, 'expected'))} - ${renderCustomSection('Found', renderDerivationNode(e, e.found, 'found'))} + ${renderExpressionSection('Expected', e.expected)} + ${renderCustomSection('Found', renderVCImplication(e, e.found))} `, 'invalid-refinement-error': (e: InvalidRefinementError) => /*html*/ ` ${renderExpressionSection('Refinement', e.refinement)} diff --git a/client/src/webview/views/diagnostics/vc-implications.ts b/client/src/webview/views/diagnostics/vc-implications.ts new file mode 100644 index 0000000..848220b --- /dev/null +++ b/client/src/webview/views/diagnostics/vc-implications.ts @@ -0,0 +1,73 @@ +import type { RefinementMismatchError } from "../../../types/diagnostics"; +import type { VCImplication, VCSimplificationResult } from "../../../types/vc-implications"; +import { renderHighlightedExpression, renderHighlightedInlineExpression } from "../../highlighting"; +import { renderCodicon } from "../../icons"; +import { escapeHtml } from "../../utils"; + +const stepIndexes = new Map(); // step index => errorId to preserve step state across re-renders + +function renderImplication(node: VCImplication): string { + const lines: string[] = []; + + for (let current: VCImplication | null = node; current; current = current.next) { + const binder = current.name !== null && current.type !== null; + if (!binder && current.next || current.predicate === "true" && current.next !== null) continue; + + const content = /*binder + ? `∀${escapeHtml(current.name!)}: ${escapeHtml(current.type!)}. ${renderHighlightedInlineExpression(current.predicate)}` + :*/ renderHighlightedInlineExpression(current.predicate); + lines.push(`
${content}
`); + } + + return lines.join(""); +} + +function renderStepButton(errorId: string, step: "previous" | "next", disabled: boolean): string { + const label = `${step === "previous" ? "Previous" : "Next"} simplification`; + const icon = step === "previous" ? "arrow-small-left" : "arrow-small-right"; + return ``; +} + +export function handleVCImplicationStepClick(target: Element): boolean { + const errorId = target.getAttribute("data-error-id"); + const step = target.getAttribute("data-vc-step"); + if (!errorId || target.hasAttribute("disabled")) return false; + + const index = stepIndexes.get(errorId) ?? 0; + const nextIndex = step === "previous" ? index + 1 : step === "next" ? index - 1 : -1; + if (nextIndex < 0) return false; + + stepIndexes.set(errorId, nextIndex); + return true; +} + +export function renderVCImplication( + error: RefinementMismatchError, + result: VCSimplificationResult +): string { + if (!result?.implication) return renderHighlightedExpression(String(result)); + + const errorId = encodeURIComponent(JSON.stringify([ + error.file, + error.position?.lineStart, + error.title, + error.message + ])); + const history: VCSimplificationResult[] = []; + for (let current: VCSimplificationResult | null = result; current; current = current.origin) { + history.push(current); + } + + const index = Math.min(stepIndexes.get(errorId) ?? 0, history.length - 1); + stepIndexes.set(errorId, index); + + return /*html*/ ` +
+
${renderImplication(history[index].implication)}
+
+ ${renderStepButton(errorId, "previous", index === history.length - 1)} + ${renderStepButton(errorId, "next", index === 0)} +
+
+ `; +} diff --git a/server/pom.xml b/server/pom.xml index 640a129..d4ba04d 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -172,7 +172,7 @@ io.github.liquid-java liquidjava-verifier - 0.0.25 + 0.0.26 tools.aqua diff --git a/server/src/main/java/dtos/diagnostics/RefinementDTO.java b/server/src/main/java/dtos/diagnostics/RefinementDTO.java new file mode 100644 index 0000000..5489f40 --- /dev/null +++ b/server/src/main/java/dtos/diagnostics/RefinementDTO.java @@ -0,0 +1,17 @@ +package dtos.diagnostics; + +import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.ast.formatter.ExpressionFormatter; + +/** + * DTO for serializing refinement predicates. + */ +public record RefinementDTO(String predicate) { + + public static RefinementDTO from(Predicate refinement) { + if (refinement == null) + return null; + + return new RefinementDTO(ExpressionFormatter.format(refinement)); + } +} diff --git a/server/src/main/java/dtos/diagnostics/VCBinderDTO.java b/server/src/main/java/dtos/diagnostics/VCBinderDTO.java new file mode 100644 index 0000000..8002743 --- /dev/null +++ b/server/src/main/java/dtos/diagnostics/VCBinderDTO.java @@ -0,0 +1,20 @@ +package dtos.diagnostics; + +import liquidjava.processor.VCImplication; +import liquidjava.rj_language.ast.formatter.VariableFormatter; +import liquidjava.utils.Utils; + +/** + * DTO for serializing the binder part of a VC implication node. + */ +public record VCBinderDTO(String name, String type) { + + public static VCBinderDTO from(VCImplication implication) { + if (implication == null || !implication.hasBinder()) + return null; + + String qualifiedType = implication.getType().getQualifiedName(); + String simpleType = qualifiedType.contains(".") ? Utils.getSimpleName(qualifiedType) : qualifiedType; + return new VCBinderDTO(VariableFormatter.format(implication.getName()), simpleType); + } +} diff --git a/server/src/main/java/dtos/diagnostics/VCImplicationDTO.java b/server/src/main/java/dtos/diagnostics/VCImplicationDTO.java new file mode 100644 index 0000000..4bc76f4 --- /dev/null +++ b/server/src/main/java/dtos/diagnostics/VCImplicationDTO.java @@ -0,0 +1,28 @@ +package dtos.diagnostics; + +import liquidjava.processor.VCImplication; +import liquidjava.rj_language.ast.formatter.VariableFormatter; +import liquidjava.utils.Utils; + +/** + * DTO for serializing a complete VC implication chain. + */ +public record VCImplicationDTO(String name, String type, String predicate, VCImplicationDTO next) { + + public static VCImplicationDTO from(VCImplication implication) { + if (implication == null) + return null; + + String name = null; + String type = null; + if (implication.hasBinder()) { + name = VariableFormatter.format(implication.getName()); + type = Utils.getSimpleName(implication.getType().getQualifiedName()); + } + return new VCImplicationDTO( + name, + type, + implication.getRefinement().getExpression().toDisplayString(), + from(implication.getNext())); + } +} diff --git a/server/src/main/java/dtos/diagnostics/VCSimplificationResultDTO.java b/server/src/main/java/dtos/diagnostics/VCSimplificationResultDTO.java new file mode 100644 index 0000000..155c3ac --- /dev/null +++ b/server/src/main/java/dtos/diagnostics/VCSimplificationResultDTO.java @@ -0,0 +1,18 @@ +package dtos.diagnostics; + +import liquidjava.rj_language.opt.VCSimplificationResult; + +/** + * DTO for serializing a simplified VC and its complete predecessor states. + */ +public record VCSimplificationResultDTO(VCImplicationDTO implication, VCSimplificationResultDTO origin) { + + public static VCSimplificationResultDTO from(VCSimplificationResult result) { + if (result == null) + return null; + + return new VCSimplificationResultDTO( + VCImplicationDTO.from(result.getImplication()), + from(result.getOrigin())); + } +} diff --git a/server/src/main/java/dtos/errors/RefinementErrorDTO.java b/server/src/main/java/dtos/errors/RefinementErrorDTO.java index 196befa..599fd21 100644 --- a/server/src/main/java/dtos/errors/RefinementErrorDTO.java +++ b/server/src/main/java/dtos/errors/RefinementErrorDTO.java @@ -1,22 +1,23 @@ package dtos.errors; +import dtos.diagnostics.VCSimplificationResultDTO; import liquidjava.diagnostics.errors.RefinementError; -import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; +import liquidjava.rj_language.ast.formatter.ExpressionFormatter; /** * DTO for serializing RefinementError instances to JSON */ public class RefinementErrorDTO extends LJErrorDTO { - public final ValDerivationNode expected; - public final ValDerivationNode found; + public final String expected; + public final VCSimplificationResultDTO found; public final String customMessage; public final String counterexample; public RefinementErrorDTO(RefinementError error) { super("refinement-error", error); - this.expected = error.getExpected(); - this.found = error.getFound(); + this.expected = error.getExpected() == null ? null : ExpressionFormatter.format(error.getExpected()); + this.found = VCSimplificationResultDTO.from(error.getFound()); this.customMessage = error.getCustomMessage(); this.counterexample = error.getCounterExampleString(); } diff --git a/server/src/main/java/dtos/errors/StateRefinementErrorDTO.java b/server/src/main/java/dtos/errors/StateRefinementErrorDTO.java index ab1a217..a985e2f 100644 --- a/server/src/main/java/dtos/errors/StateRefinementErrorDTO.java +++ b/server/src/main/java/dtos/errors/StateRefinementErrorDTO.java @@ -1,21 +1,22 @@ package dtos.errors; +import dtos.diagnostics.VCSimplificationResultDTO; import liquidjava.diagnostics.errors.StateRefinementError; -import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; +import liquidjava.rj_language.ast.formatter.ExpressionFormatter; /** * DTO for serializing StateRefinementError instances to JSON */ public class StateRefinementErrorDTO extends LJErrorDTO { - public final ValDerivationNode expected; - public final ValDerivationNode found; + public final String expected; + public final VCSimplificationResultDTO found; public final String customMessage; public StateRefinementErrorDTO(StateRefinementError error) { super("state-refinement-error", error); - this.expected = error.getExpected(); - this.found = error.getFound(); + this.expected = error.getExpected() == null ? null : ExpressionFormatter.format(error.getExpected()); + this.found = VCSimplificationResultDTO.from(error.getFoundSimplification()); this.customMessage = error.getCustomMessage(); }