builtin_match_signatures #1

Merged
simponic merged 4 commits from builtin_match_signatures into main 2024-02-28 14:59:29 -05:00
5 changed files with 222 additions and 41 deletions
Showing only changes of commit 68745f5b94 - Show all commits

View File

@ -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);
}; };

View File

@ -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]);
});
}); });
}; };

View File

@ -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 };
} }
} }

View File

@ -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);

View File

@ -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();
});