In tutorials that the Della-Vos group publish, there is a rule that no example should go beyond one page. I tried to follow this rule in the tutorial about the Nim language. However, I am not entirely satisfied with the small web application described in chapter 7 of the tutorial, here:
https://github.com/FemtoEmacs/nimacros
You can also visit the dynamic web page, which deploys the tiny web application:
http://medicina.tips/nim/nimweb.n
Basically, you visit the page and read a few lines from Homer, and can type your name into the visitor field. The name is stored into the visitors.txt file. I added the name of Adilya Kotovskaya to the list of visitors both to pay a homage to the 92 years old mother of the telemedicine and to test the utf-8 coding. In fact, it was mainly to test the utf-8 coding. As for the program, it reads a markdown file line by line and translates titles and line breaks to html. I only implemented title tags, since it is a sufficient proof of concept. The problem is that I am not familiar with pattern matching in Nim, therefore the program became much more complex than it would be in Haskell or Lisp. Here is a somewhat simplified Haskell program:
import System.Environment
conv [] = "<p/>"
conv ('#':'#':title) = "<h2>"++title++"</h2>"
conv ('#':title) = "<h1>"++title++"</h1>"
conv x = x++"<br/>"
main = do
  [inFile] <- getArgs
  s <- readFile inFile
  let xs = lines s
  let html = [conv x | x <- xs]
  putStr (unlines html)
I would appreciate if somebody could suggest a way to use pattern match in Nim, in order to choose the right method for processing each line. I don't want to install any library, but restrict the examples to resources that come with Nim out of the box. Another thing I didn't like in my implementation is the use of split to remove the title prefix from the string:
for line in input.lines:
  if startsWith(line, "##"):
    echo "<h2>", line.split({'#'}).join(), "</h2>"
  elif startsWith(line, "#"):
    echo "<h1>", line.split({'#'}).join(), "</h1>"
  else: echo line, "<br/>"
This solution that I found in Rosetta stone is not elegant at all. Any suggestion for a better solution?
So originally I wanted to write a up a nice example to do the replacements via the scanf macro:
https://nim-lang.github.io/Nim/strscans.html
by defining tuples of strings to match against and their replacements, but I hit a dead end, because an element of a const tuple doesn't count as a static string for the pattern.
Also scanf turned out to be more problematic than I thought, because the $* term does not like to match any string until the end.
But since your book is (at least partly) about Nim macros and writing macros is fun, I built the following even longer version of your code, haha. It also includes a custom matcher that matches anything until the end of the string.
# File: web.nim
import strutils, os, strscans, macros
let input = open("rage.md")
let form = """
<p> <form name='form' method='get'>
       <input type='type='text' name='Name'>
    </form>
</p>
  """
echo "Content-type: text/html\n\n<html>"
echo """
  <head>
    <meta http-equiv= 'Content-type'
      content= 'text/html; charset=utf-8' />
  </head>
"""
echo "<body>"
proc rest(input: string; match: var string, start: int): int =
  ## matches until the end of the string
  match = input[start .. input.high]
  # result is either 1 (string is empty) or the number of found chars
  result = max(1, input.len - start)
macro match(args, line: typed): untyped =
  ## match the `args` via `scanf` in `line`. `args` must be a `[]` of
  ## `(scanf string matcher, replacement string)` tuples, where the latter
  ## has to include a single `$#` to indicate the position of the replacement.
  ## The order of the `args` is important, since an if statement is built.
  let argImpl = args.getImpl
  expectKind argImpl, nnkBracket
  result = newStmtList()
  let matched = genSym(nskVar, "matched")
  result.add quote do:
    var `matched`: string
  var ifStmt = nnkIfStmt.newTree()
  for el in argImpl:
    expectKind el, nnkTupleConstr
    let toMatch = el[0]
    let toReplace = el[1]
    let ifBody = nnkStmtList.newTree(nnkCall.newTree(ident"echo",
                                                     nnkCall.newTree(ident"%",
                                                                     toReplace,
                                                                     matched)),
                                     nnkAsgn.newTree(matched, newLit("")))
    let ifCond = nnkCall.newTree(ident"scanf", line, toMatch, matched)
    ifStmt.add nnkElifBranch.newTree(ifCond, ifBody)
  result.add ifStmt
  echo result.repr
const h1title = ("# ${rest}", "<h2>$#</h2>")
const h2title = ("## ${rest}", "<h1>$#</h1>")
const elseLine = ("${rest}", "$#<br/>")
const replacements = [h1title, h2title, elseLine]
for line in input.lines:
  match(replacements, line)
  # produces:
  # var matched: string
  # if scanf("# ${rest}", line, matched):
  #   echo h1title[1] % matched
  # if scanf("## ${rest}", line, matched):
  #   echo h2title[1] % matched
  # if scanf("${rest}", line, matched):
  #   echo elseLine[1] % matched
echo form
let qs = getEnv("QUERY_STRING", "none").split({'+'}).join(" ")
if qs != "none" and qs.len > 0:
  let output = open("visitors.txt", fmAppend)
  write(output, qs&"\n")
  output.close
let inputVisitors= open("visitors.txt")
for line in inputVisitors.lines:
  match(replacements, line)
inputVisitors.close
echo "</body></html>"
input.close
This is totally not practicle I'd say and one's better off writing something by hand or using the excellent https://github.com/zevv/npeg by @zevv.
Still fun though. And if someone wants to improve on this...
Finally, to just remove a prefix of a string, you may just use removePrefix from strutils:
https://nim-lang.github.io/Nim/strutils.html#removePrefix%2Cstring%2Cstring
Note that it only works inplace on string. You could use the new outplace though:
Hi, Vindaar. I will use the solution you pointed out in the tutorial. By the way, if you visit the demo page, you will see that I already replaced the inicial version of the program with another one, which is based on the scanf. I also used the decode.Url procedure in order to test the utf-8 encoding.
I still have a few questions, where I will need your help again.
Good to hear! I couldn't find the source for the new version of the live demo though. In the markdown document it's still the old code as far as I can tell.
Yes, please just ask!