author | berghofe |
Fri, 16 Jul 1999 14:06:13 +0200 | |
changeset 7020 | 75ff179df7b7 |
parent 104 | d8205bb279a7 |
permissions | -rw-r--r-- |
\chapter{Defining Logics} \label{Defining-Logics} This chapter is intended for Isabelle experts. It explains how to define new logical systems, Isabelle's {\it raison d'\^etre}. Isabelle logics are hierarchies of theories. A number of simple examples are contained in the introductory manual; the full syntax of theory definitions is shown in the {\em Reference Manual}. The purpose of this chapter is to explain the remaining subtleties, especially some context conditions on the class structure and the definition of new mixfix syntax. A full understanding of the material requires knowledge of the internal representation of terms (data type {\tt term}) as detailed in the {\em Reference Manual}. Sections marked with a * can be skipped on first reading. \section{Classes and Types *} \index{*arities!context conditions} Type declarations are subject to the following two well-formedness conditions: \begin{itemize} \item There are no two declarations $ty :: (\vec{r})c$ and $ty :: (\vec{s})c$ with $\vec{r} \neq \vec{s}$. For example \begin{ttbox} types ty 1 arities ty :: (\{logic\}) logic ty :: (\{\})logic \end{ttbox} leads to an error message and fails. \item If there are two declarations $ty :: (s@1,\dots,s@n)c$ and $ty :: (s@1',\dots,s@n')c'$ such that $c' < c$ then $s@i' \preceq s@i$ must hold for $i=1,\dots,n$. The relationship $\preceq$, defined as \[ s' \preceq s \iff \forall c\in s. \exists c'\in s'.~ c'\le c, \] expresses that the set of types represented by $s'$ is a subset of the set of types represented by $s$. For example \begin{ttbox} classes term < logic types ty 1 arities ty :: (\{logic\})logic ty :: (\{\})term \end{ttbox} leads to an error message and fails. \end{itemize} These conditions guarantee principal types~\cite{nipkow-prehofer}. \section{Precedence Grammars} \label{PrecedenceGrammars} \index{precedence grammar|(} The precise syntax of a logic is best defined by a context-free grammar. These grammars obey the following conventions: identifiers denote nonterminals, {\tt typewriter} fount denotes terminals, repetition is indicated by \dots, and alternatives are separated by $|$. In order to simplify the description of mathematical languages, we introduce an extended format which permits {\bf precedences}\index{precedence}. This scheme generalizes precedence declarations in \ML\ and {\sc prolog}. In this extended grammar format, nonterminals are decorated by integers, their precedence. In the sequel, precedences are shown as subscripts. A nonterminal $A@p$ on the right-hand side of a production may only be replaced using a production $A@q = \gamma$ where $p \le q$. Formally, a set of context free productions $G$ induces a derivation relation $\rew@G$ on strings as follows: \[ \alpha A@p \beta ~\rew@G~ \alpha\gamma\beta ~~~iff~~~ \exists q \ge p.~(A@q=\gamma) \in G \] Any extended grammar of this kind can be translated into a normal context free grammar. However, this translation may require the introduction of a large number of new nonterminals and productions. \begin{example} \label{PrecedenceEx} The following simple grammar for arithmetic expressions demonstrates how binding power and associativity of operators can be enforced by precedences. \begin{center} \begin{tabular}{rclr} $A@9$ & = & {\tt0} \\ $A@9$ & = & {\tt(} $A@0$ {\tt)} \\ $A@0$ & = & $A@0$ {\tt+} $A@1$ \\ $A@2$ & = & $A@3$ {\tt*} $A@2$ \\ $A@3$ & = & {\tt-} $A@3$ \end{tabular} \end{center} The choice of precedences determines that \verb$-$ binds tighter than \verb$*$ which binds tighter than \verb$+$, and that \verb$+$ and \verb$*$ associate to the left and right, respectively. \end{example} To minimize the number of subscripts, we adopt the following conventions: \begin{itemize} \item all precedences $p$ must be in the range $0 \leq p \leq max_pri$ for some fixed $max_pri$. \item precedence $0$ on the right-hand side and precedence $max_pri$ on the left-hand side may be omitted. \end{itemize} In addition, we write the production $A@p = \alpha$ as $A = \alpha~(p)$. Using these conventions and assuming $max_pri=9$, the grammar in Example~\ref{PrecedenceEx} becomes \begin{center} \begin{tabular}{rclc} $A$ & = & {\tt0} & \hspace*{4em} \\ & $|$ & {\tt(} $A$ {\tt)} \\ & $|$ & $A$ {\tt+} $A@1$ & (0) \\ & $|$ & $A@3$ {\tt*} $A@2$ & (2) \\ & $|$ & {\tt-} $A@3$ & (3) \end{tabular} \end{center} \index{precedence grammar|)} \section{Basic syntax *} An informal account of most of Isabelle's syntax (meta-logic, types etc) is contained in {\em Introduction to Isabelle}. A precise description using a precedence grammar is shown in Figure~\ref{MetaLogicSyntax}. This description is the basis of all extensions by object-logics. \begin{figure}[htb] \begin{center} \begin{tabular}{rclc} $prop$ &=& \ttindex{PROP} $aprop$ ~~$|$~~ {\tt(} $prop$ {\tt)} \\ &$|$& $logic@3$ \ttindex{==} $logic@2$ & (2) \\ &$|$& $prop@2$ \ttindex{==>} $prop@1$ & (1) \\ &$|$& {\tt[|} $prop$ {\tt;} \dots {\tt;} $prop$ {\tt|]} {\tt==>} $prop@1$ & (1) \\ &$|$& {\tt!!} $idts$ {\tt.} $prop$ & (0) \\\\ $logic$ &=& $prop$ ~~$|$~~ $fun$ \\\\ $aprop$ &=& $id$ ~~$|$~~ $var$ ~~$|$~~ $fun@{max_pri}$ {\tt(} $logic$ {\tt,} \dots {\tt,} $logic$ {\tt)} \\\\ $fun$ &=& $id$ ~~$|$~~ $var$ ~~$|$~~ {\tt(} $fun$ {\tt)} \\ &$|$& \ttindex{\%} $idts$ {\tt.} $logic$ & (0) \\\\ $idts$ &=& $idt$ ~~$|$~~ $idt@1$ $idts$ \\\\ $idt$ &=& $id$ ~~$|$~~ {\tt(} $idt$ {\tt)} \\ &$|$& $id$ \ttindex{::} $type$ & (0) \\\\ $type$ &=& $tfree$ ~~$|$~~ $tvar$ \\ &$|$& $tfree$ {\tt::} $sort$ ~~$|$~~ $tvar$ {\tt::} $sort$ \\ &$|$& $id$ ~~$|$~~ $type@{max_pri}$ $id$ ~~$|$~~ {\tt(} $type$ {\tt,} \dots {\tt,} $type$ {\tt)} $id$ \\ &$|$& $type@1$ \ttindex{=>} $type$ & (0) \\ &$|$& {\tt[} $type$ {\tt,} \dots {\tt,} $type$ {\tt]} {\tt=>} $type$&(0)\\ &$|$& {\tt(} $type$ {\tt)} \\\\ $sort$ &=& $id$ ~~$|$~~ {\tt\{\}} ~~$|$~~ {\tt\{} $id$ {\tt,} \dots {\tt,} $id$ {\tt\}} \end{tabular}\index{*"!"!}\index{*"["|}\index{*"|"]} \indexbold{type@$type$}\indexbold{sort@$sort$}\indexbold{idts@$idts$} \indexbold{logic@$logic$}\indexbold{prop@$prop$}\indexbold{fun@$fun$} \end{center} \caption{Meta-Logic Syntax} \label{MetaLogicSyntax} \end{figure} The following main categories are defined: \begin{description} \item[$prop$] Terms of type $prop$, i.e.\ formulae of the meta-logic. \item[$aprop$] Atomic propositions. \item[$logic$] Terms of types in class $logic$. Initially, $logic$ contains merely $prop$. As the syntax is extended by new object-logics, more productions for $logic$ are added (see below). \item[$fun$] Terms potentially of function type. \item[$type$] Types. \item[$idts$] a list of identifiers, possibly constrained by types. Note that $x::nat~y$ is parsed as $x::(nat~y)$, i.e.\ $y$ is treated like a type constructor applied to $nat$. \end{description} The predefined types $id$, $var$, $tfree$ and $tvar$ represent identifiers ({\tt f}), unknowns ({\tt ?f}), type variables ({\tt 'a}), and type unknowns ({\tt ?'a}) respectively. If we think of them as nonterminals with predefined syntax, we may assume that all their productions have precedence $max_pri$. \subsection{Logical types and default syntax} Isabelle is concerned with mathematical languages which have a certain minimal vocabulary: identifiers, variables, parentheses, and the lambda calculus. Logical types, i.e.\ those of class $logic$, are automatically equipped with this basic syntax. More precisely, for any type constructor $ty$ with arity $(\dots)c$, where $c$ is a subclass of $logic$, the following productions are added: \begin{center} \begin{tabular}{rclc} $ty$ &=& $id$ ~~$|$~~ $var$ ~~$|$~~ {\tt(} $ty$ {\tt)} \\ &$|$& $fun@{max_pri}$ {\tt(} $logic$ {\tt,} \dots {\tt,} $logic$ {\tt)}\\ &$|$& $ty@{max_pri}$ {\tt::} $type$\\\\ $logic$ &=& $ty$ \end{tabular} \end{center} \section{Mixfix syntax} \index{mixfix|(} We distinguish between abstract and concrete syntax. The {\em abstract} syntax is given by the typed constants of a theory. Abstract syntax trees are well-typed terms, i.e.\ values of \ML\ type {\tt term}. If none of the constants are introduced with mixfix annotations, there is no concrete syntax to speak of: terms can only be abstractions or applications of the form $f(t@1,\dots,t@n)$, where $f$ is a constant or variable. Since this notation quickly becomes unreadable, Isabelle supports syntax definitions in the form of unrestricted context-free grammars using mixfix annotations. Mixfix annotations describe the {\em concrete} syntax, its translation into the abstract syntax, and a pretty-printing scheme, all in one. Isabelle syntax definitions are inspired by \OBJ's~\cite{OBJ} {\em mixfix\/} syntax. Each mixfix annotation defines a precedence grammar production and associates an Isabelle constant with it. A {\em mixfix declaration} {\tt consts $c$ ::\ $\tau$ ($sy$ $ps$ $p$)} is interpreted as a grammar pro\-duction as follows: \begin{itemize} \item $sy$ is the right-hand side of this production, specified as a {\em mixfix annotation}. In general, $sy$ is of the form $\alpha@0\_\alpha@1\dots\alpha@{n-1}\_\alpha@n$, where each occurrence of ``\ttindex{_}'' denotes an argument/nonterminal and the strings $\alpha@i$ do not contain ``{\tt_}''. \item $\tau$ specifies the types of the nonterminals on the left and right hand side. If $sy$ is of the form above, $\tau$ must be of the form $[\tau@1,\dots,\tau@n] \To \tau'$. Then argument $i$ is of type $\tau@i$ and the result, i.e.\ the left-hand side of the production, is of type $\tau'$. Both the $\tau@i$ and $\tau'$ may be function types. \item $c$ is the name of the Isabelle constant associated with this production. Parsing an instance of the phrase $sy$ generates the {\tt term} {\tt Const($c$,dummyT\footnote{Proper types are inserted later on. See \S\ref{Typing}})\$$a@1$\$$\dots$\$$a@n$}\index{*dummyT}, where $a@i$ is the term generated by parsing the $i^{th}$ argument. \item $ps$ must be of the form $[p@1,\dots,p@n]$, where $p@i$ is the minimal precedence\index{precedence} required of any phrase that may appear as the $i^{th}$ argument. The null list is interpreted as a list of 0's of the appropriate length. \item $p$ is the precedence of this production. \end{itemize} Notice that there is a close connection between abstract and concrete syntax: each production has an associated constant, and types act as {\bf syntactic categories} in the concrete syntax. To emphasize this connection, we sometimes refer to the nonterminals on the right-hand side of a production as its arguments and to the nonterminal on the left-hand side as its result. The maximal legal precedence is called \ttindexbold{max_pri}, which is currently 1000. If you want to ignore precedences, the safest way to do so is to use the annotation {\tt($sy$)}: this production puts no precedence constraints on any of its arguments and has maximal precedence itself, i.e.\ it is always applicable and does not exclude any productions of its arguments. \begin{example} In mixfix notation the grammar in Example~\ref{PrecedenceEx} can be written as follows: \begin{ttbox} types exp 0 consts "0" :: "exp" ("0" 9) "+" :: "[exp,exp] => exp" ("_ + _" [0,1] 0) "*" :: "[exp,exp] => exp" ("_ * _" [3,2] 2) "-" :: "exp => exp" ("- _" [3] 3) \end{ttbox} Parsing the string \verb!"0 + - 0 + 0"! produces the term {\tt $p$\$($p$\$($m$\$$z$)\$$z$)\$$z$} where {\tt$p =$ Const("+",dummyT)}, {\tt$m =$ Const("-",dummyT)}, and {\tt$z =$ Const("0",dummyT)}. \end{example} The interpretation of \ttindex{_} in a mixfix annotation is always as a {\bf meta-character}\index{meta-character} which does not represent itself but an argument position. The following characters are also meta-characters: \begin{ttbox} ' ( ) / \end{ttbox} Preceding any character with a quote (\verb$'$) turns it into an ordinary character. Thus you can write \verb!''! if you really want a single quote. The purpose of the other meta-characters is explained in \S\ref{PrettyPrinting}. Remember that in \ML\ strings \verb$\$ is already a (different kind of) meta-character. \subsection{Types and syntactic categories *} The precise mapping from types to syntactic categories is defined by the following function: \begin{eqnarray*} N(\tau@1\To\tau@2) &=& fun \\ N((\tau@1,\dots,\tau@n)ty) &=& ty \\ N(\alpha) &=& logic \end{eqnarray*} Only the outermost type constructor is taken into account and type variables can range over all logical types. This catches some ill-typed terms (like $Cons(x,0)$, where $Cons :: [\alpha,\alpha list] \To \alpha list$ and $0 :: nat$) but leaves the real work to the type checker. In terms of the precedence grammar format introduced in \S\ref{PrecedenceGrammars}, the declaration \begin{ttbox} consts \(c\) :: "[\(\tau@1\),\dots,\(\tau@n\)]\(\To\tau\)" ("\(\alpha@0\_\alpha@1\dots\alpha@{n-1}\_\alpha@n\)") [\(p@1\),\dots,\(p@n\)] \(p\)) \end{ttbox} defines the production \[ N(\tau)@p ~~=~~ \alpha@0 ~N(\tau@1)@{p@1}~ \alpha@1~ \dots ~\alpha@{n-1} ~N(\tau@n)@{p@n}~ \alpha@n \] \subsection{Copy productions *} Productions which do not create a new node in the abstract syntax tree are called {\bf copy productions}. They must have exactly one nonterminal on the right hand side. The term generated when parsing that nonterminal is simply passed up as the result of parsing the whole copy production. In Isabelle a copy production is indicated by an empty constant name, i.e.\ by \begin{ttbox} consts "" :: \(\tau\) (\(sy\) \(ps\) \(p\)) \end{ttbox} A special kind of copy production is one where, modulo white space, $sy$ is {\tt"_"}. It is called a {\bf chain production}. Chain productions should be seen as an abbreviation mechanism. Conceptually, they are removed from the grammar by adding appropriate new rules. Precedence information attached to chain productions is ignored. The following example demonstrates the effect: the grammar defined by \begin{ttbox} types A,B,C 0 consts AB :: "B => A" ("A _" [10] 517) "" :: "C => B" ("_" [0] 100) x :: "C" ("x" 5) y :: "C" ("y" 15) \end{ttbox} admits {\tt"A y"} but not {\tt"A x"}. Had the constant in the second production been some non-empty string, both {\tt"A y"} and {\tt"A x"} would be legal. \index{mixfix|)} \section{Lexical conventions} The lexical analyzer distinguishes the following kinds of tokens: delimiters, identifiers, unknowns, type variables and type unknowns. Delimiters are user-defined, i.e.\ they are extracted from the syntax definition. If $\alpha@0\_\alpha@1\dots\alpha@{n-1}\_\alpha@n$ is a mixfix annotation, each $\alpha@i$ is decomposed into substrings $\beta@1~\dots~\beta@k$ which are separated by and do not contain \bfindex{white space} ( = blanks, tabs, newlines). Each $\beta@j$ becomes a delimiter. Thus a delimiter can be an arbitrary string not containing white space. The lexical syntax of identifiers and variables ( = unknowns) is defined in the introductory manual. Parsing an identifier $f$ generates {\tt Free($f$,dummyT)}\index{*dummyT}. Parsing a variable {\tt?}$v$ generates {\tt Var(($u$,$i$),dummyT)} where $i$ is the integer value of the longest numeric suffix of $v$ (possibly $0$), and $u$ is the remaining prefix. Parsing a variable {\tt?}$v{.}i$ generates {\tt Var(($v$,$i$),dummyT)}. The following table covers the four different cases that can arise: \begin{center}\tt \begin{tabular}{cccc} "?v" & "?v.7" & "?v5" & "?v7.5" \\ Var(("v",0),$d$) & Var(("v",7),$d$) & Var(("v",5),$d$) & Var(("v7",5),$d$) \end{tabular} \end{center} where $d = {\tt dummyT}$. In mixfix annotations, \ttindexbold{id}, \ttindexbold{var}, \ttindexbold{tfree} and \ttindexbold{tvar} are the predefined categories of identifiers, unknowns, type variables and type unknowns, respectively. The lexical analyzer translates input strings to token lists by repeatedly taking the maximal prefix of the input string that forms a valid token. A maximal prefix that is both a delimiter and an identifier or variable (like {\tt ALL}) is treated as a delimiter. White spaces are separators. An important consequence of this translation scheme is that delimiters need not be separated by white space to be recognized as separate. If \verb$"-"$ is a delimiter but \verb$"--"$ is not, the string \verb$"--"$ is treated as two consecutive occurrences of \verb$"-"$. This is in contrast to \ML\ which would treat \verb$"--"$ as a single (undeclared) identifier. The consequence of Isabelle's more liberal scheme is that the same string may be parsed in a different way after extending the syntax: after adding \verb$"--"$ as a delimiter, the input \verb$"--"$ is treated as a single occurrence of \verb$"--"$. \section{Infix operators} {\tt Infixl} and {\tt infixr} declare infix operators which associate to the left and right respectively. As in \ML, prefixing infix operators with \ttindexbold{op} turns them into curried functions. Infix declarations can be reduced to mixfix ones as follows: \begin{center}\tt \begin{tabular}{l@{~~$\Longrightarrow$~~}l} "$c$" ::~$\tau$ (\ttindexbold{infixl} $p$) & "op $c$" ::~$\tau$ ("_ $c$ _" [$p$,$p+1$] $p$) \\ "$c$" ::~$\tau$ (\ttindexbold{infixr} $p$) & "op $c$" ::~$\tau$ ("_ $c$ _" [$p+1$,$p$] $p$) \end{tabular} \end{center} \section{Binders} A {\bf binder} is a variable-binding constant, such as a quantifier. The declaration \begin{ttbox} consts \(c\) :: \(\tau\) (binder \(Q\) \(p\)) \end{ttbox}\indexbold{*binder} introduces a binder $c$ of type $\tau$, which must have the form $(\tau@1\To\tau@2)\To\tau@3$. Its concrete syntax is $Q~x.t$. A binder is like a generalized quantifier where $\tau@1$ is the type of the bound variable $x$, $\tau@2$ the type of the body $t$, and $\tau@3$ the type of the whole term. For example $\forall$ can be declared like this: \begin{ttbox} consts All :: "('a => o) => o" (binder "ALL " 10) \end{ttbox} This allows us to write $\forall x.P$ either as {\tt ALL $x$.$P$} or {\tt All(\%$x$.$P$)}; the latter form is for purists only. In case $\tau@2 = \tau@3$, nested quantifications can be written as $Q~x@1 \dots x@n.t$. From a syntactic point of view, \begin{ttbox} consts \(c\) :: "\((\tau@1\To\tau@2)\To\tau@3\)" (binder "\(Q\)" \(p\)) \end{ttbox} is equivalent to \begin{ttbox} consts \(c\) :: "\((\tau@1\To\tau@2)\To\tau@3\)" "\(Q\)" :: "[idts,\(\tau@2\)] => \(\tau@3\)" ("\(Q\)_. _" \(p\)) \end{ttbox} where {\tt idts} is the syntactic category $idts$ defined in Figure~\ref{MetaLogicSyntax}. However, there is more to binders than concrete syntax: behind the scenes the body of the quantified expression has to be converted into a $\lambda$-abstraction (when parsing) and back again (when printing). This is performed by the translation mechanism, which is discussed below. For binders, the definition of the required translation functions has been automated. Many other syntactic forms, such as set comprehension, require special treatment. \section{Parse translations *} \label{Parse-translations} \index{parse translation|(} So far we have pretended that there is a close enough relationship between concrete and abstract syntax to allow an automatic translation from one to the other using the constant name supplied with each production. In many cases this scheme is not powerful enough, especially for constructs involving variable bindings. Therefore the $ML$-section of a theory definition can associate constant names with user-defined translation functions by including a line \begin{ttbox} val parse_translation = \dots \end{ttbox} where the right-hand side of this binding must be an \ML-expression of type \verb$(string * (term list -> term))list$. After the input string has been translated into a term according to the syntax definition, there is a second phase in which the term is translated using the user-supplied functions in a bottom-up manner. Given a list $tab$ of the above type, a term $t$ is translated as follows. If $t$ is not of the form {\tt Const($c$,$\tau$)\$$t@1$\$\dots\$$t@n$}, then $t$ is returned unchanged. Otherwise all $t@i$ are translated into $t@i'$. Let {\tt $t' =$ Const($c$,$\tau$)\$$t@1'$\$\dots\$$t@n'$}. If there is no pair $(c,f)$ in $tab$, return $t'$. Otherwise apply $f$ to $[t@1',\dots,t@n']$. If that raises an exception, return $t'$, otherwise return the result. \begin{example}\label{list-enum} \ML-lists are constructed by {\tt[]} and {\tt::}. For readability the list \hbox{\tt$x$::$y$::$z$::[]} can be written \hbox{\tt[$x$,$y$,$z$]}. In Isabelle the two forms of lists are declared as follows: \begin{ttbox} types list 1 enum 0 arities list :: (term)term consts "[]" :: "'a list" ("[]") ":" :: "['a, 'a list] => 'a list" (infixr 50) enum :: "enum => 'a list" ("[_]") sing :: "'a => enum" ("_") cons :: "['a,enum] => enum" ("_, _") end \end{ttbox} Because \verb$::$ is already used for type constraints, it is replaced by \verb$:$ as the infix list constructor. In order to allow list enumeration, the new type {\tt enum} is introduced. Its only purpose is syntactic and hence it does not need an arity, in contrast to the logical type {\tt list}. Although \hbox{\tt[$x$,$y$,$z$]} is syntactically legal, it needs to be translated into a term built up from \verb$[]$ and \verb$:$. This is what \verb$make_list$ accomplishes: \begin{ttbox} val cons = Const("op :", dummyT); fun make_list (Const("sing",_)$e) = cons $ e $ Const("[]", dummyT) | make_list (Const("cons",_)$e$es) = cons $ e $ make_list es; \end{ttbox} To hook this translation up to Isabelle's parser, the theory definition needs to contain the following $ML$-section: \begin{ttbox} ML fun enum_tr[enum] = make_list enum; val parse_translation = [("enum",enum_tr)] \end{ttbox} This causes \verb!Const("enum",_)$!$t$ to be replaced by \verb$enum_tr[$$t$\verb$]$. Of course the definition of \verb$make_list$ should be included in the $ML$-section. \end{example} \begin{example}\label{SET} Isabelle represents the set $\{ x \mid P(x) \}$ internally by $Set(\lambda x.P(x))$. The internal and external forms need separate constants:\footnote{In practice, the external form typically has a name beginning with an {\at} sign, such as {\tt {\at}SET}. This emphasizes that the constant should be used only for parsing/printing.} \begin{ttbox} types set 1 arities set :: (term)term consts Set :: "('a => o) => 'a set" SET :: "[id,o] => 'a set" ("\{_ | _\}") \end{ttbox} Parsing {\tt"\{$x$ | $P$\}"} according to this syntax yields the term {\tt Const("SET",dummyT) \$ Free("\(x\)",dummyT) \$ \(p\)}, where $p$ is the result of parsing $P$. What we need is the term {\tt Const("Set",dummyT)\$Abs("$x$",dummyT,$p'$)}, where $p'$ is some ``abstracted'' version of $p$. Therefore we define a function \begin{ttbox} fun set_tr[Free(s,T), p] = Const("Set", dummyT) $ Abs(s, T, abstract_over(Free(s,T), p)); \end{ttbox} where \verb$abstract_over: term*term -> term$ is a predefined function such that {\tt abstract_over($u$,$t$)} replaces every occurrence of $u$ in $t$ by a {\tt Bound} variable of the correct index (i.e.\ 0 at top level). Remember that {\tt dummyT} is replaced by the correct types at a later stage (see \S\ref{Typing}). Function {\tt set_tr} is associated with {\tt SET} by including the \ML-text \begin{ttbox} val parse_translation = [("SET", set_tr)]; \end{ttbox} \end{example} If you want to run the above examples in Isabelle, you should note that an $ML$-section needs to contain not just a definition of \verb$parse_translation$ but also of a variable \verb$print_translation$. The purpose of the latter is to reverse the effect of the former during printing; details are found in \S\ref{Print-translations}. Hence you need to include the line \begin{ttbox} val print_translation = []; \end{ttbox} This is instructive because the terms are then printed out in their internal form. For example the input \hbox{\tt[$x$,$y$,$z$]} is echoed as \hbox{\tt$x$:$y$:$z$:[]}. This helps to check that your parse translation is working correctly. %\begin{warn} %Explicit type constraints disappear with type checking but are still %visible to the parse translation functions. %\end{warn} \index{parse translation|)} \section{Printing} Syntax definitions provide printing information in three distinct ways: through \begin{itemize} \item the syntax of the language (as used for parsing), \item pretty printing information, and \item print translation functions. \end{itemize} The bare mixfix declarations enable Isabelle to print terms, but the result will not necessarily be pretty and may look different from what you expected. To produce a pleasing layout, you need to read the following sections. \subsection{Printing with mixfix declarations} Let {\tt$t =$ Const($c$,_)\$$t@1$\$\dots\$$t@n$} be a term and let \begin{ttbox} consts \(c\) :: \(\tau\) (\(sy\)) \end{ttbox} be a mixfix declaration where $sy$ is of the form $\alpha@0\_\alpha@1\dots\alpha@{n-1}\_\alpha@n$. Printing $t$ according to $sy$ means printing the string $\alpha@0\beta@1\alpha@1\ldots\alpha@{n-1}\beta@n\alpha@n$, where $\beta@i$ is the result of printing $t@i$. Note that the system does {\em not\/} insert blanks. They should be part of the mixfix syntax if they are required to separate tokens or achieve a certain layout. \subsection{Pretty printing} \label{PrettyPrinting} \index{pretty printing} In order to format the output, it is possible to embed pretty printing directives in mixfix annotations. These directives are ignored during parsing and affect only printing. The characters {\tt(}, {\tt)} and {\tt/} are interpreted as meta-characters\index{meta-character} when found in a mixfix annotation. Their meaning is \begin{description} \item[~{\tt(}~] Open a block. A sequence of digits following it is interpreted as the \bfindex{indentation} of this block. It causes the output to be indented by $n$ positions if a line break occurs within the block. If {\tt(} is not followed by a digit, the indentation defaults to $0$. \item[~{\tt)}~] Close a block. \item[~\ttindex{/}~] Allow a \bfindex{line break}. White space immediately following {\tt/} is not printed if the line is broken at this point. \end{description} \subsection{Print translations *} \label{Print-translations} \index{print translation|(} Since terms are translated after parsing (see \S\ref{Parse-translations}), there is a similar mechanism to translate them back before printing. Therefore the $ML$-section of a theory definition can associate constant names with user-defined translation functions by including a line \begin{ttbox} val print_translation = \dots \end{ttbox} where the right-hand side of this binding is again an \ML-expression of type \verb$(string * (term list -> term))list$. Including a pair $(c,f)$ in this list causes the printer to print $f[t@1,\dots,t@n]$ whenever it finds {\tt Const($c$,_)\$$t@1$\$\dots\$$t@n$}. \begin{example} Reversing the effect of the parse translation in Example~\ref{list-enum} is accomplished by the following function: \begin{ttbox} fun make_enum (Const("op :",_) $ e $ es) = case es of Const("[]",_) => Const("sing",dummyT) $ e | _ => Const("enum",dummyT) $ e $ make_enum es; \end{ttbox} It translates \hbox{\tt$x$:$y$:$z$:[]} to \hbox{\tt[$x$,$y$,$z$]}. However, if the input does not terminate with an empty list, e.g.\ \hbox{\tt$x$:$xs$}, \verb$make_enum$ raises exception {\tt Match}. This signals that the attempted translation has failed and the term should be printed as is. The connection with Isabelle's pretty printer is established as follows: \begin{ttbox} fun enum_tr'[x,xs] = Const("enum",dummyT) $ make_enum(Const("op :",dummyT)$x$xs); val print_translation = [("op :", enum_tr')]; \end{ttbox} This declaration causes the printer to print \hbox{\tt enum_tr'[$x$,$y$]} whenever it finds \verb!Const("op :",_)$!$x$\verb!$!$y$. \end{example} \begin{example} In Example~\ref{SET} we showed how to translate the concrete syntax for set comprehension into the proper internal form. The string {\tt"\{$x$ | $P$\}"} now becomes {\tt Const("Set",_)\$Abs("$x$",_,$p$)}. If, however, the latter term were printed without translating it back, it would result in {\tt"Set(\%$x$.$P$)"}. Therefore the abstraction has to be turned back into a term that matches the concrete mixfix syntax: \begin{ttbox} fun set_tr'[Abs(x,T,P)] = let val (x',P') = variant_abs(x,T,P) in Const("SET",dummyT) $ Free(x',T) $ P' end; \end{ttbox} The function \verb$variant_abs$, a basic term manipulation function, replaces the bound variable $x$ by a {\tt Free} variable $x'$ having a unique name. A term produced by {\tt set_tr'} can now be printed according to the concrete syntax defined in Example~\ref{SET} above. Notice that the application of {\tt set_tr'} fails if the second component of the argument is not an abstraction, but for example just a {\tt Free} variable. This is intentional because it signals to the caller that the translation is inapplicable. As a result {\tt Const("Set",_)\$Free("P",_)} prints as {\tt"Set(P)"}. The full theory extension, including concrete syntax and both translation functions, has the following form: \begin{ttbox} types set 1 arities set :: (term)term consts Set :: "('a => o) => 'a set" SET :: "[id,o] => 'a set" ("\{_ | _\}") end ML fun set_tr[Free(s,T), p] = \dots; val parse_translation = [("SET", set_tr)]; fun set_tr'[Abs(x,T,P)] = \dots; val print_translation = [("Set", set_tr')]; \end{ttbox} \end{example} As during the parse translation process, the types attached to constants during print translation are ignored. Thus {\tt Const("SET",dummyT)} in {\tt set_tr'} above is acceptable. The types of {\tt Free}s and {\tt Var}s however must be preserved because they may get printed (see {\tt show_types}). \index{print translation|)} \subsection{Printing a term} Let $tab$ be the set of all string-function pairs of print translations in the current syntax. Terms are printed recursively; print translations are applied top down: \begin{itemize} \item {\tt Free($x$,_)} is printed as $x$. \item {\tt Var(($x$,$i$),_)} is printed as $x$, if $i = 0$ and $x$ does not end with a digit, as $x$ followed by $i$, if $i \neq 0$ and $x$ does not end with a digit, and as {\tt $x$.$i$}, if $x$ ends with a digit. Thus the following cases can arise: \begin{center} {\tt\begin{tabular}{cccc} \verb$Var(("v",0),_)$ & \verb$Var(("v",7),_)$ & \verb$Var(("v5",0),_)$ \\ "?v" & "?v7" & "?v5.0" \end{tabular}} \end{center} \item {\tt Abs($x@1$,_,Abs($x@2$,_,\dots Abs($x@n$,_,$p$)\dots))}, where $p$ is not an abstraction, is printed as {\tt \%$y@1\dots y@n$.$P$}, where $P$ is the result of printing $p$, and the $x@i$ are replaced by $y@i$. The latter are (possibly new) unique names. \item {\tt Bound($i$)} is printed as {\tt B.$i$} \footnote{The occurrence of such ``loose'' bound variables indicates that either you are trying to print a subterm of an abstraction, or there is something wrong with your print translations.}. \item The application {\tt$t =$ Const($c$,_)\$$t@1$\$\dots\$$t@n$} (where $n$ may be $0$!) is printed as follows: If there is a pair $(c,f)$ in $tab$, print $f[t@1,\dots,t@n]$. If this application raises exception {\tt Match} or there is no pair $(c,f)$ in $tab$, let $sy$ be the mixfix annotation associated with $c$. If there is no such $sy$, or if $sy$ does not have exactly $n$ argument positions, $t$ is printed as an application; otherwise $t$ is printed according to $sy$. All other applications are printed as applications. \end{itemize} Printing a term {\tt $c$\$$t@1$\$\dots\$$t@n$} as an application means printing it as {\tt $s$($s@1$,\dots,$s@n$)}, where $s@i$ is the result of printing $t@i$. If $c$ is a {\tt Const}, $s$ is its first argument; otherwise $s$ is the result of printing $c$ as described above. \medskip The printer also inserts parentheses where they are necessary for reasons of precedence. \section{Identifiers, constants, and type inference *} \label{Typing} There is one final step in the translation from strings to terms that we have not covered yet. It explains how constants are distinguished from {\tt Free}s and how {\tt Free}s and {\tt Var}s are typed. Both issues arise because {\tt Free}s and {\tt Var}s are not declared. An identifier $f$ that does not appear as a delimiter in the concrete syntax can be either a free variable or a constant. Since the parser knows only about those constants which appear in mixfix annotations, it parses $f$ as {\tt Free("$f$",dummyT)}, where \ttindex{dummyT} is the predefined dummy {\tt typ}. Although the parser produces these very raw terms, most user interface level functions like {\tt goal} type terms according to the given theory, say $T$. In a first step, every occurrence of {\tt Free($f$,_)} or {\tt Const($f$,_)} is replaced by {\tt Const($f$,$\tau$)}, provided there is a constant $f$ of {\tt typ} $\tau$ in $T$. This means that identifiers are treated as {\tt Free}s iff they are not declared in the theory. The types of the remaining {\tt Free}s (and {\tt Var}s) are inferred as in \ML. Type constraints can be used to remove ambiguities. One peculiarity of the current type inference algorithm is that variables with the same name must have the same type, irrespective of whether they are schematic, free or bound. For example, take the first-order formula $f(x) = x \land (\forall f.~ f=f)$ where ${=} :: [\alpha{::}term,\alpha]\To o$ and $\forall :: (\alpha{::}term\To o)\To o$. The first conjunct forces $x::\alpha{::}term$ and $f::\alpha\To\alpha$, the second one $f::\beta{::}term$. Although the two $f$'s are distinct, they are required to have the same type. Unifying $\alpha\To\alpha$ and $\beta{::}term$ fails because, in first-order logic, function types are not in class $term$. \section{Putting it all together} Having discussed the individual building blocks of a logic definition, it remains to be shown how they fit together. In particular we need to say how an object-logic syntax is hooked up to the meta-logic. Since all theorems must conform to the syntax for $prop$ (see Figure~\ref{MetaLogicSyntax}), that syntax has to be extended with the object-level syntax. Assume that the syntax of your object-logic defines a category $o$ of formulae. These formulae can now appear in axioms and theorems wherever $prop$ does if you add the production \[ prop ~=~ form. \] More precisely, you need a coercion from formulae to propositions: \begin{ttbox} Base = Pure + types o 0 arities o :: logic consts Trueprop :: "o => prop" ("_" 5) end \end{ttbox} The constant {\tt Trueprop} (the name is arbitrary) acts as an invisible coercion function. Assuming this definition resides in a file {\tt base.thy}, you have to load it with the command {\tt use_thy"base"}. One of the simplest nontrivial logics is {\em minimal logic} of implication. Its definition in Isabelle needs no advanced features but illustrates the overall mechanism quite nicely: \begin{ttbox} Hilbert = Base + consts "-->" :: "[o,o] => o" (infixr 10) rules K "P --> Q --> P" S "(P --> Q --> R) --> (P --> Q) --> P --> R" MP "[| P --> Q; P |] ==> Q" end \end{ttbox} After loading this definition you can start to prove theorems in this logic: \begin{ttbox} goal Hilbert.thy "P --> P"; {\out Level 0} {\out P --> P} {\out 1. P --> P} by (resolve_tac [Hilbert.MP] 1); {\out Level 1} {\out P --> P} {\out 1. ?P --> P --> P} {\out 2. ?P} by (resolve_tac [Hilbert.MP] 1); {\out Level 2} {\out P --> P} {\out 1. ?P1 --> ?P --> P --> P} {\out 2. ?P1} {\out 3. ?P} by (resolve_tac [Hilbert.S] 1); {\out Level 3} {\out P --> P} {\out 1. P --> ?Q2 --> P} {\out 2. P --> ?Q2} by (resolve_tac [Hilbert.K] 1); {\out Level 4} {\out P --> P} {\out 1. P --> ?Q2} by (resolve_tac [Hilbert.K] 1); {\out Level 5} {\out P --> P} {\out No subgoals!} \end{ttbox} As you can see, this Hilbert-style formulation of minimal logic is easy to define but difficult to use. The following natural deduction formulation is far preferable: \begin{ttbox} MinI = Base + consts "-->" :: "[o,o] => o" (infixr 10) rules impI "(P ==> Q) ==> P --> Q" impE "[| P --> Q; P |] ==> Q" end \end{ttbox} Note, however, that although the two systems are equivalent, this fact cannot be proved within Isabelle: {\tt S} and {\tt K} can be derived in \verb$MinI$ (exercise!), but {\tt impI} cannot be derived in \verb!Hilbert!. The reason is that {\tt impI} is only an {\em admissible} rule in \verb!Hilbert!, something that can only be shown by induction over all possible proofs in \verb!Hilbert!. It is a very simple matter to extend minimal logic with falsity: \begin{ttbox} MinIF = MinI + consts False :: "o" rules FalseE "False ==> P" end \end{ttbox} On the other hand, we may wish to introduce conjunction only: \begin{ttbox} MinC = Base + consts "&" :: "[o,o] => o" (infixr 30) rules conjI "[| P; Q |] ==> P & Q" conjE1 "P & Q ==> P" conjE2 "P & Q ==> Q" end \end{ttbox} And if we want to have all three connectives together, we define: \begin{ttbox} MinIFC = MinIF + MinC \end{ttbox} Now we can prove mixed theorems like \begin{ttbox} goal MinIFC.thy "P & False --> Q"; by (resolve_tac [MinI.impI] 1); by (dresolve_tac [MinC.conjE2] 1); by (eresolve_tac [MinIF.FalseE] 1); \end{ttbox} Try this as an exercise!