Learning Nim - Argument Parsing
Table of Contents
1. What is Nim
Nim is a statically typed language with python-like syntax and the ability to add metaprogramming. It was designed to used for systems and embedded programming. It can be directly compiled to c, c++, objective-c, or javascript depending on the use case.
From the cyber security side of things, nim has had a number of offensive application interest in it. Mostly due to the ease of programming at the systems level, unfamiliarity of the compiled programs it generates with AV, the inherent "obfuscation" of windows api calls, and that reverse engineering tools /plugins arent readily built to handle the nim compiled binaries.
2. Overview
Coming from a strong python and minor c, it feel familiar and definitely has its quirks, like argument parsing. I didn't find a lot of great sources on argument parsing, and the exmamples I did find, while would work, felt lacking in the explanation of what was going on and why thing were named the way they were.
Hopefully, today (or whenever you are reading this) you will be a little more understanding of argument parsing in nim. Additionally, please note, this isn't meant to be the guide on production system argument parsing in nim. Just a cyber security guy trying to write quick programs, if I find a new, safer or more robust, way of parsing later, I'll hopefully edit this with a link.
Also, reading this will have code snipets that explain the code in the source block below. To get the whole gist, click here.
3. Brief Basic Argument Parsing
Some overview notes:
- see documentation page for slightly more details: https://nim-lang.org/docs/parseopt.html
- each argument token has a kind, a key, and val.
- kind being the type of argument, cmdEnd signaling the end of the command line
- key being the name
- val being the key's value provided or empty string
- in nim, argument values must be passed with
=
or:
3.1. Creating & Setting Variables First
First I added variables that the command line arguments would change or add. In this case, I only have portNum, which will specify the port number to use when listening for connections.
We use a variable here because the immutable types aren't compatible with a default value that changes in or out of the loop. immutable let
with no assignment other than the argument value itself should be fine.
var portNum: int = 5454
3.2. Iterating Through the Arguments
Using the proc getopt, we iterate through the command line arguments, returning a tuple. This tuple has the conents of the kind, key, and value of the command line argument currently present in the interation.
for kind, key, value in getopt(): # we do this because it is a tuple of three returned
3.3. Case Switching Kind of Argument
Kind (CmdLineKind) is the type of argument present:
- cmdArgument
- cmdLongOption
- cmdShortOption
- cmdEnd
The first case switch dictates how we process the argument (since cmdArgument doesn't have a value). If we encounter this we could nest another case switch under that checks a counter of cmdArguments (maybe with an int.inc) that would assign variables based on the position, thus being a positional argument.
The other case switches are based on the value of kind (the list of types above). These can be processed further with keys and values.
case kind of cmdArgument: of cmdLongOption, cmdShortOption: of cmdEnd:
3.4. Case Switching Key & Value
With cmdLongOption
and cmdShortOption
, we are also passed a key and value. To pass this it expects the -
hyphen. So to have the key "p", like in the example below with a value of 5000. The command line argument would look something like -p=5000
or -p:5000
but NOT like -p 5000
.
In both of the first two statements (-p=5000
and -p:5000
):
- the kind of argument is: cmdShortOption
- the key of the argument is just: p
- the value of the argument is: 5000
Note, the value passed may be an empty string ""
so check for the value being empty and handle appropriately.
of cmdLongOption, cmdShortOption: case key of "p": discard parseInt(value, portNum, 0)
3.5. Case Switching cmdEnd
In this case, since we are using getopt
we don't need to check for kind =
cmdEnd= because getopt
handles that for us. I just discard
here to show one method of potentially handling with another way (not using getopt).
of cmdEnd: discard
4. Putting it all Together
This is putting all of the information above along with some comments, and the discard to handle the cmdArgument kind (if needed for your program).
Note: The gist has syntax highlighting for easier reading.
# default variables that could be modified from cmd arguments (if needed /desired) var portNum: int = 5454 # CLI Arguments for the Server # in nim argument values must be passed with = or : # Brief Basic Arugment Parsing in Nim, probably not for production arg parsing # see documentation page for slightly more details: https://nim-lang.org/docs/parseopt.html # each argument token has a kind, a key, and val. # ... kind being the type of argument, cmdEnd signaling the end of the command line # ... key being the name # ... val being the key's value provided or empty string # getopt: is for iterating over the arguments with it returning the tuple with kind, key, val # ... https://nim-lang.org/docs/parseopt.html#getopt for kind, key, value in getopt(): # we do this because it is a tuple of three returned # kind (CmdLineKind): this is the type of argument seen, cmdArgument, cmdLongOption, or cmdShortOption # our first case switch because it dictates how we process the argument (since cmdArgument doesnt have a value) case kind # cmdArgument is without the "-" in the arguments, # like nim c ./file.nim, the c is a cmdArgument of cmdArgument: # echo "Got arg with key: ", key discard #(should discard this iteration of the loop and move to the next one) # cmdLongOption, cmdShortOption: of cmdLongOption, cmdShortOption: case key # -p is to specify port number as in -p=443 or -p:443 but not -p 443 of "p": # echo "Got arg 'p' with value: ", value # change the port value string, to an int & store in portNum variable defined above discard parseInt(value, portNum, 0) # getopt tells us this is not needed to check for (but may for other procs with arguments parsing) of cmdEnd: discard # we shouldn't need to discard this and move on to after the loop now