Parsing of interval parameters
This commit is contained in:
parent
2a362e6362
commit
04467b944f
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -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 => {
|
||||||
|
|
|
||||||
|
|
@ -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() }];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue