Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codegen improvements #549

Merged
merged 26 commits into from
Oct 14, 2022
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c248318
Implement codegen for the not() function
bwrrp Oct 4, 2022
1c45543
Make codegen child axis lazy and simplify generator
bwrrp Oct 5, 2022
abdf47c
Fix buckets for type tests
bwrrp Oct 5, 2022
84fb01c
Check function namespace and arity. Fix empty check in name functions.
bwrrp Oct 6, 2022
1d7fe6b
Fix multiplicity of value and node comparisons
bwrrp Oct 6, 2022
b17fde6
Clean up runtimeLib
bwrrp Oct 6, 2022
29c76ef
Fix type check and a few tests
bwrrp Oct 6, 2022
76191e1
Remove unnecessary indirection for emitBaseExpr
bwrrp Oct 7, 2022
7aba083
Fix fn:node for empty sequences
bwrrp Oct 11, 2022
42decff
Refactor codegen
bwrrp Oct 11, 2022
28533c9
Implement general compare for single item vs sequence
bwrrp Oct 11, 2022
7a03631
Restore emitBaseExpr indirection
bwrrp Oct 11, 2022
da83bb5
Make tslint and old node happy
bwrrp Oct 11, 2022
6287968
Implement calling registered functions
bwrrp Oct 11, 2022
4960611
Implement filterExpr
bwrrp Oct 12, 2022
ab33d42
Implement contextItemExpr codegen and fix predicates in filterExpr
bwrrp Oct 12, 2022
d86df3b
Pass options in codegen tests
bwrrp Oct 12, 2022
7790b40
Fix ordering in codegen for single-node axis
bwrrp Oct 12, 2022
9003cfe
Add more codegen unit tests
bwrrp Oct 13, 2022
8c632d1
Remove unused Function GeneratedCodeBaseType
bwrrp Oct 13, 2022
3fd53da
Remove old TODOs
bwrrp Oct 13, 2022
f367bd1
Emit simpler code for a single-self-step path expression
bwrrp Oct 14, 2022
70d514c
Fix conversion of null node value to nodes array
bwrrp Oct 14, 2022
3c8557e
PR feedback
bwrrp Oct 14, 2022
000b00d
Fix linter issue
bwrrp Oct 14, 2022
01eb49e
[CodeFactor] Apply fixes
code-factor Oct 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/expressions/XPathErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const errFORG0001 = (value: string, type: ValueType, reason?: string) =>
new Error(
`FORG0001: Cannot cast ${value} to ${valueTypeToString(type)}${reason ? `, ${reason}` : ''}`
);
export const XPDY0002 = (message: string) => new Error(`XPDY0002: ${message}`);
export const errXPDY0002 = (message: string) => new Error(`XPDY0002: ${message}`);
export const errXPTY0004 = (message: string) => new Error(`XPTY0004: ${message}`);
export const errFOTY0013 = (type: ValueType) =>
new Error(`FOTY0013: Atomization is not supported for ${valueTypeToString(type)}.`);
Expand Down
24 changes: 23 additions & 1 deletion src/expressions/dataTypes/typeHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import builtinDataTypesByType, { TypeModel } from './builtins/builtinDataTypesByType';
import { ValueType } from './Value';
import { SequenceMultiplicity, SequenceType, ValueType } from './Value';
import { Variety } from './Variety';

export function getPrimitiveTypeName(typeName: ValueType): ValueType | null {
Expand Down Expand Up @@ -89,3 +89,25 @@ export function validateRestrictions(value: string, valueType: ValueType): boole
}
return true;
}

export function doesTypeAllowEmpty(type: SequenceType | null): boolean {
if (!type) {
// Unknown types can be anything
return true;
}
return (
type.mult === SequenceMultiplicity.ZERO_OR_MORE ||
type.mult === SequenceMultiplicity.ZERO_OR_ONE
);
}

export function doesTypeAllowMultiple(type: SequenceType | null): boolean {
if (!type) {
// Unknown types can be anything
return true;
}
return (
type.mult === SequenceMultiplicity.ZERO_OR_MORE ||
type.mult === SequenceMultiplicity.ONE_OR_MORE
);
}
6 changes: 3 additions & 3 deletions src/expressions/functions/builtInFunctions_identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import isSubtypeOf from '../dataTypes/isSubtypeOf';
import sequenceFactory from '../dataTypes/sequenceFactory';
import { SequenceMultiplicity, ValueType } from '../dataTypes/Value';
import { BUILT_IN_NAMESPACE_URIS } from '../staticallyKnownNamespaces';
import { errXPTY0004, XPDY0002 } from '../XPathErrors';
import { errXPDY0002, errXPTY0004 } from '../XPathErrors';
import { BuiltinDeclarationType } from './builtInFunctions';
import FunctionDefinitionType from './FunctionDefinitionType';

Expand Down Expand Up @@ -44,7 +44,7 @@ const fnId: FunctionDefinitionType = (
) => {
const targetNodeValue = targetNodeSequence.first();
if (!targetNodeValue) {
throw XPDY0002('The context is absent, it needs to be present to use id function.');
throw errXPDY0002('The context is absent, it needs to be present to use id function.');
}
if (!isSubtypeOf(targetNodeValue.type, ValueType.NODE)) {
throw errXPTY0004(
Expand Down Expand Up @@ -102,7 +102,7 @@ const fnIdref: FunctionDefinitionType = (
) => {
const targetNodeValue = targetNodeSequence.first();
if (!targetNodeValue) {
throw XPDY0002('The context is absent, it needs to be present to use idref function.');
throw errXPDY0002('The context is absent, it needs to be present to use idref function.');
}
if (!isSubtypeOf(targetNodeValue.type, ValueType.NODE)) {
throw errXPTY0004(
Expand Down
4 changes: 2 additions & 2 deletions src/expressions/functions/builtInFunctions_node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const fnName: FunctionDefinitionType = (
staticContext,
fnNodeName(dynamicContext, executionParameters, staticContext, sequence)
),
empty: () => sequenceFactory.empty(),
empty: () => sequenceFactory.singleton(createAtomicValue('', ValueType.XSSTRING)),
});
};

Expand Down Expand Up @@ -403,7 +403,7 @@ const declarations: BuiltinDeclarationType[] = [
callFunction: fnName,
localName: 'name',
namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
},

{
Expand Down
78 changes: 66 additions & 12 deletions src/jsCodegen/CodeGenContext.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,67 @@
import { Bucket } from '../expressions/util/Bucket';
import { IAST } from '../parsing/astHelper';
import { NamespaceResolver } from '../types/Options';
import { FunctionIdentifier, PartialCompilationResult } from './JavaScriptCompiledXPath';

export type CodeGenContext = {
emitBaseExpr?: (
ast: IAST,
identifier: FunctionIdentifier,
staticContext: CodeGenContext
) => [PartialCompilationResult, Bucket];
resolveNamespace: NamespaceResolver;
};
import { emitBaseExpr } from './emitBaseExpr';
import { mapPartialCompilationResult } from './emitHelpers';
import {
acceptAst,
GeneratedCodeBaseType,
PartialCompilationResult,
PartiallyCompiledAstAccepted,
} from './JavaScriptCompiledXPath';

export class CodeGenContext {
public defaultFunctionNamespaceUri: string;

// We need to create indirection through context for emitBaseExpr to avoid a cyclic dependency
// that the Closure compiler can't handle
public emitBaseExpr: typeof emitBaseExpr;

public resolveNamespace: NamespaceResolver;

private _identifierExprByExpr = new Map<
PartiallyCompiledAstAccepted,
PartiallyCompiledAstAccepted
>();

private _nextByPrefix = new Map<string, number>();

public constructor(resolveNamespace: NamespaceResolver, defaultFunctionNamespaceUri: string) {
this.resolveNamespace = resolveNamespace;
this.defaultFunctionNamespaceUri = defaultFunctionNamespaceUri;
this.emitBaseExpr = emitBaseExpr;
}

public getIdentifierFor(
expr: PartialCompilationResult,
prefix = 'v'
): PartialCompilationResult {
return mapPartialCompilationResult(expr, (expr) => {
let identifierExpr = this._identifierExprByExpr.get(expr);
if (!identifierExpr) {
const identifier = this._getNewIdentifier(prefix);
identifierExpr = acceptAst(identifier, expr.generatedCodeType, [
...expr.variables,
`const ${identifier} = ${expr.code};`,
]);
this._identifierExprByExpr.set(expr, identifierExpr);
this._identifierExprByExpr.set(identifierExpr, identifierExpr);
}
return identifierExpr;
});
}

public getNewIdentifier(prefix = 'v'): PartiallyCompiledAstAccepted {
return this.getVarInScope(this._getNewIdentifier(prefix));
}

public getVarInScope(identifier: string): PartiallyCompiledAstAccepted {
const expr = acceptAst(identifier, { type: GeneratedCodeBaseType.Value }, []);
this._identifierExprByExpr.set(expr, expr);
return expr;
}

private _getNewIdentifier(prefix = 'v'): string {
const next = this._nextByPrefix.get(prefix) || 0;
this._nextByPrefix.set(prefix, next + 1);
return `${prefix}${next}`;
}
}
58 changes: 9 additions & 49 deletions src/jsCodegen/JavaScriptCompiledXPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,80 +9,40 @@ export enum CompiledResultType {
}

export type PartiallyCompiledAstAccepted = {
// Expression that was generated
code: string;
// What kind of code was generated.
generatedCodeType: GeneratedCodeType;
isAstAccepted: true;
// Contains variable (and function) declarations for the upper compiled
// scope.
variables?: string[];
variables: string[];
};

export enum GeneratedCodeBaseType {
// A normal value: 5, "test", true, ...
Value,
// Defining a variable: const test = "test";
Variable,
// A statement: if (this) { do that; }
// A generator function representing a lazy sequence
Generator,
// Statements, used to build the generator body - doesn't have a value
Statement,
// A function has been generated: function foo() { return true; }
Function,
// An iterator is returned.
Iterator,
// Used in a single case where no code is generated.
None,
}

export type GeneratedCodeType =
| { type: GeneratedCodeBaseType.Value }
| { type: GeneratedCodeBaseType.Variable }
| { type: GeneratedCodeBaseType.Statement }
| { returnType: GeneratedCodeType; type: GeneratedCodeBaseType.Function }
| { type: GeneratedCodeBaseType.Iterator }
| { type: GeneratedCodeBaseType.None };

export function getCompiledValueCode(
identifier: string,
generatedCodeType: GeneratedCodeType,
contextItemName?: string
): [string, GeneratedCodeType] {
switch (generatedCodeType.type) {
case GeneratedCodeBaseType.Value:
return [identifier, { type: GeneratedCodeBaseType.Value }];
case GeneratedCodeBaseType.Variable:
return [identifier, { type: GeneratedCodeBaseType.Variable }];
case GeneratedCodeBaseType.Function: {
const code = `${
getCompiledValueCode(
`${identifier}(${contextItemName ? contextItemName : 'contextItem'})`,
generatedCodeType.returnType,
contextItemName
)[0]
}`;
return [code, generatedCodeType.returnType];
}
case GeneratedCodeBaseType.Iterator: {
return [identifier, { type: GeneratedCodeBaseType.Iterator }];
}
case GeneratedCodeBaseType.None:
throw new Error('Trying to get value of generated code with type None');
default:
throw new Error(
'Unreachable! Trying to get compiled value of unsupported GeneratedCodeType.'
);
}
}
| { type: GeneratedCodeBaseType.Generator }
| { type: GeneratedCodeBaseType.Statement };

export function acceptAst(
code: string,
generatedCodeType: GeneratedCodeType,
variables?: string[]
variables: string[]
): PartiallyCompiledAstAccepted {
return {
code,
generatedCodeType,
isAstAccepted: true,
variables,
isAstAccepted: true,
};
}

Expand Down
Loading