|
Warning: this is an htmlized version!
The original is here, and the conversion rules are here. |
-- This file:
-- http://anggtwu.net/LUA/ELpeg1.lua.html
-- http://anggtwu.net/LUA/ELpeg1.lua
-- (find-angg "LUA/ELpeg1.lua")
-- Author: Eduardo Ochs <eduardoochs@gmail.com>
-- Version: 20240122
--
-- This file implements a way to build lpeg grammars incrementally
-- using REPLs. My presentation at the EmacsConf2023 was partly about
-- this! See:
-- http://anggtwu.net/emacsconf2023.html
-- https://emacsconf.org/2023/talks/repl/
-- http://anggtwu.net/2023-hacking-lpegrex.html
-- (find-1stclassvideo-links "eev2023repls")
-- (find-1stclassvideo-links "eev2023replsb")
--
-- Based on:
-- (find-angg "LUA/Gram1.lua")
-- (find-angg "LUA/Gram2.lua")
--
-- (defun e1 () (interactive) (find-angg "LUA/ELpeg1.lua"))
-- (defun e2 () (interactive) (find-angg "LUA/ELpeg2.lua"))
-- (defun g2 () (interactive) (find-angg "LUA/Gram2.lua"))
--
-- (find-Deps1-links "ELpeg1 Re2 LpegRex3")
-- (find-Deps1-cps "ELpeg1 Re2 LpegRex3")
-- (find-Deps1-anggs "ELpeg1 Re2 LpegRex3")
-- «.globals» (to "globals")
-- «.lpeg.pm» (to "lpeg.pm")
-- «.lpeg.pm-tests» (to "lpeg.pm-tests")
-- «.lpeg.Cobeying» (to "lpeg.Cobeying")
-- «.lpeg.Cfromthere» (to "lpeg.Cfromthere")
-- «.AST» (to "AST")
-- «.AST-tests» (to "AST-tests")
-- «.E» (to "E")
-- «.E-tests» (to "E-tests")
-- «.Cast» (to "Cast")
-- «.Cast-tests» (to "Cast-tests")
-- «.Expected» (to "Expected")
-- «.Expected-tests» (to "Expected-tests")
-- «.Gram» (to "Gram")
-- «.Gram-Vlast» (to "Gram-Vlast")
-- «.Gram-tests» (to "Gram-tests")
-- «.Keywords» (to "Keywords")
-- «.Keywords-tests» (to "Keywords-tests")
--
-- «.LpegDebug» (to "LpegDebug")
-- «.LpegDebug-tests» (to "LpegDebug-tests")
-- «.GramDebug» (to "GramDebug")
-- «.GramDebug-tests» (to "GramDebug-tests")
--
-- «.folds» (to "folds")
-- «.folds-tests» (to "folds-tests")
-- «.anyof» (to "anyof")
-- «.anyof-tests» (to "anyof-tests")
-- «.assocs» (to "assocs")
-- «.assocs-test» (to "assocs-test")
-- «.endingwith» (to "endingwith")
-- «.endingwith-tests» (to "endingwith-tests")
--
require "Tree1" -- (find-angg "LUA/Tree1.lua")
require "lpeg" -- (find-es "lpeg" "lpeg-quickref")
-- (find-es "lpeg" "globals")
--require "Subst1" -- (find-angg "LUA/Subst1.lua")
--require "Globals1" -- (find-angg "LUA/Globals1.lua")
-- «globals» (to ".globals")
-- Note that running
-- require "ELpeg1"
-- will (re)define all the "scratch globals" below... running
-- gr,V,VA,VE,PE = Gram.new()
-- redefines V in a way that is compatible with V = lpeg.V, but
-- require "Pict3"
-- redefines V as V = MiniV. So take care!
--
lpeg = lpeg or require "lpeg"
B,C,P,R,S,V = lpeg.B,lpeg.C,lpeg.P,lpeg.R,lpeg.S,lpeg.V
Cb,Cc,Cf,Cg = lpeg.Cb,lpeg.Cc,lpeg.Cf,lpeg.Cg
Cp,Cs,Ct = lpeg.Cp,lpeg.Cs,lpeg.Ct
Carg,Cmt = lpeg.Carg,lpeg.Cmt
-- L = Code.L -- delete this?
-- _
-- | |_ __ ___ __ _ _ __ _ __ ___
-- | | '_ \ / _ \/ _` | | '_ \| '_ ` _ \
-- | | |_) | __/ (_| |_| |_) | | | | | |
-- |_| .__/ \___|\__, (_) .__/|_| |_| |_|
-- |_| |___/ |_|
--
-- "Print match", or
-- "(Pretty-)print (with PP) (the results of a) match".
--
-- This block defines a method :pm(...) for lpeg patterns and another
-- :pm(...) for Lua patterns; also, the test block loads Re2.lua, that
-- defines a :pm(...) for re.lua, and LpegRex3.lua, that defines a
-- :pm(...) for lpegrex. This sort of lets us compare the syntax of Lua
-- patterns, lpeg, re, and lpegrex.
--
-- Note that this is just one way to pretty-print results of matches!
-- Most of my patterns return ASTs, that have "__tostring"s that
-- prints them as trees. PP ignores "__tostring"s, so patterns that
-- return ASTs need another printing function. See:
-- (to "Gram")
-- (to "Gram" "trees")
--
-- «lpeg.pm» (to ".lpeg.pm")
lpeg.pm = function (lpat,subj,init,...) PP(lpat:match(subj,init,...)) end
string.pm = function (spat,subj,init,...) PP(subj:match(spat,init,...)) end
-- «lpeg.pm-tests» (to ".lpeg.pm-tests")
--[[
* (eepitch-lua52)
* (eepitch-kill)
* (eepitch-lua52)
dofile "ELpeg1.lua"
require "Re2" -- (find-angg "LUA/Re2.lua" "Re-tests")
("(<([io])([0-9]+)>)(.*)") :pm "<i42> 2+3;" -- lua
(C("<"*C(S"io")* C(R"09"^1)*">")*C(P(1)^0)) :pm "<i42> 2+3;" -- lpeg
rre "{ '<' {[io]} {[0-9]+} '>' } {.*}" :pm "<i42> 2+3;" -- re
require "LpegRex3" -- (find-angg "LUA/LpegRex3.lua")
loadlpegrex() -- (find-angg "LUA/lua50init.lua" "loadlpegrex")
rre "{ '<' {[io]} {[0-9]+} '>' } {.*}" :pm "<i42> 2+3;" -- re
lre "{ '<' {[io]} {[0-9]+} '>' } {.*}" :pm "<i42> 2+3;" -- lpregrex
--]]
-- «lpeg.Cobeying» (to ".lpeg.Cobeying")
-- «lpeg.Cfromthere» (to ".lpeg.Cfromthere")
-- See: (find-es "lpeg" "lpeg.Cobeying")
-- See: (find-es "lpeg" "lpeg.Cfromthere")
lpeg.ptmatch = function (pat, str) PP(pat:Ct():match(str)) end
lpeg.Cobeying = function (pat, f)
return pat:Cmt(function(subj,pos,o)
if f(o) then return true,o else return false end
end)
end
lpeg.Cfromthere = function (pat)
return pat:Cmt(function(subj,pos,there)
return pos,subj:sub(there,pos-1)
end)
end
-- _ ____ _____
-- / \ / ___|_ _|
-- / _ \ \___ \ | |
-- / ___ \ ___) || |
-- /_/ \_\____/ |_|
--
-- "Abstract Syntax Trees".
--
-- «AST» (to ".AST")
--
AST = Class {
type = "AST",
alttags = VTable {}, -- Override this!
ify = function (o) -- "o" needs to be a (scratch) table.
if o[0] and AST.alttags[o[0]] -- In some cases
then o[0] = AST.alttags[o[0]] -- we change o[0],
end -- and in all cases
return AST(o) -- we change the metatable of o.
end,
__tostring = function (o) return tostring(SynTree.from(o)) end,
__index = {
-- See: (find-angg "LUA/Subst1.lua" "totex")
-- (find-angg "LUA/Show2.lua" "string.show")
totex00 = function (o,...) return totex00(o,...) end, -- returns a tt
totex0 = function (o,...) return totex0 (o,...) end, -- returns a tt
totex = function (o,...) return totex (o,...) end, -- returns linear text
show = function (o,...) return totex(o):show(...) end -- needs Show
},
}
mkast = function (op, ...) return AST.ify {[0]=op, ...} end
-- «AST-tests» (to ".AST-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "ELpeg1.lua"
= AST {2, 3, 4}
= AST {[0]="mul", 2, 3, 4}
= AST {[0]="mul", 2, 3, AST {4,5,6}}
= AST.ify {[0]="mul", 2, 3, AST {4,5,6}}
= mkast ("mul", 2, 3, AST {4,5,6})
= AST.alttags
AST.alttags.mul = "*" -- changes "mul" to "*" in AST.ify
= AST.alttags
= AST {[0]="mul", 2, 3, AST {[0]="mul",4,5,6}}
= AST {[0]="mul", 2, 3, AST.ify {[0]="mul",4,5,6}}
= AST.ify {[0]="mul", 2, 3, AST {[0]="mul",4,5,6}}
= AST.ify {[0]="mul", 2, 3, AST.ify {[0]="mul",4,5,6}}
--]]
-- _____
-- | ____|
-- | _|
-- | |___
-- |_____|
--
-- "E" lets us create a dictionary whose entries are strings,
-- and whose values are ASTs that show how those strings are
-- expected to be parsed - usually by a grammar that we haven't
-- written yet.
-- «E» (to ".E")
E_entries = VTable {}
E = setmetatable({}, {
__call = function (_,name) return E_entries[name] end,
__index = function (_,name) return E_entries[name] end,
__newindex = function (_,name,o) E_entries[name] = o end,
})
-- «E-tests» (to ".E-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "ELpeg1.lua"
E[ "4*5"] = mkast("*", 4, 5)
E["2*3" ] = mkast("*", 2, 3)
E["2*3 + 4*5"] = mkast("+", E"2*3", E"4*5")
= E["2*3 + 4*5"]
--]]
-- «Cast» (to ".Cast")
-- These two are similar:
-- Cast(tag, pat)
-- lpeg.Ct (pat)
-- but the version with "Cast" converts the table to an AST.
Ckv = function (key, val) return Cg(Cc(val), key) end
C0 = function (tag) return Cg(Cc(tag), 0) end
Cast = function (tag, pat) return Ct(C0(tag)*pat) / AST.ify end
-- «Cast-tests» (to ".Cast-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "ELpeg1.lua"
N = Cs(R"09")
op = Cs(R"*/")
expr = Cast("muls", N*(op*N)^0)
= expr:match("2*3/4*5")
--]]
-- _____ _ _
-- | ____|_ ___ __ ___ ___| |_ ___ __| |
-- | _| \ \/ / '_ \ / _ \/ __| __/ _ \/ _` |
-- | |___ > <| |_) | __/ (__| || __/ (_| |
-- |_____/_/\_\ .__/ \___|\___|\__\___|\__,_|
-- |_|
-- Usage:
-- Expected.C("patfoo", patfoo)
-- Cexpect ("patfoo", patfoo)
-- creates an lpeg pattern that tries to match patfoo,
-- and when that fails it aborts with an error that
-- mentions the name "patfoo".
-- This is used by the class Gram.
--
-- «Expected» (to ".Expected")
Expected = Class {
type = "Expected",
-- prat = prat1,
prat0 = function (subj, pos, patname)
print("At: "..pos) -- very primitive
end,
prat1 = function (subj, pos, patname)
print("At: "..(" "):rep(pos).."^") -- slightly better
end,
err = function (subj, pos, patname)
print("Subj: "..subj)
Expected.prat(subj, pos, patname)
print("Expected: "..patname)
error()
end,
--
C = function (patname, pat)
local f = function(subj, pos) Expected.err(subj, pos, patname) end
return pat + (P""):Cmt(f)
end,
P = function (str)
return Expected.C(str, P(str))
end,
__index = {
},
}
-- Choose one:
-- Expected.prat = Expected.prat0
Expected.prat = Expected.prat1
Cexpected = Expected.C
-- «Expected-tests» (to ".Expected-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "ELpeg1.lua"
Expected.prat = Expected.prat0
pa = P"a"
pb = P"b"
pat = pa*pb
= pat:match("abc")
pat = pa*Cexpected("pb", pb)
= pat:match("abc")
= pat:match("ac")
Expected.prat = Expected.prat1
= pat:match("abc")
= pat:match("ac")
--]]
-- ____
-- / ___|_ __ __ _ _ __ ___
-- | | _| '__/ _` | '_ ` _ \
-- | |_| | | | (_| | | | | | |
-- \____|_| \__,_|_| |_| |_|
--
-- In the terminology of
-- http://www.inf.puc-rio.br/~roberto/lpeg/lpeg.html#grammar
-- an object of the class Gram is a grammar that:
-- 1) hasn't been "fixed" yet,
-- 2) doesn't have an initial rule yet,
-- 3) has a nicer interface.
-- We "fix" it by calling gr:compile("topname") or one of its friends.
--
-- The typical usage is:
-- gr,V,VAST,VEXPECT,PEXPECT = Gram.new()
-- or: gr,V,VA,VE,PE = Gram.new()
-- and when we use that in a repl these symbols are globals.
--
-- Note that the V above will usually override the "V = lpeg.V" at the
-- top of this file, but it will override it in a compatible way -
-- see the __call method in mt_V below.
--
-- «Gram» (to ".Gram")
Gram = Class {
type = "Gram",
new = function ()
return Gram.fromentries(VTable {})
end,
fromentries = function (entries)
local gr = Gram {entries=entries}
local V = gr:mk_V()
local VAST = gr:mk_VAST()
local VEXPECT = gr:mk_VEXPECT()
local PEXPECT = gr:mk_PEXPECT()
return gr,V,VAST,VEXPECT,PEXPECT
end,
--
from = function (oldgram) -- untested
return Gram.fromentries(VTable(copy(oldgram.entries)))
end,
--
__tostring = function (gr) return gr:tostring() end,
__index = {
--
-- Metatables
-- To save the last definition, use: (to "Gram-Vlast")
set = function (gr, name, pat) gr.entries[name] = pat end,
mt_V = function (gr) return {
__call = function (_,name) return lpeg.V(name) end,
__index = function (_,name) return lpeg.V(name) end,
__newindex = function (_,name,pat) gr:set(name, pat) end,
}
end,
mt_VAST = function (gr) return {
__newindex = function (v,name,pat)
-- If gr.be is true the modify pat to make it
-- save beginpos and endpos in .b and .e
if gr.be then pat = Cp():Cg"b" * pat * Cp():Cg"e" end
gr:set(name, Cast(name,pat))
end,
}
end,
mt_VEXPECT = function (gr) return {
__call = function (_,name) return Expected.C(name, lpeg.V(name)) end,
__index = function (_,name) return Expected.C(name, lpeg.V(name)) end,
}
end,
mt_PEXPECT = function (gr) return {
__call = function (_,str) return Expected.P(str) end,
__index = function (_,str) return Expected.P(str) end,
}
end,
mk_V = function (gr) return setmetatable({}, gr:mt_V()) end,
mk_VAST = function (gr) return setmetatable({}, gr:mt_VAST()) end,
mk_VEXPECT = function (gr) return setmetatable({}, gr:mt_VEXPECT()) end,
mk_PEXPECT = function (gr) return setmetatable({}, gr:mt_PEXPECT()) end,
--
-- Return a fixed version of the grammar
grammar0 = function (gr, top)
local entries = copy(gr.entries)
entries[1] = top
return entries
end,
grammar = function (gr, top) return gr:grammar0(top) end,
--
-- Compile, match, print
compile = function (gr, top) return lpeg.P(gr:grammar(top)) end,
cm0 = function (gr, top, subj, pos)
if type(pos) == "string" then pos = subj:match(pos) end
return gr:compile(top):match(subj, pos)
end,
cm = function (gr, ...) return trees(gr:cm0(...)) end,
cmp = function (gr, ...) PP(gr:cm0(...)) end,
tostring = function (gr, sep)
local ks = sorted(keys(gr.entries))
return mapconcat(mytostring, ks, sep or "\n")
end,
--
debug = function (gr, ...) return GramDebug.from(gr, ...) end,
},
}
grcm = function (...) print(gr:cm(...)) end
-- «Gram-Vlast» (to ".Gram-Vlast")
-- This is useful, but uses a global with a hardcoded name:
-- Gram.__index.set = function (gr, name, pat)
-- Vlast = pat
-- gr.entries[name] = pat
-- end
-- «Gram-tests» (to ".Gram-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "ELpeg1.lua"
gr,V,VA,VE,PE = Gram.new()
_ = S(" ")^0
V.N = Cs(R"09")
V.pow = V.N * C"^" * V.N grcm("pow", "1^2^3")
V.pow = V.N * (C"^" * V.N)^0 grcm("pow", "1^2^3")
V.pow = Ct( V.N * (C"^" * V.N )^0) grcm("pow", "1^2^3")
V.div = Ct( V.pow * (C"/" * V.pow)^0) grcm("div", "4/5/6")
V.sub = Ct( V.div * (C"-" * V.div)^0) grcm("sub", "1^2^3-4/5/6")
VA.pow = V.N * (C"^" * V.N )^0 grcm("pow", "1^2^3")
VA.div = V.pow * (C"/" * V.pow)^0 grcm("div", "4/5/6")
VA.sub = V.div * (C"-" * V.div)^0 grcm("sub", "1^2^3-4/5/6")
V.pow = assocr(V.N, C"^") grcm("pow", "1^2^3")
V.div = assocl(V.pow, C"/") grcm("div", "4/5/6")
V.sub = assocl(V.div, C"-") grcm("sub", "1^2^3 - 4/5/6 - 7")
grcm("sub", "1^2^3 - 4/5/(6-7)")
V.basic = "("*_* V.expr *_*")" + V.N
V.pow = assocr(V.basic, C"^")
V.div = assocl(V.pow, C"/")
V.sub = assocl(V.div, C"-")
V.expr = V.sub grcm("expr", "1^2^3 - 4/5/(6-7)")
V. basic = V.paren + V.N
V .paren = "("*_*V.expr*_*")" grcm("expr", "1^2^3 - 4/5/(6-7)")
VA.paren = "("*_*V.expr*_*")" grcm("expr", "1^2^3 - 4/5/(6-7)")
V .paren = Cast("Paren", "("*_*V.expr*_*")") grcm("expr", "1^2^3 - 4/5/(6-7)")
V .paren = Cast("()", "("*_*V.expr*_*")") grcm("expr", "1^2^3 - 4/5/(6-7)")
V .paren = Cast(nil, "("*_*V.expr*_*")") grcm("expr", "1^2^3 - 4/5/(6-7)")
V .paren = "("*_*V.expr*_*")" grcm("expr", "1^2^3 - 4/5/(6-7")
V .paren = "("*_*V.expr*_*PE")" grcm("expr", "1^2^3 - 4/5/(6-7")
V .paren = "("*_*V.expr*_*PE")" grcm("expr", "1^2^3 - 4/5/(6-7)")
--]]
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "ELpeg1.lua"
gr,V,VA,VE,PE = Gram.new()
VA.one = C(1)
VA.two = V.one * V.one
o = gr:cm0("two", "xyz")
= o
PPV(o)
gr.be = true
-- Gram.__index.be = true
VA.one = C(1)
VA.two = V.one * V.one
o = gr:cm0("two", "xyz")
= o
PPV(o)
--]]
-- Old?
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "ELpeg1.lua"
gr,V,VA,VE = Gram.new()
grcm = function (...) print(gr:cm(...)) end
_ = S(" ")^0
VA.pow = V.N * (C"^" * V.N)^0
= gr:cm("pow", "1^2^3")
VA.sub = V.N * C"-" * V.N
= gr:cm("sub", "2-3-4")
VA.sub = V.N * (C"-" * V.N)^0
= gr:cm("sub", "2-3-4")
VA.N = Cs((R"09")^1)
V .op = Cs(P"-")
VA.A = V.N * (V.op * V .N)^0
VA.B = V.N * (V.op * VE.N)^0
= gr
= gr:grammar()
= gr:grammar("A")
= gr:cm("A", "2-3-4, foo")
= gr:cm("A", "2-3-4-, foo")
= gr:cm("B", "2-3-4-, foo")
--]]
-- _ __ _
-- | |/ /___ _ ___ _____ _ __ __| |___
-- | ' // _ \ | | \ \ /\ / / _ \| '__/ _` / __|
-- | . \ __/ |_| |\ V V / (_) | | | (_| \__ \
-- |_|\_\___|\__, | \_/\_/ \___/|_| \__,_|___/
-- |___/
--
-- «Keywords» (to ".Keywords")
-- This class implements the idea that some "words"
-- are "keywords" and the other ones are "non-keywords".
-- A linguistic trick: an "anonkeyword"
-- is "a non-key word".
-- Typical usage:
-- word = Cs(R"az"^1)
-- keywords,K,KE,KW,NKW = Keywords.from(word)
--
Keywords = Class {
type = "Keywords",
from = function (wordpat)
local keywords = Set.new()
local addkeyword = function (kw) keywords:add(kw); return kw end
local isakeyword = function (subj,pos,captk)
if keywords:has(captk) then return pos,captk end
end
local isanonkeyword = function (subj,pos,captk)
if not keywords:has(captk) then return pos,captk end
end
local isthis = function (kw)
return function (subj,pos,captk)
if captk == kw then return pos,captk end
end
end
local AKEYWORD = wordpat:Cmt(isakeyword)
local ANONKEYWORD = wordpat:Cmt(isanonkeyword)
local THISKEYWORD = function (kw)
addkeyword(kw)
return wordpat:Cmt(isthis(kw))
end
local EXPECTTHISKEYWORD = function (kw)
return Cexpected(kw, THISKEYWORD(kw))
end
local K = setmetatable({}, {
__call = function (_,kw) return THISKEYWORD(kw) end,
})
local KE = setmetatable({}, {
__call = function (_,kw) return EXPECTTHISKEYWORD(kw) end,
})
local KW = AKEYWORD
local NKW = ANONKEYWORD
return Keywords {
wordpat = wordpat,
keywords = keywords,
addkeyword = addkeyword,
isakeyword = isakeyword,
isanonkeyword = isanonkeyword,
isthis = isthis,
AKEYWORD = AKEYWORD,
ANONKEYWORD = ANONKEYWORD,
THISKEYWORD = THISKEYWORD,
EXPECTTHISKEYWORD = EXPECTTHISKEYWORD,
K = K,
}, K,KE,KW,NKW
end,
__tostring = function (ks) return ks.keywords:ksc(" ") end,
__index = {
},
}
-- «Keywords-tests» (to ".Keywords-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "ELpeg1.lua"
word = Cs(R"az"^1)
keywords,K,KE,KW,NKW = Keywords.from(word)
pdo = K"do"
pend = K"end"
edo = KE"do"
PPPV(keywords)
= keywords --> do end
= word:match("doo_0") --> doo
= word:match("do_0") --> do
= KW :match("doo_0") --> nil
= KW :match("do_0") --> do
= NKW :match("doo_0") --> doo
= NKW :match("do_0") --> nil
= pdo :match("doo_0") --> nil
= pdo :match("do_0") --> do
= edo :match("do_0") --> do
= edo :match("doo_0") --> do
--]]
-- _ ____ _
-- | | _ __ ___ __ _| _ \ ___| |__ _ _ __ _
-- | | | '_ \ / _ \/ _` | | | |/ _ \ '_ \| | | |/ _` |
-- | |___| |_) | __/ (_| | |_| | __/ |_) | |_| | (_| |
-- |_____| .__/ \___|\__, |____/ \___|_.__/ \__,_|\__, |
-- |_| |___/ |___/
--
-- An object of the class LpegDebug contains a way of adding debug
-- information to an lpeg pattern. Normally this is used by the class
-- GramDebug, defined below, that implements ways to add debug
-- information to several entries of a grammar.
--
-- This "debug information" consists of several "pieces", and this
-- class contains methods for adding some, all, or the default "pieces
-- of debug information" to an lpeg pattern. The default is to add
-- only "match-time debugging" (style = "cmt"); use style = "slash" to
-- add only "slash debugging", and use style = "cmt slash" to add both.
--
-- See: (find-es "lpeg" "pegdebug0")
-- (find-es "lpeg" "pegdebug")
-- «LpegDebug» (to ".LpegDebug")
--
LpegDebug = Class {
type = "LpegDebug",
from = function (name)
return LpegDebug {name=name}
end,
__tostring = function (lpd) return mytostringp(lpd) end,
__index = {
subst = function (lpd, fmt, pos)
local name = lpd.name
local A = {name=name, pos=pos}
return (fmt:gsub("<(.-)>", A))
end,
--
-- Methods for matchtime debugging
cmtfmt = function (lpd, fmt)
return function (subj,pos,...)
printf(lpd:subst(fmt, pos))
return pos
end
end,
cmt0 = function (lpd, fmt, pat) return pat:Cmt(lpd:cmtfmt(fmt)) end,
cmtt = function (lpd, fmt) return lpd:cmt0(fmt, lpeg.P(true)) end,
pcbeg = function (lpd) return lpd:cmtt("(<name>:<pos>\n") end,
pcmid = function (lpd) return lpd:cmtt(" <name>:<pos>\n") end,
pcend = function (lpd) return lpd:cmtt(" <name>:<pos>)\n") end,
pcfail = function (lpd) return lpd:cmtt(" <name>:fail)\n") end,
cmtdbg = function (lpd, pat)
return lpd:pcbeg()*pat*lpd:pcend()
+ lpd:pcfail()*P(false)
end,
--
-- Mathods for slash debugging
slfmt = function (lpd, fmt)
return function (pos)
printf(lpd:subst(fmt, pos))
end
end,
psl0 = function (lpd, fmt) return Cp() / lpd:slfmt(fmt) end,
pslbeg = function (lpd) return lpd:psl0("[<name>:<pos>\n") end,
pslmid = function (lpd) return lpd:psl0(" <name>:<pos>\n") end,
pslend = function (lpd) return lpd:psl0(" <name>:<pos>]\n") end,
psldbg = function (lpd, pat) return lpd:pslbeg()*pat*lpd:pslend() end,
--
-- Choose a style
style = "cmt",
dbg = function (lpd, pat)
if lpd.style:match("cmt") then pat = lpd:cmtdbg(pat) end
if lpd.style:match("slash") then pat = lpd:psldbg(pat) end
return pat
end,
},
}
-- «LpegDebug-tests» (to ".LpegDebug-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "ELpeg1.lua"
LpegDebug.__index.style = "slash"
LpegDebug.__index.style = "cmt"
lpd = LpegDebug.from("A")
= lpd
= lpd:subst("foo")
= lpd:subst("foo:<name>")
= lpd:subst("foo:<name>:<pos>.", 42)
pa = P"aa":Cs()
pb = P"bb":Cs()
pc = P"cc":Cs()
-- Test matchtime debugging
pbeg = lpd:cmtt("(<name>:<pos>\n")
pmid = lpd:cmtt(" <name>:<pos>\n")
pend = lpd:cmtt(" <name>:<pos>)\n")
pfail = lpd:cmtt(" <name>:fail)\n")
pbeg = lpd:pcbeg()
pmid = lpd:pcmid()
pend = lpd:pcend()
pfail = lpd:pcfail()
= (pa*pb) : match("aabbcc")
= (pbeg*pa*pmid*pb*pend) : match("aabbcc")
= (pbeg*pa*pb*pend) : match("aabbcc")
= (pbeg*pa*pb*pend + pfail) : match("aabbcc")
= (pbeg*pa*pb*pend + pfail) : match("aacc")
--]]
--[[
-- Test changing styles
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "ELpeg1.lua"
LpegDebug.__index.style = "cmt" -- Start with matchtime
dbg = function (name, pat) return LpegDebug.from(name):dbg(pat) end
pa = P"aa":Cs()
pb = P"bb":Cs()
pc = P"cc":Cs()
pab = pa * (pb+pc)
pda = dbg("a", pa)
= pda:match("aa")
= pda:match("cc")
LpegDebug.__index.style = "slash" -- Switch to slash
pda = dbg("a", pa)
= pda:match("aa")
= pda:match("cc")
-- Bigger examples. Choose one style
LpegDebug.__index.style = "cmt"
LpegDebug.__index.style = "slash"
LpegDebug.__index.style = "cmt slash"
pda = dbg("a", pa)
pdb = dbg("b", pb)
pdc = dbg("c", pc)
pdab = dbg("ab", pda * (pdb+pdc))
= pab :match("aacc")
= pdab:match("aacc")
--]]
-- ____ ____ _
-- / ___|_ __ __ _ _ __ ___ | _ \ ___| |__ _ _ __ _
-- | | _| '__/ _` | '_ ` _ \| | | |/ _ \ '_ \| | | |/ _` |
-- | |_| | | | (_| | | | | | | |_| | __/ |_) | |_| | (_| |
-- \____|_| \__,_|_| |_| |_|____/ \___|_.__/ \__,_|\__, |
-- |___/
--
-- An object of the class GramDebug contains an object of the class
-- Gram and a set of "dbgnames". A dbgname is the name of an entry of
-- the grammar that has to be modified by the method "modifyentry"
-- when the grammar is compiled; the modification is performed by the
-- class LpegDebug, that puts some debugging code around the original
-- pattern associated to that name.
--
-- «GramDebug» (to ".GramDebug")
--
GramDebug = Class {
type = "GramDebug",
from = function (gr, style)
local dbgnames = Set.new()
if style then LpegDebug.__index.style = style end
return GramDebug {gr=gr, dbgnames=dbgnames}
end,
__tostring = function (lds)
return "dbgnames: "..lds.dbgnames:ksc(" ")
end,
__index = {
dbg = function (grd, names)
for _,name in ipairs(split(names)) do grd.dbgnames:add(name) end
end,
--
-- See: (to "Gram")
modifyentry = function (grd, entries, name)
entries[name] = LpegDebug.from(name):dbg(grd.gr.entries[name])
end,
compile = function (grd, top)
local entries = copy(grd.gr.entries)
for name in grd.dbgnames:gen() do grd:modifyentry(entries, name) end
entries[1] = top
return lpeg.P(entries)
end,
cm0 = function (gr, top, subj, pos)
if type(pos) == "string" then pos = subj:match(pos) end
return grd:compile(top):match(subj, pos)
end,
cm = function (grd, ...) return trees(grd:cm0(...)) end,
},
}
-- «GramDebug-tests» (to ".GramDebug-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "ELpeg1.lua"
gr,V = Gram.new()
V.aa = Cs"aa"
V.bb = Cs"bb"
V.cc = Cs"cc"
V.ab = V.aa * (V.bb + V.cc)
grd = GramDebug.from(gr)
grd:dbg "aa"
= grd:cm("ab", "aabbcc")
grd:dbg "bb"
= grd:cm("ab", "aabbcc")
grd = GramDebug.from(gr)
grd:dbg "bb"
= grd:cm("ab", "aabbcc")
--]]
-- _____ _ _
-- | ___|__ | | __| |___
-- | |_ / _ \| |/ _` / __|
-- | _| (_) | | (_| \__ \
-- |_| \___/|_|\__,_|___/
--
-- «folds» (to ".folds")
-- Based on: (find-angg "LUA/lua50init.lua" "fold")
-- TODO: Replace by:
-- (find-angg "LUA/Fold1.lua")
--
foldl2 = function (A) -- starts from the left
local B = A[1]
for i=3,#A,2 do
local op,c = A[i-1],A[i]
B = AST {[0]=op, B, c}
end
return B
end
foldr2 = function (A) -- starts from the right
local B = A[#A]
for i=#A-2,1,-2 do
local c,op = A[i],A[i+1]
B = AST {[0]=op, c, B}
end
return B
end
foldh2 = function (A)
if type(A) ~= "table" then return A end
if #A == 1 then return A end
local B = AST {[0]=A[2], A[1]}
for i=3,#A,2 do table.insert(B, A[i]) end
return B
end
foldh = function (A) -- an horizontal pseudo-fold, for debugging
if type(A) ~= "table" then return A end
if #A == 1 then return A[1] end
return AST(A)
end
foldpost = function (A)
local f = function (e, op) return AST {[0]="Post", e, op} end
return foldl1(f, A)
end
foldpre = function (A)
local f = function (op, e) return AST {[0]="Pre", op, e} end
return foldr1(f, A)
end
-- «folds-tests» (to ".folds-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "ELpeg1.lua"
= foldl2 {2, "-", 4, "-", 6, "-", 8} --> ((2-4)-6)-8
= foldr2 {2, "^", 4, "^", 6, "^", 8} --> 2^(4^(6^8))
= foldl2 {2, "+", 4, "*", 6, "/", 8}
= foldr2 {2, "+", 4, "*", 6, "/", 8}
= foldh2 {2, "+", 4, "*", 6, "/", 8}
= foldh {2, "+", 4, "*", 6, "/", 8}
= foldl2 {2}
= foldr2 {2}
= foldh2 {2}
= foldh {2}
= foldh (2)
= foldpost {"a", "()", "{}", "[]"}
= foldpre {"!", "~", "#", "a"}
--]]
-- _ __
-- / \ _ __ _ _ ___ / _|
-- / _ \ | '_ \| | | |/ _ \| |_
-- / ___ \| | | | |_| | (_) | _|
-- /_/ \_\_| |_|\__, |\___/|_|
-- |___/
--
-- «anyof» (to ".anyof")
-- Uses: (find-angg "LUA/lua50init.lua" "fold" "foldl1")
-- Based on: (find-angg "LUA/Caepro4.lua" "AnyOf")
-- Usage:
-- anyof("+ - *")
-- returns this pattern:
-- Cs("+") + Cs("-") + Cs("*")
--
anyof = function (str)
local plus = function (a, b) return a+b end
return foldl1(plus, map(Cs, split(str)))
end
Cparen0 = function (o,c,pat) return P(o)*_* pat *_*P(c) end
Cparen = function (o,c,pat) return Cast(o..c, P(o)*_* pat *_*P(c)) end
-- «anyof-tests» (to ".anyof-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "ELpeg1.lua"
gr,V,VA = Gram.new()
V.op = anyof "+ - * /"
V.N = Cs(R"09"^1)
V.foo = V.N * (V.op * V.N)^0
VA.Foo = V.N * (V.op * V.N)^0
gr:cmp("foo", "1*2+3-4/5")
= gr:cm("foo", "1*2+3-4/5")
= gr:cm("Foo", "1*2+3-4/5")
_ = S" "^0
V.p = Cparen(".(", ").", V.Foo)
= gr:cm("p", ".(1*2+3-4/5).")
--]]
-- _
-- / \ ___ ___ ___ ___ ___
-- / _ \ / __/ __|/ _ \ / __/ __|
-- / ___ \\__ \__ \ (_) | (__\__ \
-- /_/ \_\___/___/\___/ \___|___/
--
-- «assocs» (to ".assocs")
-- "pe" is the "expression pattern".
-- "po" is the "operator pattern".
assoct = function (pe, po) return Ct(pe * (_* po *_* pe)^0) end
assocl = function (pe, po) return assoct(pe, po) / foldl2 end
assocr = function (pe, po) return assoct(pe, po) / foldr2 end
assoch = function (pe, po) return assoct(pe, po) / foldh end
assocpost = function (pe, po) return Ct(pe*(_*po)^0) / foldpost end
assocpostp = function (pe, po) return Ct(pe*(_*po)^1) / foldpost end
assocpre = function (po, pe) return Ct((po*_)^0*pe) / foldpre end
-- «assocs-test» (to ".assocs-test")
-- TODO: tests for assocpost and assocpre
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "ELpeg1.lua"
gr,V = Gram.new()
V.Num = (R"09")^1
V.S = (S" \t\n")^0
V.sNum = Cs(V"Num")
_ = V.S
V.expr2 = assocl(V.expr1, Cs"^")
V.expr3 = assocr(V.expr2, Cs"/")
V.expr4 = assocr(V.expr3, Cs"-")
V.pexpr4 = "(" * _ * V.expr4 * _ * ")"
V.expr1 = V.sNum + V.pexpr4
V.expr1 = V.pexpr4 + V.sNum
subj = "1^2^3 - 4/5/6 / 7^8^9^(10-11)"
= subj
= gr:cm("expr4", subj)
= Ct(Cg(Cc("name"),"tag"))
= Ct(Cg(Cc("name"),0))
--]]
-- _ _ _ _ _
-- ___ _ __ __| (_)_ __ __ ___ _(_) |_| |__
-- / _ \ '_ \ / _` | | '_ \ / _` \ \ /\ / / | __| '_ \
-- | __/ | | | (_| | | | | | (_| |\ V V /| | |_| | | |
-- \___|_| |_|\__,_|_|_| |_|\__, | \_/\_/ |_|\__|_| |_|
-- |___/
--
-- «endingwith» (to ".endingwith")
-- Some simple parser combinators.
-- See: (find-es "haskell" "ReadS")
-- "endingwith" is used to define statements and lvalues in Lua.
--
-- oneormorec (expr, comma): one or more exprs, separated by commas
-- oneormorecc (expr, comma): one or more exprs, separated by commas,
-- allowing an optional extra comma at the end
-- zeroormorec (expr, comma): zero or more exprs, separated by commas
-- zeroormorecc(expr, comma): zero or more exprs, separated by commas,
-- allowing an optional extra comma at the end
-- endingwith (pa, pb): a sequence of pas and pbs ending with a pb
oneormorec = function (pe, pc) return pe * (_*pc*_*pe)^0 end
oneormorecc = function (pe, pc) return oneormorec (pe, pc) * (_*pc)^-1 end
zeroormorec = function (pe, pc) return oneormorec (pe, pc)^-1 end
zeroormorecc = function (pe, pc) return oneormorecc(pe, pc)^-1 end
endingwith = function (pa, pb) return (_* (pa*_)^0 * pb)^1 end
-- «endingwith-tests» (to ".endingwith-tests")
-- TODO: write better tests.
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "ELpeg1.lua"
_ = (P" ")^0
pe = Cs "4"
pa = Cs".a"
pb = Cs"(b)"
pc = Cs ","
= oneormorec (pe, pc):match("4, 4,")
= oneormorecc (pe, pc):match("4, 4,")
= zeroormorec (pe, pc):match("4, 4,")
= zeroormorecc(pe, pc):match("4, 4,")
= endingwith (pa, pb):match(" .a (b) .a .a (b) .a")
--]]
-- Some obsolete classes were moved to:
-- (find-angg "LUA/Freeze1.lua")
-- Most "Gram"s produce ASTs.
-- The code that converts ASTs to TeX is in another file:
-- (find-angg "LUA/ToTeX1.lua")
-- Local Variables:
-- coding: utf-8-unix
-- modes: (lua-mode fundamental-mode)
-- End: