Модуль:Re
| ||
---|---|---|
Тематические статьи | ||
Техническая справка | ||
Тэги MediaWiki: | ||
Общие правила | ||
Эта страница документации воспроизведена по источнику с изменениями, обусловленными спецификой вики.
The re
Module
The re
module
(provided by file re.lua
in the distribution)
supports a somewhat conventional regex syntax
for pattern usage within LPeg.
The next table summarizes re
's syntax.
A p
represents an arbitrary pattern;
num
represents a number ([0-9]+
);
name
represents an identifier
([a-zA-Z][a-zA-Z0-9_]*
).
Constructions are listed in order of decreasing precedence.
Syntax | Description |
---|---|
( p ) |
grouping |
'string' |
literal string |
"string" |
literal string |
[class] |
character class |
. |
any character |
%name |
pattern defs[name] or a pre-defined pattern
|
name |
non terminal |
<name> |
non terminal |
{} |
position capture |
{` value `} |
constant capture[o 1] |
{# arg #} |
argument capture[o 1] |
{ p } |
simple capture |
{: p :} |
anonymous group capture |
{:name: p :} |
named group capture |
{~ p ~} |
substitution capture |
{| p |} |
table capture |
=name |
back reference |
p ? |
optional match |
p * |
zero or more repetitions |
p + |
one or more repetitions |
p^num |
exactly n repetitions
|
p^+num |
at least n repetitions
|
p^-num |
at most n repetitions
|
p -> 'string' |
string capture |
p -> "string" |
string capture |
p -> num |
numbered capture |
p -> name |
function/query/string capture equivalent to p / defs[name]
|
p ~> name |
fold[o 1] |
p => name |
match-time capture equivalent to lpeg.Cmt(p, defs[name])
|
& p |
and predicate |
< p |
back assertion[o 1] |
! p |
not predicate |
p1 p2 |
concatenation |
p1 / p2 |
ordered choice |
(name <- p )+ |
grammar |
Any space appearing in a syntax description can be
replaced by zero or more space characters and Lua-style comments
(--
until end of line).
Character classes define sets of characters.
An initial ^
complements the resulting set.
A range x-y
includes in the set
all characters with codes between the codes of x and y.
A pre-defined class %
name includes all
characters of that class.
A simple character includes itself in the set.
The only special characters inside a class are ^
(special only if it is the first character);
]
(can be included in the set as the first character,
after the optional ^
);
%
(special only if followed by a letter);
and -
(can be included in the set as the first or the last character).
Currently the pre-defined classes are similar to those from the
Lua’s string library
(%a
for letters,
%A
for non letters, etc.).
There is also a class %nl
containing only the newline character,
which is particularly handy for grammars written inside long strings,
as long strings do not interpret escape sequences like \n
.
Functions
re.compile (string, [, defs])
Compiles the given string and
returns an equivalent LPeg pattern.
The given string may define either an expression or a grammar.
The optional defs
table provides extra Lua values
to be used by the pattern.
re.find (subject, pattern [, init])
Searches the given pattern in the given subject. If it finds a match, returns the index where this occurrence starts and the index where it ends. Otherwise, returns nil.
An optional numeric argument init
makes the search
starts at that position in the subject string.
As usual in Lua libraries,
a negative value counts from the end.
re.gsub (subject, pattern, replacement)
Does a global substitution,
replacing all occurrences of pattern
in the given subject
by replacement
.
re.match (subject, pattern)
Matches the given pattern against the given subject, returning all captures.
re.updatelocale ()
Updates the pre-defined character classes to the current locale.
Some Examples
A complete simple program
The next code shows a simple complete Lua program using
the re
module:
local re = require ("Модуль:re")
-- find the position of the first numeral in a string
print(re.find("the number 423 is odd", "[0-9]+")) --> 12 14
-- returns all words in a string
print(re.match("the number 423 is odd", "({%a+} / .)*")) --> the number is odd
-- returns the first numeral in a string
print(re.match("the number 423 is odd", "s <- {%d+} / . s")) --> 423
print(re.gsub("hello World", "[aeiou]", ".")) --> h.ll. W.rld
Balanced parentheses
The following call will produce the same pattern produced by the Lua expression in the balanced parentheses example:
b = re.compile[[ balanced <- "(" ([^()] / balanced)* ")" ]]
String reversal
The next example reverses a string:
rev = re.compile[[ R <- (!.) -> '' / ({.} R) -> '%2%1']]
print(rev:match"0123456789") --> 9876543210
CSV decoder
The next example replicates the CSV decoder:
record = re.compile[[
record <- {| field (',' field)* |} (%nl / !.)
field <- escaped / nonescaped
nonescaped <- { [^,"%nl]* }
escaped <- '"' {~ ([^"] / '""' -> '"')* ~} '"'
]]
Lua’s long strings
The next example matches Lua long strings:
c = re.compile([[
longstring <- ('[' {:eq: '='* :} '[' close)
close <- ']' =eq ']' / . close
]])
print(c:match'[==[]]===]]]]==]===[]') --> 17
Abstract Syntax Trees
This example shows a simple way to build an abstract syntax tree (AST) for a given grammar. To keep our example simple, let us consider the following grammar for lists of names:
p = re.compile[[
listname <- (name s)*
name <- [a-z][a-z]*
s <- %s*
]]
Now, we will add captures to build a corresponding AST. As a first step, the pattern will build a table to represent each non terminal; terminals will be represented by their corresponding strings:
c = re.compile[[
listname <- {| (name s)* |}
name <- {| {[a-z][a-z]*} |}
s <- %s*
]]
Now, a match against "hi hello bye"
results in the table
{{"hi"}, {"hello"}, {"bye"}}
.
For such a simple grammar,
this AST is more than enough;
actually, the tables around each single name
are already overkilling.
More complex grammars,
however, may need some more structure.
Specifically,
it would be useful if each table had
a tag
field telling what non terminal
that table represents.
We can add such a tag using
named group captures:
x = re.compile[[
listname <- {| {:tag: '' -> 'list':} (name s)* |}
name <- {| {:tag: '' -> 'id':} {[a-z][a-z]*} |}
s <- ' '*
]]
With these group captures,
a match against "hi hello bye"
results in the following table:
{tag="list",
{tag="id", "hi"},
{tag="id", "hello"},
{tag="id", "bye"}
}
Indented blocks
This example breaks indented blocks into tables, respecting the indentation:
p = re.compile[[
block <- {| {:ident:' '*:} line
((=ident !' ' line) / &(=ident ' ') block)* |}
line <- {[^%nl]*} %nl
]]
As an example, consider the following text:
t = p:match[[
first line
subline 1
subline 2
second line
third line
subline 3.1
subline 3.1.1
subline 3.2
]]
The resulting table t
will be like this:
{'first line'; {'subline 1'; 'subline 2'; ident = ' '};
'second line';
'third line'; { 'subline 3.1'; {'subline 3.1.1'; ident = ' '};
'subline 3.2'; ident = ' '};
ident = ''}
Macro expander
This example implements a simple macro expander. Macros must be defined as part of the pattern, following some simple rules:
p = re.compile[[
text <- {~ item* ~}
item <- macro / [^()] / '(' item* ')'
arg <- ' '* {~ (!',' item)* ~}
args <- '(' arg (',' arg)* ')'
-- now we define some macros
macro <- ('apply' args) -> '%1(%2)'
/ ('add' args) -> '%1 + %2'
/ ('mul' args) -> '%1 * %2'
]]
print(p:match"add(mul(a,b), apply(f,x))") --> a * b + f(x)
A text
is a sequence of items,
wherein we apply a substitution capture to expand any macros.
An item
is either a macro,
any character different from parentheses,
or a parenthesized expression.
A macro argument (arg
) is a sequence
of items different from a comma.
(Note that a comma may appear inside an item,
e.g., inside a parenthesized expression.)
Again we do a substitution capture to expand any macro
in the argument before expanding the outer macro.
args
is a list of arguments separated by commas.
Finally we define the macros.
Each macro is a string substitution;
it replaces the macro name and its arguments by its corresponding string,
with each %
n replaced by the n-th argument.
Patterns
This example shows the complete syntax
of patterns accepted by re
.
p = [=[
pattern <- exp !.
exp <- S (alternative / grammar)
alternative <- seq ('/' S seq)*
seq <- prefix*
prefix <- '&' S prefix / '!' S prefix / '<' S prefix / suffix
suffix <- primary S (([+*?]
/ '^' [+-]? num
/ '->' S (string / '{}' / name)
/ '~>' S name
/ '=>' S name) S)*
primary <- '(' exp ')' / string / class / defined
/ '{:' (name ':')? exp ':}'
/ '=' name
/ '{}'
/ '{~' exp '~}'
/ '{' exp '}'
/ '{`' exp '`}'
/ '{#' exp '#}'
/ '.'
/ name S !arrow
/ '<' name '>' -- old-style non terminals
grammar <- definition+
definition <- name S arrow exp
class <- '[' '^'? item (!']' item)* ']'
item <- defined / range / .
range <- . '-' [^]]
S <- (%s / '--' [^%nl]*)* -- spaces and comments
name <- [A-Za-z][A-Za-z0-9_]*
arrow <- '<-'
num <- [0-9]+
string <- '"' [^"]* '"' / "'" [^']* "'"
defined <- '%' name
]=]
print(re.match(p, p)) -- a self description must match itself
License
Copyright © 2008‒2010 Lua.org, PUC-Rio.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the «Software»), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED «AS IS», WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Источник
См. также
--[[
Основано на коде Роберту Иерусалимски:
https://github.com/lua/lpeg/blob/master/re.lua
Дополнено для traditio.wiki Александром Машиным:
* < -- back assertion (lpeg.B),
* ~> -- fold capture (lpeg.Cf),
* {` `} -- constant capture (lpeg.Cc),
* {# #} -- argument capture (lpeg.Carg).
Для работы в MediaWiki, требует подключения lpeg к Lua как глобальной переменной.
--]]
-- $Id: re.lua,v 1.44 2013/03/26 20:11:40 roberto Exp $
-- imported functions and modules
local tonumber, type, print, error = tonumber, type, print, error
local setmetatable = setmetatable
local __found, m = pcall (require, 'lpeg')
-- In MediaWiki, lpeg has to be made available as a global.
m = __found and m or lpeg
-- 'm' will be used to parse expressions, and 'mm' will be used to
-- create expressions; that is, 're' runs on 'm', creating patterns
-- on 'mm'
local mm = m
-- pattern's metatable
local mt = getmetatable(mm.P(0)) or {
-- In MediaWiki, no metatable for userdata.
__unm = function (a) return -lpeg.P(a) end
, __len = function (a) return #lpeg.P(a) end
, __add = function (a, b) return lpeg.P(a) + lpeg.P(b) end
, __mul = function (a, b) return lpeg.P(a) * lpeg.P(b) end
, __div = function (a, b) return lpeg.P(a) / b end
, __pow = function (a, b) return lpeg.P(a) ^ b end
}
-- No more global accesses after this point
local version = _VERSION
if version == "Lua 5.2" then _ENV = nil end
local any = m.P(1)
-- Pre-defined names
local Predef = { nl = m.P"\n" }
local mem
local fmem
local gmem
local function updatelocale ()
mm.locale(Predef)
Predef.a = Predef.alpha
Predef.c = Predef.cntrl
Predef.d = Predef.digit
Predef.g = Predef.graph
Predef.l = Predef.lower
Predef.p = Predef.punct
Predef.s = Predef.space
Predef.u = Predef.upper
Predef.w = Predef.alnum
Predef.x = Predef.xdigit
Predef.A = any - Predef.a
Predef.C = any - Predef.c
Predef.D = any - Predef.d
Predef.G = any - Predef.g
Predef.L = any - Predef.l
Predef.P = any - Predef.p
Predef.S = any - Predef.s
Predef.U = any - Predef.u
Predef.W = any - Predef.w
Predef.X = any - Predef.x
mem = {} -- restart memoization
fmem = {}
gmem = {}
local mt = {__mode = "v"}
setmetatable(mem, mt)
setmetatable(fmem, mt)
setmetatable(gmem, mt)
end
updatelocale()
local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end)
local function getdef (id, defs)
local c = defs and defs[id]
if not c then error("undefined name: " .. id) end
return c
end
local function patt_error (s, i)
local msg = (#s < i + 20) and s:sub(i)
or s:sub(i,i+20) .. "..."
msg = ("pattern error near '%s'"):format(msg)
error(msg, 2)
end
local function mult (p, n)
local np = mm.P(true)
while n >= 1 do
if n%2 >= 1 then np = np * p end
p = p * p
n = n/2
end
return np
end
local function equalcap (s, i, c)
if type(c) ~= "string" then return nil end
local e = #c + i
if s:sub(i, e - 1) == c then return e else return nil end
end
local S = (Predef.space + "--" * (any - Predef.nl)^0)^0
local name = m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0
local arrow = S * "<-"
local seq_follow = m.P"/" + ")" + "}" + ":}" + "~}" + "|}" + "`}" + (name * arrow) + -1
name = m.C(name)
-- a defined name only have meaning in a given environment
local Def = name * m.Carg(1)
local num = m.C(m.R"09"^1) * S / tonumber
local String = "'" * m.C((any - "'")^0) * "'" +
'"' * m.C((any - '"')^0) * '"'
local defined = "%" * Def / function (c,Defs)
local cat = Defs and Defs[c] or Predef[c]
if not cat then error ("name '" .. c .. "' undefined") end
return cat
end
local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R
local item = defined + Range + m.C(any)
local Class =
"["
* (m.C(m.P"^"^-1)) -- optional complement symbol
* m.Cf(item * (item - "]")^0, mt.__add) /
function (c, p) return c == "^" and any - p or p end
* "]"
local function adddef (t, k, exp)
if t[k] then
error("'"..k.."' already defined as a rule")
else
t[k] = exp
end
return t
end
local function firstdef (n, r) return adddef({n}, n, r) end
local function NT (n, b)
if not b then
error("rule '"..n.."' used outside a grammar")
else return mm.V(n)
end
end
local exp = m.P{ "Exp",
Exp = S * ( m.V"Grammar"
+ m.Cf(m.V"Seq" * ("/" * S * m.V"Seq")^0, mt.__add) );
Seq = m.Cf(m.Cc(m.P"") * m.V"Prefix"^0 , mt.__mul)
* (#seq_follow + patt_error);
Prefix = "&" * S * m.V"Prefix" / mt.__len
+ "!" * S * m.V"Prefix" / mt.__unm
-- < -- back assertion. Added for traditio.wiki by Alexander Mashin:
+ "<" * S * m.V"Prefix" / mm.B
+ m.V"Suffix";
Suffix = m.Cf(m.V"Primary" * S *
( ( m.P"+" * m.Cc(1, mt.__pow)
+ m.P"*" * m.Cc(0, mt.__pow)
+ m.P"?" * m.Cc(-1, mt.__pow)
+ "^" * ( m.Cg(num * m.Cc(mult))
+ m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow))
)
+ "->" * S * ( m.Cg((String + num) * m.Cc(mt.__div))
+ m.P"{}" * m.Cc(nil, m.Ct)
+ m.Cg(Def / getdef * m.Cc(mt.__div))
)
+ "=>" * S * m.Cg(Def / getdef * m.Cc(m.Cmt))
-- ~> -- fold capture. Inserted for traditio.wiki by Alexander Mashin:
+ "~>" * S * m.Cg(Def / getdef * m.Cc(m.Cf))
) * S
)^0, function (a,b,f) return f(a,b) end );
Primary = "(" * m.V"Exp" * ")"
+ String / mm.P
+ Class
+ defined
+ "{:" * (name * ":" + m.Cc(nil)) * m.V"Exp" * ":}" /
function (n, p) return mm.Cg(p, n) end
+ "=" * name / function (n) return mm.Cmt(mm.Cb(n), equalcap) end
+ m.P"{}" / mm.Cp
+ "{~" * m.V"Exp" * "~}" / mm.Cs
+ "{|" * m.V"Exp" * "|}" / mm.Ct
+ "{" * m.V"Exp" * "}" / mm.C
-- {` `} -- constant capture. Inserted for traditio.wiki by Alexander Mashin:
+ "{`" * Predef.space^0 * m.C((any - "`")^1) * S * "`}" / mm.Cc
-- {# #} == argument capture. Inserted for traditio.wiki by Alexander Mashin:
+ "{#" * S * Def * "#}" / getdef
+ m.P"." * m.Cc(any)
+ (name * -arrow + "<" * name * ">") * m.Cb("G") / NT;
Definition = name * arrow * m.V"Exp";
Grammar = m.Cg(m.Cc(true), "G") *
m.Cf(m.V"Definition" / firstdef * m.Cg(m.V"Definition")^0,
adddef) / mm.P
}
local pattern = S * m.Cg(m.Cc(false), "G") * exp / mm.P * (-any + patt_error)
local function compile (p, defs)
if mm.type(p) == "pattern" then return p end -- already compiled
local cp = pattern:match(p, 1, defs)
if not cp then error("incorrect pattern", 3) end
return cp
end
local function match (s, p, i)
local cp = mem[p]
if not cp then
cp = compile(p)
mem[p] = cp
end
return cp:match(s, i or 1)
end
local function find (s, p, i)
local cp = fmem[p]
if not cp then
cp = compile(p) / 0
cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) }
fmem[p] = cp
end
local i, e = cp:match(s, i or 1)
if i then return i, e - 1
else return i
end
end
local function gsub (s, p, rep)
local g = gmem[p] or {} -- ensure gmem[p] is not collected while here
gmem[p] = g
local cp = g[rep]
if not cp then
cp = compile(p)
cp = mm.Cs((cp / rep + 1)^0)
g[rep] = cp
end
return cp:match(s)
end
-- exported names
local re = {
compile = compile,
match = match,
find = find,
gsub = gsub,
updatelocale = updatelocale,
}
if version == "Lua 5.1" then _G.re = re end
return re