This repository has been archived by the owner on Aug 21, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparser.lua
226 lines (191 loc) · 4.97 KB
/
parser.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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
-- combinators
-- return success, value, pos
local function sym(t)
return function(text, pos)
if text:sub(pos, pos + #t - 1) == t then
return true, nil, pos + #t
else
return false, text:sub(pos, pos + #t), pos + #t
end
end
end
local function pattern(pat)
return function(text, pos)
local s, e = text:find(pat, pos)
if s then
local v = text:sub(s, e)
return true, v, pos + #v
else
return false, nil, pos
end
end
end
local function map(p, f)
return function(text, pos)
local succ, val, new_pos = p(text, pos)
if succ then
return true, f(val), new_pos
end
return false, nil, pos
end
end
local function any(...)
local parsers = { ... }
return function(text, pos)
for _, p in ipairs(parsers) do
local succ, val, new_pos = p(text, pos)
if succ then
return true, val, new_pos
end
end
return false, nil, pos
end
end
local function seq(...)
local parsers = { ... }
return function(text, pos)
local original_pos = pos
local values = {}
for _, p in ipairs(parsers) do
local succ, val, new_pos = p(text, pos)
pos = new_pos
if not succ then
return false, nil, original_pos
end
table.insert(values, val)
end
return true, values, pos
end
end
local function many(p)
return function(text, pos)
local len = #text
local values = {}
while pos <= len do
local succ, val, new_pos = p(text, pos)
if succ then
pos = new_pos
table.insert(values, val)
else
break
end
end
return #values > 0, values, pos
end
end
local function take_until(patterns)
return function(text, pos)
local s, e = text:find(patterns, pos)
-- TODO: handle escaping
if s then
-- would be empty string
if pos == s then
return false, nil, pos
else
-- consume up to the match point
return true, text:sub(pos, s - 1), s
end
elseif pos <= #text then
-- no match but there's text to consume
return true, text:sub(pos), #text + 1
else
return false, nil, pos
end
end
end
local function separated(sep, p)
return function(text, pos)
local len = #text
local values = {}
local succ, val, new_pos = p(text, pos)
if not succ then
return false, nil, pos
end
table.insert(values, val)
pos = new_pos
while pos <= len do
local succ, _, new_pos = sep(text, pos)
if not succ then
break
end
pos = new_pos
local succ, val, new_pos = p(text, pos)
if not succ then
break
end
table.insert(values, val)
pos = new_pos
end
return true, values, pos
end
end
local function lazy(f)
return function(text, pos)
return f()(text, pos)
end
end
-- parsers
local dollar = sym("$")
local open = sym("{")
local close = sym("}")
local colon = sym(":")
local slash = sym("/")
local comma = sym(",")
local pipe = sym("|")
local var = pattern("^[_a-zA-Z][_a-zA-Z0-9]*")
local int = map(pattern("^%d+"), function(v) return tonumber(v) end)
local text = take_until
-- TODO: opt so we can avoid swallowing the close here
local regex = map(
seq(slash, take_until("/"), slash, take_until("/"), slash, any(take_until("}"), close)),
function(v) return { type = "regex", value = v[1], format = v[2], options = v[3]} end
)
local tabstop, placeholder, choice, variable, anything
-- need to make lazy so that tabstop/placeholder/variable aren't nil at
-- declaration time because of mutual recursion.
anything = lazy(function() return any(
tabstop,
placeholder,
choice,
variable
-- -- text: we do this on a per usecase basis
) end)
tabstop = map(
any(
seq(dollar, int),
seq(dollar, open, int, close)
),
function(v) return { type = "tabstop", id = v[1] } end
)
placeholder = map(
-- match until $ or }
seq(dollar, open, int, colon, many(any(anything, text("[%$}]"))), close),
function(v) return { type = "placeholder", id = v[1], value = v[2] } end
)
choice = map(
-- match until , or |
seq(dollar, open, int, pipe, separated(comma, text("[,|]")), pipe, close),
function(v) return { type = "choice", id = v[1], value = v[2] } end
)
variable = any(
map(
seq(dollar, var),
function(v) return { type = "variable", name = v[1] } end
),
map(
seq(dollar, open, var, colon, many(any(anything, text("}"))), close),
function(v) return { type = "variable", name = v[1], default = v[2] } end
),
map(
seq(dollar, open, var, regex), -- regex already eats the close
function(v) return { type = "variable", name = v[1], regex = v[2] } end
)
)
-- toplevel text matches until $
parse = many(any(anything, text("%$")))
-- s, v, _ = parse("", 1)
-- s, v, _ = parse("hello $1${2} ${1|one,two,three|} ${name:foo} $var $TM ${TM_FILENAME/(.*).+$/$1/} ${1: $TM_SELECTED_TEXT }", 1)
-- s, v, p = parse("local ${1:var} = ${1:value}", 1)
-- print(s, vim.inspect(v), p)
return { parse = parse }
-- vim:et ts=2 sw=2