2223 lines
88 KiB
Scheme
2223 lines
88 KiB
Scheme
; Module header is generated automatically
|
|
#cs(module ddo-txpath mzscheme
|
|
(require (lib "string.ss" "srfi/13"))
|
|
(require (lib "ssax.ss" "web-server/tests/tmp/ssax"))
|
|
(require "sxpathlib.ss")
|
|
(require "sxml-tools.ss")
|
|
(require "sxpath-ext.ss")
|
|
(require "xpath-parser.ss")
|
|
(require "txpath.ss")
|
|
(require "xpath-ast.ss")
|
|
(require "xpath-context_xlink.ss")
|
|
(require "ddo-axes.ss")
|
|
|
|
;; XPath implementation with distinct document order support
|
|
;
|
|
; This software is in Public Domain.
|
|
; IT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
|
;
|
|
; Please send bug reports and comments to:
|
|
; lizorkin@hotbox.ru Dmitry Lizorkin
|
|
|
|
;=========================================================================
|
|
; Miscellaneous
|
|
|
|
; Implement 'or' as a function, so that we could 'apply' it
|
|
(define (ddo:or . args)
|
|
(if (null? args) #f (or (car args) (apply ddo:or (cdr args)))))
|
|
|
|
;(define (ddo:foldl op init lst)
|
|
; (if (null? lst)
|
|
; init
|
|
; (ddo:foldl op (op (car lst) init) (cdr lst))))
|
|
|
|
(define (ddo:foldr op init lst)
|
|
(if (null? lst)
|
|
init
|
|
(op (car lst)
|
|
(ddo:foldr op init (cdr lst)))))
|
|
|
|
; Definition of types
|
|
(define ddo:type-nodeset 'ddo:type-nodeset)
|
|
(define ddo:type-number 'ddo:type-number)
|
|
(define ddo:type-string 'ddo:type-string)
|
|
(define ddo:type-boolean 'ddo:type-boolean)
|
|
(define ddo:type-any 'ddo:type-any)
|
|
|
|
;------------------------------------------------
|
|
; Comparison for nodesets
|
|
; In order to compare nodesets produced by conventional SXPath and SXPath with
|
|
; distinct document order support, we must take into account that members in
|
|
; each of the nodesets being compared can be ordered differently.
|
|
|
|
; Whether all members from the first nodeset are contained in the second
|
|
; nodeset
|
|
(define (ddo:nset-contained? nodeset1 nodeset2)
|
|
(cond
|
|
((null? nodeset1) #t)
|
|
((memq (car nodeset1) nodeset2)
|
|
(ddo:nset-contained? (cdr nodeset1) nodeset2))
|
|
(else #f)))
|
|
|
|
(define (ddo:nset-equal? nodeset1 nodeset2)
|
|
(and (ddo:nset-contained? nodeset1 nodeset2)
|
|
(ddo:nset-contained? nodeset2 nodeset1)))
|
|
|
|
|
|
;=========================================================================
|
|
; Different cases of nodeset filtering
|
|
|
|
;------------------------------------------------
|
|
; Filtering pos-result with (position-based) predicates and combining
|
|
; a filtered pos-result into a distinct document order nodeset
|
|
; pos-result ::= (listof pos-nodeset)
|
|
; pos-nodeset ::= (listof (cons node order-num))
|
|
; Each pos-nodeset is a result of applying the axis to a single node in the
|
|
; input nodeset. Pos-result can be informally considered as
|
|
; (map axis-pos input-nodeset)
|
|
; Each node in the pos-nodeset comes with its order number. An order-num is
|
|
; an integer, possibly a negative one. A node precedes another node in
|
|
; document order if the order-num of the former node is less than the order-num
|
|
; of the latter node. Equal order-nums (in different pos-nodesets) correspond
|
|
; to equal nodes.
|
|
; Each pos-nodeset is sorted in accordance with the position() of each of its
|
|
; members. Consequently, order-nums increase within pos-nodeset for forward
|
|
; XPath axes and decrease for reverse XPath axes.
|
|
|
|
; Whether pos-result in a forward order
|
|
; Return #t if in document order, #f if in reverse document order
|
|
(define (ddo:pos-result-forward? pos-result)
|
|
(let loop ((pos-res pos-result))
|
|
(cond
|
|
((null? pos-res) ; every pos-nodeset has the length of <2
|
|
#t)
|
|
((or (null? (car pos-res)) (null? (cdar pos-res)))
|
|
; this pos-nodeset has the length of less or equal to 1
|
|
(loop (cdr pos-res)))
|
|
(else
|
|
(< (cdaar pos-res) (cdadar pos-res))))))
|
|
|
|
; Unites pos-result into a nodeset in distinct document order
|
|
(define (ddo:pos-result->nodeset pos-result)
|
|
(letrec (; Combines 2 pos-nodesets into a single one
|
|
(combine-2-pos-nodesets
|
|
(lambda (chain1 chain2)
|
|
(cond
|
|
((null? chain1) chain2)
|
|
((null? chain2) chain1)
|
|
; None of the chains are null
|
|
((eq? (caar chain1) (caar chain2)) ; equal nodes
|
|
; the same with (= (cdar chain1) (cdar chain2))
|
|
(cons (car chain1)
|
|
(combine-2-pos-nodesets (cdr chain1) (cdr chain2))))
|
|
((< (cdar chain1) (cdar chain2))
|
|
(cons (car chain1)
|
|
(combine-2-pos-nodesets (cdr chain1) chain2)))
|
|
(else
|
|
(cons (car chain2)
|
|
(combine-2-pos-nodesets chain1 (cdr chain2))))))))
|
|
(if
|
|
(null? pos-result) ; nothing to do
|
|
pos-result
|
|
(let ((pos-result (if (ddo:pos-result-forward? pos-result)
|
|
pos-result
|
|
(map reverse pos-result))))
|
|
(let loop ((res (car pos-result))
|
|
(to-scan (cdr pos-result)))
|
|
(if (null? to-scan)
|
|
res
|
|
(loop (combine-2-pos-nodesets res (car to-scan))
|
|
(cdr to-scan))))))))
|
|
|
|
; pos-axis-impl ::= lambda
|
|
; pred-impl-lst ::= (listof lambda)
|
|
; Every predicate is called with respect to each node
|
|
; Returns: lambda
|
|
; lambda ::= (lambda (nodeset position+size var-binding) ...)
|
|
(define (ddo:location-step-pos pos-axis-impl pred-impl-lst)
|
|
(lambda (nodeset position+size var-binding)
|
|
(map
|
|
car
|
|
(ddo:pos-result->nodeset
|
|
(map
|
|
(lambda (pos-nodeset)
|
|
(let iter-preds ((nset pos-nodeset)
|
|
(preds pred-impl-lst))
|
|
(if
|
|
(null? preds)
|
|
nset
|
|
(let ((size (length nset))) ; context size
|
|
(let iter-pairs ((nset nset)
|
|
(res '())
|
|
(pos 1))
|
|
(if
|
|
(null? nset) ; continue with the next predicate
|
|
(iter-preds (reverse res) (cdr preds))
|
|
(let ((val ((car preds) ; predicate value
|
|
(list (caar nset)) (cons pos size) var-binding)))
|
|
(iter-pairs (cdr nset)
|
|
(if (if (number? val)
|
|
(= val pos)
|
|
(sxml:boolean val))
|
|
(cons (car nset) res)
|
|
res)
|
|
(+ pos 1)))))))))
|
|
(pos-axis-impl nodeset))))))
|
|
|
|
;------------------------------------------------
|
|
; Implementation for location step for the other cases
|
|
|
|
; A location step for the axis which doesn't return a result in the form of
|
|
; a pos-nodeset, but instead resulting nodesets for each input node are in
|
|
; document order
|
|
; pos-axis-impl ::= lambda
|
|
; pred-impl-lst ::= (listof lambda)
|
|
; Every predicate is called with respect to each node
|
|
; Returns: lambda
|
|
; lambda ::= (lambda (nodeset position+size var-binding) ...)
|
|
; This function is somewhat similar to 'sxml:xpath-nodeset-filter' from
|
|
; "txpath.scm"
|
|
(define (ddo:location-step-non-intersect axis-impl pred-impl-lst)
|
|
(lambda (nodeset position+size var-binding)
|
|
(map-union
|
|
(lambda (node)
|
|
(let iter-preds ((nset (axis-impl node))
|
|
(preds pred-impl-lst))
|
|
(if
|
|
(null? preds)
|
|
nset
|
|
(let ((size (length nset))) ; context size
|
|
(let iter-nodes ((nset nset)
|
|
(res '())
|
|
(pos 1))
|
|
(if
|
|
(null? nset) ; continue with the next predicate
|
|
(iter-preds (reverse res) (cdr preds))
|
|
(let ((val ((car preds) ; predicate value
|
|
(list (car nset)) (cons pos size) var-binding)))
|
|
(iter-nodes (cdr nset)
|
|
(if (if (number? val)
|
|
(= val pos)
|
|
(sxml:boolean val))
|
|
(cons (car nset) res)
|
|
res)
|
|
(+ pos 1)))))))))
|
|
nodeset)))
|
|
|
|
; A location step doesn't contain position-based predicates
|
|
(define (ddo:location-step-non-pos axis-impl pred-impl-lst)
|
|
(lambda (nodeset position+size var-binding)
|
|
(let iter-preds ((nset (axis-impl nodeset))
|
|
(preds pred-impl-lst))
|
|
(if
|
|
(null? preds)
|
|
nset
|
|
(let ((curr-pred (car preds)))
|
|
(iter-preds
|
|
(filter
|
|
(lambda (node)
|
|
(sxml:boolean
|
|
(curr-pred (list node)
|
|
(cons 1 1) ; dummy
|
|
var-binding)))
|
|
nset)
|
|
(cdr preds)))))))
|
|
|
|
;------------------------------------------------
|
|
; Implementations for FilterExpr
|
|
|
|
; Implementing FilterExpr in the general case, for position-based predicates
|
|
(define (ddo:filter-expr-general expr-impl pred-impl-lst)
|
|
(lambda (nodeset position+size var-binding)
|
|
(let ((prim-res (expr-impl nodeset position+size var-binding)))
|
|
(cond
|
|
((not (nodeset? prim-res))
|
|
(sxml:xpointer-runtime-error
|
|
"expected - nodeset instead of " prim-res)
|
|
'())
|
|
(else
|
|
(let iter-preds ((nset prim-res)
|
|
(preds pred-impl-lst))
|
|
(if
|
|
(null? preds)
|
|
nset
|
|
(let ((size (length nset))) ; context size
|
|
(let iter-nodes ((nset nset)
|
|
(res '())
|
|
(pos 1))
|
|
(if
|
|
(null? nset) ; continue with the next predicate
|
|
(iter-preds (reverse res) (cdr preds))
|
|
(let ((val ((car preds) ; predicate value
|
|
(list (car nset)) (cons pos size) var-binding)))
|
|
(iter-nodes (cdr nset)
|
|
(if (if (number? val)
|
|
(= val pos)
|
|
(sxml:boolean val))
|
|
(cons (car nset) res)
|
|
res)
|
|
(+ pos 1)))))))))))))
|
|
|
|
; A FilterExpr doesn't contain position-based predicates
|
|
; NOTE: This function is very similar to 'ddo:location-step-non-pos'
|
|
; Should think of combining them.
|
|
(define (ddo:filter-expr-non-pos expr-impl pred-impl-lst)
|
|
(lambda (nodeset position+size var-binding)
|
|
(let ((prim-res (expr-impl nodeset position+size var-binding)))
|
|
(cond
|
|
((not (nodeset? prim-res))
|
|
(sxml:xpointer-runtime-error
|
|
"expected - nodeset instead of " prim-res)
|
|
'())
|
|
(else
|
|
(let iter-preds ((nset prim-res)
|
|
(preds pred-impl-lst))
|
|
(if
|
|
(null? preds)
|
|
nset
|
|
(let ((curr-pred (car preds)))
|
|
(iter-preds
|
|
(filter
|
|
(lambda (node)
|
|
(sxml:boolean
|
|
(curr-pred (list node)
|
|
(cons 1 1) ; dummy
|
|
var-binding)))
|
|
nset)
|
|
(cdr preds))))))))))
|
|
|
|
; Filter expression, with a single predicate of the special structure, like
|
|
; [position()=1]
|
|
; special-pred-impl ::= (lambda (nodeset) ...) - filters the nodeset
|
|
(define (ddo:filter-expr-special-predicate expr-impl special-pred-impl)
|
|
(lambda (nodeset position+size var-binding)
|
|
(let ((prim-res (expr-impl nodeset position+size var-binding)))
|
|
(if
|
|
(not (nodeset? prim-res))
|
|
(begin
|
|
(sxml:xpointer-runtime-error
|
|
"expected - nodeset instead of " prim-res)
|
|
'())
|
|
(special-pred-impl prim-res)))))
|
|
|
|
|
|
;=========================================================================
|
|
; Uniting context-sets, preserving distinct document order
|
|
; Is required for XPath UnionExpr
|
|
|
|
; Returns all contexts of the document, including the ones for attribute nodes
|
|
; and for attribute value nodes. All contexts are returned in document order,
|
|
; attribute value nodes immediately follow attribute nodes
|
|
(define (ddo:all-contexts-in-doc doc)
|
|
(let iter-nodes ((contents (map
|
|
(lambda (kid) (list kid doc))
|
|
((sxml:child sxml:node?) doc)))
|
|
(res (list doc)))
|
|
(cond
|
|
((null? contents) ; every content processed
|
|
(reverse res))
|
|
((not ((ntype?? '*) (caar contents))) ; text node or PI or etc.
|
|
(iter-nodes (cdr contents)
|
|
(cons
|
|
(draft:make-context (caar contents) (cdar contents))
|
|
res)))
|
|
(else ; element node
|
|
(let iter-attrs ((attrs (sxml:attr-list (caar contents)))
|
|
(res (cons
|
|
(draft:make-context
|
|
(caar contents) (cdar contents))
|
|
res)))
|
|
(cond
|
|
((null? attrs) ; all attributes of a given element processed
|
|
(iter-nodes
|
|
(append (map
|
|
(lambda (kid) (cons kid (car contents)))
|
|
((sxml:child sxml:node?) (caar contents)))
|
|
(cdr contents))
|
|
res))
|
|
((not (sxml:node? (car attrs))) ; aux node of SXML 3.0
|
|
(iter-attrs (cdr attrs) res))
|
|
((null? (cdar attrs)) ; singular attribute
|
|
(iter-attrs (cdr attrs)
|
|
(cons
|
|
(draft:make-context (car attrs) (car contents))
|
|
res)))
|
|
(else ; an attribute has a value
|
|
(iter-attrs
|
|
(cdr attrs)
|
|
(cons ; attribute value
|
|
(draft:make-context (cadar attrs)
|
|
(cons (car attrs) (car contents)))
|
|
(cons
|
|
(draft:make-context (car attrs) (car contents))
|
|
res))))))))))
|
|
|
|
; Every context in both context-sets must contain all the ancestors of the
|
|
; context node (this corresponds to the num-ancestors=#f)
|
|
; All nodes must have one and the same root node (i.e. this function cannot
|
|
; correctly unite context-sets whose members belong to different documents)
|
|
; Returns the context-set that is a distinct-document-order union of the
|
|
; argument context-sets
|
|
(define (ddo:unite-2-contextsets cntset1 cntset2)
|
|
(if
|
|
(null? cntset1) ; nothing to do
|
|
cntset2
|
|
(let loop ((order (ddo:all-contexts-in-doc
|
|
(draft:list-last
|
|
(sxml:context->content (car cntset1)))))
|
|
(cntset1 cntset1)
|
|
(cntset2 cntset2)
|
|
(res '()))
|
|
(cond
|
|
((null? cntset1)
|
|
(append (reverse res) cntset2))
|
|
((null? cntset2)
|
|
(append (reverse res) cntset1))
|
|
; order should never be null
|
|
((eq? (sxml:context->node (car order))
|
|
(sxml:context->node (car cntset1)))
|
|
(loop (cdr order)
|
|
(cdr cntset1)
|
|
(if (eq? (sxml:context->node (car cntset1))
|
|
(sxml:context->node (car cntset2)))
|
|
(cdr cntset2)
|
|
cntset2)
|
|
(cons (car cntset1) res)))
|
|
((eq? (sxml:context->node (car order))
|
|
(sxml:context->node (car cntset2)))
|
|
(loop (cdr order)
|
|
cntset1
|
|
(cdr cntset2)
|
|
(cons (car cntset2) res)))
|
|
(else
|
|
(loop (cdr order) cntset1 cntset2 res))))))
|
|
|
|
; Based on the function for uniting 2 context-sets, unites multiple
|
|
; context-sets
|
|
(define (ddo:unite-multiple-context-sets . context-sets)
|
|
(if (null? context-sets) ; nothing to do
|
|
'()
|
|
(let loop ((res (car context-sets))
|
|
(more (cdr context-sets)))
|
|
(if (null? more)
|
|
res
|
|
(loop (ddo:unite-2-contextsets res (car more))
|
|
(cdr more))))))
|
|
|
|
|
|
;=========================================================================
|
|
; Optimizing special predicates like [position()=1] and the like
|
|
|
|
; Similar to R5RS list-tail, but returns an empty list when k > (length lst)
|
|
(define (ddo:list-tail lst k)
|
|
(if (or (null? lst) (<= k 0))
|
|
lst
|
|
(ddo:list-tail (cdr lst) (- k 1))))
|
|
|
|
; Takes the first k members of the list
|
|
; The whole list is taken when k > (length lst)
|
|
(define (ddo:list-head lst k)
|
|
(if (or (null? lst) (<= k 0))
|
|
'()
|
|
(cons (car lst) (ddo:list-head (cdr lst) (- k 1)))))
|
|
|
|
; Similar to R5RS list-tail, but returns an empty list when
|
|
; (or (< k 0) (> k (length lst))
|
|
(define (ddo:list-ref lst k)
|
|
(cond ((null? lst) lst)
|
|
((zero? k) (car lst))
|
|
(else (ddo:list-ref (cdr lst) (- k 1)))))
|
|
|
|
;-------------------------------------------------
|
|
; Checks for a special structure of the predicate in its AST representation
|
|
|
|
; Checks whether the given op is the AST representation to a function call
|
|
; to position()
|
|
(define ddo:check-ast-position?
|
|
(let ((ddo:ast-for-position-fun-call ; evaluate just once
|
|
(txp:expr->ast "position()")))
|
|
(lambda (op)
|
|
(equal? op ddo:ast-for-position-fun-call))))
|
|
|
|
; If the given op is the AST representation for a number and this number is
|
|
; exact, returns this number. Otherwise returns #f
|
|
(define (ddo:check4ast-number op)
|
|
(if
|
|
(eq? (car op) 'number)
|
|
(let ((number (cadr op)))
|
|
(if (and (number? number) (exact? number))
|
|
number #f))
|
|
#f))
|
|
|
|
; In case when the predicate has one of the following forms:
|
|
; SpecialPredicate ::= [ Number ]
|
|
; | [ position() CmpOp Number ]
|
|
; | [ Number CmpOp position() ]
|
|
; CmpOp ::= > | < | >= | <= | =
|
|
; Number - an integer
|
|
; than returns (lambda (nodeset) ...), where the lambda performs the required
|
|
; filtering as specified by the predicate.
|
|
; For a different sort of a predicate, returns #f
|
|
; The function doesn't signal of any semantic errors.
|
|
(define (ddo:check-special-predicate op)
|
|
(if
|
|
(not (eq? (car op) 'predicate))
|
|
#f ; an improper AST
|
|
(let ((expr (cadr op)))
|
|
(cond
|
|
((ddo:check4ast-number expr)
|
|
=> (lambda (num)
|
|
(lambda (nodeset) (ddo:list-ref nodeset (- num 1)))))
|
|
((and (memq (car expr) '(= > < >= <=))
|
|
(= (length expr) 3))
|
|
(call-with-values
|
|
(lambda ()
|
|
(cond
|
|
((and (ddo:check-ast-position? (cadr expr))
|
|
(ddo:check4ast-number (caddr expr)))
|
|
=> (lambda (num) (values (car expr) num)))
|
|
((and (ddo:check-ast-position? (caddr expr))
|
|
(ddo:check4ast-number (cadr expr)))
|
|
=> (lambda (num)
|
|
(values
|
|
(cond ; invert the cmp-op
|
|
((assq (car expr)
|
|
'((< . >) (> . <) (>= . <=) (<= . >=)))
|
|
=> cdr)
|
|
(else (car expr)))
|
|
num)))
|
|
(else
|
|
(values #f #f))))
|
|
(lambda (cmp-op num)
|
|
(if
|
|
(not num)
|
|
#f
|
|
(case cmp-op
|
|
((=)
|
|
(lambda (nodeset) (ddo:list-ref nodeset (- num 1))))
|
|
((>)
|
|
(lambda (nodeset) (ddo:list-tail nodeset num)))
|
|
((>=)
|
|
(lambda (nodeset) (ddo:list-tail nodeset (- num 1))))
|
|
((<)
|
|
(lambda (nodeset) (ddo:list-head nodeset (- num 1))))
|
|
((<=)
|
|
(lambda (nodeset) (ddo:list-head nodeset num)))
|
|
(else ; internal error
|
|
#f))))))
|
|
(else ; not an equality or relational expr with 2 arguments
|
|
#f)))))
|
|
|
|
|
|
;=========================================================================
|
|
; Some simple rewrites for XPath AST
|
|
|
|
; Whether a given AST node is the representation of the location step
|
|
; "descendant-or-self::node()", which is the full syntax for its abbreviated
|
|
; equivalent "//"
|
|
(define ddo:check-ast-desc-os?
|
|
(let ((ddo:ast-for-desc-os ; evaluate just once
|
|
(cadr ; selects the first location step
|
|
(txp:xpath->ast "//dummy"))))
|
|
(lambda (op)
|
|
(equal? op ddo:ast-for-desc-os))))
|
|
|
|
; Rewrites the sequence of location steps, by combining the two consecutive
|
|
; steps "//para" into a single one "descendant::para"
|
|
; Returns the reconstructed list of steps
|
|
(define (ddo:rewrite-step* op-lst)
|
|
(cond
|
|
((or (null? op-lst) (null? (cdr op-lst))) ; nothing to rewrite
|
|
op-lst)
|
|
; There are at least 2 steps in a sequence of steps
|
|
((and (ddo:check-ast-desc-os? (car op-lst))
|
|
; Next step uses a child axis specifier
|
|
(equal? (txp:step-axis (cadr op-lst)) '(child))
|
|
; Next step doesn't use any predicates
|
|
(null? (txp:step-preds (cadr op-lst))))
|
|
(cons
|
|
(txp:construct-step
|
|
'(descendant) ; rewrite into descendant axis
|
|
(txp:step-node-test (cadr op-lst)) ; Node test of the next step
|
|
)
|
|
(ddo:rewrite-step* (cddr op-lst))))
|
|
(else ; Any other case
|
|
(cons (car op-lst)
|
|
(ddo:rewrite-step* (cdr op-lst))))))
|
|
|
|
|
|
;=========================================================================
|
|
; Optimization for deeply nested predicates
|
|
; For predicates whose level of nesting exceeds 3, these predicates are likely
|
|
; to be called for more than n^3 times, where n is the number of nodes in an
|
|
; SXML document being processed. For such predicates, it is desirable to
|
|
; evaluate them in advance, for every combination of context node, context
|
|
; position and context size (the latter two components are not even required
|
|
; if the predicate doesn't use position).
|
|
; Such an optimization allows achieving a polinomial-time complexity for any
|
|
; XPath expression
|
|
|
|
(define (ddo:generate-pred-id)
|
|
(string->symbol
|
|
(string-append "*predicate-" (symbol->string (gensym)) "*")))
|
|
|
|
;-------------------------------------------------
|
|
; Search for predicate values
|
|
; Predicate values are added to var-binding
|
|
|
|
; Predicate value for a predicate that doesn't require position
|
|
; Predicate values are stored in the form of
|
|
; pred-values ::= (listof (cons node pred-value))
|
|
; NOTE: A node (and not a context) is used as a key in the alist
|
|
(define (ddo:get-pred-value pred-id)
|
|
(lambda (nodeset position+size var-binding)
|
|
(cond
|
|
((not (and (nodeset? nodeset)
|
|
(null? (cdr nodeset))))
|
|
(sxml:xpointer-runtime-error
|
|
"internal DDO SXPath error - "
|
|
"a predicate is supplied with a non-singleton nodeset: " pred-id)
|
|
#f)
|
|
((or (null? var-binding)
|
|
(not (eq? (caar var-binding) '*var-vector*)))
|
|
(sxml:xpointer-runtime-error
|
|
"internal DDO SXPath error - predicate value not found: " pred-id)
|
|
#f)
|
|
; predicate value as expected
|
|
((assq (sxml:context->node (car nodeset))
|
|
(vector-ref (cdar var-binding) pred-id))
|
|
=> (lambda (pair) (force (cdr pair)))
|
|
; => cdr ; DL: was
|
|
)
|
|
(else ; predicate value for the given node not found
|
|
(sxml:xpointer-runtime-error
|
|
"internal DDO SXPath error - no predicate value for node: "
|
|
pred-id (sxml:context->node (car nodeset)))
|
|
#f))))
|
|
|
|
; Predicate value for a predicate that requires position
|
|
; Predicate values are stored in the form of
|
|
; pred-values ::=
|
|
; (listof
|
|
; (cons node
|
|
; (listof
|
|
; (cons size
|
|
; (listof
|
|
; (cons position pred-value))))))
|
|
; NOTE: A node (and not a context) is used as a key in the alist
|
|
(define (ddo:get-pred-value-pos pred-id)
|
|
(lambda (nodeset position+size var-binding)
|
|
(cond
|
|
((not (and (nodeset? nodeset)
|
|
(null? (cdr nodeset))))
|
|
(sxml:xpointer-runtime-error
|
|
"internal DDO SXPath error - "
|
|
"a predicate is supplied with a non-singleton nodeset: " pred-id)
|
|
#f)
|
|
((or (null? var-binding)
|
|
(not (eq? (caar var-binding) '*var-vector*)))
|
|
(sxml:xpointer-runtime-error
|
|
"internal DDO SXPath error - predicate value not found: " pred-id)
|
|
#f)
|
|
; predicate value as expected
|
|
((assq (sxml:context->node (car nodeset))
|
|
(vector-ref (cdar var-binding) pred-id))
|
|
=> (lambda (size-pair)
|
|
(if
|
|
(> (cdr position+size) ; context size
|
|
(vector-length (cdr size-pair)))
|
|
(begin
|
|
(sxml:xpointer-runtime-error
|
|
"internal DDO SXPath error - "
|
|
"vector member for context size not found: " pred-id)
|
|
#f)
|
|
(let ((pos-vect (vector-ref (cdr size-pair)
|
|
(- (cdr position+size) 1))))
|
|
(if
|
|
(> (car position+size) ; context position
|
|
(vector-length pos-vect))
|
|
(begin
|
|
(sxml:xpointer-runtime-error
|
|
"internal DDO SXPath error - "
|
|
"vector member for context position not found: "
|
|
pred-id)
|
|
#f)
|
|
(force (vector-ref pos-vect
|
|
(- (car position+size) 1))))))))
|
|
(else ; predicate value for the given node not found
|
|
(sxml:xpointer-runtime-error
|
|
"internal DDO SXPath error - no predicate value for node: "
|
|
pred-id (sxml:context->node (car nodeset)))
|
|
#f))))
|
|
|
|
; Value that results from evaluating the absolute location path
|
|
; The argument is named `pred-id' for the sake of mere unification with
|
|
; deep predicates
|
|
(define (ddo:get-abs-lpath-value pred-id)
|
|
(lambda (nodeset position+size var-binding)
|
|
(if
|
|
(or (null? var-binding)
|
|
(not (eq? (caar var-binding) '*var-vector*)))
|
|
(begin
|
|
(sxml:xpointer-runtime-error
|
|
"internal DDO SXPath error - "
|
|
"value for absolute location path not found: " pred-id)
|
|
'() ; the value defaults to an empty nodeset
|
|
)
|
|
(vector-ref (cdar var-binding) pred-id))))
|
|
|
|
;-------------------------------------------------
|
|
; Construct predicate values
|
|
|
|
; Construct alist of values for a predicate that doesn't require position
|
|
; pred-impl - lambda that implements the predicate
|
|
; context-set - set of contexts for all nodes in the source document
|
|
; var-bindings - include variables supplied by user and the ones formed by
|
|
; deeper level predicates
|
|
(define (ddo:construct-pred-values pred-impl context-set var-binding)
|
|
(map
|
|
(lambda (context)
|
|
(cons (sxml:context->node context)
|
|
(delay
|
|
(sxml:boolean ; since return type cannot be number
|
|
(pred-impl (list context)
|
|
(cons 1 1) ; dummy context position and size
|
|
var-binding)))))
|
|
context-set))
|
|
|
|
; Construct alist of values for a predicate that requires position
|
|
; pred-impl - lambda that implements the predicate
|
|
; context-set - set of contexts for all nodes in the source document
|
|
; var-bindings - include variables supplied by user and the ones formed by
|
|
; deeper level predicates
|
|
; max-size - maximal context size possible in the document
|
|
(define (ddo:construct-pred-values-pos
|
|
pred-impl context-set var-binding max-size)
|
|
(map
|
|
(lambda (context)
|
|
(cons
|
|
(sxml:context->node context)
|
|
(let ((context (list context)))
|
|
(let iter-size ((size 1)
|
|
(size-lst '()))
|
|
(if
|
|
(> size max-size) ; iteration is over
|
|
(list->vector (reverse size-lst))
|
|
(let iter-pos ((position 1)
|
|
(pos-lst '()))
|
|
(if
|
|
(> position size) ; iteration is over
|
|
(iter-size
|
|
(+ size 1)
|
|
(cons (list->vector (reverse pos-lst))
|
|
size-lst))
|
|
(iter-pos
|
|
(+ position 1)
|
|
(cons
|
|
(delay
|
|
(let ((pred-value
|
|
(pred-impl
|
|
context (cons position size) var-binding)))
|
|
(if (number? pred-value)
|
|
(= pred-value position)
|
|
(sxml:boolean pred-value))))
|
|
pos-lst)))))))))
|
|
context-set))
|
|
|
|
; DL: obsolete
|
|
;; Evaluates all predicates specified in deep-predicates
|
|
;; deep-predicates ::= (listof (list pred-id requires-position? impl))
|
|
;; Returns var-bindings extended with predicate values evaluated
|
|
;; ATTENTION: in deep-predicates, each predicate must come after a predicate it
|
|
;; is dependent on.
|
|
;(define (ddo:evaluate-deep-predicates deep-predicates doc var-binding)
|
|
; (let* ((context-set (ddo:all-contexts-in-doc doc))
|
|
; (max-size (if
|
|
; ; position-required? for at least one deep predicate
|
|
; (not (null? (filter cadr deep-predicates)))
|
|
; (length context-set)
|
|
; 1 ; dummy
|
|
; )))
|
|
; (let iter-preds ((deep-predicates deep-predicates)
|
|
; (var-binding var-binding))
|
|
; (if
|
|
; (null? deep-predicates) ; iteration is over
|
|
; var-binding
|
|
; (iter-preds
|
|
; (cdr deep-predicates)
|
|
; (cons
|
|
; (if
|
|
; (cadar deep-predicates) ; requires-position?
|
|
; (ddo:construct-pred-values-pos (caar deep-predicates) ; pred-id
|
|
; (caddar deep-predicates) ; pred-impl
|
|
; context-set
|
|
; var-binding max-size)
|
|
; (ddo:construct-pred-values (caar deep-predicates) ; pred-id
|
|
; (caddar deep-predicates) ; pred-impl
|
|
; context-set
|
|
; var-binding))
|
|
; var-binding))))))
|
|
|
|
|
|
;=========================================================================
|
|
; Optimization for achieving constant access time to XPath variables
|
|
|
|
; Allocates the new vector from `vect' with the exception of position `k' which
|
|
; is replaced with `obj'
|
|
(define (ddo:vector-copy-set vect k obj)
|
|
(let loop ((src (vector->list vect))
|
|
(pos 0)
|
|
(res '()))
|
|
(if
|
|
(null? src) ; iteration is over
|
|
(list->vector (reverse res))
|
|
(loop (cdr src)
|
|
(+ pos 1)
|
|
(cons
|
|
(if (= pos k) obj (car src))
|
|
res)))))
|
|
|
|
; Extends `var-binding' with a vector data structure for binding variable
|
|
; values and values for deep predicates.
|
|
; Returns extended var-binding, which is constructed as follows:
|
|
; (cons (cons '*var-vector* ,vector)
|
|
; var-binding)
|
|
(define (ddo:add-vector-to-var-binding
|
|
vars2offsets deep-predicates doc var-binding)
|
|
(let ((cons-var-vector ; cons var-vector to var-binding
|
|
(lambda (var-vector var-binding)
|
|
(cons (cons '*var-vector* var-vector)
|
|
var-binding))))
|
|
(if
|
|
(and (null? deep-predicates) (null? var-binding))
|
|
var-binding ; nothing to add
|
|
(let* ((var-tree
|
|
(if
|
|
(< (length var-binding) 100) ; not too many variables
|
|
#f ; do not need any tree
|
|
(ddo:var-binding->tree var-binding)))
|
|
(var-vector
|
|
(let iter-offsets ((pos (- (car vars2offsets) 1))
|
|
(vars-alist (cdr vars2offsets))
|
|
(lst '()))
|
|
(cond
|
|
((< pos 0) ; iteration is over
|
|
(list->vector lst))
|
|
((or (null? vars-alist) ; no more vars in the alist
|
|
(not (= pos (cdar vars-alist))))
|
|
(iter-offsets (- pos 1)
|
|
vars-alist
|
|
(cons #f lst) ; cons a dummy value
|
|
))
|
|
(else ; this position is in the 1st member of vars-alist
|
|
(iter-offsets
|
|
(- pos 1)
|
|
(cdr vars-alist)
|
|
(cons
|
|
(cond ; more sophisticated way of searching for value
|
|
(var-tree ; access variables through var-tree
|
|
(ddo:get-var-value-from-tree ; checks for declared var
|
|
(caar vars-alist) var-tree))
|
|
((assq (caar vars-alist) var-binding)
|
|
=> cdr)
|
|
(else
|
|
(sxml:xpointer-runtime-error "unbound variable - "
|
|
(cdar vars-alist))
|
|
'()))
|
|
lst)))))))
|
|
(if
|
|
(null? deep-predicates)
|
|
(cons-var-vector var-vector var-binding)
|
|
(let* ((context-set
|
|
(if (null? ; just absolute location paths
|
|
(filter
|
|
(lambda (triple)
|
|
(not (eq? (cadr triple) 'absolute-location-path)))
|
|
deep-predicates))
|
|
'() ; dummy
|
|
(ddo:all-contexts-in-doc doc)))
|
|
(max-size
|
|
(if ; position-required? for at least one deep predicate
|
|
(not (null? (filter cadr deep-predicates)))
|
|
(length context-set)
|
|
1 ; dummy
|
|
)))
|
|
(let iter-preds ((deep-predicates deep-predicates)
|
|
(var-vector var-vector))
|
|
(if
|
|
(null? deep-predicates) ; iteration is over
|
|
(cons-var-vector var-vector var-binding)
|
|
(iter-preds
|
|
(cdr deep-predicates)
|
|
(ddo:vector-copy-set
|
|
var-vector
|
|
(caar deep-predicates) ; pred-id
|
|
(cond
|
|
((eq? (cadar deep-predicates) 'absolute-location-path)
|
|
((caddar deep-predicates) ; absolute lpath impl
|
|
(as-nodeset doc)
|
|
(cons 1 1) ; dummy context position and size
|
|
(cons-var-vector var-vector var-binding)))
|
|
((cadar deep-predicates) ; requires-position?
|
|
(ddo:construct-pred-values-pos
|
|
(caddar deep-predicates) ; pred-impl
|
|
context-set
|
|
(cons-var-vector var-vector var-binding)
|
|
max-size))
|
|
(else
|
|
(ddo:construct-pred-values
|
|
(caddar deep-predicates) ; pred-impl
|
|
context-set
|
|
(cons-var-vector var-vector var-binding))))))))))))))
|
|
|
|
;-------------------------------------------------
|
|
; Methods similar to radix sort for linear access time for all variables
|
|
|
|
; Represents a list of chars as a branch in the string-tree
|
|
; The list of chars must be non-empty
|
|
(define (ddo:charlst->branch lst value)
|
|
(if (null? (cdr lst)) ; this is the last character in the lst
|
|
(list (car lst) (cons 'value value))
|
|
`(,(car lst) #f ,(ddo:charlst->branch (cdr lst) value))))
|
|
|
|
; Adds a new string to string-tree
|
|
(define (ddo:add-var-to-tree var-name var-value tree)
|
|
(letrec
|
|
((add-lst-to-tree ; adds the list of chars to tree
|
|
(lambda (lst tree)
|
|
(if
|
|
(null? lst) ; the lst is over
|
|
(cons (car tree)
|
|
(cons (cons 'value var-value) ; replace variable value
|
|
(cddr tree)))
|
|
(let ((curr-char (car lst)))
|
|
(let iter-alist ((alist (cddr tree))
|
|
(res (list (cadr tree) (car tree))))
|
|
(cond
|
|
((null? alist) ; branch not in a tree
|
|
(reverse
|
|
(cons
|
|
(ddo:charlst->branch lst var-value)
|
|
res)))
|
|
((char=? (caar alist) curr-char) ; entry found
|
|
(if
|
|
(null? (cdr alist)) ; nothing more in the alist
|
|
(reverse
|
|
(cons
|
|
(add-lst-to-tree (cdr lst) (car alist))
|
|
res))
|
|
(append
|
|
(reverse
|
|
(cons
|
|
(add-lst-to-tree (cdr lst) (car alist))
|
|
res))
|
|
(cdr alist))))
|
|
((char>? (caar alist) curr-char)
|
|
(if
|
|
(null? (cdr alist)) ; nothing more in the alist
|
|
(reverse
|
|
(cons
|
|
(car alist)
|
|
(cons (ddo:charlst->branch lst var-value)
|
|
res)))
|
|
(append
|
|
(reverse
|
|
(cons
|
|
(ddo:charlst->branch lst var-value)
|
|
res))
|
|
alist)))
|
|
(else
|
|
(iter-alist (cdr alist)
|
|
(cons (car alist) res))))))))))
|
|
(add-lst-to-tree (string->list (symbol->string var-name))
|
|
tree)))
|
|
|
|
; Convert var-binding to their tree representation
|
|
; var-binding is supposed to be non-null
|
|
(define (ddo:var-binding->tree var-binding)
|
|
(let loop ((var-binding (cdr var-binding))
|
|
(tree
|
|
(list '*top*
|
|
#f
|
|
(ddo:charlst->branch
|
|
(string->list
|
|
(symbol->string (caar var-binding))) ; var name
|
|
(cdar var-binding)))))
|
|
(if (null? var-binding)
|
|
tree
|
|
(loop (cdr var-binding)
|
|
(ddo:add-var-to-tree
|
|
(caar var-binding) (cdar var-binding) tree)))))
|
|
|
|
; Obtain variable value from the tree
|
|
(define (ddo:get-var-value-from-tree var-name tree)
|
|
(let loop ((lst (string->list (symbol->string var-name)))
|
|
(tree tree))
|
|
(cond
|
|
((and (not (null? lst))
|
|
(assv (car lst) (cddr tree)))
|
|
=> (lambda (new-tree)
|
|
(loop (cdr lst) new-tree)))
|
|
((and (null? lst) ; lst is over
|
|
(cadr tree) ; value for variable in the tree supplied
|
|
)
|
|
(cdadr tree))
|
|
(else
|
|
(sxml:xpointer-runtime-error "unbound variable - " var-name)
|
|
'() ; dummy value
|
|
))))
|
|
|
|
|
|
;=========================================================================
|
|
; XPath AST processing
|
|
; AST is considered to be properly formed
|
|
; In the signature of functions below, the following terms are taken:
|
|
; op - S-expression which represents the operation
|
|
; num-anc - how many ancestors are required in the context after that
|
|
; operation
|
|
|
|
; {5} <AxisSpecifier> ::= (axis-specifier <AxisName> )
|
|
; {6} <AxisName> ::= (ancestor)
|
|
; | (ancestor-or-self)
|
|
; | (attribute)
|
|
; | (child)
|
|
; | (descendant)
|
|
; | (descendant-or-self)
|
|
; | (following)
|
|
; | (following-sibling)
|
|
; | (namespace)
|
|
; | (parent)
|
|
; | (preceding)
|
|
; | (preceding-sibling)
|
|
; | (self)
|
|
;
|
|
; single-level? - whether all nodes in the input nodeset are located on the
|
|
; same level of tree hierarchy
|
|
; requires-position? - whether context position or context size are required to
|
|
; filter the result produced by the axis
|
|
;
|
|
; For requires-position?=#f, the function returns
|
|
; (list axis-lambda
|
|
; num-anc-it-requires
|
|
; single-level?)
|
|
; For requires-position?=#t, the function returns
|
|
; (list axis-lambda
|
|
; num-anc-it-requires
|
|
; single-level?
|
|
; pos-result?)
|
|
; single-level? - whether nodes are in the single level after the axis
|
|
; pos-result? - whether the result of the axis has the form of pos-result.
|
|
; If #f, the axis returns its result in the form of the common nodeset
|
|
(define (ddo:ast-axis-specifier op num-anc single-level? requires-position?)
|
|
(cond
|
|
((not (eq? (car op) 'axis-specifier)) ; AST error
|
|
(draft:signal-semantic-error "not an AxisSpecifier - " op))
|
|
(requires-position?
|
|
(case (caadr op) ; AxisName
|
|
((ancestor)
|
|
(list ddo:ancestor-pos
|
|
#f #f #t))
|
|
((ancestor-or-self)
|
|
(list ddo:ancestor-or-self-pos
|
|
#f #f #t))
|
|
((attribute)
|
|
(list draft:attribute
|
|
(draft:na-minus-nneg num-anc 1) single-level? #f))
|
|
((child)
|
|
(if single-level?
|
|
(list draft:child
|
|
(draft:na-minus-nneg num-anc 1) #t #f)
|
|
(list ddo:child-pos
|
|
(draft:na-minus-nneg num-anc 1) #f #t)))
|
|
((descendant)
|
|
(if single-level?
|
|
(list draft:descendant
|
|
(draft:na-minus-nneg num-anc 1) #f #f)
|
|
(list ddo:descendant-pos
|
|
(draft:na-minus-nneg num-anc 1) #f #t)))
|
|
((descendant-or-self)
|
|
(if single-level?
|
|
(list draft:descendant-or-self
|
|
num-anc #f #f)
|
|
(list ddo:descendant-or-self-pos
|
|
num-anc #f #t)))
|
|
((following)
|
|
; DL: this is incorrect for single-level?=#f
|
|
(list ddo:following-single-level-pos
|
|
#f #f #t))
|
|
((following-sibling)
|
|
(list (if single-level?
|
|
ddo:following-sibling-single-level-pos
|
|
ddo:following-sibling-pos)
|
|
(draft:na-max num-anc 1) single-level? #t))
|
|
((namespace)
|
|
(list draft:namespace
|
|
(draft:na-minus-nneg num-anc 1) single-level? #f))
|
|
((parent)
|
|
(list (if single-level? ddo:parent-single-level-pos ddo:parent-pos)
|
|
(draft:na+ num-anc 1) single-level? #t))
|
|
((preceding)
|
|
; DL: this is incorrect for single-level?=#f
|
|
(list ddo:preceding-single-level-pos
|
|
#f #f #t))
|
|
((preceding-sibling)
|
|
(list (if single-level?
|
|
ddo:preceding-sibling-single-level-pos
|
|
ddo:preceding-sibling-pos)
|
|
(draft:na-max num-anc 1) single-level? #t))
|
|
((self)
|
|
(list draft:self num-anc single-level? #f))
|
|
(else
|
|
(draft:signal-semantic-error "unknown AxisName - " op))))
|
|
(else ; doesn't require to keep position
|
|
(case (caadr op) ; AxisName
|
|
((ancestor)
|
|
(list ddo:ancestor #f #f))
|
|
((ancestor-or-self)
|
|
(list ddo:ancestor-or-self #f #f))
|
|
((attribute)
|
|
(list draft:attribute
|
|
(draft:na-minus-nneg num-anc 1) single-level?))
|
|
((child)
|
|
(list (if single-level? draft:child ddo:child)
|
|
(draft:na-minus-nneg num-anc 1) single-level?))
|
|
((descendant)
|
|
(list (if single-level? draft:descendant ddo:descendant)
|
|
(draft:na-minus-nneg num-anc 1) #f))
|
|
((descendant-or-self)
|
|
(list (if single-level?
|
|
draft:descendant-or-self ddo:descendant-or-self)
|
|
num-anc #f))
|
|
((following)
|
|
(list (if single-level? ddo:following-single-level ddo:following)
|
|
#f #f))
|
|
((following-sibling)
|
|
(list (if single-level?
|
|
ddo:following-sibling-single-level ddo:following-sibling)
|
|
(draft:na-max num-anc 1) single-level?))
|
|
((namespace)
|
|
(list draft:namespace
|
|
(draft:na-minus-nneg num-anc 1) single-level?))
|
|
((parent)
|
|
(list (if single-level? ddo:parent-single-level ddo:parent)
|
|
(draft:na+ num-anc 1) single-level?))
|
|
((preceding)
|
|
(list (if single-level? ddo:preceding-single-level ddo:preceding)
|
|
#f #f))
|
|
((preceding-sibling)
|
|
(list (if single-level?
|
|
ddo:preceding-sibling-single-level ddo:preceding-sibling)
|
|
(draft:na-max num-anc 1) single-level?))
|
|
((self)
|
|
(list draft:self num-anc single-level?))
|
|
(else
|
|
(draft:signal-semantic-error "unknown AxisName - " op))))))
|
|
|
|
; {7} <NodeTest> ::= (node-test (*))
|
|
; | (node-test (namespace-uri <String> ))
|
|
; | (node-test (namespace-uri <String> )?
|
|
; (local-name <String> ))
|
|
; | (node-test (comment))
|
|
; | (node-test (text))
|
|
; | (node-test (pi <String>? ))
|
|
; | (node-test (point))
|
|
; | (node-test (range))
|
|
; For processing a node test, 'draft:ast-node-test' from "xpath-context.scm"
|
|
; can be used
|
|
|
|
;-------------------------------------------------
|
|
; In this section, each function accepts 5 arguments
|
|
; op - S-expression which represents the operation
|
|
; num-anc - how many ancestors are required in the context after that
|
|
; operation
|
|
; single-level? - for grammar rules that consume the nodeset type as input:
|
|
; whether all nodes in the nodeset are located on the single level of the
|
|
; tree hierarchy. If this is the case, most axes can be evaluated ealier than
|
|
; in the general case.
|
|
; pred-nesting - nesting of the expression being processed within predicates.
|
|
; In particular, pred-nesting=0 denotes the outer expression, pred-nesting=1
|
|
; denotes the expression enclosed into a predicate, pred-nesting=2 for an
|
|
; expression that is enclosed into 2 predicates, etc
|
|
; vars2offsets - mapping from variable names to their offsets in a var-vector
|
|
; vars2offsets ::= (cons vacant-offset
|
|
; (listof (cons var-name var-offset))
|
|
; vacant-offset - a number, initially starts from 0
|
|
; var-offset - offset for a particular variable within a vector
|
|
;
|
|
; AST processing functions return either #f, which signals of a
|
|
; semantic error, or
|
|
; (list (lambda (nodeset position+size var-binding) ...)
|
|
; num-anc-it-requires
|
|
; single-level?
|
|
; requires-position?
|
|
; expr-type
|
|
; deep-predicates
|
|
; vars2offsets )
|
|
; position+size - the same to what was called 'context' in TXPath-1
|
|
; requires-position? - whether position() or last() functions are encountered
|
|
; in the internal expression
|
|
; expr-type - the type returned by the expression being process. The type is
|
|
; determined by symbols. Possible types: number, string, boolean, nodeset and
|
|
; any
|
|
; deep-predicates - an associative list that contains deeply nested predicates,
|
|
; whose pred-nesting>3:
|
|
; deep-predicates ::= (listof (list pred-id requires-position? impl))
|
|
; pred-id - a symbol that identifies the predicate among others
|
|
; impl - the implementation for this predicate
|
|
|
|
; {1} <LocationPath> ::= <RelativeLocationPath>
|
|
; | <AbsoluteLocationPath>
|
|
(define (ddo:ast-location-path
|
|
op num-anc single-level? pred-nesting vars2offsets)
|
|
(case (car op)
|
|
((absolute-location-path)
|
|
(ddo:ast-absolute-location-path
|
|
op num-anc single-level? pred-nesting vars2offsets))
|
|
((relative-location-path)
|
|
(ddo:ast-relative-location-path
|
|
op num-anc single-level? pred-nesting vars2offsets))
|
|
(else
|
|
(draft:signal-semantic-error "improper LocationPath - " op))))
|
|
|
|
; {2} <AbsoluteLocationPath> ::= (absolute-location-path <Step>* )
|
|
; NOTE: single-level? is dummy here, since AbsoluteLocationPath always
|
|
; starts from a single node - the root of the document
|
|
(define (ddo:ast-absolute-location-path
|
|
op num-anc single-level? pred-nesting vars2offsets)
|
|
(cond
|
|
((not (eq? (car op) 'absolute-location-path))
|
|
(draft:signal-semantic-error "not an AbsoluteLocationPath - " op))
|
|
((null? (cdr op)) ; no Steps
|
|
(list
|
|
(lambda (nodeset position+size var-binding)
|
|
(draft:reach-root nodeset))
|
|
#f ; requires all ancestors
|
|
#t ; on single level
|
|
#f ; doesn't require position
|
|
ddo:type-nodeset
|
|
'() ; no deep predicates
|
|
vars2offsets
|
|
))
|
|
(else
|
|
(and-let*
|
|
((steps-res (ddo:ast-step-list
|
|
(cdr op) num-anc #t pred-nesting vars2offsets)))
|
|
(let ((impl ; implementation of the absolute location path
|
|
(if
|
|
(null? (cdar steps-res)) ; only a single step
|
|
(let ((step-impl (caar steps-res)))
|
|
(lambda (nodeset position+size var-binding)
|
|
(step-impl
|
|
(draft:reach-root nodeset) position+size var-binding)))
|
|
(let ((converters (car steps-res)))
|
|
(lambda (nodeset position+size var-binding)
|
|
(let rpt ((nset (draft:reach-root nodeset))
|
|
(fs converters))
|
|
(if (null? fs)
|
|
nset
|
|
(rpt ((car fs) nset position+size var-binding)
|
|
(cdr fs)))))))))
|
|
(if
|
|
(> pred-nesting 0) ; absolute location path inside a predicate
|
|
(let ((vars2offsets (list-ref steps-res 6)))
|
|
(list
|
|
(ddo:get-abs-lpath-value (car vars2offsets))
|
|
#f ; all ancestors required
|
|
(caddr steps-res) ; single-level
|
|
#f ; doesn't require position
|
|
ddo:type-nodeset
|
|
(cons
|
|
(list (car vars2offsets) ; identifier
|
|
'absolute-location-path ; flag to denote absolute lpath
|
|
impl)
|
|
(list-ref steps-res 5) ; deep-predicates
|
|
)
|
|
(cons (+ (car vars2offsets) 1)
|
|
(cdr vars2offsets))))
|
|
(cons impl
|
|
(cons #f ; all ancestors required
|
|
(cddr steps-res) ; the remaining parameters
|
|
))))))))
|
|
|
|
; {3} <RelativeLocationPath> ::= (relative-location-path <Step>+ )
|
|
(define (ddo:ast-relative-location-path
|
|
op num-anc single-level? pred-nesting vars2offsets)
|
|
(if
|
|
(not (eq? (car op) 'relative-location-path))
|
|
(draft:signal-semantic-error "not a RelativeLocationPath - " op)
|
|
(and-let*
|
|
((steps-res
|
|
(ddo:ast-step-list
|
|
(cdr op) num-anc single-level? pred-nesting vars2offsets)))
|
|
(cons
|
|
(if
|
|
(null? (cdar steps-res)) ; only a single step
|
|
(caar steps-res)
|
|
(let ((converters (car steps-res)))
|
|
(lambda (nodeset position+size var-binding)
|
|
(let rpt ((nset nodeset)
|
|
(fs converters))
|
|
(if (null? fs)
|
|
nset
|
|
(rpt ((car fs) nset position+size var-binding)
|
|
(cdr fs)))))))
|
|
(cdr steps-res) ; the remaining parameters
|
|
))))
|
|
|
|
; {4} <Step> ::= (step <AxisSpecifier> <NodeTest> <Predicate>* )
|
|
; | (range-to (expr <Expr>) <Predicate>* )
|
|
(define (ddo:ast-step op num-anc single-level? pred-nesting vars2offsets)
|
|
(cond
|
|
((eq? (car op) 'range-to)
|
|
(draft:signal-semantic-error "range-to function not implemented"))
|
|
((eq? (car op) 'filter-expr)
|
|
(ddo:ast-filter-expr op num-anc single-level? pred-nesting vars2offsets))
|
|
((eq? (car op) 'lambda-step) ; created by sxpath
|
|
(let ((proc (cadr op)))
|
|
(list
|
|
(if
|
|
(and num-anc (zero? num-anc)) ; no ancestors required
|
|
(lambda (nodeset position+size var-binding)
|
|
(proc (draft:contextset->nodeset (as-nodeset nodeset))
|
|
(if (and (pair? var-binding) ; non-null
|
|
(eq? (caar var-binding) '*var-vector*))
|
|
(cdr var-binding) var-binding)))
|
|
(lambda (nodeset position+size var-binding)
|
|
(draft:find-proper-context
|
|
(proc (draft:contextset->nodeset (as-nodeset nodeset))
|
|
(if (and (pair? var-binding) ; non-null
|
|
(eq? (caar var-binding) '*var-vector*))
|
|
(cdr var-binding) var-binding))
|
|
(map sxml:context->content ; TODO: should add variables
|
|
(as-nodeset nodeset))
|
|
num-anc)))
|
|
num-anc ; num-ancestors
|
|
#f ; single-level? after this step
|
|
#f ; position-required?
|
|
ddo:type-any
|
|
'() ; no deep predicates
|
|
vars2offsets
|
|
)))
|
|
((eq? (car op) 'step)
|
|
(if
|
|
(null? (cdddr op)) ; no Predicates
|
|
(and-let*
|
|
((axis-lst (ddo:ast-axis-specifier
|
|
(cadr op) num-anc single-level? #f))
|
|
(ntest (draft:ast-node-test (caddr op))))
|
|
(let ((axis ((car axis-lst) ntest num-anc)))
|
|
(list
|
|
(lambda (nodeset position+size var-binding)
|
|
(axis nodeset))
|
|
(cadr axis-lst)
|
|
(caddr axis-lst)
|
|
#f
|
|
ddo:type-nodeset
|
|
'() ; no deep predicates
|
|
vars2offsets
|
|
)))
|
|
; There are Predicates
|
|
(and-let*
|
|
((preds-res (ddo:ast-predicate-list
|
|
(cdddr op) 0 #t (+ pred-nesting 1) vars2offsets))
|
|
(preds-res
|
|
(if (and (list-ref preds-res 3) ; position required for the predicate
|
|
(< pred-nesting 3)) ; level of nesting matters
|
|
(ddo:ast-predicate-list ; the second pass
|
|
(cdddr op) 0 #t
|
|
(+ pred-nesting 2) ; called for quadratic number of times
|
|
vars2offsets
|
|
)
|
|
preds-res ; do not need to change anything
|
|
))
|
|
(axis-lst (ddo:ast-axis-specifier
|
|
(cadr op)
|
|
(draft:na-max num-anc (cadr preds-res))
|
|
single-level?
|
|
(list-ref preds-res 3) ; whether position required
|
|
))
|
|
(ntest (draft:ast-node-test (caddr op))))
|
|
(let ((axis ((car axis-lst)
|
|
ntest (draft:na-max num-anc (cadr preds-res))))
|
|
(pred-impl-lst (car preds-res)))
|
|
(list
|
|
(cond
|
|
((not (list-ref preds-res 3)) ; whether position required
|
|
(ddo:location-step-non-pos axis pred-impl-lst))
|
|
((list-ref axis-lst 3) ; pos-result?
|
|
(ddo:location-step-pos axis pred-impl-lst))
|
|
(else ; non-intersect
|
|
(ddo:location-step-non-intersect axis pred-impl-lst)))
|
|
(cadr axis-lst) ; num-ancestors
|
|
(caddr axis-lst) ; single-level? after this step
|
|
#f ; position-required?
|
|
ddo:type-nodeset
|
|
(list-ref preds-res 5) ; deep predicates
|
|
(list-ref preds-res 6) ; new var-binding
|
|
)))))
|
|
(else
|
|
(draft:signal-semantic-error "not a Step - " op))))
|
|
|
|
; {4a} ( <Step>+ )
|
|
; Returns (list (listof step-impl)
|
|
; num-anc single-level? requires-position? expr-type
|
|
; deep-predicates vars2offsets)
|
|
; or #f
|
|
; TECHNICAL NOTE: To calculate 'single-level?', we need to process steps in
|
|
; straight orger. To calculate 'num-anc', we need to process steps in reverse
|
|
; order. This thus has to be implemented in 2 passes
|
|
(define (ddo:ast-step-list
|
|
step-lst num-anc single-level? pred-nesting vars2offsets)
|
|
(let ((step-lst (ddo:rewrite-step* step-lst))
|
|
; Calculates single-level? for each step in the step-lst
|
|
; Returns: (listof single-level?)
|
|
; where each member of the REVERSED result list corresponds to the step
|
|
; in the corresponding position of a step-lst
|
|
; We can notice that when single-level?=#f for some step, it remains
|
|
; #f for all the subsequent steps
|
|
(calculate-single-level
|
|
(lambda (step-lst single-level?)
|
|
(let iter-steps ((steps step-lst)
|
|
(sl? single-level?)
|
|
(res '()))
|
|
(cond
|
|
((null? steps) res)
|
|
((or (memq (caar steps) '(range-to filter-expr lambda-step))
|
|
(not sl?))
|
|
; #f for the remaining steps
|
|
(append (map
|
|
(lambda (step) #f)
|
|
steps) ; DL: was: step-lst
|
|
res))
|
|
(else ; evaluate single-level? for the current step
|
|
(and-let*
|
|
((axis-lst (ddo:ast-axis-specifier
|
|
(cadar steps) ; is to be axis specifier
|
|
0 sl? #f)))
|
|
(iter-steps (cdr steps)
|
|
(caddr axis-lst) ; single-level for next step
|
|
(cons sl? res)))))))))
|
|
(and-let*
|
|
((single-level-lst (calculate-single-level step-lst single-level?)))
|
|
(let loop ((steps-to-view (reverse step-lst))
|
|
(sl?-lst single-level-lst)
|
|
(res-lst '())
|
|
(num-anc num-anc)
|
|
(deep-predicates '())
|
|
(vars2offsets vars2offsets))
|
|
(if
|
|
(null? steps-to-view) ; everyone processed
|
|
(list res-lst
|
|
num-anc (car single-level-lst) #f
|
|
ddo:type-nodeset deep-predicates vars2offsets)
|
|
(and-let*
|
|
((step-res
|
|
(ddo:ast-step
|
|
(car steps-to-view) num-anc (car sl?-lst)
|
|
pred-nesting vars2offsets)))
|
|
(loop
|
|
(cdr steps-to-view)
|
|
(cdr sl?-lst)
|
|
(cons (car step-res) res-lst)
|
|
(cadr step-res)
|
|
(append (list-ref step-res 5) deep-predicates)
|
|
(list-ref step-res 6) ; new vars2offsets
|
|
)))))))
|
|
|
|
; {8} <Predicate> ::= (predicate <Expr> )
|
|
; NOTE: num-anc is dummy here, since it is always 0 for Predicates
|
|
; NOTE: single-level? is dummy here, since a Predicate is always called for
|
|
; a single node to be filtered
|
|
; NOTE: Unlike 'draft:ast-predicate', we don't implement any filtering here,
|
|
; because it depends on the particular axis in the step. Filtering is
|
|
; performed on the higher level
|
|
(define (ddo:ast-predicate op num-anc single-level? pred-nesting vars2offsets)
|
|
(if
|
|
(not (eq? (car op) 'predicate))
|
|
(draft:signal-semantic-error "not an Predicate - " op)
|
|
(and-let*
|
|
((expr-res (ddo:ast-expr (cadr op) 0 #t pred-nesting vars2offsets)))
|
|
(let ((requires-position?
|
|
(or (cadddr expr-res) ; predicate expression requires position
|
|
(memq (list-ref expr-res 4) ; involves position implicitly
|
|
'(ddo:type-number ddo:type-any))))
|
|
(vars2offsets (list-ref expr-res 6)))
|
|
(call-with-values
|
|
(lambda ()
|
|
(if
|
|
(or ; this is a deep predicate
|
|
(> pred-nesting 3)
|
|
; DL: theoretically reasonable although impractical condition:
|
|
;(and (not requires-position?) (> pred-nesting 1))
|
|
)
|
|
(let ((pred-id (car vars2offsets)
|
|
; was: (ddo:generate-pred-id)
|
|
))
|
|
(values
|
|
((if requires-position?
|
|
ddo:get-pred-value-pos ddo:get-pred-value)
|
|
pred-id)
|
|
(cons
|
|
(list pred-id
|
|
requires-position?
|
|
(car expr-res) ; implementation
|
|
)
|
|
(list-ref expr-res 5) ; deep-predicates
|
|
)
|
|
(cons (+ (car vars2offsets) 1)
|
|
(cdr vars2offsets))))
|
|
(values (car expr-res) ; implementation
|
|
(list-ref expr-res 5)
|
|
vars2offsets)))
|
|
(lambda (pred-impl deep-predicates vars2offsets)
|
|
(list pred-impl
|
|
(cadr expr-res) ; num-ancestors required
|
|
(caddr expr-res) ; single-level? - we don't care
|
|
requires-position?
|
|
(list-ref expr-res 4) ; return type
|
|
deep-predicates
|
|
vars2offsets)))))))
|
|
|
|
; {8a} ( <Predicate>+ )
|
|
; Returns (list (listof pred-impl)
|
|
; num-anc single-level? requires-position? expr-type
|
|
; deep-predicates)
|
|
; or #f
|
|
; NOTE: num-anc is dummy here, since it is always 0 for Predicates
|
|
; NOTE: single-level? is dummy here, since a Predicate is always called for
|
|
; a single node to be filtered
|
|
; NOTE: information about the type for each Predicate is lost
|
|
(define (ddo:ast-predicate-list
|
|
op-lst num-anc single-level? pred-nesting vars2offsets)
|
|
(let ((pred-res-lst
|
|
(ddo:foldr
|
|
(lambda (op init)
|
|
(cons
|
|
(ddo:ast-predicate
|
|
op 0 #t pred-nesting
|
|
(if (or (null? init) ; called for the first time
|
|
(not (car init)))
|
|
vars2offsets
|
|
(list-ref (car init) 6) ; vars2offsets from previous pred
|
|
))
|
|
init))
|
|
'()
|
|
op-lst)))
|
|
(and
|
|
(not (memv #f pred-res-lst)) ; error detected
|
|
(list (map car pred-res-lst)
|
|
(apply draft:na-max (map cadr pred-res-lst))
|
|
#t
|
|
(apply ddo:or (map cadddr pred-res-lst))
|
|
ddo:type-any
|
|
(apply append ; deep-predicates
|
|
(map
|
|
(lambda (pred-res) (list-ref pred-res 5))
|
|
pred-res-lst))
|
|
(list-ref (car pred-res-lst) 6) ; vars2offsets
|
|
))))
|
|
|
|
; {9} <Expr> ::= <OrExpr>
|
|
; | <AndExpr>
|
|
; | <EqualityExpr>
|
|
; | <RelationalExpr>
|
|
; | <AdditiveExpr>
|
|
; | <MultiplicativeExpr>
|
|
; | <UnionExpr>
|
|
; | <PathExpr>
|
|
; | <FilterExpr>
|
|
; | <VariableReference>
|
|
; | <Literal>
|
|
; | <Number>
|
|
; | <FunctionCall>
|
|
; | <LocationPath>
|
|
(define (ddo:ast-expr op num-anc single-level? pred-nesting vars2offsets)
|
|
(case (car op)
|
|
((or)
|
|
(ddo:ast-or-expr op num-anc single-level? pred-nesting vars2offsets))
|
|
((and)
|
|
(ddo:ast-and-expr op num-anc single-level? pred-nesting vars2offsets))
|
|
((= !=)
|
|
(ddo:ast-equality-expr op num-anc single-level? pred-nesting vars2offsets))
|
|
((< > <= >=)
|
|
(ddo:ast-relational-expr
|
|
op num-anc single-level? pred-nesting vars2offsets))
|
|
((+ -)
|
|
(ddo:ast-additive-expr op num-anc single-level? pred-nesting vars2offsets))
|
|
((* div mod)
|
|
(ddo:ast-multiplicative-expr
|
|
op num-anc single-level? pred-nesting vars2offsets))
|
|
((union-expr)
|
|
(ddo:ast-union-expr op num-anc single-level? pred-nesting vars2offsets))
|
|
((path-expr)
|
|
(ddo:ast-path-expr op num-anc single-level? pred-nesting vars2offsets))
|
|
((filter-expr)
|
|
(ddo:ast-filter-expr op num-anc single-level? pred-nesting vars2offsets))
|
|
((variable-reference)
|
|
(ddo:ast-variable-reference
|
|
op num-anc single-level? pred-nesting vars2offsets))
|
|
((literal)
|
|
(ddo:ast-literal op num-anc single-level? pred-nesting vars2offsets))
|
|
((number)
|
|
(ddo:ast-number op num-anc single-level? pred-nesting vars2offsets))
|
|
((function-call)
|
|
(ddo:ast-function-call op num-anc single-level? pred-nesting vars2offsets))
|
|
((absolute-location-path)
|
|
(ddo:ast-absolute-location-path
|
|
op num-anc single-level? pred-nesting vars2offsets))
|
|
((relative-location-path)
|
|
(ddo:ast-relative-location-path
|
|
op num-anc single-level? pred-nesting vars2offsets))
|
|
(else
|
|
(draft:signal-semantic-error "unknown Expr - " op))))
|
|
|
|
; Applies AST processing to a list of operations
|
|
(define (ddo:apply-ast-procedure
|
|
ast-procedure op-lst num-anc single-level? pred-nesting vars2offsets)
|
|
(ddo:foldr
|
|
(lambda (expr init)
|
|
(cons
|
|
(ast-procedure
|
|
expr num-anc single-level? pred-nesting
|
|
(if (or (null? init) ; called for the first time
|
|
(not (car init)) ; error during previously processed expr
|
|
)
|
|
vars2offsets
|
|
(list-ref (car init) 6) ; vars2offsets from previous expr
|
|
))
|
|
init))
|
|
'()
|
|
op-lst))
|
|
|
|
; {10} <OrExpr> ::= (or <Expr> <Expr>+ )
|
|
; NOTE: num-anc is dummy here, since it is always 0 for OrExpr
|
|
(define (ddo:ast-or-expr op num-anc single-level? pred-nesting vars2offsets)
|
|
(let ((expr-res-lst
|
|
(ddo:apply-ast-procedure
|
|
ddo:ast-expr
|
|
(cdr op) 0 single-level? pred-nesting vars2offsets)))
|
|
(and
|
|
(not (memv #f expr-res-lst)) ; error detected
|
|
(let ((expr-impls (map car expr-res-lst)))
|
|
(list
|
|
(lambda (nodeset position+size var-binding)
|
|
(let rpt ((fs expr-impls))
|
|
(cond
|
|
((null? fs) #f)
|
|
((sxml:boolean ((car fs) nodeset position+size var-binding)) #t)
|
|
(else (rpt (cdr fs))))))
|
|
(apply draft:na-max (map cadr expr-res-lst)) ; num-ancestors
|
|
#t ; single-level? after this step
|
|
(apply ddo:or (map cadddr expr-res-lst)) ; position-required?
|
|
ddo:type-boolean
|
|
(apply append ; deep-predicates
|
|
(map
|
|
(lambda (expr-res) (list-ref expr-res 5))
|
|
expr-res-lst))
|
|
(list-ref (car expr-res-lst) 6) ; vars2offsets
|
|
)))))
|
|
|
|
; {11} <AndExpr> ::= (and <Expr> <Expr>+ )
|
|
; NOTE: num-anc is dummy here, since it is always 0 for AndExpr
|
|
(define (ddo:ast-and-expr op num-anc single-level? pred-nesting vars2offsets)
|
|
(let ((expr-res-lst
|
|
(ddo:apply-ast-procedure
|
|
ddo:ast-expr
|
|
(cdr op) 0 single-level? pred-nesting vars2offsets)))
|
|
(and
|
|
(not (memv #f expr-res-lst)) ; error detected
|
|
(let ((expr-impls (map car expr-res-lst)))
|
|
(list
|
|
(lambda (nodeset position+size var-binding)
|
|
(let rpt ((fs expr-impls))
|
|
(cond
|
|
((null? fs) #t)
|
|
((not
|
|
(sxml:boolean ((car fs) nodeset position+size var-binding)))
|
|
#f)
|
|
(else (rpt (cdr fs))))))
|
|
(apply draft:na-max (map cadr expr-res-lst)) ; num-ancestors
|
|
#t ; single-level? after this step
|
|
(apply ddo:or (map cadddr expr-res-lst)) ; position-required?
|
|
ddo:type-boolean
|
|
(apply append ; deep-predicates
|
|
(map
|
|
(lambda (expr-res) (list-ref expr-res 5))
|
|
expr-res-lst))
|
|
(list-ref (car expr-res-lst) 6) ; vars2offsets
|
|
)))))
|
|
|
|
; {12} <EqualityExpr> ::= (= <Expr> <Expr> )
|
|
; | (!= <Expr> <Expr> )
|
|
; NOTE: num-anc is dummy here, since it is always 0 for EqualityExpr
|
|
(define (ddo:ast-equality-expr
|
|
op num-anc single-level? pred-nesting vars2offsets)
|
|
(and-let*
|
|
((left-lst
|
|
(ddo:ast-expr (cadr op) 0 single-level? pred-nesting vars2offsets))
|
|
(right-lst
|
|
(ddo:ast-expr (caddr op) 0 single-level? pred-nesting
|
|
(list-ref left-lst 6) ; vars2offsets for left part
|
|
)))
|
|
(let ((cmp-op (cadr (assq (car op) `((= ,sxml:equal?)
|
|
(!= ,sxml:not-equal?)))))
|
|
(left (car left-lst))
|
|
(right (car right-lst)))
|
|
(list
|
|
(lambda (nodeset position+size var-binding)
|
|
(cmp-op
|
|
(draft:contextset->nodeset
|
|
(left nodeset position+size var-binding))
|
|
(draft:contextset->nodeset
|
|
(right nodeset position+size var-binding))))
|
|
(draft:na-max (cadr left-lst) (cadr right-lst)) ; num-ancestors
|
|
#t ; single-level? after this step
|
|
(or (cadddr left-lst) (cadddr right-lst)) ; position-required?
|
|
ddo:type-boolean
|
|
(append (list-ref left-lst 5) ; deep-predicates
|
|
(list-ref right-lst 5))
|
|
(list-ref right-lst 6) ; vars2offsets for right part
|
|
))))
|
|
|
|
; {13} <RelationalExpr> ::= (< <Expr> <Expr> )
|
|
; | (> <Expr> <Expr> )
|
|
; | (<= <Expr> <Expr> )
|
|
; | (>= <Expr> <Expr> )
|
|
; NOTE: num-anc is dummy here, since it is always 0 for RelationalExpr
|
|
(define (ddo:ast-relational-expr
|
|
op num-anc single-level? pred-nesting vars2offsets)
|
|
(and-let*
|
|
((left-lst
|
|
(ddo:ast-expr (cadr op) 0 single-level? pred-nesting vars2offsets))
|
|
(right-lst
|
|
(ddo:ast-expr (caddr op) 0 single-level? pred-nesting
|
|
(list-ref left-lst 6) ; vars2offsets for left part
|
|
)))
|
|
(let ((cmp-op
|
|
(sxml:relational-cmp
|
|
(cadr (assq (car op) `((< ,<) (> ,>) (<= ,<=) (>= ,>=))))))
|
|
(left (car left-lst))
|
|
(right (car right-lst)))
|
|
(list
|
|
(lambda (nodeset position+size var-binding)
|
|
(cmp-op
|
|
(draft:contextset->nodeset
|
|
(left nodeset position+size var-binding))
|
|
(draft:contextset->nodeset
|
|
(right nodeset position+size var-binding))))
|
|
(draft:na-max (cadr left-lst) (cadr right-lst)) ; num-ancestors
|
|
#t ; single-level? after this step
|
|
(or (cadddr left-lst) (cadddr right-lst)) ; position-required?
|
|
ddo:type-boolean
|
|
(append (list-ref left-lst 5) ; deep-predicates
|
|
(list-ref right-lst 5))
|
|
(list-ref right-lst 6) ; vars2offsets for right part
|
|
))))
|
|
|
|
; {14} <AdditiveExpr> ::= (+ <Expr> <Expr> )
|
|
; | (- <Expr> <Expr>? )
|
|
; NOTE: num-anc is dummy here, since it is always 0 for AdditiveExpr
|
|
(define (ddo:ast-additive-expr
|
|
op num-anc single-level? pred-nesting vars2offsets)
|
|
(let ((expr-res-lst
|
|
(ddo:apply-ast-procedure
|
|
ddo:ast-expr
|
|
(cdr op) 0 single-level? pred-nesting vars2offsets)))
|
|
(and
|
|
(not (memv #f expr-res-lst)) ; error detected
|
|
(let ((add-op (cadr (assq (car op) `((+ ,+) (- ,-)))))
|
|
(expr-impls (map car expr-res-lst)))
|
|
(list
|
|
(lambda (nodeset position+size var-binding)
|
|
(apply
|
|
add-op
|
|
(map
|
|
(lambda (expr)
|
|
(sxml:number
|
|
(draft:contextset->nodeset
|
|
(expr nodeset position+size var-binding))))
|
|
expr-impls)))
|
|
(apply draft:na-max (map cadr expr-res-lst)) ; num-ancestors
|
|
#t ; single-level? after this step
|
|
(apply ddo:or (map cadddr expr-res-lst)) ; position-required?
|
|
ddo:type-number
|
|
(apply append ; deep-predicates
|
|
(map
|
|
(lambda (expr-res) (list-ref expr-res 5))
|
|
expr-res-lst))
|
|
(list-ref (car expr-res-lst) 6) ; vars2offsets
|
|
)))))
|
|
|
|
; {15} <MultiplicativeExpr> ::= (* <Expr> <Expr> )
|
|
; | (div <Expr> <Expr> )
|
|
; | (mod <Expr> <Expr> )
|
|
; NOTE: num-anc is dummy here, since it is always 0 for MultiplicativeExpr
|
|
(define (ddo:ast-multiplicative-expr
|
|
op num-anc single-level? pred-nesting vars2offsets)
|
|
(and-let*
|
|
((left-lst
|
|
(ddo:ast-expr (cadr op) 0 single-level? pred-nesting vars2offsets))
|
|
(right-lst
|
|
(ddo:ast-expr (caddr op) 0 single-level? pred-nesting
|
|
(list-ref left-lst 6) ; vars2offsets for left part
|
|
)))
|
|
(let ((mul-op
|
|
(sxml:relational-cmp
|
|
(cadr (assq (car op) `((* ,*) (div ,/) (mod ,remainder))))))
|
|
(left (car left-lst))
|
|
(right (car right-lst)))
|
|
(list
|
|
(lambda (nodeset position+size var-binding)
|
|
(mul-op
|
|
(sxml:number
|
|
(draft:contextset->nodeset
|
|
(left nodeset position+size var-binding)))
|
|
(sxml:number
|
|
(draft:contextset->nodeset
|
|
(right nodeset position+size var-binding)))))
|
|
(draft:na-max (cadr left-lst) (cadr right-lst)) ; num-ancestors
|
|
#t ; single-level? after this step
|
|
(or (cadddr left-lst) (cadddr right-lst)) ; position-required?
|
|
ddo:type-number
|
|
(append (list-ref left-lst 5) ; deep-predicates
|
|
(list-ref right-lst 5))
|
|
(list-ref right-lst 6) ; vars2offsets for right part
|
|
))))
|
|
|
|
; {16} <UnionExpr> ::= (union-expr <Expr> <Expr>+ )
|
|
; TECHNICAL NOTE: For implementing the union while supporting distinct document
|
|
; order, we need num-ancestors=#f for the arguments of the union-expr. This
|
|
; operation is time-consuming and should be avoided
|
|
(define (ddo:ast-union-expr op num-anc single-level? pred-nesting vars2offsets)
|
|
(let ((expr-res-lst
|
|
(ddo:foldr
|
|
(lambda (expr init)
|
|
(let ((expr-res
|
|
(if
|
|
(or (null? init) ; called for the first time
|
|
(not (car init)))
|
|
(ddo:ast-expr
|
|
expr num-anc ; not necessarily all ancestors
|
|
single-level? pred-nesting vars2offsets)
|
|
(ddo:ast-expr
|
|
expr #f single-level? pred-nesting
|
|
(list-ref (car init) 6) ; vars2offsets from previous expr
|
|
))))
|
|
(cons
|
|
(if
|
|
(not (or (eq? (list-ref expr-res 4) ddo:type-nodeset)
|
|
(eq? (list-ref expr-res 4) ddo:type-any)))
|
|
(draft:signal-semantic-error
|
|
"expression to be unioned evaluates to a non-nodeset - "
|
|
expr)
|
|
expr-res)
|
|
init)))
|
|
'()
|
|
(cdr op))))
|
|
(and
|
|
(not (memv #f expr-res-lst)) ; error detected
|
|
(let ((expr-impls (map car expr-res-lst)))
|
|
(list
|
|
(lambda (nodeset position+size var-binding)
|
|
(let rpt ((res '())
|
|
(fs expr-impls))
|
|
(if
|
|
(null? fs)
|
|
res
|
|
(let ((nset ((car fs) nodeset position+size var-binding)))
|
|
(rpt
|
|
(ddo:unite-2-contextsets
|
|
res
|
|
(cond
|
|
((not (nodeset? nset))
|
|
(sxml:xpointer-runtime-error
|
|
"expected - nodeset instead of " nset)
|
|
'())
|
|
(else nset)))
|
|
(cdr fs))))))
|
|
#f ; num-ancestors
|
|
#f ; single-level? after this step
|
|
(apply ddo:or (map cadddr expr-res-lst)) ; position-required?
|
|
ddo:type-nodeset
|
|
(apply append ; deep-predicates
|
|
(map
|
|
(lambda (expr-res) (list-ref expr-res 5))
|
|
expr-res-lst))
|
|
(list-ref (car expr-res-lst) 6) ; vars2offsets
|
|
)))))
|
|
|
|
; {17} <PathExpr> ::= (path-expr <FilterExpr> <Step>+ )
|
|
; TECHNICAL NOTE: To calculate 'single-level?', we need to process components
|
|
; in straight orger. To calculate 'num-anc', we need to process steps in
|
|
; reverse order. It is too expensive to make the 2 passes, that's why we
|
|
; consider single-level?=#f for steps
|
|
(define (ddo:ast-path-expr op num-anc single-level? pred-nesting vars2offsets)
|
|
(and-let*
|
|
((steps-res (ddo:ast-step-list
|
|
(cddr op) num-anc
|
|
#f ; consider single-level?=#f after FilterExpr
|
|
pred-nesting
|
|
vars2offsets))
|
|
(filter-lst (ddo:ast-filter-expr
|
|
(cadr op)
|
|
(cadr steps-res) ; num-ancestors
|
|
single-level?
|
|
pred-nesting
|
|
(list-ref steps-res 6) ; vars2offsets from steps-list
|
|
)))
|
|
(if
|
|
(not (or (eq? (list-ref filter-lst 4) ddo:type-nodeset)
|
|
(eq? (list-ref filter-lst 4) ddo:type-any)))
|
|
(draft:signal-semantic-error
|
|
"location steps are applied to a non-nodeset result - " (cadr op))
|
|
(let ((init-impl (car filter-lst))
|
|
(converters (car steps-res)))
|
|
(list
|
|
(lambda (nodeset position+size var-binding)
|
|
(let ((nset
|
|
(init-impl nodeset position+size var-binding)))
|
|
(let rpt ((nset
|
|
(cond
|
|
((nodeset? nset) nset)
|
|
(else
|
|
(sxml:xpointer-runtime-error
|
|
"expected - nodeset instead of " nset)
|
|
'())))
|
|
(fs converters))
|
|
(if (null? fs)
|
|
nset
|
|
(rpt ((car fs) nset position+size var-binding)
|
|
(cdr fs))))))
|
|
(cadr filter-lst) ; num-ancestors
|
|
(cadddr steps-res) ; single-level?, =#f in our assumption
|
|
(cadddr filter-lst) ; position-required?
|
|
ddo:type-nodeset
|
|
(append (list-ref filter-lst 5) ; deep-predicates
|
|
(list-ref steps-res 5))
|
|
(list-ref filter-lst 6) ; vars2offsets from filter-lst
|
|
)))))
|
|
|
|
; {18} <FilterExpr> ::= (filter-expr (primary-expr <Expr> )
|
|
; <Predicate>* )
|
|
(define (ddo:ast-filter-expr op num-anc single-level? pred-nesting vars2offsets)
|
|
(cond
|
|
((not (eq? (car op) 'filter-expr))
|
|
(draft:signal-semantic-error "not an FilterExpr - " op))
|
|
((not (eq? (caadr op) 'primary-expr))
|
|
(draft:signal-semantic-error "not an PrimaryExpr - " (cadr op)))
|
|
((null? (cddr op)) ; no Predicates
|
|
(ddo:ast-expr (cadadr op) num-anc single-level? pred-nesting vars2offsets))
|
|
((and (null? (cdddr op)) ; a single predicate
|
|
(ddo:check-special-predicate (caddr op)))
|
|
=> (lambda (special-pred-impl)
|
|
(and-let*
|
|
((expr-lst (ddo:ast-expr
|
|
(cadadr op)
|
|
num-anc ; special predicate doesn't require ancestors
|
|
single-level? pred-nesting vars2offsets)))
|
|
(list
|
|
(ddo:filter-expr-special-predicate
|
|
(car expr-lst) special-pred-impl)
|
|
(cadr expr-lst) ; num-ancestors
|
|
(caddr expr-lst) ; single-level? after this step
|
|
(cadddr expr-lst) ; position-required?
|
|
ddo:type-nodeset
|
|
(list-ref expr-lst 5) ; deep-predicates
|
|
(list-ref expr-lst 6) ; vars2offsets
|
|
))))
|
|
(else ; the general case
|
|
(and-let*
|
|
((preds-res (ddo:ast-predicate-list
|
|
(cddr op) 0 #t (+ pred-nesting 1) vars2offsets))
|
|
(expr-lst (ddo:ast-expr
|
|
(cadadr op)
|
|
(draft:na-max num-anc (cadr preds-res)) ; num-anc
|
|
single-level? pred-nesting
|
|
(list-ref preds-res 6) ; vars2offsets from predicates
|
|
)))
|
|
(if
|
|
(not (or (eq? (list-ref expr-lst 4) ddo:type-nodeset)
|
|
(eq? (list-ref expr-lst 4) ddo:type-any)))
|
|
(draft:signal-semantic-error
|
|
"expression to be filtered evaluates to a non-nodeset - " (cadr op))
|
|
(let ((expr-impl (car expr-lst))
|
|
(pred-impl-lst (car preds-res)))
|
|
(list
|
|
(if
|
|
(list-ref preds-res 3) ; position required
|
|
(ddo:filter-expr-general expr-impl pred-impl-lst)
|
|
(ddo:filter-expr-non-pos expr-impl pred-impl-lst))
|
|
(cadr expr-lst) ; num-ancestors
|
|
(caddr expr-lst) ; single-level? after this step
|
|
(cadddr expr-lst) ; position-required?
|
|
ddo:type-nodeset
|
|
(append (list-ref expr-lst 5) ; deep-predicates
|
|
(list-ref preds-res 5))
|
|
(list-ref expr-lst 6) ; vars2offsets from expr-lst
|
|
)))))))
|
|
|
|
; {19} <VariableReference> ::= (variable-reference <String> )
|
|
(define (ddo:ast-variable-reference
|
|
op num-anc single-level? pred-nesting vars2offsets)
|
|
(let ((name (string->symbol (cadr op))))
|
|
(call-with-values
|
|
(lambda ()
|
|
(cond
|
|
((assq name (cdr vars2offsets)) ; this variable already in alist
|
|
=> (lambda (pair)
|
|
(values (cdr pair) vars2offsets)))
|
|
(else ; this is a new variable
|
|
(values (car vars2offsets)
|
|
(cons
|
|
(+ (car vars2offsets) 1)
|
|
(cons (cons name (car vars2offsets))
|
|
(cdr vars2offsets)))))))
|
|
(lambda (var-offset new-vars2offsets)
|
|
(list
|
|
(lambda (nodeset position+size var-binding)
|
|
(cond
|
|
((and (not (null? var-binding))
|
|
(eq? (caar var-binding) '*var-vector*))
|
|
(vector-ref (cdar var-binding) var-offset))
|
|
; For backward compatibility
|
|
((assq name var-binding)
|
|
=> cdr)
|
|
(else
|
|
(sxml:xpointer-runtime-error "unbound variable - " name)
|
|
'())))
|
|
0
|
|
#t ; ATTENTION: in is not generally on the single-level
|
|
#f
|
|
ddo:type-any ; type cannot be statically determined
|
|
'() ; deep-predicates
|
|
new-vars2offsets)))))
|
|
|
|
; {20} <Literal> ::= (literal <String> )
|
|
(define (ddo:ast-literal op num-anc single-level? pred-nesting vars2offsets)
|
|
(let ((literal (cadr op)))
|
|
(list
|
|
(lambda (nodeset position+size var-binding) literal)
|
|
0 #t #f ddo:type-string '() vars2offsets)))
|
|
|
|
; {21} <Number> :: (number <Number> )
|
|
(define (ddo:ast-number op num-anc single-level? pred-nesting vars2offsets)
|
|
(let ((number (cadr op)))
|
|
(list
|
|
(lambda (nodeset position+size var-binding) number)
|
|
0 #t #f ddo:type-number '() vars2offsets)))
|
|
|
|
; {22} <FunctionCall> ::= (function-call (function-name <String> )
|
|
; (argument <Expr> )* )
|
|
(define (ddo:ast-function-call
|
|
op num-anc single-level? pred-nesting vars2offsets)
|
|
(let ((core-alist
|
|
; (list fun-name min-num-args max-num-args na4res impl
|
|
; single-level? requires-position? expr-type)
|
|
`((last 0 0 0 ,draft:core-last
|
|
#t #t ,ddo:type-number)
|
|
(position 0 0 0 ,draft:core-position
|
|
#t #t ,ddo:type-number)
|
|
(count 1 1 0 ,draft:core-count
|
|
#t #f ,ddo:type-number)
|
|
(id 1 1 #f ,draft:core-id
|
|
#f #f ,ddo:type-nodeset)
|
|
(local-name 0 1 0 ,draft:core-local-name
|
|
#t #f ,ddo:type-string)
|
|
(namespace-uri 0 1 0 ,draft:core-namespace-uri
|
|
#t #f ,ddo:type-string)
|
|
(name 0 1 0 ,draft:core-name
|
|
#t #f ,ddo:type-string)
|
|
(string 0 1 0 ,draft:core-string
|
|
#t #f ,ddo:type-string)
|
|
(concat 2 -1 0 ,draft:core-concat
|
|
#t #f ,ddo:type-string)
|
|
(starts-with 2 2 0 ,draft:core-starts-with
|
|
#t #f ,ddo:type-boolean)
|
|
(contains 2 2 0 ,draft:core-contains
|
|
#t #f ,ddo:type-boolean)
|
|
(substring-before 2 2 0 ,draft:core-substring-before
|
|
#t #f ,ddo:type-boolean)
|
|
(substring-after 2 2 0 ,draft:core-substring-after
|
|
#t #f ,ddo:type-boolean)
|
|
(substring 2 3 0 ,draft:core-substring
|
|
#t #f ,ddo:type-boolean)
|
|
(string-length 0 1 0 ,draft:core-string-length
|
|
#t #f ,ddo:type-number)
|
|
(normalize-space 0 1 0 ,draft:core-normalize-space
|
|
#t #f ,ddo:type-string)
|
|
(translate 3 3 0 ,draft:core-translate
|
|
#t #f ,ddo:type-string)
|
|
(boolean 1 1 0 ,draft:core-boolean
|
|
#t #f ,ddo:type-boolean)
|
|
(not 1 1 0 ,draft:core-not
|
|
#t #f ,ddo:type-boolean)
|
|
(true 0 0 0 ,draft:core-true
|
|
#t #f ,ddo:type-boolean)
|
|
(false 0 0 0 ,draft:core-false
|
|
#t #f ,ddo:type-boolean)
|
|
(lang 1 1 #f ,draft:core-lang
|
|
#t #f ,ddo:type-boolean)
|
|
(number 0 1 0 ,draft:core-number
|
|
#t #f ,ddo:type-number)
|
|
(sum 1 1 0 ,draft:core-sum
|
|
#t #f ,ddo:type-number)
|
|
(floor 1 1 0 ,draft:core-floor
|
|
#t #f ,ddo:type-number)
|
|
(ceiling 1 1 0 ,draft:core-ceiling
|
|
#t #f ,ddo:type-number)
|
|
(round 1 1 0 ,draft:core-round
|
|
#t #f ,ddo:type-number))))
|
|
(cond
|
|
((not (eq? (caadr op) 'function-name))
|
|
(draft:signal-semantic-error "not an FunctionName - " (cadr op)))
|
|
((assq (string->symbol (cadadr op)) core-alist)
|
|
=> (lambda (description) ; Core function found
|
|
(cond
|
|
((< (length (cddr op)) (cadr description))
|
|
(draft:signal-semantic-error
|
|
"too few arguments for the Core Function call - "
|
|
(cadadr op)))
|
|
((and (>= (caddr description) 0)
|
|
(> (length (cddr op)) (caddr description)))
|
|
(draft:signal-semantic-error
|
|
"too many arguments for the Core Function call - "
|
|
(cadadr op)))
|
|
(else ; correct number of arguments
|
|
(and-let*
|
|
((args-impl-lst (ddo:ast-function-arguments
|
|
(cddr op) ; list of arguments
|
|
single-level? pred-nesting vars2offsets)))
|
|
(list
|
|
; Producing a function implementation
|
|
(apply (list-ref description 4)
|
|
num-anc
|
|
(map car args-impl-lst))
|
|
(apply ; num-ancestors required for function
|
|
draft:na-max
|
|
(cons
|
|
(list-ref description 3) ; from function description
|
|
(map cadr args-impl-lst) ; from arguments
|
|
))
|
|
(list-ref description 5) ; single-level?
|
|
(or (list-ref description 6) ; position-required?
|
|
(not (null?
|
|
(filter cadddr args-impl-lst))))
|
|
(list-ref description 7) ; return type
|
|
(apply append ; deep-predicates
|
|
(map
|
|
(lambda (arg-res) (list-ref arg-res 5))
|
|
args-impl-lst))
|
|
(if (null? args-impl-lst) ; no arguments
|
|
vars2offsets
|
|
(list-ref (car args-impl-lst) 6))
|
|
))))))
|
|
(else ; function definition not found
|
|
(draft:signal-semantic-error
|
|
"function call to an unknown function - " (cadadr op))))))
|
|
|
|
; {22a} ( (argument <Expr> )* )
|
|
; na-lst - number of ancestors required for each of the arguments
|
|
; Returns: #f or
|
|
; (listof
|
|
; (list expr-impl num-anc single-level? requires-position? expr-type
|
|
; deep-predicates vars2offsets))
|
|
; NOTE: In XPath Core Function Library, none of the function arguments
|
|
; is required to save any ancestors in the context
|
|
(define (ddo:ast-function-arguments
|
|
op-lst single-level? pred-nesting vars2offsets)
|
|
(let ((arg-res-lst
|
|
(ddo:foldr
|
|
(lambda (op init)
|
|
(cons
|
|
(if
|
|
(not (eq? (car op) 'argument))
|
|
(draft:signal-semantic-error "not an Argument - " op)
|
|
(ddo:ast-expr
|
|
(cadr op) 0 single-level? pred-nesting
|
|
(if (or (null? init) ; called for the first time
|
|
(not (car init)))
|
|
vars2offsets
|
|
(list-ref (car init) 6) ; vars2offsets from previous pred
|
|
)))
|
|
init))
|
|
'()
|
|
op-lst)))
|
|
(and
|
|
(not (memv #f arg-res-lst)) ; semantic error detected
|
|
arg-res-lst)))
|
|
|
|
|
|
;=========================================================================
|
|
; Highest level API functions
|
|
;
|
|
; procedure ddo:sxpath :: query [ns-binding] [num-ancestors] ->
|
|
; -> node-or-nodeset [var-binding] -> nodeset
|
|
; procedure ddo:txpath :: location-path [ns-binding] [num-ancestors] ->
|
|
; -> node-or-nodeset [var-binding] -> nodeset
|
|
;
|
|
; Polynomial-time XPath implementation with distinct document order support.
|
|
;
|
|
; The API is identical to the API of a context-based SXPath (here we even use
|
|
; API helpers from "xpath-context.scm"). For convenience, below we repeat
|
|
; comments for the API (borrowed from "xpath-context.scm").
|
|
;
|
|
; query - a query in SXPath native syntax
|
|
; location-path - XPath location path represented as a string
|
|
; ns-binding - declared namespace prefixes (an optional argument)
|
|
; ns-binding ::= (listof (prefix . uri))
|
|
; prefix - a symbol
|
|
; uri - a string
|
|
; num-ancestors - number of ancestors required for resulting nodeset. Can
|
|
; generally be omitted and is than defaulted to 0, which denotes a
|
|
; _conventional_ nodeset. If a negative number, this signals that all
|
|
; ancestors should be remembered in the context.
|
|
;
|
|
; Returns: (lambda (node-or-nodeset . var-binding) ...)
|
|
; var-binding - XPath variable bindings (an optional argument)
|
|
; var-binding = (listof (var-name . value))
|
|
; var-name - (a symbol) a name of a variable
|
|
; value - its value. The value can have the following type: boolean, number,
|
|
; string, nodeset. NOTE: a node must be represented as a singleton nodeset.
|
|
;
|
|
; The result of applying the latter lambda to an SXML node or nodeset is the
|
|
; result of evaluating the query / location-path for that node / nodeset.
|
|
|
|
; Helper for constructing several highest-level API functions
|
|
; ns+na - can contain 'ns-binding' and/or 'num-ancestors' and/or none of them
|
|
(define (ddo:api-helper grammar-parser ast-parser)
|
|
(lambda (xpath-string . ns+na)
|
|
(call-with-values
|
|
(lambda () (draft:arglist->ns+na ns+na))
|
|
(lambda (ns-binding num-anc)
|
|
(and-let*
|
|
((ast (grammar-parser xpath-string ns-binding))
|
|
(impl-lst (ast-parser ast num-anc
|
|
#t ; we suppose single-level?=#t for src
|
|
0 ; predicate nesting is zero
|
|
'(0) ; initial vars2offsets
|
|
)))
|
|
(let ((impl-lambda
|
|
(if
|
|
(and num-anc (zero? num-anc))
|
|
(let ((impl-car (car impl-lst)))
|
|
(lambda (node position+size var-binding)
|
|
(draft:contextset->nodeset
|
|
(impl-car node position+size var-binding))))
|
|
(car impl-lst))))
|
|
(lambda (node . var-binding) ; common implementation
|
|
(impl-lambda
|
|
(as-nodeset node)
|
|
(cons 1 1)
|
|
(ddo:add-vector-to-var-binding
|
|
(list-ref impl-lst 6) ; vars2offsets
|
|
(reverse ; deep-predicates: need to reverse
|
|
(list-ref impl-lst 5))
|
|
node
|
|
(if (null? var-binding) var-binding (car var-binding)))))))))))
|
|
|
|
(define ddo:txpath (ddo:api-helper txp:xpath->ast ddo:ast-location-path))
|
|
(define ddo:xpath-expr (ddo:api-helper txp:expr->ast ddo:ast-expr))
|
|
(define ddo:sxpath (ddo:api-helper txp:sxpath->ast ddo:ast-expr))
|
|
|
|
(provide (all-defined)))
|