Skip to content
Open
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
37 changes: 32 additions & 5 deletions extension/client/src/language/columnAssist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,38 @@ let currentEditorLine = -1;

import { SpecFieldDef, SpecFieldValue, SpecRulers, specs } from '../schemas/specs';

/**
* Resolves the spec lookup key for a line, detecting O-spec sub-types
* (OAnd, OF, OFC, OXF) so they map to the correct SpecFieldDef array.
* All other spec letters are returned as-is.
*/
function resolveSpecKey(line: string): string {
const specLetter = line[5]?.toUpperCase() ?? ``;
if (specLetter !== `O`) return specLetter;

const paddedLine = line.padEnd(80);
const andOrKeyword = paddedLine.substring(15, 20).trim().toUpperCase();
if (andOrKeyword === `AND` || andOrKeyword === `OR`) return `OAnd`;

const filename = paddedLine.substring(6, 16).trim();
const type = paddedLine.substring(16, 17).trim();
const fieldName = paddedLine.substring(29, 43).trim();
const endPos = paddedLine.substring(46, 51).trim();
const constant = paddedLine.substring(52).trim();

if (endPos && (fieldName || constant)) return `OF`;
if (filename || type) return `O`;
if (!fieldName && constant) return `OFC`;
if (fieldName) return `OXF`;
return `O`;
}

const getAreasForLine = (line: string, index: number) => {
if (line.length < 6) return undefined;
if (line[6] === `*` || line[6] === `/`) return undefined;

const specLetter = line[5].toUpperCase();
const specLetter = resolveSpecKey(line);
const baseSpecLetter = line[5]?.toUpperCase() ?? ``;
if (specs[specLetter]) {
const specification = specs[specLetter];

Expand All @@ -33,13 +60,13 @@ const getAreasForLine = (line: string, index: number) => {
return {
specification,
active,
outline: SpecRulers[specLetter]
outline: SpecRulers[specLetter] ?? SpecRulers[baseSpecLetter]
};
} else if (SpecRulers[specLetter]) {
} else if (SpecRulers[specLetter] ?? SpecRulers[baseSpecLetter]) {
return {
specification: [] as SpecFieldDef[],
active: -1,
outline: SpecRulers[specLetter]
outline: SpecRulers[specLetter] ?? SpecRulers[baseSpecLetter]
};
}
}
Expand Down Expand Up @@ -231,7 +258,7 @@ async function promptLine (line: string, _index: number): Promise<string|undefin
if (line[6] === `*`) return undefined;
line = line.padEnd(80);

const specLetter = line[5].toUpperCase();
const specLetter = resolveSpecKey(line);
if (specs[specLetter]) {
const specification = specs[specLetter];

Expand Down
50 changes: 48 additions & 2 deletions extension/client/src/schemas/specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ export const SpecRulers: {[spec: string]: string} = {
C: `.....CL0N01Factor1+++++++Opcode&ExtFactor2+++++++Result++++++++Len++D+HiLoEq....`,
D: `.....DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++++++`,
F: `.....FFilename++IPEASFRlen+LKlen+AIDevice+.Keywords+++++++++++++++++++++++++++++`,
I: `.....IFilename++SqNORiPos1+NCCPos2+NCCPos3+NCCDcField+++++++++L1M1FrPlMnZr......`,
O: `.....OFilename++DF..N01N02N03Excnam++++B++A++Sb+Sa+.Constant/Editword/DateFormat`,
I: `.....IFilename++SqNORiPos1+NCCPos2+NCCPos3+NCCDcField+++++++++L1M1FrPlMnZr......`,
O: `.....OFilename++DF..N01N02N03Excnam++++B++A++Sb+Sa+.Constant/Editword/DateFormat`,
OAnd: `.....O.........And++N01N02N03ExceptNameB++A++Sb+Sa+.............................`,
OF: `.....O..............N01N02N03FieldName+++++BEcEndPoDConstant/Editword+++++++++++`,
OFC: `.....O..............................................Constant/Editword+++++++++++`,
OXF: `.....O..............N01N02N03FieldName++++++B+++++++++++++++++++++++++++++++++++`,
P: `.....PName+++++++++++..T...................Keywords+++++++++++++++++++++++++++++`
}

Expand Down Expand Up @@ -152,6 +156,48 @@ export const specs: {[spec: string]: SpecFieldDef[]} = {
{start: 35, end: 41, name: `Device`, id: `device`},
{start: 43, end: 79, name: `Keywords`, id: `keywords`}
],
// E, H, I specs are OPM-only or have significant differences - see opmSpecs below
O: [
{start: 6, end: 15, name: `Filename`, id: `fileName`},
{start: 16, end: 16, name: `Type`, id: `type`},
{start: 17, end: 19, name: `Fetch Overflow`, id: `fetchOverflow`},
{start: 20, end: 22, name: `Add/Delete Indicator`, id: `addDeleteIndicator`},
{start: 23, end: 25, name: `Output Indicator 1`, id: `outputIndicator1`},
{start: 26, end: 28, name: `Output Indicator 2`, id: `outputIndicator2`},
{start: 29, end: 38, name: `Except Name`, id: `exceptName`},
{start: 39, end: 41, name: `Space Before`, id: `spaceBefore`},
{start: 42, end: 44, name: `Space After`, id: `spaceAfter`},
{start: 45, end: 47, name: `Skip Before`, id: `skipBefore`},
{start: 48, end: 79, name: `Skip After`, id: `skipAfter`},
],
OAnd: [
{start: 15, end: 19, name: `AND/OR Keyword`, id: `andOrKeyword`},
{start: 20, end: 22, name: `Output Indicator 1`, id: `outputIndicator1`},
{start: 23, end: 25, name: `Output Indicator 2`, id: `outputIndicator2`},
{start: 26, end: 28, name: `Output Indicator 3`, id: `outputIndicator3`},
{start: 29, end: 79, name: `Except Name`, id: `exceptName`},
],
OF: [
{start: 20, end: 22, name: `Output Indicator 1`, id: `outputIndicator1`},
{start: 23, end: 25, name: `Output Indicator 2`, id: `outputIndicator2`},
{start: 26, end: 28, name: `Output Indicator 3`, id: `outputIndicator3`},
{start: 29, end: 42, name: `Field Name`, id: `fieldName`},
{start: 43, end: 43, name: `Blank After`, id: `blankAfter`},
{start: 44, end: 45, name: `Edit Codes`, id: `editCodes`},
{start: 46, end: 50, name: `End Position`, id: `endPosition`, padStart: true},
{start: 51, end: 51, name: `Data Format`, id: `dataFormat`},
{start: 52, end: 79, name: `Constant/Edit Word`, id: `constantOrEdit`},
],
OFC: [
{start: 52, end: 79, name: `Constant/Edit Word`, id: `constantOrEdit`},
],
OXF: [
{start: 20, end: 22, name: `Output Indicator 1`, id: `outputIndicator1`},
{start: 23, end: 25, name: `Output Indicator 2`, id: `outputIndicator2`},
{start: 26, end: 28, name: `Output Indicator 3`, id: `outputIndicator3`},
{start: 29, end: 43, name: `Field Name`, id: `fieldName`},
{start: 44, end: 79, name: `Blank After`, id: `blankAfter`},
],
P: [
{start: 6, end: 20, name: `Name`, id: `name`},
{start: 23, end: 23, name: `Begin/End Procedure`, id: `proc`},
Expand Down
1 change: 1 addition & 0 deletions extension/server/src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export function handleClientRequests() {
indicators: doc.indicators,
tags: doc.tags,
includes: doc.includes,
outputs: doc.outputs
}
});
}
Expand Down
12 changes: 12 additions & 0 deletions extension/server/src/providers/documentSymbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,18 @@ export default async function documentSymbolProvider(handler: DocumentSymbolPara

currentScopeDefs.push(fileDef);
});
scope.outputs
.filter(output => output.position && output.position.path === currentPath && validRange(output))
.forEach(output => {
const outputDef = DocumentSymbol.create(
output.name,
prettyKeywords(output.keyword),
SymbolKind.String, // or SymbolKind.Field
Range.create(output.range.start!, 0, output.range.end!, 0),
Range.create(output.range.start!, 0, output.range.end!, 0)
);
currentScopeDefs.push(outputDef);
});

scope.structs
.filter(struct => struct.position && struct.position.path === currentPath && validRange(struct))
Expand Down
4 changes: 4 additions & 0 deletions language/models/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ export default class Cache {
return this.symbols.filter(s => s.type === `parameter`);
}

get outputs() {
return this.symbols.filter(s => s.type === `output`);
}

addSymbol(symbol: Declaration) {
const name = symbol.name.toUpperCase();
if (this.symbolRegister.has(name)) {
Expand Down
2 changes: 1 addition & 1 deletion language/models/declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Keywords, Reference } from "../parserTypes";
import { IRangeWithLine } from "../types";
import Cache from "./cache";

export type DeclarationType = "parameter"|"procedure"|"subroutine"|"file"|"struct"|"subitem"|"variable"|"constant"|"tag"|"indicator";
export type DeclarationType = "parameter"|"procedure"|"subroutine"|"file"|"struct"|"subitem"|"variable"|"constant"|"tag"|"indicator"| "output";

export default class Declaration {
name: string = ``;
Expand Down
204 changes: 204 additions & 0 deletions language/models/fixed.js → language/models/fixed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,210 @@ export function parsePLine(content, lineNumber, lineIndex) {
};
}

/**
* Detect O-Spec line type
* Note: Comment lines (* in column 7) are filtered out before reaching this function
*/
function detectOSpecType(content: string): 'O' | 'OAnd' | 'OF' | 'OFC' | 'OXF' {
// Check for comment line (though this should already be filtered by parser)
const commentChar = content.substr(6, 1);
if (commentChar === '*' || commentChar === '/') {
return 'OF'; // Return default to avoid processing
}

const filename = content.substr(6, 10).trim();
const type = content.substr(16, 1).trim();
const andOrKeyword = content.substr(15, 5).trim().toUpperCase();
const fieldName = content.substr(29, 14).trim();
const endPos = content.substr(46, 5).trim();
const constant = content.substr(52).trim();

if (andOrKeyword === 'AND' || andOrKeyword === 'OR') {
return 'OAnd';
}

// Check if this line has field/constant data (endPos + fieldName/constant)
// If so, it's a field line (OF), even if it also has type/filename
if (endPos && (fieldName || constant)) {
if (!endPos) {
return 'OXF';
}
return 'OF';
}

if (filename || type) {
return 'O';
}

if (!fieldName && constant) {
return 'OFC';
}

if (fieldName) {
const endPosCheck = content.substr(46, 5).trim();
if (!endPosCheck) {
return 'OXF';
}
return 'OF';
}

return 'OF';
}

/**
* Parse O-Spec (Output Specification) line
* @param {number} lineNumber
* @param {number} lineIndex
* @param {string} content
*/
export function parseOLine(lineNumber, lineIndex, content) {
content = content.padEnd(80);

const lineType = detectOSpecType(content);

const filename = content.substr(6, 10);
const type = content.substr(16, 1);

if (lineType === 'O') {
const fetchOverflow = content.substr(17, 3);
const addDeleteIndicator = content.substr(20, 3);
const outputIndicator1 = content.substr(23, 3);
const outputIndicator2 = content.substr(26, 3);
const exceptName = content.substr(29, 10);
const spaceBefore = content.substr(39, 3);
const spaceAfter = content.substr(42, 3);
const skipBefore = content.substr(45, 3);
const skipAfter = content.substr(48, 3);

return {
lineType: 'O',
filename: calculateToken(lineNumber, lineIndex+6, filename),
type: calculateToken(lineNumber, lineIndex+16, type),
fetchOverflow: calculateToken(lineNumber, lineIndex+17, fetchOverflow),
addDeleteIndicator: calculateToken(lineNumber, lineIndex+20, addDeleteIndicator),
outputIndicator1: calculateToken(lineNumber, lineIndex+23, outputIndicator1),
outputIndicator2: calculateToken(lineNumber, lineIndex+26, outputIndicator2),
exceptName: calculateToken(lineNumber, lineIndex+29, exceptName),
spaceBefore: calculateToken(lineNumber, lineIndex+39, spaceBefore),
spaceAfter: calculateToken(lineNumber, lineIndex+42, spaceAfter),
skipBefore: calculateToken(lineNumber, lineIndex+45, skipBefore),
skipAfter: calculateToken(lineNumber, lineIndex+48, skipAfter),
andOr: undefined,
fieldName: undefined,
blankAfter: undefined,
editCodes: undefined,
endPosition: undefined,
dataFormat: undefined,
constantOrEdit: undefined
};
} else if (lineType === 'OAnd') {
const andOrKeyword = content.substr(15, 5);
const outputIndicator1 = content.substr(20, 3);
const outputIndicator2 = content.substr(23, 3);
const outputIndicator3 = content.substr(26, 3);
const exceptName = content.substr(29, 10);

return {
lineType: 'OAnd',
andOrKeyword: calculateToken(lineNumber, lineIndex+15, andOrKeyword),
outputIndicator1: calculateToken(lineNumber, lineIndex+20, outputIndicator1),
outputIndicator2: calculateToken(lineNumber, lineIndex+23, outputIndicator2),
outputIndicator3: calculateToken(lineNumber, lineIndex+26, outputIndicator3),
exceptName: calculateToken(lineNumber, lineIndex+29, exceptName),
filename: undefined,
type: undefined,
fetchOverflow: undefined,
andOr: calculateToken(lineNumber, lineIndex+15, andOrKeyword),
fieldName: undefined,
blankAfter: undefined,
editCodes: undefined,
endPosition: undefined,
dataFormat: undefined,
constantOrEdit: undefined
};
} else if (lineType === 'OF') {
const outputIndicator1 = content.substr(20, 3);
const outputIndicator2 = content.substr(23, 3);
const outputIndicator3 = content.substr(26, 3);
const fieldName = content.substr(29, 14);
const blankAfter = content.substr(43, 1);
const editCodes = content.substr(44, 2);
const endPosition = content.substr(46, 5);
const dataFormat = content.substr(51, 1);
const constantOrEdit = content.substr(52, 28);

return {
lineType: 'OF',
outputIndicator1: calculateToken(lineNumber, lineIndex+20, outputIndicator1),
outputIndicator2: calculateToken(lineNumber, lineIndex+23, outputIndicator2),
outputIndicator3: calculateToken(lineNumber, lineIndex+26, outputIndicator3),
fieldName: calculateToken(lineNumber, lineIndex+29, fieldName),
blankAfter: calculateToken(lineNumber, lineIndex+43, blankAfter),
editCodes: calculateToken(lineNumber, lineIndex+44, editCodes),
endPosition: calculateToken(lineNumber, lineIndex+46, endPosition),
dataFormat: calculateToken(lineNumber, lineIndex+51, dataFormat),
constantOrEdit: calculateToken(lineNumber, lineIndex+52, constantOrEdit),
filename: undefined,
type: undefined,
fetchOverflow: undefined,
andOr: undefined
};
} else if (lineType === 'OFC') {
const constantOrEdit = content.substr(52, 28);

return {
lineType: 'OFC',
constantOrEdit: calculateToken(lineNumber, lineIndex+52, constantOrEdit),
filename: undefined,
type: undefined,
fetchOverflow: undefined,
andOr: undefined,
fieldName: undefined,
blankAfter: undefined,
editCodes: undefined,
endPosition: undefined,
dataFormat: undefined
};
} else if (lineType === 'OXF') {
const outputIndicator1 = content.substr(20, 3);
const outputIndicator2 = content.substr(23, 3);
const outputIndicator3 = content.substr(26, 3);
const fieldName = content.substr(29, 14);
const blankAfter = content.substr(44, 1);

return {
lineType: 'OXF',
outputIndicator1: calculateToken(lineNumber, lineIndex+20, outputIndicator1),
outputIndicator2: calculateToken(lineNumber, lineIndex+23, outputIndicator2),
outputIndicator3: calculateToken(lineNumber, lineIndex+26, outputIndicator3),
fieldName: calculateToken(lineNumber, lineIndex+29, fieldName),
blankAfter: calculateToken(lineNumber, lineIndex+44, blankAfter),
filename: undefined,
type: undefined,
fetchOverflow: undefined,
andOr: undefined,
editCodes: undefined,
endPosition: undefined,
dataFormat: undefined,
constantOrEdit: undefined
};
}

return {
lineType: 'OF',
filename: undefined,
type: undefined,
fetchOverflow: undefined,
andOr: undefined,
fieldName: undefined,
blankAfter: undefined,
editCodes: undefined,
endPosition: undefined,
dataFormat: undefined,
constantOrEdit: undefined
};
}

export function prettyTypeFromToken(dSpec) {
return getPrettyType({
type: dSpec.type ? dSpec.type.value : ``,
Expand Down
Loading