Skip to content

Commit

Permalink
6 another way to create interface (#7)
Browse files Browse the repository at this point in the history
* fix

* fix test

* 0.2.4

* fix test yaml
  • Loading branch information
itsumura-h authored Jan 24, 2024
1 parent 3b64362 commit 3e15837
Show file tree
Hide file tree
Showing 23 changed files with 355 additions and 54 deletions.
36 changes: 18 additions & 18 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,25 @@ jobs:
- windows-latest
# - macOS-latest
nim-version:
- 1.2.0
- 1.4.0
- 1.6.0
- stable
needs: before
steps:
- uses: actions/checkout@v2
- name: Cache choosenim
id: cache-choosenim
uses: actions/cache@v1
with:
path: ~/.choosenim
key: ${{ runner.os }}-choosenim-${{ matrix.nim-version }}
- name: Cache nimble
id: cache-nimble
uses: actions/cache@v1
with:
path: ~/.nimble
key: ${{ runner.os }}-nimble-${{ matrix.nim-version }}
- uses: jiro4989/setup-nim-action@v1
with:
nim-version: ${{ matrix.nim-version }}
- run: testament p tests/*.nim
- uses: actions/checkout@v2
- name: Cache choosenim
id: cache-choosenim
uses: actions/cache@v1
with:
path: ~/.choosenim
key: ${{ runner.os }}-choosenim-${{ matrix.nim-version }}
- name: Cache nimble
id: cache-nimble
uses: actions/cache@v1
with:
path: ~/.nimble
key: ${{ runner.os }}-nimble-${{ matrix.nim-version }}
- uses: jiro4989/setup-nim-action@v1
with:
nim-version: ${{ matrix.nim-version }}
- run: testament p tests/*.nim
144 changes: 135 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,49 @@ interface-implements

![](https://github.com/itsumura-h/nim-interface-implements/workflows/Build%20and%20test%20Nim/badge.svg)

`implements` macro creates `toInterface` proc. It provides polymorphism.
Multiple procedures can be set in `implements` block.
There are two ways to achieve polymorphism in Nim. One is to create `tuple` and another is `dynamic dispatch`.

**tuple**
```nim
type IRepository* = tuple
exec: proc(msg:string):string
```

```nim
type Repository* = object
proc exec(self:Repository, msg:string):string =
return &"Repository {msg}"
proc toInterface*(selfRepository):IRepository =
return (
exec: proc(msg:string):string = self.exec(msg)
)
```

**dynamic dispatch**
```nim
type IRepository* = object of RootObj
method exec(self:IRepository, msg:string):string {.base.} = raise newException(CatchableError, "error")
```

```nim
type Repository* = object of IRepository
method exec(self:Repository, msg:string):string =
return &"Repository {msg}"
```

## install

```sh
nimble install interface_implements
```

## implements
`implements` macro creates `toInterface` proc.

```nim
import interface_implements
Expand All @@ -22,7 +58,7 @@ implements Repository, IRepository:
proc func2(self:Repository, number:int):string =
return "Repository2 " & $number
```
This is converted to bellow
This is converted to bellow.

```nim
proc func1(self:Repository, msg:string):string =
Expand All @@ -38,13 +74,12 @@ proc toInterface*(self:Repository):IRepository =
)
```

## API
### API
```nim
macro implements*(implName, interfaceName, procs:untyped):untyped
```

## Example

### Example
repository_interface.nim
```nim
type IRepository* = tuple
Expand All @@ -53,8 +88,8 @@ type IRepository* = tuple

mock_repository.nim
```nim
import repository_interface
import interface_implements
import ./repository_interface
type MockRepository = ref object
Expand All @@ -68,8 +103,8 @@ implements MockRepository, IRepository:

repository.nim
```nim
import repository_interface
import interface_implements
import ./repository_interface
type Repository = ref object
Expand All @@ -83,7 +118,7 @@ implements Repository, IRepository:

usecase.nim
```nim
import repository_interface
import ./repository_interface
type Usecase = ref object
repository: IRepository
Expand All @@ -108,4 +143,95 @@ block:
assert "Repository exec" == usecase.exec("exec")
```

## interfaceDef
`interfaceDef` macro creates `method` of fields of Object.
This is only available in Nim 2.0.0 and above.


```nim
import interface_implements
interfaceDef:
type IRepository* = object of RootObj
exec: proc(self:IRepository, msg:string):string
```
This is converted to bellow.

```nim
type IRepository* = object of RootObj
method exec(self:IRepository, msg:string):string {.base.} = raise newException(CatchableError, "Implementation exec of IRepository is not found")
```


### API
```nim
macro interfaceDefs*(body:untyped):untyped
```

### Example

repository_interface.nim
```nim
interfaceDef:
type IRepository* = object of RootObj
exec: proc(msg:string):string
```

mock_repository.nim
```nim
import interface_implements
type MockRepository = object of IRepository
proc newMockRepository*():MockRepository =
return MockRepository()
method exec*(self:MockRepository, msg:string):string =
return "MockRepository " & msg
```

repository.nim
```nim
import interface_implements
type Repository = object of IRepository
proc newRepository*():Repository =
return Repository()
method exec*(self:Repository, msg:string):string =
return "Repository " & msg
```

usecase.nim
```nim
import ./repository_interface
type Usecase = object
repository: IRepository
func newUsecase*(repository:IRepository):Usecase =
return Usecase(repository:repository)
proc exec*(self:Usecase, msg:string):string =
return self.repository.exec(msg)
```

presentation layer
```nim
block:
let repository = newMockRepository()
let usecase = newUsecase(repository)
assert "MockRepository mock" == usecase.exec("mock")
block:
let repository = newRepository()
let usecase = newUsecase(repository)
assert "Repository exec" == usecase.exec("exec")
```

---

Both pattern achieve structure as bellow.
![](./design.png)
9 changes: 9 additions & 0 deletions examples/field_assign/main.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import ./repository
import ./usecase

proc main() =
let repo = Repository.new()
let usecase = Usecase.new(repo)
usecase.exec()

main()
11 changes: 11 additions & 0 deletions examples/field_assign/repository.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import ./repository_interface

type Repository* = object of IRepository

proc execImpl(self:IRepository, msg:string):string =
return "Repository " & msg

proc new*(_:type Repository):Repository =
return Repository(
exec:execImpl
)
4 changes: 4 additions & 0 deletions examples/field_assign/repository_interface.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type IRepository* = object of RootObj
exec:proc(self:IRepository, msg:string):string

proc exec*(self:IRepository, msg:string):string = self.exec(self, msg)
12 changes: 12 additions & 0 deletions examples/field_assign/usecase.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import ./repository_interface

type Usecase* = object
repo:IRepository

proc new*(_:type Usecase, repo:IRepository):Usecase =
return Usecase(
repo:repo
)

proc exec*(self:Usecase) =
echo self.repo.exec("hoge")
9 changes: 9 additions & 0 deletions examples/use_method/main.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import ./repository
import ./usecase

proc main() =
let repo = Repository.new()
let usecase = Usecase.new(repo)
usecase.exec()

main()
9 changes: 9 additions & 0 deletions examples/use_method/repository.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import ./repository_interface

type Repository* = object of IRepository

proc new*(_:type Repository):Repository =
return Repository()

method exec*(self:Repository, msg:string):string =
return "Repository " & msg
3 changes: 3 additions & 0 deletions examples/use_method/repository_interface.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
type IRepository* = object of RootObj

method exec*(self:IRepository, msg:string):string {.base.} = raise newException(Exception, "")
12 changes: 12 additions & 0 deletions examples/use_method/usecase.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import ./repository_interface

type Usecase* = object
repo:IRepository

proc new*(_:type Usecase, repo:IRepository):Usecase =
return Usecase(
repo:repo
)

proc exec*(self:Usecase) =
echo self.repo.exec("hoge")
12 changes: 11 additions & 1 deletion interface_implements.nimble
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Package

version = "0.2.3"
version = "0.2.4"
author = "dumblepy"
description = "Creating toInterface macro."
license = "MIT"
Expand All @@ -10,3 +10,13 @@ srcDir = "src"
# Dependencies

requires "nim >= 1.2.0"


import std/strformat
import std/os

task test, "run testament test":
exec &"testament p 'tests/test_*.nim'"
for kind, path in walkDir(getCurrentDir() / "tests"):
if not path.contains(".") and path.fileExists():
exec "rm -f " & path
43 changes: 43 additions & 0 deletions src/interface_implements.nim
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,46 @@ macro implements*(implName, interfaceName, procs:untyped):untyped =
)
"""
return (procsStr & '\n' & resultStr).parseStmt

# ================================================================================

# interfaceDefs:
# type IRepository* = object of RootObj
# hoge: proc (self: IRepository, str:string):Future[string]
# type IRepository2* = object of RootObj
# hoge: proc (self: IRepository2, str:string):Future[string]

when NimMajor >= 2:
macro interfaceDefs*(body:untyped):untyped =
var res = ""
for i, interfaceRows in body: # type IRepository* = object of RootObj | type IRepository2* = object of RootObj
let interfaceName = interfaceRows[0][0].repr.replace("*", "")
let interfaceDef = interfaceRows[0].repr.splitLines()[0] # IRepository* = object of RootObj
var methods:seq[string]
let procRows = interfaceRows[0][2][2]
for procRow in procRows: # hoge: proc (self: IRepository, str:string):Future[string]
let procName = procRow[0].repr.replace("*", "") # hoge
let procDef = procRow[1][0] # (self: IRepository, str:string):Future[string]
var returnType = ""
var args:seq[string] # @["self: IRepository", "str: string"]
var argNames:seq[string] # @["self", "str"]
for i, argsRow in procDef:
if i == 0:
if argsRow.repr.len > 0: returnType = ":" & argsRow.repr # :Future[string]
continue
let argDef = argsRow.repr # self: IRepository | str: string
args.add(argDef)
let argName = argsRow[0].repr # self | str
argNames.add(argName)

let argsInMethod = args.join(", ")
let methodRow = fmt(
"method [procName]*([argsInMethod])[returnType] {.base.} = raise newException(CatchableError, \"Implementation [procName] of [interfaceName] is not found\")",
'[',
']',
)
methods.add(methodRow)
let interfaceStr = if i == 0: &"type {interfaceDef}\n" else: &"\ntype {interfaceDef}\n"
res.add(interfaceStr & methods.join("\n") & "\n" )
when not defined(release): echo res
return parseStmt(res)
Loading

0 comments on commit 3e15837

Please sign in to comment.