if not modules then modules = { } end modules ['lang-rep'] = { version = 1.001, comment = "companion to lang-rep.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- A BachoTeX 2013 experiment, probably not that useful. Eventually I used a simpler -- more generic example. I'm sure no one ever notices of even needs this code. -- -- As a follow up on a question by Alan about special treatment of dropped caps I wonder -- if I can make this one more clever (probably in a few more dev steps). For instance -- injecting nodes or replacing nodes. It's a prelude to a kind of lpeg for nodes, -- although (given experiences so far) we don't really need that. After all, each problem -- is somewhat unique. local type = type local utfbyte, utfsplit = utf.byte, utf.split local P, C, U, Cc, Ct, lpegmatch = lpeg.P, lpeg.C, lpeg.patterns.utf8character, lpeg.Cc, lpeg.Ct, lpeg.match local find = string.find local grouped = P("{") * ( Ct((U/utfbyte-P("}"))^1) + Cc(false) ) * P("}")-- grouped local splitter = Ct((Ct(Cc("discretionary") * grouped * grouped * grouped) + U/utfbyte)^1) local trace_replacements = false trackers.register("languages.replacements", function(v) trace_replacements = v end) local trace_detail = false trackers.register("languages.replacements.detail", function(v) trace_detail = v end) local report_replacement = logs.reporter("languages","replacements") local glyph_code = nodes.nodecodes.glyph local nuts = nodes.nuts local tonut = nuts.tonut local tonode = nuts.tonode local setfield = nuts.setfield local getnext = nuts.getnext local getprev = nuts.getprev local getattr = nuts.getattr local getid = nuts.getid local getchar = nuts.getchar local insert_node_before = nuts.insert_before local remove_node = nuts.remove local copy_node = nuts.copy local flush_list = nuts.flush_list local insert_after = nuts.insert_after local nodepool = nuts.pool local new_glyph = nodepool.glyph local new_disc = nodepool.disc local texsetattribute = tex.setattribute local unsetvalue = attributes.unsetvalue local v_reset = interfaces.variables.reset local replacements = languages.replacements or { } languages.replacements = replacements local a_replacements = attributes.private("replacements") local lists = { } local last = 0 local trees = { } table.setmetatableindex(lists,function(lists,name) last = last + 1 local list = { } local data = { name = name, list = list, attribute = last } lists[last] = data lists[name] = data trees[last] = list return data end) -- todo: glue kern local function add(root,word,replacement) local list = utfsplit(word,true) local size = #list for i=1,size do local l = utfbyte(list[i]) if not root[l] then root[l] = { } end if i == size then -- local newlist = utfsplit(replacement,true) -- for i=1,#newlist do -- newlist[i] = utfbyte(newlist[i]) -- end local special = find(replacement,"{") local newlist = lpegmatch(splitter,replacement) -- root[l].final = { word = word, replacement = replacement, oldlength = size, newcodes = newlist, special = special, } end root = root[l] end end function replacements.add(category,word,replacement) local root = lists[category].list if type(word) == "table" then for word, replacement in next, word do add(root,word,replacement) end else add(root,word,replacement or "") end end local function hit(a,head) local tree = trees[a] if tree then local root = tree[getchar(head)] if root then local current = getnext(head) local lastrun = false local lastfinal = false while current and getid(current) == glyph_code do local newroot = root[getchar(current)] if not newroot then return lastrun, lastfinal else local final = newroot.final if final then if trace_detail then report_replacement("hitting word %a, replacement %a",final.word,final.replacement) end lastrun = current lastfinal = final else root = newroot end end current = getnext(current) end if lastrun then return lastrun, lastfinal end end end end local function tonodes(list,template) local head, current for i=1,#list do local new = copy_node(template) setfield(new,"char",list[i]) if head then head, current = insert_after(head,current,new) else head, current = new, new end end return head end function replacements.handler(head) head = tonut(head) local current = head local done = false while current do if getid(current) == glyph_code then local a = getattr(current,a_replacements) if a then local last, final = hit(a,current) if last then local oldlength = final.oldlength local newcodes = final.newcodes local newlength = #newcodes if trace_replacement then report_replacement("replacing word %a by %a",final.word,final.replacement) end if final.special then -- easier is to delete and insert (a simple callout to tex would be more efficient) -- maybe just walk over a replacement string instead local prev = getprev(current) local next = getnext(last) local list = current setfield(last,"next",nil) setfield(prev,"next",next) if next then setfield(next,"prev",prev) end current = prev if not current then head = nil end for i=1,newlength do local codes = newcodes[i] local new = nil if type(codes) == "table" then local method = codes[1] if method == "discretionary" then local pre, post, replace = codes[2], codes[3], codes[4] new = new_disc() if pre then setfield(new,"pre",tonodes(pre,last)) end if post then setfield(new,"post",tonodes(post,last)) end if replace then setfield(new,"replace",tonodes(replace,last)) end else -- todo end else new = copy_node(last) setfield(new,"char",codes) end if new then head, current = insert_after(head,current,new) end end flush_list(list) elseif oldlength == newlength then -- #old == #new for i=1,newlength do setfield(current,"char",newcodes[i]) current = getnext(current) end elseif oldlength < newlength then -- #old < #new for i=1,newlength-oldlength do local n = copy_node(current) setfield(n,"char",newcodes[i]) head, current = insert_node_before(head,current,n) current = getnext(current) end for i=newlength-oldlength+1,newlength do setfield(current,"char",newcodes[i]) current = getnext(current) end else -- #old > #new for i=1,oldlength-newlength do head, current = remove_node(head,current,true) end for i=1,newlength do setfield(current,"char",newcodes[i]) current = getnext(current) end end done = true end end end current = getnext(current) end return tonode(head), done end local enabled = false function replacements.set(n) -- number or 'reset' if n == v_reset then n = unsetvalue else n = lists[n].attribute if not enabled then nodes.tasks.enableaction("processors","languages.replacements.handler") if trace_replacements then report_replacement("enabling replacement handler") end enabled = true end end texsetattribute(a_replacements,n) end -- interface commands.setreplacements = replacements.set commands.addreplacements = replacements.add