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!