if not modules then modules = { } end modules ['buff-ini'] = { version = 1.001, comment = "companion to core-buf.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- ctx lua reference model / hooks and such -- to be optimized -- redefine buffers.get buffers = { } buffers.data = { } buffers.hooks = { } buffers.flags = { } buffers.commands = { } buffers.visualizers = { } -- if needed we can make 'm local local trace_run = false trackers.register("buffers.run", function(v) trace_run = v end) local trace_visualize = false trackers.register("buffers.visualize", function(v) trace_visualize = v end) local utf = unicode.utf8 local concat, texsprint, texprint, texwrite = table.concat, tex.sprint, tex.print, tex.write local utfbyte, utffind, utfgsub = utf.byte, utf.find, utf.gsub local type, next = type, next local huge = math.huge local byte, sub, find, char, gsub, rep, lower, format, gmatch, match = string.byte, string.sub, string.find, string.char, string.gsub, string.rep, string.lower, string.format, string.gmatch, string.match local utfcharacters, utfvalues = string.utfcharacters, string.utfvalues local ctxcatcodes = tex.ctxcatcodes local variables = interfaces.variables local lpegmatch = lpeg.match local data, flags, hooks, visualizers = buffers.data, buffers.flags, buffers.hooks, buffers.visualizers visualizers.defaultname = variables.typing function buffers.raw(name) return data[name] or { } end function buffers.erase(name) data[name] = nil end function buffers.set(name, str) data[name] = { str } -- CHECK THIS end function buffers.append(name, str) data[name] = (data[name] or "") .. str end buffers.flags.store_as_table = true -- to be sorted out: crlf + \ ; slow now local n = 0 function buffers.grab(name,begintag,endtag,bufferdata) local dn = data[name] or "" if dn == "" then buffers.level = 0 end buffers.level = buffers.level + bufferdata:count("\\"..begintag) - bufferdata:count("\\"..endtag) local more = buffers.level > 0 if more then dn = dn .. bufferdata .. endtag buffers.level = buffers.level - 1 else if dn == "" then dn = sub(bufferdata,1,#bufferdata-1) else dn = dn .. "\n" .. sub(bufferdata,1,#bufferdata-1) end dn = gsub(dn,"[\010\013]$","") if flags.store_as_table then dn = dn:splitlines() end end data[name] = dn cs.testcase(more) end function buffers.exists(name) return data[name] ~= nil end function buffers.doifelsebuffer(name) cs.testcase(data[name] ~= nil) end flags.optimize_verbatim = true flags.count_empty_lines = false local no_break_command = "\\doverbatimnobreak" local do_break_command = "\\doverbatimgoodbreak" local begin_of_line_command = "\\doverbatimbeginofline" local end_of_line_command = "\\doverbatimendofline" local empty_line_command = "\\doverbatimemptyline" local begin_of_display_command = "\\doverbatimbeginofdisplay" local end_of_display_command = "\\doverbatimendofdisplay" local begin_of_inline_command = "\\doverbatimbeginofinline" local end_of_inline_command = "\\doverbatimendofinline" function buffers.verbatimbreak(n,m) if flags.optimize_verbatim then if n == 2 or n == m then texsprint(no_break_command) elseif n > 1 then texsprint(do_break_command) end end end function buffers.strip(lines,first,last) local first, last = first or 1, last or #lines for i=first,last do local li = lines[i] if #li == 0 or find(li,"^%s*$") then first = first + 1 else break end end for i=last,first,-1 do local li = lines[i] if #li == 0 or find(li,"^%s*$") then last = last - 1 else break end end return first, last, last - first + 1 end function buffers.range(lines,first,last,range) -- 1,3 1,+3 fromhere,tothere local first, last = first or 1, last or #lines local what = aux.settings_to_array(range) local r_first, r_last = what[1], what[2] local f, l = tonumber(r_first), tonumber(r_last) if r_first then if f then if f > first then first = f end else for i=first,last do if find(lines[i],r_first) then first, strip = i + 1 break end end end end if r_last then if l then if find(r_last,"^[%+]") then -- 1,+3 l = first + l end if l < last then last = l end else for i=first,last do if find(lines[i],r_last) then last = i - 1 break end end end end return first, last end function buffers.type(name,realign,range) local lines = data[name] local action = buffers.typeline if lines then if type(lines) == "string" then lines = lines:splitlines() data[name] = lines end if realign then lines = buffers.realign(lines,realign) end local line, n = 0, 0 local first, last, m = buffers.strip(lines) if range then first, last = buffers.range(lines,first,last,range) first, last = buffers.strip(lines,first,last) end hooks.begin_of_display() for i=first,last do n, line = action(lines[i], n, m, line) end hooks.end_of_display() end end function buffers.loaddata(filename) -- this one might go away -- this will be cleaned up when we have split supp-fil completely -- instead of half-half local ok, str, n = resolvers.loaders.tex(filename) if not str then ok, str, n = resolvers.loaders.tex(file.addsuffix(filename,'tex')) end end function buffers.loaddata(filename) -- this one might go away local foundname = resolvers.findtexfile(filename) or "" if foundname == "" then foundname = resolvers.findtexfile(file.addsuffix(filename,'tex')) or "" end if foundname == "" then return "" else return resolvers.loadtexfile(foundname) end end function buffers.typefile(name,realign,range) -- still somewhat messy, since name can be be suffixless local str = buffers.loaddata(name) if str and str~= "" then local lines = str:splitlines() if realign then lines = buffers.realign(lines,realign) end local line, n, action = 0, 0, buffers.typeline local first, last, m = buffers.strip(lines) hooks.begin_of_display() if range then first, last = buffers.range(lines,first,last,range) first, last = buffers.strip(lines,first,last) end for i=first,last do n, line = action(lines[i], n, m, line) end hooks.end_of_display() end end function buffers.typeline(str,n,m,line) n = n + 1 buffers.verbatimbreak(n,m) if find(str,"%S") then line = line + 1 hooks.begin_of_line(line) hooks.flush_line(hooks.line(str)) hooks.end_of_line() else if flags.count_empty_lines then line = line + 1 end hooks.empty_line(line) end return n, line end -- The optional prefix hack is there for the typesetbuffer feature and -- in mkii we needed that (this hidden feature is used in a manual). local function prepared(name,list) -- list is optional if not list or list == "" then list = name end if not name or name == "" then name = tex.jobname .. "-" .. list .. ".tmp" end local content = buffers.collect(list,nil) or "" if content == "" then content = "empty buffer" end return name, content end local capsule = "\\starttext\n%s\n\\stoptext\n" local command = "context %s" function buffers.save(name,list,encapsulate) -- list is optional local name, content = prepared(name,list) io.savedata(name, (encapsulate and format(capsule,content)) or content) end function commands.savebuffer(list,name) -- name is optional buffers.save(name,list) end function buffers.run(name,list,encapsulate) local name, content = prepared(name,list) local data = io.loaddata(name) content = (encapsulate and format(capsule,content)) or content if data ~= content then if trace_run then commands.writestatus("buffers","changes in '%s', processing forced",name) end io.savedata(name,content) os.execute(format(command,name)) elseif trace_run then commands.writestatus("buffers","no changes in '%s', not processed",name) end end local printer = (lpeg.patterns.textline/texprint)^0 function buffers.get(name) local b = buffers.data[name] if b then if type(b) == "table" then for i=1,#b do texprint(b[i]) end else lpegmatch(printer,b) end end end local function content(name,separator) -- no print local b = data[name] if b then if type(b) == "table" then return concat(b,separator or "\n") else return b end else return "" end end buffers.content = content function buffers.collect(names,separator) -- no print -- maybe we should always store a buffer as table so -- that we can pass it directly if type(names) == "string" then names = aux.settings_to_array(names) end local t = { } for i=1,#names do local c = content(names[i],separator) if c ~= "" then t[#t+1] = c end end return concat(t,separator or "\r") -- "\n" is safer due to comments and such end function buffers.feedback(names,separator) -- don't change the texprint into texsprint as it fails on mp buffers -- because (penddef) becomes penddef then texprint(ctxcatcodes,string.splitlines(buffers.collect(names,separator))) end local function tobyte(c) return " [" .. utfbyte(c) .. "] " end function buffers.inspect(name) local b = data[name] if b then if type(b) == "table" then for k=1,#b do local v = b[k] if v == "" then texsprint(ctxcatcodes,"[crlf]\\par ") -- space ? else texsprint(ctxcatcodes,(gsub(v,"(.)",tobyte)),"\\par") end end else texsprint(ctxcatcodes,(gsub(b,"(.)",tobyte))) end end end -- maybe just line(n,str) empty(n,str) visualizers.tablength = 7 visualizers.enabletab = true -- false visualizers.obeyspace = true function buffers.settablength(tablength) visualizers.tablength = tablength and tonumber(tablength) or 7 end visualizers.handlers = visualizers.handlers or { } local handlers = visualizers.handlers function buffers.newvisualizer(name) name = lower(name) local handler = { } handlers[name] = handler return handler end function buffers.getvisualizer(name) name = lower(name) return handlers[name] or buffers.loadvisualizer(name) end function buffers.loadvisualizer(name) name = lower(name) local hn = handlers[name] if hn then return hn else environment.loadluafile("pret-" .. name) local hn = handlers[name] if not hn then -- hn = buffers.newvisualizer(name) hn = handlers[visualizers.defaultname] handlers[name] = hn if trace_visualize then logs.report("buffers","mapping '%s' visualizer onto '%s'",name,visualizers.defaultname) end elseif trace_visualize then logs.report("buffers","loading '%s' visualizer",name) end return hn end end -- was "default", should be set at tex end (todo) local default = buffers.newvisualizer(visualizers.defaultname) --~ print(variables.typing) os.exit() -- will become cleaner local currentvisualizer, currenthandler function buffers.setvisualizer(str) currentvisualizer = lower(str) currenthandler = handlers[currentvisualizer] if currenthandler then -- if trace_visualize then -- logs.report("buffers","enabling specific '%s' visualizer",currentvisualizer) -- end else currentvisualizer = visualizers.defaultname currenthandler = handlers.default -- if trace_visualize then -- logs.report("buffers","enabling default visualizer '%s'",currentvisualizer) -- end end if currenthandler.reset then currenthandler.reset() end end function buffers.resetvisualizer() currentvisualizer = visualizers.defaultname currenthandler = handlers.default if currenthandler.reset then currenthandler.reset() end end buffers.setvisualizer(visualizers.defaultname) function visualizers.reset() end function buffers.doifelsevisualizer(str) cs.testcase((str ~= "") and (handlers[lower(str)] ~= nil)) end -- calling routines, don't change function hooks.begin_of_display() (currenthandler.begin_of_display or default.begin_of_display)(currentvisualizer) end function hooks.end_of_display() (currenthandler.end_of_display or default.end_of_display)() end function hooks.begin_of_inline() (currenthandler.begin_of_inline or default.begin_of_inline)(currentvisualizer) end function hooks.end_of_inline() (currenthandler.end_of_inline or default.end_of_inline)() end function hooks.flush_line(str,nesting) local fl = currenthandler.flush_line if fl then str = gsub(str," *[\n\r]+ *"," ") ; -- semi colon needed fl(str,nesting) else -- gsub done later default.flush_line(str,nesting) end end function hooks.flush_inline(str,nesting) hooks.begin_of_inline() hooks.flush_line(str,nesting) hooks.end_of_inline() end function hooks.begin_of_line(n) (currenthandler.begin_of_line or default.begin_of_line)(n) end function hooks.end_of_line() (currenthandler.end_of_line or default.end_of_line)() end function hooks.empty_line() (currenthandler.empty_line or default.empty_line)() end function hooks.line(str) if visualizers.enabletab then str = string.tabtospace(str,visualizers.tablength) else str = gsub(str,"\t"," ") end return (currenthandler.line or default.line)(str) end -- defaults function default.begin_of_display(currentvisualizer) texsprint(ctxcatcodes,begin_of_display_command,"{",currentvisualizer,"}") end function default.end_of_display() texsprint(ctxcatcodes,end_of_display_command) end function default.begin_of_inline(currentvisualizer) texsprint(ctxcatcodes,begin_of_inline_command,"{",currentvisualizer,"}") end function default.end_of_inline() texsprint(ctxcatcodes,end_of_inline_command) end function default.begin_of_line(n) texsprint(ctxcatcodes, begin_of_line_command,"{",n,"}") end function default.end_of_line() texsprint(ctxcatcodes,end_of_line_command) end function default.empty_line() texsprint(ctxcatcodes,empty_line_command) end function default.line(str) return str end function default.flush_line(str) str = gsub(str," *[\n\r]+ *"," ") if visualizers.obeyspace then for c in utfcharacters(str) do if c == " " then texsprint(ctxcatcodes,"\\obs") else texwrite(c) end end else texwrite(str) end end -- not needed any more local function escaped_token(c) if utffind(c,"^(%a%d)$") then return c elseif c == " " then return "\\obs " else return "\\char" .. utfbyte(c) .. " " end end buffers.escaped_token = escaped_token function buffers.escaped(str) -- use the utfcharacters loop return (utfgsub(str,"(.)", escaped_token)) end -- special one buffers.commands.nested = "\\switchslantedtype " -- todo : utf + faster, direct print and such. no \\char, vrb catcodes, see end function visualizers.flush_nested(str, enable) -- no utf, kind of obsolete mess str = gsub(str," *[\n\r]+ *"," ") local result, c, nested, i = "", "", 0, 1 local commands = buffers.commands -- otherwise wrong commands while i < #str do -- slow c = sub(str,i,i+1) if c == "<<" then nested = nested + 1 if enable then result = result .. "{" .. commands.nested else result = result .. "{" end i = i + 2 elseif c == ">>" then if nested > 0 then nested = nested - 1 result = result .. "}" end i = i + 2 else c = sub(str,i,i) if c == " " then result = result .. "\\obs " elseif find(c,"%a") then result = result .. c else result = result .. "\\char" .. byte(c) .. " " end i = i + 1 end end result = result .. "\\char" .. byte(sub(str,i,i)) .. " " .. rep("}",nested) texsprint(ctxcatcodes,result) end -- handy helpers -- -- \sop[color] switch_of_pretty -- \bop[color] begin_of_pretty -- \eop end_of_pretty -- \obs obeyedspace -- \char special characters buffers.currentcolors = { } function buffers.change_state(n, state) if n then if state ~= n then if state > 0 then texsprint(ctxcatcodes,"\\sop[",buffers.currentcolors[n],"]") else texsprint(ctxcatcodes,"\\bop[",buffers.currentcolors[n],"]") end return n end elseif state > 0 then texsprint(ctxcatcodes,"\\eop") return 0 end return state end function buffers.finish_state(state) if state > 0 then texsprint(ctxcatcodes,"\\eop") return 0 else return state end end buffers.open_nested = rep("\\char"..byte('<').." ",2) buffers.close_nested = rep("\\char"..byte('>').." ",2) function buffers.replace_nested(result) result = gsub(result,buffers.open_nested, "{") result = gsub(result,buffers.close_nested,"}") return result end function buffers.flush_result(result,nested) if nested then texsprint(ctxcatcodes,buffers.replace_nested(concat(result,""))) else texsprint(ctxcatcodes,concat(result,"")) end end -- new function buffers.realign(name,forced_n) -- no, auto, local n, d if type(name) == "string" then d = data[name] if type(d) == "string" then d = d:splitlines() end else d = name -- already a buffer end forced_n = (forced_n == variables.auto and huge) or tonumber(forced_n) if forced_n then for i=1, #d do local spaces = find(d[i],"%S") if not spaces then -- empty line elseif not n then n = spaces elseif spaces == 0 then n = 0 break elseif n > spaces then n = spaces end end if n > 0 then if n > forced_n then n = forced_n end for i=1,#d do d[i] = sub(d[i],n) end end end return d end -- escapes: buffers.set_escape("tex","/BTEX","/ETEX") local function flush_escaped_line(str,pattern,flushline) while true do local a, b, c = match(str,pattern) if a and a ~= "" then flushline(a) end if b and b ~= "" then texsprint(ctxcatcodes,"{",b,"}") end if c then if c == "" then break else str = c end else flushline(str) break end end end function buffers.set_escape(name,pair) if pair and pair ~= "" then local visualizer = buffers.getvisualizer(name) visualizer.normal_flush_line = visualizer.normal_flush_line or visualizer.flush_line if pair == variables.no then visualizer.flush_line = visualizer.normal_flush_line or visualizer.flush_line if trace_visualize then logs.report("buffers","resetting escape range for visualizer '%s'",name) end else local start, stop if pair == variables.yes then start, stop = "/BTEX", "/ETEX" else pair = string.split(pair,",") start, stop = string.esc(pair[1] or ""), string.esc(pair[2] or "") end if start ~= "" then local pattern if stop == "" then pattern = "^(.-)" .. start .. "(.*)(.*)$" else pattern = "^(.-)" .. start .. "(.-)" .. stop .. "(.*)$" end function visualizer.flush_line(str) flush_escaped_line(str,pattern,visualizer.normal_flush_line) end if trace_visualize then logs.report("buffers","setting escape range for visualizer '%s' to %s -> %s",name,start,stop) end elseif trace_visualize then logs.report("buffers","problematic escape specification '%s' for visualizer '%s'",pair,name) end end end end -- THIS WILL BECOME A FRAMEWORK: the problem with prety printing is that -- we deal with snippets and therefore we need tolerant parsing --~ local type = type --~ visualizers = visualizers or { } --~ local function fallback(s) return s end --~ function visualizers.visualize(visualizer,kind,pattern) --~ if type(visualizer) == "table" and type(kind) == "string" then --~ kind = visualizer[kind] or visualizer.default or fallback --~ else --~ kind = fallback --~ end --~ return (lpeg.C(pattern))/kind --~ end --~ local flusher = texio.write --~ local format = string.format --~ local visualizer = { --~ word = function(s) return flusher(format("\\bold{%s}",s)) end, --~ number = function(s) return flusher(format("\\slanted{%s}",s)) end, --~ default = function(s) return flusher(s) end, --~ } --~ local word = lpeg.R("AZ","az")^1 --~ local number = lpeg.R("09")^1 --~ local any = lpeg.P(1) --~ local pattern = lpeg.P { "start", --~ start = ( --~ visualizers.visualize(visualizer,"word",word) + --~ visualizers.visualize(visualizer,"number",number) + --~ visualizers.visualize(visualizer,"default",any) --~ )^1 --~ } --~ str = [[test 123 test $oeps$]] --~ lpegmatch(pattern,str)