{"version":3,"file":"parseSPARQL.mjs","names":["SparqlParser"],"sources":["../src/utils/parseSPARQL/parseSPARQL.ts"],"sourcesContent":["import { Parser as SparqlParser } from '@traqula/parser-sparql-1-2';\nimport type {\n  DefaultRuleGroupType,\n  DefaultRuleGroupTypeAny,\n  DefaultRuleGroupTypeIC,\n  DefaultRuleType,\n} from '../../types';\n\n// Reuse a single parser instance (Chevrotain-backed; creating parsers is expensive)\nlet parserInstance: SparqlParser | undefined;\nconst getParser = (): SparqlParser => {\n  if (!parserInstance) {\n    parserInstance = new SparqlParser();\n  }\n  return parserInstance;\n};\n\n// ─── Traqula AST Type Subset ─────────────────────────────────────────────────\n\ninterface TraqulaQuery {\n  type: 'query';\n  subType: string;\n  where?: TraqulaPatternGroup;\n  [key: string]: unknown;\n}\n\ninterface TraqulaPatternGroup {\n  type: 'pattern';\n  subType: 'group';\n  patterns: TraqulaPattern[];\n}\n\ntype TraqulaPattern =\n  | TraqulaBgp\n  | TraqulaOptional\n  | TraqulaFilter\n  | TraqulaPatternGroup\n  | TraqulaUnion\n  | { type: 'pattern'; subType: string; [key: string]: unknown }\n  | TraqulaQuery;\n\ninterface TraqulaBgp {\n  type: 'pattern';\n  subType: 'bgp';\n  triples: TraqulaTriple[];\n}\n\ninterface TraqulaOptional {\n  type: 'pattern';\n  subType: 'optional';\n  patterns: TraqulaPattern[];\n}\n\ninterface TraqulaFilter {\n  type: 'pattern';\n  subType: 'filter';\n  expression: TraqulaExpression;\n}\n\ninterface TraqulaUnion {\n  type: 'pattern';\n  subType: 'union';\n  patterns: TraqulaPatternGroup[];\n}\n\ninterface TraqulaTriple {\n  type: 'triple';\n  subject: TraqulaTerm;\n  predicate: TraqulaTerm;\n  object: TraqulaTerm;\n}\n\ninterface TraqulaTerm {\n  type: 'term' | 'path' | 'wildcard';\n  subType?: string;\n  value?: string;\n  prefix?: string;\n  langOrIri?: unknown;\n  [key: string]: unknown;\n}\n\ninterface TraqulaExpression {\n  type: 'expression' | 'term';\n  subType: string;\n  operator?: string;\n  args?: (TraqulaExpression | TraqulaTerm)[];\n  value?: string;\n  prefix?: string;\n  langOrIri?: unknown;\n  [key: string]: unknown;\n}\n\n// ─── AST Walker ──────────────────────────────────────────────────────────────\n\n/** Serializes a Traqula term into a user-facing string. */\nconst termToString = (term: TraqulaTerm): string => {\n  if (term.type === 'term') {\n    switch (term.subType) {\n      case 'variable':\n        return `?${term.value}`;\n      case 'namedNode':\n        if (term.prefix !== undefined && term.prefix !== '') {\n          return `${term.prefix}:${term.value}`;\n        }\n        return term.value ?? '';\n      case 'literal':\n        return term.value ?? '';\n      case 'blankNode':\n        return `_:${(term as { label?: string }).label ?? ''}`;\n      default:\n        return term.value ?? '';\n    }\n  }\n  return '';\n};\n\n/** Extracts a literal JS value from a Traqula term. */\nconst termToLiteralValue = (term: TraqulaTerm | TraqulaExpression): unknown => {\n  if (term.type === 'term' && term.subType === 'literal') {\n    const raw = term.value ?? '';\n    if (term.langOrIri && typeof term.langOrIri === 'object') {\n      const iri = term.langOrIri as TraqulaTerm;\n      const datatype = iri.value ?? '';\n      if (\n        datatype.endsWith('#integer') ||\n        datatype.endsWith('#decimal') ||\n        datatype.endsWith('#double') ||\n        datatype.endsWith('#float')\n      ) {\n        return Number(raw);\n      }\n      if (datatype.endsWith('#boolean')) {\n        return raw === 'true';\n      }\n    }\n    const num = Number(raw);\n    if (raw !== '' && !Number.isNaN(num)) return num;\n    return raw;\n  }\n  if (term.type === 'term' && term.subType === 'variable') {\n    return `?${term.value}`;\n  }\n  if (term.type === 'term' && term.subType === 'namedNode') {\n    return termToString(term as TraqulaTerm);\n  }\n  return term.value ?? '';\n};\n\n/** Walks patterns, extracting only FILTER expressions (skipping BGP triples). */\nconst visitPatterns = (\n  patterns: TraqulaPattern[],\n  out: (DefaultRuleType | DefaultRuleGroupType)[]\n): void => {\n  for (const p of patterns) {\n    if (p.type === 'pattern') {\n      switch (p.subType) {\n        case 'bgp':\n          // Skip triple patterns — Phase 1 only extracts filter conditions\n          break;\n        case 'optional':\n          visitPatterns((p as TraqulaOptional).patterns, out);\n          break;\n        case 'filter':\n          visitFilter((p as TraqulaFilter).expression, out);\n          break;\n        case 'group':\n          visitPatterns((p as TraqulaPatternGroup).patterns, out);\n          break;\n        case 'union': {\n          const union = p as TraqulaUnion;\n          const subRules: (DefaultRuleType | DefaultRuleGroupType)[] = [];\n          for (const branch of union.patterns) {\n            const branchRules: (DefaultRuleType | DefaultRuleGroupType)[] = [];\n            visitPatterns(branch.patterns, branchRules);\n            if (branchRules.length === 1) {\n              subRules.push(branchRules[0]);\n            } else if (branchRules.length > 1) {\n              subRules.push({ combinator: 'and', rules: branchRules });\n            }\n          }\n          if (subRules.length > 0) {\n            out.push({ combinator: 'or', rules: subRules });\n          }\n          break;\n        }\n        default:\n          break;\n      }\n    }\n  }\n};\n\n/** Maps a SPARQL comparison operator string to an RQB operator. */\nconst sparqlOpToRqb: Record<string, string> = {\n  '=': '=',\n  '!=': '!=',\n  '<': '<',\n  '>': '>',\n  '<=': '<=',\n  '>=': '>=',\n};\n\n/** Converts a FILTER expression into rules. */\nconst visitFilter = (\n  expr: TraqulaExpression,\n  out: (DefaultRuleType | DefaultRuleGroupType)[]\n): void => {\n  if (expr.type === 'expression' && expr.subType === 'operation') {\n    const op = expr.operator ?? '';\n    const args = expr.args ?? [];\n\n    // Logical operators\n    if (op === '&&') {\n      for (const arg of args) {\n        visitFilter(arg as TraqulaExpression, out);\n      }\n      return;\n    }\n    if (op === '||') {\n      const subRules: (DefaultRuleType | DefaultRuleGroupType)[] = [];\n      for (const arg of args) {\n        const branchRules: (DefaultRuleType | DefaultRuleGroupType)[] = [];\n        visitFilter(arg as TraqulaExpression, branchRules);\n        subRules.push(...branchRules);\n      }\n      out.push({ combinator: 'or', rules: subRules });\n      return;\n    }\n\n    // Negation\n    if (op === '!') {\n      if (args.length === 1) {\n        const inner = args[0] as TraqulaExpression;\n        // !BOUND(x) → null check\n        if (\n          inner.type === 'expression' &&\n          inner.subType === 'operation' &&\n          inner.operator === 'bound'\n        ) {\n          const field = termToString((inner.args?.[0] ?? {}) as TraqulaTerm);\n          out.push({ field, operator: 'null', value: null } as DefaultRuleType);\n          return;\n        }\n        const innerRules: (DefaultRuleType | DefaultRuleGroupType)[] = [];\n        visitFilter(inner, innerRules);\n        if (innerRules.length === 1 && !('combinator' in innerRules[0])) {\n          out.push(negateRule(innerRules[0]));\n        } else {\n          out.push({ combinator: 'and', not: true, rules: innerRules });\n        }\n        return;\n      }\n    }\n\n    // Comparison operators\n    if (op in sparqlOpToRqb && args.length === 2) {\n      const field = termToString(args[0] as TraqulaTerm);\n      const value = termToLiteralValue(args[1]);\n      out.push({ field, operator: sparqlOpToRqb[op], value } as DefaultRuleType);\n      return;\n    }\n\n    // BOUND(?x) → notNull\n    if (op === 'bound' && args.length === 1) {\n      const field = termToString(args[0] as TraqulaTerm);\n      out.push({ field, operator: 'notNull', value: null } as DefaultRuleType);\n      return;\n    }\n\n    // String functions\n    if (op === 'contains' && args.length === 2) {\n      const field = termToString(args[0] as TraqulaTerm);\n      const value = termToLiteralValue(args[1]);\n      out.push({ field, operator: 'contains', value } as DefaultRuleType);\n      return;\n    }\n    if (op === 'strstarts' && args.length === 2) {\n      const field = termToString(args[0] as TraqulaTerm);\n      const value = termToLiteralValue(args[1]);\n      out.push({ field, operator: 'beginsWith', value } as DefaultRuleType);\n      return;\n    }\n    if (op === 'strends' && args.length === 2) {\n      const field = termToString(args[0] as TraqulaTerm);\n      const value = termToLiteralValue(args[1]);\n      out.push({ field, operator: 'endsWith', value } as DefaultRuleType);\n      return;\n    }\n  }\n};\n\n/** Negates a rule by converting its operator. */\nconst negateRule = (rule: DefaultRuleType): DefaultRuleType => {\n  const negMap: Record<string, string> = {\n    contains: 'doesNotContain',\n    beginsWith: 'doesNotBeginWith',\n    endsWith: 'doesNotEndWith',\n    notNull: 'null',\n    null: 'notNull',\n    '=': '!=',\n    '!=': '=',\n    '<': '>=',\n    '>': '<=',\n    '<=': '>',\n    '>=': '<',\n  };\n  return { ...rule, operator: negMap[rule.operator] ?? rule.operator } as DefaultRuleType;\n};\n\n/**\n * Ensures all prefixed names in a SPARQL query have corresponding PREFIX\n * declarations so the parser accepts them without requiring full URIs.\n */\nconst ensurePrefixes = (sparql: string): string => {\n  const declaredPrefixes = new Set<string>();\n  const prefixDeclRegex = /PREFIX\\s+(\\w*):/gi;\n  let m: RegExpExecArray | null;\n  while ((m = prefixDeclRegex.exec(sparql)) !== null) {\n    declaredPrefixes.add(m[1].toLowerCase());\n  }\n\n  const usedPrefixes = new Set<string>();\n  const prefixUsageRegex = /\\b(\\w+):\\w+/g;\n  while ((m = prefixUsageRegex.exec(sparql)) !== null) {\n    const prefix = m[1];\n    if (/^https?$/i.test(prefix) || /^urn$/i.test(prefix)) continue;\n    const before = sparql.slice(0, m.index);\n    const lastOpen = before.lastIndexOf('<');\n    const lastClose = before.lastIndexOf('>');\n    if (lastOpen > lastClose) continue;\n    if (!declaredPrefixes.has(prefix.toLowerCase())) {\n      usedPrefixes.add(prefix);\n    }\n  }\n\n  if (usedPrefixes.size === 0) return sparql;\n\n  const stubs = [...usedPrefixes].map(p => `PREFIX ${p}: <urn:rqb:prefix:${p}:>`).join('\\n');\n  return stubs + '\\n' + sparql;\n};\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Options for {@link parseSPARQL}.\n */\nexport interface ParseSPARQLOptions {\n  independentCombinators?: boolean;\n}\n\n/**\n * Parses a SPARQL query string into a {@link DefaultRuleGroupType}.\n *\n * Accepts a full SPARQL query or a `FILTER` expression. Triple patterns are\n * consumed but discarded — only FILTER conditions are returned.\n *\n * @example\n * ```ts\n * // Full query — extracts FILTER conditions only\n * parseSPARQL('SELECT ?x WHERE { ?x foaf:name ?name . FILTER(?age > 30) }');\n *\n * // Bare FILTER expression wrapped in a stub query\n * parseSPARQL('?age > 30 && ?name != \"Alice\"');\n * ```\n */\nexport function parseSPARQL(sparql: string): DefaultRuleGroupType;\nexport function parseSPARQL(\n  sparql: string,\n  options: Omit<ParseSPARQLOptions, 'independentCombinators'> & {\n    independentCombinators?: false;\n  }\n): DefaultRuleGroupType;\nexport function parseSPARQL(\n  sparql: string,\n  options: Omit<ParseSPARQLOptions, 'independentCombinators'> & {\n    independentCombinators: true;\n  }\n): DefaultRuleGroupTypeIC;\nexport function parseSPARQL(\n  sparql: string,\n  _options?: ParseSPARQLOptions\n): DefaultRuleGroupTypeAny {\n  const trimmed = sparql.trim();\n  if (!trimmed) return { combinator: 'and', rules: [] };\n\n  // Auto-detect input shape\n  let input = trimmed;\n  if (/^\\s*(select|prefix|construct|describe|ask)\\b/i.test(input)) {\n    // Full SPARQL query — parse as-is\n  } else {\n    // Bare expression — wrap in a stub query with FILTER\n    input = `SELECT * WHERE { FILTER(${input}) }`;\n  }\n\n  const prepared = ensurePrefixes(input);\n\n  let ast: unknown;\n  try {\n    ast = getParser().parse(prepared);\n  } catch {\n    return { combinator: 'and', rules: [] };\n  }\n\n  const typed = ast as TraqulaQuery;\n  if (!typed || typed.type !== 'query' || !typed.where) {\n    return { combinator: 'and', rules: [] };\n  }\n\n  const rules: (DefaultRuleType | DefaultRuleGroupType)[] = [];\n  visitPatterns(typed.where.patterns ?? [], rules);\n  return { combinator: 'and', rules };\n}\n"],"mappings":"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"}