3 local lpeg
= require('lpeg')
4 local wcwidth
= require('wcwidth')
5 local utf8
= require('utf8')
7 local V
, R
, P
, S
, C
, Ct
, Cg
, Cmt
, Cb
=
8 lpeg
.V
, lpeg
.R
, lpeg
.P
, lpeg
.S
, lpeg
.C
, lpeg
.Ct
, lpeg
.Cg
, lpeg
.Cmt
,
14 if not l
then return {} end
16 for _
, v
in ipairs(l
) do s
[v
] = true end
20 function load_conf (p
) pcall(function () conf
= dofile(p
) end) end
24 load_conf('/usr/share/llf/llfrc.lua')
26 if os
.getenv('HOME') then
27 load_conf(os
.getenv('HOME')..'/.config/llf/llfrc.lua')
30 if os
.getenv('XDG_CONFIG_HOME') then
31 load_conf(os
.getenv('XDG_CONFIG_HOME')..'/llf/llfrc.lua')
35 elseif #arg
== 2 and arg
[1] == '-c' then
36 if pcall(function () conf
= dofile(arg
[2]) end) then
38 io
.stderr
:write("couldn't load config file "..arg
[2].."\n")
42 io
.write('Usage: '..arg
[0]..' [ -c /path/to/config/file ]\n')
47 io
.stderr
:write("config file appears malformed\n")
51 conf
.inline_environments
= to_set(conf
.inline_environments
)
52 conf
.own_line_controlseqs
= to_set(conf
.own_line_controlseqs
)
53 conf
.start_line_controlseqs
= to_set(conf
.start_line_controlseqs
)
54 conf
.tabular_like_environments
= to_set(conf
.tabular_like_environments
)
55 conf
.own_paragraphs
= to_set(conf
.own_paragraphs
)
56 conf
.indent_incrementers
= to_set(conf
.indent_incrementers
)
57 conf
.indent_decrementers
= to_set(conf
.indent_decrementers
)
58 conf
.max_len
= math
.max(conf
.max_len
, 2)
61 function f (s
, i
, delim
, c
) return delim
== c
end
63 constructed_verbatims
= (Cg("\\verb", "beginbit") *
64 Cg(V
"verbposdelims", "delim") *
65 Cg((P(1) - Cmt(Cb("delim") * C(1), f
))^
0, "content") *
66 Cg(Cmt(Cb("delim") * C(1), f
), "endbit"))
67 for _
, n
in ipairs(conf
.verbatim_like_environments
) do
68 constructed_verbatims
= constructed_verbatims
+
69 (Cg("\\begin{"..n
.."}", "beginbit") *
70 Cg((P(1) - #P("\\end{"..n
.."}"))^
0, "content") *
71 Cg("\\end{"..n
.."}", "endbit"))
77 comment
= (P
"%" * (P(1) - P
"\n")^
0),
78 controlname
= C((P
"left" + P
"right") *
79 ((P(1) - P
"\\") + (V
"controlseq"))) +
80 (R
"az" + R
"AZ" + R
"09" + "@")^
1 * P
"*" *
81 (#(1 - (R
"az" + R
"AZ" + R
"09" + S
"@*") + -1)) +
82 (R
"az" + R
"AZ" + R
"09" + "@")^
1 *
83 (#(1 - (R
"az" + R
"AZ" + R
"09") + -1)),
84 controlseq
= Ct(P
"\\" * Cg(V
"controlname", "name") *
85 Cg(Ct((V
"subbody")^
0), "args")),
86 verbposdelims
= P(P(1) - P
"*"),
87 verbdelim
= C(V
"verbposdelims"),
88 verbatimbody
= Ct((Cg("%noformat{\n", "beginbit") *
89 Cg((P(1) - #P
"%}noformat\n")^
0, "content") *
90 Cg("%}noformat", "endbit") * #P
"\n") +
91 constructed_verbatims
),
92 specialcontrol
= Ct(P
"\\" * Cg(S
"\\ \n%+,/:;\"\'-{}$^_`~#&[]()!|", "name")),
93 word
= S
"[]" + (1 - S(" \t\n\v\\%{}[]"))^
1,
94 wordnb
= (1 - S(" \t\n\v\\%{}[]"))^
1,
95 subbody
= Ct(Cg(P
"{", "l") * Cg(V
"body", "body") * Cg(P
"}", "r")) +
96 Ct(Cg(P
"[", "l") * Cg(V
"bodynb", "body") * Cg(P
"]", "r")),
97 subbodynb
= Ct(Cg(P
"{", "l") * Cg(V
"body", "body") * Cg(P
"}", "r")),
98 body
= Ct(Ct(Cg(V
"verbatimbody", "verbatim") +
99 Cg(V
"comment", "comment") +
100 Cg(V
"ws"^
1, "whitespace") +
101 Cg(V
"subbody", "subbody") +
102 Cg(V
"specialcontrol", "controlseq") +
103 Cg(V
"controlseq", "controlseq") +
104 Cg(V
"word", "word"))^
0),
105 bodynb
= Ct(Ct(Cg(V
"verbatimbody", "verbatim") +
106 Cg(V
"comment", "comment") +
107 Cg(V
"ws"^
1, "whitespace") +
108 Cg(V
"subbody", "subbody") +
109 Cg(V
"specialcontrol", "controlseq") +
110 Cg(V
"controlseq", "controlseq") +
111 Cg(V
"wordnb", "word"))^
0),
112 document
= V
"body" * (-1)
121 printed_anything
= false,
122 sp_before_next
= false,
125 next_chunk_indent
= 0,
128 -- put enough spaces to start a new line at proper indentation
129 function indent_to(state
)
130 i
= state
.indent_level
131 if state
.next_chunk
~= "" then
132 i
= state
.next_chunk_indent
134 l
= (i
* 2) % conf
.max_len
135 io
.write(string.rep(" ", l
))
138 state
.sp_before_next
= false
141 -- calculate (by wcwidth()) cells needed to display a string
142 function display_width(s
)
144 for _
, rune
in utf8
.codes(s
) do
145 local l
= wcwidth(rune
)
153 -- handle seeing \begin{something}
154 function begin_env(state
, e
)
155 state
.env_depth
= state
.env_depth
+ 1
156 state
.indent_level
= state
.indent_level
+ 1
157 if conf
.tabular_like_environments
[e
] and
158 state
.tabular_depth
== 0 then
159 state
.tabular_depth
= state
.env_depth
161 if not conf
.inline_environments
[e
] then
162 state
.nl_before_next
= math
.max(state
.nl_before_next
, 1)
166 -- handle seeing \end{something}
167 function end_env(state
, e
)
168 state
.env_depth
= state
.env_depth
- 1
169 if state
.tabular_depth
> state
.env_depth
then
170 state
.tabular_depth
= 0
172 state
.indent_level
= state
.indent_level
- 1
173 if not conf
.inline_environments
[e
] then
174 state
.nl_before_next
= math
.max(state
.nl_before_next
, 1)
178 function indent_check(state
, c
)
179 if conf
.indent_incrementers
[c
.name
] then
181 state
.indent_level
= state
.indent_level
+ 1
185 function deindent_check(state
, c
)
186 if conf
.indent_decrementers
[c
.name
] then
188 state
.indent_level
= state
.indent_level
- 1
192 -- handle some control sequences starting their own lines
193 function ensure_start_line(state
, c
)
194 if conf
.start_line_controlseqs
[c
.name
] then
196 state
.nl_before_next
= math
.max(state
.nl_before_next
, 1)
200 -- handle some control sequences being on their own lines
201 function ensure_own_line(state
, c
)
202 if c
.name
== "begin" or c
.name
== "end" then
203 if c
.args
and c
.args
[1] and c
.args
[1].body
and
204 c
.args
[1].body
and c
.args
[1].body
[1] then
205 e
= c
.args
[1].body
[1].word
206 if type(e
) == "string" and
207 conf
.inline_environments
[e
] then
212 state
.nl_before_next
= math
.max(state
.nl_before_next
, 1)
215 if conf
.own_line_controlseqs
[c
.name
] then
217 state
.nl_before_next
= math
.max(state
.nl_before_next
, 1)
220 if conf
.own_paragraphs
[c
.name
] then
222 state
.nl_before_next
= math
.max(state
.nl_before_next
, 2)
226 -- print something to the screen, properly indented
227 function write_out_string(state
, s
)
228 if state
.nl_before_next
> 0 then
229 if state
.printed_anything
then
230 io
.write(string.rep("\n", state
.nl_before_next
))
233 state
.sp_before_next
= false
236 spacelen
= (state
.sp_before_next
and 1) or 0
238 slen
= display_width(s
)
240 projected_cell_pos
= state
.cell_pos
+ spacelen
+ slen
241 if_newlined
= ((state
.indent_level
* 2) % conf
.max_len
) + spacelen
+
243 if (projected_cell_pos
> conf
.max_len
) and
244 (if_newlined
<= conf
.max_len
) and
245 (state
.tabular_depth
== 0) then
254 state
.cell_pos
= state
.cell_pos
+ 1
258 state
.printed_anything
= true
259 state
.cell_pos
= state
.cell_pos
+ slen
260 state
.nl_before_next
= 0
261 state
.sp_before_next
= false
264 -- commit a chunk of text to be displayed
265 function commit_string(state
, s
)
266 if state
.next_chunk
== "" then
267 state
.next_chunk_indent
= state
.indent_level
269 state
.next_chunk
= state
.next_chunk
..s
272 -- flush out the last chunk completely
273 function flush_chunk(state
)
274 if state
.next_chunk
== "" then return end
275 write_out_string(state
, state
.next_chunk
)
276 state
.next_chunk
= ""
280 function p_whitespace(state
, w
)
283 for i
= 1, string.len(w
), 1 do
284 if string.sub(w
, i
, i
) == "\n" then
287 state
.sp_before_next
= true
291 if nls
> 0 and state
.tabular_depth
> 0 then
292 state
.nl_before_next
= math
.max(state
.nl_before_next
, 1)
294 state
.sp_before_next
= true
296 state
.nl_before_next
= math
.max(state
.nl_before_next
, 2)
300 -- print out a control sequence, and handle indentation
301 function p_controlseq(state
, c
)
302 deindent_check(state
, c
)
303 if c
.name
== "end" and c
.args
and c
.args
[1] and
304 c
.args
[1].body
and c
.args
[1].body
[1] and
305 c
.args
[1].body
[1].word
then
307 end_env(state
, c
.args
[1].body
[1].word
)
310 ensure_start_line(state
, c
)
311 ensure_own_line(state
, c
)
313 if c
.name
== "\n" then
314 commit_string(state
, "\\")
316 state
.nl_before_next
= math
.max(state
.nl_before_next
, 1)
320 commit_string(state
, "\\"..c
.name
)
321 if not c
.args
then c
.args
= {} end
322 for _
, a
in ipairs(c
.args
) do
326 indent_check(state
, c
)
327 ensure_own_line(state
, c
)
328 if c
.name
== "begin" and c
.args
and c
.args
[1] and
329 c
.args
[1].body
and c
.args
[1].body
[1] and
330 c
.args
[1].body
[1].word
then
331 begin_env(state
, c
.args
[1].body
[1].word
)
335 -- print out a body that was enclosed by { and }
336 function p_subbody(state
, b
)
337 commit_string(state
, b
.l
)
338 state
.indent_level
= state
.indent_level
+ 1
339 p_body(state
, b
.body
)
340 state
.indent_level
= state
.indent_level
- 1
341 commit_string(state
, b
.r
)
344 -- a verbatim environment
345 function p_verbatim(state
, v
)
346 -- XXX: fix the grammar that caused this mess
350 if v
.beginbit
== "\\begin{verbatim}" then
351 control
= {name
="begin", body
="verbatim"}
352 elseif v
.beginbit
== "\\begin{Verbatim}" then
353 control
= {name
="begin", body
="Verbatim"}
354 elseif v
.beginbit
== "\\begin{alltt}" then
355 control
= {name
="begin", body
="alltt"}
356 elseif v
.beginbit
== "\\verb" then
357 control
= {name
="verb"}
358 elseif string.sub(v
.beginbit
, 1, 1) == "%" then
362 if control
then ensure_own_line(state
, control
) end
364 first_nl_i
, first_nl_j
= string.find(v
.content
, "\n")
367 if v
.delim
then s
= s
..v
.delim
end
369 s
= s
..string.sub(v
.content
, 1, first_nl_i
- 1)
370 v
.content
= string.sub(v
.content
, first_nl_i
, -1)
375 commit_string(state
, s
)
381 state
.nl_before_next
= math
.max(state
.nl_before_next
, 1)
384 rest
= v
.content
..v
.endbit
387 i
= string.find(rest
, "\n", last_nl
+ 1)
388 if i
== nil then break end
392 last_line_len
= display_width(string.sub(rest
, last_nl
+ 1, -1))
394 state
.cell_pos
= last_line_len
396 state
.cell_pos
= state
.cell_pos
+ last_line_len
400 ensure_own_line(state
, control
)
404 -- print out a comment
405 function p_comment(state
, c
)
407 write_out_string(state
, c
)
408 state
.nl_before_next
= math
.max(state
.nl_before_next
, 1)
411 -- print out a document contents
412 function p_body (state
, b
)
413 if b
== nil or b
== "" then return end
414 for _
, bb
in ipairs(b
) do
415 if bb
.comment
then p_comment(state
, bb
.comment
) end
416 if bb
.whitespace
then p_whitespace(state
, bb
.whitespace
) end
417 if bb
.controlseq
then p_controlseq(state
, bb
.controlseq
) end
418 if bb
.word
then commit_string(state
, bb
.word
) end
419 if bb
.verbatim
then p_verbatim(state
, bb
.verbatim
) end
420 if bb
.subbody
then p_subbody(state
, bb
.subbody
) end
428 local line
= io
.read()
429 if line
== nil then break end
433 corpus
= corpus
.. "\n" .. line
438 -- parse, perhaps print
439 local m
= texish
:match(corpus
)
443 io
.stderr
:write("llf: cannot parse input\n")
446 p_body(prog_state
, m
)
447 flush_chunk(prog_state
)