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
2 changes: 1 addition & 1 deletion antlr/src/main/antlr/FSH.g4
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ parent: KW_PARENT name;
id: KW_ID name;
title: KW_TITLE STRING;
description: KW_DESCRIPTION (STRING | MULTILINE_STRING);
expression: KW_EXPRESSION STRING;
expression: KW_EXPRESSION (STRING | MULTILINE_STRING);
xpath: KW_XPATH STRING;
severity: KW_SEVERITY CODE;
instanceOf: KW_INSTANCEOF name;
Expand Down
8 changes: 7 additions & 1 deletion src/fshtypes/Invariant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,13 @@ export class Invariant extends FshEntity {
resultLines.push(`* severity = ${this.severity}`);
}
if (this.expression) {
resultLines.push(`* expression = "${fshifyString(this.expression)}"`);
// Expression can be a multiline string.
// If it contains newline characters, treat it as a multiline string.
if (this.expression.indexOf('\n') > -1) {
resultLines.push(`* expression = """${this.expression}"""`);
} else {
resultLines.push(`* expression = "${fshifyString(this.expression)}"`);
}
}
if (this.xpath) {
resultLines.push(`* xpath = "${fshifyString(this.xpath)}"`);
Expand Down
8 changes: 7 additions & 1 deletion src/import/FSHImporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,13 @@ export class FSHImporter extends FSHVisitor {
}

visitExpression(ctx: pc.ExpressionContext): string {
return this.extractString(ctx.STRING());
if (ctx.STRING()) {
return this.extractString(ctx.STRING());
} else if (ctx.MULTILINE_STRING()) {
return this.extractMultilineString(ctx.MULTILINE_STRING());
}
// this can happen due to parsing errors, so just return empty string
return '';
}

visitXpath(ctx: pc.XpathContext): string {
Expand Down
2 changes: 1 addition & 1 deletion src/import/generated/FSH.interp

Large diffs are not rendered by default.

16 changes: 14 additions & 2 deletions src/import/generated/FSHParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ const serializedATN = [4,1,89,837,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,
0,0,0,454,453,1,0,0,0,455,63,1,0,0,0,456,457,5,13,0,0,457,458,3,144,72,0,
458,65,1,0,0,0,459,460,5,14,0,0,460,461,3,144,72,0,461,67,1,0,0,0,462,463,
5,15,0,0,463,464,5,58,0,0,464,69,1,0,0,0,465,466,5,16,0,0,466,467,7,4,0,
0,467,71,1,0,0,0,468,469,5,17,0,0,469,470,5,58,0,0,470,73,1,0,0,0,471,472,
0,467,71,1,0,0,0,468,469,5,17,0,0,469,470,7,4,0,0,470,73,1,0,0,0,471,472,
5,18,0,0,472,473,5,58,0,0,473,75,1,0,0,0,474,475,5,19,0,0,475,476,5,62,0,
0,476,77,1,0,0,0,477,478,5,5,0,0,478,479,3,144,72,0,479,79,1,0,0,0,480,481,
5,20,0,0,481,482,5,62,0,0,482,81,1,0,0,0,483,484,5,21,0,0,484,485,3,144,
Expand Down Expand Up @@ -1990,12 +1990,20 @@ export default class FSHParser extends antlr4.Parser {
expression() {
let localctx = new ExpressionContext(this, this._ctx, this.state);
this.enterRule(localctx, 72, FSHParser.RULE_expression);
var _la = 0;
try {
this.enterOuterAlt(localctx, 1);
this.state = 468;
this.match(FSHParser.KW_EXPRESSION);
this.state = 469;
this.match(FSHParser.STRING);
_la = this._input.LA(1);
if(!(_la===58 || _la===59)) {
this._errHandler.recoverInline(this);
}
else {
this._errHandler.reportMatch(this);
this.consume();
}
} catch (re) {
if(re instanceof antlr4.error.RecognitionException) {
localctx.exception = re;
Expand Down Expand Up @@ -6647,6 +6655,10 @@ class ExpressionContext extends antlr4.ParserRuleContext {
return this.getToken(FSHParser.STRING, 0);
};

MULTILINE_STRING() {
return this.getToken(FSHParser.MULTILINE_STRING, 0);
};

enterRule(listener) {
if(listener instanceof FSHListener ) {
listener.enterExpression(this);
Expand Down
1 change: 1 addition & 0 deletions src/import/parserContexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ export interface UsageContext extends ParserRuleContext {

export interface ExpressionContext extends ParserRuleContext {
STRING(): ParserRuleContext;
MULTILINE_STRING(): ParserRuleContext;
}

export interface XpathContext extends ParserRuleContext {
Expand Down
19 changes: 19 additions & 0 deletions test/fshtypes/Invariant.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,25 @@ describe('Invariant', () => {
expect(result).toBe(expectedResult);
});

it('should produce FSH for an invariant with a multiline expression', () => {
const input = new Invariant('inv-3a');
input.description = 'This invariant has a multiline expression.';
input.severity = new FshCode('error');
input.expression =
'(cage.exists() and aquarium.exists()) or\n(habitat.exists() and enclosure.exists())';
input.xpath = 'f:requirement';

const expectedResult = [
'Invariant: inv-3a',
'Description: "This invariant has a multiline expression."',
'* severity = #error',
'* expression = """(cage.exists() and aquarium.exists()) or\n(habitat.exists() and enclosure.exists())"""',
'* xpath = "f:requirement"'
].join(EOL);
const result = input.toFSH();
expect(result).toBe(expectedResult);
});

it('should produce FSH for an invariant with additional rules', () => {
const input = new Invariant('inv-4');
input.description = 'This is an important condition.';
Expand Down
31 changes: 31 additions & 0 deletions test/import/FSHImporter.Invariant.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,37 @@ describe('FSHImporter', () => {
expect(invariant.sourceInfo.file).toBe('Full.fsh');
});

it('should parse an invariant with multiline expression', () => {
const input = `
Invariant: multiline-1
Description: "This invariant has a multiline expression."
Expression: """
(cage.exists() and aquarium.exists()) or
(habitat.exists() and enclosure.exists())
"""
Severity: #error
`;
const result = importSingleText(input, 'Multiline.fsh');
expect(result.invariants.size).toBe(1);
const invariant = result.invariants.get('multiline-1');
expect(invariant.name).toBe('multiline-1');
expect(invariant.description).toBe('This invariant has a multiline expression.');
expect(invariant.expression).toBe(
'(cage.exists() and aquarium.exists()) or\n(habitat.exists() and enclosure.exists())'
);
const severityCode = new FshCode('error')
.withLocation([8, 19, 8, 24])
.withFile('Multiline.fsh');
expect(invariant.severity).toEqual(severityCode);
expect(invariant.sourceInfo.location).toEqual({
startLine: 2,
startColumn: 9,
endLine: 8,
endColumn: 24
});
expect(invariant.sourceInfo.file).toBe('Multiline.fsh');
});

it('should only apply each metadata attribute the first time it is declared', () => {
const input = `
Invariant: twice-1
Expand Down