Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ Marketplace listing: <https://marketplace.visualstudio.com/items?itemName=magne-
section keyword to the next) or individual keywords in the gutter.
- **Align Record Columns** — tidy up record blocks so every column lines up;
handles comment lines inside the group and aligns to heading comments above
the group.
the group. `UDQ` expression blocks get a dedicated three-column layout —
the control word (`DEFINE`/`ASSIGN`/`UNITS`/`UPDATE`) and the variable name
both left-aligned, and the expression right-aligned so every statement's
terminating `/` lines up (a `/` used for division inside the expression is
not mistaken for the terminator).
- **Add Column Headers** — insert a `--` heading comment with parameter names
from the reference manual and align the record group to those positions
(idempotent). For multi-record keywords the names come from the record
Expand Down
33 changes: 33 additions & 0 deletions vscode-extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ Provides syntax highlighting for OPM Flow simulation deck files with support for
- **Strings**: Text in single quotes
- **Template variables**: `<NAME>` placeholders used in macro/ERT workflows
- **END keyword**: Specially highlighted file terminator
- **UDQ / ACTIONX constructs**: UDQ control words (`DEFINE`, `ASSIGN`, `UNITS`,
`UPDATE`), UDQ functions (`SORTA`, `SUM`, `ABS`, …), the comparison and
logical operators used in expressions (`>=`, `<=`, `==`, `AND`, `OR`), and the
`ACTIONX` / `ENDACTIO` action-block delimiters

### Keyword Autocompletion

Expand Down Expand Up @@ -56,6 +60,21 @@ start typing an uppercase letter (e.g. `O` for `OPEN`) or press
`Ctrl+Space` (`Cmd+Space` on macOS) to open the suggestions manually. Selecting
an option inserts the value quoted, e.g. `'OPEN'`.

### UDQ and ACTIONX Support

The user-defined-quantity sub-language (`UDQ` blocks) and `ACTIONX` action blocks
are recognised so the editor can assist with their distinct syntax:

- **Completion** — inside a `UDQ` block, the start of a statement offers the
control words (`ASSIGN`, `DEFINE`, `UNITS`, `UPDATE`); inside a UDQ formula or
an `ACTIONX` condition, the UDQ functions (`SORTA`, `SUM`, `ABS`, …) are
offered and inserted with parentheses ready for the argument.
- **Hover** — hovering a UDQ control word shows what it does, and hovering a UDQ
function shows its signature and description.
- **Diagnostics** — a `UDQ` statement that doesn't start with a control word, and
an `ACTIONX` block left unclosed by `ENDACTIO`, are flagged (see
[Diagnostics](#diagnostics)).

### Hover Tooltips

Hover over any keyword to see a quick tooltip with:
Expand Down Expand Up @@ -119,6 +138,11 @@ Squiggles in the editor catch the most common deck-shape mistakes:
header (an include fragment, not a complete deck).
- **Mutually exclusive keywords** — two keywords that `opm-common` marks as
`prohibits` partners both appearing in the same deck.
- **UDQ statement without a control word** — a statement inside a `UDQ` block
that does not begin with `ASSIGN`, `DEFINE`, `UNITS`, or `UPDATE`.
Continuation lines of a statement whose `/` is deferred are not flagged.
- **Unclosed `ACTIONX` block** — an `ACTIONX` action block with no matching
`ENDACTIO` before the end of the deck.

Keywords whose record bodies don't fit the generic model can be silenced
wholesale via the `opm-flow.diagnostics.excludedKeywords` setting — see
Expand Down Expand Up @@ -355,6 +379,15 @@ The language is registered as `opm-flow`.

### Unreleased

- **UDQ and ACTIONX support** — the `UDQ` expression sub-language and `ACTIONX`
action blocks are now recognised. Syntax highlighting scopes UDQ control
words, UDQ functions, and expression operators, plus the `ACTIONX` /
`ENDACTIO` block delimiters. Hover and completion cover UDQ control words and
functions, and two new diagnostics flag a UDQ statement that doesn't start
with a control word and an `ACTIONX` block left unclosed by `ENDACTIO`.
Column alignment also gives `UDQ` expression blocks a dedicated three-column
layout (control word, variable, expression) that treats a `/` used for
division as part of the expression rather than the record terminator.
- **Boilerplate keyword completion** — accepting a keyword completion now inserts
a sample data record as a tab-navigable snippet (documented defaults or
type-appropriate dummy values, terminated to match the keyword's shape) instead
Expand Down
129 changes: 129 additions & 0 deletions vscode-extension/src/analysis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,31 @@ const index: Record<string, AnalysisEntry> = {
BARE: {
name: 'BARE',
},
UDQ: {
name: 'UDQ',
sections: ['SCHEDULE'],
size_kind: 'list',
},
ACTIONX: {
name: 'ACTIONX',
sections: ['SCHEDULE'],
size_kind: 'list',
},
ENDACTIO: {
name: 'ENDACTIO',
sections: ['SCHEDULE'],
size_kind: 'none',
},
WELOPEN: {
name: 'WELOPEN',
sections: ['SCHEDULE'],
size_kind: 'list',
},
TSTEP: {
name: 'TSTEP',
sections: ['SCHEDULE'],
size_kind: 'array',
},
};

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1776,3 +1801,107 @@ describe('computeDiagnostics — quick-fix codes', () => {
expect(d!.suggestion).toBeUndefined();
});
});

// ---------------------------------------------------------------------------
// UDQ control-word validation
// ---------------------------------------------------------------------------

describe('computeDiagnostics — UDQ control words', () => {
it('accepts a UDQ block whose statements use control words', () => {
const lines = [
'SCHEDULE',
'UDQ',
"DEFINE WUPR1 1/(WWCT 'OP*') /",
'DEFINE WUPR3 SORTA(WUPR1) /',
'ASSIGN WU2 3.0 /',
'UNITS WUPR1 BARSA /',
'/',
];
expect(computeDiagnostics(lines, index)).toEqual([]);
});

it('flags a UDQ statement that does not start with a control word', () => {
const lines = [
'SCHEDULE',
'UDQ',
'DEFIN WUPR1 1 /',
'/',
];
const diags = computeDiagnostics(lines, index);
expect(diags).toHaveLength(1);
expect(diags[0].line).toBe(2);
expect(diags[0].message).toMatch(/expected a control word/);
expect(diags[0].message).toContain('DEFIN');
});

it('does not flag a continuation line of an open UDQ statement', () => {
// The '/' is deferred to the next line, so the second line continues the
// statement and must not be checked for a leading control word.
const lines = [
'SCHEDULE',
'UDQ',
'DEFINE WUPR1',
" 1/(WWCT 'OP*') /",
'/',
];
expect(computeDiagnostics(lines, index)).toEqual([]);
});
});

// ---------------------------------------------------------------------------
// ACTIONX ... ENDACTIO block
// ---------------------------------------------------------------------------

describe('computeDiagnostics — ACTIONX block', () => {
it('accepts a complete ACTIONX ... ENDACTIO block', () => {
const lines = [
'SCHEDULE',
'ACTIONX',
'ACT01 10 /',
'FMWPR >= 4 AND /',
"WUPR3 'OP*' = 1 /",
'/',
'WELOPEN',
" '?' SHUT 0 0 0 2* /",
'/',
'ENDACTIO',
];
expect(computeDiagnostics(lines, index)).toEqual([]);
});

it('flags an ACTIONX block that is never closed by ENDACTIO', () => {
const lines = [
'SCHEDULE',
'ACTIONX',
'ACT01 10 /',
'FMWPR >= 4 /',
'/',
'WELOPEN',
" '?' SHUT 0 0 0 2* /",
'/',
];
const diags = computeDiagnostics(lines, index);
expect(diags).toHaveLength(1);
expect(diags[0].line).toBe(1);
expect(diags[0].message).toMatch(/ENDACTIO/);
});

it('does not flag when a later ACTIONX block is properly closed', () => {
const lines = [
'SCHEDULE',
'ACTIONX',
'ACT01 10 /',
'FMWPR >= 4 /',
'/',
'ENDACTIO',
'TSTEP',
' 10 10 /',
'ACTIONX',
'ACT02 10 /',
'FMWPR >= 5 /',
'/',
'ENDACTIO',
];
expect(computeDiagnostics(lines, index)).toEqual([]);
});
});
54 changes: 54 additions & 0 deletions vscode-extension/src/analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,14 @@ const TEMPLATE_SUFFIX_RE = /^[A-Z0-9]+$/;
*/
const UDQ_NAME_RE = /^[ABCFGRSW]U[A-Z0-9_]+$/;

/**
* Control words that introduce a statement inside a `UDQ` block. Every record
* in a UDQ block must begin with one of these (see the OPM Flow manual `UDQ`
* keyword): `ASSIGN` a constant, `DEFINE` a formula, set the display `UNITS`,
* or `UPDATE` the evaluation state.
*/
const UDQ_CONTROL_WORDS = new Set(['ASSIGN', 'DEFINE', 'UNITS', 'UPDATE']);

/**
* Region summary vector qualified by a named FIP region set, e.g. ``ROIP_ABC``
* (= base vector ``ROIP`` over region set ``ABC``) or ``RPR__ABC``. The base is
Expand Down Expand Up @@ -475,6 +483,13 @@ export function computeDiagnostics(
// deck, the rest pulled in via INCLUDE), so once we've seen one we can no
// longer trust `currentSection` and must suppress the wrong-section check.
let includeSinceSection = false;
// Tracks an open `ACTIONX` block. ACTIONX opens a block of nested SCHEDULE
// keywords (the action) that must be closed by an `ENDACTIO`; the active
// keyword moves on to those nested keywords, so this is tracked separately
// and evaluated at end-of-deck to flag a block that is never closed.
let actionxOpenLine = -1;
let actionxStart = 0;
let actionxEnd = 0;
// First occurrence of each recognised keyword (by canonical entry name),
// collected during the walk and evaluated once at the end for the
// document-wide requires/prohibits constraints.
Expand Down Expand Up @@ -718,6 +733,18 @@ export function computeDiagnostics(
continue;
}

// ACTIONX ... ENDACTIO block tracking. ACTIONX opens an action block
// that must be closed by ENDACTIO. The intervening (nested) keywords
// become the active keyword in turn, so the open state is tracked on
// the side and reported at end-of-deck if never closed.
if (activeKw.name === 'ACTIONX') {
actionxOpenLine = i;
actionxStart = activeKwIndent;
actionxEnd = activeKwIndent + kw.length;
} else if (activeKw.name === 'ENDACTIO') {
actionxOpenLine = -1;
}

// Record the first occurrence of this keyword for the document-wide
// requires/prohibits checks. Keyed by the canonical entry name so a
// templated deck token (FTPRSEA) maps to its base (FTPR); the range
Expand Down Expand Up @@ -766,6 +793,23 @@ export function computeDiagnostics(
const tokens = tokenizeLine(text);
if (tokens.length === 0) continue;

// UDQ body statements must begin with a control word
// (ASSIGN/DEFINE/UNITS/UPDATE). Check only the first line of a statement —
// `openRecordLine < 0` means no earlier statement is still awaiting its
// '/', so this line starts a new statement rather than continuing one.
if (activeKw.name === 'UDQ' && openRecordLine < 0) {
const head = tokens[0].text.toUpperCase();
if (!UDQ_CONTROL_WORDS.has(head)) {
out.push({
line: i,
startChar: tokens[0].start,
endChar: tokens[0].end,
message:
`UDQ: expected a control word (ASSIGN, DEFINE, UNITS or UPDATE) but found '${tokens[0].text}'.`,
});
}
}

const lastTok = tokens[tokens.length - 1];
const hasTerm = lineHasRecordTerminator(text, lastTok.end);

Expand Down Expand Up @@ -857,6 +901,16 @@ export function computeDiagnostics(

closeKw();

// An ACTIONX block left open at end of deck has no matching ENDACTIO.
if (actionxOpenLine >= 0) {
out.push({
line: actionxOpenLine,
startChar: actionxStart,
endChar: actionxEnd,
message: `ACTIONX: action block is not closed; a matching ENDACTIO is required.`,
});
}

// --- Cross-keyword constraints (requires / prohibits) -------------------
// Evaluated document-wide once all keyword occurrences are known.
const reportedProhibitPairs = new Set<string>();
Expand Down
Loading