diff --git a/src/parser/parser.test.ts b/src/parser/parser.test.ts index 5b4c635..20ea7e3 100644 --- a/src/parser/parser.test.ts +++ b/src/parser/parser.test.ts @@ -555,6 +555,94 @@ Rest: 5:00 50% `); }); + it("parses intervals with negative comment offsets", () => { + expect( + parse(` +Name: My Workout +Interval: 10:00 90% + @ 0:10 Find your rythm. + @ -0:10 Final push. YOU GOT IT! + +Rest: 5:00 50% + @ -4:30 Great effort! + @ -2:00 Cool down well after all of this. +`), + ).toMatchInlineSnapshot(` + Object { + "author": "", + "description": "", + "intervals": Array [ + Object { + "cadence": undefined, + "comments": Array [ + Object { + "loc": Object { + "col": 4, + "row": 3, + }, + "offset": Duration { + "seconds": 10, + }, + "text": "Find your rythm.", + }, + Object { + "loc": Object { + "col": 4, + "row": 4, + }, + "offset": Duration { + "seconds": 590, + }, + "text": "Final push. YOU GOT IT!", + }, + ], + "duration": Duration { + "seconds": 600, + }, + "intensity": ConstantIntensity { + "_value": 0.9, + }, + "type": "Interval", + }, + Object { + "cadence": undefined, + "comments": Array [ + Object { + "loc": Object { + "col": 4, + "row": 7, + }, + "offset": Duration { + "seconds": 30, + }, + "text": "Great effort!", + }, + Object { + "loc": Object { + "col": 4, + "row": 8, + }, + "offset": Duration { + "seconds": 180, + }, + "text": "Cool down well after all of this.", + }, + ], + "duration": Duration { + "seconds": 300, + }, + "intensity": ConstantIntensity { + "_value": 0.5, + }, + "type": "Rest", + }, + ], + "name": "My Workout", + "tags": Array [], + } + `); + }); + it("throws error when comment offset is outside of interval length", () => { expect(() => parse(` diff --git a/src/parser/parser.ts b/src/parser/parser.ts index 4c9876f..ce97be6 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -57,7 +57,7 @@ const parseHeader = (tokens: Token[]): [Header, Token[]] => { return [header, tokens]; }; -const parseIntervalComments = (tokens: Token[]): [Comment[], Token[]] => { +const parseIntervalComments = (tokens: Token[], intervalDuration: Duration): [Comment[], Token[]] => { const comments: Comment[] = []; while (tokens[0]) { const [start, offset, text, ...rest] = tokens; @@ -72,7 +72,8 @@ const parseIntervalComments = (tokens: Token[]): [Comment[], Token[]] => { throw new ParseError(`Expected [comment text] instead got ${tokenToString(text)}`, text?.loc || offset.loc); } comments.push({ - offset: new Duration(offset.value), + // when offset is negative, recalculate it based on interval length + offset: new Duration(offset.value >= 0 ? offset.value : intervalDuration.seconds + offset.value), text: text.value, loc: offset.loc, }); @@ -115,7 +116,7 @@ const parseIntervalParams = (type: IntervalType, tokens: Token[], loc: SourceLoc intensity = new FreeIntensity(); } - const [comments, rest] = parseIntervalComments(tokens); + const [comments, rest] = parseIntervalComments(tokens, duration); return [{ type, duration, intensity, cadence, comments }, rest]; }; diff --git a/src/parser/tokenizer.ts b/src/parser/tokenizer.ts index 8efb7e3..4b273a1 100644 --- a/src/parser/tokenizer.ts +++ b/src/parser/tokenizer.ts @@ -89,16 +89,17 @@ const tokenizeParams = (text: string, loc: SourceLocation): Token[] => { }; const tokenizeComment = (line: string, row: number): Token[] | undefined => { - const [, commentHead, offset, commentText] = line.match(/^(\s*@\s*)([0-9:]+)(.*?)$/) || []; + const [, commentHead, minus, offset, commentText] = line.match(/^(\s*@\s*)(-?)([0-9:]+)(.*?)$/) || []; if (!commentHead) { return undefined; } + const sign = minus ? -1 : 1; if (!DURATION_REGEX.test(offset)) { throw new ParseError("Invalid comment offset", { row, col: commentHead.length }); } return [ { type: "comment-start", loc: { row, col: line.indexOf("@") } }, - { type: "duration", value: toSeconds(offset), loc: { row, col: commentHead.length } }, + { type: "duration", value: sign * toSeconds(offset), loc: { row, col: commentHead.length } }, { type: "text", value: commentText.trim(), loc: { row, col: commentHead.length + offset.length } }, ]; };