From 574272602ff68e7b43b60d8b4de010a368cfede5 Mon Sep 17 00:00:00 2001 From: Rene Saarsoo Date: Tue, 2 Mar 2021 21:58:34 +0200 Subject: [PATCH] Positive comment offset syntax: +01:00 --- src/parser/parser.test.ts | 137 ++++++++++++++++++++++++++++++++++++++ src/parser/parser.ts | 16 ++++- src/parser/tokenizer.ts | 15 ++++- 3 files changed, 163 insertions(+), 5 deletions(-) diff --git a/src/parser/parser.test.ts b/src/parser/parser.test.ts index 114fb8f..9bf5265 100644 --- a/src/parser/parser.test.ts +++ b/src/parser/parser.test.ts @@ -643,6 +643,143 @@ Rest: 5:00 50% `); }); + it("parses intervals with positive comment offsets", () => { + expect( + parse(` +Name: My Workout +Interval: 10:00 90% + @ 0:50 First comment + @ +0:10 Comment #2 10 seconds later + @ +0:10 Comment #3 another 10 seconds later + @ 5:00 Half way! + @ +0:10 Comment #5 10 seconds later + @ +0:10 Comment #6 another 10 seconds later +`), + ).toMatchInlineSnapshot(` + Object { + "author": "", + "description": "", + "intervals": Array [ + Object { + "cadence": undefined, + "comments": Array [ + Object { + "loc": Object { + "col": 4, + "row": 3, + }, + "offset": Duration { + "seconds": 50, + }, + "text": "First comment", + }, + Object { + "loc": Object { + "col": 4, + "row": 4, + }, + "offset": Duration { + "seconds": 60, + }, + "text": "Comment #2 10 seconds later", + }, + Object { + "loc": Object { + "col": 4, + "row": 5, + }, + "offset": Duration { + "seconds": 70, + }, + "text": "Comment #3 another 10 seconds later", + }, + Object { + "loc": Object { + "col": 4, + "row": 6, + }, + "offset": Duration { + "seconds": 300, + }, + "text": "Half way!", + }, + Object { + "loc": Object { + "col": 4, + "row": 7, + }, + "offset": Duration { + "seconds": 310, + }, + "text": "Comment #5 10 seconds later", + }, + Object { + "loc": Object { + "col": 4, + "row": 8, + }, + "offset": Duration { + "seconds": 320, + }, + "text": "Comment #6 another 10 seconds later", + }, + ], + "duration": Duration { + "seconds": 600, + }, + "intensity": ConstantIntensity { + "_value": 0.9, + }, + "type": "Interval", + }, + ], + "name": "My Workout", + "tags": Array [], + } + `); + }); + + it("treats positive comment offset as relative to interval start when there's no previous comment", () => { + expect( + parse(` +Name: My Workout +Interval: 10:00 90% + @ +1:00 First comment +`), + ).toMatchInlineSnapshot(` + Object { + "author": "", + "description": "", + "intervals": Array [ + Object { + "cadence": undefined, + "comments": Array [ + Object { + "loc": Object { + "col": 4, + "row": 3, + }, + "offset": Duration { + "seconds": 60, + }, + "text": "First comment", + }, + ], + "duration": Duration { + "seconds": 600, + }, + "intensity": ConstantIntensity { + "_value": 0.9, + }, + "type": "Interval", + }, + ], + "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 97c9668..34954e0 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -1,8 +1,9 @@ +import { last } from "ramda"; import { Interval, Workout, Comment } from "../ast"; import { Duration } from "../Duration"; import { ConstantIntensity, FreeIntensity, RangeIntensity } from "../Intensity"; import { ParseError } from "./ParseError"; -import { IntervalType, SourceLocation, Token } from "./tokenizer"; +import { IntervalType, OffsetToken, SourceLocation, Token } from "./tokenizer"; type Header = Partial>; @@ -72,8 +73,7 @@ const parseIntervalComments = (tokens: Token[], intervalDuration: Duration): [Co throw new ParseError(`Expected [comment text] instead got ${tokenToString(text)}`, text?.loc || offset.loc); } comments.push({ - // when offset is negative, recalculate it based on interval length - offset: new Duration(offset.kind === "absolute" ? offset.value : intervalDuration.seconds - offset.value), + offset: absoluteOffset(offset, intervalDuration, last(comments)), text: text.value, loc: offset.loc, }); @@ -85,6 +85,16 @@ const parseIntervalComments = (tokens: Token[], intervalDuration: Duration): [Co return [comments, tokens]; }; +const absoluteOffset = (offset: OffsetToken, intervalDuration: Duration, previousComment?: Comment): Duration => { + if (offset.kind === "relative-minus") { + return new Duration(intervalDuration.seconds - offset.value); + } else if (offset.kind === "relative-plus" && previousComment) { + return new Duration(previousComment.offset.seconds + offset.value); + } else { + return new Duration(offset.value); + } +}; + const parseIntervalParams = (type: IntervalType, tokens: Token[], loc: SourceLocation): [Interval, Token[]] => { let duration; let cadence; diff --git a/src/parser/tokenizer.ts b/src/parser/tokenizer.ts index bc87bd0..b3dc9b5 100644 --- a/src/parser/tokenizer.ts +++ b/src/parser/tokenizer.ts @@ -102,7 +102,7 @@ const tokenizeParams = (text: string, loc: SourceLocation): Token[] => { }; const tokenizeComment = (line: string, row: number): Token[] | undefined => { - const [, commentHead, minus, offset, commentText] = line.match(/^(\s*@\s*)(-?)([0-9:]+)(.*?)$/) || []; + const [, commentHead, sign, offset, commentText] = line.match(/^(\s*@\s*)([-+]?)([0-9:]+)(.*?)$/) || []; if (!commentHead) { return undefined; } @@ -113,7 +113,7 @@ const tokenizeComment = (line: string, row: number): Token[] | undefined => { { type: "comment-start", loc: { row, col: line.indexOf("@") } }, { type: "offset", - kind: minus ? "relative-minus" : "absolute", + kind: signToKind(sign), value: toSeconds(offset), loc: { row, col: commentHead.length }, }, @@ -121,6 +121,17 @@ const tokenizeComment = (line: string, row: number): Token[] | undefined => { ]; }; +const signToKind = (sign: string) => { + switch (sign) { + case "-": + return "relative-minus"; + case "+": + return "relative-plus"; + default: + return "absolute"; + } +}; + const tokenizeHeader = (label: HeaderType, separator: string, paramString: string, row: number): Token[] => { const token: HeaderToken = { type: "header",