Embeddings

Pre Process

Load Libraries

We’ll be using the quanteda package for text processing and quanteda.textmodels for embeddings.

library(quanteda)
library(quanteda.textmodels)
library(tidyverse)
library(ggwordcloud)
library(jsonlite)
library(httr)
library(jsonlite)

Load Data

To utilize text embeddings using quanteda, our raw data should meet the following structure:

  • A CSV file containing two columns: one for the ID (Subject.ID) and another for text (Words.Spoken).
data <- read.csv("full_text.csv")
head(data, n = 2)

Corpus Data

Convert data into corpus format to apply NLP transformations in quanteda by defining ID and text columns names.

# Define id and text column to conduct a text corpus
data_corp <- corpus(data,
                        docid_field = "Subject.ID",
                        text_field = "Words.Spoken")

head(data_corp)
Corpus consisting of 6 documents.
117 :
"&  – טוב, מוכן, אודי?   – יאללה, שלוש, ארבע...   – אוקי. דוג..."

118 :
" – אני מוכן.   – ארבע, ו...   – אה, לי יש "מה אתה לומד או עו..."

123 :
" &   – רגע, אני לא יודעת איפה זה. אוקיי. רגע... סבבה. עכשיו ..."

124 :
" – שלוש, ארבע...?   – יאללה.   – אה, יש עוד דף כזה? לא, עברת..."

129 :
"129_130  – קיצור מי ש… אני מתחילה אני שואלת את השאלה ואז אני..."

130 :
" – בדיוק, יהיה בסדר יאללה. אנחנו מעבירות?  -יאללה שלוש ארבע ..."

Text Tokenization

Text tokenization involves splitting each document into smaller units, called tokens, which are typically individual words. By default, the tokens() function splits the text at spaces, treating each word as a separate token. In this case, we also remove punctuation to further clean the data, ensuring only meaningful text is analyzed.

# Text Tokenization: Split text into single words, remove punctuation
data_token <- tokens(data_corp, 
                            remove_punct = TRUE)
head(data_token)
Tokens consisting of 6 documents.
117 :
 [1] "טוב"   "מוכן"  "אודי"  "יאללה" "שלוש"  "ארבע"  "אוקי"  "דוגמה" "למסך"  "יש"    "לי"    "נתקדם"
[ ... and 2,473 more ]

118 :
 [1] "אני"  "מוכן" "ארבע" "ו"    "אה"   "לי"   "יש"   "מה"   "אתה"  "לומד" "או"   "עושה"
[ ... and 2,110 more ]

123 :
 [1] "רגע"    "אני"    "לא"     "יודעת"  "איפה"   "זה"     "אוקיי"  "רגע"    "סבבה"   "עכשיו"  "עוברים" "להבא"  
[ ... and 1,837 more ]

124 :
 [1] "שלוש"  "ארבע"  "יאללה" "אה"    "יש"    "עוד"   "דף"    "כזה"   "לא"    "עברתי" "את"    "העמוד"
[ ... and 1,741 more ]

129 :
 [1] "129_130" "קיצור"   "מי"      "ש"       "אני"     "מתחילה"  "אני"     "שואלת"   "את"      "השאלה"   "ואז"     "אני"    
[ ... and 1,671 more ]

130 :
 [1] "בדיוק"   "יהיה"    "בסדר"    "יאללה"   "אנחנו"   "מעבירות" "יאללה"   "שלוש"    "ארבע"    "ו"       "אוקי"    "אה"     
[ ... and 2,865 more ]

Text Lemmatization

Lemmatization is the process of reducing words to their base or dictionary form, known as lemmas. we utilize the nakdan API, which provides a comprehensive Hebrew lemma dictionary. This API allows us to process a list of Hebrew words and return their corresponding base forms.


BaseWords <- function(words) {
  headers <- c('Content-Type' = 'application/json;charset=utf-8')
  # API Configurations
  params <- list(
    task = "nakdan",
    genre = "modern",
    data = paste(words, collapse = " "),
    addmorph = TRUE,
    matchpartial = TRUE,
    apiKey = "4b65be84-35f2-443b-ab3d-b18f3b82b27d"  # API key
  )
  
  response <- POST(
    url = "https://nakdan-3-2.loadbalancer.dicta.org.il/addnikud",
    add_headers(.headers = headers),
    body = params,
    encode = "json"
  )
  
  if (response$status_code != 200) {
    stop("API request failed with status code: ", response$status_code)
  }

  response_data <- fromJSON(content(response, as = "text", encoding = "UTF-8"), simplifyVector = FALSE)
  
  sapply(words, function(word) {
    idx <- which(sapply(response_data, function(x) x$word) == word)
    if (length(idx) == 0) return(word)  # Use original word if not found
    base_word <- response_data[[idx[1]]]
    if (!is.null(base_word$options) && length(base_word$options) > 0) {
      return(base_word$options[[1]]$lex)
    }
    return(base_word$word)
  })
}

Lemmatizing Example:

hello_wolrd <- c("שלומי", "עולמות")
BaseWords(hello_wolrd)
 שלומי עולמות 
"שָׁלוֹם" "עוֹלָם" 

Replace Tokens With Lemmas

# Step 1: Extract unique tokens
unique_tokens <- types(data_token)

# Step 2: Split tokens into batches to speed up the process
batch_size <- 250  # Adjust the batch size if necessary
token_batches <- split(unique_tokens, ceiling(seq_along(unique_tokens) / batch_size))

# Step 3: Apply BaseWords to each batch
lemmas_list <- lapply(token_batches, BaseWords)

# Combine lemmas into a single vector
lemmas <- unlist(lemmas_list, use.names = FALSE)

# Step 4: Create a token-to-lemma mapping
token_to_lemma <- setNames(lemmas, unique_tokens)

# Step 5: Replace tokens with lemmas
data_lemma <- tokens_replace(
  data_token,
  pattern = unique_tokens,
  replacement = lemmas,
  valuetype = "fixed"
)

head(data_lemma)
Tokens consisting of 6 documents.
117 :
 [1] "טוֹב"   "מוּכָן"  "אוּדִי"  "יַאלְלָה" "שְׁלוֹשָׁה" "אַרְבָּעָה" "אוֹקֵי"  "דֻּגְמָה"  "מָסָךְ"   "יֵשׁ"    "לְ"     "קדם"  
[ ... and 2,473 more ]

118 :
 [1] "אֲנִי"   "מוּכָן"  "אַרְבָּעָה" "ו"     "אָה"    "לְ"     "יֵשׁ"    "מָה"    "אֲנִי"   "למד"   "אוֹ"    "עשׂי"  
[ ... and 2,110 more ]

123 :
 [1] "רֶגַע"  "אֲנִי"  "לֹא"   "ידע"  "אֵיפֹה" "זֶה"   "אוֹקֵי" "רֶגַע"  "סַבַּבָּה" "עַכְשָׁו" "עֻבָּר"  "לְהַבָּא"
[ ... and 1,837 more ]

124 :
 [1] "שְׁלוֹשָׁה" "אַרְבָּעָה" "יַאלְלָה" "אָה"    "יֵשׁ"    "עוֹד"   "דַּף"    "זֶה"    "לֹא"    "עבר"   "אֶת"    "עַמּוּד" 
[ ... and 1,741 more ]

129 :
 [1] "129_130" "קִצּוּר"    "מִי"      "ש"       "אֲנִי"     "תחל"     "אֲנִי"     "שׁאל"     "אֶת"      "שְׁאֵלָה"    "אָז"      "אֲנִי"    
[ ... and 1,671 more ]

130 :
 [1] "בְּדִיּוּק" "היי"   "בְּסֵדֶר"  "יַאלְלָה" "אֲנִי"   "עֲבִירָה" "יַאלְלָה" "שְׁלוֹשָׁה" "אַרְבָּעָה" "ו"     "אוֹקֵי"  "אָה"   
[ ... and 2,865 more ]

Document-Feature Matrix (DFM):

Token Frequencies Table

We create a document-feature matrix (DFM) to examine word frequency distributions across participants. Each token acts as a feature in the frequency table, with rows representing subjects.

data_dfm <- dfm(data_lemma)

head(data_dfm)
Document-feature matrix of: 6 documents, 5,247 features (91.09% sparse) and 0 docvars.
     features
docs     טוֹב מוּכָן אוּדִי יַאלְלָה שְׁלוֹשָׁה אַרְבָּעָה אוֹקֵי דֻּגְמָה מָסָךְ
  117 39  23    2    2     8    13    16   19    3   2
  118 26  14    2    1    14     8    13   12    0   2
  123 14  12    0    0     0     4     5   10    0   0
  124 11   5    0    0     4    10     5    5    1   0
  129  6  27    2    0     0    18    18    8    0   1
  130 12  15    1    0     5    13    15    8    0   1
[ reached max_nfeat ... 5,237 more features ]

Trim Data

The DFM includes many rare tokens, which can introduce noise into the analysis. To address this, we filter out tokens that occur in less than 1% of the documents, a common threshold for eliminating irrelevant words.

# Define Token Frequency Threshold within documents
data_trim <- data_dfm |> # Omit tokens the appear in less then 1% of documents
  dfm_trim(min_docfreq = 0.01, docfreq_type = "prop")

head(data_trim)
Document-feature matrix of: 6 documents, 2,899 features (84.71% sparse) and 0 docvars.
     features
docs     טוֹב מוּכָן אוּדִי יַאלְלָה שְׁלוֹשָׁה אַרְבָּעָה אוֹקֵי דֻּגְמָה מָסָךְ
  117 39  23    2    2     8    13    16   19    3   2
  118 26  14    2    1    14     8    13   12    0   2
  123 14  12    0    0     0     4     5   10    0   0
  124 11   5    0    0     4    10     5    5    1   0
  129  6  27    2    0     0    18    18    8    0   1
  130 12  15    1    0     5    13    15    8    0   1
[ reached max_nfeat ... 2,889 more features ]

We significantly decreased the number of By trimming the DFM, we significantly reduce the number of features and focus on the most informative tokens.

TF-IDF: Token Frequencies Normalization

Term Frequency-Inverse Document Frequency (TF-IDF) is a method to down-weight common, less informative words while emphasizing unique and meaningful tokens. This reduces the bias toward frequently occurring words and highlights important words in each document.

# TF-IDF scaled dfm
data_tfidf <- data_trim |>
  dfm_tfidf(scheme_tf = "prop")

head(data_tfidf)
Document-feature matrix of: 6 documents, 2,899 features (84.71% sparse) and 0 docvars.
     features
docs               טוֹב         מוּכָן         אוּדִי        יַאלְלָה        שְׁלוֹשָׁה        אַרְבָּעָה         אוֹקֵי         דֻּגְמָה          מָסָךְ
  117 3.553290e-04   0 2.233139e-04 0.0013104785 0.0003622030 1.387879e-04 0.0004898806 2.631113e-04 0.0003105601 0.0005572525
  118 2.764612e-04   0 2.606217e-04 0.0007647064 0.0007397500 9.967657e-05 0.0004645243 1.939376e-04 0            0.0006503496
  123 1.703274e-04   0 0            0            0            5.702415e-05 0.0002044235 1.849168e-04 0            0           
  124 1.431310e-04   0 0            0            0.0002586409 1.524696e-04 0.0002186328 9.888513e-05 0.0001478425 0           
  129 7.993922e-05   0 3.265565e-04 0            0            2.810110e-04 0.0008059077 1.620013e-04 0            0.0004074409
  130 9.376245e-05   0 9.575632e-05 0            0.0001941394 1.190237e-04 0.0003938611 9.500741e-05 0            0.0002389482
[ reached max_nfeat ... 2,889 more features ]

Latent Semantic Analysis (LSA)

Latent Semantic Analysis (LSA) is a dimensionality reduction technique that identifies patterns in the relationships between terms (words) and documents (subjects). By applying Singular Value Decomposition (SVD) to a term-document matrix, LSA captures the underlying semantic structure of the text data. This method focuses on the most relevant topics or concepts, effectively grouping words into semantic fields and revealing trends where words commonly occur together within the same contexts—in this case, the subjects’ transcripts.

By proceeding with 10 dimensions (the function’s default), we maintain a model that is both robust and interpretable. To visualize the contribution of additional dimensions, we can plot the singular values, which reveal an “elbow” point around the 10-dimensional mark, suggesting 10 dimensions are sufficient to capture the essential semantic structures in our data without introducing unnecessary complexity.

data_lsa_max <- textmodel_lsa(data_tfidf, nd = 119)
singular_values <- data_lsa_max$sk

plot(singular_values, type = "b", xlab = "Dimensions", ylab = "Singular Values", main = "Singular Value Decay")

# Apply LSA with 10
data_lsa <- textmodel_lsa(data_tfidf)

After applying LSA, the text data is embedded into a reduced-dimensional space. Each document (row) represents a specific context or subject, with the 10 dimensions (columns) capturing distinct semantic relationships. Tokens are considered similar if they frequently appear among same subjects.

Predictive Variable: GOI

Pre Process

Load Data

GOI <- read.csv("Data.csv")
GOI <- GOI |>
  arrange(iSubject) |>
  select(iSubject, iPartner, iDyad, GOI)

head(GOI)
NA

Add GOI to data

sub_doc_df <- as.data.frame(data_lsa$docs)
head(sub_doc_df)
NA

We will insert the interaction quality score (GOI) to our data

GOI <- sub_doc_df |>
  rownames_to_column(var = "iSubject") |> # Make ID column
  arrange(iSubject) |> # Arrange to fit GOI structure
  mutate(iPartner = GOI$iPartner,# Add partner column
         iDyad = GOI$iDyad,
         GOI = GOI$GOI) |> # Add GOI column
  select(iSubject, iPartner, iDyad, GOI, everything())

head(GOI)
NA

Synchrony

We can now explore similarities between subjects using common distance metrics such as cosine similarity, allowing for deeper insights into shared linguistic patterns. to do so we will first need to add the partner variables to our data.

GOI_merged <- GOI %>%
  mutate(iSubject = as.numeric(iSubject),
         iPartner = as.numeric(iPartner)) |>
  left_join(GOI, by = c("iSubject" = "iPartner"), suffix = c("", "_partner")) |>
  select(-iSubject_partner)

head(GOI_merged)

Cosine Similarity Calculation

We will now calculate the cosine similarities between subjects semantic vectors.

library(proxy)

Attaching package: ‘proxy’

The following object is masked from ‘package:Matrix’:

    as.matrix

The following objects are masked from ‘package:stats’:

    as.dist, dist

The following object is masked from ‘package:base’:

    as.matrix
cosine_similarities <- numeric(nrow(GOI_merged))

for (i in 1:nrow(GOI_merged)) {
  # Extract subject and partner vector:
  subject_vector <- as.numeric(GOI_merged[i, 5:14])
  partner_vector <- as.numeric(GOI_merged[i, 17:26])
  
  # Bind vectors into single matrix
  vectors <- rbind(subject_vector, partner_vector)
  
  # Calculate cosine similarity
  cosine_similarity <- 1 - proxy::dist(vectors, method = "cosine")
  
  # Store result in the vector
  cosine_similarities[i] <- cosine_similarity
}  

GOI_merged$cosine_similarity <- cosine_similarities

GOI <- GOI_merged |>
  select(iSubject, iPartner, iDyad, cosine_similarity, GOI, everything(), -ends_with("_partner"))

head(GOI)
NA
NA
NA

Predictive Model Configuration

Load Libraries

library(caret)
library(recipes)
library(rsample)
library(yardstick)

Recipe

Predict GOI with d1 + d2 + …d10 + cosine similarity

# Predict GOI with all variables 
rec <- recipe(GOI ~ ., data = GOI) |>
  step_rm(iSubject, iPartner, iDyad)

rec

── Recipe ──────────────────────────────────────────────────────────────────────────────────────

── Inputs 
Number of variables by role
outcome:    1
predictor: 14

── Operations 
• Variables removed: iSubject, iPartner, iDyad

Cross Validation: Leave one Dyad Out

We will conduct leave one dyad out cross validation, testing our tuning on each dyad separtly.

# Extract iDyad for indexing
GOI_index <- GOI %>%
  select(iDyad)

# Create a vector of unique dyad IDs and make folds for LODOCV
dyads <- unique(GOI_index$iDyad)
n_dyads <- length(dyads)  # Number of dyads (should be 60)
folds <- lapply(dyads, function(d) which(GOI_index$iDyad != d))
names(folds) <- paste0("Fold", dyads)

# 4. Set up trainControl for Leave-One-Dyad-Out Cross-Validation
tc <- trainControl(
  method = "cv",      # Cross-validation
  index = folds,      # Custom indices for LODOCV
  number = n_dyads
)

Best Subset

We can observe that V4 is included in all prediction combinations. Additionally, cosine similarity appears in every model except the one with a single predictor, underscoring the importance of semantic similarity for interaction quality assessment (GOI).

bestsub.GOI <- leaps::regsubsets(GOI ~ . - iSubject - iPartner -iDyad, data = GOI)

summary(bestsub.GOI)
Subset selection object
Call: regsubsets.formula(GOI ~ . - iSubject - iPartner - iDyad, data = GOI)
11 Variables  (and intercept)
                  Forced in Forced out
cosine_similarity     FALSE      FALSE
V1                    FALSE      FALSE
V2                    FALSE      FALSE
V3                    FALSE      FALSE
V4                    FALSE      FALSE
V5                    FALSE      FALSE
V6                    FALSE      FALSE
V7                    FALSE      FALSE
V8                    FALSE      FALSE
V9                    FALSE      FALSE
V10                   FALSE      FALSE
1 subsets of each size up to 8
Selection Algorithm: exhaustive
         cosine_similarity V1  V2  V3  V4  V5  V6  V7  V8  V9  V10
1  ( 1 ) " "               " " " " " " "*" " " " " " " " " " " " "
2  ( 1 ) "*"               " " " " " " "*" " " " " " " " " " " " "
3  ( 1 ) "*"               " " " " " " "*" " " " " " " " " "*" " "
4  ( 1 ) "*"               " " " " " " "*" " " "*" " " " " "*" " "
5  ( 1 ) "*"               " " " " "*" "*" " " "*" " " " " "*" " "
6  ( 1 ) "*"               " " "*" "*" "*" " " "*" " " " " "*" " "
7  ( 1 ) "*"               "*" "*" "*" "*" " " "*" " " " " "*" " "
8  ( 1 ) "*"               "*" "*" "*" "*" " " "*" " " "*" "*" " "

Regularized Regression

Ridge Vs Lasso:

Tune Grid Configurations

Tune lambda over alpha = 0 vs alpha = 1

set.seed(1)

tg <- expand.grid(
  alpha = c(0, 1),
  lambda = 10^seq(3, -3, by = -0.1) # Range of lambda
)

Train L1 L2 Model

l1_l2.GOI <- train(
  rec,
  data = GOI,
  method = "glmnet",
  tuneGrid = tg,
  trControl = tc
)
Loading required package: Matrix

Attaching package: ‘Matrix’

The following objects are masked from ‘package:tidyr’:

    expand, pack, unpack

Loaded glmnet 4.1-8
Warning: There were missing values in resampled performance measures.

Results

plot(l1_l2.GOI, xlim = c(0, 400))

l1_l2.GOI$bestTune
min(l1_l2.GOI$results$RMSE)
[1] 17.20466

Elastic Net

Tune Grid Configurations

Tune alpha skewed towards Ridge.

set.seed(1)

tg <- expand.grid(
  alpha = seq(0, 0.5, by = 0.1),
  lambda = 10^seq(3, -3, by = -0.5) # Range of lambda
)

Train Elastic Net Model

elastic.GOI <- train(
  rec,
  data = GOI,
  method = "glmnet",
  tuneGrid = tg,
  trControl = tc
)
Warning: There were missing values in resampled performance measures.

Results

plot(elastic.GOI, xlim = c(0, 400))

elastic.GOI$bestTune
min(elastic.GOI$results$RMSE)
[1] 17.20466
#GOI.null <- lm(GOI ~ 1, data = GOI)
sst <- sum((GOI$GOI - mean(GOI$GOI))^2)
sse <- min(l1_l2.GOI$results$RMSE)^2 * 120

R2 <- 1 - sse / sst
sst
[1] 44130.99
sse
[1] 35520.04
R2
[1] 0.1951223
print(l1_l2.GOI$resample)
NA

Trees Based Models

Random Forest

Use same rec and tc from last models

rec

── Recipe ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

── Inputs 
Number of variables by role
outcome:    1
predictor: 14

── Operations 
• Variables removed: iSubject, iPartner, iDyad
head(tc)
$method
[1] "cv"

$number
[1] 60

$repeats
[1] NA

$search
[1] "grid"

$p
[1] 0.75

$initialWindow
NULL

RF - Tune Grid

tg <- expand.grid(
  mtry = c(1:11) # Evaluate 1 to 11(p) predictors
)

set.seed(1234)
rf.GOI <- train(
  rec,
  data = GOI,
  method = "rf",
  tuneGrid = tg,
  trControl = tc
)
Loading required namespace: randomForest
randomForest 4.7-1.1
Type rfNews() to see new features/changes/bug fixes.

Attaching package: ‘randomForest’

The following object is masked from ‘package:dplyr’:

    combine

The following object is masked from ‘package:ggplot2’:

    margin

Results

plot(rf.GOI)

rf.GOI$bestTune
min(rf.GOI$results$RMSE)
[1] 16.30243

Compare Models

sst <- sum((GOI$GOI - mean(GOI$GOI))^2)
cat("SST:", sst)
SST: 44130.99
# Min RMSE for L1 L2
min_rmse_l1l2 <- min(l1_l2.GOI$results$RMSE)

# L1 L2 Explained Variance
ssm_l1l2 <- min_rmse_l1l2^2 * nrow(GOI)


# Elastic Net Explained Variance
ssm_elastic <- min_rmse_elastic^2 * nrow(GOI)


# Min RMSE for RF
min_rmse_rf <- min(rf.GOI$results$RMSE)

# RF Explained Variance
ssm_rf <- min_rmse_rf^2 * nrow(GOI)


R2_l1l2 <- 1 - (ssm_l1l2 / sst)
cat("L1 L2 Perfomance:",  "\nMin RMSE:", min_rmse_l1l2, "\nExplained Variance:", ssm_l1l2, "\nRsqrd:", R2_l1l2)
L1 L2 Perfomance: 
Min RMSE: 17.20559 
Explained Variance: 35523.88 
Rsqrd: 0.1950354
R2_elastic <- 1 - (ssm_elastic / sst)
cat("Elastic Net Perfomance:",  "\nMin RMSE:", min_rmse_elastic, "\nExplained Variance:", ssm_elastic, "\nRsqrd:", R2_elastic)
Elastic Net Perfomance: 
Min RMSE: 17.20559 
Explained Variance: 35523.88 
Rsqrd: 0.1950354
R2_rf <- 1 - (ssm_rf / sst)
cat("Random Forest Perfomance:",  "\nMin RMSE:", min_rmse_rf, "\nExplained Variance:", ssm_rf, "\nRsqrd:", R2_rf)
Random Forest Perfomance: 
Min RMSE: 16.30243 
Explained Variance: 31892.32 
Rsqrd: 0.2773259
varImp(rf.GOI) |>
  plot()


varImp(l1_l2.GOI) |>
  plot()

NA
NA

Tokens Encoding:

We will Plot the 10 must representative words for the most informative dimensions (V9, V4)

V_matrix <- data_lsa$features  # Get the word loadings

# Vector of the terms (words) in corpus
terms <- colnames(data_tfidf)

# For each dimension, extract the top contributing words
get_top_words_per_dimension <- function(V_matrix, terms, top_n = 10) {
  top_words <- list()
  for (dim in 1:ncol(V_matrix)) {
    # Get the loadings for this dimension
    loadings <- V_matrix[, dim]
    
    # Rank the words by the absolute value of their loadings
    ranked_indices <- order(abs(loadings), decreasing = TRUE)[1:top_n]
    
    # Get the corresponding words
    top_words[[paste("Dimension", dim)]] <- terms[ranked_indices]
  }
  return(top_words)
}

# Get the top 10 words for each dimension
top_words_per_dimension <- get_top_words_per_dimension(V_matrix, terms, top_n = 10)

# Display Best Dimensions to Predcit GOI

top_words_per_dimension$`Dimension 4`
 [1] "נשׁר"     "צֶמַח"     "עלם"     "אִצְטַדְיוֹן" "רִצְפָּה"    "אוֹפַנַּיִם"  "פּוּאֶנְטָה"  "מִתְעַנְיֵן"  "רֶכֶב"    
[10] "שַׁיִט"    
top_words_per_dimension$`Dimension 9`
 [1] "חָחָחָ"    "כְּלוֹמַר"  "תַּכְשִׁיט"  "עַרְבִית"  "כֶּלֶב"    "גּוּגְל"   "מִשְׂגָּב"   "3-4"    "עתק"    "כַּדּוּרֶגֶל"
LS0tDQp0aXRsZTogIk1hY2hpbmUgTGVhcm5pbmcgLSBGaW5hbCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6IA0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KLS0tDQoNCiMgKipFbWJlZGRpbmdzKioNCg0KIyMgKipQcmUgUHJvY2VzcyoqDQoNCiMjIyAqKkxvYWQgTGlicmFyaWVzKioNCg0KV2UnbGwgYmUgdXNpbmcgdGhlIGBxdWFudGVkYWAgcGFja2FnZSBmb3IgdGV4dCBwcm9jZXNzaW5nIGFuZCBgcXVhbnRlZGEudGV4dG1vZGVsc2AgZm9yIGVtYmVkZGluZ3MuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShxdWFudGVkYSkNCmxpYnJhcnkocXVhbnRlZGEudGV4dG1vZGVscykNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShnZ3dvcmRjbG91ZCkNCmxpYnJhcnkoanNvbmxpdGUpDQpsaWJyYXJ5KGh0dHIpDQpsaWJyYXJ5KGpzb25saXRlKQ0KYGBgDQoNCiMjIyAqKkxvYWQgRGF0YSoqDQoNClRvIHV0aWxpemUgdGV4dCBlbWJlZGRpbmdzIHVzaW5nIGBxdWFudGVkYWAsIG91ciByYXcgZGF0YSBzaG91bGQgbWVldCB0aGUgZm9sbG93aW5nIHN0cnVjdHVyZToNCg0KLSBBIENTViBmaWxlIGNvbnRhaW5pbmcgdHdvIGNvbHVtbnM6IG9uZSBmb3IgdGhlIElEIChgU3ViamVjdC5JRGApIGFuZCBhbm90aGVyIGZvciB0ZXh0IChgV29yZHMuU3Bva2VuYCkuDQoNCmBgYHtyfQ0KZGF0YSA8LSByZWFkLmNzdigiZnVsbF90ZXh0LmNzdiIpDQpoZWFkKGRhdGEsIG4gPSAyKQ0KYGBgDQoNCiMjIyAqKkNvcnB1cyBEYXRhKioNCg0KQ29udmVydCBkYXRhIGludG8gY29ycHVzIGZvcm1hdCB0byBhcHBseSBOTFAgdHJhbnNmb3JtYXRpb25zIGluIGBxdWFudGVkYWAgYnkgZGVmaW5pbmcgSUQgYW5kIHRleHQgY29sdW1ucyBuYW1lcy4NCg0KYGBge3J9DQojIERlZmluZSBpZCBhbmQgdGV4dCBjb2x1bW4gdG8gY29uZHVjdCBhIHRleHQgY29ycHVzDQpkYXRhX2NvcnAgPC0gY29ycHVzKGRhdGEsDQogICAgICAgICAgICAgICAgICAgICAgICBkb2NpZF9maWVsZCA9ICJTdWJqZWN0LklEIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIHRleHRfZmllbGQgPSAiV29yZHMuU3Bva2VuIikNCg0KaGVhZChkYXRhX2NvcnApDQpgYGANCiMjIyAqKlRleHQgVG9rZW5pemF0aW9uKioNCg0KVGV4dCB0b2tlbml6YXRpb24gaW52b2x2ZXMgc3BsaXR0aW5nIGVhY2ggZG9jdW1lbnQgaW50byBzbWFsbGVyIHVuaXRzLCBjYWxsZWQgdG9rZW5zLCB3aGljaCBhcmUgdHlwaWNhbGx5IGluZGl2aWR1YWwgd29yZHMuIEJ5IGRlZmF1bHQsIHRoZSBgdG9rZW5zKClgIGZ1bmN0aW9uIHNwbGl0cyB0aGUgdGV4dCBhdCBzcGFjZXMsIHRyZWF0aW5nIGVhY2ggd29yZCBhcyBhIHNlcGFyYXRlIHRva2VuLiBJbiB0aGlzIGNhc2UsIHdlIGFsc28gcmVtb3ZlIHB1bmN0dWF0aW9uIHRvIGZ1cnRoZXIgY2xlYW4gdGhlIGRhdGEsIGVuc3VyaW5nIG9ubHkgbWVhbmluZ2Z1bCB0ZXh0IGlzIGFuYWx5emVkLg0KDQpgYGB7cn0NCiMgVGV4dCBUb2tlbml6YXRpb246IFNwbGl0IHRleHQgaW50byBzaW5nbGUgd29yZHMsIHJlbW92ZSBwdW5jdHVhdGlvbg0KZGF0YV90b2tlbiA8LSB0b2tlbnMoZGF0YV9jb3JwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZW1vdmVfcHVuY3QgPSBUUlVFKQ0KaGVhZChkYXRhX3Rva2VuKQ0KYGBgDQoNCiMjIyAqKlRleHQgTGVtbWF0aXphdGlvbioqDQoNCkxlbW1hdGl6YXRpb24gaXMgdGhlIHByb2Nlc3Mgb2YgcmVkdWNpbmcgd29yZHMgdG8gdGhlaXIgYmFzZSBvciBkaWN0aW9uYXJ5IGZvcm0sIGtub3duIGFzIGxlbW1hcy4gd2UgdXRpbGl6ZSB0aGUgYG5ha2RhbiBBUElgLCB3aGljaCBwcm92aWRlcyBhIGNvbXByZWhlbnNpdmUgSGVicmV3IGxlbW1hIGRpY3Rpb25hcnkuIFRoaXMgQVBJIGFsbG93cyB1cyB0byBwcm9jZXNzIGEgbGlzdCBvZiBIZWJyZXcgd29yZHMgYW5kIHJldHVybiB0aGVpciBjb3JyZXNwb25kaW5nIGJhc2UgZm9ybXMuDQoNCmBgYHtyfQ0KDQpCYXNlV29yZHMgPC0gZnVuY3Rpb24od29yZHMpIHsNCiAgaGVhZGVycyA8LSBjKCdDb250ZW50LVR5cGUnID0gJ2FwcGxpY2F0aW9uL2pzb247Y2hhcnNldD11dGYtOCcpDQogICMgQVBJIENvbmZpZ3VyYXRpb25zDQogIHBhcmFtcyA8LSBsaXN0KA0KICAgIHRhc2sgPSAibmFrZGFuIiwNCiAgICBnZW5yZSA9ICJtb2Rlcm4iLA0KICAgIGRhdGEgPSBwYXN0ZSh3b3JkcywgY29sbGFwc2UgPSAiICIpLA0KICAgIGFkZG1vcnBoID0gVFJVRSwNCiAgICBtYXRjaHBhcnRpYWwgPSBUUlVFLA0KICAgIGFwaUtleSA9ICI0YjY1YmU4NC0zNWYyLTQ0M2ItYWIzZC1iMThmM2I4MmIyN2QiICAjIEFQSSBrZXkNCiAgKQ0KICANCiAgcmVzcG9uc2UgPC0gUE9TVCgNCiAgICB1cmwgPSAiaHR0cHM6Ly9uYWtkYW4tMy0yLmxvYWRiYWxhbmNlci5kaWN0YS5vcmcuaWwvYWRkbmlrdWQiLA0KICAgIGFkZF9oZWFkZXJzKC5oZWFkZXJzID0gaGVhZGVycyksDQogICAgYm9keSA9IHBhcmFtcywNCiAgICBlbmNvZGUgPSAianNvbiINCiAgKQ0KICANCiAgaWYgKHJlc3BvbnNlJHN0YXR1c19jb2RlICE9IDIwMCkgew0KICAgIHN0b3AoIkFQSSByZXF1ZXN0IGZhaWxlZCB3aXRoIHN0YXR1cyBjb2RlOiAiLCByZXNwb25zZSRzdGF0dXNfY29kZSkNCiAgfQ0KDQogIHJlc3BvbnNlX2RhdGEgPC0gZnJvbUpTT04oY29udGVudChyZXNwb25zZSwgYXMgPSAidGV4dCIsIGVuY29kaW5nID0gIlVURi04IiksIHNpbXBsaWZ5VmVjdG9yID0gRkFMU0UpDQogIA0KICBzYXBwbHkod29yZHMsIGZ1bmN0aW9uKHdvcmQpIHsNCiAgICBpZHggPC0gd2hpY2goc2FwcGx5KHJlc3BvbnNlX2RhdGEsIGZ1bmN0aW9uKHgpIHgkd29yZCkgPT0gd29yZCkNCiAgICBpZiAobGVuZ3RoKGlkeCkgPT0gMCkgcmV0dXJuKHdvcmQpICAjIFVzZSBvcmlnaW5hbCB3b3JkIGlmIG5vdCBmb3VuZA0KICAgIGJhc2Vfd29yZCA8LSByZXNwb25zZV9kYXRhW1tpZHhbMV1dXQ0KICAgIGlmICghaXMubnVsbChiYXNlX3dvcmQkb3B0aW9ucykgJiYgbGVuZ3RoKGJhc2Vfd29yZCRvcHRpb25zKSA+IDApIHsNCiAgICAgIHJldHVybihiYXNlX3dvcmQkb3B0aW9uc1tbMV1dJGxleCkNCiAgICB9DQogICAgcmV0dXJuKGJhc2Vfd29yZCR3b3JkKQ0KICB9KQ0KfQ0KDQpgYGANCg0KDQoNCiMjIyMgKipMZW1tYXRpemluZyBFeGFtcGxlOioqDQoNCmBgYHtyfQ0KaGVsbG9fd29scmQgPC0gYygi16nXnNeV157XmSIsICLXoteV15zXnteV16oiKQ0KQmFzZVdvcmRzKGhlbGxvX3dvbHJkKQ0KYGBgDQoNCiMjIyMgKipSZXBsYWNlIFRva2VucyBXaXRoIExlbW1hcyoqDQoNCmBgYHtyfQ0KIyBTdGVwIDE6IEV4dHJhY3QgdW5pcXVlIHRva2Vucw0KdW5pcXVlX3Rva2VucyA8LSB0eXBlcyhkYXRhX3Rva2VuKQ0KDQojIFN0ZXAgMjogU3BsaXQgdG9rZW5zIGludG8gYmF0Y2hlcyB0byBzcGVlZCB1cCB0aGUgcHJvY2Vzcw0KYmF0Y2hfc2l6ZSA8LSAyNTAgICMgQWRqdXN0IHRoZSBiYXRjaCBzaXplIGlmIG5lY2Vzc2FyeQ0KdG9rZW5fYmF0Y2hlcyA8LSBzcGxpdCh1bmlxdWVfdG9rZW5zLCBjZWlsaW5nKHNlcV9hbG9uZyh1bmlxdWVfdG9rZW5zKSAvIGJhdGNoX3NpemUpKQ0KDQojIFN0ZXAgMzogQXBwbHkgQmFzZVdvcmRzIHRvIGVhY2ggYmF0Y2gNCmxlbW1hc19saXN0IDwtIGxhcHBseSh0b2tlbl9iYXRjaGVzLCBCYXNlV29yZHMpDQoNCiMgQ29tYmluZSBsZW1tYXMgaW50byBhIHNpbmdsZSB2ZWN0b3INCmxlbW1hcyA8LSB1bmxpc3QobGVtbWFzX2xpc3QsIHVzZS5uYW1lcyA9IEZBTFNFKQ0KDQojIFN0ZXAgNDogQ3JlYXRlIGEgdG9rZW4tdG8tbGVtbWEgbWFwcGluZw0KdG9rZW5fdG9fbGVtbWEgPC0gc2V0TmFtZXMobGVtbWFzLCB1bmlxdWVfdG9rZW5zKQ0KDQojIFN0ZXAgNTogUmVwbGFjZSB0b2tlbnMgd2l0aCBsZW1tYXMgdXNpbmcgYHRva2VuX3JlcGxhY2UoKWANCmRhdGFfbGVtbWEgPC0gdG9rZW5zX3JlcGxhY2UoDQogIGRhdGFfdG9rZW4sDQogIHBhdHRlcm4gPSB1bmlxdWVfdG9rZW5zLA0KICByZXBsYWNlbWVudCA9IGxlbW1hcywNCiAgdmFsdWV0eXBlID0gImZpeGVkIg0KKQ0KDQpoZWFkKGRhdGFfbGVtbWEpDQoNCmBgYA0KDQoNCg0KIyMjICoqRG9jdW1lbnQtRmVhdHVyZSBNYXRyaXggKERGTSk6KiogDQoNClRva2VuIEZyZXF1ZW5jaWVzIFRhYmxlDQoNCldlIGNyZWF0ZSBhIGRvY3VtZW50LWZlYXR1cmUgbWF0cml4IChERk0pIHRvIGV4YW1pbmUgd29yZCBmcmVxdWVuY3kgZGlzdHJpYnV0aW9ucyBhY3Jvc3MgcGFydGljaXBhbnRzLiBFYWNoIHRva2VuIGFjdHMgYXMgYSBmZWF0dXJlIGluIHRoZSBmcmVxdWVuY3kgdGFibGUsIHdpdGggcm93cyByZXByZXNlbnRpbmcgc3ViamVjdHMuDQoNCg0KYGBge3J9DQpkYXRhX2RmbSA8LSBkZm0oZGF0YV9sZW1tYSkNCg0KaGVhZChkYXRhX2RmbSkNCg0KYGBgDQoNCiMjIyAqKlRyaW0gRGF0YSoqDQoNClRoZSBERk0gaW5jbHVkZXMgbWFueSByYXJlIHRva2Vucywgd2hpY2ggY2FuIGludHJvZHVjZSBub2lzZSBpbnRvIHRoZSBhbmFseXNpcy4gVG8gYWRkcmVzcyB0aGlzLCB3ZSBmaWx0ZXIgb3V0IHRva2VucyB0aGF0IG9jY3VyIGluIGxlc3MgdGhhbiAxJSBvZiB0aGUgZG9jdW1lbnRzLCBhIGNvbW1vbiB0aHJlc2hvbGQgZm9yIGVsaW1pbmF0aW5nIGlycmVsZXZhbnQgd29yZHMuDQoNCmBgYHtyfQ0KIyBEZWZpbmUgVG9rZW4gRnJlcXVlbmN5IFRocmVzaG9sZCB3aXRoaW4gZG9jdW1lbnRzDQpkYXRhX3RyaW0gPC0gZGF0YV9kZm0gfD4gIyBPbWl0IHRva2VucyB0aGUgYXBwZWFyIGluIGxlc3MgdGhlbiAxJSBvZiBkb2N1bWVudHMNCiAgZGZtX3RyaW0obWluX2RvY2ZyZXEgPSAwLjAxLCBkb2NmcmVxX3R5cGUgPSAicHJvcCIpDQoNCmhlYWQoZGF0YV90cmltKQ0KDQpgYGANCg0KV2Ugc2lnbmlmaWNhbnRseSBkZWNyZWFzZWQgdGhlIG51bWJlciBvZiBCeSB0cmltbWluZyB0aGUgREZNLCB3ZSBzaWduaWZpY2FudGx5IHJlZHVjZSB0aGUgbnVtYmVyIG9mIGZlYXR1cmVzIGFuZCBmb2N1cyBvbiB0aGUgbW9zdCBpbmZvcm1hdGl2ZSB0b2tlbnMuDQoNCg0KIyMjICoqVEYtSURGOiBUb2tlbiBGcmVxdWVuY2llcyBOb3JtYWxpemF0aW9uKioNCg0KVGVybSBGcmVxdWVuY3ktSW52ZXJzZSBEb2N1bWVudCBGcmVxdWVuY3kgKFRGLUlERikgaXMgYSBtZXRob2QgdG8gZG93bi13ZWlnaHQgY29tbW9uLCBsZXNzIGluZm9ybWF0aXZlIHdvcmRzIHdoaWxlIGVtcGhhc2l6aW5nIHVuaXF1ZSBhbmQgbWVhbmluZ2Z1bCB0b2tlbnMuIFRoaXMgcmVkdWNlcyB0aGUgYmlhcyB0b3dhcmQgZnJlcXVlbnRseSBvY2N1cnJpbmcgd29yZHMgYW5kIGhpZ2hsaWdodHMgaW1wb3J0YW50IHdvcmRzIGluIGVhY2ggZG9jdW1lbnQuDQoNCmBgYHtyfQ0KIyBURi1JREYgc2NhbGVkIGRmbQ0KZGF0YV90ZmlkZiA8LSBkYXRhX3RyaW0gfD4NCiAgZGZtX3RmaWRmKHNjaGVtZV90ZiA9ICJwcm9wIikNCg0KaGVhZChkYXRhX3RmaWRmKQ0KDQpgYGANCg0KIyMgKipMYXRlbnQgU2VtYW50aWMgQW5hbHlzaXMgKExTQSkqKg0KDQpMYXRlbnQgU2VtYW50aWMgQW5hbHlzaXMgKExTQSkgaXMgYSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gdGVjaG5pcXVlIHRoYXQgaWRlbnRpZmllcyBwYXR0ZXJucyBpbiB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHRlcm1zICh3b3JkcykgYW5kIGRvY3VtZW50cyAoc3ViamVjdHMpLiBCeSBhcHBseWluZyBTaW5ndWxhciBWYWx1ZSBEZWNvbXBvc2l0aW9uIChTVkQpIHRvIGEgdGVybS1kb2N1bWVudCBtYXRyaXgsIExTQSBjYXB0dXJlcyB0aGUgdW5kZXJseWluZyBzZW1hbnRpYyBzdHJ1Y3R1cmUgb2YgdGhlIHRleHQgZGF0YS4gVGhpcyBtZXRob2QgZm9jdXNlcyBvbiB0aGUgbW9zdCByZWxldmFudCB0b3BpY3Mgb3IgY29uY2VwdHMsIGVmZmVjdGl2ZWx5IGdyb3VwaW5nIHdvcmRzIGludG8gc2VtYW50aWMgZmllbGRzIGFuZCByZXZlYWxpbmcgdHJlbmRzIHdoZXJlIHdvcmRzIGNvbW1vbmx5IG9jY3VyIHRvZ2V0aGVyIHdpdGhpbiB0aGUgc2FtZSBjb250ZXh0c+KAlGluIHRoaXMgY2FzZSwgdGhlIHN1YmplY3RzJyB0cmFuc2NyaXB0cy4NCg0KQnkgcHJvY2VlZGluZyB3aXRoIDEwIGRpbWVuc2lvbnMgKHRoZSBmdW5jdGlvbidzIGRlZmF1bHQpLCB3ZSBtYWludGFpbiBhIG1vZGVsIHRoYXQgaXMgYm90aCByb2J1c3QgYW5kIGludGVycHJldGFibGUuIFRvIHZpc3VhbGl6ZSB0aGUgY29udHJpYnV0aW9uIG9mIGFkZGl0aW9uYWwgZGltZW5zaW9ucywgd2UgY2FuIHBsb3QgdGhlIHNpbmd1bGFyIHZhbHVlcywgd2hpY2ggcmV2ZWFsIGFuICJlbGJvdyIgcG9pbnQgYXJvdW5kIHRoZSAxMC1kaW1lbnNpb25hbCBtYXJrLCBzdWdnZXN0aW5nIDEwIGRpbWVuc2lvbnMgYXJlIHN1ZmZpY2llbnQgdG8gY2FwdHVyZSB0aGUgZXNzZW50aWFsIHNlbWFudGljIHN0cnVjdHVyZXMgaW4gb3VyIGRhdGEgd2l0aG91dCBpbnRyb2R1Y2luZyB1bm5lY2Vzc2FyeSBjb21wbGV4aXR5Lg0KDQpgYGB7cn0NCmRhdGFfbHNhX21heCA8LSB0ZXh0bW9kZWxfbHNhKGRhdGFfdGZpZGYsIG5kID0gMTE5KQ0Kc2luZ3VsYXJfdmFsdWVzIDwtIGRhdGFfbHNhX21heCRzaw0KDQpwbG90KHNpbmd1bGFyX3ZhbHVlcywgdHlwZSA9ICJiIiwgeGxhYiA9ICJEaW1lbnNpb25zIiwgeWxhYiA9ICJTaW5ndWxhciBWYWx1ZXMiLCBtYWluID0gIlNpbmd1bGFyIFZhbHVlIERlY2F5IikNCmBgYA0KYGBge3J9DQojIEFwcGx5IExTQSB3aXRoIDEwIERpbWVuc2lvbnMNCmRhdGFfbHNhIDwtIHRleHRtb2RlbF9sc2EoZGF0YV90ZmlkZikNCg0KYGBgDQoNCg0KQWZ0ZXIgYXBwbHlpbmcgTFNBLCB0aGUgdGV4dCBkYXRhIGlzIGVtYmVkZGVkIGludG8gYSByZWR1Y2VkLWRpbWVuc2lvbmFsIHNwYWNlLiBFYWNoIGRvY3VtZW50IChyb3cpIHJlcHJlc2VudHMgYSBzcGVjaWZpYyBjb250ZXh0IG9yIHN1YmplY3QsIHdpdGggdGhlIDEwIGRpbWVuc2lvbnMgKGNvbHVtbnMpIGNhcHR1cmluZyBkaXN0aW5jdCBzZW1hbnRpYyByZWxhdGlvbnNoaXBzLiBUb2tlbnMgYXJlIGNvbnNpZGVyZWQgc2ltaWxhciBpZiB0aGV5IGZyZXF1ZW50bHkgYXBwZWFyIGFtb25nIHNhbWUgc3ViamVjdHMuDQoNCiMgKipQcmVkaWN0aXZlIFZhcmlhYmxlOiBHT0kqKg0KDQojIyAqKlByZSBQcm9jZXNzKioNCg0KIyMjICoqTG9hZCBEYXRhKioNCg0KYGBge3J9DQpHT0kgPC0gcmVhZC5jc3YoIkRhdGEuY3N2IikNCkdPSSA8LSBHT0kgfD4NCiAgYXJyYW5nZShpU3ViamVjdCkgfD4NCiAgc2VsZWN0KGlTdWJqZWN0LCBpUGFydG5lciwgaUR5YWQsIEdPSSkNCg0KaGVhZChHT0kpDQoNCmBgYA0KDQojIyMgKipBZGQgR09JIHRvIGRhdGEqKg0KDQpgYGB7cn0NCnN1Yl9kb2NfZGYgPC0gYXMuZGF0YS5mcmFtZShkYXRhX2xzYSRkb2NzKQ0KaGVhZChzdWJfZG9jX2RmKQ0KDQpgYGANCg0KV2Ugd2lsbCBpbnNlcnQgdGhlIGludGVyYWN0aW9uIHF1YWxpdHkgc2NvcmUgKEdPSSkgdG8gb3VyIGRhdGENCg0KYGBge3J9DQpHT0kgPC0gc3ViX2RvY19kZiB8Pg0KICByb3duYW1lc190b19jb2x1bW4odmFyID0gImlTdWJqZWN0IikgfD4gIyBNYWtlIElEIGNvbHVtbg0KICBhcnJhbmdlKGlTdWJqZWN0KSB8PiAjIEFycmFuZ2UgdG8gZml0IEdPSSBzdHJ1Y3R1cmUNCiAgbXV0YXRlKGlQYXJ0bmVyID0gR09JJGlQYXJ0bmVyLCMgQWRkIHBhcnRuZXIgY29sdW1uDQogICAgICAgICBpRHlhZCA9IEdPSSRpRHlhZCwNCiAgICAgICAgIEdPSSA9IEdPSSRHT0kpIHw+ICMgQWRkIEdPSSBjb2x1bW4NCiAgc2VsZWN0KGlTdWJqZWN0LCBpUGFydG5lciwgaUR5YWQsIEdPSSwgZXZlcnl0aGluZygpKQ0KDQpoZWFkKEdPSSkNCg0KYGBgDQoNCiMgKipTeW5jaHJvbnkqKg0KDQpXZSBjYW4gbm93IGV4cGxvcmUgc2ltaWxhcml0aWVzIGJldHdlZW4gc3ViamVjdHMgdXNpbmcgY29tbW9uIGRpc3RhbmNlIG1ldHJpY3Mgc3VjaCBhcyBjb3NpbmUgc2ltaWxhcml0eSwgYWxsb3dpbmcgZm9yIGRlZXBlciBpbnNpZ2h0cyBpbnRvIHNoYXJlZCBsaW5ndWlzdGljIHBhdHRlcm5zLiB0byBkbyBzbyB3ZSB3aWxsIGZpcnN0IG5lZWQgdG8gYWRkIHRoZSBwYXJ0bmVyIHZhcmlhYmxlcyB0byBvdXIgZGF0YS4NCg0KYGBge3J9DQpHT0lfbWVyZ2VkIDwtIEdPSSAlPiUNCiAgbXV0YXRlKGlTdWJqZWN0ID0gYXMubnVtZXJpYyhpU3ViamVjdCksDQogICAgICAgICBpUGFydG5lciA9IGFzLm51bWVyaWMoaVBhcnRuZXIpKSB8Pg0KICBsZWZ0X2pvaW4oR09JLCBieSA9IGMoImlTdWJqZWN0IiA9ICJpUGFydG5lciIpLCBzdWZmaXggPSBjKCIiLCAiX3BhcnRuZXIiKSkgfD4NCiAgc2VsZWN0KC1pU3ViamVjdF9wYXJ0bmVyKQ0KDQpoZWFkKEdPSV9tZXJnZWQpDQpgYGANCg0KIyMgKipDb3NpbmUgU2ltaWxhcml0eSBDYWxjdWxhdGlvbioqDQoNCldlIHdpbGwgbm93IGNhbGN1bGF0ZSB0aGUgY29zaW5lIHNpbWlsYXJpdGllcyBiZXR3ZWVuIHN1YmplY3RzIHNlbWFudGljIHZlY3RvcnMuIA0KDQpgYGB7cn0NCmxpYnJhcnkocHJveHkpDQoNCmNvc2luZV9zaW1pbGFyaXRpZXMgPC0gbnVtZXJpYyhucm93KEdPSV9tZXJnZWQpKQ0KDQpmb3IgKGkgaW4gMTpucm93KEdPSV9tZXJnZWQpKSB7DQogICMgRXh0cmFjdCBzdWJqZWN0IGFuZCBwYXJ0bmVyIHZlY3RvcjoNCiAgc3ViamVjdF92ZWN0b3IgPC0gYXMubnVtZXJpYyhHT0lfbWVyZ2VkW2ksIDU6MTRdKQ0KICBwYXJ0bmVyX3ZlY3RvciA8LSBhcy5udW1lcmljKEdPSV9tZXJnZWRbaSwgMTc6MjZdKQ0KICANCiAgIyBCaW5kIHZlY3RvcnMgaW50byBzaW5nbGUgbWF0cml4DQogIHZlY3RvcnMgPC0gcmJpbmQoc3ViamVjdF92ZWN0b3IsIHBhcnRuZXJfdmVjdG9yKQ0KICANCiAgIyBDYWxjdWxhdGUgY29zaW5lIHNpbWlsYXJpdHkNCiAgY29zaW5lX3NpbWlsYXJpdHkgPC0gMSAtIHByb3h5OjpkaXN0KHZlY3RvcnMsIG1ldGhvZCA9ICJjb3NpbmUiKQ0KICANCiAgIyBTdG9yZSByZXN1bHQgaW4gdGhlIHZlY3Rvcg0KICBjb3NpbmVfc2ltaWxhcml0aWVzW2ldIDwtIGNvc2luZV9zaW1pbGFyaXR5DQp9ICANCg0KR09JX21lcmdlZCRjb3NpbmVfc2ltaWxhcml0eSA8LSBjb3NpbmVfc2ltaWxhcml0aWVzDQoNCkdPSSA8LSBHT0lfbWVyZ2VkIHw+DQogIHNlbGVjdChpU3ViamVjdCwgaVBhcnRuZXIsIGlEeWFkLCBjb3NpbmVfc2ltaWxhcml0eSwgR09JLCBldmVyeXRoaW5nKCksIC1lbmRzX3dpdGgoIl9wYXJ0bmVyIikpDQoNCmhlYWQoR09JKQ0KDQoNCg0KYGBgDQoNCiMgKipQcmVkaWN0aXZlIE1vZGVsIENvbmZpZ3VyYXRpb24qKg0KDQojIyAqKkxvYWQgTGlicmFyaWVzKioNCg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHJlY2lwZXMpDQpsaWJyYXJ5KHJzYW1wbGUpDQpsaWJyYXJ5KHlhcmRzdGljaykNCg0KYGBgDQoNCg0KDQojIyAqKlJlY2lwZSoqDQoNClByZWRpY3QgR09JIHdpdGggZDEgKyBkMiArIC4uLmQxMCArIGNvc2luZSBzaW1pbGFyaXR5DQoNCmBgYHtyfQ0KIyBQcmVkaWN0IEdPSSB3aXRoIGFsbCB2YXJpYWJsZXMgDQpyZWMgPC0gcmVjaXBlKEdPSSB+IC4sIGRhdGEgPSBHT0kpIHw+DQogIHN0ZXBfcm0oaVN1YmplY3QsIGlQYXJ0bmVyLCBpRHlhZCkNCg0KcmVjDQoNCmBgYA0KDQojIyAqKkNyb3NzIFZhbGlkYXRpb246IExlYXZlIG9uZSBEeWFkIE91dCoqDQoNCldlIHdpbGwgY29uZHVjdCBsZWF2ZSBvbmUgZHlhZCBvdXQgY3Jvc3MgdmFsaWRhdGlvbiwgdGVzdGluZyBvdXIgdHVuaW5nIG9uIGVhY2ggZHlhZCBzZXBhcnRseS4NCg0KYGBge3J9DQojIEV4dHJhY3QgaUR5YWQgZm9yIGluZGV4aW5nDQpHT0lfaW5kZXggPC0gR09JICU+JQ0KICBzZWxlY3QoaUR5YWQpDQoNCiMgQ3JlYXRlIGEgdmVjdG9yIG9mIHVuaXF1ZSBkeWFkIElEcyBhbmQgbWFrZSBmb2xkcyBmb3IgTE9ET0NWDQpkeWFkcyA8LSB1bmlxdWUoR09JX2luZGV4JGlEeWFkKQ0Kbl9keWFkcyA8LSBsZW5ndGgoZHlhZHMpICAjIE51bWJlciBvZiBkeWFkcyAoc2hvdWxkIGJlIDYwKQ0KZm9sZHMgPC0gbGFwcGx5KGR5YWRzLCBmdW5jdGlvbihkKSB3aGljaChHT0lfaW5kZXgkaUR5YWQgIT0gZCkpDQpuYW1lcyhmb2xkcykgPC0gcGFzdGUwKCJGb2xkIiwgZHlhZHMpDQoNCiMgNC4gU2V0IHVwIHRyYWluQ29udHJvbCBmb3IgTGVhdmUtT25lLUR5YWQtT3V0IENyb3NzLVZhbGlkYXRpb24NCnRjIDwtIHRyYWluQ29udHJvbCgNCiAgbWV0aG9kID0gImN2IiwgICAgICAjIENyb3NzLXZhbGlkYXRpb24NCiAgaW5kZXggPSBmb2xkcywgICAgICAjIEN1c3RvbSBpbmRpY2VzIGZvciBMT0RPQ1YNCiAgbnVtYmVyID0gbl9keWFkcw0KKQ0KDQpgYGANCg0KDQoNCg0KDQojICoqQmVzdCBTdWJzZXQqKg0KDQpXZSBjYW4gb2JzZXJ2ZSB0aGF0IFY0IGlzIGluY2x1ZGVkIGluIGFsbCBwcmVkaWN0aW9uIGNvbWJpbmF0aW9ucy4gQWRkaXRpb25hbGx5LCBjb3NpbmUgc2ltaWxhcml0eSBhcHBlYXJzIGluIGV2ZXJ5IG1vZGVsIGV4Y2VwdCB0aGUgb25lIHdpdGggYSBzaW5nbGUgcHJlZGljdG9yLCB1bmRlcnNjb3JpbmcgdGhlIGltcG9ydGFuY2Ugb2Ygc2VtYW50aWMgc2ltaWxhcml0eSBmb3IgaW50ZXJhY3Rpb24gcXVhbGl0eSBhc3Nlc3NtZW50IChHT0kpLg0KDQpgYGB7cn0NCmJlc3RzdWIuR09JIDwtIGxlYXBzOjpyZWdzdWJzZXRzKEdPSSB+IC4gLSBpU3ViamVjdCAtIGlQYXJ0bmVyIC1pRHlhZCwgZGF0YSA9IEdPSSkNCg0Kc3VtbWFyeShiZXN0c3ViLkdPSSkNCg0KYGBgDQoNCg0KDQoNCiMgKipSZWd1bGFyaXplZCBSZWdyZXNzaW9uKioNCg0KIyMgKipSaWRnZSBWcyBMYXNzbzoqKg0KDQojIyMgKipUdW5lIEdyaWQgQ29uZmlndXJhdGlvbnMqKg0KDQogVHVuZSBsYW1iZGEgb3ZlciBhbHBoYSA9IDAgdnMgYWxwaGEgPSAxDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCg0KdGcgPC0gZXhwYW5kLmdyaWQoDQogIGFscGhhID0gYygwLCAxKSwNCiAgbGFtYmRhID0gMTBec2VxKDMsIC0zLCBieSA9IC0wLjEpICMgUmFuZ2Ugb2YgbGFtYmRhDQopDQoNCmBgYA0KDQojIyAqKlRyYWluIEwxIEwyIE1vZGVsKioNCg0KYGBge3J9DQpsMV9sMi5HT0kgPC0gdHJhaW4oDQogIHJlYywNCiAgZGF0YSA9IEdPSSwNCiAgbWV0aG9kID0gImdsbW5ldCIsDQogIHR1bmVHcmlkID0gdGcsDQogIHRyQ29udHJvbCA9IHRjDQopDQoNCmBgYA0KDQojIyAqKlJlc3VsdHMqKg0KDQpgYGB7cn0NCnBsb3QobDFfbDIuR09JLCB4bGltID0gYygwLCA0MDApKQ0KbDFfbDIuR09JJGJlc3RUdW5lDQptaW4obDFfbDIuR09JJHJlc3VsdHMkUk1TRSkNCg0KYGBgDQoNCiMjICoqRWxhc3RpYyBOZXQqKg0KDQojIyMgKipUdW5lIEdyaWQgQ29uZmlndXJhdGlvbnMqKg0KDQpUdW5lIGFscGhhIHNrZXdlZCB0b3dhcmRzIFJpZGdlLg0KDQpgYGB7cn0NCnNldC5zZWVkKDEpDQoNCnRnIDwtIGV4cGFuZC5ncmlkKA0KICBhbHBoYSA9IHNlcSgwLCAwLjUsIGJ5ID0gMC4xKSwNCiAgbGFtYmRhID0gMTBec2VxKDMsIC0zLCBieSA9IC0wLjUpICMgUmFuZ2Ugb2YgbGFtYmRhDQopDQoNCmBgYA0KDQoNCiMjICoqVHJhaW4gRWxhc3RpYyBOZXQgTW9kZWwqKg0KDQpgYGB7cn0NCmVsYXN0aWMuR09JIDwtIHRyYWluKA0KICByZWMsDQogIGRhdGEgPSBHT0ksDQogIG1ldGhvZCA9ICJnbG1uZXQiLA0KICB0dW5lR3JpZCA9IHRnLA0KICB0ckNvbnRyb2wgPSB0Yw0KKQ0KDQpgYGANCg0KIyMgKipSZXN1bHRzKioNCg0KYGBge3J9DQpwbG90KGVsYXN0aWMuR09JLCB4bGltID0gYygwLCA0MDApKQ0KZWxhc3RpYy5HT0kkYmVzdFR1bmUNCm1pbihlbGFzdGljLkdPSSRyZXN1bHRzJFJNU0UpDQoNCmBgYA0KDQoNCg0KYGBge3J9DQoNCnNzdCA8LSBzdW0oKEdPSSRHT0kgLSBtZWFuKEdPSSRHT0kpKV4yKQ0Kc3NlIDwtIG1pbihsMV9sMi5HT0kkcmVzdWx0cyRSTVNFKV4yICogMTIwDQoNClIyIDwtIDEgLSBzc2UgLyBzc3QNCnNzdA0Kc3NlDQpSMg0KDQpwcmludChsMV9sMi5HT0kkcmVzYW1wbGUpDQoNCg0KDQpgYGANCg0KIyAqKlRyZWVzIEJhc2VkIE1vZGVscyoqDQoNCiMjICoqUmFuZG9tIEZvcmVzdCoqDQoNClVzZSBzYW1lIHJlYyBhbmQgdGMgZnJvbSBsYXN0IG1vZGVscw0KDQpgYGB7cn0NCnJlYw0KI3RjDQoNCmBgYA0KIyMjICoqUkYgLSBUdW5lIEdyaWQqKg0KDQpgYGB7cn0NCnRnIDwtIGV4cGFuZC5ncmlkKA0KICBtdHJ5ID0gYygxOjExKSAjIEV2YWx1YXRlIDEgdG8gMTEocCkgcHJlZGljdG9ycw0KKQ0KDQpzZXQuc2VlZCgxMjM0KQ0KcmYuR09JIDwtIHRyYWluKA0KICByZWMsDQogIGRhdGEgPSBHT0ksDQogIG1ldGhvZCA9ICJyZiIsDQogIHR1bmVHcmlkID0gdGcsDQogIHRyQ29udHJvbCA9IHRjDQopDQoNCmBgYA0KIyMjICoqUmVzdWx0cyoqDQoNCmBgYHtyfQ0KcGxvdChyZi5HT0kpDQpyZi5HT0kkYmVzdFR1bmUNCm1pbihyZi5HT0kkcmVzdWx0cyRSTVNFKQ0KDQpgYGANCiMgKipDb21wYXJlIE1vZGVscyoqDQoNCg0KYGBge3J9DQpzc3QgPC0gc3VtKChHT0kkR09JIC0gbWVhbihHT0kkR09JKSleMikNCmNhdCgiU1NUOiIsIHNzdCkNCg0KIyBNaW4gUk1TRSBmb3IgTDEgTDINCm1pbl9ybXNlX2wxbDIgPC0gbWluKGwxX2wyLkdPSSRyZXN1bHRzJFJNU0UpDQoNCiMgTDEgTDIgRXhwbGFpbmVkIFZhcmlhbmNlDQpzc21fbDFsMiA8LSBtaW5fcm1zZV9sMWwyXjIgKiBucm93KEdPSSkNCg0KDQojIEVsYXN0aWMgTmV0IEV4cGxhaW5lZCBWYXJpYW5jZQ0Kc3NtX2VsYXN0aWMgPC0gbWluX3Jtc2VfZWxhc3RpY14yICogbnJvdyhHT0kpDQoNCg0KIyBNaW4gUk1TRSBmb3IgUkYNCm1pbl9ybXNlX3JmIDwtIG1pbihyZi5HT0kkcmVzdWx0cyRSTVNFKQ0KDQojIFJGIEV4cGxhaW5lZCBWYXJpYW5jZQ0Kc3NtX3JmIDwtIG1pbl9ybXNlX3JmXjIgKiBucm93KEdPSSkNCg0KDQpSMl9sMWwyIDwtIDEgLSAoc3NtX2wxbDIgLyBzc3QpDQpjYXQoIkwxIEwyIFBlcmZvbWFuY2U6IiwgICJcbk1pbiBSTVNFOiIsIG1pbl9ybXNlX2wxbDIsICJcbkV4cGxhaW5lZCBWYXJpYW5jZToiLCBzc21fbDFsMiwgIlxuUnNxcmQ6IiwgUjJfbDFsMikNCg0KDQoNClIyX2VsYXN0aWMgPC0gMSAtIChzc21fZWxhc3RpYyAvIHNzdCkNCmNhdCgiRWxhc3RpYyBOZXQgUGVyZm9tYW5jZToiLCAgIlxuTWluIFJNU0U6IiwgbWluX3Jtc2VfZWxhc3RpYywgIlxuRXhwbGFpbmVkIFZhcmlhbmNlOiIsIHNzbV9lbGFzdGljLCAiXG5Sc3FyZDoiLCBSMl9lbGFzdGljKQ0KDQoNClIyX3JmIDwtIDEgLSAoc3NtX3JmIC8gc3N0KQ0KY2F0KCJSYW5kb20gRm9yZXN0IFBlcmZvbWFuY2U6IiwgICJcbk1pbiBSTVNFOiIsIG1pbl9ybXNlX3JmLCAiXG5FeHBsYWluZWQgVmFyaWFuY2U6Iiwgc3NtX3JmLCAiXG5Sc3FyZDoiLCBSMl9yZikNCg0KDQoNCnZhckltcChyZi5HT0kpIHw+DQogIHBsb3QoKQ0KDQp2YXJJbXAobDFfbDIuR09JKSB8Pg0KICBwbG90KCkNCiAgICAgICAgIA0KDQpgYGANCg0KIyAqKlRva2VucyBFbmNvZGluZzoqKg0KDQpXZSB3aWxsIFBsb3QgdGhlIDEwIG11c3QgcmVwcmVzZW50YXRpdmUgd29yZHMgZm9yIHRoZSBtb3N0IGluZm9ybWF0aXZlIGRpbWVuc2lvbnMgKFY5LCBWNCkNCg0KYGBge3J9DQpWX21hdHJpeCA8LSBkYXRhX2xzYSRmZWF0dXJlcyAgIyBHZXQgdGhlIHdvcmQgbG9hZGluZ3MNCg0KIyBWZWN0b3Igb2YgdGhlIHRlcm1zICh3b3JkcykgaW4gY29ycHVzDQp0ZXJtcyA8LSBjb2xuYW1lcyhkYXRhX3RmaWRmKQ0KDQojIEZvciBlYWNoIGRpbWVuc2lvbiwgZXh0cmFjdCB0aGUgdG9wIGNvbnRyaWJ1dGluZyB3b3Jkcw0KZ2V0X3RvcF93b3Jkc19wZXJfZGltZW5zaW9uIDwtIGZ1bmN0aW9uKFZfbWF0cml4LCB0ZXJtcywgdG9wX24gPSAxMCkgew0KICB0b3Bfd29yZHMgPC0gbGlzdCgpDQogIGZvciAoZGltIGluIDE6bmNvbChWX21hdHJpeCkpIHsNCiAgICAjIEdldCB0aGUgbG9hZGluZ3MgZm9yIHRoaXMgZGltZW5zaW9uDQogICAgbG9hZGluZ3MgPC0gVl9tYXRyaXhbLCBkaW1dDQogICAgDQogICAgIyBSYW5rIHRoZSB3b3JkcyBieSB0aGUgYWJzb2x1dGUgdmFsdWUgb2YgdGhlaXIgbG9hZGluZ3MNCiAgICByYW5rZWRfaW5kaWNlcyA8LSBvcmRlcihhYnMobG9hZGluZ3MpLCBkZWNyZWFzaW5nID0gVFJVRSlbMTp0b3Bfbl0NCiAgICANCiAgICAjIEdldCB0aGUgY29ycmVzcG9uZGluZyB3b3Jkcw0KICAgIHRvcF93b3Jkc1tbcGFzdGUoIkRpbWVuc2lvbiIsIGRpbSldXSA8LSB0ZXJtc1tyYW5rZWRfaW5kaWNlc10NCiAgfQ0KICByZXR1cm4odG9wX3dvcmRzKQ0KfQ0KDQojIEdldCB0aGUgdG9wIDEwIHdvcmRzIGZvciBlYWNoIGRpbWVuc2lvbg0KdG9wX3dvcmRzX3Blcl9kaW1lbnNpb24gPC0gZ2V0X3RvcF93b3Jkc19wZXJfZGltZW5zaW9uKFZfbWF0cml4LCB0ZXJtcywgdG9wX24gPSAxMCkNCg0KIyBEaXNwbGF5IEJlc3QgRGltZW5zaW9ucyB0byBQcmVkY2l0IEdPSQ0KDQp0b3Bfd29yZHNfcGVyX2RpbWVuc2lvbiRgRGltZW5zaW9uIDRgDQoNCnRvcF93b3Jkc19wZXJfZGltZW5zaW9uJGBEaW1lbnNpb24gOWANCmBgYA0K