Skip to content

Commit

Permalink
Update to v0.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
xomachine committed Feb 27, 2018
2 parents aad2ce3 + 0da16e2 commit b28dbce
Show file tree
Hide file tree
Showing 25 changed files with 1,034 additions and 289 deletions.
110 changes: 110 additions & 0 deletions demos/ntp.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#
# NTP client demo
# ---------------
# Makes a request to ntp server and shows information.
# All serialization/deserialization work is being performed by NESM.
#

import
nativesockets,
net,
os,
sequtils,
strutils,
times

import NESM


serializable:
static:
# Types are static, so their size after serialization
# can be calculated at compile time
type
FixedPoint16dot16 = uint32

NTPTimeStamp = object
set: {endian: bigEndian}
# The endian of following fields is set to bigEndian,
# so following values will be swapped while serialization
# and there is no necessity to use htonl/ntohl
seconds: uint32
fraction: uint32

LeapVersionMode = uint8

NTPPacket = object
leap_version_mode: LeapVersionMode
stratum: uint8
polling_interval: int8
clock_precision: uint8
delay: FixedPoint16dot16
dispersion: FixedPoint16dot16
reference_id: uint32
reference_ts: NTPTimeStamp
origin_ts: NTPTimeStamp
receive_ts: NTPTimeStamp
transmit_ts: NTPTimeStamp

const twoto32 = 4294967296.0
const packet_size = NTPPacket.size() # Compile-time type size calculation
const unix_time_delta = 2208988800.0 # 1900 to 1970 in seconds

proc toEpoch(ts: NTPTimeStamp): float64 =
let
sec = ts.seconds.float - unix_time_delta
frac = ts.fraction.float / twoto32
sec + frac

proc fromEpoch(epoch: float64): NTPTimeStamp =
let secs = epoch.uint32
NTPTimeStamp(seconds: secs,
fraction: uint32((epoch - secs.float) * twoto32))

when isMainModule:
if paramCount() != 1:
echo "Usage $# <ipaddr>" % paramStr(0)
quit(1)

let target = paramStr(1)
let b = NTPPacket(

# example values
leap_version_mode: 0x23,
stratum: 0x03,
polling_interval: 0x06,
clock_precision: 0xfa,
delay: 0x00010000,
dispersion: 0x00010000,
reference_id: 0xaabbcc,
origin_ts: epochTime().fromEpoch,
)

let bytes = cast[string](b.serialize())

var socket = newSocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, true)
# client send time
let sendtime = epochTime()
doAssert socket.sendTo(target, Port(123), bytes) == packet_size

let raw_resp = socket.recv(packet_size, timeout=1000)

# client receive time
let resptime = epochTime()

assert raw_resp.len == packet_size
let resp: NTPPacket = deserialize(NTPPacket, raw_resp)

let recvtime = resp.receive_ts.toEpoch
let anstime = resp.transmit_ts.toEpoch

# NTP delay / clock_offset calculation
let delay = (resptime - sendtime) - (anstime - recvtime)
let clock_offset = (recvtime - sendtime + anstime - resptime)/2

echo " Request sent: ", sendtime
echo " Request received: ", recvtime
echo " Answer sent: ", anstime
echo " Answer received: ", resptime
echo " Round-trip delay: ", delay * 1000, " ms"
echo " Clock offset: ", clock_offset * 1000, " ms"
231 changes: 75 additions & 156 deletions nesm.nim
Original file line number Diff line number Diff line change
@@ -1,147 +1,39 @@
when defined(js):
error("Non C-like targets non supported yet.")

{.deadCodeElim: on.}
import macros
from strutils import `%`
from sequtils import toSeq
from tables import Table, initTable, contains, `[]`, `[]=`
from streams import Stream, newStringStream
from tables import contains, `[]`, `[]=`
from streams import Stream

when not defined(nimdoc):
from nesm.typesinfo import TypeChunk, Context
from nesm.generator import genTypeChunk, STREAM_NAME
from nesm.typesinfo import TypeChunk, Context, initContext
from nesm.generator import genTypeChunk
from nesm.procgen import generateProcs
from nesm.settings import applyOptions, splitSettingsExpr
else:
import endians
type TypeChunk = object
include nesm.documentation

const SERIALIZER_INPUT_NAME = "obj"
const DESERIALIZER_DATA_NAME = "data"
const SERIALIZE_DECLARATION = """proc serialize$1(""" &
SERIALIZER_INPUT_NAME & """: $2): string = discard"""
const DESERIALIZE_DECLARATION = """proc deserialize$1""" &
"""(thetype: typedesc[$2], """ & DESERIALIZER_DATA_NAME &
""": seq[byte | char | int8 | uint8] | string):""" &
"""$2 = discard"""

when not defined(nimdoc):
proc makeSerializeStreamDeclaration(typename: string,
is_exported: bool,
body: NimNode): NimNode {.compileTime.} =
let itn = !typename
let fname =
if is_exported: newIdentNode("serialize").postfix("*")
else: newIdentNode("serialize")
let isin = !SERIALIZER_INPUT_NAME
quote do:
proc `fname`(`isin`: `itn`,
`STREAM_NAME`: Stream) = `body`

proc makeDeserializeStreamDeclaration(typename: string,
is_exported: bool,
body: NimNode): NimNode {.compileTime.} =
let itn = !typename
let fname =
if is_exported:
newIdentNode("deserialize").postfix("*")
else: newIdentNode("deserialize")
quote do:
proc `fname`(thetype: typedesc[`itn`],
`STREAM_NAME`: Stream): `itn` = `body`

proc makeSerializeStreamConversion(): NimNode {.compileTime.} =
let isin = !SERIALIZER_INPUT_NAME
quote do:
let ss = newStringStream()
serialize(`isin`, ss)
ss.data

proc makeDeserializeStreamConversion(name: string): NimNode {.compileTime.} =
let iname = !name
let ddn = !DESERIALIZER_DATA_NAME
quote do:
assert(`ddn`.len >= type(`iname`).size(),
"Given sequence should contain at least " &
$(type(`iname`).size()) & " bytes!")
let ss = newStringStream(cast[string](`ddn`))
deserialize(type(`iname`), ss)

const STATIC_SIZE_DECLARATION =
"""proc size$1(thetype: typedesc[$2]): int = discard"""
const SIZE_DECLARATION = "proc size$1(" &
SERIALIZER_INPUT_NAME &
": $2): int = discard"


when not defined(nimdoc):
static:
var ctx: Context
ctx.declared = initTable[string, TypeChunk]()
proc generateProc(pattern: string, name: string,
sign: string,
body: NimNode = newEmptyNode()): NimNode =
result = parseExpr(pattern % [sign, name])
if body.kind != nnkEmpty:
result.body = body

proc generateProcs(context: var Context,
obj: NimNode): NimNode {.compileTime.} =
expectKind(obj, nnkTypeDef)
expectMinLen(obj, 3)
expectKind(obj[1], nnkEmpty)
let typename = if obj[0].kind == nnkPragmaExpr: obj[0][0] else: obj[0]
let is_shared = typename.kind == nnkPostfix
let name = if is_shared: $typename.basename else: $typename
let sign =
if is_shared: "*"
else: ""
let body = obj[2]
let info = context.genTypeChunk(body)
let size_node =
info.size(newIdentNode(SERIALIZER_INPUT_NAME))
context.declared[name] = info
let writer_conversion = makeSerializeStreamConversion()
let serializer = generateProc(SERIALIZE_DECLARATION,
name, sign,
writer_conversion)
let serialize_stream =
makeSerializeStreamDeclaration(name, is_shared,
info.serialize(newIdentNode(SERIALIZER_INPUT_NAME)))
let obtainer_conversion =
if context.is_static:
makeDeserializeStreamConversion("result")
else: newEmptyNode()
let deserializer =
if context.is_static:
generateProc(DESERIALIZE_DECLARATION, name, sign,
obtainer_conversion)
else: newEmptyNode()
let deserialize_stream =
makeDeserializeStreamDeclaration(name, is_shared,
info.deserialize(newIdentNode("result")))
let size_declaration =
if context.is_static: STATIC_SIZE_DECLARATION
else: SIZE_DECLARATION
let sizeProc = generateProc(size_declaration, name, sign,
size_node)
newStmtList(sizeProc, serialize_stream, serializer,
deserialize_stream, deserializer)

proc prepare(context: var Context, statements: NimNode
): NimNode {.compileTime.} =
result = newStmtList()
case statements.kind
of nnkStmtList, nnkTypeSection, nnkStaticStmt:
let oldstatic = context.is_static
context.is_static = context.is_static or
(statements.kind == nnkStaticStmt)
for child in statements.children():
result.add(context.prepare(child))
context.is_static = oldstatic
of nnkTypeDef:
result.add(context.generateProcs(statements))
else:
error("Only type declarations can be serializable")
static:
var ctx = initContext()
proc prepare(context: var Context, statements: NimNode
): NimNode {.compileTime.} =
result = newStmtList()
case statements.kind
of nnkStmtList, nnkTypeSection, nnkStaticStmt:
let oldstatic = context.is_static
context.is_static = context.is_static or
(statements.kind == nnkStaticStmt)
for child in statements.children():
result.add(context.prepare(child))
context.is_static = oldstatic
of nnkTypeDef:
result.add(context.generateProcs(statements))
else:
error("Only type declarations can be serializable")

proc cleanupTypeDeclaration(declaration: NimNode): NimNode =
var children = newSeq[NimNode]()
Expand All @@ -154,20 +46,64 @@ proc cleanupTypeDeclaration(declaration: NimNode): NimNode =
for cc in c.children():
children.add(cleanupTypeDeclaration(cc))
of nnkIdentDefs:
if c[^2].repr == "cstring":
let last = if c[^1].kind == nnkEmpty: c.len - 2 else: c.len - 1
let (originalType, options) = c[last].splitSettingsExpr()
if options.len > 0:
# newID need to be a NimNode that contains IdentDefs node
# to utilze recursive call of cleanupTypeDeclaration
var newID = newTree(nnkStmtList, c)
#copyChildrenTo(c, newID[0])
# first element of CurlyExpr is an actual type
newID[0][last] = originalType
children.add(newID.cleanupTypeDeclaration()[0])
elif c[last].repr == "cstring":
var newID = newNimNode(nnkIdentDefs)
copyChildrenTo(c, newID)
newID[^2] = newIdentNode("string")
newID[last] = newIdentNode("string")
children.add(newID)
elif c.len == 3 and c[0] == settingsKeyword and
c[1].kind == nnkTableConstr:
continue
elif c[last].kind == nnkTupleTy:
var newID = c
newID[last] = cleanupTypeDeclaration(c[last])
children.add(newID)
else:
children.add(c)
else:
children.add(cleanupTypeDeclaration(c))
newTree(declaration.kind, children)

macro nonIntrusiveBody(typename: typed, o: untyped, de: static[bool]): untyped =
let typebody = getTypeImpl(typename)
when defined(debug):
hint("Deserialize? " & $de)
hint(typebody.treeRepr)
hint(typebody.repr)
let chunk = ctx.genTypeChunk(typebody)
result = if de: chunk.deserialize(o) else: chunk.serialize(o)
when defined(debug):
hint(result.repr)

template nonIntrusiveTemplate[S](o: S, de: static[bool]) =
nonIntrusiveBody(S, o, de)

proc serialize*[T](obj: T, thestream: Stream) =
## The non-intrusive serialize proc which allows to perform object
## serialization without special declaration.
## The negative side-effect of such approach is inability to pass any options
## (like `endian` or `dynamic`) to the serializer and lack of nested objects
## support. This proc should be imported directly.
nonIntrusiveTemplate(obj, false)

proc deserialize*[T](thestream: Stream): T =
## The non-intrusive deserialize proc which allows to perform object
## deserialization without special declaration.
## The negative side-effect of such approach is inability to pass any options
## (like `endian` or `dynamic`) to the serializer and lack of nested objects
## support. This proc should be imported directly.
nonIntrusiveTemplate(result, true)

macro toSerializable*(typedecl: typed, settings: varargs[untyped]): untyped =
## Generate [de]serialize procedures for existing type with given settings.
##
Expand All @@ -185,30 +121,13 @@ macro toSerializable*(typedecl: typed, settings: varargs[untyped]): untyped =
when defined(debug):
hint(typedecl.symbol.getImpl().treeRepr())
var ast = typedecl.symbol.getImpl()
for arg in settings:
arg.expectKind(nnkExprColonExpr)
arg.expectMinLen(2)
let key = $arg[0]
let value = $arg[1]
case key
of "dynamic":
if value == "false":
ctx.is_static = true
elif value == "true":
ctx.is_static = false
else:
error("'dynamic' property can be only 'true' or 'false', but not: " &
value)
of "endian":
if value != $cpuEndian:
ctx.swapEndian = true
else:
error("Unknown property: " & key)
var newctx = ctx.applyOptions(settings)
when defined(debug):
hint(ast.treeRepr)
result.add(ctx.prepare(ast))
ctx.is_static = false
ctx.swapEndian = false
result.add(newctx.prepare(ast))
ctx.declared = newctx.declared
when defined(debug):
hint(result.repr)

macro serializable*(typedecl: untyped): untyped =
## The main macro that generates code.
Expand Down
2 changes: 1 addition & 1 deletion nesm.nimble
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

version = "0.3.1"
version = "0.4.0"
author = "xomachine (Fomichev Dmitriy)"
description = "NESM stands for Nim's Easy Serialization Macro. The macro allowing generation of serialization functions by one line of code!"
license = "MIT"
Expand Down
Loading

0 comments on commit b28dbce

Please sign in to comment.