Skip to content
This repository has been archived by the owner on Jul 3, 2024. It is now read-only.

[#8] Add proper exception handling #9

Merged
merged 2 commits into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ dist/
lib/
storybook-static/
docs/
coverage/
6 changes: 6 additions & 0 deletions src/exceptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class InferenceError extends Error {
constructor(message: string) {
super(message);
this.name = 'InferenceError';
}
}
23 changes: 13 additions & 10 deletions src/helper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2023 Adam Jones
//
// SPDX-License-Identifier: MIT
import {InferenceError} from './exceptions';
import {
Context,
ExplainPath,
Expand Down Expand Up @@ -56,7 +57,7 @@ function apply(
return {...value, sigma: apply(s, value.sigma)};
}
((_: never): never => {
throw new Error('Unknown argument passed to substitution');
throw new InferenceError('Unknown argument passed to substitution');
})(value);
}

Expand Down Expand Up @@ -96,7 +97,7 @@ export const instantiate = (
}

((_: never): never => {
throw new Error('Unknown type passed to instantiate');
throw new InferenceError('Unknown type passed to instantiate');
})(type);
};

Expand Down Expand Up @@ -133,7 +134,7 @@ const freeVars = (value: PolyType | Context): string[] => {
}

((_: never): never => {
throw new Error('Unknown argument passed to substitution');
throw new InferenceError('Unknown argument passed to substitution');
})(value);
};

Expand All @@ -152,7 +153,7 @@ export const unify = (

if (type1.type === 'ty-var') {
if (contains(type2, type1))
throw new Error(`Infinite type detected: ${type1} occurs in ${type2}`);
throw new InferenceError(`Infinite type detected: ${type1} occurs in ${type2}`);

if (type2.type === 'ty-var') {
// var with other name -> explain
Expand All @@ -164,7 +165,7 @@ export const unify = (
type1.explain = [type2, {type: 'ExplainInstan', path: path1, expr}];
} else {
((_: never): never => {
throw new Error('Unknown argument passed to unify');
throw new InferenceError('Unknown argument passed to unify');
})(type2);
}
return makeSubstitution({
Expand All @@ -178,11 +179,13 @@ export const unify = (

if (type1.C !== type2.C) {
const msg = formatUnificationError(type1, type2, expr, path1, path2);
throw new Error(msg);
throw new InferenceError(msg);
}

if (type1.mus.length !== type2.mus.length) {
throw new Error(`Could not unify types (different argument lengths): ${type1} and ${type2}`);
throw new InferenceError(
`Could not unify types (different argument lengths): ${type1} and ${type2}`
);
}

let s: Substitution = makeSubstitution({});
Expand All @@ -208,7 +211,7 @@ const formatUnificationError = (
}, but it is a ${type2.C}`;
return msg;
}
throw new Error(`Unexpected expression type ${expr}`);
throw new InferenceError(`Unexpected expression type ${expr}`);
};

export const reprExpression = (expr: Expression): string => {
Expand All @@ -220,7 +223,7 @@ export const reprExpression = (expr: Expression): string => {
if (expr.type === 'let')
return `"${expr.x}" = ${reprExpression(expr.e1)} in ${reprExpression(expr.e2)}`;
((_: never): never => {
throw new Error(`Unexpected expression type ${expr}`);
throw new InferenceError(`Unexpected expression type ${expr}`);
})(expr);
};

Expand All @@ -246,6 +249,6 @@ const contains = (value: MonoType, type2: TypeVariable): boolean => {
}

((_: never): never => {
throw new Error('Unknown argument passed to substitution');
throw new InferenceError('Unknown argument passed to substitution');
})(value);
};
22 changes: 13 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import type {Substitution} from './helper';
import type {Context} from './models';
import type {JSONValue} from './parser';
import {defaultContext, parseContext, parseJsonLogicExpression} from './parser';
import {W} from './w';

export {defaultContext};

interface InferenceResult {
resultType: string;
intermediateVariables: Substitution['raw'];
}

/**
* @beta
* @param jsonLogic - JsonLogic expression/rule
Expand All @@ -16,14 +22,12 @@ export const infer = (
jsonLogic: JSONValue,
data: JSONValue,
context: Context = defaultContext
): string => {
): InferenceResult => {
const typeenv = parseContext(data, context);
try {
const [subsitution, t] = W(...parseJsonLogicExpression(jsonLogic, typeenv));
const type: string = 'C' in t ? t.C : JSON.stringify([t.a, Object.keys(subsitution.raw)]);
const ctx: string = JSON.stringify(subsitution.raw);
return `result type: ${type}\n(intermediate) variables:${ctx}`;
} catch (error) {
return error.toString();
}
const [subsitution, t] = W(...parseJsonLogicExpression(jsonLogic, typeenv));
const type: string = 'C' in t ? t.C : JSON.stringify([t.a, Object.keys(subsitution.raw)]);
return {
resultType: type,
intermediateVariables: subsitution.raw,
};
};
5 changes: 3 additions & 2 deletions src/m.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2023 Adam Jones
//
// SPDX-License-Identifier: MIT
import {InferenceError} from './exceptions';
import {Substitution, generalise, instantiate, newTypeVar, unify} from './helper';
import {Context, Expression, MonoType, makeContext} from './models';

Expand All @@ -9,7 +10,7 @@ export const M = (typEnv: Context, expr: Expression, type: MonoType): Substituti
console.log(`Variable ${expr.x}: expected to have type ${JSON.stringify(type)}`);

const value = typEnv[expr.x];
if (value === undefined) throw new Error(`Undefined variable: ${expr.x}`);
if (value === undefined) throw new InferenceError(`Undefined variable: ${expr.x}`);
return unify(type, instantiate(value), expr);
}

Expand Down Expand Up @@ -69,5 +70,5 @@ export const M = (typEnv: Context, expr: Expression, type: MonoType): Substituti
return s2(s1);
}

throw new Error('Unknown expression type');
throw new InferenceError('Unknown expression type');
};
3 changes: 2 additions & 1 deletion src/parser.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {InferenceError} from './exceptions';
import {
ApplicationExpression,
Context,
Expand Down Expand Up @@ -221,7 +222,7 @@ const applyRight = (expressions: Expression[]): ApplicationExpression => {
// [...rest, e1, e2] = expressions; isn't legal TS
const length = expressions.length;
if (length < 2) {
throw new Error('applyRight requires at least two expressions');
throw new InferenceError('applyRight requires at least two expressions');
}
const rest = expressions.slice(0, length - 2);
const [e1, e2] = expressions.slice(length - 2);
Expand Down
5 changes: 3 additions & 2 deletions src/w.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
// Copyright (c) 2023 Maykin
//
// SPDX-License-Identifier: MIT
import {InferenceError} from './exceptions';
import {Substitution, generalise, instantiate, makeSubstitution, newTypeVar, unify} from './helper';
import {Context, Expression, MonoType, PolyType, makeContext} from './models';

export const W = (typEnv: Context, expr: Expression): [Substitution, MonoType] => {
if (expr.type === 'var') {
const value = typEnv[expr.x];
if (value === undefined) throw new Error(`Undefined variable: ${expr.x}`);
if (value === undefined) throw new InferenceError(`Undefined variable: ${expr.x}`);
return [makeSubstitution({}), instantiate(value)];
}

Expand Down Expand Up @@ -78,5 +79,5 @@ export const W = (typEnv: Context, expr: Expression): [Substitution, MonoType] =
);
return [s2(s1), t2];
}
throw new Error('Unknown expression type');
throw new InferenceError('Unknown expression type');
};