diff --git a/lib/ast/base.ts b/lib/ast/base.ts index 27216273..9368a4fe 100644 --- a/lib/ast/base.ts +++ b/lib/ast/base.ts @@ -23,7 +23,7 @@ import NodeVisitor from './visitor'; import type { Invocation, - ExternalBooleanExpression, + ExternalBooleanExpression } from './expression'; import type { Value } from './values'; import type { diff --git a/lib/ast/expression.ts b/lib/ast/expression.ts index d8eb8e48..ad47a018 100644 --- a/lib/ast/expression.ts +++ b/lib/ast/expression.ts @@ -24,7 +24,7 @@ import Node, { SourceRange } from './base'; import NodeVisitor from './visitor'; import { FunctionDef } from './function_def'; import { Value } from './values'; -import { Expression } from './expression2'; +import { Expression, FilterExpression, InvocationExpression, ProjectionExpression } from './expression2'; import Type from '../type'; import * as Optimizer from '../optimize'; @@ -43,6 +43,7 @@ import { import { TokenStream } from '../new-syntax/tokenstream'; import List from '../utils/list'; +import { UnserializableError } from "../utils/errors"; import { SyntaxPriority, addParenthesis @@ -392,6 +393,7 @@ export class Invocation extends Node { * @property {boolean} isAtom - true if this is an instance of {@link Ast.BooleanExpression.Atom} * @property {boolean} isNot - true if this is an instance of {@link Ast.BooleanExpression.Not} * @property {boolean} isExternal - true if this is an instance of {@link Ast.BooleanExpression.External} + * @property {boolean} isExistentialSubquery - true if this is an instance of {@link Ast.BooleanExpression.ExistentialSubquery} * @property {boolean} isComparisonSubquery - true if is an instance of {@link Ast.BooleanExpression.ComparisonSubquery} * @property {boolean} isTrue - true if this is {@link Ast.BooleanExpression.True} * @property {boolean} isFalse - true if this is {@link Ast.BooleanExpression.False} @@ -408,6 +410,8 @@ export abstract class BooleanExpression extends Node { isNot ! : boolean; static External : any; isExternal ! : boolean; + static ExistentialSubquery : any; + isExistentialSubquery ! : boolean; static ComparisonSubquery : any; isComparisonSubquery ! : boolean; static True : any; @@ -427,6 +431,7 @@ export abstract class BooleanExpression extends Node { abstract clone() : BooleanExpression; abstract equals(other : BooleanExpression) : boolean; + abstract toLegacy() : BooleanExpression; /** * Iterate all slots (scalar value nodes) in this boolean expression. @@ -449,6 +454,7 @@ BooleanExpression.prototype.isOr = false; BooleanExpression.prototype.isAtom = false; BooleanExpression.prototype.isNot = false; BooleanExpression.prototype.isExternal = false; +BooleanExpression.prototype.isExistentialSubquery = false; BooleanExpression.prototype.isComparisonSubquery = false; BooleanExpression.prototype.isTrue = false; BooleanExpression.prototype.isFalse = false; @@ -503,6 +509,10 @@ export class AndBooleanExpression extends BooleanExpression { return List.join(this.operands.map((op) => addParenthesis(this.priority, op.priority, op.toSource())), '&&'); } + toLegacy() : AndBooleanExpression { + return new AndBooleanExpression(null, this.operands.map((op) => op.toLegacy())); + } + equals(other : BooleanExpression) : boolean { return other instanceof AndBooleanExpression && arrayEquals(this.operands, other.operands); @@ -573,6 +583,10 @@ export class OrBooleanExpression extends BooleanExpression { return List.join(this.operands.map((op) => addParenthesis(this.priority, op.priority, op.toSource())), '||'); } + toLegacy() : OrBooleanExpression { + return new OrBooleanExpression(null, this.operands.map((op) => op.toLegacy())); + } + equals(other : BooleanExpression) : boolean { return other instanceof OrBooleanExpression && arrayEquals(this.operands, other.operands); @@ -681,6 +695,10 @@ export class AtomBooleanExpression extends BooleanExpression { } } + toLegacy() : AtomBooleanExpression { + return this; + } + equals(other : BooleanExpression) : boolean { return other instanceof AtomBooleanExpression && this.name === other.name && @@ -758,6 +776,10 @@ export class NotBooleanExpression extends BooleanExpression { return List.concat('!', addParenthesis(this.priority, this.expr.priority, this.expr.toSource())); } + toLegacy() : NotBooleanExpression { + return new NotBooleanExpression(null, this.expr.toLegacy()); + } + equals(other : BooleanExpression) : boolean { return other instanceof NotBooleanExpression && this.expr.equals(other.expr); @@ -876,6 +898,10 @@ export class ExternalBooleanExpression extends BooleanExpression { return `External(${this.selector}, ${this.channel}, ${this.in_params}, ${this.filter})`; } + toLegacy() : ExternalBooleanExpression { + return this; + } + equals(other : BooleanExpression) : boolean { return other instanceof ExternalBooleanExpression && this.selector.equals(other.selector) && @@ -924,6 +950,91 @@ export class ExternalBooleanExpression extends BooleanExpression { BooleanExpression.External = ExternalBooleanExpression; BooleanExpression.External.prototype.isExternal = true; +/** + * A boolean expression that calls a Thingpedia query function + * and filters the result. + * + * The boolean expression is true if at least one result from the function + * call satisfies the filter. + * + * @alias Ast.BooleanExpression.ExistentialSubquery + * @extends Ast.BooleanExpression + */ +export class ExistentialSubqueryBooleanExpression extends BooleanExpression { + subquery : Expression; + + /** + * Construct a new existential subquery boolean expression. + * + * @param location + * @param subquery: the query used for check existence of result + */ + constructor(location : SourceRange|null, + subquery : Expression) { + super(location); + this.subquery = subquery; + } + + get priority() : SyntaxPriority { + return SyntaxPriority.Primary; + } + + toSource() : TokenStream { + return List.concat('any', '(', this.subquery.toSource(), ')'); + } + + toString() : string { + return `ExistentialSubquery(${this.subquery})`; + } + + toLegacy() : ExternalBooleanExpression { + if (this.subquery instanceof FilterExpression && this.subquery.expression instanceof InvocationExpression) { + const invocation = this.subquery.expression.invocation; + return new ExternalBooleanExpression( + null, + invocation.selector, + invocation.channel, + invocation.in_params, + this.subquery.filter.toLegacy(), + this.subquery.schema + ); + } + throw new UnserializableError('Existential Subquery'); + } + + equals(other : BooleanExpression) : boolean { + return other instanceof ExistentialSubqueryBooleanExpression && + this.subquery.equals(other.subquery); + } + + visit(visitor : NodeVisitor) : void { + visitor.enter(this); + if (visitor.visitExistentialSubqueryBooleanExpression(this)) + this.subquery.visit(visitor); + visitor.exit(this); + } + + clone() : ExistentialSubqueryBooleanExpression { + return new ExistentialSubqueryBooleanExpression( + this.location, + this.subquery.clone() + ); + } + + *iterateSlots(schema : FunctionDef|null, + prim : InvocationLike|null, + scope : ScopeMap) : Generator { + yield* this.subquery.iterateSlots(scope); + } + + *iterateSlots2(schema : FunctionDef|null, + prim : InvocationLike|null, + scope : ScopeMap) : Generator { + yield* this.subquery.iterateSlots2(scope); + } +} +BooleanExpression.ExistentialSubquery = ExistentialSubqueryBooleanExpression; +BooleanExpression.ExistentialSubquery.prototype.isExistentialSubquery = true; /** * A boolean expression that calls a Thingpedia query function @@ -939,7 +1050,7 @@ export class ComparisonSubqueryBooleanExpression extends BooleanExpression { overload : Type[]|null; /** - * Construct a new external boolean expression. + * Construct a new comparison subquery boolean expression. * * @param location * @param lhs - the parameter name to compare @@ -972,6 +1083,31 @@ export class ComparisonSubqueryBooleanExpression extends BooleanExpression { return `ComparisonSubquery(${this.lhs}, ${this.operator}, ${this.rhs})`; } + toLegacy() : ExternalBooleanExpression { + if (this.rhs instanceof ProjectionExpression && this.rhs.args.length + this.rhs.computations.length === 1) { + const expr = this.rhs.expression; + if (expr instanceof FilterExpression && expr.expression instanceof InvocationExpression) { + const invocation = expr.expression.invocation; + const extraFilter = new ComputeBooleanExpression( + null, + this.lhs, + this.operator, + this.rhs.args.length ? new Value.VarRef(this.rhs.args[0]) : this.rhs.computations[0] + ); + const filter = new AndBooleanExpression(null, [expr.filter.toLegacy(), extraFilter]); + return new ExternalBooleanExpression( + null, + invocation.selector, + invocation.channel, + invocation.in_params, + filter, + invocation.schema + ); + } + } + throw new UnserializableError('Comparison Subquery'); + } + equals(other : BooleanExpression) : boolean { return other instanceof ComparisonSubqueryBooleanExpression && this.lhs.equals(other.lhs) && @@ -1037,6 +1173,10 @@ export class DontCareBooleanExpression extends BooleanExpression { return List.concat('true', '(', this.name, ')'); } + toLegacy() : DontCareBooleanExpression { + return this; + } + equals(other : BooleanExpression) : boolean { return other instanceof DontCareBooleanExpression && this.name === other.name; } @@ -1076,6 +1216,10 @@ export class TrueBooleanExpression extends BooleanExpression { return List.singleton('true'); } + toLegacy() : TrueBooleanExpression { + return this; + } + equals(other : BooleanExpression) : boolean { return this === other; } @@ -1123,6 +1267,10 @@ export class FalseBooleanExpression extends BooleanExpression { return List.singleton('false'); } + toLegacy() : FalseBooleanExpression { + return this; + } + equals(other : BooleanExpression) : boolean { return this === other; } @@ -1233,6 +1381,10 @@ export class ComputeBooleanExpression extends BooleanExpression { } } + toLegacy() : ComputeBooleanExpression { + return this; + } + equals(other : BooleanExpression) : boolean { return other instanceof ComputeBooleanExpression && this.lhs.equals(other.lhs) && diff --git a/lib/ast/visitor.ts b/lib/ast/visitor.ts index 09017736..0f66317d 100644 --- a/lib/ast/visitor.ts +++ b/lib/ast/visitor.ts @@ -270,6 +270,10 @@ export default abstract class NodeVisitor { visitComparisonSubqueryBooleanExpression(node : Exp.ComparisonSubqueryBooleanExpression) : boolean { return true; } + /* istanbul ignore next */ + visitExistentialSubqueryBooleanExpression(node : Exp.ExistentialSubqueryBooleanExpression) : boolean { + return true; + } diff --git a/lib/compiler/ast-to-ops.ts b/lib/compiler/ast-to-ops.ts index d1926116..65573a10 100644 --- a/lib/compiler/ast-to-ops.ts +++ b/lib/compiler/ast-to-ops.ts @@ -770,6 +770,13 @@ function compileBooleanExpressionToOp(expr : Ast.BooleanExpression) : BooleanExp ); } + if (expr instanceof Ast.ExistentialSubqueryBooleanExpression) { + return new BooleanExpressionOp.ExistentialSubquery( + expr, + compileTableToOps(expr.subquery.toLegacy() as Ast.Table, [], new QueryInvocationHints(new Set)) + ); + } + if (expr instanceof Ast.ComparisonSubqueryBooleanExpression) { assert(expr.rhs instanceof Ast.ProjectionExpression && expr.rhs.args.length + expr.rhs.computations.length === 1); let rhs, hints; diff --git a/lib/compiler/ops-to-jsir.ts b/lib/compiler/ops-to-jsir.ts index 84009f98..971cc6f9 100644 --- a/lib/compiler/ops-to-jsir.ts +++ b/lib/compiler/ops-to-jsir.ts @@ -306,6 +306,20 @@ export default class OpCompiler { this._irBuilder.popBlock(); // for-of this._irBuilder.popBlock(); // try-catch + } else if (expr instanceof Ops.ExistentialSubqueryBooleanExpressionOp) { + this._irBuilder.add(new JSIr.LoadConstant(new Ast.Value.Boolean(false), cond)); + + const blockStack = this._irBuilder.saveStackState(); + const tmpScope = this._currentScope; + + this._compileTable(expr.subquery); + + this._irBuilder.add(new JSIr.LoadConstant(new Ast.Value.Boolean(true), cond)); + this._irBuilder.add(new JSIr.Break()); + this._irBuilder.popBlock(); + + this._currentScope = tmpScope; + this._irBuilder.popTo(blockStack); } else if (expr instanceof Ops.ComparisonSubqueryBooleanExpressionOp) { this._irBuilder.add(new JSIr.LoadConstant(new Ast.Value.Boolean(false), cond)); diff --git a/lib/compiler/ops.ts b/lib/compiler/ops.ts index 88c54328..7988a50f 100644 --- a/lib/compiler/ops.ts +++ b/lib/compiler/ops.ts @@ -353,6 +353,7 @@ export abstract class BooleanExpressionOp { static Not : typeof NotBooleanExpressionOp; static Atom : typeof AtomBooleanExpressionOp; static External : typeof ExternalBooleanExpressionOp; + static ExistentialSubquery : typeof ExistentialSubqueryBooleanExpressionOp; static ComparisonSubquery : typeof ComparisonSubqueryBooleanExpressionOp; static True : TrueBooleanExpressionOp; static False : FalseBooleanExpressionOp; @@ -412,6 +413,14 @@ export class ExternalBooleanExpressionOp extends BooleanExpressionOp { } BooleanExpressionOp.External = ExternalBooleanExpressionOp; +export class ExistentialSubqueryBooleanExpressionOp extends BooleanExpressionOp { + constructor(ast : Ast.ExistentialSubqueryBooleanExpression, + public subquery : TableOp) { + super(ast); + } +} +BooleanExpressionOp.ExistentialSubquery = ExistentialSubqueryBooleanExpressionOp; + export class ComparisonSubqueryBooleanExpressionOp extends BooleanExpressionOp { constructor(ast : Ast.ComparisonSubqueryBooleanExpression, public lhs : Ast.Value, diff --git a/lib/grammar.pegjs b/lib/grammar.pegjs index b4c69a26..2a4cce17 100644 --- a/lib/grammar.pegjs +++ b/lib/grammar.pegjs @@ -645,7 +645,15 @@ executable_statement = body:rule_body _ ';' { get_predicate = fn:thingpedia_function_name _ in_params:input_param_list _ '{' _ filter:or_expr _ '}' { let [selector, function_name] = fn; - return new Ast.BooleanExpression.External(location(), selector, function_name, in_params, filter, null); + const invocation = new Ast.Invocation(location(), selector, function_name, in_params, null); + return new Ast.BooleanExpression.ExistentialSubquery( + location(), + new Ast.FilterExpression( + location(), + new Ast.InvocationExpression(location(), invocation, null), + filter + ) + ); } function_style_predicate = fn:$(ident '~'? / '~' ident) _ '(' _ lhs:qualified_name _ ',' _ rhs:value _ ')' !'(' { diff --git a/lib/legacy-prettyprint.ts b/lib/legacy-prettyprint.ts index 211b3d5f..0bb89493 100644 --- a/lib/legacy-prettyprint.ts +++ b/lib/legacy-prettyprint.ts @@ -206,6 +206,8 @@ function prettyprintFilterExpression(ast : Ast.BooleanExpression) : string { return prettyprintExternalFilter(ast); if (ast instanceof Ast.ComputeBooleanExpression) return `${prettyprintValue(ast.lhs)} ${ast.operator} ${prettyprintValue(ast.rhs)}`; + if (ast instanceof Ast.ComparisonSubqueryBooleanExpression || ast instanceof Ast.ExistentialSubqueryBooleanExpression) + return prettyprintExternalFilter(ast.toLegacy()); assert(ast instanceof Ast.AtomBooleanExpression); if (INFIX_FILTERS.has(ast.operator)) diff --git a/lib/new-syntax/parser.lr b/lib/new-syntax/parser.lr index fd764339..cb57dc40 100644 --- a/lib/new-syntax/parser.lr +++ b/lib/new-syntax/parser.lr @@ -1211,10 +1211,8 @@ atom_filter : Ast.BooleanExpression = { op:function_like_comparison_op '(' lhs:value ',' 'any' '(' rhs:projection_expression ')' ')' => new Ast.BooleanExpression.ComparisonSubquery($.location, lhs, op, rhs); - 'any' '(' inv:thingpedia_call 'filter' filter:or_filter ')' => - new Ast.BooleanExpression.External($.location, inv.selector, inv.channel, inv.in_params, filter, null); - 'any' '(' inv:thingpedia_call ',' filter:or_filter ')' => - new Ast.BooleanExpression.External($.location, inv.selector, inv.channel, inv.in_params, filter, null); + 'any' '(' subquery:chain_expression ')' => + new Ast.BooleanExpression.ExistentialSubquery($.location, subquery); } primary_value : Ast.Value = { diff --git a/lib/nn-syntax/parser.lr b/lib/nn-syntax/parser.lr index 482ab2ff..83b05af8 100644 --- a/lib/nn-syntax/parser.lr +++ b/lib/nn-syntax/parser.lr @@ -392,7 +392,13 @@ atom_filter : Ast.BooleanExpression = { return new Ast.BooleanExpression.Compute($.location, lhs, op, rhs); }; fn:call '{' filter '}' => { - return new Ast.BooleanExpression.External($.location, fn.selector, fn.channel, fn.in_params, filter, null); + const expr = new Ast.FilterExpression( + $.location, + new Ast.InvocationExpression($.location, fn, null), + filter, + null + ); + return new Ast.BooleanExpression.ExistentialSubquery($.location, expr); }; } diff --git a/lib/nn-syntax/tonn_converter.js b/lib/nn-syntax/tonn_converter.js index c8cca341..6bf1c593 100644 --- a/lib/nn-syntax/tonn_converter.js +++ b/lib/nn-syntax/tonn_converter.js @@ -25,6 +25,7 @@ import Type from '../type'; import List from '../utils/list'; import { UnsynthesizableError } from './errors'; +import { UnserializableError } from "../utils/errors"; // small integers are predicted/translated by the neural network, while // large integers are copied using NUMBER_* tokens @@ -103,8 +104,15 @@ function filterToCNF(filter) { ors.push(...or.operands); continue; } + if (or.isExistentialSubquery || or.isComparisonSubquery) { + const externalEquivalent = or.toLegacy(); + if (externalEquivalent) + currentClause.push(externalEquivalent); + else + throw new UnserializableError('Subquery'); + } if (or.isAnd) - throw new Error('TODO'); + throw new UnserializableError('AND boolean expression'); } clauses.push(new Ast.BooleanExpression.Or(null, currentClause)); } @@ -473,6 +481,7 @@ export default class ToNNConverter { lhs = this.valueToNN(or.lhs, scope); orclause = List.concat(lhs, or.operator, this.valueToNN(or.rhs, scope)); } else { + assert(or.isExternal); orclause = List.concat(`@${or.selector.kind}.${or.channel}`); for (let inParam of or.in_params) { let ptype = or.schema.inReq[inParam.name] || or.schema.inOpt[inParam.name]; diff --git a/lib/optimize.ts b/lib/optimize.ts index 8d2497b3..fb40367a 100644 --- a/lib/optimize.ts +++ b/lib/optimize.ts @@ -176,6 +176,12 @@ function optimizeFilter(expr : Ast.BooleanExpression) : Ast.BooleanExpression { return new Ast.BooleanExpression.External(expr.location, expr.selector, expr.channel, expr.in_params, subfilter, expr.schema); } + if (expr instanceof Ast.ExistentialSubqueryBooleanExpression) { + return new Ast.BooleanExpression.ExistentialSubquery( + expr.location, + optimizeExpression(expr.subquery) + ); + } if (expr instanceof Ast.ComputeBooleanExpression) { const lhs = expr.lhs; const rhs = expr.rhs; diff --git a/lib/permission_checker.ts b/lib/permission_checker.ts index 7cfcd47f..18d5968a 100644 --- a/lib/permission_checker.ts +++ b/lib/permission_checker.ts @@ -417,6 +417,15 @@ class SmtReduction { return smt.Not(this._processFilter(ast.expr, scope, scopeType)); if (ast instanceof Ast.ExternalBooleanExpression) { return this._addGetPredicate(ast, scope, scopeType); + } else if (ast instanceof Ast.ExistentialSubqueryBooleanExpression) { + const externalEquivalent = ast.toLegacy(); + if (externalEquivalent) + return this._addGetPredicate(externalEquivalent, scope, scopeType); + // TODO: add support for existential subquery in general + throw new Error('Unsupported subquery'); + } else if (ast instanceof Ast.ComparisonSubqueryBooleanExpression) { + // TODO: add support for comparison subquery + throw new Error('Unsupported subquery'); } else { assert(ast instanceof Ast.AtomBooleanExpression); @@ -482,6 +491,15 @@ class SmtReduction { return smt.Not(this._processPermissionFilter(ast.expr, ufvar, schema, scope, scopeType)); if (ast instanceof Ast.ExternalBooleanExpression) { return this._addGetPredicate(ast, {}, {}); + } else if (ast instanceof Ast.ExistentialSubqueryBooleanExpression) { + const externalEquivalent = ast.toLegacy(); + if (externalEquivalent) + return this._addGetPredicate(externalEquivalent, scope, scopeType); + // TODO: add support for existential subquery in general + throw new Error('Unsupported subquery'); + } else if (ast instanceof Ast.ComparisonSubqueryBooleanExpression) { + // TODO: add support for comparison subquery + throw new Error('Unsupported subquery'); } else { assert(ast instanceof Ast.AtomBooleanExpression); @@ -1077,6 +1095,10 @@ class RuleTransformer { return new Ast.BooleanExpression.Not(expr.location, recursiveHelper(expr.expr)); if (expr instanceof Ast.ExternalBooleanExpression) // external predicates don't refer to the inputs or outputs of the function so we're good return expr; + if (expr instanceof Ast.ExistentialSubqueryBooleanExpression) + return expr; + if (expr instanceof Ast.ComparisonSubqueryBooleanExpression) + return expr; let lhs : Ast.Value|undefined, rhs : Ast.Value; let filter : Ast.ComputeBooleanExpression|Ast.AtomBooleanExpression; diff --git a/lib/typecheck.ts b/lib/typecheck.ts index 0b037609..3dadb33f 100644 --- a/lib/typecheck.ts +++ b/lib/typecheck.ts @@ -473,13 +473,25 @@ export default class TypeChecker { return; } - assert(ast instanceof Ast.ExternalBooleanExpression); - if (ast.schema === null) - await this._loadTpSchema(ast); - if (ast.schema!.functionType !== 'query') - throw new TypeError(`Subquery function must be a query, not ${ast.schema!.functionType}`); - await this._typeCheckInputArgs(ast, ast.schema!, scope); - await this._typeCheckFilterHelper(ast.filter, ast.schema, scope); + if (ast instanceof Ast.ExternalBooleanExpression) { + if (ast.schema === null) + await this._loadTpSchema(ast); + if (ast.schema!.functionType !== 'query') + throw new TypeError(`Subquery function must be a query, not ${ast.schema!.functionType}`); + await this._typeCheckInputArgs(ast, ast.schema!, scope); + await this._typeCheckFilterHelper(ast.filter, ast.schema, scope); + return; + } + + assert(ast instanceof Ast.ExistentialSubqueryBooleanExpression); + await this._typeCheckSubquery(ast.subquery, scope); + } + + private async _typeCheckSubquery(expr : Ast.Expression, scope : Scope) { + if (expr.schema === null) + await this._loadAllSchemas(expr); + await this._typeCheckExpression(expr, scope); + this._checkExpressionType(expr, ['query'], 'subquery'); } private async _typeCheckSubqueryValue(expr : Ast.Expression, scope : Scope) { diff --git a/lib/utils/errors.ts b/lib/utils/errors.ts index fd250b9e..f34b31e3 100644 --- a/lib/utils/errors.ts +++ b/lib/utils/errors.ts @@ -48,3 +48,9 @@ export class ThingTalkSyntaxError extends Error { this.location = location || null; } } + +export class UnserializableError extends Error { + constructor(what : string) { + super(what + ' is not serializable'); + } +} diff --git a/test/test_compiler.js b/test/test_compiler.js index f0508979..a5c20535 100644 --- a/test/test_compiler.js +++ b/test/test_compiler.js @@ -1323,7 +1323,7 @@ const TEST_CASES = [ // 25 [`monitor(@com.twitter.home_timeline(), any(@org.thingpedia.builtin.thingengine.builtin.get_time(), time >= new Time(9,0) && time <= new Time(10, 0))); monitor(@com.twitter.home_timeline(), text =~ "lol" && any(@org.thingpedia.builtin.thingengine.builtin.get_time(), time >= new Time(9,0) && time <= new Time(10, 0)));`, - [`"use strict"; + [` "use strict"; let _t_0; let _t_1; let _t_2; @@ -1354,6 +1354,7 @@ const TEST_CASES = [ let _t_27; let _t_28; let _t_29; + let _t_30; _t_0 = await __env.readState(0); try { _t_1 = {}; @@ -1378,26 +1379,27 @@ const TEST_CASES = [ if (_t_13) { _t_15 = false; try { - _t_17 = {}; - _t_16 = await __env.invokeQuery("org.thingpedia.builtin.thingengine.builtin", { }, "get_time", _t_17, { projection: ["time"] }); - _t_18 = __builtin.getAsyncIterator(_t_16); + _t_16 = {}; + _t_17 = await __env.invokeQuery("org.thingpedia.builtin.thingengine.builtin", { }, "get_time", _t_16, { projection: ["time"] }); + _t_18 = __builtin.getAsyncIterator(_t_17); { let _iter_tmp = await _t_18.next(); while (!_iter_tmp.done) { _t_19 = _iter_tmp.value; _t_20 = _t_19[0]; _t_21 = _t_19[1]; - _t_22 = _t_21.time; - _t_23 = true; - _t_25 = __builtin.getTime (_t_22); - _t_26 = new __builtin.Time(10, 0, 0); - _t_24 = _t_25 <= _t_26; - _t_23 = _t_23 && _t_24; - _t_28 = __builtin.getTime (_t_22); - _t_29 = new __builtin.Time(9, 0, 0); - _t_27 = _t_28 >= _t_29; - _t_23 = _t_23 && _t_27; - if (_t_23) { + _t_22 = _t_21.__response; + _t_23 = _t_21.time; + _t_24 = true; + _t_26 = __builtin.getTime (_t_23); + _t_27 = new __builtin.Time(10, 0, 0); + _t_25 = _t_26 <= _t_27; + _t_24 = _t_24 && _t_25; + _t_29 = __builtin.getTime (_t_23); + _t_30 = new __builtin.Time(9, 0, 0); + _t_28 = _t_29 >= _t_30; + _t_24 = _t_24 && _t_28; + if (_t_24) { _t_15 = true; break; } else { @@ -1407,7 +1409,7 @@ const TEST_CASES = [ } } } catch(_exc_) { - __env.reportError("Failed to invoke get-predicate query", _exc_); + __env.reportError("Failed to invoke query", _exc_); } if (_t_15) { try { @@ -1426,7 +1428,7 @@ const TEST_CASES = [ } } catch(_exc_) { __env.reportError("Failed to invoke trigger", _exc_); - }`, `"use strict"; + }`, ` "use strict"; let _t_0; let _t_1; let _t_2; @@ -1465,6 +1467,7 @@ const TEST_CASES = [ let _t_35; let _t_36; let _t_37; + let _t_38; _t_0 = await __env.readState(1); try { _t_1 = {}; @@ -1499,26 +1502,27 @@ const TEST_CASES = [ _t_20 = true; _t_21 = false; try { - _t_23 = {}; - _t_22 = await __env.invokeQuery("org.thingpedia.builtin.thingengine.builtin", { }, "get_time", _t_23, { projection: ["time"] }); - _t_24 = __builtin.getAsyncIterator(_t_22); + _t_22 = {}; + _t_23 = await __env.invokeQuery("org.thingpedia.builtin.thingengine.builtin", { }, "get_time", _t_22, { projection: ["time"] }); + _t_24 = __builtin.getAsyncIterator(_t_23); { let _iter_tmp = await _t_24.next(); while (!_iter_tmp.done) { _t_25 = _iter_tmp.value; _t_26 = _t_25[0]; _t_27 = _t_25[1]; - _t_28 = _t_27.time; - _t_29 = true; - _t_31 = __builtin.getTime (_t_28); - _t_32 = new __builtin.Time(10, 0, 0); - _t_30 = _t_31 <= _t_32; - _t_29 = _t_29 && _t_30; - _t_34 = __builtin.getTime (_t_28); - _t_35 = new __builtin.Time(9, 0, 0); - _t_33 = _t_34 >= _t_35; - _t_29 = _t_29 && _t_33; - if (_t_29) { + _t_28 = _t_27.__response; + _t_29 = _t_27.time; + _t_30 = true; + _t_32 = __builtin.getTime (_t_29); + _t_33 = new __builtin.Time(10, 0, 0); + _t_31 = _t_32 <= _t_33; + _t_30 = _t_30 && _t_31; + _t_35 = __builtin.getTime (_t_29); + _t_36 = new __builtin.Time(9, 0, 0); + _t_34 = _t_35 >= _t_36; + _t_30 = _t_30 && _t_34; + if (_t_30) { _t_21 = true; break; } else { @@ -1528,12 +1532,12 @@ const TEST_CASES = [ } } } catch(_exc_) { - __env.reportError("Failed to invoke get-predicate query", _exc_); + __env.reportError("Failed to invoke query", _exc_); } _t_20 = _t_20 && _t_21; - _t_37 = "lol"; - _t_36 = __builtin.like(_t_12, _t_37); - _t_20 = _t_20 && _t_36; + _t_38 = "lol"; + _t_37 = __builtin.like(_t_12, _t_38); + _t_20 = _t_20 && _t_37; if (_t_20) { try { await __env.output(String(_t_9), _t_10); @@ -9513,6 +9517,125 @@ const TEST_CASES = [ await __env.exitProcedure(0, null); }`] ], + + // 107 existential subquery + [`@com.spotify2.song() filter any([id] of @com.spotify2.album() filter id =~ "lol");`, + [`"use strict"; + let _t_0; + let _t_1; + let _t_2; + let _t_3; + let _t_4; + let _t_5; + let _t_6; + let _t_7; + let _t_8; + let _t_9; + let _t_10; + let _t_11; + let _t_12; + let _t_13; + let _t_14; + let _t_15; + let _t_16; + let _t_17; + let _t_18; + let _t_19; + let _t_20; + let _t_21; + let _t_22; + let _t_23; + let _t_24; + let _t_25; + let _t_26; + let _t_27; + let _t_28; + let _t_29; + let _t_30; + let _t_31; + let _t_32; + let _t_33; + let _t_34; + await __env.enterProcedure(0, null); + try { + try { + _t_0 = {}; + _t_1 = await __env.invokeQuery("com.spotify2", { }, "song", _t_0, { projection: ["id", "artists", "album", "genres", "release_date", "popularity", "energy", "danceability"] }); + _t_2 = __builtin.getAsyncIterator(_t_1); + { + let _iter_tmp = await _t_2.next(); + while (!_iter_tmp.done) { + _t_3 = _iter_tmp.value; + _t_4 = _t_3[0]; + _t_5 = _t_3[1]; + _t_6 = _t_5.__response; + _t_7 = _t_5.id; + _t_8 = _t_5.artists; + _t_9 = _t_5.album; + _t_10 = _t_5.genres; + _t_11 = _t_5.release_date; + _t_12 = _t_5.popularity; + _t_13 = _t_5.energy; + _t_14 = _t_5.danceability; + _t_15 = false; + try { + _t_16 = {}; + _t_17 = new Array(1); + _t_18 = new Array(3); + _t_19 = "id"; + _t_18[0] = _t_19; + _t_20 = "=~"; + _t_18[1] = _t_20; + _t_21 = "lol"; + _t_18[2] = _t_21; + _t_17[0] = _t_18; + _t_22 = await __env.invokeQuery("com.spotify2", { }, "album", _t_16, { projection: ["id"], filter: _t_17 }); + _t_23 = __builtin.getAsyncIterator(_t_22); + { + let _iter_tmp = await _t_23.next(); + while (!_iter_tmp.done) { + _t_24 = _iter_tmp.value; + _t_25 = _t_24[0]; + _t_26 = _t_24[1]; + _t_27 = _t_26.__response; + _t_28 = _t_26.id; + _t_29 = _t_26.artists; + _t_30 = _t_26.release_date; + _t_31 = _t_26.popularity; + _t_33 = "lol"; + _t_32 = __builtin.like(_t_28, _t_33); + if (_t_32) { + _t_34 = {}; + _t_15 = true; + break; + } else { + + } + _iter_tmp = await _t_23.next(); + } + } + } catch(_exc_) { + __env.reportError("Failed to invoke query", _exc_); + } + if (_t_15) { + try { + await __env.output(String(_t_4), _t_5); + } catch(_exc_) { + __env.reportError("Failed to invoke action", _exc_); + } + } else { + + } + _iter_tmp = await _t_2.next(); + } + } + } catch(_exc_) { + __env.reportError("Failed to invoke query", _exc_); + } + } finally { + await __env.exitProcedure(0, null); + }`] + ], ]; // eslint-disable-next-line prefer-arrow-callback diff --git a/test/test_iteration_apis.js b/test/test_iteration_apis.js index 94bff3f6..7103adba 100644 --- a/test/test_iteration_apis.js +++ b/test/test_iteration_apis.js @@ -465,11 +465,11 @@ now => [food] of ((@uk.ac.cam.multiwoz.Restaurant.Restaurant()), true) => notify [`monitor( @security-camera.current_event()), (has_person == true && any(@org.thingpedia.builtin.thingengine.builtin.get_gps(), location == new Location(1, 2))) => notify;`, ['query: Invocation(Device(security-camera, , ), current_event, , )', - 'filter: External(Device(org.thingpedia.builtin.thingengine.builtin, , ), get_gps, , Atom(location, ==, Location(Absolute(1, 2, null))))'], + 'query: Invocation(Device(org.thingpedia.builtin.thingengine.builtin, , ), get_gps, , )'], [ 'Device(security-camera, , ) security-camera:current_event', 'Device(org.thingpedia.builtin.thingengine.builtin, , ) org.thingpedia.builtin.thingengine.builtin:get_gps', - 'Atom(location, ==, Location(Absolute(1, 2, null))) security-camera:current_event', + 'Atom(location, ==, Location(Absolute(1, 2, null))) org.thingpedia.builtin.thingengine.builtin:get_gps', 'Atom(has_person, ==, Boolean(true)) security-camera:current_event', ], [ diff --git a/test/test_syntax.tt b/test/test_syntax.tt index dc1db39e..34a3cbdb 100644 --- a/test/test_syntax.tt +++ b/test/test_syntax.tt @@ -1887,4 +1887,37 @@ now => @com.facebook.post(); // $context.result $dialogue @org.thingpedia.dialogue.transaction.execute; @com.thecatapi.get(); -@com.twitter.post_picture(picture_url=$context.result.picture_url : Entity(tt:picture)); \ No newline at end of file +@com.twitter.post_picture(picture_url=$context.result.picture_url : Entity(tt:picture)); + +==== + +// existential subquery +// simple invocation no filter +monitor(@com.twitter.home_timeline() filter any(@org.thingpedia.builtin.thingengine.builtin.get_gps())); + + +==== + +// existential subquery +// projection +monitor(@com.twitter.home_timeline() filter any([speed] of @org.thingpedia.builtin.thingengine.builtin.get_gps())); + +==== + +// existential subquery +// computation +monitor(@com.twitter.home_timeline() filter any([distance(location, new Location(1, 2))] of @org.thingpedia.builtin.thingengine.builtin.get_gps())); + +==== + +// existential subquery +// sort +monitor(@com.twitter.home_timeline() filter any(sort(file_size asc of @com.dropbox.list_folder()))); + +==== + +// ** expect TypeError ** +// existential subquery on action +monitor(@com.twitter.home_timeline() filter any(@com.twitter.post())); + +