Nim
Getting Started
Helloworld.nim
echo "Hello World!"#---------------------------## This is a commentecho "What's your name? "var name: string = readLine(stdin)echo "Hi, ", name, "!"
Compiling and Running
$ nim c helloworld.nim# if os is windows then$ helloworld.exe# if os is linux then$ ./Hello_World# outputHello, World!
# There is also a possibility# to both compile and run the# program with just one command.# We need to type:$ nim c -r helloworld.nim
comment
# This is a comment
#[ This is a multiline comment. In Nim, multiline comments can be nested, beginning with #[ ... and ending with ]#]#
Variable declaration
var letter: char = 'n' lang = "N" & "im" nLength: int = len(lang) boat: float truth: bool = false b = 7 c = -11 d = "Hello" e = '!'
Let
# Use let to declare and bind variableslet legs = 400 #legs is immutable. arms = 2_000 # are ignored and are useful for long numbers. aboutPi = 3.15 let input = readLine(stdin) # works
Const
# Constants are computed at compileconst debug = true # performance and is useful in compile time expressions. compileBadCode = false
Basic data types
Integers
et a = 11 b = 4
echo "a + b = ", a + becho "a - b = ", a - becho "a * b = ", a * becho "a / b = ", a / becho "a div b = ", a div becho "a mod b = ", a mod b#output#a + b = 15#a - b = 7#a * b = 44#a / b = 2.75#a div b = 2#a mod b = 3
Floats
let c = 6.75 d = 2.25
echo "c + d = ", c + decho "c - d = ", c - decho "c * d = ", c * decho "c / d = ", c / d#output#c + d = 9.0#c - d = 4.5#c * d = 15.1875#c / d = 3.0
Strings
#Strings can be described#as a series of characters.#Their content is written between two double quotes (").let m = "word" n = "A sentence with interpunction." o = "" p = "32" q = "!"
Characters
#Characters are single#characters. They are#written between two single quotes (').let h = 'z' i = '+' j = '2' k = '35' # error l = 'xy' # error
Special characters
#\n is a newline character#\t is a tab character#\\ is a backslash (since one \ is used as the escape character)
echo "some\nim\tips"echo "some\\nim\\tips"echo r"some\nim\tips"#output#some#im ips#some\nim\tips#some\nim\tips
Boolean
#A boolean (or just bool)# data type can only have#two values: true or false.let isEmpty = truelet isFull = false
Converting floats and integers
let e = 5 f = 23.987echo e + f # errorecho float(e)echo int(f)
echo float(e) + fecho e + int(f)#output#5.0#23#28.987#28
String concatenation
var p = "abc" q = "xy" r = 'z'
p.add("def")echo "p is now: ", p
q.add(r)echo "q is now: ", q
echo "concat: ", p & q
echo "p is still: ", pecho "q is still: ", q#output#p is now: abcdef#q is now: xyz#concat: abcdefxyz#p is still: abcdef#q is still: xyz
Opatators
Relational operators
let g = 31 h = 99
echo "g is greater than h: ", g > hecho "g is smaller than h: ", g < hecho "g is equal to h: ", g == hecho "g is not equal to h: ", g != hecho "g is greater or equal to h: ", g >= hecho "g is smaller or equal to h: ", g <= h#outputg is greater than h: falseg is smaller than h: trueg is equal to h: falseg is not equal to h: trueg is greater or equal to h: falseg is smaller or equal to h: true
#examplelet i = 'a' j = 'd' k = 'Z'
echo i < jecho i < k
let m = "axyb" n = "axyz" o = "ba" p = "ba "
echo m < necho n < oecho o < p#output#false#true#true#true#true
Logical operators
echo "T and T: ", true and trueecho "T and F: ", true and falseecho "F and F: ", false and falseecho "---"echo "T or T: ", true or trueecho "T or F: ", true or falseecho "F or F: ", false or falseecho "---"echo "T xor T: ", true xor trueecho "T xor F: ", true xor falseecho "F xor F: ", false xor falseecho "---"echo "not T: ", not trueecho "not F: ", not false#output#T and T: true#T and F: false#F and F: false---#T or T: true#T or F: true#F or F: false---#T xor T: false#T xor F: true#F xor F: false---#not T: false#not F: true
Control flow
If statement
let a = 11 b = 22 c = 999
if a < b: echo "a is smaller than b" if 10*a < b: echo "not only that, a is *much* smaller than b"
if b < c: echo "b is smaller than c" if 10*b < c: echo "not only that, b is *much* smaller than c"
if a+b > c: echo "a and b are larger than c" if 1 < 100 and 321 > 123: echo "did you know that 1 is smaller than 100?" echo "and 321 is larger than 123! wow!"#output#a is smaller than b#b is smaller than c#not only that, b is *much* smaller than c
Case statement
let name = readLine(stdin)case nameof "": echo "Poor soul, you lost your name?"of "name": echo "Very funny, your name is name."of "Dave", "Frank": echo "Cool name!"else: echo "Hi, ", name, "!"
While statement
echo "What's your name? "var name = readLine(stdin)while name == "": echo "Please tell me your name: " name = readLine(stdin) # no `var`, because we do not declare a new variable here
For statement
echo "Counting to ten: "for i in countup(1, 10): echo i# --> Outputs 1 2 3 4 5 6 7 8 9 10 on different lines
Scopes and the block statement
while false: var x = "hi"echo x # does not work#-----------------------#block myblock: var x = "hi"echo x # does not work either
Break statement
block myblock: echo "entering block" while true: echo "looping" break # leaves the loop, but not the block echo "still in block"echo "outside the block"
Continue statement
for i in 1 .. 5: if i <= 3: continue echo i # will only print 4 and 5
When statement
when system.hostOS == "windows": echo "running on Windows!"elif system.hostOS == "linux": echo "running on Linux!"elif system.hostOS == "macosx": echo "running on Mac OS X!"else: echo "unknown operating system"
Statements and indentation
# no indentation needed for single-assignment statement:if x: x = false
# indentation needed for nested if statement:if x: if y: y = false else: y = true
# indentation needed, because two statements follow the condition:if x: x = false y = false
Procedures
proc yes(question: string): bool = echo question, " (y/n)" while true: case readLine(stdin) of "y", "Y", "yes", "Yes": return true of "n", "N", "no", "No": return false else: echo "Please be clear: yes or no"
if yes("Should I delete all your important files?"): echo "I'm sorry Dave, I'm afraid I can't do that."else: echo "I think you know what the problem is just as well as I do."
Result variable
proc sumTillNegative(x: varargs[int]): int = for i in x: if i < 0: return result = result + i
echo sumTillNegative() # echoes 0echo sumTillNegative(3, 4, 5) # echoes 12echo sumTillNegative(3, 4 , -1 , 6) # echoes 7
Parameters
proc printSeq(s: seq, nprinted: int = -1) = var nprinted = if nprinted == -1: s.len else: min(nprinted, s.len) for i in 0 ..< nprinted: echo s[i]#------------------- #proc divmod(a, b: int; res, remainder: var int) = res = a div b # integer division remainder = a mod b # integer modulo operation
var x, y: intdivmod(8, 5, x, y) # modifies x and yecho xecho y
Discard statement
discard yes("May I ask a pointless question?")
proc p(x, y: int): int {.discardable.} = return x + y
p(3, 4) # now valid
Named arguments
proc createWindow(x, y, width, height: int; title: string; show: bool): Window = ...
var w = createWindow(show = true, title = "My Application", x = 0, y = 0, height = 600, width = 800)
var w = createWindow(0, 0, title = "My Application", height = 600, width = 800, true)
Default values
proc createWindow(x = 0, y = 0, width = 500, height = 700, title = "unknown", show = true): Window =
var w = createWindow(title = "My Application", height = 600, width = 800)
Overloaded procedures
proc toString(x: int): string = result = if x < 0: "negative" elif x > 0: "positive" else: "zero"
proc toString(x: bool): string = result = if x: "yep" else: "nope"
assert toString(13) == "positive" # calls the toString(x: int) procassert toString(true) == "yep" # calls the toString(x: bool) proc
Forward declarations
proc odd(n: int): bool = assert(n >= 0) # makes sure we don't run into negative recursion if n == 0: false else: n == 1 or even(n-1)
proc even(n: int): bool = assert(n >= 0) # makes sure we don't run into negative recursion if n == 1: false else: n == 0 or odd(n-1)
Iterators
echo "Counting to ten: "for i in countup(1, 10): echo i
proc countup(a, b: int): int = var res = a while res <= b: return res inc(res)
iterator countup(a, b: int): int = var res = a while res <= b: yield res inc(res)
Type Conversion
var x: int32 = 1.int32 # same as calling int32(1) y: int8 = int8('a') # 'a' == 97'i8 z: float = 2.5 # int(2.5) rounds down to 2 sum: int = int(x) + int(y) + int(z) # sum == 100
Internal type representation
var myBool = true myCharacter = 'n' myString = "nim" myInteger = 42 myFloat = 3.14echo myBool, ":", repr(myBool)# --> true:trueecho myCharacter, ":", repr(myCharacter)# --> n:'n'echo myString, ":", repr(myString)# --> nim:0x10fa8c050"nim"echo myInteger, ":", repr(myInteger)# --> 42:42echo myFloat, ":", repr(myFloat)# --> 3.14:3.14
Enumerations
type Direction = enum north, east, south, west
var x = south# `x` is of type `Direction`; its value is `south`#prints "south"echo x
Ordinal types
Operation | Comment |
---|---|
ord(x) | returns the integer value that is used to represent x’s value |
inc(x) | increments x by one |
inc(x, n) | increments x by n; n is an integer |
dec(x) | decrements x by one |
dec(x, n) | decrements x by n; n is an integer |
succ(x) | returns the successor of x |
succ(x, n) | returns the n’th successor of x |
pred(x) | returns the predecessor of x |
pred(x, n) | returns the n’th predecessor of x |
Subranges
type MySubrange = range[0..5]
Sets
var s: set[int64] # Error: set is too large; use `std/sets` for ordinal types # with more than 2^16 elements type CharSet = set[char]var x: CharSetx = {'a'..'z', '0'..'9'} # This constructs a set that contains the # letters from 'a' to 'z' and the digits # from '0' to '9'
Bit fields
type MyFlag* {.size: sizeof(cint).} = enum A B C D MyFlags = set[MyFlag]
proc toNum(f: MyFlags): int = cast[cint](f)proc toFlags(v: int): MyFlags = cast[MyFlags](v)
assert toNum({}) == 0assert toNum({A}) == 1assert toNum({D}) == 8assert toNum({A, C}) == 5assert toFlags(0) == {}assert toFlags(7) == {A, B, C}
Arrays
type IntArray = array[0..5, int] # an array that is indexed with 0..5var x: IntArrayx = [1, 2, 3, 4, 5, 6]for i in low(x) .. high(x): echo x[i]
Sequences
var x: seq[int] # a reference to a sequence of integersx = @[1, 2, 3, 4, 5, 6] # the @ turns the array into a sequence allocated on the heapfor value in @[3, 4, 5]: echo value# --> 3# --> 4# --> 5
for i, value in @[3, 4, 5]: echo "index: ", $i, ", value:", $value# --> index: 0, value:3# --> index: 1, value:4# --> index: 2, value:5
Open arrays
var fruits: seq[string] # reference to a sequence of strings that is initialized with '@[]' capitals: array[3, string]
# array of strings with a fixed size
capitals = ["New York", "London", "Berlin"] # array 'capitals' allows assignment of only three elementsfruits.add("Banana") # sequence 'fruits' is dynamically expandable during runtimefruits.add("Mango")
proc openArraySize(oa: openArray[string]): int = oa.len
assert openArraySize(fruits) == 2 # procedure accepts a sequence as parameterassert openArraySize(capitals) == 3 # but also an array type
Varargs
proc myWriteln(f: File, a: varargs[string]) = for s in items(a): write(f, s) write(f, "\n")
myWriteln(stdout, "abc", "def", "xyz")# is transformed by the compiler to:myWriteln(stdout, ["abc", "def", "xyz"])
Slices
var a = "Nim is a programming language" b = "Slices are useless."
echo a[7 .. 12] # --> 'a prog'b[11 .. ^2] = "useful"echo b # --> 'Slices are useful.'
Objects
type Person = object name: string age: int
var person1 = Person(name: "Peter", age: 30)
echo person1.name # "Peter"echo person1.age # 30
var person2 = person1 # copy of person 1
person2.age += 14
echo person1.age # 30echo person2.age # 44
# the order may be changedlet person3 = Person(age: 12, name: "Quentin")
# not every member needs to be specifiedlet person4 = Person(age: 3)# unspecified members will be initialized with their default# values. In this case it is the empty string.doAssert person4.name == ""
Tuples
type Person = tuple name: string age: int
PersonX = tuple[name: string, age: int]
PersonY = (string, int)
var person: Person personX: PersonX personY: PersonY
person = (name: "Peter", age: 30)# Person and PersonX are equivalentpersonX = person
# Create a tuple with anonymous fields:personY = ("Peter", 30)
person = personYpersonY = person
person = ("Peter", 30)
echo person.name # "Peter"echo person.age # 30
echo person[0] # "Peter"echo person[1] # 30
var building: tuple[street: string, number: int]building = ("Rue del Percebe", 13)echo building.street
Reference and pointer types
type Node = ref object le, ri: Node data: int
var n = Node(data: 9)echo n.data# no need to write n[].data; in fact n[].data is highly discouraged!
Procedural type
proc greet(name: string): string = "Hello, " & name & "!"
proc bye(name: string): string = "Goodbye, " & name & "."
proc communicate(greeting: proc (x: string): string, name: string) = echo greeting(name)
communicate(greet, "John")communicate(bye, "Mary")
Modules
# Module Avar x*, y: int
proc `*` *(a, b: seq[int]): seq[int] = # allocate a new sequence: newSeq(result, len(a)) # multiply two int sequences: for i in 0 ..< len(a): result[i] = a[i] * b[i]
when isMainModule: # test the new `*` operator for sequences: assert(@[1, 2, 3] * @[1, 2, 3] == @[1, 4, 9])
Excluding symbols
import mymodule except y
# From statementfrom mymodule import x, y, zfrom mymodule import x, y, z
x() # use x without any qualification
#Include statementinclude fileA, fileB, fileC
Inheritance
type Person = ref object of RootObj name*: string # the * means that `name` is accessible from other modules age: int # no * means that the field is hidden from other modules
Student = ref object of Person # Student inherits from Person id: int # with an id field
var student: Student person: Personassert(student of Student) # is true# object construction:student = Student(name: "Anton", age: 5, id: 2)echo student[]
Mutually recursive types
type Node = ref object # a reference to an object with the following field: le, ri: Node # left and right subtrees sym: ref Sym # leaves contain a reference to a Sym
Sym = object # a symbol name: string # the symbol's name line: int # the line the symbol was declared in code: Node # the symbol's abstract syntax tree
Object variants
# This is an example how an abstract syntax tree could be modelled in Nimtype 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 `FieldDefect` exception, because# n.kind's value does not fit:n.strVal = ""
Method call syntax
import std/strutils
echo "abc".len # is the same as echo len("abc")echo "abc".toUpperAscii()echo({'a', 'b', 'c'}.card)stdout.writeLine("Hallo") # the same as writeLine(stdout, "Hallo")##############################import std/[strutils, sequtils]
stdout.writeLine("Give a list of numbers (separated by spaces): ")stdout.write(stdin.readLine.splitWhitespace.map(parseInt).max.`$`)stdout.writeLine(" is the maximum!")
Properties
type Socket* = ref object of RootObj h: int # cannot be accessed from the outside of the module due to missing star
proc `host=`*(s: var Socket, value: int) {.inline.} = ## setter of host address s.h = value
proc host*(s: Socket): int {.inline.} = ## getter of host address s.h
var s: Socketnew ss.host = 34 # same as `host=`(s, 34)
type Vector* = object x, y, z: float
proc `[]=`* (v: var Vector, i: int, value: float) = # setter case i of 0: v.x = value of 1: v.y = value of 2: v.z = value else: assert(false)
proc `[]`* (v: Vector, i: int): float = # getter case i of 0: result = v.x of 1: result = v.y of 2: result = v.z else: assert(false)
Dynamic dispatch
type Expression = ref object of RootObj ## abstract base class for an expression Literal = ref object of Expression x: int PlusExpr = ref object of Expression a, b: Expression
# watch out: 'eval' relies on dynamic bindingmethod eval(e: Expression): int {.base.} = # override this base method quit "to override!"
method eval(e: Literal): int = e.xmethod eval(e: PlusExpr): int = eval(e.a) + eval(e.b)
proc newLit(x: int): Literal = Literal(x: x)proc newPlus(a, b: Expression): PlusExpr = PlusExpr(a: a, b: b)
echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4)))
Raise statement
var e: ref OSErrornew(e)e.msg = "the request to the OS failed"raise e
raise newException(OSError, "the request to the OS failed")
Try statement
from std/strutils import parseInt
# read the first two lines of a text file that should contain numbers# and tries to add themvar f: Fileif open(f, "numbers.txt"): try: let a = readLine(f) let b = readLine(f) echo "sum: ", parseInt(a) + parseInt(b) except OverflowDefect: echo "overflow!" except ValueError: echo "could not convert string to integer" except IOError: echo "IO error!" except CatchableError: echo "Unknown exception!" # reraise the unknown exception: raise finally: close(f)
Annotating procs with raised exceptions
proc complexProc() {.raises: [IOError, ArithmeticDefect].} =
proc simpleProc() {.raises: [].} = ...
Generics
type BinaryTree*[T] = ref object # BinaryTree is a generic type with # generic param `T` le, ri: BinaryTree[T] # left and right subtrees; may be nil data: T # the data stored in a node
proc newNode*[T](data: T): BinaryTree[T] = # constructor for a node new(result) result.data = data
proc add*[T](root: var BinaryTree[T], n: BinaryTree[T]) = # insert a node into the tree if root == nil: root = n else: var it = root while it != nil: # compare the data items; uses the generic `cmp` proc # that works for any type that has a `==` and `<` operator var c = cmp(it.data, n.data) if c < 0: if it.le == nil: it.le = n return it = it.le else: if it.ri == nil: it.ri = n return it = it.ri
proc add*[T](root: var BinaryTree[T], data: T) = # convenience proc: add(root, newNode(data))
iterator preorder*[T](root: BinaryTree[T]): T = # Preorder traversal of a binary tree. # This uses an explicit stack (which is more efficient than # a recursive iterator factory). var stack: seq[BinaryTree[T]] = @[root] while stack.len > 0: var n = stack.pop() while n != nil: yield n.data add(stack, n.ri) # push right subtree onto the stack n = n.le # and follow the left pointer
var root: BinaryTree[string] # instantiate a BinaryTree with `string`add(root, newNode("hello")) # instantiates `newNode` and `add`add(root, "world") # instantiates the second `add` procfor str in preorder(root): stdout.writeLine(str)
Templates
template `!=` (a, b: untyped): untyped = # this definition exists in the System module not (a == b)
assert(5 != 6) # the compiler rewrites that to: assert(not (5 == 6))#############################################const debug = true
proc log(msg: string) {.inline.} = if debug: stdout.writeLine(msg)
var x = 4log("x has the value: " & $x)####################################template withFile(f: untyped, filename: string, mode: FileMode, body: untyped) = 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")
Static Arguments
import std/macros
macro myMacro(arg: static[int]): untyped = echo arg # just an int (7), not `NimNode`
myMacro(1 + 2 * 3)
Code Blocks as Arguments
echo "Hello ": let a = "Wor" let b = "ld!" a & b
The Syntax Tree
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"
Custom Semantic Checking
macro myAssert(arg: untyped): untyped = arg.expectKind nnkInfix
Generating Code
import std/macros
typeMyType = 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")
Building Your First Macro
import std/macros
macro myAssert(arg: untyped): untyped = # all node kind identifiers are prefixed with "nnk" arg.expectKind nnkInfix arg.expectLen 3 # operator as string literal let op = newLit(" " & arg[0].repr & " ") let lhs = arg[1] let rhs = arg[2]
result = quote do: if not `arg`: raise newException(AssertionDefect,$`lhs` & `op` & $`rhs`)
let a = 1let b = 2
myAssert(a != b)myAssert(a == b)