-- blogme.lua - a program to generate html -- Author: Eduardo Ochs -- Version: 2005feb19 -- -- The "language" that this program accepts is extensible and can deal -- with input having a lot of explicit mark-up, like this, -- -- [HLIST2 Items: -- [HREF http://foo/bar a link] -- [HREF http://another/link] -- [IT Italic text] -- [BF Boldface] -- ] -- -- and conceivably also with input with a lot of _implicit_ mark-up -- and with control structures, like these examples (which haven't -- been implemented yet): -- -- [BLOGME -- Tuesday, February 15, 2005 -- -- I usually write my notes in plain text files using Emacs; in -- these files "["s and "]"s can appear unquoted, urls appear -- anywhere without any special markup (like http://angg.twu.net/) -- and should be recognized and htmlized to links, some lines are -- dates or "anchors" and should be treated in special ways, the -- number of blank lines between paragraphs matter, in text -- paragraphs maybe _this markup_ should mean bold or italic, and -- there may be links to images that should be inlined, etc etc -- etc. -- ] -- -- [IF somecondition [(then this)] -- [(else this)] -- ] -- -- We also support executing blocks of Lua code on-the-fly, like this: -- -- [lua: -- -- we can put any block of Lua code here -- -- as long as its "["s and "]"s are balanced -- ] -- -- The trick is simple. In this language there is only one special -- syntactical construct, "[...]". We only have four classes of -- characters "[", "]", whitespace, and "word"; "[...]" blocks in the -- text are treated specially, and we use Lua's "%b[]" regexp-ish -- construct to skip over the body of a "[...]" quickly, skipping over -- all balanced "[]" pairs inside. The first "word" of such a block -- (we call it the "head" of the block) determines how to deal with -- the "rest" of the block. -- -- To "evaluate" an expression like -- -- [HREF http://foo/bar a link] -- -- we only parse its "head" - "HREF" - and then we run the Lua -- function called HREF. It is up to that function HREF to parse what -- comes after the head (the "rest"); HREF may evaluate the -- []-expressions in the rest, or use the rest without evaluations, or -- even ignore the rest completely. After the execution of HREF the -- parsing resumes from the point after the associated "]". -- -- Actually the evaluation process is a bit more sophisticated than -- that. Instead of executing just HREF() we use an auxiliary table, -- _GETARGS, and we execute: -- -- HREF(_GETARGS["HREF"]()) -- -- _GETARGS["HREF"] returns a function, vargs2, that uses the rest to -- produce arguments for HREF. Running vargs2() in that situation returns -- -- "http://foo/bar", "a link" -- -- and HREF is called as HREF("http://foo/bar", "a link"). So, to -- define HREF as a head all we would need to do ("would" because it's -- already defined) is: -- -- HREF = function (url, text) -- return ""..text.."" -- end -- _GETARGS["HREF"] = vargs2 -- -- More later. --[[ # (eechannel-xterm "LUA") lua = strfind("abcde", "cd()", 2+1) -- 2+1 4 4+1 = strfind("abcde", "cd()", 3+1) -- nil = strsub("abcde", 2+1, 4) -- "cd" --]] -- (find-fline "brackets.lua") --;; --;; The basic parsers (culminating at "{}:eval") --;; subj = "" -- will be changed later pos = 0 -- all my positions will be 0-based val = nil b,e = 0, 0 -- beginning and end of the text inside []s substring = function (b, e) return strsub(subj, b+1, e) end parser = {} parse = function (tag) return parser[tag]() end -- a variation of `parse' that is useful for debugging (and only for that): PARSE = function (prompt, tag) local b = pos if parse(tag) then print(prompt, "<".. substring(b, pos) ..">") return true end end parsepat = function (patstr) local _, __, e = string.find(subj, patstr, pos+1) if _ then pos = e-1; return true end end parser["_*"] = function () return parsepat("^[ \t\n]*()") end parser["w+"] = function () return parsepat("^[^ \t\n%[%]]+()") end parser["{}"] = function () return parsepat("^%b[]()") end parser["w+:string"] = function () local b = pos if parse("w+") then val = substring(b, pos); return true end end parser["(w+:string|{}:eval)+:concat"] = function () local empty, result = true, nil while parse("w+:string") or parse("{}:eval") do if empty then result = val; empty = false else result = result .. val end end if not empty then val = result; return true end end parser["bigword"] = parser["(w+:string|{}:eval)+:concat"] parser["_*bigword"] = function () parse("_*"); return parse("bigword") end -- heads = {} -- head_do = function (head) return heads[head]() end _GETARGS = {} functionp = function (obj) return type(obj) == "function" end head_do = function (head) local f, g = _G[head], _GETARGS[head] if functionp(f) and functionp(g) then return f(g()) else print("Bad head:", head) printpos("pos:", pos) printpos("b:", b) printpos("e:", e) error() end end -- [ head args ] -- /\pos instant 0 -- /\b /\pos instant 1 -- /\pos /\e instant 2 -- /\pos instant 3 -- parser["{}:eval"] = function () local oldb, olde = b, e b = pos+1 if parse("{}") then e = pos-1 pos = b parse("_*bigword") val = head_do(val) b, e, pos = oldb, olde, e+1 return true end b, e = oldb, olde end --;; --;; Two parsers for "all the other bigwords" --;; parser["(_*bigword)*:list"] = function () local blist = {} while parse("_*bigword") do tinsert(blist, val) end val = blist return true end parser["bigwordlist"] = parser["(_*bigword)*:list"] parser["(_|w)+"] = function () return parsepat("^[^%[%]]+()") end parser["(_|w)+:string"] = function () local b = pos if parse("(_|w)+") then val = substring(b, pos); return true end end parser["((_|w)+:string|{}:eval)+:concat"] = function () local empty, result = true, nil while parse("(_|w)+:string") or parse("{}:eval") do if empty then result = val; empty = false else result = result .. val end end if not empty then val = result; return true end end parser["bigwords:concat"] = parser["((_|w)+:string|{}:eval)+:concat"] parser["rest:eval"] = parser["((_|w)+:string|{}:eval)+:concat"] parser["_*rest:eval"] = function () parse("_*"); return parse("rest:eval") end --;; --;; functions to build transformers --;; vparse = function (tag) if parse(tag or "rest:eval") then return val end end pparse = function (str, tag) subj, pos = str, 0; print(vparse(tag)) end -- dbg vword = function () return vparse("_*bigword") end vrest = function () return vparse("_*rest:eval") end vrest_a = function () return vparse("bigwordlist") end vargs1_ = function () return vrest() end vargs2_ = function () return vword(), vrest() end vargs3_ = function () return vword(), vword(), vrest() end vargs4_ = function () return vword(), vword(), vword(), vrest() end vargs1 = function () return vrest() or "" end vargs2 = function () return vword(), vrest() or "" end vargs3 = function () return vword(), vword(), vrest() or "" end vargs4 = function () return vword(), vword(), vword(), vrest() or "" end vargs1_a = function () return vrest_a() end vargs2_a = function () return vword(), vrest_a() end vargs3_a = function () return vword(), vword(), vrest_a() end vargs4_a = function () return vword(), vword(), vword(), vrest_a() end nop = function () end --;; --;; tests - new style --;; setgetargs = function (argf, headnames) headnames = split(headnames) for i=1,getn(headnames) do _GETARGS[headnames[i]] = argf end end setstubs = function (headnames) headnames = split(headnames) for i=1,getn(headnames) do local name = headnames[i] _G[name] = function (str) return "("..name.." "..str..")" end _GETARGS[name] = vargs1 end end --[[ -- _GETARGS["R"] = vargs2 -- R = function (a, b) return "<<"..a.." __ "..b..">>" end -- pparse("foo [R 1 [R http://foo/bar ab cd ] ] bar", "rest:eval") --]] --;; --;; eval, expr and lambda --;; eval = function (body) return assert(loadstring(body))() end expr = function (body) return assert(loadstring("return "..body))() end slambda = function (arglist, body) -- here body is made of statements return assert(loadstring( "return function ("..arglist..")\n"..body.."\nend"))() end lambda = function (arglist, body) -- here body is an expression return assert(loadstring( "return function ("..arglist..")\nreturn "..body.."\nend"))() end --;; --;; undollar, map, join, smash, nonvoids --;; -- undollar = lambda("str", [[string.gsub(str, "%$([a-z]+)", "\"..%1..\"")]]) undollar = function (str) str = string.gsub(str, "%$([a-z]+)", "\"..%1..\"") str = string.gsub(str, "%$(%b())", "\"..%1..\"") return str end map = function (f, arr) local brr = {} for i=1,getn(arr) do tinsert(brr, f(arr[i])) end return brr end join = function (arr, sep) local str, n = {}, getn(arr) if n==0 then return "" end str = arr[1] for i=2,n do str = str .. sep .. arr[i] end return str end smash = function (obj) if obj=="" then return nil else return obj end end nonvoids = function (arr) local brr = {} for i=1,getn(arr) do if not(smash(obj)) then tinsert(brr, arr[i]) end end return brr end --;; --;; Html functions --;; _P = P -- P is a debugging function that I use; here we backup it as _P J = function (str) return str end -- join / identity HREF = lambda("url, str", undollar [["$str"]]) H1 = lambda("str", undollar [["
$str
"]])
EM = lambda("str", undollar [["$str"]])
PRE = lambda("str", undollar [["$str"]]) NAME = lambda("tag, str", undollar [["$str"]]) COLOR = lambda("color, str", undollar [["$str"]]) IMAGE = lambda("url, text", undollar [[HREF(url, "
$str"]]) setgetargs(vargs1, "J H1 H2 H3 H4 H5 H6 UL LI BF IT RM TT EM PRE P") setgetargs(vargs2, "HREF NAME COLOR IMAGE") setgetargs(vargs1_a, "LIST1 LIST2 LIST3") setgetargs(vargs2_a, "HLIST1 HLIST2 HLIST3") -- (find-angg "TH/Htmllib.tcl") -- (find-angg "TH/index-old.th") TITLE = lambda("str", undollar [["