Surface Syntax for OWL-S

Author:
Drew McDermott, Yale University

Abstract

This document presents a grammar and semantics for the OWL-S "surface syntax." The semantics are provided by presenting an algorithm for translating the surface syntax into RDF.


1 Goals

This is a formalization of the surface syntax for processes in OWL-S.

The goals of this exercise are to

  1. Provide readable surface syntax
  2. Explain how it relates to the usual RDF syntax

The syntax notation is for the Lexiparse parser [McD04b]. This document consists of Lexiparse definitions intermixed with text, in a "literate programming" style [Knu84]. See [McD04b], appendix 1, for a brief explanation of this incarnation of literate programming.

Here are some examples of process definitions. An atomic process has no body:

define atomic process foo(inputs: (x,y - integer),
                          outputs: (xx - String),
                          precondition: loves(x,y),
                          result: (forall (z - integer u,v - string) 
                                        purple(x,z) |-> mauve(y,z)
                                   &
                                   output(xx <= "kool")))

The result field(s) specify what values are produced and what effects occur when the process is performed by a web-service client.

A composite has a body in addition to a result.

define composite process baz(outputs: (x - String),
                             inputs: (u,v),
                             result: purple(v))
      {perform do_something();
        {
           {
            g :: perform a(n <= v); 
            perform foo(x <= u, y <= g.out1)
           }
           ||;
           { h :: perform c();
             produce (x <= h.w) }
        }
      }

In what follows I will specify the grammar that accepts these definitions (sect. "Syntax"), give the parsetrees for them (sect. "Translation to RDF/XML"), and then sketch the XMLified RDF that would be produced by "internalizing" the parsetrees.


Syntax

The OWL-S grammar is expressed using the Lexiparse formalism. A syntactic token is defined in terms of three components:

  1. Its precedence, "fixity" (prefix, suffix, or infix), and "context," which includes whether it should be considered as an open bracket (to be matched by a closer). The parsetree for an expression is built initially using just this information.
  2. Its "tidier," an optional transformation that simplifies a parsetree headed by the token.
  3. Its "checkers," which note violations of constraints on the neighbors (ancestors and descendents) of any sub-parsetree headed by the token after the entire tree has been tidied.
Note that last clause: All the tidiers run as soon as a subtree is built, and none of the checkers is run until the entire parsetree is finished. That means that the checkers should examine the tree "as tidied"; in particular, since tidying can eliminate some tokens and introduce others, the checkers must in many cases be defined as adjuncts of newly introduced tokens. It also means that all they must do with the eliminated tokens is verify that they were indeed eliminated.

The tidier and checker make use of a simple notation for indicating patterns that might match a parsetree, and, in the case of tidiers, indicate how the tree is to be simplified. This notation is implemented as Lisp macros; in fact all the resources of Lisp are available for tidying and checking (but an elegant grammar will stay within the pattern-oriented subset as much as possible).

For example, the following spec for the token <left-paren> (produced by lexical analysis from an occurrence of the character '(') specifies that it can occur as either a prefix or infix operator, in each case requiring a matched <right-paren>. The tidier transforms the prefix version into the token <group> and the infix version into the token <fun-app>.

<<Define left-paren

   ;; Eliminate commas from parenthesized expressions
   (def-syntactic left-paren :lex "("
                             :prefix (:precedence 0
                                      :context (:open comma right-paren))
                             :infix (:left-precedence 200 :right-precedence 0
                                     :context (:open comma right-paren))
      :tidier
         ((:? ?(:^+ left-paren [*_] ?e)
             e)
          (:? ?(:^+ left-paren [*_] ?@subs)
             !~(:^+ group [*_] ,@subs))
          (:? ?(:^+ left-paren ?f [_*_] ?@args)
             !~(:^+ fun-app ,f [_*_] ,@args)))) >>

(The symbol ':^+' heads lists that stand for parsetrees. The pattern ?(:^+ o –subs–) matches a parsetree with operator o and subtrees subs, while the construct !~(:^+ o –subs–) builds a parsetree.)

Because this tidier runs right after the parsetree with operator <left-paren> is created, so that the token <left-paren> is replaced by a more revealing version, no other tidier or checker will ever bother to check for occurrences of it.

Tables "Right Precedence" and "Left Precedence" show the precedence structure of OWL-S. By itself this information signifies little, although it demonstrates that the order we are presenting syntactic constructs in does obey the "low precedence first" rule. (If a token appears in a precedence list twice, it's because it can appear in either a prefix or infix role; the left precedence for the former is written as "--".)

Table 2: OWL-S operators in order of increasing ...
right precedence
Op Left Right
left-brace "{" -- 0
left-paren "(" -- 0
left-paren "(" 200 0
| "" 3 3
'define' -- 5
'atomic' -- 15
'composite' -- 15
'simple' -- 15
'with_namespaces' -- 15
'process' -- 25
anyord "||;" 60 60
choice ";?" 60 60
split "||<" 60 60
split-join "||>" 60 60
semicolon ";" 80 80
tag "::" 81 81
'if' -- 85
'then' -- 87
'else' 88 88
'exists' -- 90
'forall' -- 90
'perform' -- 90
'produce' -- 90
comma "," 100 100
bind-param "<=" 110 110
when "=>" 110 111
colon ":" -- 120
'inputs' -- 120
'locals' -- 120
'outputs' -- 120
'participants' -- 120
'precondition' -- 120
'result' -- 120
or "|" 120 120
and "&" 130 130
not "~" -- 140
equals "=" 160 160
geq ">=" 160 160
greater ">" 160 160
leq "=<" 160 160
less "<" 160 160
minus "-" -- 180
plus "+" -- 180
minus "-" 180 180
plus "+" 180 180
divide "/" 190 190
times "*" 190 190
'output' -- 200
dot "." 205 205
'uri' -- 210
colon ":" 210 210
left precedence
Op Left Right
right-brace "}" 0 --
right-paren ")" 0 --
| "" 3 3
anyord "||;" 60 60
choice ";?" 60 60
split "||<" 60 60
split-join "||>" 60 60
semicolon ";" 80 80
tag "::" 81 81
'else' 88 88
comma "," 100 100
bind-param "<=" 110 110
when "=>" 110 111
or "|" 120 120
and "&" 130 130
equals "=" 160 160
geq ">=" 160 160
greater ">" 160 160
leq "=<" 160 160
less "<" 160 160
minus "-" 180 180
plus "+" 180 180
divide "/" 190 190
times "*" 190 190
left-paren "(" 200 0
dot "." 205 205
colon ":" 210 210

Later, in Table Precedence Array, we show the "cross product" of these two tables, indicating more clearly which tokens tend to occur below which other tokens.

2.1  Upper-Level Syntax: Process Name Declarations

We start by declaring that we're constructing a grammar, called owl-s:

<<Define grammar-def

(def-grammar owl-s
    :top-tokens (define with_namespaces)
    :lex-parent standard-arith-syntax
    :syn-parent standard-arith-syntax
    :sym-case #-allegro :up #+allegro (allegro-case-choice)) >>
The :top-tokens declaration indicates that a legal process definition in OWL-S must be headed by one of the tokens define or with_namespaces, both of which will be explained shortly.

The rest of the grammar consists of definitions of the tokens of the language, starting with those that appear higher (closer to the root) in parsetrees, which are (more or less) the same as the :top-tokens.

A process-model definition consists of a sequence of process definitions, each atomic, simple, or composite. All three stem from the define operator.

<<Define process-definition

   ;; Top node is of form
   ;;     ::= define [atomic | simple] <process-spec>
   ;;         | define composite <process-spec>{ stmt }
   (def-syntactic define :reserved true
                         :prefix (:precedence 5 :numargs 1)
      :tidier
      ((:? ?(:^+ define (:^+ composite 
                              ?(:^+ process ?name ?@iopr-spec)
                              ?@body-spec))
          !~(:^+ define composite ,name (:^+ iopr ,@iopr-spec) ,@body-spec))

       (:? ?(:^+ define (:^+ atomic (:^+ process ?name ?@iopr-spec)))
          !~(:^+ define atomic ,name (:^+ iopr ,@iopr-spec) nil))

       (:? ?(:^+ define (:^+ simple (:^+ process ?name ?@iopr-spec)))
          !~(:^+ define simple ,name (:^+ iopr ,@iopr-spec) nil))

       (:else (defect "Unintelligible 'define' expression")))

      :checkers

       ((:up *
          (:? ?(:^+ ?op ?@_)
             (cond ((not (memq op '(with_namespaces left-brace)))
                    (defect "'define' can't appear below " op)))))

        (:? ?(:~ ?(:^+ define ?_ ?(:+ ?name is-name) ?@_))
           (defect "Process has illegal name: " name))

        (:? ?(:^+ define ?_ ?_ (:^+ iopr ?@ioprs) ?_)
           (check-all iopr ioprs this-grammar
                  (:? ?(:^+ ?(:~ ?(:|| inputs outputs locals participants
                                       precondition result))
                            ?_)
                     (defect "Process has illegal IOPR spec: " iopr))))

        (:? ?(:^+ define ?_ ?_ (:^+ iopr ?@ioprs) ?_)
           (nconc (occ-range ioprs 0 1 ?(:^+ inputs ?@_))
                  (occ-range ioprs 0 1 ?(:^+ outputs ?@_))
                  (occ-range ioprs 0 1 ?(:^+ locals ?@_))
                  (occ-range ioprs 0 1 ?(:^+ precondition ?@_))
                  )))) >>

Here we have our first checkers, which also use pattern matching. Their output is a list of syntactic defects, hopefully empty. The notation (:up * --checkers--) checks all the ancestors of the current parsetree. In this case it limits define to appearing either at the top level or below a with_namespaces, possibly with some enclosing curly braces.

The syntactic rules above introduce the acronym IOPR, which stands for "Inputs, Outputs, Preconditions, and/or Results" --- all the types of entities that define the external view of the process. For atomic and simple processes, the external view is all there is; for composites, the body-spec provides a view of the inner works of the process.

We analyze atomic, simple, and composite as simple prefix operators, followed by a process-spec.

<<Define process-classes

   ;; __::= atomic <process-spec>
   (def-syntactic atomic
                  :reserved true
                  :prefix (:precedence 15 :numargs 1))

   ;; __::= simple <process-spec> 
   (def-syntactic simple :reserved true
                  :prefix (:precedence 15 :numargs 1))

   ;; __::=  composite <process-spec>... { ...
   (def-syntactic composite :reserved true
                  :prefix (:precedence 15 :numargs 2)
      :tidier
        ((:? ?(:^+ composite ?proc (:^+ left-brace ?body))
            !~(:^+ composite ,proc ,body))
         (:? ?(:^+ composite ?proc ?b)
            (defect "Ill-formed body for composite process " proc
                    :% " (must be surrounded by braces): " b)))) >>

2.2 Namespaces

Before presenting the syntax of processes, I digress to talk about namespaces. In the RDF/XML representation, namespaces are treated using the standard XML namespace notations, that is, as attributes whose names start with xmlns. In the proposed surface syntax, we must supply a special notation:

<<Define namespace-spec

   (def-syntactic with_namespaces :reserved true
                  :prefix (:precedence 15 :numargs 2)
      :tidier
         ((:? ?(:^+ with_namespaces ?spec ?e)
             (let* ((namespace-pairs (namespace-spec-listify spec))
                    (defects (<? is-Defect namespace-pairs)))
                (cond ((null defects)
                       !~(:^+ with_namespaces ,e ,@namespace-pairs))
                      (t
                       defects))))))
                  
   (defun namespace-spec-listify (spec)
      (match-cond spec
         (:? ?(:^+ uri ?s)
            (list !~(:^+ namespace+name ,false  (:^+ uri ,s))))
         (:? ?(:^+ ?(:|| equals name-wrt-space)
                   ?(:+ ?sym is-Symbol)
                   (:^+ uri ?s))
            (list !~(:^+ namespace+name ,sym (:^+ uri ,s))))
         (:? ?(:^+ group ?@specs)
            (<! namespace-spec-listify specs))
         (t
          (list (defect "Illegal namespace spec " spec))))) >>
(The notation !() is short for (empty-list), which evaluates to the empty list. The expression (<! f l) is the list obtained by concatenating (f l0), (f l1), ..., and (f ln-1), where n is the length of the list l. Used in the context of a checker, it produces a list of the defects obtained by running a subchecker f on all the elements of the list l.)

In XML, URIs are represented as strings, and are recognized as URIs by their context. We take a slightly different tack, and supply a syntactic operator uri"..." that indicates a string is to be taken as a URI.

<<Define uri-spec

   (def-syntactic uri :reserved true
                  :prefix (:precedence 210 :numargs 1)
      :checkers
         ((:? ?(:~ ?(:^+ uri ?(:+ ?s is-String)))
           (defect "uri followed by non-string " s)))) >>

2.3  Process Syntax

The syntax finally gets interesting with the operator process:

<<Define process-spec

   
   ;; __::= process <name>(-IOPRs-)
   (def-syntactic process :reserved true
                  :prefix (:precedence 25 :numargs 1)  
      :tidier
         ((:? ?(:^+ process (:^+ fun-app ?name [_*_] (:^+ comma ?@ioprs)))
             !~(:^+ process ,name ,@ioprs))
          (:? ?(:^+ process (:^+ fun-app ?name [_*_] ?@subs))
            !~(:^+ process ,name ,@subs))
          (:else (defect "'process' must be followed by <name>"
                         " and IOPRs in parens")))

      :checkers

         ((:? ?(:^+ process ?@_)
             (defect "Stray occurrence of 'process'")))) >>
The odd-looking checker for <process> is explained by the fact that the tidiers for <define> are supposed to eliminate all occurrences of <process>.

There are two kinds of IOPRs, the IOs (parameters) and the PRs (preconditions and results). One key feature of the former is that they are followed by a parameter-declaration list with a distinctive syntax.

<<Define param-decls

   
   ;; <IOPR> ::= inputs : (...)
   (def-syntactic inputs :reserved true
                         :prefix (:context 1 :precedence 120
                                  :local-grammar ((1 typed-var-grammar)))
      :tidier
         (typed-vars-tidier 'inputs)

      :checkers
         ((:up 1 (:? ?(:^+ iopr ?@_)))))

   ;; <IOPR> ::= outputs : (...)
   (def-syntactic outputs :reserved true
                          :prefix (:context 1 :precedence 120
                                   :local-grammar ((1 typed-var-grammar)))
      :tidier
         (typed-vars-tidier 'outputs)

      :checkers
         ((:up 1 (:? ?(:^+ iopr ?@_)))))

   ;; <IOPR> ::= locals : (...)
   (def-syntactic locals :reserved true
                        :prefix (:context 1 :precedence 120
                                 :local-grammar ((1 typed-var-grammar)))
      :tidier
         (typed-vars-tidier 'locals)

      :checkers
      ((:up 1 (:? ?(:^+ iopr ?@_)))))

   ;; <IOPR> ::= participants : (...)
   (def-syntactic participants :reserved true
                        :prefix (:context 1 :precedence 120
                                 :local-grammar ((1 typed-var-grammar)))
      :tidier
         (typed-vars-tidier 'participants)

      :checkers
         ((:up 1 (:? ?(:^+ iopr ?@_))))) >>
The notation (:up 1 pat [e]) checks a pattern at the parent of the current node. For these operators, we use it to check that the only place <inputs>, <outputs>, etc., can occur is inside a subtree with operator <iopr>. (Remember that this operator was introduced by the tidiers for <define>.)
<<Define typed-vars-tidier

(defun typed-vars-tidier (sort)
   (\\ (this-ptree _)
      (match-cond this-ptree
         (:? ?(:^+ ?,sort (:^+ colon (:^+ group ?@decls)))
            !~(:^+ ,sort ,@decls))
         (:? ?(:^+ ?,sort (:^+ colon ?decl))
            !~(:^+ ,sort ,decl))       
         (:else (defect "'" sort "' doesn't occur in the form "
                        sort ":(...)"))))) >>

(The construct

    (\\ (—params—) —body—)
is equivalent to
     #'(lambda (—params—) —body—)
except that ignorable variables can be declared as such by being named "_".)

The local grammar typed-var-grammar is for typed variable declarations such as (x,y - String n - Integer), in which the hyphens must have precedence lower than that of the commas.

<<Define typed-var-grammar

(def-grammar typed-var-grammar
    :lex-parent owl-s
    :syn-parent owl-s
    :replace ((minus hyphen))
    :sym-case #-allegro :up #+allegro (allegro-case-choice)

 :definitions
 (
   (def-syntactic \| :lex "" :infix (:precedence 16 :context :grouping))

   (def-syntactic left-paren :lex "("
                             :prefix (:left-precedence 200 :right-precedence 0
                                      :context (:open \| right-paren)))

   ;; hyphens can be generated only by replacement of <minus>
   ;; Precedence puts hyphen above comma in parsetree --
   (def-syntactic hyphen :lex "-" :infix (:precedence 17 :context :binary)
       :tidier
          ;; Eliminate commas --
          ((:? ?(:^+ hyphen (:^+ comma ?@vars) ?type)
             !~(:^+ hyphen ,@vars ,type)))

       :checkers
          ((:? ?(:^+ hyphen ?@vars ?_)
             (and (not (is-list-of vars #'is-Symbol))
                  (defect "Vars in declarations are not all symbols: "
                                  vars)))

           (:? ?(:^+ hyphen ?@_ (:^+ comma ?@_))
              (defect "Comma occurs to right of hyphen"))))
 )) >>

The token with name "|" is the invisible "contiguity" operator that Lexiparse inserts whenever it expects an operator and none appears in the lexeme stream. In the declaration (x,y - String n - Integer), such an operator will be inserted between String and n. (The regular OWL-S grammar expects the contiguity operator only as the separator of elements between curly braces.)

The clause :replace ((minus hyphen)) means to replace the token <minus> generated by the lexer with <hyphen> when typed-var-grammar is in control. That means that even when control returns to the normal owl-s grammar, the token derived from "-" will not start looking like a minus again.

The <colon> token is yet another prefix operator. Like process, it gets tidied away in the only contexts where it is legal. So one function in its :checkers list just checks to see if it actually vanished.

<<Define colon

   ;; <name> ::= <name> : <name>
   ;; 
   (def-syntactic colon :lex ":" :infix (:context :binary :precedence 210)
                         ;; -- namespace construct, with high precedence
                         :prefix (:context 1 :precedence 120)
                         ;; -- Occurs only after inputs, outputs, preconditions, effects
                         ;; Low precedence, but higher than comma
      :tidier
         ((:? ?(:^+ colon ?namespace [_*_] ?name)
             !~(:^+ name-wrt-space ,namespace ,name)))

      :checkers
         ((:? ?(:^+ colon ?@_)
            (defect "Stray colon"))

          (:? ?(:^+ name-wrt-space ?namespace ?name)
             (nconc (cond ((and namespace (is-Symbol namespace)) !())
                          (t (list (defect "Namespace must be a non-nil symbol: "
                                           namespace))))
                    (cond ((is-Symbol name) !())
                          (t (list (defect "Name must be a symbol: "
                                           name)))))))) >>
The colon is also used as an infix operator. ecom:interest-rate means the symbol interest-rate in a namespace associated with the prefix ecom. Colons of this kind are converted to the token <name-wrt-space>, partly so that the checker attached to <colon> runs only for the prefix version. The checker for space:name verifies that space and name are symbols. The expectation is that space is bound by an outer with_namespaces expression, but bindings are not checked until expressions are internalized.

The other two things that can appear in an IOPR list are preconditions and results:

<<Define preconds

   ;; <IOPR> ::= precondition : ...
   (def-syntactic precondition :reserved true
                               :prefix (:context 1 :precedence 120)
      :tidier
         ((:? ?(:^+ precondition (:^+ colon ?exp))
             !~(:^+ precondition ,exp))
          (:else (defect "'precondition' not followed by ': <expression>'")))

      :checkers
         ((:up 1 (:? ?(:^+ iopr ?@_))))) >>
The formula in the precondition field of a process definition has no special syntax, but is just part of the general-purpose logical-expression system that generates all those XML literals at the leaves of OWL-S. This system is discussed in section "Formulas."

2.4  The Syntax of results

The result field(s) are different.

<<Define results

   ;; <IOPR> ::= result : ...
   (def-syntactic result :reserved true
                          :prefix (:context 1 :precedence 120)
      :tidier
         ((:? ?(:^+ result (:^+ colon ?exp))
             !~(:^+ result ,exp))
          (:else (defect "'result' not followed by ': <expression>'")))

      :checkers
         ((:up 1 (:? ?(:^+ iopr ?@_)))
             <<Insert: result-tree-checker>>)) >>
Results can include several constructs that are not meaningful, or mean something different, in other contexts. Even though a result may look like a predicate-calculus formula, it is not really an entity with a truth value, but a recipe for changing the truth values of various propositions. For this reason, I prefer using the verb "impose" to describe a result. A result R does not become "true"; it is imposed following an action or event. Here is a rough definition of what that means:

  1. To impose an atomic formula p is to make it true
  2. To impose a negated atomic formula not p is to make p false. For many implementations, this translates into deleting p from a representation of the current situation, so that the use of a closed-world assumption [Rei78] will allow an implementation to conclude that p is false.
  3. To impose p |-> q is to impose q if p was true before the action or event.
  4. To impose forall (x) p[x] is to impose p[a] for all objects a. In practice, most implementations only handle the special case forall (x) p[x] |-> q[x], which means "For every set of values v for vars such that p[v] is true before the action or event, impose q[a]." OWL-S is typical in singling this case out.
  5. To impose output(p1 <= v1, ..., pk <= vk), where each pi is an output of the current process, make each vi the value of pi. (This notation corresponds to the withOutput construct of OWL-S.)

Various syntactic constraints are implied by this definition of "impose." There are others as well: We don't allow disjunctive effects in OWL-S. To check that a result is syntactically legal, we must write a straightforward Lisp procedure (called result-defects) to walk through a parsetree headed by <result>.

First, we define the special operators (<when> and <output>) that can occur only in results.

<<Define lex-when

   ;; '=>' becomes the token <when>, although '|->' is now the preferred form
   (def-lexical #\= (dispatch
                       (#\> (token when))
                       (:else (token equals)))) >>
<<Define syn-when

   (def-syntactic when :lex "=>"
                       :infix (:context :binary
                               :left-precedence 110 :right-precedence 111)) >>
<<Define lex-bind-param

   ;; '<=' becomes the token <bind-param>
   (def-lexical #\< (dispatch
                       (#\= (token bind-param))
                       (:else (token less)))) >>
<<Define bind-param-syn

   (def-syntactic bind-param :lex "<="
                             :infix (:context :binary
                                     :precedence 110)
      :checkers
         ((:? ?(:^+ bind-param ?p ?_)
            (and (not (is-Symbol p))
                 (defect "Non-symbol to left of '<=': " p)))
          <<Insert: bind-param-context>>)) >>
<<Define output-syntax

   (def-syntactic output :reserved true
                  :prefix (:precedence 200 :numargs 1)
      :tidier
         ((:? ?(:^+ output (:^+ group ?@bindings))
             !~(:^+ output ,@bindings))
;;;;      (:? ?(:^+ output ?binding)
;;;;         !~(:^+ output ?binding))
;;;;          (:else (defect "Output must be followed by bindings in parens"))
          )

      :checkers
         ((:? ?(:^+ output ?@bindings)
             (check-all b bindings this-grammar
                (:? ?(:~ ?(:^+ bind-param ?@_))
                   (defect "Outputs must all be of form 'param <= exp', not "
                           b)))))) >>
The checker for results calls the recursive procedure result-defects:
<<Define result-tree-checker

:. (def-syntactic result ... 
      ...
      :checkers
         (...  .:
             (match-let ?(:^+ result ?exp)
                        this-ptree
                (result-defects exp)) >>
<<Define result-syntax-checker

(defun result-defects (exp)
   (let-fun ()
      (match-cond exp
         (:? ?(:^+ forall ?vars ?r)
            (append (decls-defects vars)
                    (forall-body-must-be-whens r)))
         (:? ?(:^+ exists ?vars ?r)
            (list (defect "Existential quantifiers are illegal in results")))
         (:? ?(:^+ when ?_ ?effect)
            (result-defects effect))
         (:? ?(:^+ and ?@conjuncts)
            (<! result-defects conjuncts))
         (:? ?(:^+ group ?elt)
            (result-defects elt))
         (:? ?(:^+ group ?@_)
            (list (defect "Can't have <comma> as a connective")))
         (:? ?(:^+ not ?elt)
            (must-be-fun-app elt))
         (:? ?(:^+ output ?@_)
            '())
         (t (must-be-fun-app exp)))

    :where

       (:def decls-defects (vl)
           (match-cond vl
              (:? ?(:^+ group ?@decls)
                 (repeat :for ((decl :in decls))
                  :nconc (decl-defects decl)))
              (:else (decl-defects vl))))

       (:def decl-defects (decl)
          (match-cond decl
             (:? ?(:^+ hyphen ?@_)
                   '())
             (:? ?(:^+ comma ?@vars)
                (and (not (is-list-of vars #'is-Symbol))
                     (list (defect "Vars in declarations are not all symbols: "
                                   vars))))
             (:? ?var
                (and (not (is-Symbol var))
                     (list (defect "Var in declaration is not a symbol: "
                                   var))))))

       (:def forall-body-must-be-whens (r)
          (match-cond r
             (:? ?(:^+ when ?_ ?e)
                (result-defects e))
             (:? ?(:^+ and ?@conjuncts)
                (<! forall-body-must-be-whens
                    conjuncts))
             (:else (list (defect "Body of 'forall' must be a '|->' or a conjunction"
                                  " of '|->' expressions"))))))) >>
<<Define must-be-atomic

(defun must-be-fun-app (e)
   (match-cond e
      (:? ?(:^+ fun-app ?_ ?@_)
        '())
      (:else (list (defect "Context requires atomic formula"))))) >>
One thing this procedure does not check is whether each occurrence of a variable in a result is bound by a dominating forall. That check is deferred until the internalization phase, although it would have been reasonable to do it here. Internalization is managed by a subgrammar called owl-s-as-rdf, discussed in section "Translation to RDF/XML".

2.5  The Bodies of Composite Processes

Unlike atomic and simple processes, composite processes have bodies, the stuff in left braces after the IOPR specs.

<<Define syn-braces

   (def-syntactic left-brace :lex "{"
                             :prefix (:precedence 0
                                      :context (:open \| right-brace))
      :tidier
      ((:? ?(:^+ left-brace ?sub)
          sub)))

   (def-syntactic right-brace :lex "}"
                              :suffix (:left-precedence 0 :context :close)) >>
(See the syntactic definition for composite.) The current grammar handles only a few control constructs, but their definitions should suffice to give the flavor. Some are defined using reserved words, others with special character sequences.
<<Define lex-vbar

   ;; ||; = any-order, ||> = split+join, ||< = split.
   ;; '|->' becomes the token <when> --
:. (def-lexical #\| .:
                    (dispatch     
                       (#\| (dispatch 
                               (#\; (token anyord))
                               (#\> (token split-join))
                               (#\< (token split))))
                       (#\- (dispatch (#\> (token when)))) >>
<<Define lex-semicolon

   ;; ;? = choice, ; = sequence
   (def-lexical #\;
         (dispatch (#\? (token choice))
                   (:else (token semicolon)))) >>
<<Define control-constructs

   (def-syntactic anyord :lex "||;"
                         :infix (:precedence 60 :context :grouping)
      :tidier 'elim-left-braces

      :checkers (control-composition-check

                 (:up 1 (control-context-check this-ptree that-ptree))))


   (def-syntactic split-join :lex "||>"
                             :infix (:precedence 60 :context :grouping)
       :tidier 'elim-left-braces

       :checkers (control-composition-check

                 (:up 1 (control-context-check this-ptree that-ptree))))

   (def-syntactic split :lex "||<"
                        :infix (:precedence 60 :context :grouping)
       :tidier 'elim-left-braces

       :checkers (control-composition-check

                 (:up 1 (control-context-check this-ptree that-ptree))))

   (def-syntactic choice :lex ";?"
                         :infix (:precedence 60 :context :grouping)
       :tidier 'elim-left-braces

       :checkers (control-composition-check

                 (:up 1 (control-context-check this-ptree that-ptree))))

   (def-syntactic semicolon :lex ";"
                            :infix (:precedence 80 :context :grouping)
       :tidier 'elim-left-braces

       :checkers (control-composition-check

                 (:up 1 (control-context-check this-ptree that-ptree))))

   (def-syntactic if :reserved true
                     :prefix (:numargs 2 :precedence 85)
      :tidier ((:? ?(:^+ if ?test (:^+ then (:^+ else ?iftrue ?iffalse)))
                  !~(:^+ if ,test ,iftrue ,iffalse))
               (:? ?(:^+ if ?test (:^+ then ?iftrue))
                  !~(:^+ if ?test ?iftrue))
               (t
                (defect "'if' has no 'then' part"))))

   (def-syntactic then :reserved true
                       :prefix (:precedence 87 :numargs 1))

   (def-syntactic else :reserved true
                       :infix (:precedence 88 :context :binary))

   (def-syntactic perform :reserved true
                          :prefix (:context 1 :precedence 90)
      :tidier
         ((:? ?(:^+ perform (:^+ fun-app ?name [_*_] ?@pbs))
             !~(:^+ perform ,name ,@pbs))
          (:else (defect "Unintelligible")))

      :checkers
         ((:? ?(:^+ perform ?name ?@_)
             (and (not (is-name name))
                  (defect "Perform of process with illegal name " name)))

          (:? ?(:^+ perform ?name ?@bdgs)
             (check-all b bdgs this-grammar
                (:? ?(:~ ?(:^+ bind-param ?_ ?_))
                   (defect "Illegal data input " b " to perform of " name))))

          ))

   ;; 'produce (p1 <= v1 , ...)' indicates bindings to outputs of this
   ;; process
   (def-syntactic produce :reserved true
                         :prefix (:context 1 :precedence 90)
      :tidier
         ((:? ?(:^+ produce (:^+ group ?@bindings))
             !~(:^+ produce ,@bindings))
          (:? ?(:^+ produce ?binding)
             !~(:^+ produce ,binding))
          (:else (defect "'produce' must be followed by bindings in parens")))

      :checkers
         ((:? ?(:^+ produce ?@bindings)
             (check-all b bindings this-grammar
                (:? ?(:~ ?(:^+ bind-param ?@_))
                   (defect "'produce' args must all be of form 'param <= exp', not "
                           b)))))) >>
Note that the syntax of the parenthesized items after perform and produce is identical to that of the items after output, although they all serve different purposes. In each case, the items must be a sequence of parameter bindings, p <= v. (Remember that the characters "<=" are converted to the token <bind-param>.) For output and produce, they describe how the results of the current process are set; the former is for atomic and simple processes, and occurs in a result construct, while the latter occurs in the body of a composite process.)
<<Define bind-param-context

:. (def-syntactic bind-param :lex "<="
                             ...
      :checkers
         (...      .:
          (:up 1
             (:? ?(:^+ ?(:|| output perform produce) ?@_))) :.)).: >>
Any component of a control construct can be tagged. We indicate a tag using a double colon:
<<Define lex-colon

   ;; '::' is for step tags --
   (def-lexical #\: (dispatch
                        (#\: (token tag))
                        (:else (token colon)))) >>
The syntax is simple:
<<Define tag-syn

   (def-syntactic tag :lex "::" :infix (:context :binary :precedence 81)
      :checkers
         ((:? ?(:^+ tag ?name ?_)
             (and (not (is-Symbol name))
                  (defect "Illegal tag: " name)))

          (:? ?(:^+ tag ?_ ?x)
             (match-cond x
                (:? ?(:^+ ?op ?@_)
                   (cond ((memq op +owl-s-taggables+)
                          !())
                         (t (defect "Illegal as tagged element: " x))))))

          (:up 1 (control-context-check this-ptree that-ptree)))) >>
The composition and context of the control constructs are governed by the following tidiers and checkers:
<<Define control-checks

(defun elim-left-braces (pt _)
   (match-cond pt
      (:? ?(:^+ ?c ?@subs)
         (multi-let (((okay defects)
                      (repeat :for ((sub :in subs)
                                    :collectors okay defects)
                       :result (values okay defects)
                         (match-cond sub
                            (:? ?(:^+ left-brace ?@subsubs)
                               (cond ((= (len subsubs) 1)
                                      (one-collect okay
                                         (first subsubs)))
                                     (t
                                      (one-collect defects
                                         (defect "Left-brace encompasses"
                                                 " strange number of subtrees")))))
                            (t (one-collect okay sub))))))

            (cond ((null defects)
                   !~(:^+ ,c ,@okay))
                  (t defects))))
      (t false)))

(defun control-composition-check (this-ptree _ _)
   (match-let ?(:^+ ?_ ?@elements)
              this-ptree
      (repeat :for ((that-ptree :in elements)
                    :collector defects)
         (check-down-to-control-constructs that-ptree)

       :where
          (:def check-down-to-control-constructs (that-ptree)
              (match-cond that-ptree
                 (:? ?(:^+ ?c ?@_)
                    (cond ((or (memq c +owl-s-control-operators+)
                               (memq c +owl-s-taggables+)
                               (eq c 'tag))
                           !())
                          ((eq c 'with_namespaces)
                           (check-down-to-control-constructs
                               (first (Parsetree-subtrees that-ptree))))
                          (t (list (defect "Illegal as element of"
                                           " composed process: "
                                           that-ptree)))))

                 (:else
                  (list (defect "Atomic expression " that-ptree
                                " not allowed as"
                                " element of composed process"))))))))

;;; This is applied one level above the tree we are actually checking,
;;; 'lo-ptree' --
(defun control-context-check (lo-ptree hi-ptree)
      (match-cond hi-ptree
         (:? ?(:^+ if ?_ ?,lo-ptree ?@_)
            !())
         (:? ?(:^+ if ?_ ?_ ?,lo-ptree)
            !())
         (:? ?(:^+ define ?sort ?_ ?_ ?body)
            (cond ((and (eq sort 'composite)
                        (eq body lo-ptree))
                   !())
                  (t (list (defect "Control construct can appear"
                                   " below 'define' only as the body of"
                                   :% " a composite process.")))))
         (:? ?(:^+ ?c ?@_)
            (cond ((memq c +owl-s-control-operators+)
                   !())
                  (t (list (defect "Control construct in illegal context (op "
                                   c ")"))))))) >>
where
<<Define control-constants

;;; These are the syntactic operators that can used to build
;;; arbitrary control structures.      
(defconstant +owl-s-control-operators+
    '(anyord split-join split semicolon))

(defconstant +owl-s-taggables+
    '(perform produce)) >>


2.6 Formulas

The only parts of OWL-S that are left to deal with are the logical formulas in conditions, effects, and various output constructs. Here we incorporate a simple logical language capturing the normal encodings of Lisp as infix syntax.

Most of this grammar is inherited from the simple-arith grammar that is part of the Lexiparse package All this grammar does is provide the usual precedence hierarchy for multiplication (*), addition (+), and their ilk. All the precedences for the operators lie in the range 100 to 200.

All we introduce here are the usual quantifiers:

<<Define quantifiers

   (def-syntactic forall :reserved true
                         :prefix (:context 2 :precedence 90
                                  :local-grammar ((1 typed-var-grammar))))

   (def-syntactic exists :reserved true
                         :prefix (:context 2 :precedence 90
                                  :local-grammar ((1 typed-var-grammar)))) >>

Finally, in table Cross Precedence, we show which operators a given operator "dominates." The row for operator i shows how it behaves when competing with other operators to its right. The first column shows how it behaves when matched against itself. The second column shows all the operators whose left precedence is greater than operator i. If operator j appears in the list, it will tend to appear in trees headed by operator i. (A bullet to the left of an operator's name in the row labels indicates that it is an infix operator, to minimize confusion about operators with infix and prefix versions.) Of course, this table gives only a crude indication of how each operator interacts with the entities below it in the parsetree; the details are spelled out in the syntactic spec for the operators in question.

Table 2-2:Operator precedence comparisons
Operators on right
Operator
on left
Associa
tivity
Dominated tokens
left-brace "{" --   ||;  ;?  ||<  ||>  ;  ::  else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
left-paren "(" R   ||;  ;?  ||<  ||>  ;  ::  else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
•left-paren "(" R   ||;  ;?  ||<  ||>  ;  ::  else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
•| "" L ||;  ;?  ||<  ||>  ;  ::  else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
'define' -- ||;  ;?  ||<  ||>  ;  ::  else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
'atomic' -- ||;  ;?  ||<  ||>  ;  ::  else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
'composite' -- ||;  ;?  ||<  ||>  ;  ::  else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
'simple' -- ||;  ;?  ||<  ||>  ;  ::  else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
'with_namespaces' -- ||;  ;?  ||<  ||>  ;  ::  else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
'process' -- ||;  ;?  ||<  ||>  ;  ::  else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
•anyord "||;" L ;  ::  else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
•choice ";?" L ;  ::  else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
•split "||<" L ;  ::  else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
•split-join "||>" L ;  ::  else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
•semicolon ";" L ::  else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
•tag "::" L else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
'if' -- else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
'then' -- else  ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
•'else' L ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
'exists' -- ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
'forall' -- ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
'perform' -- ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
'produce' -- ,  <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
•comma "," L <=  =>  |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
•bind-param "<=" L |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
•when "=>" L |  &  =  >=  >  =<  <  -  +  /  *  (  .  :
colon ":" R &  =  >=  >  =<  <  -  +  /  *  (  .  :
'inputs' -- &  =  >=  >  =<  <  -  +  /  *  (  .  :
'locals' -- &  =  >=  >  =<  <  -  +  /  *  (  .  :
'outputs' -- &  =  >=  >  =<  <  -  +  /  *  (  .  :
'participants' -- &  =  >=  >  =<  <  -  +  /  *  (  .  :
'precondition' -- &  =  >=  >  =<  <  -  +  /  *  (  .  :
'result' -- &  =  >=  >  =<  <  -  +  /  *  (  .  :
•or "|" L &  =  >=  >  =<  <  -  +  /  *  (  .  :
•and "&" L =  >=  >  =<  <  -  +  /  *  (  .  :
not "~" -- =  >=  >  =<  <  -  +  /  *  (  .  :
•equals "=" L -  +  /  *  (  .  :
•geq ">=" L -  +  /  *  (  .  :
•greater ">" L -  +  /  *  (  .  :
•leq "=<" L -  +  /  *  (  .  :
•less "<" L -  +  /  *  (  .  :
minus "-" L /  *  (  .  :
plus "+" L /  *  (  .  :
•minus "-" L /  *  (  .  :
•plus "+" L /  *  (  .  :
•divide "/" L (  .  :
•times "*" L (  .  :
'output' -- .  :
•dot "." L :
'uri' --
•colon ":" L


3  Translation to RDF/XML

The grammar laid out in section "Syntax" yields the parsetrees shown in table 1 for the processes foo and baz presented in section "Goals". To convince yourself of this, you can verify that anywhere a parsetree with op1 appears as an immediate subtree of a parsetree with op0, either op1 has higher precedence, or it originally appeared inside brackets of some sort (which may have been tidied away). You can also verify that none of the checkers in section "Syntax" would find any defects.

Table 3: Parsetrees for Processes foo and baz
"*** PARSETREE for 'foo' FUMBLED COMPLETELY ***" "*** PARSETREE for 'baz' FUMBLED COMPLETELY ***"

Having successfully parsed expressions of a language, the next step is to translate the parsed expressions into some internal representation. This process is called internalization. There are various possibilities. The most attractive is to produce a plan structure that can be used to implement or reason about a web service. The exercise we focus on here is translation to the XML serialization of RDF. This will relate the surface syntax to the ontology of [Coa05], the latest release of OWL-S.

3.1  Recursive Transformation of Parsetrees

All programs that traverse one recursively defined data structure in order to produce another recursively defined data structure look pretty much alike. So I will only sketch the algorithm. The entire thing is available upon request, but if I were you I wouldn't even read the sketch. If you really want to make sense of it, you have to be familiar with the tools documented in [McD04a] as well as the parser itself [McD04b]].

XML infosets are represented using Lisp structures in the obvious way. There is a structure called XML-element that has fields type, attributes, and contents. The type is an XML-name, which is defined by a namespace and a string. The attributes are a list of (name, string) pairs, where the name is an XML-name denoting an attribute and the string contains the literal that is its value. The contents field is occupied by a list of strings and XML-elements.

We start by creating a subgrammar of the basic OWL-S grammar.

<<Define def-rdf-grammar

(def-grammar owl-s-as-rdf
    :parent owl-s
    :sym-case #+ansi-cl :up #-ansi-cl :preserve) >>
We also define the usual namespaces, omitting most details:
<<Define namespace-definitions


(defvar owl-s-namespace*
        (make-XML-namespace
           :global true
           :governor
              (string->uri
                 "http://www.daml.org/services/owl-s/1.1/Process.owl")))

(defvar rdf-namespace*
        (make-XML-namespace ...))

(defvar xsd-namespace*
        (make-XML-namespace ...))

(defvar comlog-namespace*
        (make-XML-namespace ...))

(defvar shadow-rdf-namespace*
        (make-XML-namespace ...))

;;; Third element in each tuple is the entity name, which is set by
;;; 'rdf-xml-out' --
(defvar example-ns-tab*
    (list (tuple 'owls owl-s-namespace* 'owls)
          (tuple 'rdf rdf-namespace* 'rdf)
          (tuple 'xsd xsd-namespace* 'xsd)
          (tuple 'clog comlog-namespace* 'clog)
          (tuple 'lisht shadow-rdf-namespace* 'lisht)))


;;;;(defun rdf-xml-out (rdf-xml-tree srm)
;;;;   (xml-document-out rdf-xml-tree example-ns-tab*
;;;;                     (XML-namespace-governor owl-s-namespace*)
;;;;                     srm))
  >>
Internalizing OWL-S processes requires internalizing their IOPR declarations and, in the case of composites, their bodies.
<<Define process-internalizations

   (defvar process-kind-owl-s-equivs*
           (list (tuple 'atomic (owl-s-name "AtomicProcess"))
                 (tuple 'simple (owl-s-name "SimpleProcess"))
                 (tuple 'composite (owl-s-name "CompositeProcess"))))

   (def-internal define (pt _ ns-tab)
      (match-cond pt
          (:? ?(:^+ define ?kind ?name (:^+ iopr ?@iopr-specs) ?body-exp)
             (multi-let (((bound-vars output-vars var-xmls)
                          (iopr->xml iopr-specs)))
                (make-XML-element
                   :type (alref process-kind-owl-s-equivs*
                                kind)
                   :attributes (list (tuple rdf-ID-name* name))
                   :contents
                      (cond ((eq kind 'composite)
                             (append var-xmls
                                     (list
                                        (composite-body->xml
                                           body-exp bound-vars output-vars
                                           ns-tab))))
                            (t var-xmls)))))
         (t (signal-problem define-internalizer
               "Unintelligible define-headed parsetree: "
               :% (:e (parsetree-show pt))))))

   (defun composite-body->xml (body-exp bound-vars output-vars ns-tab)
      (make-XML-element
         :type (owl-s-name "composedOf")
         :contents
            (list (body->xml
                    body-exp bound-vars output-vars
                    (extract-step-tags body-exp)
                    ns-tab)))) >>
Internalizing the IOPRs requires internalizing declarations, plus conditions and effects.
<<Define iopr-internalizer


;;; Each element of 'iopr-specs' is an 'inputs', 'outputs', etc.
;;; parsetree.
;;; For 'inputs', 'outputs", and 'locals', sus are parsetrees headed
;;;    by <hyphen>, <comma>, or a variable.
;;; Returns < input-n-local-vars, output-vars, xml-elements >
(defun iopr->xml (iopr-specs)
...) >>
Declaration parsetrees are headed by <hyphen> or <comma>, or are simple variables. The internalizer for a list of such parsetrees reflects that fact:
<<Define vardecl-internalizer

(defun vardecls->xml (labeled-decls role var-role)
   (let-fun ()
      (match-let ?(:^+ ?_ ?@decls)
                 labeled-decls
         (repeat :for ((decl :in decls)
                       :collectors vars xmls)
          :result (values vars xmls)
          :within
            (multi-let (((vl xl)
                        (match-cond decl
                           (:? ?(:^+ hyphen ?@vl ?ty)
                              (declarations vl ty))
                           (:? ?(:^+ comma ?@vl)
                              (declarations vl false))
                           (t
                            (declarations (list decl) false)))))
               (:continue
                :nconc (:into vars vl)
                :nconc (:into xmls xl)))))

    :where

       (:def declarations (vars type)
          (values 
             (<# (\\ (v) (tuple v role))
                 vars)
             (repeat :for ((v :in vars))
              :collect
                (make-XML-element ...)))))) >>
Note that vardecls->xml returns two elements: an alist giving the variables and their role (:input, :output, or :local), and the corresponding XML.

Preconditions are internalized by turning them into XML literals (see sect. "Formulas"). Results require analysis in order to resolve them into the standard OWL-S components:

<<Define res-tree-analyzer

;;; 'ins' are variables bound as inputs and locals.  
;;; 'outs' are variables bound as outputs.
;;; Both lists are alists with pairs (var [:input|:local|:outputs]
;;; The latter are the only variables that can appear to the left of
;;; '<='.  Everywhere else a variable must be an element of 'ins' (or
;;; bound by a local quantifier).
(defun res-tree->xml (res ins outs)
   (let-fun ()
      (walk-through res false (append ins outs))

    :where

      (:def walk-through (subres must-see-when bvars)
         (match-cond subres
            (:? ?(:^+ forall ?vars ?r)
               (multi-let (((vl resVars)
                            (decls->resVars vars)))
                  (append resVars
                          (walk-through r true (append vl ins)))))
            (:? ?(:^+ when ?condition ?effect)
               (cons (logical-expression-element
                         "inCondition" condition bvars !())
                     (effect-elements effect bvars)))
            (:? ?(:^+ ?(:|| and group) ?@elts)
               (<! (\\ (e) (walk-through e must-see-when bvars))
                   elts))
            (must-see-when
             (signal-problem res-tree->xml
                "Result contains 'forall' without '|->' to make bindings work: "
                :% res))
            (t (effect-elements subres bvars))))

       (:def effect-elements (eff bvars) ...)) >>

... and so forth.

Finally, a word about namespaces. In section "Namespaces", we presented a general-purpose namespace-declaration notation with_namespaces (—ns-decls—){...}. To translate such an expression into XML requires rewriting the ns-decls into the standard notation, then attaching them to the correct XML element.

<<Define namespace-internalizer

      
  (def-internal with_namespaces (pt g ns-tab)
     (match-let ?(:^+ with_namespaces ?sub-pt ?@nsl)
                pt
        (let* ((namespace-pairs
                  (nconc (<# (\\ (nsp)
                                (match-let
                                       ?(:^+ namespace+name
                                             ?name (:^+ uri ?uri))
                                       nsp
                                   (tuple name
                                          (make-XML-namespace
                                             :global true
                                             :governor
                                                (make-interned-uri
                                                   uri)))))
                             nsl)
                         ns-tab))
               (i (internalize sub-pt g namespace-pairs)))
           (cond ((is-XML-element i)
                  (!= (XML-element-namespace-decls i)
                      namespace-pairs)
                  i)
                 ((is-list-of i #'is-XML-element)
                  (repeat :for ((xe :in i))
                     (!= (XML-element-namespace-decls xe)
                         namespace-pairs))
                  i)
                 (t
                  (signal-problem with_namespaces
                     "Somehow failed to internalize"
                     :% 3 pt
                     :% " to XML-element: "
                     :% 3 i))))))

  (def-internal name-wrt-space (pt _ ns-tab)
     (match-let ?(:^+ name-wrt-space ?spacename ?name)
                pt
        (let ((space (alref ns-tab spacename)))
           (cond (space
                  (make-XML-name
                      :string (Symbol-name name)
                      :namespace space
                      :prefix spacename))
                 (t
                  (signal-problem name-wrt-space
                     "Undeclared namespace " spacename)))))) >>

It may seem odd, but here is the place we put the internalizer for left braces. Curly braces are useful for grouping control structures, but they are supposed to be tidied away in almost every context. For instance, an expression of the form {... ||> ... ||> ...} is transformed into a parsetree with operator ||>. The only exception is inside a with_namespaces at the top level, whose argument can be a group of defines surrounded by curly braces. So we have the left-brace internalizer return a list of XML elements, instead of the single element produced by all the others.

<<Define left-brace-internalizer

   (def-internal left-brace (pt g ns-tab)
      (<# (\\ (sub) (internalize sub g ns-tab))
          (Parsetree-subtrees pt))) >>

3.2  A Sow's Ear from a Silk Purse: the XML Version

Putting all the pieces together, the XML for process foo is shown in table 2 for baz, in table 3.

Table 3-4: XML for process foo
*** RDF for 'foo' FUMBLED COMPLETELY ***
Table 3-5: XML for process baz
*** RDF for 'baz' FUMBLED COMPLETELY ***

A more realistic example is the "Bravo Air" process model from [Coa05]. Here are the main processes as expressed in the surface syntax:

with_namespaces
  (uri"http://www.daml.org/services/owl-s/1.1/Process.owl",
   rdf: uri"http://www.w3.org/1999/02/22-rdf-syntax-ns",
   rdfs: uri"http://www.w3.org/2000/01/rdf-schema",
   shadow_rdf:
       uri"http://www.daml.org/services/owl-s/1.1/generic/ObjectList.owl",
   expr: uri"http://www.daml.org/services/owl-s/1.1/generic/Expression.owl",
   owl: uri"http://www.w3.org/2002/07/owl",
   xsd: uri"http://www.w3.org/2001/XMLSchema",
   service: uri"http://www.daml.org/services/owl-s/1.1/Service.owl",
   bravo: uri"http://www.daml.org/services/owl-s/1.1/BravoAirProcess.owl",
   profile: uri"http://www.daml.org/services/owl-s/1.1/Profile.owl",
   ba_service: uri"http://www.daml.org/services/owl-s/1.1/BravoAirService.owl",
   swrl: uri"http://www.w3.org/2003/11/swrl",
   concepts: uri"http://www.daml.org/services/owl-s/1.1/Concepts.owl")
{   
  define composite process
           BravoAir(inputs: (DepartureAirport, ArrivalAirport
                               - AirportURI
                             OutboundDate, InboundDate
                                - DateURI
                             RoundTrip - Boolean
                             AcctName - NameURI
                             Password - StringURI
                             Confirm - ConfirmURI),
                    outputs: (FlightsFound,
                              PreferredFlightItinerary,
                              ReservationID - URI),
                    result: (output(FlightsFound <= 
                                       PerformGetDesiredFlightDetails
                                          .GetDesiredFlightDetails_DepartureAirport,
                                    PreferredFlightItinerary <=
                                       PerformBookFlight
                                          .BookFlight_PreferredFlightItinerary,
                                    ReservationID <=
                                       PerformBookFlight.BookFlight_ReservationID)
                             &
                             hasFlightItinerary(TheClient,
                                                PreferredFlightItinerary)))
     {
       PerformGetDesiredFlightDetails ::
          perform GetDesiredFlightDetails(DepartureAirport <= DepartureAirport,
                                          ArrivalAirport <= ArrivalAirport,
                                          OutboundDate <= OutboundDate,
                                          RoundTrip <= RoundTrip);
       PerformSelectAvailableFlight ::
          perform SelectAvailableFlight(FlightsAvailable
                                        <= PerformGetDesiredFlightDetails
                                             .FlightsFound);
       perform BookFlight(SelectedFlight <= PerformSelectAvailableFlight
                                               .SelectedFlight,
                          AcctName <= AcctName,
                          Password <= Password)
     }

  define composite process
        BookFlight(inputs: (AcctName - NameURI
                            Password - StringURI
                            SelectedFlight - FlightItineraryList),
                   outputs: (PreferredFlightItinerary,
                             ReservationID - ReservationNumber),
                   result: (output(PreferredFlightItinerary
                                      <= PerformCompleteReservation
                                            .PreferredFlightItinerary,
                                   ReservationID
                                      <= PerformCompleteReservation.ReservationID)))
    {
      perform Login(AcctName <= AcctName, Password <= Password);
      perform CompleteReservation(AcctName <= AcctName,
                                  SelectedFlight <= SelectedFlight)
    }

  define composite process CompleteReservation
                    (inputs: (AcctName - NameURI
                              SelectedFlight - FlightItineraryList),
                     outputs: (PreferredFlightItinerary - FlightItinerary
                               ReservationID - ReservationNumber))
 {
   if LoggedIn(AcctName)
     then {
             PerformConfirmReservation ::
              perform ConfirmReservation(SelectedFlight <= SelectedFlight);
             produce(PreferredFlightItinerary
                       <= PerformConfirmReservation
                             .PreferredFlightItinerary,
                     ReservationID
                       <= PerformConfirmReservation.ReservationID)
         }
     else
         {
           produce(PreferredFlightItinerary <= null,
                   ReservationID <= null)
         }
 }
                              

  define atomic process GetDesiredFlightDetails
                  (inputs: (DepartureAirport ArrivalAirport - AirportURI
                            OutboundDate, InboundDate - DateURI
                            RoundTrip - Boolean),
                   outputs: (FlightsFound - FlightList))

  define atomic process SelectAvailableFlight
                   (inputs: (FlightsAvailable - FlightList),
                    outputs: (SelectedFlight - FlightItineraryList))

  define atomic process LogIn(inputs: (AcctName - NameURI
                                       Password - String),
                              outputs: (Success - Boolean),
                              result: (hasPassword(AcctName, Password)
                                       |-> output(Success <= true)
                                           & LoggedIn(AcctName)),
                              result: (forall(Correct_Password - String)
                                         (hasPassword(AcctName, Correct_Password)
                                          & ~(Correct_Password = Password)
                                          |-> output(Success <= false)
                                              & ~LoggedIn(AcctName))))

  define atomic process ConfirmReservation(inputs: (SelectedFlight
                                                       - FlightItineraryList
                                                    Confirm - Confirmation),
                                           outputs: (PreferredFlightItinerary
                                                        - FlightItinerary
                                                     ReservationID
                                                        - ReservationNumber),
                                           result: (hasFlightItinerary
                                                       (TheClient,
                                                        PreferredFlightItinerary)))





}

There are two versions of the translation. The concise version is produced by using the "parsetype="Collection" device that allows lists to be expressed in a linear format. This device is technically illegal, and a purer version can be obtained only by using a list vocabulary in which list structures can be built using only XML equivalents of cons and nil. The resulting long version satisfies the canons of OWL-DL , but is not readable or writable by human beings. (Because browsers differ in how they display random XML trees, it might be better to view the source of these files in a text editor than to just click on the links to them.) Of course, the "concise" version is not terribly user-friendly either. That's why we need the surface syntax.

A. OWL-S Syntax: Top-Level

For the complete listing for this file, see owl-s-syn.lisp

<<Define File owl-s-syn.lisp
;-*- Mode: Common-lisp; Package: lexiparse; Readtable: ytools; -*-
(in-package :lexiparse)

(depends-on %module/ ytools lexiparse)

(depends-on %lexiparse/ xml arithgram)

#+allegro
(defun allegro-case-choice ()
   (cond ((eq excl:*current-case-mode* ':case-sensitive-lower)
          ':preserve)
         (t ':up))) 

<<Insert: grammar-def>>

(defvar owl-s-lexical-chars* '(#\_))

<<Insert: control-constants>>

<<Insert: typed-vars-tidier>>

<<Insert: result-syntax-checker>>

<<Insert: must-be-atomic>>

<<Insert: control-checks>>

(with-grammar owl-s

;;; LEXICAL

   (def-lexical-tokens ((#\{ left-brace)
                        (#\} right-brace)
                        (#\. dot)))

   <<Insert: lex-semicolon>>

   (def-lexical #\| 
                :. (dispatch .:
   <<Insert: lex-vbar>>
                       (:else (token or))))

   ;; '->' is ordinary if-then --
   (def-lexical #\- (dispatch
                       (#\> (token imply))
                       (:else (token minus))))

   <<Insert: lex-when>>

   <<Insert: lex-bind-param>>

   <<Insert: lex-colon>>

   (def-lexical (#\_ #\a - #\z #\A - #\Z #\?) (lex-sym owl-s-lexical-chars*)
       :name alphabetic)


;;; SYNTACTIC

   <<Insert: namespace-spec>>

   <<Insert: uri-spec>>

   <<Insert: process-definition>>

   <<Insert: process-classes>>

   <<Insert: process-spec>>

   <<Insert: param-decls>>

   <<Insert: preconds>>

   <<Insert: results>>

   <<Insert: output-syntax>>

   <<Insert: colon>>

   <<Insert: quantifiers>>

   <<Insert: syn-when>>

   <<Insert: syn-braces>>

   <<Insert: bind-param-syn>>

   <<Insert: control-constructs>>

   <<Insert: tag-syn>>

   <<Insert: left-paren>>

   (def-syntactic \| :lex "" :infix (:precedence 3 :context :grouping))

   (def-syntactic dot :lex "." :infix (:precedence 205 :context :binary)
      :checkers
      ((:? ?(:^+ dot ?step ?output)
          (nconc (cond ((is-Symbol step) !())
                       (t (list (defect "Illegal name to left of dot in "
                                        step "." output))))
                 (cond ((is-Symbol output) !())
                       (t (list (defect "Illegal name to right of dot in "
                                        step "." output))))))))
)


;;; LOCAL GRAMMAR for type declarations

<<Insert: typed-var-grammar>>

(defun is-name (x)
   (or (is-Symbol x)
       (matchq ?(:^+ name-wrt-space ?_ ?_)
               x))) >>

B. OWL-S Internalization Code for RDF/XML: Top-Level

Parts of this code have been elided. For a complete listing, see owl-s-rdf.lisp.

<<Define File owl-s-rdf.lisp
;-*- Mode: Common-lisp; Package: lexiparse; Readtable: ytools; -*-
(in-package :lexiparse)

(depends-on %owl-s-gram/ owl-s-syn)

;;; INTERNALIZATION A: Translation to RDF

<<Insert: def-rdf-grammar>>

(defun string->uri (s)
   (net.uri:intern-uri
      (net.uri:parse-uri s)))

(defun is-uri (s)
   (net.uri:uri-p s))

<<Insert: namespace-definitions>>

(defvar rdf-ID-name*
        (make-XML-name
           :string "ID" :namespace rdf-namespace*))

(defvar rdf-resource-name*
        (make-XML-name :string "resource" :namespace rdf-namespace*))

(defvar rdf-parseType-name*
        (make-XML-name :string "parseType" :namespace rdf-namespace*))

(defvar rdf-shadow-list-name*
        (make-XML-name :string "List"
                       :namespace shadow-rdf-namespace*))

(defvar rdf-shadow-first-name*
        (make-XML-name :string "First"
                       :namespace shadow-rdf-namespace*))

(defvar rdf-shadow-rest-name*
        (make-XML-name :string "Rest"
                       :namespace shadow-rdf-namespace*))

(defvar rdf-shadow-empty-list-name*
        (make-XML-name :string "nil"
                       :namespace shadow-rdf-namespace*))


(defun owl-s-name (str)
   (make-XML-name
      :string str
      :namespace owl-s-namespace*)) 

(with-grammar owl-s-as-rdf

      <<Insert: namespace-internalizer>>

   <<Insert: left-brace-internalizer>>

   <<Insert: process-internalizations>>

)

<<Insert: iopr-internalizer>>

<<Insert: vardecl-internalizer>>

<<Insert: res-tree-analyzer>>

;;; Returns < bvars, resVar-XML-list >.  'bvars' is alist of pairs (var :local).
(defun decls->resVars (vars)
...)

...

;;; Builds a description RDF element.
(defun body->xml (body bvars output-vars step-tags ns-tab) ...)

... >>

Bibliography

Coa05
The OWL-S Coalition. OWL-S Release 1.1beta. Recent draft, 2004.
Knu84
Donald E. Knuth. Literate programming. The Computer Journal , 27(2):97-111, 1984.
McD04a
Drew McDermott. The YTools Manual, version 1.4
McD04b
Drew McDermott. Lexiparse: A Lexicon-based Parser for Lisp Applications. Draft, 2004.
MH04
Deborah L. McGuinness and Frank van Harmelen (eds.) OWL Web Ontology Language Overview. W3C Recommendation 2004
Rei78
Raymond Reiter. On closed world data bases. In Gallaire and Minker (eds). Logic and Databases, Plenum Press.