diff options
Diffstat (limited to 'tests/niminaction')
43 files changed, 1862 insertions, 0 deletions
diff --git a/tests/niminaction/Chapter1/various1.nim b/tests/niminaction/Chapter1/various1.nim new file mode 100644 index 000000000..21553dc40 --- /dev/null +++ b/tests/niminaction/Chapter1/various1.nim @@ -0,0 +1,45 @@ +discard """ + exitCode: 0 + outputsub: "Woof!" +""" + +import strutils +echo("hello".to_upper()) +echo("world".toUpper()) + +type + Dog = object #<1> + age: int #<2> + +let dog = Dog(age: 3) #<3> + +proc showNumber(num: int | float) = + echo(num) + +showNumber(3.14) +showNumber(42) + +for i in 0 ..< 10: + echo(i) + +block: # Block added due to clash. + type + Dog = object + + proc bark(self: Dog) = #<1> + echo("Woof!") + + let dog = Dog() + dog.bark() #<2> + +import sequtils, sugar, strutils +let list = @["Dominik Picheta", "Andreas Rumpf", "Desmond Hume"] +list.map( + (x: string) -> (string, string) => (x.split[0], x.split[1]) +).echo + +import strutils +let list1 = @["Dominik Picheta", "Andreas Rumpf", "Desmond Hume"] +for name in list1: + echo((name.split[0], name.split[1])) + diff --git a/tests/niminaction/Chapter2/explicit_discard.nim b/tests/niminaction/Chapter2/explicit_discard.nim new file mode 100644 index 000000000..7f3b3395e --- /dev/null +++ b/tests/niminaction/Chapter2/explicit_discard.nim @@ -0,0 +1,7 @@ +discard """ + errormsg: "has to be used (or discarded)" + line: 7 +""" + +proc myProc(name: string): string = "Hello " & name +myProc("Dominik") diff --git a/tests/niminaction/Chapter2/no_def_eq.nim b/tests/niminaction/Chapter2/no_def_eq.nim new file mode 100644 index 000000000..b9d62e036 --- /dev/null +++ b/tests/niminaction/Chapter2/no_def_eq.nim @@ -0,0 +1,16 @@ +discard """ + errormsg: "type mismatch" + line: 16 +""" + +type + Dog = object + name: string + + Cat = object + name: string + +let dog: Dog = Dog(name: "Fluffy") +let cat: Cat = Cat(name: "Fluffy") + +echo(dog == cat) diff --git a/tests/niminaction/Chapter2/no_iterator.nim b/tests/niminaction/Chapter2/no_iterator.nim new file mode 100644 index 000000000..555fac21a --- /dev/null +++ b/tests/niminaction/Chapter2/no_iterator.nim @@ -0,0 +1,7 @@ +discard """ + errormsg: "type mismatch" + line: 6 +""" + +for i in 5: + echo i diff --git a/tests/niminaction/Chapter2/no_seq_type.nim b/tests/niminaction/Chapter2/no_seq_type.nim new file mode 100644 index 000000000..f1494124b --- /dev/null +++ b/tests/niminaction/Chapter2/no_seq_type.nim @@ -0,0 +1,6 @@ +discard """ + errormsg: "cannot infer the type of the sequence" + line: 6 +""" + +var list = @[] diff --git a/tests/niminaction/Chapter2/resultaccept.nim b/tests/niminaction/Chapter2/resultaccept.nim new file mode 100644 index 000000000..390f7b329 --- /dev/null +++ b/tests/niminaction/Chapter2/resultaccept.nim @@ -0,0 +1,28 @@ +discard """ + output: "" +""" + +# Page 35. + +proc implicit: string = + "I will be returned" + +proc discarded: string = + discard "I will not be returned" + +proc explicit: string = + return "I will be returned" + +proc resultVar: string = + result = "I will be returned" + +proc resultVar2: string = + result = "" + result.add("I will be ") + result.add("returned") + +doAssert implicit() == "I will be returned" +doAssert discarded().len == 0 +doAssert explicit() == "I will be returned" +doAssert resultVar() == "I will be returned" +doAssert resultVar2() == "I will be returned" \ No newline at end of file diff --git a/tests/niminaction/Chapter2/resultreject.nim b/tests/niminaction/Chapter2/resultreject.nim new file mode 100644 index 000000000..145345072 --- /dev/null +++ b/tests/niminaction/Chapter2/resultreject.nim @@ -0,0 +1,33 @@ +discard """ + errormsg: "has to be used (or discarded)" + line: 27 +""" + +# Page 35. + +proc implicit: string = + "I will be returned" + +proc discarded: string = + discard "I will not be returned" + +proc explicit: string = + return "I will be returned" + +proc resultVar: string = + result = "I will be returned" + +proc resultVar2: string = + result = "" + result.add("I will be ") + result.add("returned") + +proc resultVar3: string = + result = "I am the result" + "I will cause an error" + +doAssert implicit() == "I will be returned" +doAssert discarded() == nil +doAssert explicit() == "I will be returned" +doAssert resultVar() == "I will be returned" +doAssert resultVar2() == "I will be returned" diff --git a/tests/niminaction/Chapter2/various2.nim b/tests/niminaction/Chapter2/various2.nim new file mode 100644 index 000000000..921f38c7d --- /dev/null +++ b/tests/niminaction/Chapter2/various2.nim @@ -0,0 +1,369 @@ +discard """ + exitCode: 0 + outputsub: '''42 is greater than 0''' +""" + +if 42 >= 0: + echo "42 is greater than 0" + + +echo("Output: ", + 5) +echo(5 + + 5) +# --- Removed code that is supposed to fail here. Not going to test those. --- + +# Single-line comment +#[ +Multiline comment +]# +when false: + echo("Commented-out code") + +let decimal = 42 +let hex = 0x42 +let octal = 0o42 +let binary = 0b101010 + +let a: int16 = 42 +let b = 42'i8 + +let c = 1'f32 # --- Changed names here to avoid clashes --- +let d = 1.0e19 + +let e = false +let f = true + +let g = 'A' +let h = '\109' +let i = '\x79' + +let text = "The book title is \"Nim in Action\"" + +let filepath = "C:\\Program Files\\Nim" + +# --- Changed name here to avoid clashes --- +let filepath1 = r"C:\Program Files\Nim" + +let multiLine = """foo + bar + baz +""" +echo multiLine + +import strutils +# --- Changed name here to avoid clashes --- +let multiLine1 = """foo + bar + baz +""" +echo multiLine1.unindent +doAssert multiLine1.unindent == "foo\nbar\nbaz\n" + +proc fillString(): string = + result = "" + echo("Generating string") + for i in 0 .. 4: + result.add($i) #<1> + +const count = fillString() + +var + text1 = "hello" + number: int = 10 + isTrue = false + +var 火 = "Fire" +let ogień = true + +var `var` = "Hello" +echo(`var`) + +proc myProc(name: string): string = "Hello " & name +discard myProc("Dominik") + +proc bar(): int #<1> + +proc foo(): float = bar().float +proc bar(): int = foo().int + +proc noReturn() = echo("Hello") +proc noReturn2(): void = echo("Hello") + +proc noReturn3 = echo("Hello") + +proc message(recipient: string): auto = + "Hello " & recipient + +doAssert message("Dom") == "Hello Dom" + +proc max(a: int, b: int): int = + if a > b: a else: b + +doAssert max(5, 10) == 10 + +proc max2(a, b: int): int = + if a > b: a else: b + +proc genHello(name: string, surname = "Doe"): string = + "Hello " & name & " " & surname + +# -- Leaving these as asserts as that is in the original code, just in case +# -- somehow in the future `assert` is removed :) +assert genHello("Peter") == "Hello Peter Doe" +assert genHello("Peter", "Smith") == "Hello Peter Smith" + +proc genHello2(names: varargs[string]): string = + result = "" + for name in names: + result.add("Hello " & name & "\n") + +doAssert genHello2("John", "Bob") == "Hello John\nHello Bob\n" + +proc getUserCity(firstName, lastName: string): string = + case firstName + of "Damien": return "Tokyo" + of "Alex": return "New York" + else: return "Unknown" + +proc getUserCity(userID: int): string = + case userID + of 1: return "Tokyo" + of 2: return "New York" + else: return "Unknown" + +doAssert getUserCity("Damien", "Lundi") == "Tokyo" +doAssert getUserCity(2) == "New York" # -- Errata here: missing closing " + +import sequtils +let numbers = @[1, 2, 3, 4, 5, 6] +let odd = filter(numbers, proc (x: int): bool = x mod 2 != 0) +doAssert odd == @[1, 3, 5] + +import sequtils, sugar +let numbers1 = @[1, 2, 3, 4, 5, 6] +let odd1 = filter(numbers1, (x: int) -> bool => x mod 2 != 0) +assert odd1 == @[1, 3, 5] + +proc isValid(x: int, validator: proc (x: int): bool) = + if validator(x): echo(x, " is valid") + else: echo(x, " is NOT valid") + +import sugar +proc isValid2(x: int, validator: (x: int) -> bool) = + if validator(x): echo(x, " is valid") + else: echo(x, " is NOT valid") + +var list: array[3, int] +list[0] = 1 +list[1] = 42 +assert list[0] == 1 +assert list[1] == 42 +assert list[2] == 0 #<1> + +echo list.repr #<2> + +# echo list[500] + +var list2: array[-10 .. -9, int] +list2[-10] = 1 +list2[-9] = 2 + +var list3 = ["Hi", "There"] + +var list4 = ["My", "name", "is", "Dominik"] +for item in list4: + echo(item) + +for i in list4.low .. list4.high: + echo(list4[i]) + +var list5: seq[int] = @[] +doAssertRaises(IndexDefect): + list5[0] = 1 + +list5.add(1) + +assert list5[0] == 1 +doAssertRaises(IndexDefect): + echo list5[42] + +# -- Errata: var list: seq[int]; echo(list[0]). This now creates an exception, +# -- not a SIGSEGV. + +block: + var list = newSeq[string](3) + assert list[0].len == 0 + list[0] = "Foo" + list[1] = "Bar" + list[2] = "Baz" + + list.add("Lorem") + +block: + let list = @[4, 8, 15, 16, 23, 42] + for i in 0 ..< list.len: + stdout.write($list[i] & " ") + +var collection: set[int16] +doAssert collection == {} + +block: + let collection = {'a', 'x', 'r'} + doAssert 'a' in collection + +block: + let collection = {'a', 'T', 'z'} + let isAllLowerCase = {'A' .. 'Z'} * collection == {} + doAssert(not isAllLowerCase) + +let age = 10 +if age > 0 and age <= 10: + echo("You're still a child") +elif age > 10 and age < 18: + echo("You're a teenager") +else: + echo("You're an adult") + +let variable = "Arthur" +case variable +of "Arthur", "Zaphod", "Ford": + echo("Male") +of "Marvin": + echo("Robot") +of "Trillian": + echo("Female") +else: + echo("Unknown") + +let ageDesc = if age < 18: "Non-Adult" else: "Adult" + +block: + var i = 0 + while i < 3: + echo(i) + i.inc + +block label: + var i = 0 + while true: + while i < 5: + if i > 3: break label + i.inc + +iterator values(): int = + var i = 0 + while i < 5: + yield i + i.inc + +for value in values(): + echo(value) + +import os +for filename in walkFiles("*.nim"): + echo(filename) + +for item in @[1, 2, 3]: + echo(item) + +for i, value in @[1, 2, 3]: echo("Value at ", i, ": ", value) + +doAssertRaises(IOError): + proc second() = + raise newException(IOError, "Somebody set us up the bomb") + + proc first() = + second() + + first() + +block: + proc second() = + raise newException(IOError, "Somebody set us up the bomb") + + proc first() = + try: + second() + except: + echo("Cannot perform second action because: " & + getCurrentExceptionMsg()) + + first() + +block: + type + Person = object + name: string + age: int + + var person: Person + var person1 = Person(name: "Neo", age: 28) + +block: + type + PersonObj = object + name: string + age: int + PersonRef = ref PersonObj + + # proc setName(person: PersonObj) = + # person.name = "George" + + proc setName(person: PersonRef) = + person.name = "George" + +block: + type + Dog = object + name: string + + Cat = object + name: string + + let dog: Dog = Dog(name: "Fluffy") + let cat: Cat = Cat(name: "Fluffy") + +block: + type + Dog = tuple + name: string + + Cat = tuple + name: string + + let dog: Dog = (name: "Fluffy") + let cat: Cat = (name: "Fluffy") + + echo(dog == cat) + +block: + type + Point = tuple[x, y: int] + Point2 = (int, int) + + let pos: Point = (x: 100, y: 50) + doAssert pos == (100, 50) + + let pos1: Point = (x: 100, y: 50) + let (x, y) = pos1 #<1> + let (left, _) = pos1 + doAssert x == pos1[0] + doAssert y == pos1[1] + doAssert left == x + +block: + type + Color = enum + colRed, + colGreen, + colBlue + + let color: Color = colRed + +block: + type + Color {.pure.} = enum + red, green, blue + + let color = Color.red diff --git a/tests/niminaction/Chapter3/ChatApp/readme.markdown b/tests/niminaction/Chapter3/ChatApp/readme.markdown new file mode 100644 index 000000000..200b4df1d --- /dev/null +++ b/tests/niminaction/Chapter3/ChatApp/readme.markdown @@ -0,0 +1,26 @@ +# The ChatApp source code + +This directory contains the ChatApp project, which is the project that is +created as part of Chapter 3 of the Nim in Action book. + +To compile run: + +``` +nim c src/client +nim c src/server +``` + +You can then run the ``server`` in one terminal by executing ``./src/server``. + +After doing so you can execute multiple clients in different terminals and have +them communicate via the server. + +To execute a client, make sure to specify the server address and user name +on the command line: + +```bash +./src/client localhost Peter +``` + +You should then be able to start typing in messages and sending them +by pressing the Enter key. \ No newline at end of file diff --git a/tests/niminaction/Chapter3/ChatApp/src/client.nim b/tests/niminaction/Chapter3/ChatApp/src/client.nim new file mode 100644 index 000000000..d479ebf43 --- /dev/null +++ b/tests/niminaction/Chapter3/ChatApp/src/client.nim @@ -0,0 +1,58 @@ +discard """ +action: compile +""" + +import os, threadpool, asyncdispatch, asyncnet +import protocol + +proc connect(socket: AsyncSocket, serverAddr: string) {.async.} = + ## Connects the specified AsyncSocket to the specified address. + ## Then receives messages from the server continuously. + echo("Connecting to ", serverAddr) + # Pause the execution of this procedure until the socket connects to + # the specified server. + await socket.connect(serverAddr, 7687.Port) + echo("Connected!") + while true: + # Pause the execution of this procedure until a new message is received + # from the server. + let line = await socket.recvLine() + # Parse the received message using ``parseMessage`` defined in the + # protocol module. + let parsed = parseMessage(line) + # Display the message to the user. + echo(parsed.username, " said ", parsed.message) + +echo("Chat application started") +# Ensure that the correct amount of command line arguments was specified. +if paramCount() < 2: + # Terminate the client early with an error message if there was not + # enough command line arguments specified by the user. + quit("Please specify the server address, e.g. ./client localhost username") + +# Retrieve the first command line argument. +let serverAddr = paramStr(1) +# Retrieve the second command line argument. +let username = paramStr(2) +# Initialise a new asynchronous socket. +var socket = newAsyncSocket() + +# Execute the ``connect`` procedure in the background asynchronously. +asyncCheck connect(socket, serverAddr) +# Execute the ``readInput`` procedure in the background in a new thread. +var messageFlowVar = spawn stdin.readLine() +while true: + # Check if the ``readInput`` procedure returned a new line of input. + if messageFlowVar.isReady(): + # If a new line of input was returned, we can safely retrieve it + # without blocking. + # The ``createMessage`` is then used to create a message based on the + # line of input. The message is then sent in the background asynchronously. + asyncCheck socket.send(createMessage(username, ^messageFlowVar)) + # Execute the ``readInput`` procedure again, in the background in a + # new thread. + messageFlowVar = spawn stdin.readLine() + + # Execute the asyncdispatch event loop, to continue the execution of + # asynchronous procedures. + asyncdispatch.poll() diff --git a/tests/niminaction/Chapter3/ChatApp/src/client.nim.cfg b/tests/niminaction/Chapter3/ChatApp/src/client.nim.cfg new file mode 100644 index 000000000..aed303eef --- /dev/null +++ b/tests/niminaction/Chapter3/ChatApp/src/client.nim.cfg @@ -0,0 +1 @@ +--threads:on diff --git a/tests/niminaction/Chapter3/ChatApp/src/protocol.nim b/tests/niminaction/Chapter3/ChatApp/src/protocol.nim new file mode 100644 index 000000000..4c122d4cc --- /dev/null +++ b/tests/niminaction/Chapter3/ChatApp/src/protocol.nim @@ -0,0 +1,55 @@ +import json + +type + Message* = object + username*: string + message*: string + + MessageParsingError* = object of Exception + +proc parseMessage*(data: string): Message {.raises: [MessageParsingError, KeyError].} = + var dataJson: JsonNode + try: + dataJson = parseJson(data) + except JsonParsingError: + raise newException(MessageParsingError, "Invalid JSON: " & + getCurrentExceptionMsg()) + except: + raise newException(MessageParsingError, "Unknown error: " & + getCurrentExceptionMsg()) + + if not dataJson.hasKey("username"): + raise newException(MessageParsingError, "Username field missing") + + result.username = dataJson["username"].getStr() + if result.username.len == 0: + raise newException(MessageParsingError, "Username field is empty") + + if not dataJson.hasKey("message"): + raise newException(MessageParsingError, "Message field missing") + result.message = dataJson["message"].getStr() + if result.message.len == 0: + raise newException(MessageParsingError, "Message field is empty") + +proc createMessage*(username, message: string): string = + result = $(%{ + "username": %username, + "message": %message + }) & "\c\l" + +when true: + block: + let data = """{"username": "dom", "message": "hello"}""" + let parsed = parseMessage(data) + doAssert parsed.message == "hello" + doAssert parsed.username == "dom" + + # Test failure + block: + try: + let parsed = parseMessage("asdasd") + except MessageParsingError: + doAssert true + except: + doAssert false + diff --git a/tests/niminaction/Chapter3/ChatApp/src/server.nim b/tests/niminaction/Chapter3/ChatApp/src/server.nim new file mode 100644 index 000000000..fbf0e5110 --- /dev/null +++ b/tests/niminaction/Chapter3/ChatApp/src/server.nim @@ -0,0 +1,88 @@ +discard """ +action: compile +""" + +import asyncdispatch, asyncnet + +type + Client = ref object + socket: AsyncSocket + netAddr: string + id: int + connected: bool + + Server = ref object + socket: AsyncSocket + clients: seq[Client] + +proc newServer(): Server = + ## Constructor for creating a new ``Server``. + Server(socket: newAsyncSocket(), clients: @[]) + +proc `$`(client: Client): string = + ## Converts a ``Client``'s information into a string. + $client.id & "(" & client.netAddr & ")" + +proc processMessages(server: Server, client: Client) {.async.} = + ## Loops while ``client`` is connected to this server, and checks + ## whether as message has been received from ``client``. + while true: + # Pause execution of this procedure until a line of data is received from + # ``client``. + let line = await client.socket.recvLine() + + # The ``recvLine`` procedure returns ``""`` (i.e. a string of length 0) + # when ``client`` has disconnected. + if line.len == 0: + echo(client, " disconnected!") + client.connected = false + # When a socket disconnects it must be closed. + client.socket.close() + return + + # Display the message that was sent by the client. + echo(client, " sent: ", line) + + # Send the message to other clients. + for c in server.clients: + # Don't send it to the client that sent this or to a client that is + # disconnected. + if c.id != client.id and c.connected: + await c.socket.send(line & "\c\l") + +proc loop(server: Server, port = 7687) {.async.} = + ## Loops forever and checks for new connections. + + # Bind the port number specified by ``port``. + server.socket.bindAddr(port.Port) + # Ready the server socket for new connections. + server.socket.listen() + echo("Listening on localhost:", port) + + while true: + # Pause execution of this procedure until a new connection is accepted. + let (netAddr, clientSocket) = await server.socket.acceptAddr() + echo("Accepted connection from ", netAddr) + + # Create a new instance of Client. + let client = Client( + socket: clientSocket, + netAddr: netAddr, + id: server.clients.len, + connected: true + ) + # Add this new instance to the server's list of clients. + server.clients.add(client) + # Run the ``processMessages`` procedure asynchronously in the background, + # this procedure will continuously check for new messages from the client. + asyncCheck processMessages(server, client) + +# Check whether this module has been imported as a dependency to another +# module, or whether this module is the main module. +when true: + # Initialise a new server. + var server = newServer() + echo("Server initialised!") + # Execute the ``loop`` procedure. The ``waitFor`` procedure will run the + # asyncdispatch event loop until the ``loop`` procedure finishes executing. + waitFor loop(server) diff --git a/tests/niminaction/Chapter3/various3.nim b/tests/niminaction/Chapter3/various3.nim new file mode 100644 index 000000000..c7cdf7db4 --- /dev/null +++ b/tests/niminaction/Chapter3/various3.nim @@ -0,0 +1,99 @@ +discard """ +matrix: "--mm:refc" +output: ''' +Future is no longer empty, 42 +''' +""" + +import threadpool +proc foo: string = "Dog" +var x: FlowVar[string] = spawn foo() +doAssert(^x == "Dog") + +block: + type + Box = object + case empty: bool + of false: + contents: string + else: + discard + + var obj = Box(empty: false, contents: "Hello") + doAssert obj.contents == "Hello" + + var obj2 = Box(empty: true) + doAssertRaises(FieldDefect): + echo(obj2.contents) + +import json +doAssert parseJson("null").kind == JNull +doAssert parseJson("true").kind == JBool +doAssert parseJson("42").kind == JInt +doAssert parseJson("3.14").kind == JFloat +doAssert parseJson("\"Hi\"").kind == JString +doAssert parseJson("""{ "key": "value" }""").kind == JObject +doAssert parseJson("[1, 2, 3, 4]").kind == JArray + +import json +let data = """ + {"username": "Dominik"} +""" + +let obj = parseJson(data) +doAssert obj.kind == JObject +doAssert obj["username"].kind == JString +doAssert obj["username"].str == "Dominik" + +block: + proc count10(): int = + for i in 0 ..< 10: + result.inc + doAssert count10() == 10 + +type + Point = tuple[x, y: int] + +var point = (5, 10) +var point2 = (x: 5, y: 10) + +type + Human = object + name: string + age: int + +var jeff = Human(name: "Jeff", age: 23) +var amy = Human(name: "Amy", age: 20) + +import asyncdispatch + +var future = newFuture[int]() +doAssert(not future.finished) + +future.callback = + proc (future: Future[int]) = + echo("Future is no longer empty, ", future.read) + +future.complete(42) + +import asyncdispatch, asyncfile + +when false: + var file = openAsync("") + let dataFut = file.readAll() + dataFut.callback = + proc (future: Future[string]) = + echo(future.read()) + + asyncdispatch.runForever() + +import asyncdispatch, asyncfile, os + +proc readFiles() {.async.} = + # --- Changed to getTempDir here. + var file = openAsync(getTempDir() / "test.txt", fmReadWrite) + let data = await file.readAll() + echo(data) + await file.write("Hello!\n") + +waitFor readFiles() diff --git a/tests/niminaction/Chapter3/various3.nim.cfg b/tests/niminaction/Chapter3/various3.nim.cfg new file mode 100644 index 000000000..6c1ded992 --- /dev/null +++ b/tests/niminaction/Chapter3/various3.nim.cfg @@ -0,0 +1 @@ +threads:on \ No newline at end of file diff --git a/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim b/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim new file mode 100644 index 000000000..913cd77db --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim @@ -0,0 +1,83 @@ +discard """ +action: compile +""" + +# See this page for info about the format https://wikitech.wikimedia.org/wiki/Analytics/Data/Pagecounts-all-sites +import tables, parseutils, strutils, threadpool + +const filename = "pagecounts-20160101-050000" + +type + Stats = ref object + projectName, pageTitle: string + requests, contentSize: int + +proc `$`(stats: Stats): string = + "(projectName: $#, pageTitle: $#, requests: $#, contentSize: $#)" % [ + stats.projectName, stats.pageTitle, $stats.requests, $stats.contentSize + ] + +proc parse(chunk: string): Stats = + # Each line looks like: en Main_Page 242332 4737756101 + result = Stats(projectName: "", pageTitle: "", requests: 0, contentSize: 0) + + var projectName = "" + var pageTitle = "" + var requests = "" + var contentSize = "" + for line in chunk.splitLines: + var i = 0 + projectName.setLen(0) + i.inc parseUntil(line, projectName, Whitespace, i) + i.inc skipWhitespace(line, i) + pageTitle.setLen(0) + i.inc parseUntil(line, pageTitle, Whitespace, i) + i.inc skipWhitespace(line, i) + requests.setLen(0) + i.inc parseUntil(line, requests, Whitespace, i) + i.inc skipWhitespace(line, i) + contentSize.setLen(0) + i.inc parseUntil(line, contentSize, Whitespace, i) + i.inc skipWhitespace(line, i) + + if requests.len == 0 or contentSize.len == 0: + # Ignore lines with either of the params that are empty. + continue + + let requestsInt = requests.parseInt + if requestsInt > result.requests and projectName == "en": + result = Stats( + projectName: projectName, + pageTitle: pageTitle, + requests: requestsInt, + contentSize: contentSize.parseInt + ) + +proc readChunks(filename: string, chunksize = 1000000): Stats = + result = Stats(projectName: "", pageTitle: "", requests: 0, contentSize: 0) + var file = open(filename) + var responses = newSeq[FlowVar[Stats]]() + var buffer = newString(chunksize) + var oldBufferLen = 0 + while not endOfFile(file): + let readSize = file.readChars(buffer, oldBufferLen, chunksize - oldBufferLen) + oldBufferLen + var chunkLen = readSize + + while chunkLen >= 0 and buffer[chunkLen - 1] notin NewLines: + # Find where the last line ends + chunkLen.dec + + responses.add(spawn parse(buffer[0 ..< chunkLen])) + oldBufferLen = readSize - chunkLen + buffer[0 ..< oldBufferLen] = buffer[readSize - oldBufferLen .. ^1] + + for resp in responses: + let statistic = ^resp + if statistic.requests > result.requests: + result = statistic + + file.close() + + +when true: + echo readChunks(filename) diff --git a/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim.cfg b/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim.cfg new file mode 100644 index 000000000..aed303eef --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim.cfg @@ -0,0 +1 @@ +--threads:on diff --git a/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim b/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim new file mode 100644 index 000000000..102313de9 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim @@ -0,0 +1,68 @@ +discard """ +action: compile +""" + +# See this page for info about the format https://wikitech.wikimedia.org/wiki/Analytics/Data/Pagecounts-all-sites +import tables, parseutils, strutils, threadpool, re + +const filename = "pagecounts-20160101-050000" + +type + Stats = ref object + projectName, pageTitle: string + requests, contentSize: int + +proc `$`(stats: Stats): string = + "(projectName: $#, pageTitle: $#, requests: $#, contentSize: $#)" % [ + stats.projectName, stats.pageTitle, $stats.requests, $stats.contentSize + ] + +proc parse(chunk: string): Stats = + # Each line looks like: en Main_Page 242332 4737756101 + result = Stats(projectName: "", pageTitle: "", requests: 0, contentSize: 0) + + var matches: array[4, string] + var reg = re"([^\s]+)\s([^\s]+)\s(\d+)\s(\d+)" + for line in chunk.splitLines: + + let start = find(line, reg, matches) + if start == -1: continue + + let requestsInt = matches[2].parseInt + if requestsInt > result.requests and matches[0] == "en": + result = Stats( + projectName: matches[0], + pageTitle: matches[1], + requests: requestsInt, + contentSize: matches[3].parseInt + ) + +proc readChunks(filename: string, chunksize = 1000000): Stats = + result = Stats(projectName: "", pageTitle: "", requests: 0, contentSize: 0) + var file = open(filename) + var responses = newSeq[FlowVar[Stats]]() + var buffer = newString(chunksize) + var oldBufferLen = 0 + while not endOfFile(file): + let readSize = file.readChars(buffer, oldBufferLen, chunksize - oldBufferLen) + oldBufferLen + var chunkLen = readSize + + while chunkLen >= 0 and buffer[chunkLen - 1] notin NewLines: + # Find where the last line ends + chunkLen.dec + + responses.add(spawn parse(buffer[0 ..< chunkLen])) + oldBufferLen = readSize - chunkLen + buffer[0 ..< oldBufferLen] = buffer[readSize - oldBufferLen .. ^1] + + echo("Spawns: ", responses.len) + for resp in responses: + let statistic = ^resp + if statistic.requests > result.requests: + result = statistic + + file.close() + + +when true: + echo readChunks(filename) diff --git a/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim.cfg b/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim.cfg new file mode 100644 index 000000000..aed303eef --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim.cfg @@ -0,0 +1 @@ +--threads:on diff --git a/tests/niminaction/Chapter6/WikipediaStats/naive.nim b/tests/niminaction/Chapter6/WikipediaStats/naive.nim new file mode 100644 index 000000000..687177f74 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/naive.nim @@ -0,0 +1,33 @@ +discard """ +action: compile +""" + +# See this page for info about the format https://wikitech.wikimedia.org/wiki/Analytics/Data/Pagecounts-all-sites +import tables, parseutils, strutils + +const filename = "pagecounts-20150101-050000" + +proc parse(filename: string): tuple[projectName, pageTitle: string, + requests, contentSize: int] = + # Each line looks like: en Main_Page 242332 4737756101 + var file = open(filename) + for line in file.lines: + var i = 0 + var projectName = "" + i.inc parseUntil(line, projectName, Whitespace, i) + i.inc + var pageTitle = "" + i.inc parseUntil(line, pageTitle, Whitespace, i) + i.inc + var requests = 0 + i.inc parseInt(line, requests, i) + i.inc + var contentSize = 0 + i.inc parseInt(line, contentSize, i) + if requests > result[2] and projectName == "en": + result = (projectName, pageTitle, requests, contentSize) + + file.close() + +when true: + echo parse(filename) diff --git a/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim b/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim new file mode 100644 index 000000000..379ec7364 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim @@ -0,0 +1,76 @@ +discard """ +action: compile +""" + +import os, parseutils, threadpool, strutils + +type + Stats = ref object + domainCode, pageTitle: string + countViews, totalSize: int + +proc newStats(): Stats = + Stats(domainCode: "", pageTitle: "", countViews: 0, totalSize: 0) + +proc `$`(stats: Stats): string = + "(domainCode: $#, pageTitle: $#, countViews: $#, totalSize: $#)" % [ + stats.domainCode, stats.pageTitle, $stats.countViews, $stats.totalSize + ] + +proc parse(line: string, domainCode, pageTitle: var string, + countViews, totalSize: var int) = + if line.len == 0: return + var i = 0 + domainCode.setLen(0) + i.inc parseUntil(line, domainCode, {' '}, i) + i.inc + pageTitle.setLen(0) + i.inc parseUntil(line, pageTitle, {' '}, i) + i.inc + countViews = 0 + i.inc parseInt(line, countViews, i) + i.inc + totalSize = 0 + i.inc parseInt(line, totalSize, i) + +proc parseChunk(chunk: string): Stats = + result = newStats() + var domainCode = "" + var pageTitle = "" + var countViews = 0 + var totalSize = 0 + for line in splitLines(chunk): + parse(line, domainCode, pageTitle, countViews, totalSize) + if domainCode == "en" and countViews > result.countViews: + result = Stats(domainCode: domainCode, pageTitle: pageTitle, + countViews: countViews, totalSize: totalSize) + +proc readPageCounts(filename: string, chunkSize = 1_000_000) = + var file = open(filename) + var responses = newSeq[FlowVar[Stats]]() + var buffer = newString(chunksize) + var oldBufferLen = 0 + while not endOfFile(file): + let reqSize = chunksize - oldBufferLen + let readSize = file.readChars(buffer, oldBufferLen, reqSize) + oldBufferLen + var chunkLen = readSize + + while chunkLen >= 0 and buffer[chunkLen - 1] notin NewLines: + chunkLen.dec + + responses.add(spawn parseChunk(buffer[0 ..< chunkLen])) + oldBufferLen = readSize - chunkLen + buffer[0 ..< oldBufferLen] = buffer[readSize - oldBufferLen .. ^1] + + var mostPopular = newStats() + for resp in responses: + let statistic = ^resp + if statistic.countViews > mostPopular.countViews: + mostPopular = statistic + + echo("Most popular is: ", mostPopular) + +when true: + const file = "pagecounts-20160101-050000" + let filename = getCurrentDir() / file + readPageCounts(filename) diff --git a/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim.cfg b/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim.cfg new file mode 100644 index 000000000..9d57ecf93 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim.cfg @@ -0,0 +1 @@ +--threads:on \ No newline at end of file diff --git a/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim b/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim new file mode 100644 index 000000000..f4b072204 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim @@ -0,0 +1,17 @@ +discard """ +action: compile +""" + +import threadpool + +var counter = 0 + +proc increment(x: int) = + for i in 0 ..< x: + let value = counter + 1 + counter = value + +spawn increment(10_000) +spawn increment(10_000) +sync() +echo(counter) diff --git a/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim.cfg b/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim.cfg new file mode 100644 index 000000000..9d57ecf93 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim.cfg @@ -0,0 +1 @@ +--threads:on \ No newline at end of file diff --git a/tests/niminaction/Chapter6/WikipediaStats/sequential_counts.nim b/tests/niminaction/Chapter6/WikipediaStats/sequential_counts.nim new file mode 100644 index 000000000..f4bae3df5 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/sequential_counts.nim @@ -0,0 +1,38 @@ +discard """ +action: compile +""" + +import os, parseutils + +proc parse(line: string, domainCode, pageTitle: var string, + countViews, totalSize: var int) = + var i = 0 + domainCode.setLen(0) + i.inc parseUntil(line, domainCode, {' '}, i) + i.inc + pageTitle.setLen(0) + i.inc parseUntil(line, pageTitle, {' '}, i) + i.inc + countViews = 0 + i.inc parseInt(line, countViews, i) + i.inc + totalSize = 0 + i.inc parseInt(line, totalSize, i) + +proc readPageCounts(filename: string) = + var domainCode = "" + var pageTitle = "" + var countViews = 0 + var totalSize = 0 + var mostPopular = ("", "", 0, 0) + for line in filename.lines: + parse(line, domainCode, pageTitle, countViews, totalSize) + if domainCode == "en" and countViews > mostPopular[2]: + mostPopular = (domainCode, pageTitle, countViews, totalSize) + + echo("Most popular is: ", mostPopular) + +when true: + const file = "pagecounts-20160101-050000" + let filename = getCurrentDir() / file + readPageCounts(filename) diff --git a/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim b/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim new file mode 100644 index 000000000..7bdde8397 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim @@ -0,0 +1,20 @@ +discard """ + errormsg: "unguarded access: counter" + line: 14 +""" + +import threadpool, locks + +var counterLock: Lock +initLock(counterLock) +var counter {.guard: counterLock.} = 0 + +proc increment(x: int) = + for i in 0 ..< x: + let value = counter + 1 + counter = value + +spawn increment(10_000) +spawn increment(10_000) +sync() +echo(counter) diff --git a/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim.cfg b/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim.cfg new file mode 100644 index 000000000..9d57ecf93 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim.cfg @@ -0,0 +1 @@ +--threads:on \ No newline at end of file diff --git a/tests/niminaction/Chapter7/Tweeter/Tweeter.nimble b/tests/niminaction/Chapter7/Tweeter/Tweeter.nimble new file mode 100644 index 000000000..0a0ffad1a --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/Tweeter.nimble @@ -0,0 +1,14 @@ +# Package + +version = "0.1.0" +author = "Dominik Picheta" +description = "A simple Twitter clone developed in Nim in Action." +license = "MIT" + +bin = @["tweeter"] +skipExt = @["nim"] + +# Dependencies + +requires "nim >= 0.13.1" +requires "jester >= 0.0.1" diff --git a/tests/niminaction/Chapter7/Tweeter/public/style.css b/tests/niminaction/Chapter7/Tweeter/public/style.css new file mode 100644 index 000000000..baacfaf9d --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/public/style.css @@ -0,0 +1,117 @@ +body { + background-color: #f1f9ea; + margin: 0; + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; +} + +div#main { + width: 80%; + margin-left: auto; + margin-right: auto; +} + +div#user { + background-color: #66ac32; + width: 100%; + color: #c7f0aa; + padding: 5pt; +} + +div#user > h1 { + color: #ffffff; +} + +h1 { + margin: 0; + display: inline; + padding-left: 10pt; + padding-right: 10pt; +} + +div#user > form { + float: right; + margin-right: 10pt; +} + +div#user > form > input[type="submit"] { + border: 0px none; + padding: 5pt; + font-size: 108%; + color: #ffffff; + background-color: #515d47; + border-radius: 5px; + cursor: pointer; +} + +div#user > form > input[type="submit"]:hover { + background-color: #538c29; +} + + +div#messages { + background-color: #a2dc78; + width: 90%; + margin-left: auto; + margin-right: auto; + color: #1a1a1a; +} + +div#messages > div { + border-left: 1px solid #869979; + border-right: 1px solid #869979; + border-bottom: 1px solid #869979; + padding: 5pt; +} + +div#messages > div > a, div#messages > div > span { + color: #475340; +} + +div#messages > div > a:hover { + text-decoration: none; + color: #c13746; +} + +h3 { + margin-bottom: 0; + font-weight: normal; +} + +div#login { + width: 200px; + margin-left: auto; + margin-right: auto; + margin-top: 20%; + + font-size: 130%; +} + +div#login span.small { + display: block; + font-size: 56%; +} + +div#newMessage { + background-color: #538c29; + width: 90%; + margin-left: auto; + margin-right: auto; + color: #ffffff; + padding: 5pt; +} + +div#newMessage span { + padding-right: 5pt; +} + +div#newMessage form { + display: inline; +} + +div#newMessage > form > input[type="text"] { + width: 80%; +} + +div#newMessage > form > input[type="submit"] { + font-size: 80%; +} diff --git a/tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim b/tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim new file mode 100644 index 000000000..67d9323f2 --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim @@ -0,0 +1,11 @@ +discard """ +disabled: true +output: "Database created successfully!" +""" + +import database + +var db = newDatabase() +db.setup() +echo("Database created successfully!") +db.close() diff --git a/tests/niminaction/Chapter7/Tweeter/src/database.nim b/tests/niminaction/Chapter7/Tweeter/src/database.nim new file mode 100644 index 000000000..bd6667f70 --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/src/database.nim @@ -0,0 +1,93 @@ +import times, db_sqlite, strutils #<1> +type #<2> + Database* = ref object + db*: DbConn + + User* = object #<3> + username*: string #<4> + following*: seq[string] #<5> + + Message* = object #<6> + username*: string #<7> + time*: Time #<8> + msg*: string #<9> + +proc newDatabase*(filename = "tweeter.db"): Database = + new result + result.db = open(filename, "", "", "") + +proc close*(database: Database) = + database.db.close() + +proc setup*(database: Database) = + database.db.exec(sql""" + CREATE TABLE IF NOT EXISTS User( + username text PRIMARY KEY + ); + """) + + database.db.exec(sql""" + CREATE TABLE IF NOT EXISTS Following( + follower text, + followed_user text, + PRIMARY KEY (follower, followed_user), + FOREIGN KEY (follower) REFERENCES User(username), + FOREIGN KEY (followed_user) REFERENCES User(username) + ); + """) + + database.db.exec(sql""" + CREATE TABLE IF NOT EXISTS Message( + username text, + time integer, + msg text NOT NULL, + FOREIGN KEY (username) REFERENCES User(username) + ); + """) + +proc post*(database: Database, message: Message) = + if message.msg.len > 140: #<1> + raise newException(ValueError, "Message has to be less than 140 characters.") + + database.db.exec(sql"INSERT INTO Message VALUES (?, ?, ?);", #<2> + message.username, $message.time.toUnix().int, message.msg) #<3> + +proc follow*(database: Database, follower: User, user: User) = + database.db.exec(sql"INSERT INTO Following VALUES (?, ?);",#<2> + follower.username, user.username) + +proc create*(database: Database, user: User) = + database.db.exec(sql"INSERT INTO User VALUES (?);", user.username) #<2> + +proc findUser*(database: Database, username: string, user: var User): bool = + let row = database.db.getRow( + sql"SELECT username FROM User WHERE username = ?;", username) + if row[0].len == 0: return false + else: user.username = row[0] + + let following = database.db.getAllRows( + sql"SELECT followed_user FROM Following WHERE follower = ?;", username) + user.following = @[] + for row in following: + if row[0].len != 0: + user.following.add(row[0]) + + return true + +proc findMessages*(database: Database, usernames: seq[string], + limit = 10): seq[Message] = + result = @[] + if usernames.len == 0: return + var whereClause = " WHERE " + for i in 0 ..< usernames.len: + whereClause.add("username = ? ") + if i != usernames.high: + whereClause.add("or ") + + let messages = database.db.getAllRows( + sql("SELECT username, time, msg FROM Message" & + whereClause & + "ORDER BY time DESC LIMIT " & $limit), + usernames) + for row in messages: + result.add(Message(username: row[0], time: fromUnix(row[1].parseInt), msg: row[2])) diff --git a/tests/niminaction/Chapter7/Tweeter/src/tweeter.nim b/tests/niminaction/Chapter7/Tweeter/src/tweeter.nim new file mode 100644 index 000000000..1b521521c --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/src/tweeter.nim @@ -0,0 +1,68 @@ +discard """ +disabled: true +action: compile +matrix: "--threads:off" +""" + +import asyncdispatch, times + +import jester + +import database, views/user, views/general + +proc userLogin(db: Database, request: Request, user: var User): bool = + if request.cookies.hasKey("username"): + if not db.findUser(request.cookies["username"], user): + user = User(username: request.cookies["username"], following: @[]) + db.create(user) + return true + else: + return false + +let db = newDatabase() +routes: + get "/": + var user: User + if db.userLogin(request, user): + let messages = db.findMessages(user.following & user.username) + resp renderMain(renderTimeline(user.username, messages)) + else: + resp renderMain(renderLogin()) + + get "/@name": + cond '.' notin @"name" + var user: User + if not db.findUser(@"name", user): + halt "User not found" + let messages = db.findMessages(@[user.username]) + + var currentUser: User + if db.userLogin(request, currentUser): + resp renderMain(renderUser(user, currentUser) & renderMessages(messages)) + else: + resp renderMain(renderUser(user) & renderMessages(messages)) + + post "/follow": + var follower: User + var target: User + if not db.findUser(@"follower", follower): + halt "Follower not found" + if not db.findUser(@"target", target): + halt "Follow target not found" + db.follow(follower, target) + redirect(uri("/" & @"target")) + + post "/login": + setCookie("username", @"username", getTime().utc() + 2.hours) + redirect("/") + + post "/createMessage": + let message = Message( + username: @"username", + time: getTime(), + msg: @"message" + ) + db.post(message) + redirect("/") + +runForever() diff --git a/tests/niminaction/Chapter7/Tweeter/src/views/general.nim b/tests/niminaction/Chapter7/Tweeter/src/views/general.nim new file mode 100644 index 000000000..0e920b1de --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/src/views/general.nim @@ -0,0 +1,51 @@ +#? stdtmpl(subsChar = '$', metaChar = '#') +#import "../database" +#import user +#import xmltree +# +#proc `$!`(text: string): string = escape(text) +#end proc +# +#proc renderMain*(body: string): string = +# result = "" +<!DOCTYPE html> +<html> + <head> + <title>Tweeter written in Nim</title> + <link rel="stylesheet" type="text/css" href="style.css"> + </head> + + <body> + ${body} + </body> + +</html> +#end proc +# +#proc renderLogin*(): string = +# result = "" +<div id="login"> + <span>Login</span> + <span class="small">Please type in your username...</span> + <form action="login" method="post"> + <input type="text" name="username"> + <input type="submit" value="Login"> + </form> +</div> +#end proc +# +#proc renderTimeline*(username: string, messages: seq[Message]): string = +# result = "" +<div id="user"> + <h1>${$!username}'s timeline</h1> +</div> +<div id="newMessage"> + <span>New message</span> + <form action="createMessage" method="post"> + <input type="text" name="message"> + <input type="hidden" name="username" value="${$!username}"> + <input type="submit" value="Tweet"> + </form> +</div> +${renderMessages(messages)} +#end proc \ No newline at end of file diff --git a/tests/niminaction/Chapter7/Tweeter/src/views/user.nim b/tests/niminaction/Chapter7/Tweeter/src/views/user.nim new file mode 100644 index 000000000..4abcf440d --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/src/views/user.nim @@ -0,0 +1,49 @@ +#? stdtmpl(subsChar = '$', metaChar = '#', toString = "xmltree.escape") +#import "../database" +#import xmltree +#import times +# +#proc renderUser*(user: User): string = +# result = "" +<div id="user"> + <h1>${user.username}</h1> + <span>Following: ${$user.following.len}</span> +</div> +#end proc +# +#proc renderUser*(user: User, currentUser: User): string = +# result = "" +<div id="user"> + <h1>${user.username}</h1> + <span>Following: ${$user.following.len}</span> + #if user.username notin currentUser.following: + <form action="follow" method="post"> + <input type="hidden" name="follower" value="${currentUser.username}"> + <input type="hidden" name="target" value="${user.username}"> + <input type="submit" value="Follow"> + </form> + #end if +</div> +# +#end proc +# +#proc renderMessages*(messages: seq[Message]): string = +# result = "" +<div id="messages"> + #for message in messages: + <div> + <a href="/${message.username}">${message.username}</a> + <span>${message.time.utc().format("HH:mm MMMM d',' yyyy")}</span> + <h3>${message.msg}</h3> + </div> + #end for +</div> +#end proc +# +#when true: +# echo renderUser(User(username: "d0m96<>", following: @[])) +# echo renderMessages(@[ +# Message(username: "d0m96", time: getTime(), msg: "Hello World!"), +# Message(username: "d0m96", time: getTime(), msg: "Testing") +# ]) +#end when diff --git a/tests/niminaction/Chapter7/Tweeter/tests/database_test.nim b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nim new file mode 100644 index 000000000..c8beb4a30 --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nim @@ -0,0 +1,33 @@ +discard """ +disabled: true +outputsub: "All tests finished successfully!" +""" + +import database, os, times + +when true: + removeFile("tweeter_test.db") + var db = newDatabase("tweeter_test.db") + db.setup() + + db.create(User(username: "d0m96")) + db.create(User(username: "nim_lang")) + + db.post(Message(username: "nim_lang", time: getTime() - 4.seconds, + msg: "Hello Nim in Action readers")) + db.post(Message(username: "nim_lang", time: getTime(), + msg: "99.9% off Nim in Action for everyone, for the next minute only!")) + + var dom: User + doAssert db.findUser("d0m96", dom) + var nim: User + doAssert db.findUser("nim_lang", nim) + db.follow(dom, nim) + + doAssert db.findUser("d0m96", dom) + + let messages = db.findMessages(dom.following) + echo(messages) + doAssert(messages[0].msg == "99.9% off Nim in Action for everyone, for the next minute only!") + doAssert(messages[1].msg == "Hello Nim in Action readers") + echo("All tests finished successfully!") diff --git a/tests/niminaction/Chapter7/Tweeter/tests/database_test.nims b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nims new file mode 100644 index 000000000..226905fbf --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nims @@ -0,0 +1,2 @@ +--path:"../src" +#switch("path", "./src") diff --git a/tests/niminaction/Chapter8/canvas/canvas.nim b/tests/niminaction/Chapter8/canvas/canvas.nim new file mode 100644 index 000000000..ae2765630 --- /dev/null +++ b/tests/niminaction/Chapter8/canvas/canvas.nim @@ -0,0 +1,23 @@ +discard """ +action: compile +""" + +import dom + +type + CanvasRenderingContext* = ref object + fillStyle* {.importc.}: cstring + strokeStyle* {.importc.}: cstring + +{.push importcpp.} + +proc getContext*(canvasElement: Element, + contextType: cstring): CanvasRenderingContext + +proc fillRect*(context: CanvasRenderingContext, x, y, width, height: int) + +proc moveTo*(context: CanvasRenderingContext, x, y: int) + +proc lineTo*(context: CanvasRenderingContext, x, y: int) + +proc stroke*(context: CanvasRenderingContext) diff --git a/tests/niminaction/Chapter8/canvas/canvas_test.nim b/tests/niminaction/Chapter8/canvas/canvas_test.nim new file mode 100644 index 000000000..42d222b7b --- /dev/null +++ b/tests/niminaction/Chapter8/canvas/canvas_test.nim @@ -0,0 +1,19 @@ +import canvas, dom + +proc onLoad() {.exportc.} = + var canvas = document.getElementById("canvas").EmbedElement + canvas.width = window.innerWidth + canvas.height = window.innerHeight + var ctx = canvas.getContext("2d") + + ctx.fillStyle = "#1d4099" + ctx.fillRect(0, 0, window.innerWidth, window.innerHeight) + + ctx.strokeStyle = "#ffffff" + let letterWidth = 100 + let letterLeftPos = (window.innerWidth div 2) - (letterWidth div 2) + ctx.moveTo(letterLeftPos, 320) + ctx.lineTo(letterLeftPos, 110) + ctx.lineTo(letterLeftPos + letterWidth, 320) + ctx.lineTo(letterLeftPos + letterWidth, 110) + ctx.stroke() diff --git a/tests/niminaction/Chapter8/sdl/sdl.nim b/tests/niminaction/Chapter8/sdl/sdl.nim new file mode 100644 index 000000000..212f7b022 --- /dev/null +++ b/tests/niminaction/Chapter8/sdl/sdl.nim @@ -0,0 +1,38 @@ +when defined(windows): + const libName* = "SDL2.dll" +elif defined(linux) or defined(freebsd) or defined(netbsd): + const libName* = "libSDL2.so" +elif defined(macosx): + const libName* = "libSDL2.dylib" +elif defined(openbsd): + const libName* = "libSDL2.so.0.6" +else: + {.error: "SDL library name not set for this platform".} + +type + SdlWindow = object + SdlWindowPtr* = ptr SdlWindow + SdlRenderer = object + SdlRendererPtr* = ptr SdlRenderer + +const INIT_VIDEO* = 0x00000020 + +{.push dynlib: libName.} +proc init*(flags: uint32): cint {.importc: "SDL_Init".} + +proc createWindowAndRenderer*(width, height: cint, window_flags: cuint, + window: var SdlWindowPtr, renderer: var SdlRendererPtr): cint + {.importc: "SDL_CreateWindowAndRenderer".} + +proc pollEvent*(event: pointer): cint {.importc: "SDL_PollEvent".} + +proc setDrawColor*(renderer: SdlRendererPtr, r, g, b, a: uint8): cint + {.importc: "SDL_SetRenderDrawColor", discardable.} + +proc present*(renderer: SdlRendererPtr) {.importc: "SDL_RenderPresent".} + +proc clear*(renderer: SdlRendererPtr) {.importc: "SDL_RenderClear".} + +proc drawLines*(renderer: SdlRendererPtr, points: ptr tuple[x, y: cint], + count: cint): cint {.importc: "SDL_RenderDrawLines", discardable.} +{.pop.} diff --git a/tests/niminaction/Chapter8/sdl/sdl_test.nim b/tests/niminaction/Chapter8/sdl/sdl_test.nim new file mode 100644 index 000000000..db1700e0d --- /dev/null +++ b/tests/niminaction/Chapter8/sdl/sdl_test.nim @@ -0,0 +1,41 @@ +discard """ +action: compile +""" + +import os +import sdl + +if sdl.init(INIT_VIDEO) == -1: + quit("Couldn't initialise SDL") + +var window: SdlWindowPtr +var renderer: SdlRendererPtr +if createWindowAndRenderer(640, 480, 0, window, renderer) == -1: + quit("Couldn't create a window or renderer") + +discard pollEvent(nil) +renderer.setDrawColor 29, 64, 153, 255 +renderer.clear +renderer.setDrawColor 255, 255, 255, 255 + +when false: # no long work with gcc 14! + # just to ensure code from NimInAction still works, but + # the `else` branch would work as well in C mode + var points = [ + (260'i32, 320'i32), + (260'i32, 110'i32), + (360'i32, 320'i32), + (360'i32, 110'i32) + ] +else: + var points = [ + (260.cint, 320.cint), + (260.cint, 110.cint), + (360.cint, 320.cint), + (360.cint, 110.cint) + ] + +renderer.drawLines(addr points[0], points.len.cint) + +renderer.present +sleep(5000) diff --git a/tests/niminaction/Chapter8/sfml/sfml.nim b/tests/niminaction/Chapter8/sfml/sfml.nim new file mode 100644 index 000000000..fea85fcd4 --- /dev/null +++ b/tests/niminaction/Chapter8/sfml/sfml.nim @@ -0,0 +1,26 @@ +{.passL: "-lsfml-graphics -lsfml-system -lsfml-window".} + +type + VideoMode* {.importcpp: "sf::VideoMode".} = object + RenderWindowObj {.importcpp: "sf::RenderWindow".} = object + RenderWindow* = ptr RenderWindowObj + Color* {.importcpp: "sf::Color".} = object + Event* {.importcpp: "sf::Event".} = object + +{.push cdecl, header: "<SFML/Graphics.hpp>".} + +proc videoMode*(modeWidth, modeHeight: cuint, modeBitsPerPixel: cuint = 32): VideoMode + {.importcpp: "sf::VideoMode(@)", constructor.} + +proc newRenderWindow*(mode: VideoMode, title: cstring): RenderWindow + {.importcpp: "new sf::RenderWindow(@)", constructor.} + +proc pollEvent*(window: RenderWindow, event: var Event): bool + {.importcpp: "#.pollEvent(@)".} + +proc newColor*(red, green, blue, alpha: uint8): Color + {.importcpp: "sf::Color(@)", constructor.} + +proc clear*(window: RenderWindow, color: Color) {.importcpp: "#.clear(@)".} + +proc display*(window: RenderWindow) {.importcpp: "#.display()".} diff --git a/tests/niminaction/Chapter8/sfml/sfml_test.nim b/tests/niminaction/Chapter8/sfml/sfml_test.nim new file mode 100644 index 000000000..e71060cb4 --- /dev/null +++ b/tests/niminaction/Chapter8/sfml/sfml_test.nim @@ -0,0 +1,14 @@ +discard """ +action: compile +disabled: "windows" +""" + +import sfml, os +var window = newRenderWindow(videoMode(800, 600), "SFML works!") + +var event: Event +discard window.pollEvent(event) +window.clear(newColor(29, 64, 153, 255)) +window.display() + +sleep(1000) diff --git a/tests/niminaction/Chapter9/configurator/configurator.nim b/tests/niminaction/Chapter9/configurator/configurator.nim new file mode 100644 index 000000000..0d5627889 --- /dev/null +++ b/tests/niminaction/Chapter9/configurator/configurator.nim @@ -0,0 +1,84 @@ +import macros + +proc createRefType(ident: NimIdent, identDefs: seq[NimNode]): NimNode = + result = newTree(nnkTypeSection, + newTree(nnkTypeDef, + newIdentNode(ident), + newEmptyNode(), + newTree(nnkRefTy, + newTree(nnkObjectTy, + newEmptyNode(), + newEmptyNode(), + newTree(nnkRecList, + identDefs + ) + ) + ) + ) + ) + +proc toIdentDefs(stmtList: NimNode): seq[NimNode] = + expectKind(stmtList, nnkStmtList) + result = @[] + + for child in stmtList: + expectKind(child, nnkCall) + result.add(newIdentDefs(child[0], child[1][0])) + +template constructor(ident: untyped): untyped = + proc `new ident`(): `ident` = + new result + +proc createLoadProc(typeName: NimIdent, identDefs: seq[NimNode]): NimNode = + var cfgIdent = newIdentNode("cfg") + var filenameIdent = newIdentNode("filename") + var objIdent = newIdentNode("obj") + + var body = newStmtList() + body.add quote do: + var `objIdent` = parseFile(`filenameIdent`) + + for identDef in identDefs: + let fieldNameIdent = identDef[0] + let fieldName = $fieldNameIdent.ident + case $identDef[1].ident + of "string": + body.add quote do: + `cfgIdent`.`fieldNameIdent` = `objIdent`[`fieldName`].getStr + of "int": + body.add quote do: + `cfgIdent`.`fieldNameIdent` = `objIdent`[`fieldName`].getNum().int + else: + doAssert(false, "Not Implemented") + + return newProc(newIdentNode("load"), + [newEmptyNode(), + newIdentDefs(cfgIdent, newIdentNode(typeName)), + newIdentDefs(filenameIdent, newIdentNode("string"))], + body) + +macro config*(typeName: untyped, fields: untyped): untyped = + result = newStmtList() + + let identDefs = toIdentDefs(fields) + result.add createRefType(typeName.ident, identDefs) + result.add getAst(constructor(typeName.ident)) + result.add createLoadProc(typeName.ident, identDefs) + + echo treeRepr(typeName) + echo treeRepr(fields) + + echo treeRepr(result) + echo toStrLit(result) + # TODO: Verify that we can export fields in config type so that it can be + # used in another module. + +import json +config MyAppConfig: + address: string + port: int + +var myConf = newMyAppConfig() +myConf.load("myappconfig.cfg") +echo("Address: ", myConf.address) +echo("Port: ", myConf.port) |