Skip to content

Commit

Permalink
Merge pull request #310 from stanford-oval/wip/existential-subquery
Browse files Browse the repository at this point in the history
Add support for existential subquery
  • Loading branch information
Silei Xu authored Mar 1, 2021
2 parents ba10fa4 + 0a25e78 commit fbb5680
Show file tree
Hide file tree
Showing 18 changed files with 466 additions and 55 deletions.
2 changes: 1 addition & 1 deletion lib/ast/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import NodeVisitor from './visitor';

import type {
Invocation,
ExternalBooleanExpression,
ExternalBooleanExpression
} from './expression';
import type { Value } from './values';
import type {
Expand Down
156 changes: 154 additions & 2 deletions lib/ast/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -43,6 +43,7 @@ import {

import { TokenStream } from '../new-syntax/tokenstream';
import List from '../utils/list';
import { UnserializableError } from "../utils/errors";
import {
SyntaxPriority,
addParenthesis
Expand Down Expand Up @@ -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}
Expand All @@ -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;
Expand All @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 &&
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) &&
Expand Down Expand Up @@ -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<OldSlot, void> {
yield* this.subquery.iterateSlots(scope);
}

*iterateSlots2(schema : FunctionDef|null,
prim : InvocationLike|null,
scope : ScopeMap) : Generator<DeviceSelector|AbstractSlot, void> {
yield* this.subquery.iterateSlots2(scope);
}
}
BooleanExpression.ExistentialSubquery = ExistentialSubqueryBooleanExpression;
BooleanExpression.ExistentialSubquery.prototype.isExistentialSubquery = true;

/**
* A boolean expression that calls a Thingpedia query function
Expand All @@ -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
Expand Down Expand Up @@ -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) &&
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -1076,6 +1216,10 @@ export class TrueBooleanExpression extends BooleanExpression {
return List.singleton('true');
}

toLegacy() : TrueBooleanExpression {
return this;
}

equals(other : BooleanExpression) : boolean {
return this === other;
}
Expand Down Expand Up @@ -1123,6 +1267,10 @@ export class FalseBooleanExpression extends BooleanExpression {
return List.singleton('false');
}

toLegacy() : FalseBooleanExpression {
return this;
}

equals(other : BooleanExpression) : boolean {
return this === other;
}
Expand Down Expand Up @@ -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) &&
Expand Down
4 changes: 4 additions & 0 deletions lib/ast/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}



Expand Down
7 changes: 7 additions & 0 deletions lib/compiler/ast-to-ops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
14 changes: 14 additions & 0 deletions lib/compiler/ops-to-jsir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down
9 changes: 9 additions & 0 deletions lib/compiler/ops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
10 changes: 9 additions & 1 deletion lib/grammar.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _ ')' !'(' {
Expand Down
2 changes: 2 additions & 0 deletions lib/legacy-prettyprint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
6 changes: 2 additions & 4 deletions lib/new-syntax/parser.lr
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
Loading

0 comments on commit fbb5680

Please sign in to comment.