Parsing of interval parameters

This commit is contained in:
Rene Saarsoo 2020-09-18 15:15:35 +03:00
parent 2a362e6362
commit 04467b944f
3 changed files with 95 additions and 31 deletions

View File

@ -1,3 +1,5 @@
import { IntervalLabelTokenValue } from "./tokenizer";
export type Workout = { export type Workout = {
name: string; name: string;
author: string; author: string;
@ -5,9 +7,12 @@ export type Workout = {
intervals: Interval[]; intervals: Interval[];
}; };
export type Interval = { export type IntervalData = {
type: "Warmup" | "Cooldown" | "Interval" | "Rest";
duration: number; duration: number;
power: { from: number; to: number }; power: { from: number; to: number };
cadence?: number; cadence?: number;
}; };
export type Interval = IntervalData & {
type: IntervalLabelTokenValue;
};

View File

@ -1,5 +1,5 @@
import { Interval, Workout } from "./ast"; import { Interval, IntervalData, Workout } from "./ast";
import { LabelTokenValue, Token } from "./tokenizer"; import { isIntervalLabelTokenValue, Token } from "./tokenizer";
type Header = { type Header = {
name?: string; name?: string;
@ -25,23 +25,17 @@ const parseHeader = (tokens: Token[]): [Header, Token[]] => {
while (tokens[0]) { while (tokens[0]) {
const token = tokens[0]; const token = tokens[0];
if (token.type === "label" && token.value === LabelTokenValue.Name) { if (token.type === "label" && token.value === "Name") {
tokens.shift(); tokens.shift();
const [name, rest] = extractText(tokens); const [name, rest] = extractText(tokens);
header.name = name; header.name = name;
tokens = rest; tokens = rest;
} else if ( } else if (token.type === "label" && token.value === "Author") {
token.type === "label" &&
token.value === LabelTokenValue.Author
) {
tokens.shift(); tokens.shift();
const [author, rest] = extractText(tokens); const [author, rest] = extractText(tokens);
header.author = author; header.author = author;
tokens = rest; tokens = rest;
} else if ( } else if (token.type === "label" && token.value === "Description") {
token.type === "label" &&
token.value === LabelTokenValue.Description
) {
tokens.shift(); tokens.shift();
const [description, rest] = extractText(tokens); const [description, rest] = extractText(tokens);
header.description = description; header.description = description;
@ -55,8 +49,60 @@ const parseHeader = (tokens: Token[]): [Header, Token[]] => {
return [header, tokens]; return [header, tokens];
}; };
const parseIntervalParams = (tokens: Token[]): [IntervalData, Token[]] => {
const data: Partial<IntervalData> = {};
while (tokens[0]) {
const token = tokens[0];
if (token.type === "duration") {
data.duration = token.value;
tokens.shift();
} else if (token.type === "cadence") {
data.cadence = token.value;
tokens.shift();
} else if (token.type === "power") {
data.power = { from: token.value, to: token.value };
tokens.shift();
} else if (token.type === "power-range") {
data.power = { from: token.value[0], to: token.value[1] };
tokens.shift();
} else {
break;
}
}
if (!("duration" in data)) {
throw new Error("Duration not specified");
}
if (!("power" in data)) {
throw new Error("Power not specified");
}
return [data as IntervalData, tokens];
};
const parseIntervals = (tokens: Token[]): Interval[] => { const parseIntervals = (tokens: Token[]): Interval[] => {
return []; const intervals: Interval[] = [];
while (tokens[0]) {
const token = tokens.shift() as Token;
if (token.type === "label" && isIntervalLabelTokenValue(token.value)) {
const [{ duration, power, cadence }, rest] = parseIntervalParams(tokens);
intervals.push({
type: token.value,
duration,
power,
cadence,
});
tokens = rest;
} else if (token.type === "text" && token.value === "") {
// Ignore empty lines
} else {
throw new Error(`Unexpected token [${token.type} ${token.value}]`);
}
}
return intervals;
}; };
export const parse = (tokens: Token[]): Workout => { export const parse = (tokens: Token[]): Workout => {

View File

@ -1,12 +1,25 @@
export enum LabelTokenValue { export type HeaderLabelTokenValue = "Name" | "Author" | "Description";
Name = "Name", export type IntervalLabelTokenValue =
Author = "Author", | "Warmup"
Description = "Description", | "Rest"
Warmup = "Warmup", | "Interval"
Rest = "Rest", | "Cooldown";
Interval = "Interval", export type LabelTokenValue = HeaderLabelTokenValue | IntervalLabelTokenValue;
Cooldown = "Cooldown",
} export const isHeaderLabelTokenValue = (
value: string
): value is HeaderLabelTokenValue => {
return ["Name", "Author", "Description"].includes(value);
};
export const isIntervalLabelTokenValue = (
value: string
): value is IntervalLabelTokenValue => {
return ["Warmup", "Rest", "Interval", "Cooldown"].includes(value);
};
export const isLabelTokenValue = (value: string): value is LabelTokenValue => {
return isHeaderLabelTokenValue(value) || isIntervalLabelTokenValue(value);
};
export type LabelToken = { export type LabelToken = {
type: "label"; type: "label";
value: LabelTokenValue; value: LabelTokenValue;
@ -53,15 +66,15 @@ const tokenizeValueParam = (text: string): Token => {
const tokenizeParams = (type: LabelTokenValue, text: string): Token[] => { const tokenizeParams = (type: LabelTokenValue, text: string): Token[] => {
switch (type) { switch (type) {
case LabelTokenValue.Name: case "Name":
case LabelTokenValue.Author: case "Author":
case LabelTokenValue.Description: { case "Description": {
return [{ type: "text", value: text }]; return [{ type: "text", value: text }];
} }
case LabelTokenValue.Warmup: case "Warmup":
case LabelTokenValue.Rest: case "Rest":
case LabelTokenValue.Interval: case "Interval":
case LabelTokenValue.Cooldown: case "Cooldown":
return text.split(" ").map(tokenizeValueParam); return text.split(" ").map(tokenizeValueParam);
} }
}; };
@ -71,7 +84,7 @@ const tokenizeRule = (line: string): Token[] => {
if (!matches) { if (!matches) {
return [{ type: "text", value: line.trim() }]; return [{ type: "text", value: line.trim() }];
} }
if (!Object.keys(LabelTokenValue).includes(matches[1])) { if (!isLabelTokenValue(matches[1])) {
return [{ type: "text", value: line.trim() }]; return [{ type: "text", value: line.trim() }];
} }