Skip to content

Commit

Permalink
feat: support for inline and subdirective comments for price directives
Browse files Browse the repository at this point in the history
This part of the price directive syntax was previously overlooked. Journals containing price
directive comments can now be parsed without producing errors.

closes #25
  • Loading branch information
darylwright committed Apr 21, 2024
1 parent 6cfbf9a commit 7c0976d
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 7 deletions.
47 changes: 47 additions & 0 deletions diagram.html
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,53 @@
"name": "amount",
"idx": 0
},
{
"type": "Option",
"idx": 0,
"definition": [
{
"type": "NonTerminal",
"name": "inlineComment",
"idx": 0
}
]
},
{
"type": "Terminal",
"name": "NEWLINE",
"label": "NEWLINE",
"idx": 0,
"pattern": "(\\r\\n|\\r|\\n)"
},
{
"type": "Repetition",
"idx": 0,
"definition": [
{
"type": "NonTerminal",
"name": "priceDirectiveContentLine",
"idx": 0
}
]
}
]
},
{
"type": "Rule",
"name": "priceDirectiveContentLine",
"orgText": "",
"definition": [
{
"type": "Terminal",
"name": "INDENT",
"label": "INDENT",
"idx": 0
},
{
"type": "NonTerminal",
"name": "inlineComment",
"idx": 0
},
{
"type": "Terminal",
"name": "NEWLINE",
Expand Down
59 changes: 58 additions & 1 deletion src/__tests__/cst_to_raw_visitor/price_directive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,65 @@ test('returns a price directive object', (t) => {
commodity: 'CAD',
value: '10CAD',
sign: undefined
}
},
comments: undefined,
contentLines: []
},
'should correctly return all price directive fields'
);
});

test('returns a price directive object with inline comment', (t) => {
const cstResult = parseLedgerToCST(`P 1900/01/01 $ 10CAD ; comment\n`);

assertNoLexingOrParsingErrors(t, cstResult);

const result = CstToRawVisitor.journal(cstResult.cstJournal.children);

t.is(result.length, 1, 'should modify a price directive');
t.is(result[0].type, 'priceDirective', 'should be a priceDirective object');

const priceDirective = result[0] as Raw.PriceDirective;

t.truthy(
priceDirective.value.comments,
'price directive should contain an inline comment'
);
t.is(
priceDirective.value.comments?.value[0],
'comment',
'price directive should contain the correct inline comment text'
);
});

test('returns a price directive object with subdirective comments', (t) => {
const cstResult = parseLedgerToCST(`P 1900/01/01 $ 10CAD
; subdirective comment
; more subdirective comments
`);

assertNoLexingOrParsingErrors(t, cstResult);

const result = CstToRawVisitor.journal(cstResult.cstJournal.children);

t.is(result.length, 1, 'should modify a price directive');
t.is(result[0].type, 'priceDirective', 'should be a priceDirective object');

const priceDirective = result[0] as Raw.PriceDirective;

t.is(
priceDirective.value.contentLines.length,
2,
'price directive should contain two subdirective comments'
);
t.is(
priceDirective.value.contentLines[0].value.inlineComment.value[0],
'subdirective comment',
'price directive should contain the correct subdirective comment text'
);
t.is(
priceDirective.value.contentLines[1].value.inlineComment.value[0],
'more subdirective comments',
'price directive should contain the correct subdirective comment text'
);
});
17 changes: 17 additions & 0 deletions src/__tests__/lexer/price_directives.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,20 @@ test(
'p 1900/01/01 USD -$1',
[]
);

test(
'recognizes an inline comment',
macro,
'P 2024.01.03 USD $1.37 ; comment\n',
[
'PDirective',
'SimpleDate',
{ PDirectiveCommodityText: 'USD' },
{ CommodityText: '$' },
'Number',
'AMOUNT_WS',
'SemicolonComment',
'InlineCommentText',
'NEWLINE'
]
);
169 changes: 169 additions & 0 deletions src/__tests__/parser/price_directive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import anyTest, { TestFn } from 'ava';

import {
CommodityText,
INDENT,
InlineCommentText,
JournalNumber,
NEWLINE,
PDirective,
PDirectiveCommodityText,
SemicolonComment,
SimpleDate
} from '../../lib/lexer/tokens';
import HLedgerParser from '../../lib/parser';
Expand Down Expand Up @@ -88,3 +91,169 @@ test('does not parse a price directive line without a date', (t) => {

t.falsy(HLedgerParser.priceDirective(), '<priceDirective!> P € $1.50\\n');
});

test('parses a price directive with an inline comment', (t) => {
t.context.lexer
.addToken(PDirective, 'P')
.addToken(SimpleDate, '2024.01.01')
.addToken(PDirectiveCommodityText, '€')
.addToken(CommodityText, '¥')
.addToken(JournalNumber, '15000')
.addToken(SemicolonComment, ';')
.addToken(InlineCommentText, 'a comment')
.addToken(NEWLINE, '\n');
HLedgerParser.input = t.context.lexer.tokenize();

t.deepEqual(
simplifyCst(HLedgerParser.priceDirective()),
{
PDirective: 1,
SimpleDate: 1,
PDirectiveCommodityText: 1,
NEWLINE: 1,
amount: [
{
CommodityText: 1,
Number: 1
}
],
inlineComment: [
{
SemicolonComment: 1,
inlineCommentItem: [
{
InlineCommentText: 1
}
]
}
]
},
'<priceDirective> P 2024.01.01 € ¥15000 ; a comment\\n'
);
});

test('parses a price directive with an inline comment and subdirective comment', (t) => {
t.context.lexer
.addToken(PDirective, 'P')
.addToken(SimpleDate, '2024.01.01')
.addToken(PDirectiveCommodityText, '€')
.addToken(CommodityText, '¥')
.addToken(JournalNumber, '15000')
.addToken(SemicolonComment, ';')
.addToken(InlineCommentText, 'a comment')
.addToken(NEWLINE, '\n')
.addToken(INDENT, ' ')
.addToken(SemicolonComment, ';')
.addToken(InlineCommentText, 'subdirective comment')
.addToken(NEWLINE, '\n');
HLedgerParser.input = t.context.lexer.tokenize();

t.deepEqual(
simplifyCst(HLedgerParser.priceDirective()),
{
PDirective: 1,
SimpleDate: 1,
PDirectiveCommodityText: 1,
NEWLINE: 1,
amount: [
{
CommodityText: 1,
Number: 1
}
],
inlineComment: [
{
SemicolonComment: 1,
inlineCommentItem: [
{
InlineCommentText: 1
}
]
}
],
priceDirectiveContentLine: [
{
INDENT: 1,
NEWLINE: 1,
inlineComment: [
{
SemicolonComment: 1,
inlineCommentItem: [
{
InlineCommentText: 1
}
]
}
]
}
]
},
'<priceDirective> P 2024.01.01 € ¥15000 ; a comment\\n ; subdirective comment\\n'
);
});

test('parses a price directive with several subdirective comments', (t) => {
t.context.lexer
.addToken(PDirective, 'P')
.addToken(SimpleDate, '2024.01.01')
.addToken(PDirectiveCommodityText, '€')
.addToken(CommodityText, '¥')
.addToken(JournalNumber, '15000')
.addToken(NEWLINE, '\n')
.addToken(INDENT, ' ')
.addToken(SemicolonComment, ';')
.addToken(InlineCommentText, 'subdirective comment')
.addToken(NEWLINE, '\n')
.addToken(INDENT, ' ')
.addToken(SemicolonComment, ';')
.addToken(InlineCommentText, 'another comment')
.addToken(NEWLINE, '\n');
HLedgerParser.input = t.context.lexer.tokenize();

t.deepEqual(
simplifyCst(HLedgerParser.priceDirective()),
{
PDirective: 1,
SimpleDate: 1,
PDirectiveCommodityText: 1,
NEWLINE: 1,
amount: [
{
CommodityText: 1,
Number: 1
}
],
priceDirectiveContentLine: [
{
INDENT: 1,
NEWLINE: 1,
inlineComment: [
{
SemicolonComment: 1,
inlineCommentItem: [
{
InlineCommentText: 1
}
]
}
]
},
{
INDENT: 1,
NEWLINE: 1,
inlineComment: [
{
SemicolonComment: 1,
inlineCommentItem: [
{
InlineCommentText: 1
}
]
}
]
}
]
},
'<priceDirective> P 2024.01.01 € ¥15000\\n ; subdirective comment\\n ; another comment\\n'
);
});
14 changes: 14 additions & 0 deletions src/lib/hledger_cst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@ export type PriceDirectiveCstChildren = {
SimpleDate: IToken[];
PDirectiveCommodityText: IToken[];
amount: AmountCstNode[];
inlineComment?: InlineCommentCstNode[];
NEWLINE: IToken[];
priceDirectiveContentLine?: PriceDirectiveContentLineCstNode[];
};

export interface PriceDirectiveContentLineCstNode extends CstNode {
name: "priceDirectiveContentLine";
children: PriceDirectiveContentLineCstChildren;
}

export type PriceDirectiveContentLineCstChildren = {
INDENT: IToken[];
inlineComment: InlineCommentCstNode[];
NEWLINE: IToken[];
};

Expand Down Expand Up @@ -380,6 +393,7 @@ export interface ICstNodeVisitor<IN, OUT> extends ICstVisitor<IN, OUT> {
journalItem(children: JournalItemCstChildren, param?: IN): OUT;
transaction(children: TransactionCstChildren, param?: IN): OUT;
priceDirective(children: PriceDirectiveCstChildren, param?: IN): OUT;
priceDirectiveContentLine(children: PriceDirectiveContentLineCstChildren, param?: IN): OUT;
accountDirective(children: AccountDirectiveCstChildren, param?: IN): OUT;
accountDirectiveContentLine(children: AccountDirectiveContentLineCstChildren, param?: IN): OUT;
transactionInitLine(children: TransactionInitLineCstChildren, param?: IN): OUT;
Expand Down
3 changes: 2 additions & 1 deletion src/lib/lexer/modes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ export const price_amounts_mode = [
JournalNumber,
CommodityText,
DASH,
PLUS
PLUS,
SemicolonComment
];

export const memo_mode = [SemicolonComment, NEWLINE, Memo];
Expand Down
17 changes: 13 additions & 4 deletions src/lib/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,19 +110,28 @@ class HLedgerParser extends CstParser {

public transaction = this.RULE('transaction', () => {
this.SUBRULE(this.transactionInitLine);
this.MANY(() => {
this.SUBRULE(this.transactionContentLine);
});
this.MANY(() => this.SUBRULE(this.transactionContentLine));
});

public priceDirective = this.RULE('priceDirective', () => {
this.CONSUME(PDirective);
this.CONSUME(SimpleDate);
this.CONSUME(PDirectiveCommodityText);
this.SUBRULE(this.amount);
this.CONSUME(NEWLINE); // TODO: There is support for inline comments prior to NEWLINE.
this.OPTION(() => this.SUBRULE(this.inlineComment));
this.CONSUME(NEWLINE);
this.MANY(() => this.SUBRULE(this.priceDirectiveContentLine));
});

public priceDirectiveContentLine = this.RULE(
'priceDirectiveContentLine',
() => {
this.CONSUME(INDENT);
this.SUBRULE(this.inlineComment);
this.CONSUME(NEWLINE);
}
);

public accountDirective = this.RULE('accountDirective', () => {
this.CONSUME(AccountDirective);
this.CONSUME(AccountName);
Expand Down
Loading

0 comments on commit 7c0976d

Please sign in to comment.