Модуль:Re

Материал из свободной русской энциклопедии «Традиция»
Перейти к навигации Перейти к поиску
Написание статей Написание статей
Тематические статьи
Техническая справка
Общие правила

Список правил и руководств Справка

Эта страница документации воспроизведена по источнику с изменениями, обусловленными спецификой вики.

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
{/regular expression/flags} or {pcre/regular expression/flags} PERL-compatible regular expression[o 1]
{} 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 capture[o 1] (deprecated)
p >> funcname Accummulator capture (gathers captures from p into the previous capture through function funcname)
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
  1. а б в г д Расширение re в «Традиции»

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          <- '&amp;' 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.

Источник

См. также


--[[
	Based on the code by Roberto Ierusalimschy:
	https://github.com/lua/lpeg/blob/master/re.lua
	
	Modified initially for traditio.wiki and then for SummaryII by Alexander Mashin:
		* <		-- back assertion	(lpeg.B),
		* {` `}	-- constant capture	(lpeg.Cc),
		* {# #}	-- argument capture	(lpeg.Carg),
		* {/ /} -- a regular expression match,
		* case_insensitive parametre for compile().
--]]

--
-- Copyright 2007-2023, Lua.org & PUC-Rio  (see 'lpeg.html' for license)
-- written by Roberto Ierusalimschy
--

-- 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
  , __mod = function (a, b) return lpeg.P(a) % b end
  , __pow = function (a, b) return lpeg.P(a) ^ b end
}

local version = _VERSION

-- We need re here.
local re = {
	string = string,
	rex_flavour = 'pcre2'
}

local paths = {
	rex_pcre	= { 'rex_pcre', 'rex_pcre2' },
	rex_pcre2	= { 'rex_pcre2', 'rex_pcre' }
}
--[[
	Load a named library or return already loaded.
	@param string|table library
	@return table
--]]
local function load_library (library)
	for _, path in ipairs (paths [library] or { library }) do
		if _G [path] then
			return _G [path]
		end
		local ok, lib = pcall (require, path)
		if ok and lib then
			return lib
		end
	end
	return nil -- failed to load the library.
end

local regex_flavours = (function ()
	local flavours = m.P (false)
	local loaded = {}
	for _, flavour in ipairs { 'posix', 'pcre2', 'pcre', 'onig', 'tre' } do
		local lib = load_library ('rex_' .. flavour)
		if lib then
			flavours = flavours + m.C (flavour) * m.Cc (lib.new)
			loaded [flavour] = lib.new
		end
	end
	local default = re.rex_flavour
	-- Default rex flavour:
	if loaded [default] then
		flavours = flavours + m.Cc (default) * m.Cc (loaded [default])
	end
	return flavours
end) ()

local pcall, unpack = pcall, unpack or table.unpack

-- No more global accesses after this point
_ENV = nil	 -- does no harm in Lua 5.1

-- @TODO: multibyte characters.
local any, start = m.P (1), m.P (true)

-- 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 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 function getdef (id, defs)
  local c = defs and defs[id]
  if not c then error("undefined name: " .. id) end
  return c
end

-- match a name and return a group of its corresponding definition
-- and 'f' (to be folded in 'Suffix')
local function defwithfunc (f)
  return m.Cg(Def / getdef * m.Cc(f))
end

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 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 function do_nothing (...)
	return ...
end

local string = re.string
local gmatch, match, upper, lower = string.gmatch, string.match, string.upper, string.lower
local set = mm.S

local function metagrammar (case_insensitive)

	local string2range = case_insensitive and function (str)
		local uppercase, lowercase = upper (str), lower (str)
		return uppercase ~= lowercase and mm.R (uppercase) + mm.R (lowercase) or mm.R (str)
	end or mm.R

	local Range = mm.Cs(any * (m.P'-' / '') * (any - ']')) / string2range

	local add_capital = case_insensitive and function (char)
		local uppercase, lowercase = upper (char), lower (char)
		return uppercase ~= lowercase and set (uppercase .. lowercase) or char
	end or do_nothing
	
	local item = (defined + Range + any / add_capital ) / m.P
	
	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 string2pattern = case_insensitive and function (str)
		local pattern = start
		for char in gmatch (str, '.') do
			local uppercase, lowercase = upper (char), lower (char)
			local char = uppercase ~= lowercase and set (uppercase .. lowercase) or char
			pattern = pattern * char
		end
		return pattern
	end or mm.P
	
	local exp = m.P{ "Exp",
	  Exp = S * ( m.V"Grammar"
			-- % on userdata does nt seem to work in this context, even with a metatable.
			-- Therefore, falling back to lpeg.Cf.  	
			--	+ m.V"Seq" * ("/" * S * m.V"Seq" % mt.__add)^0 );
			+ m.Cf(m.V"Seq" * ("/" * S * m.V"Seq")^0, mt.__add) );
		-- % on userdata does nt seem to work in this context, even with a metatable.
		-- Therefore, falling back to lpeg.Cf.  
	  -- Seq = (m.Cc(m.P"") * (m.V"Prefix" % mt.__mul)^0)
	  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";
		-- % on userdata does nt seem to work in this context, even with a metatable.
		-- Therefore, falling back to lpeg.Cf. 
		-- Suffix = m.V"Primary" * S *
		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)
							 + defwithfunc(mt.__div)
							 )
				+ "=>" * S * defwithfunc(mm.Cmt)
				+ ">>" * S * defwithfunc(mt.__mod)
				+ "~>" * S * defwithfunc(mm.Cf)
		-- % on userdata does nt seem to work in thiscontext, even with a metatable.
		-- Therefore, falling back to lpeg.Cf.			 
		-- ) % function (a,b,f) return f(a,b) end * S
		--)^0;
			) * S
		)^0, function (a,b,f) return f(a,b) end );
	  Primary = "(" * m.V"Exp" * ")"
				+ String / string2pattern
				+ 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
				-- Inject regular expressions:
				+ '{' * S * regex_flavours * S
					* '/' * mm.C (( any - '/' + '\\/') ^ 1) * '/'
					* mm.C (mm.S 'imsxUX' ^ 0)
					* S * '}' / function (flavour, compiler, regex, flags)
						local absolute_start = false
						if string.sub (regex, 1, 1) == '^' then
							-- Anchoring to string's absolute beginning is required:
							absolute_start = true
						else
							-- Force anchoring to current position:
							regex = '^' .. regex
						end
						local valid, compiled = pcall (compiler, regex, flags)
							if not valid or not compiled then
								error (flavour .. ' regular expression /' .. regex .. '/' .. (flags or '') .. ' does not compile')
							end
						return mm.Cmt ( any, function (s, p)
							local p = p - 1 -- one character has been consumed by any, so that a loop containg only {//} is never empty.
							if absolute_start and p > 1 then
								-- Not the beginning of the string, so already failed:
								return false
							end
							local remainder = string.sub (s, p)
							local start, finish, captures = compiled:tfind (remainder)
							if not start then
								return false
							end
							if #captures == 0 then
								captures = { string.sub (remainder, 1, finish) }
							end
							return p + finish, unpack (captures)
						end)
					end
				-- {` `} -- 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.V"Exp" * "}" / mm.C
				+ 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") *
				-- % on userdata does nt seem to work in thiscontext, even with a metatable.
				-- Therefore, falling back to lpeg.Cf.	  
				-- ((m.V"Definition" / firstdef) * (m.V"Definition" % adddef)^0) / mm.P
				m.Cf(m.V"Definition" / firstdef * m.Cg(m.V"Definition")^0, adddef) / mm.P
	}

	return S * m.Cg(m.Cc(false), "G") * exp / mm.P * (-any + patt_error)
end


local function compile (p, defs, case_insensitive)
	if mm.type(p) == "pattern" then return p end   -- already compiled
	local cp = metagrammar (case_insensitive):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. re has been declare above.
re.compile, re.match, re.find, re.gsub, re.updatelocale = compile, match, find, gsub, updatelocale

if version == "Lua 5.1" then _G.re = re end

return re