switch to new Google Translate API * fix #45

####################################################################
# Translate.awk                                                    #
####################################################################

# Detect external audio player (mplayer, mpv, mpg123).
function initAudioPlayer() {
    AudioPlayer = !system("mplayer >/dev/null 2>/dev/null") ?
        "mplayer" :
        (!system("mpv >/dev/null 2>/dev/null") ?
         "mpv" :
         (!system("mpg123 >/dev/null 2>/dev/null") ?
          "mpg123" :
          ""))
}

# Detect external speech synthesizer (say, espeak).
function initSpeechSynthesizer() {
    SpeechSynthesizer = !system("say '' >/dev/null 2>/dev/null") ?
        "say" :
        (!system("espeak '' >/dev/null 2>/dev/null") ?
         "espeak" :
         "")
}

# Initialize `HttpService`.
function initHttpService() {
    HttpProtocol = "http://"
    HttpHost = "translate.google.com"
    HttpPort = 80
    if (Option["proxy"]) {
        match(Option["proxy"], /^(http:\/*)?([^\/]*):([^\/:]*)/, HttpProxySpec)
        HttpService = "/inet/tcp/0/" HttpProxySpec[2] "/" HttpProxySpec[3]
        HttpPathPrefix = HttpProtocol HttpHost
    } else {
        HttpService = "/inet/tcp/0/" HttpHost "/" HttpPort
        HttpPathPrefix = ""
    }
}

# Pre-process string (URL-encode before send).
function preprocess(text) {
    return quote(text)
}

# Post-process string (remove any redundant whitespace).
function postprocess(text) {
    text = gensub(/ ([.,;:?!"])/, "\\1", "g", text)
    text = gensub(/(["]) /, "\\1", "g", text)
    return text
}

# Send an HTTP request and get response from Google Translate.
function getResponse(text, sl, tl, hl,    content, url) {
    url = HttpPathPrefix "/translate_a/single?client=t"                 \
        "&ie=UTF-8&oe=UTF-8"                                            \
        "&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&dt=t&dt=at"  \
        "&q=" preprocess(text) "&sl=" sl "&tl=" tl "&hl=" hl

    print "GET " url " HTTP/1.1\n"             \
          "Host: " HttpHost "\n"               \
          "Connection: close\n" |& HttpService
    while ((HttpService |& getline) > 0)
        content = $0
    close(HttpService)

    return assert(content, "[ERROR] Null response.")
}

# Play using Google Text-to-Speech engine.
function play(text, tl,    url) {
    url = HttpProtocol HttpHost "/translate_tts?ie=UTF-8"       \
        "&tl=" tl "&q=" preprocess(text)

    # Don't use getline from pipe here - the same pipe will be run only once for each AWK script!
    system(Option["player"] " '" url "' >/dev/null 2>/dev/null")
}

# Get the translation of a string.
function getTranslation(text, sl, tl, hl,
                        isVerbose, toSpeech, returnPlaylist,
                        ####
                        altTranslations, article, ast, content,
                        explanation, group, i, il, ils,
                        isPhonetic, j, original, phonetics,
                        r, rtl, saveSortedIn, segments,
                        temp, tokens, translation, translations,
                        word, words, wordClasses) {
    isPhonetic = match(tl, /^@/)
    tl = substr(tl, 1 + isPhonetic)

    if (!getCode(tl)) {
        # Check if target language is supported
        w("[WARNING] Unknown target language code: " tl)
    } else if (getCode(tl) != "auto" && rtl = Locale[getCode(tl)]["rtl"]) {
        # Check if target language is RTL
        if (!FriBidi)
            w("[WARNING] " Locale[getCode(tl)]["name"] " is a right-to-left language, but GNU FriBidi is not found on your system.\nText might be displayed incorrectly.")
    }

    content = getResponse(text, sl, tl, hl)

    plTokenize(tokens, content)
    plParse(ast, tokens)

    if (!anything(ast)) {
        e("[ERROR] Oops! Something went wrong and I can't translate it for you :(")
        ExitCode = 1
    }

    # Debug mode
    if (Option["debug"]) {
        d(content)
        da(tokens, "tokens[%s]='%s'")
        da(ast, "ast[%s]='%s'")
    }

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in ast) {
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "0$")
            append(translations, postprocess(literal(ast[i])))
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "1$")
            append(original, literal(ast[i]))
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "2$")
            append(phonetics, literal(ast[i]))

        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            wordClasses[group[1]] = literal(ast[i])

        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "([[:digit:]]+)$", group))
            words[group[1]][group[2]][group[3]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)$", group))
            words[group[1]][group[2]]["1"][group[3]] = literal(ast[i])

        if (match(i, "^0" SUBSEP "5" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group)) {
            segments[group[1]] = literal(ast[i])
            altTranslations[group[1]][0] = ""
        }

        if (match(i, "^0" SUBSEP "5" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            altTranslations[group[1]][group[2]] = postprocess(literal(ast[i]))

        # Identified source languages
        if (i ~ "^0" SUBSEP "8" SUBSEP "0" SUBSEP "[[:digit:]]+$" ||
            i ~ "^0" SUBSEP "2$")
            append(ils, literal(ast[i]))
    }
    PROCINFO["sorted_in"] = saveSortedIn

    translation = join(translations)

    il = !anything(ils) || belongsTo(sl, ils) ? sl : ils[0]

    # Generate output
    if (!isVerbose) {
        # Brief mode

        r = isPhonetic && anything(phonetics) ?
            join(phonetics) :  # phonetic transcription
            s(translation, tl) # target language

        if (toSpeech) {
            returnPlaylist[0]["text"] = translation
            returnPlaylist[0]["tl"] = tl
        }

    } else {
        # Verbose mode

        r = AnsiCode["bold"] s(translation, tl) AnsiCode["no bold"] # target language
        if (anything(phonetics))
            r = r "\n" AnsiCode["bold"] join(phonetics) AnsiCode["no bold"] # phonetic transcription

        if (isarray(altTranslations[0]) && anything(altTranslations[0])) {
            # List alternative translations

            if (Locale[getCode(hl)]["rtl"] || Locale[getCode(il)]["rtl"])
                r = r "\n\n" s(sprintf(Locale[getCode(hl)]["message"], join(original))) # caution: mixed languages, BiDi invoked must be implemented correctly (i.e. FriBidi is required)
            else
                r = r "\n\n" sprintf(Locale[getCode(hl)]["message"], join(original))
            if (Locale[getCode(il)]["rtl"] || Locale[getCode(tl)]["rtl"])
                r = r "\n" s("(" Locale[getCode(il)]["endonym"] " ➔ " Locale[getCode(tl)]["endonym"] ")") # caution: mixed languages
            else
                r = r "\n" "(" Locale[getCode(il)]["endonym"] " ➔ " Locale[getCode(tl)]["endonym"] ")"

            temp = segments[0] "(" join(altTranslations[0], "/") ")"
            for (i = 1; i < length(altTranslations); i++)
                temp = temp " " segments[i] "(" join(altTranslations[i], "/") ")"
            if (Locale[getCode(il)]["rtl"] || Locale[getCode(tl)]["rtl"])
                r = r "\n" AnsiCode["bold"] s(temp) AnsiCode["no bold"] # caution: mixed languages
            else
                r = r "\n" AnsiCode["bold"] temp AnsiCode["no bold"]
        }

        if (isarray(wordClasses) && anything(wordClasses)) {
            # List dictionary entries

            for (i = 0; i < length(words); i++) {
                r = r "\n\n" s("[" wordClasses[i] "]", hl) # home language
                for (j = 0; j < length(words[i]); j++) {
                    word = words[i][j][0]
                    explanation = join(words[i][j][1], ", ")
                    article = words[i][j][4]

                    if (rtl) {
                        r = r "\n" AnsiCode["bold"] sprintf("%" Option["width"] - 4 "s", s((article ?
                                                                                "(" article ")" :
                                                                                "") " " word, tl, Option["width"] - 4)) AnsiCode["no bold"] # target language
                        r = r "\n" s(explanation, il, Option["width"] - 8) # identified source language
                    } else {
                        r = r "\n" "    " AnsiCode["bold"] show((article ?
                                                                 "(" article ") " :
                                                                 "") word, tl) AnsiCode["no bold"] # target language
                        r = r "\n" "        " s(explanation, il, Option["width"] - 8) # identified source language
                    }
                }
            }
        }

        if (toSpeech) {
            if (index(Locale[getCode(hl)]["message"], "%s") > 2) {
                returnPlaylist[0]["text"] = sprintf(Locale[getCode(hl)]["message"], "")
                returnPlaylist[0]["tl"] = hl
                returnPlaylist[1]["text"] = join(original)
                returnPlaylist[1]["tl"] = il
            } else {
                returnPlaylist[0]["text"] = join(original)
                returnPlaylist[0]["tl"] = il
                returnPlaylist[1]["text"] = sprintf(Locale[getCode(hl)]["message"], "")
                returnPlaylist[1]["tl"] = hl
            }
            returnPlaylist[2]["text"] = translation
            returnPlaylist[2]["tl"] = tl
        }
    }

    return r
}

# Translate a file.
function fileTranslation(uri,    group, temp1, temp2) {
    temp1 = Option["input"]
    temp2 = Option["verbose"]

    match(uri, /^file:\/\/(.*)/, group)
    Option["input"] = group[1]
    Option["verbose"] = 0

    translateMain()

    Option["input"] = temp1
    Option["verbose"] = temp2
}

# Start a browser session and translate a web page.
function webTranslation(uri, sl, tl, hl) {
    system(Option["browser"] " " parameterize("https://translate.google.com/translate?" \
                                              "hl=" hl "&sl=" sl "&tl=" tl "&u=" uri) "&")
}

# Translate the source text (into all target languages).
function translate(text, inline,
                   ####
                   i, j, playlist, saveSortedIn) {

    if (!getCode(Option["hl"])) {
        # Check if home language is supported
        w("[WARNING] Unknown language code: " Option["hl"] ", fallback to English: en")
        Option["hl"] = "en" # fallback to English
    } else if (getCode(Option["hl"]) != "auto" && Locale[getCode(Option["hl"])]["rtl"]) {
        # Check if home language is RTL
        if (!FriBidi)
            w("[WARNING] " Locale[getCode(Option["hl"])]["name"] " is a right-to-left language, but GNU FriBidi is not found on your system.\nText might be displayed incorrectly.")
    }

    if (!getCode(Option["sl"])) {
        # Check if source language is supported
        w("[WARNING] Unknown source language code: " Option["sl"])
    } else if (getCode(Option["sl"]) != "auto" && Locale[getCode(Option["sl"])]["rtl"]) {
        # Check if source language is RTL
        if (!FriBidi)
            w("[WARNING] " Locale[getCode(Option["sl"])]["name"] " is a right-to-left language, but GNU FriBidi is not found on your system.\nText might be displayed incorrectly.")
    }

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in Option["tl"]) {
        # Non-interactive verbose mode: separator between targets
        if (!Option["interactive"])
            if (Option["verbose"] && i > 1)
                print replicate("─", Option["width"])

        if (inline &&
            startsWithAny(text, UriSchemes) == "file://") {
            fileTranslation(text)
        } else if (inline &&
                   startsWithAny(text, UriSchemes) == "http://" ||
                   startsWithAny(text, UriSchemes) == "https://") {
            webTranslation(text, Option["sl"], Option["tl"][i], Option["hl"])
        } else {
            print getTranslation(text, Option["sl"], Option["tl"][i], Option["hl"], Option["verbose"], Option["play"], playlist) > Option["output"]

            if (Option["play"])
                if (Option["player"])
                    for (j in playlist)
                        play(playlist[j]["text"], playlist[j]["tl"])
                else if (SpeechSynthesizer)
                    for (j in playlist)
                        print playlist[j]["text"] | SpeechSynthesizer
        }
    }
    PROCINFO["sorted_in"] = saveSortedIn
}

# Read from input and translate each line.
function translateMain(    i, line) {
    if (Option["interactive"])
        prompt()

    i = 0
    while (getline line < Option["input"]) {
        # Non-interactive verbose mode: separator between sources
        if (!Option["interactive"])
            if (Option["verbose"] && i++ > 0)
                print replicate("═", Option["width"])

        if (Option["interactive"]) {
            if (line ~ /:(q|quit)/)
                exit
            else if (line ~ /:(s|source)/)
                print Option["sl"]
            else if (line ~ /:(t|target)/) {
                printf "(" Option["tl"][1]
                for (i = 2; i <= length(Option["tl"]); i++)
                    printf ", " Option["tl"][i]
                print ")"
            }

            else {
                translate(line)

                # Interactive verbose mode: newline after each translation
                if (Option["verbose"]) printf "\n"
            }

            prompt()
        } else
            translate(line)
    }
}

####################################################################
# Translate.awk                                                    #
####################################################################

# Detect external audio player (mplayer, mpv, mpg123).
function initAudioPlayer() {
    AudioPlayer = !system("mplayer >/dev/null 2>/dev/null") ?
        "mplayer" :
        (!system("mpv >/dev/null 2>/dev/null") ?
         "mpv" :
         (!system("mpg123 >/dev/null 2>/dev/null") ?
          "mpg123" :
          ""))
}

# Detect external speech synthesizer (say, espeak).
function initSpeechSynthesizer() {
    SpeechSynthesizer = !system("say '' >/dev/null 2>/dev/null") ?
        "say" :
        (!system("espeak '' >/dev/null 2>/dev/null") ?
         "espeak" :
         "")
}

# Initialize `HttpService`.
function initHttpService() {
    HttpProtocol = "http://"
    HttpHost = "translate.google.com"
    HttpPort = 80
    if (Option["proxy"]) {
        match(Option["proxy"], /^(http:\/*)?([^\/]*):([^\/:]*)/, HttpProxySpec)
        HttpService = "/inet/tcp/0/" HttpProxySpec[2] "/" HttpProxySpec[3]
        HttpPathPrefix = HttpProtocol HttpHost
    } else {
        HttpService = "/inet/tcp/0/" HttpHost "/" HttpPort
        HttpPathPrefix = ""
    }
}

# Pre-process string (URL-encode before send).
function preprocess(text) {
    return quote(text)
}

# Post-process string (remove any redundant whitespace).
function postprocess(text) {
    text = gensub(/ ([.,;:?!"])/, "\\1", "g", text)
    text = gensub(/(["]) /, "\\1", "g", text)
    return text
}

# Send an HTTP request and get response from Google Translate.
function getResponse(text, sl, tl, hl,    content, url) {
    url = HttpPathPrefix "/translate_a/t?client=t"              \
        "&ie=UTF-8&oe=UTF-8"                                    \
        "&text=" preprocess(text) "&sl=" sl "&tl=" tl "&hl=" hl

    print "GET " url " HTTP/1.1\n"             \
          "Host: " HttpHost "\n"               \
          "Connection: close\n" |& HttpService
    while ((HttpService |& getline) > 0)
        content = $0
    close(HttpService)

    return assert(content, "[ERROR] Null response.")
}

# Play using Google Text-to-Speech engine.
function play(text, tl,    url) {
    url = HttpProtocol HttpHost "/translate_tts?ie=UTF-8"       \
        "&tl=" tl "&q=" preprocess(text)

    # Don't use getline from pipe here - the same pipe will be run only once for each AWK script!
    system(Option["player"] " '" url "' >/dev/null 2>/dev/null")
}

# Get the translation of a string.
function getTranslation(text, sl, tl, hl,
                        isVerbose, toSpeech, returnPlaylist,
                        ####
                        altTranslations, article, ast, content,
                        explanation, group, i, il, ils,
                        isPhonetic, j, original, phonetics,
                        r, rtl, saveSortedIn, segments,
                        temp, tokens, translation, translations,
                        word, words, wordClasses) {
    isPhonetic = match(tl, /^@/)
    tl = substr(tl, 1 + isPhonetic)

    if (!getCode(tl)) {
        # Check if target language is supported
        w("[WARNING] Unknown target language code: " tl)
    } else if (getCode(tl) != "auto" && rtl = Locale[getCode(tl)]["rtl"]) {
        # Check if target language is RTL
        if (!FriBidi)
            w("[WARNING] " Locale[getCode(tl)]["name"] " is a right-to-left language, but GNU FriBidi is not found on your system.\nText might be displayed incorrectly.")
    }

    content = getResponse(text, sl, tl, hl)

    plTokenize(tokens, content)
    plParse(ast, tokens)

    if (!anything(ast)) {
        e("[ERROR] Oops! Something went wrong and I can't translate it for you :(")
        ExitCode = 1
    }

    # Debug mode
    if (Option["debug"]) {
        d(sprintf("content='%s'", content))
        da(tokens, "tokens[%s]='%s'")
        da(ast, "ast[%s]='%s'")
    }

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in ast) {
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "0$")
            append(translations, postprocess(literal(ast[i])))
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "1$")
            append(original, literal(ast[i]))
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "2$")
            append(phonetics, literal(ast[i]))

        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            wordClasses[group[1]] = literal(ast[i])

        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "([[:digit:]]+)$", group))
            words[group[1]][group[2]][group[3]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)$", group))
            words[group[1]][group[2]]["1"][group[3]] = literal(ast[i])

        if (match(i, "^0" SUBSEP "5" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group)) {
            segments[group[1]] = literal(ast[i])
            altTranslations[group[1]][0] = ""
        }

        if (match(i, "^0" SUBSEP "5" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            altTranslations[group[1]][group[2]] = postprocess(literal(ast[i]))

        # Identified source languages
        if (i ~ "^0" SUBSEP "8" SUBSEP "0" SUBSEP "[[:digit:]]+$" ||
            i ~ "^0" SUBSEP "2$")
            append(ils, literal(ast[i]))
    }
    PROCINFO["sorted_in"] = saveSortedIn

    translation = join(translations)

    il = !anything(ils) || belongsTo(sl, ils) ? sl : ils[0]

    # Generate output
    if (!isVerbose) {
        # Brief mode

        r = isPhonetic && anything(phonetics) ?
            join(phonetics) :  # phonetic transcription
            s(translation, tl) # target language

        if (toSpeech) {
            returnPlaylist[0]["text"] = translation
            returnPlaylist[0]["tl"] = tl
        }

    } else {
        # Verbose mode

        r = AnsiCode["bold"] s(translation, tl) AnsiCode["no bold"] # target language
        if (anything(phonetics))
            r = r "\n" AnsiCode["bold"] join(phonetics) AnsiCode["no bold"] # phonetic transcription

        if (isarray(altTranslations[0]) && anything(altTranslations[0])) {
            # List alternative translations

            if (Locale[getCode(hl)]["rtl"] || Locale[getCode(il)]["rtl"])
                r = r "\n\n" s(sprintf(Locale[getCode(hl)]["message"], join(original))) # caution: mixed languages, BiDi invoked must be implemented correctly (i.e. FriBidi is required)
            else
                r = r "\n\n" sprintf(Locale[getCode(hl)]["message"], join(original))
            if (Locale[getCode(il)]["rtl"] || Locale[getCode(tl)]["rtl"])
                r = r "\n" s("(" Locale[getCode(il)]["endonym"] " ➔ " Locale[getCode(tl)]["endonym"] ")") # caution: mixed languages
            else
                r = r "\n" "(" Locale[getCode(il)]["endonym"] " ➔ " Locale[getCode(tl)]["endonym"] ")"

            temp = segments[0] "(" join(altTranslations[0], "/") ")"
            for (i = 1; i < length(altTranslations); i++)
                temp = temp " " segments[i] "(" join(altTranslations[i], "/") ")"
            if (Locale[getCode(il)]["rtl"] || Locale[getCode(tl)]["rtl"])
                r = r "\n" AnsiCode["bold"] s(temp) AnsiCode["no bold"] # caution: mixed languages
            else
                r = r "\n" AnsiCode["bold"] temp AnsiCode["no bold"]
        }

        if (isarray(wordClasses) && anything(wordClasses)) {
            # List dictionary entries

            for (i = 0; i < length(words); i++) {
                r = r "\n\n" s("[" wordClasses[i] "]", hl) # home language
                for (j = 0; j < length(words[i]); j++) {
                    word = words[i][j][0]
                    explanation = join(words[i][j][1], ", ")
                    article = words[i][j][4]

                    if (rtl) {
                        r = r "\n" AnsiCode["bold"] sprintf("%" Option["width"] - 4 "s", s((article ?
                                                                                "(" article ")" :
                                                                                "") " " word, tl, Option["width"] - 4)) AnsiCode["no bold"] # target language
                        r = r "\n" s(explanation, il, Option["width"] - 8) # identified source language
                    } else {
                        r = r "\n" "    " AnsiCode["bold"] show((article ?
                                                                 "(" article ") " :
                                                                 "") word, tl) AnsiCode["no bold"] # target language
                        r = r "\n" "        " s(explanation, il, Option["width"] - 8) # identified source language
                    }
                }
            }
        }

        if (toSpeech) {
            if (index(Locale[getCode(hl)]["message"], "%s") > 2) {
                returnPlaylist[0]["text"] = sprintf(Locale[getCode(hl)]["message"], "")
                returnPlaylist[0]["tl"] = hl
                returnPlaylist[1]["text"] = join(original)
                returnPlaylist[1]["tl"] = il
            } else {
                returnPlaylist[0]["text"] = join(original)
                returnPlaylist[0]["tl"] = il
                returnPlaylist[1]["text"] = sprintf(Locale[getCode(hl)]["message"], "")
                returnPlaylist[1]["tl"] = hl
            }
            returnPlaylist[2]["text"] = translation
            returnPlaylist[2]["tl"] = tl
        }
    }

    return r
}

# Translate a file.
function fileTranslation(uri,    group, temp1, temp2) {
    temp1 = Option["input"]
    temp2 = Option["verbose"]

    match(uri, /^file:\/\/(.*)/, group)
    Option["input"] = group[1]
    Option["verbose"] = 0

    translateMain()

    Option["input"] = temp1
    Option["verbose"] = temp2
}

# Start a browser session and translate a web page.
function webTranslation(uri, sl, tl, hl) {
    system(Option["browser"] " " parameterize("https://translate.google.com/translate?" \
                                              "hl=" hl "&sl=" sl "&tl=" tl "&u=" uri) "&")
}

# Translate the source text (into all target languages).
function translate(text, inline,
                   ####
                   i, j, playlist, saveSortedIn) {

    if (!getCode(Option["hl"])) {
        # Check if home language is supported
        w("[WARNING] Unknown language code: " Option["hl"] ", fallback to English: en")
        Option["hl"] = "en" # fallback to English
    } else if (getCode(Option["hl"]) != "auto" && Locale[getCode(Option["hl"])]["rtl"]) {
        # Check if home language is RTL
        if (!FriBidi)
            w("[WARNING] " Locale[getCode(Option["hl"])]["name"] " is a right-to-left language, but GNU FriBidi is not found on your system.\nText might be displayed incorrectly.")
    }

    if (!getCode(Option["sl"])) {
        # Check if source language is supported
        w("[WARNING] Unknown source language code: " Option["sl"])
    } else if (getCode(Option["sl"]) != "auto" && Locale[getCode(Option["sl"])]["rtl"]) {
        # Check if source language is RTL
        if (!FriBidi)
            w("[WARNING] " Locale[getCode(Option["sl"])]["name"] " is a right-to-left language, but GNU FriBidi is not found on your system.\nText might be displayed incorrectly.")
    }

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in Option["tl"]) {
        # Non-interactive verbose mode: separator between targets
        if (!Option["interactive"])
            if (Option["verbose"] && i > 1)
                print replicate("─", Option["width"])

        if (inline &&
            startsWithAny(text, UriSchemes) == "file://") {
            fileTranslation(text)
        } else if (inline &&
                   startsWithAny(text, UriSchemes) == "http://" ||
                   startsWithAny(text, UriSchemes) == "https://") {
            webTranslation(text, Option["sl"], Option["tl"][i], Option["hl"])
        } else {
            print getTranslation(text, Option["sl"], Option["tl"][i], Option["hl"], Option["verbose"], Option["play"], playlist) > Option["output"]

            if (Option["play"])
                if (Option["player"])
                    for (j in playlist)
                        play(playlist[j]["text"], playlist[j]["tl"])
                else if (SpeechSynthesizer)
                    for (j in playlist)
                        print playlist[j]["text"] | SpeechSynthesizer
        }
    }
    PROCINFO["sorted_in"] = saveSortedIn
}

# Read from input and translate each line.
function translateMain(    i, line) {
    if (Option["interactive"])
        prompt()

    i = 0
    while (getline line < Option["input"]) {
        # Non-interactive verbose mode: separator between sources
        if (!Option["interactive"])
            if (Option["verbose"] && i++ > 0)
                print replicate("═", Option["width"])

        if (Option["interactive"]) {
            if (line ~ /:(q|quit)/)
                exit
            else if (line ~ /:(s|source)/)
                print Option["sl"]
            else if (line ~ /:(t|target)/) {
                printf "(" Option["tl"][1]
                for (i = 2; i <= length(Option["tl"]); i++)
                    printf ", " Option["tl"][i]
                print ")"
            }

            else {
                translate(line)

                # Interactive verbose mode: newline after each translation
                if (Option["verbose"]) printf "\n"
            }

            prompt()
        } else
            translate(line)
    }
}


TestUtils: perform curl-related tests only if NETWORK_ACCESS is set to yes (#221)

@include "include/Utils"

BEGIN {
    START_TEST("Utils.awk")

    T("GawkVersion", 1)
    {
        initGawk()
        assertTrue(GawkVersion ~ "^4.")
    }

    T("Rlwrap", 1)
    {
        initRlwrap()
        assertEqual(Rlwrap, "rlwrap")
    }

    T("Emacs", 1)
    {
        initEmacs()
        assertEqual(Emacs, "emacs")
    }

    T("newerVersion()", 5)
    {
        assertTrue(newerVersion("0.9", "0.8"))
        assertTrue(newerVersion("0.9.0.1", "0.9.0"))
        assertTrue(newerVersion("1.0", "0.9.9999"))
        assertTrue(newerVersion("1.9.9999", "1.9.10"))
        assertTrue(newerVersion("2", "1.9.9999"))
    }

    if (yn(ENVIRON["NETWORK_ACCESS"])) { # if network access enabled
        T("curl()", 1)
        {
            delete tokens; delete ast
            tokenize(tokens, curl("https://httpbin.org/get"))
            parseJson(ast, tokens)
            assertEqual(unparameterize(ast[0 SUBSEP "url"]),
                        "https://httpbin.org/get")
        }

        T("curlPost()", 1)
        {
            delete tokens; delete ast
            tokenize(tokens, curlPost("https://httpbin.org/post", "fizz=buzz"))
            parseJson(ast, tokens)
            assertEqual(unparameterize(ast[0 SUBSEP "url"]),
                        "https://httpbin.org/post")
        }
    }

    T("dump()", 3)
    {
        delete group
        assertEqual(dump("a", group), 1)

        delete group
        assertEqual(dump("Århus", group), 6)

        delete group
        assertEqual(dump("안녕하세요 세계", group), 22)
    }

    T("base64()", 1)
    {
        assertEqual(base64("ninja"), "bmluamE=")
    }

    T("uprintf", 1)
    {
        assertEqual(uprintf("Ma\\u00f1ana"), "Mañana")
    }

    END_TEST()
}

@include "include/Utils"

BEGIN {
    START_TEST("Utils.awk")

    T("GawkVersion", 1)
    {
        initGawk()
        assertTrue(GawkVersion ~ "^4.")
    }

    T("Rlwrap", 1)
    {
        initRlwrap()
        assertEqual(Rlwrap, "rlwrap")
    }

    T("Emacs", 1)
    {
        initEmacs()
        assertEqual(Emacs, "emacs")
    }

    T("newerVersion()", 5)
    {
        assertTrue(newerVersion("0.9", "0.8"))
        assertTrue(newerVersion("0.9.0.1", "0.9.0"))
        assertTrue(newerVersion("1.0", "0.9.9999"))
        assertTrue(newerVersion("1.9.9999", "1.9.10"))
        assertTrue(newerVersion("2", "1.9.9999"))
    }

    T("curl()", 1)
    {
        delete tokens; delete ast
        tokenize(tokens, curl("https://httpbin.org/get"))
        parseJson(ast, tokens)
        assertEqual(unparameterize(ast[0 SUBSEP "url"]),
                    "https://httpbin.org/get")
    }

    T("curlPost()", 1)
    {
        delete tokens; delete ast
        tokenize(tokens, curlPost("https://httpbin.org/post", "fizz=buzz"))
        parseJson(ast, tokens)
        assertEqual(unparameterize(ast[0 SUBSEP "url"]),
                    "https://httpbin.org/post")
    }

    T("dump()", 3)
    {
        delete group
        assertEqual(dump("a", group), 1)

        delete group
        assertEqual(dump("Århus", group), 6)

        delete group
        assertEqual(dump("안녕하세요 세계", group), 22)
    }

    T("base64()", 1)
    {
        assertEqual(base64("ninja"), "bmluamE=")
    }

    T("uprintf", 1)
    {
        assertEqual(uprintf("Ma\\u00f1ana"), "Mañana")
    }

    END_TEST()
}


Handle movies rented on Amazon Prime Video

# Helper for converting links copied and pasted from a webpage
#
# Since there is no way to paste an embedded hyperlink into a text file,
# paste the link and the title on two consecutive lines, e.g.
#
# https://www.netflix.com/title/80988955
# Death in Paradise, S06E01, Erupting in Murder
#
# https://www.netflix.com/title/80174814
# Borderliner: Season 1: "Milla’s Future"
#
# https://www.netflix.com/title/80203122
# Luxury Travel Show: Season 1: "Chiang Mai & Eze"
#
# https://www.amazon.com/gp/video/detail/B07QBFM357/ref=stream_prime_hd_ep?autoplay=1&t=3128
# Silent Witness, S16E001, Change, Part One
#
# Paste them in the same order they are in the Netflix or Amazon "viewing activity" page
#
# INVOCATION:
#    awk -f generateLinkFrom-text.awk watched.txt

/ref=stream_prime/ {
    sub (/\/ref=stream.*/,"")
}

/ref=pd_/ {
    sub (/\/ref=pd_.*/,"")
}

/^https:/ {
    link = $0
    if ((getline title) > 0) {
        gsub (/"/,"\"\"",title)
        printf ("=HYPERLINK(\"%s\";\"%s\")\n",link,title)
    }
}

# Helper for converting links copied and pasted from a webpage
#
# Since there is no way to paste an embedded hyperlink into a text file,
# paste the link and the title on two consecutive lines, e.g.
#
# https://www.netflix.com/title/80988955
# Death in Paradise, S06E01, Erupting in Murder
#
# https://www.netflix.com/title/80174814
# Borderliner: Season 1: "Milla’s Future"
#
# https://www.netflix.com/title/80203122
# Luxury Travel Show: Season 1: "Chiang Mai & Eze"
#
# https://www.amazon.com/gp/video/detail/B07QBFM357/ref=stream_prime_hd_ep?autoplay=1&t=3128
# Silent Witness, S16E001, Change, Part One
#
# Paste them in the same order they are in the Netflix or Amazon "viewing activity" page
#
# INVOCATION:
#    awk -f generateLinkFrom-text.awk watched.txt

/ref=stream_prime/ {
    sub (/\/ref=stream.*/,"")
}

/^https:/ {
    link = $0
    if ((getline title) > 0) {
        gsub (/"/,"\"\"",title)
        printf ("=HYPERLINK(\"%s\";\"%s\")\n",link,title)
    }
}


Fix import modue for Tests due to the module rename (#33)

See https://github.com/apple/swift-protobuf/commit/6b1a0b0a270f31b93c3d4255d94f8d8f2f9e1ae0

#!/usr/bin/awk -f

# ProtobufRuntime/Sources/Protobuf/ProtobufBinaryEncoding.swift - Binary encoding support
#
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
#
# -----------------------------------------------------------------------------
#
# The Linux version of XCTest cannot automatically
# discover tests at run-time.  So you can either manually
# maintain lists of tests or collect them at build time.
#
# This script is used by the Makefile to do the latter:
#  = Scans each XCTest source file for 'class Foo: XCTestCase'
#  = Looks for 'func test*' within those classes
#  = Emits a class extension with the necessary 'allTests' property
#  = Emits an XCTMain invocation to run all the classes
#
# The macOS version of XCTest has another mechanism for
# finding tests at run-time, so this is not needed there.
#
# -----------------------------------------------------------------------------

BEGIN {
    CLASS=""
    TESTCASES=""
    TESTCASE_separator=""
    printf("import XCTest\n")
    printf("@testable import SwiftProtobufTests\n")
    printf("\n")
    printf("private func run_test(test:() -> ()) throws {\n")
    printf("    test()\n")
    printf("}\n")
    printf("\n")
    printf("private func run_test(test:() throws -> ()) throws {\n")
    printf("    try test()\n")
    printf("}\n")
    printf("\n")
    printf("\n")
}

/class .*:.* XCTestCase/ {
    if (CLASS != "") {
	printf("        ]\n")
	printf("    }\n")
	printf("}\n")
    }
    split($0, a, ":")
    split(a[1], words, " ")
    CLASS=words[2]
    TESTCASES = TESTCASES TESTCASE_separator "\n" "        (testCaseClass: " CLASS ".self, allTests: " CLASS ".allTests)"
    TESTCASE_separator = ","
    printf("\n")
    printf("extension %s {\n", CLASS)
    printf("    static var allTests: [(String, (XCTestCase) throws -> ())] {\n")
    printf("        return [")
    FUNC_separator=""
}

/ *func *test.*/ {
    split($0, a, "(")
    split(a[1], words, " ")
    FUNC=words[2]
    printf("")
    printf("%s\n            (\"%s\", {try run_test(test:($0 as! %s).%s)})", FUNC_separator, FUNC, CLASS, FUNC)
    FUNC_separator = ","
}

END {
    if (CLASS != "") {
	printf("\n        ]\n")
	printf("    }\n")
	printf("}\n")
    }
    printf("\n")

    printf("XCTMain(\n")
    printf("    [")
    printf(TESTCASES)
    printf("\n    ]\n)\n")
}

#!/usr/bin/awk -f

# ProtobufRuntime/Sources/Protobuf/ProtobufBinaryEncoding.swift - Binary encoding support
#
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
#
# -----------------------------------------------------------------------------
#
# The Linux version of XCTest cannot automatically
# discover tests at run-time.  So you can either manually
# maintain lists of tests or collect them at build time.
#
# This script is used by the Makefile to do the latter:
#  = Scans each XCTest source file for 'class Foo: XCTestCase'
#  = Looks for 'func test*' within those classes
#  = Emits a class extension with the necessary 'allTests' property
#  = Emits an XCTMain invocation to run all the classes
#
# The macOS version of XCTest has another mechanism for
# finding tests at run-time, so this is not needed there.
#
# -----------------------------------------------------------------------------

BEGIN {
    CLASS=""
    TESTCASES=""
    TESTCASE_separator=""
    printf("import XCTest\n")
    printf("@testable import ProtobufTests\n")
    printf("\n")
    printf("private func run_test(test:() -> ()) throws {\n")
    printf("    test()\n")
    printf("}\n")
    printf("\n")
    printf("private func run_test(test:() throws -> ()) throws {\n")
    printf("    try test()\n")
    printf("}\n")
    printf("\n")
    printf("\n")
}

/class .*:.* XCTestCase/ {
    if (CLASS != "") {
	printf("        ]\n")
	printf("    }\n")
	printf("}\n")
    }
    split($0, a, ":")
    split(a[1], words, " ")
    CLASS=words[2]
    TESTCASES = TESTCASES TESTCASE_separator "\n" "        (testCaseClass: " CLASS ".self, allTests: " CLASS ".allTests)"
    TESTCASE_separator = ","
    printf("\n")
    printf("extension %s {\n", CLASS)
    printf("    static var allTests: [(String, (XCTestCase) throws -> ())] {\n")
    printf("        return [")
    FUNC_separator=""
}

/ *func *test.*/ {
    split($0, a, "(")
    split(a[1], words, " ")
    FUNC=words[2]
    printf("")
    printf("%s\n            (\"%s\", {try run_test(test:($0 as! %s).%s)})", FUNC_separator, FUNC, CLASS, FUNC)
    FUNC_separator = ","
}

END {
    if (CLASS != "") {
	printf("\n        ]\n")
	printf("    }\n")
	printf("}\n")
    }
    printf("\n")

    printf("XCTMain(\n")
    printf("    [")
    printf(TESTCASES)
    printf("\n    ]\n)\n")
}


Update filter_en_uk.awk

Update filter_en_uk.awk

Typos

BEGIN {}
/^macro:[:alpha: ]+/ {
    macroLine=""
    # I check every field from the second one (the first one is "macro:")
    for(x=2;x<=NF;x++){
        do{
            # I found a keys being kept pressed or just released, I print it as it is
            # This works because (probably) I don't want to keep pressed keys that need to be translated such as symbols
            if(length($x)>1&&(substr($x,1,1)=="+"||substr($x,1,1)=="-")){macroLine=macroLine $x; break;}
            # An upper-case key, I need to press shift to do it. Using the !=toupper I avoid triggering things like ","==tolower(",") and ","==toupper(",")
            if($x!=tolower($x)){macroLine=macroLine "+lshift," "+" tolower($x) ",-" tolower($x) ",-lshift"; break;}
            if($x=="`"){ macroLine=macroLine "+grave,-grave"; break;}
            if($x=="-"){ macroLine=macroLine "+minus,-minus"; break;}
            if($x=="["){ macroLine=macroLine "+lbrace,-lbrace"; break;}
            if($x=="]"){ macroLine=macroLine "+rbrace,-rbrace"; break;}
            if($x==";"){ macroLine=macroLine "+colon,-colon"; break;}
            if($x=="'"){ macroLine=macroLine "+quote,-quote"; break;}
            if($x=="\\"){ macroLine=macroLine "+bslash_iso,-bslash_iso"; break;}
            if($x==","){ macroLine=macroLine "+comma,-comma"; break;}
            if($x=="."){ macroLine=macroLine "+dot,-dot"; break;}
            if($x=="#"){ macroLine=macroLine "+hash,-hash"; break;}
            if($x=="="){ macroLine=macroLine "+equal,-equal"; break;}
            if($x=="/"){ macroLine=macroLine "+slash,-slash"; break;}
            #if($x=="/"){ macroLine=macroLine "+numslash,-numslash"; break;}
            #if($x=="!"){ macroLine=macroLine "+exclam,-exclam"; break;}
            if($x=="!"){ macroLine=macroLine "+lctrl,+lshift,+u,+0,-0,+0,-0,+2,-2,+1,-1,-u,-lshift,-lctrl"; break;}
            if($x=="?"){ macroLine=macroLine "+lctrl,+lshift,+u,+0,-0,+0,-0,+3,-3,+f,-f,-u,-lshift,-lctrl"; break;}
            if($x=="("){ macroLine=macroLine "+lctrl,+lshift,+u,+0,-0,+0,-0,+2,-2,+8,-8,-u,-lshift,-lctrl"; break;}
            if($x==")"){ macroLine=macroLine "+lctrl,+lshift,+u,+0,-0,+0,-0,+2,-2,+9,-9,-u,-lshift,-lctrl"; break;}
            if($x=="\""){ macroLine=macroLine "+lctrl,+lshift,+u,+2,-2,+0,-0,+1,-1,+c,-c,-u,-lshift,-lctrl"; break;}

            # A normal key, press and release it, catches all the remaining cases
            macroLine=macroLine "+" $x ",-" $x
        }
        while(0)  # 0 means it runs only once instead of endlessly looping in case of errors

        if(x<NF)
        # I add a comma between this key and the next unless I'm at the last one
            macroLine=macroLine ","
        else
        # sending the macro to the output
            print macroLine
        }
}
END {}

BEGIN {}
/^macro:[:alpha: ]+/ {
    macroLine=""
    # I check every field from the second one (the first one is "macro:")
    for(x=2;x<=NF;x++){
        do{
            # I found a keys being kept pressed or just released, I print it as it is
            # This works because (probably) I don't want to keep pressed keys that need to be translated (symbols)
            if(length($x)>1&&(substr($x,1,1)=="+"||substr($x,1,1)=="-")){macroLine=macroLine $x; break;}
            # An upper-case key, I need to press shift to do it. Using the !=toupper I avoid triggering things like ","==tolower(",") and ","==toupper(",")
            if($x!=tolower($x)){macroLine=macroLine "+lshift," "+" tolower($x) ",-" tolower($x) ",-lshift"; break;}
            if($x=="`"){ macroLine=macroLine "+grave,-grave"; break;}
            if($x=="-"){ macroLine=macroLine "+minus,-minus"; break;}
            if($x=="["){ macroLine=macroLine "+lbrace,-lbrace"; break;}
            if($x=="]"){ macroLine=macroLine "+rbrace,-rbrace"; break;}
            if($x==";"){ macroLine=macroLine "+colon,-colon"; break;}
            if($x=="'"){ macroLine=macroLine "+quote,-quote"; break;}
            if($x=="\\"){ macroLine=macroLine "+bslash_iso,-bslash_iso"; break;}
            if($x==","){ macroLine=macroLine "+comma,-comma"; break;}
            if($x=="."){ macroLine=macroLine "+dot,-dot"; break;}
            if($x=="#"){ macroLine=macroLine "+hash,-hash"; break;}
            if($x=="="){ macroLine=macroLine "+equal,-equal"; break;}
            if($x=="/"){ macroLine=macroLine "+slash,-slash"; break;}
            #if($x=="/"){ macroLine=macroLine "+numslash,-numslash"; break;}
            #if($x=="!"){ macroLine=macroLine "+exclam,-exclam"; break;}
            if($x=="!"){ macroLine=macroLine "+lctrl,+lshift,+u,+0,-0,+0,-0,+2,-2,+1,-1,-u,-lshift,-lctrl"; break;}
            if($x=="?"){ macroLine=macroLine "+lctrl,+lshift,+u,+0,-0,+0,-0,+3,-3,+f,-f,-u,-lshift,-lctrl"; break;}
            if($x=="("){ macroLine=macroLine "+lctrl,+lshift,+u,+0,-0,+0,-0,+2,-2,+8,-8,-u,-lshift,-lctrl"; break;}
            if($x==")"){ macroLine=macroLine "+lctrl,+lshift,+u,+0,-0,+0,-0,+2,-2,+9,-9,-u,-lshift,-lctrl"; break;}
            if($x=="\""){ macroLine=macroLine "+lctrl,+lshift,+u,+2,-2,+0,-0,+1,-1,+c,-c,-u,-lshift,-lctrl"; break;}
            # !"!""£$
            # A normal key, press and release it, catches all the remaining cases
            macroLine=macroLine "+" $x ",-" $x
        }
        while(0)  # 0 means it runs only once instead of endlessly looping in case of errors

        if(x<NF)
        # I add a comma between this key and the next unless I'm at the last one
            macroLine=macroLine ","
        else
        # sending the macro to the output
            print macroLine
        }
}
END {}


awk: fix ignored escaped 'u' warning.

awk: fix ignored escaped 'u' warning.

function reader_read_string(token,    v, r)
{
	token = substr(token, 1, length(token) - 1)
	gsub(/\\\\/, "\xf7", token)
	gsub(/\\"/, "\"", token)
	gsub(/\\n/, "\n", token)
	gsub("\xf7", "\\", token)
	return token
}

function reader_read_atom(token)
{
	switch (token) {
	case "true":
	case "false":
	case "nil":
		return "#" token
	case /^:/:
		return ":" token
	case /^"/:
		return reader_read_string(token)
	case /^-?[0-9]+$/:
		return "+" token
	default:
		return "'" token
	}
}

function reader_read_list(reader, type, end,    idx, len, ret)
{
	idx = types_allocate()
	len = 0
	while (reader["curidx"] in reader) {
		if (reader[reader["curidx"]] == end) {
			types_heap[idx]["len"] = len
			reader["curidx"]++
			return type idx
		}
		ret = reader_read_from(reader)
		if (ret ~ /^!/) {
			types_heap[idx]["len"] = len
			types_release(type idx)
			return ret
		}
		types_heap[idx][len++] = ret
	}
	types_heap[idx]["len"] = len
	types_release(type idx)
	return "!\"expect " end ", got EOF"
}

function reader_read_hash(reader,    idx, key, val)
{
	idx = types_allocate()
	while (reader["curidx"] in reader) {
		if (reader[reader["curidx"]] == "}") {
			reader["curidx"]++
			return "{" idx
		}
		key = reader_read_from(reader)
		if (key ~ /^!/) {
			types_release("{" idx)
			return key
		}
		if (key !~ /^[":]/) {
			types_release(key)
			types_release("{" idx)
			return "!\"Hash-map key must be string or keyword."
		}
		if (!(reader["curidx"] in reader)) {
			types_release("{" idx)
			return "!\"Element count of hash-map must be even."
		}
		val = reader_read_from(reader)
		if (val ~ /^!/) {
			types_release("{" idx)
			return val
		}
		types_heap[idx][key] = val
	}
	types_release("{" idx)
	return "!\"expect }, got EOF"
}

function reader_read_abbrev(reader, symbol,    val, idx)
{
	val = reader_read_from(reader)
	if (val ~ /^!/) {
		return val
	}
	idx = types_allocate()
	types_heap[idx]["len"] = 2
	types_heap[idx][0] = symbol
	types_heap[idx][1] = val
	return "(" idx
}

function reader_read_with_meta(reader,    meta, val, idx)
{
	meta = reader_read_from(reader)
	if (meta ~ /^!/) {
		return meta
	}
	val = reader_read_from(reader)
	if (val ~ /^!/) {
		types_release(meta)
		return val
	}
	idx = types_allocate()
	types_heap[idx]["len"] = 3
	types_heap[idx][0] = "'with-meta"
	types_heap[idx][1] = val
	types_heap[idx][2] = meta
	return "(" idx
}

function reader_read_from(reader,    current)
{
	current = reader[reader["curidx"]++]
	switch (current) {
	case "(":
		return reader_read_list(reader, "(", ")")
	case "[":
		return reader_read_list(reader, "[", "]")
	case "{":
		return reader_read_hash(reader)
	case ")":
	case "]":
	case "}":
		return "!\"Unexpected token '" current "'."
	case "'":
		return reader_read_abbrev(reader, "'quote")
	case "`":
		return reader_read_abbrev(reader, "'quasiquote")
	case "~":
		return reader_read_abbrev(reader, "'unquote")
	case "~@":
		return reader_read_abbrev(reader, "'splice-unquote")
	case "@":
		return reader_read_abbrev(reader, "'deref")
	case "^":
		return reader_read_with_meta(reader)
	default:
		return reader_read_atom(current)
	}
}

function reader_tokenizer(str,    reader,    len, r)
{
	for (len = 0; match(str, /^[ \t\r\n,]*(~@|[\[\]{}()'`~^@]|\"(\\[^\r\n]|[^\\"\r\n])*\"|;[^\r\n]*|[^ \t\r\n\[\]{}('"`,;)^~@][^ \t\r\n\[\]{}('"`,;)]*)/, r); ) {
		if (substr(r[1], 1, 1) != ";") {
			reader[len++] = r[1]
		}
		str = substr(str, RSTART + RLENGTH)
	}
	if (str !~ /^[ \t\r\n,]*$/) {
		return "!\"Cannot tokenize '" str "'."
	}
	reader["len"] = len
	return ""
}

function reader_read_str(str,    reader, ret)
{
	ret = reader_tokenizer(str,    reader)
	if (ret != "") {
		return ret
	}
	if (reader["len"] == 0) {
		return "#nil"
	}
	ret = reader_read_from(reader)
	if (ret ~ /^!/) {
		return ret
	}
	if (reader["len"] != reader["curidx"]) {
		types_release(ret)
		return "!\"Unexpected token '" reader[reader["curidx"]] "'."
	}
	return ret
}

function reader_read_string(token,    v, r)
{
	token = substr(token, 1, length(token) - 1)
	gsub(/\\\\/, "\u029e", token)
	gsub(/\\"/, "\"", token)
	gsub(/\\n/, "\n", token)
	gsub("\u029e", "\\", token)
	return token
}

function reader_read_atom(token)
{
	switch (token) {
	case "true":
	case "false":
	case "nil":
		return "#" token
	case /^:/:
		return ":" token
	case /^"/:
		return reader_read_string(token)
	case /^-?[0-9]+$/:
		return "+" token
	default:
		return "'" token
	}
}

function reader_read_list(reader, type, end,    idx, len, ret)
{
	idx = types_allocate()
	len = 0
	while (reader["curidx"] in reader) {
		if (reader[reader["curidx"]] == end) {
			types_heap[idx]["len"] = len
			reader["curidx"]++
			return type idx
		}
		ret = reader_read_from(reader)
		if (ret ~ /^!/) {
			types_heap[idx]["len"] = len
			types_release(type idx)
			return ret
		}
		types_heap[idx][len++] = ret
	}
	types_heap[idx]["len"] = len
	types_release(type idx)
	return "!\"expect " end ", got EOF"
}

function reader_read_hash(reader,    idx, key, val)
{
	idx = types_allocate()
	while (reader["curidx"] in reader) {
		if (reader[reader["curidx"]] == "}") {
			reader["curidx"]++
			return "{" idx
		}
		key = reader_read_from(reader)
		if (key ~ /^!/) {
			types_release("{" idx)
			return key
		}
		if (key !~ /^[":]/) {
			types_release(key)
			types_release("{" idx)
			return "!\"Hash-map key must be string or keyword."
		}
		if (!(reader["curidx"] in reader)) {
			types_release("{" idx)
			return "!\"Element count of hash-map must be even."
		}
		val = reader_read_from(reader)
		if (val ~ /^!/) {
			types_release("{" idx)
			return val
		}
		types_heap[idx][key] = val
	}
	types_release("{" idx)
	return "!\"expect }, got EOF"
}

function reader_read_abbrev(reader, symbol,    val, idx)
{
	val = reader_read_from(reader)
	if (val ~ /^!/) {
		return val
	}
	idx = types_allocate()
	types_heap[idx]["len"] = 2
	types_heap[idx][0] = symbol
	types_heap[idx][1] = val
	return "(" idx
}

function reader_read_with_meta(reader,    meta, val, idx)
{
	meta = reader_read_from(reader)
	if (meta ~ /^!/) {
		return meta
	}
	val = reader_read_from(reader)
	if (val ~ /^!/) {
		types_release(meta)
		return val
	}
	idx = types_allocate()
	types_heap[idx]["len"] = 3
	types_heap[idx][0] = "'with-meta"
	types_heap[idx][1] = val
	types_heap[idx][2] = meta
	return "(" idx
}

function reader_read_from(reader,    current)
{
	current = reader[reader["curidx"]++]
	switch (current) {
	case "(":
		return reader_read_list(reader, "(", ")")
	case "[":
		return reader_read_list(reader, "[", "]")
	case "{":
		return reader_read_hash(reader)
	case ")":
	case "]":
	case "}":
		return "!\"Unexpected token '" current "'."
	case "'":
		return reader_read_abbrev(reader, "'quote")
	case "`":
		return reader_read_abbrev(reader, "'quasiquote")
	case "~":
		return reader_read_abbrev(reader, "'unquote")
	case "~@":
		return reader_read_abbrev(reader, "'splice-unquote")
	case "@":
		return reader_read_abbrev(reader, "'deref")
	case "^":
		return reader_read_with_meta(reader)
	default:
		return reader_read_atom(current)
	}
}

function reader_tokenizer(str,    reader,    len, r)
{
	for (len = 0; match(str, /^[ \t\r\n,]*(~@|[\[\]{}()'`~^@]|\"(\\[^\r\n]|[^\\"\r\n])*\"|;[^\r\n]*|[^ \t\r\n\[\]{}('"`,;)^~@][^ \t\r\n\[\]{}('"`,;)]*)/, r); ) {
		if (substr(r[1], 1, 1) != ";") {
			reader[len++] = r[1]
		}
		str = substr(str, RSTART + RLENGTH)
	}
	if (str !~ /^[ \t\r\n,]*$/) {
		return "!\"Cannot tokenize '" str "'."
	}
	reader["len"] = len
	return ""
}

function reader_read_str(str,    reader, ret)
{
	ret = reader_tokenizer(str,    reader)
	if (ret != "") {
		return ret
	}
	if (reader["len"] == 0) {
		return "#nil"
	}
	ret = reader_read_from(reader)
	if (ret ~ /^!/) {
		return ret
	}
	if (reader["len"] != reader["curidx"]) {
		types_release(ret)
		return "!\"Unexpected token '" reader[reader["curidx"]] "'."
	}
	return ret
}


ahrf.awk: allow grave as the first paragraph symbol

#!/usr/bin/awk -f
# Script by Ypnose - http://ywstd.fr

BEGIN { FS = "\n"; RS = "" }

{
	# Common shell symbols to HTML
	# Two backslashes for nawk(1) and OpenBSD awk(1)
	gsub(/\&/,"\\&")
	gsub(/</,"\\<")
	gsub(/>/,"\\>")
}

# h1, h2, h3, h4, h5, h6
/^[\t ]*:/ {
	# Count ':' and match the valid "level"
	match($0,":+")
	cnt = RLENGTH
	gsub(/^[\t ]*:+[\t ]*|[\t ]*:+[\t ]*$/,"")
	# Create the anchor
	anc = tolower($0)
	gsub(/ +/,"-",anc)
	# length($0) would also work
	if (cnt <= 6 && $0 != "")
		printf("<h%d id=\"%s\">%s</h%d>\n", cnt, anc, $0, cnt)
	next
}

# Paragraph
/^[A-Za-z0-9_("`]+/ {
	printf("
")
	for (p=1; p<=NF; p++) {
		# Line break
		gsub(/ +$/,"
\n",$p)
		x = split($p,word," ")
		for (w=1; w<=x; w++) {
			printf("%s", word[w])
			# If EOL is not reached, add a space
			if (w != x)
				printf(" ")
		}
		# If it's the last paragraph line, close the paragraph
		if (p == NF)
			printf("
")
		printf("\n")
	}
	next
}

# Code
/^====+/ {
	if (NF > 2 && $NF ~ /====+/) {
		gsub(/^====+[\t ]*\n|\n[\t ]*====+$/,"")
		printf("
")
		for (c=1; c<NF; c++) {
			gsub(/^ +$/,"",$c)
			printf("%s\n", $c)
		}
		printf("%s
\n", $NF)
	}
	next
}

# Tables
/^%%%%+/ {
	if (NF > 2 && $NF ~ /%%%%+/) {
		gsub(/^%%%%+[\t ]*\n|\n[\t ]*%%%%+$/,"")
		printf("
\n")
		for (t=1; t<=NF; t++) {
			printf("\t
\n")
			y = split($t,column,"%%%")
			for (c=1; c<=y; c++) {
				gsub(/^[\t ]+|[\t ]+$/,"",column[c])
				printf("\t%s	\n", column[c])
			}
			printf("\t
\n")
		}
		printf("
\n")
	}
}

# List
/^[\t ]*\* +/ {
	printf("
\n")
	for (l=1; l<=NF; l++) {
		gsub(/^[\t ]*/,"",$l)
		if ($l ~ /\* +/) {
			# Only match the text
			match($l,"\\* +")
			str = substr($l,RSTART+RLENGTH)
			printf("\t
%s
\n", str)
		}
	}
	printf("
\n")
	next
}

# End links
/^[\t ]*\[[01]/ {
	printf("
\n")
	for (u=1; u<=NF; u++) {
		gsub(/^[\t ]*/,"",$u)
		if (match($u,"\\[[0-9]+\\]")) {
			# RSTART can be replaced by 1, because we already removed
			# useless tabs and/or spaces
			num = substr($u,RSTART,RLENGTH)
			url = substr($u,RSTART+RLENGTH+1)
			# Do not print the entire link if chars > 61
			if (length(url) >= 60)
				printf("\t
%s %.60s…
\n", num, url, url)
			else
				printf("\t
%s %s
\n", num, url, url)
		}
	}
	printf("
\n")
	next
}

#!/usr/bin/awk -f
# Script by Ypnose - http://ywstd.fr

BEGIN { FS = "\n"; RS = "" }

{
	# Common shell symbols to HTML
	# Two backslashes for nawk(1) and OpenBSD awk(1)
	gsub(/\&/,"\\&")
	gsub(/</,"\\<")
	gsub(/>/,"\\>")
}

# h1, h2, h3, h4, h5, h6
/^[\t ]*:/ {
	# Count ':' and match the valid "level"
	match($0,":+")
	cnt = RLENGTH
	gsub(/^[\t ]*:+[\t ]*|[\t ]*:+[\t ]*$/,"")
	# Create the anchor
	anc = tolower($0)
	gsub(/ +/,"-",anc)
	# length($0) would also work
	if (cnt <= 6 && $0 != "")
		printf("<h%d id=\"%s\">%s</h%d>\n", cnt, anc, $0, cnt)
	next
}

# Paragraph
/^[A-Za-z0-9_("]+/ {
	printf("
")
	for (p=1; p<=NF; p++) {
		# Line break
		gsub(/ +$/,"
\n",$p)
		x = split($p,word," ")
		for (w=1; w<=x; w++) {
			printf("%s", word[w])
			# If EOL is not reached, add a space
			if (w != x)
				printf(" ")
		}
		# If it's the last paragraph line, close the paragraph
		if (p == NF)
			printf("
")
		printf("\n")
	}
	next
}

# Code
/^====+/ {
	if (NF > 2 && $NF ~ /====+/) {
		gsub(/^====+[\t ]*\n|\n[\t ]*====+$/,"")
		printf("
")
		for (c=1; c<NF; c++) {
			gsub(/^ +$/,"",$c)
			printf("%s\n", $c)
		}
		printf("%s
\n", $NF)
	}
	next
}

# Tables
/^%%%%+/ {
	if (NF > 2 && $NF ~ /%%%%+/) {
		gsub(/^%%%%+[\t ]*\n|\n[\t ]*%%%%+$/,"")
		printf("
\n")
		for (t=1; t<=NF; t++) {
			printf("\t
\n")
			y = split($t,column,"%%%")
			for (c=1; c<=y; c++) {
				gsub(/^[\t ]+|[\t ]+$/,"",column[c])
				printf("\t%s	\n", column[c])
			}
			printf("\t
\n")
		}
		printf("
\n")
	}
}

# List
/^[\t ]*\* +/ {
	printf("
\n")
	for (l=1; l<=NF; l++) {
		gsub(/^[\t ]*/,"",$l)
		if ($l ~ /\* +/) {
			# Only match the text
			match($l,"\\* +")
			str = substr($l,RSTART+RLENGTH)
			printf("\t
%s
\n", str)
		}
	}
	printf("
\n")
	next
}

# End links
/^[\t ]*\[[01]/ {
	printf("
\n")
	for (u=1; u<=NF; u++) {
		gsub(/^[\t ]*/,"",$u)
		if (match($u,"\\[[0-9]+\\]")) {
			# RSTART can be replaced by 1, because we already removed
			# useless tabs and/or spaces
			num = substr($u,RSTART,RLENGTH)
			url = substr($u,RSTART+RLENGTH+1)
			# Do not print the entire link if chars > 61
			if (length(url) >= 60)
				printf("\t
%s %.60s…
\n", num, url, url)
			else
				printf("\t
%s %s
\n", num, url, url)
		}
	}
	printf("
\n")
	next
}


change behavior of option -dictionary

change behavior of option -dictionary

####################################################################
# Main.awk                                                         #
####################################################################

# Detect gawk version.
function initGawk(    group) {
    Gawk = "gawk"
    GawkVersion = PROCINFO["version"]

    split(PROCINFO["version"], group, ".")
    if (group[1] < 4) {
        e("[ERROR] Oops! Your gawk (version " GawkVersion ") "          \
          "appears to be too old.\n"                                    \
          "        You need at least gawk 4.0.0 to run this program.")
        exit 1
    }
}

# Initialization #1. (prior to option parsing)
function init1() {
    initGawk()          #<< Commons.awk/AnsiCode

    # Languages.awk
    initBiDi()
    initLocale()
    initLocaleDisplay() #<< Locale, BiDi
    initUserLang()      #<< Locale

    RS = "\n"

    ExitCode = 0

    Option["debug"] = 0

    Option["verbose"] = 1
    Option["show-original"] = 1
    Option["show-original-phonetics"] = 1
    Option["show-translation"] = 1
    Option["show-translation-phonetics"] = 1
    Option["show-prompt-message"] = 1
    Option["show-languages"] = 1
    Option["show-original-dictionary"] = 0
    Option["show-dictionary"] = 1
    Option["show-alternatives"] = 1

    Option["width"] = ENVIRON["COLUMNS"] ? ENVIRON["COLUMNS"] : 64
    Option["indent"] = 4

    Option["view"] = 0
    Option["pager"] = ENVIRON["PAGER"]

    Option["browser"] = ENVIRON["BROWSER"]

    Option["play"] = 0
    Option["player"] = ENVIRON["PLAYER"]

    Option["proxy"] = ENVIRON["HTTP_PROXY"] ? ENVIRON["HTTP_PROXY"] : ENVIRON["http_proxy"]

    Option["interactive"] = 0
    Option["no-rlwrap"] = 0
    Option["emacs"] = 0
    Option["prompt"] = ENVIRON["TRANS_PS"] ? ENVIRON["TRANS_PS"] : "%s>"
    Option["prompt-color"] = ENVIRON["TRANS_PS_COLOR"] ? ENVIRON["TRANS_PS_COLOR"] : "default"

    Option["input"] = ""
    Option["output"] = STDOUT

    Option["hl"] = ENVIRON["HOME_LANG"] ? ENVIRON["HOME_LANG"] : UserLang
    Option["sl"] = ENVIRON["SOURCE_LANG"] ? ENVIRON["SOURCE_LANG"] : "auto"
    Option["tl"][1] = ENVIRON["TARGET_LANG"] ? ENVIRON["TARGET_LANG"] : UserLang
}

# Initialization #2. (prior to interactive mode handling)
function init2() {
    # Disable ANSI escape codes if required
    if (Option["no-ansi"])
        delete AnsiCode
}

# Initialization #3.
function init3(    group) {
    # Translate.awk
    initHttpService()

    # Initialize pager
    if (Option["view"]) {
        if (!Option["pager"]) {
            initPager()
            Option["pager"] = Pager
        }

        if (!Option["pager"]) {
            w("[WARNING] No available terminal pager.")
            Option["view"] = 0
        }
    }

    # Initialize browser
    if (!Option["browser"]) {
        "xdg-mime query default text/html" SUPERR |& getline Option["browser"]
        match(Option["browser"], "(.*).desktop$", group)
        Option["browser"] = group[1]
    }

    # Initialize audio player or speech synthesizer
    if (Option["play"]) {
        if (!Option["player"]) {
            initAudioPlayer()
            Option["player"] = AudioPlayer ? AudioPlayer : Option["player"]
            if (!Option["player"])
                initSpeechSynthesizer()
        }

        if (!Option["player"] && !SpeechSynthesizer) {
            w("[WARNING] No available audio player or speech synthesizer.")
            Option["play"] = 0
        }
    }
}

# Main entry point.
BEGIN {
    init1() # initialization #1

    # Option parsing
    pos = 0
    while (ARGV[++pos]) {
        # -, -no-op
        match(ARGV[pos], /^-(-?no-op)?$/)
        if (RSTART) continue

        # -V, -version
        match(ARGV[pos], /^--?(vers(i(on?)?)?|V)$/)
        if (RSTART) {
            print getVersion()
            print
            printf("%-22s%s\n", "gawk (GNU Awk)", PROCINFO["version"])
            printf("%s\n", FriBidi ? FriBidi : "fribidi (GNU FriBidi) [NOT INSTALLED]")
            printf("%-22s%s\n", "User Language", getName(UserLang) " (" getDisplay(UserLang) ")")
            exit
        }

        # -H, -h, -help
        match(ARGV[pos], /^--?(h(e(lp?)?)?|H)$/)
        if (RSTART) {
            print getHelp()
            exit
        }

        # -M, -m, -manual
        match(ARGV[pos], /^--?(m(a(n(u(al?)?)?)?)?|M)$/)
        if (RSTART) {
            man()
            exit
        }

        # -r, -reference
        match(ARGV[pos], /^--?r(e(f(e(r(e(n(ce?)?)?)?)?)?)?)?$/)
        if (RSTART) {
            print getReference("endonym")
            exit
        }

        # -R, -reference-english
        match(ARGV[pos], /^--?(reference-(e(n(g(l(i(sh?)?)?)?)?)?)?|R)$/)
        if (RSTART) {
            print getReference("name")
            exit
        }

        # -D, -debug
        match(ARGV[pos], /^--?(debug|D)$/)
        if (RSTART) {
            Option["debug"] = 1
            continue
        }

        # -verbose
        match(ARGV[pos], /^--?verbose$/)
        if (RSTART) {
            Option["verbose"] = 1 # default value
            continue
        }

        # -d, -dictionary
        match(ARGV[pos], /^--?d(i(c(t(i(o(n(a(ry?)?)?)?)?)?)?)?)?$/)
        if (RSTART) {
            Option["show-original-dictionary"] = 1
            Option["show-dictionary"] = 0
            Option["show-alternatives"] = 0
            continue
        }

        # -b, -brief
        match(ARGV[pos], /^--?b(r(i(ef?)?)?)?$/)
        if (RSTART) {
            Option["verbose"] = 0
            continue
        }

        # -show-original [yes|no]
        match(ARGV[pos], /^--?show-original(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-original"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-original-phonetics [yes|no]
        match(ARGV[pos], /^--?show-original-phonetics(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-original-phonetics"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-translation [yes|no]
        match(ARGV[pos], /^--?show-translation(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-translation"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-translation-phonetics [yes|no]
        match(ARGV[pos], /^--?show-translation-phonetics(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-translation-phonetics"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-prompt-message [yes|no]
        match(ARGV[pos], /^--?show-prompt-message(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-prompt-message"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-languages [yes|no]
        match(ARGV[pos], /^--?show-languages(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-languages"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-original-dictionary [yes|no]
        match(ARGV[pos], /^--?show-original-dictionary(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-original-dictionary"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-dictionary [yes|no]
        match(ARGV[pos], /^--?show-dictionary(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-dictionary"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-alternatives [yes|no]
        match(ARGV[pos], /^--?show-alternatives(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-alternatives"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -no-ansi
        match(ARGV[pos], /^--?no-ansi/)
        if (RSTART) {
            Option["no-ansi"] = 1
            continue
        }

        # -w [num], -width [num]
        match(ARGV[pos], /^--?w(i(d(th?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["width"] = group[4] ?
                (group[5] ? group[5] : Option["width"]) :
                ARGV[++pos]
            continue
        }

        # -indent [num]
        match(ARGV[pos], /^--?indent(=(.*)?)?$/, group)
        if (RSTART) {
            Option["indent"] = group[1] ?
                (group[2] ? group[2] : Option["indent"]) :
                ARGV[++pos]
            continue
        }

        # -v, -view
        match(ARGV[pos], /^--?v(i(ew?)?)?$/)
        if (RSTART) {
            Option["view"] = 1
            continue
        }

        # -pager [program]
        match(ARGV[pos], /^--?pager(=(.*)?)?$/, group)
        if (RSTART) {
            Option["view"] = 1
            Option["pager"] = group[1] ?
                (group[2] ? group[2] : Option["pager"]) :
                ARGV[++pos]
            continue
        }

        # -browser [program]
        match(ARGV[pos], /^--?browser(=(.*)?)?$/, group)
        if (RSTART) {
            Option["browser"] = group[1] ?
                (group[2] ? group[2] : Option["browser"]) :
                ARGV[++pos]
            continue
        }

        # -p, -play
        match(ARGV[pos], /^--?p(l(ay?)?)?$/)
        if (RSTART) {
            Option["play"] = 1
            continue
        }

        # -player [program]
        match(ARGV[pos], /^--?player(=(.*)?)?$/, group)
        if (RSTART) {
            Option["play"] = 1
            Option["player"] = group[1] ?
                (group[2] ? group[2] : Option["player"]) :
                ARGV[++pos]
            continue
        }

        # -x [proxy], -proxy [proxy]
        match(ARGV[pos], /^--?(proxy|x)(=(.*)?)?$/, group)
        if (RSTART) {
            Option["proxy"] = group[2] ?
                (group[3] ? group[3] : Option["proxy"]) :
                ARGV[++pos]
            continue
        }

        # -I, -interactive
        match(ARGV[pos], /^--?(int(e(r(a(c(t(i(ve?)?)?)?)?)?)?)?|I)$/)
        if (RSTART) {
            Option["interactive"] = 1
            continue
        }

        # -no-rlwrap
        match(ARGV[pos], /^--?no-rlwrap/)
        if (RSTART) {
            Option["no-rlwrap"] = 1
            continue
        }

        # -E, -emacs
        match(ARGV[pos], /^--?(emacs|E)$/)
        if (RSTART) {
            Option["emacs"] = 1
            continue
        }

        # -prompt [prompt_string]
        match(ARGV[pos], /^--?prompt(=(.*)?)?$/, group)
        if (RSTART) {
            Option["prompt"] = group[1] ?
                (group[2] ? group[2] : Option["prompt"]) :
                ARGV[++pos]
            continue
        }

        # -prompt-color [color_code]
        match(ARGV[pos], /^--?prompt-color(=(.*)?)?$/, group)
        if (RSTART) {
            Option["prompt-color"] = group[1] ?
                (group[2] ? group[2] : Option["prompt-color"]) :
                ARGV[++pos]
            continue
        }

        # -i [file], -input [file]
        match(ARGV[pos], /^--?i(n(p(ut?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["input"] = group[4] ?
                (group[5] ? group[5] : Option["input"]) :
                ARGV[++pos]
            continue
        }

        # -o [file], -output [file]
        match(ARGV[pos], /^--?o(u(t(p(ut?)?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["output"] = group[5] ?
                (group[6] ? group[6] : Option["output"]) :
                ARGV[++pos]
            continue
        }

        # -l [code], -lang [code]
        match(ARGV[pos], /^--?l(a(ng?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["hl"] = group[3] ?
                (group[4] ? group[4] : Option["hl"]) :
                ARGV[++pos]
            continue
        }

        # -s [code], -source [code]
        match(ARGV[pos], /^--?s(o(u(r(ce?)?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["sl"] = group[5] ?
                (group[6] ? group[6] : Option["sl"]) :
                ARGV[++pos]
            continue
        }

        # -t [codes], -target [codes]
        match(ARGV[pos], /^--?t(a(r(g(et?)?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            if (group[5]) {
                if (group[6]) split(group[6], Option["tl"], "+")
            } else
                split(ARGV[++pos], Option["tl"], "+")
            continue
        }

        # Shortcut format
        # '[code]:[code]+...' or '[code]=[code]+...'
        match(ARGV[pos], /^[{(\[]?([[:alpha:]][[:alpha:]][[:alpha:]]?(-[[:alpha:]][[:alpha:]])?)?(:|=)((@?[[:alpha:]][[:alpha:]][[:alpha:]]?(-[[:alpha:]][[:alpha:]])?\+)*(@?[[:alpha:]][[:alpha:]][[:alpha:]]?(-[[:alpha:]][[:alpha:]])?)?)[})\]]?$/, group)
        if (RSTART) {
            if (group[1]) Option["sl"] = group[1]
            if (group[4]) split(group[4], Option["tl"], "+")
            continue
        }

        # --
        match(ARGV[pos], /^--$/)
        if (RSTART) {
            ++pos # skip the end-of-options option
            break # no more option from here
        }

        break # no more option from here
    }

    init2() # initialization #2

    if (Option["interactive"] && !Option["no-rlwrap"]) {
        # Interactive mode
        initRlwrap() # initialize Rlwrap

        if (Rlwrap && (ENVIRON["TRANS_PROGRAM"] || fileExists(EntryPoint))) {
            command = Rlwrap " " Gawk " " (ENVIRON["TRANS_PROGRAM"] ?
                                           "\"${TRANS_PROGRAM}\"" :
                                           "-f " EntryPoint) " -" \
                " -no-rlwrap" # be careful - never fork Rlwrap recursively!
            for (i = 1; i < length(ARGV); i++)
                if (ARGV[i])
                    command = command " " parameterize(ARGV[i])

            if (!system(command))
                exit # child process finished, exit
            else
                ; # skip
        } else
            ; # skip

    } else if (!Option["interactive"] && !Option["no-rlwrap"] && Option["emacs"]) {
        # Emacs interface
        Emacs = "emacs"

        if (ENVIRON["TRANS_PROGRAM"] || fileExists(EntryPoint)) {
            params = ""
            for (i = 1; i < length(ARGV); i++)
                if (ARGV[i])
                    params = params " " (parameterize(ARGV[i], "\""))
            if (ENVIRON["TRANS_PROGRAM"]) {
                el = "(progn (setq trans-program (getenv \"TRANS_PROGRAM\")) " \
                    "(setq explicit-shell-file-name \"" Gawk "\") " \
                    "(setq explicit-" Gawk "-args (cons trans-program '(\"-\" \"-I\" \"-no-rlwrap\"" params "))) " \
                    "(command-execute 'shell) (rename-buffer \"" Name "\"))"
            } else {
                el = "(progn (setq explicit-shell-file-name \"" Gawk "\") " \
                    "(setq explicit-" Gawk "-args '(\"-f\" \"" EntryPoint "\" \"--\" \"-I\" \"-no-rlwrap\"" params ")) " \
                    "(command-execute 'shell) (rename-buffer \"" Name "\"))"
            }
            command = Emacs " --eval " parameterize(el)

            if (!system(command))
                exit # child process finished, exit
            else
                Option["interactive"] = 1 # skip
        } else
            Option["interactive"] = 1 # skip
    }

    if (Option["interactive"]) {
        print AnsiCode["bold"] AnsiCode[tolower(Option["prompt-color"])] getVersion() AnsiCode[0] > STDERR
        print AnsiCode[tolower(Option["prompt-color"])] "(:q to quit)" AnsiCode[0] > STDERR
    }

    init3() # initialization #3

    if (pos < ARGC) {
        # More parameters

        # Translate remaining parameters
        for (i = pos; i < ARGC; i++) {
            # Verbose mode: separator between sources
            if (Option["verbose"] && i > pos)
                p(replicate("═", Option["width"]))

            translate(ARGV[i], 1) # inline mode
        }

        # If input not specified, we're done
    } else {
        # No more parameter besides options

        # If input not specified, use stdin
        if (!Option["input"]) Option["input"] = STDIN
    }

    # If input specified, start translating
    if (Option["input"])
        translateMain()

    exit ExitCode
}

####################################################################
# Main.awk                                                         #
####################################################################

# Detect gawk version.
function initGawk(    group) {
    Gawk = "gawk"
    GawkVersion = PROCINFO["version"]

    split(PROCINFO["version"], group, ".")
    if (group[1] < 4) {
        e("[ERROR] Oops! Your gawk (version " GawkVersion ") "          \
          "appears to be too old.\n"                                    \
          "        You need at least gawk 4.0.0 to run this program.")
        exit 1
    }
}

# Initialization #1. (prior to option parsing)
function init1() {
    initGawk()          #<< Commons.awk/AnsiCode

    # Languages.awk
    initBiDi()
    initLocale()
    initLocaleDisplay() #<< Locale, BiDi
    initUserLang()      #<< Locale

    RS = "\n"

    ExitCode = 0

    Option["debug"] = 0

    Option["verbose"] = 1
    Option["show-original"] = 1
    Option["show-original-phonetics"] = 1
    Option["show-translation"] = 1
    Option["show-translation-phonetics"] = 1
    Option["show-prompt-message"] = 1
    Option["show-languages"] = 1
    Option["show-original-dictionary"] = 0
    Option["show-dictionary"] = 1
    Option["show-alternatives"] = 1

    Option["width"] = ENVIRON["COLUMNS"] ? ENVIRON["COLUMNS"] : 64
    Option["indent"] = 4

    Option["view"] = 0
    Option["pager"] = ENVIRON["PAGER"]

    Option["browser"] = ENVIRON["BROWSER"]

    Option["play"] = 0
    Option["player"] = ENVIRON["PLAYER"]

    Option["proxy"] = ENVIRON["HTTP_PROXY"] ? ENVIRON["HTTP_PROXY"] : ENVIRON["http_proxy"]

    Option["interactive"] = 0
    Option["no-rlwrap"] = 0
    Option["emacs"] = 0
    Option["prompt"] = ENVIRON["TRANS_PS"] ? ENVIRON["TRANS_PS"] : "%s>"
    Option["prompt-color"] = ENVIRON["TRANS_PS_COLOR"] ? ENVIRON["TRANS_PS_COLOR"] : "default"

    Option["input"] = ""
    Option["output"] = STDOUT

    Option["hl"] = ENVIRON["HOME_LANG"] ? ENVIRON["HOME_LANG"] : UserLang
    Option["sl"] = ENVIRON["SOURCE_LANG"] ? ENVIRON["SOURCE_LANG"] : "auto"
    Option["tl"][1] = ENVIRON["TARGET_LANG"] ? ENVIRON["TARGET_LANG"] : UserLang
}

# Initialization #2. (prior to interactive mode handling)
function init2() {
    # Disable ANSI escape codes if required
    if (Option["no-ansi"])
        delete AnsiCode
}

# Initialization #3.
function init3(    group) {
    # Translate.awk
    initHttpService()

    # Initialize pager
    if (Option["view"]) {
        if (!Option["pager"]) {
            initPager()
            Option["pager"] = Pager
        }

        if (!Option["pager"]) {
            w("[WARNING] No available terminal pager.")
            Option["view"] = 0
        }
    }

    # Initialize browser
    if (!Option["browser"]) {
        "xdg-mime query default text/html" SUPERR |& getline Option["browser"]
        match(Option["browser"], "(.*).desktop$", group)
        Option["browser"] = group[1]
    }

    # Initialize audio player or speech synthesizer
    if (Option["play"]) {
        if (!Option["player"]) {
            initAudioPlayer()
            Option["player"] = AudioPlayer ? AudioPlayer : Option["player"]
            if (!Option["player"])
                initSpeechSynthesizer()
        }

        if (!Option["player"] && !SpeechSynthesizer) {
            w("[WARNING] No available audio player or speech synthesizer.")
            Option["play"] = 0
        }
    }
}

# Main entry point.
BEGIN {
    init1() # initialization #1

    # Option parsing
    pos = 0
    while (ARGV[++pos]) {
        # -, -no-op
        match(ARGV[pos], /^-(-?no-op)?$/)
        if (RSTART) continue

        # -V, -version
        match(ARGV[pos], /^--?(vers(i(on?)?)?|V)$/)
        if (RSTART) {
            print getVersion()
            print
            printf("%-22s%s\n", "gawk (GNU Awk)", PROCINFO["version"])
            printf("%s\n", FriBidi ? FriBidi : "fribidi (GNU FriBidi) [NOT INSTALLED]")
            printf("%-22s%s\n", "User Language", getName(UserLang) " (" getDisplay(UserLang) ")")
            exit
        }

        # -H, -h, -help
        match(ARGV[pos], /^--?(h(e(lp?)?)?|H)$/)
        if (RSTART) {
            print getHelp()
            exit
        }

        # -M, -m, -manual
        match(ARGV[pos], /^--?(m(a(n(u(al?)?)?)?)?|M)$/)
        if (RSTART) {
            man()
            exit
        }

        # -r, -reference
        match(ARGV[pos], /^--?r(e(f(e(r(e(n(ce?)?)?)?)?)?)?)?$/)
        if (RSTART) {
            print getReference("endonym")
            exit
        }

        # -R, -reference-english
        match(ARGV[pos], /^--?(reference-(e(n(g(l(i(sh?)?)?)?)?)?)?|R)$/)
        if (RSTART) {
            print getReference("name")
            exit
        }

        # -D, -debug
        match(ARGV[pos], /^--?(debug|D)$/)
        if (RSTART) {
            Option["debug"] = 1
            continue
        }

        # -verbose
        match(ARGV[pos], /^--?verbose$/)
        if (RSTART) {
            Option["verbose"] = 1 # default value
            continue
        }

        # -d, -dictionary
        match(ARGV[pos], /^--?d(i(c(t(i(o(n(a(ry?)?)?)?)?)?)?)?)?$/)
        if (RSTART) {
            Option["show-original-dictionary"] = 1
            continue
        }

        # -b, -brief
        match(ARGV[pos], /^--?b(r(i(ef?)?)?)?$/)
        if (RSTART) {
            Option["verbose"] = 0
            continue
        }

        # -show-original [yes|no]
        match(ARGV[pos], /^--?show-original(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-original"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-original-phonetics [yes|no]
        match(ARGV[pos], /^--?show-original-phonetics(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-original-phonetics"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-translation [yes|no]
        match(ARGV[pos], /^--?show-translation(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-translation"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-translation-phonetics [yes|no]
        match(ARGV[pos], /^--?show-translation-phonetics(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-translation-phonetics"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-prompt-message [yes|no]
        match(ARGV[pos], /^--?show-prompt-message(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-prompt-message"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-languages [yes|no]
        match(ARGV[pos], /^--?show-languages(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-languages"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-original-dictionary [yes|no]
        match(ARGV[pos], /^--?show-original-dictionary(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-original-dictionary"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-dictionary [yes|no]
        match(ARGV[pos], /^--?show-dictionary(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-dictionary"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -show-alternatives [yes|no]
        match(ARGV[pos], /^--?show-alternatives(=(.*)?)?$/, group)
        if (RSTART) {
            Option["show-alternatives"] = yn(group[1] ? group[2] : ARGV[++pos])
            continue
        }

        # -no-ansi
        match(ARGV[pos], /^--?no-ansi/)
        if (RSTART) {
            Option["no-ansi"] = 1
            continue
        }

        # -w [num], -width [num]
        match(ARGV[pos], /^--?w(i(d(th?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["width"] = group[4] ?
                (group[5] ? group[5] : Option["width"]) :
                ARGV[++pos]
            continue
        }

        # -indent [num]
        match(ARGV[pos], /^--?indent(=(.*)?)?$/, group)
        if (RSTART) {
            Option["indent"] = group[1] ?
                (group[2] ? group[2] : Option["indent"]) :
                ARGV[++pos]
            continue
        }

        # -v, -view
        match(ARGV[pos], /^--?v(i(ew?)?)?$/)
        if (RSTART) {
            Option["view"] = 1
            continue
        }

        # -pager [program]
        match(ARGV[pos], /^--?pager(=(.*)?)?$/, group)
        if (RSTART) {
            Option["view"] = 1
            Option["pager"] = group[1] ?
                (group[2] ? group[2] : Option["pager"]) :
                ARGV[++pos]
            continue
        }

        # -browser [program]
        match(ARGV[pos], /^--?browser(=(.*)?)?$/, group)
        if (RSTART) {
            Option["browser"] = group[1] ?
                (group[2] ? group[2] : Option["browser"]) :
                ARGV[++pos]
            continue
        }

        # -p, -play
        match(ARGV[pos], /^--?p(l(ay?)?)?$/)
        if (RSTART) {
            Option["play"] = 1
            continue
        }

        # -player [program]
        match(ARGV[pos], /^--?player(=(.*)?)?$/, group)
        if (RSTART) {
            Option["play"] = 1
            Option["player"] = group[1] ?
                (group[2] ? group[2] : Option["player"]) :
                ARGV[++pos]
            continue
        }

        # -x [proxy], -proxy [proxy]
        match(ARGV[pos], /^--?(proxy|x)(=(.*)?)?$/, group)
        if (RSTART) {
            Option["proxy"] = group[2] ?
                (group[3] ? group[3] : Option["proxy"]) :
                ARGV[++pos]
            continue
        }

        # -I, -interactive
        match(ARGV[pos], /^--?(int(e(r(a(c(t(i(ve?)?)?)?)?)?)?)?|I)$/)
        if (RSTART) {
            Option["interactive"] = 1
            continue
        }

        # -no-rlwrap
        match(ARGV[pos], /^--?no-rlwrap/)
        if (RSTART) {
            Option["no-rlwrap"] = 1
            continue
        }

        # -E, -emacs
        match(ARGV[pos], /^--?(emacs|E)$/)
        if (RSTART) {
            Option["emacs"] = 1
            continue
        }

        # -prompt [prompt_string]
        match(ARGV[pos], /^--?prompt(=(.*)?)?$/, group)
        if (RSTART) {
            Option["prompt"] = group[1] ?
                (group[2] ? group[2] : Option["prompt"]) :
                ARGV[++pos]
            continue
        }

        # -prompt-color [color_code]
        match(ARGV[pos], /^--?prompt-color(=(.*)?)?$/, group)
        if (RSTART) {
            Option["prompt-color"] = group[1] ?
                (group[2] ? group[2] : Option["prompt-color"]) :
                ARGV[++pos]
            continue
        }

        # -i [file], -input [file]
        match(ARGV[pos], /^--?i(n(p(ut?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["input"] = group[4] ?
                (group[5] ? group[5] : Option["input"]) :
                ARGV[++pos]
            continue
        }

        # -o [file], -output [file]
        match(ARGV[pos], /^--?o(u(t(p(ut?)?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["output"] = group[5] ?
                (group[6] ? group[6] : Option["output"]) :
                ARGV[++pos]
            continue
        }

        # -l [code], -lang [code]
        match(ARGV[pos], /^--?l(a(ng?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["hl"] = group[3] ?
                (group[4] ? group[4] : Option["hl"]) :
                ARGV[++pos]
            continue
        }

        # -s [code], -source [code]
        match(ARGV[pos], /^--?s(o(u(r(ce?)?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            Option["sl"] = group[5] ?
                (group[6] ? group[6] : Option["sl"]) :
                ARGV[++pos]
            continue
        }

        # -t [codes], -target [codes]
        match(ARGV[pos], /^--?t(a(r(g(et?)?)?)?)?(=(.*)?)?$/, group)
        if (RSTART) {
            if (group[5]) {
                if (group[6]) split(group[6], Option["tl"], "+")
            } else
                split(ARGV[++pos], Option["tl"], "+")
            continue
        }

        # Shortcut format
        # '[code]:[code]+...' or '[code]=[code]+...'
        match(ARGV[pos], /^[{(\[]?([[:alpha:]][[:alpha:]][[:alpha:]]?(-[[:alpha:]][[:alpha:]])?)?(:|=)((@?[[:alpha:]][[:alpha:]][[:alpha:]]?(-[[:alpha:]][[:alpha:]])?\+)*(@?[[:alpha:]][[:alpha:]][[:alpha:]]?(-[[:alpha:]][[:alpha:]])?)?)[})\]]?$/, group)
        if (RSTART) {
            if (group[1]) Option["sl"] = group[1]
            if (group[4]) split(group[4], Option["tl"], "+")
            continue
        }

        # --
        match(ARGV[pos], /^--$/)
        if (RSTART) {
            ++pos # skip the end-of-options option
            break # no more option from here
        }

        break # no more option from here
    }

    init2() # initialization #2

    if (Option["interactive"] && !Option["no-rlwrap"]) {
        # Interactive mode
        initRlwrap() # initialize Rlwrap

        if (Rlwrap && (ENVIRON["TRANS_PROGRAM"] || fileExists(EntryPoint))) {
            command = Rlwrap " " Gawk " " (ENVIRON["TRANS_PROGRAM"] ?
                                           "\"${TRANS_PROGRAM}\"" :
                                           "-f " EntryPoint) " -" \
                " -no-rlwrap" # be careful - never fork Rlwrap recursively!
            for (i = 1; i < length(ARGV); i++)
                if (ARGV[i])
                    command = command " " parameterize(ARGV[i])

            if (!system(command))
                exit # child process finished, exit
            else
                ; # skip
        } else
            ; # skip

    } else if (!Option["interactive"] && !Option["no-rlwrap"] && Option["emacs"]) {
        # Emacs interface
        Emacs = "emacs"

        if (ENVIRON["TRANS_PROGRAM"] || fileExists(EntryPoint)) {
            params = ""
            for (i = 1; i < length(ARGV); i++)
                if (ARGV[i])
                    params = params " " (parameterize(ARGV[i], "\""))
            if (ENVIRON["TRANS_PROGRAM"]) {
                el = "(progn (setq trans-program (getenv \"TRANS_PROGRAM\")) " \
                    "(setq explicit-shell-file-name \"" Gawk "\") " \
                    "(setq explicit-" Gawk "-args (cons trans-program '(\"-\" \"-I\" \"-no-rlwrap\"" params "))) " \
                    "(command-execute 'shell) (rename-buffer \"" Name "\"))"
            } else {
                el = "(progn (setq explicit-shell-file-name \"" Gawk "\") " \
                    "(setq explicit-" Gawk "-args '(\"-f\" \"" EntryPoint "\" \"--\" \"-I\" \"-no-rlwrap\"" params ")) " \
                    "(command-execute 'shell) (rename-buffer \"" Name "\"))"
            }
            command = Emacs " --eval " parameterize(el)

            if (!system(command))
                exit # child process finished, exit
            else
                Option["interactive"] = 1 # skip
        } else
            Option["interactive"] = 1 # skip
    }

    if (Option["interactive"]) {
        print AnsiCode["bold"] AnsiCode[tolower(Option["prompt-color"])] getVersion() AnsiCode[0] > STDERR
        print AnsiCode[tolower(Option["prompt-color"])] "(:q to quit)" AnsiCode[0] > STDERR
    }

    init3() # initialization #3

    if (pos < ARGC) {
        # More parameters

        # Translate remaining parameters
        for (i = pos; i < ARGC; i++) {
            # Verbose mode: separator between sources
            if (Option["verbose"] && i > pos)
                p(replicate("═", Option["width"]))

            translate(ARGV[i], 1) # inline mode
        }

        # If input not specified, we're done
    } else {
        # No more parameter besides options

        # If input not specified, use stdin
        if (!Option["input"]) Option["input"] = STDIN
    }

    # If input specified, start translating
    if (Option["input"])
        translateMain()

    exit ExitCode
}


output change to formatted printf

output change to formatted printf

# find minimal and maximal values in a column
# column number can be set from command line: -v col=2
BEGIN { mi = 1e10; ma = -1e10;
  if (col+0 == 0) { col = 1; }	# is col defined?
}
{ if (NF >= col) {
    if ($col < mi) { mi = $col; }
    if ($col > ma) { ma = $col; }
  }
}
END { printf("%.3f %.3f", mi, ma); }

# find minimal and maximal values in a column
# column number can be set from command line: -v col=2
BEGIN { mi = 1e10; ma = -1e10;
  if (col+0 == 0) { col = 1; }	# is col defined?
}
{ if (NF >= col) {
    if ($col < mi) { mi = $col; }
    if ($col > ma) { ma = $col; }
  }
}
END { print mi, ma; }


hmm: Sort target list during output.

hmm: Sort target list during output.

Change-Id: Ida1e8ebddfb0f47120160fcf45e630ccfd7e0ffa
GitOrigin-RevId: ecf5325329ec42aaa01c803620e82c37043800a6

#!/bin/env awk
# Script to extract make-style documentation comments for various targets in the
# Sparrow build system.
#
# Documentation comments are of the form:
#
#   ## Short one-line summary comment
#   #
#   # Additional descriptive text.
#   target:
#
# Note that the target definition terminates the doc-comment!
#
## Theory of operation
#
# This script operates in two passes: first it accumulates all doc comments from
# the files specified on the command line, and then it looks up the target in
# the `targetname` environment variable for the name of the target to extract
# docs for.
#
# If `targetname` is empty or a target we didn't find, hmm will output a list of
# targets that have doccomments instead.

BEGIN {
  debug = 0;
}

function flush_comment() {
  doc_accumulator = "";
  in_doccomment = 0;
}

# Start of a doc-comment. This enables accumulation of a doc-comment into the
# accumulator in the later patterns.
/^## / {
  in_doccomment = 1;
  doc_accumulator = substr($0, 3);

  doc_filename = ARGV[ARGIND];
  sub(ENVIRON["ROOTDIR"] "/", "", doc_filename);
}

# Continuation of a doc-comment
in_doccomment && /^# / {
  comment = substr($0, 2);
  doc_accumulator = sprintf("%s\n%s", doc_accumulator, comment);
}

# Doc-comment empty line, so we can separate paragraphs.
in_doccomment && /^#$/ {
  doc_accumulator = sprintf("%s\n", doc_accumulator);
}

# Doc-comment proper terminator -- the target name.
in_doccomment && /^[^#]*:/ {
  target = substr($1, 0, index($1, ":") - 1);
  docs[target] = doc_accumulator;
  files[target] = doc_filename;
  flush_comment();
}

END {
  targetname = ENVIRON["targetname"];

  if (targetname in docs) {
    print "";
    print targetname ": (defined in " files[targetname] ")";
    print docs[targetname];
    print "";
    exit(0);
  }

  print "";
  print "Targets available are:";
  print "";
  column = 0;

  # Sort the target list keys into a normal array so we can sort 'em.
  for (target in docs) {
    targets[++j] = target
  }

  asort(targets)

  for (target in targets) {
    target = targets[target]
    printf("%s ", target);
    column += length(target) + 1;
    if (column > 75) {
      print "";
      column = 0;
    }
  }
  print "";

  print "";
  print "To get more information on a target, use 'hmm [target]'"
  print "";
}

#!/bin/env awk
# Script to extract make-style documentation comments for various targets in the
# Sparrow build system.
#
# Documentation comments are of the form:
#
#   ## Short one-line summary comment
#   #
#   # Additional descriptive text.
#   target:
#
# Note that the target definition terminates the doc-comment!
#
## Theory of operation
#
# This script operates in two passes: first it accumulates all doc comments from
# the files specified on the command line, and then it looks up the target in
# the `targetname` environment variable for the name of the target to extract
# docs for.
#
# If `targetname` is empty or a target we didn't find, hmm will output a list of
# targets that have doccomments instead.

BEGIN {
  debug = 0;
}

function flush_comment() {
  doc_accumulator = "";
  in_doccomment = 0;
}

# Start of a doc-comment. This enables accumulation of a doc-comment into the
# accumulator in the later patterns.
/^## / {
  in_doccomment = 1;
  doc_accumulator = substr($0, 3);

  doc_filename = ARGV[ARGIND];
  sub(ENVIRON["ROOTDIR"] "/", "", doc_filename);
}

# Continuation of a doc-comment
in_doccomment && /^# / {
  comment = substr($0, 2);
  doc_accumulator = sprintf("%s\n%s", doc_accumulator, comment);
}

# Doc-comment empty line, so we can separate paragraphs.
in_doccomment && /^#$/ {
  doc_accumulator = sprintf("%s\n", doc_accumulator);
}

# Doc-comment proper terminator -- the target name.
in_doccomment && /^[^#]*:/ {
  target = substr($1, 0, index($1, ":") - 1);
  docs[target] = doc_accumulator;
  files[target] = doc_filename;
  flush_comment();
}

END {
  targetname = ENVIRON["targetname"];

  if (targetname in docs) {
    print "";
    print targetname ": (defined in " files[targetname] ")";
    print docs[targetname];
    print "";
    exit(0);
  }

  print "";
  print "Targets available are:";
  print "";
  column = 0;
  for (target in docs) {
    printf("%s ", target);
    column += length(target) + 1;
    if (column > 75) {
      print "";
      column = 0;
    }
  }
  print "";

  print "";
  print "To get more information on a target, use 'hmm [target]'"
  print "";
}