Preface
This tutorial presumes that you have R and R Studio installed, or that you have an account on rstudio.cloud. If you do not already have R and/or R Studio you will need to download and install them. You must first install R from R Project and then the R Studio IDE from R Studio.
In this is R Markdown Notebook we will demonstrate how to parse, process, search, and work with text (aka, character strings). When you execute code within the notebook, the results appear beneath the code.
Basic Text Functionality
The functions below are part of “Base R” and do not require any additional packages, although in practice many R programmers prefer string processing packages such as stringr.
paste
: glue (concatenate) text and numeric values together
substr
and substring
: extract or replace substrings in a character vector
grep
: use regular expressions to deal with patterns of text
regexpr
: find matching pattern using regular expressions
sub
and gsub
: replace parts of a string
strsplit
: split strings
nchar
: find number of characters in a string
as.numeric
: convert a string to numeric if feasible
strtoi
: convert a string to integer if it can be (faster than as.integer
)
A common issue in R is that often functions return a list object, when one would expect a vector. This sometimes requires an additional step or two of further processing that ought not to be necessary, but unfortunately often is. So be prepared for some extra code gymnastics using unlist
.
Dates are not always text in R; they are more likely of a Date or DateTime type and require different functions for processing.
Text in R
Text in R are character strings. The data type (class) of a character string is “characters”. There is no need to declare that a variable is of a string type; just assigning a string (anything in either single quotes ’ or double quotes ” is automatically a string). The fact that both ” and ’ can be used is useful when needing to embed quotes or apostrophies within strings. You can also “escape” special characters by prefixing with them with a backslash .
Displaying (or “printing”) text is done by either using the variable name by itself or by using the print
function.
s <- "this is a text or character string"
w <- 'and this is also text'
u <- 'and this is text with "double quotes"'
q <- "and this is a quote's quote"
t <- "text with escaped \"double quotes\" inside a string defined with \""
u
## [1] "and this is text with \"double quotes\""
## [1] "and this is a quote's quote"
Convert Strings to Numbers
Use the coercion functions as.type to convert (coerce) strings to other data types, most commonly Booleans, dates, and numbers. Converting to an integer does not round; it simply drops the fractional part (it “truncates”). The function function strtoi
is a faster way to convert strings to integers assuming that there is no fractional part.
s1 <- "123.99"
n1 <- as.numeric(s1)
n1
## [1] 123.99
## [1] "numeric"
## [1] 123
## [1] "integer"
## [1] 2234
Note that for conversions to work, the text cannot contain any non-numeric characters. The dot (.) is allowed as a decimal separator, but the comma (,) is not recognized as a thousands separator. It returns the error NAs introduced by coercion.
# the following would not work
e <- as.numeric("$123.99") # contains $
## Warning: NAs introduced by coercion
e <- as.numeric("123,99") # comma not recognized
## Warning: NAs introduced by coercion
Convert Strings to Dates
Dates are a separate data type in R. A date object can be used in “date aware” calculations. Note that strings must be in the format YYYY-MM-DD unless the format parameter is specified. The lubridate package has many more functions for converting from various other formats. The strings are converted to the standard format YYYY-MM-DD.
To get the current date from the system’s clock, use Sys.Date()
. To get the current date and time, use date()
.
s2 <- "2021-02-15"
d1 <- as.Date(s2)
d1
## [1] "2021-02-15"
## [1] "Date"
s3 <- "2019/06/30"
d2 <- as.Date(s3)
d2
## [1] "2019-06-30"
as.Date("07/04/20", format = "%m/%d/%y")
## [1] "2020-07-04"
as.Date("JUL 04 2021", format = "%b %d %Y")
## [1] "2021-07-04"
as.Date("February 11 80", format = "%B %d %y")
## [1] "1980-02-11"
# get current date and time
date()
## [1] "Wed Feb 14 09:47:19 2024"
# get current date only as YYYY-MM-DD
Sys.Date()
## [1] "2024-02-14"
Here are the various date format specifications:
%d |
day as a number (01-31) |
06 |
%a |
abbreviated weekday |
Mon |
%A |
full weekday |
Monday |
%m |
month (00-12) |
11 |
%b |
abbreviated month |
Nov |
%B |
full month |
March |
%y |
two-digit year |
20 |
%Y |
four-digit year |
2021 |
String Concatenation
Since print
only takes a single string as a parameter, you need to concatenate (i.e., combine) multiple strings using either paste
or paste0
. The former inserts a space after each string. paste
also allows you to specify a separator other than a space.
s <- "this is a text or character string"
w <- 'and this is also text'
r <- paste(s, w)
t <- paste(s, w, sep = "/")
print(r)
## [1] "this is a text or character string and this is also text"
## [1] "this is a text or character string/and this is also text"
Replacing Parts of a String
The sub
function is used to replace parts of a string with new text. Note that it only replaces the first occurrence while the function gsub
replaces all occurrences. The function returns the new string.
s <- "Boston, MA 02115"
s <- sub("02115","02117",s)
print(s)
## [1] "Boston, MA 02117"
The replacement text does not have to be of the same length as the replaced text.
s <- "Boston, MA 02125"
s <- sub("Boston","Charlestown",s)
print(s)
## [1] "Charlestown, MA 02125"
The sub
and gsub
functions can also contain regular expressions. More on regular expressions below.
s <- c("Charlestown, MA 02121","Brighton, MA 02137")
s <- sub("Charlestown|Brighton","Boston",s)
print(s)
## [1] "Boston, MA 02121" "Boston, MA 02137"
Splitting a String into Tokens
Splitting a string into tokens is done with the splitstr
function.
s <- "MA,CT,RI,NH,CA,FL"
# split based on comma ,
states <- strsplit(s,",")
print(states)
## [[1]]
## [1] "MA" "CT" "RI" "NH" "CA" "FL"
The function returns a “list” rather than a “vector”, so we often use the function unlist()
to convert the list to a vector. We can then use vector access to get to specific elements.
s <- "MA,CT,RI,NH,CA,FL"
# split based on comma ,
states <- unlist(strsplit(s,","))
# print all elements in the vector
print(states)
## [1] "MA" "CT" "RI" "NH" "CA" "FL"
# print a specific element
print(states[2])
## [1] "CT"
Converting to Upper or Lower Case
Matching is often easier if a string is in all upper or lower cases. The functions toupper
and tolower
do that.
s <- "Fred Flintstone"
u <- toupper(s)
l <- tolower(s)
print(u)
## [1] "FRED FLINTSTONE"
## [1] "fred flintstone"
Finding Parts of a String
The grep
and grepl
functions find pattern in strings and return a Boolean if there the pattern is found. The patterns are expressed as regular expressions.
x <- c("door", "apple", "color", "abba", "pattern")
# find which strings contain the pattern
grep("a", x)
## [1] 2 4 5
## [1] FALSE TRUE FALSE TRUE TRUE
The regexpr
returns the position in the string where there is first match occurs or a -1 if there is no match.
# find where the pattern starts
m <- regexpr("a", x)
m
## [1] -1 1 -1 1 2
## attr(,"match.length")
## [1] -1 1 -1 1 1
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
## [1] 2
In the example below we are looking for an occurrence of * which is a regular expression character, so we need to “escape its meaning” with a \. However, the \ is also a special character for regular expression so we need to also escape its meaning with a \\, hence the strange regular expression \\*.
# let's see if there is an * in a string
s <- "US AIRWAYS*"
p <- regexpr("\\*", s)
if (p > 0)
{
print(paste("* at position: ",p,"in string",s))
} else {
print("no asterisk found")
}
## [1] "* at position: 11 in string US AIRWAYS*"
To find, for example, the first space in a string, you need to use regexpr
and then process the return value which is a list.
x <- "Mazda Miata L"
# find where the pattern starts
m <- regexpr(" ", x)
post.first.space <- m[1]
The variable pos.first.space
now contains the position of the first space.
The function gregexpr()
returns all occurrences of a character and not just the first one. Inspecting the return list m
reveals that it returns a list and that the first element in the list is a vector of all occurences of the character:
x <- "Mazda Miata L"
# find where the pattern starts
m <- gregexpr(" ", x)
print(m)
## [[1]]
## [1] 6 12
## attr(,"match.length")
## [1] 1 1
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
# all occurrences of " "
print(unlist(m))
## [1] 6 12
Regular Expression Syntax
Several string processing functions take regular expressions as input. The table below summarizes the most common regular expression syntax constructs.
^ |
Beginning of the string |
$ |
End of the string |
[ab] |
a or b |
[^ab] |
Any character except a and b |
[0-9] |
Any digit |
[A-Z] |
Any uppercase letters from A to Z |
[a-z] |
Any uppercase letters from a to a |
[A-z] |
Any letter |
i+ |
i at least one time |
i* |
i zero or more times |
i? |
i zero or 1 time |
i{n} |
i occurs n times in sequence |
[:alnum:] |
Alphanumeric characters: [:alpha:] and [:digit:] |
[:alpha:] |
Alphabetic characters: [:lower:] and [:upper:] |
[:blank:] |
Blank characters, e.g., space, tab |
[:digit:] |
Digits: 0 1 2 3 4 5 6 7 8 9 |
[:punct:] |
Punctuation character: ! ” # $ % & ’ ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { |
Examples
# remove the $ from a vector of "numbers" and convert to numbers then calculate mean
v <- c("$99.12","13387.3","$0.998")
v <- as.numeric(substring(v, 2))
mean(v)
## [1] 1162.473
# remove all * characters from a vector of strings
v <- c("US AIRWAYS*","CONTINENTAL*","LUFTHANSA*","UNITED*","WOW*")
v <- substring(v, 1, nchar(v)-1)
v
## [1] "US AIRWAYS" "CONTINENTAL" "LUFTHANSA" "UNITED" "WOW"
# remove * characters from a vector of strings if there is one
v <- c("US AIRWAYS*","CONTINENTAL*","LUFTHANSA","UNITED","WOW*")
# the function below finds the starting positions of all *
# if there is no *, it returns -1
p <- regexpr("\\*", v)
# the function below returns the indexes of the strings in the vector v
# which actually contain a 0
g <- grep("\\*",v)
# only remove the * from those strings in the vector v that have it
v[g] <- substring(v[g],1,p[g]-1)
# our new vector with * removed
print(v)
## [1] "US AIRWAYS" "CONTINENTAL" "LUFTHANSA" "UNITED" "WOW"
# remove all commas for a number string as coercion with as.numeric(s)
# fail if the string contains commas as thousands separator
s <- "100,340,998"
numParts <- unlist(strsplit(s, ","))
num <- paste(numParts, collapse = "")
n <- as.numeric(num)
print(n)
## [1] 100340998
Tutorial
The tutorial below demonstrates how to create a project in R Studio and add files to the project.
References
No references.
Errata
None collected yet. Let us know.
LS0tDQp0aXRsZTogIkJhc2ljcyBvZiBUZXh0ICYgU3RyaW5nIFByb2Nlc3NpbmcgaW4gUiINCnBhcmFtczoNCiAgY2F0ZWdvcnk6IDYNCiAgbnVtYmVyOiAxMTINCiAgdGltZTogNDUNCiAgbGV2ZWw6IGJlZ2lubmVyDQogIHRhZ3M6ICJyLHIgc3R1ZGlvLHByaW1lciINCiAgZGVzY3JpcHRpb246ICJFeHBsYWlucyBzdHJpbmcgYW5kIHRleHQgcHJvY2Vzc2luZyBpbiBSLCBpbmNsdWRpbmcNCiAgICAgICAgICAgICAgICByZWd1bGFyIGV4cHJlc3Npb25zLiINCmRhdGU6ICI8c21hbGw+YHIgU3lzLkRhdGUoKWA8L3NtYWxsPiINCmF1dGhvcjogIjxzbWFsbD5NYXJ0aW4gU2NoZWRsYmF1ZXI8L3NtYWxsPiINCmVtYWlsOiAibS5zY2hlZGxiYXVlckBuZXUuZWR1Ig0KYWZmaWxpdGF0aW9uOiAiTm9ydGhlYXN0ZXJuIFVuaXZlcnNpdHkiDQpvdXRwdXQ6IA0KICBib29rZG93bjo6aHRtbF9kb2N1bWVudDI6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29sbGFwc2VkOiBmYWxzZQ0KICAgIG51bWJlcl9zZWN0aW9uczogZmFsc2UNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgdGhlbWU6IHNwYWNlbGFiDQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KLS0tDQoNCi0tLQ0KdGl0bGU6ICI8c21hbGw+YHIgcGFyYW1zJGNhdGVnb3J5YC5gciBwYXJhbXMkbnVtYmVyYDwvc21hbGw+PGJyLz48c3BhbiBzdHlsZT0nY29sb3I6ICMyRTQwNTM7IGZvbnQtc2l6ZTogMC45ZW0nPmByIHJtYXJrZG93bjo6bWV0YWRhdGEkdGl0bGVgPC9zcGFuPiINCi0tLQ0KDQpgYGB7ciBjb2RlPXhmdW46OnJlYWRfdXRmOChwYXN0ZTAoaGVyZTo6aGVyZSgpLCcvUi9faW5zZXJ0MkRCLlInKSksIGluY2x1ZGUgPSBGQUxTRX0NCmBgYA0KDQojIyBQcmVmYWNlDQoNClRoaXMgdHV0b3JpYWwgcHJlc3VtZXMgdGhhdCB5b3UgaGF2ZSBSIGFuZCBSIFN0dWRpbyBpbnN0YWxsZWQsIG9yIHRoYXQgeW91IGhhdmUgYW4gYWNjb3VudCBvbiBbcnN0dWRpby5jbG91ZF0oaHR0cDovL3JzdHVkaW8uY2xvdWQpLiBJZiB5b3UgZG8gbm90IGFscmVhZHkgaGF2ZSBSIGFuZC9vciBSIFN0dWRpbyB5b3Ugd2lsbCBuZWVkIHRvIGRvd25sb2FkIGFuZCBpbnN0YWxsIHRoZW0uIFlvdSBtdXN0IGZpcnN0IGluc3RhbGwgUiBmcm9tIFtSIFByb2plY3RdKGh0dHBzOi8vY2xvdWQuci1wcm9qZWN0Lm9yZy8pIGFuZCB0aGVuIHRoZSBSIFN0dWRpbyBJREUgZnJvbSBbUiBTdHVkaW9dKGh0dHBzOi8vcnN0dWRpby5jb20vcHJvZHVjdHMvcnN0dWRpby9kb3dubG9hZC8pLg0KDQpJbiB0aGlzIGlzIFtSIE1hcmtkb3duXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tKSBOb3RlYm9vayB3ZSB3aWxsIGRlbW9uc3RyYXRlIGhvdyB0byBwYXJzZSwgcHJvY2Vzcywgc2VhcmNoLCBhbmQgd29yayB3aXRoIHRleHQgKCpha2EqLCBjaGFyYWN0ZXIgc3RyaW5ncykuIFdoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4NCg0KIyMgQmFzaWMgVGV4dCBGdW5jdGlvbmFsaXR5DQoNClRoZSBmdW5jdGlvbnMgYmVsb3cgYXJlIHBhcnQgb2YgIkJhc2UgUiIgYW5kIGRvIG5vdCByZXF1aXJlIGFueSBhZGRpdGlvbmFsIHBhY2thZ2VzLCBhbHRob3VnaCBpbiBwcmFjdGljZSBtYW55IFIgcHJvZ3JhbW1lcnMgcHJlZmVyIHN0cmluZyBwcm9jZXNzaW5nIHBhY2thZ2VzIHN1Y2ggYXMgKipzdHJpbmdyKiouDQoNCi0gICA8Y29kZT5wYXN0ZTwvY29kZT46IGdsdWUgKGNvbmNhdGVuYXRlKSB0ZXh0IGFuZCBudW1lcmljIHZhbHVlcyB0b2dldGhlcg0KLSAgIDxjb2RlPnN1YnN0cjwvY29kZT4gYW5kIDxjb2RlPnN1YnN0cmluZzwvY29kZT46IGV4dHJhY3Qgb3IgcmVwbGFjZSBzdWJzdHJpbmdzIGluIGEgY2hhcmFjdGVyIHZlY3Rvcg0KLSAgIDxjb2RlPmdyZXA8L2NvZGU+OiB1c2UgcmVndWxhciBleHByZXNzaW9ucyB0byBkZWFsIHdpdGggcGF0dGVybnMgb2YgdGV4dA0KLSAgIDxjb2RlPnJlZ2V4cHI8L2NvZGU+OiBmaW5kIG1hdGNoaW5nIHBhdHRlcm4gdXNpbmcgcmVndWxhciBleHByZXNzaW9ucw0KLSAgIDxjb2RlPnN1YjwvY29kZT4gYW5kIDxjb2RlPmdzdWI8L2NvZGU+OiByZXBsYWNlIHBhcnRzIG9mIGEgc3RyaW5nDQotICAgPGNvZGU+c3Ryc3BsaXQ8L2NvZGU+OiBzcGxpdCBzdHJpbmdzDQotICAgPGNvZGU+bmNoYXI8L2NvZGU+OiBmaW5kIG51bWJlciBvZiBjaGFyYWN0ZXJzIGluIGEgc3RyaW5nDQotICAgPGNvZGU+YXMubnVtZXJpYzwvY29kZT46IGNvbnZlcnQgYSBzdHJpbmcgdG8gbnVtZXJpYyBpZiBmZWFzaWJsZQ0KLSAgIDxjb2RlPnN0cnRvaTwvY29kZT46IGNvbnZlcnQgYSBzdHJpbmcgdG8gaW50ZWdlciBpZiBpdCBjYW4gYmUgKGZhc3RlciB0aGFuIDxjb2RlPmFzLmludGVnZXI8L2NvZGU+KQ0KDQpBIGNvbW1vbiBpc3N1ZSBpbiBSIGlzIHRoYXQgb2Z0ZW4gZnVuY3Rpb25zIHJldHVybiBhICpsaXN0KiBvYmplY3QsIHdoZW4gb25lIHdvdWxkIGV4cGVjdCBhICp2ZWN0b3IqLiBUaGlzIHNvbWV0aW1lcyByZXF1aXJlcyBhbiBhZGRpdGlvbmFsIHN0ZXAgb3IgdHdvIG9mIGZ1cnRoZXIgcHJvY2Vzc2luZyB0aGF0IG91Z2h0IG5vdCB0byBiZSBuZWNlc3NhcnksIGJ1dCB1bmZvcnR1bmF0ZWx5IG9mdGVuIGlzLiBTbyBiZSBwcmVwYXJlZCBmb3Igc29tZSBleHRyYSBjb2RlIGd5bW5hc3RpY3MgdXNpbmcgPGNvZGU+dW5saXN0PC9jb2RlPi4NCg0KPiBEYXRlcyBhcmUgbm90IGFsd2F5cyB0ZXh0IGluIFI7IHRoZXkgYXJlIG1vcmUgbGlrZWx5IG9mIGEgKkRhdGUqIG9yICpEYXRlVGltZSogdHlwZSBhbmQgcmVxdWlyZSBkaWZmZXJlbnQgZnVuY3Rpb25zIGZvciBwcm9jZXNzaW5nLg0KDQojIyBUZXh0IGluIFINCg0KVGV4dCBpbiBSIGFyZSBjaGFyYWN0ZXIgc3RyaW5ncy4gVGhlIGRhdGEgdHlwZSAoY2xhc3MpIG9mIGEgY2hhcmFjdGVyIHN0cmluZyBpcyAqImNoYXJhY3RlcnMiKi4gVGhlcmUgaXMgbm8gbmVlZCB0byBkZWNsYXJlIHRoYXQgYSB2YXJpYWJsZSBpcyBvZiBhIHN0cmluZyB0eXBlOyBqdXN0IGFzc2lnbmluZyBhIHN0cmluZyAoYW55dGhpbmcgaW4gZWl0aGVyIHNpbmdsZSBxdW90ZXMgJyBvciBkb3VibGUgcXVvdGVzICIgaXMgYXV0b21hdGljYWxseSBhIHN0cmluZykuIFRoZSBmYWN0IHRoYXQgYm90aCAiIGFuZCAnIGNhbiBiZSB1c2VkIGlzIHVzZWZ1bCB3aGVuIG5lZWRpbmcgdG8gZW1iZWQgcXVvdGVzIG9yIGFwb3N0cm9waGllcyB3aXRoaW4gc3RyaW5ncy4gWW91IGNhbiBhbHNvICJlc2NhcGUiIHNwZWNpYWwgY2hhcmFjdGVycyBieSBwcmVmaXhpbmcgd2l0aCB0aGVtIHdpdGggYSBiYWNrc2xhc2ggLg0KDQpEaXNwbGF5aW5nIChvciAicHJpbnRpbmciKSB0ZXh0IGlzIGRvbmUgYnkgZWl0aGVyIHVzaW5nIHRoZSB2YXJpYWJsZSBuYW1lIGJ5IGl0c2VsZiBvciBieSB1c2luZyB0aGUgPGNvZGU+cHJpbnQ8L2NvZGU+IGZ1bmN0aW9uLg0KDQpgYGB7cn0NCnMgPC0gInRoaXMgaXMgYSB0ZXh0IG9yIGNoYXJhY3RlciBzdHJpbmciDQp3IDwtICdhbmQgdGhpcyBpcyBhbHNvIHRleHQnDQp1IDwtICdhbmQgdGhpcyBpcyB0ZXh0IHdpdGggImRvdWJsZSBxdW90ZXMiJw0KcSA8LSAiYW5kIHRoaXMgaXMgYSBxdW90ZSdzIHF1b3RlIg0KdCA8LSAidGV4dCB3aXRoIGVzY2FwZWQgXCJkb3VibGUgcXVvdGVzXCIgaW5zaWRlIGEgc3RyaW5nIGRlZmluZWQgd2l0aCBcIiINCg0KdQ0KcHJpbnQocSkNCmBgYA0KDQojIyBDb252ZXJ0IFN0cmluZ3MgdG8gTnVtYmVycw0KDQpVc2UgdGhlIGNvZXJjaW9uIGZ1bmN0aW9ucyAqYXMudHlwZSogdG8gY29udmVydCAoY29lcmNlKSBzdHJpbmdzIHRvIG90aGVyIGRhdGEgdHlwZXMsIG1vc3QgY29tbW9ubHkgQm9vbGVhbnMsIGRhdGVzLCBhbmQgbnVtYmVycy4gQ29udmVydGluZyB0byBhbiBpbnRlZ2VyIGRvZXMgbm90IHJvdW5kOyBpdCBzaW1wbHkgZHJvcHMgdGhlIGZyYWN0aW9uYWwgcGFydCAoaXQgInRydW5jYXRlcyIpLiBUaGUgZnVuY3Rpb24gZnVuY3Rpb24gPGNvZGU+c3RydG9pPC9jb2RlPiBpcyBhIGZhc3RlciB3YXkgdG8gY29udmVydCBzdHJpbmdzIHRvIGludGVnZXJzIGFzc3VtaW5nIHRoYXQgdGhlcmUgaXMgbm8gZnJhY3Rpb25hbCBwYXJ0Lg0KDQpgYGB7cn0NCnMxIDwtICIxMjMuOTkiDQpuMSA8LSBhcy5udW1lcmljKHMxKQ0KbjENCmNsYXNzKG4xKQ0KDQppMSA8LSBhcy5pbnRlZ2VyKHMxKQ0KaTENCmNsYXNzKGkxKQ0KDQppMiA8LSBzdHJ0b2koIjIyMzQiKQ0KaTINCg0KDQpgYGANCg0KTm90ZSB0aGF0IGZvciBjb252ZXJzaW9ucyB0byB3b3JrLCB0aGUgdGV4dCBjYW5ub3QgY29udGFpbiBhbnkgbm9uLW51bWVyaWMgY2hhcmFjdGVycy4gVGhlIGRvdCAoLikgaXMgYWxsb3dlZCBhcyBhIGRlY2ltYWwgc2VwYXJhdG9yLCBidXQgdGhlIGNvbW1hICgsKSBpcyBub3QgcmVjb2duaXplZCBhcyBhIHRob3VzYW5kcyBzZXBhcmF0b3IuIEl0IHJldHVybnMgdGhlIGVycm9yICpOQXMgaW50cm9kdWNlZCBieSBjb2VyY2lvbiouDQoNCmBgYHtyfQ0KIyB0aGUgZm9sbG93aW5nIHdvdWxkIG5vdCB3b3JrDQplIDwtIGFzLm51bWVyaWMoIiQxMjMuOTkiKSAgICAgICAgIyBjb250YWlucyAkDQplIDwtIGFzLm51bWVyaWMoIjEyMyw5OSIpICAgICAgICAgIyBjb21tYSBub3QgcmVjb2duaXplZA0KYGBgDQoNCiMjIENvbnZlcnQgU3RyaW5ncyB0byBEYXRlcw0KDQpEYXRlcyBhcmUgYSBzZXBhcmF0ZSBkYXRhIHR5cGUgaW4gUi4gQSBkYXRlIG9iamVjdCBjYW4gYmUgdXNlZCBpbiAiZGF0ZSBhd2FyZSIgY2FsY3VsYXRpb25zLiBOb3RlIHRoYXQgc3RyaW5ncyBtdXN0IGJlIGluIHRoZSBmb3JtYXQgWVlZWS1NTS1ERCB1bmxlc3MgdGhlICpmb3JtYXQqIHBhcmFtZXRlciBpcyBzcGVjaWZpZWQuIFRoZSAqKmx1YnJpZGF0ZSoqIHBhY2thZ2UgaGFzIG1hbnkgbW9yZSBmdW5jdGlvbnMgZm9yIGNvbnZlcnRpbmcgZnJvbSB2YXJpb3VzIG90aGVyIGZvcm1hdHMuIFRoZSBzdHJpbmdzIGFyZSBjb252ZXJ0ZWQgdG8gdGhlIHN0YW5kYXJkIGZvcm1hdCBZWVlZLU1NLURELg0KDQpUbyBnZXQgdGhlIGN1cnJlbnQgZGF0ZSBmcm9tIHRoZSBzeXN0ZW0ncyBjbG9jaywgdXNlIDxjb2RlPlN5cy5EYXRlKCk8L2NvZGU+LiBUbyBnZXQgdGhlIGN1cnJlbnQgZGF0ZSAqKmFuZCoqIHRpbWUsIHVzZSA8Y29kZT5kYXRlKCk8L2NvZGU+Lg0KDQpgYGB7cn0NCnMyIDwtICIyMDIxLTAyLTE1Ig0KZDEgPC0gYXMuRGF0ZShzMikNCmQxDQpjbGFzcyhkMSkNCg0KczMgPC0gIjIwMTkvMDYvMzAiDQpkMiA8LSBhcy5EYXRlKHMzKQ0KZDINCg0KYXMuRGF0ZSgiMDcvMDQvMjAiLCBmb3JtYXQgPSAiJW0vJWQvJXkiKQ0KYXMuRGF0ZSgiSlVMIDA0IDIwMjEiLCBmb3JtYXQgPSAiJWIgJWQgJVkiKQ0KYXMuRGF0ZSgiRmVicnVhcnkgMTEgODAiLCBmb3JtYXQgPSAiJUIgJWQgJXkiKQ0KDQojIGdldCBjdXJyZW50IGRhdGUgYW5kIHRpbWUNCmRhdGUoKQ0KDQojIGdldCBjdXJyZW50IGRhdGUgb25seSBhcyBZWVlZLU1NLUREDQpTeXMuRGF0ZSgpDQpgYGANCg0KSGVyZSBhcmUgdGhlIHZhcmlvdXMgZGF0ZSAqZm9ybWF0KiBzcGVjaWZpY2F0aW9uczoNCg0KfCBTeW1ib2wgfCBNZWFuaW5nICAgICAgICAgICAgICAgICB8IEV4YW1wbGUgfA0KfC0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tfA0KfCAlZCAgICAgfCBkYXkgYXMgYSBudW1iZXIgKDAxLTMxKSB8IDA2ICAgICAgfA0KfCAlYSAgICAgfCBhYmJyZXZpYXRlZCB3ZWVrZGF5ICAgICB8IE1vbiAgICAgfA0KfCAlQSAgICAgfCBmdWxsIHdlZWtkYXkgICAgICAgICAgICB8IE1vbmRheSAgfA0KfCAlbSAgICAgfCBtb250aCAoMDAtMTIpICAgICAgICAgICB8IDExICAgICAgfA0KfCAlYiAgICAgfCBhYmJyZXZpYXRlZCBtb250aCAgICAgICB8IE5vdiAgICAgfA0KfCAlQiAgICAgfCBmdWxsIG1vbnRoICAgICAgICAgICAgICB8IE1hcmNoICAgfA0KfCAleSAgICAgfCB0d28tZGlnaXQgeWVhciAgICAgICAgICB8IDIwICAgICAgfA0KfCAlWSAgICAgfCBmb3VyLWRpZ2l0IHllYXIgICAgICAgICB8IDIwMjEgICAgfA0KDQojIyBTdHJpbmcgQ29uY2F0ZW5hdGlvbg0KDQpTaW5jZSA8Y29kZT5wcmludDwvY29kZT4gb25seSB0YWtlcyBhIHNpbmdsZSBzdHJpbmcgYXMgYSBwYXJhbWV0ZXIsIHlvdSBuZWVkIHRvIGNvbmNhdGVuYXRlICgqaS5lLiosIGNvbWJpbmUpIG11bHRpcGxlIHN0cmluZ3MgdXNpbmcgZWl0aGVyIDxjb2RlPnBhc3RlPC9jb2RlPiBvciA8Y29kZT5wYXN0ZTA8L2NvZGU+LiBUaGUgZm9ybWVyIGluc2VydHMgYSBzcGFjZSBhZnRlciBlYWNoIHN0cmluZy4gPGNvZGU+cGFzdGU8L2NvZGU+IGFsc28gYWxsb3dzIHlvdSB0byBzcGVjaWZ5IGEgc2VwYXJhdG9yIG90aGVyIHRoYW4gYSBzcGFjZS4NCg0KYGBge3J9DQpzIDwtICJ0aGlzIGlzIGEgdGV4dCBvciBjaGFyYWN0ZXIgc3RyaW5nIg0KdyA8LSAnYW5kIHRoaXMgaXMgYWxzbyB0ZXh0Jw0KDQpyIDwtIHBhc3RlKHMsIHcpDQp0IDwtIHBhc3RlKHMsIHcsIHNlcCA9ICIvIikNCg0KcHJpbnQocikNCnByaW50KHQpDQpgYGANCg0KIyMgRXh0cmFjdCBTdWJzdHJpbmdzDQoNCkV4dHJhY3RpbmcgYSBzcGVjaWZpYyBzZXQgb2YgY2hhcmFjdGVycyBmcm9tIGEgc3RyaW5nIGlzIGRvbmUgd2l0aCA8Y29kZT5zdWJzdHIodGV4dCwgc3RhcnQsIHN0b3ApPC9jb2RlPi4gQW4gYWx0ZXJuYXRpdmUgaXMgdGhlIDxjb2RlPnN1YnN0cmluZyh0ZXh0LCBmaXJzdCwgbGFzdCA9IDEwMDAwMDBMKTwvY29kZT4gZnVuY3Rpb247IGl0IGZ1bmN0aW9ucyB0aGUgc2FtZSB3YXkgZXhjZXB0IHRoYXQgaXQgZ29lcyB0byB0aGUgZW5kIG9mIHRoZSBzdHJpbmcgaWYgdGhlIGxhc3QgcGFyYW1ldGVyIGlzIG9taXR0ZWQuIEFub3RoZXIgZGlmZmVyZW5jZSBiZXR3ZWVuIDxjb2RlPnN1YnN0cjwvY29kZT4gYW5kIDxjb2RlPnN1YnN0cmluZzwvY29kZT4gaXMgdGhlIHBvc3NpYmlsaXR5IHRvIGV4dHJhY3Qgc2V2ZXJhbCBzdWJzdHJpbmdzIHdpdGggb25lIGxpbmUgb2YgY29kZS4gV2l0aCA8Y29kZT5zdWJzdHI8L2NvZGU+LCB0aGlzIGlzIG5vdCBwb3NzaWJsZS4NCg0KVGhlIGZ1bmN0aW9ucyBleHRyYWN0IGJ5IHBvc2l0aW9uLCB3aGlsZSB0aGUgbW9yZSBnZW5lcmFsIDxjb2RlPmdyZXA8L2NvZGU+IGZ1bmN0aW9uIHByb3ZpZGVzIHRoZSBsb2NhdGlvbiBvZiBhIHN1YnN0cmluZyB3aXRoaW5nIGEgc3RyaW5nLg0KDQpCb3RoIGZ1bmN0aW9ucyB0cmVhdCBhIHN0cmluZyBhcyBhICJ2ZWN0b3IiIG9mIGNoYXJhY3RlcnMgKGFsdGhvdWdoIGl0IHJlYWxseSBpc24ndCBhICp2ZWN0b3IqIHR5cGUpLiBDaGFyYWN0ZXJzIGFyZSBpbiBwb3NpdGlvbnMgZnJvbSAxIChsZWZ0bW9zdCBjaGFyYWN0ZXIpIHRvIHRoZSBlbmQuIFlvdSBjYW4gZmluZCB0aGUgbGVuZ3RoIG9mIGEgc3RyaW5nICh0aGUgbnVtYmVyIG9mIGNoYXJhY3RlcnMgaW4gaXQpIHdpdGggdGhlIGZ1bmN0aW9uIDxjb2RlPm5jaGFyPC9jb2RlPi4NCg0KYGBge3J9DQpzIDwtICJCb3N0b24sIE1BIDAyMTE1IFVTQSINCg0KbCA8LSBuY2hhcihzKQ0KcHJpbnQocGFzdGUoImxlbmd0aCBvZiBzIGlzOiIsIGwpKQ0KDQojIGV4dHJhY3QgdGhlIGNoYXJhY3RlcnMgZnJvbSBwb3NpdGlvbiAxIHRocm91Z2ggNiwgaW5jbHVzaXZlLg0KY2l0eSA8LSBzdWJzdHIocywgMSwgNikNCnByaW50KGNpdHkpDQoNCmNpdHkgPC0gc3Vic3RyaW5nKHMsIDEsIDYpDQpwcmludChjaXR5KQ0KDQojIGV4dHJhY3QgdGhlIGNoYXJhY3RlcnMgZnJvbSBwb3NpdGlvbiA5IHRvIHRoZSBlbmQsIGluY2x1c2l2ZS4NCnN0YXRlIDwtIHN1YnN0cmluZyhzLCA5KQ0KcHJpbnQoc3RhdGUpDQoNCiMgZXh0cmFjdCB0aHJlZSBjaGFyYWN0ZXJzIGZyb20gdGhlIHJpZ2h0DQpjb3VudHJ5IDwtIHN1YnN0cmluZyhzLCBuY2hhcihzKS0yKQ0KcHJpbnQoY291bnRyeSkNCg0KYGBgDQoNClRoZSBmdW5jdGlvbnMgY2FuIGFsc28gYmUgYXBwbGllZCB0byB2ZWN0b3JzIG9mIHN0cmluZ3Mgd2hpY2ggaXMgdXNlZnVsIGZvciBwcm9jZXNzaW5nIGRhdGEgZnJhbWVzIGFzIGVhY2ggY29sdW1uIGluIGEgZGF0YSBmcmFtZSBpcyBhIHZlY3Rvci4NCg0KYGBge3J9DQp2cyA8LSBjKCIkMTIzLjc3IiwiJDEyLjk5IiwiJDEuOTkiKQ0KDQojIGV4dHJhY3QgYWxsIGNoYXJhY3RlcnMgZnJvbSB0aGUgc2Vjb25kIHRvIHRoZSBlbmQNCnZzMiA8LSBzdWJzdHJpbmcodnMsMikNCnByaW50KHZzMikNCg0KIyBjb3VudCBhbGwgc3RyaW5nIGxlbmd0aHMgaW4gdGhlIHZlY3Rvcg0KdnNsIDwtIG5jaGFyKHZzMikNCnByaW50KHZzbCkNCmBgYA0KDQpUaGUgPGNvZGU+c3Vic3RyPC9jb2RlPiBmdW5jdGlvbiBjYW4gYWxzbyBiZSBvbiB0aGUgbGVmdCBzaWRlIG9mIGFuIGFzc2lnbm1lbnQgaW4gd2hpY2ggY2FzZSBwYXJ0cyBvZiBhIHN0cmluZyBhcmUgcmVwbGFjZWQgd2l0aCBuZXcgY2hhcmFjdGVycy4gVGhlIG5ldyBjaGFyYWN0ZXJzIGhhdmUgdG8gYmUgb2YgdGhlIHNhbWUgbnVtYmVyIGFzIHRoZSBvbmVzIGJlaW5nIHJlcGxhY2VkLg0KDQpgYGB7cn0NCnMgPC0gIkJvc3RvbiwgTUEgMDIxMTUgVVNBIg0KDQpzdWJzdHJpbmcocyw3LDgpIDwtICIuIg0KcHJpbnQocykNCg0Kc3Vic3RyaW5nKHMsOSkgPC0gIlJJIg0KcHJpbnQocykNCmBgYA0KDQojIyBSZXBsYWNpbmcgUGFydHMgb2YgYSBTdHJpbmcNCg0KVGhlIDxjb2RlPnN1YjwvY29kZT4gZnVuY3Rpb24gaXMgdXNlZCB0byByZXBsYWNlIHBhcnRzIG9mIGEgc3RyaW5nIHdpdGggbmV3IHRleHQuIE5vdGUgdGhhdCBpdCBvbmx5IHJlcGxhY2VzIHRoZSBmaXJzdCBvY2N1cnJlbmNlIHdoaWxlIHRoZSBmdW5jdGlvbiA8Y29kZT5nc3ViPC9jb2RlPiByZXBsYWNlcyBhbGwgb2NjdXJyZW5jZXMuIFRoZSBmdW5jdGlvbiByZXR1cm5zIHRoZSBuZXcgc3RyaW5nLg0KDQpgYGB7cn0NCnMgPC0gIkJvc3RvbiwgTUEgMDIxMTUiDQoNCnMgPC0gc3ViKCIwMjExNSIsIjAyMTE3IixzKQ0KcHJpbnQocykNCmBgYA0KDQpUaGUgcmVwbGFjZW1lbnQgdGV4dCBkb2VzIG5vdCBoYXZlIHRvIGJlIG9mIHRoZSBzYW1lIGxlbmd0aCBhcyB0aGUgcmVwbGFjZWQgdGV4dC4NCg0KYGBge3J9DQpzIDwtICJCb3N0b24sIE1BIDAyMTI1Ig0KcyA8LSBzdWIoIkJvc3RvbiIsIkNoYXJsZXN0b3duIixzKQ0KcHJpbnQocykNCmBgYA0KDQpUaGUgPGNvZGU+c3ViPC9jb2RlPiBhbmQgPGNvZGU+Z3N1YjwvY29kZT4gZnVuY3Rpb25zIGNhbiBhbHNvIGNvbnRhaW4gcmVndWxhciBleHByZXNzaW9ucy4gTW9yZSBvbiByZWd1bGFyIGV4cHJlc3Npb25zIGJlbG93Lg0KDQpgYGB7cn0NCnMgPC0gYygiQ2hhcmxlc3Rvd24sIE1BIDAyMTIxIiwiQnJpZ2h0b24sIE1BIDAyMTM3IikNCg0KcyA8LSBzdWIoIkNoYXJsZXN0b3dufEJyaWdodG9uIiwiQm9zdG9uIixzKQ0KcHJpbnQocykNCmBgYA0KDQojIyBTcGxpdHRpbmcgYSBTdHJpbmcgaW50byBUb2tlbnMNCg0KU3BsaXR0aW5nIGEgc3RyaW5nIGludG8gdG9rZW5zIGlzIGRvbmUgd2l0aCB0aGUgPGNvZGU+c3BsaXRzdHI8L2NvZGU+IGZ1bmN0aW9uLg0KDQpgYGB7cn0NCnMgPC0gIk1BLENULFJJLE5ILENBLEZMIg0KDQojIHNwbGl0IGJhc2VkIG9uIGNvbW1hICwNCnN0YXRlcyA8LSBzdHJzcGxpdChzLCIsIikNCnByaW50KHN0YXRlcykNCmBgYA0KDQpUaGUgZnVuY3Rpb24gcmV0dXJucyBhICJsaXN0IiByYXRoZXIgdGhhbiBhICJ2ZWN0b3IiLCBzbyB3ZSBvZnRlbiB1c2UgdGhlIGZ1bmN0aW9uIGB1bmxpc3QoKWAgdG8gY29udmVydCB0aGUgbGlzdCB0byBhIHZlY3Rvci4gV2UgY2FuIHRoZW4gdXNlIHZlY3RvciBhY2Nlc3MgdG8gZ2V0IHRvIHNwZWNpZmljIGVsZW1lbnRzLg0KDQpgYGB7cn0NCnMgPC0gIk1BLENULFJJLE5ILENBLEZMIg0KDQojIHNwbGl0IGJhc2VkIG9uIGNvbW1hICwNCnN0YXRlcyA8LSB1bmxpc3Qoc3Ryc3BsaXQocywiLCIpKQ0KDQojIHByaW50IGFsbCBlbGVtZW50cyBpbiB0aGUgdmVjdG9yDQoNCnByaW50KHN0YXRlcykNCiMgcHJpbnQgYSBzcGVjaWZpYyBlbGVtZW50DQpwcmludChzdGF0ZXNbMl0pDQpgYGANCg0KIyMgQ29udmVydGluZyB0byBVcHBlciBvciBMb3dlciBDYXNlDQoNCk1hdGNoaW5nIGlzIG9mdGVuIGVhc2llciBpZiBhIHN0cmluZyBpcyBpbiBhbGwgdXBwZXIgb3IgbG93ZXIgY2FzZXMuIFRoZSBmdW5jdGlvbnMgPGNvZGU+dG91cHBlcjwvY29kZT4gYW5kIDxjb2RlPnRvbG93ZXI8L2NvZGU+IGRvIHRoYXQuDQoNCmBgYHtyfQ0KcyA8LSAiRnJlZCBGbGludHN0b25lIg0KDQp1IDwtIHRvdXBwZXIocykNCmwgPC0gdG9sb3dlcihzKQ0KDQpwcmludCh1KQ0KcHJpbnQobCkNCmBgYA0KDQojIyBGaW5kaW5nIFBhcnRzIG9mIGEgU3RyaW5nDQoNClRoZSA8Y29kZT5ncmVwPC9jb2RlPiBhbmQgPGNvZGU+Z3JlcGw8L2NvZGU+IGZ1bmN0aW9ucyBmaW5kIHBhdHRlcm4gaW4gc3RyaW5ncyBhbmQgcmV0dXJuIGEgQm9vbGVhbiBpZiB0aGVyZSB0aGUgcGF0dGVybiBpcyBmb3VuZC4gVGhlIHBhdHRlcm5zIGFyZSBleHByZXNzZWQgYXMgcmVndWxhciBleHByZXNzaW9ucy4NCg0KYGBge3J9DQp4IDwtIGMoImRvb3IiLCAiYXBwbGUiLCAiY29sb3IiLCAiYWJiYSIsICJwYXR0ZXJuIikNCg0KIyBmaW5kIHdoaWNoIHN0cmluZ3MgY29udGFpbiB0aGUgcGF0dGVybg0KZ3JlcCgiYSIsIHgpDQpncmVwbCgiYSIsIHgpDQoNCmBgYA0KDQpUaGUgPGNvZGU+cmVnZXhwcjwvY29kZT4gcmV0dXJucyB0aGUgcG9zaXRpb24gaW4gdGhlIHN0cmluZyB3aGVyZSB0aGVyZSBpcyBmaXJzdCBtYXRjaCBvY2N1cnMgb3IgYSAtMSBpZiB0aGVyZSBpcyBubyBtYXRjaC4NCg0KYGBge3J9DQojIGZpbmQgd2hlcmUgdGhlIHBhdHRlcm4gc3RhcnRzDQptIDwtIHJlZ2V4cHIoImEiLCB4KQ0KbQ0KbVs1XQ0KYGBgDQoNCkluIHRoZSBleGFtcGxlIGJlbG93IHdlIGFyZSBsb29raW5nIGZvciBhbiBvY2N1cnJlbmNlIG9mIFwqIHdoaWNoIGlzIGEgcmVndWxhciBleHByZXNzaW9uIGNoYXJhY3Rlciwgc28gd2UgbmVlZCB0byAiZXNjYXBlIGl0cyBtZWFuaW5nIiB3aXRoIGEgKlxcKi4gSG93ZXZlciwgdGhlICpcXCogaXMgYWxzbyBhIHNwZWNpYWwgY2hhcmFjdGVyIGZvciByZWd1bGFyIGV4cHJlc3Npb24gc28gd2UgbmVlZCB0byBhbHNvIGVzY2FwZSBpdHMgbWVhbmluZyB3aXRoIGEgKlxcXFwqLCBoZW5jZSB0aGUgc3RyYW5nZSByZWd1bGFyIGV4cHJlc3Npb24gKlxcXFxcKiouDQoNCmBgYHtyfQ0KIyBsZXQncyBzZWUgaWYgdGhlcmUgaXMgYW4gKiBpbiBhIHN0cmluZw0KcyA8LSAiVVMgQUlSV0FZUyoiDQpwIDwtIHJlZ2V4cHIoIlxcKiIsIHMpDQoNCmlmIChwID4gMCkNCnsNCiAgcHJpbnQocGFzdGUoIiogYXQgcG9zaXRpb246ICIscCwiaW4gc3RyaW5nIixzKSkNCn0gZWxzZSB7DQogIHByaW50KCJubyBhc3RlcmlzayBmb3VuZCIpDQp9DQpgYGANCg0KVG8gZmluZCwgZm9yIGV4YW1wbGUsIHRoZSBmaXJzdCBzcGFjZSBpbiBhIHN0cmluZywgeW91IG5lZWQgdG8gdXNlIGByZWdleHByYCBhbmQgdGhlbiBwcm9jZXNzIHRoZSByZXR1cm4gdmFsdWUgd2hpY2ggaXMgYSBsaXN0Lg0KDQpgYGB7cn0NCnggPC0gIk1hemRhIE1pYXRhIEwiDQojIGZpbmQgd2hlcmUgdGhlIHBhdHRlcm4gc3RhcnRzDQptIDwtIHJlZ2V4cHIoIiAiLCB4KQ0KDQpwb3N0LmZpcnN0LnNwYWNlIDwtIG1bMV0NCmBgYA0KDQpUaGUgdmFyaWFibGUgYHBvcy5maXJzdC5zcGFjZWAgbm93IGNvbnRhaW5zIHRoZSBwb3NpdGlvbiBvZiB0aGUgZmlyc3Qgc3BhY2UuDQoNClRoZSBmdW5jdGlvbiBgZ3JlZ2V4cHIoKWAgcmV0dXJucyBhbGwgb2NjdXJyZW5jZXMgb2YgYSBjaGFyYWN0ZXIgYW5kIG5vdCBqdXN0IHRoZSBmaXJzdCBvbmUuIEluc3BlY3RpbmcgdGhlIHJldHVybiBsaXN0IGBtYCByZXZlYWxzIHRoYXQgaXQgcmV0dXJucyBhIGxpc3QgYW5kIHRoYXQgdGhlIGZpcnN0IGVsZW1lbnQgaW4gdGhlIGxpc3QgaXMgYSB2ZWN0b3Igb2YgYWxsIG9jY3VyZW5jZXMgb2YgdGhlIGNoYXJhY3RlcjoNCg0KYGBge3J9DQp4IDwtICJNYXpkYSBNaWF0YSBMIg0KIyBmaW5kIHdoZXJlIHRoZSBwYXR0ZXJuIHN0YXJ0cw0KbSA8LSBncmVnZXhwcigiICIsIHgpDQoNCnByaW50KG0pDQoNCiMgYWxsIG9jY3VycmVuY2VzIG9mICIgIg0KcHJpbnQodW5saXN0KG0pKQ0KYGBgDQoNCiMjIFJlZ3VsYXIgRXhwcmVzc2lvbiBTeW50YXgNCg0KU2V2ZXJhbCBzdHJpbmcgcHJvY2Vzc2luZyBmdW5jdGlvbnMgdGFrZSByZWd1bGFyIGV4cHJlc3Npb25zIGFzIGlucHV0LiBUaGUgdGFibGUgYmVsb3cgc3VtbWFyaXplcyB0aGUgbW9zdCBjb21tb24gcmVndWxhciBleHByZXNzaW9uIHN5bnRheCBjb25zdHJ1Y3RzLg0KDQp8IFN5bnRheCAgICB8IERlc2NyaXB0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfC0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwNCnwgXF4gICAgICAgIHwgQmVnaW5uaW5nIG9mIHRoZSBzdHJpbmcgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IFwkICAgICAgICB8IEVuZCBvZiB0aGUgc3RyaW5nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCBbYWJdICAgICAgfCBhIG9yIGIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgW1xeYWJdICAgIHwgQW55IGNoYXJhY3RlciBleGNlcHQgYSBhbmQgYiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IFswLTldICAgICB8IEFueSBkaWdpdCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCBbQS1aXSAgICAgfCBBbnkgdXBwZXJjYXNlIGxldHRlcnMgZnJvbSBBIHRvIFogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgW2Etel0gICAgIHwgQW55IHVwcGVyY2FzZSBsZXR0ZXJzIGZyb20gYSB0byBhICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IFtBLXpdICAgICB8IEFueSBsZXR0ZXIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCBpKyAgICAgICAgfCAqaSogYXQgbGVhc3Qgb25lIHRpbWUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgaVwqICAgICAgIHwgKmkqIHplcm8gb3IgbW9yZSB0aW1lcyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IGk/ICAgICAgICB8ICppKiB6ZXJvIG9yIDEgdGltZSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCBpe259ICAgICAgfCAqaSogb2NjdXJzICpuKiB0aW1lcyBpbiBzZXF1ZW5jZSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgWzphbG51bTpdIHwgQWxwaGFudW1lcmljIGNoYXJhY3RlcnM6IFs6YWxwaGE6XSBhbmQgWzpkaWdpdDpdICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IFs6YWxwaGE6XSB8IEFscGhhYmV0aWMgY2hhcmFjdGVyczogWzpsb3dlcjpdIGFuZCBbOnVwcGVyOl0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCBbOmJsYW5rOl0gfCBCbGFuayBjaGFyYWN0ZXJzLCAqZS5nLiosIHNwYWNlLCB0YWIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgWzpkaWdpdDpdIHwgRGlnaXRzOiAwIDEgMiAzIDQgNSA2IDcgOCA5ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IFs6cHVuY3Q6XSB8IFB1bmN0dWF0aW9uIGNoYXJhY3RlcjogISAiIFwjIFwkICUgJiAnICggKSBcKiArICwgLSAuIC8gOiA7IFw8ID0gXD4gPyBcQCBbIMKgXSBcXiBcXyBcYCB7IHwNCg0KIyMgRXhhbXBsZXMNCg0KYGBge3J9DQojIHJlbW92ZSB0aGUgJCBmcm9tIGEgdmVjdG9yIG9mICJudW1iZXJzIiBhbmQgY29udmVydCB0byBudW1iZXJzIHRoZW4gY2FsY3VsYXRlIG1lYW4NCnYgPC0gYygiJDk5LjEyIiwiMTMzODcuMyIsIiQwLjk5OCIpDQp2IDwtIGFzLm51bWVyaWMoc3Vic3RyaW5nKHYsIDIpKQ0KbWVhbih2KQ0KYGBgDQoNCmBgYHtyfQ0KIyByZW1vdmUgYWxsICogY2hhcmFjdGVycyBmcm9tIGEgdmVjdG9yIG9mIHN0cmluZ3MNCnYgPC0gYygiVVMgQUlSV0FZUyoiLCJDT05USU5FTlRBTCoiLCJMVUZUSEFOU0EqIiwiVU5JVEVEKiIsIldPVyoiKQ0KDQp2IDwtIHN1YnN0cmluZyh2LCAxLCBuY2hhcih2KS0xKQ0Kdg0KYGBgDQoNCmBgYHtyfQ0KIyByZW1vdmUgKiBjaGFyYWN0ZXJzIGZyb20gYSB2ZWN0b3Igb2Ygc3RyaW5ncyBpZiB0aGVyZSBpcyBvbmUNCnYgPC0gYygiVVMgQUlSV0FZUyoiLCJDT05USU5FTlRBTCoiLCJMVUZUSEFOU0EiLCJVTklURUQiLCJXT1cqIikNCg0KIyB0aGUgZnVuY3Rpb24gYmVsb3cgZmluZHMgdGhlIHN0YXJ0aW5nIHBvc2l0aW9ucyBvZiBhbGwgKg0KIyBpZiB0aGVyZSBpcyBubyAqLCBpdCByZXR1cm5zIC0xDQpwIDwtIHJlZ2V4cHIoIlxcKiIsIHYpDQoNCiMgdGhlIGZ1bmN0aW9uIGJlbG93IHJldHVybnMgdGhlIGluZGV4ZXMgb2YgdGhlIHN0cmluZ3MgaW4gdGhlIHZlY3RvciB2DQojIHdoaWNoIGFjdHVhbGx5IGNvbnRhaW4gYSAwDQpnIDwtIGdyZXAoIlxcKiIsdikNCg0KIyBvbmx5IHJlbW92ZSB0aGUgKiBmcm9tIHRob3NlIHN0cmluZ3MgaW4gdGhlIHZlY3RvciB2IHRoYXQgaGF2ZSBpdA0KdltnXSA8LSBzdWJzdHJpbmcodltnXSwxLHBbZ10tMSkNCg0KIyBvdXIgbmV3IHZlY3RvciB3aXRoICogcmVtb3ZlZA0KcHJpbnQodikNCmBgYA0KDQpgYGB7cn0NCiMgcmVtb3ZlIGFsbCBjb21tYXMgZm9yIGEgbnVtYmVyIHN0cmluZyBhcyBjb2VyY2lvbiB3aXRoIGFzLm51bWVyaWMocykNCiMgZmFpbCBpZiB0aGUgc3RyaW5nIGNvbnRhaW5zIGNvbW1hcyBhcyB0aG91c2FuZHMgc2VwYXJhdG9yDQpzIDwtICIxMDAsMzQwLDk5OCINCg0KbnVtUGFydHMgPC0gdW5saXN0KHN0cnNwbGl0KHMsICIsIikpDQpudW0gPC0gcGFzdGUobnVtUGFydHMsIGNvbGxhcHNlID0gIiIpDQpuIDwtIGFzLm51bWVyaWMobnVtKQ0KDQpwcmludChuKQ0KYGBgDQoNCiMjIFR1dG9yaWFsDQoNClRoZSB0dXRvcmlhbCBiZWxvdyBkZW1vbnN0cmF0ZXMgaG93IHRvIGNyZWF0ZSBhIHByb2plY3QgaW4gUiBTdHVkaW8gYW5kIGFkZCBmaWxlcyB0byB0aGUgcHJvamVjdC4NCg0KYGBgez1odG1sfQ0KPGlmcmFtZSBzcmM9IiIgd2lkdGg9IjQ4MCIgaGVpZ2h0PSIyNzAiIGZyYW1lYm9yZGVyPSIwIiBhbGxvdz0iYXV0b3BsYXk7IGZ1bGxzY3JlZW47IHBpY3R1cmUtaW4tcGljdHVyZSIgYWxsb3dmdWxsc2NyZWVuIGRhdGEtZXh0ZXJuYWw9IjEiPjwvaWZyYW1lPg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyBGaWxlcyAmIFJlc291cmNlcw0KDQpgYGB7ciB6aXBGaWxlcywgZWNobz1GQUxTRX0NCnppcE5hbWUgPSBzcHJpbnRmKCJMZXNzb25GaWxlcy0lcy0lcy56aXAiLCANCiAgICAgICAgICAgICAgICAgcGFyYW1zJGNhdGVnb3J5LA0KICAgICAgICAgICAgICAgICBwYXJhbXMkbnVtYmVyKQ0KDQp0ZXh0QUxpbmsgPSBwYXN0ZTAoIkFsbCBGaWxlcyBmb3IgTGVzc29uICIsIA0KICAgICAgICAgICAgICAgcGFyYW1zJGNhdGVnb3J5LCIuIixwYXJhbXMkbnVtYmVyKQ0KDQojIGRvd25sb2FkRmlsZXNMaW5rKCkgaXMgaW5jbHVkZWQgZnJvbSBfaW5zZXJ0MkRCLlINCmtuaXRyOjpyYXdfaHRtbChkb3dubG9hZEZpbGVzTGluaygiLiIsIHppcE5hbWUsIHRleHRBTGluaykpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIFJlZmVyZW5jZXMNCg0KTm8gcmVmZXJlbmNlcy4NCg0KIyMgRXJyYXRhDQoNCk5vbmUgY29sbGVjdGVkIHlldC4gTGV0IHVzIGtub3cuDQoNCmBgYHs9aHRtbH0NCjxzY3JpcHQgc3JjPSJodHRwczovL2Zvcm0uam90Zm9ybS5jb20vc3RhdGljL2ZlZWRiYWNrMi5qcyIgdHlwZT0idGV4dC9qYXZhc2NyaXB0Ij4NCiAgbmV3IEpvdGZvcm1GZWVkYmFjayh7DQogICAgZm9ybUlkOiAiMjEyMTg3MDcyNzg0MTU3IiwNCiAgICBidXR0b25UZXh0OiAiRmVlZGJhY2siLA0KICAgIGJhc2U6ICJodHRwczovL2Zvcm0uam90Zm9ybS5jb20vIiwNCiAgICBiYWNrZ3JvdW5kOiAiI0Y1OTIwMiIsDQogICAgZm9udENvbG9yOiAiI0ZGRkZGRiIsDQogICAgYnV0dG9uU2lkZTogImxlZnQiLA0KICAgIGJ1dHRvbkFsaWduOiAiY2VudGVyIiwNCiAgICB0eXBlOiBmYWxzZSwNCiAgICB3aWR0aDogNzAwLA0KICAgIGhlaWdodDogNTAwLA0KICAgIGlzQ2FyZEZvcm06IGZhbHNlDQogIH0pOw0KPC9zY3JpcHQ+DQpgYGANCmBgYHtyIGNvZGU9eGZ1bjo6cmVhZF91dGY4KHBhc3RlMChoZXJlOjpoZXJlKCksJy9SL19kZXBsb3lLbml0LlInKSksIGluY2x1ZGUgPSBGQUxTRX0NCmBgYA0K