Split LabelToken to: HeaderToken & IntervalToken

This commit is contained in:
Rene Saarsoo 2020-09-22 21:51:58 +03:00
parent d998caf91d
commit 955c74be42
3 changed files with 51 additions and 46 deletions

View File

@ -1,4 +1,4 @@
import { IntervalLabelTokenValue } from "./parser/tokenizer"; import { IntervalType } from "./parser/tokenizer";
import { Seconds } from "./types"; import { Seconds } from "./types";
export type Workout = { export type Workout = {
@ -9,7 +9,7 @@ export type Workout = {
}; };
export type Interval = { export type Interval = {
type: IntervalLabelTokenValue; type: IntervalType;
duration: Seconds; duration: Seconds;
intensity: { from: number; to: number }; intensity: { from: number; to: number };
cadence?: number; cadence?: number;

View File

@ -1,7 +1,7 @@
import { Interval, Workout, Comment } from "../ast"; import { Interval, Workout, Comment } from "../ast";
import { Seconds } from "../types"; import { Seconds } from "../types";
import { ParseError } from "./ParseError"; import { ParseError } from "./ParseError";
import { isIntervalLabelTokenValue, SourceLocation, Token } from "./tokenizer"; import { SourceLocation, Token } from "./tokenizer";
type Header = Partial<Omit<Workout, "intervals">>; type Header = Partial<Omit<Workout, "intervals">>;
@ -30,17 +30,17 @@ const parseHeader = (tokens: Token[]): [Header, Token[]] => {
if (token.type === "text" && token.value === "") { if (token.type === "text" && token.value === "") {
// Ignore empty lines before header // Ignore empty lines before header
tokens.shift(); tokens.shift();
} else if (token.type === "label" && token.value === "Name") { } else if (token.type === "header" && 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 (token.type === "label" && token.value === "Author") { } else if (token.type === "header" && token.value === "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 (token.type === "label" && token.value === "Description") { } else if (token.type === "header" && token.value === "Description") {
tokens.shift(); tokens.shift();
const [description, rest] = extractText(tokens); const [description, rest] = extractText(tokens);
header.description = description; header.description = description;
@ -125,7 +125,7 @@ const parseIntervals = (tokens: Token[]): Interval[] => {
while (tokens[0]) { while (tokens[0]) {
const token = tokens.shift() as Token; const token = tokens.shift() as Token;
if (token.type === "label" && isIntervalLabelTokenValue(token.value)) { if (token.type === "interval") {
const [{ duration, intensity, cadence, comments }, rest] = parseIntervalParams(tokens, token.loc); const [{ duration, intensity, cadence, comments }, rest] = parseIntervalParams(tokens, token.loc);
intervals.push({ intervals.push({
type: token.value, type: token.value,

View File

@ -1,18 +1,14 @@
import { ParseError } from "./ParseError"; import { ParseError } from "./ParseError";
export type HeaderLabelTokenValue = "Name" | "Author" | "Description"; export type HeaderType = "Name" | "Author" | "Description";
export type IntervalLabelTokenValue = "Warmup" | "Rest" | "Interval" | "Cooldown"; export type IntervalType = "Warmup" | "Rest" | "Interval" | "Cooldown";
export type LabelTokenValue = HeaderLabelTokenValue | IntervalLabelTokenValue;
export const isHeaderLabelTokenValue = (value: string): value is HeaderLabelTokenValue => { const isHeaderType = (value: string): value is HeaderType => {
return ["Name", "Author", "Description"].includes(value); return ["Name", "Author", "Description"].includes(value);
}; };
export const isIntervalLabelTokenValue = (value: string): value is IntervalLabelTokenValue => { const isIntervalType = (value: string): value is IntervalType => {
return ["Warmup", "Rest", "Interval", "Cooldown"].includes(value); return ["Warmup", "Rest", "Interval", "Cooldown"].includes(value);
}; };
export const isLabelTokenValue = (value: string): value is LabelTokenValue => {
return isHeaderLabelTokenValue(value) || isIntervalLabelTokenValue(value);
};
// 0-based row and column indexes. First line is 0th. // 0-based row and column indexes. First line is 0th.
export type SourceLocation = { export type SourceLocation = {
@ -20,9 +16,14 @@ export type SourceLocation = {
col: number; col: number;
}; };
export type LabelToken = { export type HeaderToken = {
type: "label"; type: "header";
value: LabelTokenValue; value: HeaderType;
loc: SourceLocation;
};
export type IntervalToken = {
type: "interval";
value: IntervalType;
loc: SourceLocation; loc: SourceLocation;
}; };
export type TextToken = { export type TextToken = {
@ -45,7 +46,7 @@ export type CommentStartToken = {
value?: undefined; value?: undefined;
loc: SourceLocation; loc: SourceLocation;
}; };
export type Token = LabelToken | TextToken | NumberToken | IntensityRangeToken | CommentStartToken; export type Token = HeaderToken | IntervalToken | TextToken | NumberToken | IntensityRangeToken | CommentStartToken;
const toInteger = (str: string): number => { const toInteger = (str: string): number => {
return parseInt(str.replace(/[^0-9]/, ""), 10); return parseInt(str.replace(/[^0-9]/, ""), 10);
@ -87,21 +88,6 @@ const tokenizeParams = (text: string, loc: SourceLocation): Token[] => {
}); });
}; };
const tokenizeLabelTokenParams = (type: LabelTokenValue, text: string, loc: SourceLocation): Token[] => {
switch (type) {
case "Name":
case "Author":
case "Description": {
return [{ type: "text", value: text, loc }];
}
case "Warmup":
case "Rest":
case "Interval":
case "Cooldown":
return tokenizeParams(text, loc);
}
};
const tokenizeComment = (line: string, row: number): Token[] | undefined => { const tokenizeComment = (line: string, row: number): Token[] | undefined => {
const [, commentHead, offset, commentText] = line.match(/^(\s*#\s*)([0-9:]+)(.*?)$/) || []; const [, commentHead, offset, commentText] = line.match(/^(\s*#\s*)([0-9:]+)(.*?)$/) || [];
if (!commentHead) { if (!commentHead) {
@ -122,19 +108,38 @@ const tokenizeLabelToken = (line: string, row: number): Token[] | undefined => {
if (!label) { if (!label) {
return undefined; return undefined;
} }
if (!isLabelTokenValue(label)) {
throw new ParseError(`Unknown label "${label}:"`, { row, col: 0 }); if (isHeaderType(label)) {
const token: HeaderToken = {
type: "header",
value: label,
loc: { row, col: 0 },
};
const param: TextToken = {
type: "text",
value: paramString,
loc: {
row,
col: label.length + separator.length,
},
};
return [token, param];
} }
const labelToken: LabelToken = {
type: "label", if (isIntervalType(label)) {
value: label as LabelTokenValue, const token: IntervalToken = {
loc: { row, col: 0 }, type: "interval",
}; value: label,
const params = tokenizeLabelTokenParams(labelToken.value, paramString, { loc: { row, col: 0 },
row, };
col: label.length + separator.length, const params = tokenizeParams(paramString, {
}); row,
return [labelToken, ...params]; col: label.length + separator.length,
});
return [token, ...params];
}
throw new ParseError(`Unknown label "${label}:"`, { row, col: 0 });
}; };
const tokenizeRule = (line: string, row: number): Token[] => { const tokenizeRule = (line: string, row: number): Token[] => {