code / bp

Lines4.3K C3.3K Markdown541 YAML273 make110 Shell77 Lua54
(349 lines)
1 /*
2 * lbp.c - bp library for lua
3 * API:
4 * bp.match(pat, str, [start_index]) -> nil or match_table
5 * bp.replace(pat, replacement, str, [start_index]) -> str with replacements, num_replacements
6 * for match_table in bp.matches(pat, str, [start_index]) do ... end
7 * bp.compile(pat) -> pattern object
8 * pat:match(str, [start_index])
9 * pat:replace(replacement, str, [start_index])
10 * for match in pat:matches(str, [start_index]) do ... end
11 */
13 #include <fcntl.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <sys/mman.h>
17 #include <sys/stat.h>
19 #include "lauxlib.h"
20 #include "lua.h"
22 #include "../match.h"
23 #include "../pattern.h"
24 #include "../printmatch.h"
25 #include "../utils.h"
27 static const char *builtins_source = (
28 #include "builtins.h"
29 );
30 static int MATCH_METATABLE = 0, PAT_METATABLE = 0;
31 static bp_pat_t *builtins;
33 static void push_match(lua_State *L, bp_match_t *m, const char *start);
35 lua_State *cur_state = NULL;
37 static void match_error(char **msg) {
38 lua_pushstring(cur_state, *msg);
39 free(*msg);
40 *msg = NULL;
41 lua_error(cur_state);
44 static inline void raise_parse_error(lua_State *L, maybe_pat_t m) {
45 size_t err_len = (size_t)(m.value.error.end - m.value.error.start);
46 char *buf = calloc(err_len + 1, sizeof(char));
47 memcpy(buf, m.value.error.start, err_len);
48 luaL_error(L, "%s: \"%s\"", m.value.error.msg, buf);
49 free(buf);
52 static int Lcompile(lua_State *L) {
53 size_t patlen;
54 const char *pat_text = luaL_checklstring(L, 1, &patlen);
55 maybe_pat_t maybe_pat = bp_pattern(pat_text, pat_text + patlen);
56 if (!maybe_pat.success) {
57 raise_parse_error(L, maybe_pat);
58 return 0;
60 bp_pat_t **pat_storage = (bp_pat_t **)lua_newuserdatauv(L, sizeof(bp_pat_t *), 1);
61 *pat_storage = maybe_pat.value.pat;
62 lua_pushvalue(L, 1);
63 lua_setiuservalue(L, -2, 1);
64 lua_pushlightuserdata(L, (void *)&PAT_METATABLE);
65 lua_gettable(L, LUA_REGISTRYINDEX);
66 lua_setmetatable(L, -2);
67 return 1;
70 static void push_matchstring(lua_State *L, bp_match_t *m) {
71 char *buf = NULL;
72 size_t size = 0;
73 FILE *out = open_memstream(&buf, &size);
74 fprint_match(out, m->start, m, NULL);
75 fflush(out);
76 lua_pushlstring(L, buf, size);
77 fclose(out);
80 static bp_match_t *get_first_capture(bp_match_t *m) {
81 if (m->pat->type == BP_TAGGED) {
82 return m;
83 } else if (m->pat->type == BP_CAPTURE && !When(m->pat, BP_CAPTURE)->name) {
84 return m;
85 } else if (m->children) {
86 for (int i = 0; m->children[i]; i++) {
87 bp_match_t *cap = get_first_capture(m->children[i]);
88 if (cap) return cap;
91 return NULL;
94 static void set_capture_fields(lua_State *L, bp_match_t *m, int *n, const char *start) {
95 if (m->pat->type == BP_CAPTURE) {
96 bp_match_t *cap = get_first_capture(m->children[0]);
97 if (!cap) cap = m->children[0];
98 auto capture = When(m->pat, BP_CAPTURE);
99 if (capture->namelen > 0) {
100 lua_pushlstring(L, capture->name, capture->namelen);
101 push_match(L, cap, start);
102 lua_settable(L, -3);
103 } else {
104 push_match(L, cap, start);
105 lua_seti(L, -2, (*n)++);
107 } else if (m->pat->type == BP_TAGGED) {
108 push_match(L, m, start);
109 lua_seti(L, -2, (*n)++);
110 } else if (m->children) {
111 for (int i = 0; m->children[i]; i++)
112 set_capture_fields(L, m->children[i], n, start);
116 static void push_match(lua_State *L, bp_match_t *m, const char *start) {
117 lua_createtable(L, 1, 2);
118 lua_pushlightuserdata(L, (void *)&MATCH_METATABLE);
119 lua_gettable(L, LUA_REGISTRYINDEX);
120 lua_setmetatable(L, -2);
121 push_matchstring(L, m);
122 lua_seti(L, -2, 0);
124 if (m->pat->type == BP_TAGGED) {
125 auto tagged = When(m->pat, BP_TAGGED);
126 lua_pushlstring(L, tagged->name, tagged->namelen);
127 lua_setfield(L, -2, "__tag");
130 int capture_num = 1;
131 for (int i = 0; m->children && m->children[i]; i++)
132 set_capture_fields(L, m->children[i], &capture_num, start);
134 lua_pushinteger(L, 1 + (int)(m->start - start));
135 lua_setfield(L, -2, "start");
136 lua_pushinteger(L, 1 + (int)(m->end - start));
137 lua_setfield(L, -2, "after");
140 static int Lmatch(lua_State *L) {
141 if (lua_isstring(L, 1)) {
142 if (Lcompile(L) != 1) return 0;
143 lua_replace(L, 1);
145 bp_pat_t **at_pat = lua_touserdata(L, 1);
146 bp_pat_t *pat = at_pat ? *at_pat : NULL;
147 if (!pat) luaL_error(L, "Not a valid pattern");
149 size_t textlen;
150 const char *text = luaL_checklstring(L, 2, &textlen);
151 lua_Integer index;
152 if (lua_istable(L, 3)) {
153 lua_getfield(L, 3, "start");
154 lua_getfield(L, 3, "after");
155 index = luaL_optinteger(L, -1, 1);
156 if (lua_rawequal(L, -1, -2)) ++index;
157 } else {
158 index = luaL_optinteger(L, 3, 1);
160 if (index > (lua_Integer)strlen(text) + 1) return 0;
162 bp_match_t *m = NULL;
163 int ret = 0;
164 cur_state = L;
165 bp_errhand_t old = bp_set_error_handler(match_error);
166 if (next_match(&m, text + index - 1, &text[textlen], pat, builtins, NULL, false)) {
167 push_match(L, m, text);
168 stop_matching(&m);
169 ret = 1;
171 bp_set_error_handler(old);
172 return ret;
175 static int Lreplace(lua_State *L) {
176 if (lua_isstring(L, 1)) {
177 if (Lcompile(L) != 1) return 0;
178 lua_replace(L, 1);
180 bp_pat_t **at_pat = lua_touserdata(L, 1);
181 bp_pat_t *pat = at_pat ? *at_pat : NULL;
182 if (!pat) luaL_error(L, "Not a valid pattern");
184 size_t replen, textlen;
185 const char *rep_text = luaL_checklstring(L, 2, &replen);
186 const char *text = luaL_checklstring(L, 3, &textlen);
187 lua_Integer index = luaL_optinteger(L, 4, 1);
188 if (index > (lua_Integer)strlen(text) + 1) index = (lua_Integer)strlen(text) + 1;
190 maybe_pat_t maybe_replacement = bp_replacement(pat, rep_text, rep_text + replen);
191 if (!maybe_replacement.success) {
192 raise_parse_error(L, maybe_replacement);
193 return 0;
196 char *buf = NULL;
197 size_t size = 0;
198 FILE *out = open_memstream(&buf, &size);
199 int replacements = 0;
200 const char *prev = text;
201 bp_pat_t *rep_pat = maybe_replacement.value.pat;
202 cur_state = L;
203 bp_errhand_t old = bp_set_error_handler(match_error);
204 for (bp_match_t *m = NULL; next_match(&m, text, &text[textlen], rep_pat, builtins, NULL, false);) {
205 fwrite(prev, sizeof(char), (size_t)(m->start - prev), out);
206 fprint_match(out, text, m, NULL);
207 prev = m->end;
208 ++replacements;
210 bp_set_error_handler(old);
211 fwrite(prev, sizeof(char), (size_t)(&text[textlen] - prev), out);
212 fflush(out);
213 lua_pushlstring(L, buf, size);
214 lua_pushinteger(L, replacements);
215 fclose(out);
217 delete_pat(&rep_pat, false);
219 return 2;
222 static int iter(lua_State *L) {
223 lua_geti(L, 1, 1);
224 lua_geti(L, 1, 2);
225 lua_replace(L, 1);
226 lua_rotate(L, 1, 1);
227 return Lmatch(L);
230 static int Lmatches(lua_State *L) {
231 int nargs = lua_gettop(L);
232 lua_pushcfunction(L, iter); // iter
233 lua_createtable(L, 2, 0); // state: {pat, str}
234 if (lua_isstring(L, 1)) {
235 if (Lcompile(L) != 1) return 0;
236 } else {
237 lua_pushvalue(L, 1);
239 lua_seti(L, -2, 1);
240 lua_pushvalue(L, 2);
241 lua_seti(L, -2, 2);
242 if (nargs >= 3) // start index
243 lua_pushvalue(L, 3);
244 else lua_pushnil(L);
245 return 3;
248 static int Lmatch_tostring(lua_State *L) {
249 lua_geti(L, 1, 0);
250 return 1;
253 static int Lpat_source(lua_State *L) {
254 lua_getiuservalue(L, 1, 1);
255 return 1;
258 static int Lpat_tostring(lua_State *L) {
259 luaL_Buffer b;
260 luaL_buffinit(L, &b);
261 luaL_addstring(&b, "Pattern [[");
262 lua_getiuservalue(L, 1, 1);
263 luaL_addvalue(&b);
264 luaL_addstring(&b, "]]");
265 luaL_pushresult(&b);
266 return 1;
269 static int Lpat_gc(lua_State *L) {
270 (void)L;
271 bp_pat_t **at_pat = lua_touserdata(L, 1);
272 bp_pat_t *pat = *at_pat;
273 if (pat) delete_pat(at_pat, true);
274 (void)pat;
275 return 0;
278 static int Lpat_join(lua_State *L, const char *joiner) {
279 if (!lua_isstring(L, 1)) {
280 lua_pushcfunction(L, Lpat_source);
281 lua_pushvalue(L, 1);
282 lua_call(L, 1, 1);
283 lua_replace(L, 1);
285 if (!lua_isstring(L, 2)) {
286 lua_pushcfunction(L, Lpat_source);
287 lua_pushvalue(L, 2);
288 lua_call(L, 1, 1);
289 lua_replace(L, 2);
292 lua_pushcfunction(L, Lcompile);
293 luaL_Buffer b;
294 luaL_buffinit(L, &b);
295 luaL_addstring(&b, "(");
296 lua_pushvalue(L, 1);
297 luaL_addvalue(&b);
298 luaL_addstring(&b, ")");
299 luaL_addstring(&b, joiner);
300 luaL_addstring(&b, "(");
301 lua_pushvalue(L, 2);
302 luaL_addvalue(&b);
303 luaL_addstring(&b, ")");
305 luaL_pushresult(&b);
306 lua_call(L, 1, 1);
308 return 1;
311 static int Lpat_concat(lua_State *L) { return Lpat_join(L, " "); }
313 static int Lpat_div(lua_State *L) { return Lpat_join(L, " / "); }
315 static const luaL_Reg match_metamethods[] = {{"__tostring", Lmatch_tostring}, {NULL, NULL}};
317 static const luaL_Reg pat_methods[] = {
318 {"match", Lmatch}, {"replace", Lreplace}, {"matches", Lmatches}, {"getsource", Lpat_source}, {NULL, NULL}};
320 static const luaL_Reg pat_metamethods[] = {{"__gc", Lpat_gc}, {"__concat", Lpat_concat},
321 {"__div", Lpat_div}, {"__tostring", Lpat_tostring},
322 {"__index", NULL}, // placeholder for pat_methods
323 {NULL, NULL}};
325 static const luaL_Reg bp_methods[] = {
326 {"match", Lmatch}, {"replace", Lreplace}, {"compile", Lcompile}, {"matches", Lmatches}, {NULL, NULL}};
328 public
329 LUALIB_API int luaopen_bp(lua_State *L) {
330 maybe_pat_t maybe_pat = bp_pattern(builtins_source, builtins_source + strlen(builtins_source));
331 if (!maybe_pat.success) {
332 raise_parse_error(L, maybe_pat);
333 return 0;
335 builtins = maybe_pat.value.pat;
337 lua_pushlightuserdata(L, (void *)&PAT_METATABLE);
338 luaL_newlib(L, pat_metamethods);
339 luaL_newlib(L, pat_methods);
340 lua_setfield(L, -2, "__index");
341 lua_settable(L, LUA_REGISTRYINDEX);
343 lua_pushlightuserdata(L, (void *)&MATCH_METATABLE);
344 luaL_newlib(L, match_metamethods);
345 lua_settable(L, LUA_REGISTRYINDEX);
347 luaL_newlib(L, bp_methods);
348 return 1;