trellis
Version:
Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications
518 lines (514 loc) • 15 kB
JavaScript
import {
QueryEngine,
literal,
variable
} from "./chunk-BYTAOXGW.js";
// src/core/query/parser.ts
function tokenize(input) {
const tokens = [];
let i = 0;
while (i < input.length) {
if (/\s/.test(input[i])) {
i++;
continue;
}
if (input[i] === "/" && input[i + 1] === "/") {
while (i < input.length && input[i] !== "\n") i++;
continue;
}
const pos = i;
if ("[](){},:".includes(input[i])) {
tokens.push({ kind: "symbol", value: input[i], pos });
i++;
continue;
}
if (input[i] === "!" && input[i + 1] === "=") {
tokens.push({ kind: "symbol", value: "!=", pos });
i += 2;
continue;
}
if (input[i] === "<" && input[i + 1] === "=") {
tokens.push({ kind: "symbol", value: "<=", pos });
i += 2;
continue;
}
if (input[i] === ">" && input[i + 1] === "=") {
tokens.push({ kind: "symbol", value: ">=", pos });
i += 2;
continue;
}
if ("<>=".includes(input[i])) {
tokens.push({ kind: "symbol", value: input[i], pos });
i++;
continue;
}
if (input[i] === '"') {
i++;
let s = "";
while (i < input.length && input[i] !== '"') {
if (input[i] === "\\" && i + 1 < input.length) {
s += input[i + 1];
i += 2;
} else {
s += input[i];
i++;
}
}
if (i < input.length) i++;
tokens.push({ kind: "string", value: s, pos });
continue;
}
if (/[0-9]/.test(input[i]) || input[i] === "-" && i + 1 < input.length && /[0-9]/.test(input[i + 1])) {
let n = input[i];
i++;
while (i < input.length && /[0-9.]/.test(input[i])) {
n += input[i];
i++;
}
tokens.push({ kind: "number", value: n, pos });
continue;
}
if (/[?a-zA-Z_]/.test(input[i])) {
let w = "";
while (i < input.length && /[?a-zA-Z0-9_.:/-]/.test(input[i])) {
w += input[i];
i++;
}
tokens.push({ kind: "word", value: w, pos });
continue;
}
i++;
}
tokens.push({ kind: "eof", value: "", pos: input.length });
return tokens;
}
var Parser = class {
tokens;
pos = 0;
constructor(tokens) {
this.tokens = tokens;
}
peek() {
return this.tokens[this.pos];
}
advance() {
return this.tokens[this.pos++];
}
expect(kind, value) {
const t = this.advance();
if (t.kind !== kind || value !== void 0 && t.value !== value) {
throw new Error(`Expected ${kind}${value ? ` "${value}"` : ""} at pos ${t.pos}, got ${t.kind} "${t.value}"`);
}
return t;
}
match(kind, value) {
const t = this.peek();
if (t.kind === kind && (value === void 0 || t.value === value)) {
this.pos++;
return true;
}
return false;
}
isAt(kind, value) {
const t = this.peek();
return t.kind === kind && (value === void 0 || t.value === value);
}
// -----------------------------------------------------------------------
// Terms
// -----------------------------------------------------------------------
parseTerm() {
const t = this.peek();
if (t.kind === "word" && t.value.startsWith("?")) {
this.advance();
return variable(t.value.slice(1));
}
if (t.kind === "string") {
this.advance();
return literal(t.value);
}
if (t.kind === "number") {
this.advance();
const n = Number(t.value);
return literal(n);
}
if (t.kind === "word") {
const v = t.value;
this.advance();
if (v === "true") return literal(true);
if (v === "false") return literal(false);
return literal(v);
}
throw new Error(`Unexpected token at pos ${t.pos}: ${t.kind} "${t.value}"`);
}
// -----------------------------------------------------------------------
// Patterns
// -----------------------------------------------------------------------
parsePattern() {
const t = this.peek();
if (t.kind === "word" && t.value.toUpperCase() === "NOT") {
this.advance();
const inner = this.parsePattern();
return { kind: "not", pattern: inner };
}
if (t.kind === "word" && t.value.toUpperCase() === "OR") {
this.advance();
const branches = [];
while (this.isAt("symbol", "{")) {
this.advance();
const branch = [];
while (!this.isAt("symbol", "}") && !this.isAt("eof", void 0)) {
branch.push(this.parsePattern());
}
this.expect("symbol", "}");
branches.push(branch);
}
return { kind: "or", branches };
}
if (t.kind === "symbol" && t.value === "[") {
this.advance();
const entity = this.parseTerm();
const attribute = this.parseTerm();
const value = this.parseTerm();
this.expect("symbol", "]");
return { kind: "fact", entity, attribute, value };
}
if (t.kind === "symbol" && t.value === "(") {
this.advance();
const source = this.parseTerm();
const attribute = this.parseTerm();
const target = this.parseTerm();
this.expect("symbol", ")");
return { kind: "link", source, attribute, target };
}
if (t.kind === "word" && !t.value.startsWith("?")) {
const name = this.advance().value;
if (this.isAt("symbol", "(")) {
this.advance();
const args = [];
while (!this.isAt("symbol", ")") && !this.isAt("eof", void 0)) {
args.push(this.parseTerm());
this.match("symbol", ",");
}
this.expect("symbol", ")");
return { kind: "rule", name, args };
}
throw new Error(`Expected '(' after rule name "${name}" at pos ${t.pos}`);
}
throw new Error(`Cannot parse pattern at pos ${t.pos}: ${t.kind} "${t.value}"`);
}
// -----------------------------------------------------------------------
// Filter
// -----------------------------------------------------------------------
parseFilter() {
const left = this.parseTerm();
const op = this.advance().value;
const right = this.parseTerm();
return { kind: "filter", left, op, right };
}
// -----------------------------------------------------------------------
// Full Query
// -----------------------------------------------------------------------
parseQuery() {
const query = {
select: [],
where: [],
filters: [],
aggregates: [],
orderBy: [],
limit: 0,
offset: 0
};
while (!this.isAt("eof", void 0)) {
const kw = this.peek();
if (kw.kind !== "word") {
throw new Error(`Expected keyword at pos ${kw.pos}, got ${kw.kind} "${kw.value}"`);
}
switch (kw.value.toUpperCase()) {
case "SELECT": {
this.advance();
while (this.peek().kind === "word" && this.peek().value.startsWith("?")) {
query.select.push(this.advance().value.slice(1));
}
break;
}
case "WHERE": {
this.advance();
this.expect("symbol", "{");
while (!this.isAt("symbol", "}") && !this.isAt("eof", void 0)) {
query.where.push(this.parsePattern());
}
this.expect("symbol", "}");
break;
}
case "FILTER": {
this.advance();
query.filters.push(this.parseFilter());
break;
}
case "AGGREGATE": {
this.advance();
const op = this.advance().value;
this.expect("symbol", "(");
const varName = this.advance().value;
const varClean = varName.startsWith("?") ? varName.slice(1) : varName;
this.expect("symbol", ")");
this.expect("word", "AS");
const asName = this.advance().value;
const asClean = asName.startsWith("?") ? asName.slice(1) : asName;
query.aggregates.push({ op, variable: varClean, as: asClean });
break;
}
case "ORDER": {
this.advance();
this.expect("word", "BY");
while (this.peek().kind === "word" && this.peek().value.startsWith("?")) {
const v = this.advance().value.slice(1);
let dir = "asc";
if (this.peek().kind === "word" && ["ASC", "DESC"].includes(this.peek().value.toUpperCase())) {
dir = this.advance().value.toLowerCase();
}
query.orderBy.push({ variable: v, direction: dir });
}
break;
}
case "LIMIT": {
this.advance();
query.limit = Number(this.expect("number").value);
break;
}
case "OFFSET": {
this.advance();
query.offset = Number(this.expect("number").value);
break;
}
default:
throw new Error(`Unknown keyword "${kw.value}" at pos ${kw.pos}`);
}
}
return query;
}
// -----------------------------------------------------------------------
// Datalog Rule
// -----------------------------------------------------------------------
parseRule() {
const name = this.expect("word").value;
this.expect("symbol", "(");
const params = [];
while (!this.isAt("symbol", ")") && !this.isAt("eof", void 0)) {
const v = this.expect("word").value;
params.push(v.startsWith("?") ? v.slice(1) : v);
this.match("symbol", ",");
}
this.expect("symbol", ")");
if (this.isAt("symbol", ":")) {
this.advance();
if (this.peek().kind === "number" && this.peek().value.startsWith("-")) {
this.advance();
}
} else if (this.isAt("word", ":-")) {
this.advance();
}
const body = [];
const filters = [];
while (!this.isAt("eof", void 0)) {
if (this.peek().kind === "word" && this.peek().value.toUpperCase() === "FILTER") {
this.advance();
filters.push(this.parseFilter());
} else {
body.push(this.parsePattern());
}
this.match("symbol", ",");
}
return { name, params, body, filters };
}
};
function parseQuery(input) {
const tokens = tokenize(input);
return new Parser(tokens).parseQuery();
}
function parseRule(input) {
const tokens = tokenize(input);
return new Parser(tokens).parseRule();
}
function parseSimple(input) {
const trimmed = input.trim();
const upper = trimmed.toUpperCase();
if (upper.startsWith("SELECT") || upper.startsWith("WHERE")) {
return parseQuery(trimmed);
}
const findMatch = trimmed.match(/^find\s+(.+?)\s+where\s+(.+)$/i);
if (findMatch) {
const vars = findMatch[1].trim().split(/\s+/);
const conditions = findMatch[2].trim();
const selectVars = vars.map((v) => v.startsWith("?") ? v : `?${v}`);
const entity = selectVars[0];
const parts = conditions.split(/\s+and\s+/i);
const patterns = [];
const filters = [];
for (const part of parts) {
const eqMatch = part.match(/^(\S+)\s*(=|!=|<|<=|>|>=|contains|startsWith|endsWith|matches)\s*(.+)$/);
if (eqMatch) {
const [, attr, op, val] = eqMatch;
const valTrimmed = val.trim();
if (op === "=") {
patterns.push(`[${entity} "${attr}" ${valTrimmed}]`);
} else {
const tmpVar = `?_${attr.replace(/[^a-zA-Z0-9]/g, "_")}`;
patterns.push(`[${entity} "${attr}" ${tmpVar}]`);
filters.push(`FILTER ${tmpVar} ${op} ${valTrimmed}`);
}
}
}
const fullQuery = `SELECT ${selectVars.join(" ")}
WHERE {
${patterns.join("\n ")}
}
${filters.join("\n")}`;
return parseQuery(fullQuery);
}
throw new Error(`Cannot parse query: "${trimmed}". Use full EQL-S syntax or "find ?e where attr = value".`);
}
// src/core/query/datalog.ts
function transitiveClosureRules(ruleName, linkAttribute) {
return [
// Base case: direct link
{
name: ruleName,
params: ["x", "y"],
body: [
{
kind: "link",
source: variable("x"),
attribute: literal(linkAttribute),
target: variable("y")
}
],
filters: []
},
// Recursive case: indirect via intermediate
{
name: ruleName,
params: ["x", "y"],
body: [
{
kind: "link",
source: variable("x"),
attribute: literal(linkAttribute),
target: variable("z")
},
{
kind: "rule",
name: ruleName,
args: [variable("z"), variable("y")]
}
],
filters: []
}
];
}
function reverseReachabilityRules(ruleName, linkAttribute) {
return [
{
name: ruleName,
params: ["x", "y"],
body: [
{
kind: "link",
source: variable("y"),
attribute: literal(linkAttribute),
target: variable("x")
}
],
filters: []
},
{
name: ruleName,
params: ["x", "y"],
body: [
{
kind: "link",
source: variable("z"),
attribute: literal(linkAttribute),
target: variable("x")
},
{
kind: "rule",
name: ruleName,
args: [variable("z"), variable("y")]
}
],
filters: []
}
];
}
function siblingRules(ruleName, linkAttribute) {
return [
{
name: ruleName,
params: ["a", "b"],
body: [
{
kind: "link",
source: variable("a"),
attribute: literal(linkAttribute),
target: variable("parent")
},
{
kind: "link",
source: variable("b"),
attribute: literal(linkAttribute),
target: variable("parent")
}
],
filters: [
{
kind: "filter",
left: variable("a"),
op: "!=",
right: variable("b")
}
]
}
];
}
var DatalogRuntime = class {
engine;
constructor(store) {
this.engine = new QueryEngine(store);
}
/** Register a Datalog rule (or multiple). */
addRule(rule) {
this.engine.addRule(rule);
}
addRules(rules) {
for (const r of rules) this.engine.addRule(r);
}
removeRule(name) {
this.engine.removeRule(name);
}
/** Register built-in transitive closure for a link attribute. */
registerTransitiveClosure(ruleName, linkAttribute) {
this.addRules(transitiveClosureRules(ruleName, linkAttribute));
}
/** Register built-in reverse reachability for a link attribute. */
registerReverseReachability(ruleName, linkAttribute) {
this.addRules(reverseReachabilityRules(ruleName, linkAttribute));
}
/** Register built-in sibling rule for a link attribute. */
registerSiblings(ruleName, linkAttribute) {
this.addRules(siblingRules(ruleName, linkAttribute));
}
/** Get the underlying QueryEngine for direct query execution. */
getEngine() {
return this.engine;
}
};
export {
parseQuery,
parseRule,
parseSimple,
transitiveClosureRules,
reverseReachabilityRules,
siblingRules,
DatalogRuntime
};