meta-log-db
Version:
Native database package for Meta-Log (ProLog, DataLog, R5RS)
1,424 lines (1,414 loc) • 230 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.MetaLogDbBrowser = {}));
})(this, (function (exports) { 'use strict';
/**
* Unification algorithm for ProLog
*/
class Unification {
/**
* Unify two terms
*/
static unify(term1, term2, bindings = new Map()) {
// If terms are identical
if (term1 === term2) {
return bindings;
}
// If term1 is a variable
if (this.isVariable(term1)) {
const value = bindings.get(term1);
if (value !== undefined) {
return this.unify(value, term2, bindings);
}
bindings.set(term1, term2);
return bindings;
}
// If term2 is a variable
if (this.isVariable(term2)) {
const value = bindings.get(term2);
if (value !== undefined) {
return this.unify(term1, value, bindings);
}
bindings.set(term2, term1);
return bindings;
}
// If both are arrays (compound terms)
if (Array.isArray(term1) && Array.isArray(term2)) {
if (term1.length !== term2.length) {
return null;
}
for (let i = 0; i < term1.length; i++) {
const result = this.unify(term1[i], term2[i], bindings);
if (result === null) {
return null;
}
bindings = result;
}
return bindings;
}
// If both are objects
if (typeof term1 === 'object' && typeof term2 === 'object' && term1 !== null && term2 !== null) {
const keys1 = Object.keys(term1);
const keys2 = Object.keys(term2);
if (keys1.length !== keys2.length) {
return null;
}
for (const key of keys1) {
if (!keys2.includes(key)) {
return null;
}
const result = this.unify(term1[key], term2[key], bindings);
if (result === null) {
return null;
}
bindings = result;
}
return bindings;
}
// Terms don't unify
return null;
}
/**
* Check if term is a variable (starts with ?)
*/
static isVariable(term) {
return typeof term === 'string' && term.startsWith('?');
}
/**
* Apply bindings to a term
*/
static applyBindings(term, bindings) {
if (this.isVariable(term)) {
return bindings.get(term) || term;
}
if (Array.isArray(term)) {
return term.map(t => this.applyBindings(t, bindings));
}
if (typeof term === 'object' && term !== null) {
const result = {};
for (const [key, value] of Object.entries(term)) {
result[key] = this.applyBindings(value, bindings);
}
return result;
}
return term;
}
/**
* Merge bindings
*/
static mergeBindings(bindings1, bindings2) {
const merged = new Map(bindings1);
for (const [key, value] of bindings2) {
const existing = merged.get(key);
if (existing !== undefined) {
const unified = this.unify(existing, value, new Map(merged));
if (unified === null) {
return null;
}
merged.set(key, unified.get(key) || value);
}
else {
merged.set(key, value);
}
}
return merged;
}
}
/**
* SLD Resolution for ProLog
*/
class Resolution {
/**
* Resolve a goal against facts and rules
*/
static resolve(goal, facts, rules, bindings = new Map()) {
const results = [];
// Parse goal
const goalTerm = this.parseTerm(goal);
// Try to match against facts
for (const fact of facts) {
const factTerm = { predicate: fact.predicate, args: fact.args };
const unified = Unification.unify(goalTerm, factTerm, new Map(bindings));
if (unified !== null) {
results.push(unified);
}
}
// Try to match against rules
for (const rule of rules) {
const ruleHead = this.parseTerm(rule.head);
const unified = Unification.unify(goalTerm, ruleHead, new Map(bindings));
if (unified !== null) {
// Resolve body goals
const bodyResults = this.resolveBody(rule.body, facts, rules, unified);
results.push(...bodyResults);
}
}
return results;
}
/**
* Resolve body goals
*/
static resolveBody(body, facts, rules, bindings) {
if (body.length === 0) {
return [bindings];
}
const [firstGoal, ...restGoals] = body;
const firstResults = this.resolve(firstGoal, facts, rules, bindings);
const allResults = [];
for (const result of firstResults) {
const restResults = this.resolveBody(restGoals, facts, rules, result);
allResults.push(...restResults);
}
return allResults;
}
/**
* Parse a ProLog term string into structured format
*/
static parseTerm(termStr) {
const trimmed = termStr.trim();
// Simple predicate(args) format
const match = trimmed.match(/^(\w+)\((.*)\)$/);
if (match) {
const predicate = match[1];
const argsStr = match[2];
const args = this.parseArgs(argsStr);
return { predicate, args };
}
// Simple predicate format
if (/^\w+$/.test(trimmed)) {
return { predicate: trimmed, args: [] };
}
// Variable
if (trimmed.startsWith('?')) {
return trimmed;
}
// Fallback: treat as string
return { predicate: trimmed, args: [] };
}
/**
* Parse arguments from string
*/
static parseArgs(argsStr) {
if (!argsStr.trim()) {
return [];
}
const args = [];
let current = '';
let depth = 0;
for (const char of argsStr) {
if (char === '(') {
depth++;
current += char;
}
else if (char === ')') {
depth--;
current += char;
}
else if (char === ',' && depth === 0) {
args.push(this.parseArg(current.trim()));
current = '';
}
else {
current += char;
}
}
if (current.trim()) {
args.push(this.parseArg(current.trim()));
}
return args;
}
/**
* Parse a single argument
*/
static parseArg(argStr) {
const trimmed = argStr.trim();
// Variable
if (trimmed.startsWith('?')) {
return trimmed;
}
// Number
if (/^-?\d+$/.test(trimmed)) {
return parseInt(trimmed, 10);
}
if (/^-?\d+\.\d+$/.test(trimmed)) {
return parseFloat(trimmed);
}
// String (quoted)
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
return trimmed.slice(1, -1);
}
// Compound term
if (trimmed.includes('(')) {
return this.parseTerm(trimmed);
}
// Atom
return trimmed;
}
}
/**
* ProLog Engine for Meta-Log Database
*/
class PrologEngine {
constructor() {
this.facts = [];
this.rules = [];
}
/**
* Add facts to the database
*/
addFacts(facts) {
this.facts.push(...facts);
}
/**
* Add a rule to the database
*/
addRule(rule) {
this.rules.push(rule);
}
/**
* Build database from facts
*/
buildDb(facts) {
this.facts = [...facts];
}
/**
* Query the database
*/
async query(goal) {
const results = Resolution.resolve(goal, this.facts, this.rules);
// Convert results to bindings format
const bindings = [];
for (const resultBindings of results) {
const binding = {};
for (const [key, value] of resultBindings) {
binding[key] = value;
}
bindings.push(binding);
}
return { bindings };
}
/**
* Get all facts
*/
getFacts() {
return [...this.facts];
}
/**
* Get all rules
*/
getRules() {
return [...this.rules];
}
/**
* Clear database
*/
clear() {
this.facts = [];
this.rules = [];
}
}
/**
* Fixed-point computation for DataLog
*/
class FixedPoint {
/**
* Compute fixed point of a DataLog program
*/
static compute(program) {
let facts = new Set(this.factsToStrings(program.facts));
let previousSize = 0;
let iterations = 0;
const maxIterations = 1000;
while (facts.size !== previousSize && iterations < maxIterations) {
previousSize = facts.size;
// Apply all rules
for (const rule of program.rules) {
const newFacts = this.applyRule(rule, Array.from(facts).map(s => this.stringToFact(s)));
for (const fact of newFacts) {
facts.add(this.factToString(fact));
}
}
iterations++;
}
return Array.from(facts).map(s => this.stringToFact(s));
}
/**
* Apply a rule to generate new facts
*/
static applyRule(rule, facts) {
const newFacts = [];
// Match body predicates against facts
const bodyMatches = this.matchBody(rule.body, facts);
// Generate head facts for each match
for (const match of bodyMatches) {
const headFact = this.instantiateHead(rule.head, match);
if (headFact) {
newFacts.push(headFact);
}
}
return newFacts;
}
/**
* Match body predicates against facts
*/
static matchBody(body, facts) {
if (body.length === 0) {
return [new Map()];
}
const [firstPred, ...restPreds] = body;
const firstMatches = this.matchPredicate(firstPred, facts);
const allMatches = [];
for (const match of firstMatches) {
const restMatches = this.matchBody(restPreds, facts);
for (const restMatch of restMatches) {
const merged = this.mergeMatches(match, restMatch);
if (merged) {
allMatches.push(merged);
}
}
}
return allMatches;
}
/**
* Match a predicate against facts
*/
static matchPredicate(predicate, facts) {
const matches = [];
const parsed = this.parsePredicate(predicate);
for (const fact of facts) {
if (fact.predicate === parsed.predicate) {
const match = this.matchArgs(parsed.args, fact.args);
if (match) {
matches.push(match);
}
}
}
return matches;
}
/**
* Match arguments (with variable binding)
*/
static matchArgs(patternArgs, factArgs) {
if (patternArgs.length !== factArgs.length) {
return null;
}
const bindings = new Map();
for (let i = 0; i < patternArgs.length; i++) {
const pattern = patternArgs[i];
const fact = factArgs[i];
if (pattern.startsWith('?')) {
// Variable
const existing = bindings.get(pattern);
if (existing !== undefined && existing !== fact) {
return null;
}
bindings.set(pattern, fact);
}
else if (pattern !== fact.toString()) {
// Constant doesn't match
return null;
}
}
return bindings;
}
/**
* Instantiate head with bindings
*/
static instantiateHead(head, bindings) {
const parsed = this.parsePredicate(head);
const args = [];
for (const arg of parsed.args) {
if (arg.startsWith('?')) {
const value = bindings.get(arg);
if (value === undefined) {
return null;
}
args.push(value);
}
else {
args.push(arg);
}
}
return { predicate: parsed.predicate, args };
}
/**
* Parse predicate string
*/
static parsePredicate(predStr) {
const match = predStr.match(/^(\w+)\((.*)\)$/);
if (match) {
const predicate = match[1];
const argsStr = match[2];
const args = argsStr ? argsStr.split(',').map(s => s.trim()) : [];
return { predicate, args };
}
return { predicate: predStr, args: [] };
}
/**
* Merge two match bindings
*/
static mergeMatches(match1, match2) {
const merged = new Map(match1);
for (const [key, value] of match2) {
const existing = merged.get(key);
if (existing !== undefined && existing !== value) {
return null;
}
merged.set(key, value);
}
return merged;
}
/**
* Convert fact to string for set operations
*/
static factToString(fact) {
return `${fact.predicate}(${fact.args.join(',')})`;
}
/**
* Convert facts to strings
*/
static factsToStrings(facts) {
return facts.map(f => this.factToString(f));
}
/**
* Convert string back to fact
*/
static stringToFact(str) {
const match = str.match(/^(\w+)\((.*)\)$/);
if (match) {
const predicate = match[1];
const args = match[2] ? match[2].split(',').map(s => s.trim()) : [];
return { predicate, args };
}
return { predicate: str, args: [] };
}
}
/**
* Fact extraction utilities for DataLog
*/
class FactExtraction {
/**
* Extract facts from JSONL canvas objects
*/
static extractFromCanvas(objects) {
const facts = [];
for (const obj of objects) {
// Node facts
if (obj.type === 'node' || obj.id) {
facts.push({
predicate: 'node',
args: [
obj.id || obj._id || 'unknown',
obj.type || 'unknown',
obj.x || 0,
obj.y || 0,
obj.text || obj.label || ''
]
});
// Extract properties
for (const [key, value] of Object.entries(obj)) {
if (!['id', '_id', 'type', 'x', 'y', 'text', 'label'].includes(key)) {
facts.push({
predicate: `has_${key}`,
args: [obj.id || obj._id, value]
});
}
}
}
// Edge facts
if (obj.type === 'edge' || obj.fromNode || obj.toNode) {
facts.push({
predicate: 'edge',
args: [
obj.id || obj._id || 'unknown',
obj.type || 'unknown',
obj.fromNode || obj.from || 'unknown',
obj.toNode || obj.to || 'unknown'
]
});
// Vertical/horizontal relationships
if (obj.type?.startsWith('v:') || obj.type === 'vertical') {
facts.push({
predicate: 'vertical',
args: [obj.fromNode || obj.from, obj.toNode || obj.to]
});
}
if (obj.type?.startsWith('h:') || obj.type === 'horizontal') {
facts.push({
predicate: 'horizontal',
args: [obj.fromNode || obj.from, obj.toNode || obj.to]
});
}
}
}
return facts;
}
/**
* Extract facts from structured data
*/
static extractFromData(data) {
const facts = [];
if (Array.isArray(data)) {
for (const item of data) {
facts.push(...this.extractFromData(item));
}
}
else if (typeof data === 'object' && data !== null) {
// Extract object properties as facts
for (const [key, value] of Object.entries(data)) {
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
facts.push({
predicate: key,
args: [value]
});
}
else if (Array.isArray(value)) {
for (const item of value) {
facts.push({
predicate: key,
args: [item]
});
}
}
}
}
return facts;
}
}
/**
* DataLog Engine for Meta-Log Database
*/
class DatalogEngine {
constructor() {
this.facts = [];
this.rules = [];
}
/**
* Add facts to the database
*/
addFacts(facts) {
this.facts.push(...facts);
}
/**
* Add a rule to the database
*/
addRule(rule) {
this.rules.push(rule);
}
/**
* Build DataLog program from rules
*/
buildProgram(rules) {
return {
rules: [...rules],
facts: [...this.facts]
};
}
/**
* Query the database
*/
async query(goal, program) {
const targetProgram = program || this.buildProgram(this.rules);
// Compute fixed point
const allFacts = FixedPoint.compute(targetProgram);
// Match goal against facts
const parsedGoal = this.parseGoal(goal);
const matchingFacts = allFacts.filter(fact => {
if (fact.predicate !== parsedGoal.predicate) {
return false;
}
if (fact.args.length !== parsedGoal.args.length) {
return false;
}
// Match arguments (variables match anything)
for (let i = 0; i < fact.args.length; i++) {
const goalArg = parsedGoal.args[i];
if (!goalArg.startsWith('?') && goalArg !== fact.args[i].toString()) {
return false;
}
}
return true;
});
return { facts: matchingFacts };
}
/**
* Compute fixed point
*/
fixedPoint(program) {
return FixedPoint.compute(program);
}
/**
* Extract facts from canvas objects
*/
extractFacts(objects) {
return FactExtraction.extractFromCanvas(objects);
}
/**
* Parse goal string
*/
parseGoal(goal) {
const match = goal.match(/^(\w+)\((.*)\)$/);
if (match) {
const predicate = match[1];
const argsStr = match[2];
const args = argsStr ? argsStr.split(',').map(s => s.trim()) : [];
return { predicate, args };
}
return { predicate: goal, args: [] };
}
/**
* Get all facts
*/
getFacts() {
return [...this.facts];
}
/**
* Get all rules
*/
getRules() {
return [...this.rules];
}
/**
* Clear database
*/
clear() {
this.facts = [];
this.rules = [];
}
}
/**
* Enhanced SPARQL Query Parser
* Supports: SELECT, DISTINCT, ORDER BY, LIMIT, OFFSET, FILTER, OPTIONAL, UNION
*/
/**
* Parse SPARQL query string into structured query object
*/
class SparqlParser {
/**
* Parse SPARQL query
*/
static parse(query) {
const normalized = this.normalizeQuery(query);
// Parse query type
const queryType = this.parseQueryType(normalized);
// Parse SELECT clause
const selectMatch = normalized.match(/SELECT\s+(DISTINCT\s+)?(.*?)\s+WHERE/i);
if (!selectMatch) {
throw new Error('Invalid SPARQL SELECT query');
}
const distinct = !!selectMatch[1];
const variablesStr = selectMatch[2].trim();
const variables = this.parseVariables(variablesStr);
// Parse WHERE clause
const whereMatch = normalized.match(/WHERE\s*\{([^}]+)\}/is);
if (!whereMatch) {
throw new Error('Invalid SPARQL WHERE clause');
}
const whereClause = whereMatch[1];
const patterns = this.parsePatterns(whereClause);
const filters = this.parseFilters(whereClause);
const optional = this.parseOptional(whereClause);
// Parse ORDER BY
const orderByMatch = normalized.match(/ORDER\s+BY\s+(.*?)(?:\s+LIMIT|\s+OFFSET|$)/i);
const orderBy = orderByMatch ? this.parseOrderBy(orderByMatch[1]) : undefined;
// Parse LIMIT
const limitMatch = normalized.match(/LIMIT\s+(\d+)/i);
const limit = limitMatch ? parseInt(limitMatch[1], 10) : undefined;
// Parse OFFSET
const offsetMatch = normalized.match(/OFFSET\s+(\d+)/i);
const offset = offsetMatch ? parseInt(offsetMatch[1], 10) : undefined;
return {
type: queryType,
distinct,
variables,
where: patterns,
optional,
filters,
orderBy,
limit,
offset
};
}
/**
* Normalize query string
*/
static normalizeQuery(query) {
return query
.replace(/\s+/g, ' ')
.replace(/\s*\{\s*/g, ' { ')
.replace(/\s*\}\s*/g, ' } ')
.trim();
}
/**
* Parse query type
*/
static parseQueryType(query) {
if (query.match(/^\s*SELECT/i))
return 'SELECT';
if (query.match(/^\s*ASK/i))
return 'ASK';
if (query.match(/^\s*CONSTRUCT/i))
return 'CONSTRUCT';
if (query.match(/^\s*DESCRIBE/i))
return 'DESCRIBE';
return 'SELECT'; // Default
}
/**
* Parse variables from SELECT clause
*/
static parseVariables(variablesStr) {
if (variablesStr === '*') {
return ['*'];
}
return variablesStr
.split(/\s+/)
.filter(v => v.trim())
.map(v => v.trim());
}
/**
* Parse triple patterns from WHERE clause
*/
static parsePatterns(whereClause) {
const patterns = [];
const lines = whereClause.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('FILTER') && !l.startsWith('OPTIONAL'));
for (const line of lines) {
const match = line.match(/(\S+)\s+(\S+)\s+(\S+)\s*\.?/);
if (match) {
patterns.push({
subject: match[1],
predicate: match[2],
object: match[3]
});
}
}
return patterns;
}
/**
* Parse OPTIONAL patterns
*/
static parseOptional(whereClause) {
const optionalMatch = whereClause.match(/OPTIONAL\s*\{([^}]+)\}/is);
if (!optionalMatch)
return undefined;
return this.parsePatterns(optionalMatch[1]);
}
/**
* Parse FILTER expressions
*/
static parseFilters(whereClause) {
const filters = [];
const filterMatches = whereClause.matchAll(/FILTER\s*\(([^)]+)\)/gi);
for (const match of filterMatches) {
const expression = match[1].trim();
// Parse different filter types
if (expression.includes('=')) {
const [left, right] = expression.split('=').map(s => s.trim());
filters.push({
expression,
type: 'equals',
left,
right
});
}
else if (expression.includes('!=')) {
const [left, right] = expression.split('!=').map(s => s.trim());
filters.push({
expression,
type: 'notEquals',
left,
right
});
}
else if (expression.includes('>')) {
const [left, right] = expression.split('>').map(s => s.trim());
filters.push({
expression,
type: 'greaterThan',
left,
right
});
}
else if (expression.includes('<')) {
const [left, right] = expression.split('<').map(s => s.trim());
filters.push({
expression,
type: 'lessThan',
left,
right
});
}
else if (expression.includes('regex')) {
filters.push({
expression,
type: 'regex',
left: expression
});
}
else if (expression.includes('bound')) {
filters.push({
expression,
type: 'bound',
left: expression
});
}
else {
filters.push({
expression,
type: 'custom',
left: expression
});
}
}
return filters;
}
/**
* Parse ORDER BY clause
*/
static parseOrderBy(orderByStr) {
const orders = [];
const parts = orderByStr.split(/\s+/);
for (let i = 0; i < parts.length; i++) {
const part = parts[i].trim();
if (part === 'ASC' || part === 'DESC') {
if (i > 0) {
orders[orders.length - 1].direction = part;
}
}
else if (part && !part.match(/^(ASC|DESC)$/i)) {
orders.push({
variable: part,
direction: 'ASC' // Default
});
}
}
return orders;
}
}
/**
* Enhanced SPARQL Query Executor
* Executes parsed SPARQL queries with full feature support
*/
class SparqlExecutor {
constructor(triples) {
this.triples = triples;
}
/**
* Execute SPARQL query
*/
async execute(query) {
// Execute WHERE clause patterns
let bindings = this.executePatterns(query.where);
// Apply OPTIONAL patterns
if (query.optional) {
bindings = this.applyOptional(bindings, query.optional);
}
// Apply FILTER expressions
if (query.filters && query.filters.length > 0) {
bindings = this.applyFilters(bindings, query.filters);
}
// Apply DISTINCT
if (query.distinct) {
bindings = this.applyDistinct(bindings);
}
// Apply ORDER BY
if (query.orderBy && query.orderBy.length > 0) {
bindings = this.applyOrderBy(bindings, query.orderBy);
}
// Apply LIMIT and OFFSET
if (query.offset) {
bindings = bindings.slice(query.offset);
}
if (query.limit) {
bindings = bindings.slice(0, query.limit);
}
// Project variables
bindings = this.projectVariables(bindings, query.variables);
return {
results: {
bindings: bindings.map(b => this.formatBinding(b))
}
};
}
/**
* Execute triple patterns
*/
executePatterns(patterns) {
if (patterns.length === 0) {
return [{}];
}
let bindings = [{}];
for (const pattern of patterns) {
const newBindings = [];
for (const binding of bindings) {
const matches = this.matchPattern(pattern, binding);
for (const match of matches) {
newBindings.push({ ...binding, ...match });
}
}
bindings = newBindings;
}
return bindings;
}
/**
* Match a pattern against triples with existing bindings
*/
matchPattern(pattern, existingBindings) {
const subject = this.resolveValue(pattern.subject, existingBindings);
const predicate = this.resolveValue(pattern.predicate, existingBindings);
const object = this.resolveValue(pattern.object, existingBindings);
const matches = [];
const patternTriple = {
subject: subject.startsWith('?') ? undefined : subject,
predicate: predicate.startsWith('?') ? undefined : predicate,
object: object.startsWith('?') ? undefined : object
};
const matchingTriples = this.queryTriples(patternTriple);
for (const triple of matchingTriples) {
const newBinding = {};
if (subject.startsWith('?')) {
newBinding[subject] = triple.subject;
}
if (predicate.startsWith('?')) {
newBinding[predicate] = triple.predicate;
}
if (object.startsWith('?')) {
const objValue = typeof triple.object === 'string' ? triple.object : triple.object.value;
newBinding[object] = objValue;
}
// Check if binding is consistent with existing bindings
let consistent = true;
for (const [key, value] of Object.entries(newBinding)) {
if (existingBindings[key] && existingBindings[key] !== value) {
consistent = false;
break;
}
}
if (consistent) {
matches.push(newBinding);
}
}
return matches;
}
/**
* Resolve variable or literal value
*/
resolveValue(value, bindings) {
if (value.startsWith('?')) {
return bindings[value] || value;
}
return value;
}
/**
* Query triples by pattern
*/
queryTriples(pattern) {
return this.triples.filter(triple => {
if (pattern.subject && triple.subject !== pattern.subject) {
return false;
}
if (pattern.predicate && triple.predicate !== pattern.predicate) {
return false;
}
if (pattern.object) {
const objStr = typeof triple.object === 'string'
? triple.object
: triple.object.value;
if (objStr !== pattern.object) {
return false;
}
}
return true;
});
}
/**
* Apply OPTIONAL patterns
*/
applyOptional(bindings, optionalPatterns) {
const result = [];
for (const binding of bindings) {
const optionalMatches = this.executePatterns(optionalPatterns);
if (optionalMatches.length > 0) {
for (const match of optionalMatches) {
result.push({ ...binding, ...match });
}
}
else {
result.push(binding);
}
}
return result;
}
/**
* Apply FILTER expressions
*/
applyFilters(bindings, filters) {
return bindings.filter(binding => {
return filters.every(filter => this.evaluateFilter(filter, binding));
});
}
/**
* Evaluate a filter expression
*/
evaluateFilter(filter, binding) {
const leftValue = this.resolveValue(filter.left, binding);
const rightValue = filter.right ? this.resolveValue(filter.right, binding) : undefined;
switch (filter.type) {
case 'equals':
return leftValue === rightValue;
case 'notEquals':
return leftValue !== rightValue;
case 'greaterThan':
return parseFloat(leftValue) > parseFloat(rightValue || '0');
case 'lessThan':
return parseFloat(leftValue) < parseFloat(rightValue || '0');
case 'bound':
return binding[filter.left] !== undefined;
case 'regex':
// Simplified regex - full implementation would parse regex properly
return true;
default:
return true;
}
}
/**
* Apply DISTINCT
*/
applyDistinct(bindings) {
const seen = new Set();
const result = [];
for (const binding of bindings) {
const key = JSON.stringify(binding);
if (!seen.has(key)) {
seen.add(key);
result.push(binding);
}
}
return result;
}
/**
* Apply ORDER BY
*/
applyOrderBy(bindings, orderBy) {
return [...bindings].sort((a, b) => {
for (const order of orderBy) {
const aValue = a[order.variable] || '';
const bValue = b[order.variable] || '';
let comparison = 0;
if (aValue < bValue)
comparison = -1;
else if (aValue > bValue)
comparison = 1;
if (order.direction === 'DESC') {
comparison = -comparison;
}
if (comparison !== 0) {
return comparison;
}
}
return 0;
});
}
/**
* Project variables (SELECT clause)
*/
projectVariables(bindings, variables) {
if (variables.includes('*')) {
return bindings;
}
return bindings.map(binding => {
const projected = {};
for (const variable of variables) {
if (binding[variable]) {
projected[variable] = binding[variable];
}
}
return projected;
});
}
/**
* Format binding for output
*/
formatBinding(binding) {
const formatted = {};
for (const [key, value] of Object.entries(binding)) {
formatted[key] = {
value,
type: this.inferType(value)
};
}
return formatted;
}
/**
* Infer RDF type from value
*/
inferType(value) {
if (value.startsWith('http://') || value.startsWith('https://') || value.startsWith('<')) {
return 'uri';
}
if (value.startsWith('"') && value.endsWith('"')) {
return 'literal';
}
if (/^-?\d+$/.test(value)) {
return 'typed-literal';
}
return 'literal';
}
}
/**
* RDF Triple Store
*/
class TripleStore {
constructor() {
this.triples = [];
this.queryCache = new Map();
this.cacheEnabled = true;
}
/**
* Add triples to the store
*/
addTriples(triples) {
this.triples.push(...triples);
}
/**
* Query triples by pattern
*/
query(pattern) {
return this.triples.filter(triple => {
if (pattern.subject && triple.subject !== pattern.subject) {
return false;
}
if (pattern.predicate && triple.predicate !== pattern.predicate) {
return false;
}
if (pattern.object) {
const objStr = typeof triple.object === 'string'
? triple.object
: triple.object.value;
if (objStr !== pattern.object) {
return false;
}
}
return true;
});
}
/**
* Execute SPARQL query (enhanced implementation)
* Supports: SELECT, DISTINCT, ORDER BY, LIMIT, OFFSET, FILTER, OPTIONAL
*/
async sparql(query) {
// Check cache
if (this.cacheEnabled) {
const cached = this.queryCache.get(query);
if (cached) {
return cached;
}
}
try {
// Parse query using enhanced parser
const parsedQuery = SparqlParser.parse(query);
// Execute query using enhanced executor
const executor = new SparqlExecutor(this.triples);
const result = await executor.execute(parsedQuery);
// Cache result
if (this.cacheEnabled) {
this.queryCache.set(query, result);
}
return result;
}
catch (error) {
// Fallback to simplified parser for backward compatibility
return this.sparqlSimple(query);
}
}
/**
* Simple SPARQL query execution (backward compatibility)
*/
async sparqlSimple(query) {
// Simple SELECT query parser
const selectMatch = query.match(/SELECT\s+(.*?)\s+WHERE/i);
if (!selectMatch) {
throw new Error('Unsupported SPARQL query format');
}
selectMatch[1].split(/\s+/).filter(v => v.startsWith('?'));
// Extract WHERE clause patterns
const whereMatch = query.match(/WHERE\s*\{([^}]+)\}/is);
if (!whereMatch) {
return { results: { bindings: [] } };
}
const patterns = this.parseSparqlPatterns(whereMatch[1]);
const bindings = [];
// Simple pattern matching
for (const pattern of patterns) {
const matches = this.query(pattern);
for (const match of matches) {
const binding = {};
if (pattern.subject?.startsWith('?')) {
binding[pattern.subject] = { value: match.subject, type: 'uri' };
}
if (pattern.predicate?.startsWith('?')) {
binding[pattern.predicate] = { value: match.predicate, type: 'uri' };
}
if (pattern.object?.startsWith('?')) {
const objValue = typeof match.object === 'string' ? match.object : match.object.value;
binding[pattern.object] = { value: objValue, type: 'uri' };
}
bindings.push(binding);
}
}
return { results: { bindings } };
}
/**
* Enable/disable query caching
*/
setCacheEnabled(enabled) {
this.cacheEnabled = enabled;
if (!enabled) {
this.queryCache.clear();
}
}
/**
* Clear query cache
*/
clearCache() {
this.queryCache.clear();
}
/**
* Parse SPARQL patterns from WHERE clause
*/
parseSparqlPatterns(whereClause) {
const patterns = [];
const lines = whereClause.split('\n').map(l => l.trim()).filter(l => l);
for (const line of lines) {
const match = line.match(/(\S+)\s+(\S+)\s+(\S+)\s*\./);
if (match) {
patterns.push({
subject: match[1],
predicate: match[2],
object: match[3]
});
}
}
return patterns;
}
/**
* RDFS entailment (simplified)
*/
rdfsEntailment(triples) {
const entailed = [...triples];
const rdfType = '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>';
const rdfsSubClassOf = '<http://www.w3.org/2000/01/rdf-schema#subClassOf>';
// Find subClassOf relationships
const subClassTriples = triples.filter(t => t.predicate === rdfsSubClassOf);
// Apply subClassOf transitivity
for (const subClassTriple of subClassTriples) {
const subClass = subClassTriple.subject;
const superClass = typeof subClassTriple.object === 'string'
? subClassTriple.object
: subClassTriple.object.value;
// Find all instances of subClass
const instances = triples.filter(t => t.predicate === rdfType &&
(typeof t.object === 'string' ? t.object : t.object.value) === subClass);
// Add type assertions for superClass
for (const instance of instances) {
entailed.push({
subject: instance.subject,
predicate: rdfType,
object: superClass
});
}
}
return entailed;
}
/**
* Get all triples
*/
getTriples() {
return [...this.triples];
}
/**
* Clear all triples
*/
clear() {
this.triples = [];
}
}
/**
* Turtle/RDF Parser for SHACL Shapes
* Parses Turtle format RDF files into structured data
*/
/**
* Turtle Parser
* Basic Turtle parser for SHACL shape files
*/
class TurtleParser {
/**
* Parse Turtle content into triples
*/
static parse(content) {
const triples = [];
const lines = content.split('\n');
let inMultiLine = false;
let multiLineValue = '';
for (let i = 0; i < lines.length; i++) {
let line = lines[i].trim();
// Skip empty lines and comments
if (!line || line.startsWith('#')) {
continue;
}
// Handle multi-line strings
if (inMultiLine) {
multiLineValue += ' ' + line;
if (line.endsWith('"""') || line.endsWith("'''")) {
inMultiLine = false;
line = multiLineValue;
multiLineValue = '';
}
else {
continue;
}
}
// Check for multi-line string start
if (line.includes('"""') || line.includes("'''")) {
inMultiLine = true;
multiLineValue = line;
continue;
}
// Parse prefixes
if (line.startsWith('@prefix') || line.startsWith('PREFIX')) {
// Prefix handling would go here
continue;
}
// Parse triples
const parts = this.splitTripleLine(line);
if (parts.length >= 3) {
const subject = this.expandIRI(parts[0]);
const predicate = this.expandIRI(parts[1]);
const object = this.parseObject(parts[2]);
triples.push({
subject,
predicate,
object
});
// Handle predicate lists (a b c .)
if (parts.length > 3) {
for (let j = 2; j < parts.length - 1; j++) {
const nextPredicate = this.expandIRI(parts[j]);
const nextObject = this.parseObject(parts[j + 1]);
triples.push({
subject,
predicate: nextPredicate,
object: nextObject
});
}
}
}
}
return triples;
}
/**
* Split a triple line into parts
*/
static splitTripleLine(line) {
const parts