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: