Skip to content

Commit

Permalink
Errors (#380)
Browse files Browse the repository at this point in the history
* prepare Error classes

* use new errors everywhere

* update cli

* refactor getOutput func

* forward Invariant errors as warnings when recoverable

* keepDistinctWarningsOnly when all results are errors too
  • Loading branch information
MichalMarsalek authored Mar 22, 2024
1 parent c427ffe commit 9b09c6f
Show file tree
Hide file tree
Showing 27 changed files with 422 additions and 326 deletions.
6 changes: 3 additions & 3 deletions src/IR/assignments.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PolygolfError } from "../common/errors";
import { UserError } from "../common/errors";
import {
type BaseNode,
id,
Expand Down Expand Up @@ -138,9 +138,9 @@ export function varDeclarationWithAssignment<T extends SomeAssignment>(
(!isAssignment(assignment) &&
assignment.variables.some((y) => !isIdent()(y)))
) {
throw new PolygolfError(
throw new UserError(
"VarDeclarationWithAssignment needs assignments to variables.",
assignment.source,
assignment,
);
}
return {
Expand Down
3 changes: 2 additions & 1 deletion src/IR/collections.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { InvariantError } from "../common/errors";
import {
type BaseNode,
type Node,
Expand Down Expand Up @@ -62,7 +63,7 @@ export function isEqualToLiteral(x: Node, literal: Literal): boolean {
isEqualToLiteral(x.value, literal.value)
);
}
throw new Error("Unknown literal kind.");
throw new InvariantError(`Unknown literal kind. ${literal.kind}`);
}

export function array(value: readonly Node[]): Array {
Expand Down
29 changes: 17 additions & 12 deletions src/IR/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { InvariantError, UserError } from "../common/errors";
import { getType } from "../common/getType";
import { type Spine } from "../common/Spine";
import {
Expand Down Expand Up @@ -358,7 +359,7 @@ export function intersection(a: Type, b: Type): Type {
} else if (a.kind === b.kind) {
return a;
}
throw new Error("Empty intersection.");
throw new UserError("Empty intersection.", undefined);
}

export function union(a: Type, b: Type): Type {
Expand Down Expand Up @@ -397,12 +398,16 @@ export function union(a: Type, b: Type): Type {
} else if (a.kind === b.kind) {
return a;
}
throw new Error(`Cannot model union of ${toString(a)} and ${toString(b)}.`);
throw new UserError(
`Cannot model union of ${toString(a)} and ${toString(b)}.`,
undefined,
);
} catch (e) {
throw new Error(
throw new UserError(
`Cannot model union of ${toString(a)} and ${toString(b)}.\n${
e instanceof Error ? e.message : ""
}`,
undefined,
);
}
}
Expand Down Expand Up @@ -461,7 +466,7 @@ export function neg(a: IntegerBound): IntegerBound {
export function add(a: IntegerBound, b: IntegerBound): IntegerBound {
if (leq(b, a)) [a, b] = [b, a];
if (a === "-oo" && b === "oo")
throw new Error("Indeterminate result of -oo + oo.");
throw new InvariantError("Indeterminate result of -oo + oo.");
if (a === "-oo") return a;
if (b === "oo") return b;
return (a as bigint) + (b as bigint);
Expand All @@ -472,7 +477,7 @@ export function sub(a: IntegerBound, b: IntegerBound): IntegerBound {
export function mul(a: IntegerBound, b: IntegerBound): IntegerBound {
if (leq(b, a)) [a, b] = [b, a];
if ((a === "-oo" && b === 0n) || (b === "oo" && a === 0n))
throw new Error("Indeterminate result of 0 * oo.");
throw new InvariantError("Indeterminate result of 0 * oo.");
if (a === "-oo") return lt(b, 0n) ? "oo" : "-oo";
if (b === "oo") return lt(a, 0n) ? "-oo" : "oo";
return (a as bigint) * (b as bigint);
Expand All @@ -482,9 +487,9 @@ export function floorDiv(a: IntegerBound, b: IntegerBound): IntegerBound {
return mul(res, b) !== a && lt(a, 0n) !== lt(b, 0n) ? sub(res, 1n) : res;
}
export function truncDiv(a: IntegerBound, b: IntegerBound): IntegerBound {
if (b === 0n) throw new Error("Indeterminate result of x / 0.");
if (b === 0n) throw new InvariantError("Indeterminate result of x / 0.");
if (!isFiniteBound(a) && !isFiniteBound(b))
throw new Error("Indeterminate result of +-oo / +-oo.");
throw new InvariantError("Indeterminate result of +-oo / +-oo.");
if (!isFiniteBound(a)) {
if (lt(a, 0n) === lt(b, 0n)) return "oo";
else return "-oo";
Expand Down Expand Up @@ -531,7 +536,7 @@ export function defaultValue(a: Type): Node {
if (lt(0n, a.low)) return int(a.low as bigint);
return int(0);
}
throw new Error(`Unsupported default value for type ${toString(a)}`);
throw new InvariantError(`Unsupported default value for type ${toString(a)}`);
}

export function instantiateGenerics(
Expand All @@ -542,7 +547,7 @@ export function instantiateGenerics(
case "Array": {
const lengthType = instantiate(type.length);
if (lengthType.kind !== "TypeArg" && !isArrayIndexType(lengthType))
throw new Error(
throw new InvariantError(
"Array type's second argument must be a constant integer type.",
);
return arrayType(instantiate(type.member), lengthType);
Expand All @@ -555,7 +560,7 @@ export function instantiateGenerics(
case "KeyValue": {
const keyType = instantiate(type.key);
if (keyType.kind !== "integer" && keyType.kind !== "text")
throw new Error(
throw new InvariantError(
"KeyValue type's first argument must be an integer or text type.",
);
return keyValueType(keyType, instantiate(type.value));
Expand All @@ -567,7 +572,7 @@ export function instantiateGenerics(
keyType.kind !== "text" &&
keyType.kind !== "TypeArg"
)
throw new Error(
throw new InvariantError(
"Table type's first argument must be an integer or text type.",
);
return tableType(keyType, instantiate(type.value));
Expand Down Expand Up @@ -626,5 +631,5 @@ export function getLiteralOfType(type: Type, nonEmpty = false): Node {
: [],
);
}
throw new Error(`There's no literal of type '${type.kind}'.`);
throw new InvariantError(`There's no literal of type '${type.kind}'.`);
}
154 changes: 82 additions & 72 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import yargs from "yargs";
import fs from "fs";
import path from "path";
import compile, { debugEmit } from "./common/compile";
import { PolygolfError } from "./common/errors";
import {
InvariantError,
NotImplementedError,
UserError,
} from "./common/errors";
import languages, { findLang } from "./languages/languages";
import { EmitError } from "./common/emit";

const languageChoices = [
...new Set(languages.flatMap((x) => [x.name.toLowerCase(), x.extension])),
Expand Down Expand Up @@ -49,85 +52,92 @@ const options = yargs()
.parseSync(process.argv.slice(2));

if (options.all === true && options.output !== undefined) {
throw new Error(
console.log(
"All variants options is only allowed when the output file is not specified.",
);
}
} else {
const langs =
options.lang === undefined ? languages : [findLang(options.lang)!];
let input = options.input;
if (!fs.existsSync(input)) input += ".polygolf";
const code = fs.readFileSync(input, { encoding: "utf-8" });
const printingMultipleLangs =
langs.length > 1 && options.output === undefined;
for (const result of compile(
code,
{
objective: options.chars === true ? "chars" : "bytes",
getAllVariants: options.all === true,
},
...langs,
)) {
if (printingMultipleLangs) console.log(result.language);
if (typeof result.result === "string") {
if (options.output !== undefined) {
fs.mkdirSync(path.dirname(options.output), { recursive: true });
fs.writeFileSync(
options.output +
(langs.length > 1 || !options.output.includes(".")
? "." + findLang(result.language)!.extension
: ""),
result.result,
);
} else {
console.log(result.result);
}
if (result.warnings.length > 0) {
console.log("Warnings:");
console.log(result.warnings.map((x) => x.message).join("\n"));
}
if (options.debug === true) {
console.log("History:");
console.log(
result.history.map(([c, name]) => `${c} ${name}`).join("\n"),
);

const langs =
options.lang === undefined ? languages : [findLang(options.lang)!];
let input = options.input;
if (!fs.existsSync(input)) input += ".polygolf";
const code = fs.readFileSync(input, { encoding: "utf-8" });
const printingMultipleLangs = langs.length > 1 && options.output === undefined;
for (const result of compile(
code,
{
objective: options.chars === true ? "chars" : "bytes",
getAllVariants: options.all === true,
},
...langs,
)) {
if (printingMultipleLangs) console.log(result.language);
if (typeof result.result === "string") {
if (options.output !== undefined) {
fs.mkdirSync(path.dirname(options.output), { recursive: true });
fs.writeFileSync(
options.output +
(langs.length > 1 || !options.output.includes(".")
? "." + findLang(result.language)!.extension
: ""),
result.result,
);
if (result.errors.length > 0) {
console.log("Errors:");
console.log(
result.errors
.map(
(e) =>
e.message +
(e instanceof NotImplementedError
? "\n" + debugEmit(e.expr)
: ""),
)
.join("\n"),
);
}
}
} else {
console.log(result.result);
if (!printingMultipleLangs && langs.length > 1)
console.log(result.language);
handleError(result.result);
}
if (result.warnings.length > 0) {
console.log("Warnings:");
console.log(result.warnings.map((x) => x.message).join("\n"));
}
if (options.debug === true) {
console.log("History:");
console.log(result.history.map(([c, name]) => `${c} ${name}`).join("\n"));
console.log("");
}

if (result.errors.length > 0) {
console.log("Errors:");
function handleError(e: unknown) {
if (e instanceof UserError) {
console.log(e.message);
if (e.source != null) {
const startLine = e.source.line === 0 ? 0 : e.source.line - 2;
console.log(
result.errors
.map(
(e) =>
e.message +
(e instanceof EmitError ? "\n" + debugEmit(e.expr) : ""),
)
.join("\n"),
code
.split(/\r?\n/)
.slice(startLine, e.source.line)
.map((x, i) => `${startLine + i + 1}`.padStart(3, " ") + " " + x)
.join("\n") +
"\n" +
" ".repeat(e.source.column + 3) +
"^",
);
}
} else if (e instanceof InvariantError) {
console.log(e);
} else {
throw e;
}
} else {
if (!printingMultipleLangs && langs.length > 1)
console.log(result.language);
handleError(result.result);
}
console.log("");
}

function handleError(e: unknown) {
if (e instanceof PolygolfError) {
console.log(e.message);
if (e.source != null) {
const startLine = e.source.line === 0 ? 0 : e.source.line - 2;
console.log(
code
.split(/\r?\n/)
.slice(startLine, e.source.line)
.map((x, i) => `${startLine + i + 1}`.padStart(3, " ") + " " + x)
.join("\n") +
"\n" +
" ".repeat(e.source.column + 3) +
"^",
);
}
} else {
throw e;
}
}
17 changes: 11 additions & 6 deletions src/common/compile.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { isOp, isText, text } from "@/IR";
import { type Language, type Plugin, search, required } from "./Language";
import { EmitError } from "./emit";
import compile from "./compile";
import { compilationOptionsFromKeywords } from "@/markdown-tests";
import { PolygolfError } from "./errors";
import { NotImplementedError, UserError } from "./errors";

const textLang: Language = {
name: "text",
Expand All @@ -14,18 +13,24 @@ const textLang: Language = {
.map((x) => {
if (isOp()(x) && x.args.length > 0 && isText()(x.args[0]!)) {
if (x.args[0].value.endsWith("X")) {
context.addWarning(new PolygolfError("global warning"), true);
context.addWarning(
new PolygolfError("local warning that should not be visible"),
new UserError("global warning", undefined),
true,
);
context.addWarning(
new UserError(
"local warning that should not be visible",
undefined,
),
false,
);
}
context.addWarning(
new PolygolfError("local warning that should be visible"),
new UserError("local warning that should be visible", undefined),
false,
);
return [x.args[0].value];
} else throw new EmitError(x);
} else throw new NotImplementedError(x);
})
.join("");
},
Expand Down
Loading

0 comments on commit 9b09c6f

Please sign in to comment.