Query Expression Sequence (QES)
Richard Andrew Cattermole (Rikki)
richard at cattermole.co.nz
Sat Nov 23 16:44:12 UTC 2024
As a design I was inspired from C#'s Linq, it is not tied to a
specific library, it is available based upon the context
expression.
It should not require AST modifications and can be implemented
purely in the parser, with lowerings into the library code as per
the context expression.
I do not believe this should continue, due to a statement by Adam
Wilson regarding the language integrations of Linq being less
used today.
However the basic design is described here.
Some examples:
```d
Database db;
Book book;
Person[] authors = book -> Person {
on: db,
where: book.authors
};
authors = (book, mydb: db) -> Person {
on: mydb,
where book.authors
};
ulong bannedCount = authors -> {
on: db,
where: !authors.cool && authors.banned
}.count;
bannedCount = authors -> ulong {
on: db,
where: !(authors.cool || authors.profit > 1_000_000) &&
authors.banned,
result: authors as count
};
Person[] allAuthors = db -> Person {
from: Person,
where: Person in -> Person {
result: Book.authors
}
};
Person[] allAliveAuthors = db -> Person {
from: Book.authors as authors,
where: authors if alive,
result: authors as unique
};
QESCompiledRef booksForAuthorRef = db -> Book (Person person) {
from: Book as book,
where: person in book.authors
}.compile();
auto builder = booksForAuthorRef();
builder["person"] = ...;
Book[] booksForAuthor = builder();
```
Grammar:
```diff
OrOrExpression:
+ QESStart QESExpressionContinue
+ QESExpressionContinue:
+ "->" Type|opt QESExpressionParams|opt '{' QESControlBody '}'
+ QESExpressionParams:
+ QESExpressionParams ',' QESExpressionParams|opt
+ Type Identifier
+ QESStart:
+ Identifier
+ Tuple
+ QESControlBody:
+ QESControlBody ',' QESControlBody|opt
+ Identifier ':' QESControlExpression
+ QESControlExpression:
+ QESControlExpression QESBinaryOp QESControlExpression
+ QESControlExpression "if" QESControlCall
+ QESControlExpression "as" QESControlCall
+ '(' QESControlExpression ')'
+ '-' QESControlExpression
+ '!' QESControlValue
+ QESControlValue
+ QESBinaryOp:
+ "&&"
+ "||"
+ "in"
+ '!' "in"
+ "=="
+ "!="
+ ">="
+ '>'
+ "<="
+ '<'
+ '+'
+ '-'
+ '*'
+ '-'
+ '*'
+ '/'
+ '%'
+ '~'
+ QESControlCall:
+ Identifier '(' QESControlCallArguments ')'
+ Identifier
+ QESControlCallArguments:
+ QESControlCallArguments ',' QESControlCallArguments|opt
+ QESControlExpression
+ QESControlValue:
+ QESExpressionOp
+ IdentifierList
+ QESVariable
+ QESControlLiteral
+ '\' '(' Expression ')'
+ Expression
+ QESVariable:
+ '$' Identifier
+ QESExpressionOp:
+ QESExpression '.' Identifier
+ QESExpression
+ QESExpressionContinue '.' Identifier
+ QESExpressionContinue
+ QESControlLiteral:
+ StringLiteral
+ CharacterLiteral
+ IntegerLiteral
+ FloatLiteral
```
A simplified example of a library type:
```d
struct QESControlState(ResultType, alias Types, alias
ParameterizedVariableTypes) {
Types context;
string[] contextNames, parameterizedVariableNames;
@disable(__compilerOnly, "User code should not know about the
control state of a QES")
static (QESControlRef, QESControlState*) allocate(Types context,
string[] contextNames, string[] parameterizedVariableNames) {
QESControlRef ret = QESControlRef.allocate;
ret.state.context = context;
ret.state.contextNames = contextNames;
ret.state.parameterizedVariableNames =
parameterizedVariableNames;
return (ret, ret.state);
}
@disable(__compilerOnly, "User code should not be interacting
with the parse tree of a QES") {
// QESControlBody
ParseTree* control(string identifier);
// QESControlCall
ParseTree* queryOp(ResultType,
Types...)(QESControlState!(ResultType, Types)* other, string
op=null);
ParseTree* literal(LiteralType)(LiteralType value);
ParseTree* expression(ExpressionType)(ExpressionType value);
}
static if (is(ResultType == void) {
} else {
alias getResult this;
// Your ``getResult`` may return a wrapped slice, i.e.
``DynamicArray!ResultType``
ResultType[] getResult();
QESCompiledRef compile();
}
ulong count();
bool any();
bool empty();
struct QESCompiledRef {
QESBuilderRef opCall();
}
struct QESBuilderRef {
ParameterizedVariableTypes parameterizedVariables;
void opIndexAssign(PType)(PType value, string
parameterizedVariable);
static if (is(ResultType == void) {
} else {
alias getResult this;
// Your ``getResult`` may return a wrapped slice, i.e.
``DynamicArray!ResultType``
ResultType[] getResult();
}
ulong count();
bool any();
bool empty();
}
struct ParseTree {
...
}
}
```
What the parse tree type should look like:
```d
struct ParseTree {
ParseTree* negateTruthiness();
ParseTree* negateNumber();
ParseTree* asOp(string op);
ParseTree* checkOp(string op, ParseTree*[] arguments...);
ParseTree* identifierOp(immutable(string[]) identifiers);
ParseTree* and(ParseTree* rhs);
ParseTree* or(ParseTree* rhs);
ParseTree* equals(bool negate, ParseTree* rhs);
ParseTree* moreThan(bool orEqualsTo, ParseTree* rhs);
ParseTree* lessThan(bool orEqualsTo, ParseTree* rhs);
ParseTree* condition(ParseTree* truthy, ParseTree* falsey);
ParseTree* testInQuery(bool negate, ParseTree* query);
ParseTree* addition(ParseTree* rhs);
ParseTree* subtract(ParseTree* rhs);
ParseTree* multiply(ParseTree* rhs);
ParseTree* divide(ParseTree* rhs);
ParseTree* modulas(ParseTree* rhs);
ParseTree* append(ParseTree* rhs);
}
```
More information about the dip.ideas
mailing list