University of Edinburgh

Department of Computer Science

Proving Theorems about

Algebraic Specifications

MSc Project Report

Donald Baillie

September 14, 1999

Abstract

The recently developed algebraic specification language CASL is intended to be a standard in the field. This project is concerned with providing theorem proving support for CASL by writing a tool to translate CASL basic specifications for use with the commercially and academically proven verification system PVS.

Acknowledgements

Above all I wish to thank my supervisor, Don Sannella, for proposing an interesting and challenging project, for unfailing guidance in carrying it out and continuous reassurance in the face of the author’s increasing panic.

I would also like to thank Paul Jackson for advice on PVS, Till Mossakowski and the University of Bremen CoFI group for their parser and advice on using it and Mark van den Brand and Pieter Olivier for advice on the Java ATerm library.

Finally I would like to thank my father for the use of his study and his PC (on which this was written) and the University of Edinburgh Division of Informatics for all the other facilities used in this project and for accepting me to do the MSc. course in the first place.

Contents

6. Conclusion and Further Work

Introduction

In algebraic specification a program is modelled as a description of sets of data values (called sorts or types) and functions over these collections. The behaviour of a program is then described by adding axioms constraining the properties of the functions.

Of the many algebraic specification languages available none has achieved wide use. So, since 1995 CoFI, the Common Framework Initiative, which is an international collaboration aiming to develop a standard for algebraic specification, has been developing CASL, the Common Algebraic Specification Language, which, it is hoped, will become a standard for algebraic specification since it incorporates the best features of a range of other such languages [San99].

There already exist a wide variety of tools, parsers, type checkers, provers, etc for other specification languages so it is proposed in [BKO98] that pre-existing tools should be adapted for use with CASL rather than being written specially. So the object of this project is to provide theorem proving support for the core CASL language by developing a tool that converts CASL specifications for use with the PVS theorem prover.

PVS (Prototype Verification System), developed by SRI International, is a specification language fully integrated with a parser, a type-checker and a powerful theorem prover. It has been designed to be easy to use and has been widely used in industry as well as academia. The specification language uses a simply typed higher-order logic that only admits total functions, but which includes a powerful subtyping mechanism [OSRS98a].

The goal of the project was to write a tool that translated CASL specifications into PVS theories. Since PVS only admits total functions I started by restricting the specifications considered by excluding those with partial functions. Further restrictions proved necessary because of the different ways CASL and PVS dealt with subtyping. The tool was firstly to deal with basic specifications and then move on to the more complicated, structured specifications. As it turned out I was able to translate almost all the constructs in basic specifications, but shortage of time prevented me moving on to structured specifications. The implementation I have produced is a prototype that builds on PVS and a pre-existing CASL parser, but does not integrate these components into one system.

Previously in this area work has been carried out to support theorem proving in CASL by embedding it in the Isabelle theorem prover [MKKB98]. Also the inka 5.0 system, which is a theorem prover with a specification language, has been designed to include a subset of CASL within its language [AHMS99].

Following chapters will describe CASL and then PVS. After that I will describe the details of my translation from CASL basic specifications to PVS, then the implementation itself, with examples. Finally I will sum up my work and consider how it could be further developed.

CASL

CASL comes in four parts: basic specifications, which contain simple declarations, definitions and axioms; structured specifications, for combining basic specifications; architectural specifications, for specifying the modular structure of the specified system and libraries of reusable specifications. A full description of CASL can be found in [COFI98], with introductions in [Mos99] and [San99].

Basic specifications, which I will be considering in this project, consist of a signature and sentences, or axioms, constraining the properties of the elements of the signature.

2.1.1 Signatures

A signature determines the sorts, operations and predicates of a specification. Simple sorts may be declared. Subsorts may be simply declared, e.g.

s<t

declares s as a subsort of t. Or subsets may be defined as follows :-

s={x:t . F}

which declares s as a subsort of t consisting of those values of t that satisfy the formula F. The subtyping relation between sorts is modelled by embeddings of subsorts into supersorts rather than set inclusion as in some other frameworks [CHKBM97]. This greater flexibility means that subsorts need not be assumed to have the same data representation as their supersorts, e.g. in integers and floating point decimals, which are represented differently for efficiency reasons, though the former may be taken as a subsort of the other.

Operations may be declared total or partial, e.g.

ops f:sà t; g:sà ?t

declares f as total from s into t and g as partial from s into t. Whether some term involving partial functions is defined can be asserted in a definedness formula. When a binary operation is declared it can be additionally declared to have any number of four standard attributes: commutativity, associativity, idempotency, or possession of a specified identity element.

A datatype declaration declares a sort with constructors and optional selectors (both may be total or partial) and determines sentences on these. E.g.:

type list::=empty|cons(hd:?s;tl:?list)

determines a type of lists with elements belonging to s with two constructors and two partial selectors, hd and tl. A datatype declaration, as above, also asserts that the selectors give the expected values, e.g. that

hd(cons(h,t))=h,

and

tl(cons(h,t))=t

for all lists t and elements of s h.

If a datatype is declared free then this also determines that the constructors are injective, that the ranges of the constructors are disjoint and cover the entire set and that the value of applying the selectors to values of constructors for which they have not been declared is always undefined. It is also possible to use a ‘subsort alternative’ to build a datatype from previously declared sorts without constructors and in free datatypes these sorts will have no common subsorts. Within a list of datatypes the order is not significant. This non-linear visibility allows the declaration of mutually recursive datatypes.

A sort generation constraint may be placed on a list of signature items indicating that the sorts declared are generated by the operations declared. I.e. there are no members of the sorts not in the range of some operation within the constraint, so an induction principle may be inferred.

2.1.2 Identifiers

In CASL operations may be declared using ‘mixfix’ notation, a system that generalises infix, postfix and prefix notation by allowing placeholders and tokens to be mixed together in the declaration of an operation, e.g.:

op __+__: nat*natà nat

declares the binary operation ‘+’. Application of this operation to variables x and y may be written ‘x+y’, +(x,y) or ‘__+__(x,y)’. ‘Invisible’ identifiers consisting only of placeholders are permitted.

2.1.3 Sentences and Models

The sentences are written in first order logic with two kinds of equality for dealing with possibly undefined values. Given terms T and T', existential equality between them requires that T and T' are both defined and their values are equal. Strong equality requires that if T is defined then T' is defined and they have equal values and if T is not defined then T’ is not defined.

A model of the specification is an interpretation of the sorts, operations and predicates declared for which the sentences hold.

Structured specifications may build on basic specifications. Specifications may be named and made generic, so they can be instantiated with different parameters. Specifications may be joined together in unions. Specifications can be restricted by hiding elements (sorts, operations or predicates) or extended by adding elements. The models of a structured specification are the same as those of a basic specification. I.e. the structure of a specification is not reflected in its models, but is just meant to allow the presentation of a large specification to be broken into smaller pieces.

An architectural specification specifies the structure of a program. It gives a series of unit modules, which may be specified by basic or structured specifications, and a ‘unit term’, which indicates how the modules should fit together [BST99].

Finally libraries store collections of named specifications, either locally or in distributed libraries.

Each of the above parts of CASL is orthogonal to the others, which means they are independent and can be adapted without affecting the other parts [Mos99]. This means that CASL basic specifications may be easily restricted to sublanguages or extended to deal with specialised areas like concurrent systems without the other parts of CASL changing [San99].

PVS

PVS (The Prototype Verification System) is a specification language smoothly integrated with a powerful theorem prover. It is implemented in LISP and works with a classical, higher order logic with total functions and a sophisticated, though undecidable, type system. The PVS system, language and prover are fully described in [OSRSC98a], [OSRSC98b] and [SORSC98].

A PVS specification is a collection of named theories. A theory consists of a signature specifying types and operations, axioms constraining these and theorems to be proved about them. It is possible for theories to import other theories and the theories to be imported can specify which elements of them are to be imported, so data hiding is possible. Theories can also be parameterised in specified types or values. Theories within a current directory form a context. For every theory PVS generates a proof file for storing proofs of theorems and for the context PVS generates a ‘.pvscontext file, which keeps track of which theorems have been proved.

3.1.1 Types

All PVS theories implicitly include a built-in prelude, which includes the types ‘real’, ‘rational’, ‘integer’ and ‘naturalnumber’. New types can then be created by declaring new uninterpreted types (which may be empty unless specified non-empty) or using type constructors to create subtypes, function types, tuple types or record types. Subtypes may be declared using predicates, e.g.

nonzero: TYPE = {n:nat | n/=0}

So subtypes are interpreted simply as subsets of their supertypes.

Function, tuple and record types may be dependent in that some of the component types may depend on previous components.

The complexity of the type system means that it is in general undecidable. When a theory is typechecked PVS will typically produce type-correctness conditions (tccs). These are proof obligations that the user must prove before the theory is properly typechecked. For instance in the following theory extract:

T,T1: TYPE

S: TYPE FROM T

x: T

op: Sà T1

Ax1: AXIOM F(op(x))

where F is a formula containing the function op applied to x, PVS will produce the following tcc.

Ax1_TCC1: OBLIGATION S_pred(x)

where S_pred is a predicate, automatically generated by PVS, such that

S={t: T | S_pred(t)}.

3.1.2 Datatypes

A special kind of type in PVS is the user defined abstract datatype. If a datatype is recursively defined it must be declared in a separate ‘Datatype’ theory, which may be parameterised. Datatypes are types declared with constructors, accessors and recognisers. E.g. a stack with elements of type t may be declared as follows:

stack [t: TYPE]: DATATYPE

BEGIN

empty: empty?

push (top: t, pop: stack): nonempty?

END stack

This declares a type of stacks containing elements of some type t, constructors empty and push, accessors top and pop, and empty? and nonempty? as predicates for recognising which constructor a stack is constructed from. When this is typechecked PVS creates a theory called stack_adt containing all the relevant declarations, axioms asserting that two stacks with the same components (e.g. given by top and pop) are the same; that top and pop have the correct behaviour with respect to push; that empty? and nonempty? do not overlap and that the type of stacks is the union of empty? and nonempty?. Stack_adt then defines a large number of useful functions on stacks, including various ‘reduce’ functions, for use in creating measures for recursive functions (see below) along with an order on stacks and an axiom asserting that this order is well-founded. Stack_adt is included in full as an appendix.

3.1.3 Constants and Formulae

‘Constants’ refers to functions and relations as well as 0-ary constants and these can be declared with or without giving a definition. If functions are recursive they must be declared as such with the keyword "RECURSIVE" and given a ‘measure’ function and an order relation (which defaults to the usual order on natural numbers or ordinals up to epsilon-0) to guarantee they terminate. When this is typechecked a type-correctness condition is generated requiring the user to prove that the measure strictly decreases with respect to the given order with every step in the recursion. E.g. in PVS one can define the factorial functions as follows [OSRSC98b]:

fact(x:nat):RECURSIVE nat = if x=0 THEN 1 ELSE x*fact(x-1)

MEASURE (LAMBDA (x:nat): x)

When typechecked this produces the following tcc:

FORALL (x:nat): NOT x=0 IMPLIES x>x-1

Formulae are either axioms asserting properties of the specified constants or they are theorems giving desired properties to be proved.

The PVS prover is a powerful, interactive proof checker that has commands to carry out propositional, quantificational and equality reasoning easily. Proof commands can be combined to form strategies and new strategies can be written by the user. Proofs are stored and can be rerun or edited.

Translation of CASL to PVS

The biggest difference between CASL basic specifications and PVS theories is that in CASL operations may be declared total or partial, while in PVS only total functions are permitted. Trying to reason about total functions is a lot easier than reasoning about partial functions, so a restriction to partial functions has practical benefits. In PVS partiality can be avoided by considering a subtype of the domain, e.g. division is partial on the real numbers, but total on the non-zero real numbers. Or one can think of a partial function f:Dà R as a total function f:Dà R' where R' is R with an extra element to denote 'undefined'. However, it was felt that to start off with, partial functions should be excluded from consideration. The exception to this rule is selectors or accessors of datatypes, which in most cases have to be partial over a datatype, e.g. in stacks, top and pop, which are only defined on non-empty stacks. To handle this subtypes of stack are introduced to turn top and pop into total functions.

The input into the program is an abstract representation of a basic specification, consisting of a list of ‘basic items’ which add to the signature, determine sentences or declare variables.

Most of the constructs in basic CASL specifications can be mapped directly onto PVS constructs. However, there are some subtle differences, which I shall deal with in turn.

Declarations that add to the signature are sorts, operations, predicates or datatypes. As a general rule in CASL items may be declared more than once, e.g. a sort may be declared as the subsort of more than one other sort. In PVS all items must be declared only once and additional information about items should be declared as axioms.

4.1.1 Sorts

Sort declarations may be of uninterpreted sorts or subsorts, subsort definitions or isomorphism declarations. Sort declarations are exactly identical to declarations of uninterpreted types in PVS, but subsort declarations are subtly different. In PVS subtyping is modelled as simple set inclusion, while in CASL it is modelled as an embedding of the subsort into its supersort. As a result of this the subsort relationship can be much more complicated in CASL than in PVS. A sort can be a subsort of two non-overlapping supersorts, which is definitely not possible in PVS. Because of this the set of CASL specifications considered had to be restricted. In PVS it is not possible to declare a type as a subtype more than once to two different supertypes, although it is possible for a defined subtype to have the subtyping relationship to several non-equal supertypes by virtue of its definition. E.g.

O: TYPE = {x:int | odd(x)}

P: TYPE = {x:int | prime(x) }

OP: TYPE = {x:O | prime(x),

where odd and prime are predicates on the integers. Here O and P are both defined as subtypes of the integers and OP is defined as a subtype of O, but is also a subtype of P by definition. If a function is applied to an element x of OP when it expects an element of P PVS generates a tcc (type-correctness condition) asking the user to prove that x is also of type P.

It is noted in [CHKBM97] that it is possible to pass from a model with embedded subsorts to an equivalent one with subsorts modelled as set inclusions, provided the subsorting relationship is "locally upward filtered". This means that any two sorts in the same locally connected part of the relation have a common supersort [GM92]. However, in view of the above a stronger condition must be placed on CASL specifications to be translated to PVS, namely that no subsort can be declared more than once to different supersorts.

PVS types may be empty, but can be declared to be non-empty. In CASL all sorts are non-empty, so in translation to PVS all sorts are converted to non-empty types.

An isomorphism declaration declares a series of distinct sorts, all subsorts of each other, implying they all have isomorphic interpretations. In translation to PVS one of these is declared as an uninterpreted type and the rest defined as equal to the first. This is okay provided none of these is then declared to be a subsort of some other sort without the others being so as well. This accords with the principle above of not declaring items more than once.

4.1.2 Datatypes

CASL datatypes concisely define a sort along with constructor and optional accessor functions. E.g:

sort s

type stack::=empty|push(top:?s;pop:?stack)

declares stacks with elements containing of s with empty and push as constructors and pop and stack as partial accessors and also declares sentences ensuring that the selectors behave appropriately with respect to the constructors. E.g. from the example above we have,

forall x:s,t:stack . top(push(x,s))=x

and

forall x:s,t:stack . pop(push(x,s))=s

In datatypes selectors are usually partial. In translation the type is declared. Then one subtype is declared for each constructor corresponding to the set of values produced by the constructor. Then the constructors and the selectors are declared and finally the appropriate sentences declared as axioms. E.g. the stack above translates to:

s: TYPE+

stack: TYPE+

stackcons1?: TYPE+ FROM stack

empty: stackcons1?

stackcons2?: TYPE+ FROM stack

top: [stackcons2?à s]

pop: [stackcons2?à stack]

push: [s,stackà stackcons2?]

stack_top_push: AXIOM (FORALL (x1:s,x2:stack): top(push(x1,x2))=x1)

stack_pop_push: AXIOM (FORALL (x1:s,x2:stack): pop(push(x1,x2))=x2)

In the above ‘TYPE+’ indicates that types must be non-empty. stackcons1? and stackcons2? are subtypes of stack and are required to make the selectors push and pop total. The subsort alternative of datatypes is not supported because it declares new information about sorts already declared in the local environment. However, this could conceivably be handled by explicitly declaring injection functions to embed the subsorts into the type.

If a selector is declared total, and so declared for each alternative, this is equivalent to declaring overloaded selectors, each with a domain corresponding to a different subtype of the datatype. So, partial and total selectors are translated equivalently.

4.1.3 Free Datatypes

Datatypes declared as free determine the same sorts, operations and axioms as non-free datatypes and additional axioms further constraining the properties of the datatype. In fact CASL free datatypes are very similar to the PVS datatype construct so the translation makes direct use of this to translate PVS datatypes. E.g.

sort s

free type stack::=empty|push(top:?s; pop:?stack)

is translated in the main theory to

s: TYPE+

IMPORTING stack_adt[s]

Then an additional file is created called stack.pvs, which contains the following:

stack[s: TYPE]: DATATYPE

BEGIN

empty:stackcons1?

push(top:s,pop:stack):stackcons2?

END stack

When this is typechecked the file ‘stack_adt.pvs’ is created by PVS as described in chapter 3 and the contents of this are imported into the main theory. In PVS accessors (selectors in CASL) must be declared for every argument of every constructor so if some selectors are absent in the CASL specification they are automatically declared when the specification is translated. Here stackcons1? and stackcons2?, called recognisers in PVS are predicates, defining subtypes of stack implicitly. Again the subsort alternative is not handled because it does not fit in with PVS datatypes.

Additionally, for each datatype a ‘measure’ function is constructed to deal with recursive operations, as described below.

Again, partial and total selectors are translated identically, as described above.

In CASL a list of datatypes, free or non-free, can have non-linear visibility, allowing for mutually recursive datatype definitions. In PVS all visibility is strictly linear, so mutually recursive datatypes are not explicitly supported, although [OSRSC98b] suggests how this can be achieved in most typical cases by defining the datatypes together as one datatype with ‘subdatatypes’. The translation could possibly be adapted to achieve this.

4.1.4 Operations and Predicates

Declarations and definitions of operations are almost exactly similar in PVS and CASL. The exception is recursive operations. In CASL recursive operations need not be declared as such, but in PVS they must be declared, using the keyword ‘RECURSIVE’. Then a ‘measure’ function must be given to ensure the function terminates and hence is total. A measure function must have the same profile as the recursive function, but with its range of the same type as the domain of the order. CASL has no built in types, so well-founded recursive functions should only be defined over recursively defined datatpes, described above. E.g. in CASL

free type nnat::=zero|suc(nnat)

op plus(n:nnat; m:nnat):nnat = m when n=zero else suc(plus(pre(n), m));

is translated into PVS as follows:

IMPORTING nnat_adt

plus (n:nnat, m:nnat): RECURSIVE nnat = IF (n=zero) THEN m

ELSE suc(plus(pre(n),m)) ENDIF

MEASURE (LAMBDA (x1:nnat,x2:nnat):

reduce_nat (0,LAMBDA (x1:nnat):x1+1)(x1) +

reduce_nat (0,LAMBDA (x1:nnat):x1+1)(x2)+0)

reduce_nat, of type [nat,[nnatà nat]à [nnatà nat], is a function produced by PVS in the nnat_adt.pvs file created by PVS when nnat.pvs is typechecked. As is seen in the example, it gives a function from the datatype concerned to the built-in type of natural numbers (not to be confused in the example with the generated type nnat) which can then be used for generating measures. The term ‘reduce_nat (0,LAMBDA (x1:nnat):x1+1)(x1)’ is generated and stored by the translation when the nnat datatype is translated, so when any recursive function is translated the appropriate measure is generated by adding together the measures associated with each datatype in the arguments of the function. This will no doubt prove clumsy in specifications with many recursive datatypes and recursive functions defined on them and an obvious improvement is to declare e.g. a ‘stackmeas’ function with the datatype, to be used for any measure requiring it.

When typechecked the above example produces two type-correctness conditions, one to check that the accessor ‘pre’ is being applied correctly and one to ensure that the measure guarantees termination. They are as follows:

plus_TCC1: OBLIGATION (FORALL (n: nnat): NOT (n = zero)

IMPLIES nnatcons2?(n))

i.e. if n is not zero it must have been produced by the second constructor, suc,

plus_TCC2: OBLIGATION

(FORALL (n: nnat): NOT (n = zero) IMPLIES

reduce_nat (0, LAMBDA (x1: nat): x1 + 1)(pre(n))

+ reduce_nat (0, LAMBDA (x2: nat): x2+1)(m) + 0

< reduce_nat (0, LAMBDA (x1: nat): x1 + 1)(n)

+ reduce_nat (0, LAMBDA (x2: nat): x2 + 1)(m) + 0

i.e. that the measure strictly decreases for every step of the recursion.

The measure produced in translation will work for most simple examples but there are occasions when a more complicated measure will be required. E.g. the ackerman function on natural numbers, which can be written in CASL as

op ack(m:nnat; n:nnat):nnat = suc(n) when m = zero else

(ack (pre(m), suc(zero)) when n = zero else

ack (pre(m), ack(m, pre(n))))

is translated to PVS as

ack (m:nnat, n:nnat): RECURSIVE nnat = IF (m=zero) THEN suc(n) ELSE

IF (n=zero) THEN ack(pre(m), suc(zero)) ELSE

ack(pre(m), ack(m,pre(n))) ENDIF ENDIF

MEASURE (LAMBDA (x1:nnat, x2:nnat):

reduce_nat (0, LAMBDA (x1:nat):x1+1)(x1) +

reduce_nat (0, LAMBDA (x1:nat):x1+1)(x2)+0)

and this produces (amongst others) the following type-correctness condition:

ack_TCC6: OBLIGATION (FORALL (m: nnat, n: nnat):

NOT (m = zero) AND (n = zero) IMPLIES

reduce_nat(0, LAMBDA (x1: nat): x1+1)(pre(m)) +

reduce_nat (0, LAMBDA (x2: nat): x2+1)(suc(zero))+0

< reduce_nat (0, LAMBDA (x1: nat): x1+1)(m)

+ reduce_nat (0, LAMBDA (x2: nat): x2+1)(n)+0

which is not true.

In such cases the user must supply an appropriate measure himself.

A CASL binary operation may be declared to have any of four ‘attributes’, associativity, commutativity, idempotency or possession of an identity element, and in translation to PVS these are explicitly written out as axioms. E.g. in CASL:

sort S

op z:S

op f:S*Sà S, assoc, comm, idem, unit z

will be translated in PVS as:

S: TYPE

z:S

f:S*Sà S

Ax0: AXIOM (FORALL (x,y,z:S): f(x,f(y,z)=f(f(x,y),z)

Ax1: AXIOM (FORALL (x,y:S): f(x,y)=f(y,x)

Ax2: AXIOM (FORALL (x:S): f(x,x)=x

Ax3: AXIOM (FORALL (x:S): f(z,x)=x /\ f(x,z)=x

Predicates are translated into functions onto PVS’s built-in boolean type. If they are recursive they need to be declared with the keword ‘INDUCTIVE’, e.g. on stacks

pred contains (x:s; a:stack) ó (not a = empty) /\

(top(a)=x) \/ contains (x, pop(a))

translates to

contains(x:s, a:stack): INDUCTIVE bool = (NOT a = empty) AND

(top(a)=x) OR contains (x, pop(a)))

When this is typechecked PVS automatically produces a pair of implicit induction principles to assist in proving theorems about it. These are as follows:

contains_weak_induction: AXIOM

(FORALL (x:s, P:[stack[s]à boolean]):

(FORALL (a: stack):

(NOT (a = empty) AND ((top(a) = x) OR P (pop(a))))

IMPLIES P(a))

IMPLIES (FORALL (a: stack): contains(x, a) IMPLIES P(a)))

contains_induction: AXIOM

(FORALL (x:s, P: [stack[s]à boolean]):

(FORALL (a: stack):

(NOT (a=empty) AND ((top(a)=x) OR contains (x,pop(a)) AND

P(pop(a)))) IMPLIES P(a))

IMPLIES (FORALL (a: stack): contains(x,a) IMPLIES P(a)));

In testing for recursion of operations or inductions I only test for recurrence of the identifier in the definition. So here overloading is not supported.

In addition basic items can declare variables or axioms. CASL can declare local variable axioms, in which the declared variables are local to specific axioms. This is semantically equivalent to universally quantifying these variables for each individual axiom and this is what happens in translation.

The first order logic of CASL is directly translatable into PVS with certain exceptions. CASL has existential equality and strong equality to deal with partial functions, but when all functions are total, these notions of equality are identical, so no distinction is made in the translation. CASL includes a ‘membership’ predicate asserting that a term belongs to some sort. This proved clumsy to translate to PVS and has been excluded. CASL includes an ‘exists uniquely’ quantifier, which PVS doesn’t. So it must be written out in full using a lambda term to affect substitution. E.g. a CASL formula

exists! x:s . F(x)

where the formula F contains x, is translated into PVS as:

EXIST(x:s): F(x) AND FORALL (x1):

(LAMBDA (x): F(x)) (x1) IMPLIES (x=x1)

CASL sorted terms, in which an expression is given with its sort, are fully translatable into PVS as coercion expressions.

There are as yet no constructs for declaring theorems in CASL. The ad-hoc solution to this that was constructed was to declare theorems in CASL as axioms starting ‘forall f:THEOREM . F’ for a formula F. This is translated into PVS as a theorem declaration.

In PVS axioms and theorems require unique identifiers and these are supplied automatically.

Mixfix notation is not supported by PVS. So the translation of a mixfix identifier will omit the place-markers and run the tokens together. Invisible identifiers, consististing solely of invisible placeholders are not supported.

Chapter 5

When using tools to manipulate or store CASL specifications an intermediate form of representation for specifications is required to allow easy transfer of specifications between the tools and it has been agreed [BKO98] to use the Annotated Term Format or ATerm. The ATerm datatype was developed as a means of exchanging and storing complex, possibly tree-like data structures such as syntax trees in a way that is software independent, easy to use and efficient [BJKO99]. ATerms may be transferred or stored in an efficient ‘Binary ATerm Format’ or as a human-readable text file or string. A library of functions for creating and manipulating ATerms has been implemented in C and Java1.2 and the Java ATerm Library was chosen for this project because of Java’s ease of use.

It was decided to make use of the HOL-CASL parser, developed by the Bremen CoFI group for their HOL-CASL project to embed CASL in the theorem prover Isabelle [MKKB98]. This performs parsing and static semantic checking on concrete CASL specifications, converting them into an abstract syntax tree in ATerm format based on an adapted version of the abbreviated abstract syntax of CASL in [CoFI98]. It also produces a global environment, listing the sorts, operations and predicates declared in a specification.

My implementation operates on the ATerm syntax tree to produce a file containing a PVS theory and further files for every datatype.

The Bremen Parser only deals with structured specifications. So the implementation first takes a ‘specification definition’ consisting of a name followed by a basic specification (but no genericity, as the program does not deal with parameterised specifications) and converts it into a PVS theory of the same name.

So given a CASL named specification as follows, saved as ‘example.casl’:

spec example =

sort s

free type nnat::=zero|suc(pre:?nnat);

ops plus (n:nnat;m:nnat): nnat = m when n=zero else suc(plus(pre(n), m));

axiom forall f: THEOREM . forall x,y,z: nnat . plus (x,plus(y,z)) = plus (plus (x,y),z)

free type llist::=empty|lcons(hd:?s;tl:?llist)

ops append (l:llist;m:llist):llist = m when l = empty else

lcons (hd(l), append(tl(l),m));

len (l:llist): nnat = zero when l = empty else suc(len(tl(l)))

axiom forall f: THEOREM . forall l,m:llist . len (append (l,m)) = plus (len(l), len(m))

the parser will convert this to a textfile named ‘example.aterm’ and when my program is run it will take this file and produce three new files, ‘example.pvs’ for the main specification along with ‘nnat.pvs’ and ‘llist.pvs’ for the datatypes. example.pvs looks as follows:

example: THEORY

BEGIN

s:TYPE+

IMPORTING nnat_adt

plus (n:nnat, m:nnat): RECURSIVE nnat=IF (n=zero) THEN m ELSE suc( plus (pre (n),m)) ENDIF

MEASURE (LAMBDA (x1:nnat,x2:nnat): reduce_nat (0,LAMBDA(x1:nat):x1+1)(x1)+reduce_nat(0, LAMBDA (x2:nat):x2+1)(x2)+0)

Theorem0: THEOREM FORALL (x,y,z:nnat): (plus(x,plus(y,z)=plus(plus(x,y),z))

IMPORTING llist_adt[s]

append (l:llist,m:llist): RECURSIVE llist=IF (l=empty) THEN m ELSE lcons (hd(l),append(tl(l),m)) ENDIF

MEASURE (LAMBDA (x1:llist,x2:llist): reduce_nat(0,LAMBDA(x1:s,x2:nat):x2+1)(x1)+reduce_nat (0,LAMBDA(x1:s,x2:nat):x2+1)(x2)+0)

len (l:llist):RECURSIVE nnat=IF (l=empty) THEN zero ELSE suc(len(tl(l))) ENDIF

MEASURE (LAMBDA (x1:llist): reduce_nat (0,LAMBDA (x1:s,x2:nat):x2+1)(x1)+0)

Theorem1: THEOREM FORALL (l,m:llist): (len(append(l,m))=plus(len(l),len(m)))

END example

And nnat.pvs and llist.pvs are as follows

nnat: DATATYPE

BEGIN

zero:nnatcons1?

suc(pre:nnat):nnatcons2?

END nnat

llist [s:TYPE]: DATATYPE

BEGIN

empty: llistcons1?

lcons(hd:s,tl:llist):llist cons2?

END llist

When PVS is started and all these files are typechecked seven type-correctness conditions are generated, of which two are subsumed immediately and the other five are simple enough to be discharged automatically, and the theories nnat_adt and llist_adt are generated. After doing this the PVS prover may be invoked to prove the theorems.

Conclusion and Further Work

The object of this project was to write a tool which translated CASL specifications into PVS. What I have achieved is a program which handles most of the constructs in CASL basic specifications while restricting attention to specifications with only total functions and a simple subsort relation, as described in the report.

Possible further work would involve expanding the implementation to deal with more CASL constructs and/or loosening the restrictions.

The main basic construct I did not manage to deal with is the sort generation constraint, which takes a collection of signature items and determines sentences declaring that the sort items declared are generated by the operations declared (with the exception of datatype selectors). This is the most difficult basic construct to handle because it requires the ATerm tree to be analysed to a greater depth than the other constructs and the pressure of time did not allow me to attempt this.

The constructs of structured CASL specifications are mainly concerned with combining basic specifications, adding elements, hiding elements forming a union of specifications etc, and achieving this would involve making use of PVS’s ‘IMPORTING’ construct, which allows PVS theories to be combined, with data hiding if desired.

The restriction on partial functions could be loosened in one of two different ways. Given a partial function f with domain D and range R it can be made total either by considering f to be total over a subtype of D or by adding an extra element to R to denote ‘undefined’.

Finally, the restriction on the subsorting relation might be relaxed if for each CASL subsort declaration the translation explicitly declared an embedding function from the subsort to the supersort, instead of using PVS’s subtypes.

[AHMS99] Serge Autexier, Dieter Hutter, Heiko Mantel and Axel Schairer. Inka 5.0 – a logical voyager. In H. Ganziger, editor, Proceedings 16th International Conference on Automated Deduction, CADE-16. LNAI 1632, 207-211, 1999.

[BJKO99] Mark G.J. van den Brand, H.A. de Jong, Paul Klint and Pieter Olivier. Efficient Annotated Terms. Technical report, CWI, 1999. http://.wins.uva.nl/pub/programming-research/software/aterm/

[BKO98] Mark van den Brand, Paul Kling and Pieter Olivier. Aterms: exchanging data between heterogeneous tools for CASL. CoFI Note T-3, http://www.brics.dk/Projects/CoFI/Notes/T-3/ 1998.

[BST99] Michel Bidoit, Donald Sannella and Andrzej Tarlecki. Architectural specifications in CASL. Report ECS-LFCS-99-407, University of Edinburgh, April 1999.

[CHKBM97] Maura Cerioli, Anne Haxthausen, Bernd Krieg-Bruckner and Till Mossakowski. Permissive Subsorted Partial Logic in CASL, In: Michael Johnson (ed.) Proc. 6th Intl. Conference on Algebraic Methodology and Software Technology (AMAST’97), volume 1349 of LNCS, pages 91-107. Springer, 1997.

[CoFI98] CoFI. Language Design Task Group. CASL – The CoFI Algebraic Specification Language – Summary (version 1.0).

http://www.brics.dk/Projects/CoFI/Documents/CAS:/Summary/ 1998

[GM92] J.Goguen and J.Meseguer. Order-sorted algebra I: equational deduction for multiple inheritance, overloading, exceptions and partial operations. Theoretical Computer Science, 105:217-273, 1992

[MKKB98] Till Mossakowski, Kolyang and Bernd Krieg-Bruckner. Static semantic analysis and theorem proving for CASL. In 12th Workshop on Algebraic Development Techniques, Tarquinia, volume 1376 of LNCS, pages 333-348. Springer-Verlag, 1998.

[Mos99] P. Mosses. CASL: a guided tour of its design. Recent Trends in Algebraic specification and development. Proc. 7th Intl. Joint Conf. on Theory and Practice of Software Development, Lille. Springer LNCS 1589, 1999.

[OSRSC98a] S. Owre, N. Shankar, J. M. Rushby and D. W. J. Stringer-Calvert. PVS System Guide. Computer Science Laboratory, SRI International, Menlo Park, CA, September 1998.

[OSRSC98b] S. Owre, N. Shankar, J. M. Rushby and D. W. J. Stringer-Calvert. PVS Language Reference. Computer Science Laboratory, SRI International, Menlo Park, CA, September 1998.

[San99] Donald Sannella. The Common Framework Initiative for algebraic specification and development of software. In Proc. 3rd Intl. Conf. on Perspectives of System Informatics, Lecture Notes in Computer Science. Springer, 1999.

[SORSC98] N. Shankar, S. Owre, J. M. Rushby and D. W. J. Stringer-Calvert. PVS Prover Guide. Computer Science Laboratory, SRI International, Menlo Park, CA, September 1998.

An Adt Example

I include here a listing, in full, of stack_adt.pvs, automatically generated by PVS when stack.pvs, described in Chapter 3, is typechecked.

%%% ADT file generated from stack

stack_adt[s: TYPE]: THEORY

BEGIN

stack: TYPE

stackcons1?, stackcons2?: [stack -> boolean]

empty: (stackcons1?)

push: [[s, stack] -> (stackcons2?)]

top: [(stackcons2?) -> s]

pop: [(stackcons2?) -> stack]

ord(x: stack): upto(1) =

CASES x OF

empty: 0,

push(push1_var, push2_var): 1

ENDCASES

stack_empty_extensionality: AXIOM

(FORALL (stackcons1?_var: (stackcons1?),

stackcons1?_var2: (stackcons1?)):

stackcons1?_var = stackcons1?_var2);

stack_push_extensionality: AXIOM

(FORALL (stackcons2?_var: (stackcons2?),

stackcons2?_var2: (stackcons2?)):

top(stackcons2?_var) = top(stackcons2?_var2)

AND pop(stackcons2?_var)

= pop(stackcons2?_var2)

IMPLIES stackcons2?_var = stackcons2?_var2);

stack_push_eta: AXIOM

(FORALL (stackcons2?_var: (stackcons2?)):

push(top(stackcons2?_var), pop(stackcons2?_var))

= stackcons2?_var);

stack_top_push: AXIOM

(FORALL (push1_var: s, push2_var: stack):

top(push(push1_var, push2_var)) = push1_var);

stack_pop_push: AXIOM

(FORALL (push1_var: s, push2_var: stack):

pop(push(push1_var, push2_var)) = push2_var);

stack_inclusive: AXIOM

(FORALL (stack_var: stack):

stackcons1?(stack_var)

OR stackcons2?(stack_var));

stack_induction: AXIOM

(FORALL (p: [stack -> boolean]):

p(empty)

AND

(FORALL (push1_var: s, push2_var: stack):

p(push2_var)

IMPLIES p(push(push1_var, push2_var)))

IMPLIES

(FORALL (stack_var: stack): p(stack_var)));

every(p: PRED[s])(a: stack): boolean =

CASES a OF

empty: TRUE,

push(push1_var, push2_var):

p(push1_var) AND every(p)(push2_var)

ENDCASES;

every(p: PRED[s], a: stack): boolean =

CASES a OF

empty: TRUE,

push(push1_var, push2_var):

p(push1_var) AND every(p, push2_var)

ENDCASES;

some(p: PRED[s])(a: stack): boolean =

CASES a OF

empty: FALSE,

push(push1_var, push2_var):

p(push1_var) OR some(p)(push2_var)

ENDCASES;

some(p: PRED[s], a: stack): boolean =

CASES a OF

empty: FALSE,

push(push1_var, push2_var):

p(push1_var) OR some(p, push2_var)

ENDCASES;

subterm(x: stack, y: stack): boolean =

x = y

OR CASES y OF

empty: FALSE,

push(push1_var, push2_var):

subterm(x, push2_var)

ENDCASES;

<<(x: stack, y: stack): boolean =

CASES y OF

empty: FALSE,

push(push1_var, push2_var):

x = push2_var OR x << push2_var

ENDCASES;

stack_well_founded: AXIOM well_founded?[stack](<<);

reduce_nat(stackcons1?_fun: nat,

stackcons2?_fun: [[s, nat] -> nat]):

[stack -> nat] =

LAMBDA (stack_adtvar: stack):

CASES stack_adtvar OF

empty: stackcons1?_fun,

push(push1_var, push2_var):

stackcons2?_fun

(push1_var,

reduce_nat

(stackcons1?_fun,

stackcons2?_fun)

(push2_var))

ENDCASES;

REDUCE_nat(stackcons1?_fun: [stack -> nat],

stackcons2?_fun: [[s, nat, stack] -> nat]):

[stack -> nat] =

LAMBDA (stack_adtvar: stack):

CASES stack_adtvar OF

empty:

stackcons1?_fun

(stack_adtvar),

push(push1_var, push2_var):

stackcons2?_fun

(push1_var,

REDUCE_nat

(stackcons1?_fun,

stackcons2?_fun)

(push2_var),

stack_adtvar)

ENDCASES;

reduce_ordinal(stackcons1?_fun: ordinal,

stackcons2?_fun: [[s, ordinal]

-> ordinal]):

[stack -> ordinal] =

LAMBDA (stack_adtvar: stack):

CASES stack_adtvar OF

empty: stackcons1?_fun,

push(push1_var, push2_var):

stackcons2?_fun

(push1_var,

reduce_ordinal

(stackcons1?_fun,

stackcons2?_fun)

(push2_var))

ENDCASES;

REDUCE_ordinal(stackcons1?_fun: [stack -> ordinal],

stackcons2?_fun: [[s, ordinal, stack]

-> ordinal]):

[stack -> ordinal] =

LAMBDA (stack_adtvar: stack):

CASES stack_adtvar OF

empty:

stackcons1?_fun

(stack_adtvar),

push(push1_var, push2_var):

stackcons2?_fun

(push1_var,

REDUCE_ordinal

(stackcons1?_fun,

stackcons2?_fun)

(push2_var),

stack_adtvar)

ENDCASES;

END stack_adt

stack_adt_map[s: TYPE, s1: TYPE]: THEORY

BEGIN

IMPORTING stack_adt

map(f: [s -> s1])(a: stack[s]): stack[s1] =

CASES a OF

empty: empty,

push(push1_var, push2_var):

push(f(push1_var), map(f)(push2_var))

ENDCASES;

map(f: [s -> s1], a: stack[s]): stack[s1] =

CASES a OF

empty: empty,

push(push1_var, push2_var):

push(f(push1_var), map(f, push2_var))

ENDCASES;

END stack_adt_map

stack_adt_reduce[s: TYPE, range: TYPE]: THEORY

BEGIN

IMPORTING stack_adt[s]

reduce(stackcons1?_fun: range,

stackcons2?_fun: [[s, range] -> range]):

[stack -> range] =

LAMBDA (stack_adtvar: stack):

CASES stack_adtvar OF

empty: stackcons1?_fun,

push(push1_var, push2_var):

stackcons2?_fun

(push1_var,

reduce

(stackcons1?_fun,

stackcons2?_fun)

(push2_var))

ENDCASES;

REDUCE(stackcons1?_fun: [stack -> range],

stackcons2?_fun: [[s, range, stack] -> range]):

[stack -> range] =

LAMBDA (stack_adtvar: stack):

CASES stack_adtvar OF

empty:

stackcons1?_fun

(stack_adtvar),

push(push1_var, push2_var):

stackcons2?_fun

(push1_var,

REDUCE

(stackcons1?_fun,

stackcons2?_fun)

(push2_var),

stack_adtvar)

ENDCASES;

END stack_adt_reduce