Warning: this is an htmlized version!
The original is here, and the conversion rules are here. |
-- This file: -- http://anggtwu.net/LUA/BlogMe3.lua.html -- http://anggtwu.net/LUA/BlogMe3.lua -- (find-angg "LUA/BlogMe3.lua") -- Author: Eduardo Ochs <[email protected]> -- -- This is a rewrite of blogme3/brackets.lua. -- The code is as similar to the original as possible, -- but structured as classes, and with test blocks. -- Version: 2023feb02 -- -- See: (find-blogme3 "brackets.lua") -- (find-blogme4 "brackets.lua" "bracketstructure") -- (find-blogme3 "definers.lua") -- (find-blogme3 "definers.lua" "_AA") -- -- (defun e () (interactive) (find-angg "LUA/BlogMe3.lua")) -- «.BlogMe» (to "BlogMe") -- «.BlogMe-tostring» (to "BlogMe-tostring") -- «.BlogMe-lowlevel» (to "BlogMe-lowlevel") -- «.BlogMe-mygather» (to "BlogMe-mygather") -- «.BlogMe-midlevel» (to "BlogMe-midlevel") -- «.BlogMe-highlevel» (to "BlogMe-highlevel") -- «.BlogMe-evalblock» (to "BlogMe-evalblock") -- «.BlogMe-run» (to "BlogMe-run") -- «.BlogMe-highlevel-tests» (to "BlogMe-highlevel-tests") -- «.BlogMe-lowlevel-tests» (to "BlogMe-lowlevel-tests") -- «.BlogMe-midlevel-tests» (to "BlogMe-midlevel-tests") -- «._A-and-_B» (to "_A-and-_B") -- «._A-and-_B-tests» (to "_A-and-_B-tests") -- «.BlogMeShort» (to "BlogMeShort") -- «.BlogMeShort-tests» (to "BlogMeShort-tests") -- «.BlogMeDef» (to "BlogMeDef") -- «.BlogMeDef-tests» (to "BlogMeDef-tests") -- «.def» (to "def") -- «.def-tests» (to "def-tests") -- ____ _ __ __ -- | __ )| | ___ __ _| \/ | ___ -- | _ \| |/ _ \ / _` | |\/| |/ _ \ -- | |_) | | (_) | (_| | | | | __/ -- |____/|_|\___/ \__, |_| |_|\___| -- |___/ -- -- «BlogMe» (to ".BlogMe") BlogMe = Class { type = "BlogMe", from = function (subj) local n2pos,pos2pos,pos2n = BlogMe.bracketstructure(subj) return BlogMe { subj = subj, pos = 1, n2pos = HTable(n2pos), pos2n = HTable(pos2n), pos2pos = HTable(pos2pos), } end, -- -- See: (find-blogme3 "brackets.lua" "bracketstructure") bracketstructure = function (subj) local pos2n, n2pos, pos2pos = {}, {}, {} local opens = {} local n = 0 for pos,bracket in string.gmatch(subj, "()([%[%]])") do n = n + 1 pos2n[pos] = n n2pos[n] = pos if bracket == "[" then table.insert(opens, pos) pos2pos[pos] = "?" else -- bracket == "]" if #opens > 0 then local openpos = table.remove(opens) pos2pos[openpos] = pos pos2pos[pos] = openpos else error("Extra closing bracket at pos " .. pos) end end end if #opens > 0 then error("Extra opening bracket at pos " .. opens[#opens]) end return n2pos, pos2pos, pos2n end, __tostring = function (b) return b:tostring() end, __index = { -- -- See: (find-es "lua5" "posnumbers") -- «BlogMe-tostring» (to ".BlogMe-tostring") tostring = function(b) local line2,line1 = b:posnumbers(1, #b.subj) local caret = b.pos and (string.rep(" ", b.pos-1).."^") or "" local fmt = " %s\n" .. " %s\n" .. "subj = %s\n" .. " %s\n" .. "pos = %s\n" .. "n2pos = %s\n" .. "pos2n = %s\n" .. "pos2pos = %s\n" return format(fmt, line1, line2, b.subj, caret, tostring(b.pos), tostring(b.n2pos), tostring(b.pos2n), tostring(b.pos2pos)) end, posnumbers = function (b,i,j) local fmt = "%2d" local hnum = function (k) return format(fmt, k) end local digit = function (k, pos) return hnum(k):sub(pos,pos) end local line = function (pos) local f = function (k) return digit(k, pos) end return mapconcat(f, seq(i,j), "") end return line(2), line(1) end, -- -- «BlogMe-lowlevel» (to ".BlogMe-lowlevel") -- See: (find-blogme3 "brackets.lua" "parsers") -- "wchars" means "word chars", -- "rchars" means "regular chars". parsebypattern = function (b, pat) local capture, newpos = string.match(b.subj, pat, b.pos) if newpos then b.pos = newpos; return capture end end, parsespaces = function (b) return b:parsebypattern("^([ \t\n]+)()") end, parsewchars = function (b) return b:parsebypattern("^([^ \t\n%[%]]+)()") end, parserchars = function (b) return b:parsebypattern("^([^%[%]]+)()") end, parseblock = function (b) if b.pos2pos[b.pos] and b.pos < b.pos2pos[b.pos] then local inside = b.pos + 1 b.pos = b.pos2pos[b.pos] + 1 return inside end end, -- -- «BlogMe-mygather» (to ".BlogMe-mygather") -- (find-blogme3 "brackets.lua" "myconcat") -- (find-blogme3 "brackets.lua" "mygather") mygather = function (b, methodname) local T = {} while true do local val = b[methodname](b) if val then table.insert(T, val) else return T end end end, myconcat = function (b, T) if #T > 1 then return table.concat(T, "") end if #T == 1 then return T[1] end end, myconcatgather = function (b, methodname) return b:myconcat(b:mygather(methodname)) end, -- -- «BlogMe-midlevel» (to ".BlogMe-midlevel") -- See: (find-blogme3 "brackets.lua" "parsers_") -- "q" means "quoted". -- "v" means "value", or "evaluated". -- The method evalblock is defined at the end of the class. -- readvblock_ = function (b) local blockstart = b:parseblock() if blockstart then return b:evalblock(blockstart) or "" end end, readqblock_ = function (b) local blockstart = b:parseblock() if blockstart then return b.subj:sub(blockstart - 1, b.pos - 1) end end, readwcharsorqblock_ = function (b) return b:parsewchars() or b:readqblock_() end, readwcharsorvblock_ = function (b) return b:parsewchars() or b:readvblock_() end, readrcharsorqblock_ = function (b) return b:parserchars() or b:readqblock_() end, readrcharsorvblock_ = function (b) return b:parserchars() or b:readvblock_() end, readqword__ = function (b) return b:myconcatgather("readwcharsorqblock_") end, readvword__ = function (b) return b:myconcatgather("readwcharsorvblock_") end, readqrest__ = function (b) return b:myconcatgather("readrcharsorqblock_") end, readvrest__ = function (b) return b:myconcatgather("readrcharsorvblock_") end, readqword_ = function (b) b:parsespaces(); return b:readqword__() end, readvword_ = function (b) b:parsespaces(); return b:readvword__() end, readqrest_ = function (b) b:parsespaces(); return b:readqrest__() end, readvrest_ = function (b) b:parsespaces(); return b:readvrest__() end, -- -- «BlogMe-highlevel» (to ".BlogMe-highlevel") -- See: (find-blogme3 "brackets.lua" "readvword") readqword = function (b) return b:readqword_() or "" end, readvword = function (b) return b:readvword_() or "" end, readqrest = function (b) return b:readqrest_() or "" end, readvrest = function (b) return b:readvrest_() or "" end, readqlist = function (b) return b:mygather("readqword_") end, readvlist = function (b) return b:mygather("readvword_") end, readqargs = function (b) return unpack(b:readqlist()) end, readvargs = function (b) return unpack(b:readvlist()) end, -- readqqrest = function (b) return b:readqword(), b:readqrest() end, readqqqrest = function (b) return b:readqword(), b:readqqrest() end, readqqqqrest = function (b) return b:readqword(), b:readqqqrest() end, readqqqqqrest = function (b) return b:readqword(), b:readqqqqrest() end, readvvrest = function (b) return b:readvword(), b:readvrest() end, readvvvrest = function (b) return b:readvword(), b:readvvrest() end, readvvvvrest = function (b) return b:readvword(), b:readvvvrest() end, readvvvvvrest = function (b) return b:readvword(), b:readvvvvrest() end, -- readqqlist = function (b) return b:readqword(), b:readqlist() end, readqqqlist = function (b) return b:readqword(), b:readqqlist() end, readqqqqlist = function (b) return b:readqword(), b:readqqqlist() end, readqqqqqlist = function (b) return b:readqword(), b:readqqqqlist() end, readvvlist = function (b) return b:readvword(), b:readvlist() end, readvvvlist = function (b) return b:readvword(), b:readvvlist() end, readvvvvlist = function (b) return b:readvword(), b:readvvvlist() end, readvvvvvlist = function (b) return b:readvword(), b:readvvvvlist() end, -- nop = function (b) return end, -- -- «BlogMe-evalblock» (to ".BlogMe-evalblock") -- See: (find-blogme3 "brackets.lua" "evalblock") readeval = function (b) b:parsespaces() local word = b:parsewchars() -- should this be global (for debugging)? local headfun = _B[word] or _G[word] or error("Not in _B or _G: " .. word) local argsfun = _A[word] or error("Not in _A: " .. word) return headfun(b[argsfun](b)) end, evalblock = function (b, start) local oldpos = b.pos b.pos = start local result = b:readeval() b.pos = oldpos return result end, -- -- «BlogMe-run» (to ".BlogMe-run") runat = function (b, pos, parsername) if pos then b.pos = pos end PP(b[parsername](b)) end, runats = function (b, pos, parsernames) for _,parsername in ipairs(split(parsernames)) do b.pos = pos printf("%-21s", parsername..":") PP(b[parsername](b)) end end, run = function (b) b.pos = 1 return b["readvrest"](b) end, }, } -- «BlogMe-highlevel-tests» (to ".BlogMe-highlevel-tests") --[==[ * (eepitch-lua51) * (eepitch-kill) * (eepitch-lua51) dofile "BlogMe3.lua" def [[ IT 1 str "<i>$str</i>" ]] def [[ EM 1 str "<em>$str</em>" ]] = IT("foo") bm = BlogMe.from [[ abc[IT de]fg hi ]] bm:runats(1, [[ readqlist readqrest readqqrest readqqqrest readqword ]]) bm:runats(1, [[ readvlist readvrest readvvrest readvvvrest readvword ]]) --]==] -- «BlogMe-lowlevel-tests» (to ".BlogMe-lowlevel-tests") --[==[ * (eepitch-lua51) * (eepitch-kill) * (eepitch-lua51) dofile "BlogMe.lua" b = BlogMe.from [=[ a[b[c]d[e]]f ]=] = b PP(b:parsespaces(), b:parsewchars(), b:parseblock(), b:parsewchars(), b.pos) -- «BlogMe-midlevel-tests» (to ".BlogMe-midlevel-tests") * (eepitch-lua51) * (eepitch-kill) * (eepitch-lua51) dofile "BlogMe.lua" prb = function () print(b) end b = BlogMe.from [=[ a b[c]d e[f [g h]][i j]k l m ]=] b.pos = 12 = b = b:readqblock_() b.pos = 12 = b PP(b:readqblock_(), b:readqblock_(), b:readqblock_()) b.pos = 11 = b PP(b:readwcharsorqblock_(), b:readwcharsorqblock_(), b:readwcharsorqblock_(), b:readwcharsorqblock_(), b:readwcharsorqblock_()) b.pos = 12 = b PP(b:mygather("readqblock_")) b.pos = 12 PP(b:myconcatgather("readqblock_")) b.pos = 11 = b PP(b:readqword_()) b.pos = 11; PP(b:readqrest_()) b.pos = 11; PP(b:readqword()) b.pos = 11; PP(b:readqrest()) b.pos = 11; PP(b:readqqrest()) b.pos = 11; PP(b:readqqqrest()) b.pos = 11; PP(b:readqqqqrest()) b.pos = 11; PP(b:readqlist()) b.pos = 11; PP(b:readqqlist()) b.pos = 11; PP(b:readqqqlist()) b.pos = 11; PP(b:readqqqqlist()) --]==] --------[ Parsers and Evaluators ]-------- -- This is the most twisted part of blogme3... -- That's because it involves some recursions. -- The problem: evaluating a "block" usually involve "reading vwords", -- where a vword is the "value of a (big) word"; and to obtain the -- value of a big word we need to evaluate all the blocks in it. -- -- Something like "[print foo[+ 22 33]bar]" is a "block", and parsing -- it with parseblock() just returns the position of the "[" (as a -- number!) and advances pos past the "]"... But that's just the -- "syntactical level", and that's the easy part; above that there's a -- "semantical level", where blocks can have "values", obtained by -- evaluation. To understand how evaluation works we need to -- understand a function, "evalblock(start)" - the argument "start" is -- the position of a "[", as a number -, and two tables, _A and _B, -- whose keys are strings and whose values are functions: -- -- _A["print"] is the "argument parser" for "print"; -- _B["print"] is the "blogme code" for "print". -- -- They are similar to Lua's "_G": _G["print"] is the "Lua code" for -- "print". -- -- So: if we run "evalblock(start)" after the "parseblock()" then -- blogme tries to execute the "print": it first parses "print", then -- uses the function in _A["print"] to parse the argument list, then -- runs the code in _B["print"] with those arguments: -- _B["print"]("foo55bar"). -- -- _A["print"] knows that the arguments are a series of "vwords" - -- "values of (big) words". A similar idea is that of "qwords" - -- "quoted (big) words". Parsing "foo[+ 22 33]bar" as a qword (by -- calling getqword() with pos at the "f") would return this, as a -- string: "foo[+ 22 33]bar"; but parsing "foo[+ 22 33]bar" as vword -- (by calling readqword() with pos at the "f") involves evaluating the -- blocks in the way - and "[+ 22 33]" evaluates to 55 (a number), and -- myconcat {"foo", 55, "bar"} returns "foo55bar". -- -- _A["print"] is set to `readvargs' - a function that returns a -- variable number of results. In "[HREF http://foo/bar Foo bar]" the -- function in _A["HREF"] is `readvvrest', that returns exactly two -- results: first a vword, then a "vrest" - and "vrests" are like -- vwords, but whitespace chars are treated as regular chars, not as -- separators; the result of running readvrest() with pos at the "F" -- is "Foo bar". -- -- "readvrest" returns the "rest of the arguments" as a string; -- "readvlist" returns it as an array of vwords; and "readvargs" is -- like "readvlist" but varargs-ish - it returns the vwords that it -- can read as several values, like in "return v1, v2, v3". (Note: the -- choice of terms is not very good - "list" could become "array", and -- maybe "args" should become "list"...) -- -- "Parsers" return positions, as numbers; "readers" return "values", -- that are usually strings. Readers are divided into two classes: -- "quoters", that don't call evalblock and always return strings, and -- "evaluators", that call evalblock on blocks; all readers use -- myconcat and mygather to build their results. -- -- "Parsers" return nil - and don't advance pos - when they "fail"; -- that is, when they can't parse what they expected. "Readers" return -- the empty string. -- -- Readers whose names have the suffix "_" (meaning "low-level") don't -- advance pos when they fail; readers without the "_" in their names -- are higher-level versions that call "parsespaces" at some places - -- high-level readers may advance pos past some whitespace then fail, -- and when that happens pos is not returned to before the whitespace. -- -- Char classes: Basic parsers: Quoters: Evaluators: -- wordchar parsewchars evalblock -- regularchar parserchars readqblock readvblock -- spacechar parsespaces readqword readvword -- parseblock readqrest readvrest -- readqqrest readvvrest -- readqqqrest readvvvrest -- readqlist readvlist -- readqqlist readvvlist -- readqqqlist readvvvlist -- readqargs readvargs -- (find-blogmefile "blogme2-inner.lua" "-- run_head:") -- (find-blogmefile "blogme2-middle.lua") -- _ _ ____ -- / \ __ _ _ __ __| | | __ ) -- / _ \ / _` | '_ \ / _` | | _ \ -- / ___ \ | (_| | | | | (_| | | |_) | -- ___/_/ \_\ \__,_|_| |_|\__,_| ___|____/ -- |____| |____| -- -- «_A-and-_B» (to "._A-and-_B") -- The low-level way to define blogme words is with _A and _B. -- The high-level way is -- See: (find-blogme3 "brackets.lua" "evalblock") _A = VTable {} -- arglist parser functions for blogme words _B = VTable {} -- like _G, but for blogme words _A["PP"] = "readvlist" _B["PP"] = PP _A["lua"] = "readqrest" _B["lua"] = expr -- «_A-and-_B-tests» (to "._A-and-_B-tests") --[==[ * (eepitch-lua51) * (eepitch-kill) * (eepitch-lua51) dofile "BlogMe.lua" b = BlogMe.from [=[ a[lua 2+3]b ]=] b.pos = 1 = b = b:readqword() b.pos = 1 = b = b:readvword() --]==] -- ____ _ ____ _ _ -- | __ )| | ___ __ _ _ __ ___ ___/ ___|| |__ ___ _ __| |_ ___ -- | _ \| |/ _ \ / _` | '_ ` _ \ / _ \___ \| '_ \ / _ \| '__| __/ __| -- | |_) | | (_) | (_| | | | | | | __/___) | | | | (_) | | | |_\__ \ -- |____/|_|\___/ \__, |_| |_| |_|\___|____/|_| |_|\___/|_| \__|___/ -- |___/ -- -- «BlogMeShort» (to ".BlogMeShort") -- The global variable "_AA" holds a list of shorthands for "_A" that -- is used by "def" and friends. This class helps me to initialize and -- maintain _AA and to avoid certain common errors. -- See: (find-blogme3 "definers.lua" "_AA") -- BlogMeShort = Class { type = "BlogMeShort", -- isshort = function (short) if not _AA[short] then error("Not in _AA: %S", short) end return short end, islong = function (long) local method = BlogMe.__index[long] if not method then error(format("%q: argparser method not found", long)) end return long end, expand = function (short) return BlogMeShort.isshort(short) and _AA[short] end, defshort = function (short, long) _AA[short] = BlogMeShort.islong(long) end, defshorts = function (bigstr) for short,long in bigstr:gmatch("(%S+)%s+(%S+)") do BlogMeShort.defshort(short, long) end end, -- shorthands = [[ 1 readvrest 1L readvlist 1Q readqrest 0 nop 2 readvvrest 2L readvvlist 2Q readqqrest * readvargs 3 readvvvrest 3L readvvvlist 3Q readqqqrest 4 readvvvvrest 4L readvvvvlist 4Q readqqqqrest 5 readvvvvvrest 5L readvvvvvlist 5Q readqqqqqrest ]], init = function () local bigstr = BlogMeShort.shorthands BlogMeShort.defshorts(bigstr) end, } _AA = VTable {} BlogMeShort.init() -- «BlogMeShort-tests» (to ".BlogMeShort-tests") --[[ * (eepitch-lua51) * (eepitch-kill) * (eepitch-lua51) dofile "BlogMe3.lua" = _AA = BlogMeShort.expand("1L") = BlogMeShort.expand("99") = BlogMeShort.defshort("99", "readqrest") = BlogMeShort.expand("99") = BlogMeShort.defshort("99", "foo") = _AA --]] -- ____ _ __ __ ____ __ -- | __ )| | ___ __ _| \/ | ___| _ \ ___ / _| -- | _ \| |/ _ \ / _` | |\/| |/ _ \ | | |/ _ \ |_ -- | |_) | | (_) | (_| | | | | __/ |_| | __/ _| -- |____/|_|\___/ \__, |_| |_|\___|____/ \___|_| -- |___/ -- -- «BlogMeDef» (to ".BlogMeDef") -- The function "def" is defined using this class. -- The variants "def_", "bdef", and "bdef_" too, -- and new variants are easy to define. -- The suffix "_" means: don't add a "return". -- The prefix "b" means: store in _B, not in _G. -- -- See: (find-blogme3 "anggdefs.lua" "basic-html") -- (find-blogme3 "definers.lua" "undollar") -- (find-blogme3 "definers.lua" "def") -- BlogMeDef = Class { type = "BlogMeDef", fromsplit = function (defstr) local pat = "^%s*(%S+)%s+(%S+)%s+(%S+)%s(.*)" local name,short,arglist,body0 = defstr:match(pat) return BlogMeDef { name=name, short=short, arglist=arglist, body0=body0, body1=body0 } end, run0 = function (definer, defstr) local bd = BlogMeDef.fromsplit(defstr) bd:doword(definer) return bd end, run = function (definer, defstr) local bd = BlogMeDef.run0(definer, defstr) eval(bd.body1) return bd end, -- __tostring = function (bd) return bd:tostring() end, -- __index = { tostring = function (bd) local f = function (o) return tostring(o) end return "name: " .. f(bd.name) .."\nshort: " .. f(bd.short) .."\nlong: " .. f(bd.long) .."\narglist: " .. f(bd.arglist) .."\nstorein: " .. f(bd.storein) .."\nbody0: " .. f(bd.body0) .."\nbody1: " .. f(bd.body1) end, -- doword = function (bd, word) bd["do_"..word](bd) end, dowords = function (bd, words) for _,word in ipairs(split(words)) do bd:doword(word) end end, -- do_def = function (bd) bd:dowords("undollar ret f _G set _A") end, do_def_ = function (bd) bd:dowords("undollar f _G set _A") end, do_bdef = function (bd) bd:dowords("undollar ret f _B set _A") end, do_bdef_ = function (bd) bd:dowords("undollar f _B set _A") end, -- do__G = function (bd) bd.storein = "_G" end, do__B = function (bd) bd.storein = "_B" end, do_ret = function (bd) bd.body1 = "return "..bd.body1 end, -- do_set = function (bd) local fmt = "%s[%q] = %s" bd.body1 = format(fmt, bd.storein, bd.name, bd.body1) end, do_f = function (bd) local fmt = "function (%s)\n %s\n end" bd.body1 = format(fmt, bd.arglist, bd.body1) end, do__A = function (bd) local fmt = '\n_A["%s"] = "%s"\n%s' bd.long = BlogMeShort.expand(bd.short) bd.body1 = format(fmt, bd.name, bd.long, bd.body1) end, do_undollar = function (bd) bd.body1 = bd.body1:gsub("%$([a-z]+)", "\"..%1..\"") bd.body1 = bd.body1:gsub("%$(%b())", "\"..%1..\"") bd.body1 = bd.body1:gsub("%$(%b[])", function (s) return "]]..("..strsub(s, 2, -2)..")..[[" end) return bd.body1 end, }, } -- «BlogMeDef-tests» (to ".BlogMeDef-tests") -- See: (find-blogme3 "anggdefs.lua" "basic-html") --[==[ * (eepitch-lua51) * (eepitch-kill) * (eepitch-lua51) dofile "BlogMe3.lua" bd = BlogMeDef.fromsplit [[ H1 1 str "<h1>$str</h1>\n" ]] = bd bd:do_def() = bd = BlogMeDef.run0("def", [[ foo 2 a,b "<foo>$a:$b</foo>" ]]) = BlogMeDef.run0("def_", [[ foo 2 a,b return "<foo>$a:$b</foo>" ]]) = BlogMeDef.run0("bdef", [[ foo 2 a,b "<foo>$a:$b</foo>" ]]) = BlogMeDef.run0("bdef_", [[ foo 2 a,b return "<foo>$a:$b</foo>" ]]) BlogMeDef.run ("def", [[ foo 2 a,b "<foo>$a:$b</foo>" ]]) = foo("aaa", "bbb") --]==] -- _ __ -- __| | ___ / _| -- / _` |/ _ \ |_ -- | (_| | __/ _| -- \__,_|\___|_| -- -- «def» (to ".def") -- "def" and friends. def = function (defstr) return BlogMeDef.run("def", defstr) end def_ = function (defstr) return BlogMeDef.run("def_", defstr) end bdef = function (defstr) return BlogMeDef.run("bdef", defstr) end bdef_ = function (defstr) return BlogMeDef.run("bdef_", defstr) end -- «def-tests» (to ".def-tests") --[==[ * (eepitch-lua51) * (eepitch-kill) * (eepitch-lua51) dofile "BlogMe3.lua" = def [[ FOO 2 a,b "($a:$b)" ]] = def [[ HREF 2 tgt,body "<a href=\"$tgt\">$body</a>" ]] = def [[ lua: 1Q code eval(code) ]] = bdef [[ P 1Q code P(code) ]] PP(FOO("xy","zw")) bm = BlogMe.from [[ ab[FOO c d e]fg ]] PP(bm:run()) --]==] -- Local Variables: -- coding: utf-8-unix -- End: