snapshot: writing working?
This is a complete mess. I want to abstract reading multiline strings behind a function, but the lookahead requirements for that are quite stringent. What's a reasonable abstraction here?
This commit is contained in:
parent
052c5501ac
commit
d5038fe514
41
src/lua.c
41
src/lua.c
|
@ -307,13 +307,15 @@ int load_definitions(lua_State *L) {
|
|||
|
||||
|
||||
char *Image_name = NULL;
|
||||
void load_tlv (lua_State *L, char *filename);
|
||||
extern void load_tlv (lua_State *L, char *filename);
|
||||
static int handle_image (lua_State *L, char **argv, int n) {
|
||||
int status;
|
||||
/* TODO: pass args in */
|
||||
/* parse and load file contents (teliva_program array) */
|
||||
Image_name = argv[n];
|
||||
load_tlv(L, Image_name);
|
||||
//? save_tlv(L, Image_name); // manual test; should always return identical result, modulo key order
|
||||
//? exit(1);
|
||||
status = load_definitions(L);
|
||||
if (status != 0) return 0;
|
||||
/* call main() */
|
||||
|
@ -379,41 +381,12 @@ static void update_definition (lua_State *L, const char *name, char *new_content
|
|||
}
|
||||
|
||||
|
||||
static void save_image (lua_State *L) {
|
||||
lua_getglobal(L, "teliva_program");
|
||||
int history_array = lua_gettop(L);
|
||||
int history_array_size = luaL_getn(L, history_array);
|
||||
FILE *out = fopen(Image_name, "w");
|
||||
fprintf(out, "teliva_program = {\n");
|
||||
for (int i = 1; i <= history_array_size; ++i) {
|
||||
lua_rawgeti(L, history_array, i);
|
||||
int table = lua_gettop(L);
|
||||
fprintf(out, " {\n");
|
||||
for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) {
|
||||
const char* key = lua_tostring(L, -2);
|
||||
if (strcmp(key, "__teliva_undo") == 0) {
|
||||
fprintf(out, " %s = %ld,\n", key, lua_tointeger(L, -1));
|
||||
continue;
|
||||
}
|
||||
const char* value = lua_tostring(L, -1);
|
||||
fprintf(out, " %s = [==[\n", key);
|
||||
fprintf(out, "%s", value);
|
||||
fprintf(out, "]==],\n");
|
||||
}
|
||||
fprintf(out, " },\n");
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
fprintf(out, "}\n");
|
||||
fclose(out);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
|
||||
extern void save_tlv (lua_State *L, char *filename);
|
||||
int load_editor_buffer_to_current_definition_in_image(lua_State *L) {
|
||||
char new_contents[8192] = {0};
|
||||
read_editor_buffer(new_contents);
|
||||
update_definition(L, Current_definition, new_contents);
|
||||
save_image(L);
|
||||
save_tlv(L, Image_name);
|
||||
/* reload binding */
|
||||
return luaL_loadbuffer(L, new_contents, strlen(new_contents), Current_definition)
|
||||
|| docall(L, 0, 1);
|
||||
|
@ -651,12 +624,12 @@ void recent_changes_view (lua_State *L) {
|
|||
/* TODO: go hotkey is misleading. edits will not be persisted until you return to recent changes */
|
||||
edit(L, "teliva_editor_buffer");
|
||||
load_note_from_editor_buffer(L, cursor);
|
||||
save_image(L);
|
||||
save_tlv(L, Image_name);
|
||||
break;
|
||||
case CTRL_U:
|
||||
if (cursor < history_array_size) {
|
||||
add_undo_event(L, cursor);
|
||||
save_image(L);
|
||||
save_tlv(L, Image_name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
104
src/tlv.c
104
src/tlv.c
|
@ -7,22 +7,37 @@
|
|||
#include "lauxlib.h"
|
||||
|
||||
/* If you encounter assertion failures in this file and _didn't_ manually edit
|
||||
* it, lease report the .tlv file that caused them: http://akkartik.name/contact. */
|
||||
* it, lease report the .tlv file that caused them: http://akkartik.name/contact.
|
||||
*
|
||||
* Manually edited files can have cryptic errors. Teliva's first priority is
|
||||
* to be secure, so it requires a fairly rigid file format and errors out if
|
||||
* things are even slightly amiss. */
|
||||
|
||||
void teliva_load_multiline_string(lua_State* L, FILE* in, char* line, int capacity) {
|
||||
/* This code is surprisingly hairy. Estimate of buffer overflows: 2. */
|
||||
|
||||
static void teliva_load_multiline_string(lua_State* L, FILE* in, char* line, int capacity) {
|
||||
luaL_Buffer b;
|
||||
luaL_buffinit(L, &b);
|
||||
int expected_indent = -1;
|
||||
while (1) {
|
||||
if (feof(in)) break;
|
||||
char c = fgetc(in);
|
||||
//? printf("^%d$\n", (int)c);
|
||||
ungetc(c, in);
|
||||
if (c != ' ') {
|
||||
/* new definition; signal end to caller without reading a new line */
|
||||
strcpy(line, "-\n");
|
||||
break;
|
||||
}
|
||||
memset(line, '\0', capacity);
|
||||
if (fgets(line, capacity, in) == NULL) break; /* eof */
|
||||
//? printf("ml: %s", line);
|
||||
int max = strlen(line);
|
||||
assert(line[max-1] == '\n');
|
||||
int indent = 0;
|
||||
while (indent < max-1 && line[indent] == ' ')
|
||||
++indent;
|
||||
if (line[indent] != '>') break;
|
||||
if (line[indent] != '>') break; /* new key/value pair in definition */
|
||||
if (expected_indent == -1)
|
||||
expected_indent = indent;
|
||||
else
|
||||
|
@ -35,7 +50,8 @@ void teliva_load_multiline_string(lua_State* L, FILE* in, char* line, int capaci
|
|||
}
|
||||
|
||||
/* leave a single table on stack containing the next top-level definition from the file */
|
||||
void teliva_load_definition(lua_State* L, FILE* in) {
|
||||
static void teliva_load_definition(lua_State* L, FILE* in) {
|
||||
//? printf("== new definition\n");
|
||||
lua_newtable(L);
|
||||
int def_idx = lua_gettop(L);
|
||||
char line[1024] = {'\0'};
|
||||
|
@ -49,6 +65,7 @@ void teliva_load_definition(lua_State* L, FILE* in) {
|
|||
do {
|
||||
assert(line[0] == '-' || line[0] == ' ');
|
||||
assert(line[1] == ' ');
|
||||
//? printf("l: %s", line);
|
||||
/* key/value pair always indented at 0, never empty, unambiguously not a multiline string */
|
||||
char key[512] = {'\0'};
|
||||
char value[1024] = {'\0'};
|
||||
|
@ -62,18 +79,33 @@ void teliva_load_definition(lua_State* L, FILE* in) {
|
|||
assert(key[strlen(key)-1] == ':');
|
||||
key[strlen(key)-1] = '\0';
|
||||
lua_pushstring(L, key);
|
||||
//? printf("value: %s$\n", value);
|
||||
if (value[0] != '\0') {
|
||||
//? printf("single-line\n");
|
||||
lua_pushstring(L, value); /* value string on same line */
|
||||
assert(fgets(line, 1024, in));
|
||||
char c = fgetc(in);
|
||||
ungetc(c, in);
|
||||
if (c == '-') {
|
||||
strcpy(line, "-\n");
|
||||
}
|
||||
else {
|
||||
assert(fgets(line, 1024, in));
|
||||
//? printf("new line: %s", line);
|
||||
}
|
||||
}
|
||||
else {
|
||||
//? printf("multi-line\n");
|
||||
teliva_load_multiline_string(L, in, line, 1024); /* load from later lines */
|
||||
//? printf("new line: %s", line);
|
||||
}
|
||||
//? printf("adding key: %s$\n", lua_tostring(L, -2));
|
||||
//? printf("adding value: %s$\n", lua_tostring(L, -1));
|
||||
lua_settable(L, def_idx);
|
||||
} while (line[0] == ' ');
|
||||
}
|
||||
|
||||
void load_tlv(lua_State* L, char* filename) {
|
||||
endwin();
|
||||
lua_newtable(L);
|
||||
int history_array = lua_gettop(L);
|
||||
FILE* in = fopen(filename, "r");
|
||||
|
@ -85,3 +117,65 @@ void load_tlv(lua_State* L, char* filename) {
|
|||
fclose(in);
|
||||
lua_setglobal(L, "teliva_program");
|
||||
}
|
||||
|
||||
static void emit_multiline_string(FILE* out, const char* value) {
|
||||
fprintf(out, " >");
|
||||
for (const char* curr = value; *curr != '\0'; ++curr) {
|
||||
if (*curr == '\n' && *(curr+1) != '\0')
|
||||
fprintf(out, "\n >");
|
||||
else
|
||||
fprintf(out, "%c", *curr);
|
||||
}
|
||||
}
|
||||
|
||||
void save_tlv(lua_State* L, char* filename) {
|
||||
//? printf("SAVE\n");
|
||||
lua_getglobal(L, "teliva_program");
|
||||
int history_array = lua_gettop(L);
|
||||
int history_array_size = luaL_getn(L, history_array);
|
||||
FILE *out = fopen(filename, "w");
|
||||
fprintf(out, "# .tlv file generated by https://github.com/akkartik/teliva\n");
|
||||
fprintf(out, "# You may edit it if you are careful; however, you may see cryptic errors if you\n");
|
||||
fprintf(out, "# violate Teliva's assumptions.\n");
|
||||
fprintf(out, "#\n");
|
||||
fprintf(out, "# .tlv files are representations of Teliva programs. Teliva programs consist of\n");
|
||||
fprintf(out, "# sequences of definitions. Each definition is a table of key/value pairs. Keys\n");
|
||||
fprintf(out, "# and values are both strings.\n");
|
||||
fprintf(out, "#\n");
|
||||
fprintf(out, "# Lines in .tlv files always follow exactly one of the following forms:\n");
|
||||
fprintf(out, "# - comment lines at the top of the file starting with '#' at column 0\n");
|
||||
fprintf(out, "# - beginnings of definitions starting with '- ' at column 0, followed by a\n");
|
||||
fprintf(out, "# key/value pair\n");
|
||||
fprintf(out, "# - key/value pairs consisting of ' ' at column 0, containing either a\n");
|
||||
fprintf(out, "# spaceless value on the same line, or a multi-line value\n");
|
||||
fprintf(out, "# - multiline values indented by more than 2 spaces, starting with a '>'\n");
|
||||
fprintf(out, "#\n");
|
||||
fprintf(out, "# If these constraints are violated, Teliva may unceremoniously crash. Please\n");
|
||||
fprintf(out, "# report bugs at http://akkartik.name/contact\n");
|
||||
for (int i = 1; i <= history_array_size; ++i) {
|
||||
lua_rawgeti(L, history_array, i);
|
||||
int table = lua_gettop(L);
|
||||
int first = 1;
|
||||
for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) {
|
||||
if (first) fprintf(out, "- ");
|
||||
else fprintf(out, " ");
|
||||
first = 0;
|
||||
const char* key = lua_tostring(L, -2);
|
||||
if (strcmp(key, "__teliva_undo") == 0) {
|
||||
fprintf(out, "%s: %ld\n", key, lua_tointeger(L, -1));
|
||||
continue;
|
||||
}
|
||||
const char* value = lua_tostring(L, -1);
|
||||
if (strchr(value, ' ') || strchr(value, '\n')) {
|
||||
fprintf(out, "%s:\n", key);
|
||||
emit_multiline_string(out, value);
|
||||
}
|
||||
else {
|
||||
fprintf(out, "%s: %s\n", key, value);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
fclose(out);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
|
12
src/x.tlv
12
src/x.tlv
|
@ -1,10 +1,10 @@
|
|||
- __teliva_timestamp: original
|
||||
- __teliva_timestamp: foo1
|
||||
window:
|
||||
>window = curses.stdscr()
|
||||
- __teliva_timestamp: original
|
||||
- __teliva_timestamp: foo2
|
||||
n:
|
||||
>n = 0
|
||||
- __teliva_timestamp: original
|
||||
- __teliva_timestamp: foo3
|
||||
render:
|
||||
>function render(window)
|
||||
> window:clear()
|
||||
|
@ -17,10 +17,10 @@
|
|||
> curses.refresh()
|
||||
>end
|
||||
__teliva_note: foo
|
||||
- __teliva_timestamp: original
|
||||
- __teliva_timestamp: foo4
|
||||
menu:
|
||||
>menu = {Enter="increment"}
|
||||
- __teliva_timestamp: original
|
||||
- __teliva_timestamp: foo5
|
||||
update:
|
||||
>function update(window)
|
||||
> local key = curses.getch()
|
||||
|
@ -28,7 +28,7 @@
|
|||
> n = n+1
|
||||
> end
|
||||
>end
|
||||
- __teliva_timestamp: original
|
||||
- __teliva_timestamp: foo6
|
||||
main:
|
||||
>function main()
|
||||
> for i=1,7 do
|
||||
|
|
Loading…
Reference in New Issue