mtx-check.lua /size: 8266 b    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['mtx-check'] = {
2    version   = 1.001,
3    comment   = "companion to mtxrun.lua",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9local P, R, S, V, C, CP, CC, lpegmatch = lpeg.P, lpeg.R, lpeg.S, lpeg.V, lpeg.C, lpeg.Cp, lpeg.Cc, lpeg.match
10local gsub, sub, format = string.gsub, string.sub, string.format
11local insert, remove = table.insert, table.remove
12
13local helpinfo = [[
14<?xml version="1.0"?>
15<application>
16 <metadata>
17  <entry name="name">mtx-check</entry>
18  <entry name="detail">Basic ConTeXt Syntax Checking</entry>
19  <entry name="version">0.10</entry>
20 </metadata>
21 <flags>
22  <category name="basic">
23   <subcategory>
24    <flag name="check"><short>check tex file for errors</short></flag>
25   </subcategory>
26  </category>
27 </flags>
28</application>
29]]
30
31local application = logs.application {
32    name     = "mtx-check",
33    banner   = "Basic ConTeXt Syntax Checking 0.10",
34    helpinfo = helpinfo,
35}
36
37local report      = application.report
38
39scripts           = scripts         or { }
40scripts.checker   = scripts.checker or { }
41
42local validator   = { }
43
44validator.n       = 1
45validator.errors  = { }
46validator.trace   = false
47validator.direct  = false
48
49validator.printer = print
50validator.tracer  = print
51
52local message = function(position, kind, extra)
53    local ve = validator.errors
54    ve[#ve+1] = { kind, position, validator.n, extra }
55    if validator.direct then
56        position = position or "eof"
57        if extra then
58            validator.printer(format("%s error at position %s (line %s) (%s)",kind,position,validator.n,extra))
59        else
60            validator.printer(format("%s error at position %s (line %s)",kind,position,validator.n))
61        end
62    end
63end
64
65local progress = function(position, data, kind)
66    if validator.trace then
67        validator.tracer(format("%s at position %s: %s", kind, position, data or ""))
68    end
69end
70
71local i_m, d_m     = P("$"), P("$$")
72local l_s, r_s     = P("["), P("]")
73local l_g, r_g     = P("{"), P("}")
74
75local okay         = lpeg.P("{[}") + lpeg.P("{]}")
76
77local esc          = P("\\")
78local space        = S(" \t\f\v")
79local newline      = lpeg.patterns.newline
80
81local line         = newline / function() validator.n = validator.n + 1 end
82
83local startluacode = P("\\startluacode")
84local stopluacode  = P("\\stopluacode")
85
86local somecode     = startluacode * (1-stopluacode)^1 * stopluacode
87
88local stack        = { }
89
90local function push(p,s)
91-- print("start",p,s)
92    insert(stack,{ p, s, validator.n })
93end
94
95local function pop(p,s)
96-- print("stop",p,s)
97    local top = remove(stack)
98    if not top then
99        message(p,"missing start")
100    elseif top[2] ~= s then
101        message(p,"missing stop",format("see line %s",top[3]))
102    else
103        -- okay
104    end
105end
106
107local cstoken = R("az","AZ","\127\255")
108
109local start   = CP() * P("\\start") * C(cstoken^0) / push
110local stop    = CP() * P("\\stop")  * C(cstoken^0) / pop
111
112local contextgrammar = P { "tokens",
113    ["tokens"]      = (V("ignore") + V("start") + V("stop") + V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + V("errors") + 1)^0,
114    ["start"]       = start,
115    ["stop"]        = stop,
116    ["whatever"]    = line + esc * 1 + C(P("%") * (1-line)^0),
117    ["grouped"]     = l_g * (V("start") + V("stop") + V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + line + (1 - l_g - r_g))^0 * r_g,
118    ["setup"]       = l_s * (okay + V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + (1 - l_s - r_s))^0 * r_s,
119    ["display"]     = d_m * (V("whatever") + V("grouped") + (1 - d_m))^0 * d_m,
120    ["inline"]      = i_m * (V("whatever") + V("grouped") + (1 - i_m))^0 * i_m,
121    ["errors"]      = V("gerror") + V("serror") + V("derror") + V("ierror"),
122    ["gerror"]      = CP() * (l_g + r_g) * CC("grouping error") / message,
123    ["serror"]      = CP() * (l_s + r_g) * CC("setup error") / message,
124    ["derror"]      = CP() * d_m * CC("display math error") / message,
125    ["ierror"]      = CP() * i_m * CC("inline math error") / message,
126    ["ignore"]      = somecode,
127}
128
129-- metafun variant
130
131local function push(p,s)
132    insert(stack,{ p, s, validator.n })
133end
134
135local function pop(p,s)
136    local top = remove(stack)
137    if not top then
138        message(p,"missing <some>def")
139    end
140end
141
142local function finish(p)
143    local bot = stack[1]
144    if bot then
145        message(false,format("missing enddef for %s",bot[2]),format("see line %s",bot[3]))
146    end
147    stack = { }
148end
149
150local l_b, r_b  = P("["), P("]")
151local l_g, r_g  = P("{"), P("}")
152local l_p, r_p  = P("("), P(")")
153
154local start     = CP() * C( P("vardef") + P("primarydef") + P("secondarydef") + P("tertiarydef") + P("def") ) / push
155local stop      = CP() * C( P("enddef") )                                                                     / pop
156
157local dstring   = P('"') * (1-P('"'))^0 * P('"')
158local semicolon = P(";")
159
160local separator = line + space + semicolon
161
162-- todo: start/stop also in ()
163
164local metafungrammar = P { "tokens",
165    ["tokens"]      = (V("start") + V("stop") + V("string") + V("whatever") + V("braces") + V("brackets") + V("parentheses") + V("errors") + 1)^0
166                    * (CP() / finish),
167    ["start"]       = separator * start * #separator,
168    ["stop"]        = separator * stop  * #separator,
169    ["string"]      = dstring,
170    ["whatever"]    = line + C(P("%") * (1-line)^0),
171    ["braces"]      = l_g * (V("whatever") + V("string") + V("braces") + V("brackets") + V("parentheses") + (1 - l_g - r_g))^0 * r_g,
172    ["brackets"]    = l_b * (V("whatever") + V("string") + V("braces") + V("brackets") + V("parentheses") + (1 - l_b - r_b))^0 * r_b,
173    ["parentheses"] = l_p * (V("whatever") + V("string") + V("braces") + V("brackets") + V("parentheses") + (1 - l_p - r_p))^0 * r_p,
174    ["errors"]      = V("gerror") + V("berror") + V("perror"),
175    ["gerror"]      = CP() * (l_g + r_g) * CC("braces error") / message,
176    ["berror"]      = CP() * (l_b + r_b) * CC("brackets error") / message,
177    ["perror"]      = CP() * (l_p + r_p) * CC("parentheses error") / message,
178}
179
180local grammars = {
181    mp   = metafungrammar,
182    mpii = metafungrammar,
183    mpiv = metafungrammar,
184    tex  = contextgrammar,
185    mkii = contextgrammar,
186    mkiv = contextgrammar,
187    mkvi = contextgrammar,
188    mkil = contextgrammar,
189    mkli = contextgrammar,
190}
191
192function validator.check(str,filetype)
193    validator.n = 1
194    validator.errors = { }
195    local grammar = grammars[filetype] or grammars.tex
196    lpegmatch(grammar,str)
197end
198
199--~ str = [[
200--~ a{oeps {oe\{\}ps} }
201--~ test { oeps \} \[\] oeps \setupxxx[oeps=bla]}
202--~ test $$ \hbox{$ oeps \} \[\] oeps $} $$
203--~ {$x\$xx$ $
204--~ ]]
205--~ str = string.rep(str,10)
206
207local remapper = {
208    ["\n"] = " <lf> ",
209    ["\r"] = " <cr> ",
210    ["\t"] = " <tab> ",
211}
212
213function scripts.checker.check(filename)
214    local str = io.loaddata(filename)
215    if str then
216        validator.check(str,file.suffix(filename))
217        local errors = validator.errors
218        if #errors > 0 then
219            for k=1,#errors do
220                local v = errors[k]
221                local kind, position, line, extra = v[1], v[2], v[3], v[4]
222                if not position then
223                    position = #str
224                end
225                local data = sub(str,position-30,position+30)
226                data = gsub(data,".", remapper)
227                data = gsub(data,"^ *","")
228                if extra then
229                    print(format("% 5i  %-10s  %s (%s)", line, kind, data, extra))
230                else
231                    print(format("% 5i  %-10s  %s", line, kind, data))
232                end
233            end
234        else
235            print("no error")
236        end
237    else
238        print("no file")
239    end
240end
241
242if environment.argument("check") then
243    scripts.checker.check(environment.files[1])
244elseif environment.argument("help") then
245    application.help()
246elseif environment.argument("exporthelp") then
247    application.export(environment.argument("exporthelp"),environment.files[1])
248elseif environment.files[1] then
249    scripts.checker.check(environment.files[1])
250else
251    application.help()
252end
253
254