Wiki

root/cobra/trunk/HowTo/360-DeclareContracts.cobra

Revision 2337, 4.7 KB (checked in by Chuck.Esterbrook, 2 years ago)

Improvements to a How To.

  • Property svn:eol-style set to native
Line 
1"""
2Cobra supports contracts which are expressed through lists of truthful
3expressions attached to objects and methods (and properties). They act as
4documentation, specification and run-time assertions.
5
6Cobra's approach to contracts is modeled directly after Eiffel.
7
8Like the other How To's, this program demonstrates language features with
9explanatory comments. It is not a practical sample.
10
11Keywords and implicit variables related to contracts include:
12    invariant, require, ensure
13    result, old, implies
14    or require
15    and ensure
16
17When a contract condition fails, an exception is raised. The exception message
18shows the value of each subexpression in the condition, just like the Cobra
19'assert' exception. For example, 'x > y' will show the values of 'x' and 'y'.
20
21You may wish to turn off contracts before shipping software in order to boost
22run-time performance and/or reduce executable size. From the command line, you
23can turn off contracts with:
24    cobra -contracts:none foo.cobra
25
26See also a forum post on contracts:
27http://cobra-language.com/forums/viewtopic.php?f=4&t=26
28"""
29
30class Entity
31
32    # 'invariant' is followed by a list of truthful expressions
33    # These are executed at the end of each code member (initializer, method,
34    # property, etc.). They speak to the state of the object.
35    invariant
36        .serialNum >= 1000
37        .name.length > 0
38        .actionCount >= 0
39
40    cue init(serialNum as int, name as String)
41        require
42            serialNum > 0
43            name.length > 0
44        body
45            base.init
46            _serialNum = serialNum
47            _name = name
48
49    get serialNum from var as int
50   
51    get name from var as String
52
53    pro isActive from var as bool
54
55    get actionCount from var as int
56
57    def act(ctx as Context)
58        require           # Requirements can speak to...
59            .isActive     #   the state of the current object
60            ctx.isActive  #   the state of the arguments
61        ensure
62            # Can refer to 'old' values in 'ensure'
63            .actionCount == old .actionCount + 1
64        body
65            _actionCount += 1
66
67    # Properties can have contracts too:
68    get upperName as String
69        ensure
70            # 'result' is an implicit variable in the 'ensure' clause
71            result.length == .name.length
72        body
73            return .name.toUpper
74
75
76class Context
77    """
78    Dummy support class.
79    """
80
81    get isActive as bool
82        return true
83
84
85class Utils
86
87    shared
88
89        def compute(x as float, y as float) as float
90            require
91                x > 0
92                y > 0
93            ensure
94                result > 0   
95            body
96                return x.sqrt + y.sqrt
97
98        def compute(numbers as IList<of number>) as number
99            # Overloaded methods get their own contracts independent of
100            # other overloads.
101            # Also the 'all' and 'any' operators can be use to check
102            # conditions on a series of elements.
103            require
104                all for n in numbers get n > 0
105            body
106                return numbers.random
107
108        def compute(s as String) as String
109            # If a condition is too complex to express directly,
110            # just break it out to a utility method.
111            require
112                .isComputable(s)
113            body
114                return s.toUpper
115
116        def isComputable(s as String) as bool
117            """ Returns true if ... """
118            # complex logic here
119            return true
120
121        def normalizeName(name as String) as String
122            # If a contract has only one condition, you can write it
123            # on one line and skip 'body'.
124            require name.trim.length
125            ensure result.length <= name.length
126            return Utils.capped(name.trim)
127
128        def capped(s as String) as String
129            """
130            Returns the string with the first character capitalized.
131            Returns a blank string for a blank string.
132            """
133            ensure
134                result.length == s.length
135                result.length implies result[0] == result[0].toUpper
136            test
137                # Unit tests can enhance contracts by showing example
138                # invocations and verifying behavior.
139                assert Utils.capped('chuck')=='Chuck'
140                assert Utils.capped('Chuck')=='Chuck'
141                assert Utils.capped('')==''
142                assert Utils.capped(' foo')==' foo'
143                assert Utils.capped('f')=='F'
144                assert Utils.capped('1aoeu')=='1aoeu'
145            body
146                if s.length
147                    return s[0:1].toUpper + s[1:]
148                else
149                    return s
150
151
152class BaseClass
153
154    def normalizeName(name as String) as String
155        require name.trim.length
156        return Utils.capped(name.trim)
157
158
159class DerivedClass inherits BaseClass
160
161    def normalizeName(name as String) as String is override
162        # When method contracts are inherited, the 'require' can be weakened
163        # through 'or require' meaning that the DerivedClass is more flexible
164        # about what conditions the work can be done in. The 'ensure' can be
165        # strengthened with 'and ensure' meaning that DerivedClass can
166        # guarantee additional conditions about the results of the work.
167        or require
168            true   # take any kind of string
169        and ensure
170            name.trim.length == 0 implies result == '(noname)'
171        body
172            return Utils.capped(if(name.trim.length, name.trim, '(noname)'))
173
174
175class Program
176
177    def main
178        expect RequireException
179            Utils.normalizeName(''# contract violations throw exceptions
Note: See TracBrowser for help on using the browser.