From c161b7d6fe142231346cc1844e6e27c0ab7718c1 Mon Sep 17 00:00:00 2001 From: Hans Hagen Date: Fri, 16 Sep 2022 15:53:42 +0200 Subject: 2022-09-16 14:41:00 --- source/luametatex/tools/mp.patch.lua | 66 ++++ source/luametatex/tools/mtx-wtoc.lua | 667 +++++++++++++++++++++++++++++++++++ 2 files changed, 733 insertions(+) create mode 100644 source/luametatex/tools/mp.patch.lua create mode 100644 source/luametatex/tools/mtx-wtoc.lua (limited to 'source/luametatex/tools') diff --git a/source/luametatex/tools/mp.patch.lua b/source/luametatex/tools/mp.patch.lua new file mode 100644 index 000000000..116f4f0a7 --- /dev/null +++ b/source/luametatex/tools/mp.patch.lua @@ -0,0 +1,66 @@ +local gsub = string.gsub + +return { + + action = function(data,report) + + if true then + -- we have no patches + return data + end + + if not report then + report = print -- let it look bad + end + + local n, m = 0, 0 + + statistics.starttiming() + + local function okay(i,str) + n = n + 1 + report("patch %02i ok : %s",i,str) + end + + -- not used + + -- data = gsub(data,"(#include )",function(s) + -- okay(1,"zlib header file commented") + -- return "/* " .. s .. "*/" + -- end,1) + -- + -- data = gsub(data,"(#include )",function(s) + -- okay(2,"png header file commented") + -- return "/* " .. s .. "*/" + -- end,1) + + -- patched + + -- data = gsub(data,"calloc%((%w+),%s*(%w+)%)",function(n,m) + -- okay(3,"calloc replaced by malloc") + -- return "malloc(" .. n .. "*" .. m .. ")" + -- end,1) + + -- not used + + -- data = gsub(data,"(mp_show_library_versions%s*%(%s*%w+%s*%)%s*)%b{}",function(s) + -- okay(4,"reporting library versions removed") + -- return s .. "\n{\n}" + -- end,1) + + -- data = gsub(data,"#if INTEGER_MAX == LONG_MAX",function(s) + -- okay(5,"fix INTEGER_TYPE") + -- return "#if INTEGER_TYPE == long" + -- end,1) + + -- done + + statistics.stoptiming() + + report("patching time: %s", statistics.elapsedtime()) + report("patches left : %i", m - n) + + return data + end + +} diff --git a/source/luametatex/tools/mtx-wtoc.lua b/source/luametatex/tools/mtx-wtoc.lua new file mode 100644 index 000000000..a28ff0f2d --- /dev/null +++ b/source/luametatex/tools/mtx-wtoc.lua @@ -0,0 +1,667 @@ +if not modules then modules = { } end modules ['mtx-wtoc'] = { + version = 1.001, + comment = "a hack to avoid a dependency on cweb / web2c", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- This is a hack. When I have time and motivation I'll make a better version. Sorry +-- for the mess. It's not an example of proper coding. It's also not that efficient. +-- It is not general purpose too, just a helper for luametatex in order to not be +-- dependent on installing the cweb infrastructure (which normally gets compiled as +-- part of the complex tl build). Okay, we do have a dependency on luametatex as lua +-- runner although this script can be easily turned into a pure lua variant (not +-- needing mtxrun helpers). If really needed one could build luametatex without +-- mplib and then do the first bootstrap, but there's always a c to start with +-- anyway; only when mp.w cum suis get updated we need to convert. +-- +-- The w files get converted to into in .25 seconds which is not that bad. + +-- @, @/ @| @# @+ @; @[ @] +-- @.text @>(monospaced) | @:text @>(macro driven) | @= verbose@> | @! underlined @>| @t text @> (hbox) | @q ignored @> +-- @^index@> +-- @f text renderclass +-- @s idem | @p idem | @& strip (spaces before) | @h +-- @'char' (ascii code) +-- @l nonascii +-- @x @y @z changefile | @i webfile +-- @* title. +-- @ explanation (not ok ... stops at outer @ +-- +-- The comment option doesn't really work so one needs to do some manual work +-- afterwards but I'll only use that when we move away from w files. + +local next = next +local lower, find, gsub = string.lower, string.find, string.gsub +local topattern = string.topattern +local striplines = utilities.strings.striplines +local concat = table.concat + +local P, R, S, C, Cs, Ct, Cc = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Ct, lpeg.Cc +local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns + +local newline = lpegpatterns.newline +local space = lpegpatterns.space -- S(" \n\r\t\f\v") +local restofline = (1-newline)^0 + +local cweb = { } + +-- We have several ways to look at and filter the data so we have different +-- lpegs. The output looks ugly but that is the whole idea I think as cweb. + +local report = logs.reporter("cweb to normal c") +local verbose = false +-- local verbose = true + +-- common + +local p_beginofweb = P("@") +local p_endofweb = P("@>") +local p_noweb = (1-p_endofweb)^1 +local p_squote = P("'") +local p_period = P(".") +local p_noperiod = (1-p_period)^1 +local p_spacing = space^1 +local p_nospacing = (1-space)^1 +local p_equal = P("=") +local p_noequal = (1-p_equal)^1 +local p_rest = P(1) +local p_escape = p_beginofweb * p_beginofweb +local c_unescape = p_escape / "@" +local p_structure = p_beginofweb * (S("*dc \t\n\r")) +local p_content = (p_escape + (1 - p_structure))^1 +local c_noweb = C(p_noweb) +local c_content = C(p_content) +local c_nospacing = C(p_nospacing) +local c_noperiod = C(p_noperiod) + +local function clean(s) + s = lower(s) + s = gsub(s,"%s+"," ") + s = gsub(s,"%s+$","") + s = gsub(s,"%s*%.%.%.$","...") + return s +end + +local cleanup do + + local p_ignore_1 = S(",/|#+;[]sp&") + local p_ignore_2 = S("^.:=!tq") * p_noweb * p_endofweb + local p_ignore_3 = S("f") * p_spacing * p_nospacing * p_spacing * p_nospacing + local p_ignore_4 = p_squote * (1-p_squote)^0 * p_squote + local p_ignore_5 = S("l") * p_spacing * p_nospacing + + local p_replace_1 = P("h") / "\n@
\n" + local p_replace_2 = (P("#") * space^0) / "\n" + + local p_strip_1 = (newline * space^1) / "\n" + + local p_whatever = ( + p_beginofweb / "" + * ( + p_replace_1 + + p_replace_2 + + Cs( + p_ignore_1 + + p_ignore_2 + + p_ignore_3 + + p_ignore_4 + + p_ignore_5 + ) / "" + ) + ) + + local p_whatever = + (newline * space^1) / "" + * p_whatever + * (space^0 * newline) / "\n" + + p_whatever + + local pattern = Cs ( ( + p_escape + + p_whatever + + p_rest + )^1 ) + + cleanup = function(s) + return lpegmatch(pattern,s) + end + +end + +local finalize do + + -- The reason why we need to strip leading spaces it that compilers complain about this: + -- + -- if (what) + -- this; + -- that; + -- + -- with the 'that' being confusingly indented. The fact that it has to be mentioned is of + -- course a side effect of compact c coding which can introduce 'errors'. Now, this + -- 'confusing' indentatoin is a side effect of + -- + -- if (what) + -- this; + -- @; + -- + -- + -- or actually: + -- + -- @; + -- this; + -- @; + -- + -- which then lead to the conclusion that @ should not be indented! But ... cweb + -- removes all leading spaces in lines, so that obscured the issue. Bad or not? It is + -- anyway a very strong argument for careful coding and matbe using some more { } in case + -- of snippets because web2c obscures some warnings! + + ----- strip_display = (P("/*") * (1 - P("*/"))^1 * P("*/")) / " " + local strip_inline = (P("//") * (1 - newline)^0) / "" + local keep_inline = P("//") * space^0 * P("fall through") + + local strip_display = (P("/*") * (1 - P("*/"))^1 * P("*/")) + + strip_display = + (newline * space^0 * strip_display * newline) / "\n" + + strip_display / " " + + local strip_spaces = (space^1 * newline) / "\n" + ----- strip_lines = (space^0 * newline * space^0)^3 / "\n\n" + local strip_lines = newline * (space^0 * newline)^3 / "\n\n" + + local strip_empties = newline/"" * newline * space^1 * P("}") + + space^2 * P("}") * (newline * space^0 * newline / "\n") + + space^2 * R("AZ") * R("AZ","__","09")^1 * P(":") * (space^0 * newline * space^0 * newline / "\n") + + local finalize_0 = Cs((c_unescape + p_rest)^0) + local finalize_1 = Cs((strip_display + keep_inline + strip_inline + c_unescape + p_rest)^0) + local finalize_2 = Cs((strip_lines + p_rest)^0) + local finalize_3 = Cs((c_unescape + strip_spaces + p_rest)^1) + local finalize_4 = Cs((c_unescape + strip_empties + p_rest)^1) + + finalize = function(s,keepcomment) + s = keepcomment and lpegmatch(finalize_0,s) or lpegmatch(finalize_1,s) + s = lpegmatch(finalize_2,s) + s = lpegmatch(finalize_3,s) + s = lpegmatch(finalize_4,s) + -- maybe also empty lines after a LABEL: + return s + end + +end + +local function fixdefine(s) + s = finalize(s) + s = gsub(s,"[\n\r\t ]+$","") + s = gsub(s,"[\t ]*[\n\r]+"," \\\n") + return s +end + +local function addcomment(c,s) + if c ~= "" then + c = striplines(c) + if find(c,"\n") then + c = "\n\n/*\n" .. c .. "\n*/\n\n" + else + c = "\n\n/* " .. c .. " */\n\n" + end + return c .. s + else + return s + end +end + +do + + local result = { } + + local p_nothing = Cc("") + local p_comment = Cs(((p_beginofweb * (space + newline + P("*")))/"" * c_content)^1) + + p_nothing + + local p_title = c_noperiod * (p_period/"") + local p_skipspace = newline + space + local c_skipspace = p_skipspace / "" + local c_title = c_skipspace * p_title * c_skipspace * Cc("\n\n") + local c_obeyspace = p_skipspace / "\n\n" + + local p_comment = Cs( ( + ((p_beginofweb * p_skipspace)/"" * c_content) + + ((p_beginofweb * P("*")^1 )/"" * c_title * c_content) + + c_obeyspace + )^1 ) + + p_nothing + + local p_define = C(p_beginofweb * P("d")) * Cs(Cc("# define ") * p_content) + local p_code = C(p_beginofweb * P("c")) * c_content + local p_header = C(p_beginofweb * P("(")) * c_noweb * C(p_endofweb * p_equal) * c_content + local p_snippet = C(p_beginofweb * S("<")) * c_noweb * C(p_endofweb * p_equal) * c_content + local p_reference = C(p_beginofweb * S("<")) * c_noweb * C(p_endofweb ) * #(1-p_equal) + local p_preset = p_beginofweb * S("<") * c_noweb * p_endofweb + + local p_indent = C(space^0) + local p_reference = p_indent * p_reference + + local p_c_define = p_comment * p_define + local p_c_code = p_comment * p_code + local p_c_header = p_comment * p_header + local p_c_snippet = p_comment * p_snippet + + local p_n_define = p_nothing * p_define + local p_n_code = p_nothing * p_code + local p_n_header = p_nothing * p_header + local p_n_snippet = p_nothing * p_snippet + + local function preset(tag) + tag = clean(tag) + if find(tag,"%.%.%.$") then + result.dottags[tag] = false + end + result.alltags[tag] = tag + end + + local p_preset = (p_preset / preset + p_rest)^1 + + -- we can have both definitions and references with trailing ... and this is imo + -- a rather error prone feature: i'd expect the definitions to be the expanded one + -- so that references can be shorter ... anyway, we're stuck with this (also with + -- inconsistent usage of "...", " ...", "... " and such. + + local function getpresets(data) + + local alltags = result.alltags + local dottags = result.dottags + + lpegmatch(p_preset,data) + + local list = table.keys(alltags) + + table.sort(list,function(a,b) + a = gsub(a,"%.+$"," ") -- slow + b = gsub(b,"%.+$"," ") -- slow + return a < b + end) + + for k, v in next, dottags do + local s = gsub(k,"%.%.%.$","") + local p = "^" .. topattern(s,false,"all") + for i=1,#list do + local a = list[i] + if a ~= k and find(a,p) then + dottags[k] = true + alltags[k] = a + end + end + end + + for k, v in next, alltags do + local t = alltags[v] + if t then + alltags[k] = t + end + end + + end + + local function addsnippet(c,b,tag,e,s) + if c ~= "" then + s = addcomment(c,s) + end + local alltags = result.alltags + local snippets = result.snippets + local tag = clean(tag) + local name = alltags[tag] + if snippets[name] then + if verbose then + report("add snippet : %s",name) + end + s = snippets[name] .. "\n" .. s + else + if verbose then + report("new snippet : %s",name) + end + s = "/* snippet: " .. name .. " */\n" .. s + end + snippets[name] = s + result.nofsnippets = result.nofsnippets + 1 + return "" + end + + local function addheader(c,b,tag,e,s) + if c ~= "" then + s = addcomment(c,s) + end + local headers = result.headers + local headerorder = result.headerorder + if headers[tag] then + if verbose then + report("add header : %s",tag) + end + s = headers[tag] .. "\n" .. s + else + if verbose then + report("new header : %s",tag) + end + headerorder[#headerorder+1] = tag + end + headers[tag] = s + result.nofheaders = result.nofheaders + 1 + return "" + end + + local function addcode(c,b,s) + if c ~= "" then + s = addcomment(c,s) + end + local nofcode = result.nofcode + 1 + result.codes[nofcode] = s + result.nofcode = nofcode + return "" + end + + local function adddefine(c,b,s) + s = fixdefine(s) + if c ~= "" then + s = addcomment(c,s) + end + nofdefines = result.nofdefines + 1 + result.defines[nofdefines] = s + result.nofdefines = nofdefines + return "" + end + + local p_n_collect_1 = Cs ( ( + p_n_snippet / addsnippet + + p_n_header / addheader + + p_rest + )^1 ) + + local p_n_collect_2 = Cs ( ( + p_n_code / addcode + + p_n_define / adddefine + + p_rest + )^1 ) + + local p_c_collect_1 = Cs ( ( + p_c_snippet / addsnippet + + p_c_header / addheader + + p_rest + )^1 ) + + local p_c_collect_2 = Cs ( ( + p_c_code / addcode + + p_c_define / adddefine + + p_rest + )^1 ) + + local function getcontent_1(data) + return lpegmatch(result.keepcomment and p_c_collect_1 or p_n_collect_1,data) + end + + local function getcontent_2(data) + return lpegmatch(result.keepcomment and p_c_collect_2 or p_n_collect_2,data) + end + + -- local function dereference(b,tag,e) + local function dereference(indent,b,tag,e) + local tag = clean(tag) + local name = result.alltags[tag] + if name then + local data = result.snippets[name] + if data then + result.usedsnippets[name] = true + result.unresolved[name] = nil + result.nofresolved = result.nofresolved + 1 + if verbose then + report("resolved : %s",tag) + end + -- return data + return indent .. string.gsub(data,"[\n\r]+","\n" .. indent) + elseif tag == "header goes here" then + return "@
" + else + result.nofunresolved = result.nofunresolved + 1 + result.unresolved[name] = name + report("unresolved : %s",tag) + return "\n/* unresolved: " .. tag .. " */\n" + end + else + report("fatal error : invalid tag") + os.exit() + end + end + + local p_resolve = Cs((p_reference / dereference + p_rest)^1) + + local function resolve(data) + local iteration = 0 + while true do + iteration = iteration + 1 + if data == "" then + if verbose then + report("warning : empty code at iteration %i",iteration) + end + return data + else + local done = lpegmatch(p_resolve,data) + if not done then + report("fatal error : invalid code at iteration %i",iteration) + os.exit() + elseif done == data then + return done + else + data = done + end + end + end + return data + end + + local function patch(filename,data) + local patchfile = file.replacesuffix(filename,"patch.lua") + local patches = table.load(patchfile) + if not patches then + patchfile = file.basename(patchfile) + patches = table.load(patchfile) + end + if patches then + local action = patches.action + if type(action) == "function" then + if verbose then + report("patching : %s", filename) + end + data = action(data,report) + end + end + return data + end + + function cweb.convert(filename,target) + + statistics.starttiming(filename) + + result = { + snippets = { }, + usedsnippets = { }, + alltags = { }, + dottags = { }, + headers = { }, + headerorder = { }, + defines = { }, + codes = { }, + unresolved = { }, + nofsnippets = 0, + nofheaders = 0, + nofdefines = 0, + nofcode = 0, + nofresolved = 0, + nofunresolved = 0, + + -- keepcomment = true, - not okay but good enough for a rough initial + + } + + local data = io.loaddata(filename) + local banner = '/* This file is generated by "mtxrun --script "mtx-wtoc.lua" from the metapost cweb files. */\n\n' + + report("main file : %s", filename) + report("main size : %i bytes", #data) + + data = patch(filename,data) + data = cleanup(data) + + result.alltags["header goes here"] = clean("header goes here") + + getpresets(data) -- into result + + data = getcontent_1(data) -- into result + data = getcontent_2(data) -- into result + + result.defines = concat(result.defines,"\n\n") + result.codes = concat(result.codes,"\n\n") + + result.snippets["header goes here"] = result.defines + + result.codes = resolve(result.codes) + result.codes = finalize(result.codes,result.keepcomment) + + for i=1,#result.headerorder do + local name = result.headerorder[i] + local code = result.headers[name] + report("found header : %s", name) + code = resolve(code) + code = finalize(code,result.keepcomment) + result.headers[name] = code + end + + local fullname = file.join(target,file.addsuffix(file.nameonly(filename),"c")) + + report("result file : %s", fullname) + report("result size : %i bytes", result.codes and #result.codes or 0) + + if result.keepcomment then + report("unprocessed : %i bytes", #data) + print(data) + end + + io.savedata(fullname,banner .. result.codes) + + -- save header files + + for i=1,#result.headerorder do + local name = result.headerorder[i] + local code = result.headers[name] + local fullname = file.join(target,name) + report("extra file %i : %s", i, fullname) + report("extra size %i : %i bytes", i, #code) + io.savedata(fullname,banner .. code) + end + + -- some statistics + + report("nofsnippets : %i", result.nofsnippets) + report("nofheaders : %i", result.nofheaders) + report("nofdefines : %i", result.nofdefines) + report("nofcode : %i", result.nofcode) + report("nofresolved : %i", result.nofresolved) + report("nofunresolved: %i", result.nofunresolved) + + for tag in table.sortedhash(result.unresolved) do + report("fuzzy tag : %s",tag) + end + + for tag in table.sortedhash(result.snippets) do + if not result.usedsnippets[tag] then + report("unused tag : %s",tag) + end + end + + statistics.stoptiming(filename) + + report("run time : %s", statistics.elapsedtime(filename)) + + end + +end + +function cweb.convertfiles(source,target) + + report("source path : %s", source) + report("target path : %s", target) + + report() + + local files = dir.glob(file.join(source,"*.w")) + + statistics.starttiming(files) + for i=1,#files do + cweb.convert(files[i],target) + report() + end + statistics.stoptiming(files) + + report("total time : %s", statistics.elapsedtime(files)) + +end + +-- We sort of hard code the files that we convert. In principle we can make a more +-- general converter but I don't need to convert cweb files other than these. The +-- converter tries to make the H/C files look kind of good so that I can expect then +-- in (for instance) Visual Studio. + +local source = file.join(dir.current(),"../source/mp/mpw") +local target = file.join(dir.current(),"../source/mp/mpc") + +-- local source = file.join("e:/luatex/luatex-experimental-export/source/texk/web2c/mplibdir/") +-- local target = file.join("e:/luatex/luatex-experimental-export/source/texk/web2c") + +cweb.convertfiles(source,target) + +-- -- inefficient but good enough +-- +-- local function strip(s) +-- +-- local newline = lpeg.patterns.newline +-- local spaces = S(" \t") +-- +-- local strip_comment = (P("/*") * (1-P("*/"))^1 * P("*/")) / "" +-- local strip_line = (P("#line") * (1 - newline)^1 * newline * spaces^0) / "" +-- local strip_spaces = spaces^1 / " " +-- local strip_trailing = (P("//") * (1 - newline)^0) / "" +-- local strip_final = (spaces^0 * P("\\") * spaces^0) / "" * newline +-- local strip_lines = (spaces^0 / "") * newline^1 * (spaces^0 / "") / "\n" +-- local strip_weird = (spaces + newline)^0 * (P("{") * (spaces + newline)^0 * P("}")) * (spaces + newline)^0 / "{}\n" +-- local strip_singles = (spaces^0 / "") * S("^`'\"&%|()[]#?!<>\\/{}=,.*+-;:") * (spaces^0 / "") +-- +-- local pattern_1 = Cs ( ( +-- strip_singles + +-- P(1) +-- )^1 ) +-- +-- local pattern_2 = Cs ( ( +-- strip_weird + +-- strip_comment + +-- strip_line + +-- strip_trailing + +-- strip_lines + +-- strip_final + +-- strip_spaces + +-- P(1) +-- )^1 ) +-- +-- while true do +-- local r = lpegmatch(pattern_1,s) +-- local r = lpegmatch(pattern_2,r) +-- if s == r then +-- break +-- else +-- s = r +-- end +-- end +-- +-- return s +-- +-- end -- cgit v1.2.3