Viktor Kirilov
C++ dev
by Viktor Kirilov
echo "Hello World"
type
# or use {.borrow.} here to inherit everything
Dollars* = distinct float
proc `+` *(a, b: Dollars): Dollars {.borrow.}
var a = 20.Dollars
a = 25 # Doesn't compile
a = 25.Dollars # Works fine
a = 20.Dollars * 20.Dollars # Doesn't compile
a = 20.Dollars + 20.Dollars # Works fine
type
CustomRange = object
low: int
high: int
iterator items(range: CustomRange): int =
var i = range.low
while i <= range.high:
yield i
inc i
iterator pairs(range: CustomRange): tuple[a: int, b: char] =
for i in range: # uses CustomRange.items
yield (i, char(i + ord('a')))
for i, c in CustomRange(low: 1, high: 3):
echo c
# prints: b, c, d
# This is an example how an abstract syntax tree could be modelled in Nim
type
NodeKind = enum # the different node types
nkInt, # a leaf with an integer value
nkFloat, # a leaf with a float value
nkString, # a leaf with a string value
nkAdd, # an addition
nkSub, # a subtraction
nkIf # an if statement
Node = ref object
case kind: NodeKind # the ``kind`` field is the discriminator
of nkInt: intVal: int
of nkFloat: floatVal: float
of nkString: strVal: string
of nkAdd, nkSub:
leftOp, rightOp: Node
of nkIf:
condition, thenPart, elsePart: Node
var n = Node(kind: nkFloat, floatVal: 1.0)
# the following statement raises an `FieldError` exception, because
# n.kind's value does not fit:
n.strVal = ""
type
Thing = ref object of RootObj
Unit = ref object of Thing
x: int
method collide(a, b: Thing) {.inline.} =
quit "to override!"
method collide(a: Thing, b: Unit) {.inline.} =
echo "1"
method collide(a: Unit, b: Thing) {.inline.} =
echo "2"
var a, b: Unit
new a
new b
collide(a, b) # output: 2
template withFile(f: untyped, filename: string,
mode: FileMode,
body: untyped): typed =
let fn = filename
var f: File
if open(f, fn, mode):
try:
body
finally:
close(f)
else:
quit("cannot open: " & fn)
withFile(txt, "ttempl3.txt", fmWrite):
txt.writeLine("line 1")
txt.writeLine("line 2")
dumpTree:
var mt: MyType = MyType(a:123.456, b:"abcdef")
# output:
# StmtList
# VarSection
# IdentDefs
# Ident "mt"
# Ident "MyType"
# ObjConstr
# Ident "MyType"
# ExprColonExpr
# Ident "a"
# FloatLit 123.456
# ExprColonExpr
# Ident "b"
# StrLit "abcdef"
import macros
type
MyType = object
a: float
b: string
macro myMacro(arg: untyped): untyped =
var mt: MyType = MyType(a:123.456, b:"abcdef")
let mtLit = newLit(mt)
result = quote do:
echo `arg`
echo `mtLit`
myMacro("Hallo")
# The call to myMacro will generate the following code:
echo "Hallo"
echo MyType(a: 123.456'f64, b: "abcdef")
import macros
dumpAstGen:
proc hello() =
echo "hi"
nnkStmtList.newTree(
nnkProcDef.newTree(
newIdentNode(!"hello"),
newEmptyNode(),
newEmptyNode(),
nnkFormalParams.newTree(
newEmptyNode()
),
newEmptyNode(),
newEmptyNode(),
nnkStmtList.newTree(
nnkCommand.newTree(
newIdentNode(!"echo"),
newLit("hi")
)
)
)
)
import macros
macro gen_hello(): typed =
result = nnkStmtList.newTree(
nnkProcDef.newTree(
newIdentNode(!"hello"),
newEmptyNode(),
newEmptyNode(),
nnkFormalParams.newTree(
newEmptyNode()
),
newEmptyNode(),
newEmptyNode(),
nnkStmtList.newTree(
nnkCommand.newTree(
newIdentNode(!"echo"),
newLit("hi")
)
)
)
)
gen_hello()
hello() # << same as from last slide!
import html_dsl
html page:
head:
title("Title")
body:
p("Hello")
p("World")
dv:
p "Example"
echo render(page())
<!DOCTYPE html>
<html class='has-navbar-fixed-top' >
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
</head>
<body class='has-navbar-fixed-top' >
<p >Hello</p>
<p >World</p>
<div>
<p>Example</p>
</div>
</body>
</html>
# main.nim
import a
echo a()
# a.nim
import b
proc a*(): string =
return from_b
# b.nim
let local = "B!"
let from_b* = local
^
# means "exported"
// nimbase.h
#define N_NIMCALL(rettype, name) rettype __fastcall name
#define N_CDECL(rettype, name) rettype __cdecl name
//...
#define N_NIMCALL_PTR(rettype, name) rettype (__fastcall *name)
//...
#define N_LIB_PRIVATE __attribute__((visibility("hidden")))
//...
#define N_LIB_EXPORT extern __declspec(dllexport)
//...
#define STRING_LITERAL(name, str, length) \
static const struct { \
TGenericSeq Sup; \
char data[(length) + 1]; \
} name = {{length, (int) ((unsigned)length | NIM_STRLIT_FLAG)}, str}
included by all .c/.cpp files
handles different platforms - convenience macros
proc foo() =
echo "hello"
foo()
#include <nimbase.h>
// forward declarations / type definitions / constants section
struct TGenericSeq { int len; int reserved; };
struct NimStringDesc : public TGenericSeq { ... };
typedef NimStringDesc* tyArray_nHXaesL0DJZHyVS07ARPRA[1];
STRING_LITERAL(TM_r9bkcJ6PRJ5n7ORNxxJ5ryg_3, "hello", 5); // << string literal
NIM_CONST tyArray_nHXaesL0DJZHyVS07ARPRA TM_r9bkcJ6PRJ5n7ORNxxJ5ryg_2 =
{((NimStringDesc*) &TM_r9bkcJ6PRJ5n7ORNxxJ5ryg_3)};
N_LIB_PRIVATE N_NIMCALL(void, foo_iineYNh8S9cE6Ry7dr2Tz2A)(void); // << fwd decl
// definition section
N_LIB_PRIVATE N_NIMCALL(void, foo_iineYNh8S9cE6Ry7dr2Tz2A)(void) { // << def
echoBinSafe(TM_r9bkcJ6PRJ5n7ORNxxJ5ryg_2, 1); // the echo call
}
// code execution section
foo_iineYNh8S9cE6Ry7dr2Tz2A(); // << call
type
MyData = object
answer: int
ready: bool
proc newData(): MyData = return MyData(answer: 42, ready: true)
echo newData().answer
// forward declarations / type definitions / constants section
struct tyObject_MyData {
int answer;
bool ready;
};
// definition section
N_LIB_PRIVATE N_NIMCALL(tyObject_MyData, newData)(void) {
tyObject_MyData result; // always an implicit "result"
nimZeroMem((void*)(&result), sizeof(tyObject_MyData));
result.answer = ((int) 42);
result.ready = true;
return result;
}
// code execution section
tyObject_MyData T2_;
T2_ = newData(); // << call
//...
iterator closure_iter*(): int {.closure.} = # a resumable function
var x = 1
while x < 10:
yield x
inc x
for i in closure_iter(): echo i
struct state_type : public RootObj {
int colonstate_; // state progress - there are some GOTOs using this
int x1; // the state
};
struct closure_type {
N_NIMCALL_PTR(int, c_ptr) (void* e_ptr); // function ptr
void* e_ptr; // environment ptr
};
N_LIB_PRIVATE N_CLOSURE(int, func)(void* e_ptr) { // def omitted for simplicity
state_type st; // the state
closure_type local; // the closure
local.c_ptr = func; // assign the func
local.e_ptr = &st; // assign environment
//...
i = local.c_ptr(local.e_ptr); // the call in the loop
proc printf(formatstr: cstring)
{.header: "<stdio.h>", importc: "printf", varargs.}
{.emit: """
using namespace core;
""".}
{.compile: "logic.c".}
other pragmas - for use in Nim:
We can also call Nim code from C/C++:
# fib.nim
proc fib(a: cint): cint {.exportc.} # do not mangle
nim c --noMain --noLinking --header:fib.h fib.nim
// user.c
#include <fib.h>
c2nim tool - generate C/C++ bindings for Nim
type
StdMap {.importcpp: "std::map", header: "<map>".} [K, V] = object
proc `[]=`[K, V](this: var StdMap[K, V]; key: K; val: V) {.
importcpp: "#[#] = #", header: "<map>".}
var x: StdMap[cint, cdouble]
x[6] = 91.4
std::map<int, double> x;
x[6] = 91.4;
C++ template constructs
Generated C++
# main.nim
import hotcodereloading # for reload
import other
while true:
echo readLine(stdin) # pause
performCodeReload() # reload
echo getInt() # call
# other.nim
import hotcodereloading # for after handler
var glob = 42
proc getInt*(): int = return glob + 1 # exported
afterCodeReload:
glob = 666
built as an .exe/.dll depending on the project type
built as a reloadable .dll
ends up in the "nimcache"
// fwd decl/globals section
static N_NIMCALL_PTR(int, getInt_omy6T2FkprLEReOy2ITmIQ)(void);
static int* glob_v1zK9aUOu9aNNcsxruuK8NdA;
// definitions
N_LIB_PRIVATE N_NIMCALL(int, getInt_omy6T2FkprLEReOy2ITmIQ_actual)(void) {
int result; // ^^ the suffix
result = (*glob_v1zK9aUOu9aNNcsxruuK8NdA);
return result;
}
// usage
(*glob_v1zK9aUOu9aNNcsxruuK8NdA) = getInt_omy6T2FkprLEReOy2ITmIQ();
// init on startup (naive)
glob_v1zK9aUOu9aNNcsxruuK8NdA = new int(42);
getInt_omy6T2FkprLEReOy2ITmIQ = getInt_omy6T2FkprLEReOy2ITmIQ_actual
// naive
glob_v1zK9aUOu9aNNcsxruuK8NdA = new int(42);
getInt_omy6T2FkprLEReOy2ITmIQ = getInt_omy6T2FkprLEReOy2ITmIQ_actual
// reality
getInt_omy6T2FkprLEReOy2ITmIQ = (tyProc_vVu2P82aVLv9c8X0xbI1NJw) hcrRegisterProc(
"D:\\play\\nimcache/play.cpp.dll", // "domain" (AKA module)
"getInt_omy6T2FkprLEReOy2ITmIQ", // "key"
(void*)getInt_omy6T2FkprLEReOy2ITmIQ_actual); // the real function
if(hcrRegisterGlobal("D:\\play\\nimcache/play.cpp.dll", // "domain" (AKA module)
"glob_v1zK9aUOu9aNNcsxruuK8NdA", // "key"
sizeof((*glob_v1zK9aUOu9aNNcsxruuK8NdA)), // size for allocation
NULL, // for the GC - simple integer is simple, so NULL
(void**)&glob_v1zK9aUOu9aNNcsxruuK8NdA)) // address to pointer
{
// hcrRegisterGlobal returns "true" only if not already inited
(*glob_v1zK9aUOu9aNNcsxruuK8NdA) = ((int) 42); // init with value (or side effects)
}
# main.nim
import a, b
echo from_a()
echo from_b()
# a.nim
import b
proc from_a*(): string =
result = "A!"
result.add from_b()
# b.nim
proc from_b*(): string =
return "B!"
when we call performCodeReload():
# main.nim
import a, b, hotcodereloading
beforeCodeReload:
echo "before main"
afterCodeReload:
echo "after main"
# a.nim
import b, hotcodereloading
beforeCodeReload:
echo "before a"
afterCodeReload:
echo "after a"
# b.nim
import hotcodereloading
beforeCodeReload:
echo "before b"
afterCodeReload:
echo "after b"
only A changes => all handlers are executed on reload:
before b
before a
before main
after b
after a
after main
# main.nim
import hotcodereloading # for reload
import other
while true:
echo readLine(stdin) # pause
performCodeReload() # reload
echo getInt() # call
# other.nim
import hotcodereloading # for after handler
var glob = 42
proc getInt*(): int = return glob # exported
afterCodeReload:
glob = 666
Makes more sense now, doesn't it?
.dll/.exe have hardcoded paths to the .pdb (copying the .dll doesn't matter)
the VS Debugger keeps the .pdb files locked for .dlls even after unloaded
someone managed to close the file handles to no longer needed .pdb files (.dll has been unloaded) to the external VS debugger process (live++)
embed the debug info in the actual binaries just like on unix
/Z7 embeds it in .obj files but not for the final .dll/.exe when linking them
solutions:
for reference: zlib (c code) to javascript (asm.js) ==> x2 slow down
calls within a translation unit are direct (the "_actual" version gets called)
calls between modules => indirection: pointer to function
+ additional jump from trampoline to actual function
not going through function pointers
by default there are no jumps in the function preamble (padding)
possible optimizations:
Nim is the language I have always thought was a brilliant idea that I never get to use. It's a shame.
Nim is to C/C++ as CoffeeScript is to JavaScript. A highly extensible template language atop a portable language with libraries for practically everything.
So why haven't I hopped on the bandwagon? Outside of C++, C, and Fortran - the only way I have ever learned a new language is through using a REPL. How much of Python's and MATLAB's (and maybe even Julia's) success is due to having a brilliant REPL?
I am not complaining, and I do not have any free time to fix it. But man... if Nim just had a killer REPL that allowed me to slowly learn the language properly while not being blocked from my daily work... it would be just killer!
cjhanks on Apr 18, 2017
2 files:
Talk abstract was a lie! didn't get to implementing it in time...
should be well below half a second
you submit this:
import tables
var a = {1: "one", 2: "two"}.toTable
echo a
and it gets translated to this:
import hotcodereloading # for the before/after handlers
import tables
var a = {1: "one", 2: "two"}.toTable
afterCodeReload:
echo a
later you append:
let b = a
echo b
and it gets translated to this:
import hotcodereloading # for the before/after handlers
import tables
var a = {1: "one", 2: "two"}.toTable
let b = a # the new code
# only the new side effects are still present
afterCodeReload:
echo b
By Viktor Kirilov
About the Nim programming language and the implementation of this feature: https://github.com/nim-lang/Nim/issues/8927