compiling XPath to Scheme source code

"Oleg A. Paraschenko" <[email protected]> Fri, 19 Nov 2004 08:34:29 +0300
Newsgroups gmane.lisp.scheme.ssax-sxml
Organization xmlhack.ru
Message-ID <[email protected]>
  Hello all,

  I'm working on translating XPath expressions to Scheme
source code which evaluates the XPath. As a proof of concept
I translated some expressions which test XPath issues. Here
are these expressions:

/usr/bin/xmllint   # trivial xpath + referencing root node
/AAA/CCC           # resulted nodeset contains several nodes
//DDD/BBB          # test of 'descendant' axis
/AAA/*/*/BBB       # test of '*'
//BBB/@name        # test of 'attribute' axis
/AAA/BBB[1]        # selecting a node by its position
/AAA/BBB[last()]   # using a function to filter nodeset
/AAA/BBB[CCC]      # XPath in the predicate
  # nested predicates, relational operator
//openssh[.//sshd[@date<20040923]]
  # function in function
//bin/*[contains(name(), 'vi')]

  I'd like to get suggestions what other XPath issues should
be tested before starting to make a real implementation.

--

  Why compile to Scheme _source_ code?

  Some of the reasons:

* one can see what is going to be executed,

* it is possible to rewrite parts of the source code to make
  some sorts of optimization,

* in some cases it is possible to translate Scheme source code
  into the other programming languages,

* smart Scheme compiler can optimize code while compiling it.

  Your ideas are welcome.

--

  An example of simple XPath.

[example 1]

; Define a tree and an xpath expression
(define tree1 '(*top*
  (AAA (BBB) (CCC) (BBB) (BBB) (DDD (BBB)) (CCC))))
(define xpath-str1 "AAA/CCC")

; Translate the xpath to a Scheme source code
(define xpath-proc-src1 (xpath->scheme xpath-str1))
; A value of the variable "xpath-proc-src1" is:
;
;   (node-join
;     (sxml:child (ntype?? 'AAA))
;     (sxml:child (ntype?? 'CCC)))

; Compile the XPath code and execute it
(define xpath-proc1
  (eval xpath-proc-src1 (scheme-report-environment 5)))
(define result1 (xpath-proc1 tree1))
; A value of the variable "result1" is:
;
;    ((CCC) (CCC))

[/example 1]

--

  Referring the root node.

  The root node is stored in the XPath variable "*root*".
An XPath expression like

/AAA/CCC

  is interpreted like

$*root*/AAA/CCC

  So we don't need to pass root node as parameter, and
txpath's core function definitions can be simplified. The

(lambda (nodeset root-node context var-binding) ...)

  becomes

(lambda (nodeset context var-binding) ...)

--

  Binding of variables (on example of "*root*").

[example 2]

; Define variables.
(define tree2 tree1)            ; see example 1
(define xpath-str2 "/AAA/CCC")  ; like "AAA/CCC"

; Translate the xpath to a Scheme source code
(define xpath-proc-src2 (xpath->scheme xpath-str2))

; A value of the variable "xpath-proc-src2" is:

; (node-join
;   *root*
;   (sxml:child (ntype?? 'AAA))
;   (sxml:child (ntype?? 'CCC)))
;
; "eval" will fail on this expression because the variable
; "*root*" is not bound. So the new step appears.

; Bind variables.
(define xpath-proc-src2b
  (sxlet ((*root* tree2)) xpath-proc-src2))

; A value of the variable "xpath-proc-src2" is:
;
; (let ((*root* (lambda (dummy) tree2)))
;      (node-join
;         *root*
;         (sxml:child (ntype?? 'AAA))
;         (sxml:child (ntype?? 'CCC))))
;
; This can be compiled and executed.

(define xpath-proc2
  (eval xpath-proc-src2b (scheme-report-environment 5)))
(define result2 (xpath-proc2 tree2))
;
;   ((CCC) (CCC))

  The variable "*root*" is indeed a lambda because the function
"node-join" expects only expressions of the type "converter"
(converter: (node|nodeset) -> nodeset).

  Now it is not required to pass bindings of XPath variables
because Scheme compiler can do it. And definitions of sxpath
library functions can be further simplified.

(lambda (nodeset root-node context var-binding) ...)

  becomes

(lambda (nodeset context) ...)

[/example 2]

--

  Filtering.

[example 3]

(define tree3 '(*top* (AAA "1") (AAA "2") (AAA "3")))
(define xpath-str3 "AAA[last()]")

(define xpath-proc-src3 (xpath->scheme xpath-str3))
;
; (node-join
;   (node-reduce
;     (sxml:child (ntype?? 'AAA))
;     (xpath-filter (scxml:core-last))))

(define xpath-proc3
  (eval xpath-proc-src3 (scheme-report-environment 5)))
(define result3 (xpath-proc3 tree3))
;
; ((AAA "3"))

[/example 3]

  The "xpath-filter" is indeed a macro. It does what it should
do (for each node, evaluates a predicate. If the result is
a number, then it filters by the position, else it filters by
the boolean value of result).

  The "side-effect" of "xpath-filter" (and reason why it is
a macro) is that it introduces Scheme variables:

- *contextNodeset*
- *contextNode*
- *contextTail*
- *contextPosition*

  The code of predicates can use these variables. For example,
"scxml:core-last" is macrodefined as:

(define-macro (scxml:core-last)
  `(lambda (unused-nodeset)
    (null? (cdr *contextTail*))))

Notes:

1) Essential part is "(null? (cdr *contextTail*))", the only
   use of lambda is to make type "converter").

2) Good Scheme compiler should remove unused variable
   *contextPosition* here.

  Now it is not required to pass context variables to the
predicates, and the definitions of sxpath library functions
can be simplified again.

(lambda (nodeset root-node context var-binding) ...)

  becomes

(lambda (nodeset) ...)


--

  The current code is available at

http://xmlhack.ru/protva/tmp/20041119_scpath.tar.gz

  The code is just a mess. It works only under Bigloo.
Don't look inside. I'm going to rewrite the code from scratch.
The only reason to show codes now is to prove the text above.

--

Regards, 

Oleg Paraschenko


-------------------------------------------------------
This SF.Net email is sponsored by: InterSystems CACHE
FREE OODBMS DOWNLOAD - A multidimensional database that combines
robust object and relational technologies, making it a perfect match
for Java, C++,COM, XML, ODBC and JDBC. www.intersystems.com/match8