# Set up code
    
# Use if kdict package not installed, run 'devtools::install_github("fauxneticien/kdict")'
library(kdict)
library(DT)
    
diagnostics <- list()
    
# Helpers
preview_df <- function(df, ...) {
    df %>%
    ungroup() %>% 
    slice(1:params$preview_rows) %>%
        mutate_if(is.numeric, funs(round(., digits = params$round_digits))) %>% 
        datatable(rownames = FALSE, ...)
}
    
show_diag_df <- function(diagnostics_df, ...) {
    diagnostics_df %>% 
        mutate_if(is.numeric, funs(round(., digits = params$round_digits))) %>%
        datatable(rownames = FALSE, ...)
}

1 About

This R Markdown document steps through pre-processing the raw annotations (e.g. b001_file01_ns.TextGrid) and formant (e.g. b001_file01.formants.forest.csv) data, located in the default path of ../../data/raw relative to the root of the project (this Rmd file should be in src/data).

Code blocks are hidden by default, so press the Code buttons on the top right to see each processing block, or ‘Show all code’ on the top right of this document.

Note. Semantically-meaningful word-seperators are used in variable names. Unprocessed data columns have . in their name (e.g. vowel.text), while derived/transformed data have _ in their name (e.g. vowel_num).

By default, 5 first 5 rows from various steps of the data processing are showed (and numbers are rounded to 2 decimal places). Change this by adjusting the preview_rows and round_digits parameters at the top of the R Markdown file.

At the end of each step of the data transformation step, there are various diagnostics, which report commonly-occuring data entry errors by annotators.

2 Annotations (raw data)

2.1 Read in all TextGrid data from ../../data/raw into a data frame

In particular, we read in the ipa and vowels tiers, and place them into a list of two data frames, called kdicts_dfs.

kdict_dfs <- read_kdict_tiers(
        kdict_path = params$kdict_path,
        tier_names = c("ipa", "vowels")
    ) %>%
    split(.$tier_name) %>%
    map(~ select(., -tier_name) %>% mutate(annotator = str_to_lower(annotator)))

2.1.1 Data from ipa tier

Since each file generally has two repetitions of a word, this data frame’s unit of observation is at the repetition level (i.e. not word level), and we name the columns accordingly (e.g. using rep.txt and not word.text).

kdict_dfs$ipa <- kdict_dfs$ipa %>%
    group_by(source_file, annotator) %>%
    rename(rep.xmin = xmin, rep.xmax = xmax, rep.text = text) %>%
    filter(nzchar(str_remove_all(rep.text, "\\s"))) %>%
    mutate(rep_num = 1:n()) %>%
    ungroup()
    
kdict_dfs$ipa %>%
    preview_df(caption = "Word repetition annotation data from the 'ipa' tiers")

2.1.2 Data from vowels tier

kdict_dfs$vowels <-
    kdict_dfs$vowels %>%
    rename(vowel.xmin = xmin, vowel.xmax = xmax, vowel.text = text)
kdict_dfs$vowels %>%
    filter(nzchar(vowel.text)) %>% 
    preview_df(caption = "Vowel annotations data from the 'vowel' tiers")

2.1.3 Resulting joined data

We join the two data frames by their common source_file, and keep only the resulting rows where the vowel’s midpoint is within the repetition’s time range see mutate(xmid = vowel.xmin + (vowel.xmax - vowel.xmin) / 2) and filter(xmid >= rep.xmin, xmid <= rep.xmax) in the code below.

vowels_reps_df <-
    kdict_dfs$vowels %>%
    filter(str_detect(vowel.text, get_vowels_regex())) %>% 
    mutate(xmid = vowel.xmin + (vowel.xmax - vowel.xmin) / 2) %>% 
    left_join(
        y  = kdict_dfs$ipa,
        by = c("annotator", "source_file") 
    ) %>%
    filter(xmid >= rep.xmin, xmid <= rep.xmax) %>%
    select(-xmid)
    
vowels_reps_df %>%
    preview_df(caption = "Joined data betwen 'vowel' and 'ipa' tiers")

2.2 Diagnostics

2.2.1 Mismatched repetition counts

The data frame below displays the files for which annotators have differing number of repetition-level transcriptions within the source_file.

diagnostics$word_counts_mismatched <-
    kdict_dfs$ipa %>%
    group_by(source_file, annotator) %>%
    summarise(reps_total = max(rep_num)) %>%
    group_by(source_file) %>%
    filter(n_distinct(reps_total) > 1) %>%
    spread(annotator, reps_total)
    
diagnostics$word_counts_mismatched %>%
    show_diag_df(caption = "Mismatched number of repetitions on 'ipa' tier within each file")

2.2.2 Non-vocalic labels

The following data displays counts on the columns of non-vocalic labels on the vowels tier within the each source_file. These labels are not analysed, so one should make sure no false negatives in the columns (i.e. vowel labels that should be analysed but have been mistakenly caught by this filter).

diagnostics$unanalysed_vowel_tier_labels <-
    anti_join(
        x = kdict_dfs$vowels,
        y = vowels_reps_df
    ) %>% 
    filter(!is.na(vowel.text)) %>%
    # mutate(text = str_remove_all(text, ":|\\?|'|#|ˈ|ː")) %>% 
    group_by(source_file, annotator, vowel.text) %>% 
    tally() %>%
    spread(key = vowel.text, value = n)
    
diagnostics$unanalysed_vowel_tier_labels %>%
    show_diag_df(caption = "Non-vocalic labels on the 'vowels' tiers")

2.2.3 Orphaned transcriptions

The data frame below displays repetition-level transcriptions for which no vowels have been assigned. Likely only the ipa tier in the source_file has been completed, and not the vowels tier as well.

diagnostics$orphaned_reps <- 
    kdict_dfs$ipa %>%
    anti_join(select(vowels_reps_df, source_file, rep_num, annotator)) %>% 
    filter(str_detect(rep.text, get_vowels_regex()))
    
diagnostics$orphaned_reps %>%
    show_diag_df(caption = "Orphaned transcriptions on 'ipa' tier, unmatched by any interval on its 'vowel' tier")

2.2.4 Vowelless transcriptions

The data frame below displays likely incomplete repetition-level transcriptions, in which no vowels were detected.

diagnostics$novowel_ipa <-
    kdict_dfs$ipa %>%
    filter(!str_detect(rep.text, get_vowels_regex()), nzchar(str_remove_all(rep.text, "\\s")))
    
diagnostics$novowel_ipa %>%
    show_diag_df(caption = "Vowelless transcriptions")

3 Annotations (derived data)

3.1 Consonantal context

We derive consonantal context (const_ctx) by stripping both vowel.text and rep.text of various diacritics; the diacritic-stripped version of these columns are called base_vowel and base_transcription, respectively.

We then derive, for each annotator and word repetition, the consonantal contexts of all vowels on the vowels tier by matching them to the vowels present on the ipa tier.

Of course, if they cannot be exactly matched, the const_ctx returned will be NA for the whole repetition (such mismatches are given in a diagnostics data frame at the end of this section).

vowels_contexts_df <-
    vowels_reps_df %>%
    mutate(
        base_transcription = remove_diacritics(rep.text) %>% str_remove_all(":|\\?|'|#|ˈ|ː| "),
        base_vowel         = remove_diacritics(vowel.text) %>% str_remove_all(":|\\?|'|#|ˈ|ː|j|ɹ|ɺ|w| "),
        rep_vowels         = future_map_chr(base_transcription, ~ str_extract_all(., get_vowels_regex()) %>% unlist(use.names = FALSE) %>% paste0(collapse = ""))
    ) %>%
    group_by(source_file, annotator, rep.xmin, rep.xmax, base_transcription) %>%
    nest() %>%
    mutate(
        base_vowel = map(data, ~ .$base_vowel),
        const_ctx = future_map2(base_transcription, base_vowel, ~ get_cons_context(.x, .y))
    ) %>%
    select(-base_vowel) %>%
    unnest()
    
vowels_contexts_df %>%
    select(base_vowel, base_transcription, const_ctx, vowel.xmin, vowel.xmax, rep_num, annotator, source_file) %>%
    preview_df(caption = "Consonantal context for vowel dervied from repetition-level transcription data")

3.2 Diagnostics

3.2.1 Mismatched boundary markers

Initial- and final-vowels are required to be marked by # on, respectively, the left- and right-edge of intervals on the vowels tier. The following table displays vowels on the vowels tier for which the derived consonantal context was either initial or final, but ‘#’ had not been detected as present in the interval(s) on the vowels tier.

diagnostics$mismatched_edges <-
    vowels_contexts_df %>%
    filter(str_detect(const_ctx, "#"), !str_detect(vowel.text, "#"))
diagnostics$mismatched_edges %>%
    select(source_file, annotator, rep_num, vowel.xmin, vowel.xmax, const_ctx, vowel.text, base_transcription) %>% 
    show_diag_df()

3.2.2 Mismatched vowels

The following data frame displays repetitions where where ipa tier’s vowels and vowels tier’s vowels are mismatched within the given annotator’s source_file.

diagnostics$ipa_vowels_mismatched <-
    vowels_contexts_df %>% 
    rename(ipa_tier_vowels = rep_vowels) %>% 
    group_by(source_file, annotator, rep_num, base_transcription, ipa_tier_vowels) %>%
    summarise(vowels_tier_vowels = paste0(base_vowel, collapse = "")) %>%
    ungroup %>% 
    filter(vowels_tier_vowels != ipa_tier_vowels)
diagnostics$ipa_vowels_mismatched %>%
    show_diag_df(caption = "Word repetitions where 'ipa' and 'vowels' tiers' vowels are mismatched")

3.2.3 Underivable consonantal context

The data frame below displays vowels for which consonantal context could not be derived. Troubleshooting for these observations will probably require a bit of detective work.

diagnostics$const_ctx_na <-
    vowels_contexts_df %>%
    filter(is.na(const_ctx))
    
diagnostics$const_ctx_na %>%
    select(source_file, annotator, rep_num, vowel.xmin, vowel.xmax, base_vowel, base_transcription, const_ctx) %>% 
    show_diag_df()

4 Medial vowels, pre-processed

Excluding any vowels in the diagnostic tables above, we know that within the remaining data that a) vowels and ipa tiers’ vowel labels are matched and, therefore, b) we have a dervied cosonantal context.

Thus, we can exclude all initial and final vowels (i.e. where # detected in const_ctx), and then further exclude repetitions for which annotators disagree on the number of medial vowels.

Finally, we also exclude vowel transcriptions not having at least 2 different annotators.

vowels_med_raw <-
    vowels_contexts_df %>%
    filter(!str_detect(const_ctx, "#"), nchar(base_vowel) == 1) %>%   # Keep only medial monophthongs
    reduce(.x = diagnostics, .f = anti_join, .init = .)               # Discard vowels with any matches in any of the diagnostics data frames
    
vowels_med_preprocessed <-
    vowels_med_raw %>% 
    group_by(source_file, rep_num, annotator) %>%
    nest() %>%
    mutate(n_vowels = map_int(data, nrow)) %>%                       # find number of vowels per word rep per annotator
    group_by(source_file, rep_num) %>%
    filter(n_distinct(n_vowels) == 1) %>%                            # keep word rep iff all annotator agree on num of vowels
    select(-n_vowels) %>%
    unnest() %>%
    
    group_by(source_file, rep_num, annotator) %>%                    # given all annotators agree, only now give sequential ids
    mutate(vowel_num = 1:n()) %>%                                    #   to vowel tokens within word repetitions (e.g. rep 2 vowel 1)
    group_by(source_file, rep_num, vowel_num) %>%
    filter(dplyr::n_distinct(annotator) >= 2) %>%                    # keep only vowel tokens with more than 1 annotator
    ungroup() %>%
    select(
        source_file, rep_num, vowel_num, base_vowel, const_ctx, base_transcription, annotator,
        vowel.text, vowel.xmin, vowel.xmax, rep.text, rep.xmin, rep.xmax
    )
    
vowels_med_preprocessed %>%
    select(source_file, rep_num, vowel_num, base_vowel, const_ctx, annotator, vowel.xmin, vowel.xmax) %>% 
    preview_df(caption = "Preprocessed medial vowel annotations with at least 2 transcribers per vowel")

5 Join corresponding mid-point formant values for pre-processed medial vowels

5.1 Read in formant data and remove outliers

5.1.1 Raw data

Read in in raw data for each format tracker (forest, praat) from all formants.*.csv files under the ../../data/raw directory.

kdict_formants <- read_kdict_formants(kdict_path = params$kdict_path)
    
kdict_formants %>%
    sample_n(10) %>%
    preview_df()

5.1.2 Select mid-point formants, then remove outliers

We define outliers here as any datum outside 3 standard deviations of the respective mean. We thus derive and filter based on z-scores for:

    1. within-tracker variance, and
    1. difference in measurement between the Forest and Praat formant trackers
formants_preprocessed <-
    vowels_med_preprocessed %>%
    select(source_file, rep_num, annotator, vowel_num, vowel.xmin, vowel.xmax) %>%
    left_join(kdict_formants, by = "source_file") %>%
    filter(time >= vowel.xmin, time <= vowel.xmax) %>%
    group_by(source_file, rep_num, annotator, vowel_num, tracker) %>%
    filter(row_number() == ceiling(n()/2)) %>% # keep only mid-point measure
    ungroup() %>%
    unite(formants, f1, f2, sep = "-") %>%
    select(source_file, rep_num, annotator, vowel_num, tracker, formants) %>%
    spread(tracker, formants) %>%
    remove_outlier_formants()
    
formants_preprocessed %>%
    preview_df(caption = "Formant values filtered on being within +/- 3 SD of respective means for {forest,praat}.f{1,2}_z and f{1,2}_diff_z")

6 Analysis data frame for medial vowels

vowels_med_analysis <- 
    left_join(
        x = vowels_med_preprocessed,
        y = select(formants_preprocessed, -contains("diff"))
    ) %>%
    filter_all(all_vars(!is.na(.))) %>%
    group_by(source_file, rep_num, vowel_num)
    
med_analysis_coverage <-
    `/`(
        nrow(vowels_med_analysis),
        nrow(vowels_med_raw)
    ) %>%     
    `*`(100) %>% 
    round(digits = 0) 
    
vowels_med_analysis %>%
    ungroup() %>% 
    sample_n(10) %>% 
    select(source_file:const_ctx, annotator, forest.f1:praat.f2_z)
NA

6.1 Coverage of pre-processed dataset: 64%

The analysis data in vowels_med_analysis retains 11703 rows out of 18165 in vowels_med_raw.

7 Write data (not run by default)

7.1 Write vowels_med_analysis.csv

To have data (over)written, change the write_csvs parameter.

readr::write_csv(
    x    = vowels_med_analysis,
    path = file.path(params$output_path, "vowels_med_analysis.csv")
)

7.2 Write diagnostics/*.csv

diags_dir <- file.path(params$output_path, "diagnostics")
    
if(!dir.exists(diags_dir)) { dir.create(diags_dir) }

diagnostics %>%
    iwalk(~ readr::write_csv(x = .x, path = file.path(diags_dir, paste0(.y, ".csv")), na = ""))
LS0tCnRpdGxlOiAiUHJlcHJvY2VzcyBhbm5vdGF0aW9ucyBhbmQgZm9ybWFudCBkYXRhIgphdXRob3I6ICJOYXkgU2FuIgpkYXRlOiAnR2VuZXJhdGVkIGByIGZvcm1hdChTeXMudGltZSgpLCAiJUYgJVIgVVRDJXoiKWAnCm91dHB1dDoKICBodG1sX25vdGVib29rOiAKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRvYzogeWVzCnBhcmFtczoKICAgIGtkaWN0X3BhdGg6ICIuLi8uLi9kYXRhL3JhdyIKICAgIHByZXZpZXdfcm93czogNQogICAgcm91bmRfZGlnaXRzOiAyCiAgICB3cml0ZV9jc3ZzOiBmYWxzZQogICAgb3V0cHV0X3BhdGg6ICIuLi8uLi9kYXRhL3Byb2Nlc3NlZCIKLS0tCgpgYGB7ciBTZXR1cCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBTZXQgdXAgY29kZQogICAgCiMgVXNlIGlmIGtkaWN0IHBhY2thZ2Ugbm90IGluc3RhbGxlZCwgcnVuICdkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImZhdXhuZXRpY2llbi9rZGljdCIpJwpsaWJyYXJ5KGtkaWN0KQpsaWJyYXJ5KERUKQogICAgCmRpYWdub3N0aWNzIDwtIGxpc3QoKQogICAgCiMgSGVscGVycwpwcmV2aWV3X2RmIDwtIGZ1bmN0aW9uKGRmLCAuLi4pIHsKICAgIGRmICU+JQogICAgdW5ncm91cCgpICU+JSAKICAgIHNsaWNlKDE6cGFyYW1zJHByZXZpZXdfcm93cykgJT4lCiAgICAgICAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGZ1bnMocm91bmQoLiwgZGlnaXRzID0gcGFyYW1zJHJvdW5kX2RpZ2l0cykpKSAlPiUgCiAgICAgICAgZGF0YXRhYmxlKHJvd25hbWVzID0gRkFMU0UsIC4uLikKfQogICAgCnNob3dfZGlhZ19kZiA8LSBmdW5jdGlvbihkaWFnbm9zdGljc19kZiwgLi4uKSB7CiAgICBkaWFnbm9zdGljc19kZiAlPiUgCiAgICAgICAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGZ1bnMocm91bmQoLiwgZGlnaXRzID0gcGFyYW1zJHJvdW5kX2RpZ2l0cykpKSAlPiUKICAgICAgICBkYXRhdGFibGUocm93bmFtZXMgPSBGQUxTRSwgLi4uKQp9CmBgYAoKIyBBYm91dAoKVGhpcyBSIE1hcmtkb3duIGRvY3VtZW50IHN0ZXBzIHRocm91Z2ggcHJlLXByb2Nlc3NpbmcgdGhlIHJhdyBhbm5vdGF0aW9ucyAoZS5nLiBgYjAwMV9maWxlMDFfbnMuVGV4dEdyaWRgKSBhbmQgZm9ybWFudCAoZS5nLiBgYjAwMV9maWxlMDEuZm9ybWFudHMuZm9yZXN0LmNzdmApIGRhdGEsIGxvY2F0ZWQgaW4gdGhlIGRlZmF1bHQgcGF0aCBvZiBgYHIgcGFyYW1zJGtkaWN0X3BhdGhgYCByZWxhdGl2ZSB0byB0aGUgcm9vdCBvZiB0aGUgcHJvamVjdCAodGhpcyBSbWQgZmlsZSBzaG91bGQgYmUgaW4gYHNyYy9kYXRhYCkuCgpDb2RlIGJsb2NrcyBhcmUgaGlkZGVuIGJ5IGRlZmF1bHQsIHNvIHByZXNzIHRoZSBgQ29kZWAgYnV0dG9ucyBvbiB0aGUgdG9wIHJpZ2h0IHRvIHNlZSBlYWNoIHByb2Nlc3NpbmcgYmxvY2ssIG9yICdTaG93IGFsbCBjb2RlJyBvbiB0aGUgdG9wIHJpZ2h0IG9mIHRoaXMgZG9jdW1lbnQuCgoqKio8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5Ob3RlPC9zcGFuPioqKi4gU2VtYW50aWNhbGx5LW1lYW5pbmdmdWwgd29yZC1zZXBlcmF0b3JzIGFyZSB1c2VkIGluIHZhcmlhYmxlIG5hbWVzLiBVbnByb2Nlc3NlZCBkYXRhIGNvbHVtbnMgaGF2ZSBgLmAgaW4gdGhlaXIgbmFtZSAoZS5nLiBgdm93ZWwudGV4dGApLCB3aGlsZSBkZXJpdmVkL3RyYW5zZm9ybWVkIGRhdGEgaGF2ZSBgX2AgaW4gdGhlaXIgbmFtZSAoZS5nLiBgdm93ZWxfbnVtYCkuCgpCeSBkZWZhdWx0LCBgciBwYXJhbXMkcHJldmlld19yb3dzYCBmaXJzdCA1IHJvd3MgZnJvbSB2YXJpb3VzIHN0ZXBzIG9mIHRoZSBkYXRhIHByb2Nlc3NpbmcgYXJlIHNob3dlZCAoYW5kIG51bWJlcnMgYXJlIHJvdW5kZWQgdG8gMiBkZWNpbWFsIHBsYWNlcykuIENoYW5nZSB0aGlzIGJ5IGFkanVzdGluZyB0aGUgYHByZXZpZXdfcm93c2AgYW5kIGByb3VuZF9kaWdpdHNgIHBhcmFtZXRlcnMgYXQgdGhlIHRvcCBvZiB0aGUgUiBNYXJrZG93biBmaWxlLgoKQXQgdGhlIGVuZCBvZiBlYWNoIHN0ZXAgb2YgdGhlIGRhdGEgdHJhbnNmb3JtYXRpb24gc3RlcCwgdGhlcmUgYXJlIHZhcmlvdXMgZGlhZ25vc3RpY3MsIHdoaWNoIHJlcG9ydCBjb21tb25seS1vY2N1cmluZyBkYXRhIGVudHJ5IGVycm9ycyBieSBhbm5vdGF0b3JzLiAKCiMgQW5ub3RhdGlvbnMgKHJhdyBkYXRhKQoKIyMgUmVhZCBpbiBhbGwgVGV4dEdyaWQgZGF0YSBmcm9tIGBgciBwYXJhbXMka2RpY3RfcGF0aGBgIGludG8gYSBkYXRhIGZyYW1lIHsudGFic2V0fQoKSW4gcGFydGljdWxhciwgd2UgcmVhZCBpbiB0aGUgYGlwYWAgYW5kIGB2b3dlbHNgIHRpZXJzLCBhbmQgcGxhY2UgdGhlbSBpbnRvIGEgbGlzdCBvZiB0d28gZGF0YSBmcmFtZXMsIGNhbGxlZCBga2RpY3RzX2Rmc2AuCgpgYGB7ciBNYWtlIGtkaWN0X2RmcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Ka2RpY3RfZGZzIDwtIHJlYWRfa2RpY3RfdGllcnMoCiAgICAgICAga2RpY3RfcGF0aCA9IHBhcmFtcyRrZGljdF9wYXRoLAogICAgICAgIHRpZXJfbmFtZXMgPSBjKCJpcGEiLCAidm93ZWxzIikKICAgICkgJT4lCiAgICBzcGxpdCguJHRpZXJfbmFtZSkgJT4lCiAgICBtYXAofiBzZWxlY3QoLiwgLXRpZXJfbmFtZSkgJT4lIG11dGF0ZShhbm5vdGF0b3IgPSBzdHJfdG9fbG93ZXIoYW5ub3RhdG9yKSkpCmBgYAoKIyMjIERhdGEgZnJvbSBgaXBhYCB0aWVyCgpTaW5jZSBlYWNoIGZpbGUgZ2VuZXJhbGx5IGhhcyB0d28gcmVwZXRpdGlvbnMgb2YgYSB3b3JkLCB0aGlzIGRhdGEgZnJhbWUncyB1bml0IG9mIG9ic2VydmF0aW9uIGlzIGF0IHRoZSByZXBldGl0aW9uIGxldmVsIChpLmUuIG5vdCB3b3JkIGxldmVsKSwgYW5kIHdlIG5hbWUgdGhlIGNvbHVtbnMgYWNjb3JkaW5nbHkgKGUuZy4gdXNpbmcgYHJlcC50eHRgIGFuZCBub3QgYHdvcmQudGV4dGApLgoKYGBge3IgUHJldmlldyBpcGEgZGF0YSBmcmFtZX0Ka2RpY3RfZGZzJGlwYSA8LSBrZGljdF9kZnMkaXBhICU+JQogICAgZ3JvdXBfYnkoc291cmNlX2ZpbGUsIGFubm90YXRvcikgJT4lCiAgICByZW5hbWUocmVwLnhtaW4gPSB4bWluLCByZXAueG1heCA9IHhtYXgsIHJlcC50ZXh0ID0gdGV4dCkgJT4lCiAgICBmaWx0ZXIobnpjaGFyKHN0cl9yZW1vdmVfYWxsKHJlcC50ZXh0LCAiXFxzIikpKSAlPiUKICAgIG11dGF0ZShyZXBfbnVtID0gMTpuKCkpICU+JQogICAgdW5ncm91cCgpCiAgICAKa2RpY3RfZGZzJGlwYSAlPiUKICAgIHByZXZpZXdfZGYoY2FwdGlvbiA9ICJXb3JkIHJlcGV0aXRpb24gYW5ub3RhdGlvbiBkYXRhIGZyb20gdGhlICdpcGEnIHRpZXJzIikKYGBgCgojIyMgRGF0YSBmcm9tIGB2b3dlbHNgIHRpZXIKCmBgYHtyIFByZXZpZXcgdm93ZWwgZGF0YSBmcmFtZX0Ka2RpY3RfZGZzJHZvd2VscyA8LQogICAga2RpY3RfZGZzJHZvd2VscyAlPiUKICAgIHJlbmFtZSh2b3dlbC54bWluID0geG1pbiwgdm93ZWwueG1heCA9IHhtYXgsIHZvd2VsLnRleHQgPSB0ZXh0KQoKa2RpY3RfZGZzJHZvd2VscyAlPiUKICAgIGZpbHRlcihuemNoYXIodm93ZWwudGV4dCkpICU+JSAKICAgIHByZXZpZXdfZGYoY2FwdGlvbiA9ICJWb3dlbCBhbm5vdGF0aW9ucyBkYXRhIGZyb20gdGhlICd2b3dlbCcgdGllcnMiKQpgYGAKCiMjIyBSZXN1bHRpbmcgam9pbmVkIGRhdGEKCldlIGpvaW4gdGhlIHR3byBkYXRhIGZyYW1lcyBieSB0aGVpciBjb21tb24gYHNvdXJjZV9maWxlYCwgYW5kIGtlZXAgb25seSB0aGUgcmVzdWx0aW5nIHJvd3Mgd2hlcmUgdGhlIHZvd2VsJ3MgbWlkcG9pbnQgaXMgd2l0aGluIHRoZSByZXBldGl0aW9uJ3MgdGltZSByYW5nZSBzZWUgYG11dGF0ZSh4bWlkID0gdm93ZWwueG1pbiArICh2b3dlbC54bWF4IC0gdm93ZWwueG1pbikgLyAyKWAgYW5kIGBmaWx0ZXIoeG1pZCA+PSByZXAueG1pbiwgeG1pZCA8PSByZXAueG1heClgIGluIHRoZSBjb2RlIGJlbG93LgoKYGBge3IgTWFrZSB2b3dlbF9yZXBzX2RmIGFuZCBwcmV2aWV3IHJlc3VsdH0Kdm93ZWxzX3JlcHNfZGYgPC0KICAgIGtkaWN0X2RmcyR2b3dlbHMgJT4lCiAgICBmaWx0ZXIoc3RyX2RldGVjdCh2b3dlbC50ZXh0LCBnZXRfdm93ZWxzX3JlZ2V4KCkpKSAlPiUgCiAgICBtdXRhdGUoeG1pZCA9IHZvd2VsLnhtaW4gKyAodm93ZWwueG1heCAtIHZvd2VsLnhtaW4pIC8gMikgJT4lIAogICAgbGVmdF9qb2luKAogICAgICAgIHkgID0ga2RpY3RfZGZzJGlwYSwKICAgICAgICBieSA9IGMoImFubm90YXRvciIsICJzb3VyY2VfZmlsZSIpIAogICAgKSAlPiUKICAgIGZpbHRlcih4bWlkID49IHJlcC54bWluLCB4bWlkIDw9IHJlcC54bWF4KSAlPiUKICAgIHNlbGVjdCgteG1pZCkKICAgIAp2b3dlbHNfcmVwc19kZiAlPiUKICAgIHByZXZpZXdfZGYoY2FwdGlvbiA9ICJKb2luZWQgZGF0YSBiZXR3ZW4gJ3Zvd2VsJyBhbmQgJ2lwYScgdGllcnMiKQpgYGAKCgojIyBEaWFnbm9zdGljcyB7LnRhYnNldH0KCiMjIyBNaXNtYXRjaGVkIHJlcGV0aXRpb24gY291bnRzCgpUaGUgZGF0YSBmcmFtZSBiZWxvdyBkaXNwbGF5cyB0aGUgZmlsZXMgZm9yIHdoaWNoIGFubm90YXRvcnMgaGF2ZSBkaWZmZXJpbmcgbnVtYmVyIG9mIHJlcGV0aXRpb24tbGV2ZWwgdHJhbnNjcmlwdGlvbnMgd2l0aGluIHRoZSBgc291cmNlX2ZpbGVgLgoKYGBge3IgTWFrZSBkaWFnbm9zdGljcyR3b3JkX2NvdW50c19taXNtYXRjaGVkfQpkaWFnbm9zdGljcyR3b3JkX2NvdW50c19taXNtYXRjaGVkIDwtCiAgICBrZGljdF9kZnMkaXBhICU+JQogICAgZ3JvdXBfYnkoc291cmNlX2ZpbGUsIGFubm90YXRvcikgJT4lCiAgICBzdW1tYXJpc2UocmVwc190b3RhbCA9IG1heChyZXBfbnVtKSkgJT4lCiAgICBncm91cF9ieShzb3VyY2VfZmlsZSkgJT4lCiAgICBmaWx0ZXIobl9kaXN0aW5jdChyZXBzX3RvdGFsKSA+IDEpICU+JQogICAgc3ByZWFkKGFubm90YXRvciwgcmVwc190b3RhbCkKICAgIApkaWFnbm9zdGljcyR3b3JkX2NvdW50c19taXNtYXRjaGVkICU+JQogICAgc2hvd19kaWFnX2RmKGNhcHRpb24gPSAiTWlzbWF0Y2hlZCBudW1iZXIgb2YgcmVwZXRpdGlvbnMgb24gJ2lwYScgdGllciB3aXRoaW4gZWFjaCBmaWxlIikKYGBgCgoKIyMjIE5vbi12b2NhbGljIGxhYmVscwoKVGhlIGZvbGxvd2luZyBkYXRhIGRpc3BsYXlzIGNvdW50cyBvbiB0aGUgY29sdW1ucyBvZiBub24tdm9jYWxpYyBsYWJlbHMgb24gdGhlIGB2b3dlbHNgIHRpZXIgd2l0aGluIHRoZSBlYWNoIGBzb3VyY2VfZmlsZWAuIFRoZXNlIGxhYmVscyBhcmUgbm90IGFuYWx5c2VkLCBzbyBvbmUgc2hvdWxkIG1ha2Ugc3VyZSBubyBmYWxzZSBuZWdhdGl2ZXMgaW4gdGhlIGNvbHVtbnMgKGkuZS4gdm93ZWwgbGFiZWxzIHRoYXQgc2hvdWxkIGJlIGFuYWx5c2VkIGJ1dCBoYXZlIGJlZW4gbWlzdGFrZW5seSBjYXVnaHQgYnkgdGhpcyBmaWx0ZXIpLgoKYGBge3IgTWFrZSBkaWFnbm9zdGljcyR1bmFuYWx5c2VkX3Zvd2VsX3RpZXJfbGFiZWxzLCBtZXNzYWdlPUZBTFNFfQpkaWFnbm9zdGljcyR1bmFuYWx5c2VkX3Zvd2VsX3RpZXJfbGFiZWxzIDwtCiAgICBhbnRpX2pvaW4oCiAgICAgICAgeCA9IGtkaWN0X2RmcyR2b3dlbHMsCiAgICAgICAgeSA9IHZvd2Vsc19yZXBzX2RmCiAgICApICU+JSAKICAgIGZpbHRlcighaXMubmEodm93ZWwudGV4dCkpICU+JQogICAgIyBtdXRhdGUodGV4dCA9IHN0cl9yZW1vdmVfYWxsKHRleHQsICI6fFxcP3wnfCN8y4h8y5AiKSkgJT4lIAogICAgZ3JvdXBfYnkoc291cmNlX2ZpbGUsIGFubm90YXRvciwgdm93ZWwudGV4dCkgJT4lIAogICAgdGFsbHkoKSAlPiUKICAgIHNwcmVhZChrZXkgPSB2b3dlbC50ZXh0LCB2YWx1ZSA9IG4pCiAgICAKZGlhZ25vc3RpY3MkdW5hbmFseXNlZF92b3dlbF90aWVyX2xhYmVscyAlPiUKICAgIHNob3dfZGlhZ19kZihjYXB0aW9uID0gIk5vbi12b2NhbGljIGxhYmVscyBvbiB0aGUgJ3Zvd2VscycgdGllcnMiKQpgYGAKCgojIyMgT3JwaGFuZWQgdHJhbnNjcmlwdGlvbnMKClRoZSBkYXRhIGZyYW1lIGJlbG93IGRpc3BsYXlzIHJlcGV0aXRpb24tbGV2ZWwgdHJhbnNjcmlwdGlvbnMgZm9yIHdoaWNoIG5vIHZvd2VscyBoYXZlIGJlZW4gYXNzaWduZWQuIExpa2VseSBvbmx5IHRoZSBgaXBhYCB0aWVyIGluIHRoZSBgc291cmNlX2ZpbGVgIGhhcyBiZWVuIGNvbXBsZXRlZCwgYW5kIG5vdCB0aGUgYHZvd2Vsc2AgdGllciBhcyB3ZWxsLgoKYGBge3IgTWFrZSBkaWFnbm9zdGljcyRvcnBoYW5lZF9yZXBzLCBtZXNzYWdlPUZBTFNFfQpkaWFnbm9zdGljcyRvcnBoYW5lZF9yZXBzIDwtIAogICAga2RpY3RfZGZzJGlwYSAlPiUKICAgIGFudGlfam9pbihzZWxlY3Qodm93ZWxzX3JlcHNfZGYsIHNvdXJjZV9maWxlLCByZXBfbnVtLCBhbm5vdGF0b3IpKSAlPiUgCiAgICBmaWx0ZXIoc3RyX2RldGVjdChyZXAudGV4dCwgZ2V0X3Zvd2Vsc19yZWdleCgpKSkKICAgIApkaWFnbm9zdGljcyRvcnBoYW5lZF9yZXBzICU+JQogICAgc2hvd19kaWFnX2RmKGNhcHRpb24gPSAiT3JwaGFuZWQgdHJhbnNjcmlwdGlvbnMgb24gJ2lwYScgdGllciwgdW5tYXRjaGVkIGJ5IGFueSBpbnRlcnZhbCBvbiBpdHMgJ3Zvd2VsJyB0aWVyIikKYGBgCgojIyMgVm93ZWxsZXNzIHRyYW5zY3JpcHRpb25zCgpUaGUgZGF0YSBmcmFtZSBiZWxvdyBkaXNwbGF5cyBsaWtlbHkgaW5jb21wbGV0ZSByZXBldGl0aW9uLWxldmVsIHRyYW5zY3JpcHRpb25zLCBpbiB3aGljaCBubyB2b3dlbHMgd2VyZSBkZXRlY3RlZC4KCmBgYHtyIE1ha2UgZGlhZ25vc3RpY3Mkbm92b3dlbF9pcGF9CmRpYWdub3N0aWNzJG5vdm93ZWxfaXBhIDwtCiAgICBrZGljdF9kZnMkaXBhICU+JQogICAgZmlsdGVyKCFzdHJfZGV0ZWN0KHJlcC50ZXh0LCBnZXRfdm93ZWxzX3JlZ2V4KCkpLCBuemNoYXIoc3RyX3JlbW92ZV9hbGwocmVwLnRleHQsICJcXHMiKSkpCiAgICAKZGlhZ25vc3RpY3Mkbm92b3dlbF9pcGEgJT4lCiAgICBzaG93X2RpYWdfZGYoY2FwdGlvbiA9ICJWb3dlbGxlc3MgdHJhbnNjcmlwdGlvbnMiKQpgYGAKCiMgQW5ub3RhdGlvbnMgKGRlcml2ZWQgZGF0YSkKCiMjIENvbnNvbmFudGFsIGNvbnRleHQKCldlIGRlcml2ZSBjb25zb25hbnRhbCBjb250ZXh0IChgY29uc3RfY3R4YCkgYnkgc3RyaXBwaW5nIGJvdGggYHZvd2VsLnRleHRgIGFuZCBgcmVwLnRleHRgIG9mIHZhcmlvdXMgZGlhY3JpdGljczsgdGhlIGRpYWNyaXRpYy1zdHJpcHBlZCB2ZXJzaW9uIG9mIHRoZXNlIGNvbHVtbnMgYXJlIGNhbGxlZCBgYmFzZV92b3dlbGAgYW5kIGBiYXNlX3RyYW5zY3JpcHRpb25gLCByZXNwZWN0aXZlbHkuCgpXZSB0aGVuIGRlcml2ZSwgZm9yIGVhY2ggYW5ub3RhdG9yIGFuZCB3b3JkIHJlcGV0aXRpb24sIHRoZSBjb25zb25hbnRhbCBjb250ZXh0cyBvZiBhbGwgdm93ZWxzIG9uIHRoZSBgdm93ZWxzYCB0aWVyIGJ5IG1hdGNoaW5nIHRoZW0gdG8gdGhlIHZvd2VscyBwcmVzZW50IG9uIHRoZSBgaXBhYCB0aWVyLgoKT2YgY291cnNlLCBpZiB0aGV5IGNhbm5vdCBiZSBleGFjdGx5IG1hdGNoZWQsIHRoZSBgY29uc3RfY3R4YCByZXR1cm5lZCB3aWxsIGJlIGBOQWAgZm9yIHRoZSAqKndob2xlIHJlcGV0aXRpb24qKiAoc3VjaCBtaXNtYXRjaGVzIGFyZSBnaXZlbiBpbiBhIGRpYWdub3N0aWNzIGRhdGEgZnJhbWUgYXQgdGhlIGVuZCBvZiB0aGlzIHNlY3Rpb24pLgoKYGBge3IgTWFrZSB2b3dlbHNfY29udGV4dHNfZGZ9CnZvd2Vsc19jb250ZXh0c19kZiA8LQogICAgdm93ZWxzX3JlcHNfZGYgJT4lCiAgICBtdXRhdGUoCiAgICAgICAgYmFzZV90cmFuc2NyaXB0aW9uID0gcmVtb3ZlX2RpYWNyaXRpY3MocmVwLnRleHQpICU+JSBzdHJfcmVtb3ZlX2FsbCgiOnxcXD98J3wjfMuIfMuQfCAiKSwKICAgICAgICBiYXNlX3Zvd2VsICAgICAgICAgPSByZW1vdmVfZGlhY3JpdGljcyh2b3dlbC50ZXh0KSAlPiUgc3RyX3JlbW92ZV9hbGwoIjp8XFw/fCd8I3zLiHzLkHxqfMm5fMm6fHd8ICIpLAogICAgICAgIHJlcF92b3dlbHMgICAgICAgICA9IGZ1dHVyZV9tYXBfY2hyKGJhc2VfdHJhbnNjcmlwdGlvbiwgfiBzdHJfZXh0cmFjdF9hbGwoLiwgZ2V0X3Zvd2Vsc19yZWdleCgpKSAlPiUgdW5saXN0KHVzZS5uYW1lcyA9IEZBTFNFKSAlPiUgcGFzdGUwKGNvbGxhcHNlID0gIiIpKQogICAgKSAlPiUKICAgIGdyb3VwX2J5KHNvdXJjZV9maWxlLCBhbm5vdGF0b3IsIHJlcC54bWluLCByZXAueG1heCwgYmFzZV90cmFuc2NyaXB0aW9uKSAlPiUKICAgIG5lc3QoKSAlPiUKICAgIG11dGF0ZSgKICAgICAgICBiYXNlX3Zvd2VsID0gbWFwKGRhdGEsIH4gLiRiYXNlX3Zvd2VsKSwKICAgICAgICBjb25zdF9jdHggPSBmdXR1cmVfbWFwMihiYXNlX3RyYW5zY3JpcHRpb24sIGJhc2Vfdm93ZWwsIH4gZ2V0X2NvbnNfY29udGV4dCgueCwgLnkpKQogICAgKSAlPiUKICAgIHNlbGVjdCgtYmFzZV92b3dlbCkgJT4lCiAgICB1bm5lc3QoKQogICAgCnZvd2Vsc19jb250ZXh0c19kZiAlPiUKICAgIHNlbGVjdChiYXNlX3Zvd2VsLCBiYXNlX3RyYW5zY3JpcHRpb24sIGNvbnN0X2N0eCwgdm93ZWwueG1pbiwgdm93ZWwueG1heCwgcmVwX251bSwgYW5ub3RhdG9yLCBzb3VyY2VfZmlsZSkgJT4lCiAgICBwcmV2aWV3X2RmKGNhcHRpb24gPSAiQ29uc29uYW50YWwgY29udGV4dCBmb3Igdm93ZWwgZGVydmllZCBmcm9tIHJlcGV0aXRpb24tbGV2ZWwgdHJhbnNjcmlwdGlvbiBkYXRhIikKYGBgCgojIyBEaWFnbm9zdGljcyB7LnRhYnNldH0KCiMjIyBNaXNtYXRjaGVkIGJvdW5kYXJ5IG1hcmtlcnMKCkluaXRpYWwtIGFuZCBmaW5hbC12b3dlbHMgYXJlIHJlcXVpcmVkIHRvIGJlIG1hcmtlZCBieSBgI2Agb24sIHJlc3BlY3RpdmVseSwgdGhlIGxlZnQtIGFuZCByaWdodC1lZGdlIG9mIGludGVydmFscyBvbiB0aGUgYHZvd2Vsc2AgdGllci4KVGhlIGZvbGxvd2luZyB0YWJsZSBkaXNwbGF5cyB2b3dlbHMgb24gdGhlIGB2b3dlbHNgIHRpZXIgZm9yIHdoaWNoIHRoZSBkZXJpdmVkIGNvbnNvbmFudGFsIGNvbnRleHQgd2FzIGVpdGhlciBpbml0aWFsIG9yIGZpbmFsLCBidXQgJyMnIGhhZCBub3QgYmVlbiBkZXRlY3RlZCBhcyBwcmVzZW50IGluIHRoZSBpbnRlcnZhbChzKSBvbiB0aGUgYHZvd2Vsc2AgdGllci4KCmBgYHtyIE1ha2UgZGlhZ25vc3RpY3MkbWlzbWF0Y2hlZF9lZGdlc30KZGlhZ25vc3RpY3MkbWlzbWF0Y2hlZF9lZGdlcyA8LQogICAgdm93ZWxzX2NvbnRleHRzX2RmICU+JQogICAgZmlsdGVyKHN0cl9kZXRlY3QoY29uc3RfY3R4LCAiIyIpLCAhc3RyX2RldGVjdCh2b3dlbC50ZXh0LCAiIyIpKQoKZGlhZ25vc3RpY3MkbWlzbWF0Y2hlZF9lZGdlcyAlPiUKICAgIHNlbGVjdChzb3VyY2VfZmlsZSwgYW5ub3RhdG9yLCByZXBfbnVtLCB2b3dlbC54bWluLCB2b3dlbC54bWF4LCBjb25zdF9jdHgsIHZvd2VsLnRleHQsIGJhc2VfdHJhbnNjcmlwdGlvbikgJT4lIAogICAgc2hvd19kaWFnX2RmKCkKYGBgCgoKIyMjIE1pc21hdGNoZWQgdm93ZWxzCgpUaGUgZm9sbG93aW5nIGRhdGEgZnJhbWUgZGlzcGxheXMgcmVwZXRpdGlvbnMgd2hlcmUgd2hlcmUgYGlwYWAgdGllcidzIHZvd2VscyBhbmQgYHZvd2Vsc2AgdGllcidzIHZvd2VscyBhcmUgbWlzbWF0Y2hlZCB3aXRoaW4gdGhlIGdpdmVuIGFubm90YXRvcidzIGBzb3VyY2VfZmlsZWAuCgpgYGB7ciBNYWtlIGRpYWdub3N0aWNzJGlwYV92b3dlbHNfbWlzbWF0Y2hlZH0KZGlhZ25vc3RpY3MkaXBhX3Zvd2Vsc19taXNtYXRjaGVkIDwtCiAgICB2b3dlbHNfY29udGV4dHNfZGYgJT4lIAogICAgcmVuYW1lKGlwYV90aWVyX3Zvd2VscyA9IHJlcF92b3dlbHMpICU+JSAKICAgIGdyb3VwX2J5KHNvdXJjZV9maWxlLCBhbm5vdGF0b3IsIHJlcF9udW0sIGJhc2VfdHJhbnNjcmlwdGlvbiwgaXBhX3RpZXJfdm93ZWxzKSAlPiUKICAgIHN1bW1hcmlzZSh2b3dlbHNfdGllcl92b3dlbHMgPSBwYXN0ZTAoYmFzZV92b3dlbCwgY29sbGFwc2UgPSAiIikpICU+JQogICAgdW5ncm91cCAlPiUgCiAgICBmaWx0ZXIodm93ZWxzX3RpZXJfdm93ZWxzICE9IGlwYV90aWVyX3Zvd2VscykKCmRpYWdub3N0aWNzJGlwYV92b3dlbHNfbWlzbWF0Y2hlZCAlPiUKICAgIHNob3dfZGlhZ19kZihjYXB0aW9uID0gIldvcmQgcmVwZXRpdGlvbnMgd2hlcmUgJ2lwYScgYW5kICd2b3dlbHMnIHRpZXJzJyB2b3dlbHMgYXJlIG1pc21hdGNoZWQiKQpgYGAKCgojIyMgVW5kZXJpdmFibGUgY29uc29uYW50YWwgY29udGV4dAoKVGhlIGRhdGEgZnJhbWUgYmVsb3cgZGlzcGxheXMgdm93ZWxzIGZvciB3aGljaCBjb25zb25hbnRhbCBjb250ZXh0IGNvdWxkIG5vdCBiZSBkZXJpdmVkLiAKVHJvdWJsZXNob290aW5nIGZvciB0aGVzZSBvYnNlcnZhdGlvbnMgd2lsbCBwcm9iYWJseSByZXF1aXJlIGEgYml0IG9mIGRldGVjdGl2ZSB3b3JrLgoKYGBge3IgTWFrZSBkaWFnbm9zdGljcyRjb25zdF9jdHhfbmF9CmRpYWdub3N0aWNzJGNvbnN0X2N0eF9uYSA8LQogICAgdm93ZWxzX2NvbnRleHRzX2RmICU+JQogICAgZmlsdGVyKGlzLm5hKGNvbnN0X2N0eCkpCiAgICAKZGlhZ25vc3RpY3MkY29uc3RfY3R4X25hICU+JQogICAgc2VsZWN0KHNvdXJjZV9maWxlLCBhbm5vdGF0b3IsIHJlcF9udW0sIHZvd2VsLnhtaW4sIHZvd2VsLnhtYXgsIGJhc2Vfdm93ZWwsIGJhc2VfdHJhbnNjcmlwdGlvbiwgY29uc3RfY3R4KSAlPiUgCiAgICBzaG93X2RpYWdfZGYoKQpgYGAKCiMgTWVkaWFsIHZvd2VscywgcHJlLXByb2Nlc3NlZAoKRXhjbHVkaW5nIGFueSB2b3dlbHMgaW4gdGhlIGRpYWdub3N0aWMgdGFibGVzIGFib3ZlLCB3ZSBrbm93IHRoYXQgd2l0aGluIHRoZSByZW1haW5pbmcgZGF0YSB0aGF0IGEpIHZvd2VscyBhbmQgaXBhIHRpZXJzJyB2b3dlbCBsYWJlbHMgYXJlIG1hdGNoZWQgYW5kLCB0aGVyZWZvcmUsIGIpIHdlIGhhdmUgYSBkZXJ2aWVkIGNvc29uYW50YWwgY29udGV4dC4KClRodXMsIHdlIGNhbiBleGNsdWRlIGFsbCBpbml0aWFsIGFuZCBmaW5hbCB2b3dlbHMgKGkuZS4gd2hlcmUgYCNgIGRldGVjdGVkIGluIGBjb25zdF9jdHhgKSwgYW5kIHRoZW4gZnVydGhlciBleGNsdWRlIHJlcGV0aXRpb25zIGZvciB3aGljaCBhbm5vdGF0b3JzIGRpc2FncmVlIG9uIHRoZSBudW1iZXIgb2YgbWVkaWFsIHZvd2Vscy4KCkZpbmFsbHksIHdlIGFsc28gZXhjbHVkZSB2b3dlbCB0cmFuc2NyaXB0aW9ucyBub3QgaGF2aW5nIGF0IGxlYXN0IDIgZGlmZmVyZW50IGFubm90YXRvcnMuCgpgYGB7ciBNYWtlIHZvd2Vsc19tZWRfcHJlcHJvY2Vzc2VkLCBtZXNzYWdlPUZBTFNFfQp2b3dlbHNfbWVkX3JhdyA8LQogICAgdm93ZWxzX2NvbnRleHRzX2RmICU+JQogICAgZmlsdGVyKCFzdHJfZGV0ZWN0KGNvbnN0X2N0eCwgIiMiKSwgbmNoYXIoYmFzZV92b3dlbCkgPT0gMSkgJT4lICAgIyBLZWVwIG9ubHkgbWVkaWFsIG1vbm9waHRob25ncwogICAgcmVkdWNlKC54ID0gZGlhZ25vc3RpY3MsIC5mID0gYW50aV9qb2luLCAuaW5pdCA9IC4pICAgICAgICAgICAgICAgIyBEaXNjYXJkIHZvd2VscyB3aXRoIGFueSBtYXRjaGVzIGluIGFueSBvZiB0aGUgZGlhZ25vc3RpY3MgZGF0YSBmcmFtZXMKICAgIAp2b3dlbHNfbWVkX3ByZXByb2Nlc3NlZCA8LQogICAgdm93ZWxzX21lZF9yYXcgJT4lIAogICAgZ3JvdXBfYnkoc291cmNlX2ZpbGUsIHJlcF9udW0sIGFubm90YXRvcikgJT4lCiAgICBuZXN0KCkgJT4lCiAgICBtdXRhdGUobl92b3dlbHMgPSBtYXBfaW50KGRhdGEsIG5yb3cpKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICMgZmluZCBudW1iZXIgb2Ygdm93ZWxzIHBlciB3b3JkIHJlcCBwZXIgYW5ub3RhdG9yCiAgICBncm91cF9ieShzb3VyY2VfZmlsZSwgcmVwX251bSkgJT4lCiAgICBmaWx0ZXIobl9kaXN0aW5jdChuX3Zvd2VscykgPT0gMSkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICMga2VlcCB3b3JkIHJlcCBpZmYgYWxsIGFubm90YXRvciBhZ3JlZSBvbiBudW0gb2Ygdm93ZWxzCiAgICBzZWxlY3QoLW5fdm93ZWxzKSAlPiUKICAgIHVubmVzdCgpICU+JQogICAgCiAgICBncm91cF9ieShzb3VyY2VfZmlsZSwgcmVwX251bSwgYW5ub3RhdG9yKSAlPiUgICAgICAgICAgICAgICAgICAgICMgZ2l2ZW4gYWxsIGFubm90YXRvcnMgYWdyZWUsIG9ubHkgbm93IGdpdmUgc2VxdWVudGlhbCBpZHMKICAgIG11dGF0ZSh2b3dlbF9udW0gPSAxOm4oKSkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyAgIHRvIHZvd2VsIHRva2VucyB3aXRoaW4gd29yZCByZXBldGl0aW9ucyAoZS5nLiByZXAgMiB2b3dlbCAxKQoKICAgIGdyb3VwX2J5KHNvdXJjZV9maWxlLCByZXBfbnVtLCB2b3dlbF9udW0pICU+JQogICAgZmlsdGVyKGRwbHlyOjpuX2Rpc3RpbmN0KGFubm90YXRvcikgPj0gMikgJT4lICAgICAgICAgICAgICAgICAgICAjIGtlZXAgb25seSB2b3dlbCB0b2tlbnMgd2l0aCBtb3JlIHRoYW4gMSBhbm5vdGF0b3IKICAgIHVuZ3JvdXAoKSAlPiUKCiAgICBzZWxlY3QoCiAgICAgICAgc291cmNlX2ZpbGUsIHJlcF9udW0sIHZvd2VsX251bSwgYmFzZV92b3dlbCwgY29uc3RfY3R4LCBiYXNlX3RyYW5zY3JpcHRpb24sIGFubm90YXRvciwKICAgICAgICB2b3dlbC50ZXh0LCB2b3dlbC54bWluLCB2b3dlbC54bWF4LCByZXAudGV4dCwgcmVwLnhtaW4sIHJlcC54bWF4CiAgICApCiAgICAKdm93ZWxzX21lZF9wcmVwcm9jZXNzZWQgJT4lCiAgICBzZWxlY3Qoc291cmNlX2ZpbGUsIHJlcF9udW0sIHZvd2VsX251bSwgYmFzZV92b3dlbCwgY29uc3RfY3R4LCBhbm5vdGF0b3IsIHZvd2VsLnhtaW4sIHZvd2VsLnhtYXgpICU+JSAKICAgIHByZXZpZXdfZGYoY2FwdGlvbiA9ICJQcmVwcm9jZXNzZWQgbWVkaWFsIHZvd2VsIGFubm90YXRpb25zIHdpdGggYXQgbGVhc3QgMiB0cmFuc2NyaWJlcnMgcGVyIHZvd2VsIikKYGBgCgojIEpvaW4gY29ycmVzcG9uZGluZyBtaWQtcG9pbnQgZm9ybWFudCB2YWx1ZXMgZm9yIHByZS1wcm9jZXNzZWQgbWVkaWFsIHZvd2VscwoKIyMgUmVhZCBpbiBmb3JtYW50IGRhdGEgYW5kIHJlbW92ZSBvdXRsaWVycyB7LnRhYnNldH0KCmBgYHtyIEZvcm1hbnRzIGhlbHBlcnMsIGluY2x1ZGU9RkFMU0V9CnJlbW92ZV9vdXRsaWVyX2Zvcm1hbnRzIDwtIGZ1bmN0aW9uKGRmKSB7CiAgICBuX2Zvcm1hbnRzICA8LSAyCiAgICBmb3Jlc3RfY29scyA8LSBwYXN0ZTAoImZvcmVzdC5mIiwgMTpuX2Zvcm1hbnRzKQogICAgcHJhYXRfY29scyAgPC0gcGFzdGUwKCJwcmFhdC5mIiwgMTpuX2Zvcm1hbnRzKQogICAgCiAgICBkZiAlPiUKICAgICAgICAjIFVuZG8gYW55IGN1cnJlbnQgZ3JvdXBpbmcgZm9yIHBlcmZvcm1pbmcgcm93LXdpc2UgY2FsY3VsYXRpb25zCiAgICAgICAgdW5ncm91cCAlPiUKICAgICAgICAKICAgICAgICBzZXBhcmF0ZShmb3Jlc3QsIGZvcmVzdF9jb2xzKSAlPiUgCiAgICAgICAgc2VwYXJhdGUocHJhYXQsIHByYWF0X2NvbHMsIHNlcCA9ICItIikgJT4lCiAgICAgICAgbXV0YXRlX2F0KHZhcnMobWF0Y2hlcygiZm9yZXN0fHByYWF0IikpLCBmdW5zKHN1cHByZXNzV2FybmluZ3MoYXMubnVtZXJpYyguKSkpKSAlPiUKICAgICAgICBtdXRhdGUoCiAgICAgICAgICAgICMgWi1ub3JtYWxpc2UgYWxsIEYxIGFuZCBGMiBtZWFzdXJlcywgYW5kIGRpZmZzCiAgICAgICAgICAgIGZvcmVzdC5mMV96ID0gc2NhbGUoZm9yZXN0LmYxKSwKICAgICAgICAgICAgZm9yZXN0LmYyX3ogPSBzY2FsZShmb3Jlc3QuZjIpLAogICAgICAgICAgICBwcmFhdC5mMV96ICA9IHNjYWxlKHByYWF0LmYxKSwKICAgICAgICAgICAgcHJhYXQuZjJfeiAgPSBzY2FsZShwcmFhdC5mMiksCiAgICAgICAgICAgICMgRGlmZmVyZW5jZSBpbiBIeiBiZXR3ZWVuIEZvcmVzdCBhbmQgUHJhYXQgdHJhY2tlcnMKICAgICAgICAgICAgZjFfZGlmZl96ICAgICA9IHNjYWxlKGZvcmVzdC5mMSAtIHByYWF0LmYxKSwKICAgICAgICAgICAgZjJfZGlmZl96ICAgICA9IHNjYWxlKGZvcmVzdC5mMiAtIHByYWF0LmYyKQogICAgICAgICkgJT4lIAogICAgICAgIGZpbHRlcigKICAgICAgICAgICAgIyBLZWVwIG9ubHkgbWVhc3VyZXMgYm90aCB3aXRoaW4gMyBzdGFuZGFyZCBkZXZpYXRpb25zIG9mCiAgICAgICAgICAgICMgd2l0aGluLXRyYWNrZXIgbWVhbiBhbmQgYmV0d2Vlbi10cmFja2VyIGRpZmZlcmVuY2UgbWVhbgogICAgICAgICAgICBiZXR3ZWVuKGZvcmVzdC5mMV96LCAtMywgMyksIGJldHdlZW4oZm9yZXN0LmYyX3osIC0zLCAzKSwKICAgICAgICAgICAgYmV0d2VlbihwcmFhdC5mMV96LCAtMywgMyksIGJldHdlZW4ocHJhYXQuZjJfeiwgLTMsIDMpLAogICAgICAgICAgICBiZXR3ZWVuKGYxX2RpZmZfeiwgLTMsIDMpLCBiZXR3ZWVuKGYyX2RpZmZfeiwgLTMsIDMpCiAgICAgICAgKSAKfQpgYGAKCgojIyMgUmF3IGRhdGEKClJlYWQgaW4gaW4gcmF3IGRhdGEgZm9yIGVhY2ggZm9ybWF0IHRyYWNrZXIgKGZvcmVzdCwgcHJhYXQpIGZyb20gYWxsIGBmb3JtYW50cy4qLmNzdmAgZmlsZXMgdW5kZXIgdGhlIGByIHBhcmFtcyRrZGljdF9wYXRoYCBkaXJlY3RvcnkuCgpgYGB7ciBNYWtlIGtkaWN0X2Zvcm1hbnRzfQprZGljdF9mb3JtYW50cyA8LSByZWFkX2tkaWN0X2Zvcm1hbnRzKGtkaWN0X3BhdGggPSBwYXJhbXMka2RpY3RfcGF0aCkKICAgIAprZGljdF9mb3JtYW50cyAlPiUKICAgIHNhbXBsZV9uKDEwKSAlPiUKICAgIHByZXZpZXdfZGYoKQpgYGAKCiMjIyBTZWxlY3QgbWlkLXBvaW50IGZvcm1hbnRzLCB0aGVuIHJlbW92ZSBvdXRsaWVycwoKV2UgZGVmaW5lIG91dGxpZXJzIGhlcmUgYXMgYW55IGRhdHVtIG91dHNpZGUgMyBzdGFuZGFyZCBkZXZpYXRpb25zIG9mIHRoZSByZXNwZWN0aXZlIG1lYW4uCldlIHRodXMgZGVyaXZlIGFuZCBmaWx0ZXIgYmFzZWQgb24gei1zY29yZXMgZm9yOgoKLSBhKSB3aXRoaW4tdHJhY2tlciB2YXJpYW5jZSwgYW5kCi0gYikgZGlmZmVyZW5jZSBpbiBtZWFzdXJlbWVudCBiZXR3ZWVuIHRoZSBGb3Jlc3QgYW5kIFByYWF0IGZvcm1hbnQgdHJhY2tlcnMKCgpgYGB7ciBNYWtlIGZvcm1hbnRzX3ByZXByb2Nlc3NlZH0KZm9ybWFudHNfcHJlcHJvY2Vzc2VkIDwtCiAgICB2b3dlbHNfbWVkX3ByZXByb2Nlc3NlZCAlPiUKICAgIHNlbGVjdChzb3VyY2VfZmlsZSwgcmVwX251bSwgYW5ub3RhdG9yLCB2b3dlbF9udW0sIHZvd2VsLnhtaW4sIHZvd2VsLnhtYXgpICU+JQogICAgbGVmdF9qb2luKGtkaWN0X2Zvcm1hbnRzLCBieSA9ICJzb3VyY2VfZmlsZSIpICU+JQogICAgZmlsdGVyKHRpbWUgPj0gdm93ZWwueG1pbiwgdGltZSA8PSB2b3dlbC54bWF4KSAlPiUKCiAgICBncm91cF9ieShzb3VyY2VfZmlsZSwgcmVwX251bSwgYW5ub3RhdG9yLCB2b3dlbF9udW0sIHRyYWNrZXIpICU+JQogICAgZmlsdGVyKHJvd19udW1iZXIoKSA9PSBjZWlsaW5nKG4oKS8yKSkgJT4lICMga2VlcCBvbmx5IG1pZC1wb2ludCBtZWFzdXJlCiAgICB1bmdyb3VwKCkgJT4lCgogICAgdW5pdGUoZm9ybWFudHMsIGYxLCBmMiwgc2VwID0gIi0iKSAlPiUKICAgIHNlbGVjdChzb3VyY2VfZmlsZSwgcmVwX251bSwgYW5ub3RhdG9yLCB2b3dlbF9udW0sIHRyYWNrZXIsIGZvcm1hbnRzKSAlPiUKICAgIHNwcmVhZCh0cmFja2VyLCBmb3JtYW50cykgJT4lCiAgICByZW1vdmVfb3V0bGllcl9mb3JtYW50cygpCiAgICAKZm9ybWFudHNfcHJlcHJvY2Vzc2VkICU+JQogICAgcHJldmlld19kZihjYXB0aW9uID0gIkZvcm1hbnQgdmFsdWVzIGZpbHRlcmVkIG9uIGJlaW5nIHdpdGhpbiArLy0gMyBTRCBvZiByZXNwZWN0aXZlIG1lYW5zIGZvciB7Zm9yZXN0LHByYWF0fS5mezEsMn1feiBhbmQgZnsxLDJ9X2RpZmZfeiIpCmBgYAoKIyBBbmFseXNpcyBkYXRhIGZyYW1lIGZvciBtZWRpYWwgdm93ZWxzCgpgYGB7ciBNYWtlIHZvd2Vsc19tZWRfYW5hbHlzaXMsIG1lc3NhZ2U9RkFMU0V9CnZvd2Vsc19tZWRfYW5hbHlzaXMgPC0gCiAgICBsZWZ0X2pvaW4oCiAgICAgICAgeCA9IHZvd2Vsc19tZWRfcHJlcHJvY2Vzc2VkLAogICAgICAgIHkgPSBzZWxlY3QoZm9ybWFudHNfcHJlcHJvY2Vzc2VkLCAtY29udGFpbnMoImRpZmYiKSkKICAgICkgJT4lCiAgICBmaWx0ZXJfYWxsKGFsbF92YXJzKCFpcy5uYSguKSkpICU+JQogICAgZ3JvdXBfYnkoc291cmNlX2ZpbGUsIHJlcF9udW0sIHZvd2VsX251bSkKICAgIAptZWRfYW5hbHlzaXNfY292ZXJhZ2UgPC0KICAgIGAvYCgKICAgICAgICBucm93KHZvd2Vsc19tZWRfYW5hbHlzaXMpLAogICAgICAgIG5yb3codm93ZWxzX21lZF9yYXcpCiAgICApICU+JSAgICAgCiAgICBgKmAoMTAwKSAlPiUgCiAgICByb3VuZChkaWdpdHMgPSAwKSAKICAgIAp2b3dlbHNfbWVkX2FuYWx5c2lzICU+JQogICAgdW5ncm91cCgpICU+JSAKICAgIHNhbXBsZV9uKDEwKSAlPiUgCiAgICBzZWxlY3Qoc291cmNlX2ZpbGU6Y29uc3RfY3R4LCBhbm5vdGF0b3IsIGZvcmVzdC5mMTpwcmFhdC5mMl96KQogICAgCmBgYAoKIyMgQ292ZXJhZ2Ugb2YgcHJlLXByb2Nlc3NlZCBkYXRhc2V0OiBgciBtZWRfYW5hbHlzaXNfY292ZXJhZ2VgJQoKVGhlIGFuYWx5c2lzIGRhdGEgaW4gYHZvd2Vsc19tZWRfYW5hbHlzaXNgIHJldGFpbnMgYHIgbnJvdyh2b3dlbHNfbWVkX2FuYWx5c2lzKWAgcm93cyBvdXQgb2YgYHIgbnJvdyh2b3dlbHNfbWVkX3JhdylgIGluIGB2b3dlbHNfbWVkX3Jhd2AuCgojIFdyaXRlIGRhdGEgKG5vdCBydW4gYnkgZGVmYXVsdCkgCgojIyBXcml0ZSBgdm93ZWxzX21lZF9hbmFseXNpcy5jc3ZgCgpUbyBoYXZlIGRhdGEgKG92ZXIpd3JpdHRlbiwgY2hhbmdlIHRoZSBgd3JpdGVfY3N2c2AgcGFyYW1ldGVyLgoKYGBge3IgV3JpdGUgdm93ZWxzX21lZF9hbmFseXNpcywgZXZhbCA9IHBhcmFtcyR3cml0ZV9jc3ZzfQpyZWFkcjo6d3JpdGVfY3N2KAogICAgeCAgICA9IHZvd2Vsc19tZWRfYW5hbHlzaXMsCiAgICBwYXRoID0gZmlsZS5wYXRoKHBhcmFtcyRvdXRwdXRfcGF0aCwgInZvd2Vsc19tZWRfYW5hbHlzaXMuY3N2IikKKQpgYGAKCiMjIFdyaXRlIGBkaWFnbm9zdGljcy8qLmNzdmAKCmBgYHtyIFdyaXRlIGRpYWdub3N0aWNzLCBldmFsID0gcGFyYW1zJHdyaXRlX2NzdnN9CmRpYWdzX2RpciA8LSBmaWxlLnBhdGgocGFyYW1zJG91dHB1dF9wYXRoLCAiZGlhZ25vc3RpY3MiKQogICAgCmlmKCFkaXIuZXhpc3RzKGRpYWdzX2RpcikpIHsgZGlyLmNyZWF0ZShkaWFnc19kaXIpIH0KCmRpYWdub3N0aWNzICU+JQogICAgaXdhbGsofiByZWFkcjo6d3JpdGVfY3N2KHggPSAueCwgcGF0aCA9IGZpbGUucGF0aChkaWFnc19kaXIsIHBhc3RlMCgueSwgIi5jc3YiKSksIG5hID0gIiIpKQpgYGAKCgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgpoMSwgaDIsIGgzLCBoNCwgaDUgeyBmb250LXNpemU6MS4xZW0gfQo8L3N0eWxlPgo=