From 3878ee0ad0a710f47b1ca9d5fc84983d3ef233d1 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 22 May 2018 20:48:12 +0100 Subject: [PATCH] Implemented signature parsing to create client rpc procs based on params --- eth-rpc/client/clientdispatch.nim | 119 +++++++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 11 deletions(-) diff --git a/eth-rpc/client/clientdispatch.nim b/eth-rpc/client/clientdispatch.nim index 36dacfa..59888ef 100644 --- a/eth-rpc/client/clientdispatch.nim +++ b/eth-rpc/client/clientdispatch.nim @@ -1,4 +1,5 @@ import asyncnet, asyncdispatch, tables, json, oids, ethcalls, macros +import ../ ethtypes, stint, ../ jsonconverters type RpcClient* = ref object @@ -88,17 +89,113 @@ proc connect*(self: RpcClient, address: string, port: Port) {.async.} = self.port = port asyncCheck processData(self) -macro generateCalls: untyped = - ## Generate templates for client calls so that: - ## client.call("web3_clientVersion", params) - ## can be written as: - ## client.web3_clientVersion(params) +import jsonmarshal + +proc createRpcFromSig*(rpcDecl: NimNode): NimNode = + let procNameState = rpcDecl[0] + var + parameters = rpcDecl.findChild(it.kind == nnkFormalParams).copy + path: NimNode + pathStr: string + + # get proc signature's name. This becomes the path we send to the server + if procNameState.kind == nnkPostFix: + path = rpcDecl[0][1] + else: + path = rpcDecl[0] + pathStr = $path + + if parameters.isNil or parameters.kind == nnkEmpty: + parameters = newNimNode(nnkFormalParams) + + # if no return parameters specified (parameters[0]) + if parameters.len == 0: parameters.add(newEmptyNode()) + # insert rpc client as first parameter + let + clientParam = + nnkIdentDefs.newTree( + ident"client", + ident"RpcClient", + newEmptyNode() + ) + parameters.insert(1, clientParam) + + # For each input parameter we need to + # take the Nim type and translate to json with `%`. + # For return types, we need to take the json and + # convert it to the Nim type. + var + callBody = newStmtList() + returnType: NimNode + let jsonParamIdent = genSym(nskVar, "jsonParam") + callBody.add(quote do: + var `jsonParamIdent` = newJArray() + ) + if parameters.len > 2: + # skip return type and the inserted rpc client parameter + # add the rest to json node via `%` + for i in 2 ..< parameters.len: + let curParam = parameters[i][0] + if curParam.kind != nnkEmpty: + callBody.add(quote do: + `jsonParamIdent`.add(%`curParam`) + ) + if parameters[0].kind != nnkEmpty: + returnType = parameters[0] + else: + returnType = ident"JsonNode" + + # convert return type to Future + parameters[0] = nnkBracketExpr.newTree(ident"Future", returnType) + + # client call to server using json params + var updatedParams = newSeq[NimNode]() + # convert parameter tree to seq + for p in parameters: updatedParams.add(p) + # create new proc + result = newProc(path, updatedParams, callBody) + # convert this proc to async + result.addPragma ident"async" + # export this proc + result[0] = nnkPostFix.newTree(ident"*", ident(pathStr)) + # add rpc call to proc body + var callResult = genSym(nskVar, "res") + + callBody.add(quote do: + let res = await client.call(`pathStr`, `jsonParamIdent`) + if res.error: raise newException(ValueError, $res.result) + var `callResult` = res.result + ) + let + procRes = ident"result" + # now we need to extract the response and build it into the expected type + if returnType != ident"JsonNode": + let setup = setupParamFromJson(procRes, returnType, callResult) + callBody.add(quote do: `setup`) + else: + callBody.add(quote do: + `procRes` = `callResult` + ) + when defined(nimDumpRpcs): + echo pathStr, ":\n", result.repr + +from os import getCurrentDir, DirSep +from strutils import rsplit + +macro processRpcSigs(): untyped = result = newStmtList() - for callName in ETHEREUM_RPC_CALLS: - let nameLit = ident(callName) - result.add(quote do: - template `nameLit`*(client: RpcClient, params: JsonNode): Future[Response] = client.call(`callName`, params) # TODO: Back to template - ) + const + codePath = currentSourcePath.rsplit(DirSep, 1)[0] & DirSep & "ethcallsigs.nim" + code = staticRead(codePath) + + let parsedCode = parseStmt(code) + var line = 0 + while line < parsedCode.len: + if parsedCode[line].kind == nnkProcDef: break + line += 1 + for curLine in line ..< parsedCode.len: + var procDef = createRpcFromSig(parsedCode[curLine]) + result.add(procDef) # generate all client ethereum rpc calls -generateCalls() +processRpcSigs()