# -*- mode: org -*- * Strange things - case & underscore insensitivity (except for first char): fooBar = foo_bar = foobar != FooBar - stropping for using keywords as identifiers (like `if`) - method call syntax: f(a, b, c) = a.f(b, c) ; f(a) = a.f() = a.f - optional function call parentheses: f(a, b, c) = f a, b, c [only single argument for expressions] - implicit "result" variable - return values must be used or explicitly discarded (unless {.discardable}) - star shows export - proc vs. func (the latter is side effect free, syntactic sugar for {.noSideEffect}) - anaphoric templates (e.g. mapIt or foldl in sequtils) - arrays can use arbitrary indexing (e.g. array[-2 .. 3, int]) * Lexical elements ** Strings - characters: 'a' - strings: "abc" (with escapes \\ \n \t etc.) - raw strings: r"abc" (without escapes) - long strings: """ ... """ (without escapes, multiple lines) ** Comments - comments: # until the end of the line - documentation comments: ## until the end of the line (at special places) - multiline comments: #[ ... ]# (can be nested) - multiline documentation comments: ##[ ... ]## - discard """ ... """ can also be used as a comment ** Numbers - integers: 1_234_567 - floats: 1.2e-5 (float is any number with . or e/E) - hex/oct/bin: 0x2F / 0o25 / 0b1101 * Variables - new variables: var name1, name2: type [ only of the same types ] - new variables with value: var name1, name2 = value [ all have the same value ] (value should not be a computation with side effects) - new constant: const name = value [ compile type evaluation ] - new constant-variable: let name = value [ cannot be changed, but can be runtime evaluated ] - both can be indented like this: var const x, y: int x = 1 # comment here # comment here a, b, c: string y = x + 3 * Control flow - if ...: / elif ...: / else: [ no parentheses required; elif/else optional ] - when ...: / elif ...: / else: [ same but compile-time; no new variable scope inside ] - case ... / of ...[,...]: / else: [ allowed types: integers, ordinal types, strings ] - while ...: - for var in ...: [ see iterators ] - block name: [ named block, name is optional ] - break, continue as expected [ break can have a block argument ] - indentation: single simple statements can be written on the same line with the colon e.g.: if x == 1: y = 2 - multiple statements can be combined into an expression: ( ... ; ... ; ... ) e.g.: const fac4 = (var x = 1; for i in 1..4: x *= i; x) * Procedures (functions) - proc foo(var1, var2: type1, var3: var type2, var4 = defval): rettype = [here var1 & var2 are both type1, var3 is mutable, var4 has a default value, so no type needed] - arguments can be separated by semicolon, e.g. proc foo(i, j: int; s: string) = - callers can refer to the variables by name, e.g. foo(1, var3 = 5, var2 = 2) - immutable variables are idiomatically shadowed by new variables of the same name, e.g.: proc foo(n: int): int = var n = n * 2 ... - implicit result variable, initialized to the type's default value - return (without argument) = return result - when there is no explicit return statement and result is not set, the last expression is returned - forward declaration is the same, without the final = * Operator - special characters: + - * \ / < > = @ $ ~ & % ! ? ^ . | [+ a few built-ins: and not or etc.] - non-infix/prefix use: `...`, e.g. `+`(1, 2) (this can be used for definition, as well) - binary operators starting with ^ are right-associative, others are left-associative * Safety - discard foo() : explicitly throw away the return value [ without argument it is a NOP ] - a function can declare the result as discardable: proc foo(): int {.discardable} = - assert(...) * Iterators - countup(1,10) = 1..10 = 1..<11 , countdown(10,1) - ..^n : .. to len()-n [ so n should be at least 1 ] - element (immutable / mutable): items / mitems, e.g. [1,1,2,3,5,8].iter - element + index number (immutable / mutable): pairs / mpairs, e.g. [1,1,2,3,5,8].pairs - defining iterators: iterator foo(var1: type1, var2: type2): rettype = - using "yield ..." for creating values - iterators are only called from for loops; no implicit result; no return; no recursion * Basic types - conversion: type name as function, e.g. int8('a') = 'a'.int8 = 'a'.int8() - $: stringifies anything that has a toString function - repr: internal representation (works for everything) ** Boolean (bool) - and / or : short-circuit - also not, xor, ==, != etc. ** Character (char) - one byte - $ operator converts to string, e.g. $c - integer conversion: ord / chr ** String (string) - mutable! (appending is efficient) - zero-terminated + .len field [ efficient C string conversion ] - string concatenation: "Hello " & "world!" - string append: str1.add("!") ** Integers (int) - int / [u]int(8|16|32|64) - int is the default (pointer size), others with suffixes: -123'i64, 55'u32 - integer division: div, modulo: mod - bit shift: shl / shr [ always unsigned ] ** Floats (float) - float / float(32|64) - float is the default (= float64), others with suffixes: 0.1'f32 - int conversion: toInt / toFloat * Advanced types - type name = ... [ multiple new types can go under type ] ** Enumeration - enum val0 val1 ... valn - internally represented by 0, 1, ..., n - can be qualified, e.g. type MyEnum = enum a b c => MyEnum.a etc. - int conversion: ord(), string conversion: $ ** Subranges - range[from..to] - range of values from integers or enumarations - e.g. (std.library) type Natural = range[0..high(int)] <- better than uint, does not wrap! ** Ordinal types - enumarations, integers, char, bool, subranges - special operations: ord, inc=succ/dec=pred [1- and 2-argument] ** Set - set[type] - from small-ish ordinal types ([u]int8-[u]int16, byte, char, enum) - internally a bitvector - constructor: { ... }, can contain ranges, e.g. { 1..5, 10..30, 40 } - operators: +, * (intersection), -, ==, <= (subset), <, in, notin - functions: contains, card (cardinality), incl/excl (desctructive add/subtract element) - set of an enum type can be used as bitfields (conversion to int with cast[int]) ** Array - array[indices, valtype] <- indices is a range of an ordinal type - array[n, valtype] === array[0..n-1, valtype] - fixed length array - constructor: [ ... ] - random access: a[i] - assignment copies the contents - length: len(a)/a.len/a.len(); low(a)/a.low/a.low() and high(a)/... is the lowest/highest index - arrays can drive a for-loop with one or two variables (two -> index + value) ** OpenArray - openArray[valtype] - only used as function parameter, so it can accept arrays of any size - multi-dimensional openArrays are not supported ** Varargs - varargs[valtype] - like an openArray, but accepts a variable number of arguments - e.g. proc foo(x: int, ys: varargs[float]) -> foo(1, 2.1, 3.2, 4.3) - should be the last parameter ** Sequences - seq[valtype] <- always indexed from 0 - dynamic arrays - array to sequence operator: @, e.g. var x: seq[int] = @[1, 2, 3, 5, 8] - otherwise like arrays ** Tuple - declaration: tuple[field1: type1, field2: type2] OR tuple field1: type1 field2: type2 ... - constructor: (field1: val1, field2: val2, ...) OR (val1, val2, ...) - fields can be accessed by dot (foo.field1 oR foo[0] etc.) - all fields are public - can be unpacked as: let (x, y) = point ** Objects - declaration: object field1: type1 field2: type2 ... - constructor: Foo(field1: val1, ...) [ unspecified fields get the default value ] - fields can be accessed by dot (foo.field1 etc.) - exported fields (visible outside the module) are masrked with an asterisk: field1*: type1 ** References - ref type (traced) and ptr type (untraced - unsafe!) - empty [] dereferences - the dot (.) and [] operators automatically dereference - null reference: nil - new traced reference: new - new untraced reference: alloc / dealloc / realloc ** Distinct types - distinct originaltype - same type, but not convertable etc. * Modules - each module has its own file - top-level symbols marked with an asterisk (*) are exported - special constant: isMainModule -> true when the module is compiled as the main file (for tests) - import Module -> loads all exported symbol from Module - Module.symbol qualification can be used to avoid ambuigity - import Module except sym1, sym2 -> loads all except sym1, sym2 - from Module import sym1, sym2 -> loads only sym1, sym2 - from Module as M import sym1, sym2 -> also adds M as an alias (so M.sym1, M.sym2 also works) - include file <- includes the file (so modules _can_ be split into several files) * OOP * Generics * Exceptions * Metaprogramming * Tools - compile (debug): nim compile test.nim [ nim c test.nim ] - compile (release): nim compile -d:release test.nim - compile & run: nim compile --run test.nim [ nim c -r test.nim ]