Refactor DocumentRange to allow multiline ranges

This commit is contained in:
Nilay Majorwar
2022-02-16 21:20:44 +05:30
parent 0a3bac3517
commit a05731e91d
9 changed files with 120 additions and 92 deletions

View File

@ -30,8 +30,9 @@ export const parseProgram = (code: string): T.ChefProgram => {
// Location of code's last char, used for errors
const lastCharPosition = stack[0]?.line.length - 1 || 0;
const lastCharRange: DocumentRange = {
line: stack.length - 1,
charRange: { start: lastCharPosition, end: lastCharPosition + 1 },
startLine: stack.length - 1,
startCol: lastCharPosition,
endCol: lastCharPosition + 1,
};
// Exhaust any empty lines at the start of the program
@ -69,9 +70,11 @@ const parseTitle = (stack: CodeStack, lastCharRange: DocumentRange): string => {
const { line, row } = popCodeStack(stack, true);
if (line === null)
throw new ParseError("Expected recipe title", lastCharRange);
if (!line) throw new ParseError("Expected recipe title", { line: row });
if (!line) throw new ParseError("Expected recipe title", { startLine: row });
if (!line.endsWith("."))
throw new ParseError("Recipe title must end with period", { line: row });
throw new ParseError("Recipe title must end with period", {
startLine: row,
});
return line.slice(0, -1);
};
@ -86,7 +89,7 @@ const parseEmptyLine = (
): void => {
const { line, row } = popCodeStack(stack, true);
if (line === null) throw new ParseError("Expected blank line", lastCharRange);
if (line) throw new ParseError("Expected blank line", { line: row });
if (line) throw new ParseError("Expected blank line", { startLine: row });
};
/** Parse the stack for method instructions section */
@ -98,7 +101,7 @@ const parseRecipeComments = (stack: CodeStack): void => {
const parseIngredientsHeader = (stack: CodeStack): void => {
const { line, row } = popCodeStack(stack, true);
if (line !== "Ingredients.")
throw new ParseError("Expected ingredients header", { line: row });
throw new ParseError("Expected ingredients header", { startLine: row });
};
/** Parse the stack for ingredient definition lines */
@ -117,7 +120,7 @@ const parseCookingTime = (stack: CodeStack): void => {
const regex = /^Cooking time: \d+ (?:hours?|minutes?).$/;
const { line, row } = popCodeStack(stack, true);
if (!line!.match(regex))
throw new ParseError("Invalid cooking time statement", { line: row });
throw new ParseError("Invalid cooking time statement", { startLine: row });
};
/** Parse stack for oven setting statement. No data is returned. */
@ -126,14 +129,14 @@ const parseOvenSetting = (stack: CodeStack): void => {
/^Pre-heat oven to \d+ degrees Celsius(?: \(gas mark [\d/]+\))?.$/;
const { line, row } = popCodeStack(stack, true);
if (!line!.match(regex))
throw new ParseError("Invalid oven setting statement", { line: row });
throw new ParseError("Invalid oven setting statement", { startLine: row });
};
/** Parse the stack for the header of method section */
const parseMethodHeader = (stack: CodeStack): void => {
const { line, row } = popCodeStack(stack, true);
if (line !== "Method.")
throw new ParseError('Expected "Method."', { line: row });
throw new ParseError('Expected "Method."', { startLine: row });
};
/** Parse the stack for method instructions section */
@ -233,7 +236,7 @@ const serializeMethodOps = (stack: CodeStack): MethodSegment[] => {
for (let i = 0; i < periodIdxs.length - 1; ++i) {
const start = periodIdxs[i] + 1;
const end = periodIdxs[i + 1];
const range = { line: item.row, charRange: { start, end } };
const range = { startLine: item.row, startCol: start, endCol: end };
segments.push({
str: item.line.slice(start, end).trim(),
location: range,
@ -248,7 +251,8 @@ const serializeMethodOps = (stack: CodeStack): MethodSegment[] => {
const parseServesLine = (stack: CodeStack): T.ChefRecipeServes => {
const { line, row } = popCodeStack(stack, true);
const match = line!.match(/^Serves (\d+).$/);
if (!match) throw new ParseError("Malformed serves statement", { line: row });
if (!match)
throw new ParseError("Malformed serves statement", { startLine: row });
return { line: row, num: parseInt(match[1], 10) };
};

View File

@ -96,7 +96,7 @@ export default class ChefLanguageEngine implements LanguageEngine<T.ChefRS> {
const currFrame = this.getCurrentFrame();
if (currFrame.pc === currFrame.recipe.method.length) {
// Next step is "Serves" statement
nextStepLocation = { line: currFrame.recipe.serves!.line + 1 };
nextStepLocation = { startLine: currFrame.recipe.serves!.line };
} else {
// Next step is a regular method instruction
const nextOp = currFrame.recipe.method[currFrame.pc];

View File

@ -30,11 +30,11 @@ describe("Parsing entire programs", () => {
expect(method.length).toBe(14);
expect(method.slice(0, 12).every((m) => m.op.code === "PUSH")).toBe(true);
expect(method[12].op.code).toBe("LIQ-BOWL");
expect(method[12].location.line).toBe(17);
expect([403, 404]).toContain(method[12].location.charRange?.start);
expect([439, 440]).toContain(method[12].location.charRange?.end);
expect(method[12].location.startLine).toBe(17);
expect([403, 404]).toContain(method[12].location.startCol);
expect([439, 440]).toContain(method[12].location.endCol);
expect(method[13].op.code).toBe("COPY");
expect(method[13].location.line).toBe(17);
expect(method[13].location.startLine).toBe(17);
});
test("Fibonacci Du Fromage", () => {
@ -54,13 +54,13 @@ describe("Parsing entire programs", () => {
const mainMethod = program.main.method;
expect(mainMethod.length).toBe(19);
expect(mainMethod[0].op.code).toBe("STDIN");
expect(mainMethod[0].location.line).toBe(10);
expect(mainMethod[0].location.charRange?.start).toBe(0);
expect(mainMethod[0].location.charRange?.end).toBe(30);
expect(mainMethod[0].location.startLine).toBe(10);
expect(mainMethod[0].location.startCol).toBe(0);
expect(mainMethod[0].location.endCol).toBe(30);
expect(mainMethod[18].op.code).toBe("COPY");
expect(mainMethod[18].location.line).toBe(28);
expect(mainMethod[18].location.charRange?.start).toBe(0);
expect(mainMethod[18].location.charRange?.end).toBe(57);
expect(mainMethod[18].location.startLine).toBe(28);
expect(mainMethod[18].location.startCol).toBe(0);
expect(mainMethod[18].location.endCol).toBe(57);
// Check loop jump addresses in method
const mainOpener1 = mainMethod[8].op as LoopOpenOp;
@ -85,13 +85,13 @@ describe("Parsing entire programs", () => {
const auxMethod = program.auxes["salt and pepper"].method;
expect(auxMethod.length).toBe(5);
expect(auxMethod[0].op.code).toBe("POP");
expect(auxMethod[0].location.line).toBe(39);
expect(auxMethod[0].location.charRange?.start).toBe(0);
expect(auxMethod[0].location.charRange?.end).toBe(26);
expect(auxMethod[0].location.startLine).toBe(39);
expect(auxMethod[0].location.startCol).toBe(0);
expect(auxMethod[0].location.endCol).toBe(26);
expect(auxMethod[4].op.code).toBe("ADD");
expect(auxMethod[4].location.line).toBe(43);
expect(auxMethod[4].location.charRange?.start).toBe(0);
expect(auxMethod[4].location.charRange?.end).toBe(10);
expect(auxMethod[4].location.startLine).toBe(43);
expect(auxMethod[4].location.startCol).toBe(0);
expect(auxMethod[4].location.endCol).toBe(10);
});
test("Hello World Cake with Chocolate Sauce", () => {
@ -112,13 +112,13 @@ describe("Parsing entire programs", () => {
const mainMethod = program.main.method;
expect(mainMethod.length).toBe(15);
expect(mainMethod[0].op.code).toBe("PUSH");
expect(mainMethod[0].location.line).toBe(27);
expect(mainMethod[0].location.charRange?.start).toBe(0);
expect(mainMethod[0].location.charRange?.end).toBe(40);
expect(mainMethod[0].location.startLine).toBe(27);
expect(mainMethod[0].location.startCol).toBe(0);
expect(mainMethod[0].location.endCol).toBe(40);
expect(mainMethod[14].op.code).toBe("FNCALL");
expect(mainMethod[14].location.line).toBe(41);
expect(mainMethod[14].location.charRange?.start).toBe(0);
expect(mainMethod[14].location.charRange?.end).toBe(26);
expect(mainMethod[14].location.startLine).toBe(41);
expect(mainMethod[14].location.startCol).toBe(0);
expect(mainMethod[14].location.endCol).toBe(26);
// Check loop jump addresses in method
const mainOpener = mainMethod[12].op as LoopOpenOp;
@ -142,13 +142,13 @@ describe("Parsing entire programs", () => {
const auxMethod = program.auxes["chocolate sauce"].method;
expect(auxMethod.length).toBe(13);
expect(auxMethod[0].op.code).toBe("CLEAR");
expect(auxMethod[0].location.line).toBe(53);
expect(auxMethod[0].location.charRange?.start).toBe(0);
expect(auxMethod[0].location.charRange?.end).toBe(21);
expect(auxMethod[0].location.startLine).toBe(53);
expect(auxMethod[0].location.startCol).toBe(0);
expect(auxMethod[0].location.endCol).toBe(21);
expect(auxMethod[12].op.code).toBe("END");
expect(auxMethod[12].location.line).toBe(65);
expect(auxMethod[12].location.charRange?.start).toBe(0);
expect(auxMethod[12].location.charRange?.end).toBe(22);
expect(auxMethod[12].location.startLine).toBe(65);
expect(auxMethod[12].location.startCol).toBe(0);
expect(auxMethod[12].location.endCol).toBe(22);
// Check loop jump addresses in method
const auxOpener = auxMethod[4].op as LoopOpenOp;