Skip to content

Commit

Permalink
remove recursive evaluator
Browse files Browse the repository at this point in the history
  • Loading branch information
jitsedesmet committed Oct 23, 2023
1 parent c987901 commit 7c2179e
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 283 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { isSubTypeOf } from '../util/TypeHandling';
import type { ExpressionEvaluator } from './ExpressionEvaluator';

/**
* Abstract aggregator actor. This is the base class for all aggregator actors.
* Only the wildcard count aggregator significantly differs from the others.
* This is the base class for all aggregators.
* NOTE: The wildcard count aggregator significantly differs from the others and overloads parts of this class.
*/
export abstract class AggregateEvaluator {
protected readonly evaluator: IExpressionEvaluator;
Expand Down Expand Up @@ -100,11 +100,11 @@ export abstract class AggregateEvaluator {
throw new Error(`Term with value ${term.value} has type ${term.termType} and is not a numeric literal`);
} else if (
!isSubTypeOf(term.datatype.value, TypeAlias.SPARQL_NUMERIC, (<ExpressionEvaluator> this.evaluator)
.context.superTypeProvider)
.superTypeProvider)
) {
throw new Error(`Term datatype ${term.datatype.value} with value ${term.value} has type ${term.termType} and is not a numeric literal`);
}
return <E.NumericLiteral> new TermTransformer((
<ExpressionEvaluator> this.evaluator).context.superTypeProvider).transformLiteral(term);
<ExpressionEvaluator> this.evaluator).superTypeProvider).transformLiteral(term);
}
}
129 changes: 83 additions & 46 deletions packages/expression-evaluator/lib/evaluators/ExpressionEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,22 @@ import type { IActionContext, IExpressionEvaluator } from '@comunica/types';
import type * as RDF from '@rdfjs/types';
import { LRUCache } from 'lru-cache';
import type { Algebra as Alg } from 'sparqlalgebrajs';
import type * as E from '../expressions';
import * as E from '../expressions';
import { regularFunctions } from '../functions';
import { expressionToVar } from '../functions/Helpers';
import type { FunctionArgumentsCache } from '../functions/OverloadTree';
import { AlgebraTransformer } from '../transformers/AlgebraTransformer';
import { TermTransformer } from '../transformers/TermTransformer';
import * as C from '../util/Consts';
import type { ITimeZoneRepresentation } from '../util/DateTimeHelpers';
import { extractTimeZone } from '../util/DateTimeHelpers';
import * as Err from '../util/Errors';
import type { SuperTypeCallback, TypeCache } from '../util/TypeHandling';
import type { ICompleteEEContext } from './evaluatorHelpers/AsyncRecursiveEvaluator';
import { AsyncRecursiveEvaluator } from './evaluatorHelpers/AsyncRecursiveEvaluator';
import type { SuperTypeCallback, TypeCache, ISuperTypeProvider } from '../util/TypeHandling';

export type AsyncExtensionFunction = (args: RDF.Term[]) => Promise<RDF.Term>;
export type AsyncExtensionFunctionCreator = (functionNamedNode: RDF.NamedNode) => AsyncExtensionFunction | undefined;

// TODO: make this fields of the EE
export interface IAsyncEvaluatorContext {
exists: (expression: Alg.ExistenceExpression, mapping: RDF.Bindings) => Promise<boolean>;
aggregate?: (expression: Alg.AggregateExpression) => Promise<RDF.Term>;
bnode: (input?: string) => Promise<RDF.BlankNode>;
extensionFunctionCreator?: AsyncExtensionFunctionCreator;
now?: Date;
Expand All @@ -35,57 +31,69 @@ export interface IAsyncEvaluatorContext {

export class ExpressionEvaluator implements IExpressionEvaluator {
private readonly transformer: AlgebraTransformer;
public readonly evaluator: AsyncRecursiveEvaluator;
public readonly context: ICompleteEEContext;
public readonly expr: E.Expression;

public static completeContext(context: IAsyncEvaluatorContext): ICompleteEEContext {
const now = context.now || new Date(Date.now());
return {
now,
baseIRI: context.baseIRI || undefined,
functionArgumentsCache: context.functionArgumentsCache || {},
superTypeProvider: {
cache: context.typeCache || new LRUCache({ max: 1_000 }),
discoverer: context.getSuperType || (() => 'term'),
},
extensionFunctionCreator: context.extensionFunctionCreator,
exists: context.exists,
aggregate: context.aggregate,
bnode: context.bnode,
defaultTimeZone: context.defaultTimeZone || extractTimeZone(now),
actionContext: context.actionContext,
};
}
private readonly subEvaluators: Record<string,
(expr: E.Expression, mapping: RDF.Bindings) => Promise<E.Term> | E.Term> =
{
[E.ExpressionType.Term]: this.term.bind(this),
[E.ExpressionType.Variable]: this.variable.bind(this),
[E.ExpressionType.Operator]: this.evalFunction.bind(this),
[E.ExpressionType.SpecialOperator]: this.evalFunction.bind(this),
[E.ExpressionType.Named]: this.evalFunction.bind(this),
[E.ExpressionType.Existence]: this.evalExistence.bind(this),
[E.ExpressionType.Aggregate]: this.evalAggregate.bind(this),
[E.ExpressionType.AsyncExtension]: this.evalFunction.bind(this),
};

// Context items
public readonly exists: (expression: Alg.ExistenceExpression, mapping: RDF.Bindings) => Promise<boolean>;
public readonly bnode: (input?: string) => Promise<RDF.BlankNode>;
public readonly extensionFunctionCreator?: AsyncExtensionFunctionCreator;
public readonly now: Date;
public readonly baseIRI?: string;
public readonly functionArgumentsCache: FunctionArgumentsCache;
public readonly superTypeProvider: ISuperTypeProvider;
public readonly defaultTimeZone: ITimeZoneRepresentation;
public readonly actionContext: IActionContext;

public constructor(public algExpr: Alg.Expression, context: IAsyncEvaluatorContext) {
this.now = context.now || new Date(Date.now());
this.baseIRI = context.baseIRI || undefined;
this.functionArgumentsCache = context.functionArgumentsCache || {};
this.superTypeProvider = {
cache: context.typeCache || new LRUCache({ max: 1_000 }),
discoverer: context.getSuperType || (() => 'term'),
};
// eslint-disable-next-line unicorn/no-useless-undefined
const creator = context.extensionFunctionCreator || (() => undefined);
this.context = ExpressionEvaluator.completeContext(context);
this.extensionFunctionCreator = context.extensionFunctionCreator || (() => undefined);
this.exists = context.exists;
this.bnode = context.bnode;
this.defaultTimeZone = context.defaultTimeZone || extractTimeZone(this.now);
this.actionContext = context.actionContext;

this.transformer = new AlgebraTransformer({
creator,
...this.context,
}, this);

this.evaluator = new AsyncRecursiveEvaluator(this.context, this, this.transformer);
this.transformer = new AlgebraTransformer(this.superTypeProvider);
this.expr = this.transformer.transformAlgebra(algExpr);
}

public async evaluateAsInternal(expr: E.Expression, mapping: RDF.Bindings): Promise<E.Term> {
const evaluator = this.subEvaluators[expr.expressionType];
if (!evaluator) {
throw new Err.InvalidExpressionType(expr);
}
return evaluator.bind(this)(expr, mapping);
}

public async evaluate(mapping: RDF.Bindings): Promise<RDF.Term> {
const result = await this.evaluator.evaluate(this.expr, mapping);
const result = await this.evaluateAsInternal(this.expr, mapping);
return result.toRDF();
}

public async evaluateAsEBV(mapping: RDF.Bindings): Promise<boolean> {
const result = await this.evaluator.evaluate(this.expr, mapping);
const result = await this.evaluateAsInternal(this.expr, mapping);
return result.coerceEBV();
}

public async evaluateAsInternal(mapping: RDF.Bindings): Promise<E.TermExpression> {
return await this.evaluator.evaluate(this.expr, mapping);
}

// ==================================================================================================================
// ==================================================================================================================
// ==================================================================================================================
Expand Down Expand Up @@ -142,7 +150,7 @@ export class ExpressionEvaluator implements IExpressionEvaluator {

// Handle literals
if (termA.termType === 'Literal') {
return this.orderLiteralTypes(termA, <RDF.Literal>termB, this.context);
return this.orderLiteralTypes(termA, <RDF.Literal>termB);
}

// Handle all other types
Expand All @@ -152,13 +160,12 @@ export class ExpressionEvaluator implements IExpressionEvaluator {
return this.comparePrimitives(termA.value, termB.value);
}

private orderLiteralTypes(litA: RDF.Literal, litB: RDF.Literal, context: ICompleteEEContext): -1 | 0 | 1 {
private orderLiteralTypes(litA: RDF.Literal, litB: RDF.Literal): -1 | 0 | 1 {
const isGreater = regularFunctions[C.RegularOperator.GT];
const isEqual = regularFunctions[C.RegularOperator.EQUAL];

const termTransformer = new TermTransformer(context.superTypeProvider);
const myLitA = termTransformer.transformLiteral(litA);
const myLitB = termTransformer.transformLiteral(litB);
const myLitA = this.transformer.transformLiteral(litA);
const myLitB = this.transformer.transformLiteral(litB);

try {
if ((<E.BooleanLiteral> isEqual.applyOnTerms([ myLitA, myLitB ], this)).typedValue) {
Expand Down Expand Up @@ -192,4 +199,34 @@ export class ExpressionEvaluator implements IExpressionEvaluator {
Quad: 4,
DefaultGraph: 5,
};

// ======================================= Sub evaluators ==========================================================
private term(expr: E.Term, _: RDF.Bindings): E.Term {
return expr;
}

private variable(expr: E.Variable, mapping: RDF.Bindings): E.Term {
const term = mapping.get(expressionToVar(expr));
if (!term) {
throw new Err.UnboundVariableError(expr.name, mapping);
}
return this.transformer.transformRDFTermUnsafe(term);
}

private async evalFunction(expr: E.Operator | E.SpecialOperator | E.Named | E.AsyncExtension, mapping: RDF.Bindings):
Promise<E.Term> {
return expr.apply({
args: expr.args,
mapping,
exprEval: this,
});
}

private async evalExistence(expr: E.Existence, mapping: RDF.Bindings): Promise<E.Term> {
return new E.BooleanLiteral(await this.exists(expr.expression, mapping));
}

private evalAggregate(): never {
throw new Err.NoAggregator();
}
}

This file was deleted.

14 changes: 6 additions & 8 deletions packages/expression-evaluator/lib/functions/Core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { OverloadTree } from './OverloadTree';

export abstract class SparqlFunction implements IFunctionExpression {
protected abstract readonly arity: number | number[];
public abstract apply(evalContext: IEvalContext): Promise<E.TermExpression>;
public abstract apply: (evalContext: IEvalContext) => Promise<E.TermExpression>;

public checkArity(args: E.Expression[]): boolean {
if (Array.isArray(this.arity)) {
Expand Down Expand Up @@ -50,17 +50,15 @@ export abstract class TermSparqlFunction<O extends C.RegularOperator | C.NamedOp

public applyOnTerms(args: E.TermExpression[], exprEval: ExpressionEvaluator): E.TermExpression {
const concreteFunction =
this.overloads.search(args, exprEval.context.superTypeProvider, exprEval.context.functionArgumentsCache) ||
this.overloads.search(args, exprEval.superTypeProvider, exprEval.functionArgumentsCache) ||
this.handleInvalidTypes(args);
return concreteFunction(exprEval)(args);
}

public async apply({ args, exprEval, mapping }: IEvalContext): Promise<E.TermExpression> {
return this.applyOnTerms(
await Promise.all(args.map(arg => exprEval.evaluator.evaluate(arg, mapping))),
exprEval,
);
}
public apply = async({ args, exprEval, mapping }: IEvalContext): Promise<E.TermExpression> => this.applyOnTerms(
await Promise.all(args.map(arg => exprEval.evaluateAsInternal(arg, mapping))),
exprEval,
);

protected handleInvalidTypes(args: E.TermExpression[]): never {
throw new Err.InvalidArgumentTypes(args, this.operator);
Expand Down
Loading

0 comments on commit 7c2179e

Please sign in to comment.