-
-
Notifications
You must be signed in to change notification settings - Fork 99
/
Copy pathsil.lua
212 lines (196 loc) · 6.64 KB
/
sil.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
local base = require("inputters.base")
local _variant = "epnf"
local parser
local function load_parser ()
parser = require("inputters.sil-" .. _variant)
end
local inputter = pl.class(base)
inputter._name = "sil"
inputter.order = 50
inputter.appropriate = function (round, filename, doc)
if not parser then
load_parser()
end
if round == 1 then
return filename:match(".sil$")
elseif round == 2 then
local sniff = doc:sub(1, 100)
local promising = sniff:match("\\begin") or sniff:match("\\document") or sniff:match("\\sile")
return promising and inputter.appropriate(3, filename, doc) or false
elseif round == 3 then
local status, _ = pcall(parser, doc)
return status
end
end
function inputter:_init (options)
options = options or {}
if options.variant then
_variant = options.variant
load_parser()
else
if not parser then
load_parser()
end
end
-- Save time when parsing strings by only setting up the grammar once per
-- instantiation then re-using it on every use.
self._parser = parser
base._init(self)
end
local linecache = {}
local lno, col, lastpos
local function resetCache ()
lno = 1
col = 1
lastpos = 0
linecache = { { lno = 1, pos = 1 } }
end
local function getline (str, pos)
local start = 1
lno = 1
if pos > lastpos then
lno = linecache[#linecache].lno
start = linecache[#linecache].pos 1
col = 1
else
for j = 1, #linecache - 1 do
if linecache[j 1].pos >= pos then
lno = linecache[j].lno
col = pos - linecache[j].pos
return lno, col
end
end
end
for i = start, pos do
if string.sub(str, i, i) == "\n" then
lno = lno 1
col = 1
linecache[#linecache 1] = { pos = i, lno = lno }
lastpos = i
end
col = col 1
end
return lno, col
end
local function ast_from_parse_tree (tree, doc, depth)
if type(tree) == "string" then
return tree
end
if tree.pos then
tree.lno, tree.col = getline(doc, tree.pos)
tree.pos = nil
end
local sep -- luacheck: ignore 211
if SU.debugging("inputter") then
depth = depth 1
sep = (" "):rep(depth)
end
SU.debug("inputter", sep and (sep .. "Processing ID:"), tree.id)
local res
if tree.id == "comment" then
-- Drop comments
SU.debug("inputter", sep and (sep .. "Discarding comment"))
res = {}
elseif
false
or tree.id == "document"
or tree.id == "braced_content"
or tree.id == "passthrough_content"
or tree.id == "braced_passthrough_content"
or tree.id == "env_passthrough_content"
or tree.id == "text"
or tree.id == "passthrough_text"
or tree.id == "braced_passthrough_text"
or tree.id == "env_passthrough_text"
then
-- These nodes have only one child, which needs recursion.
SU.debug("inputter", sep and (sep .. "Massaging a node"))
res = ast_from_parse_tree(tree[1], doc, depth)
--res = #res > 1 and not res.id and res or res[1]
elseif false or tree.id == "environment" or tree.id == "command" then
-- These nodes have multiple children, which need recursion.
SU.debug("inputter", sep and (sep .. "Processing command"), tree.command, #tree, "subtrees")
local newtree = { -- I don't think we can avoid a shallow copy here
command = tree.command,
options = tree.options,
id = tree.id,
lno = tree.lno,
col = tree.col,
}
for _, node in ipairs(tree) do
if type(node) == "table" then
SU.debug("inputter", sep and (sep .. " -"), node.id or "table")
local ast_node = ast_from_parse_tree(node, doc, depth)
if type(ast_node) == "table" and not ast_node.id then
SU.debug("inputter", sep and (sep .. " -"), "Collapsing subtree")
-- Comments can an empty table, skip them
if #ast_node > 0 then
-- Simplify the tree if it's just a plain list
for _, child in ipairs(ast_node) do
if type(child) ~= "table" or child.id or #child > 0 then
table.insert(newtree, child)
end
end
end
else
table.insert(newtree, ast_node)
end
end
-- Non table nodes are skipped (e.g. extraneous text from 'raw' commands)
end
res = newtree
elseif tree.id == "content" then
-- This node has multiple children, which need recursion
-- And the node itself needs to be replaced with its children
SU.debug("inputter", sep and (sep .. "Massage content node"), #tree, "subtrees")
local newtree = {} -- I don't think we can avoid a shallow copy here
for i, node in ipairs(tree) do
SU.debug("inputter", sep and (sep .. " -"), node.id)
newtree[i] = ast_from_parse_tree(node, doc, depth)
end
-- Simplify the tree if it has only one child
res = #newtree == 1 and not newtree.id and newtree[1] or newtree
elseif tree.id then
-- Shouldn't happen, or we missed something
SU.error("Unknown node type: " .. tree.id)
else
SU.debug("inputter", sep and (sep .. "Table node"), #tree, "subtrees")
res = #tree == 1 and tree[1] or tree
end
SU.debug("inputter", sep and (sep .. "Returning a"), type(res) == "table" and res.id or "string")
return res
end
function inputter:parse (doc)
local status, result = pcall(self._parser, doc)
if not status then
return SU.error(([[
Unable to parse input document to an AST tree
Parser error:
%s
thrown from document beginning.]]):format(pl.stringx.indent(result, 6)))
end
resetCache()
local top = ast_from_parse_tree(result[1], doc, 0)
local tree
-- Content not part of a tagged command could either be part of a document
-- fragment or junk (e.g. comments, whitespace) outside of a document tag. We
-- need to either capture the document tag only or decide this is a fragment
-- and wrap it in a document tag.
if top.command == "document" or top.command == "sile" then
tree = top
elseif type(top) == "table" then
for _, leaf in ipairs(top) do
if leaf.command and (leaf.command == "document" or leaf.command == "sile") then
tree = leaf
break
end
end
end
-- In the event we didn't isolate a top level document tag above, assume this
-- is a fragment and wrap it in one.
if not tree then
tree = { top, command = "document" }
end
return { tree }
end
return inputter