Skip to content

Initial work on parsing I specifications #430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
31 changes: 28 additions & 3 deletions extension/server/src/providers/documentSymbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,21 +97,22 @@ export default async function documentSymbolProvider(handler: DocumentSymbolPara
scope.constants
.filter(constant => constant.position && constant.position.path === currentPath)
.forEach(def => {
const isEnum = def.subItems && def.subItems.length > 0;
const constantDef = DocumentSymbol.create(
def.name,
prettyKeywords(def.keyword),
SymbolKind.Constant,
isEnum ? SymbolKind.Enum : SymbolKind.Constant,
Range.create(def.range.start!, 0, def.range.end!, 0),
Range.create(def.range.start!, 0, def.range.end!, 0)
);

if (def.subItems.length > 0) {
if (isEnum) {
constantDef.children = def.subItems
.filter(subitem => subitem.position && subitem.position.path === currentPath)
.map(subitem => DocumentSymbol.create(
subitem.name,
prettyKeywords(subitem.keyword),
SymbolKind.Property,
SymbolKind.EnumMember,
Range.create(subitem.range.start!, 0, subitem.range.start!, 0),
Range.create(subitem.range.end!, 0, subitem.range.end!, 0)
));
Expand Down Expand Up @@ -170,6 +171,30 @@ export default async function documentSymbolProvider(handler: DocumentSymbolPara
currentScopeDefs.push(expandStruct(struct));
});

scope.inputs
.filter(input => input.position && input.position.path === currentPath && validRange(input))
.forEach(input => {
const inputSymbol = DocumentSymbol.create(
input.name,
prettyKeywords(input.keyword),
SymbolKind.Interface,
Range.create(input.range.start!, 0, input.range.end!, 0),
Range.create(input.range.start!, 0, input.range.end!, 0)
);

inputSymbol.children = input.subItems
.filter(subitem => subitem.position && subitem.position.path === currentPath && validRange(subitem))
.map(subitem => DocumentSymbol.create(
subitem.name,
prettyKeywords(subitem.keyword),
SymbolKind.Property,
Range.create(subitem.range.start!, 0, subitem.range.end!, 0),
Range.create(subitem.range.start!, 0, subitem.range.end!, 0)
));

currentScopeDefs.push(inputSymbol);
});

return currentScopeDefs;
};

Expand Down
83 changes: 69 additions & 14 deletions language/models/cache.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CacheProps, IncludeStatement, Keywords } from "../parserTypes";
import { trimQuotes } from "../tokens";
import { IRange } from "../types";
import Declaration, { DeclarationType } from "./declaration";

Expand Down Expand Up @@ -89,6 +90,10 @@ export default class Cache {
return this.symbols.filter(s => s.type === `file`);
}

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

get constants() {
return this.symbols.filter(s => s.type === `constant`);
}
Expand Down Expand Up @@ -142,7 +147,7 @@ export default class Cache {
return (lines.length >= 1 ? lines[0] : 0);
}

find(name: string, specificType?: DeclarationType): Declaration | undefined {
find(name: string, specificType?: DeclarationType, ignorePrefix?: boolean): Declaration | undefined {
name = name.toUpperCase();

const existing = this.symbolRegister.get(name);
Expand All @@ -162,32 +167,82 @@ export default class Cache {
}
}

// If we didn't find it, let's check for subfields
const [subfield] = this.findSubfields(name, ignorePrefix, true);

return subfield;
}

findAll(name: string, ignorePrefix?: boolean): Declaration[] {
name = name.toUpperCase();
let symbols = this.symbolRegister.get(name) || [];

symbols.push(...this.findSubfields(name, ignorePrefix));

// Remove duplicates by position, since we can have the same reference to symbols in structures due to I-spec
symbols = symbols.filter((s, index, self) => {
return self.findIndex(item => item.position.path === s.position.path && s.position.range.line === item.position.range.line) === index;
});

return symbols || [];
}

private findSubfields(name: string, ignorePrefix: boolean, onlyOne?: boolean): Declaration[] {
let symbols: Declaration[] = [];

// Additional logic to check for subItems in symbols
const symbolsWithSubs = [...this.structs, ...this.files];
const symbolsWithSubs = [...this.structs, ...this.files, ...this.inputs];

const subNameIsValid = (sub: Declaration, name: string, prefix?: string) => {
if (prefix) {
name = `${prefix}${name}`;
}

return sub.name.toUpperCase() === name;
}

// First we do a loop to check all names without changing the prefix.
// This only applied to files
for (const struct of symbolsWithSubs) {
if (struct.keyword[`QUALIFIED`] !== true) {

// If the symbol is qualified, we need to check the subItems
const subItem = struct.subItems.find(sub => sub.name.toUpperCase() === name);
if (subItem) return subItem;
const subItem = struct.subItems.find(sub => subNameIsValid(sub, name));
if (subItem) {
symbols.push(subItem);
if (onlyOne) return symbols;
}

// If it's a file, we also need to check the subItems of the file's recordformats
for (const subFile of struct.subItems) {
const subSubItem = subFile.subItems.find(sub => subNameIsValid(sub, name));
if (subSubItem) {
symbols.push(subSubItem);
if (onlyOne) return symbols;
}
}
}
}

// Then we check the names, ignoring the prefix
if (ignorePrefix) {
for (const struct of symbolsWithSubs) {
if (struct.type === `file` && struct.keyword[`QUALIFIED`] !== true) {
const prefix = ignorePrefix && struct.keyword[`PREFIX`] && typeof struct.keyword[`PREFIX`] === `string` ? trimQuotes(struct.keyword[`PREFIX`].toUpperCase()) : ``;

if (struct.type === `file`) {
// If it's a file, we also need to check the subItems of the file's recordformats
for (const subFile of struct.subItems) {
const subSubItem = subFile.subItems.find(sub => sub.name.toUpperCase() === name);
if (subSubItem) return subSubItem;
const subSubItem = subFile.subItems.find(sub => subNameIsValid(sub, name, prefix));
if (subSubItem) {
symbols.push(subSubItem);
if (onlyOne) return symbols;
}
}
}
}
}

return;
}

findAll(name: string): Declaration[] {
name = name.toUpperCase();
const symbols = this.symbolRegister.get(name);
return symbols || [];
return symbols;
}

public findProcedurebyLine(lineNumber: number): Declaration | undefined {
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"|"input";

export default class Declaration {
name: string = ``;
Expand Down
134 changes: 120 additions & 14 deletions language/models/fixed.js → language/models/fixed.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@

import Parser from "../parser";
import { Keywords } from "../parserTypes";
import { Token } from "../types";

/**
* @param {number} lineNumber
Expand All @@ -7,7 +10,7 @@ import Parser from "../parser";
* @param {string} [type]
* @returns {import("../types").Token|undefined}
*/
function calculateToken(lineNumber, startingPos, value, type) {
function calculateToken(lineNumber: number, startingPos: number, value: string, type?: string): Token | undefined {
let resultValue = value.trim();

if (resultValue === ``) {
Expand Down Expand Up @@ -62,7 +65,7 @@ export function parseFLine(lineNumber, lineIndex, content) {
/**
* @param {string} content
*/
export function parseCLine(lineNumber, lineIndex, content) {
export function parseCLine(lineNumber: number, lineIndex: number, content: string) {
content = content.padEnd(80);
const clIndicator = content.substr(7, 8).toUpperCase();
const indicator = content.substr(9, 11);
Expand Down Expand Up @@ -97,10 +100,7 @@ export function parseCLine(lineNumber, lineIndex, content) {
};
}

/**
* @param {string} content
*/
export function parseDLine(lineNumber, lineIndex, content) {
export function parseDLine(lineNumber: number, lineIndex: number, content: string) {
content = content.padEnd(80);
const longForm = content.substring(6).trimEnd();
const potentialName = longForm.endsWith(`...`) ? calculateToken(lineNumber, lineIndex+6, longForm.substring(0, longForm.length - 3)) : undefined;
Expand Down Expand Up @@ -129,7 +129,7 @@ export function parseDLine(lineNumber, lineIndex, content) {
/**
* @param {string} content
*/
export function parsePLine(content, lineNumber, lineIndex) {
export function parsePLine(content: string, lineNumber: number, lineIndex: number) {
content = content.padEnd(80);
let name = content.substr(6, 16).trimEnd();
if (name.endsWith(`...`)) {
Expand All @@ -151,7 +151,7 @@ export function parsePLine(content, lineNumber, lineIndex) {
};
}

export function prettyTypeFromToken(dSpec) {
export function prettyTypeFromDSpecTokens(dSpec) {
return getPrettyType({
type: dSpec.type ? dSpec.type.value : ``,
keywords: dSpec.keywords,
Expand All @@ -162,12 +162,18 @@ export function prettyTypeFromToken(dSpec) {
})
}

/**
*
* @param {{type: string, keywords: import("../parserTypes").Keywords, len: string, pos: string, decimals: string, field: string}} lineData
* @returns {import("../parserTypes").Keywords}
*/
export function getPrettyType(lineData) {
export function prettyTypeFromISpecTokens(iSpec) {
return getPrettyType({
type: iSpec.dataFormat ? iSpec.dataFormat.value : ``,
keywords: {},
len: iSpec.length ? iSpec.length.value : ``,
pos: iSpec.fieldLocation ? iSpec.fieldLocation.value : ``,
decimals: iSpec.decimalPositions ? iSpec.decimalPositions.value : ``,
field: ``
})
}

export function getPrettyType(lineData: {type: string, keywords: Keywords, len: string, pos: string, decimals: string, field: string}): Keywords {
let outType = ``;
let length = Number(lineData.len);

Expand Down Expand Up @@ -304,4 +310,104 @@ export function getPrettyType(lineData) {
}

return Parser.expandKeywords(Parser.getTokens(outType));
}

// https://www.ibm.com/docs/fr/i/7.4.0?topic=specifications-input
export function parseISpec(lineNumber: number, lineIndex: number, content: string) {
content = content.padEnd(80);
let iType: "programRecord"|"programField"|"externalRecord"|"externalField"|undefined;

const name = content.substring(6, 16).trimEnd();

if (name) {
// RECORD
const externalReserved = content.substring(15, 20).trim();
if (externalReserved) {
// If this reserved area is not empty, then it is a program record
iType = `programRecord`;
} else {
iType = `externalRecord`;
}

} else {
// FIELD
const externalName = content.substring(20, 30).trim();

if (externalName) {
iType = `externalField`;
} else {
iType = `programField`;
}
}

const getPart = (start: number, end: number, type?: string) => {
return calculateToken(lineNumber, lineIndex + (start-1), content.substring(start-1, end).trimEnd(), type);
}

switch (iType) {
case `programRecord`:
// Handle program record
// https://www.ibm.com/docs/fr/i/7.4.0?topic=specifications-record-identification-entries#iri

return {
iType,
name: getPart(7, 16),
logicalRelationship: getPart(16, 18),
sequence: getPart(18, 19),
number: getPart(19, 20),
option: getPart(20, 21),
recordIdentifyingIndicator: getPart(21, 23, `special-ind`) // 2 characters
}
break;
case `programField`:
// Handle program field
// https://www.ibm.com/docs/fr/i/7.4.0?topic=specifications-field-description-entries#ifd

return {
iType,
dataAttributes: getPart(31, 34),
dateTimeSeparator: getPart(35, 36),
dataFormat: getPart(36, 37),
fieldLocation: getPart(37, 41),
length: getPart(42, 46),
decimalPositions: getPart(47, 48),
fieldName: getPart(49, 62),
controlLevel: getPart(63, 64, `special-ind`),
matchingFields: getPart(65, 66, `special-ind`),
fieldRecordRelation: getPart(67, 68, `special-ind`),
fieldIndicators: [
getPart(69, 70, `special-ind`),
getPart(71, 72, `special-ind`),
getPart(73, 74, `special-ind`)
]
}

case `externalRecord`:
// Handle external record
// https://www.ibm.com/docs/fr/i/7.4.0?topic=is-record-identification-entries#ier

return {
iType,
name: getPart(7, 16),
recordIdentifyingIndicator: getPart(21, 22, `special-ind`), // 2 characters
};
break;
case `externalField`:
// Handle external field
// https://www.ibm.com/docs/fr/i/7.4.0?topic=is-field-description-entries#ied

return {
iType,
externalName: getPart(21, 30),
fieldName: getPart(49, 62),
controlLevel: getPart(63, 64, `special-ind`),
matchingFields: getPart(65, 66, `special-ind`),
fieldIndicators: [
getPart(69, 70, `special-ind`),
getPart(71, 72, `special-ind`),
getPart(73, 74, `special-ind`)
]
};
break;
}
}
Loading