├── .gitignore ├── LICENSE ├── README.md ├── examples ├── README.md ├── basic.nim ├── basic_static.nim ├── benchmark_1.nim ├── benchmark_2.nim ├── benchmark_3.nim ├── generic.nim ├── inheritance_1.nim ├── inheritance_2.nim ├── injection_1.nim ├── injection_2.nim ├── interface.nim └── nestedsuper.nim ├── nimcls.nimble ├── src ├── nimcls.nim └── nimcls │ ├── builder.nim │ ├── classobj.nim │ ├── clsmacro.nim │ ├── common.nim │ └── di.nim └── tests ├── classes.nim ├── classes_generic.nim ├── classes_static.nim ├── config.nims ├── injection.nim ├── injection_static.nim ├── macro.nim └── mock.nim /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | vc140.pdb 4 | *.exe 5 | bin -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Yaser A 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 all 13 | 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 THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NimCLS - Nim Classes & Dependency Injection Library 2 | 3 | Classes' macro, interfaces' macro and a lightweight dependency injection library for the Nim programming language, designed to help easily create and manage dependency injection by using classes and interfaces. 4 | 5 | ## Features 6 | 7 | - **Classes**: Create Nim objects using a simple syntax, similar to Python. 8 | - **Interfaces**: Define interfaces using a concise syntax. 9 | - **Debugging and Inspection Methods**: Make development easier with methods that help debug and inspect class objects. 10 | - **Superclass Invocation**: Effortlessly call the super class's methods, procedures and functions of an object. 11 | - **Dependency Injection**: Easily inject dependencies into your code and reduce boilerplate code. 12 | - **Singleton Management**: Manage and create singleton instances of classes. 13 | - **Custom Injectors**: Create custom injectors to manage dependencies just the way you need. 14 | - **Pass Injection**: Pass dependencies through constructors, procedures, functions and methods parameters. 15 | - **Minimal Overhead**: Keep your application lightweight with NimCLS's minimal overhead (Zero Dependencies). 16 | 17 | ## Usage 18 | 19 | ### Installing NimCLS 20 | 21 | You can install NimCLS via Nim's package manager, Nimble: 22 | 23 | ```bash 24 | nimble install nimcls 25 | ``` 26 | 27 | ### Basic Usage 28 | 29 | 1. Define your classes: 30 | 31 | ```nim 32 | import nimcls 33 | 34 | Class MyClass: 35 | var number: int = 12 36 | var config: string 37 | method call(self: MyClass) : string {.base.} = 38 | return "Class" 39 | ``` 40 | 41 | 2. Set up an instance of the class and register it: 42 | 43 | ```nim 44 | 45 | let myClassObj = MyClass() 46 | addSingleton(myClassObj) 47 | 48 | ``` 49 | 50 | 3. Retrieve the created instance with injected dependencies: 51 | 52 | ```nim 53 | 54 | proc run(obj : MyClass = inject(MyClass)) = 55 | obj.call 56 | 57 | ``` 58 | 59 | ### Example 60 | 61 | ```nim 62 | import nimcls 63 | 64 | Class Parent: 65 | var value: int = 10 66 | proc getValue(self: Parent): int = 67 | return self.value 68 | method hello(self: Parent) {.base.} = 69 | echo "Hello!! Parent" 70 | 71 | Class Child(Parent): 72 | proc getNewValue(self: Child): int = 73 | return self.value + 10 74 | method hello(self: Child) = 75 | echo "Hello!! Child" 76 | 77 | proc callHello(classObject: Parent = inject(Parent)) = 78 | classObject.hello 79 | 80 | let createChild = proc(): Child = Child() 81 | addInjector(Parent, createChild) 82 | 83 | ## prints "Hello!! Child" 84 | callHello() 85 | 86 | ``` 87 | 88 | ### Classes Usage 89 | 90 | #### Overview 91 | 92 | ```nim 93 | import nimcls 94 | 95 | # ClassObj is RootObj 96 | Class MyClass ## type MyClass = ref object of ClassObj 97 | Class static NextClass ## type NextClass = object of ClassObj 98 | Class OtherClass(MyClass) ## type MyClass = ref object of MyClass 99 | Class static OtherNextClass(NextClass) ## type OtherNextClass = object of NextClass 100 | Class TryGeneric[K,V] ## type TryGeneric[K,V] = ref object of ClassObj 101 | 102 | 103 | Class GenericClass[T]: 104 | var value: T 105 | ## 106 | ## Translated to: 107 | ## type GenericClass[T] 108 | ## = ref object of ClassObj 109 | ## value: T 110 | ``` 111 | 112 | 113 | There are two types of classes that can be used: 114 | 115 | 1- Regular Class (Nim's `ref object`) 116 | 117 | ```nim 118 | import nimcls 119 | 120 | Class Person: 121 | var name: string 122 | method hello(self: Person) {.base.} = 123 | echo "Hello!! my name is " & self.name 124 | 125 | let person = Person() 126 | person.name = "Nim Dev" 127 | ``` 128 | 129 | 2- Static Class (Nim's `object`) 130 | 131 | ```nim 132 | import nimcls 133 | 134 | Class static Person: 135 | var name: string = "" 136 | proc hello(self: Person) = 137 | echo "Hello!! my name is " & self.name 138 | 139 | let person = Person(name: "Other Name") 140 | # Error: properties cannot be updated 141 | # person.name = "Nim Dev" 142 | ``` 143 | 144 | **What are the differences:** 145 | 146 | 1. ***ref object***: 147 | 148 | - A ref object is a reference type. When you assign a ref object to another variable or pass it as a parameter to a procedure, you're passing a reference to the same object. 149 | - ref object types are typically used for larger, mutable data structures where you want reference semantics, such as classes in other programming languages. 150 | - They are allocated on the heap, and their memory is managed by the garbage collector. They are automatically deallocated when there are no more references to them. 151 | 152 | 2. ***object***: 153 | 154 | - An object is a value type. When you assign an object to another variable or pass it as a parameter to a procedure, a copy of the object is made. 155 | - object types are typically used for small, immutable data structures where you want value semantics, such as structs in other programming languages. 156 | - They are allocated on the stack, and their memory is automatically deallocated when they go out of scope. 157 | 158 | ### Conditional Statements 159 | 160 | ```nim 161 | import nimcls 162 | 163 | # When statement example 164 | Class TryGeneric[T]: 165 | var value: T 166 | when T is string: 167 | var length: int 168 | var user: string 169 | when T is int: 170 | discard 171 | else: 172 | var name: string 173 | 174 | 175 | # Case statement example 176 | Class Next: 177 | var input: string 178 | switch myChar: char : 179 | of 'A': 180 | var aVal: int 181 | of 'Z': 182 | var zVal: string 183 | else: 184 | var val: float 185 | 186 | ``` 187 | 188 | ### Interface Usage 189 | 190 | ```nim 191 | import nimcls 192 | 193 | Interface IRunner: 194 | method run(self: IRunner) 195 | method getFilePath(self: IRunner): string 196 | method isRunning(self: IRunner): bool 197 | ``` 198 | 199 | #### Interface Macro Rules: 200 | 1. An interface cannot have a parent class/object. 201 | 2. An interface cannot be a generic object. 202 | 2. An interface must have at least one method. 203 | 3. Only ***methods*** can be added to the interface. 204 | 205 | 206 | ### Pragmas Usage 207 | 208 | ```nim 209 | import nimcls 210 | # Example 1 211 | Class myFinalClass {.final.} 212 | 213 | # Example 2 214 | Class NextClass {.requiresInit, borrow: `.`.} : 215 | var 216 | name: string 217 | id: int 218 | 219 | ``` 220 | 221 | 222 | ### Injection Usage 223 | 224 | There are two ways to register an injection: 225 | 226 | 1. Create an instance of the class and register it using `addSingleton` procedure. 227 | 2. Create a procedure which returns a class object and register it using `addInjector` procedure. 228 | 229 | After registering an injection, call `inject` and pass the registered type to get the object. 230 | 231 | #### Examples 232 | 233 | 1. Adding a singleton: 234 | 235 | - *addSingleton[T]* 236 | 237 | ```nim 238 | ## only this instance of the class "MyClass" will be used. 239 | let myClassObj = MyClass() 240 | addSingleton(myClassObj) 241 | ``` 242 | 243 | - *addSingleton[R,T]* 244 | 245 | ```nim 246 | ## only this instance of the class "ChildClass" will be used 247 | ## but it will be registered as "ParentClass". 248 | ## "ChildClass" must be a subclass of "ParentClass". 249 | let myClassObj = MyClass() 250 | addSingleton(ParentClass, myClassObj) 251 | ``` 252 | 253 | 2. Adding a procedure: 254 | 255 | - *addInjector[T]* 256 | 257 | ```nim 258 | ## A procedure that builds an instance of "ChildClass" 259 | proc createChild() : ChildClass = 260 | let child = ChildClass() 261 | child.init("configurations") 262 | return child 263 | ## Each time "ChildClass" is injected, "createChild" will be called 264 | ## and a new instance will be created 265 | addInjector(createChild) 266 | ``` 267 | 268 | - *addInjector[R,T]* 269 | 270 | ```nim 271 | ## A procedure that builds an instance of "ChildClass" 272 | proc createChild() : ChildClass = 273 | let child = ChildClass() 274 | child.init("configuration") 275 | return child 276 | ## Each time "ParentClass" is injected, "createChild" will be called 277 | ## and a new instance will be created 278 | ## "ChildClass" must be a subclass of "ParentClass". 279 | addInjector(ParentClass , createChild) 280 | ``` 281 | 282 | 3. Injecting: 283 | 284 | - *Passing it as a parameter* 285 | 286 | ```nim 287 | ## A procedure that has a parameter of type "ChildClass" 288 | proc runChild(child: ChildClass = inject(ChildClass) ) = 289 | child.run 290 | 291 | ``` 292 | 293 | - *Passing it to a constructor* 294 | 295 | ```nim 296 | Class static User: 297 | var tools : Tools 298 | 299 | let user = User(tools: inject(Tools)) 300 | ``` 301 | 302 | - *Getting it through a call* 303 | 304 | ```nim 305 | ## A procedure that makes inject call 306 | proc runChild() = 307 | let child = inject(ChildClass) 308 | child.run 309 | 310 | ``` 311 | 312 | ## Limitations 313 | 314 | 1. **Constructors**: In the Nim programming language, objects cannot be created with a custom constructor. 315 | 2. **Constants**: Nim programming language does not support constant properties for objects. So, `let` and `const` cannot be used for **classes' properties**. 316 | 3. **Exporting classes with no parent**: While it is simple to export any class using the asterisk (`*`) symbol, Nim's compiler doesn't permit the following: 317 | 318 | ```nim 319 | # causes a syntax error !?!? 320 | Class MyClass*: 321 | var me: string 322 | ``` 323 | 324 | However, you can solve this issue in **two ways**: 325 | 326 | ```nim 327 | # Exported and Runs! 328 | Class *MyClass: 329 | var me: string 330 | ``` 331 | 332 | OR 333 | 334 | ```nim 335 | # All classes are subclasses of "ClassObj" 336 | Class MyClass*(ClassObj): 337 | var me: string 338 | ``` 339 | 340 | 4. **Super class calls**: Calling a super class's methods is easy using the class's method `super` but `procCall` must be used to make the call. 341 | 342 | ```nim 343 | # Not working, it will call the child object 'init' method 344 | child.super.init 345 | 346 | # Works, it will call the super class method 347 | procCall child.super.init 348 | ``` 349 | 350 | 5. **Methods, Procedures and functions**: The class's macro only allows methods, procedures, and functions that utilize the class's object as their **first parameter** within the class's body. By keeping unrelated methods, procedures, and functions outside of classes, we ensure their focus remains on their intended functionality. Placing them within a class may introduce unnecessary complexity, making the code harder to understand and maintain. 351 | 352 | 6. **Objects casting**: Nim's limitations regarding **non-ref objects** casting, introduce challenges for dependency injection. These limitations elevate the risk of data loss, raise concerns about type safety, complicate debugging, and may result in unexpected output and errors when accessing object fields post-downcasting due to the possibility of encountering invalid addresses. 353 | 354 | ## Methods and Procedures 355 | 356 | - Each class's object has the following methods or procedures : 357 | 358 | 359 | | Name | Arguments | Returns | Description | 360 | |----------------------|-----------|--------------------------------|------------------------------------------------------------------------------------| 361 | | `getClassName` | ─ | `string` | Returns the class's name as a`string`. | 362 | | `getClassCalls` | ─ | `seq[string]` | Returns the class's procedures, functions and methods in a sequence of`string`. | 363 | | `getClassProperties` | ─ | `seq[string]` | Returns the class's properties in a sequence of`string`. | 364 | | `getParentClassName` | ─ | `string` | Returns the class's parent's class name as a`string`. | 365 | | `super` | ─ | `ClassObj` | Upcasts the object and returns it. | 366 | 367 | - Dependency injection's procedures : 368 | 369 | 370 | | Name | Argument 1 | Argument 2 | Returns | Description | 371 | |---------------------|--------------------------------------------------|------------------------|---------|-------------------------------------------------------------------------------------------------------| 372 | | `addSingleton[T]` | `ClassObj` or `ref ClassObj` | ─ | ─ | Adds a singleton object of type`T` to the injection table and uses `T` as key for it. | 373 | | `addSingleton[R,T]` | `typedesc[ref ClassObj]` | `ref ClassObj` | ─ | Adds a singleton object of type`T` to the injection table and uses `R` as key for it. | 374 | | `addInjector[T]` | `proc(): ClassObj` or `proc(): ref ClassObj` | ─ | ─ | Adds a procedure that returns an object of type`T` to the injectors table and uses `T` as key for it. | 375 | | `addInjector[R,T]` | `typedesc[ref ClassObj]` | `proc(): ref ClassObj` | ─ | Adds a procedure that returns an object of type`T` to the injectors table and uses `R` as key for it. | 376 | | `inject[T]` | `typedesc[ClassObj]` or `typedesc[ref ClassObj]` | ─ | `T` | Returns an object of type`T` which exists in the injection tables. | 377 | | `isInjectable[T]` | `typedesc[ClassObj]` or `typedesc[ref ClassObj]` | ─ | `bool` | Returns`true` if an object or a procedure of type `T` exists in the tables otherwise `false`. | 378 | | `isSingleton[T]` | `typedesc[ClassObj]` or `typedesc[ref ClassObj]` | ─ | `bool` | Returns`true` if an object of type `T` exists in the injection table otherwise `false`. | 379 | | `resetInjectTbl` | ─ | ─ | ─ | Resets and removes all entries in the injection and injectors tables. | 380 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | 4 | 5 | | Name | Description | 6 | |---------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| 7 | | [basic.nim](https://github.com/YaDev/NimCLS/blob/master/examples/basic.nim) | A basic example for creating a class. | 8 | | [basic_static.nim](https://github.com/YaDev/NimCLS/blob/master/examples/basic_static.nim) | A basic example for creating a static class. | 9 | | [inheritance_1.nim](https://github.com/YaDev/NimCLS/blob/master/examples/inheritance_1.nim) | A basic example for creating a class and a subclass. | 10 | | [generic.nim](https://github.com/YaDev/NimCLS/blob/master/examples/generic.nim) | An example for creating generic classes. | 11 | | [interface.nim](https://github.com/YaDev/NimCLS/blob/master/examples/interface.nim) | An example for creating nad using an interface. | 12 | | [inheritance_2.nim](https://github.com/YaDev/NimCLS/blob/master/examples/inheritance_2.nim) | A basic example for creating a static class and a subclass. | 13 | | [injection_1.nim](https://github.com/YaDev/NimCLS/blob/master/examples/injection_1.nim) | An example for dependency injection using `addSingleton`. | 14 | | [injection_2.nim](https://github.com/YaDev/NimCLS/blob/master/examples/injection_2.nim) | An example for dependency injection using `addSingleton` and `addInjector`. | 15 | | [nestedsuper.nim](https://github.com/YaDev/NimCLS/blob/master/examples/nestedsuper.nim) | An example for calling super class method and procedure. | 16 | | [benchmark_1.nim](https://github.com/YaDev/NimCLS/blob/master/examples/benchmark_1.nim) | A simple comparison of static and dynamic dispatch using classes and DI. | 17 | | [benchmark_2.nim](https://github.com/YaDev/NimCLS/blob/master/examples/benchmark_2.nim) | A simple comparison of `object` and `ref object` creation cost. | 18 | | [benchmark_3.nim](https://github.com/YaDev/NimCLS/blob/master/examples/benchmark_3.nim) | A simple comparison of procedures calls vs objects's procedures calls. | -------------------------------------------------------------------------------- /examples/basic.nim: -------------------------------------------------------------------------------- 1 | import ../src/nimcls 2 | 3 | Class MyClass: 4 | var one: int = 12 5 | var two: string 6 | method first(self: MyClass) : string {.base.} = 7 | return "" 8 | method second(self: MyClass) : float {.base.} = 9 | return 1.0 10 | 11 | let basic = MyClass() 12 | echo basic.getClassName & " methods :" 13 | echo basic.getClassCalls 14 | echo basic.getClassName & " properties :" 15 | echo basic.getClassProperties 16 | echo basic.getClassName & " parent class's name :" 17 | echo basic.getParentClassName -------------------------------------------------------------------------------- /examples/basic_static.nim: -------------------------------------------------------------------------------- 1 | import ../src/nimcls 2 | 3 | Class static MyClass: 4 | var one: int = 12 5 | var two: string 6 | method first(self: MyClass) : string {.base.} = 7 | return "" 8 | method second(self: MyClass) : float {.base.} = 9 | return 1.0 10 | 11 | let basic = MyClass(two: "a string") 12 | echo "one value: " & $basic.one 13 | echo "two value: " & basic.two 14 | echo basic.getClassName & " methods :" 15 | echo basic.getClassCalls 16 | echo basic.getClassName & " propearties :" 17 | echo basic.getClassProperties 18 | echo basic.getClassName & " parent class's name :" 19 | echo basic.getParentClassName 20 | echo "Calling method `second`: " & $basic.second -------------------------------------------------------------------------------- /examples/benchmark_1.nim: -------------------------------------------------------------------------------- 1 | import ../src/nimcls 2 | import times 3 | import strutils 4 | 5 | 6 | proc factorial_1(number : int) : int64 = 7 | if number == 1: 8 | return number 9 | else: 10 | return number*factorial_1(number-1) 11 | 12 | proc factorial_2(number : int) : int64 = 13 | if number == 1: 14 | return number 15 | else: 16 | return number*factorial_1(number-1) 17 | 18 | proc factorial_3(number : int) : int64 = 19 | if number == 1: 20 | return number 21 | else: 22 | return number*factorial_2(number-1) 23 | 24 | 25 | 26 | Class F1: 27 | method factorial(self: F1, number : int) : int64 {.base.} = 28 | if number == 1: 29 | return number 30 | else: 31 | return number*self.factorial(number-1) 32 | 33 | Class F2: 34 | var nextone: F1 35 | method factorial(self: F2, number : int) : int64 {.base.} = 36 | #let nextone = inject(F1) 37 | if number == 1: 38 | return number 39 | else: 40 | return number*self.nextone.factorial(number-1) 41 | 42 | Class F3: 43 | var nextone: F2 44 | method factorial(self: F3, number : int) : int64 {.base.} = 45 | #let nextone = inject(F2) 46 | if number == 1: 47 | return number 48 | else: 49 | return number*self.nextone.factorial(number-1) 50 | 51 | 52 | ## Variables ## 53 | 54 | 55 | const value: int = 20 56 | const loops_1: int = 50 57 | const loops_2: int = 1000000 58 | var runTimes: seq[float] = @[] 59 | var sumTime: float = 0.0 60 | 61 | 62 | 63 | ## dynamic dispatch ## 64 | 65 | let f1 : F1 = F1() 66 | addSingleton(f1) 67 | let f2 : F2 = F2(nextone: inject(F1)) 68 | addSingleton(f2) 69 | let f3 : F3 = F3(nextone: inject(F2)) 70 | 71 | for i in countup(0, loops_1): 72 | let start = cpuTime() 73 | for i in countup(1, loops_2): 74 | discard f1.factorial(value) 75 | discard f2.factorial(value) 76 | discard f3.factorial(value) 77 | let endTime = cpuTime() 78 | echo "Time: " & (endTime - start).formatFloat(ffDecimal, 5) 79 | runTimes.add(endTime - start) 80 | 81 | for j in runTimes: 82 | sumTime += j 83 | 84 | echo "Dynamic dispatch" 85 | echo "Mean Time: " & (sumTime/runTimes.len.float).formatFloat(ffDecimal, 5) 86 | echo "" 87 | 88 | 89 | 90 | 91 | ## Static dispatch ## 92 | 93 | 94 | runTimes.setLen(0) 95 | 96 | for i in countup(0, loops_1): 97 | let start = cpuTime() 98 | for i in countup(1, loops_2): 99 | discard factorial_1(value) 100 | discard factorial_2(value) 101 | discard factorial_3(value) 102 | let endTime = cpuTime() 103 | echo "Time: " & (endTime - start).formatFloat(ffDecimal, 5) 104 | runTimes.add(endTime - start) 105 | 106 | sumTime = 0.0 107 | for j in runTimes: 108 | sumTime += j 109 | 110 | echo "Static dispatch" 111 | echo "Mean Time: " & (sumTime/runTimes.len.float).formatFloat(ffDecimal, 5) 112 | echo "" -------------------------------------------------------------------------------- /examples/benchmark_2.nim: -------------------------------------------------------------------------------- 1 | import ../src/nimcls 2 | import times 3 | import strutils 4 | 5 | 6 | 7 | Class F1: 8 | method factorial(self: F1, number : int) : int64 {.base.} = 9 | if number == 1: 10 | return number 11 | else: 12 | return number*self.factorial(number-1) 13 | 14 | Class F2: 15 | method factorial(self: F2, number : int, nextone: F1 = inject(F1)) : int64 {.base.} = 16 | if number == 1: 17 | return number 18 | else: 19 | return number*nextone.factorial(number-1) 20 | 21 | Class F3: 22 | method factorial(self: F3, number : int, nextone: F2 = inject(F2)) : int64 {.base.} = 23 | if number == 1: 24 | return number 25 | else: 26 | return number*nextone.factorial(number-1) 27 | 28 | 29 | Class static S1: 30 | method factorial(self: S1, number : int) : int64 {.base.} = 31 | if number == 1: 32 | return number 33 | else: 34 | return number*self.factorial(number-1) 35 | 36 | Class static S2: 37 | method factorial(self: S2, number : int, nextone: S1 = inject(S1)) : int64 {.base.} = 38 | if number == 1: 39 | return number 40 | else: 41 | return number*nextone.factorial(number-1) 42 | 43 | Class static S3: 44 | method factorial(self: S3, number : int, nextone: S2 = inject(S2)) : int64 {.base.} = 45 | if number == 1: 46 | return number 47 | else: 48 | return number*nextone.factorial(number-1) 49 | 50 | 51 | ## Variables ## 52 | 53 | const value: int = 20 54 | const loops_1: int = 50 55 | const loops_2: int = 1000000 56 | var runTimes: seq[float] = @[] 57 | var sumTime: float = 0.0 58 | 59 | 60 | 61 | ## ref objects ## 62 | 63 | addInjector(proc(): F1 = F1()) 64 | addInjector(proc(): F2 = F2()) 65 | 66 | let f1 : F1 = F1() 67 | let f2 : F2 = F2() 68 | let f3 : F3 = F3() 69 | 70 | 71 | for i in countup(0, loops_1): 72 | let start = cpuTime() 73 | for i in countup(1, loops_2): 74 | discard f1.factorial(value) 75 | discard f2.factorial(value) 76 | discard f3.factorial(value) 77 | let endTime = cpuTime() 78 | echo "Time: " & (endTime - start).formatFloat(ffDecimal, 5) 79 | runTimes.add(endTime - start) 80 | 81 | for j in runTimes: 82 | sumTime += j 83 | 84 | echo "Ref objects creation and calls" 85 | echo "Mean Time: " & (sumTime/runTimes.len.float).formatFloat(ffDecimal, 5) 86 | echo "" 87 | 88 | 89 | runTimes.setLen(0) 90 | 91 | 92 | ## objects ## 93 | 94 | addInjector(proc(): S1 = S1()) 95 | addInjector(proc(): S2 = S2()) 96 | 97 | let s1 : S1 = S1() 98 | let s2 : S2 = S2() 99 | let s3 : S3 = S3() 100 | 101 | 102 | for i in countup(0, loops_1): 103 | let start = cpuTime() 104 | for i in countup(1, loops_2): 105 | discard s1.factorial(value) 106 | discard s2.factorial(value) 107 | discard s3.factorial(value) 108 | let endTime = cpuTime() 109 | echo "Time: " & (endTime - start).formatFloat(ffDecimal, 5) 110 | runTimes.add(endTime - start) 111 | 112 | sumTime = 0.0 113 | for j in runTimes: 114 | sumTime += j 115 | 116 | echo "Objects creation and calls" 117 | echo "Mean Time: " & (sumTime/runTimes.len.float).formatFloat(ffDecimal, 5) 118 | echo "" 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /examples/benchmark_3.nim: -------------------------------------------------------------------------------- 1 | import ../src/nimcls 2 | import times 3 | import strutils 4 | 5 | 6 | proc factorial_1(number : int) : int64 = 7 | if number == 1: 8 | return number 9 | else: 10 | return number*factorial_1(number-1) 11 | 12 | proc factorial_2(number : int) : int64 = 13 | if number == 1: 14 | return number 15 | else: 16 | return number*factorial_1(number-1) 17 | 18 | proc factorial_3(number : int) : int64 = 19 | if number == 1: 20 | return number 21 | else: 22 | return number*factorial_2(number-1) 23 | 24 | 25 | 26 | Class static F1: 27 | proc factorial(self: F1, number : int) : int64 = 28 | if number == 1: 29 | return number 30 | else: 31 | return number*self.factorial(number-1) 32 | 33 | Class static F2: 34 | var nextone: F1 35 | proc factorial(self: F2, number : int) : int64 = 36 | if number == 1: 37 | return number 38 | else: 39 | return number*self.nextone.factorial(number-1) 40 | 41 | Class static F3: 42 | var nextone: F2 43 | proc factorial(self: F3, number : int) : int64 = 44 | if number == 1: 45 | return number 46 | else: 47 | return number*self.nextone.factorial(number-1) 48 | 49 | 50 | ## Variables ## 51 | 52 | 53 | const value: int = 20 54 | const loops_1: int = 50 55 | const loops_2: int = 1000000 56 | var runTimes: seq[float] = @[] 57 | var sumTime: float = 0.0 58 | 59 | 60 | 61 | ## Classes' procedures calls ## 62 | 63 | let f1 : F1 = F1() 64 | addSingleton(f1) 65 | let f2 : F2 = F2(nextone: inject(F1)) 66 | addSingleton(f2) 67 | let f3 : F3 = F3(nextone: inject(F2)) 68 | 69 | for i in countup(0, loops_1): 70 | let start = cpuTime() 71 | for i in countup(1, loops_2): 72 | discard f1.factorial(value) 73 | discard f2.factorial(value) 74 | discard f3.factorial(value) 75 | let endTime = cpuTime() 76 | echo "Time: " & (endTime - start).formatFloat(ffDecimal, 5) 77 | runTimes.add(endTime - start) 78 | 79 | for j in runTimes: 80 | sumTime += j 81 | 82 | echo "Classes' procedures calls" 83 | echo "Mean Time: " & (sumTime/runTimes.len.float).formatFloat(ffDecimal, 5) 84 | echo "" 85 | 86 | 87 | 88 | 89 | ## Independent procedures calls ## 90 | 91 | 92 | runTimes.setLen(0) 93 | 94 | for i in countup(0, loops_1): 95 | let start = cpuTime() 96 | for i in countup(1, loops_2): 97 | discard factorial_1(value) 98 | discard factorial_2(value) 99 | discard factorial_3(value) 100 | let endTime = cpuTime() 101 | echo "Time: " & (endTime - start).formatFloat(ffDecimal, 5) 102 | runTimes.add(endTime - start) 103 | 104 | sumTime = 0.0 105 | for j in runTimes: 106 | sumTime += j 107 | 108 | echo "Independent procedures calls" 109 | echo "Mean Time: " & (sumTime/runTimes.len.float).formatFloat(ffDecimal, 5) 110 | echo "" -------------------------------------------------------------------------------- /examples/generic.nim: -------------------------------------------------------------------------------- 1 | import ../src/nimcls 2 | 3 | Class *Node[K, V]: 4 | var key : K 5 | var value: V 6 | var leftNode: Node[K, V] 7 | var rightNode: Node[K, V] 8 | 9 | Class *BTree[K,V]: 10 | var root: Node[K, V] 11 | proc printTreeTypes[K, V](self: BTree[K,V]) = 12 | echo "key's type is: " & $K & ", value's type is: " & $V 13 | 14 | 15 | var binaryTree : BTree[string, int] = BTree[string, int]() 16 | 17 | binaryTree.root = Node[string, int](key: "one", value: 1) 18 | binaryTree.root.leftNode = Node[string, int](key: "left two", value: 2) 19 | binaryTree.root.rightNode = Node[string, int](key: "right three", value: 3) 20 | 21 | binaryTree.printTreeTypes 22 | echo "Root key: " & binaryTree.root.key & " , value: " & $binaryTree.root.value 23 | echo "Left key: " & binaryTree.root.leftNode.key & " , value: " & $binaryTree.root.leftNode.value 24 | echo "Right key: " & binaryTree.root.rightNode.key & " , value: " & $binaryTree.root.rightNode.value -------------------------------------------------------------------------------- /examples/inheritance_1.nim: -------------------------------------------------------------------------------- 1 | import ../src/nimcls 2 | 3 | Class Parent: 4 | var value: int = 10 5 | method hello(self: Parent) {.base.} = 6 | echo "Hello!! Parent" 7 | 8 | Class Child(Parent): 9 | method hello(self: Child) = 10 | echo "Hello!! Child" 11 | 12 | let parent: Parent = Parent() 13 | parent.hello 14 | 15 | let child: Child = Child() 16 | child.hello 17 | 18 | echo "---- Superclass call ----" 19 | ### Call Parent class hello ### 20 | procCall child.super.hello 21 | 22 | echo "---- Parent & Child variable ----" 23 | echo "Parent 'value': " & $parent.value 24 | echo "Child 'value': " & $child.value -------------------------------------------------------------------------------- /examples/inheritance_2.nim: -------------------------------------------------------------------------------- 1 | import ../src/nimcls 2 | 3 | Class static Parent: 4 | var value: int = 10 5 | method hello(self: Parent) {.base.} = 6 | echo "Hello!! Parent" 7 | 8 | Class static Child(Parent): 9 | method hello(self: Child) = 10 | echo "Hello!! Child" 11 | 12 | let parent: Parent = Parent(value: 55) 13 | parent.hello 14 | 15 | let child: Child = Child() 16 | child.hello 17 | 18 | echo "---- Superclass call ----" 19 | ### Call Parent class hello ### 20 | procCall child.super.hello 21 | 22 | echo "---- Parent & Child variable ----" 23 | echo "Parent 'value': " & $parent.value 24 | echo "Child 'value': " & $child.value -------------------------------------------------------------------------------- /examples/injection_1.nim: -------------------------------------------------------------------------------- 1 | import ../src/nimcls 2 | 3 | Interface IService: 4 | method run(self: IService) 5 | 6 | Class ServiceImpl(IService): 7 | method run(self: ServiceImpl) = 8 | echo "=== Service ===" 9 | 10 | Class Controller: 11 | var service: IService 12 | method init(self: Controller, service: IService = inject(IService)) {.base.} = 13 | self.service = service 14 | method startService(self: Controller) {.base} = 15 | self.service.run 16 | 17 | proc setup() = 18 | let service: ServiceImpl = ServiceImpl() 19 | addSingleton(IService, service) 20 | 21 | 22 | 23 | setup() 24 | let controller = Controller() 25 | controller.init 26 | controller.startService -------------------------------------------------------------------------------- /examples/injection_2.nim: -------------------------------------------------------------------------------- 1 | import ../src/nimcls 2 | import times 3 | 4 | Class static Loader: 5 | var file: string 6 | method load(self: Loader) {.base.} = 7 | echo "Loading: " & self.file 8 | 9 | Class Logger: 10 | var timeStamp: int 11 | method log(self: Logger) {.base.} = 12 | echo "Logger time: " & $self.timeStamp 13 | 14 | Class Handler: 15 | method run(self: Handler, loader: Loader = inject(Loader), logger: Logger = inject(Logger)) {.base.} = 16 | loader.load 17 | logger.log 18 | echo "=== Running Handler ===" 19 | echo "=== Finished ===" 20 | 21 | proc setup() = 22 | let myLoader = Loader(file: "/tmp/test.txt") 23 | 24 | addSingleton(myLoader) 25 | addInjector( 26 | proc() : Logger = 27 | let logger = Logger() 28 | logger.timeStamp = getTime().nanosecond 29 | return logger 30 | ) 31 | 32 | setup() 33 | let handler = Handler() 34 | handler.run 35 | -------------------------------------------------------------------------------- /examples/interface.nim: -------------------------------------------------------------------------------- 1 | import ../src/nimcls 2 | 3 | 4 | Interface IRunner: 5 | method run(self: IRunner) 6 | method getFilePath(self: IRunner): string 7 | method isRunning(self: IRunner): bool 8 | method notImplemented(self: IRunner) 9 | 10 | Class RunnerImpl(IRunner): 11 | var running: bool = false 12 | method run(self: RunnerImpl) = 13 | echo "Running" 14 | self.running = true 15 | 16 | method getFilePath(self: RunnerImpl): string = 17 | return "/home/user/file" 18 | 19 | method isRunning(self: RunnerImpl): bool = 20 | return self.running 21 | 22 | let runner: IRunner = RunnerImpl() 23 | runner.run 24 | echo "Is it Running: " & $runner.isRunning 25 | echo "File path: " & runner.getFilePath 26 | try: 27 | runner.notImplemented 28 | except Exception: 29 | echo "Exception: The method `notImplemented` has not been implemented" -------------------------------------------------------------------------------- /examples/nestedsuper.nim: -------------------------------------------------------------------------------- 1 | import ../src/nimcls 2 | 3 | Class One: 4 | method sayHi(self: One) {.base.} = 5 | echo "Hi, this is One" 6 | 7 | proc sayName(self: One) = 8 | echo "This is One" 9 | Class Two(One): 10 | method sayHi(self: Two) = 11 | procCall self.super.sayHi 12 | echo "Hi, this is Two" 13 | 14 | proc sayName(self: Two) = 15 | procCall self.super.sayName 16 | echo "This is Two" 17 | 18 | Class Three(Two): 19 | method sayHi(self: Three) = 20 | procCall self.super.sayHi 21 | echo "Hi, this is Three" 22 | proc sayName(self: Three) = 23 | procCall self.super.sayName 24 | echo "This is Three" 25 | 26 | Class Four(Three): 27 | method sayHi(self: Four) = 28 | procCall self.super.sayHi 29 | echo "Hi, this is Four" 30 | proc sayName(self: Four) = 31 | procCall self.super.sayName 32 | echo "This is Four" 33 | 34 | let four = Four() 35 | 36 | echo "=== Calling One sayHi ===" 37 | procCall four.super.super.super.sayHi 38 | 39 | echo "=== Calling One sayName ===" 40 | procCall four.super.super.super.sayName 41 | 42 | echo "=== Calling sayHi ===" 43 | four.sayHi 44 | 45 | echo "=== Calling sayName ===" 46 | four.sayName 47 | 48 | -------------------------------------------------------------------------------- /nimcls.nimble: -------------------------------------------------------------------------------- 1 | packageName = "nimcls" 2 | version = "4.3.0" 3 | author = "Yaser A" 4 | description = "Classes and dependency injection for Nim." 5 | license = "MIT" 6 | skipDirs = @["examples", "tests"] 7 | srcDir = "src" 8 | 9 | requires: "nim >= 2.0.0" 10 | 11 | 12 | task test, "Run all tests": 13 | withDir("tests"): 14 | exec "nim c -r ./macro.nim" 15 | exec "nim c -r ./classes.nim" 16 | exec "nim c -r ./classes_static.nim" 17 | exec "nim c -r ./classes_generic.nim" 18 | exec "nim c -r ./injection.nim" 19 | exec "nim c -r ./injection_static.nim" -------------------------------------------------------------------------------- /src/nimcls.nim: -------------------------------------------------------------------------------- 1 | import nimcls/[clsmacro, classobj, di] 2 | 3 | export 4 | ClassObj, 5 | InjectionError, 6 | addSingleton, 7 | addInjector, 8 | inject, 9 | isInjectable, 10 | isSingleton, 11 | resetInjectTbl 12 | 13 | macro Interface*(head, body: untyped): untyped = 14 | result = createInterface(head, body) 15 | 16 | macro Class*(head, body: untyped): untyped = 17 | result = createClass(head, body) 18 | 19 | macro Class*(head): untyped = 20 | result = createClass(head) -------------------------------------------------------------------------------- /src/nimcls/builder.nim: -------------------------------------------------------------------------------- 1 | import 2 | macros, 3 | intsets, 4 | sequtils, 5 | random, 6 | strutils 7 | 8 | import ./classobj 9 | 10 | var signaturesSet {.compileTime.} : IntSet = initIntSet() 11 | 12 | proc genSignature() : int = 13 | var 14 | timeStamp: string = CompileTime 15 | timeSplit: seq[string] = ("1" & timeStamp).split(":") 16 | value: int = parseInt(timeSplit.join) 17 | rState: Rand = initRand(value) 18 | signature: int = rState.rand(999_999 .. 999_999_999) 19 | signaturesCount: int = signaturesSet.len 20 | signaturesSet.incl(signature) 21 | while signaturesCount == signaturesSet.len: 22 | signature = rState.rand(999_999 .. 999_999_999) 23 | signaturesSet.incl(signature) 24 | return signature 25 | 26 | proc buildGenericParams(classDef: NimNode): NimNode {.compileTime.} = 27 | var originalParam: NimNode 28 | for i in countup(0, len(classDef[0]) - 1 ): 29 | if classDef[0][i].kind == nnkGenericParams: 30 | originalParam = classDef[0][i] 31 | break 32 | var idDef: NimNode = nnkIdentDefs.newNimNode 33 | for param in originalParam: 34 | if len(param) == 3: 35 | idDef.add(param[0]) 36 | else: 37 | for elem in param: 38 | if elem.kind == nnkIdent: 39 | idDef.add(elem) 40 | idDef.add(newEmptyNode()) 41 | idDef.add(newEmptyNode()) 42 | var output: NimNode = nnkGenericParams.newNimNode 43 | output.add(idDef) 44 | return output 45 | 46 | proc buildBracketExpr(genericParams, className: NimNode): NimNode {.compileTime.} = 47 | var bracketExpr: NimNode = nnkBracketExpr.newNimNode 48 | bracketExpr.add(className) 49 | for i in countup(0, len(genericParams[0]) - 1): 50 | if genericParams[0][i].kind == nnkIdent: 51 | bracketExpr.add(genericParams[0][i]) 52 | return bracketExpr 53 | 54 | proc buildClass*(classDef, superClass, className, methodsNamesLit, propsLit: NimNode, isGeneric: bool = false): NimNode {.compileTime.} = 55 | var superClassIdent: NimNode 56 | if superClass.kind == nnkBracketExpr: 57 | superClassIdent = superClass[0] 58 | else: 59 | superClassIdent = superClass 60 | let 61 | signature: int = genSignature() 62 | signatureLit: NimNode = newLit(signature) 63 | isGenericLit: NimNode = newLit(isGeneric) 64 | superClassError: NimNode = newLit(superClassIdent.strVal & " cannot be used as a parent class. Types mismatch error class!") 65 | superClassNameLit: NimNode = newLit(superClassIdent.strVal) 66 | result = quote("@") do: 67 | @classDef 68 | 69 | proc getTypeSignature*(classType: typedesc[@className]): int = 70 | return @signatureLit 71 | 72 | when (@superClassIdent is ref ClassObj) or (@superClassIdent is ClassObj) or (@isGenericLit): 73 | 74 | method getClassName*(self: @className): string = 75 | return $self.type 76 | 77 | method getClassCalls*(self: @className): seq[string] = 78 | when not @superClass is ClassObj or not @superClass is ref ClassObj: 79 | raise newException(ValueError, @superClassError) 80 | else: 81 | var superMethods: seq[string] 82 | when self is ref ClassObj and @superClass is ClassObj: 83 | superMethods = procCall @superClass((self)[]).getClassCalls() 84 | elif self is ClassObj and @superClass is ref ClassObj: 85 | var selfRef= new(ref self.type) 86 | selfRef[] = self 87 | superMethods = procCall @superClass(selfRef).getClassCalls() 88 | else: 89 | superMethods = procCall @superClass(self).getClassCalls() 90 | return deduplicate( superMethods & @methodsNamesLit ) 91 | 92 | method getClassProperties*(self: @className): seq[string] = 93 | when not @superClass is ClassObj or not @superClass is ref ClassObj: 94 | raise newException(ValueError, @superClassError) 95 | else: 96 | var superProp: seq[string] 97 | when self is ref ClassObj and @superClass is ClassObj: 98 | superProp = procCall @superClass((self)[]).getClassProperties() 99 | elif self is ClassObj and @superClass is ref ClassObj: 100 | var selfRef= new(ref self.type) 101 | selfRef[] = self 102 | superProp = procCall @superClass(selfRef).getClassProperties() 103 | else: 104 | superProp = procCall @superClass(self).getClassProperties() 105 | return superProp & @propsLit 106 | 107 | method getParentClassName*(self: @className): string = 108 | return @superClassNameLit 109 | 110 | method super*(self: @className): @superClass {.base.} = 111 | when not @superClass is ClassObj or not @superClass is ref ClassObj: 112 | raise newException(ValueError, @superClassError) 113 | else: 114 | when self is ref ClassObj and @superClass is ClassObj: 115 | return @superClass((self)[]) 116 | elif self is ClassObj and @superClass is ref ClassObj: 117 | var selfRef= new(ref self.type) 118 | selfRef[] = self 119 | return @superClass(selfRef) 120 | else: 121 | return @superClass(self) 122 | 123 | if isGeneric: 124 | var genericParam: NimNode = buildGenericParams(classDef) 125 | var bracketExpr: NimNode = buildBracketExpr(genericParam, className) 126 | result[1][2] = genericParam 127 | var newnnkBracketExpr = nnkBracketExpr.newNimNode 128 | newnnkBracketExpr.add(ident("typedesc")) 129 | newnnkBracketExpr.add(bracketExpr) 130 | result[1][3][1][1] = newnnkBracketExpr 131 | for i in countup(0, len(result[2][0][1]) - 1): 132 | var elem = result[2][0][1][i] 133 | if elem.kind == nnkMethodDef : 134 | elem[2] = genericParam 135 | elem[3][1][1] = bracketExpr 136 | elem[4] = newEmptyNode() 137 | var procNode: NimNode = nnkProcDef.newNimNode 138 | elem.copyChildrenTo(procNode) 139 | result[2][0][1][i] = procNode 140 | -------------------------------------------------------------------------------- /src/nimcls/classobj.nim: -------------------------------------------------------------------------------- 1 | type 2 | ClassObj* 3 | = object of RootObj 4 | 5 | ## ClassObj ## 6 | method getClassName*(self: ClassObj): string {.base.} = 7 | return $self.type 8 | 9 | method getClassCalls*(self: ClassObj): seq[string] {.base.} = 10 | return @["getClassName", "getClassProperties", "getClassCalls", "getParentClassName", "super"] 11 | 12 | method getClassProperties*(self: ClassObj): seq[string] {.base.} = 13 | return @[] 14 | 15 | method getParentClassName*(self: ClassObj): string {.base.} = 16 | return $RootObj 17 | 18 | 19 | ## Ref ClassObj ## 20 | method getClassName*(self: ref ClassObj): string {.base.} = 21 | return $self.type 22 | 23 | method getClassCalls*(self: ref ClassObj): seq[string] {.base.} = 24 | return @["getClassName", "getClassProperties", "getClassCalls", "getParentClassName", "super"] 25 | 26 | method getClassProperties*(self: ref ClassObj): seq[string] {.base.} = 27 | return @[] 28 | 29 | method getParentClassName*(self: ref ClassObj): string {.base.} = 30 | return $RootObj -------------------------------------------------------------------------------- /src/nimcls/clsmacro.nim: -------------------------------------------------------------------------------- 1 | import macros, sets 2 | import 3 | ./builder, 4 | ./classobj, 5 | ./common 6 | 7 | proc createInterface*(head, body: NimNode): NimNode {.compileTime.} = 8 | var 9 | bodyNodes: seq[NimNode] = @[] 10 | methodsNames: HashSet[string] = initHashSet[string]() 11 | callsNames: seq[string] = @[] 12 | isStatic: bool = false 13 | isGeneric: bool = false 14 | 15 | if isItStatic(head): 16 | error("An interface cannot be an object!") 17 | 18 | if isGeneric(head): 19 | error("An interface cannot be a generic object!") 20 | 21 | if head.kind != nnkPrefix and head.kind != nnkIdent: 22 | error("An interface cannot have a parent!") 23 | 24 | let isExported: bool = isClassExported(head, isStatic, isGeneric) 25 | filterInterfaceBodyNodes(body, bodyNodes, methodsNames) 26 | for name in methodsNames: 27 | callsNames.add(name) 28 | let 29 | superClass: NimNode = quote do: ClassObj 30 | className: NimNode = getClassNameNode(head, isExported, isStatic, isGeneric) 31 | classDef: NimNode = genClassDef(className, superClass, isExported, isStatic) 32 | props: seq[string] = @[] 33 | var 34 | propsLit: NimNode = newLit(props) 35 | methodsNamesLit: NimNode = newLit(callsNames) 36 | result = buildClass(classDef, superClass, className, methodsNamesLit, propsLit, isGeneric) 37 | for node in bodyNodes: 38 | if node.kind == nnkMethodDef: 39 | if isValidInterfaceMethod(className, node): 40 | updateInterfaceMethod(node) 41 | result.add(node) 42 | else: 43 | var nameOfProc: string 44 | if node[0].kind == nnkPostfix: 45 | nameOfProc = $node[0][1] 46 | else: 47 | nameOfProc = $node[0] 48 | error("Invalid or unrelated method was found : '" & nameOfProc & "'. The first parameter of the method must be its object and it cannot be generic.") 49 | 50 | proc createClass*(head: NimNode): NimNode {.compileTime.} = 51 | var headCopy: NimNode = head.copy 52 | let 53 | pragmas: NimNode = extractPragmas(headCopy) 54 | isGeneric: bool = isGeneric(headCopy) 55 | isStatic: bool = isItStatic(headCopy) 56 | isExported: bool = isClassExported(headCopy, isStatic, isGeneric) 57 | var superClass: NimNode 58 | if isStatic: 59 | superClass = getParentClassStatic(headCopy, isGeneric) 60 | else: 61 | superClass = getParentClass(headCopy, isExported, isGeneric) 62 | let 63 | className: NimNode = getClassNameNode(headCopy, isExported, isStatic, isGeneric) 64 | var 65 | classDef: NimNode = genClassDef(className, superClass, isExported, isStatic) 66 | if isGeneric: 67 | var genParam: NimNode = genGenericParams(headCopy) 68 | if len(genParam) < 1 : 69 | error("Invalid class's syntax") 70 | for i in countup(0, len(classDef[0]) - 1 ): 71 | if classDef[0][i].kind == nnkEmpty: 72 | classDef[0][i] = genParam 73 | if pragmas.kind == nnkPragma: 74 | var pragmaExpr: NimNode = nnkPragmaExpr.newNimNode 75 | pragmaExpr.add(classDef[0][0]) 76 | pragmaExpr.add(pragmas) 77 | classDef[0][0] = pragmaExpr 78 | let 79 | props: seq[string] = @[] 80 | callsNames: seq[string] = @[] 81 | var propsLit: NimNode = newLit(props) 82 | var methodsNamesLit: NimNode = newLit(callsNames) 83 | result = buildClass(classDef, superClass, className, methodsNamesLit, propsLit, isGeneric) 84 | 85 | proc createClass*(head, body: NimNode): NimNode {.compileTime.} = 86 | var 87 | headCopy: NimNode = head.copy 88 | bodyNodes: seq[NimNode] = @[] 89 | variablesSec: seq[NimNode] = @[] 90 | methodsProcFuncNames: HashSet[string] = initHashSet[string]() 91 | callsNames: seq[string] = @[] 92 | scratchRecList = newNimNode(nnkRecList) 93 | 94 | let 95 | pragmas: NimNode = extractPragmas(headCopy) 96 | isStatic: bool = isItStatic(headCopy) 97 | isGeneric: bool = isGeneric(headCopy) 98 | isExported: bool = isClassExported(headCopy, isStatic, isGeneric) 99 | 100 | filterBodyNodes(body, bodyNodes, variablesSec, methodsProcFuncNames) 101 | for name in methodsProcFuncNames: 102 | callsNames.add(name) 103 | var superClass: NimNode 104 | if isStatic: 105 | superClass = getParentClassStatic(headCopy, isGeneric) 106 | else: 107 | superClass = getParentClass(headCopy, isExported, isGeneric) 108 | 109 | let className: NimNode = getClassNameNode(headCopy, isExported, isStatic, isGeneric) 110 | var classDef: NimNode = genClassDef(className, superClass, isExported, isStatic) 111 | if isGeneric: 112 | var genParam: NimNode = genGenericParams(headCopy) 113 | if len(genParam) < 1 : 114 | error("Invalid syntax") 115 | for i in countup(0, len(classDef[0]) - 1 ): 116 | if classDef[0][i].kind == nnkEmpty: 117 | classDef[0][i] = genParam 118 | 119 | if pragmas.kind == nnkPragma: 120 | var pragmaExpr: NimNode = nnkPragmaExpr.newNimNode 121 | pragmaExpr.add(classDef[0][0]) 122 | pragmaExpr.add(pragmas) 123 | classDef[0][0] = pragmaExpr 124 | echo classDef.treeRepr 125 | 126 | var recSecList: NimNode 127 | if isStatic: 128 | if classDef[0][2][2].kind == nnkEmpty: 129 | for sec in variablesSec: 130 | for variable in sec: 131 | scratchRecList.add(variable) 132 | classDef[0][2][2] = scratchRecList 133 | elif classDef[0][2][2].kind == nnkRecList: 134 | for sec in variablesSec: 135 | for variable in sec: 136 | classDef[0][2][2].add(variable) 137 | 138 | recSecList = classDef[0][2][2] 139 | else: 140 | if classDef[0][2][0][2].kind == nnkEmpty: 141 | for sec in variablesSec: 142 | for variable in sec: 143 | scratchRecList.add(variable) 144 | classDef[0][2][0][2] = scratchRecList 145 | elif classDef[0][2][0][2].kind == nnkRecList: 146 | for sec in variablesSec: 147 | for variable in sec: 148 | classDef[0][2][0][2].add(variable) 149 | 150 | recSecList = classDef[0][2][0][2] 151 | var props: seq[string] 152 | props = buildClassPropertiesSeq(recSecList) 153 | 154 | let whenVarSeq: seq[NimNode] = extractWhenVar(bodyNodes) 155 | for recWhen in whenVarSeq: 156 | recSecList.add(recWhen) 157 | 158 | let caseVarSeq: seq[NimNode] = extractSwitchVar(bodyNodes) 159 | for caseStmt in caseVarSeq: 160 | recSecList.add(caseStmt) 161 | 162 | var propsLit: NimNode = newLit(props) 163 | var methodsNamesLit: NimNode = newLit(callsNames) 164 | result = buildClass(classDef, superClass, className, methodsNamesLit, propsLit, isGeneric) 165 | for node in bodyNodes: 166 | if node.kind == nnkFuncDef: 167 | if isValidFuncOrProcOrMeth(node, className): 168 | result.add(node) 169 | else: 170 | var nameOfFunc: string 171 | if node[0].kind == nnkPostfix: 172 | nameOfFunc = $node[0][1] 173 | else: 174 | nameOfFunc = $node[0] 175 | error("Invalid or unrelated function was found : '" & nameOfFunc & "'. The first parameter of the function must be its class's object.") 176 | elif node.kind == nnkProcDef: 177 | if isValidFuncOrProcOrMeth(node, className): 178 | result.add(node) 179 | else: 180 | var nameOfProc: string 181 | if node[0].kind == nnkPostfix: 182 | nameOfProc = $node[0][1] 183 | else: 184 | nameOfProc = $node[0] 185 | error("Invalid or unrelated procedure was found : '" & nameOfProc & "'. The first parameter of the procedure must be its class's object.") 186 | elif node.kind == nnkMethodDef: 187 | if isValidFuncOrProcOrMeth(node, className): 188 | result.add(node) 189 | else: 190 | var nameOfProc: string 191 | if node[0].kind == nnkPostfix: 192 | nameOfProc = $node[0][1] 193 | else: 194 | nameOfProc = $node[0] 195 | error("Invalid or unrelated method was found : '" & nameOfProc & "'. The first parameter of the method must be its class's object.") 196 | elif node.kind == nnkWhenStmt: 197 | var validNodes: seq[NimNode] = @[] 198 | if isValidClassWhen(node, validNodes): 199 | for nextNode in validNodes: 200 | if not isValidFuncOrProcOrMeth(nextNode, className): 201 | var nameOfMFP: string 202 | if nextNode[0].kind == nnkPostfix: 203 | nameOfMFP = $nextNode[0][1] 204 | else: 205 | nameOfMFP = $nextNode[0] 206 | error("Invalid or unrelated method, procedure or function was found : '" & nameOfMFP & "'. The first parameter of the method must be its class's object.") 207 | result.add(node) 208 | else: 209 | error("Cannot process when's body that has a mix of variables and methods.") 210 | else: 211 | result.add(node) -------------------------------------------------------------------------------- /src/nimcls/common.nim: -------------------------------------------------------------------------------- 1 | import macros, sets 2 | 3 | proc buildClassPropertiesSeq*(recList: NimNode): seq[string] {.compileTime.} = 4 | var properties: seq[string] = @[] 5 | for node in recList: 6 | if node[0].kind == nnkPostfix: 7 | let propName: string = if len(node[0]) > 1: node[0][1].strVal else: node[0][0].strVal 8 | properties.add(propName) 9 | elif node[0].kind == nnkIdent: 10 | let propName: string = if node[0].strVal == "*" : node[1].strVal else: node[0].strVal 11 | properties.add(propName) 12 | return properties 13 | 14 | proc getClassNameNode*(head: NimNode, isExported, isStatic: bool, isGeneric: bool = false): NimNode {.compileTime.} = 15 | if isGeneric: 16 | if head.kind == nnkBracketExpr: 17 | return head[0] 18 | elif head.kind == nnkPrefix and len(head) > 1: 19 | if head[1].kind == nnkBracketExpr: 20 | return head[1][0] 21 | elif head.kind == nnkCall and len(head) > 1: 22 | if head[0].kind == nnkBracketExpr: 23 | return head[0][0] 24 | elif head.kind == nnkInfix and len(head) > 2: 25 | if head[1].kind == nnkIdent: 26 | return head[1] 27 | elif isStatic: 28 | if head[1].kind == nnkPrefix and len(head[1]) > 1: 29 | if head[1][1].kind == nnkBracketExpr: 30 | return head[1][1][0] 31 | elif head[1].kind == nnkCall and len(head[1]) > 1: 32 | if head[1][0].kind == nnkBracketExpr: 33 | return head[1][0][0] 34 | elif head[1].kind == nnkBracketExpr and len(head[1]) > 1: 35 | if head[1][0].kind == nnkIdent: 36 | return head[1][0] 37 | elif head[1].kind == nnkInfix and len(head[1]) > 1: 38 | if head[1][1].kind == nnkIdent: 39 | return head[1][1] 40 | elif isStatic: 41 | if head.len == 2: 42 | if head[1].kind == nnkPrefix: 43 | return head[1][len(head[1]) - 1] 44 | elif head[1].kind == nnkCall: 45 | return head[1][0] 46 | elif head[1].kind == nnkIdent: 47 | return head[1] 48 | elif head[1].kind == nnkInfix: 49 | if head[1][len(head[1]) - 1].kind == nnkPar: 50 | return head[1][len(head[1]) - 2] 51 | else: 52 | if len(head) == 0: 53 | return head 54 | elif isExported: 55 | return head[1] 56 | else: 57 | return head[0] 58 | error("Invalid class's definition sytanx") 59 | 60 | proc getParentClass*(head: NimNode, isExported: bool, isGeneric: bool = false): NimNode {.compileTime.} = 61 | if isGeneric: 62 | if head.kind == nnkCall and len(head) > 1: 63 | if head[1].kind == nnkIdent or head[1].kind == nnkBracketExpr: 64 | return head[1] 65 | elif head.kind == nnkInfix and len(head) > 2: 66 | if head[2].kind == nnkCall and len(head[2]) > 1: 67 | if head[2][1].kind == nnkIdent or head[2][1].kind == nnkBracketExpr: 68 | return head[2][1] 69 | elif head[2].kind == nnkBracket and len(head[2]) > 0: 70 | return quote do: ClassObj 71 | elif head.kind == nnkBracketExpr or head.kind == nnkPrefix: 72 | return quote do: ClassObj 73 | else: 74 | error("Invalid class's definition sytanx") 75 | elif len(head) > 1: 76 | if (isExported and len(head) > 2) or (not isExported): 77 | if head[len(head) - 1].kind != nnkTupleConstr: 78 | if head[len(head) - 1].kind == nnkPar: 79 | if head[len(head) - 1][0].kind == nnkObjectTy: 80 | error("Cannot use object as a parent class") 81 | if head[len(head) - 1][0].kind == nnkIdent: 82 | return head[len(head) - 1][0] 83 | else: 84 | error("Invalid parent class") 85 | if head[len(head) - 1].kind == nnkIdent: 86 | return head[len(head) - 1] 87 | else: 88 | error("Invalid class's definition sytanx") 89 | return quote do: ClassObj 90 | 91 | proc getParentClassStatic*(head: NimNode, isGeneric: bool = false): NimNode {.compileTime.} = 92 | if len(head) == 2: 93 | if head[1].kind == nnkPrefix: 94 | return quote do: ClassObj 95 | elif head[1].kind == nnkIdent: 96 | return quote do: ClassObj 97 | else: 98 | if isGeneric: 99 | if head[1].kind == nnkBracketExpr: 100 | return quote do: ClassObj 101 | elif head[1].kind == nnkCall and len(head[1]) > 1 : 102 | if head[1][1].kind == nnkIdent or head[1][1].kind == nnkBracketExpr: 103 | return head[1][1] 104 | elif head[1].kind == nnkInfix: 105 | if head[1][len(head[1]) - 1].kind == nnkCall and len(head[1][len(head[1]) - 1]) > 1: 106 | if head[1][len(head[1]) - 1][1].kind == nnkIdent or head[1][len(head[1]) - 1][1].kind == nnkBracket: 107 | return head[1][len(head[1]) - 1][1] 108 | elif head[1][len(head[1]) - 1][1].kind == nnkBracketExpr: 109 | return head[1][len(head[1]) - 1][1] 110 | elif head[1][len(head[1]) - 1].kind == nnkBracket: 111 | return quote do: ClassObj 112 | else: 113 | if head[1].kind == nnkInfix: 114 | if head[1][len(head[1]) - 1].kind == nnkPar: 115 | return head[1][len(head[1]) - 1][0] 116 | elif head[1].kind == nnkCall and head[1].len > 1: 117 | if head[1][len(head[1]) - 1].kind == nnkIdent: 118 | return head[1][len(head[1]) - 1] 119 | error("Invalid class's definition sytanx") 120 | 121 | 122 | proc genClassDef*(className, superClass: NimNode, isExported, isStatic: bool): NimNode {.compileTime.} = 123 | if isExported: 124 | if isStatic: 125 | result = quote do: 126 | type `className`* 127 | = object of `superClass` 128 | else: 129 | result = quote do: 130 | type `className`* 131 | = ref object of `superClass` 132 | else: 133 | if isStatic: 134 | result = quote do: 135 | type `className` 136 | = object of `superClass` 137 | else: 138 | result = quote do: 139 | type `className` 140 | = ref object of `superClass` 141 | 142 | proc genGenericParams*(head: NimNode): NimNode {.compileTime.} = 143 | var searchNode: NimNode = head 144 | var genParam: NimNode 145 | 146 | if head.kind == nnkPrefix: 147 | searchNode = head[1] 148 | elif head.kind == nnkCall: 149 | searchNode = head[0] 150 | elif head.kind == nnkInfix: 151 | if head[2].kind == nnkBracket: 152 | searchNode = head[2] 153 | else: 154 | searchNode = head[2][0] 155 | elif head.kind == nnkCommand: 156 | if head[1].kind == nnkCall: 157 | searchNode = head[1] 158 | if head[1].len > 1: 159 | if head[1][0].kind == nnkBracketExpr and head[1][1].kind == nnkBracketExpr: 160 | searchNode = head[1][0] 161 | elif head[1].kind == nnkInfix: 162 | if head[1][2].kind == nnkBracket: 163 | searchNode = head[1][2] 164 | else: 165 | searchNode = head[1][2][0] 166 | elif head[1].kind == nnkBracketExpr: 167 | searchNode = head[1] 168 | elif head[1].kind == nnkPrefix: 169 | searchNode = head[1][1] 170 | var loop: int = 0 171 | if searchNode.kind == nnkBracketExpr: 172 | loop += 1 173 | 174 | genParam = nnkGenericParams.newNimNode 175 | var idDef = nnkIdentDefs.newNimNode 176 | for i in countup(loop, len(searchNode)-1): 177 | if searchNode[i].kind == nnkIdent: 178 | idDef.add(searchNode[i]) 179 | if i == (len(searchNode) - 1): 180 | idDef.add(newEmptyNode()) 181 | idDef.add(newEmptyNode()) 182 | genParam.add(idDef) 183 | elif (i + 1) <= (len(searchNode) - 1): 184 | if searchNode[i + 1].kind != nnkIdent: 185 | idDef.add(newEmptyNode()) 186 | idDef.add(newEmptyNode()) 187 | genParam.add(idDef) 188 | elif searchNode[i].kind == nnkExprColonExpr: 189 | idDef = nnkIdentDefs.newNimNode 190 | searchNode[i].copyChildrenTo(idDef) 191 | idDef.add(newEmptyNode()) 192 | genParam.add(idDef) 193 | idDef = nnkIdentDefs.newNimNode 194 | return genParam 195 | 196 | proc isItStatic*(head: NimNode): bool {.compileTime.} = 197 | if len(head) == 0: 198 | return false 199 | else: 200 | if head.kind == nnkCommand and $head[0] == "static": 201 | return true 202 | else: 203 | return false 204 | 205 | proc isClassExported*(head: NimNode, isStatic : bool , isGeneric: bool = false): bool {.compileTime.} = 206 | if isGeneric: 207 | if head.kind == nnkPrefix and len(head) > 1: 208 | if head[0].kind == nnkIdent and $head[0] == "*": 209 | return true 210 | elif head[1].kind == nnkInfix and len(head[1]) > 2: 211 | if head[1][0].kind == nnkIdent and $head[1][0] == "*": 212 | return true 213 | elif head.kind == nnkInfix and len(head) > 2: 214 | if head[0].kind == nnkIdent and $head[0] == "*": 215 | return true 216 | elif isStatic: 217 | if head[1].kind == nnkPrefix and len(head[1]) > 1: 218 | if head[1][0].kind == nnkIdent and $head[1][0] == "*": 219 | return true 220 | elif head[1].kind == nnkInfix and len(head[1]) > 1: 221 | if head[1][0].kind == nnkIdent and $head[1][0] == "*": 222 | return true 223 | elif isStatic: 224 | if head.len == 2: 225 | if head[1].kind == nnkPrefix: 226 | if $head[1][0] == "*": 227 | return true 228 | elif head[1].kind == nnkInfix: 229 | if $head[1][0] == "*": 230 | return true 231 | else: 232 | if len(head) > 1: 233 | if $head[0] == "*": 234 | return true 235 | return false 236 | 237 | proc isValidFuncOrProcOrMeth*(def, className: NimNode) : bool {.compileTime.} = 238 | if def.len > 4: 239 | if def[3].kind == nnkFormalParams and len(def[3]) > 1 : 240 | if def[3][1].kind == nnkIdentDefs and len(def[3][1]) > 1 : 241 | if def[3][1][1].kind == nnkIdent: 242 | if $def[3][1][1] == $className: 243 | return true 244 | elif (def[3][1][1].kind == nnkVarTy or def[3][1][1].kind == nnkRefTy or def[3][1][1].kind == nnkPtrTy) and len(def[3][1][1]) == 1: 245 | if def[3][1][1][0].kind == nnkIdent: 246 | if $def[3][1][1][0] == $className: 247 | return true 248 | elif def[3][1][1][0].kind == nnkBracketExpr: 249 | if $def[3][1][1][0][0] == $className: 250 | return true 251 | elif (def[3][1][1].kind == nnkBracketExpr): 252 | if def[3][1][1][0].kind == nnkIdent: 253 | if $def[3][1][1][0] == $className: 254 | return true 255 | elif $def[3][1][1][0] == "typedesc": 256 | if def[3][1][1][1].kind == nnkIdent: 257 | if $def[3][1][1][1] == $className: 258 | return true 259 | elif def[3][1][1][1].kind == nnkBracketExpr: 260 | if $def[3][1][1][1][0] == $className: 261 | return true 262 | 263 | return false 264 | 265 | proc isGeneric*(head : NimNode): bool {.compileTime.} = 266 | if head.kind == nnkBracketExpr: 267 | return true 268 | elif head.kind == nnkCommand and len(head) > 1: 269 | if head[1].kind == nnkBracketExpr: 270 | return true 271 | elif head[1].kind == nnkCall and len(head[1]) > 0: 272 | if head[1][0].kind == nnkBracketExpr: 273 | return true 274 | elif head[1].kind == nnkInfix and len(head[1]) > 2: 275 | if head[1][2].kind == nnkCall and len(head[1][2]) > 0: 276 | if head[1][2][0].kind == nnkBracket: 277 | return true 278 | elif head[1][2].kind == nnkBracket and len(head[1][2]) > 0: 279 | return true 280 | elif head[1].kind == nnkPrefix and len(head[1]) > 1: 281 | if head[1][1].kind == nnkBracketExpr : 282 | return true 283 | elif head.kind == nnkPrefix and len(head) > 1: 284 | if head[1].kind == nnkBracketExpr: 285 | return true 286 | elif head.kind == nnkCall and len(head) > 0: 287 | if head[0].kind == nnkBracketExpr: 288 | return true 289 | elif head.kind == nnkInfix and len(head) > 2: 290 | if head[2].kind == nnkCall and len(head[2]) > 0: 291 | if head[2][0].kind == nnkCall or head[2][0].kind == nnkBracket: 292 | return true 293 | elif head[2].kind == nnkBracket and len(head[2]) > 0: 294 | return true 295 | else: 296 | return false 297 | 298 | 299 | proc updateInterfaceMethod*(methodNode: NimNode) {.compileTime.} = 300 | var pragma = nnkPragma.newNimNode 301 | pragma.add(ident("base")) 302 | if methodNode[4].kind == nnkPragma: 303 | var hasBase: bool = false 304 | for elem in methodNode[4]: 305 | if elem.kind == nnkIdent: 306 | if $elem == "base": 307 | hasBase = true 308 | if not hasBase: 309 | methodNode[4].add(ident("base")) 310 | elif methodNode[4].kind == nnkEmpty: 311 | methodNode[4] = pragma 312 | var stmtList: NimNode = nnkStmtList.newNimNode 313 | var raiseStmt: NimNode = nnkRaiseStmt.newNimNode 314 | var callNode: NimNode = nnkCall.newNimNode 315 | callNode.add(ident("newException")) 316 | callNode.add(ident("CatchableError")) 317 | callNode.add(newStrLitNode("Method has not yet been implemented.")) 318 | raiseStmt.add(callNode) 319 | stmtList.add(raiseStmt) 320 | methodNode[6] = stmtList 321 | 322 | proc isValidInterfaceMethod*(className, methodNode: NimNode): bool {.compileTime.} = 323 | if methodNode.len > 4: 324 | if methodNode[2].kind != nnkGenericParams: 325 | if methodNode[3].kind == nnkFormalParams and len(methodNode[3]) > 1 : 326 | if methodNode[3][1].kind == nnkIdentDefs and len(methodNode[3][1]) > 1 : 327 | if methodNode[3][1][1].kind == nnkIdent: 328 | if $methodNode[3][1][1] == $className: 329 | return true 330 | return false 331 | 332 | 333 | 334 | proc filterBodyNodes*(body: NimNode, bodyNodes: var seq[NimNode], variablesSec: var seq[NimNode], methodsProcFuncNames: var HashSet[string] ) {.compileTime.} = 335 | for elem in body: 336 | case elem.kind: 337 | of nnkVarSection: variablesSec.add(elem) 338 | of nnkCommentStmt: bodyNodes.add(elem) 339 | of nnkConstSection: error("'const' cannot be used for classes' properties !!") 340 | of nnkLetSection: error("'let' cannot be used for classes' properties !!") 341 | of nnkWhenStmt: bodyNodes.add(elem) 342 | of nnkCommand: 343 | if len(elem) == 3 and elem[0].kind == nnkIdent and elem[2].kind == nnkStmtList: 344 | if $elem[0] == "switch" : 345 | bodyNodes.add(elem) 346 | else: 347 | error("Only switch statements can be used inside the class body !!") 348 | else: 349 | error("Only switch statements can be used inside the class body !!") 350 | of nnkMethodDef: 351 | var methodName: string 352 | if elem[0].kind == nnkPostfix: 353 | methodName = $elem[0][1] 354 | else: 355 | methodName = $elem[0] 356 | methodsProcFuncNames.incl(methodName) 357 | bodyNodes.add(elem) 358 | of nnkFuncDef: 359 | var funcName: string 360 | if elem[0].kind == nnkPostfix: 361 | funcName = $elem[0][1] 362 | else: 363 | funcName = $elem[0] 364 | methodsProcFuncNames.incl(funcName) 365 | bodyNodes.add(elem) 366 | of nnkProcDef: 367 | var procName: string 368 | if elem[0].kind == nnkPostfix: 369 | procName = $elem[0][1] 370 | else: 371 | procName = $elem[0] 372 | methodsProcFuncNames.incl(procName) 373 | bodyNodes.add(elem) 374 | else: error("Only methods, procedures, functions and variables are allowed in classes' body") 375 | 376 | proc filterInterfaceBodyNodes*(body: NimNode, bodyNodes: var seq[NimNode], methodsNames: var HashSet[string] ) {.compileTime.} = 377 | for elem in body: 378 | case elem.kind: 379 | of nnkCommentStmt: bodyNodes.add(elem) 380 | of nnkWhenStmt: bodyNodes.add(elem) 381 | of nnkMethodDef: 382 | var methodName: string 383 | if elem[0].kind == nnkPostfix: 384 | methodName = $elem[0][1] 385 | else: 386 | methodName = $elem[0] 387 | methodsNames.incl(methodName) 388 | bodyNodes.add(elem) 389 | else: error("Only methods are allowed in interface's body") 390 | 391 | proc isVariablesWhen*(whenNode: NimNode): bool {.compileTime.} = 392 | for branch in whenNode: 393 | if branch[len(branch) - 1].kind == nnkStmtList: 394 | for elem in branch[len(branch) - 1]: 395 | if elem.kind != nnkVarSection and elem.kind != nnkDiscardStmt: 396 | return false 397 | return true 398 | 399 | proc extractWhenVar*(bodyNodes: var seq[NimNode]): seq[NimNode] {.compileTime.} = 400 | var whenRecList: seq[NimNode] = @[] 401 | var whenIdx: seq[int] = @[] 402 | for i in countup(0, len(bodyNodes) - 1): 403 | if bodyNodes[i].kind == nnkWhenStmt: 404 | if isVariablesWhen(bodyNodes[i]): 405 | whenIdx.add(i) 406 | 407 | for j in countdown(len(whenIdx) - 1, 0): 408 | let idx = whenIdx[j] 409 | var recWhen: NimNode = nnkRecWhen.newNimNode 410 | for branch in bodyNodes[idx]: 411 | var newBranch = branch.kind.newNimNode 412 | for elem in branch: 413 | var recList = nnkRecList.newNimNode 414 | if elem.kind == nnkStmtList: 415 | if elem[0].kind == nnkDiscardStmt: 416 | recList.add(newNilLit()) 417 | else: 418 | for varSec in elem: 419 | for variable in varSec: 420 | recList.add(variable) 421 | newBranch.add(recList) 422 | else: 423 | newBranch.add(elem) 424 | recWhen.add(newBranch) 425 | whenRecList.add(recWhen) 426 | bodyNodes.delete(idx) 427 | 428 | return whenRecList 429 | 430 | proc isValidClassWhen*(whenNode: NimNode, validNodes: var seq[NimNode]): bool {.compileTime.} = 431 | for branch in whenNode: 432 | if branch[len(branch) - 1].kind == nnkStmtList: 433 | for elem in branch[len(branch) - 1]: 434 | case elem.kind: 435 | of nnkMethodDef: validNodes.add(elem) 436 | of nnkFuncDef: validNodes.add(elem) 437 | of nnkProcDef: validNodes.add(elem) 438 | else: return false 439 | return true 440 | 441 | proc isVariablesSwitch*(switchNode: NimNode): bool {.compileTime.} = 442 | for branch in switchNode[2][0]: 443 | if branch.kind == nnkIdent: 444 | continue 445 | elif branch[len(branch) - 1].kind == nnkStmtList: 446 | for elem in branch[len(branch) - 1]: 447 | if elem.kind != nnkVarSection and elem.kind != nnkDiscardStmt: 448 | return false 449 | return true 450 | 451 | proc extractSwitchVar*(bodyNodes: var seq[NimNode]): seq[NimNode] {.compileTime.} = 452 | var caseRecList: seq[NimNode] = @[] 453 | var caseIdx: seq[int] = @[] 454 | for i in countup(0, len(bodyNodes) - 1): 455 | if bodyNodes[i].kind == nnkCommand: 456 | echo bodyNodes[i].treeRepr 457 | if isVariablesSwitch(bodyNodes[i]): 458 | caseIdx.add(i) 459 | else: 460 | error("Invalid switch statement!") 461 | 462 | for j in countdown(len(caseIdx) - 1, 0): 463 | let idx: int = caseIdx[j] 464 | var recCase: NimNode = nnkRecCase.newNimNode 465 | var recCaseIdentDef: NimNode = nnkIdentDefs.newNimNode 466 | if bodyNodes[idx][1].kind == nnkPrefix: 467 | var postFix = nnkPostfix.newNimNode 468 | postFix.add(bodyNodes[idx][1][0]) 469 | postFix.add(bodyNodes[idx][1][1]) 470 | recCaseIdentDef.add(postFix) 471 | elif bodyNodes[idx][1].kind == nnkIdent: 472 | recCaseIdentDef.add(bodyNodes[idx][1]) 473 | else: 474 | error("Invalid switch statement!!") 475 | recCaseIdentDef.add(bodyNodes[idx][2][0][0]) 476 | recCaseIdentDef.add(newEmptyNode()) 477 | recCase.add(recCaseIdentDef) 478 | 479 | for i in 1..(len(bodyNodes[idx][2][0]) - 1): 480 | var newBranch = bodyNodes[idx][2][0][i].kind.newNimNode 481 | for elem in bodyNodes[idx][2][0][i]: 482 | var recList = nnkRecList.newNimNode 483 | if elem.kind == nnkStmtList: 484 | if elem[0].kind == nnkDiscardStmt: 485 | recList.add(newNilLit()) 486 | else: 487 | for varSec in elem: 488 | for variable in varSec: 489 | recList.add(variable) 490 | newBranch.add(recList) 491 | else: 492 | newBranch.add(elem) 493 | recCase.add(newBranch) 494 | caseRecList.add(recCase) 495 | bodyNodes.delete(idx) 496 | 497 | return caseRecList 498 | 499 | proc extractPragmas*(head: var NimNode) : NimNode {.compileTime.} = 500 | var pragmasNode: NimNode = nnkEmpty.newNimNode 501 | if head.len > 0: 502 | if head.kind == nnkCommand: 503 | if head.len > 1: 504 | if head[1].kind == nnkPragmaExpr: 505 | pragmasNode = head[1][1] 506 | head[1] = head[1][0] 507 | elif head[1].kind == nnkInfix: 508 | if head[1][head[1].len - 1].kind == nnkPragmaExpr: 509 | pragmasNode = head[1][head[1].len - 1][1] 510 | head[1][head[1].len - 1] = head[1][head[1].len - 1][0] 511 | elif head.kind == nnkPragmaExpr: 512 | pragmasNode = head[1] 513 | head = head[0] 514 | elif head.kind == nnkInfix: 515 | if head[head.len - 1].kind == nnkPragmaExpr: 516 | pragmasNode = head[head.len - 1][1] 517 | head[head.len - 1] = head[head.len - 1][0] 518 | return pragmasNode -------------------------------------------------------------------------------- /src/nimcls/di.nim: -------------------------------------------------------------------------------- 1 | from locks import Lock, initLock, withLock 2 | import tables 3 | import ./classobj 4 | 5 | const 6 | DUPLICATE_ERROR_1 : string = "Duplicate injections for " 7 | DUPLICATE_ERROR_2: string = "Duplicate injections for the class: " 8 | SUBCLASS_1_ERROR: string = " is not a subclass of " 9 | SUBCLASS_2_ERROR: string = " and cannot be added to the injection table!!!" 10 | NOT_FOUND_ERROR_1: string = "Could not find an injection for the class: " 11 | 12 | type InjectionError* = object of KeyError 13 | 14 | ## Injection & injectors tables 15 | var 16 | injectionsRefTblSingleton {.global.} = initTable[int, ref ClassObj]() 17 | injectorsRefTbl {.global.} = initTable[int, proc(): ref ClassObj]() 18 | injectionsTblSingleton {.global.} = initTable[int, pointer]() 19 | injectorsTbl {.global.} = initTable[int, proc(): pointer]() 20 | classRefObjTblLock {.global.}: Lock 21 | classObjTblLock {.global.}: Lock 22 | 23 | classRefObjTblLock.initLock 24 | classObjTblLock.initLock 25 | 26 | 27 | proc copyStaticObject[T](obj: T) : pointer = 28 | ## Copy object 29 | ## 30 | let objSize = sizeof(T) 31 | let objPtr = allocShared0(objSize) 32 | moveMem(objPtr, addr obj, objSize) 33 | return objPtr 34 | 35 | proc addSingleton*[T : ref ClassObj | ClassObj](obj : T) {.raises: [InjectionError, Exception].} = 36 | ## Adds a singleton object 37 | ## 38 | ## Adds a singleton object of type ClassObj 39 | ## to the injection table and uses its type as key for it. 40 | ## 41 | when T is ref ClassObj: 42 | withLock classRefObjTblLock: 43 | if injectorsRefTbl.hasKey(T.getTypeSignature): 44 | raise newException(InjectionError, DUPLICATE_ERROR_1 & $T) 45 | injectionsRefTblSingleton[T.getTypeSignature] = obj 46 | else: 47 | withLock classObjTblLock: 48 | if injectorsTbl.hasKey(T.getTypeSignature): 49 | raise newException(InjectionError, DUPLICATE_ERROR_1 & $T) 50 | if injectionsTblSingleton.hasKey(T.getTypeSignature) : 51 | dealloc injectionsTblSingleton[T.getTypeSignature] 52 | injectionsTblSingleton[T.getTypeSignature] = copyStaticObject(obj) 53 | 54 | 55 | proc addSingleton*[T, R : ref ClassObj](clsDesc : typedesc[R], obj : T) {.raises: [InjectionError].} = 56 | ## Adds a singleton object 57 | ## 58 | ## Adds a singleton object of type ClassObj 59 | ## to the injection table and uses 60 | ## its type or its parent's class as key for it. 61 | ## 62 | when T is R : 63 | withLock classRefObjTblLock: 64 | if injectorsRefTbl.hasKey(R.getTypeSignature): 65 | raise newException(InjectionError, DUPLICATE_ERROR_1 & $R) 66 | injectionsRefTblSingleton[R.getTypeSignature] = obj 67 | else: 68 | raise newException(InjectionError, $T & SUBCLASS_1_ERROR & $R & SUBCLASS_2_ERROR) 69 | 70 | 71 | proc addInjector*[T: ref ClassObj | ClassObj](builder : proc(): T) {.raises: [InjectionError].} = 72 | ## Adds a procedure as an injector 73 | ## 74 | ## Adds a procedure that returns an object 75 | ## of type ClassObj to the injectors table 76 | ## and uses the object's type as key for it. 77 | ## 78 | when T is ref ClassObj: 79 | withLock classRefObjTblLock: 80 | if injectionsRefTblSingleton.hasKey(T.getTypeSignature): 81 | raise newException(InjectionError, DUPLICATE_ERROR_2 & $T) 82 | injectorsRefTbl[T.getTypeSignature] = proc(): ref ClassObj = result = builder() 83 | else: 84 | withLock classObjTblLock: 85 | if injectionsTblSingleton.hasKey(T.getTypeSignature): 86 | raise newException(InjectionError, DUPLICATE_ERROR_2 & $T) 87 | injectorsTbl[T.getTypeSignature] 88 | = proc(): pointer = 89 | let output = builder() 90 | var objPtr: pointer = allocShared0(sizeof(output)) 91 | moveMem(objPtr, addr output, sizeof(output)) 92 | return objPtr 93 | 94 | 95 | proc addInjector*[T, R: ref ClassObj](clsDesc : typedesc[R], builder : proc(): T) {.raises: [InjectionError].} = 96 | ## Adds a procedure as an injector 97 | ## 98 | ## Adds a procedure that returns an object 99 | ## of type ref ClassObj to the injectors table 100 | ## it the object's type or its parent's class as key for it. 101 | ## 102 | when T is R : 103 | withLock classRefObjTblLock: 104 | if injectionsRefTblSingleton.hasKey(R.getTypeSignature): 105 | raise newException(InjectionError, DUPLICATE_ERROR_2 & $R) 106 | injectorsRefTbl[R.getTypeSignature] = proc(): ref ClassObj = result = builder() 107 | else: 108 | raise newException(InjectionError, $T & SUBCLASS_1_ERROR & $R & SUBCLASS_2_ERROR) 109 | 110 | 111 | 112 | proc inject*[T: ref ClassObj | ClassObj](clsDesc : typedesc[T]) : T {.thread, raises: [InjectionError, Exception].} = 113 | ## Returns an object 114 | ## 115 | ## Returns an object of type ClassObj 116 | ## which exists in the injection tables. 117 | ## 118 | {.gcsafe.}: 119 | when T is ref ClassObj : 120 | withLock classRefObjTblLock: 121 | if injectionsRefTblSingleton.hasKey(clsDesc.getTypeSignature): 122 | return T(injectionsRefTblSingleton[clsDesc.getTypeSignature]) 123 | elif injectorsRefTbl.hasKey(clsDesc.getTypeSignature) : 124 | let callProc = injectorsRefTbl[clsDesc.getTypeSignature] 125 | return T(callProc()) 126 | else: 127 | raise newException(InjectionError, NOT_FOUND_ERROR_1 & $clsDesc) 128 | else: 129 | withLock classObjTblLock: 130 | if injectionsTblSingleton.hasKey(clsDesc.getTypeSignature): 131 | return cast[T](cast[ptr T](injectionsTblSingleton[clsDesc.getTypeSignature])[]) 132 | elif injectorsTbl.hasKey(clsDesc.getTypeSignature) : 133 | let callProc = injectorsTbl[clsDesc.getTypeSignature] 134 | let objPtr = cast[ptr T](callProc()) 135 | let output = objPtr[] 136 | deallocShared(objPtr) 137 | return output 138 | else: 139 | raise newException(InjectionError, NOT_FOUND_ERROR_1 & $clsDesc) 140 | 141 | proc isInjectable*[T: ref ClassObj | ClassObj](clsDesc : typedesc[T]) : bool {.thread.} = 142 | ## Returns true if an object or a procedure of type ClassObj exists in the tables otherwise false. 143 | ## 144 | {.gcsafe.}: 145 | when T is ref ClassObj: 146 | withLock classRefObjTblLock: 147 | return injectionsRefTblSingleton.hasKey(clsDesc.getTypeSignature) or injectorsRefTbl.hasKey(clsDesc.getTypeSignature) 148 | else: 149 | withLock classObjTblLock: 150 | return injectionsTblSingleton.hasKey(clsDesc.getTypeSignature) or injectorsTbl.hasKey(clsDesc.getTypeSignature) 151 | 152 | 153 | proc isSingleton*[T: ref ClassObj | ClassObj](clsDesc : typedesc[T]) : bool {.thread.} = 154 | ## Returns true if an object of type ClassObj exists in the tables otherwise false. 155 | ## 156 | {.gcsafe.}: 157 | when T is ref ClassObj : 158 | withLock classRefObjTblLock: 159 | return injectionsRefTblSingleton.hasKey(clsDesc.getTypeSignature) 160 | else: 161 | withLock classObjTblLock: 162 | return injectionsTblSingleton.hasKey(clsDesc.getTypeSignature) 163 | 164 | proc resetInjectTbl*() = 165 | ## Resets and removes all entries in the injection and injectors tables. 166 | ## 167 | withLock classRefObjTblLock: 168 | injectionsRefTblSingleton.clear 169 | injectorsRefTbl.clear 170 | withLock classObjTblLock: 171 | for key in injectionsTblSingleton.keys: 172 | deallocShared injectionsTblSingleton[key] 173 | injectionsTblSingleton.clear 174 | injectorsTbl.clear 175 | -------------------------------------------------------------------------------- /tests/classes.nim: -------------------------------------------------------------------------------- 1 | import nimcls 2 | import ./mock 3 | 4 | proc test_classes() = 5 | ### Setup ### 6 | let sevice: Service = Service() 7 | let childA = ChildServiceA() 8 | let childB = ChildServiceB() 9 | let childC = ChildServiceC() 10 | sevice.init() 11 | childA.init(80) 12 | childB.init(443, "3yFSx01R") 13 | 14 | ### Test relationships ### 15 | proc test_classes_relationships() = 16 | assert sevice is ref ClassObj 17 | assert childA is ref ClassObj 18 | assert childB is ref ClassObj 19 | assert childC is ref ClassObj 20 | assert childA is Service 21 | assert childB is Service 22 | assert childC is Service 23 | assert childC is ChildServiceB 24 | 25 | test_classes_relationships() 26 | 27 | ### Test getClassName ### 28 | proc test_getClassName() = 29 | assert sevice.getClassName == "Service" 30 | assert childA.getClassName == "ChildServiceA" 31 | assert childB.getClassName == "ChildServiceB" 32 | assert childC.getClassName == "ChildServiceC" 33 | 34 | test_getClassName() 35 | 36 | ### Test getClassCalls ### 37 | proc test_getClassCalls() = 38 | assert sevice.getClassCalls.len == 7 39 | assert childA.getClassCalls.len == 9 40 | assert childB.getClassCalls.len == 10 41 | assert childC.getClassCalls.len == 13 42 | 43 | assert "init" in sevice.getClassCalls 44 | assert "connect" in childA.getClassCalls 45 | assert "getPort" in childB.getClassCalls 46 | assert "callParentURL" in childC.getClassCalls 47 | assert "runService" in childC.getClassCalls 48 | assert "calcNumber" in childC.getClassCalls 49 | 50 | test_getClassCalls() 51 | 52 | ### Test getClassProperties ### 53 | proc test_getClassProperties() = 54 | assert sevice.getClassProperties.len == 1 55 | assert childA.getClassProperties.len == 2 56 | assert childB.getClassProperties.len == 3 57 | assert childC.getClassProperties.len == 3 58 | 59 | assert "url" in sevice.getClassProperties 60 | assert "port" in childA.getClassProperties 61 | assert "key" in childB.getClassProperties 62 | assert "url" in childc.getClassProperties 63 | 64 | test_getClassProperties() 65 | 66 | ### Test getParentClassName ### 67 | proc test_getParentClassName() = 68 | assert sevice.getParentClassName == "ClassObj" 69 | assert childA.getParentClassName == "Service" 70 | assert childB.getParentClassName == "Service" 71 | assert childC.getParentClassName == "ChildServiceB" 72 | 73 | test_getParentClassName() 74 | 75 | ### Test override and super calls ### 76 | proc test_override_super_calls() = 77 | assert sevice.getURL == url1 78 | assert childA.getURL == url2 79 | assert childB.getURL == url4 80 | assert childA.getPort == 80 81 | assert childB.getPort == 443 82 | assert childB.callParentURL == url3 83 | childB.callParentInit() 84 | assert childB.callParentURL == url2 85 | assert childC.getURL == url1 86 | 87 | test_override_super_calls() 88 | 89 | test_classes() -------------------------------------------------------------------------------- /tests/classes_generic.nim: -------------------------------------------------------------------------------- 1 | import nimcls 2 | import ./mock 3 | 4 | 5 | proc test_generic_classes() = 6 | ### Setup ### 7 | let g1 = G1[string]() 8 | let g2 = G2[int, int]() 9 | let g3 = G3[int, string, string]() 10 | 11 | ### Test relationships ### 12 | proc test_classes_relationships() = 13 | assert g1 is ref ClassObj 14 | assert g2 is ref ClassObj 15 | assert g3 is ref ClassObj 16 | assert g2 is G1[int] 17 | assert g3 is G2[int, string] 18 | 19 | test_classes_relationships() 20 | 21 | ### Test getClassName ### 22 | proc test_getClassName() = 23 | assert g1.getClassName == "G1[system.string]" 24 | assert g2.getClassName == "G2[system.int, system.int]" 25 | assert g3.getClassName == "G3[system.int, system.string, system.string]" 26 | 27 | test_getClassName() 28 | 29 | 30 | ### Test getClassCalls ### 31 | proc test_getClassCalls() = 32 | assert g1.getClassCalls.len == 5 33 | assert g2.getClassCalls.len == 5 34 | assert g3.getClassCalls.len == 5 35 | 36 | assert "getClassName" in g1.getClassCalls 37 | assert "getClassCalls" in g2.getClassCalls 38 | assert "getClassProperties" in g3.getClassCalls 39 | 40 | test_getClassCalls() 41 | 42 | ### Test getClassProperties ### 43 | proc test_getClassProperties() = 44 | assert g1.getClassProperties.len == 1 45 | assert g2.getClassProperties.len == 2 46 | assert g3.getClassProperties.len == 3 47 | 48 | assert "value" in g1.getClassProperties 49 | assert "value" in g2.getClassProperties 50 | assert "next" in g3.getClassProperties 51 | 52 | test_getClassProperties() 53 | 54 | 55 | ### Test getParentClassName ### 56 | proc test_getParentClassName() = 57 | assert g1.getParentClassName == "ClassObj" 58 | assert g2.getParentClassName == "G1" 59 | assert g3.getParentClassName == "G2" 60 | 61 | test_getParentClassName() 62 | 63 | 64 | test_generic_classes() 65 | -------------------------------------------------------------------------------- /tests/classes_static.nim: -------------------------------------------------------------------------------- 1 | import nimcls 2 | import ./mock 3 | 4 | 5 | proc test_static_classes() = 6 | ### Setup ### 7 | let loader: Loader = Loader() 8 | let loaderA = ChildLoaderA() 9 | let loaderB = ChildLoaderB() 10 | 11 | ### Test relationships ### 12 | proc test_classes_relationships() = 13 | assert loader is ClassObj 14 | assert loaderA is ClassObj 15 | assert loaderB is ClassObj 16 | assert loaderA is Loader 17 | assert loaderB is Loader 18 | 19 | test_classes_relationships() 20 | 21 | ### Test getClassName ### 22 | proc test_getClassName() = 23 | assert loader.getClassName == "Loader" 24 | assert loaderA.getClassName == "ChildLoaderA" 25 | assert loaderB.getClassName == "ChildLoaderB" 26 | 27 | test_getClassName() 28 | 29 | 30 | ### Test getClassCalls ### 31 | proc test_getClassCalls() = 32 | assert loader.getClassCalls.len == 6 33 | assert loaderA.getClassCalls.len == 6 34 | assert loaderB.getClassCalls.len == 6 35 | 36 | assert "load" in loader.getClassCalls 37 | assert "load" in loaderA.getClassCalls 38 | assert "load" in loaderB.getClassCalls 39 | 40 | test_getClassCalls() 41 | 42 | ### Test getClassProperties ### 43 | proc test_getClassProperties() = 44 | assert loader.getClassProperties.len == 2 45 | assert loaderA.getClassProperties.len == 3 46 | assert loaderB.getClassProperties.len == 2 47 | 48 | assert "path" in loader.getClassProperties 49 | assert "more" in loaderA.getClassProperties 50 | assert "code" in loaderB.getClassProperties 51 | 52 | test_getClassProperties() 53 | 54 | 55 | ### Test getParentClassName ### 56 | proc test_getParentClassName() = 57 | assert loader.getParentClassName == "ClassObj" 58 | assert loaderA.getParentClassName == "Loader" 59 | assert loaderB.getParentClassName == "Loader" 60 | 61 | test_getParentClassName() 62 | 63 | 64 | ### Test override and super calls ### 65 | proc test_override_super_calls() = 66 | assert loader.code == 22 67 | assert loaderA.code == 22 68 | assert loaderB.code == 22 69 | assert loader.load == "LoaderCall" 70 | assert loaderA.load == "ChildLoaderACall" 71 | assert loaderB.load == "ChildLoaderBCall" 72 | let callOutput: string = procCall loaderB.super.load 73 | assert callOutput == "LoaderCall" 74 | 75 | test_override_super_calls() 76 | 77 | 78 | test_static_classes() 79 | -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "../src") -------------------------------------------------------------------------------- /tests/injection.nim: -------------------------------------------------------------------------------- 1 | import nimcls 2 | import ./mock 3 | 4 | proc test_classes_signature_injection() = 5 | ### Setup ### 6 | let aa = AA() 7 | let ab = AB() 8 | let ac = AC() 9 | 10 | 11 | 12 | ### Test signature ### 13 | proc test_signature() = 14 | assert aa.type.getTypeSignature is int 15 | assert ab.type.getTypeSignature is int 16 | assert ac.type.getTypeSignature is int 17 | 18 | assert aa.type.getTypeSignature >= 99_999 19 | assert ab.type.getTypeSignature >= 99_999 20 | assert ac.type.getTypeSignature >= 99_999 21 | 22 | assert aa.type.getTypeSignature == AA.getTypeSignature 23 | assert ab.type.getTypeSignature == AB.getTypeSignature 24 | assert ac.type.getTypeSignature == AC.getTypeSignature 25 | 26 | assert aa.type.getTypeSignature != AB.getTypeSignature 27 | assert ab.type.getTypeSignature != AC.getTypeSignature 28 | assert ac.type.getTypeSignature != AA.getTypeSignature 29 | 30 | test_signature() 31 | 32 | 33 | 34 | ### Test check existing injections using isInjectable ### 35 | proc test_pre_injection() = 36 | assert not isInjectable(AA) 37 | assert not isInjectable(AB) 38 | assert not isInjectable(AC) 39 | assert not isInjectable(Service) 40 | assert not isInjectable(ChildServiceA) 41 | assert not isInjectable(ChildServiceB) 42 | 43 | test_pre_injection() 44 | 45 | 46 | 47 | ### Test Adding injections and injectors ### 48 | proc test_addSingleton_addInjector() = 49 | addSingleton(aa) 50 | assert isInjectable(AA) 51 | assert isSingleton(AA) 52 | addInjector( 53 | proc() : AB = 54 | let inAB = AB() 55 | inAB.number = 4 56 | return inAB 57 | ) 58 | assert isInjectable(AB) 59 | assert not isSingleton(AB) 60 | 61 | let childServiceB = ChildServiceB() 62 | addSingleton(Service, childServiceB) 63 | assert isInjectable(Service) 64 | assert isSingleton(Service) 65 | 66 | let procChildC = proc(): ChildServiceC = ChildServiceC() 67 | addInjector(ChildServiceB, procChildC) 68 | assert isInjectable(ChildServiceB) 69 | assert not isSingleton(ChildServiceB) 70 | 71 | 72 | test_addSingleton_addInjector() 73 | 74 | 75 | 76 | ### Duplicate Injection ### 77 | proc test_duplicate_injection() = 78 | var passed = false 79 | ### AA was added using addSingleton ### 80 | try: 81 | proc getAA(): AA = result = AA() 82 | addInjector(getAA) 83 | except InjectionError: 84 | passed = true 85 | assert passed 86 | 87 | passed = false 88 | 89 | ### AB was added using addInjector ### 90 | try: 91 | addSingleton(ab) 92 | except InjectionError: 93 | passed = true 94 | assert passed 95 | 96 | passed = false 97 | 98 | ### Service was added using addSingleton ### 99 | try: 100 | proc getService(): Service = result = Service() 101 | addInjector(Service, getService) 102 | except InjectionError: 103 | passed = true 104 | assert passed 105 | 106 | passed = false 107 | 108 | ### ChildServiceB was added using addInjector ### 109 | try: 110 | let childServiceB = ChildServiceB() 111 | addSingleton(ChildServiceB, childServiceB) 112 | except InjectionError: 113 | passed = true 114 | assert passed 115 | 116 | 117 | test_duplicate_injection() 118 | 119 | 120 | 121 | ### Test getting and updating the Injection ### 122 | proc test_inject() = 123 | ## AA ## 124 | let aax: AA = AA() 125 | aax.number = -100 126 | var aaInjection: AA = inject(AA) 127 | assert aaInjection.number == 1 128 | assert aax.number != aaInjection.number 129 | addSingleton(aax) 130 | aaInjection = inject(AA) 131 | assert aaInjection.number != 1 132 | assert aax.number == aaInjection.number 133 | 134 | ## AB ## 135 | proc createAB(): AB = 136 | let abx = AB() 137 | abx.number = 123 138 | return abx 139 | 140 | let abSample: AB = createAB() 141 | var abInjection = inject(AB) 142 | assert abInjection.number == 4 143 | assert abSample.number != abInjection.number 144 | assert abSample.number == 123 145 | addInjector(createAB) 146 | abInjection = inject(AB) 147 | assert abInjection.number != 4 148 | assert abSample.number == abInjection.number 149 | 150 | ## Service ## 151 | let injService = inject(Service) 152 | let childServiceB = ChildServiceB() 153 | assert injService.getURL == childServiceB.getURL 154 | 155 | ## ChildServiceB ## 156 | let injChildB = inject(ChildServiceB) 157 | let childServiceC = ChildServiceC() 158 | assert injChildB.getURL == childServiceC.getURL 159 | 160 | test_inject() 161 | 162 | 163 | ### Test getting as a parameters ### 164 | proc test_injection_param(objAA: AA = inject(AA), objAB: AB = inject(AB)) = 165 | let injectedAA = objAA 166 | let injectedAB = objAB 167 | assert injectedAA.type is ref ClassObj 168 | assert injectedAB.type is ref ClassObj 169 | assert injectedAA.type is AA 170 | assert injectedAB.type is AB 171 | assert injectedAA.getClassProperties.len == 1 172 | assert injectedAB.getClassProperties.len == 1 173 | assert injectedAA.number == -100 174 | assert injectedAB.number == 123 175 | assert not isInjectable(AC) 176 | 177 | test_injection_param() 178 | 179 | 180 | 181 | ### Test adding and getting non existing injections ### 182 | proc test_injection_exceptions() = 183 | var casuedError = false 184 | 185 | proc test_inject_exceptions() = 186 | ## Injection or injector does not exist ## 187 | try: 188 | let obj = inject(AC) 189 | except InjectionError: 190 | casuedError = true 191 | 192 | assert casuedError 193 | casuedError = false 194 | 195 | test_inject_exceptions() 196 | 197 | 198 | 199 | proc test_addInjector_exceptions() = 200 | 201 | ## addInjector 2 not a subclass ## 202 | try: 203 | addInjector(AB, 204 | proc() : AA = 205 | return AA() 206 | ) 207 | except InjectionError: 208 | casuedError = true 209 | 210 | assert casuedError 211 | casuedError = false 212 | 213 | 214 | ## addInjector 2 not a subclass ## 215 | try: 216 | addInjector(ChildServiceA, 217 | proc() : Service = 218 | return Service() 219 | ) 220 | except InjectionError: 221 | casuedError = true 222 | 223 | assert casuedError 224 | casuedError = false 225 | 226 | test_addInjector_exceptions() 227 | 228 | 229 | 230 | proc test_addSingleton_exceptions() = 231 | 232 | ## addSingleton 2 not a subclass ## 233 | try: 234 | let obj = AA() 235 | addSingleton(AB, obj) 236 | except InjectionError: 237 | casuedError = true 238 | 239 | assert casuedError 240 | casuedError = false 241 | 242 | 243 | ## addSingleton 2 not a subclass ## 244 | try: 245 | let obj = Service() 246 | addSingleton(ChildServiceA, obj) 247 | except InjectionError: 248 | casuedError = true 249 | 250 | assert casuedError 251 | casuedError = false 252 | 253 | 254 | test_addSingleton_exceptions() 255 | 256 | 257 | 258 | 259 | test_injection_exceptions() 260 | 261 | ### Test reseting injections tables ### 262 | proc test_resetInjectTbl() = 263 | assert isInjectable(AA) 264 | assert isInjectable(AB) 265 | assert isInjectable(Service) 266 | assert isInjectable(ChildServiceB) 267 | resetInjectTbl() 268 | assert not isInjectable(AA) 269 | assert not isInjectable(AB) 270 | assert not isInjectable(Service) 271 | assert not isInjectable(ChildServiceB) 272 | 273 | 274 | test_resetInjectTbl() 275 | 276 | 277 | test_classes_signature_injection() -------------------------------------------------------------------------------- /tests/injection_static.nim: -------------------------------------------------------------------------------- 1 | import nimcls 2 | import ./mock 3 | 4 | proc test_classes_signature_injection() = 5 | ### Setup ### 6 | var aa = SAA() 7 | var ab = SAB() 8 | var ac = SAC() 9 | 10 | 11 | 12 | ### Test signature ### 13 | proc test_signature() = 14 | assert aa.type.getTypeSignature is int 15 | assert ab.type.getTypeSignature is int 16 | assert ac.type.getTypeSignature is int 17 | 18 | assert aa.type.getTypeSignature >= 99_999 19 | assert ab.type.getTypeSignature >= 99_999 20 | assert ac.type.getTypeSignature >= 99_999 21 | 22 | assert aa.type.getTypeSignature == SAA.getTypeSignature 23 | assert ab.type.getTypeSignature == SAB.getTypeSignature 24 | assert ac.type.getTypeSignature == SAC.getTypeSignature 25 | 26 | assert aa.type.getTypeSignature != SAB.getTypeSignature 27 | assert ab.type.getTypeSignature != SAC.getTypeSignature 28 | assert ac.type.getTypeSignature != SAA.getTypeSignature 29 | 30 | test_signature() 31 | 32 | 33 | 34 | ### Test check existing injections using isInjectable ### 35 | proc test_pre_injection() = 36 | assert not isInjectable(SAA) 37 | assert not isInjectable(SAB) 38 | assert not isInjectable(SAC) 39 | assert not isInjectable(Loader) 40 | assert not isInjectable(ChildLoaderA) 41 | assert not isInjectable(ChildLoaderB) 42 | 43 | test_pre_injection() 44 | 45 | 46 | 47 | ### Test Adding injections and injectors ### 48 | proc test_addSingleton_addInjector() = 49 | addSingleton(aa) 50 | assert isInjectable(SAA) 51 | assert isSingleton(SAA) 52 | addInjector( 53 | proc() : SAB = 54 | return SAB() 55 | ) 56 | assert isInjectable(SAB) 57 | assert not isSingleton(SAB) 58 | 59 | let procLoaderA = proc(): ChildLoaderA = ChildLoaderA() 60 | addInjector(procLoaderA) 61 | assert isInjectable(ChildLoaderA) 62 | assert not isSingleton(ChildLoaderA) 63 | 64 | 65 | test_addSingleton_addInjector() 66 | 67 | 68 | 69 | ### Duplicate Injection ### 70 | proc test_duplicate_injection() = 71 | var passed = false 72 | ### SAA was added using addSingleton ### 73 | try: 74 | proc getSAA(): SAA = result = SAA() 75 | addInjector(getSAA) 76 | except InjectionError: 77 | passed = true 78 | assert passed 79 | 80 | passed = false 81 | 82 | ### SAB was added using addInjector ### 83 | try: 84 | addSingleton(ab) 85 | except InjectionError: 86 | passed = true 87 | assert passed 88 | 89 | passed = false 90 | 91 | ### ChildLoaderA was added using addInjector ### 92 | try: 93 | let loaderB = ChildLoaderA() 94 | addSingleton(loaderB) 95 | except InjectionError: 96 | passed = true 97 | assert passed 98 | 99 | 100 | test_duplicate_injection() 101 | 102 | 103 | 104 | ### Test getting and updating the Injection ### 105 | proc test_inject() = 106 | ## SAA ## 107 | let saa: SAA = SAA(number: -100) 108 | var saaInjection: SAA = inject(SAA) 109 | assert saaInjection.number == 1 110 | assert saa.number != saaInjection.number 111 | addSingleton(saa) 112 | saaInjection = inject(SAA) 113 | assert saaInjection.number != 1 114 | assert saa.number == saaInjection.number 115 | 116 | # SAB ## 117 | proc createSAB(): SAB = 118 | let abx = SAB(number: 123) 119 | return abx 120 | 121 | let sabSample = createSAB() 122 | 123 | var sabInjection = inject(SAB) 124 | assert sabInjection.number == 2 125 | assert sabSample.number != sabInjection.number 126 | 127 | addInjector(createSAB) 128 | sabInjection = inject(SAB) 129 | assert sabInjection.number != 2 130 | assert sabSample.number == sabInjection.number 131 | 132 | ## ChildLoaderA ## 133 | let injChildA = inject(ChildLoaderA) 134 | let loaderC = ChildLoaderA() 135 | assert injChildA.code == loaderC.code 136 | 137 | test_inject() 138 | 139 | 140 | ### Test getting as a parameters ### 141 | proc test_injection_param(objSAA: SAA = inject(SAA), objSAB: SAB = inject(SAB)) = 142 | var injectedSAA = objSAA 143 | var injectedSAB = objSAB 144 | assert injectedSAA.type is ClassObj 145 | assert injectedSAB.type is ClassObj 146 | assert injectedSAA.type is SAA 147 | assert injectedSAB.type is SAB 148 | assert injectedSAA.getClassProperties.len == 1 149 | assert injectedSAB.getClassProperties.len == 1 150 | assert injectedSAA.getClassCalls.len == 5 151 | assert injectedSAB.getClassCalls.len == 5 152 | assert injectedSAA.number == -100 153 | assert injectedSAB.number == 123 154 | assert not isInjectable(SAC) 155 | 156 | test_injection_param() 157 | 158 | 159 | 160 | ### Test adding and getting non existing injections ### 161 | proc test_injection_exceptions() = 162 | var casuedError = false 163 | 164 | proc test_inject_exceptions() = 165 | ## Injection or injector does not exist ## 166 | try: 167 | let obj = inject(SAC) 168 | except InjectionError: 169 | casuedError = true 170 | 171 | assert casuedError 172 | 173 | test_inject_exceptions() 174 | 175 | 176 | test_injection_exceptions() 177 | 178 | ### Test reseting injections tables ### 179 | proc test_resetInjectTbl() = 180 | assert isInjectable(SAA) 181 | assert isInjectable(SAB) 182 | assert isInjectable(ChildLoaderA) 183 | resetInjectTbl() 184 | assert not isInjectable(SAA) 185 | assert not isInjectable(SAB) 186 | assert not isInjectable(ChildLoaderA) 187 | 188 | 189 | test_resetInjectTbl() 190 | 191 | 192 | test_classes_signature_injection() -------------------------------------------------------------------------------- /tests/macro.nim: -------------------------------------------------------------------------------- 1 | import nimcls/clsmacro 2 | import macros 3 | 4 | static: 5 | 6 | ## 7 | ## 8 | ## Check Classes Names 9 | ## 10 | ## 11 | 12 | block: # Check regular class's name 13 | let className: string = "MyClass" 14 | let ast: NimNode = parseExpr("Class " & className) 15 | let output: NimNode = createClass(ast[1]) 16 | assert output.kind == nnkStmtList 17 | assert output[0].kind == nnkTypeSection 18 | assert output[0][0].kind == nnkTypeDef 19 | assert output[0][0][0].kind == nnkIdent 20 | assert $output[0][0][0] == className 21 | 22 | block: # Check regular class's name 23 | let className: string = "MyClass" 24 | let ast: NimNode = parseExpr("Class *" & className) 25 | let output: NimNode = createClass(ast[1]) 26 | assert output.kind == nnkStmtList 27 | assert output[0].kind == nnkTypeSection 28 | assert output[0][0].kind == nnkTypeDef 29 | assert output[0][0][0].kind == nnkPostfix 30 | assert $output[0][0][0][1] == className 31 | 32 | block: # Check static class's name 33 | let className: string = "MyClass" 34 | let ast: NimNode = parseStmt("Class static " & className) 35 | let output: NimNode = createClass(ast[0][1]) 36 | assert output.kind == nnkStmtList 37 | assert output[0].kind == nnkTypeSection 38 | assert output[0][0].kind == nnkTypeDef 39 | assert output[0][0][0].kind == nnkIdent 40 | assert $output[0][0][0] == className 41 | 42 | block: # Check static class's name 43 | let className: string = "MyClass" 44 | let ast: NimNode = parseStmt("Class static *" & className) 45 | let output: NimNode = createClass(ast[0][1]) 46 | assert output.kind == nnkStmtList 47 | assert output[0].kind == nnkTypeSection 48 | assert output[0][0].kind == nnkTypeDef 49 | assert output[0][0][0].kind == nnkPostfix 50 | assert $output[0][0][0][1] == className 51 | 52 | block: # Check generic class's name 53 | let className: string = "MyClass" 54 | let genericName: string = "MyClass[T,R]" 55 | let ast: NimNode = parseStmt("Class " & genericName) 56 | let output: NimNode = createClass(ast[0][1]) 57 | assert output.kind == nnkStmtList 58 | assert output[0].kind == nnkTypeSection 59 | assert output[0][0].kind == nnkTypeDef 60 | assert output[0][0][0].kind == nnkIdent 61 | assert $output[0][0][0] == className 62 | 63 | block: # Check generic class's name 64 | let className: string = "MyClass" 65 | let genericName: string = "MyClass[T: int,R](Test[T])" 66 | let ast: NimNode = parseStmt("Class " & genericName) 67 | let output: NimNode = createClass(ast[0][1]) 68 | assert output.kind == nnkStmtList 69 | assert output[0].kind == nnkTypeSection 70 | assert output[0][0].kind == nnkTypeDef 71 | assert output[0][0][0].kind == nnkIdent 72 | assert $output[0][0][0] == className 73 | 74 | block: # Check static generic class's name 75 | let className: string = "MyClass" 76 | let genericName: string = "*MyClass[T,R]" 77 | let ast: NimNode = parseStmt("Class static " & genericName) 78 | let output: NimNode = createClass(ast[0][1]) 79 | assert output.kind == nnkStmtList 80 | assert output[0].kind == nnkTypeSection 81 | assert output[0][0].kind == nnkTypeDef 82 | assert output[0][0][0].kind == nnkPostfix 83 | assert $output[0][0][0][1] == className 84 | 85 | 86 | block: # Check static generic class's name 87 | let className: string = "MyClass" 88 | let genericName: string = "MyClass[T,R]" 89 | let ast: NimNode = parseStmt("Class static " & genericName) 90 | let output: NimNode = createClass(ast[0][1]) 91 | assert output.kind == nnkStmtList 92 | assert output[0].kind == nnkTypeSection 93 | assert output[0][0].kind == nnkTypeDef 94 | assert output[0][0][0].kind == nnkIdent 95 | assert $output[0][0][0] == className 96 | 97 | block: # Check static generic class's name 98 | let className: string = "MyClass" 99 | let genericName: string = "MyClass[T,R](Test[T])" 100 | let ast: NimNode = parseStmt("Class static " & genericName) 101 | let output: NimNode = createClass(ast[0][1]) 102 | assert output.kind == nnkStmtList 103 | assert output[0].kind == nnkTypeSection 104 | assert output[0][0].kind == nnkTypeDef 105 | assert output[0][0][0].kind == nnkIdent 106 | assert $output[0][0][0] == className 107 | 108 | ## 109 | ## 110 | ## Check Classes export 111 | ## 112 | ## 113 | 114 | block: # Check regular class is exported 115 | let ast: NimNode = parseExpr("Class *MyClass") 116 | let output: NimNode = createClass(ast[1]) 117 | assert output[0][0][0].kind == nnkPostfix 118 | assert $output[0][0][0][0] == "*" 119 | 120 | block: # Check regular class is exported 121 | let ast: NimNode = parseExpr("Class MyClass*(Test)") 122 | let output: NimNode = createClass(ast[1]) 123 | assert output[0][0][0].kind == nnkPostfix 124 | assert $output[0][0][0][0] == "*" 125 | 126 | block: # Check static class is exported 127 | let ast: NimNode = parseStmt("Class static MyClass*(Test)") 128 | let output: NimNode = createClass(ast[0][1]) 129 | assert output[0][0][0].kind == nnkPostfix 130 | assert $output[0][0][0][0] == "*" 131 | 132 | block: # Check static class is exported 133 | let ast: NimNode = parseStmt("Class static *MyClass") 134 | let output: NimNode = createClass(ast[0][1]) 135 | assert output[0][0][0].kind == nnkPostfix 136 | assert $output[0][0][0][0] == "*" 137 | 138 | block: # Check generic class is exported 139 | let ast: NimNode = parseStmt("Class *MyClass[T,R]") 140 | let output: NimNode = createClass(ast[0][1]) 141 | assert output[0][0][0].kind == nnkPostfix 142 | assert $output[0][0][0][0] == "*" 143 | 144 | block: # Check generic class is exported 145 | let ast: NimNode = parseStmt("Class MyClass*[T,R](RootObj)") 146 | let output: NimNode = createClass(ast[0][1]) 147 | assert output[0][0][0].kind == nnkPostfix 148 | assert $output[0][0][0][0] == "*" 149 | 150 | block: # Check static generic class is exported 151 | let ast: NimNode = parseStmt("Class static *MyClass[T,R]") 152 | let output: NimNode = createClass(ast[0][1]) 153 | assert output[0][0][0].kind == nnkPostfix 154 | assert $output[0][0][0][0] == "*" 155 | 156 | block: # Check static generic class is exported 157 | let ast: NimNode = parseStmt("Class static MyClass*[T,R](Test[T,R])") 158 | let output: NimNode = createClass(ast[0][1]) 159 | assert output[0][0][0].kind == nnkPostfix 160 | assert $output[0][0][0][0] == "*" 161 | 162 | ## 163 | ## 164 | ## Check Classes' parent 165 | ## 166 | ## 167 | 168 | block: # Check parent class 169 | let parentClassName = "Test" 170 | let ast: NimNode = parseStmt("Class MyClass(" & parentClassName & ")") 171 | let output: NimNode = createClass(ast[0][1]) 172 | assert $output[0][0][2][0][1][0] == parentClassName 173 | 174 | 175 | block: # Check parent class 176 | let parentClassName = "Test" 177 | let ast: NimNode = parseStmt("Class MyClass*(" & parentClassName & ")") 178 | let output: NimNode = createClass(ast[0][1]) 179 | assert $output[0][0][2][0][1][0] == parentClassName 180 | 181 | 182 | block: # Check parent class 183 | let parentClassName = "Test" 184 | let ast: NimNode = parseStmt("Class static MyClass(" & parentClassName & ")") 185 | let output: NimNode = createClass(ast[0][1]) 186 | assert $output[0][0][2][1][0] == parentClassName 187 | 188 | 189 | block: # Check parent class 190 | let parentClassName = "Test" 191 | let ast: NimNode = parseStmt("Class static MyClass*(" & parentClassName & ")") 192 | let output: NimNode = createClass(ast[0][1]) 193 | assert $output[0][0][2][1][0] == parentClassName 194 | 195 | 196 | block: # Check parent class 197 | let parentClassName = "Test" 198 | let ast: NimNode = parseStmt("Class MyClass*[T](" & parentClassName & ")") 199 | let output: NimNode = createClass(ast[0][1]) 200 | assert $output[0][0][2][0][1][0] == parentClassName 201 | 202 | 203 | block: # Check parent class 204 | let parentClassName = "Test" 205 | let ast: NimNode = parseStmt("Class static MyClass[K,V](" & parentClassName & ")") 206 | let output: NimNode = createClass(ast[0][1]) 207 | assert $output[0][0][2][1][0] == parentClassName 208 | 209 | 210 | block: # Check parent class 211 | let parentClassName = "Test" 212 | let parentClassGeneric = parentClassName & "[K,V]" 213 | let ast: NimNode = parseStmt("Class static MyClass[K,V](" & parentClassGeneric & ")") 214 | let output: NimNode = createClass(ast[0][1]) 215 | assert output[0][0][2][1][0].kind == nnkBracketExpr 216 | assert $output[0][0][2][1][0][0] == parentClassName 217 | 218 | 219 | block: # Check parent class 220 | let parentClassName = "Test" 221 | let parentClassGeneric = parentClassName & "[K,V]" 222 | let ast: NimNode = parseStmt("Class MyClass*[K: int | string, V: float | string](" & parentClassGeneric & ")") 223 | let output: NimNode = createClass(ast[0][1]) 224 | assert output[0][0][2][0][1][0].kind == nnkBracketExpr 225 | assert $output[0][0][2][0][1][0][0] == parentClassName 226 | 227 | ## 228 | ## 229 | ## Check Classes' types 230 | ## 231 | ## 232 | 233 | block: # Check class's object type 234 | let ast: NimNode = parseStmt("Class MyClass") 235 | let output: NimNode = createClass(ast[0][1]) 236 | assert output[0][0][2].kind == nnkRefTy 237 | 238 | block: # Check class's object type 239 | let ast: NimNode = parseStmt("Class static MyClass") 240 | let output: NimNode = createClass(ast[0][1]) 241 | assert output[0][0][2].kind == nnkObjectTy 242 | 243 | block: # Check class's object type 244 | let ast: NimNode = parseStmt("Class MyClass*(Test)") 245 | let output: NimNode = createClass(ast[0][1]) 246 | assert output[0][0][2].kind == nnkRefTy 247 | 248 | block: # Check class's object type 249 | let ast: NimNode = parseStmt("Class static MyClass(Test)") 250 | let output: NimNode = createClass(ast[0][1]) 251 | assert output[0][0][2].kind == nnkObjectTy 252 | 253 | block: # Check class's object type 254 | let ast: NimNode = parseStmt("Class *MyClass[T]") 255 | let output: NimNode = createClass(ast[0][1]) 256 | assert output[0][0][2].kind == nnkRefTy 257 | 258 | block: # Check class's object type 259 | let ast: NimNode = parseStmt("Class static MyClass[R, V](Test[R,V])") 260 | let output: NimNode = createClass(ast[0][1]) 261 | assert output[0][0][2].kind == nnkObjectTy 262 | 263 | block: # Check class's object type 264 | let ast: NimNode = parseStmt("Class MyClass[R, V](Test[R,V])") 265 | let output: NimNode = createClass(ast[0][1]) 266 | assert output[0][0][2].kind == nnkRefTy 267 | 268 | 269 | ## 270 | ## 271 | ## Check Classes' generic parameters 272 | ## 273 | ## 274 | 275 | block: # Check generic parameters 276 | let ast: NimNode = parseStmt("Class *MyClass[K: int | string, V: float | string]") 277 | let output: NimNode = createClass(ast[0][1]) 278 | assert output[0][0][1].kind == nnkGenericParams 279 | assert output[0][0][1].len == 2 280 | assert output[0][0][1][1][1].kind == nnkInfix 281 | assert output[0][0][1][1][1].len == 3 282 | assert $output[0][0][1][1][1][2] == "string" 283 | 284 | 285 | block: # Check generic parameters 286 | let ast: NimNode = parseStmt("Class static MyClass*[K, V, Y, Z](Test[T])") 287 | let output: NimNode = createClass(ast[0][1]) 288 | assert output[0][0][1].kind == nnkGenericParams 289 | assert output[0][0][1][0].kind == nnkIdentDefs 290 | assert output[0][0][1][0].len == 6 291 | assert $output[0][0][1][0][3] == "Z" 292 | 293 | 294 | block: # Check generic parameters 295 | let ast: NimNode = parseStmt("Class MyClass[K, V : int | float, Y, Z: string]") 296 | let output: NimNode = createClass(ast[0][1]) 297 | assert output[0][0][1].kind == nnkGenericParams 298 | assert output[0][0][1].len == 4 299 | assert output[0][0][1][0].kind == nnkIdentDefs 300 | assert $output[0][0][1][0][0] == "K" 301 | assert $output[0][0][1][1][0] == "V" 302 | assert output[0][0][1][1][1].kind == nnkInfix 303 | assert $output[0][0][1][3][1] == "string" 304 | 305 | 306 | ## 307 | ## 308 | ## Check Classes' methods and procedures 309 | ## 310 | ## 311 | 312 | 313 | block: # Check methods and procedures 314 | let ast: NimNode = parseExpr("Class *MyClass") 315 | let output: NimNode = createClass(ast[1]) 316 | var count_methods: int = 0 317 | for elem in output[2][0][1]: 318 | if elem.kind == nnkMethodDef: 319 | count_methods += 1 320 | assert count_methods == 5 321 | assert output[1].kind == nnkProcDef 322 | 323 | 324 | block: # Check methods and procedures 325 | let ast: NimNode = parseExpr("Class static MyClass(Test)") 326 | let output: NimNode = createClass(ast[1]) 327 | var count_methods: int = 0 328 | for elem in output[2][0][1]: 329 | if elem.kind == nnkMethodDef: 330 | count_methods += 1 331 | assert count_methods == 5 332 | assert output[1].kind == nnkProcDef 333 | 334 | block: # Check methods and procedures 335 | let ast: NimNode = parseExpr("Class MyClass[T,R](Test)") 336 | let output: NimNode = createClass(ast[1]) 337 | var count_methods: int = 0 338 | var count_procedures: int = 0 339 | for elem in output[2][0][1]: 340 | if elem.kind == nnkMethodDef: 341 | count_methods += 1 342 | if elem.kind == nnkProcDef: 343 | count_procedures += 1 344 | assert count_procedures == 5 345 | assert count_methods == 0 346 | assert output[1].kind == nnkProcDef 347 | 348 | 349 | block: # Check methods and procedures 350 | let ast: NimNode = parseExpr("Class static MyClass*[T,R](Test[T])") 351 | let output: NimNode = createClass(ast[1]) 352 | var count_methods: int = 0 353 | var count_procedures: int = 0 354 | for elem in output[2][0][1]: 355 | if elem.kind == nnkMethodDef: 356 | count_methods += 1 357 | if elem.kind == nnkProcDef: 358 | count_procedures += 1 359 | assert count_procedures == 5 360 | assert count_methods == 0 361 | assert output[1].kind == nnkProcDef 362 | 363 | 364 | ## 365 | ## 366 | ## Check Classes' variables 367 | ## 368 | ## 369 | 370 | block: # Check variables 371 | let ast: NimNode = parseExpr(""" 372 | Class MyClass: 373 | var s: string 374 | var i: int = 0 375 | """) 376 | let output: NimNode = createClass(ast[1], ast[2]) 377 | assert output[0][0][2].kind == nnkRefTy 378 | assert output[0][0][2][0].kind == nnkObjectTy 379 | assert output[0][0][2][0][2].kind == nnkRecList 380 | assert output[0][0][2][0][2].len == 2 381 | assert $output[0][0][2][0][2][1][1] == "int" 382 | 383 | 384 | ## 385 | ## 386 | ## Check Classes' methods 387 | ## 388 | ## 389 | 390 | block: # Check methods 391 | let ast: NimNode = parseExpr(""" 392 | Class static MyClass: 393 | method test(s: MyClass) = echo "My Class" 394 | """) 395 | let output: NimNode = createClass(ast[1], ast[2]) 396 | assert output[0][0][2].kind == nnkObjectTy 397 | assert output[3].kind == nnkMethodDef 398 | assert output[3][0].kind == nnkIdent 399 | assert $output[3][0] == "test" 400 | 401 | 402 | block: # Check methods 403 | let ast: NimNode = parseExpr(""" 404 | Class MyClass: 405 | method test(s: MyClass) = echo "My Class" 406 | """) 407 | let output: NimNode = createClass(ast[1], ast[2]) 408 | assert output[0][0][2].kind == nnkRefTy 409 | assert output[3].kind == nnkMethodDef 410 | assert output[3][0].kind == nnkIdent 411 | assert $output[3][0] == "test" 412 | 413 | 414 | block: # Check methods 415 | let ast: NimNode = parseExpr(""" 416 | Class MyClass[T]: 417 | method test[T](s: MyClass[T]) = echo "My Class" 418 | """) 419 | let output: NimNode = createClass(ast[1], ast[2]) 420 | assert output[0][0][2].kind == nnkRefTy 421 | assert output[3].kind == nnkMethodDef 422 | assert output[3][0].kind == nnkIdent 423 | assert $output[3][0] == "test" 424 | 425 | block: # Check methods 426 | let ast: NimNode = parseExpr(""" 427 | Class static MyClass[T]: 428 | method test[T](s: MyClass[T]) = echo "My Class" 429 | """) 430 | let output: NimNode = createClass(ast[1], ast[2]) 431 | assert output[0][0][2].kind == nnkObjectTy 432 | assert output[3].kind == nnkMethodDef 433 | assert output[3][0].kind == nnkIdent 434 | assert $output[3][0] == "test" 435 | 436 | 437 | block: # Check proc 438 | let ast: NimNode = parseExpr(""" 439 | Class MyClass: 440 | proc test(s: typedesc[MyClass]) = echo "My Class" 441 | """) 442 | let output: NimNode = createClass(ast[1], ast[2]) 443 | assert output[0][0][2].kind == nnkRefTy 444 | assert output[3].kind == nnkProcDef 445 | assert output[3][0].kind == nnkIdent 446 | assert $output[3][0] == "test" 447 | 448 | 449 | ## 450 | ## 451 | ## Check Interfaces' methods 452 | ## 453 | ## 454 | 455 | 456 | block: # Check Interfaces 457 | let ast: NimNode = parseExpr(""" 458 | Interface IClass: 459 | method test1(i: IClass) 460 | method test2(i: IClass, value: int): int 461 | method test3(i: IClass, next: float): string 462 | """) 463 | let output: NimNode = createInterface(ast[1], ast[2]) 464 | assert output.kind == nnkStmtList 465 | assert output[0].kind == nnkTypeSection 466 | assert output[0][0].kind == nnkTypeDef 467 | assert output[0][0][0].kind == nnkIdent 468 | assert $output[0][0][0] == "IClass" 469 | assert output[len(output) - 1].kind == nnkMethodDef 470 | assert output[len(output) - 2].kind == nnkMethodDef 471 | assert output[len(output) - 3].kind == nnkMethodDef 472 | assert output[len(output) - 3][0].kind == nnkIdent 473 | assert $output[len(output) - 1][0] == "test3" 474 | assert $output[len(output) - 2][0] == "test2" 475 | assert $output[len(output) - 3][0] == "test1" 476 | assert output[len(output) - 1][4].kind == nnkPragma 477 | assert $output[len(output) - 1][4][0] == "base" 478 | assert output[len(output) - 1][6].kind == nnkStmtList 479 | assert output[len(output) - 1][6][0].kind == nnkRaiseStmt 480 | 481 | block: # Check Interfaces 482 | let ast: NimNode = parseExpr(""" 483 | Interface *IClass: 484 | method test1(i: IClass) 485 | method test2(i: IClass, value: int): int 486 | method test3(i: IClass, next: float): string 487 | """) 488 | let output: NimNode = createInterface(ast[1], ast[2]) 489 | assert output.kind == nnkStmtList 490 | assert output[0].kind == nnkTypeSection 491 | assert output[0][0].kind == nnkTypeDef 492 | assert output[0][0][0].kind == nnkPostfix 493 | assert $output[0][0][0][1] == "IClass" 494 | assert $output[0][0][2][0][1][0] == "ClassObj" 495 | assert output[len(output) - 1].kind == nnkMethodDef 496 | assert output[len(output) - 2].kind == nnkMethodDef 497 | assert output[len(output) - 3].kind == nnkMethodDef 498 | assert output[len(output) - 3][0].kind == nnkIdent 499 | assert $output[len(output) - 1][0] == "test3" 500 | assert $output[len(output) - 2][0] == "test2" 501 | assert $output[len(output) - 3][0] == "test1" 502 | assert output[len(output) - 1][4].kind == nnkPragma 503 | assert $output[len(output) - 1][4][0] == "base" 504 | assert output[len(output) - 1][6].kind == nnkStmtList 505 | assert output[len(output) - 1][6][0].kind == nnkRaiseStmt 506 | 507 | 508 | ## 509 | ## 510 | ## Check When statements 511 | ## 512 | ## 513 | 514 | 515 | block: # Check When for variables 516 | let ast: NimNode = parseExpr(""" 517 | Class MyClass: 518 | var i: int = 0 519 | when true: 520 | var s: string 521 | else: 522 | var f: float 523 | """) 524 | let output: NimNode = createClass(ast[1], ast[2]) 525 | assert output[0][0][2].kind == nnkRefTy 526 | assert output[0][0][2][0].kind == nnkObjectTy 527 | assert output[0][0][2][0][2].kind == nnkRecList 528 | assert output[0][0][2][0][2].len == 2 529 | assert output[0][0][2][0][2][1].kind == nnkRecWhen 530 | assert output[0][0][2][0][2][1].len == 2 531 | assert output[0][0][2][0][2][1][0].kind == nnkElifBranch 532 | assert output[0][0][2][0][2][1][1].kind == nnkElse 533 | assert output[0][0][2][0][2][1][1][0].kind == nnkRecList 534 | assert $output[0][0][2][0][2][1][1][0][0][0] == "f" 535 | 536 | 537 | 538 | block: # Check When for methods, proc, functions 539 | let ast: NimNode = parseExpr(""" 540 | Class MyClass: 541 | when defined(linux): 542 | proc x(self: MyClass) = echo "linux" 543 | elif defined(windows): 544 | func x(self: MyClass) = echo "windows" 545 | else: 546 | method x(self: MyClass) = echo "others" 547 | """) 548 | let output: NimNode = createClass(ast[1], ast[2]) 549 | assert output[len(output) - 1].kind == nnkWhenStmt 550 | assert output[len(output) - 1].len == 3 551 | assert output[len(output) - 1][0].kind == nnkElifBranch 552 | assert output[len(output) - 1][1].kind == nnkElifBranch 553 | assert output[len(output) - 1][2].kind == nnkElse 554 | assert output[len(output) - 1][2][0][0].kind == nnkMethodDef 555 | assert $output[len(output) - 1][2][0][0][0] == "x" 556 | 557 | 558 | ## 559 | ## 560 | ## Check Pragmas 561 | ## 562 | ## 563 | 564 | block: # Check methods and procedures 565 | let ast: NimNode = parseExpr("Class MyClass {.final.}") 566 | let output: NimNode = createClass(ast[1]) 567 | assert output[0][0].kind == nnkTypeDef 568 | assert output[0][0][0].kind == nnkPragmaExpr 569 | assert output[0][0][0][0].kind == nnkIdent 570 | assert output[0][0][0][1].kind == nnkPragma 571 | assert $output[0][0][0][1][0] == "final" 572 | 573 | 574 | block: # Check methods and procedures 575 | let ast: NimNode = parseExpr("Class static MyClass {.final.}") 576 | let output: NimNode = createClass(ast[1]) 577 | assert output[0][0].kind == nnkTypeDef 578 | assert output[0][0][0].kind == nnkPragmaExpr 579 | assert output[0][0][0][0].kind == nnkIdent 580 | assert output[0][0][0][1].kind == nnkPragma 581 | assert $output[0][0][0][1][0] == "final" 582 | 583 | 584 | block: # Check methods and procedures 585 | let ast: NimNode = parseExpr("Class *MyClass {.final.}") 586 | let output: NimNode = createClass(ast[1]) 587 | assert output[0][0].kind == nnkTypeDef 588 | assert output[0][0][0].kind == nnkPragmaExpr 589 | assert output[0][0][0][0].kind == nnkPostfix 590 | assert output[0][0][0][1].kind == nnkPragma 591 | assert $output[0][0][0][1][0] == "final" 592 | 593 | 594 | block: # Check methods and procedures 595 | let ast: NimNode = parseExpr("Class static MyClass[XY, YZ] {.final.}") 596 | let output: NimNode = createClass(ast[1]) 597 | assert output[0][0].kind == nnkTypeDef 598 | assert output[0][0][0].kind == nnkPragmaExpr 599 | assert output[0][0][0][0].kind == nnkIdent 600 | assert output[0][0][0][1].kind == nnkPragma 601 | assert $output[0][0][0][1][0] == "final" 602 | 603 | 604 | block: # Check methods and procedures 605 | let ast: NimNode = parseExpr("Class static MyClass*[XY, YZ](Test) {.final.}") 606 | let output: NimNode = createClass(ast[1]) 607 | assert output[0][0].kind == nnkTypeDef 608 | assert output[0][0][0].kind == nnkPragmaExpr 609 | assert output[0][0][0][0].kind == nnkPostfix 610 | assert output[0][0][0][1].kind == nnkPragma 611 | assert $output[0][0][0][1][0] == "final" 612 | 613 | -------------------------------------------------------------------------------- /tests/mock.nim: -------------------------------------------------------------------------------- 1 | import nimcls 2 | 3 | const url1*: string = "http://wiki.com" 4 | const url2*: string = "https://google.com" 5 | const url3*: string = "https://x.com" 6 | const url4*: string = "https://z.com" 7 | 8 | Class *Service: 9 | var url: string 10 | method init*(self: Service, link: string = url1) {.base.} = 11 | self.url = link 12 | method getURL*(self: Service): string {.base.} = 13 | return self.url 14 | 15 | Class ChildServiceA*(Service): 16 | var port: int 17 | method init*(self: ChildServiceA, port: int) {.base.} = 18 | procCall super(self).init(url2) 19 | self.port = port 20 | method getPort*(self: ChildServiceA): int {.base.} = 21 | return self.port 22 | method connect*(self: ChildServiceA) {.base.} = 23 | echo "Connected" 24 | 25 | Class ChildServiceB*(Service): 26 | var port: int 27 | var key*: string 28 | method init*(self: ChildServiceB, port: int, key: string) {.base.} = 29 | procCall self.super.init(url3) 30 | self.port = port 31 | self.key = key 32 | method getPort*(self: ChildServiceB): int {.base.} = 33 | return self.port 34 | method getURL*(self: ChildServiceB): string = 35 | return url4 36 | method callParentURL*(self: ChildServiceB): string {.base.} = 37 | return procCall self.super.getURL 38 | method callParentInit*(self: ChildServiceB) {.base.} = 39 | procCall self.super.init(url2) 40 | 41 | Class ChildServiceC*(ChildServiceB): 42 | proc runService(self: ChildServiceC) = 43 | discard 44 | func calcNumber(self: ChildServiceC) : int = 45 | return self.port*100 46 | method getKey(self: ChildServiceC) : string {.base.} = 47 | return self.key 48 | method getURL*(self: ChildServiceC): string = 49 | return url1 50 | 51 | Class *AA: 52 | var number*: int = 1 53 | Class *AB: 54 | var number*: int = 2 55 | Class *AC: 56 | var number*: int = 3 57 | 58 | Class static *Loader: 59 | var path: string 60 | var code*: int = 22 61 | method load*(self: Loader) : string {.base.} = 62 | return "LoaderCall" 63 | 64 | Class static ChildLoaderA*(Loader): 65 | method load*(self: ChildLoaderA) : string = 66 | return "ChildLoaderACall" 67 | var more: int 68 | 69 | 70 | Class static ChildLoaderB*(Loader): 71 | method load*(self: ChildLoaderB): string = 72 | return "ChildLoaderBCall" 73 | 74 | Class static *SAA: 75 | var number*: int = 1 76 | Class static *SAB: 77 | var number*: int = 2 78 | Class static *SAC: 79 | var number*: int = 3 80 | 81 | 82 | Class *G1[T]: 83 | var value: T 84 | 85 | Class G2*[R, T](G1[T]): 86 | var next: R 87 | 88 | Class G3*[R : int, T, Y: string | float](G2[R, T]): 89 | var final: Y 90 | 91 | --------------------------------------------------------------------------------