jsoniq
Version:
JSONiq implementation for JavaScript
476 lines (418 loc) • 16.9 kB
text/typescript
import * as _ from "lodash";
import ASTNode from "./parsers/ASTNode";
import Position from "./parsers/Position";
import StaticContext from "./StaticContext";
import RootStaticContext from "./RootStaticContext";
import QName from "./QName";
import Variable from "./Variable";
import Marker from "./Marker";
import * as err from "./StaticErrors";
import * as war from "./StaticWarnings";
import Iterator from "../runtime/iterators/Iterator";
import ItemIterator from "../runtime/iterators/ItemIterator";
import AdditiveIterator from "../runtime/iterators/AdditiveIterator";
import RangeIterator from "../runtime/iterators/RangeIterator";
import SequenceIterator from "../runtime/iterators/SequenceIterator";
import MultiplicativeIterator from "../runtime/iterators/MultiplicativeIterator";
import VarRefIterator from "../runtime/iterators/VarRefIterator";
import ComparisonIterator from "../runtime/iterators/ComparisonIterator";
import ObjectIterator from "../runtime/iterators/ObjectIterator";
import PairIterator from "../runtime/iterators/PairIterator";
import ArrayIterator from "../runtime/iterators/ArrayIterator";
import SimpleMapExpr from "../runtime/iterators/SimpleMapExpr";
import UnaryExpr from "../runtime/iterators/UnaryExpr";
import ObjectLookupExpr from "../runtime/iterators/ObjectLookupExpr";
import FLWORIterator from "../runtime/iterators/flwor/FLWORIterator";
import ForIterator from "../runtime/iterators/flwor/ForIterator";
import LetIterator from "../runtime/iterators/flwor/LetIterator";
import WhereIterator from "../runtime/iterators/flwor/WhereIterator";
import OrderByIterator from "../runtime/iterators/flwor/OrderByIterator";
import ReturnIterator from "../runtime/iterators/flwor/ReturnIterator";
//TODO: remove this class
import Item from "../runtime/items/Item";
export default class Translator {
private ast: ASTNode;
private markers: Marker[] = [];
private iterators: Iterator[] = [];
private rootSctx: RootStaticContext;
private sctx: StaticContext;
constructor(rootSctx: RootStaticContext, ast: ASTNode) {
this.rootSctx = rootSctx;
this.sctx = rootSctx;
this.ast = ast;
}
resolveQName(value: string, pos: Position): QName {
var idx;
if (value.substring(0, 2) === "Q{") {
idx = value.indexOf("}");
return new QName("", value.substring(2, idx), value.substring(idx + 1));
} else {
idx = value.indexOf(":");
var prefix = value.substring(0, idx);
var qname = this.sctx.getNamespaceByPrefix(prefix);
if(!qname && prefix.length > 0) {
this.markers.push(new err.XPST0081(pos, prefix));
}
return new QName(prefix, qname ? qname.getURI() : "", value.substring(idx + 1));
}
}
private pushIt(it: Iterator): Translator {
this.iterators.push(it);
return this;
}
private popIt(): Iterator {
if(this.iterators.length === 0) {
throw new Error("Empty iterator statck.");
}
return this.iterators.pop();
}
private pushCtx(pos: Position): Translator {
this.sctx = this.sctx.createContext(pos);
return this;
}
private popCtx(pos: Position): Translator {
this.sctx.setPosition(
new Position(
this.sctx.getPosition().getStartLine(),
this.sctx.getPosition().getStartColumn(),
pos.getEndLine(),
pos.getEndColumn(),
pos.getFileName()
)
);
this.sctx.getParent().addVarRefs(this.sctx.getUnusedVarRefs());
this.sctx.getUnusedVariables().forEach((v: Variable) => {
if(v.getType() !== "GroupingVariable" && v.getType() !== "CatchVar") {
this.markers.push(new war.UnusedVariable(v));
}
});
this.sctx = this.sctx.getParent();
return this;
}
compile(): Iterator {
this.visit(this.ast);
//if iterators.lenght === 0
//TODO: [XPST0003] invalid expression: syntax error, unexpected end of file, the query body should not be empty
if(this.iterators.length !== 1) {
throw new Error("Invalid query plan.");
}
return this.iterators[0];
}
getMarkers(): Marker[] {
return this.markers;
}
VersionDecl(node: ASTNode): boolean {
return true;
}
NamespaceDecl(node: ASTNode): boolean {
var prefix = node.find(["NCName"]).toString();
var uri = node.find(["URILiteral"]).toString();
this.sctx.addNamespace(prefix, uri);
return true;
}
// Expr ::= ExprSingle ("," ExprSingle)*
Expr(node: ASTNode): boolean {
var exprs = node.find(["ExprSingle"]);
if(exprs.length > 1) {
var its = [];
exprs.forEach(expr => {
this.visit(expr);
its.push(this.popIt());
});
this.pushIt(new SequenceIterator(node.getPosition(), its));
return true;
}
return false;
}
//FLWORExpr ::= InitialClause IntermediateClause* ReturnClause
FLWORExpr(node: ASTNode): boolean {
//this.pushCtx(node.getPosition());
var clauses = [];
var children = node.getChildren().filter(node => { return node.getName() !== "WS"; });
for(var i = 0; i < children.length; i++) {
this.visit(children[i]);
clauses.push(this.popIt());
}
this.pushIt(new FLWORIterator(node.getPosition(), clauses));
for(var i = 0; i < children.length - 1; i++) {
this.popCtx(node.getPosition());
}
//this.popCtx(node.getPosition());
return true;
}
//ForBinding ::= "$" VarName TypeDeclaration? AllowingEmpty? PositionalVar? "in" ExprSingle
ForBinding(node: ASTNode): boolean {
this.visitChildren(node);
this.pushCtx(node.getPosition());
var varName = node.find(["VarName"])[0].toString();
var allowingEmpty = node.find(["AllowingEmpty"])[0] !== undefined;
var pos = node.find(["PositionalVar"])[0];
var posVarName;
if(pos) {
posVarName = pos.find(["VarName"])[0].toString();
}
this.pushIt(new ForIterator(node.getPosition(), varName, allowingEmpty, posVarName, this.popIt()));
return true;
}
//LetBinding ::= ( '$' VarName TypeDeclaration? | FTScoreVar ) ':=' ExprSingle
LetBinding(node: ASTNode): boolean {
this.visitChildren(node);
this.pushCtx(node.getPosition());
var v = node.find(["VarName"])[0];
var qname = this.resolveQName(v.toString(), v.getPosition());
var variable = new Variable(v.getPosition(), "LetBinding", qname);
var overrides = this.sctx.getVariable(variable) !== undefined;
this.sctx.addVariable(variable);
this.pushIt(new LetIterator(node.getPosition(), v.toString(), this.popIt(), overrides));
return true;
}
WhereClause(node: ASTNode): boolean {
this.visitChildren(node);
this.pushCtx(node.getPosition());
this.pushIt(new WhereIterator(node.getPosition(), this.popIt()));
return true;
}
//OrderByClause ::= (("order" "by") | ("stable" "order" "by")) OrderSpecList
OrderByClause(node: ASTNode): boolean {
this.pushCtx(node.getPosition());
var orderSpecs: { expr: Iterator; ascending: boolean; emptyGreatest: boolean }[] = [];
var specs: ASTNode[] = node.find(["OrderSpecList"])[0].getChildren();
_.chain<ASTNode[]>(specs)
.filter((spec: ASTNode) => {
return spec.getName() === "OrderSpec";
})
.forEach((spec: ASTNode) => {
this.visitChildren(spec);
orderSpecs.push({
expr: this.popIt(),
ascending: spec.find(["OrderModifier"])[0].toString().indexOf("ascending") !== -1,
emptyGreatest: spec.find(["OrderModifier"])[0].toString().indexOf("empty greatest") !== -1
});
});
this.pushIt(new OrderByIterator(node.getPosition(), false, orderSpecs));
return true;
}
ReturnClause(node: ASTNode): boolean {
this.visitChildren(node);
this.pushIt(new ReturnIterator(node.getPosition(), this.popIt()));
return true;
}
//PostfixExpr ::= PrimaryExpr ( Predicate | ArgumentList | ObjectLookup | ArrayLookup | ArrayUnboxing )*
PostfixExpr(node: ASTNode): boolean {
var primary = node.find(["PrimaryExpr"]);
this.visit(primary[0]);
var it = this.popIt();
var names = ["Predicate", "ArgumentList", "ObjectLookup", "ArrayLookup", "ArrayUnboxing"];
var exprs = [];
names.forEach(name => {
exprs = exprs.concat(node.find([name]));
});
exprs.forEach(expr => {
this.visit(expr);
//ObjectLookup ::= "." ( StringLiteral | NCName | ParenthesizedExpr | VarRef | ContextItemExpr )
if(expr.getName() === "ObjectLookup") {
var name = expr.find(["NCName"]);
if(name.length > 0) {
it = new ObjectLookupExpr(node.getPosition(), it, new ItemIterator(name[0].getPosition(), new Item(name[0].toString())));
} else {
it = new ObjectLookupExpr(node.getPosition(), it, this.popIt());
}
}
});
this.pushIt(it);
return true;
}
//ObjectLookup ::= "." ( StringLiteral | NCName | ParenthesizedExpr | VarRef | ContextItemExpr )
//ArrayLookup ::= '[' '[' Expr ']' ']'
//ArrayUnboxing ::= '[' ']'
//ArgumentList ::= '(' ( Argument ( ',' Argument )* )? ')'
//Predicate ::= '[' Expr ']'
//StringConcatExpr ::= RangeExpr ( '||' RangeExpr )*
/*
StringConcatExpr(node: ASTNode): boolean {
var exprs =
return false;
}
*/
//ParenthesizedExpr ::= "(" Expr? ")"
ParenthesizedExpr(node: ASTNode): boolean {
if(node.find(["Expr"]).length === 0) {
this.pushIt(new SequenceIterator(node.getPosition(), []));
return true;
}
return false;
}
VarRef(node: ASTNode): boolean {
var varName = node.find(["VarName"])[0].toString();
this.sctx.addVarRef(this.resolveQName(varName, node.getPosition()));
this.pushIt(new VarRefIterator(node.getPosition(), varName));
return true;
}
ContextItemExpr(node: ASTNode): boolean {
this.sctx.addVarRef(this.resolveQName("$", node.getPosition()));
this.pushIt(new VarRefIterator(node.getPosition(), "$"));
return true;
}
//RangeExpr ::= AdditiveExpr ( "to" AdditiveExpr )?
RangeExpr(node: ASTNode): boolean {
var exprs = node.find(["AdditiveExpr"]);
if(exprs.length > 1) {
this.visitChildren(node);
var to = this.popIt();
var from = this.popIt();
this.iterators.push(new RangeIterator(node.getPosition(), from, to));
return true;
}
return false;
}
//AdditiveExpr ::= MultiplicativeExpr ( ( '+' | '-' ) MultiplicativeExpr )*
AdditiveExpr(node: ASTNode): boolean {
var exprs = node.find(["MultiplicativeExpr"]);
var ops = node.find(["TOKEN"]);
if(exprs.length > 1) {
this.visit(exprs[0]);
var it = this.popIt();
for(var i = 1; i < exprs.length; i++) {
this.visit(exprs[i]);
it = new AdditiveIterator(node.getPosition(), it, this.popIt(), ops.splice(0, 1)[0].getValue() === "+");
}
this.pushIt(it);
return true;
}
return false;
}
//MultiplicativeExpr ::= UnionExpr ( ( '*' | 'div' | 'idiv' | 'mod' ) UnionExpr )*
MultiplicativeExpr(node: ASTNode): boolean {
var exprs = node.find(["UnionExpr"]);
var ops = node.find(["TOKEN"]);
if(exprs.length > 1) {
this.visit(exprs[0]);
var it = this.popIt();
for(var i = 1; i < exprs.length; i++) {
this.visit(exprs[i]);
it = new MultiplicativeIterator(node.getPosition(), it, this.popIt(), ops.splice(0, 1)[0].getValue());
}
this.pushIt(it);
return true;
}
return false;
}
// ComparisonExpr ::= FTContainsExpr ( (ValueComp | GeneralComp | NodeComp) FTContainsExpr )?
ComparisonExpr(node: ASTNode): boolean {
var exprs = node.find(["FTContainsExpr"]);
if(exprs.length > 1) {
this.visitChildren(node);
var right = this.popIt();
var left = this.popIt();
var comp = node.find(["ValueComp"]).toString();
comp = comp === "" ? node.find(["GeneralComp"]).toString() : comp;
comp = comp === "" ? node.find(["NodeComp"]).toString() : comp;
this.pushIt(new ComparisonIterator(node.getPosition(), left, right, comp));
return true;
}
return false;
}
BlockExpr(node: ASTNode): boolean {
var oldLength = this.iterators.length;
this.visitChildren(node);
if(this.iterators.length === oldLength) {
this.pushIt(new ObjectIterator(node.getPosition(), []));
}
return true;
}
//RelativePathExpr ::= PostfixExpr ( ( '/' | '//' | '!' ) StepExpr )*
RelativePathExpr(node: ASTNode): boolean {
var exprs = node.find(["PostfixExpr"]).concat(node.find(["StepExpr"]));
if(exprs.length > 1) {
this.visit(exprs[0]);
var it = this.popIt();
for(var i = 1; i < exprs.length; i++) {
this.visit(exprs[i]);
it = new SimpleMapExpr(node.getPosition(), it, this.popIt());
}
this.pushIt(it);
return true;
}
return false;
}
//UnaryExpr ::= ( '-' | '+' )* ValueExpr
UnaryExpr(node: ASTNode): boolean {
var ops = node.find(["TOKEN"]);
if(ops.length > 0) {
this.visitChildren(node);
this.pushIt(new UnaryExpr(node.getPosition(), ops.map(op => { return op.getValue(); }), this.popIt()));
return true;
}
return false;
}
ObjectConstructor(node: ASTNode): boolean {
var l = this.iterators.length;
this.visitChildren(node);
this.pushIt(new ObjectIterator(node.getPosition(), this.iterators.splice(l)));
return true;
}
//ArrayConstructor
ArrayConstructor(node: ASTNode): boolean {
this.visitChildren(node);
this.pushIt(new ArrayIterator(node.getPosition(), this.popIt()));
return true;
}
PairConstructor(node: ASTNode): boolean {
this.visitChildren(node);
var value = this.popIt();
var key;
if(node.find(["NCName"])[0]) {
key = new ItemIterator(node.getPosition(), new Item(node.find(["NCName"])[0].toString()));
} else {
key = this.popIt();
}
this.pushIt(new PairIterator(node.getPosition(), key, value));
return true;
}
DecimalLiteral(node: ASTNode): boolean {
var item = new Item(parseFloat(node.toString()));
this.pushIt(new ItemIterator(node.getPosition(), item));
return true;
}
DoubleLiteral(node: ASTNode): boolean {
var item = new Item(parseFloat(node.toString()));
this.pushIt(new ItemIterator(node.getPosition(), item));
return true;
}
IntegerLiteral(node: ASTNode): boolean {
var item = new Item(parseInt(node.toString(), 10));
this.pushIt(new ItemIterator(node.getPosition(), item));
return true;
}
StringLiteral(node: ASTNode): boolean {
var val = node.toString();
val = val.substring(1, val.length - 1);
this.pushIt(new ItemIterator(node.getPosition(), new Item(val)));
return true;
}
BooleanLiteral(node: ASTNode): boolean {
this.pushIt(new ItemIterator(node.getPosition(), new Item(node.toString() === "true")));
return true;
}
NullLiteral(node: ASTNode): boolean {
this.pushIt(new ItemIterator(node.getPosition(), new Item(null)));
return true;
}
visit(node: ASTNode): Translator {
var name = node.getName();
var skip = false;
if (typeof this[name] === "function") {
skip = this[name](node) === true;
}
if (!skip) {
this.visitChildren(node);
}
return this;
}
visitChildren(node: ASTNode): Translator {
node.getChildren().forEach(child => {
this.visit(child);
});
return this;
}
}