Skip to content

Commit

Permalink
Implement switch expression
Browse files Browse the repository at this point in the history
  • Loading branch information
DrRataplan committed Nov 23, 2023
1 parent dd07d62 commit 2f7747a
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 27 deletions.
9 changes: 5 additions & 4 deletions src/expressions/dataTypes/Value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,11 @@ export const enum ValueType {
}

/**
* Handles the occurances in the XPath specs.
* Zero or one matches to '?';
* One or more matches to '+';
* Zero or more matches to '*'
* Handles the occurences in the XPath specs.
*
* * Zero or one matches to '?';
* * One or more matches to '+';
* * Zero or more matches to '*';
*
* @private
*/
Expand Down
131 changes: 131 additions & 0 deletions src/expressions/xquery/SwitchExpression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import ISequence from '../dataTypes/ISequence';
import sequenceFactory from '../dataTypes/sequenceFactory';
import { SequenceType } from '../dataTypes/Value';
import DynamicContext from '../DynamicContext';
import ExecutionParameters from '../ExecutionParameters';
import Expression, { RESULT_ORDERINGS } from '../Expression';
import sequenceDeepEqual, {
itemDeepEqual,
} from '../functions/builtInFunctions_sequences_deepEqual';
import PossiblyUpdatingExpression, { SequenceCallbacks } from '../PossiblyUpdatingExpression';
import Specificity from '../Specificity';
import StaticContext from '../StaticContext';
import atomizeSequence from '../util/atomizeSequence';
import { IterationHint } from '../util/iterators';
import { errXUST0001 } from '../xquery-update/XQueryUpdateFacilityErrors';
import { errXPTY0004 } from './XQueryErrors';

export type SwitchExpressionClause = {
caseClauseExpression: PossiblyUpdatingExpression;
tests: Expression[];
};

class SwitchExpression extends PossiblyUpdatingExpression {
private _amountOfCases: number;
private _argExpression: Expression;
private _testsByCase: Expression[][];

constructor(
argExpression: Expression,
caseClauses: SwitchExpressionClause[],
defaultExpression: PossiblyUpdatingExpression,
type: SequenceType,
) {
const specificity = new Specificity({});
super(
specificity,
[
argExpression,
...caseClauses.map((clause) => clause.caseClauseExpression),
defaultExpression,
].concat(...caseClauses.map((clause) => clause.tests.map((test) => test))),
{
canBeStaticallyEvaluated: false,
peer: false,
resultOrder: RESULT_ORDERINGS.UNSORTED,
subtree: false,
},
type,
);
this._argExpression = argExpression;
this._amountOfCases = caseClauses.length;
this._testsByCase = caseClauses.map((clause) => clause.tests);
}

public performFunctionalEvaluation(
dynamicContext: DynamicContext,
executionParameters: ExecutionParameters,
sequenceCallbacks: ((dynamicContext: DynamicContext) => ISequence)[],
) {
// Pick the argumentExpression.
const evaluatedExpression = atomizeSequence(
sequenceCallbacks[0](dynamicContext),
executionParameters,
);

// Map over all values the type test, and return the result.
return evaluatedExpression.switchCases({
multiple: () => {
throw errXPTY0004(
'The operand for a switch expression should result in zero or one item',
);
},
default: () => {
const singleValue = evaluatedExpression.first();
const isEmpty = !singleValue;

for (let i = 0; i < this._amountOfCases; i++) {
const results = this._testsByCase[i].map((x) =>
x.evaluateMaybeStatically(dynamicContext, executionParameters),
);

for (const result of results) {
const atomizedResult = atomizeSequence(result, executionParameters);
if (atomizedResult.isEmpty()) {
if (isEmpty) {
return sequenceCallbacks[i + 1](dynamicContext);
}
continue;
}

if (!atomizedResult.isSingleton()) {
throw errXPTY0004(
'The operand for a switch case should result in zero or one item',
);
}

if (isEmpty) {
continue;
}

const first = atomizedResult.first();

if (
itemDeepEqual(
dynamicContext,
executionParameters,
null,
singleValue,
first,
).next(IterationHint.NONE).value
) {
return sequenceCallbacks[i + 1](dynamicContext);
}
}
}

return sequenceCallbacks[this._amountOfCases + 1](dynamicContext);
},
});
}

public performStaticEvaluation(staticContext: StaticContext) {
super.performStaticEvaluation(staticContext);

if (this._argExpression.isUpdating) {
throw errXUST0001();
}
}
}

export default SwitchExpression;
2 changes: 1 addition & 1 deletion src/parsing/astHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ function getTextContent(ast: IAST): string {
*/
function getTypeDeclaration(ast: IAST): SequenceType {
const typeDeclarationAst = getFirstChild(ast, 'typeDeclaration');
if (!typeDeclarationAst || getFirstChild(typeDeclarationAst, 'voidSequenceType')) {
if (!typeDeclarationAst) {
return { type: ValueType.ITEM, mult: SequenceMultiplicity.ZERO_OR_MORE };
}

Expand Down
45 changes: 43 additions & 2 deletions src/parsing/compileAstToExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import ElementConstructor from '../expressions/xquery/ElementConstructor';
import PIConstructor from '../expressions/xquery/PIConstructor';
import TextConstructor from '../expressions/xquery/TextConstructor';
import TypeSwitchExpression from '../expressions/xquery/TypeSwitchExpression';
import SwitchExpression from '../expressions/xquery/SwitchExpression';
import astHelper, { IAST } from './astHelper';

const COMPILATION_OPTIONS = {
Expand Down Expand Up @@ -204,6 +205,8 @@ function compile(ast: IAST, compilationOptions: CompilationOptions): Expression

case 'typeswitchExpr':
return typeswitchExpr(ast, compilationOptions);
case 'switchExpr':
return switchExpr(ast, compilationOptions);

// XQuery node constructors
case 'elementConstructor':
Expand Down Expand Up @@ -1481,7 +1484,7 @@ function typeswitchExpr(ast: IAST, compilationOptions: CompilationOptions) {
const caseClause = astHelper.getChildren(ast, 'typeswitchExprCaseClause');

const caseClauseExpressions = caseClause.map((caseClauseExpression) => {
const sequenceTypesAstNodes: IAST[] =
const caseNodes: IAST[] =
astHelper.getChildren(caseClauseExpression, 'sequenceTypeUnion').length === 0
? [astHelper.getFirstChild(caseClauseExpression, 'sequenceType')]
: astHelper.getChildren(
Expand All @@ -1496,7 +1499,7 @@ function typeswitchExpr(ast: IAST, compilationOptions: CompilationOptions) {

return {
caseClauseExpression: resultExpression,
typeTests: sequenceTypesAstNodes.map((sequenceTypeAstNode: IAST) => {
typeTests: caseNodes.map((sequenceTypeAstNode: IAST) => {
const occurrenceIndicator = astHelper.getFirstChild(
sequenceTypeAstNode,
'occurrenceIndicator',
Expand All @@ -1522,6 +1525,44 @@ function typeswitchExpr(ast: IAST, compilationOptions: CompilationOptions) {
return new TypeSwitchExpression(argExpr, caseClauseExpressions, defaultExpression, type);
}

function switchExpr(ast: IAST, compilationOptions: CompilationOptions) {
if (!compilationOptions.allowXQuery) {
throw new Error('XPST0003: Use of XQuery functionality is not allowed in XPath context');
}

const type = astHelper.getAttribute(ast, 'type');

const argExpr = compile(
astHelper.getFirstChild(astHelper.getFirstChild(ast, 'argExpr'), '*'),
compilationOptions,
);

const caseClauses = astHelper.getChildren(ast, 'switchExprCaseClause');

const caseClauseExpressions = caseClauses.map((caseClauseExpression) => {
const caseNodes: IAST[] = astHelper.getChildren(caseClauseExpression, 'switchCaseExpr');

const resultExpression = compile(
astHelper.followPath(caseClauseExpression, ['resultExpr', '*']),
compilationOptions,
) as PossiblyUpdatingExpression;

return {
caseClauseExpression: resultExpression,
tests: caseNodes.map((caseNode) =>
compile(astHelper.getFirstChild(caseNode, '*'), compilationOptions),
),
};
});

const defaultExpression = compile(
astHelper.followPath(ast, ['switchExprDefaultClause', 'resultExpr', '*']),
compilationOptions,
) as PossiblyUpdatingExpression;

return new SwitchExpression(argExpr, caseClauseExpressions, defaultExpression, type);
}

export default function (xPathAst: IAST, compilationOptions: CompilationOptions): Expression {
return compile(xPathAst, compilationOptions);
}
32 changes: 20 additions & 12 deletions src/parsing/prscParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1813,25 +1813,33 @@ function generateParser(options: { outputDebugInfo: boolean; xquery: boolean }):

const switchCaseClause: Parser<IAST> = then(
plus(
map(precededMultiple([tokens.CASE, whitespacePlus], switchCaseOperand), (x) => [
'switchCaseExpr',
x,
]),
then(
map(
precededMultiple([tokens.CASE, whitespacePlus], cut(switchCaseOperand)),
(x) => ['switchCaseExpr', x],
),
cut(whitespacePlus),
(x, _) => x,
),
),
precededMultiple([whitespacePlus, tokens.RETURN, whitespacePlus], exprSingle),
cut(precededMultiple([tokens.RETURN, whitespacePlus], cut(exprSingle))),
(operands, exprPart) =>
['switchExprCaseClause', ...operands, ['resultExpr', exprPart]] as IAST,
);

const switchExpr: Parser<IAST> = then3(
precededMultiple([tokens.SWITCH, whitespace, tokens.BRACE_OPEN], expr),
precededMultiple(
[whitespace, tokens.BRACE_CLOSE, whitespace],
plus(followed(switchCaseClause, whitespace)),
precededMultiple([tokens.SWITCH, whitespace, tokens.BRACE_OPEN], cut(expr)),
cut(
precededMultiple(
[whitespace, tokens.BRACE_CLOSE, cut(whitespace)],
plus(followed(switchCaseClause, whitespace)),
),
),
precededMultiple(
[tokens.DEFAULT, whitespacePlus, tokens.RETURN, whitespacePlus],
exprSingle,
cut(
precededMultiple(
[tokens.DEFAULT, whitespacePlus, tokens.RETURN, whitespacePlus],
exprSingle,
),
),
(exprPart, clauses, resultExpr) => [
'switchExpr',
Expand Down
12 changes: 10 additions & 2 deletions test/assets/failingXQueryXTestNames.csv
Original file line number Diff line number Diff line change
Expand Up @@ -2336,9 +2336,19 @@ K-FunctionProlog-3,Parser related error, Error: XQST0045: Functions and variable
K-FunctionProlog-31,Parser related error, Error: XQST0045: Functions and variables may not be declared in one of the reserved namespace URIs.
K-FunctionProlog-32,Parser related error, Error: XQST0045: Functions and variables may not be declared in one of the reserved namespace URIs.
K-FunctionProlog-4,Parser related error, Error: XQST0045: Functions and variables may not be declared in one of the reserved namespace URIs.
K-FunctionProlog-46,Parser related error, Error: Type declaration "voidSequenceType" is not supported.
K-FunctionProlog-49,Parser related error, Error: Type declaration "voidSequenceType" is not supported.
K-FunctionProlog-5,Parser related error, Error: XQST0045: Functions and variables may not be declared in one of the reserved namespace URIs.
K-FunctionProlog-50,Parser related error, Error: Type declaration "voidSequenceType" is not supported.
K-FunctionProlog-52,Parser related error, Error: Type declaration "voidSequenceType" is not supported.
K-FunctionProlog-53,Parser related error, Error: Type declaration "voidSequenceType" is not supported.
K-FunctionProlog-54,Parser related error, Error: Type declaration "voidSequenceType" is not supported.
K-FunctionProlog-55,Parser related error, Error: Type declaration "voidSequenceType" is not supported.
K-FunctionProlog-56,Parser related error, Error: Type declaration "voidSequenceType" is not supported.
K-FunctionProlog-57,Parser related error, Error: XPST0081: Invalid prefix for input :none
K-FunctionProlog-65,Parser related error, Error: Type declaration "voidSequenceType" is not supported.
K2-FunctionProlog-14,result was not equal
cbcl-function-decl-001,Parser related error, Error: Type declaration "voidSequenceType" is not supported.
function-declaration-010,result was not equal
function-declaration-016,result was not equal
function-declaration-023,Parser related error, Error: XQST0045: Functions and variables may not be declared in one of the reserved namespace URIs.
Expand Down Expand Up @@ -2670,8 +2680,6 @@ string-constructor-026,Parse error
string-constructor-910,Parse error
string-constructor-911,Parse error
string-constructor-912,Parse error
switch-004,Parse error
switch-005,Parse error
try-001,Parse error
try-002,Parse error
try-003,Parse error
Expand Down
2 changes: 1 addition & 1 deletion test/assets/runnableTestSets.csv
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ prod-SequenceType,true
prod-SquareArrayConstructor,true
prod-StepExpr,true
prod-StringConstructor,true
prod-SwitchExpr,false
prod-SwitchExpr,true
prod-TreatExpr,false
prod-TryCatchExpr,false
prod-TypeswitchExpr,true
Expand Down
Loading

0 comments on commit 2f7747a

Please sign in to comment.