implement wordMode and dictMode

implement wordMode and dictMode

####################################################################
# 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, example,
                        explanation, group, i, il, ils, isPhonetic, j, k,
                        oExamples, oPhonetics, oRefs, oSeeAlso, oSynonyms,
                        oWords, oWordClasses, original, phonetics,
                        r, ref, 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 (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "3$")
            append(oPhonetics, literal(ast[i]))

        # 1 - word classes and explanations
        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])

        # 5 - alternative translations
        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]))

        # 8 - identified source languages
        if (i ~ "^0" SUBSEP "8" SUBSEP "0" SUBSEP "[[:digit:]]+$" ||
            i ~ "^0" SUBSEP "2$")
            append(ils, literal(ast[i]))

        # 11 - (original) word classes and synonyms
        if (match(i, "^0" SUBSEP "11" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oWordClasses[group[1]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "11" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "1$", group))
            if (ast[i]) {
                oRefs[literal(ast[i])][1] = group[1]
                oRefs[literal(ast[i])][2] = group[2]
            }
        if (match(i, "^0" SUBSEP "11" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "0" SUBSEP "([[:digit:]]+)$", group))
            oSynonyms[group[1]][group[2]][group[3]] = literal(ast[i])

        # 12 - (original) word classes and explanations
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oWordClasses[group[1]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oWords[group[1]][group[2]][0] = literal(ast[i])
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "1$", group))
            oWords[group[1]][group[2]][1] = literal(ast[i])
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2$", group))
            oWords[group[1]][group[2]][2] = postprocess(literal(ast[i]))

        # 13 - (original) examples
        if (match(i, "^0" SUBSEP "13" SUBSEP "0" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oExamples[group[1]] = postprocess(literal(ast[i]))

        # 14 - (original) see also
        if (match(i, "^0" SUBSEP "14" SUBSEP "0" SUBSEP "([[:digit:]]+)$", group))
            oSeeAlso[group[1]] = 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

        if (isarray(wordClasses) && anything(wordClasses))
            wordMode = 1
        if (il == tl)
            wordMode = dictMode = 1

        if (wordMode) {
            r = AnsiCode["bold"] show(join(original), il) AnsiCode["no bold"]
            if (anything(oPhonetics))
                r = r "\n" AnsiCode["bold"] "/" join(oPhonetics) "/" AnsiCode["no bold"]
            r = r "\n"
        }
        if (dictMode) {
            # Dictionary mode
            if (isarray(oWordClasses) && anything(oWordClasses)) {
                for (i = 0; i < length(oWordClasses); i++) {
                    r = r "\n" AnsiCode["negative"] AnsiCode["bold"]    \
                        show(oWordClasses[i], hl) AnsiCode["no bold"] AnsiCode["positive"]
                    if (isarray(oWords[i])) {
                        # Detailed explanation
                        for (j = 0; j < length(oWords[i]); j++) {
                            explanation = oWords[i][j][0]
                            ref = oWords[i][j][1]
                            example = oWords[i][j][2]

                            r = r "\n    " AnsiCode["bold"] show(explanation, il) AnsiCode["no bold"]
                            if (example)
                                r = r "\n    - \"" show(example, il) "\""
                            if (ref && isarray(oRefs[ref])) {
                                r = r "\n    * synonyms: " AnsiCode["underline"] \
                                    show(oSynonyms[oRefs[ref][1]][oRefs[ref][2]][0], il) AnsiCode["no underline"]
                                for (k = 1; k < length(oSynonyms[oRefs[ref][1]][oRefs[ref][2]]); k++)
                                    r = r ", " AnsiCode["underline"]    \
                                        show(oSynonyms[oRefs[ref][1]][oRefs[ref][2]][k], il) AnsiCode["no underline"]
                            }
                            r = r "\n"
                        }
                    } else {
                        # Synonyms only
                        for (j = 0; j < length(oSynonyms[i]); j++) {
                            r = r "\n  * " show(oSynonyms[i][j][0], il)
                            for (k = 1; k < length(oSynonyms[i][j]); k++)
                                r = r ", " show(oSynonyms[i][j][k], il)
                        }
                        r = r "\n"
                    }
                }
            }
            # Examples
            if (isarray(oExamples) && anything(oExamples)) {
                r = r "\n" "Examples:"
                for (i = 0; i < length(oExamples); i++) {
                    example = oExamples[i]
                    sub(/\u003cb\u003e/, AnsiCode["negative"], example)
                    sub(/\u003c\/b\u003e/, AnsiCode["positive"], example)
                    r = r "\n  - " show(example, il)
                    r = r "\n"
                }
            }
            # See also
            if (isarray(oSeeAlso) && anything(oSeeAlso)) {
                r = r "\n" "See also:"
                r = r "\n" AnsiCode["underline"] show(oSeeAlso[0], il) AnsiCode["no underline"]
                for (k = 1; k < length(oSeeAlso); k++)
                    r = r ", " AnsiCode["underline"] show(oSeeAlso[k], il) AnsiCode["no underline"]
                r = r "\n"
            }
        }

        if (!wordMode) {
            r = 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
            r = r "\n"
        }

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

            if (Locale[getCode(hl)]["rtl"] || Locale[getCode(il)]["rtl"])
                r = r "\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" 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"] ")"

            if (wordMode) {
                r = r "\n" 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
            } else {
                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(wordClasses); i++) {
                r = r "\n\n" s(AnsiCode["negative"] wordClasses[i] AnsiCode["positive"], 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/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, example,
                        explanation, group, i, il, ils, isPhonetic, j, k,
                        oExamples, oPhonetics, oRefs, oSeeAlso, oSynonyms,
                        oWords, oWordClasses, original, phonetics,
                        r, ref, 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 (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "3$")
            append(oPhonetics, literal(ast[i]))

        # 1 - word classes and explanations
        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])

        # 5 - alternative translations
        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]))

        # 8 - identified source languages
        if (i ~ "^0" SUBSEP "8" SUBSEP "0" SUBSEP "[[:digit:]]+$" ||
            i ~ "^0" SUBSEP "2$")
            append(ils, literal(ast[i]))

        # 11 - (original) word classes and synonyms
        if (match(i, "^0" SUBSEP "11" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oWordClasses[group[1]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "11" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "1$", group))
            if (ast[i]) {
                oRefs[literal(ast[i])][1] = group[1]
                oRefs[literal(ast[i])][2] = group[2]
            }
        if (match(i, "^0" SUBSEP "11" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "0" SUBSEP "([[:digit:]]+)$", group))
            oSynonyms[group[1]][group[2]][group[3]] = literal(ast[i])

        # 12 - (original) word classes and explanations
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oWordClasses[group[1]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oWords[group[1]][group[2]][0] = literal(ast[i])
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "1$", group))
            oWords[group[1]][group[2]][1] = literal(ast[i])
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2$", group))
            oWords[group[1]][group[2]][2] = postprocess(literal(ast[i]))

        # 13 - (original) examples
        if (match(i, "^0" SUBSEP "13" SUBSEP "0" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oExamples[group[1]] = postprocess(literal(ast[i]))

        # 14 - (original) see also
        if (match(i, "^0" SUBSEP "14" SUBSEP "0" SUBSEP "([[:digit:]]+)$", group))
            oSeeAlso[group[1]] = 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"] show(join(original), il) AnsiCode["no bold"]
        if (anything(oPhonetics))
            r = r "\n" AnsiCode["bold"] "/" join(oPhonetics) "/" AnsiCode["no bold"]
        r = r "\n"
        # Dictionary mode
        if (isarray(oWordClasses) && anything(oWordClasses)) {
            for (i = 0; i < length(oWordClasses); i++) {
                r = r "\n" AnsiCode["negative"] AnsiCode["bold"]        \
                    show(oWordClasses[i], hl) AnsiCode["no bold"] AnsiCode["positive"]
                if (isarray(oWords[i])) {
                    # Detailed explanation
                    for (j = 0; j < length(oWords[i]); j++) {
                        explanation = oWords[i][j][0]
                        ref = oWords[i][j][1]
                        example = oWords[i][j][2]

                        r = r "\n    " AnsiCode["bold"] show(explanation, il) AnsiCode["no bold"]
                        if (example)
                            r = r "\n    - \"" show(example, il) "\""
                        if (ref && isarray(oRefs[ref])) {
                            r = r "\n    * synonyms: " AnsiCode["underline"] \
                                show(oSynonyms[oRefs[ref][1]][oRefs[ref][2]][0], il) AnsiCode["no underline"]
                            for (k = 1; k < length(oSynonyms[oRefs[ref][1]][oRefs[ref][2]]); k++)
                                r = r ", " AnsiCode["underline"]        \
                                    show(oSynonyms[oRefs[ref][1]][oRefs[ref][2]][k], il) AnsiCode["no underline"]
                        }
                        r = r "\n"
                    }
                } else {
                    # Synonyms only
                    for (j = 0; j < length(oSynonyms[i]); j++) {
                        r = r "\n  * " show(oSynonyms[i][j][0], il)
                        for (k = 1; k < length(oSynonyms[i][j]); k++)
                            r = r ", " show(oSynonyms[i][j][k], il)
                    }
                    r = r "\n"
                }
            }
        }
        # Examples
        if (isarray(oExamples) && anything(oExamples)) {
            r = r "\n" "Examples:"
            for (i = 0; i < length(oExamples); i++) {
                example = oExamples[i]
                sub(/\u003cb\u003e/, AnsiCode["negative"], example)
                sub(/\u003c\/b\u003e/, AnsiCode["positive"], example)
                r = r "\n  - " show(example, il)
                r = r "\n"
            }
        }
        # See also
        if (isarray(oSeeAlso) && anything(oSeeAlso)) {
            r = r "\n" "See also:"
            r = r "\n" AnsiCode["underline"] show(oSeeAlso[0], il) AnsiCode["no underline"]
            for (k = 1; k < length(oSeeAlso); k++)
                r = r ", " AnsiCode["underline"] show(oSeeAlso[k], il) AnsiCode["no underline"]
            r = r "\n"
        }

        r = r "\n" 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)
    }
}


Fix for some variable names not being returned by get_model in fzn2py.awk

# translate flatzinc format to python/Numberjack

BEGIN {
	MAXINT = 10000000;
	print "#! /usr/bin/env python"
	print "import time"
	print "from Numberjack import *"
	system("cat " MZNNJ_DIR "/fzn2py.py");
	print "";
	print "def get_model():"
	print "    model = Model()";
	parameter = 1;
	error = 0;
}

# { print $0 } # for debugging purposes only

/::_output_var/ {
	gsub("::_output_var","");
	$0 = $0 " ::_output_";
}

/::_output_array/ {
	regexp = "::_output_array[(][[](range[(][0-9]+,1[+][0-9]+[)][ ,]*)+[]][)]";
	if (match($0,regexp)) {
	  array = substr($0, RSTART, RLENGTH);
	  sub(regexp,"");
	  gsub("[[]","",array);
	  gsub("[]]","",array);
	  gsub(" ","",array);
	  gsub("range[(]","",array);
	  gsub(",1[+]","..",array);
	  gsub("[)]","",array);
	  nbdim = array;
	  gsub("[^,]","",nbdim);
	  dim = length(nbdim)+1;
	  sub("::_output_array", "array" dim "d",array);
	  $0 = $0 " ::_output_" array;
	} else {
		print "ERROR WRONG OUTPUT ARRAY DEFINITION",$0;
		error = 3;
		exit(2);
	}
}

/^var / {
	parameter = 0;
}

/ of var / {
	parameter = 0;
}

parameter {
	sub(".*: ","");
	if ($2 == "=") print "    " $0; # it might be a predicate instead if no =
}

/^var bool:/{
	print "    " $3 " = Variable('" $3 "')";
	if (match($0,"::_output_")) output[$3] = 1;
	if ($4 == "=") print "    model.add(" $3 " == " $5 ")";
}

/^var int:/{
	print "    " $3 " = Variable(-" MAXINT "," MAXINT ",'" $3 "')";
	if (match($0,"::_output_")) output[$3] = 1;
	if ($4 == "=") print "    model.add(" $3 " == " $5 ")";
}

/^var range[(][-]*[0-9]+,1[+][-]*[0-9]+[)]:/{
	sub("range[(]","",$2);
	sub("[)]:","",$2);
	sub(",1[+]",",",$2);
	print "    " $3 " = Variable(" $2 ",'" $3 "')";
	if (match($0,"::_output_")) output[$3] = 1;
	if ($4 == "=") print "    model.add(" $3 " == " $5 ")";
}

/^var {[-]*[0-9]+(,[-]*[0-9]+)*}:/{
	sub("{","[",$2);
	sub("}:","]",$2);
	print "    " $3 " = Variable(" $2 ",'" $3 "')";
	if (match($0,"::_output_")) output[$3] = 1;
	if ($4 == "=") print "    model.add(" $3 " == " $5 ")";
}

/^array [[]range[(][-]*[0-9]+,1[+][-]*[0-9]+[)][]] of var bool:/{
	sub("[[]range[(]","",$2);
	sub("[)][]]","",$2);
	sub(",1[+]",",",$2);
	iinf = $2;
	sub(",.*","",iinf);
	if (iinf != 1) {
		print "ERROR ASSUMING MINIMUM INDEX VALUE EQUAL TO ONE IN ARRAY DEFINITION",$0;
		error = 1;
		exit(1);
	}
	isup = $2;
	sub(".*,","",isup);
	if (isup < iinf) {
		print "ERROR WRONG RANGE INDEX VALUE IN ARRAY DEFINITION",$0;
		error = 2;
		exit(2);
	}
	name = $6;
	print "    " name " = VarArray(" isup ",'" name "')";
	if (match($0,"::_output_")) {
		output[ name ] = isup;
		outputstring[ name ] = substr($0, RSTART+RLENGTH);
	}
	if ($7 == "=") {
		sub(".*= ","");
		sub("::_output_.*","");
		gsub(" ","");
		for (i=0; i<isup; i++) {
			print "    model.add(" name "[" i "] == " $0 "[" i "])";
		}
	}
}

/^array [[]range[(][-]*[0-9]+,1[+][-]*[0-9]+[)][]] of var int:/{
	sub("[[]range[(]","",$2);
	sub("[)][]]","",$2);
	sub(",1[+]",",",$2);
	iinf = $2;
	sub(",.*","",iinf);
	if (iinf != 1) {
		print "ERROR ASSUMING MINIMUM INDEX VALUE EQUAL TO ONE IN ARRAY DEFINITION",$0;
		error = 1;
		exit(1);
	}
	isup = $2;
	sub(".*,","",isup);
	if (isup < iinf) {
		print "ERROR WRONG RANGE INDEX VALUE IN ARRAY DEFINITION",$0;
		error = 2;
		exit(2);
	}
	name = $6;
	print "    " name " = VarArray(" isup ",-" MAXINT "," MAXINT ",'" name "')";
	if (match($0,"::_output_")) {
		output[ name ] = isup;
		outputstring[ name ] = substr($0, RSTART+RLENGTH);
	}
	if ($7 == "=") {
		sub(".*= ","");
		sub("::_output_.*","");
		gsub(" ","");
		for (i=0; i<isup; i++) {
			print "    model.add(" name "[" i "] == " $0 "[" i "])";
		}
	}
}

/^array [[]range[(][-]*[0-9]+,1[+][-]*[0-9]+[)][]] of var range[(][-]*[0-9]+,1[+][-]*[0-9]+[)]:/{
	sub("[[]range[(]","",$2);
	sub("[)][]]","",$2);
	sub(",1[+]",",",$2);
	iinf = $2;
	sub(",.*","",iinf);
	if (iinf != 1) {
		print "ERROR ASSUMING MINIMUM INDEX VALUE EQUAL TO ONE IN ARRAY DEFINITION",$0;
		error = 1;
		exit(1);
	}
	isup = $2;
	sub(".*,","",isup);
	if (isup < iinf) {
		print "ERROR WRONG RANGE INDEX VALUE IN ARRAY DEFINITION",$0;
		error = 2;
		exit(2);
	}
	sub("range[(]","",$5);
	sub("[)]:","",$5);
	sub(",1[+]",",",$5);
	name = $6;
	print "    " name " = VarArray(" isup "," $5 ",'" name "')";
	if (match($0,"::_output_")) {
		output[ name ] = isup;
		outputstring[ name ] = substr($0, RSTART+RLENGTH);
	}
	if ($7 == "=") {
		sub(".*= ","");
		sub("::_output_.*","");
		gsub(" ","");
		for (i=0; i<isup; i++) {
			print "    model.add(" name "[" i "] == " $0 "[" i "])";
		}
	}
}

/^array [[]range[(][-]*[0-9]+,1[+][-]*[0-9]+[)][]] of var {[-]*[0-9]+(,[-]*[0-9]+)*}:/{
	sub("[[]range[(]","",$2);
	sub("[)][]]","",$2);
	sub(",1[+]",",",$2);
	iinf = $2;
	sub(",.*","",iinf);
	if (iinf != 1) {
		print "ERROR ASSUMING MINIMUM INDEX VALUE EQUAL TO ONE IN ARRAY DEFINITION",$0;
		error = 1;
		exit(1);
	}
	isup = $2;
	sub(".*,","",isup);
	if (isup < iinf) {
		print "ERROR WRONG RANGE INDEX VALUE IN ARRAY DEFINITION",$0;
		error = 2;
		exit(2);
	}
	sub("{","[",$5);
	sub("}:","]",$5);
	name = $6;
	print "    " name " = [Variable(" $5 ",'" name "_" i "') for i in range(" isup ")]";
	if (match($0,"::_output_")) {
		output[ name ] = isup;
		outputstring[ name ] = substr($0, RSTART+RLENGTH);
	}
	if ($7 == "=") {
		sub(".*= ","");
		sub("::_output_.*","");
		gsub(" ","");
		for (i=0; i<isup; i++) {
			print "    model.add(" name "[" i "] == " $0 "[" i "])";
		}
	}
}

/^constraint /{
	$1 = "    model.add(";
    	$NF = $NF " )";
	print $0;
}

/^solve / && / minimize /{
	print "    model.add(Minimize(" $NF "))";
	objective = $NF;
}

/^solve / && / maximize /{
	print "    model.add(Maximize(" $NF "))";
	objective = $NF;
}

END {
	if (!error) {
	output_vars = "";
	if(objective) output_vars = objective;

	n = asorti(output,varnames);
	for (i=1; i<=n; i++) {
		e = varnames[i];
		if(e != objective) {
			if(length(output_vars) > 0) output_vars = output_vars ", ";
			output_vars = output_vars e;
		}
	}
	print "    output_vars = (" output_vars ",)";
	print "    return model, output_vars";

	print "";
	print "model, output_vars = get_model()";
	print output_vars " = output_vars";
	print "solvers = ['Mistral', 'SCIP', 'MiniSat', 'Toulbar2', 'Gurobi']";
	print "default = dict([('solver', 'Mistral'), ('verbose', 0), ('tcutoff', 900), ('var', 'DomainOverWDegree'), ('val', 'Lex'), ('rand', 2), ('threads', 1), ('restart', GEOMETRIC), ('base', 256), ('factor', 1.3)])";
	print "param = input(default)";
	print "solver = model.load(param['solver'])";
	print "solver.setVerbosity(param['verbose'])";
	print "solver.setTimeLimit(param['tcutoff'] - int(time.clock()+0.5))";
	print "solver.setHeuristic(param['var'], param['val'], param['rand'])";
	print "if param['solver'] == 'Gurobi':";
	print "    solver.setThreadCount(param['threads'])";
	print "if param['solver'] == 'Mistral':";
	print "    # LUBY, GEOMETRIC = 0, 1"
	print "    # print 'running %d %d %.1f' % (param['restart'], param['base'], param['factor'])";
	#print "    solver.solveAndRestart(GEOMETRIC, 256, 1.3)";
	print "    solver.solveAndRestart(param['restart'], param['base'], param['factor'])";
	print "else:";
	print "    solver.solve()";
	print "if solver.is_sat():"
	for (i=1; i<=n; i++) {
		e = varnames[i];
		if (output[e]==1) {
			if (e != objective && e != "objective" && e != "obj") print "    print '" e " = '," e ".get_value(),';'";
			else print "    print '" e " = ', (solver.getOptimum() if param['solver'] == 'Toulbar2' else " e ".get_value()),';'";
		} else {
			print "    print '" e " = " outputstring[e] ",',str(" e "),');'";
		}
	}
	print "    print '----------'";
	print "if solver.is_unsat():"
	print "    print '=====UNSATISFIABLE====='"
	if (objective){
		print "elif solver.is_opt():"
		print "    print '=========='"
	} else {
		print "elif solver.is_sat():"
		print "    print '=========='"
	}
        print "else:"
	print "    print '=====UNKNOWN====='"
	print "print '% SolveTime', solver.getTime()";
	print "print '% Nodes', solver.getNodes()";
	print "print '% Failures', solver.getFailures()";
	if (objective) print "if solver.is_sat(): print '% Objective', (solver.getOptimum() if param['solver'] == 'Toulbar2' else " objective ".get_value())";
	}
}

# translate flatzinc format to python/Numberjack

BEGIN {
	MAXINT = 10000000;
	print "#! /usr/bin/env python"
	print "import time"
	print "from Numberjack import *"
	system("cat " MZNNJ_DIR "/fzn2py.py");
	print "";
	print "def get_model():"
	print "    model = Model()";
	parameter = 1;
	error = 0;
}

# { print $0 } # for debugging purposes only

/::_output_var/ {
	gsub("::_output_var","");
	$0 = $0 " ::_output_";
}

/::_output_array/ {
	regexp = "::_output_array[(][[](range[(][0-9]+,1[+][0-9]+[)][ ,]*)+[]][)]";
	if (match($0,regexp)) {
	  array = substr($0, RSTART, RLENGTH);
	  sub(regexp,"");
	  gsub("[[]","",array);
	  gsub("[]]","",array);
	  gsub(" ","",array);
	  gsub("range[(]","",array);
	  gsub(",1[+]","..",array);
	  gsub("[)]","",array);
	  nbdim = array;
	  gsub("[^,]","",nbdim);
	  dim = length(nbdim)+1;
	  sub("::_output_array", "array" dim "d",array);
	  $0 = $0 " ::_output_" array;
	} else {
		print "ERROR WRONG OUTPUT ARRAY DEFINITION",$0;
		error = 3;
		exit(2);
	}
}

/^var / {
	parameter = 0;
}

/ of var / {
	parameter = 0;
}

parameter {
	sub(".*: ","");
	if ($2 == "=") print "    " $0; # it might be a predicate instead if no =
}

/^var bool:/{
	print "    " $3 " = Variable('" $3 "')";
	if (match($0,"::_output_")) output[$3] = 1;
	if ($4 == "=") print "    model.add(" $3 " == " $5 ")";
}

/^var int:/{
	print "    " $3 " = Variable(-" MAXINT "," MAXINT ",'" $3 "')";
	if (match($0,"::_output_")) output[$3] = 1;
	if ($4 == "=") print "    model.add(" $3 " == " $5 ")";
}

/^var range[(][-]*[0-9]+,1[+][-]*[0-9]+[)]:/{
	sub("range[(]","",$2);
	sub("[)]:","",$2);
	sub(",1[+]",",",$2);
	print "    " $3 " = Variable(" $2 ",'" $3 "')";
	if (match($0,"::_output_")) output[$3] = 1;
	if ($4 == "=") print "    model.add(" $3 " == " $5 ")";
}

/^var {[-]*[0-9]+(,[-]*[0-9]+)*}:/{
	sub("{","[",$2);
	sub("}:","]",$2);
	print "    " $3 " = Variable(" $2 ",'" $3 "')";
	if (match($0,"::_output_")) output[$3] = 1;
	if ($4 == "=") print "    model.add(" $3 " == " $5 ")";
}

/^array [[]range[(][-]*[0-9]+,1[+][-]*[0-9]+[)][]] of var bool:/{
	sub("[[]range[(]","",$2);
	sub("[)][]]","",$2);
	sub(",1[+]",",",$2);
	iinf = $2;
	sub(",.*","",iinf);
	if (iinf != 1) {
		print "ERROR ASSUMING MINIMUM INDEX VALUE EQUAL TO ONE IN ARRAY DEFINITION",$0;
		error = 1;
		exit(1);
	}
	isup = $2;
	sub(".*,","",isup);
	if (isup < iinf) {
		print "ERROR WRONG RANGE INDEX VALUE IN ARRAY DEFINITION",$0;
		error = 2;
		exit(2);
	}
	name = $6;
	print "    " name " = VarArray(" isup ",'" name "')";
	if (match($0,"::_output_")) {
		output[ name ] = isup;
		outputstring[ name ] = substr($0, RSTART+RLENGTH);
	}
	if ($7 == "=") {
		sub(".*= ","");
		sub("::_output_.*","");
		gsub(" ","");
		for (i=0; i<isup; i++) {
			print "    model.add(" name "[" i "] == " $0 "[" i "])";
		}
	}
}

/^array [[]range[(][-]*[0-9]+,1[+][-]*[0-9]+[)][]] of var int:/{
	sub("[[]range[(]","",$2);
	sub("[)][]]","",$2);
	sub(",1[+]",",",$2);
	iinf = $2;
	sub(",.*","",iinf);
	if (iinf != 1) {
		print "ERROR ASSUMING MINIMUM INDEX VALUE EQUAL TO ONE IN ARRAY DEFINITION",$0;
		error = 1;
		exit(1);
	}
	isup = $2;
	sub(".*,","",isup);
	if (isup < iinf) {
		print "ERROR WRONG RANGE INDEX VALUE IN ARRAY DEFINITION",$0;
		error = 2;
		exit(2);
	}
	name = $6;
	print "    " name " = VarArray(" isup ",-" MAXINT "," MAXINT ",'" name "')";
	if (match($0,"::_output_")) {
		output[ name ] = isup;
		outputstring[ name ] = substr($0, RSTART+RLENGTH);
	}
	if ($7 == "=") {
		sub(".*= ","");
		sub("::_output_.*","");
		gsub(" ","");
		for (i=0; i<isup; i++) {
			print "    model.add(" name "[" i "] == " $0 "[" i "])";
		}
	}
}

/^array [[]range[(][-]*[0-9]+,1[+][-]*[0-9]+[)][]] of var range[(][-]*[0-9]+,1[+][-]*[0-9]+[)]:/{
	sub("[[]range[(]","",$2);
	sub("[)][]]","",$2);
	sub(",1[+]",",",$2);
	iinf = $2;
	sub(",.*","",iinf);
	if (iinf != 1) {
		print "ERROR ASSUMING MINIMUM INDEX VALUE EQUAL TO ONE IN ARRAY DEFINITION",$0;
		error = 1;
		exit(1);
	}
	isup = $2;
	sub(".*,","",isup);
	if (isup < iinf) {
		print "ERROR WRONG RANGE INDEX VALUE IN ARRAY DEFINITION",$0;
		error = 2;
		exit(2);
	}
	sub("range[(]","",$5);
	sub("[)]:","",$5);
	sub(",1[+]",",",$5);
	name = $6;
	print "    " name " = VarArray(" isup "," $5 ",'" name "')";
	if (match($0,"::_output_")) {
		output[ name ] = isup;
		outputstring[ name ] = substr($0, RSTART+RLENGTH);
	}
	if ($7 == "=") {
		sub(".*= ","");
		sub("::_output_.*","");
		gsub(" ","");
		for (i=0; i<isup; i++) {
			print "    model.add(" name "[" i "] == " $0 "[" i "])";
		}
	}
}

/^array [[]range[(][-]*[0-9]+,1[+][-]*[0-9]+[)][]] of var {[-]*[0-9]+(,[-]*[0-9]+)*}:/{
	sub("[[]range[(]","",$2);
	sub("[)][]]","",$2);
	sub(",1[+]",",",$2);
	iinf = $2;
	sub(",.*","",iinf);
	if (iinf != 1) {
		print "ERROR ASSUMING MINIMUM INDEX VALUE EQUAL TO ONE IN ARRAY DEFINITION",$0;
		error = 1;
		exit(1);
	}
	isup = $2;
	sub(".*,","",isup);
	if (isup < iinf) {
		print "ERROR WRONG RANGE INDEX VALUE IN ARRAY DEFINITION",$0;
		error = 2;
		exit(2);
	}
	sub("{","[",$5);
	sub("}:","]",$5);
	name = $6;
	print "    " name " = [Variable(" $5 ",'" name "_" i "') for i in range(" isup ")]";
	if (match($0,"::_output_")) {
		output[ name ] = isup;
		outputstring[ name ] = substr($0, RSTART+RLENGTH);
	}
	if ($7 == "=") {
		sub(".*= ","");
		sub("::_output_.*","");
		gsub(" ","");
		for (i=0; i<isup; i++) {
			print "    model.add(" name "[" i "] == " $0 "[" i "])";
		}
	}
}

/^constraint /{
	$1 = "    model.add(";
    	$NF = $NF " )";
	print $0;
}

/^solve / && / minimize /{
	print "    model.add(Minimize(" $NF "))";
	objective = $NF;
}

/^solve / && / maximize /{
	print "    model.add(Maximize(" $NF "))";
	objective = $NF;
}

END {
	if (!error) {
	output_vars = "";
	if(objective) output_vars = objective;

	n = asorti(output,varnames);
	for (i=1; i<=n; i++) {
		e = varnames[i];
		if (output[e]!=1 || e == "objective") {
			if(e != objective) {
				if(length(output_vars) > 0) output_vars = output_vars ", ";
				output_vars = output_vars e;
			}
		}
	}
	print "    output_vars = (" output_vars ",)";
	print "    return model, output_vars";

	print "";
	print "model, output_vars = get_model()";
	print output_vars " = output_vars";
	print "solvers = ['Mistral', 'SCIP', 'MiniSat', 'Toulbar2', 'Gurobi']";
	print "default = dict([('solver', 'Mistral'), ('verbose', 0), ('tcutoff', 900), ('var', 'DomainOverWDegree'), ('val', 'Lex'), ('rand', 2), ('threads', 1), ('restart', GEOMETRIC), ('base', 256), ('factor', 1.3)])";
	print "param = input(default)";
	print "solver = model.load(param['solver'])";
	print "solver.setVerbosity(param['verbose'])";
	print "solver.setTimeLimit(param['tcutoff'] - int(time.clock()+0.5))";
	print "solver.setHeuristic(param['var'], param['val'], param['rand'])";
	print "if param['solver'] == 'Gurobi':";
	print "    solver.setThreadCount(param['threads'])";
	print "if param['solver'] == 'Mistral':";
	print "    # LUBY, GEOMETRIC = 0, 1"
	print "    # print 'running %d %d %.1f' % (param['restart'], param['base'], param['factor'])";
	#print "    solver.solveAndRestart(GEOMETRIC, 256, 1.3)";
	print "    solver.solveAndRestart(param['restart'], param['base'], param['factor'])";
	print "else:";
	print "    solver.solve()";
	print "if solver.is_sat():"
	for (i=1; i<=n; i++) {
		e = varnames[i];
		if (output[e]==1) {
			if (e != objective && e != "objective" && e != "obj") print "    print '" e " = '," e ".get_value(),';'";
			else print "    print '" e " = ', (solver.getOptimum() if param['solver'] == 'Toulbar2' else " e ".get_value()),';'";
		} else {
			print "    print '" e " = " outputstring[e] ",',str(" e "),');'";
		}
	}
	print "    print '----------'";
	print "if solver.is_unsat():"
	print "    print '=====UNSATISFIABLE====='"
	if (objective){
		print "elif solver.is_opt():"
		print "    print '=========='"
	} else {
		print "elif solver.is_sat():"
		print "    print '=========='"
	}
        print "else:"
	print "    print '=====UNKNOWN====='"
	print "print '% SolveTime', solver.getTime()";
	print "print '% Nodes', solver.getNodes()";
	print "print '% Failures', solver.getFailures()";
	if (objective) print "if solver.is_sat(): print '% Objective', (solver.getOptimum() if param['solver'] == 'Toulbar2' else " objective ".get_value())";
	}
}


+ Make condor_config more VM Universe aware

##**************************************************************
##
## Copyright (C) 1990-2007, Condor Team, Computer Sciences Department,
## University of Wisconsin-Madison, WI.
##
## Licensed under the Apache License, Version 2.0 (the "License"); you
## may not use this file except in compliance with the License.  You may
## obtain a copy of the License at
##
##    http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
##
##**************************************************************

##
## convert_config_to_win32.awk
##
## Todd Tannenbaum <tannenba@cs.wisc.edu>, 10/06
##

# This is an awk script to modify condor_config.generic into a form that
# works with the InstallShield Windows installer.  The packager on Win32
# will pipe condor_config.generic through this awk script and save it as
# condor_config.orig, which is what the InstallShield script expects to
# modify after unbundling.

BEGIN {
	FS = "="
}

# Make it so we can fill in the VM_GAHP_SERVER option if we choose to in the
# installer. This is done before the .exe append bellow, because that match
# seems to block any other matches for that line.
/^#VM_GAHP_SERVER/ {
	print "#VM_GAHP_SERVER = $(BIN)/condor_vm-gahp.exe"
	print "VM_GAHP_SERVER = "
	next
}

# Add a .exe to the end of all condor daemon names
/BIN)\/condor_/ {
	printf "%s.exe\n",$0
	next
}

# Email Settings
# Use condor_mail.exe for MAIL, and put in a stub for SMTP_SERVER
/^MAIL/ {
	printf "MAIL = $(BIN)/condor_mail.exe\n\n"
	printf "## For Condor on Win32 we need to specify an SMTP server so\n"
	printf "## that Condor is able to send email.\n"
	printf "SMTP_SERVER =\n\n"
	next
}
/^SMTP_SERVER/ {
	next
}

# Specify $(FULL_HOSTNAME) as the sample commented-out entry for
# FILESYSTEM_DOMAIN and UID_DOMAIN, as this is what the installer expects.
/^UID_DOMAIN/ || /^FILESYSTEM_DOMAIN/ {
	printf "#%s= $(FULL_HOSTNAME)\n", $1
	next
}

# Have $(SBIN) point to $(BIN)
/^SBIN/ {
	printf "%s= $(BIN)\n", $1
	next
}

# QUEUE_SUPER_USERS should be condor and SYSTEM, not root! :)
/^QUEUE_SUPER_USERS/ {
	printf "%s= condor, SYSTEM\n", $1
	next
}

# JOB_RENICE_INCREMENT must be 10 or load average will be messed up
# Be careful, it may be commented out in condor_config.generic.
/^.*JOB_RENICE_INCREMENT/ {
	printf "JOB_RENICE_INCREMENT = 10\n"
	next
}

# Clean out the initial guess of /usr/bin/java for JAVA... ;)
# Note the space after JAVA below, so we do not change other JAVA settings.
/^JAVA / {
	printf "JAVA =\n"
	next
}

# Win32 has a path separator of ';', while on Unix it is ':'
/^JAVA_CLASSPATH_SEPARATOR/ {
	printf "%s= ;\n", $1
	next
}

# There's no reasonable default place to put log files for
# daemons that run as a user (on Unix we use /tmp). Use NUL.
/(^C_GAHP_LOG)|(^C_GAHP_LOCK)|(^C_GAHP_WORKER_THREAD_LOG)|(^C_GAHP_WORKER_THREAD_LOCK)|(^VM_GAHP_LOG)/ {
	printf "%s = NUL\n", $1
	next
}

# PROCD_ADDRESS needs to refer to a valid name for a Windows named
# pipe
/^PROCD_ADDRESS/ {
	print "PROCD_ADDRESS = \\\\.\\pipe\\condor_procd_pipe"
	next
}

# Make it so we can fill in the VM GAHP options if we choose to in the
# installer.
/^#VM_GAHP_CONFIG/ {
	print "#VM_GAHP_CONFIG = $(RELEASE_DIR)/condor_vmgahp_config.vmware"
	print "VM_GAHP_CONFIG ="
	next
}
/^#VM_TYPE/ {
	print "#VM_TYPE = vmware"
	print "VM_TYPE ="
	next
}

# If we made it here, print out the line unchanged
{print $0}

##**************************************************************
##
## Copyright (C) 1990-2007, Condor Team, Computer Sciences Department,
## University of Wisconsin-Madison, WI.
##
## Licensed under the Apache License, Version 2.0 (the "License"); you
## may not use this file except in compliance with the License.  You may
## obtain a copy of the License at
##
##    http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
##
##**************************************************************

##
## convert_config_to_win32.awk
##
## Todd Tannenbaum <tannenba@cs.wisc.edu>, 10/06
##

# This is an awk script to modify condor_config.generic into a form that
# works with the InstallShield Windows installer.  The packager on Win32
# will pipe condor_config.generic through this awk script and save it as
# condor_config.orig, which is what the InstallShield script expects to
# modify after unbundling.

BEGIN {
	FS = "="
}

# Add a .exe to the end of all condor daemon names
/BIN)\/condor_/ {
	printf "%s.exe\n",$0
	next
}

# Email Settings
# Use condor_mail.exe for MAIL, and put in a stub for SMTP_SERVER
/^MAIL/ {
	printf "MAIL = $(BIN)/condor_mail.exe\n\n"
	printf "## For Condor on Win32 we need to specify an SMTP server so\n"
	printf "## that Condor is able to send email.\n"
	printf "SMTP_SERVER =\n\n"
	next
}
/^SMTP_SERVER/ {
	next
}

# Specify $(FULL_HOSTNAME) as the sample commented-out entry for
# FILESYSTEM_DOMAIN and UID_DOMAIN, as this is what the installer expects.
/^UID_DOMAIN/ || /^FILESYSTEM_DOMAIN/ {
	printf "#%s= $(FULL_HOSTNAME)\n", $1
	next
}

# Have $(SBIN) point to $(BIN)
/^SBIN/ {
	printf "%s= $(BIN)\n", $1
	next
}

# QUEUE_SUPER_USERS should be condor and SYSTEM, not root! :)
/^QUEUE_SUPER_USERS/ {
	printf "%s= condor, SYSTEM\n", $1
	next
}

# JOB_RENICE_INCREMENT must be 10 or load average will be messed up
# Be careful, it may be commented out in condor_config.generic.
/^.*JOB_RENICE_INCREMENT/ {
	printf "JOB_RENICE_INCREMENT = 10\n"
	next
}

# Clean out the initial guess of /usr/bin/java for JAVA... ;)
# Note the space after JAVA below, so we do not change other JAVA settings.
/^JAVA / {
	printf "JAVA =\n"
	next
}

# Win32 has a path separator of ';', while on Unix it is ':'
/^JAVA_CLASSPATH_SEPARATOR/ {
	printf "%s= ;\n", $1
	next
}

# There's no reasonable default place to put log files for
# daemons that run as a user (on Unix we use /tmp). Use NUL.
/(^C_GAHP_LOG)|(^C_GAHP_LOCK)|(^C_GAHP_WORKER_THREAD_LOG)|(^C_GAHP_WORKER_THREAD_LOCK)|(^VM_GAHP_LOG)/ {
	printf "%s = NUL\n", $1
	next
}

# PROCD_ADDRESS needs to refer to a valid name for a Windows named
# pipe
/^PROCD_ADDRESS/ {
	print "PROCD_ADDRESS = \\\\.\\pipe\\condor_procd_pipe"
	next
}

# If we made it here, print out the line unchanged
{print $0}


rnc2rng.awk: Handle RELAX include directive

#!/bin/awk
# -*- indent-tabs-mode: nil; c-basic-offset: 2 -*-
# rnc2rng.awk - Convert RELAX NG Compact Syntax to XML Syntax
# Copyright (C) 2007 Shaun McCance <shaunm@gnome.org>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

# This program is free software, but that doesn't mean you should use it.
# I wanted to write and maintain the Mallard schema in RNC in code blocks
# within the specification, but I needed to distribute the schema in RNG.
# Since xmllint (libxml2) does not currently support RNC, and since I'm
# not willing to introduce more dependencies, awk was my only option.
#
# This awk script is *NOT* a complete implementation, and probably never
# will be.  Its sole purpose is to handle the Mallard schema, and it is
# sufficient for that task.
#
# You are, of course, free to use any of this.  If you do proliferate this
# hack, it is requested (though not required, that would be non-free) that
# you atone for your actions.  A good atonement would be contributing to
# free software.

function parse_pattern (line) {
  sub(/^ */, "", line)
  if (length(line) == 0) return;
  c = substr(line, 1, 1);
  if (c == "(" || c == "{") {
    stack[++stack_i] = substr(line, 1, 1);
    paren[++paren_i] = stack_i;
    if (length(line) >= 2)
      parse_pattern(substr(line, 2));
  }
  else if (c == ")" || c == "}") {
    open = stack[paren[paren_i]];
    oc = substr(open, 1, 1) c;
    if (oc != "()" && oc != "{}") {
      print "Mismatched parentheses on line " FNR | "cat 1>&2";
      error = 1;
      exit 1
    }

    tag = "";
    if (length(open) > 1 && substr(open, 2, 1) == "&") {
      tag = "interleave";
    }
    else if (length(open) > 1 && substr(open, 2, 1) == "|") {
      tag = "choice";
    }
    else if (oc == "()") {
      tag = "group";
    }

    tmp = "";
    if (tag != "") {
      tmp = "<" tag ">";
    }
    for (i = paren[paren_i] + 1; i <= stack_i; i++) {
      tmp = tmp stack[i] "\n";
    }
    if (tag != "") {
      tmp = tmp "</" tag ">";
    }
    stack_i = paren[paren_i];
    stack[stack_i] = tmp;
    paren_i--;

    if (oc == "{}") {
      if (match(stack[stack_i - 1], "^<element")) {
        tmp = stack[stack_i - 1] "\n";
        if (stack[stack_i] != "") {
          tmp = tmp stack[stack_i] "\n";
        } else {
          tmp = tmp "<empty/>\n";
        }
        tmp = tmp "</element>";
        stack[--stack_i] = tmp;
      }
      else if (match(stack[stack_i - 1], "^<attribute")) {
        tmp = stack[stack_i - 1] "\n";
        if (stack[stack_i] != "") {
          tmp = tmp stack[stack_i] "\n";
        } else {
          tmp = tmp "<empty/>\n";
        }
        tmp = tmp "</attribute>";
        stack[--stack_i] = tmp;
      }
      else if (stack[stack_i - 1] == "<list>") {
        tmp = stack[stack_i - 1] "\n";
        tmp = tmp stack[stack_i] "\n";
        tmp = tmp "</list>";
        stack[--stack_i] = tmp;
      }
      else if (stack[stack_i - 1] == "<mixed>") {
        tmp = stack[stack_i - 1] "\n";
        tmp = tmp stack[stack_i] "\n";
        tmp = tmp "</mixed>";
        stack[--stack_i] = tmp;
      }
    }
    if (paren_i == 0) {
      mode = "grammar";
    }
    if (length(line) >= 2)
      parse_pattern(substr(line, 2));
  }
  else if (c == "|" || c == "&" || c == ",") {
    if (length(stack[paren[paren_i]]) == 1) {
      stack[paren[paren_i]] = stack[paren[paren_i]] c;
    }
    else if (length(stack[paren[paren_i]]) < 2 || substr(stack[paren[paren_i]], 2) != c) {
      print "Mismatched infix operators on line " FNR | "cat 1>&2";
      error = 1;
      exit 1
    }
    if (length(line) >= 2)
      parse_pattern(substr(line, 2));
  }
  else if (c == "?") {
    stack[stack_i] = "<optional>" stack[stack_i] "</optional>"
    if (length(line) >= 2)
      parse_pattern(substr(line, 2));
  }
  else if (c == "*") {
    stack[stack_i] = "<zeroOrMore>" stack[stack_i] "</zeroOrMore>"
    if (length(line) >= 2)
      parse_pattern(substr(line, 2));
  }
  else if (c == "+") {
    stack[stack_i] = "<oneOrMore>" stack[stack_i] "</oneOrMore>"
    if (length(line) >= 2)
      parse_pattern(substr(line, 2));
  }
  else if (c == "\"") {
    txt = substr(line, 2);
    sub(/".*/, "", txt)
    stack[++stack_i] = "<value>" txt "</value>";
    if (length(line) >= length(txt) + 3)
      parse_pattern(substr(line, length(txt) + 3));
  }
  else if (match(line, "^element ")) {
    aft = substr(line, 8);
    sub(/^ */, "", aft);
    name = aft;
    stack[++stack_i] = "<element";
    mode = "name_class";
    name_class_i = stack_i;
    parse_name_class(name);
  }
  else if (match(line, "^attribute ")) {
    aft = substr(line, 10);
    sub(/^ */, "", aft);
    name = aft;
    stack[++stack_i] = "<attribute";
    mode = "name_class";
    name_class_i = stack_i;
    parse_name_class(name);
  }
  else if (match(line, "^list ")) {
    aft = substr(line, 5);
    sub(/^ */, "", aft);
    stack[++stack_i] = "<list>";
    if (aft != "") { parse_pattern(aft); }
  }
  else if (match(line, "^mixed ")) {
    aft = substr(line, 6);
    sub(/^ */, "", aft);
    stack[++stack_i] = "<mixed>";
    if (aft != "") { parse_pattern(aft); }
  }
  else if (match(line, /^text[^A-Za-z]/) || match(line, /^text$/)) {
    stack[++stack_i] = "<text/>";
    aft = substr(line, 5);
    sub(/^ */, "", aft);
    if (aft != "") { parse_pattern(aft); }
  }
  else if (match(line, "^default namespace ")) {
    print "default namespace appeared out of context on line " FNR | "cat 1>&2";
    error = 1;
    exit 1
  }
  else if (match(line, "^namespace ")) {
    print "namespace appeared out of context on line " FNR | "cat 1>&2";
    error = 1;
    exit 1
  }
  else if (match(line, "^start ")) {
    print "start appeared out of context on line " FNR | "cat 1>&2";
    error = 1;
    exit 1
  }
  else if (match(line, "^include ")) {
    print "include appeared out of context on line " FNR | "cat 1>&2";
    error = 1;
    exit 1
  }
  else if (match(line, /^xsd:[A-Za-z_]/)) {
    name = substr(line, 1);
    sub(/^xsd:/, "", name);
    sub(/[^A-Za-z_]+.*/, "", name);
    stack[++stack_i] = sprintf("<data type='%s' datatypeLibrary='http://www.w3.org/2001/XMLSchema-datatypes'/>",
                               name);
    if (length(line) >= length(name) + 5) {
      aft = substr(line, length(name) + 5);
      parse_pattern(aft);
    }
  }
  else if (match(line, /^[A-Za-z_]/)) {
    name = substr(line, 1);
    sub(/[^A-Za-z_]+.*/, "", name);
    if (name == "empty") {
      stack[++stack_i] = "<empty/>";
    }
    else if (name == "notAllowed") {
      stack[++stack_i] = "<notAllowed/>";
    }
    else {
      stack[++stack_i] = sprintf("<ref name='%s'/>", name);
    }
    if (length(line) >= length(name) + 1) {
      aft = substr(line, length(name) + 1);
      parse_pattern(aft);
    }
  }
}

function parse_name_class (line) {
  sub(/^ */, "", line)
  if (length(line) == 0) return;
  c = substr(line, 1, 1);
  if (c == "{") {
    if (stack_i != name_class_i) {
      tmp = "";
      for (i = stack_i; i >= name_class_i; i--) {
        if (stack[i] == "<except>") {
          tmp = "<except>" tmp "</except>";
        }
        else if (stack[i] == "<anyName>") {
          tmp = "<anyName>" tmp "</anyName>";
        }
        else {
          tmp = stack[i] tmp
        }
      }
      stack[name_class_i] = tmp;
      stack_i = name_class_i;
    }
    mode = "pattern";
    parse_pattern(line);
  }
  else if (c == "*") {
    if (stack[stack_i] == "<element" || stack[stack_i] == "<attribute") {
      stack[stack_i] = stack[stack_i] ">";
    }
    stack[++stack_i] = "<anyName>";
    parse_name_class(substr(line, 2));
  }
  else if (c == "-") {
    stack[++stack_i] = "<except>"
    parse_name_class(substr(line, 2));
  }
  else if (c == "|") {
    if (length(stack[paren[paren_i]]) == 1) {
      stack[paren[paren_i]] = stack[paren[paren_i]] c;
    }
    else if (substr(stack[paren[paren_i]], 2) != "|") {
      print "Mismatched infix operators on line " FNR | "cat 1>&2";
      error = 1;
      exit 1
    }
    parse_name_class(substr(line, 2));
  }
  else if (c == "(") {
    stack[++stack_i] = "(";
    paren[++paren_i] = stack_i;
    parse_name_class(substr(line, 2));
  }
  else if (c == ")") {
    open = stack[paren[paren_i]];
    if (substr(open, 1, 1) != "(") {
      print "Mismatched parentheses on line " FNR | "cat 1>&2";
      error = 1;
      exit 1
    }
    if (length(open) == 1 || substr(open, 2, 1) != "|") {
      print "Unknown name class pattern on line " FNR | "cat 1>&2";
      error = 1;
      exit 1
    }
    tmp = ""
    for (i = paren[paren_i] + 1; i <= stack_i; i++) {
      tmp = tmp stack[i] "\n";
    }
    stack_i = paren[paren_i];
    stack[stack_i] = tmp;
    paren_i--;
    parse_name_class(substr(line, 2));
  }
  else if (match(line, /^[A-Za-z_]/)) {
    name = substr(line, 1);
    sub(/[^A-Za-z_]+.*/, "", name);
    if (length(line) >= length(name) + 1)
      aft = substr(line, length(name) + 1);
    else
      aft = "";
    if (length(aft) >= 2 && substr(aft, 1, 2) == ":*") {
      for (i = 1; i <= namespaces_i; i++) {
        if (nsnames[i] == name) {
          stack[++stack_i] = sprintf("<nsName ns='%s'/>", namespaces[i])
          break;
        }
      }
      if (length(aft) >= 3)
        aft = substr(aft, 3);
      else
        aft = "";
    }
    else {
      if (length(aft) >= 2 && substr(aft, 1, 1) == ":") {
        checkns = "";
        pos = 1;
        while (pos <= namespaces_i) {
          if (namespaces[pos] != "" && nsnames[pos] == name) {
            checkns = namespaces[pos];
            break;
          }
          pos++;
        }
        if (checkns == "") {
          print "Unknown namespace prefix " name " on line " FNR | "cat 1>&2";
          error = 1;
          exit 1
        }
        localname = substr(aft, 2);
        sub(/[^A-Za-z_]+.*/, "", localname);
        if (length(aft) >= length(localname) + 2)
          aft = substr(aft, length(localname) + 2);
        else
          aft = "";
        name = name ":" localname;
      }
      if (stack[stack_i] == "<element" || stack[stack_i] == "<attribute") {
        stack[stack_i] = stack[stack_i] sprintf(" name='%s'>", name);
      }
      else {
        stack[++stack_i] = sprintf("<name>%s</name>", name);
      }
    }
    parse_name_class(aft);
  }
}

function printstack () {
  pos = 1;
  while (pos <= stack_i) {
    printstackone();
  }
}
function printstackone () {
  if (substr(stack[pos], 1, 1) == "#") {
    print "<!--";
    while (substr(stack[pos], 1, 1) == "#") {
      cmt = substr(stack[pos], 2);
      sub(/^ */, "", cmt);
      print cmt;
      pos++;
    }
    print "-->";
  }
  if (substr(stack[pos], 1, 6) == "<start") {
    print stack[pos];
    pos++;
    printstackone();
    print "</start>"
  }
  else if (substr(stack[pos], 1, 7) == "<define") {
    print stack[pos];
    pos++;
    printstackone();
    print "</define>"
  }
  else {
    print stack[pos];
    pos++;
  }
}

BEGIN {
  mode = "grammar";
  stack_i = 0;
  paren_i = 0;
  namespaces_i = 0;
  include_i = 0;
  error = 0;
}

END {
  if (!error) {
    print "<grammar xmlns='http://relaxng.org/ns/structure/1.0'";
    pos = 1;
    while (pos <= namespaces_i) {
      if (namespaces[pos] != "") {
        printf " xmlns:%s='%s'", nsnames[pos], namespaces[pos];
      }
      pos++;
    }
    printf " ns='%s'>\n", default_namespace;
    printstack()
    print "</grammar>"
  }
}

mode == "pattern" && paren_i == 0 && /.*=/ { mode = "grammar"; }
mode == "grammar" && /.*=/ {
  if (match($0, "^default namespace ")) {
    namespace = substr($0, index($0, "=") + 1);
    nsname = substr($0, 19, index($0, "=") - 19);
    sub(/^ */, "", nsname);
    sub(/ *$/, "", nsname);
    sub(/^ *"/, "", namespace);
    sub(/" *$/, "", namespace);
    if (nsname != "") {
      nsnames[++namespaces_i] = nsname;
      namespaces[namespaces_i] = namespace;
    }
    default_namespace = namespace
  }
  else if (match($0, "^namespace ")) {
    namespace = substr($0, index($0, "=") + 1);
    nsname = substr($0, 11, index($0, "=") - 11);
    sub(/^ */, "", nsname);
    sub(/ *$/, "", nsname);
    sub(/^ *"/, "", namespace);
    sub(/" *$/, "", namespace);
    if (nsname != "") {
      nsnames[++namespaces_i] = nsname;
      namespaces[namespaces_i] = namespace;
    }
  }
  else {
    nameix = index($0, "=");
    combine = "";
    if (nameix < 1) next;
    if (substr($0, nameix - 1, 1) == "|") {
      combine = "choice";
      name = substr($0, 1, nameix - 2);
    }
    else if (substr($0, nameix - 1, 1) == "&") {
      combine = "interleave";
      name = substr($0, 1, nameix - 2);
    }
    else {
      name = substr($0, 1, nameix - 1);
    }
    sub(/^ */, "", name);
    sub(/ *$/, "", name);
    if (name == "start")
      stack[++stack_i] = "<start>"
    else {
      define = sprintf("<define name='%s'", name);
      if (combine != "")
        define = define " combine='" combine "'"
      define = define ">"
      stack[++stack_i] = define;
    }
    mode = "pattern";
    if (length($0) >= nameix + 1)
      parse_pattern(substr($0, nameix + 1))
  }
  next;
}
mode == "grammar" && /^include / {
  href = substr($0, index($0, "\"") + 1);
  brace = substr(href, index(href, "\"") + 1);
  href = substr(href, 1, index(href, "\"") - 1);
  sub(/^ */, "", brace);
  sub(/ *$/, "", brace);
  if (brace == "{") {
    stack[++stack_i] = "<include href=\"" href "\">";
    include_i = stack_i;
  }
  else {
    stack[++stack_i] = "<include href=\"" href "\"/>";
  }
}
include_i != 0 && paren_i == 0 && /}/ {
  if (substr(stack[include_i], 1, 8) == "<include") {
    stack[++stack_i] = "</include>";
  }
  else {
    print "Mismatched parentheses on line " FNR | "cat 1>&2";
    error = 1;
    exit 1
  }
  mode = "grammar";
  include_i = 0;
  next;
}
mode == "pattern" {
  parse_pattern($0);
  next;
}
mode == "name_class" {
  parse_name_class($0);
  next;
}
# Doesn't handle all comments. That would require modifying
# parse_pattern and parse_name_class and generally being a lot
# more clever about output. But it handles comments that start
# a line outside a pattern. Enough for me.
/#.*/ {
  stack[++stack_i] = $0
}

#!/bin/awk
# -*- indent-tabs-mode: nil; c-basic-offset: 2 -*-
# rnc2rng.awk - Convert RELAX NG Compact Syntax to XML Syntax
# Copyright (C) 2007 Shaun McCance <shaunm@gnome.org>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

# This program is free software, but that doesn't mean you should use it.
# I wanted to write and maintain the Mallard schema in RNC in code blocks
# within the specification, but I needed to distribute the schema in RNG.
# Since xmllint (libxml2) does not currently support RNC, and since I'm
# not willing to introduce more dependencies, awk was my only option.
#
# This awk script is *NOT* a complete implementation, and probably never
# will be.  Its sole purpose is to handle the Mallard schema, and it is
# sufficient for that task.
#
# You are, of course, free to use any of this.  If you do proliferate this
# hack, it is requested (though not required, that would be non-free) that
# you atone for your actions.  A good atonement would be contributing to
# free software.

function parse_pattern (line) {
  sub(/^ */, "", line)
  if (length(line) == 0) return;
  c = substr(line, 1, 1);
  if (c == "(" || c == "{") {
    stack[++stack_i] = substr(line, 1, 1);
    paren[++paren_i] = stack_i;
    if (length(line) >= 2)
      parse_pattern(substr(line, 2));
  }
  else if (c == ")" || c == "}") {
    open = stack[paren[paren_i]];
    oc = substr(open, 1, 1) c;
    if (oc != "()" && oc != "{}") {
      print "Mismatched parentheses on line " FNR | "cat 1>&2";
      error = 1;
      exit 1
    }

    tag = "";
    if (length(open) > 1 && substr(open, 2, 1) == "&") {
      tag = "interleave";
    }
    else if (length(open) > 1 && substr(open, 2, 1) == "|") {
      tag = "choice";
    }
    else if (oc == "()") {
      tag = "group";
    }

    tmp = "";
    if (tag != "") {
      tmp = "<" tag ">";
    }
    for (i = paren[paren_i] + 1; i <= stack_i; i++) {
      tmp = tmp stack[i] "\n";
    }
    if (tag != "") {
      tmp = tmp "</" tag ">";
    }
    stack_i = paren[paren_i];
    stack[stack_i] = tmp;
    paren_i--;

    if (oc == "{}") {
      if (match(stack[stack_i - 1], "^<element")) {
        tmp = stack[stack_i - 1] "\n";
        if (stack[stack_i] != "") {
          tmp = tmp stack[stack_i] "\n";
        } else {
          tmp = tmp "<empty/>\n";
        }
        tmp = tmp "</element>";
        stack[--stack_i] = tmp;
      }
      else if (match(stack[stack_i - 1], "^<attribute")) {
        tmp = stack[stack_i - 1] "\n";
        if (stack[stack_i] != "") {
          tmp = tmp stack[stack_i] "\n";
        } else {
          tmp = tmp "<empty/>\n";
        }
        tmp = tmp "</attribute>";
        stack[--stack_i] = tmp;
      }
      else if (stack[stack_i - 1] == "<list>") {
        tmp = stack[stack_i - 1] "\n";
        tmp = tmp stack[stack_i] "\n";
        tmp = tmp "</list>";
        stack[--stack_i] = tmp;
      }
      else if (stack[stack_i - 1] == "<mixed>") {
        tmp = stack[stack_i - 1] "\n";
        tmp = tmp stack[stack_i] "\n";
        tmp = tmp "</mixed>";
        stack[--stack_i] = tmp;
      }
    }
    if (paren_i == 0) {
      mode = "grammar";
    }
    if (length(line) >= 2)
      parse_pattern(substr(line, 2));
  }
  else if (c == "|" || c == "&" || c == ",") {
    if (length(stack[paren[paren_i]]) == 1) {
      stack[paren[paren_i]] = stack[paren[paren_i]] c;
    }
    else if (length(stack[paren[paren_i]]) < 2 || substr(stack[paren[paren_i]], 2) != c) {
      print "Mismatched infix operators on line " FNR | "cat 1>&2";
      error = 1;
      exit 1
    }
    if (length(line) >= 2)
      parse_pattern(substr(line, 2));
  }
  else if (c == "?") {
    stack[stack_i] = "<optional>" stack[stack_i] "</optional>"
    if (length(line) >= 2)
      parse_pattern(substr(line, 2));
  }
  else if (c == "*") {
    stack[stack_i] = "<zeroOrMore>" stack[stack_i] "</zeroOrMore>"
    if (length(line) >= 2)
      parse_pattern(substr(line, 2));
  }
  else if (c == "+") {
    stack[stack_i] = "<oneOrMore>" stack[stack_i] "</oneOrMore>"
    if (length(line) >= 2)
      parse_pattern(substr(line, 2));
  }
  else if (c == "\"") {
    txt = substr(line, 2);
    sub(/".*/, "", txt)
    stack[++stack_i] = "<value>" txt "</value>";
    if (length(line) >= length(txt) + 3)
      parse_pattern(substr(line, length(txt) + 3));
  }
  else if (match(line, "^element ")) {
    aft = substr(line, 8);
    sub(/^ */, "", aft);
    name = aft;
    stack[++stack_i] = "<element";
    mode = "name_class";
    name_class_i = stack_i;
    parse_name_class(name);
  }
  else if (match(line, "^attribute ")) {
    aft = substr(line, 10);
    sub(/^ */, "", aft);
    name = aft;
    stack[++stack_i] = "<attribute";
    mode = "name_class";
    name_class_i = stack_i;
    parse_name_class(name);
  }
  else if (match(line, "^list ")) {
    aft = substr(line, 5);
    sub(/^ */, "", aft);
    stack[++stack_i] = "<list>";
    if (aft != "") { parse_pattern(aft); }
  }
  else if (match(line, "^mixed ")) {
    aft = substr(line, 6);
    sub(/^ */, "", aft);
    stack[++stack_i] = "<mixed>";
    if (aft != "") { parse_pattern(aft); }
  }
  else if (match(line, /^text[^A-Za-z]/) || match(line, /^text$/)) {
    stack[++stack_i] = "<text/>";
    aft = substr(line, 5);
    sub(/^ */, "", aft);
    if (aft != "") { parse_pattern(aft); }
  }
  else if (match(line, "^default namespace ")) {
    print "default namespace appeared out of context on line " FNR | "cat 1>&2";
    error = 1;
    exit 1
  }
  else if (match(line, "^namespace ")) {
    print "namespace appeared out of context on line " FNR | "cat 1>&2";
    error = 1;
    exit 1
  }
  else if (match(line, "^start ")) {
    print "start appeared out of context on line " FNR | "cat 1>&2";
    error = 1;
    exit 1
  }
  else if (match(line, /^xsd:[A-Za-z_]/)) {
    name = substr(line, 1);
    sub(/^xsd:/, "", name);
    sub(/[^A-Za-z_]+.*/, "", name);
    stack[++stack_i] = sprintf("<data type='%s' datatypeLibrary='http://www.w3.org/2001/XMLSchema-datatypes'/>",
                               name);
    if (length(line) >= length(name) + 5) {
      aft = substr(line, length(name) + 5);
      parse_pattern(aft);
    }
  }
  else if (match(line, /^[A-Za-z_]/)) {
    name = substr(line, 1);
    sub(/[^A-Za-z_]+.*/, "", name);
    if (name == "empty") {
      stack[++stack_i] = "<empty/>";
    }
    else if (name == "notAllowed") {
      stack[++stack_i] = "<notAllowed/>";
    }
    else {
      stack[++stack_i] = sprintf("<ref name='%s'/>", name);
    }
    if (length(line) >= length(name) + 1) {
      aft = substr(line, length(name) + 1);
      parse_pattern(aft);
    }
  }
}

function parse_name_class (line) {
  sub(/^ */, "", line)
  if (length(line) == 0) return;
  c = substr(line, 1, 1);
  if (c == "{") {
    if (stack_i != name_class_i) {
      tmp = "";
      for (i = stack_i; i >= name_class_i; i--) {
        if (stack[i] == "<except>") {
          tmp = "<except>" tmp "</except>";
        }
        else if (stack[i] == "<anyName>") {
          tmp = "<anyName>" tmp "</anyName>";
        }
        else {
          tmp = stack[i] tmp
        }
      }
      stack[name_class_i] = tmp;
      stack_i = name_class_i;
    }
    mode = "pattern";
    parse_pattern(line);
  }
  else if (c == "*") {
    if (stack[stack_i] == "<element" || stack[stack_i] == "<attribute") {
      stack[stack_i] = stack[stack_i] ">";
    }
    stack[++stack_i] = "<anyName>";
    parse_name_class(substr(line, 2));
  }
  else if (c == "-") {
    stack[++stack_i] = "<except>"
    parse_name_class(substr(line, 2));
  }
  else if (c == "|") {
    if (length(stack[paren[paren_i]]) == 1) {
      stack[paren[paren_i]] = stack[paren[paren_i]] c;
    }
    else if (substr(stack[paren[paren_i]], 2) != "|") {
      print "Mismatched infix operators on line " FNR | "cat 1>&2";
      error = 1;
      exit 1
    }
    parse_name_class(substr(line, 2));
  }
  else if (c == "(") {
    stack[++stack_i] = "(";
    paren[++paren_i] = stack_i;
    parse_name_class(substr(line, 2));
  }
  else if (c == ")") {
    open = stack[paren[paren_i]];
    if (substr(open, 1, 1) != "(") {
      print "Mismatched parentheses on line " FNR | "cat 1>&2";
      error = 1;
      exit 1
    }
    if (length(open) == 1 || substr(open, 2, 1) != "|") {
      print "Unknown name class pattern on line " FNR | "cat 1>&2";
      error = 1;
      exit 1
    }
    tmp = ""
    for (i = paren[paren_i] + 1; i <= stack_i; i++) {
      tmp = tmp stack[i] "\n";
    }
    stack_i = paren[paren_i];
    stack[stack_i] = tmp;
    paren_i--;
    parse_name_class(substr(line, 2));
  }
  else if (match(line, /^[A-Za-z_]/)) {
    name = substr(line, 1);
    sub(/[^A-Za-z_]+.*/, "", name);
    if (length(line) >= length(name) + 1)
      aft = substr(line, length(name) + 1);
    else
      aft = "";
    if (length(aft) >= 2 && substr(aft, 1, 2) == ":*") {
      for (i = 1; i <= namespaces_i; i++) {
        if (nsnames[i] == name) {
          stack[++stack_i] = sprintf("<nsName ns='%s'/>", namespaces[i])
          break;
        }
      }
      if (length(aft) >= 3)
        aft = substr(aft, 3);
      else
        aft = "";
    }
    else {
      if (length(aft) >= 2 && substr(aft, 1, 1) == ":") {
        checkns = "";
        pos = 1;
        while (pos <= namespaces_i) {
          if (namespaces[pos] != "" && nsnames[pos] == name) {
            checkns = namespaces[pos];
            break;
          }
          pos++;
        }
        if (checkns == "") {
          print "Unknown namespace prefix " name " on line " FNR | "cat 1>&2";
          error = 1;
          exit 1
        }
        localname = substr(aft, 2);
        sub(/[^A-Za-z_]+.*/, "", localname);
        if (length(aft) >= length(localname) + 2)
          aft = substr(aft, length(localname) + 2);
        else
          aft = "";
        name = name ":" localname;
      }
      if (stack[stack_i] == "<element" || stack[stack_i] == "<attribute") {
        stack[stack_i] = stack[stack_i] sprintf(" name='%s'>", name);
      }
      else {
        stack[++stack_i] = sprintf("<name>%s</name>", name);
      }
    }
    parse_name_class(aft);
  }
}

function printstack () {
  pos = 1;
  while (pos <= stack_i) {
    printstackone();
  }
}
function printstackone () {
  if (substr(stack[pos], 1, 1) == "#") {
    print "<!--";
    while (substr(stack[pos], 1, 1) == "#") {
      cmt = substr(stack[pos], 2);
      sub(/^ */, "", cmt);
      print cmt;
      pos++;
    }
    print "-->";
  }
  if (substr(stack[pos], 1, 6) == "<start") {
    print stack[pos];
    pos++;
    printstackone();
    print "</start>"
  }
  else if (substr(stack[pos], 1, 7) == "<define") {
    print stack[pos];
    pos++;
    printstackone();
    print "</define>"
  }
  else {
    print stack[pos];
    pos++;
  }
}

BEGIN {
  mode = "grammar";
  stack_i = 0;
  paren_i = 0;
  namespaces_i = 0;
  error = 0;
}

END {
  if (!error) {
    print "<grammar xmlns='http://relaxng.org/ns/structure/1.0'";
    pos = 1;
    while (pos <= namespaces_i) {
      if (namespaces[pos] != "") {
        printf " xmlns:%s='%s'", nsnames[pos], namespaces[pos];
      }
      pos++;
    }
    printf " ns='%s'>\n", default_namespace;
    printstack()
    print "</grammar>"
  }
}

mode == "pattern" && paren_i == 0 && /.*=/ { mode = "grammar"; }
mode == "grammar" && /.*=/ {
  if (match($0, "^default namespace ")) {
    namespace = substr($0, index($0, "=") + 1);
    nsname = substr($0, 19, index($0, "=") - 19);
    sub(/^ */, "", nsname);
    sub(/ *$/, "", nsname);
    sub(/^ *"/, "", namespace);
    sub(/" *$/, "", namespace);
    if (nsname != "") {
      nsnames[++namespaces_i] = nsname;
      namespaces[namespaces_i] = namespace;
    }
    default_namespace = namespace
  }
  else if (match($0, "^namespace ")) {
    namespace = substr($0, index($0, "=") + 1);
    nsname = substr($0, 11, index($0, "=") - 11);
    sub(/^ */, "", nsname);
    sub(/ *$/, "", nsname);
    sub(/^ *"/, "", namespace);
    sub(/" *$/, "", namespace);
    if (nsname != "") {
      nsnames[++namespaces_i] = nsname;
      namespaces[namespaces_i] = namespace;
    }
  }
  else {
    nameix = index($0, "=");
    combine = "";
    if (nameix < 1) next;
    if (substr($0, nameix - 1, 1) == "|") {
      combine = "choice";
      name = substr($0, 1, nameix - 2);
    }
    else if (substr($0, nameix - 1, 1) == "&") {
      combine = "interleave";
      name = substr($0, 1, nameix - 2);
    }
    else {
      name = substr($0, 1, nameix - 1);
    }
    sub(/ /, "", name);
    if (name == "start")
      stack[++stack_i] = "<start>"
    else {
      define = sprintf("<define name='%s'", name);
      if (combine != "")
        define = define " combine='" combine "'"
      define = define ">"
      stack[++stack_i] = define;
    }
    mode = "pattern";
    if (length($0) >= nameix + 1)
      parse_pattern(substr($0, nameix + 1))
  }
  next;
}
mode == "pattern" {
  parse_pattern($0);
  next;
}
mode == "name_class" {
  parse_name_class($0);
  next;
}
# Doesn't handle all comments. That would require modifying
# parse_pattern and parse_name_class and generall being a lot
# more clever about output. But it handles comments that start
# a line outside a pattern. Enough for me.
/#.*/ {
  stack[++stack_i] = $0
}