Skip to content

Commit

Permalink
parse functionCall(args).field in TS (PolymerLabs#6097)
Browse files Browse the repository at this point in the history
* parse functionCall(args).field in TS

* support (paxel expression) in in new entity fields and elsewhere

* lint
  • Loading branch information
cromwellian authored Sep 9, 2020
1 parent 846a9f3 commit ecabd35
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/runtime/manifest-ast-types/manifest-ast-nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ export interface FieldExpressionNode extends BaseNode {

export interface FunctionExpressionNode extends BaseNode {
kind: 'paxel-function';
function: PaxelFunction;
function: string;
arguments: PaxelExpressionNode[];
}

Expand Down
52 changes: 42 additions & 10 deletions src/runtime/manifest-parser.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -1720,7 +1720,7 @@ ExpressionEntityFields
}

ExpressionEntityField
= fieldName:fieldName whiteSpace? ':' whiteSpace? expression:RefinementExpression {
= fieldName:fieldName whiteSpace? ':' whiteSpace? expression:PaxelExpressionWithRefinement {
return toAstNode<AstNode.ExpressionEntityField>({
kind: 'expression-entity-field',
name: fieldName,
Expand Down Expand Up @@ -1837,9 +1837,40 @@ MultiplicativeExpression
return leftExpr;
}

FunctionArguments
= arg: PaxelExpressionWithRefinement rest:(multiLineSpace? ',' multiLineSpace? PaxelExpressionWithRefinement)*
{
return [arg, ...rest.map(item => item[3])];
}

FunctionCall
= fn: (fieldName '(' FunctionArguments? ')' / 'creationTimestamp')
{
const fnName = fn === 'creationTimestamp' ? fn : fn[0];
const args = typeof(fn) !== 'string' && fn[2] || [];

if (!isPaxelMode()) {
if (args.length > 0) {
error("Functions may have arguments only in paxel expressions.");
}
if (fnName !== 'now' && fnName !== 'creationTimestamp') {
error('Paxel function calls other to now() are permitted only in paxel expressions.');
}
return toAstNode<AstNode.BuiltInNode>({kind: 'built-in-node', value: fnName === 'now' ? 'now()' : fnName});
}
return toAstNode<AstNode.FunctionExpressionNode>({
kind: 'paxel-function',
function: fnName,
arguments: args
});
}

PrimaryExpression
= '(' whiteSpace? expr:RefinementExpression whiteSpace? ')'
= '(' whiteSpace? expr:PaxelExpressionWithRefinement whiteSpace? ')'
{
if (!isPaxelMode() && expr.kind.indexOf('paxel-') !== -1) {
error('Paxel expressions are not allowed in refinements.');
}
return expr;
}
/ op:(('not' whiteSpace) / ('-' whiteSpace?)) expr:PrimaryExpression
Expand All @@ -1853,17 +1884,18 @@ PrimaryExpression
{
return toAstNode<AstNode.BooleanNode>({kind: 'boolean-node', value: bool.toLowerCase() === 'true'});
}
/ fn: ('now()' / 'creationTimestamp')
{
return toAstNode<AstNode.BuiltInNode>({kind: 'built-in-node', value: fn});
}
/ fn: fieldName nested:('.' fieldName)*
/ fn: (FunctionCall / fieldName) nested:('.' fieldName)*
{
const fieldNode = typeof(fn) === 'string' && toAstNode<AstNode.FieldNode>({kind: 'field-name-node', value: fn}) || fn;
// nested is ignored, used only to allow Paxel expressions to parse as text
if (!isPaxelMode() && nested && nested.length > 0) {
error('Scope lookups are not permitted for refinements, only in paxel expressions.');
if (!isPaxelMode()) {
if (nested && nested.length > 0) {
error('Scope lookups are not permitted for refinements, only in paxel expressions.');
}
return fieldNode;
} else {
return toAstNode<AstNode.FieldNode>({kind: 'field-name-node', value: fn});
// TODO: placeholder, doesn't actually construct the correct/full AST node
return toAstNode<AstNode.FieldExpressionNode>({kind: 'paxel-field', scopeExpression: null, field: fieldNode});
}
}
/ fn: '?'
Expand Down
46 changes: 46 additions & 0 deletions src/runtime/tests/manifest-parser-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,24 @@ describe('manifest parser', () => {
input: reads Something {value: Text } [ value.x / 2 ]
`);
}, `Scope lookups are not permitted`);
assert.throws(() => {
parse(`
particle Foo
input: reads Something {value: Text } [ now(x) > 0 ]
`);
}, `Functions may have arguments only in paxel expressions`);
assert.throws(() => {
parse(`
particle Foo
input: reads Something {value: Text } [ average() > 0 ]
`);
}, `Paxel function calls other to now() are permitted only in paxel expressions`);
assert.throws(() => {
parse(`
particle Foo
input: reads Something {value: Text } [ (from p in q select p) > 0 ]
`);
}, `Paxel expressions are not allowed in refinements`);
});
it('parses nested referenced inline schemas', () => {
parse(`
Expand Down Expand Up @@ -875,6 +893,34 @@ describe('manifest parser', () => {
bar: writes Bar {y: Number} = from p in foo.x select new Bar {y: p.num / 2}
`);
});
it('allows paxel expressions in new nested entity selection', () => {
parse(`
particle Converter
foo: reads Foo {x: Number}
bar: writes Bar {y: Number} = from p in foo.x select new Bar {y: (from p in foo.z select first(p))/2}
`);
});
it('allows paxel expressions in new entity selection', () => {
parse(`
particle Converter
foo: reads Foo {x: Number}
bar: writes Bar {y: Number} = from p in foo.x select new Bar {y: from p in foo.z select first(p)}
`);
});
it('allows function calls as qualifiers', () => {
parse(`
particle Converter
foo: reads Foo {x: Number}
bar: writes Bar {y: Number} = from p in foo.x select new Bar {y: first(foo).x}
`);
});
it('allows complex qualifiers in where condition', () => {
parse(`
particle Converter
foo: reads Foo {x: Number}
bar: writes Bar {y: Number} = from p in foo.x where first(foo).x < p.y select new Bar {y: foo.x}
`);
});
it('fails expression without starting from', () => {
assert.throws(() => parse(`
particle Converter
Expand Down

0 comments on commit ecabd35

Please sign in to comment.