
Glyco-regex syntax, step by step
glyco-regex-syntax.RmdWhat is a pattern?
A pattern is a short description of the feature you
want to find in a glycan. For example, Gal-GlcNAc asks for
Gal directly attached to GlcNAc. A regular expression,
often shortened to regex, adds symbols that express
choices, repetition, and conditions. You do not need prior regex
knowledge: this guide introduces those symbols one at a time.
Glyco-regex searches the connected residues of a glycan, rather than
letters in its written name. Gal means one galactose
residue; it does not match the letters “Gal” within GalNAc.
Different ways of writing the same branch arrangement do not turn it
into a different structural feature.
The Get Started guide introduces the biological motivation and basic workflow. This guide explains the syntax in more detail, including optional advanced features. The examples use small illustrative glycans so that you can check each answer by eye.
library(glyrex)
glycans <- c(
lacnac = "Gal(b1-4)GlcNAc(b1-",
sialylated = "Neu5Ac(a2-3)Gal(b1-4)GlcNAc(b1-",
fucosylated = "Gal(b1-4)[Fuc(a1-3)]GlcNAc(b1-",
both = "Neu5Ac(a2-3)Gal(b1-4)[Fuc(a1-3)]GlcNAc(b1-",
glcnac = "GlcNAc(b1-"
)
rex_detect(glycans, "Gal-GlcNAc")
#> lacnac sialylated fucosylated both glcnac
#> TRUE TRUE TRUE TRUE FALSEThe first argument is the collection of glycans; the second is the
pattern in quotation marks. TRUE means a match was found,
and FALSE means none was found. Each answer corresponds to
the input with the same name.
Keep structure notation and pattern notation separate
The inputs above use IUPAC-condensed notation to describe structures. The pattern uses glyco-regex notation to describe a search:
| Feature | Input structure notation | Pattern notation |
|---|---|---|
| Gal attached by β1–4 to GlcNAc | Gal(b1-4)GlcNAc |
Galb4-GlcNAc |
| Fuc branch on the same GlcNAc | Gal(b1-4)[Fuc(a1-3)]GlcNAc |
Galb4-(Fuca3-)GlcNAc |
| Reducing-end α-GlcNAc | GlcNAc(a1- |
GlcNAca$ |
The ending (b1- in our inputs records the reducing-end
anomer and donor position; it does not add another residue. Do not copy
IUPAC linkage parentheses into a pattern: Gal(b1-4)GlcNAc
is not a valid search pattern.
Patterns are case-sensitive. Write recognized residue names such as
Gal and GlcNAc. Spaces in patterns are
ignored, although the examples omit them.
One residue, several choices, or any residue
Start with a residue name, a family, or a choice:
| Pattern | Meaning |
|---|---|
Gal |
One Gal residue |
Hex |
One residue in the Hex family, including Gal, Glc, and Man |
HexNAc |
One residue in the HexNAc family, including GlcNAc and GalNAc |
Sia |
One Neu5Ac, Neu5Gc, or Kdn residue |
. |
Any one residue |
[Gal|Man] |
Either Gal or Man |
[^Fuc] |
One residue other than Fuc |
[^Fuc|Xyl] |
One residue that is neither Fuc nor Xyl |
The vertical bar | means “or.” Square brackets collect
the alternatives. Each negated alternative (inside [^...])
must describe one residue, not a multi-residue path. The dot does not
mean “any number of residues.”
residues <- c("Gal", "GalNAc", "Man", "Fuc")
data.frame(
residue = residues,
gal = rex_detect(residues, "Gal"),
hex = rex_detect(residues, "Hex"),
gal_or_man = rex_detect(residues, "[Gal|Man]"),
not_fuc = rex_detect(residues, "[^Fuc]")
)
#> residue gal hex gal_or_man not_fuc
#> 1 Gal TRUE TRUE TRUE TRUE
#> 2 GalNAc FALSE FALSE FALSE TRUE
#> 3 Man FALSE TRUE TRUE TRUE
#> 4 Fuc FALSE FALSE FALSE FALSEResidue modifications matter. For example, Gal6S asks
for sulfated Gal, whereas plain Gal does not match that
modified residue:
rex_detect("Gal6S(b1-4)GlcNAc", c("Gal6S", "Gal", "."))
#> [1] TRUE FALSE TRUEUse residue names supported by glyrepr. A slash can also express
residue choices, as in Gal/Man; [Gal|Man]
makes the alternatives more explicit.
Build a connected path
Read from left to right, towards the reducing end. A dash joins adjacent residues:
-
Gal-GlcNAc: Gal attaches directly to GlcNAc. -
Sia-Gal-GlcNAc: a three-residue path in that order. -
Gal-.-GlcNAc: exactly one intervening residue between Gal and GlcNAc.
A path may occur inside a larger glycan. Extra residues and branches
are allowed unless you add a condition that excludes them. Finding
Gal-GlcNAc therefore does not establish that Gal is
terminal or that GlcNAc has no other branches.
Alternatives can describe whole paths too:
rex_detect(
c("Gal(b1-4)GlcNAc", "Gal(b1-3)GalNAc", "Man(a1-3)Man"),
"[Gal-GlcNAc|Gal-GalNAc]"
)
#> [1] TRUE TRUE FALSEThe choice belongs inside the square brackets. A bare
Gal|Man is not supported.
Specify a linkage only as precisely as you need
A linkage suffix belongs to the residue before the
dash. In Galb4-GlcNAc, b means beta and
4 is the acceptor position on GlcNAc. The donor position is
inferred as 1, or 2 for Neu5Ac, Neu5Gc, Kdn, and Sia. Thus
Neu5Aca3-Gal describes an α2–3 linkage.
| Pattern | Linkage requirement |
|---|---|
Gal-GlcNAc |
No linkage constraint |
Galb-GlcNAc |
Beta anomer; no position constraint |
Galb4-GlcNAc |
β1–4 |
Galb3/4-GlcNAc |
Beta, with acceptor position 3 or 4 |
Gal?4-GlcNAc |
Either anomer, with donor 1 and acceptor 4 |
Galb?-GlcNAc |
Beta, with donor 1 and any acceptor position |
linkages <- c(
beta4 = "Gal(b1-4)GlcNAc",
beta3 = "Gal(b1-3)GlcNAc",
alpha4 = "Gal(a1-4)GlcNAc",
unknown_position = "Gal(b1-?)GlcNAc"
)
rex_detect(linkages, "Galb4-GlcNAc")
#> beta4 beta3 alpha4 unknown_position
#> TRUE FALSE FALSE FALSE
rex_detect(linkages, "Galb3/4-GlcNAc")
#> beta4 beta3 alpha4 unknown_position
#> TRUE TRUE FALSE FALSEThe glycan must be at least as specific as the
pattern. Unknown fields in the pattern act as wildcards;
unknown input fields cannot satisfy a specified value. The
unknown-position example therefore does not match
Galb4-GlcNAc. Every possible input position must be allowed
by the pattern: input 4 matches pattern 3/4,
but input 3/4 does not match pattern 4. Equal
sets of possible positions match.
A linkage suffix is checked even on the final residue of a pattern.
For example, GlcNAcb3 requires that GlcNAc attach onwards
by β1–3, although that next residue is not included in the match. At the
reducing end there is no outgoing glycosidic linkage, so an acceptor
position cannot match there. Use GlcNAca$, rather than
GlcNAca4$, for reducing-end α-GlcNAc.
Restrict where the match occurs
These symbols test position without adding residues to the result:
| Pattern | Position required |
|---|---|
^Gal |
Gal is at a non-reducing terminus |
GlcNAc$ |
GlcNAc is the reducing-end residue |
Gal% |
Gal is internal: neither terminal nor reducing-end |
rex_detect(glycans, "^Galb4-GlcNAc")
#> lacnac sialylated fucosylated both glcnac
#> TRUE FALSE TRUE FALSE FALSE
rex_detect(glycans, "Gal%")
#> lacnac sialylated fucosylated both glcnac
#> FALSE TRUE FALSE TRUE FALSE
rex_detect(glycans, "GlcNAcb$")
#> lacnac sialylated fucosylated both glcnac
#> TRUE TRUE TRUE TRUE TRUEOnly the uncapped Gal residues satisfy ^Gal. In the
sialylated examples, Gal is internal and satisfies Gal%
instead. The symbol ^ means something different
inside a negated choice: [^Fuc] excludes a
residue identity; it does not ask for a terminus.
Even ^Gal-GlcNAc$ need not describe the whole glycan:
extra branches on GlcNAc remain allowed. The two symbols constrain the
ends of the selected path, not the total number of residues in the
structure.
Describe branches on a shared residue
Ordinary parentheses mean a branch. In
Galb4-(Fuca3-)GlcNAc, Gal and Fuc both attach to
GlcNAc:
Gal --β1–4-->
GlcNAc
Fuc --α1–3-->
Read (Fuca3-) as “also require a Fucα1–3 branch on the
following residue.” Keep its attachment dash inside the parentheses and
place GlcNAc immediately after the closing parenthesis.
rex_detect(glycans, "Galb4-(Fuca3-)GlcNAc")
#> lacnac sialylated fucosylated both glcnac
#> FALSE FALSE TRUE TRUE FALSE
rex_extract(glycans["fucosylated"], "Galb4-(Fuca3-)GlcNAc")
#> fucosylated
#> "Fuc(a1-3)[Gal(b1-4)]GlcNAc(b1-"A required branch is included in the extracted match. A branch can
contain a path, such as (Galb4-GlcNAcb6-)Man, and branches
can be nested:
nested <- "Gal(b1-4)[Gal(b1-4)[Fuc(a1-3)]GlcNAc(b1-6)]GlcNAc"
rex_detect(nested, "Galb4-(Galb4-(Fuca3-)GlcNAcb6-)GlcNAc")
#> [1] TRUEWork from the inside out: the inner Fuc attaches to the branch GlcNAc; that GlcNAc attaches by β1–6 to the final GlcNAc. The outer Gal attaches by β1–4 to that final GlcNAc. Extra branches are still allowed.
Require a branch to be absent
Add ! immediately inside the parentheses:
rex_detect(glycans, "Galb4-(!Fuc-)GlcNAc")
#> lacnac sialylated fucosylated both glcnac
#> TRUE TRUE FALSE FALSE FALSEThis requires the Gal–GlcNAc path with no remaining Fuc branch on its
GlcNAc. (!Fuca3-) excludes only a compatible α1–3 Fuc
branch; (!Fuc-) excludes Fuc with any linkage. An absence
condition can describe a longer branch too, for example
Gal-(!Fuc-Man-)GlcNAc.
The word remaining matters: the check examines
branches not already used by the preceding pattern.
Fuc-(!Fuc-)GlcNAc can match a single Fuc attached to
GlcNAc; it asks for no additional Fuc branch there.
Three different meanings of “not”
| Question | How to express it |
|---|---|
| Is a non-Fuc residue attached to GlcNAc? | [^Fuc]-GlcNAc |
| Does this GlcNAc lack a Fuc branch? | (!Fuc-)GlcNAc |
| Does the entire glycan lack Fuc? | rex_detect(glycans, "Fuc", negate = TRUE) |
data.frame(
glycan = names(glycans),
non_fuc_neighbor = unname(rex_detect(glycans, "[^Fuc]-GlcNAc")),
no_fuc_branch = unname(rex_detect(glycans, "(!Fuc-)GlcNAc")),
no_fuc_anywhere = unname(rex_detect(glycans, "Fuc", negate = TRUE))
)
#> glycan non_fuc_neighbor no_fuc_branch no_fuc_anywhere
#> 1 lacnac TRUE TRUE TRUE
#> 2 sialylated TRUE TRUE TRUE
#> 3 fucosylated TRUE FALSE FALSE
#> 4 both TRUE FALSE FALSE
#> 5 glcnac FALSE TRUE TRUEThe fucosylated LacNAc has a non-Fuc neighbour (Gal), so it passes the first column. GlcNAc alone has no neighbour, so it fails that column but passes the absence checks. A local branch condition says nothing about fucose elsewhere in a larger structure.
Optional residues and repeated units
Put the unit in square brackets, then say how many copies are allowed. The symbol or braces after the group apply to the whole group.
| Form | Number of consecutive copies |
|---|---|
[Gal]? |
Zero or one |
[Gal]* |
Zero or more |
[Gal]+ |
One or more |
[Gal]{2} |
Exactly two |
[Gal]{2,4} |
Two to four |
[Gal]{2,} |
At least two |
[Gal]{,4} |
Zero to four |
For example, [Sia]?-Galb4-GlcNAc allows an optional Sia
before LacNAc:
rex_detect(glycans, "[Sia]?-Galb4-GlcNAc")
#> lacnac sialylated fucosylated both glcnac
#> TRUE TRUE TRUE TRUE FALSEOptional does not mean forbidden: both sialylated and unsialylated LacNAc can match. Because a match may start within a glycan, even the zero-copy choice can find a capped Gal. Add terminal or neighbouring-path conditions if you need to distinguish those cases.
A repeated group can contain several residues:
repeat_glycan <- "Gal(b1-4)GlcNAc(b1-3)Gal(b1-4)GlcNAc(b1-3)Gal(b1-"
rex_extract(repeat_glycan, "[Galb4-GlcNAcb3]{2}")
#> [1] "Gal(b1-4)GlcNAc(b1-3)Gal(b1-4)GlcNAc(b1-"This repeats the Gal–GlcNAc unit twice. The b3 on GlcNAc
constrains its attachment to the next residue, including the final
attachment beyond the selected two units. It is a connected path, not a
request for two copies anywhere in the glycan. Use
rex_count() for counting occurrences.
Use [Gal]+, not Gal+. The alternative
grouping syntax (?:Galb4-GlcNAcb3){2} has the same purpose
as square brackets; plain parentheses would mean a branch.
A branch can also be optional, as in
Galb4-(Fuca3-)?GlcNAc. It allows the Fuc branch to
participate in the match but does not exclude other branches or other
Fuc linkages when that optional branch is skipped.
Prefer longer or shorter repeats
By default, repetition tries longer runs first. This is called
greedy matching. Add ? after the
repetition symbol to try shorter runs first, called
lazy matching. The rest of the pattern must succeed
either way.
chain <- "Gal(b1-4)GlcNAc(b1-4)GlcNAc"
rex_extract(chain, "Hex-[HexNAc]+")
#> [1] "Gal(b1-4)GlcNAc(b1-4)GlcNAc(?1-"
rex_extract(chain, "Hex-[HexNAc]+?")
#> [1] "Gal(b1-4)GlcNAc(b1-"The first includes both GlcNAc residues; the second includes just
one. In [Gal]?, the question mark means optional; in
[Gal]+?, it changes which repetition length is tried first.
In Gal?4, it instead belongs to the linkage suffix. Use the
bracketed form [Gal]? to make optionality clear.
Even when zero copies are allowed, glyrex does not return an empty
match: [Gal]* on a glycan without Gal does not produce a
successful zero-residue result. Unbounded repetition is limited by the
residues in the input glycan.
Check neighbours without including them (advanced)
Sometimes you want to extract Gal only when it has particular neighbours. A lookaround assertion checks a neighbouring path without adding those neighbours to the match. “Ahead” means towards the reducing end; “behind” means towards the non-reducing side.
| Pattern | Condition; only Gal is included |
|---|---|
Gal-(?=GlcNAc) |
The next residue towards the reducing end is GlcNAc |
Gal-(?!GlcNAc) |
That next residue is not GlcNAc, or there is none |
(?<=Sia-)Gal |
A Sia is directly attached on the non-reducing side |
(?<!Sia-)Gal |
No Sia is directly attached on the non-reducing side |
x <- "Neu5Ac(a2-3)Gal(b1-4)GlcNAc(a1-"
rex_extract(x, "Sia-Gal-GlcNAc")
#> [1] "Neu5Ac(a2-3)Gal(b1-4)GlcNAc(a1-"
rex_extract(x, "(?<=Sia-)Gal-(?=GlcNAc)")
#> [1] "Gal(b1-"
rex_detect(x, "(?<!Sia-)Gal")
#> [1] FALSEThe first extraction includes all three residues; the second returns
Gal only. Its output retains the Gal anomer in Gal(b1-. All
paths inside assertions are still written towards the reducing end: do
not reverse the order of a multi-residue path inside a lookbehind.
Unlike branch absence, negative lookbehind considers even residues
already included in the path. On Gal(b1-4)GlcNAc,
Gal-(!Gal-)GlcNAc succeeds (no additional Gal branch), but
Gal-(?<!Gal-)GlcNAc fails (Gal is attached).
A lookbehind immediately before a branch checks that branch. For
example, Gal-(?<!Sia-)(Gal-)GlcNAc requires an
additional Gal branch on GlcNAc whose Gal is not capped directly by Sia.
It does not test whether GlcNAc itself has Sia attached.
Assertions only check conditions. They cannot be repeated, and a
pattern consisting only of an assertion, such as (?=Gal),
returns no match because it includes no residues.
Give part of a match a name (advanced)
A named capture labels a selected part so you can
retrieve it separately. Write (?<name>...), replacing
name with a useful label:
rex_match(
glycans,
"(?<cap>Sia)-(?<unit>Galb4-GlcNAc)"
)
#> match cap
#> lacnac NA NA
#> sialylated "Neu5Ac(a2-3)Gal(b1-4)GlcNAc(b1-" "Neu5Ac(a2-"
#> fucosylated NA NA
#> both "Neu5Ac(a2-3)Gal(b1-4)GlcNAc(b1-" "Neu5Ac(a2-"
#> glcnac NA NA
#> unit
#> lacnac NA
#> sialylated "Gal(b1-4)GlcNAc(b1-"
#> fucosylated NA
#> both "Gal(b1-4)GlcNAc(b1-"
#> glcnac NAThe match column contains the full match;
cap and unit contain the labelled parts.
Inputs without a match have missing values. Captures include residues in
the full match, whereas lookarounds only test their presence. Ordinary
branch parentheses do not create a named column.
Names must start with a letter and contain only letters, digits, or
underscores. Use each name once, and do not use the reserved name
match. Use rex_match_all() to retrieve all
retained matches and their captures.
Build and check your own pattern
Start with the required residues, then add one condition at a time:
| Step | Pattern | Added requirement |
|---|---|---|
| 1 | Gal-GlcNAc |
Adjacent Gal and GlcNAc |
| 2 | Galb4-GlcNAc |
β1–4 linkage |
| 3 | Sia-Galb4-GlcNAc |
Sialic acid on Gal |
| 4 | ^Sia-Galb4-GlcNAc |
That sialic acid is terminal |
| 5 | ^Sia-Galb4-(!Fuc-)GlcNAc |
No remaining Fuc branch on that GlcNAc |
pattern <- rex_compile("^Sia-Galb4-(!Fuc-)GlcNAc")
rex_subset(glycans, pattern)
#> sialylated
#> "Neu5Ac(a2-3)Gal(b1-4)GlcNAc(b1-"rex_compile() checks the syntax and prepares a reusable
pattern. It does not establish that the pattern asks the biological
question you intended. Try known positive and negative structures, and
inspect extracted matches.
| Common mistake | Use instead |
|---|---|
IUPAC linkage in a pattern:
Gal(b1-4)GlcNAc
|
Galb4-GlcNAc |
Attachment dash outside branch:
Gal-(Fuc)-GlcNAc
|
Gal-(Fuc-)GlcNAc |
Ordinary parentheses for a repeated path:
(Gal)+
|
[Gal]+ |
Bare alternatives: Gal|Man
|
[Gal|Man] |
Bare negation: !Fuc or
[!Fuc]
|
[^Fuc] for a non-Fuc residue;
(!Fuc-) for branch absence |
Using . to skip an arbitrary path |
[.]* for zero or more connected
residues |
Interpret the results in structural terms
-
rex_detect()asks whether a match exists;rex_subset()keeps matching inputs. Missing inputs giveNAin detection, and are omitted by subsetting. -
rex_extract()returns one retained match;rex_extract_all()returns all retained matches. Extracted strings use standardized IUPAC notation. -
rex_count()counts retained structural matches, not glycan abundance. glyrex keeps the first successful match per starting residue, then removes matches wholly contained in larger matches. Remaining matches may overlap; it does not enumerate every possible combination of alternatives or branches. - Matches are ordered by decreasing residue count, then starting residue ID. The first result is not necessarily the leftmost text in the input string.
-
rex_locate()andrex_locate_all()report graph residue and edge IDs, not character positions in a string.
The input must describe a supported connected structure. Composition
alone cannot answer connectivity questions, and floating parts or
unspecified branch placement are not supported. Invalid structures
produce errors rather than FALSE. Unspecified pattern
fields accept unknown input information, but specified constraints
require input information at least as specific.
See ?rex_compile for compiling reusable patterns and
?rex_detect for query functions and result conventions.