├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENCE.md ├── README.md ├── contracts.nim ├── contracts.nimble ├── contracts ├── contexts.nim ├── contexts │ ├── contextType.nim │ ├── contractConds.nim │ ├── deepCopyPolyfill.js │ ├── deepCopyPolyfill.nim │ ├── explain.nim │ ├── handleContext.nim │ ├── handlers.nim │ ├── handlers │ │ ├── conIter.nim │ │ ├── conLoop.nim │ │ ├── conProc.nim │ │ └── findCon.nim │ ├── oldValueBinding.nim │ └── outsideContext.nim ├── declarations.nim ├── declarations │ ├── exceptions.nim │ ├── keywords.nim │ └── messages.nim ├── features.nim ├── features │ ├── ghostCode.nim │ └── quantifiers.nim └── overview.nim ├── development └── tests └── isqrt.nim /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - '*.md' 9 | pull_request: 10 | paths-ignore: 11 | - '*.md' 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | name: Nim tests 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: jiro4989/setup-nim-action@v1 20 | - name: Nim version 21 | run: nim -v 22 | - name: Nimble version 23 | run: nimble -v 24 | - name: Run tests 25 | run: nimble test -Y 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | *.html 3 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 by M. Kotwica (user Udiknedormin at github.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Contracts 2 | [![test](https://github.com/Udiknedormin/NimContracts/actions/workflows/test.yml/badge.svg)](https://github.com/Udiknedormin/NimContracts/actions/workflows/test.yml) 3 | [![nimble](https://raw.githubusercontent.com/yglukhov/nimble-tag/master/nimble_js.png)](https://github.com/yglukhov/nimble-tag) 4 | 5 | This module is used to make contracts – elegant promises that 6 | pieces of code will fulfill certain conditions. 7 | 8 | Contracts are essentially value counterparts of type-based concepts, 9 | generic bounds, type attributes (e.x. ``not nil``) etc. While they 10 | work similarly to assertions, as they throw exceptions when broken, 11 | main reasons for using contracts include elegance of code, clarity 12 | of exception messages, intuitive semantics and ease of analysis, 13 | both for human and external tools. 14 | 15 | As usage of this module isn't typical and some extra rules are 16 | applied, aside from documentation for each of module's elements 17 | the description of the module as a whole is included. 18 | 19 | ## Where could I hear about it? 20 | Contracts and static analysis were briefly mentioned in 21 | [June 8th 2020 Nim blog post](https://nim-lang.org/blog/2020/06/08/static-analysis.html) 22 | and 23 | [Nim v1.2.0 changelog](https://nim-lang.org/blog/2020/04/03/version-120-released.html) 24 | (it actually uses similar names, `requires` vs `require`). 25 | [DrNim (by Araq)](https://nim-lang.org/docs/drnim.html) 26 | project uses such concepts too. 27 | 28 | ## Hello contracts 29 | For most of the features of this module, ``contractual`` macro should 30 | be used. As it generates exception-raising code, some exceptions 31 | should be imported too. For now, importing whole module is advised 32 | (at least until the submodules are introduced). 33 | 34 | Example usage (hello world equivalent): 35 | ```nim 36 | import contracts 37 | from math import sqrt, floor 38 | 39 | proc isqrt[T: SomeInteger](x: T): T {.contractual.} = 40 | require: 41 | x >= 0 42 | ensure: 43 | result * result <= x 44 | (result+1) * (result+1) > x 45 | body: 46 | (T)(x.toBiggestFloat().sqrt().floor().toBiggestInt()) 47 | 48 | 49 | echo isqrt(18) # prints 4 50 | 51 | echo isqrt(-8) # runtime error: 52 | # broke 'x >= 0' promised at FILE.nim(LINE,COLUMN) 53 | # [PreConditionError] 54 | ``` 55 | 56 | ## Overview 57 | It is advised to use contracts as part of a fine documentation. 58 | Even when disabled by a switch or pragma, they still look good 59 | and describe one's intentions well. Thanks to that, the code is 60 | often much easier to read even without comments. Also, a reader 61 | experienced in contractual design (and prefferably Nim) can read 62 | them faster than natural language as they are more compact. 63 | 64 | Consider finding key's position in a sorted array: 65 | 66 | ```nim 67 | contractual: 68 | var a = 0 69 | var b = arr.len-1 70 | while a < b: 71 | invariant: 72 | if key in arr: key in arr[a..b] 73 | abs(a - b) < `abs(a - b)` 74 | ensure: 75 | if key in arr: key == arr[a] 76 | body: 77 | let mid = (a + b) div 2 78 | if arr[mid] < key: 79 | a = mid + 1 80 | else: 81 | b = mid 82 | ``` 83 | 84 | The first loop invariant is rather obvious. The other one is also 85 | intuitive if we know this module uses '`' character meaning roundly 86 | "previously" (in last iteration or yield, can also be used in postconditions 87 | to refer to values seen by preconditions). Actually, we could 88 | strengthen our invariants by adding information about how fast 89 | the range shrinks but it's not necessary to prove the algorithm 90 | works (although it is needed to prove how efficient it is). 91 | 92 | While contracts can be of great help, they require care, especially 93 | when object-oriented behaviour is expected. For present version, 94 | one has to follow certain rules described in 95 | [Object-Oriented Contracts](#object-oriented-contracts) 96 | section (in the future they will be followed automatically). 97 | 98 | ## Contractual context 99 | As already mentioned, for most of this module's features to actually 100 | work, the code has to be inside of a ``contractual`` block. For callables, 101 | it can be applied as a pragma. 102 | 103 | Syntax: 104 | 105 | ```nim 106 | contractual: 107 | ... 108 | 109 | proc ... {.contractual.} = ... 110 | ``` 111 | 112 | There are several Contractual Contexts (CoCo) inside of ``contractual`` 113 | block. They are bound to plain Nim code blocks, so no additional marking 114 | is needed. CoCo include: 115 | - `proc` is ``procedure``, ``converter`` or ``method`` declaration 116 | - `loop` is ``iterator`` declaration or either ``for`` or ``while`` loop 117 | - `type` is ``type`` declaration (to be used in future versions) 118 | 119 | It is advised, although not needed, to use ``contractual`` block 120 | for whole modules and any modules that utilize them. 121 | 122 | 123 | ## Context keywords 124 | | Keyword | Related to | Semantics |proc|loop|type| 125 | |-----------|:--------------:|:--------------:|:--:|:--:|:---: 126 | | require | PreCondition | is needed | yes| yes| no| 127 | | invariant | Invariant | doesn't change | no| yes| yes| 128 | | ensure | PostCondition | is provided | yes| yes| no| 129 | | body | implementation | how it is done | yes| yes| no| 130 | 131 | Sections should be used in the same order as in the table above. 132 | 133 | There is also ``promise`` keyword (related to CustomContract) which 134 | describes any conditions and as the only one doesn't require being 135 | used in ``contractual``. Its usage discouraged if any other CoCo 136 | would do as its less descriptive and have no additional rules to it 137 | but for the assertion. 138 | 139 | ## Other features 140 | This module also includes a few non-context tools: 141 | - ``ghost`` makes a block of code active only when contracts are turned on 142 | - ``forall`` and ``forsome`` represent quantifiers 143 | - ``assume`` is used for assumptions for human reader and external tools 144 | (no code generated) 145 | - ``promise`` makes custom contracts 146 | 147 | ## Documentation 148 | Contracts can be treated as a part of the signature of the routine 149 | or type associated with it. Due to that, this module can generate 150 | additional code block for the entity's documentation so that the code 151 | would blend in with the signature. 152 | 153 | Example code: 154 | 155 | ``` nim 156 | proc isqrt[T: SomeInteger](x: T): T {.contractual.} = 157 | ## Integer square root function. 158 | require: 159 | x >= 0 160 | ensure: 161 | result >= 0 162 | result * result <= x 163 | (result+1) * (result+1) > x 164 | body: 165 | (T)(x.toBiggestFloat().sqrt().floor().toBiggestInt()) 166 | ``` 167 | 168 | Generated docs: 169 | 170 | ```nim 171 | proc isqrt[T: SomeInteger](x: T): T 172 | ``` 173 | > ```nim 174 | > requires: 175 | > x >= 0 176 | > ensures: 177 | > result >= 0 178 | > result * result <= x 179 | > (result+1) * (result+1) > x 180 | > ``` 181 | > Integer square root function. 182 | 183 | Doc comments can be added anywhere between contractual sections, 184 | they will be treated just like when written in one place. 185 | 186 | Contracts docs generation can be disabled using `noContractsDocs` 187 | compilation flag. 188 | 189 | ## Diagnostics 190 | To diagnose contracts, use `explainContracts` compile flag. 191 | It provides diagnostic informations about contracts, according to its 192 | numerical value: 193 | - 0: None (default) --- doesn't show any diagnostics 194 | - 1: Output (when flag defined but has no value) --- show final code 195 | - 2: Basic --- show both source and final code 196 | - 3: Verbose --- as Basic + show each type 197 | of contract detected for the entity (directly or indirectly). 198 | 199 | ## Non-obvious goodies 200 | Nim has an undocumented problem with JS `deepCopy` implementation lacking. 201 | Contracts uses its own implementation of `deepCopy` for JS backend, thus 202 | solving the problem. Thanks to that, "previous values" can be used on JS 203 | backend just like on C or C++ ones. 204 | 205 | 206 | ## Future 207 | ### Exception awareness 208 | As for now, contracts cannot refer to exceptions, although it is planned 209 | for future versions to enable such a feature. In core Nim, it is possible 210 | to inform about what types of exceptions can be throw in the callable. 211 | However, there is no possibility to express what conditions should be 212 | met for these exceptions to be thrown. Additionally, it should be possible 213 | to specify what conditions will be true if an exception raises (notice they 214 | are not necessarily the same conditions). 215 | 216 | ### Object-Oriented Contracts 217 | New context `type` is planned for future versions. With it's introduction 218 | both `proc` and `loop` contexts will be changed to force object-oriented 219 | rules. For now, user should follow them manually. 220 | 221 | | contract | containing | inheritance | 222 | |----------------|:-----------:|:-----------:| 223 | | type invariant | added | ? | 224 | | pre-condition | added | alternative | 225 | | loop invariant | added | added | 226 | | post-condition | added | added | 227 | 228 | 229 | 230 | ## Comparison to other libraries 231 | 232 | ### Nim built-in pragmas 233 | 234 | Nim itself defines some contract pragmas, including `requires`, `ensures`, 235 | `assume`, `assert`, `invariant`. Note how many of them are very similar to 236 | those used by Contracts. However, these do not generate any runtime checks 237 | and are there only to be inspected by external tools. In comparison, 238 | Contracts actually generates runtime checks with meaningful errors. 239 | 240 | If some tooling starts actually using these pragmas, Contracts might get an 241 | option to inject these pragmas too, so that external tooling can use them. 242 | 243 | 244 | ### Contra 245 | 246 | Contra is overall a much more minimalistic project when it comes to 247 | contracts, both feature-wise and user-experience-wise. Listing the 248 | differences briefly: 249 | * foolproofness 250 | * Contra asks the user to use particular code order (preconditions, then 251 | postconditions, then main code). Contracts actually enforces these 252 | rules. 253 | * Contra errors on import for unsuported targets. Contracts only errors 254 | if the user uses features that cannot be supported on the given target, 255 | and when it does, it generates a meaningful error message. 256 | * types of contracts 257 | * Contra only provides preconditions and postconditions for procs 258 | and iterators. Contracts also have loop and iterator invariants (and 259 | some plans for object and methods inheritance support). 260 | * Contra doesn't provide the "old/previous value" feature. Contracts does. 261 | * Contra can only handle unconditional contracts, i.e. returning bools. 262 | Contracts can also handle conditional contracts (functionally equivalent 263 | to `if contract_cond: cond else: true`, but semantically different). 264 | * documentation 265 | * Contra can only generate documentation for preconditions (and only 266 | in some cases). Contracts generate documentation for all signature-like 267 | contracts (preconditions, postconditions, invariants) by default. 268 | * errors 269 | * Contra uses `AssertionError`. Contracts uses a separate error type 270 | hierarchy with separate types for e.g. preconditions and postconditions. 271 | That way, the user can treat caller errors (i.e. breaking preconditions) 272 | differently from the provider errors (i.e. breaking postconditions). 273 | * Contra only shows the number of contract that has been broken and only 274 | stringifies the condition for preconditions. Contracts stringifies all 275 | the contracts in its error messages. 276 | * Contra only shows the location of the contract's code when abused. 277 | Contracts provides it for all contracts by default. 278 | * additional contract-related features 279 | * Contra doesn't have "ghost code" feature (i.e. code that is only 280 | generated if runtime contracts checks are enabled), although the user 281 | can replicate it with a `when defined(contracts):` statement. Contracts 282 | does and actually implements `ghost` in a similar way. 283 | * Contra doesn't define any contract-related helper functions, such as 284 | `forall` and `forsome` (although `sequtils.allIt` and `sequtils.anyIt` 285 | can be used). Contracts does. 286 | * other features 287 | * Contra defines some debug printing optimizations with term rewriting. 288 | Contracts does not, it's a library purely for design-by-contract. 289 | * Contra defines some assert-and-print-generated-code funcionality. 290 | Contracts does not, it's a high-level library. 291 | * Contra defines some immutability-related tools. 292 | Contracts does not, it's programming style-agnostic. 293 | * support 294 | * Both Contra and Contracts fully suppor compile-time execution 295 | (`{.compiletime.}` and `static`) 296 | * Contra seems to have problems for JavaScript target on Nim >1.0. 297 | Contracts supports all of it features on JavaScript target. 298 | * Contra seems to have problems for NimScript target on Nim > 1.0. 299 | Contracts supports its basic features on NimScript target 300 | ("old/previous values" feature cannot be used though). 301 | 302 | 303 | #### FAQ 304 | 305 | * What about `assume` blocks? 306 | * Contra: Assume blocks produce no code at all and are only meant for 307 | human reading only, you can do that using discard or similar contruct 308 | on Nim. KISS. 309 | * Contracts: False. `assume` blocks are for both humans and static analysis 310 | tools. They are also checked semantically and type-wise, unlike comments 311 | or strings, but generate no code whatsoever, unlike actual code put in 312 | discard (which can but doesn't have to be optimised away). What's more, 313 | no static analysis tool can just guess than some string or discard is 314 | assumed to represent an assumption. Unless it's marked by a variable's 315 | name or a comment, human readers can't always do that either. 316 | * What about `body` blocks? 317 | * Contra: does NOT uses nor needs body blocks. 318 | * Contracts: does not in fact "need" them. They are used mostly for 319 | aesthetic reasons and user experience of users coming from languages that 320 | use them, like Ada or Cobra. It may even get optional in future releases. 321 | * What about `invariant` blocks? 322 | * Contra: You can pass Invariants on the `postconditions` block. 323 | * Contracts: False. Invariants only make sense for loops and iterators (for 324 | procs they're essentially just copying the same condition as both 325 | precondition and postcondition). Both libraries implement postconditions 326 | with a `defer` statement, which means it only runs after the return, not 327 | after each `yield`, like an `invariant` is supposed to. 328 | * What about `forall` and `forsome` blocks? 329 | * Contra: Use `sequtils.filterIt`, `sequtils.mapIt`, `sequtils.keepItIf`, 330 | `sequtils.allIt`, `sequtils.anyIt`, etc. 331 | * Contracts: True. `forall` and `forsome` are implementation-wise very 332 | close to `sequtils.allIt` and `sequtils.anyIt` (even their documentation 333 | say so). They were created first and foremost to improve readability, 334 | because Contracts assumes the more readable and self-explanatory 335 | the better, especially considering it's part of the docs. It also 336 | improves user experience for users coming from language which have such 337 | features, e.g. Ada and Cobra. 338 | * What about `ghost` block? 339 | * Contra: Use `when defined(release):` or `when defined(contracts):` 340 | * Contracts: True. `ghost` is actually implemented as `when ghostly():`. 341 | Introducing it serves two main reasons: to make it usable as a pragma 342 | and to improve user experience for users coming from Ada, which supports 343 | `ghost`. 344 | * Whats the performance and speed cost of using Contra and Contracts? 345 | * Contra: Zero cost at runtime, since it produces no code at all when build 346 | for Release. 347 | * Contracts: True for both, the approach is very similar. 348 | 349 | * How to use Contra and Contracts at Compile Time? 350 | * Contra: Add `{.compiletime.}` or `static:`. 351 | * Contracts: True for both. Note that `{.compiletime.}` procs cannot be 352 | used outside of other `{.compiletime.}`, macros and `static` blocks, 353 | because they do not exist at runtime. Using them elsewhere triggers 354 | a not-so-obvious error `request to generate code for .compileTime proc`. 355 | -------------------------------------------------------------------------------- /contracts.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Nim's Contract Library 4 | # (c) Copyright 2016 M. Kotwica 5 | # See the file "LICENSE", included in this 6 | # distribution, for details about the copyright. 7 | # 8 | 9 | # As this module is quite big but semantically divisible, 10 | # it was split into many files. 11 | # In the future, more files may be included. 12 | 13 | 14 | {.warning[ResultShadowed]:off.} # due to using findChild template 15 | 16 | import macros 17 | import typetraits 18 | import algorithm 19 | import strutils 20 | 21 | include contracts / overview 22 | include contracts / declarations 23 | include contracts / features 24 | include contracts / contexts 25 | -------------------------------------------------------------------------------- /contracts.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.2.2" 4 | author = "M. Kotwica" 5 | description = "Design by contract (DbC) library." 6 | license = "MIT" 7 | skipDirs = @["tests"] 8 | 9 | 10 | # Dependencies 11 | 12 | requires "nim >= 1.4.0" 13 | 14 | 15 | # Tests 16 | import ospaths, sequtils 17 | 18 | task tests, "run tests": 19 | --hints: off 20 | --linedir: on 21 | --stacktrace: on 22 | --linetrace: on 23 | --debuginfo 24 | --define:"explainContracts:0" 25 | --path: "." 26 | --run 27 | var dir_list = @["tests"] 28 | while dir_list.len != 0: 29 | let dir = dir_list.pop 30 | dir_list.add listDirs(dir) 31 | if splitPath(dir).tail != "nimcache": 32 | for file in listFiles(dir): 33 | var (_, _, ext) = splitFile(file) 34 | if ext == ".nim": 35 | echo "running ---- " & file 36 | setCommand "c", file 37 | 38 | task test, "run tests": 39 | setCommand "tests" 40 | -------------------------------------------------------------------------------- /contracts/contexts.nim: -------------------------------------------------------------------------------- 1 | 2 | include contexts / oldValueBinding 3 | include contexts / contractConds 4 | include contexts / outsideContext 5 | include contexts / contextType 6 | include contexts / explain 7 | include contexts / handleContext 8 | include contexts / handlers 9 | -------------------------------------------------------------------------------- /contracts/contexts/contextType.nim: -------------------------------------------------------------------------------- 1 | import tables 2 | 3 | # 4 | # General helpers 5 | # 6 | template findChildIdx(n, cond): int = 7 | ## Like ``findChild`` but returns an index (-1 if not found). 8 | block: 9 | var idx: int = -1 10 | var it {.inject.}: NimNode 11 | for i in 0 .. (n.len - 1): 12 | it = n[i] 13 | if cond: 14 | idx = i 15 | break 16 | idx 17 | 18 | ## Type of entity to produce. 19 | type EntityType {.pure.} = enum 20 | Converter = "converter", 21 | Iterator = "iterator", 22 | Method = "method", 23 | Procedure = "proc", 24 | Template = "template", 25 | Macro = "macro", 26 | Type = "type", 27 | While = "while", 28 | For = "for", 29 | Other = "entity" 30 | 31 | ## Kind of entity to produce. 32 | type EntityKind = enum 33 | declaration, # routine or type declaration 34 | blocklike, # block of code 35 | other # other code 36 | 37 | ## Context data for handling. 38 | type Context = ref object 39 | name: string 40 | typ: EntityType 41 | kind: EntityKind 42 | secNames: seq[Keyword] 43 | head: NimNode # before the implementation 44 | tail: NimNode # implementation (contains all but header) 45 | docsNode: NimNode # docs 46 | sections: Table[Keyword, NimNode] # sections 47 | original: NimNode # original source 48 | final: NimNode # final code 49 | olds: NimNode # old/previous values 50 | 51 | 52 | # forward-declaration 53 | proc findContract(thisNode: NimNode): NimNode 54 | ## Finds any occurences of contracts 55 | 56 | 57 | proc findDocs(stmts: NimNode): NimNode = 58 | ## Finds all doc-comments in the statement list. 59 | var docs = "" 60 | for child in stmts.children: 61 | if child.kind == nnkCommentStmt: 62 | docs.add child.strVal 63 | docs.add "\n" 64 | if docs != "": 65 | docs.delete(docs.len-1, docs.len-1) # delete last \n 66 | result = newCommentStmtNode(docs) 67 | 68 | proc findSection(stmts: NimNode, keyword: Keyword): NimNode = 69 | # Finds a requested section in the statement list. 70 | result = stmts.findChild(it.kind == nnkCall and 71 | it[0].kind == nnkIdent and 72 | it[0].asKeyword == keyword) 73 | if result != nil: 74 | if result.len > 2: # invalid section (more arguments than just code): 75 | error(ErrInvalidSection % [result.repr]) 76 | result = result[1] # unwrap 77 | 78 | proc isContractual(stmts: NimNode): bool = 79 | ## Checks if the entity is contractual, 80 | ## i.e. it contains a contractual section. 81 | stmts.findChild(it.kind == nnkCall and 82 | it[0].kind == nnkIdent and 83 | it[0].isKeyword) != nil 84 | 85 | proc checkContractual(ct: Context, stmts: NimNode) = 86 | ## Checks the context 87 | var child: NimNode 88 | 89 | # check if only the right contractual sections are used as children 90 | # allow doc comments 91 | child = stmts.findChild(it.kind != nnkCommentStmt and 92 | (it.kind == nnkCall and 93 | it[0].kind != nnkIdent or 94 | not it[0].isKeyword)) 95 | if child != nil: 96 | error(ErrMsgChildNotContractBlock.format(ct.typ, child[0])) 97 | 98 | # check if only right contractual keywords are used 99 | child = stmts.findChild(it.kind != nnkCommentStmt and 100 | it[0].asKeyword notin ct.secNames) 101 | if child != nil: 102 | error(ErrMsgWrongUsage.format(ct.typ, child[0])) 103 | 104 | # check if the order of keywords if right 105 | var idxOfKey = -2 106 | var newIdxOfKey = -2 107 | for child in stmts.children: 108 | if child.kind == nnkCommentStmt: 109 | continue 110 | newIdxOfKey = ct.secNames.find($child[0]) 111 | if newIdxOfKey <= idxOfKey: 112 | error(ErrMsgWrongOrder % 113 | [$ct.typ, $child[0], ct.secNames[idxOfKey]]) 114 | if newIdxOfKey == idxOfKey: 115 | error(ErrMsgDuplicate % 116 | [$ct.typ, ct.secNames[idxOfKey]]) 117 | idxOfKey = newIdxOfKey 118 | 119 | proc entityType(thisNode: NimNode): EntityType = 120 | ## Gets contractual entity's type. 121 | const mapper = { 122 | nnkConverterDef: EntityType.Converter, 123 | nnkIteratorDef: EntityType.Iterator, 124 | nnkMethodDef: EntityType.Method, 125 | nnkProcDef: EntityType.Procedure, 126 | nnkTemplateDef: EntityType.Template, 127 | nnkMacroDef: EntityType.Macro, 128 | nnkTypeDef: EntityType.Type, 129 | nnkWhileStmt: EntityType.While, 130 | nnkForStmt: EntityType.For 131 | }.toTable 132 | 133 | if mapper.hasKey(thisNode.kind): 134 | result = mapper[thisNode.kind] 135 | else: 136 | result = EntityType.Other 137 | 138 | const RoutineTypes = {EntityType.Converter, 139 | EntityType.Iterator, 140 | EntityType.Method, 141 | EntityType.Procedure, 142 | EntityType.Template, 143 | EntityType.Macro} 144 | const LoopTypes = {EntityType.While, EntityType.For} 145 | 146 | proc entityKind(typ: EntityType): EntityKind = 147 | ## Whether the entity is a declaration of routine or type. 148 | if typ in RoutineTypes or typ == EntityType.Type: 149 | EntityKind.declaration 150 | elif typ in LoopTypes: 151 | EntityKind.blocklike 152 | else: 153 | EntityKind.other 154 | 155 | proc getEntityName(code: NimNode, typ: EntityType): string = 156 | ## Gets contractual entity's name. 157 | if typ in RoutineTypes: 158 | $code.name 159 | elif typ == EntityType.Type: 160 | code[0].repr 161 | elif typ in LoopTypes: 162 | $typ 163 | else: 164 | "entity" 165 | 166 | proc newContext(code: NimNode, sections: openArray[Keyword]): Context = 167 | ## Creates a new Context based on its AST and section list. 168 | let stmts = code.findChild(it.kind == nnkStmtList) 169 | if not stmts.isContractual: 170 | return nil 171 | new(result) 172 | 173 | result.typ = code.entityType 174 | result.kind = result.typ.entityKind 175 | result.name = getEntityName(code, result.typ) 176 | result.secNames = @sections 177 | checkContractual(result, stmts) 178 | 179 | result.head = newStmtList() 180 | result.tail = code 181 | result.docsNode = stmts.findDocs() 182 | result.sections = initTable[Keyword, NimNode]() 183 | for key in ContractKeywordsNormal: 184 | result.sections[key] = stmts.findSection(key) 185 | result.original = code.copyNimTree 186 | result.final = nil 187 | 188 | iterator sections(ct: Context): (Keyword, NimNode) = 189 | ## Iterates over all sections from a context, including implementation. 190 | for key, value in ct.sections: 191 | yield (key, value) 192 | 193 | 194 | template sectionProperty(name, key) = 195 | proc name(ct: Context): NimNode = ct.sections[key] 196 | proc `name =`(ct: Context, val: NimNode) = 197 | ct.sections[key] = val 198 | 199 | macro genSectionProperties(): untyped = 200 | result = newStmtList() 201 | for key in ContractKeywordsNormal: 202 | result.add getAst(sectionProperty(ident(key.fieldName), ident(key))) 203 | 204 | genSectionProperties() 205 | -------------------------------------------------------------------------------- /contracts/contexts/contractConds.nim: -------------------------------------------------------------------------------- 1 | # 2 | # Contract assert-like checks making 3 | # 4 | proc contractInstance(exceptionName, code: NimNode): NimNode = 5 | ## Makes a list of simple assert-like contracts. 6 | if (code.len == 0): 7 | result = nil 8 | else: 9 | result = newNimNode(nnkIfStmt) 10 | 11 | for cond in code: 12 | let newCond = cond.oldValToIdent 13 | let strRepr = ContractViolatedStr % 14 | [($cond.toStrLit).unindent(60).replace("\n", " "), 15 | cond.lineinfo] 16 | 17 | template raiseImpl(ex, msg): untyped = 18 | raise newException(ex, msg) 19 | let sig = getAst(raiseImpl(exceptionName, strRepr)) 20 | 21 | # default 'true' in if-s and when-s 22 | if (cond.kind == nnkIfStmt or 23 | cond.kind == nnkWhenStmt) and 24 | cond[^1].kind != nnkElse: 25 | cond.add( 26 | newNimNode(nnkElse). 27 | add(newLit(true))) 28 | 29 | result. 30 | add(newNimNode(nnkElifBranch). 31 | add(newCond.prefix("not")). 32 | add(sig)) 33 | 34 | macro contractInstanceMacro(exceptionName, code: untyped): untyped = 35 | ghost: 36 | result = contractInstance(exceptionName, code) 37 | -------------------------------------------------------------------------------- /contracts/contexts/deepCopyPolyfill.js: -------------------------------------------------------------------------------- 1 | function __native_deepCopy(from, to) { 2 | if (from == null || typeof from != "object") { 3 | return from; 4 | } 5 | if (from.constructor != Object && from.constructor != Array) { 6 | return from; 7 | } 8 | if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function || 9 | from.constructor == String || from.constructor == Number || from.constructor == Boolean) { 10 | return new from.constructor(from); 11 | } 12 | 13 | to = to || new from.constructor(); 14 | 15 | for (var name in from) { 16 | to[name] = typeof to[name] == "undefined" ? __native_deepCopy(from[name], null) : to[name]; 17 | } 18 | 19 | return to; 20 | } 21 | -------------------------------------------------------------------------------- /contracts/contexts/deepCopyPolyfill.nim: -------------------------------------------------------------------------------- 1 | from macros import error 2 | 3 | 4 | when declared(deepCopy): 5 | template systemDeepCopy*(y): untyped = system.deepCopy(y) 6 | template systemDeepCopy*(x, y) = system.deepCopy(x, y) 7 | elif defined(js): 8 | const deepCopyPolyfill = staticRead("deepCopyPolyfill.js") 9 | {.emit: deepCopyPolyfill.} 10 | 11 | proc deepCopy[T](x: var T, y: T) {.importc: "__native_deepCopy".} 12 | proc deepCopy[T](y: T): T {.importc: "__native_deepCopy".} 13 | 14 | {.hint: "Target JS lacks deepCopy. Custom implementation used instead.".} 15 | 16 | template systemDeepCopy*(y): untyped = deepCopy(y) 17 | template systemDeepCopy*(x, y) = deepCopy(x, y) 18 | else: 19 | {.warning: 20 | "Target does not support deepCopy. 'Old values' feature cannot be used." 21 | .} 22 | 23 | template systemDeepCopy*(y): untyped = 24 | error: "Target does not support deepCopy, but 'old values' used!" 25 | y 26 | template systemDeepCopy*(x, y) = 27 | error: "Target does not support deepCopy, but 'old values' used!" 28 | x = y 29 | -------------------------------------------------------------------------------- /contracts/contexts/explain.nim: -------------------------------------------------------------------------------- 1 | import strutils 2 | import sequtils 3 | 4 | 5 | const ExplainIndent = 2 6 | 7 | type ExplainLevel {.pure.} = enum ## defines how verbose 8 | ## diagnostic outputs will be 9 | None = 0, 10 | Output = 1, 11 | Basic = 2, 12 | Verbose = 3 13 | 14 | const explainContracts {.strdefine.} = "" 15 | 16 | # None by default 17 | # Output if explainContracts is defined but has no value 18 | # value of explainContracts if defined 19 | const explainLevel = 20 | when defined(explainContracts): 21 | if explainContracts == "true": 22 | ExplainLevel.Output 23 | elif explainContracts == "" or explainContracts == "false": 24 | ExplainLevel.None 25 | else: 26 | ExplainLevel(parseInt(explainContracts)) 27 | else: 28 | ExplainLevel.None 29 | 30 | 31 | ## Contract section docs information. 32 | type ContractSectionInfo = object 33 | key: Keyword 34 | code: NimNode 35 | comment: NimNode 36 | 37 | proc `$`(csi: ContractSectionInfo, alwaysView = true): string = 38 | if csi.code == nil: 39 | if alwaysView: 40 | result = "no $1".format(csi.key.docName) 41 | else: 42 | result = "" 43 | elif csi.comment == nil: 44 | result = csi.key.docName & ":\n" 45 | for expr in csi.code: 46 | result &= ("\n\n" & expr.repr).indent(ExplainIndent) 47 | else: 48 | var comm = csi.comment.repr 49 | if comm[2] == ' ': 50 | comm.delete(0, 2) 51 | else: 52 | comm.delete(0, 1) 53 | result = "$1:\n$2".format(csi.key.docName, 54 | comm.indent(ExplainIndent)) 55 | 56 | proc strContractsAll(ct: Context, alwaysView = false): string = 57 | ## Stringify all contracts. No extracted documentation! 58 | var s = newSeq[ContractSectionInfo]() 59 | for key in ContractKeywordsNormal: 60 | if key != keyImpl: 61 | # Due to a bug in Nim's AST system, comments are not present 62 | # in AST for `EXPR ## COMM`. 63 | s.add ContractSectionInfo(key: key, 64 | code: ct.sections[key], 65 | comment: nil) 66 | s.mapIt(`$`(it, alwaysView)) 67 | .filterIt(it != "") 68 | .join("\n\n") 69 | 70 | proc explainContractBefore(ct: Context) = 71 | when explainLevel != ExplainLevel.None: 72 | echo "contractual $1 \"$2\":".format(ct.typ, ct.name) 73 | when explainLevel == ExplainLevel.Basic or 74 | explainLevel == ExplainLevel.Verbose: 75 | echo "original code:".indent(ExplainIndent) 76 | echo ct.original.repr.indent(ExplainIndent*2) 77 | when explainLevel == ExplainLevel.Verbose: 78 | echo ct.strContractsAll(alwaysView = true).indent(ExplainIndent) 79 | 80 | proc explainContractAfter(ct: Context) = 81 | when explainLevel != ExplainLevel.None: 82 | echo "final code:".indent(ExplainIndent) 83 | echo ct.final.repr.indent(ExplainIndent*2) 84 | 85 | 86 | proc genDocs(ct: Context, alwaysView = false): string = 87 | ct.strContractsAll(alwaysView) 88 | 89 | proc add2docs(docs: NimNode, new_docs: string) = 90 | when not defined(noContractsDocs): 91 | let openCode = ".. code-block:: nim" 92 | let oldDocs = docs.strVal 93 | docs.strVal = "$1\n$2".format(openCode, new_docs) 94 | if oldDocs != "": 95 | docs.strVal = "$1\n$2".format(docs.strVal, oldDocs) 96 | 97 | proc docs2body(tree: NimNode, docs: NimNode) = 98 | if docs.strVal != "": 99 | if tree.kind in RoutineNodes: 100 | tree.body.insert(0, docs) 101 | else: 102 | tree.insert(0, docs) 103 | -------------------------------------------------------------------------------- /contracts/contexts/handleContext.nim: -------------------------------------------------------------------------------- 1 | proc handle(ct: Context, handler: proc(ct: Context) {.closure.}): NimNode = 2 | ct.explainContractBefore() 3 | ct.handler() # notice invariant MUST be included in impl! 4 | 5 | result = newStmtList(ct.head) 6 | let docs = ct.genDocs() 7 | 8 | ghost do: 9 | if ct.pre != nil: 10 | ct.pre = contractInstance( 11 | ident(PreConditionDefect.name), ct.pre) 12 | 13 | if ct.post != nil: 14 | let preparationNode = getOldValues(ct.post).reduceOldValues 15 | let postCondNode = contractInstance( 16 | ident(PostConditionDefect.name), ct.post) 17 | ct.post = newTree(nnkDefer, postCondNode) 18 | ct.olds = preparationNode 19 | 20 | if ct.olds != nil: 21 | result.add ct.olds 22 | 23 | if ct.pre != nil: 24 | result.add ct.pre 25 | 26 | if ct.post != nil: # using defer 27 | result.add ct.post 28 | do: 29 | if ct.olds != nil: 30 | result.add ct.olds 31 | 32 | # add generated docs and generate it in the code 33 | if ct.kind != EntityKind.blocklike: 34 | ct.docsNode.add2docs(docs) 35 | result.docs2body(ct.docsNode) 36 | 37 | let stmtsIdx = ct.tail.findChildIdx(it.kind == nnkStmtList) 38 | ct.tail[stmtsIdx] = findContract(ct.impl) 39 | 40 | if ct.kind == EntityKind.declaration: 41 | let tmp = ct.tail[stmtsIdx] 42 | ct.tail[stmtsIdx] = newStmtList(result, tmp) 43 | result = ct.tail 44 | else: 45 | result.add ct.tail 46 | 47 | if ct.kind == EntityKind.blocklike: 48 | # for `defer` to work properly: 49 | result = newBlockStmt(result) 50 | 51 | ct.final = result 52 | ct.explainContractAfter() 53 | 54 | proc contextHandle(code: NimNode, 55 | sections: openArray[Keyword], 56 | handler: proc(ct: Context) {.closure.}): NimNode = 57 | ## Create context and handle it. 58 | let ct = newContext(code, sections) 59 | if ct == nil: 60 | result = code 61 | else: 62 | result = ct.handle(handler) 63 | -------------------------------------------------------------------------------- /contracts/contexts/handlers.nim: -------------------------------------------------------------------------------- 1 | 2 | include handlers / conProc 3 | include handlers / conIter 4 | include handlers / conLoop 5 | include handlers / findCon 6 | -------------------------------------------------------------------------------- /contracts/contexts/handlers/conIter.nim: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Iterators 4 | # 5 | proc return2breakHelper(code: NimNode): NimNode = 6 | result = code 7 | if result.kind == nnkReturnStmt: 8 | if result[0].kind == nnkEmpty: 9 | result = newNimNode(nnkBreakStmt).add(ident(keyImpl)) 10 | else: 11 | result = newStmtList( 12 | newAssignment(ident"result", result[0][1]), 13 | newNimNode(nnkBreakStmt).add(ident(keyImpl)) 14 | ) 15 | else: 16 | for i in 0 ..< result.len: 17 | result[i] = return2breakHelper(result[i]) 18 | 19 | proc return2break(code: NimNode): NimNode = 20 | ## Wrap into 'block body' and adapt 'return' statements. 21 | result = newNimNode(nnkBlockStmt). 22 | add(ident(keyImpl)). 23 | add(return2breakHelper(code)) 24 | 25 | proc yieldedVar(code: NimNode): NimNode = 26 | ## Add 'yielded' variable declaration to iterator. 27 | result = newNimNode(nnkVarSection). 28 | add(newNimNode(nnkIdentDefs). 29 | add(ident"yielded"). 30 | add(code.findChild(it.kind == nnkFormalParams)[0]). 31 | add(newEmptyNode()) 32 | ) 33 | 34 | proc yield2yielded(code, conds, binds: NimNode): NimNode = 35 | ## Add 'yielded' variable to the iterator's 'body', 36 | ## it works similar to 'result' in procs: contains the yielded value. 37 | result = code 38 | 39 | if result.kind == nnkYieldStmt: 40 | result = newStmtList( 41 | newAssignment(ident"yielded", result[0]), 42 | conds, # invariant conditions 43 | binds, # binds variables for the next iteration 44 | result) 45 | else: 46 | for i in 0 ..< result.len: 47 | result[i] = yield2yielded(result[i], conds, binds) 48 | 49 | proc iteratorContract(this: NimNode): NimNode = 50 | ## Handles contracts for iterators. 51 | warning("Loop contracts are known to be buggy as for now, use with caution.") 52 | contextHandle(this, @ContractKeywordsIter) do (it: Context): 53 | it.impl = return2break(it.impl) 54 | if it.inv != nil: 55 | let oldValuesDecl = getOldValues(it.inv).reduceOldValues 56 | let boundedFlag = if oldValuesDecl == nil: nil 57 | else: boundedFlagDecl() 58 | let invariant = contractInstance( 59 | ident(LoopInvariantDefect.name), it.inv). 60 | markBoundageDependent 61 | it.pre.add oldValuesDecl 62 | it.impl = yield2yielded( 63 | it.impl, 64 | invariant, 65 | updateOldValues(oldValuesDecl)) 66 | if boundedFlag != nil: 67 | it.impl.add updateFlag(boundedFlag) 68 | else: 69 | it.impl = yield2yielded( 70 | it.impl, 71 | newEmptyNode(), 72 | newEmptyNode()) 73 | it.impl.add this.yieldedVar 74 | 75 | -------------------------------------------------------------------------------- /contracts/contexts/handlers/conLoop.nim: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Loops 4 | # 5 | proc loopContract(this: NimNode): NimNode = 6 | contextHandle(this, @ContractKeywordsIter) do (it: Context): 7 | if it.inv != nil: 8 | let boundedFlag = boundedFlagDecl() 9 | let oldValuesDecl = getOldValues(it.inv).reduceOldValues 10 | it.inv = contractInstance( 11 | ident(LoopInvariantDefect.name), it.inv). 12 | markBoundageDependent(boundedFlag.getFlagSym) 13 | it.impl.add it.inv 14 | if oldValuesDecl != nil: 15 | if it.pre == nil: 16 | it.pre = newStmtList() 17 | it.pre.add oldValuesDecl 18 | it.pre.add boundedFlag 19 | it.impl.add updateOldValues(oldValuesDecl) 20 | it.impl.add updateFlag(boundedFlag.getFlagSym) 21 | -------------------------------------------------------------------------------- /contracts/contexts/handlers/conProc.nim: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Procedures and converters 4 | # 5 | 6 | import ../deepCopyPolyfill 7 | 8 | proc proceduralContract(thisNode: NimNode): NimNode = 9 | ## Handles contracts for procedures and converters. 10 | contextHandle(thisNode, @ContractKeywordsProc) do (it: Context): 11 | if it.olds != nil: 12 | if it.olds.kind == nnkVarSection: 13 | let new_olds = newNimNode(nnkLetSection) 14 | for i, section in it.olds: 15 | # `var name: type(impl)` --> `let name = impl` 16 | let name = section[0] 17 | let type_src = section[1][1] 18 | let impl = getAst(systemDeepCopy(type_src)) 19 | let new_section = newIdentDefs(name, newEmptyNode(), impl) 20 | new_olds.add new_section 21 | it.olds = new_olds 22 | -------------------------------------------------------------------------------- /contracts/contexts/handlers/findCon.nim: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # finding contracts 4 | # 5 | proc findContract(thisNode: NimNode): NimNode = 6 | ## Finds contractual entities inside 'contractual' block. 7 | result = thisNode 8 | case result.kind: 9 | of nnkProcDef, nnkConverterDef, nnkMethodDef: 10 | result = proceduralContract(result) 11 | of nnkIteratorDef: 12 | result = iteratorContract(result) 13 | of nnkWhileStmt, nnkForStmt: 14 | result = loopContract(result) 15 | of nnkCall: 16 | if result[0].kind in {nnkIdent, nnkSym} and $result[0] == keyCust: 17 | hint(HintMsgCustomContractUsed % [result.lineinfo]) 18 | result = contractInstance( 19 | ident(CustomContractDefect.name), result[1]) 20 | else: 21 | for i in 0 .. thisNode.len - 1: 22 | result[i] = findContract(result[i]) 23 | 24 | 25 | macro contractual*(code: untyped): untyped = 26 | ## Creates a block with contractual syntax enabled. 27 | ## Example: 28 | ## 29 | ## .. code-block:: nim 30 | ## proc checkedWrite(s: Stream; x: string) 31 | ## {.contractual, inline.} = 32 | ## require: 33 | ## not s.closed 34 | ## body: 35 | ## write(s, x) 36 | ## 37 | ## While many contractual features could be possible 38 | ## without an outside 'contractual' block, it is needed 39 | ## to check syntax (as one of the main reasons for using 40 | ## contracts is clarity). 41 | ## 42 | ## If future versions of Nim language will enable custom 43 | ## multi-block macros (similar to if-else), this macro will stop 44 | ## affecting compilation process and therefore get deprecated. 45 | findContract(code) 46 | -------------------------------------------------------------------------------- /contracts/contexts/oldValueBinding.nim: -------------------------------------------------------------------------------- 1 | # 2 | # Bind old values with escaping 3 | # 4 | 5 | import deepCopyPolyfill 6 | 7 | 8 | proc oldValIdentToStr(thisNode: NimNode): string = 9 | ## Changes old value's identifier to string 10 | result = "`" 11 | for child in thisNode.children: 12 | result.add($child) 13 | result.add("`") 14 | 15 | proc oldValToIdent(thisNode: NimNode): NimNode = 16 | ## Changes AccQuoted structure for old value 17 | ## into an identifier 18 | if thisNode.kind == nnkAccQuoted: 19 | result = thisNode.oldValIdentToStr.ident 20 | else: 21 | for idx in 0 ..< thisNode.len: 22 | thisNode[idx] = thisNode[idx].oldValToIdent 23 | result = thisNode 24 | 25 | proc getOldValuesHelper(thisNode, getter: NimNode): NimNode = 26 | ## Recursive helper for getOldValues. 27 | result = getter 28 | if thisNode.kind == nnkAccQuoted: 29 | let name = thisNode.oldValIdentToStr.ident 30 | var tyStr = "" 31 | for child in thisNode.children: 32 | tyStr.add($child) 33 | let ty = newCall(bindSym"type", parseExpr(tyStr)) 34 | 35 | result. 36 | add(newNimNode(nnkIdentDefs). 37 | add(name). 38 | add(ty). 39 | add(newEmptyNode())) 40 | else: 41 | for child in thisNode.children: 42 | result = getOldValuesHelper(child, result) 43 | 44 | proc getOldValues(thisNode: NimNode): NimNode = 45 | ## Prepares entity's old values for 'ensure' 46 | ## If a special value boundedYet is needed, 47 | ## boudageFlag should be set to true. 48 | result = getOldValuesHelper(thisNode, newNimNode(nnkVarSection)) 49 | if result.len == 0: # no variables, empty section => remove it entirely 50 | result = nil 51 | 52 | proc updateOldValues(thisNode: NimNode): NimNode = 53 | ## Updates old values for iterator or loop. 54 | if thisNode.len > 0 and thisNode[0].kind != nnkEmpty: 55 | result = newStmtList() 56 | for child in thisNode.children: 57 | let name = child[0] 58 | let value = child[1][1] 59 | result.add getAst(systemDeepCopy(name, value)) 60 | else: 61 | result = newEmptyNode() 62 | 63 | proc boundedFlagDecl(): NimNode {.compileTime.} = 64 | ## Generates boundedYet flag. 65 | let sym = genSym(nskVar, "boundedYet") 66 | template decl(sym) = 67 | var sym = false 68 | result = getAst(decl(sym)) 69 | if result.kind == nnkStmtList: # to get around the difference 70 | # in parsing on Nim stable and devel 71 | result = result[0] 72 | 73 | proc getFlagSym(flag: NimNode): NimNode = 74 | ## Gets flag symbol given its declaration. 75 | flag.expectKind(nnkVarSection) 76 | flag[0][0].expectKind({nnkIdent, nnkSym}) 77 | result = flag[0][0] 78 | 79 | proc updateFlag(flag: NimNode, value = newLit(true)): NimNode = 80 | ## Updates flag given by its symbol to a certain value 81 | ## (true literal by default). 82 | result = newAssignment(flag, value) 83 | 84 | proc reduceOldValues(thisNode: NimNode): NimNode = 85 | ## Reduces repetitions in old values, 86 | ## either 'let' or 'var'. 87 | result = thisNode 88 | var olds: seq[string] = @[] 89 | var toDelete: seq[int] = @[] 90 | for idx in 0 ..< result.len: 91 | let name = $result[idx][0] 92 | if name in olds: 93 | toDelete.add(idx) 94 | else: 95 | olds.add(name) 96 | for idx in reversed(toDelete): 97 | result.del(idx) 98 | # empty 'var' or 'let' is not ok 99 | if result.len == 0: 100 | result = newEmptyNode() 101 | 102 | proc isBoundageDependent(thisNode: NimNode): bool = 103 | ## Makes an expression only checked if variable 104 | ## boundage already happened 105 | if thisNode.kind == nnkIdent and ($thisNode)[0] == '`': 106 | result = true 107 | else: 108 | if thisNode.len == 0: 109 | result = false 110 | else: 111 | result = forsome child in thisNode.children: 112 | child.isBoundageDependent 113 | 114 | proc markBoundageDependent(thisNode: NimNode, 115 | flag: NimNode = ident"boundedYet"): NimNode = 116 | ## Marks all boundage depended conditions, use given flag. 117 | result = thisNode 118 | for idx in 0 ..< result.len: 119 | if result[idx][0].isBoundageDependent: 120 | result[idx][0] = infix(flag, "and", result[idx][0]) 121 | -------------------------------------------------------------------------------- /contracts/contexts/outsideContext.nim: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Context keywords when used outside ``contractual`` 4 | # 5 | template require* (conds: untyped) = 6 | ## Makes a list of preconditions, i.e. conditions 7 | ## to be fulfilled when entering the entity. 8 | ## 9 | ## Every expression represents a single requirement 10 | ## and should return a value convertible to bool. 11 | ## Example: 12 | ## 13 | ## .. code-block:: nim 14 | ## proc peekAll[T](s: seq[seq[T]]) 15 | ## {.contractual, inline.} = 16 | ## require: 17 | ## s.len > 0 18 | ## all e in s: e.len > 0 19 | ## body: 20 | ## for e in s: 21 | ## echo s.high 22 | ## 23 | ## Requirements are checked in the order of appearance. 24 | ## 25 | ## Some control statements can be used, namely 26 | ## ``if`` and ``when``. Both mean that a contract is valid 27 | ## only if certain condition (runtime or compile-time 28 | ## respectively) is fulfilled. ``elif`` and ``else`` branches 29 | ## are also possible. 30 | ## Example: 31 | ## 32 | ## .. code-block:: nim 33 | ## proc vecOp[T](s: seq[T], T => T) {.contractual.} = 34 | ## require: 35 | ## when T is SomeReal: 36 | ## all e in s: not e.isNaN 37 | ## ... 38 | static: 39 | error(ErrMsgOutsideContractual % $keyPre) 40 | 41 | template ensure* (conds: untyped) = 42 | ## Makes a list of postconditions, i.e. conditions 43 | ## to be fulfilled when returning from entity. 44 | ## 45 | ## Syntax is the same as for ``require`` with one addition: 46 | ## expression embraced with '`' special character means 47 | ## "the value of the expression from the time the entity 48 | ## was entered". 49 | ## Example: 50 | ## 51 | ## .. code-block:: nim 52 | ## proc checkedPop[T](s: var seq[T]): T 53 | ## {.contractual, inline, noSideEffect.} = 54 | ## require: 55 | ## s.len > 0 56 | ## ensure: 57 | ## s.len == `s.len` - 1 58 | ## result == `s[s.len - 1]` 59 | ## all i in 0 ..< s.len: `s`[i] == s[i] 60 | ## body: 61 | ## result = s.pop 62 | ## 63 | ## This escaping works for any expression, each one is 64 | ## evaluated once (which may give unexpected results 65 | ## for iterators or some other subroutines). 66 | ## 67 | ## .. code-block:: nim 68 | ## `s[s.len - 1]` 69 | ## `s`[`s.len - 1`] 70 | ## `s`[s.len - 1] 71 | ## 72 | ## The first and second expression return the same (although 73 | ## the first one binds only one variable). 74 | ## The last one's return value is the same only if s.len 75 | ## didn't change. 76 | static: 77 | error(ErrMsgOutsideContractual % $keyPost) 78 | 79 | template invariant* (conds: untyped) = 80 | ## Makes a list of loop invariants, i.e. conditions 81 | ## to be fulfilled after each loop's iteration 82 | ## or just before iterator's yield. 83 | ## 84 | ## Syntax is the same as for ``ensure`` with one modification: 85 | ## escaped expression means "expression's value from 86 | ## the previous iteration". 87 | ## Example: 88 | ## 89 | ## .. code-block:: nim 90 | ## proc sortedTreeSearch[T](tree: Node[T], key: T): Node[T] 91 | ## {.contractual, noSideEffect.} = 92 | ## ensure: 93 | ## if key in tree: 94 | ## result.data == key 95 | ## else: 96 | ## result == nil 97 | ## body: 98 | ## while result != nil and result.data != key: 99 | ## invariant: 100 | ## if key in tree: key in result 101 | ## body: 102 | ## if result.data < key: 103 | ## result = result.left 104 | ## else: 105 | ## resutl = result.right 106 | static: 107 | error(ErrMsgOutsideContractual % $keyInvL) 108 | 109 | template body* (conds: untyped) = 110 | ## Defines contractual unit's body (implementation). 111 | ## It stores the code that promises to fulfill contracts 112 | ## preceding this block. It should be proven that its does, 113 | ## either through testing or formal proof. 114 | static: 115 | error(ErrMsgOutsideContractual % $keyImpl) 116 | 117 | template promise* (conds: untyped): untyped = 118 | ## Makes a list of custom conditions, can be inserted 119 | ## even outside ``contractual`` block. 120 | ## 121 | ## It is highly discouraged to use custom contracts 122 | ## if any other contract fits for semantic reasons. 123 | static: 124 | const inst = instantiationInfo() 125 | hint(HintMsgCustomContractUsed % 126 | ["$1($2)" % [$(inst[0]), $(inst[1])]]) 127 | contractInstanceMacro(CustomContractException, conds) 128 | 129 | template assume* (conds: typed): untyped = 130 | ## Marks assumptions, i.e. conditions which are always 131 | ## fulfilled but either are either not deductable from 132 | ## the code or difficult to be deduced. It's also useful 133 | ## for external proving tools. 134 | discard 135 | 136 | -------------------------------------------------------------------------------- /contracts/declarations.nim: -------------------------------------------------------------------------------- 1 | 2 | include declarations / keywords 3 | include declarations / exceptions 4 | include declarations / messages 5 | -------------------------------------------------------------------------------- /contracts/declarations/exceptions.nim: -------------------------------------------------------------------------------- 1 | 2 | type 3 | ContractDefect* = object of Defect ## \ 4 | ## violation of any contract at runtime 5 | CustomContractDefect* = object of ContractDefect ## \ 6 | ## violation of a custom contract (promise) 7 | PreConditionDefect* = object of ContractDefect ## \ 8 | ## violation of a requirement 9 | PostConditionDefect* = object of ContractDefect ## \ 10 | ## violation of an assurance 11 | LoopInvariantDefect* = object of ContractDefect ## \ 12 | ## violation of a loop/iterator invariant 13 | -------------------------------------------------------------------------------- /contracts/declarations/keywords.nim: -------------------------------------------------------------------------------- 1 | 2 | type 3 | Keyword = enum 4 | keyPre = "require", 5 | keyPost = "ensure", 6 | keyInv = "invariant", 7 | keyCust = "promise", 8 | keyImpl = "body", 9 | keyNone 10 | 11 | const 12 | keyInvL = keyInv 13 | # keyInvT = keyInv 14 | 15 | ContractKeywordsProc = 16 | [keyPre, keyPost, keyImpl] 17 | ContractKeywordsIter = 18 | [keyPre, keyInvL, keyPost, keyImpl] 19 | ContractKeywordsNormal = 20 | [keyPre, keyInvL, keyPost, keyImpl] 21 | 22 | proc asKeyword(node: NimNode): Keyword = 23 | case $node: 24 | of $keyPre: 25 | result = keyPre 26 | of $keyPost: 27 | result = keyPost 28 | of $keyInv: 29 | result = keyInv 30 | of $keyCust: 31 | result = keyCust 32 | of $keyImpl: 33 | result = keyImpl 34 | else: 35 | result = keyNone 36 | 37 | proc isKeyword(node: NimNode): bool = 38 | node.asKeyword != keyNone 39 | 40 | converter toString(k: Keyword): string = $k 41 | 42 | proc docName(k: Keyword): string = 43 | case k: 44 | of keyPre: "Requires" 45 | of keyPost: "Ensures" 46 | of keyInv: "Invariants" 47 | of keyCust: "Promises" 48 | of keyImpl: "Body" 49 | of keyNone: "" 50 | 51 | proc fieldName(k: Keyword): string = 52 | case k: 53 | of keyPre: "pre" 54 | of keyPost: "post" 55 | of keyInv: "inv" 56 | of keyCust: "cust" 57 | of keyImpl: "impl" 58 | of keyNone: "" 59 | 60 | proc ident(k: Keyword): NimNode {.compileTime.} = 61 | ident("key_" & k.fieldName) 62 | 63 | proc newIdentNode(k: Keyword): NimNode {.compileTime.} = ident(k) 64 | -------------------------------------------------------------------------------- /contracts/declarations/messages.nim: -------------------------------------------------------------------------------- 1 | 2 | let 3 | ContractViolatedStr {.compileTime.} = 4 | "broke '$1' promised at $2" ## \ 5 | ## string to show when a contract is violated 6 | ErrMsgOutsideContractual {.compileTime.} = 7 | "keyword '$1' used outside 'contractual' block" ## \ 8 | ## string to show when contractual keyword is present 9 | ## outside ``contractual`` block 10 | ErrMsgBodyNotFound {.compileTime.} = 11 | "no '$1' part present for contractual $$1" % $keyImpl ## \ 12 | ## string to show when ``body`` part is not present 13 | ## for a contractual entity 14 | ErrMsgChildNotContractBlock {.compileTime.} = 15 | "contractual $1 contains child '$2' which is not contract block" ## \ 16 | ## string to show when contractual callable has 17 | ## a child which is not contract block 18 | ErrMsgWrongUsage {.compileTime.} = 19 | "'$2' used wrongly in contractual $1" ## \ 20 | ## string to show when contractual keyword used was valid 21 | ## but the context was wrong 22 | ErrMsgWrongOrder {.compileTime.} = 23 | "'$2' should be used before '$3' in contractual $1" ## \ 24 | ## string to show when contractual entity has 25 | ## a wrong order child which is not contract block 26 | ErrMsgDuplicate {.compileTime.} = 27 | "'$2' contractual block duplicated in contractual $1" ## \ 28 | ## string to show when contractual entity has 29 | ## reused the same contractual block 30 | ErrInvalidSection {.compileTime.} = 31 | "found parametrized contractual section:\n$1" ## \ 32 | ## string to show when contractual entity has an invalid section 33 | HintMsgCustomContractUsed {.compileTime.} = 34 | "consider using standard contracts instead of '$1' at $$1" % 35 | $keyCust ## \ 36 | ## string to show when ``promise`` block is encountered 37 | -------------------------------------------------------------------------------- /contracts/features.nim: -------------------------------------------------------------------------------- 1 | 2 | include features / quantifiers 3 | include features / ghostCode 4 | 5 | -------------------------------------------------------------------------------- /contracts/features/ghostCode.nim: -------------------------------------------------------------------------------- 1 | # 2 | # Ghost code-related 3 | # 4 | 5 | template ghostly(): untyped = 6 | ## The conditions which should be fulfilled for ghost code 7 | ## to be turned on. 8 | (not defined(release)) and compileOption("assertions") 9 | 10 | template ghost*(code: untyped): untyped = 11 | ## Marks `ghost code`, only used for tests and static analysis. 12 | ## It can be turned off by using the ``-d:release`` mode or 13 | ## turning the ``assertions`` compiler flag to "off" state. 14 | ## Example: 15 | ## 16 | ## .. code-block:: nim 17 | ## ghost: 18 | ## var Gflag: bool = true 19 | ## 20 | ## contractual: 21 | ## proc safeOpen() = 22 | ## require: Gflag 23 | ## ensure: not Gflag 24 | ## body: ... 25 | ## 26 | ## proc safeClose() = 27 | ## require: not Gflag 28 | ## ensure: Gflag 29 | ## body: ... 30 | ## 31 | ## safeOpen() # valid if contracts fulfilled 32 | ## # another safeOpen() would be invalid 33 | ## safeClose() # valid, ready for another safeOpen() 34 | ## 35 | ## As both ghost code and contracts are assertion-lived, 36 | ## release code will be equivalent to: 37 | ## 38 | ## .. code-block:: nim 39 | ## proc safeOpen() = ... 40 | ## proc safeClose() = ... 41 | ## 42 | ## safeOpen() 43 | ## safeClose() 44 | ## 45 | ## Ghost code should only be used in other ghost code, 46 | ## contracts or any other assertion-lived code. 47 | when ghostly: 48 | code 49 | 50 | template ghost*(code1, code2: untyped): untyped = 51 | ## Same as ghost but handles non-ghost context too. 52 | ## Usage discouraged. 53 | when ghostly: 54 | code1 55 | else: 56 | code2 57 | -------------------------------------------------------------------------------- /contracts/features/quantifiers.nim: -------------------------------------------------------------------------------- 1 | # 2 | # Declarative programming 3 | # 4 | 5 | proc getForLoopElems(code: NimNode): (NimNode, NimNode) = 6 | ## Get ``for`` statement's container and element 7 | if code.kind != nnkInfix or code[0] != ident("in") or code.len != 3: 8 | error("`X in Y` notation expected, found: $1".format(code.repr)) 9 | result = (code[1], code[2]) 10 | 11 | proc flattenCondList(cond: NimNode): NimNode = 12 | if cond.kind == nnkStmtList or cond.kind == nnkStmtListExpr: 13 | var newCond = cond[0] 14 | for child in cond[1 ..^ cond.len]: 15 | newCond = infix(newCond, "and", child) 16 | newCond 17 | else: 18 | cond 19 | 20 | macro forsome* (what, cond: untyped): untyped = 21 | ## For-like existential quantifier with natural syntax. 22 | ## It works similar to 23 | ## `sequtils.anyIt `_. 24 | ## 25 | ## .. code-block:: nim 26 | ## require: 27 | ## forsome x in arr: x == key 28 | let (first, second) = getForLoopElems(what) 29 | let newCond = flattenCondList(cond) 30 | template forImpl(fi, sec, cond) = 31 | block: 32 | var flag = false 33 | for fi in sec: 34 | if cond: 35 | flag = true 36 | break 37 | flag 38 | result = getAst(forImpl(first, second, newCond)) 39 | 40 | macro forall* (what, cond: untyped): untyped = 41 | ## For-like universal quantifier with natural syntax. 42 | ## It works similar to 43 | ## `sequtils.allIt `_. 44 | ## 45 | ## .. code-block:: nim 46 | ## require: 47 | ## forall x in arr: x > 0 48 | let (first, second) = getForLoopElems(what) 49 | let newCond = flattenCondList(cond) 50 | template forImpl(fi, sec, cond) = 51 | block: 52 | var flag = true 53 | for fi in sec: 54 | if not cond: 55 | flag = false 56 | break 57 | flag 58 | result = getAst(forImpl(first, second, newCond)) 59 | -------------------------------------------------------------------------------- /contracts/overview.nim: -------------------------------------------------------------------------------- 1 | ## :Author: M. Kotwica 2 | ## This module is used to make contracts – elegant promises that 3 | ## pieces of code will fulfill certain conditions. 4 | ## 5 | ## Contracts are essentially value counterparts of type-based concepts, 6 | ## generic bounds, type attributes (e.x. ``not nil``) etc. While they 7 | ## work similarly to assertions, as they throw exceptions when broken 8 | ## and can be disabled (the flag is actually the same), 9 | ## main reasons for using contracts include elegance of code, clarity 10 | ## of exception messages, intuitive semantics and ease of analysis, 11 | ## both for human and external tools. 12 | ## 13 | ## As usage of this module isn't typical and some extra rules are 14 | ## applied, aside from documentation for each of module's elements 15 | ## the description of the module as a whole is included. 16 | ## 17 | ## Hello contracts 18 | ## =============== 19 | ## For most of the features of this module, ``contractual`` macro should 20 | ## be used. As it generates exception-raising code, some exceptions 21 | ## should be imported too. For now, importing whole module is advised 22 | ## (at least until the submodules are introduced). 23 | ## 24 | ## Example usage (hello world equivalent): 25 | ## 26 | ## .. code-block:: nim 27 | ## import contracts 28 | ## from math import sqrt, floor 29 | ## 30 | ## proc isqrt[T: SomeInteger](x: T): T {.contractual.} = 31 | ## require: 32 | ## x >= 0 33 | ## ensure: 34 | ## result * result <= x 35 | ## (result+1) * (result+1) > x 36 | ## body: 37 | ## (T)(x.toBiggestFloat().sqrt().floor().toBiggestInt()) 38 | ## 39 | ## echo isqrt(18) # prints 4 40 | ## 41 | ## echo isqrt(-8) # runtime error: 42 | ## # broke 'x >= 0' promised at FILE.nim(LINE,COLUMN) 43 | ## # [PreConditionDefect] 44 | ## 45 | ## Overview 46 | ## ======== 47 | ## It is advised to use contracts as part of a fine documentation. 48 | ## Even when disabled by a switch or pragma, they still look good 49 | ## and describe one's intentions well. Thanks to that, the code is 50 | ## often much easier to read even without comments. Also, a reader 51 | ## experienced in contractual design (and prefferably Nim) can read 52 | ## them faster than natural language as they are more compact. 53 | ## 54 | ## Consider finding key's position in a sorted array: 55 | ## 56 | ## .. code-block:: nim 57 | ## contractual: 58 | ## var a = 0 59 | ## var b = arr.len-1 60 | ## while a < b: 61 | ## invariant: 62 | ## if key in arr: key in arr[a..b] 63 | ## abs(a - b) < `abs(a - b)` 64 | ## ensure: 65 | ## if key in arr: key == arr[a] 66 | ## body: 67 | ## var mid = (a + b) div 2 68 | ## if arr[mid] < key: 69 | ## a = mid + 1 70 | ## else: 71 | ## b = mid 72 | ## 73 | ## The first loop invariant is rather obvious. The other one is also 74 | ## intuitive if we know this module uses '`' character meaning roundly 75 | ## "previously" (in last iteration or yield). Actually, we could 76 | ## strengthen our invariants by adding information about how fast 77 | ## the range shrinks but it's not necessary to prove the algorithm 78 | ## works (although it is needed to prove how efficient it is). 79 | ## 80 | ## While contracts can be of great help, they require care, especially 81 | ## when object-oriented behaviour is expected. For present version, 82 | ## one has to follow certain rules described in `Object-Oriented Contracts`_ 83 | ## section (in the future they will be followed automatically). 84 | ## 85 | ## Contractual context 86 | ## =================== 87 | ## As already mentioned, for most of this module's features to actually 88 | ## work, the code has to be inside of a ``contractual`` block. 89 | ## For callables, it can be applied as a pragma. 90 | ## 91 | ## Syntax: 92 | ## 93 | ## .. code-block:: nim 94 | ## contractual: 95 | ## ... 96 | ## 97 | ## proc ... {.contractual.} = ... 98 | ## 99 | ## There are several Contractual Contexts (CoCo) inside of ``contractual`` 100 | ## block. They are bound to plain Nim code blocks, so no additional marking 101 | ## is needed. CoCo include: 102 | ## * `proc` is ``procedure``, ``converter`` or ``method`` declaration 103 | ## * `loop` is ``iterator`` declaration or either ``for`` or ``while`` loop 104 | ## * `type` is ``type`` declaration (to be used in future versions) 105 | ## 106 | ## It is advised, although not needed, to use ``contractual`` block 107 | ## for whole modules and any modules that utilize them. 108 | ## 109 | ## 110 | ## Context keywords 111 | ## ================ 112 | ## ========== ================= ================= ==== ==== ==== 113 | ## Keyword Related to Semantics proc loop type 114 | ## ========== ================= ================= ==== ==== ==== 115 | ## require PreCondition is needed yes yes no 116 | ## invariant Invariant doesn't change no yes yes 117 | ## ensure PostCondition is provided yes yes no 118 | ## body implementation how it is done yes yes no 119 | ## ========== ================= ================= ==== ==== ==== 120 | ## 121 | ## Sections should be used in the same order as in the table above. 122 | ## 123 | ## There is also ``promise`` keyword (related to CustomContract) which 124 | ## describes any conditions and as the only one doesn't require being 125 | ## used in ``contractual``. Its usage discouraged if any other CoCo 126 | ## would do as its less descriptive and have no additional rules to it 127 | ## but for the assertion. 128 | ## 129 | ## Other features 130 | ## ============== 131 | ## This module also includes a few non-context tools: 132 | ## - ``ghost`` block of code active only when contracts are turned on 133 | ## - ``forall`` and ``forsome`` represent quantifiers 134 | ## - ``assume`` represent assumptions for human reader and external tools 135 | ## (no code generated) 136 | ## - ``promise`` makes custom contracts 137 | ## 138 | ## Documentation 139 | ## ============= 140 | ## Contracts can be treated as a part of the signature of the routine 141 | ## or type associated with it. Due to that, this module can generate 142 | ## additional code block for the entity's documentation so that the code 143 | ## would blend in with the signature. 144 | ## 145 | ## Example code: 146 | ## 147 | ## .. code-block:: nim 148 | ## proc isqrt[T: SomeInteger](x: T): T {.contractual.} = 149 | ## ## Integer square root function. 150 | ## require: 151 | ## x >= 0 152 | ## ensure: 153 | ## result >= 0 154 | ## result * result <= x 155 | ## (result+1) * (result+1) > x 156 | ## body: 157 | ## (T)(x.toBiggestFloat().sqrt().floor().toBiggestInt()) 158 | ## 159 | ## Generated docs: 160 | ## 161 | ## .. code-block:: nim 162 | ## proc isqrt[T: SomeInteger](x: T): T 163 | ## requires: 164 | ## x >= 0 165 | ## ensures: 166 | ## result >= 0 167 | ## result * result <= x 168 | ## (result+1) * (result+1) > x 169 | ## Integer square root function. 170 | ## 171 | ## Doc comments can be added anywhere between contractual sections, 172 | ## they will be treated just like when written in one place. 173 | ## 174 | ## Contracts docs generation can be disabled using `noContractsDocs` 175 | ## compilation flag. 176 | ## 177 | ## Diagnostics 178 | ## =========== 179 | ## To diagnose contracts, use `explainContracts` compile flag. 180 | ## It provides diagnostic informations about contracts, according to its 181 | ## numerical value: 182 | ## - 0. None (default) --- doesn't show any diagnostics 183 | ## - 1. Output (when flag defined but has no value) --- show final code 184 | ## - 2. Basic --- show both source and final code 185 | ## - 3. Verbose --- as Basic + show each type 186 | ## of contract detected for the entity (directly or indirectly). 187 | ## 188 | ## 189 | ## Future 190 | ## ====== 191 | ## 192 | ## Exception awareness 193 | ## ------------------- 194 | ## As for now, contracts cannot refer to exceptions, although it is planned 195 | ## for future versions to enable such a feature. In core Nim, it is possible 196 | ## to inform about what types of exceptions can be throw in the callable. 197 | ## However, there is no possibility to express what conditions should be 198 | ## met for these exceptions to be thrown. Additionally, it should be 199 | ## possible to specify what conditions will be true if an exception raises 200 | ## (notice they are not necessarily the same conditions). 201 | ## 202 | ## Object-Oriented Contracts 203 | ## ------------------------- 204 | ## New context `type` is planned for future versions. With it's introduction 205 | ## both `proc` and `loop` contexts will be changed to force object-oriented 206 | ## rules. For now, user should follow them manually. 207 | ## 208 | ## ============== ============ ============= 209 | ## contract containing inheritance 210 | ## ============== ============ ============= 211 | ## type invariant added ? 212 | ## pre-condition added alternative 213 | ## loop invariant added added 214 | ## post-condition added added 215 | ## ============== ============ ============= 216 | -------------------------------------------------------------------------------- /development: -------------------------------------------------------------------------------- 1 | TODO: 2 | * fix iterator code generation bug (where code is injected) 3 | * add type invariants 4 | * more oop for docs generation 5 | 6 | DONE: 7 | * separate object for context handlers' data 8 | * simplify `getOldValues` (always Var, `boundedYet` handled separately) 9 | * fix loop code generation bug (where code is injected) 10 | * add docs generation 11 | -------------------------------------------------------------------------------- /tests/isqrt.nim: -------------------------------------------------------------------------------- 1 | import math 2 | import macros 3 | import unittest 4 | import contracts 5 | 6 | 7 | template testInt(T: typed, code: untyped): untyped = 8 | const MAX_VAL = (T)(sqrt(high(T).toBiggestFloat)-2) 9 | for i {.inject.} in 1.T .. MAX_VAL: 10 | code 11 | 12 | macro testInts(types: typed, code: untyped): untyped = 13 | result = newStmtList() 14 | for typ in types: 15 | result.add getAst(testInt(typ, code)) 16 | 17 | template testAllInts(code: untyped): untyped = 18 | testInts([int8,int16], code) 19 | 20 | 21 | suite "isqrt with floor": 22 | proc isqrt[T: SomeInteger](x: T): T {.contractual.} = 23 | require: 24 | x >= 0 25 | ensure: 26 | result * result <= x 27 | (result+1) * (result+1) > x 28 | body: 29 | (T)(x.toBiggestFloat().sqrt().floor().toBiggestInt()) 30 | 31 | test "exact squares": 32 | testAllInts: 33 | check isqrt(i*i) == i 34 | 35 | test "between squares": 36 | testAllInts: 37 | for j in i^2+1 .. (i+1)^2-1: 38 | check isqrt(j) == i 39 | 40 | suite "isqrt with round": 41 | proc isqrt[T: SomeInteger](x: T): T {.contractual.} = 42 | require: 43 | x >= 0 44 | ensure: 45 | result * result <= x 46 | (result+1) * (result+1) > x 47 | body: 48 | (T)(x.toBiggestFloat().sqrt().round().toBiggestInt()) 49 | 50 | test "exact squares": 51 | testAllInts: 52 | check isqrt(i*i) == i 53 | 54 | test "between squares (postcondition broken)": 55 | testAllInts: 56 | for j in i^2+1 .. (i+1)^2-1: 57 | expect PostConditionDefect: 58 | if isqrt(j) == i: # either it throws or the result is ok 59 | raise newException(PostConditionDefect, "") 60 | 61 | suite "isqrt with cast": 62 | proc isqrt[T: SomeInteger](x: T): T {.contractual.} = 63 | require: 64 | x >= 0 65 | ensure: 66 | result * result <= x 67 | (result+1) * (result+1) > x 68 | body: 69 | (T)(x.toBiggestFloat().sqrt().toBiggestInt()) 70 | 71 | test "exact squares": 72 | testAllInts: 73 | check isqrt(i*i) == i 74 | 75 | test "between squares": 76 | testAllInts: 77 | expect PostConditionDefect: 78 | for j in i^2+1 .. (i+1)^2-1: 79 | check isqrt(j) == i 80 | --------------------------------------------------------------------------------