Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

6 another way to create interface #7

Merged
merged 4 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading