Unfortunately, yes. My hope was that it would be compact/small enough to be "quasi-self-documenting" to the likes of "compiler writer types". Probably not to ordinary users. (The extra asterisks are to get nice ANSI SGR escape highlighting and/or rST markup out of the auto-documentation system.) {EDIT2: Also, I would be happy to add some rp.README if you want to contribute one. You seem a great explainer. }
FWIW, I think of this kind of generation as part of the Go mentality more generally, but I am not a Go user/in that community. So, maybe that is speaking out of turn.
I also have a C version of this that I call `crp` I could provide if anyone wants (and yes, short for "crap"). C is a kind of higher ceremony language for such things { EDIT1: but even more established than awk... :-) }
import std/[strutils,os,hashes,sets],cligen/[osUt,mslice] #% exec* mdOpen split
from cligen/parseopt3 import optionNormalize
proc toDef(fields, delim, genF: string): string =
result.add "char const * const rpNmFields = \"" & fields & "\";\n"
let sep = initSep(delim)
let row = fields.toMSlice
var s: seq[MSlice]
var nms: HashSet[string]
sep.split(row, s) # No maxSplit - define every field; Could infer it from the
for j, f in s: #..highest referenced field with a `where` & `stmts` parse.
let nm = optionNormalize(genF % [ $f ]) # Prevent duplicate def errors..
if nm notin nms: #..and warn users about collision.
result.add "int const " & nm & " = " & $j & ";\n"
nms.incl nm
else:
stderr.write "crp: WARNING: ", nm, " collides with earlier field\n"
proc crp(prelude="", begin="", where="1", stmts:seq[string], epilog="",
fields="", genF="$1", comp="", run=true, args="", outp="/tmp/crpXXX",
input="/dev/stdin", delim=" \t", uncheck=false, maxSplit=0): int =
## Gen+Run *prelude*,*fields*,*begin*,*where*,*stmts*,*epilog* row processor
## against *input*. Defined within *where* & every *stmt* are:
## *s[idx]* & *row* => C strings, *i(idx)* => int64, *f(idx)* => double.
## *nf* & *nr* (like *AWK*); NOTE: *idx* is **0-origin**.
## A generated program is left at *outp*.c, easily copied for "utilitizing".
## If you know *AWK* & C, you can learn *crp* PRONTO. Examples (need data):
## **crp 'printf("%s %s\\n", s[1], s[0]);'** # Swap field order
## **crp -w'nr % 100==0' 'printf("%s\\n", row);'** # Prn each 100th row
## **crp -b'int t=0' t+=nf -e'printf("%d\\n", t)'** # Prn total field count
## **crp -b'int t=0' -w'i(0)>0' 't+=i(0)' -e'printf("%d\\n", t)'** # Total>0
## **crp 'float x=f(0)' 'printf("%g\\n", (1+x)/x)'** # cache field 0 parse
## **crp -d, -fa,b,c 'printf("%s %g\\n",s[a],f(b)+i(c))'** # named fields
## Add niceties (eg. `#include "mystuff.h"`) to *prelude* in ~/.config/crp.
let fields = if fields.len == 0: fields else: toDef(fields, delim, genF)
let check = if fields.len == 0: " " elif not uncheck: """
if (nr == 0) {
if (strcmp(row, rpNmFields) == 0) {
nr++; continue; // {fields} {!uncheck}
} else {
exit(2);
}
while ((rpNmRead = getline(&row, &rpNmAlloc, rpNmFile)) != -1) {
row[rpNmRead - 1] = '\0'; // chop newline
${6}s = rpNmSplit(s, &rpNmAlloc, row, "$3", $7, &nf); // {delim,maxSplit}
if ($8) { // {where} auto ()s?
""" % [prelude, fields, delim, indent(begin, 2), input, check, $maxSplit, where]
for i, stmt in stmts:
program.add " " & stmt & "; // {stmt" & $i & "}\n"
if stmts.len == 0:
program.add " /**/;\n"
program.add " }\n nr++;\n }\n"
program.add indent(epilog, 2)
program.add "; // {epilogue}\n}\n"
let mode = if run: "-run" else: ""
let args = if args.len > 0: args else: "-I$HOME/s -O"
let digs = count(outp, 'X')
let hsh = toHex(program.hash and ((1 shl 16*digs) - 1), digs)
let outp = if digs > 0: outp[0 ..< ^digs] & hsh else: outp
let comp = if comp.len > 0: comp else: "tcc $1 $2 -o$3 $4" % [
mode, args, outp, outp & ".c"]
let f = mkdirOpen(outp & ".c", fmWrite)
f.write program
f.close
execShellCmd(comp & (if run: " < " & input else: ""))
when isMainModule:
import cligen; include cligen/mergeCfgEnv
dispatch crp,help={"prelude" : "Nim code for prelude/imports section",
"begin" : "Nim code for begin/pre-loop section",
"where" : "Nim code for row inclusion",
"stmts" : "Nim stmts to run under `where`",
"epilog" : "Nim code for epilog/end loop section",
"fields" : "`delim`-sep field names (match row0)",
"genF" : "make field names from this fmt; eg c$1",
"comp" : "\"\" => tcc {if run: \"-run\"} {args}",
"run" : "Run at once using tcc -run .. < input",
"args" : "\"\" => -I$HOME/s -O",
"outp" : "output executable; .c NOT REMOVED",
"input" : "path to read as input",
"delim" : "inp delim chars for strtok",
"uncheck" : "do not check&skip header row vs fields",
"maxSplit": "max split; 0 => unbounded"}, cmdName="crp"