builtin_match_signatures #1
@ -1,4 +1,8 @@
|
|||||||
import { type DenotableFunctionSignature, Environment } from '.';
|
import {
|
||||||
|
type DenotableFunctionSignature,
|
||||||
|
Environment,
|
||||||
|
type Denotable,
|
||||||
|
} from '.';
|
||||||
|
|
||||||
const addUnaryIntegerOperationsTo = (env: Environment) => {
|
const addUnaryIntegerOperationsTo = (env: Environment) => {
|
||||||
const unaryIntegerOperationSignatures: DenotableFunctionSignature[] = [
|
const unaryIntegerOperationSignatures: DenotableFunctionSignature[] = [
|
||||||
@ -14,7 +18,10 @@ const addUnaryIntegerOperationsTo = (env: Environment) => {
|
|||||||
]) {
|
]) {
|
||||||
env.set(name, {
|
env.set(name, {
|
||||||
type: 'function',
|
type: 'function',
|
||||||
value: { signatures: unaryIntegerOperationSignatures, body: fn },
|
value: {
|
||||||
|
signatures: unaryIntegerOperationSignatures,
|
||||||
|
body: ({ value }: Denotable) => fn(value as number),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +52,11 @@ const addBinaryIntegerOperationsTo = (env: Environment) => {
|
|||||||
]) {
|
]) {
|
||||||
env.set(name, {
|
env.set(name, {
|
||||||
type: 'function',
|
type: 'function',
|
||||||
value: { signatures: binaryIntegerOperationSignatures, body: fn },
|
value: {
|
||||||
|
signatures: binaryIntegerOperationSignatures,
|
||||||
|
body: ({ value: a }: Denotable, { value: b }: Denotable) =>
|
||||||
|
fn(a as number, b as number),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,19 +87,21 @@ const addBinaryArithmeticOperationsTo = (env: Environment) => {
|
|||||||
]) {
|
]) {
|
||||||
env.set(name, {
|
env.set(name, {
|
||||||
type: 'function',
|
type: 'function',
|
||||||
value: { signatures: binaryArithmeticSignatures, body: fn },
|
value: {
|
||||||
|
signatures: binaryArithmeticSignatures,
|
||||||
|
body: ({ value: a }: Denotable, { value: b }: Denotable) =>
|
||||||
|
fn(a as number, b as number),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return env;
|
return env;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const environmentWithBuiltins = () => {
|
export const putBuiltinsOnEnvironemtn = (env: Environment) => {
|
||||||
const environment = new Environment();
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
addBinaryArithmeticOperationsTo,
|
addBinaryArithmeticOperationsTo,
|
||||||
addBinaryIntegerOperationsTo,
|
addBinaryIntegerOperationsTo,
|
||||||
addUnaryIntegerOperationsTo,
|
addUnaryIntegerOperationsTo,
|
||||||
].reduce((acc, builtinsAdder) => builtinsAdder(acc), environment);
|
].reduce((acc, builtinsAdder) => builtinsAdder(acc), env);
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import type { Identifier } from '@/parser';
|
import type { Identifier } from '@/parser';
|
||||||
|
import { testingLogger } from '@t/logger';
|
||||||
|
|
||||||
export type UnionDenotableType =
|
export type UnionDenotableType =
|
||||||
| Array<DenotableType | DenotableFunctionSignature>
|
| Array<DenotableType | DenotableFunctionSignature>
|
||||||
| DenotableType
|
| DenotableType
|
||||||
| DenotableFunctionSignature;
|
| DenotableFunctionSignature
|
||||||
|
| Array<UnionDenotableType>;
|
||||||
|
|
||||||
export type DenotableFunctionSignature = {
|
export type DenotableFunctionSignature = {
|
||||||
arguments: Array<UnionDenotableType>;
|
arguments: Array<UnionDenotableType>;
|
||||||
return: UnionDenotableType;
|
return: DenotableType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DenotableFunction = {
|
export type DenotableFunction = {
|
||||||
@ -37,35 +39,55 @@ export type Denotable = {
|
|||||||
value: DenotableValue;
|
value: DenotableValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const functionSignaturesEqual = (
|
export const denotableTypesEquivalent = (
|
||||||
a: DenotableFunctionSignature,
|
a: UnionDenotableType,
|
||||||
b: DenotableFunctionSignature,
|
b: UnionDenotableType,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
if (a.arguments.length !== b.arguments.length) {
|
if (typeof a !== typeof b) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof a.return !== typeof b.return) {
|
if (Array.isArray(a) && Array.isArray(b)) {
|
||||||
return false;
|
if (a.length !== b.length) return false;
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (!denotableTypesEquivalent(a[i], b[i])) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof a.return === 'object' &&
|
typeof a === 'object' &&
|
||||||
typeof b.return === 'object' &&
|
typeof b === 'object' &&
|
||||||
'return' in a.return &&
|
'arguments' in a &&
|
||||||
'return' in b.return
|
'arguments' in b
|
||||||
) {
|
) {
|
||||||
return functionSignaturesEqual(a.return, b.return);
|
if (a.arguments.length !== b.arguments.length) return false;
|
||||||
|
if (!denotableTypesEquivalent(a.return, b.return)) return false;
|
||||||
|
for (let i = 0; i < a.arguments.length; i++) {
|
||||||
|
if (!denotableTypesEquivalent(a.arguments[i], b.arguments[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (a === b) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const matchSignature = (
|
export const matchSignature = (
|
||||||
args: Array<DenotableFunction | DenotableType>,
|
args: Array<UnionDenotableType>,
|
||||||
signatures: Array<DenotableFunctionSignature>,
|
signatures: Array<DenotableFunctionSignature>,
|
||||||
): DenotableFunctionSignature | undefined => {
|
): DenotableFunctionSignature | undefined => {
|
||||||
return signatures.find(signature => {
|
return signatures.find(signature => {
|
||||||
if (args.length !== signature.arguments.length) {
|
if (args.length !== signature.arguments.length) return false;
|
||||||
return false;
|
|
||||||
}
|
return args.every((arg, i) => {
|
||||||
|
const argSignature = signature.arguments[i];
|
||||||
|
if (Array.isArray(argSignature)) {
|
||||||
|
return argSignature.some(a => denotableTypesEquivalent(a, arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
return denotableTypesEquivalent(arg, signature.arguments[i]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
import { UnknownSymbolError, InvalidType } from '@/utils';
|
import { UnknownSymbolError, InvalidType, type TracingLogger } from '@/utils';
|
||||||
import type {
|
import { matchSignature, type Denotable } from '.';
|
||||||
Denotable,
|
|
||||||
DenotableFunction,
|
|
||||||
DenotableFunctionSignature,
|
|
||||||
DenotableType,
|
|
||||||
DenotableValue,
|
|
||||||
} from '.';
|
|
||||||
|
|
||||||
export class Environment {
|
export class Environment {
|
||||||
private scope: Map<string, Denotable>;
|
private scope: Map<string, Denotable>;
|
||||||
private parent: Environment | null;
|
private parent: Environment | null;
|
||||||
|
private logger: TracingLogger;
|
||||||
|
|
||||||
constructor(parent: Environment | null = null) {
|
constructor(logger: TracingLogger, parent: Environment | null = null) {
|
||||||
|
this.logger = logger;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.scope = new Map();
|
this.scope = new Map();
|
||||||
}
|
}
|
||||||
@ -22,10 +18,12 @@ export class Environment {
|
|||||||
|
|
||||||
public get(name: string): Denotable {
|
public get(name: string): Denotable {
|
||||||
if (this.scope.has(name)) {
|
if (this.scope.has(name)) {
|
||||||
|
this.logger.debug(`Found Name=(${name}) in current scope`);
|
||||||
return this.scope.get(name)!;
|
return this.scope.get(name)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.parent) {
|
if (this.parent) {
|
||||||
|
this.logger.debug(`Looking for Name=(${name}) in parent scope`);
|
||||||
return this.parent.get(name);
|
return this.parent.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,18 +32,21 @@ export class Environment {
|
|||||||
|
|
||||||
public has(name: string): boolean {
|
public has(name: string): boolean {
|
||||||
if (this.scope.has(name)) {
|
if (this.scope.has(name)) {
|
||||||
|
this.logger.debug(`Found Name=(${name}) in current scope`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.parent) {
|
if (this.parent) {
|
||||||
|
this.logger.debug(`Found Name=(${name}) in current scope`);
|
||||||
return this.parent.has(name);
|
return this.parent.has(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.debug(`Name=(${name}) not found in any scope`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public createChild(): Environment {
|
public createChild(): Environment {
|
||||||
return new Environment(this);
|
return new Environment(this.logger.createChild('Env'), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public apply(name: string, args: Denotable[]): Denotable {
|
public apply(name: string, args: Denotable[]): Denotable {
|
||||||
@ -54,6 +55,29 @@ export class Environment {
|
|||||||
throw new InvalidType(name + ' is not a valid function');
|
throw new InvalidType(name + ' is not a valid function');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { type: 'real', value: 0 };
|
const argTypes = args.map(arg => {
|
||||||
|
const { type, value } = arg;
|
||||||
|
const isFunction =
|
||||||
|
type === 'function' &&
|
||||||
|
typeof value === 'object' &&
|
||||||
|
value &&
|
||||||
|
'signatures' in value;
|
||||||
|
if (isFunction) {
|
||||||
|
return value.signatures;
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
});
|
||||||
|
|
||||||
|
const appliedSignature = matchSignature(argTypes, fn.value.signatures);
|
||||||
|
if (!appliedSignature) {
|
||||||
|
throw new InvalidType(`No matching signature for ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug(
|
||||||
|
`Applying Function=(${name}) with Args=(${JSON.stringify(args)}) with Signature=(${JSON.stringify(appliedSignature)})`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const value = fn.value.body.apply(this, args);
|
||||||
|
return { type: appliedSignature.return, value };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
NotImplementedError,
|
NotImplementedError,
|
||||||
type TracingLogger,
|
type TracingLogger,
|
||||||
} from '@/utils';
|
} from '@/utils';
|
||||||
|
import { putBuiltinsOnEnvironemtn } from './builtins';
|
||||||
|
|
||||||
const evaluateValue = (
|
const evaluateValue = (
|
||||||
value: Value,
|
value: Value,
|
||||||
@ -49,7 +50,7 @@ const evaluatePrimitiveOperation = (
|
|||||||
evaluateValue(operand, env, logger.createChild('evaluteValue')),
|
evaluateValue(operand, env, logger.createChild('evaluteValue')),
|
||||||
);
|
);
|
||||||
|
|
||||||
let result: Denotable = { type: 'null', value: null };
|
const result = env.apply(opr, operandValues);
|
||||||
const continuationEnvironment = env.createChild();
|
const continuationEnvironment = env.createChild();
|
||||||
|
|
||||||
// return the result of the last continuation
|
// return the result of the last continuation
|
||||||
@ -105,13 +106,15 @@ export const evaluate = async (
|
|||||||
ast: Program,
|
ast: Program,
|
||||||
logger: TracingLogger,
|
logger: TracingLogger,
|
||||||
): Promise<Denotable> => {
|
): Promise<Denotable> => {
|
||||||
const environment = new Environment();
|
const globalEnvironment = putBuiltinsOnEnvironemtn(
|
||||||
|
new Environment(logger.createChild('Root')),
|
||||||
|
);
|
||||||
|
|
||||||
return ast.reduce((_, continuation, i) => {
|
return ast.reduce((_, continuation, i) => {
|
||||||
const exprLogger = logger.createChild(`statement[${i}]`);
|
const exprLogger = logger.createChild(`statement[${i}]`);
|
||||||
return evaluteContinuationExpression(
|
return evaluteContinuationExpression(
|
||||||
continuation as ContinuationExpression,
|
continuation as ContinuationExpression,
|
||||||
environment,
|
globalEnvironment,
|
||||||
exprLogger,
|
exprLogger,
|
||||||
);
|
);
|
||||||
}, null);
|
}, null);
|
||||||
|
@ -4,10 +4,59 @@ import { peggyParse } from '@/parser';
|
|||||||
import {
|
import {
|
||||||
evaluate,
|
evaluate,
|
||||||
type DenotableFunctionSignature,
|
type DenotableFunctionSignature,
|
||||||
|
denotableTypesEquivalent,
|
||||||
matchSignature,
|
matchSignature,
|
||||||
} from '@/interpreter';
|
} from '@/interpreter';
|
||||||
import { testingLogger } from './logger';
|
import { testingLogger } from './logger';
|
||||||
|
|
||||||
|
test('simple denotable types are equivalent', () => {
|
||||||
|
expect(denotableTypesEquivalent('int', 'int')).toBe(true);
|
||||||
|
expect(denotableTypesEquivalent('int', 'real')).toBe(false);
|
||||||
|
expect(denotableTypesEquivalent('int', 'null')).toBe(false);
|
||||||
|
expect(denotableTypesEquivalent('null', 'null')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('union data types are equivalent', () => {
|
||||||
|
expect(denotableTypesEquivalent(['int', 'real'], ['int', 'real'])).toBe(true);
|
||||||
|
expect(denotableTypesEquivalent('int', ['int', 'real'])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('function data types are equivalent', () => {
|
||||||
|
expect(
|
||||||
|
denotableTypesEquivalent(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
arguments: ['int', 'real'],
|
||||||
|
return: 'int',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
arguments: ['int', 'real'],
|
||||||
|
return: 'int',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
denotableTypesEquivalent(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
arguments: ['int', 'real'],
|
||||||
|
return: 'real',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
arguments: ['int', 'real'],
|
||||||
|
return: 'int',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
test('matches simple signatures', async () => {
|
test('matches simple signatures', async () => {
|
||||||
const simpleSignature: DenotableFunctionSignature[] = [
|
const simpleSignature: DenotableFunctionSignature[] = [
|
||||||
{
|
{
|
||||||
@ -39,3 +88,73 @@ test('finds first match', async () => {
|
|||||||
simpleSignature[1],
|
simpleSignature[1],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('finds first match with a function signature', async () => {
|
||||||
|
const testSignature: DenotableFunctionSignature = {
|
||||||
|
arguments: ['int', 'real'],
|
||||||
|
return: 'int',
|
||||||
|
};
|
||||||
|
|
||||||
|
const simpleSignature: DenotableFunctionSignature[] = [
|
||||||
|
{
|
||||||
|
arguments: ['int', 'int'],
|
||||||
|
return: 'int',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arguments: [[testSignature, 'real'], 'int'],
|
||||||
|
return: 'function',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(matchSignature(['int', 'int'], simpleSignature)).toEqual(
|
||||||
|
simpleSignature[0],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(matchSignature(['real', 'int'], simpleSignature)).toEqual(
|
||||||
|
simpleSignature[1],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(matchSignature([testSignature, 'int'], simpleSignature)).toEqual(
|
||||||
|
simpleSignature[1],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('finds first match with a function with many signatures', async () => {
|
||||||
|
const testSignature: DenotableFunctionSignature[] = [
|
||||||
|
{
|
||||||
|
arguments: ['int', 'real'],
|
||||||
|
return: 'int',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arguments: ['int', 'int'],
|
||||||
|
return: 'int',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const simpleSignature: DenotableFunctionSignature[] = [
|
||||||
|
{
|
||||||
|
arguments: ['int', 'int'],
|
||||||
|
return: 'int',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arguments: [[testSignature, 'real'], 'int'],
|
||||||
|
return: 'function',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(matchSignature(['int', 'int'], simpleSignature)).toEqual(
|
||||||
|
simpleSignature[0],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(matchSignature(['real', 'int'], simpleSignature)).toEqual(
|
||||||
|
simpleSignature[1],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(matchSignature([testSignature, 'int'], simpleSignature)).toEqual(
|
||||||
|
simpleSignature[1],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
matchSignature([[testSignature[0]], 'int'], simpleSignature),
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user