#!/bin/awk -f # vim: ft=awk noet sts=4 ts=4 sw=4 BEGIN { tok_n = 0 # couter of tokens accumulated # token types T_TEXT = 1; type_names[1] = "text" T_EXPR = 2; type_names[2] = "expr" T_AWK = 3; type_names[3] = "awk" T_FUNC = 4; type_names[4] = "func" tok_type = T_TEXT # get current directory "pwd" | getline PWD close("pwd") # empty the depency file if ("AAT_DEP" in ENVIRON) { printf "" >ENVIRON["AAT_DEP"] } } # Append 'content' to array of tokens. Token type is taken from current value # of tok_type. It concatenates sequence of tokens of same type, unless # tok_finished is set. function token(content) { if(!content) return # concatenate tokens of same type if the previous one doesn't end in newline if((!tok_finished[tok_n]) && tok_n && tok_type == tok_types[tok_n]) { if(DEBUG) printf "concat \"%s\" \"%s\"\n", tok_contents[tok_n], content >"/dev/stderr" tok_contents[tok_n] = tok_contents[tok_n] content } else { tok_types[++tok_n] = tok_type tok_contents[tok_n] = content } if(DEBUG) printf "token %d (%s): \"%s\"\n", \ tok_n, type_names[tok_type], tok_contents[tok_n] >"/dev/stderr" } # Print file dependencies for makefile usage function print_dep(str) { if(str !~ /^\//) { str = PWD "/" str } if ("AAT_DEP" in ENVIRON) { print str >>ENVIRON["AAT_DEP"] } else { print str >>"/dev/stderr" } } function die(msg) { print msg >>"/dev/stderr" exit 1 } function sh_escape(str) { gsub(/["$\\]/, "\\&", str) return "\"" str "\"" } # find a file relative to current filename and overwrite that variable function find_file(name) { if(DEBUG) printf "find_file(\"%s\") PWD=\"%s\"\n", name, PWD >"/dev/stderr" # TODO: include search path if(name ~ /^\//) { # absolute path filename = name } else { # relative path if(match(filename, "/[^/]*$")) { filename = substr(filename, 1, RSTART) name } else { filename = name } } if(system("test -f " sh_escape(filename)) != 0) { if("MAKE_CMD" in ENVIRON) { if(system(ENVIRON["MAKE_CMD"] " " sh_escape(filename)) != 0) { die("could not build requested file: " sh_escape(filename) " (" name ") PWD: " PWD) } } else { die("could not find requested file: " sh_escape(filename) " (" name ") PWD: " PWD) } } print_dep(filename) } function macro_readinto(args, varname, fname) { varname = fname = args sub(/[ \t].*$/, "", varname) sub(/^[^ \t]+[ \t]+/, "", fname) find_file(fname) insert_comment("start @readinto "varname" "filename" {{{") tok_type=T_AWK while(getline "/dev/stderr" # store current filename so macros can change it, restore before function exits file_old = filename # Macro to recursively parse another template if(name == "include"){ find_file(args) insert_comment("start @include "filename" {{{") while(getline "/dev/stderr" # exit 1 # } # tok_type=T_AWK # token("V[\"" substr(args, 1, RSTART-1) "\"] = " \ # substr(args, RSTART+RLENGTH)) "\n" # # aat_process(substr(args, RSTART+RLENGTH)) "\n") # } # if with a query expression else if(name == "if"){ tok_type=T_AWK token("if(<" substr(args, RSTART+RLENGTH) ">) {\n") } # else if with a query expression else if(name == "elif"){ tok_type=T_AWK token("} else if(<" substr(args, RSTART+RLENGTH) ">) {\n") } # Leave the @ there for postprocessing with sed else { tok_type=T_AWK token("@" name " " args "\n") } filename = file_old } function parse_line(line) { # Handle linewise syntax if(tok_type == T_TEXT) { # if line starts with @@ or || it is actually an escape for having text # start with single @ or | respectively if(/^\(@@|\|\|\)/){ line=substr(line, 2) } # Lines starting with @ are macros. Some are handled in call_macro, # others currently by passing it through as awk code and postprocessing # with sed. else if(/^@/){ match(substr(line, 2), "[^ \t]+") call_macro( \ substr(line, 1+RSTART, RLENGTH), \ substr(line, 2+RSTART+RLENGTH) \ ) tok_type=T_TEXT return } # Lines starting with | are considered verbatim awk code else if(/^\|/) { tok_type=T_AWK token(substr(line, 2) "\n") tok_type=T_TEXT return } } # Handle text, with interleaved blocks for code and expressions while(length(line)) { if(DEBUG) printf "%d: \"%s\"\n", tok_n, line >"/dev/stderr" eat_nl = 0 if(tok_type == T_TEXT) { # all text until a start of expression "{{", or start of awk code "{%" m = match(line, /\{[{%<]/) if(m) { token(substr(line, 1, m-1)) if (substr(line, m, RLENGTH) == "{{") tok_type = T_EXPR else if (substr(line, m, RLENGTH) == "{%") tok_type = T_AWK else if (substr(line, m, RLENGTH) == "{<") tok_type = T_FUNC else { print "internal error" >"/dev/stderr"; exit 1 } line = substr(line, m+RLENGTH) } else { # no delimiter found, whole line is text token(line) line = "" } } else if(tok_type == T_FUNC) { m = match(line, />}/) if(m) { if(DEBUG) printf "expr match: \"%s\"\n", substr(line, m, RLENGTH) >"/dev/stderr" token(substr(line, 1, RSTART-1)) tok_finished[tok_n] = 1 line = substr(line, RSTART+RLENGTH) tok_type = T_TEXT } else { # did not match whole expression because of end of line token(line) line = "" } } else if(tok_type == T_EXPR || tok_type == T_AWK) { # match text inside awk code or expression # code stops on "%}" and expression on "}}" # misses few corner cases handled in the ifs below if(tok_type == T_EXPR) m = match(line, /^(}?([^}"]|("([^"]|\\")*")))+/) else m = match(line, /^([^%"]|(%+[^}%"])|("([^"]|\\")*"))+/) if(m) { if(DEBUG) printf "expr match: \"%s\"\n", substr(line, m, RLENGTH) >"/dev/stderr" token(substr(line, 1, RLENGTH)) line = substr(line, RLENGTH+1) } else if(length(line) == 1) { # did not match whole expression because of end of line token(line) line = "" } else if(match(line, /^%+$/)) { # did not match whole code, as the above regexp fails on sequence of "%" at EOL token(line) line = "" } else if( \ (tok_type == T_EXPR && substr(line, 1, 2) == "}}") || \ (tok_type == T_AWK && substr(line, 1, 2) == "%}") ) { # end of expression / code block if(tok_type == T_AWK) eat_nl = 1 tok_finished[tok_n] = 1 if(DEBUG) printf "finished (%s) \"%s\"\n", \ type_names[tok_types[tok_n]], tok_contents[tok_n] >"/dev/stderr" tok_type = T_TEXT line = substr(line, 3) } else { print "ERROR: could not parse line " NR ": " line >"/dev/stderr" exit 1 } } else { print "ERROR: unknown tok_type: " tok_type >"/dev/stderr" exit 1 } if(DEBUG) printf "-<%s>- \"%s\"\n", type_names[tok_type], line >"/dev/stderr" } # don't add newline just after the code block if(eat_nl) eat_nl = 0 else { token("\n") if(tok_type == T_TEXT) tok_finished[tok_n] = 1 # end text tokens on newlines, so we get nicer output } } # for every line in files in ARGV { # current filename being read filename = FILENAME # parse the line parse_line($0) } # print the output END { nl = 1 # are we on new line? for(tok_n=1; tok_types[tok_n]; tok_n++) { tok_type = tok_types[tok_n] c = tok_contents[tok_n] if(nl && tok_type != T_AWK) { printf "%s", "printf \"%s\"," nl = 0 } if(tok_type == T_TEXT) { linebreak = match(c, "\n$") gsub(/\\/, "\\\\", c) gsub(/"/, "\\\"", c) gsub(/\n/, "\\n", c) printf " \"%s\"%s", c, (linebreak ? "\n" : "") nl = linebreak } else if(tok_type == T_AWK) { printf "%s%s%s", (nl ? "" : "\n"), c, (c ~ /\n$/ ? "" : "\n") nl = 1 } else if(tok_type == T_EXPR) { printf " (%s)", c } else if(tok_type == T_FUNC) { # TODO printf " (<%s>)", c } else { print "ERROR: unknown tok_type: " tok_type >"/dev/stderr" exit 1 } } }