First, apologies for the long code sample below.
I am just beginning to learn nim. I am curious if I am using the language idiomatically and efficiently in the code below. This is based on reading keys and values from a file as in one of the examples, extended to load the keys, values into a hash table and then allowing the user to display keys, values, or a single key.
Specific questions:
Any other suggestions?
## read a word dictionary file containing words as keys, followed by a definition
##
## file specs:
## A term begins after a new line or the beginning of the file, followed by any amount
## of white space, then the string, and ends with a colon.
## A definition begins after the line break following the colon. It
## may be on the next line or after any number of blank lines.
## The definition continues across as many lines as desired. It
## ends at a newline that is followed by either the next key or
## the end of the file.
##
## The file of terms and definitions is read into a hashtable.
##
## Then, a command can be chosed from the prompt:
## keys - print all keys, one to a line
## all - print each key and its value
## values - print all values (without any keys)
## term - get prompted for a term to show its definition
## =term - show the definition of term
## q, quit, . - exit the program
import tables
import os
import strutils
var
cmdlist: array[0..6, string]
minidict: TTable[string, string]
fhandle: TFile
cmdin: string
termin: string
fname: string
txt: string
term: string = ""
markercol: int
cmdlist = ["keys", "all", "values", "term", "q", "quit", "."]
# access the file -- I guess this is the nim idiom...
if paramCount() < 1:
echo "Enter name of file containing your mini dictionary: "
fname = readLine(stdin)
else:
fname = ParamStr(1)
# trap the file "not found" error using Python idiom
try:
fhandle = open(fname, fmRead)
except EIO:
quit("File " & fname & " could not be opened. Exiting.")
# read file lines into the dictionary table
minidict = initTable[string, string]()
for ln in fhandle.lines:
txt = ln.strip(leading=true)
markercol = txt.find(":")
if markercol > 1 and markercol + 1 == txt.len:
markercol.dec
term = txt[..markercol]
minidict[term] = ""
else:
txt = txt.strip(trailing=true)
if len(txt) > 0:
minidict[term] = minidict[term] & txt & "\n"
# display the menu and dispatch commands
cmdin = ""
while true:
echo ""
echo "=============================="
echo "Enter command:"
echo " keys -- show the terms"
echo " values -- show the definitions"
echo " all -- show everything"
echo " term -- prompt for a term (or enter = and the term)"
write(stdout, " quit -- q, quit, or . ? ")
cmdin = readLine(stdin)
case cmdin
of "keys", "k":
echo ""
for k in minidict.keys:
echo k
of "values", "v":
echo ""
for v in minidict.values:
echo v
of "all", "a":
echo ""
for k,v in minidict.pairs:
echo k
echo v
of "term", "t":
echo "enter one term to see its definition (or . to stop): "
termin = readline(stdin)
if termin == ".": # this works using double quotes
discard
elif minidict.haskey(termin):
echo ""
echo termin
echo minidict[termin]
else:
echo ""
echo "Choose another term..."
of "quit", "q", ".":
quit("Bye")
else: # this is an ugly way to match on the first char of cmdin
if cmdin[0] == '=': # request for a term, must use single quotes
termin = cmdin[1 .. -1]
termin = termin.strip
if minidict.haskey(termin):
echo ""
echo termin
echo minidict[termin]
else:
echo ""
echo "Choose another term..."
else:
echo "Enter a valid command..."
# alternative way to loop over lines from a file
# for line in lines(fname):
# if line[end]
# this approach doesn't work
# fhandle = open(fname, fmRead)
# if fhandle == nil: quit("File " & fname & " could not be opened. Exiting.")
Checking for nil in a TFile only means you have not assigned to it before, I don't think system.open will ever return you nil, that's why it throws exceptions on error, to prevent you from continuing with a nil TFile.
I would put the single character test before the case with a continue to skip the rest to avoid nesting, but you have to do it in any case.
The difference with single and double quotes coming from python is that python doesn't have the concept of a character, even when you try to get a single letter python throws a full string at you:
chr(i) -> character
Return a string of one character with ordinal i; 0 <= i < 256.
Same thing when you try to access a single letter in a string:
In python there is no concept of a C char type and you can use single or double quotes anywhere because they will mean the same thing.
Thanks. Got it on string v. char.
On the if handle == nil issue, I think you can do handle = newFileStream("afile", fmRead) and test handle for nil without an error. But, open seems more obvious and the attempt to open naturally fails if the file doesn't exist or is otherwise inaccessible.
Personally, I don't find the usage of the 'else' in the case statement ugly - it helps distinguish the fact that the section in the 'else' clause does something different than the other sections.
Other than that, I can't find anything non-idiomatic that hasn't already been answered by gradha.
Anyway, that's a really great program you've written - is it ok if I use it for examples (and/or the official documentation)? It demonstrates many of Nim's basic features and is pretty clear/well written.
lewisl: But, open seems more obvious and the attempt to open naturally fails if the file doesn't exist or is otherwise inaccessible.
You can also use the following version of open:
if open(fhandle, fname, fmRead):
...
@Varriount: Sure, use it. Nothing special to it. I thought somehow I'd use the cmdlist array (would have in Python) to dispatch commands, but saw no beneficial way to do that. I took it out later.
@Jehan: would the 3 arg form of open allow testing the file handle variable for nil? Seems like there is something special about the newFileStream proc. Nothing wrong with trapping with a try/except--just didn't see it in any of the examples.
@Varriout: below is a simple test file for the script. The only real restriction is that a term must end at a colon and the definition must follow after at least one new line (and arbitrary white space). A trickier version would allow short definitions on the same line, but I was more interested in learning nim than being smart about parsing. Not much testing...
trapezoid: A geometric shape with 4 sides. Two opposite sides are parallel of unequal length. These sides are connected by lines that have the same angle at the 2 corners where they join either of the parallel sides.
house: A building or structure that is intended to provide a place for 1 or more persons to live. When occupied and modified for specific people a house is often referred to as a home.
brick: a rectangular prism shaped object, typically made of a hard dense material ball: a spherical object typically used as a toy or as part of a sport
drawing: a picture created by a person using soft material that rubs off on the paper to make lines undefined: new: not old (ok, that is a bad definition) hard returns: typically a line feed character at the end of a line that will cause the next character following it to appear on the next line. On Unix systems only one character is needed: line feed. On Windows, a pair of characters is used: carriage return followed by a line feed.