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:
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:
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:
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:
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);
}