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\""
print(q)
## [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
class(n1)
## [1] "numeric"
i1 <- as.integer(s1)
i1
## [1] 123
class(i1)
## [1] "integer"
i2 <- strtoi("2234")
i2
## [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"
class(d1)
## [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:

Symbol Meaning Example
%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"
print(t)
## [1] "this is a text or character string/and this is also text"

Extract Substrings

Extracting a specific set of characters from a string is done with substr(text, start, stop). An alternative is the substring(text, first, last = 1000000L) function; it functions the same way except that it goes to the end of the string if the last parameter is omitted. Another difference between substr and substring is the possibility to extract several substrings with one line of code. With substr, this is not possible.

The functions extract by position, while the more general grep function provides the location of a substring withing a string.

Both functions treat a string as a “vector” of characters (although it really isn’t a vector type). Characters are in positions from 1 (leftmost character) to the end. You can find the length of a string (the number of characters in it) with the function nchar.

s <- "Boston, MA 02115 USA"

l <- nchar(s)
print(paste("length of s is:", l))
## [1] "length of s is: 20"
# extract the characters from position 1 through 6, inclusive.
city <- substr(s, 1, 6)
print(city)
## [1] "Boston"
city <- substring(s, 1, 6)
print(city)
## [1] "Boston"
# extract the characters from position 9 to the end, inclusive.
state <- substring(s, 9)
print(state)
## [1] "MA 02115 USA"
# extract three characters from the right
country <- substring(s, nchar(s)-2)
print(country)
## [1] "USA"

The functions can also be applied to vectors of strings which is useful for processing data frames as each column in a data frame is a vector.

vs <- c("$123.77","$12.99","$1.99")

# extract all characters from the second to the end
vs2 <- substring(vs,2)
print(vs2)
## [1] "123.77" "12.99"  "1.99"
# count all string lengths in the vector
vsl <- nchar(vs2)
print(vsl)
## [1] 6 5 4

The substr function can also be on the left side of an assignment in which case parts of a string are replaced with new characters. The new characters have to be of the same number as the ones being replaced.

s <- "Boston, MA 02115 USA"

substring(s,7,8) <- "."
print(s)
## [1] "Boston. MA 02115 USA"
substring(s,9) <- "RI"
print(s)
## [1] "Boston. RI 02115 USA"

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"
print(l)
## [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
grepl("a", x)
## [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
m[5]
## [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.

Syntax Description
^ 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.


Files & Resources

All Files for Lesson 6.112

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