# `nupp.peg`
`nupp.peg` compiles textual parsing-expression grammars into reusable typed
matchers. The same grammar language works inside
[`comptime`](../../../learn/language/comptime/index.html) and at runtime.
```nupp:playground
const Identifier = comptime do
return nupp.peg.compile("[a-zA-Z_] [a-zA-Z_0-9]* !.")
end
assert(Identifier("item_2") == 7)
assert(Identifier("2_items") == nil)
```
A parsing-expression grammar describes a deterministic top-down parse. Its
choice operator is ordered, so `p / q` tries `q` only if `p` fails, where a
regular expression alternation may pick whichever arm makes the complete
expression work.
All positions and classes are byte-oriented. The grammar does not decode UTF-8
codepoints: a literal UTF-8 character matches as its encoded byte sequence, and
`.` consumes one byte rather than one Unicode character.
## Compiling at either phase
`nupp.peg.compile(source, options?)` is the only constructor. A constant call
inside `comptime` is parsed and validated by the compiler, which derives
`nupp.peg.Peg` from the validated capture shape:
```nupp
const Word = comptime do
return nupp.peg.compile("{ [a-z]+ } !.")
end
```
Recognizers, substring and position captures, and collections therefore need no
result annotation. The emitted program receives an already-materialized matcher
and does not carry the textual grammar parser unless some runtime call also
needs it.
A normal call with literal grammar text gets the same inferred `Peg`. It
still compiles at run time, though parsed plans are cached by grammar source and
backend, so compiling the same grammar again avoids parsing, lowering and
code generation. A genuinely dynamic `string` answers `Peg<...any>`, because its
capture shape is not yet known:
```nupp
local function loadMatcher(configuration: string): nupp.peg.Peg<...any>
return nupp.peg.compile(configuration)
end
```
Neither form requires a separately installed LPeg: the resolved native effect
asks the build for a host that supplies LPeg 1.1.
Prefer `comptime` for source-owned constant grammars. Runtime compilation is for
configuration, plugins, user-selected formats, and other genuinely dynamic
input. A bad expression fails during `comptime` for a static grammar and during
`compile` for a runtime one, with a line and byte-column location either way.
Every `Peg` satisfies `nupp.peg.Matcher`, so a generic adapter can
forward the complete result pack without collecting it into another value:
```nupp
local function match(matcher: nupp.peg.Matcher, subject: string): ((R...) | (nil))
return matcher:match(subject)
end
local Word = nupp.peg.compile("{ [a-z]+ }")
local word: string? = match(Word, "hello")
```
## Matching and positions
A matcher can be called directly or through `match`, which mean the same thing:
```nupp
local Word = nupp.peg.compile("{ [a-z]+ }")
local result = Word("hello")
local same = Word:match("hello")
```
Matching begins at byte position 1 unless `init` is supplied. Positions are
1-based, a negative `init` counts from the end like Lua string operations,
positions before 1 clamp to 1, and positions after `#subject + 1` fail. Every
operation on this page that takes an `init` normalizes it that way:
```nupp
local Word = nupp.peg.compile("{ [a-z]+ }")
assert(Word("one two", 5) == "two")
```
A recognizer, meaning a grammar with no captures, answers the byte position
immediately after its match rather than a boolean. Failure answers nil:
```nupp
local Prefix = nupp.peg.compile("'get'")
assert(Prefix("getter") == 4)
assert(Prefix("setter") == nil)
```
### Searching with `find`
Use `find` when the grammar may begin after the starting position. It answers
the first byte, the exclusive next byte, and every grammar result, without
constructing a match record:
```nupp
local Word = nupp.peg.compile("{ [a-z]+ }")
local first, nextPosition, value = Word:find("123 hello")
assert(first == 5 and nextPosition == 10 and value == "hello")
```
For `Peg`, success has the pack `(integer, integer, R...)` and failure has
`(nil, nil)`. The byte range is half-open, `[first, nextPosition)`, so an empty
match has equal positions. Test `first` for success rather than the value,
because a grammar action may successfully answer nil or false. A recognizer's
third result is the same next-byte position `match` would have answered.
### Testing with `isMatch`
Use `isMatch` when only existence matters. It performs the same search and
answers only a boolean:
```nupp
local Digits = nupp.peg.compile("[0-9]+")
assert(Digits:isMatch("room 42"))
assert(not Digits:isMatch("room"))
assert(not Digits:isMatch("42 rooms", 3))
```
The position after the last byte is included in the search, so an empty or end
assertion can match there. Use `match` when the exact starting position is
already known.
## Repeated matching
`forEachMatch` visits non-overlapping matches without constructing match records
or an iterator closure. Its callback receives `first, nextPosition, R...`, the
same values as `find`, and the call answers the number of visits:
```nupp
local Word = nupp.peg.compile("{ [a-z]+ }")
local words: {string} = {}
local count = Word:forEachMatch("one, two, three", function(first: integer, nextPosition: integer, value: string)
words[#words + 1] = value
end)
assert(count == 3)
assert(words[2] == "two")
```
The next search begins at the exclusive end of a consuming match. An empty match
instead advances one byte, so an empty grammar cannot repeatedly report the same
position. The boundary at `#subject + 1` remains eligible and is visited at most
once, which is why `''` visits positions 1, 2 and 3 in a two-byte subject.
Text before `init` is not visited. The visitor's return value is ignored, so
raising is the way to abort a traversal.
## Replacement
`replace` replaces the first match and `replaceAll` replaces every
non-overlapping match. A string replacement is inserted literally, with no
interpretation of `$`, `%`, or capture references:
```nupp
local Digits = nupp.peg.compile("[0-9]+")
assert(Digits:replace("room 42, floor 3", "#") == "room #, floor 3")
assert(Digits:replaceAll("room 42, floor 3", "#") == "room #, floor #")
```
Use a typed callback when the replacement text depends on the match. It receives
the raw positions followed by every grammar result and must answer a string:
```nupp
local Word = nupp.peg.compile("{ [a-z]+ }")
local output = Word:replaceAll("one, two", function(first: integer, nextPosition: integer, value: string): string
return "[" .. tostring(first) .. ":" .. tostring(nextPosition) .. " " .. value:upper() .. "]"
end)
assert(output == "[1:4 ONE], [6:9 TWO]")
```
Neither operation builds match records. A callback can use a substring capture
as above, use another typed grammar result, or slice the original subject with
the reported half-open range. When no match exists the original string is
answered, and `init` leaves the prefix before it unchanged.
Empty matches insert without removing a byte, and `replaceAll` preserves that
byte while advancing under the same progress rule `forEachMatch` uses. An empty
grammar therefore turns `"ab"` into `"-a-b-"` when replacing with `"-"`.
## Expression syntax
| Expression | Meaning |
| --- | --- |
| `'text'` or `"text"` | the exact bytes in `text` |
| `.` | any one byte |
| `[a-z_]` | one byte from a class or range |
| `[^0-9]` | one byte outside a class |
| `%a`, `%d`, `%s`, `%w`, `%x` | ASCII letter, digit, space, alphanumeric, or hex
byte; any other `%name` reads a definition |
| `p q` | `p` followed by `q` |
| `p / q` | ordered choice: try `p`, then `q` |
| `p*`, `p+`, `p?` | zero or more, one or more, or optional `p` |
| `p^4`, `p^+4`, `p^-4` | exactly, at least, or at most four repetitions |
| `&p`, `!p` | require `p`, or require that `p` fails, without consuming input |
| `{ p }` | capture the substring consumed by `p` |
| `{}` | capture the current 1-based byte position |
| `{: name: p :}` | group captures under `name`; omit `name:` for an anonymous group
|
| `{~ p ~}` | substitute captured text into the substring consumed by `p` |
| `=name` | match the text previously captured by named group `name` |
| `p -> {}` | collect `p`'s captures in a table |
| `p -> n`, `p -> 'text'` | select capture `n`, or format captures into text |
| `p -> name` | transform `p` through definition `name` |
| `p => name` | invoke match-time definition `name` |
| `p >> name`, `p ~> name` | accumulate captures, or fold them left |
| `name <- p` | define a grammar rule; the first rule is the start rule |
| `name` or `` | refer to a rule inside a grammar |
| `!.` | require end of input |
One row is missing above, because a cell cannot hold its delimiters: `{| p |}`
collects every capture produced by `p` into one array.
Whitespace between expressions is ignored, and a `--` comment outside a quoted
literal or byte class continues to the end of the line. Precedence runs from
tightest to loosest:
1. primary expressions such as literals, classes, captures, and groups;
2. repetition and capture-transformation suffixes;
3. predicates;
4. sequence;
5. ordered choice `/`.
Parentheses can make any grouping explicit.
### Literals and any byte
Single-quoted and double-quoted literals match their contents exactly:
```npeg
'GET'
"Content-Type"
```
The notation does not process backslash escapes, so a backslash in a literal is
a literal backslash byte. Use the other quote delimiter when the text contains
one kind of quote:
```npeg
"it's"
'say "yes"'
```
An empty literal `''` succeeds without consuming input. It is occasionally
useful in a choice, but it must not appear inside `*` or `+`, because such a
loop could never advance.
`.` matches any one byte and fails at the end of the subject.
### Byte classes
Square brackets match one byte from a set, and ranges are inclusive:
```npeg
[abc]
[a-zA-Z_]
[0-9a-fA-F]
```
`^` immediately after `[` complements the class:
```npeg
[^0-9]
```
The predefined ASCII classes are:
| Short | Long | Bytes |
| --- | --- | --- |
| `%a` | `%alpha` | ASCII letters |
| `%c` | `%cntrl` | control bytes and DEL |
| `%d` | `%digit` | decimal digits |
| `%g` | `%graph` | printable non-space ASCII bytes |
| `%l` | `%lower` | lowercase ASCII letters |
| `%nl` | | newline |
| `%p` | `%punct` | ASCII punctuation |
| `%s` | `%space` | ASCII whitespace |
| `%u` | `%upper` | uppercase ASCII letters |
| `%w` | `%alnum` | ASCII letters and digits |
| `%x` | `%xdigit` | hexadecimal digits |
The one-letter uppercase forms `%A`, `%C`, `%D`, `%G`, `%L`, `%P`, `%S`, `%U`,
`%W`, and `%X` match the complement of their lowercase class. Predefined classes
can also appear inside square brackets:
```npeg
[%a_]
[%w.-]
```
Class contents are literal bytes except for ranges and `%` classes. An empty
class and a descending range such as `[z-a]` are errors.
### Sequence
Adjacent expressions form a sequence and must match in order:
```npeg
'HTTP/' [0-9] '.' [0-9]
```
Spacing is optional where token boundaries stay clear, and whitespace usually
makes a grammar easier to read.
### Ordered choice
`p / q` tries `p` first and uses `q` only when `p` fails:
```npeg
'GET' / 'POST' / 'PUT'
```
Put a longer literal before its prefix. With `'in' / 'integer'`, the first arm
succeeds after two bytes and the second arm is never considered. Write
`'integer' / 'in'` instead, or add a boundary assertion to each arm.
PEG backtracking is local and deterministic. If a later expression fails, the
parser can return to a still-open choice and try its next arm. Ordinary `->`
transformations are deferred until the entire match succeeds, so speculative
paths do not run them. Match-time `=>` definitions are immediate, as in LPeg.
### Repetition
Suffix operators repeat the expression immediately to their left:
| Form | Meaning |
| --- | --- |
| `p?` | zero or one |
| `p*` | zero or more |
| `p+` | one or more |
| `p^4` | exactly four |
| `p^+4` | at least four |
| `p^-4` | at most four |
Use parentheses to repeat a sequence:
```npeg
[0-9]+ ('.' [0-9]+)?
```
Repetition is possessive in PEG fashion: it consumes as much as it can and does
not backtrack to a smaller count merely to make a following expression work. A
repeated expression must consume at least one byte whenever it succeeds, so
nullable repetition such as `('')*` is rejected rather than allowed to loop
forever. Explicit repetition counts are limited to 4096.
### Predicates and end of input
`&p` succeeds when `p` would succeed and consumes nothing. `!p` succeeds when
`p` would fail and also consumes nothing.
```npeg
&[a-z] [a-z]+
!('if' !.) [a-z]+ !.
```
The first expression requires a lowercase next byte before consuming a word. The
second rejects the complete keyword `if` while still accepting identifiers
beginning with those letters, such as `iffy`.
`!.` is the standard end-of-input assertion: it succeeds only when `.` cannot
consume another byte. Append it to require a complete match, since without it a
prefix match succeeds:
```nupp
local Whole = nupp.peg.compile("'get' !.")
assert(Whole("get") == 4)
assert(Whole("getter") == nil)
```
### Captures and result types
`{ p }` captures the substring consumed by `p`, `{}` captures the current byte
position without consuming input, and `{| p |}` collects every capture produced
by `p` into one table:
```nupp
const Name = comptime do
return nupp.peg.compile("{ [a-z]+ } !.")
end
const Start = comptime do
return nupp.peg.compile("{} [a-z]+ !.")
end
const Fields = comptime do
return nupp.peg.compile("{| { [a-z]+ } (',' { [a-z]+ })* |} !.")
end
```
Adjacent captures are adjacent native Lua results. The following grammar is
inferred as `Peg<(string, integer)>` and answers two values with no tuple or
table allocation:
```nupp
local Field = nupp.peg.compile("{ [a-z]+ } ':' {}")
local name, nextPosition = Field("size:")
```
The parentheses in `Peg<(string, integer)>` delimit one explicit type-pack
argument; they do not construct a tuple type or a runtime tuple value. The
compiler usually infers that pack, so the annotation is needed only at an API
boundary.
`{| ... |}` and `p -> {}` remain explicit table captures. Use one where the
grammar semantically produces a collection, especially around capture-producing
repetition, so the table allocation comes from the grammar rather than from the
matcher API.
Every ordered-choice arm must produce the same capture shape, which is what
keeps the inferred `Peg` result pack true whichever arm matches.
### Groups, substitution, and back captures
`{: name: p :}` groups the captures made by `p` under `name`. Inside a table
capture, that group becomes a named field. Leave out `name:` to make an
anonymous group. `=name` matches the exact string stored by an earlier named
group:
```nupp
local Pair = nupp.peg.compile("{| {: key: { [a-z]+ } :} '=' {: value: { [0-9]+ } :} |} !.")
local fields = assert(Pair("size=42"))
assert(fields.key == "size" and fields.value == "42")
local Repeated = nupp.peg.compile("{: word: { [a-z]+ } :} ':' =word !.")
assert(Repeated("same:same") == "same")
assert(Repeated("same:other") == nil)
```
`{~ p ~}` is LPeg's substitution capture. It answers the complete substring
consumed by `p`, replacing each captured range inside it by that capture's
value:
```nupp
local Normalize = nupp.peg.compile("{~ ({ [0-9]+ } -> '[%0]' / .)* ~} !.")
assert(Normalize("a12b") == "a[12]b")
```
### Transformations and definitions
The suffix `p -> {}` collects `p`'s captures into a table. `p -> n` selects
capture number `n`, and zero suppresses all captures. `p -> 'format'` uses
LPeg's capture format, where `%0` is the whole text consumed by `p`, `%1`
through `%9` select captures, and `%%` writes a percent sign.
`p -> name` applies the value named by `name`. A function receives `p`'s
captures, or the complete matched substring when `p` has no explicit capture.
LPeg-compatible string, number, and table transformations are accepted too.
For a runtime grammar, pass named values in `CompileOptions.definitions`:
```nupp
local Integer = nupp.peg.compile("[0-9]+ -> integer !.", {definitions = {integer = function(text: string): integer
return assert(tonumber(text)) as integer
end,},})
assert(Integer("42") == 42)
```
Here `Integer` is inferred as `nupp.peg.Peg` from the transformation's
declared return pack. A transformation may answer several values, and they are
spliced into the grammar's surrounding result pack. If either the grammar or the
definitions table is dynamic, annotate or cast the result where the application
has the missing knowledge.
For a static grammar, declare a factory whose parameter is a closed record
containing exactly the named callbacks:
```nupp
local record Definitions
integer: function(string): integer
end
const IntegerFactory: function(Definitions): nupp.peg.Peg = comptime do
return nupp.peg.compile("[0-9]+ -> integer !.")
end
local Integer = IntegerFactory(new Definitions(integer = function(text: string): integer
return assert(tonumber(text)) as integer
end))
```
Every named slot is required and extra slots are rejected for static factories.
`CompileOptions.actions` remains a deprecated alias for older Nupp grammars.
::: deepdive Static grammars materialize as factories
A `comptime` value cannot close over a runtime function, so a static grammar
that names transformations cannot carry them. Materializing it as a factory
keeps the grammar itself compile-time work while the callbacks are supplied
where they exist, and typing the parameter as a closed record is what makes a
missing or misspelled slot a compile error rather than a nil call during a
parse.
:::
The other LPeg `re` definition operators retain their distinct meanings:
- `%name` uses a supplied value as a pattern. Strings match literally,
non-negative integers match that many bytes, and booleans always succeed or
fail.
- `p => name` invokes a match-time function with the subject, current byte
position, and captures. It answers a new position followed by replacement
captures, or nil to fail. Because it participates in parsing, backtracking may
invoke it speculatively.
- `p >> name` combines the previous capture with `p`'s capture.
- `p ~> name` folds `p`'s captures from left to right.
### Rules and recursion
A source beginning with `name <-` is a grammar made of rule definitions, and the
first rule is the start rule:
```npeg
start <- value !.
value <- 'x' / '(' value ')'
```
Refer to a rule as `name` or ``. Angle brackets are useful where adjoining
text would make the boundary unclear.
Rules may recurse after consuming input. Direct and indirect left recursion are
rejected, because a top-down PEG cannot enter a rule again at the same position:
```npeg
-- Invalid: value calls itself before consuming anything.
value <- value ',' item / item
```
Rewrite a left-recursive list as a head followed by repetition:
```npeg
value <- item (',' item)*
```
Every reference must resolve, rule names must be unique, and expression nesting
is limited to 256 levels.
## Backends
`CompileOptions.backend` accepts `"auto"` or `"lpeg"`. `auto` is the default:
every static grammar becomes a validated canonical PEG graph, Nupp emits
straight-line Lua for the few shapes it recognizes, namely fixed-width matches,
repeated bytes and packed whole-input scans, and every other graph lowers
directly to native LPeg patterns.
```nupp
local Fast = nupp.peg.compile("[a-z]+ !.")
local General = nupp.peg.compile("[a-z]+ !.", {backend = "lpeg"})
```
`lpeg` disables Nupp's straight-line specializations and always lowers the graph
to native LPeg, which is what makes a backend comparison possible. There is no
Nupp PEG bytecode and no general-purpose interpreter.
Runtime textual grammars are compiled by LPeg's `re` module and cached by
source. They do not invoke `loadstring`, and `auto` invokes it only when a
static graph selects a Nupp specialization. A repeated byte or class plan also
emits a direct byte-scanning `forEachMatch` loop, so a traversal does not
re-enter LPeg for every match; a typed replacement callback keeps the general
search loop.
The expression syntax is LPeg 1.1 `re` syntax: the same operators have the same
parsing and capture meanings, and the test suite runs the official `re` module
as a differential oracle. Direct `require("lpeg")` answers the native LPeg 1.1
module, and `require("re")` answers the bundled official Lua frontend over it.
Nupp's declaration and operator checking track capture packs through ordinary
LPeg composition, but the runtime object stays LPeg's pattern userdata.
::: deepdive Nupp owns the static representation
LPeg pattern userdata exposes no public traversable AST, so a compiler holding
one cannot recover capture types or optimization facts from it. Nupp therefore
parses static `nupp.peg` text into its own canonical typed graph, derives the
`R...` result pack there, and then either emits a selected kernel or constructs
the equivalent LPeg pattern. The graph is a type-system and optimization layer
rather than a second parsing machine, which is what keeps one matching engine
answering for both backends.
:::
::: seealso
- [Comptime](../../../learn/language/comptime/index.html) for what a `comptime do` block may
compute and how its result reaches the program
- [Diagnostics](../../../reference/diagnostics/index.html#diagnostic-index) for the codes
a rejected grammar reports
:::
## Types
### `Action` _type_
```nupp
type Action = function(string): any
```
A legacy substring transformation callback.
Prefer `Definitions`: LPeg `re` gives `->`, `=>`, `>>`, and `~>` distinct
callback contracts. This alias remains for source compatibility with Nupp's
earlier action-only grammar surface.
### `Actions` _type_
```nupp
type Actions = {[string]: Action}
```
Legacy runtime transformation callbacks indexed by grammar name.
Prefer `Definitions`. Every grammar slot must be present and unknown names are
rejected. Static grammars use a precisely typed factory record instead.
### `Backend` _type_
```nupp
type Backend = 'auto' | 'lpeg'
```
The implementation selected after the grammar has been parsed.
`auto` is the default. It emits straight-line Lua for the small matcher shapes
where Nupp is faster and lowers every other grammar to native LPeg. `lpeg`
disables specialization and always uses LPeg. Both paths share one typed
matcher shell and identical capture semantics.
### `CompileOptions` _type_
```nupp
type CompileOptions = {
--- Values named by `%name`, `-> name`, `=> name`, `>> name`, or `~> name`.
definitions: Definitions?,
--- Deprecated alias for `definitions` retained for existing Nupp grammars.
actions: Actions?,
--- Matcher implementation, `auto` when omitted.
backend: Backend?
}
```
Controls grammar compilation.
These options are deliberately small. At runtime, grammar source plus
`backend` selects specialization or native LPeg; definitions are bound to the
returned matcher and are not part of the grammar's compile-time type.
### `Definitions` _type_
```nupp
type Definitions = {[string]: any}
```
Values referenced by LPeg `re` expressions.
A `%name` primary uses a string, non-negative byte count, or boolean as a
pattern. `p -> name` accepts the same function, table, string, or capture
number transformations as LPeg. `=>`, `>>`, and `~>` require functions with
their corresponding match-time, accumulator, and fold contracts.
### `Matcher` _interface_
```nupp
interface Matcher
match: function(self, subject: string, init: integer?): ((R...) | (nil))
find: function(self, subject: string, init: integer?): ((integer, integer, R...) | (nil, nil))
isMatch: function(self, subject: string, init: integer?): boolean
forEachMatch: function(
self,
subject: string,
visitor: function(first: integer, nextPosition: integer, R...),
init: integer?
): integer
replace: function(
self,
subject: string,
replacement: string | function(first: integer, nextPosition: integer, R...): string,
init: integer?
): string
replaceAll: function(
self,
subject: string,
replacement: string | function(first: integer, nextPosition: integer, R...): string,
init: integer?
): string
metamethod __call: function(self, subject: string, init: integer?): ((R...) | (nil))
end
```
A compiled matcher whose result pack is chosen by its declaration.
Generic adapters forward `R...` without collecting it into a table or tuple.
#### Type parameters
| Name | Description |
| --- | --- |
| `R` | |
#### Methods
##### `match`
```nupp
match: function(self, subject: string, init: integer?): ((R...) | (nil))
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
| `subject` | `string` | |
| `init` | `integer?` | |
##### `find`
```nupp
find: function(self, subject: string, init: integer?): ((integer, integer, R...) | (nil, nil))
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
| `subject` | `string` | |
| `init` | `integer?` | |
##### `isMatch`
```nupp
isMatch: function(self, subject: string, init: integer?): boolean
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
| `subject` | `string` | |
| `init` | `integer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `forEachMatch`
```nupp
forEachMatch: function(
self,
subject: string,
visitor: function(first: integer, nextPosition: integer, R...),
init: integer?
): integer
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
| `subject` | `string` | |
| `visitor` | `function(first: integer, nextPosition: integer, R...)` | |
| `init` | `integer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `integer` | |
##### `replace`
```nupp
replace: function(
self,
subject: string,
replacement: string | function(first: integer, nextPosition: integer, R...): string,
init: integer?
): string
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
| `subject` | `string` | |
| `replacement` | `string | function(first: integer, nextPosition: integer, R...): string` | |
| `init` | `integer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `string` | |
##### `replaceAll`
```nupp
replaceAll: function(
self,
subject: string,
replacement: string | function(first: integer, nextPosition: integer, R...): string,
init: integer?
): string
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
| `subject` | `string` | |
| `replacement` | `string | function(first: integer, nextPosition: integer, R...): string` | |
| `init` | `integer?` | |
###### Returns
| Type | Description |
| --- | --- |
| `string` | |
##### `__call`
```nupp
__call: function(self, subject: string, init: integer?): ((R...) | (nil))
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
| `subject` | `string` | |
| `init` | `integer?` | |
### `Peg` _record_
```nupp
record Peg is Matcher
match: function(self, subject: string, init: integer?): ((R...) | (nil))
find: function(self, subject: string, init: integer?): ((integer, integer, R...) | (nil, nil))
isMatch: function(self, subject: string, init: integer?): boolean
forEachMatch: function(
self,
subject: string,
visitor: function(first: integer, nextPosition: integer, R...),
init: integer?
): integer
replace: function(
self,
subject: string,
replacement: string | function(first: integer, nextPosition: integer, R...): string,
init: integer?
): string
replaceAll: function(
self,
subject: string,
replacement: string | function(first: integer, nextPosition: integer, R...): string,
init: integer?
): string
metamethod __call: function(self, subject: string, init: integer?): ((R...) | (nil))
end
```
A compiled and reusable parsing-expression grammar.
`R...` is the grammar's native Lua result pack. A recognizer or `{}` position
capture contributes `integer`, `{ p }` contributes `string`, and adjacent
captures contribute adjacent results. No tuple or table is allocated merely
because a grammar returns several values. An explicit `{| ... |}` table
capture still returns one table because the grammar requested one.
Matchers are immutable and callable. `peg(subject, init)` is exactly
`peg:match(subject, init)`.
#### Type parameters
| Name | Description |
| --- | --- |
| `R` | |
#### Methods
##### `match`
```nupp
match: function(self, subject: string, init: integer?): ((R...) | (nil))
```
Matches `subject` beginning at a 1-based byte position.
The default `init` is 1. A negative position counts from the end in the
same way as Lua string operations; positions before the beginning clamp to
1, and positions after `#subject + 1` fail. Success returns the grammar's
capture or, for a recognizer, the next byte position. Failure returns nil.
```nupp
local Word = nupp.peg.compile("{ [a-z]+ }")
assert(Word:match("one two", 5) == "two")
assert(Word("123") == nil)
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
| `subject` | `string` | bytes to match |
| `init` | `integer?` | 1-based starting byte position, 1 when omitted |
##### `find`
```nupp
find: function(self, subject: string, init: integer?): ((integer, integer, R...) | (nil, nil))
```
Finds the first match at or after `init` without allocating match metadata.
Success returns `first, next, R...`. The byte range is half-open:
`[first, next)`, so an empty match has `first == next`. The trailing
values are the ordinary grammar results; for a recognizer the result is the
same next-byte
position. Failure returns nil positions. Test `first`, rather than
`value`, because an action may successfully return nil or false.
```nupp
local Word = nupp.peg.compile("{ [a-z]+ }")
local first, nextPosition, value = Word:find("123 hello")
assert(first == 5 and nextPosition == 10 and value == "hello")
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
| `subject` | `string` | bytes to search |
| `init` | `integer?` | 1-based first byte position to try, 1 when omitted |
##### `isMatch`
```nupp
isMatch: function(self, subject: string, init: integer?): boolean
```
Reports whether the grammar matches anywhere at or after `init`.
Unlike `match`, this searches successive 1-based byte positions. The
default `init` is 1, and negative and out-of-range positions follow the
same rules as `match`. The position after the final byte is searched too,
so a grammar that accepts an empty suffix can match there.
```nupp
local Digits = nupp.peg.compile("[0-9]+")
assert(Digits:isMatch("room 42"))
assert(not Digits:isMatch("room", 2))
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
| `subject` | `string` | bytes to search |
| `init` | `integer?` | 1-based first byte position to try, 1 when omitted |
###### Returns
| Type | Description |
| --- | --- |
| `boolean` | |
##### `forEachMatch`
```nupp
forEachMatch: function(
self,
subject: string,
visitor: function(first: integer, nextPosition: integer, R...),
init: integer?
): integer
```
Visits every non-overlapping match at or after `init` without allocating
match records or an iterator closure.
The visitor receives `first, next, R...` in the same form as `find`.
Consuming matches resume at `next`; an empty match resumes one byte after
`first`, preventing an empty grammar from stalling. The position after the
final byte is still visited when it matches. Returning from the visitor
does not stop iteration.
```nupp
local Word = nupp.peg.compile("{ [a-z]+ }")
local seen: {string} = {}
local count = Word:forEachMatch("one, two", function(_, _, word: string)
seen[#seen + 1] = word
end)
assert(count == 2 and seen[2] == "two")
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
| `subject` | `string` | bytes to search |
| `visitor` | `function(first: integer, nextPosition: integer, R...)` | called once for each non-overlapping match |
| `init` | `integer?` | 1-based first byte position to try, 1 when omitted |
###### Returns
| Type | Description |
| --- | --- |
| `integer` | the number of matches visited |
##### `replace`
```nupp
replace: function(
self,
subject: string,
replacement: string | function(first: integer, nextPosition: integer, R...): string,
init: integer?
): string
```
Replaces the first match at or after `init`.
A string replacement is literal. A callback receives `first, next, R...`
and returns the replacement bytes. When nothing matches, the original
string is returned. Empty matches insert without consuming a byte.
```nupp
local Digits = nupp.peg.compile("[0-9]+")
assert(Digits:replace("room 42, floor 3", "#") ==
"room #, floor 3")
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
| `subject` | `string` | bytes to search and copy |
| `replacement` | `string | function(first: integer, nextPosition: integer, R...): string` | literal bytes or a typed replacement callback |
| `init` | `integer?` | 1-based first byte position to try, 1 when omitted |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the replaced string |
##### `replaceAll`
```nupp
replaceAll: function(
self,
subject: string,
replacement: string | function(first: integer, nextPosition: integer, R...): string,
init: integer?
): string
```
Replaces every non-overlapping match at or after `init`.
Matching resumes at the exclusive end of each consuming match. After an
empty match it advances one byte and preserves that skipped byte in the
output, so an empty grammar inserts before every remaining byte and once at
the end. Text before `init` is copied unchanged.
```nupp
local Digits = nupp.peg.compile("[0-9]+")
assert(Digits:replaceAll("room 42, floor 3", "#") ==
"room #, floor #")
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
| `subject` | `string` | bytes to search and copy |
| `replacement` | `string | function(first: integer, nextPosition: integer, R...): string` | literal bytes or a typed replacement callback |
| `init` | `integer?` | 1-based first byte position to try, 1 when omitted |
###### Returns
| Type | Description |
| --- | --- |
| `string` | the replaced string |
##### `__call`
```nupp
__call: function(self, subject: string, init: integer?): ((R...) | (nil))
```
###### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `?` | `self` | |
| `subject` | `string` | |
| `init` | `integer?` | |
## Functions
### `compile` _function_
```nupp
local compile: function(source: string, options: CompileOptions?): Peg<...any>
```
Compiles an LPeg-re-style byte grammar at compile time or runtime.
A literal grammar produces a precise `Peg` at either phase for ordinary
recognition and captures. A dynamic grammar string returns `Peg<...any>`. A
static grammar referring to definitions needs an explicitly typed factory,
because runtime values cannot be captured at `comptime`.
```nupp
local Words = nupp.peg.compile(
"{| { [a-z]+ } (',' { [a-z]+ })* |} !."
)
local values = assert(Words("red,green,blue")) as {string}
assert(values[2] == "green")
local General = nupp.peg.compile("[0-9]+ !.", {backend = "lpeg"})
assert(General("123") ~= nil)
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `source` | `string` | grammar expression or rule definitions |
| `options` | `CompileOptions?` | runtime definitions and backend selection |
#### Returns
| Type | Description |
| --- | --- |
| `Peg\<...any\>` | the compiled reusable matcher |