[CONTACT]

[ABOUT]

[POLICY]

At the end of Nim Day

Found at: sdf.org:70/users/ratfactor/phlog/2018-08-30-nim-day-3

Nim Day 3: IO
============================================================

At the end of Nim Day 2, I said I'd be talkin' about Nim's type
change is for one simple reason: reading and writing tends to be
more fun!

When in doubt, I'd rather err on the side of having too much fun
"fun", I mean getting into the meat of making things work rather
than worrying overly much about syntax.  To be sure, I take syntax
very seriously and learning the fine details of a language is
mportant!  But I've also learned that you can learn a *lot* of
minutia about a language's details and not ever learn *how to use
t*.

That's where I was years ago when read Stroustrup's _The C++
bought the STL reference book (some big Addison-Wesley hardcover)
and read through that, and then daydreamed about all of the things
to build anything!  What a waste!

The point is, I try not to repeat that experience.  I still forget
as much as I learn, but I try to make sure that I can apply most
of what I'm learning.


File IO in the `system` module
------------------------------------------------------------

Let's jump right in and see what Nim provides in the core standard
library.  The 'system' module contains basic IO facilities.  It's
already imported into every Nim project, so there's no need to
nclude it.  You can see the entire list of contents of the
'system' module here:


The very first IO thing we run into in the list of methods is
`open()`.  I also see a `writeLine()` and `close()` method further

	# write.nim
	var myfile: File
	discard open(myfile, "hotdata", fmWrite)
	myfile.writeLine("wigglers=3")
	myfile.writeLine("danglers=6")
	close(myfile)

A quick explanatory list:

- Create variable of type File to hold our file reference

- Call open() with the file referece, file name to open, and the
  mode

- The file mode fmWrite means...oh, I think you can guess

- Nim requires that we do something with all return values -
  `discard` is a non-action we can perform if we don't want the
  value

- Call the writeLine() method on myfile and send it a string

- Do it again

- Close the file

- Don't bother with error checking because that's for wimps

Now we compile and run:

	$ nim compile write
	$ ./write
	$

Exciting?  Maybe, let's see if our file was written:

	$ ls -l hotdata
	-rw-r--r-- 1 dave users 22 Aug 27 18:35 hotdata

Yes.

	$ file hotdata
	hotdata: ASCII text

Yes!

	$ wc hotdata
	 2  2 22 hotdata

YES!

	$ cat hotdata
	wigglers=3
	danglers=6

Yahooo!  Yessss, yessss, YES!

Okay, okay, calm down.  I'm excited too.  But can we *read* the
file?

	# read.nim
	var myfile: File
	discard open(myfile, "hotdata", fmRead)
	echo myfile.readLine()
	echo myfile.readLine()
	close(myfile)

By the way, there's a shortcut for compiling and running a Nim

	$ nim compile --run read

Or shorter:

	$ nim c --run read

Or my favorite:

	$ nim c -r read
	...
	wigglers=3
	danglers=6 

Woo!  There's our data.

Also, I find the Nim compiler to be a bit noisy by default, so we
can quiet it down like so:

	$ nim c -r --verbosity:0 read

Now how about reading from STDIN and writing to STDOUT?

The `system` module defines those as File objects, so let's see if

	# stdio.nim
	var foo = stdin.readLine()
	stdout.writeLine(foo)

Boy, since these objects are already defined and opened for us,
this is sure a small program!  Now we compile and run:

	nim c -r stdio
	...
	Hey there, Gophersphere!!!
	Hey there, Gophersphere!!!

Sweet, it echoes the line we typed and then exits.  It's like a
terrible `cat`.

Redirecting stdin from a file works great, too, so this is the

	$ ./stdio < hotdata 
	wigglers=3

Neat!


Command line params in the `os` module
------------------------------------------------------------

nterface with the outside world.  I guess these aren't what we
might think of as traditional "I/O" per se, but they do let us
"input" and "output".  For example, we can get command-line
arguments ("arguably" a very important type of input) :

	# args.nim
	import os
	echo "Argh! You gave me:"
	echo paramStr(1)

We can pass arguments to programs through the Nim compiler's
arguments:

	$ nim c -r args Foo
	...
	Argh! You gave me:
	Foo

(gracefully!) with an invalid index error:

	$ ./args 
	Argh! You gave me:
	Traceback (most recent call last)
	args.nim(4)              args
	os.nim(1466)             paramStr
	Error: unhandled exception: invalid index [IndexError]

The `os` module also lets us examine files and directories, copy,
move, and delete files, execute external commands and that sort of
thing.


Streams
------------------------------------------------------------

For more advanced file IO, there is the `streams` module, which
and types of data to File 'streams'.

Let's just see a quick example of writing some binary data, just a
couple numbers:

	# writebin.nim
	import streams
	var a: uint8 = 4
	var b: int16 = 4095
	let fs = newFileStream("hotdata2", fmWrite)
	fs.write(a)
	fs.write(b)
	fs.close

Running this should create a binary file.  Let's see:

	$ nim c -r writebin.nim
	...
	$ hexdump hotdata2
	0000000 ff04 000f 0000                         
	0000005

Okay, that *looks* roughly right.  At least there's a 4 in there
and 4095 is 'fff' in hexadecimal, so that's probably right, but
t's hard to tell when you factor in the default byte grouping,
endianess, etc.  So let's just see if Nim can read this back in
for us!

	# readbin.nim
	import streams
	let fs = newFileStream("hotdata2")
	let a = fs.readUint8()
	let b = fs.readInt16()
	echo a
	echo b
	fs.close

And here's the results...

	$ nim c -r readbin
	...
	4
	4095

Hey, there are our numbers!


Strings
------------------------------------------------------------

This doesn't have anything to do with IO, but I want to use this
feature later and we need a break after reading and writing binary

Nim's string handling is really excellent.  I'm not going to
attempt any comparisons with, say, Perl, Tcl, or Ruby.  But I
think it holds its own.

Certainly, in the world of compiled languages, Nim's string

Here are three important features:


	# stringcat.nim
	let who = "Gophers"
	echo "Hello " & who & "!"

Does what you'd expect:

	$ nim -r c strings
	...
	Hello Gophers!


	# tostring.nim
	let foo = 5
	let bar = ['a', 'b']
	echo "My Stuff! foo=" & $foo & ", bar=" & $bar

Quite nice output including a textual representation of the array:

	$ nim c -r tostring.nim 
	...
	My Stuff! foo=5, bar=['a', 'b']


	# interpolation.nim
	import strutils
	let animal = "cow"
	let beans = 4342088
	echo "Oh no! The $1 ate $2 beans!" % [animal, $beans]


	$ nim c -r interpolation
	...
	Oh no! The cow ate 4342088 beans!

By the way, when you include the `strutils` library, it brings

	Hint: system [Processing]
	Hint: interpolation [Processing]
	Hint: strutils [Processing]
	Hint: parseutils [Processing]
	Hint: math [Processing]
	Hint: algorithm [Processing]

But even though it all gets compiled in, it barely makes a
fast, compact, and dependency-free.


------------------------------------------------------------

On the subject of IO (particularly Input), I think it's highly
ncludes in the standard library.  Here's a couple that might

- `parseopt` - parser for short (POSIX-style) and long (GNU-style)
  command line options

- `parsecfg` - a superset of ".ini"-style config files with
  multi-line strings, etc.

- `parsexml` - simple XML parser - lenient enough to parse typical
  HTML

- `parsecsv` - CSV file parser

- `json` - JSON parser

- `rst` - reStructuredText parser

- `sexp` - S-expression parser

There is also a whole other section devoted to multiple libraries
for parsing and generating XML and HTML.

The other thing I love about the Nim standard library is that it
ncludes lower-level tools you can use to implement other parsers
library, Parsing Expression Grammars (PEGs) in `pegs` and
lexers/parsers in `lexbase`, `parseutils`, and `strscans`.  Those
are rich enough to warrant a post (or two or three) of their own.
But I doubt I want to be that thorough in this rambling phlog.

Rather than pick one of the parser libraries at random, I'm going
to demonstrate the one I used in real life just today to wrangle

The problem: I needed to extract a particular column from a CSV
file, perform a number of alterations to it, and then write it
back out (along with some other changes which are of no concern).
Let's pretend I just need to make one of the columns UPPER CASE.

First, our data file: (I've aligned the columns here so it's
easier to read.)

	Category, Acronym, Definition
	chat,     lol,     Laughing Out Loud
	chat,     brb,     Be Right Back
	sf,       tardis,  Time and Relative Dimensions In Space
	nasa,     sfc,     Specific Fuel Consumption
	nasa,     acrv,    Assured Crew Return Vehicle

Now our program:

	# acro.nim
	import os, parsecsv, strutils
	var csv: CsvParser
	csv.open(paramStr(1))
	csv.readHeaderRow()
	while csv.readRow():
	  echo "$1,$2,$3" % [
		csv.rowEntry("Category"),
		csv.rowEntry("Acronym").toUpper,
		csv.rowEntry("Definition")]
	csv.close

Now we cross our fingers and run to see if we were able to write a

	$ nim c -r acro.nim acronyms.csv
	...
	Category, Acronym, Definition
	chat,     LOL,     Laughing Out Loud
	chat,     BRB,     Be Right Back
	sf,       TARDIS,  Time and Relative Dimensions In Space
	nasa,     SFC,     Specific Fuel Consumption
	nasa,     ACRV,    Assured Crew Return Vehicle

Awesome!  The Acronym column has been converted to uppercase.

As I said, I actually did something very similar to this today.  I
the data.  But before I knew it, I was off in the weeds with an
AWK script that was getting towards twenty lines and I was
*fighting it*.

This is where you turn to your favorite "scripting" language such
as Python, Perl, or Ruby (I like all three).  But with Nim, you
can write the program just as quickly *and* end up with a fast,

Okay, this post is in danger of getting crazy long, but there is


Networking
------------------------------------------------------------

Nim's standard library includes libraries for CGI, an HTTP server,
an HTTP client, an SMTP client, an FTP client, and two levels of
applications right out of the box.

(Sorry, no Gopher libraries in the Nim standard library.)

Let's just see a really simple HTTP server example and call it a

	# trollserver.nim
	import asynchttpserver, asyncdispatch
	var troll = newAsyncHttpServer()
	proc handler(req: Request) {.async.} =
	  await req.respond(Http200, "Go use Gopher instead!\n")
	waitFor troll.serve(Port(8787), handler)

Now we compile and run:

	$ nim c -r trollserver

And our server is waiting for us.  In another terminal (or
full-fledged browser, for that matter) we can make a request:

	$ curl localhost:8787
	Go use Gopher instead!
	$ curl localhost:8787
	Go use Gopher instead!

What a ridiculous troll!  But you can see how easy it would be to
make a little web interface to serve up some data with Nim.

more focused.

Until the next one, happy Nim hacking!


AD: