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

Date: 2022-12-05

Author: Russell Brinson

Validate