Skip to content
This repository has been archived by the owner on Sep 19, 2023. It is now read-only.

Commit

Permalink
Refactor OverloadMap to OverloadTree
Browse files Browse the repository at this point in the history
This is as preparation for supporting more types and function overloads.
  • Loading branch information
jitsedesmet committed Aug 11, 2021
1 parent bc24b95 commit c4959d6
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 51 deletions.
34 changes: 5 additions & 29 deletions lib/functions/Core.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
// eslint-disable-next-line no-redeclare
import type { Map } from 'immutable';
import { List } from 'immutable';

import type * as E from '../expressions';
import type * as C from '../util/Consts';
import * as Err from '../util/Errors';
import type { OverloadTree } from './OverloadTree';

type Term = E.TermExpression;

// ----------------------------------------------------------------------------
// Overloaded Functions
// ----------------------------------------------------------------------------

// Maps argument types on their specific implementation.
export type OverloadMap = Map<List<ArgumentType>, E.SimpleApplication>;

// Function and operator arguments are 'flattened' in the SPARQL spec.
// If the argument is a literal, the datatype often also matters.
export type ArgumentType = 'term' | E.TermType | C.Type;

export interface IOverloadedDefinition {
arity: number | number[];
overloads: OverloadMap;
overloads: OverloadTree;
}

export abstract class BaseFunction<Operator> {
public arity: number | number[];
private readonly overloads: OverloadMap;
private readonly overloads: OverloadTree;

protected constructor(public operator: Operator, definition: IOverloadedDefinition) {
this.arity = definition.arity;
Expand Down Expand Up @@ -56,29 +50,11 @@ export abstract class BaseFunction<Operator> {
* for every concrete type when the function is generic over termtypes or
* terms.
*/
private monomorph(args: Term[]): E.SimpleApplication {
return false ||
// TODO: Maybe use non primitive types first?
this.overloads.get(Typer.asConcreteTypes(args)) ||
this.overloads.get(Typer.asTermTypes(args)) ||
this.overloads.get(Typer.asGenericTerms(args));
private monomorph(args: Term[]): E.SimpleApplication | undefined {
return this.overloads.search(args);
}
}

const Typer = {
asConcreteTypes(args: Term[]): List<ArgumentType> {
return List(args.map((term: any) => term.type || term.termType));
},

asTermTypes(args: Term[]): List<E.TermType> {
return List(args.map((term: E.TermExpression) => term.termType));
},

asGenericTerms(args: Term[]): List<'term'> {
return <List<'term'>> List(Array.from({ length: args.length }).fill('term'));
},
};

// Regular Functions ----------------------------------------------------------

/**
Expand Down
50 changes: 33 additions & 17 deletions lib/functions/Helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,45 @@
* These helpers provide a (albeit inflexible) DSL for writing function
* definitions for the SPARQL functions.
*/

// eslint-disable-next-line no-redeclare
import { List, Map } from 'immutable';

import * as E from '../expressions';
import type { SimpleApplication } from '../expressions';
import * as C from '../util/Consts';
import { TypeURL } from '../util/Consts';
import * as Err from '../util/Errors';

import type { ArgumentType, OverloadMap } from './Core';
import type { ArgumentType } from './Core';
import { promote } from './Core';
import { OverloadTree } from './OverloadTree';

type Term = E.TermExpression;

export function declare(): Builder {
return new Builder();
}

function arraysEqual<T>(fst: T[], snd: T[]): boolean {
if (fst === snd) {
return true;
}
if (fst === null || snd === null) {
return false;
}
if (fst.length !== snd.length) {
return false;
}
for (let i = 0; i < fst.length; ++i) {
if (fst[i] !== snd[i]) {
return false;
}
}
return true;
}

export class Builder {
private implementations: Impl[] = [];

public collect(): OverloadMap {
return map(this.implementations);
public collect(): OverloadTree {
return transformToNode(this.implementations);
}

public add(impl: Impl): Builder {
Expand All @@ -34,16 +49,14 @@ export class Builder {
}

public set(argTypes: ArgumentType[], func: E.SimpleApplication): Builder {
const types = List(argTypes);
return this.add(new Impl({ types, func }));
return this.add(new Impl({ types: argTypes, func }));
}

public copy({ from, to }: { from: ArgumentType[]; to: ArgumentType[] }): Builder {
const last = this.implementations.length - 1;
const _from = List(from);
for (let i = last; i >= 0; i--) {
const impl = this.implementations[i];
if (impl.types.equals(_from)) {
if (arraysEqual(impl.types, from)) {
return this.set(to, impl.func);
}
}
Expand Down Expand Up @@ -283,20 +296,20 @@ export class Builder {
*/

export interface IImplType {
types: List<ArgumentType>;
types: ArgumentType[];
func: E.SimpleApplication;
}

const implDefaults: IImplType = {
types: List(),
types: [],
func() {
const msg = 'Implementation not set yet declared as implemented';
throw new Err.UnexpectedError(msg);
},
};

export class Impl implements IImplType {
public types: List<ArgumentType>;
public types: ArgumentType[];
public func: E.SimpleApplication;

public constructor(params?: IImplType) {
Expand All @@ -309,9 +322,12 @@ export class Impl implements IImplType {
}
}

export function map(implementations: Impl[]): OverloadMap {
const typeImplPair = implementations.map(i => [ i.types, i.func ]);
return Map<List<ArgumentType>, E.SimpleApplication>(typeImplPair);
export function transformToNode(implementations: Impl[]): OverloadTree {
const res: OverloadTree = new OverloadTree();
for (const implementation of implementations) {
res.addOverload(implementation.types, implementation.func);
}
return res;
}

// ----------------------------------------------------------------------------
Expand Down
4 changes: 2 additions & 2 deletions lib/functions/NamedFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
parseXSDInteger,
} from '../util/Parsing';

import type { OverloadMap } from './Core';
import { bool, dateTime, declare, number, string } from './Helpers';
import type { OverloadTree } from './OverloadTree';

type Term = E.TermExpression;

Expand Down Expand Up @@ -176,5 +176,5 @@ export const namedDefinitions: Record<C.NamedOperator, IDefinition> = {

export interface IDefinition {
arity: number | number[];
overloads: OverloadMap;
overloads: OverloadTree;
}
88 changes: 88 additions & 0 deletions lib/functions/OverloadTree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import type * as E from '../expressions';
import type { ArgumentType } from './Core';

export type SearchStack = OverloadTree[];

/**
* Maps argument types on their specific implementation in a tree like structure.
*/
export class OverloadTree {
private implementation?: E.SimpleApplication | undefined;
private readonly subTrees: Record<string, OverloadTree>;

public constructor() {
this.implementation = undefined;
this.subTrees = {};
}

/**
* Searches in a depth first way for the best matching overload. considering this a the tree's root.
* @param args
*/
public search(args: E.TermExpression[]): E.SimpleApplication | undefined {
// SearchStack is a stack of all node's that need to be checked for implementation.
// It provides an easy way to keep order in our search.
const searchStack: { node: OverloadTree; index: number }[] = [];
const startIndex = 0;
if (args.length === 0) {
return this.implementation;
}
// GetSubTreeWithArg return a SearchStack containing the node's that should be contacted next.
// We also log the index since there is no other way to remember this index.
// the provided stack should be pushed on top of our search stack since it also has it's order.
searchStack.push(...this.getSubTreeWithArg(args[startIndex]).map(node =>
({ node, index: startIndex + 1 })));
while (searchStack.length > 0) {
const { index, node } = <{ node: OverloadTree; index: number }>searchStack.pop();
if (index === args.length) {
return node.implementation;
}
searchStack.push(...node.getSubTreeWithArg(args[index]).map(item =>
({ node: item, index: index + 1 })));
}
return this.implementation;
}

/**
* Adds an overload to the tree structure considering this as the tree's root.
* @param argumentTypes a list of ArgumentTypes that would need to be provided in the same order to
* get the implementation.
* @param func the implementation for this overload.
*/
public addOverload(argumentTypes: ArgumentType[], func: E.SimpleApplication): void {
this._addOverload([ ...argumentTypes ], func);
}

private _addOverload(argumentTypes: ArgumentType[], func: E.SimpleApplication): void {
const argumentType = argumentTypes.shift();
if (!argumentType) {
this.implementation = func;
return;
}
if (!this.subTrees[argumentType]) {
this.subTrees[argumentType] = new OverloadTree();
}
this.subTrees[argumentType]._addOverload(argumentTypes, func);
}

/**
* @param arg term to try and match to possible overloads of this node.
* @returns SearchStack a stack with top element the next node that should be asked for implementation or overload.
*/
private getSubTreeWithArg(arg: E.TermExpression): SearchStack {
const match: OverloadTree = this.subTrees[(<any>arg).type];
const res: SearchStack = [];
// These types refer to Type exported by lib/util/Consts.ts
if (this.subTrees.term) {
res.push(this.subTrees.term);
}
// TermTypes are defined in E.TermType.
if (this.subTrees[arg.termType]) {
res.push(this.subTrees[arg.termType]);
}
if (match) {
res.push(match);
}
return res;
}
}
4 changes: 2 additions & 2 deletions lib/functions/RegularFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import * as C from '../util/Consts';
import { TypeURL } from '../util/Consts';
import * as Err from '../util/Errors';
import * as P from '../util/Parsing';
import type { OverloadMap } from './Core';
import { bool, declare, langString, number, string } from './Helpers';
import type { OverloadTree } from './OverloadTree';
import * as X from './XPathFunctions';

const DF = new DataFactory();
Expand Down Expand Up @@ -789,5 +789,5 @@ export const definitions: Record<C.RegularOperator, IDefinition> = {

export interface IDefinition {
arity: number | number[];
overloads: OverloadMap;
overloads: OverloadTree;
}
2 changes: 2 additions & 0 deletions lib/functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ export const namedFunctions: NamedFunctionMap = <NamedFunctionMap> Object.fromEn
Object.entries(namedDefinitions).map(([ key, val ]) =>
[ key, new NamedFunction(<C.NamedOperator>key, val) ]),
);
export { SearchStack } from './OverloadTree';
export { OverloadTree } from './OverloadTree';
2 changes: 1 addition & 1 deletion lib/util/Consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export const commonTerms: Record<string, RDF.Term> = {
false: DF.literal('false', DF.namedNode(TypeURL.XSD_BOOLEAN)),
};

// TODO: Rename to primitive
// TODO: Rename to primitive - Should remove this and replace with aliases.
// https://www.w3.org/TR/xmlschema-2/#built-in-primitive-datatypes
export type Type =
'string'
Expand Down

0 comments on commit c4959d6

Please sign in to comment.