hugomg
hugomg

Reputation: 69944

How to use LPeg to replace parts of the matched grammar?

Given a LPeg grammar, I want to do a find/replace on some of the sub-matches.

For example, suppose that I want to replace all the occurrences of the letter "a" that are outside of double quotes. I came up with a grammar for that:

local re = require "re"
local pattern = re.compile([[
    line <- (quoted / 'a' / .)*

    quoted <- '"' rest
    rest   <- '"' / . rest
]])

It seems that re.gsub is not useful here, because it would mess with the parts inside quotes. The best I could come up with was to use a table capture, where we capture everything besides the "a". But it was unwieldy because the pattern returns a table instead of a string.

line <- {| ({quoted} / 'a' / {.})* |}

I also looked into substitution captures, but got stuck because I needed to add ->'%1' around everything other than the 'a'.

Upvotes: 0

Views: 42

Answers (2)

hugomg
hugomg

Reputation: 69944

Turns out that the "%1" bit has to do with string captures, not of substitution captures. I can solve the problem by adding a capture only around the parts that I want to replace, and I can leave the other parts alone.

local re = require "re"
local pattern = re.compile([[
    line <- {~ (quoted / 'a'->'' / .)* ~}

    quoted <- '"' rest
    rest   <- '"' / . rest
]])

Pay attention to operator precedence, because '->' binds tightly

('a' 'b')->''

Upvotes: 1

Alexander Mashin
Alexander Mashin

Reputation: 4637

Yes, you need substitution captures. You don't need re, though:

-- Localisations for syntactic sugar and performance:
local lpeg = require 'lpeg'
local P, Cs = lpeg.P, lpeg.Cs
local any = P(1)

-- A string quoted with quote1 and quote2 (also quote1 if not set):
local function quoted (quote1, quote2)
    return P (quote1) * (any - P (quote2 or quote1)) ^ 0 * P (quote2 or quote1)
end

-- The whole grammar. tbl is the replacement table:
local function replace_unquoted (tbl)
    return Cs ((quoted'"' + any / tbl) ^ 0) * -1
end

print (replace_unquoted { a = 'o' }:match 'abcd "abcd" abcd')

Upvotes: 0

Related Questions