zwiftout/src/parser/parser.ts

117 lines
3.1 KiB
TypeScript

import { Interval, Workout } from "../ast";
import { isIntervalLabelTokenValue, Token } from "./tokenizer";
type Header = Partial<Omit<Workout, "intervals">>;
const extractText = (tokens: Token[]): [string, Token[]] => {
let text;
while (tokens[0] && tokens[0].type === "text") {
if (text === undefined) {
text = tokens[0].value;
} else {
text += "\n" + tokens[0].value;
}
tokens.shift();
}
return [text ? text.trim() : "", tokens];
};
const parseHeader = (tokens: Token[]): [Header, Token[]] => {
const header: Header = {};
while (tokens[0]) {
const token = tokens[0];
if (token.type === "label" && token.value === "Name") {
tokens.shift();
const [name, rest] = extractText(tokens);
header.name = name;
tokens = rest;
} else if (token.type === "label" && token.value === "Author") {
tokens.shift();
const [author, rest] = extractText(tokens);
header.author = author;
tokens = rest;
} else if (token.type === "label" && token.value === "Description") {
tokens.shift();
const [description, rest] = extractText(tokens);
header.description = description;
tokens = rest;
} else {
// End of header
break;
}
}
return [header, tokens];
};
type IntervalData = Omit<Interval, "type">;
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 === "intensity") {
data.intensity = { from: token.value, to: token.value };
tokens.shift();
} else if (token.type === "intensity-range") {
data.intensity = { from: token.value[0], to: token.value[1] };
tokens.shift();
} else {
break;
}
}
if (!("duration" in data)) {
throw new Error("Duration not specified");
}
if (!("intensity" in data)) {
throw new Error("Power not specified");
}
return [data as IntervalData, tokens];
};
const parseIntervals = (tokens: Token[]): Interval[] => {
const intervals: Interval[] = [];
while (tokens[0]) {
const token = tokens.shift() as Token;
if (token.type === "label" && isIntervalLabelTokenValue(token.value)) {
const [{ duration, intensity, cadence }, rest] = parseIntervalParams(
tokens
);
intervals.push({
type: token.value,
duration,
intensity,
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 parseTokens = (tokens: Token[]): Workout => {
const [header, intervalTokens] = parseHeader(tokens);
return {
name: header.name || "",
author: header.author || "",
description: header.description || "",
intervals: parseIntervals(intervalTokens),
};
};