diff --git a/.gitignore b/.gitignore index e95d372..5aaaeb7 100644 --- a/.gitignore +++ b/.gitignore @@ -143,3 +143,4 @@ tags # End of https://www.toptal.com/developers/gitignore/api/c,vim,linux,macos,cmake build/ +.cache/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f2a7a2..5960a69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,13 @@ cmake_minimum_required(VERSION 3.31) project(bshell C) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + find_package(Python COMPONENTS Interpreter REQUIRED) +find_package(FX REQUIRED COMPONENTS + fx.runtime + fx.collections + fx.term) execute_process( COMMAND ${Python_EXECUTABLE} @@ -16,5 +22,6 @@ message(STATUS "B Shell version: ${bshell_version}") add_executable(bshell ${bshell_sources}) +target_link_libraries(bshell FX::Runtime FX::Collections FX::Term) target_compile_definitions(bshell PUBLIC BSHELL_VERSION="${bshell_version}") diff --git a/bshell/file.c b/bshell/file.c new file mode 100644 index 0000000..0884964 --- /dev/null +++ b/bshell/file.c @@ -0,0 +1,116 @@ +#include "file.h" + +#include "line-source.h" + +#include +#include +#include +#include +#include + +static enum bshell_status get_name( + struct line_source *src, + char *buf, + size_t count, + size_t *nr_read) +{ + struct file *f = (struct file *)src; + *nr_read = snprintf(buf, count, "%s", f->f_path); + return BSHELL_SUCCESS; +} + +static enum bshell_status get_row( + struct line_source *src, + size_t row, + char *buf, + size_t count, + size_t *nr_read) +{ + struct file *f = (struct file *)src; + size_t nr_rows = fx_array_size(f->f_lines); + + if (row > nr_rows) { + return BSHELL_ERR_EOF; + } + + fx_string *line = fx_array_at(f->f_lines, row - 1); + + const char *line_str = fx_string_get_cstr(line); + size_t line_len = fx_string_get_size(line, FX_STRLEN_NORMAL); + size_t copy_len = fx_min(ulong, count, line_len); + + memcpy(buf, line_str, copy_len); + buf[copy_len] = 0; + buf[strcspn(buf, "\n")] = 0; + *nr_read = copy_len; + + return BSHELL_SUCCESS; +} + +static enum bshell_status readline( + struct line_source *src, + fx_stringstream *out) +{ + struct file *f = (struct file *)src; + fx_wchar c = FX_WCHAR_INVALID; + size_t nr_read = 0; + + while (1) { + fx_status status = fx_stream_read_char(f->f_strp, &c); + if (!FX_OK(status)) { + break; + } + + fx_stream_write_char(out, c); + nr_read++; + + if (c == '\n') { + break; + } + } + + if (nr_read == 0) { + return BSHELL_ERR_EOF; + } + + return BSHELL_SUCCESS; +} + +enum bshell_status file_open(const char *path, struct file **out) +{ + FILE *fp = fopen(path, "r"); + if (!fp) { + return bshell_status_from_errno(errno); + } + + fx_stream *strp = fx_stream_open_fp(fp); + + struct file *file = malloc(sizeof *file); + if (!file) { + fclose(fp); + return BSHELL_ERR_NO_MEMORY; + } + + memset(file, 0x0, sizeof *file); + + file->f_base.s_get_name = get_name; + file->f_base.s_get_row = get_row; + file->f_base.s_readline = readline; + file->f_fp = fp; + file->f_strp = strp; + file->f_path = fx_strdup(path); + file->f_lines = fx_array_create(); + + *out = file; + + return BSHELL_SUCCESS; +} + +void file_close(struct file *file) +{ + fx_stream_unref(file->f_strp); + fx_array_unref(file->f_lines); + free(file->f_path); + fclose(file->f_fp); + free(file); +} diff --git a/bshell/file.h b/bshell/file.h new file mode 100644 index 0000000..bbb5d81 --- /dev/null +++ b/bshell/file.h @@ -0,0 +1,20 @@ +#ifndef FILE_H_ +#define FILE_H_ + +#include "line-source.h" + +#include +#include + +struct file { + struct line_source f_base; + fx_array *f_lines; + char *f_path; + fx_stream *f_strp; + FILE *f_fp; +}; + +extern enum bshell_status file_open(const char *path, struct file **out); +extern void file_close(struct file *file); + +#endif diff --git a/bshell/line-ed/buffer.c b/bshell/line-ed/buffer.c new file mode 100644 index 0000000..1b46fcb --- /dev/null +++ b/bshell/line-ed/buffer.c @@ -0,0 +1,40 @@ +#include "buffer.h" +#include "line-ed.h" + +const char *line_start(struct line_ed *ed, size_t y) +{ + const char *line = ed->l_buf; + + for (size_t i = 0; i < y; i++) { + line += strcspn(line, "\n"); + + if (*line == '\n') { + line++; + } + } + + return line; +} + +size_t line_length(struct line_ed *ed, size_t y) +{ + const char *line = ed->l_buf; + + for (size_t i = 0; i < y; i++) { + line += strcspn(line, "\n"); + if (*line == '\n') { + line++; + } + } + + if (*line == '\0') { + return 0; + } + + size_t len = strcspn(line, "\n"); + if (line[len] == '\n') { + len++; + } + + return len; +} diff --git a/bshell/line-ed/buffer.h b/bshell/line-ed/buffer.h new file mode 100644 index 0000000..d70956e --- /dev/null +++ b/bshell/line-ed/buffer.h @@ -0,0 +1,16 @@ +#ifndef LINE_ED_BUFFER_H_ +#define LINE_ED_BUFFER_H_ + +#include + +struct line_ed; + +/* returns a pointer to the start of the line based on the given `y` + * coordinate */ +extern const char *line_start(struct line_ed *ed, size_t y); +/* returns the length of the line based on the given `y` coordinate. + * for any line other than the last line in the buffer, this length + * INCLUDES the trailing linefeed. */ +extern size_t line_length(struct line_ed *ed, size_t y); + +#endif diff --git a/bshell/line-ed/cursor.c b/bshell/line-ed/cursor.c new file mode 100644 index 0000000..70a644d --- /dev/null +++ b/bshell/line-ed/cursor.c @@ -0,0 +1,26 @@ +#include "line-ed.h" +#include "cursor.h" +#include "prompt.h" + +void line_ed_coords_to_physical_coords( + struct line_ed *ed, size_t x, size_t y, size_t *out_x, size_t *out_y) +{ + size_t prompt_len = 0; + if (ed->l_cursor_y == 0) { + prompt_len = prompt_length(ed, PROMPT_MAIN); + } else if (ed->l_cursor_y <= ed->l_continuations) { + prompt_len = prompt_length(ed, PROMPT_CONT); + } + + if (y == 0) { + x += prompt_len; + } + + if (out_x) { + *out_x = x; + } + + if (out_y) { + *out_y = y; + } +} diff --git a/bshell/line-ed/cursor.h b/bshell/line-ed/cursor.h new file mode 100644 index 0000000..3d8a4b4 --- /dev/null +++ b/bshell/line-ed/cursor.h @@ -0,0 +1,9 @@ +#ifndef LINE_ED_CURSOR_H_ +#define LINE_ED_CURSOR_H_ + +struct line_ed; + +extern void line_ed_coords_to_physical_coords( + struct line_ed *ed, size_t x, size_t y, size_t *out_x, size_t *out_y); + +#endif diff --git a/bshell/line-ed/history.c b/bshell/line-ed/history.c new file mode 100644 index 0000000..5caa728 --- /dev/null +++ b/bshell/line-ed/history.c @@ -0,0 +1,72 @@ +#include "../misc.h" +#include "line-ed.h" + +#include +#include + +void alloc_empty_history_entry(struct line_ed *ed) +{ + fx_string *str = (fx_string *)fx_array_at( + ed->l_history, + fx_array_size(ed->l_history) - 1); + if (!str || fx_string_get_size(str, FX_STRLEN_NORMAL) > 0) { + str = fx_string_create(); + fx_array_append(ed->l_history, (fx_object *)str); + } + + ed->l_history_pos = fx_array_size(ed->l_history) - 1; +} + +void save_buf_to_history(struct line_ed *ed) +{ + fx_string *cur + = (fx_string *)fx_array_get(ed->l_history, ed->l_history_pos); + fx_string_replace_all(cur, ed->l_buf); +} + +void append_buf_to_history(struct line_ed *ed) +{ + fx_string *cur + = (fx_string *)fx_array_get(ed->l_history, ed->l_history_pos); + char s[] = {'\n', 0}; + fx_string_append_cstr(cur, s); + fx_string_append_cstr(cur, ed->l_buf); +} + +void load_buf_from_history(struct line_ed *ed) +{ + fx_string *cur + = (fx_string *)fx_array_at(ed->l_history, ed->l_history_pos); + size_t len + = MIN((size_t)(ed->l_buf_end - ed->l_buf - 1), + fx_string_get_size(cur, FX_STRLEN_NORMAL)); + + memcpy(ed->l_buf, fx_string_get_cstr(cur), len); + ed->l_buf[len] = '\0'; + + unsigned int x = 0, y = 0; + for (size_t i = 0; ed->l_buf[i]; i++) { + if (ed->l_buf[i] == '\n') { + x = 0; + y++; + } else { + x++; + } + } + + ed->l_buf_ptr = ed->l_buf + len; + ed->l_line_end = ed->l_buf_ptr; + ed->l_cursor_x = x; + ed->l_cursor_y = y; +} + +const char *last_history_line(struct line_ed *ed) +{ + size_t nlines = fx_array_size(ed->l_history); + if (nlines < 2) { + return NULL; + } + + fx_string *last = (fx_string *)fx_array_at(ed->l_history, nlines - 2); + return fx_string_get_cstr(last); +} diff --git a/bshell/line-ed/history.h b/bshell/line-ed/history.h new file mode 100644 index 0000000..d166aa2 --- /dev/null +++ b/bshell/line-ed/history.h @@ -0,0 +1,12 @@ +#ifndef LINE_ED_HISTORY_H_ +#define LINE_ED_HISTORY_H_ + +struct line_ed; + +extern void alloc_empty_history_entry(struct line_ed *ed); +extern void save_buf_to_history(struct line_ed *ed); +extern void append_buf_to_history(struct line_ed *ed); +extern void load_buf_from_history(struct line_ed *ed); +extern const char *last_history_line(struct line_ed *ed); + +#endif diff --git a/bshell/line-ed/hook.c b/bshell/line-ed/hook.c new file mode 100644 index 0000000..cb8e8f4 --- /dev/null +++ b/bshell/line-ed/hook.c @@ -0,0 +1,41 @@ +#include "hook.h" + +#include "line-ed.h" + +void line_ed_add_hook(struct line_ed *ed, struct line_ed_hook *hook) +{ + fx_queue_push_back(&ed->l_hooks, &hook->hook_entry); +} + +void line_ed_remove_hook(struct line_ed *ed, struct line_ed_hook *hook) +{ + fx_queue_delete(&ed->l_hooks, &hook->hook_entry); +} + +void hook_keypress(struct line_ed *ed, fx_keycode key) +{ + fx_queue_entry *entry = fx_queue_first(&ed->l_hooks); + while (entry) { + struct line_ed_hook *hook + = fx_unbox(struct line_ed_hook, entry, hook_entry); + if (hook->hook_keypress) { + hook->hook_keypress(ed, hook, key); + } + + entry = fx_queue_next(entry); + } +} + +void hook_buffer_modified(struct line_ed *ed) +{ + fx_queue_entry *entry = fx_queue_first(&ed->l_hooks); + while (entry) { + struct line_ed_hook *hook + = fx_unbox(struct line_ed_hook, entry, hook_entry); + if (hook->hook_buffer_modified) { + hook->hook_buffer_modified(ed, hook); + } + + entry = fx_queue_next(entry); + } +} diff --git a/bshell/line-ed/hook.h b/bshell/line-ed/hook.h new file mode 100644 index 0000000..613344a --- /dev/null +++ b/bshell/line-ed/hook.h @@ -0,0 +1,16 @@ +#ifndef LINE_ED_HOOK_H_ +#define LINE_ED_HOOK_H_ + +#include + +enum hook_id { + HOOK_KEYPRESS, + HOOK_BEFORE_PAINT, +}; + +struct line_ed; + +extern void hook_keypress(struct line_ed *ed, fx_keycode key); +extern void hook_buffer_modified(struct line_ed *ed); + +#endif diff --git a/bshell/line-ed/input.c b/bshell/line-ed/input.c new file mode 100644 index 0000000..5dbae94 --- /dev/null +++ b/bshell/line-ed/input.c @@ -0,0 +1,218 @@ +#include "buffer.h" +#include "cursor.h" +#include "history.h" +#include "hook.h" +#include "line-ed.h" +#include "prompt.h" +#include "refresh.h" + +#include + +void put_char(struct line_ed *ed, char c) +{ + if (ed->l_buf_ptr > ed->l_line_end + 1) { + return; + } + + struct refresh_state state = { + .r_prev_cursor_x = ed->l_cursor_x, + .r_prev_cursor_y = ed->l_cursor_y, + }; + + size_t prev_cursor = ed->l_buf_ptr - ed->l_buf; + + char *dest = ed->l_buf_ptr; + size_t len = ed->l_line_end - ed->l_buf_ptr + 1; + + if (dest < ed->l_line_end) { + memmove(dest + 1, dest, len); + } + + ed->l_cursor_x++; + ed->l_line_end++; + ed->l_buf_ptr++; + *dest = c; + + if (ed->l_buf_ptr == ed->l_line_end) { + *ed->l_buf_ptr = '\0'; + } + + hook_buffer_modified(ed); + + put_refresh(ed, &state); +} + +static void backspace_simple(struct line_ed *ed) +{ + if (ed->l_buf_ptr == ed->l_buf) { + return; + } + + struct refresh_state state = { + .r_prev_cursor_x = ed->l_cursor_x, + .r_prev_cursor_y = ed->l_cursor_y, + }; + + size_t prev_cursor = ed->l_buf_ptr - ed->l_buf; + + char *dest = ed->l_buf_ptr; + size_t len = ed->l_line_end - ed->l_buf_ptr + 1; + memmove(dest - 1, dest, len); + + ed->l_cursor_x--; + ed->l_line_end--; + ed->l_buf_ptr--; + + hook_buffer_modified(ed); + + backspace_simple_refresh(ed, &state); +} + +static void backspace_nl(struct line_ed *ed) +{ + size_t prev_line_len = line_length(ed, ed->l_cursor_y - 1); + + struct refresh_state state = { + .r_prev_cursor_x = ed->l_cursor_x, + .r_prev_cursor_y = ed->l_cursor_y, + .r_prev_line_len = prev_line_len, + }; + + char *dest = ed->l_buf_ptr; + size_t len = ed->l_line_end - ed->l_buf_ptr + 1; + memmove(dest - 1, dest, len); + + ed->l_cursor_x = prev_line_len - 1; + ed->l_cursor_y--; + ed->l_buf_ptr--; + ed->l_line_end--; + + hook_buffer_modified(ed); + + backspace_nl_refresh(ed, &state); +} + +void backspace(struct line_ed *ed) +{ + if (ed->l_buf_ptr == ed->l_buf) { + return; + } + + if (ed->l_cursor_x == 0 && ed->l_cursor_y <= ed->l_continuations) { + return; + } + + if (ed->l_cursor_x == 0 && ed->l_cursor_y > 0) { + backspace_nl(ed); + } else { + backspace_simple(ed); + } +} + +void cursor_left(struct line_ed *ed) +{ + if (ed->l_cursor_x != 0) { + //fputs("\010", stdout); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_CURSOR, -1); + fflush(stdout); + ed->l_cursor_x--; + ed->l_buf_ptr--; + return; + } + + if (ed->l_cursor_y <= ed->l_continuations || ed->l_buf_ptr <= ed->l_buf) { + return; + } + + ed->l_cursor_y--; + ed->l_buf_ptr--; + size_t prompt_len = 0; + if (ed->l_cursor_y == 0) { + prompt_len = prompt_length(ed, PROMPT_MAIN); + } + + size_t len = line_length(ed, ed->l_cursor_y); + ed->l_cursor_x = len - 1; + + //printf("\033[A\033[%dG", len + prompt_len); + fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, -1); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, (int)(len + prompt_len)); + + fflush(stdout); +} + +void cursor_right(struct line_ed *ed) +{ + if (ed->l_buf_ptr >= ed->l_line_end) { + return; + } + + if (*ed->l_buf_ptr != '\n') { + ed->l_cursor_x++; + ed->l_buf_ptr++; + //fputs("\033[C", stdout); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_CURSOR, 1); + fflush(stdout); + return; + } + + if (ed->l_buf_ptr >= ed->l_line_end) { + return; + } + + ed->l_cursor_y++; + ed->l_cursor_x = 0; + ed->l_buf_ptr++; + + //printf("\033[B\033[G"); + fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, 1); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, 0); + fflush(stdout); +} + +void arrow_up(struct line_ed *ed) +{ + if (ed->l_history_pos == 0) { + return; + } + + if (ed->l_cursor_y > 0) { + //printf("\033[%uA", ed->l_cursor_y); + fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, (long long)ed->l_cursor_y); + } + + //printf("\033[%zuG\033[J", prompt_length(ed, PROMPT_MAIN) + 1); + fx_tty_move_cursor_x( + ed->l_tty, FX_TTY_POS_START, (long long)prompt_length(ed, PROMPT_MAIN)); + fx_tty_clear(ed->l_tty, FX_TTY_CLEAR_SCREEN | FX_TTY_CLEAR_FROM_CURSOR); + + save_buf_to_history(ed); + ed->l_history_pos--; + load_buf_from_history(ed); + + print_buffer(ed); + fflush(stdout); +} + +void arrow_down(struct line_ed *ed) +{ + if (ed->l_history_pos == fx_array_size(ed->l_history) - 1) { + return; + } + + if (ed->l_cursor_y > 0) { + //printf("\033[%uA", ed->l_cursor_y); + fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, (int)ed->l_cursor_y); + } + + //printf("\033[%zuG\033[J", prompt_length(ed, PROMPT_MAIN) + 1); + fx_tty_move_cursor_x( + ed->l_tty, FX_TTY_POS_START, prompt_length(ed, PROMPT_MAIN) + 1); + + save_buf_to_history(ed); + ed->l_history_pos++; + load_buf_from_history(ed); + + print_buffer(ed); + fflush(stdout); +} diff --git a/bshell/line-ed/input.h b/bshell/line-ed/input.h new file mode 100644 index 0000000..bbecf93 --- /dev/null +++ b/bshell/line-ed/input.h @@ -0,0 +1,13 @@ +#ifndef LINE_ED_INPUT_H_ +#define LINE_ED_INPUT_H_ + +struct line_ed; + +extern void put_char(struct line_ed *ed, char c); +extern void backspace(struct line_ed *ed); +extern void cursor_left(struct line_ed *ed); +extern void cursor_right(struct line_ed *ed); +extern void arrow_up(struct line_ed *ed); +extern void arrow_down(struct line_ed *ed); + +#endif diff --git a/bshell/line-ed/line-ed.c b/bshell/line-ed/line-ed.c new file mode 100644 index 0000000..2bf09a1 --- /dev/null +++ b/bshell/line-ed/line-ed.c @@ -0,0 +1,301 @@ +#include "line-ed.h" + +#include "history.h" +#include "hook.h" +#include "input.h" +#include "prompt.h" + +#include +#include +#include +#include +#include +#include +#include + +#define LINE_ED_FROM_LEX_SOURCE(p) \ + ((struct line_ed *)((char *)p \ + - offsetof(struct line_ed, l_line_source))) + +static enum bshell_status readline( + struct line_source *src, + fx_stringstream *out) +{ + struct line_ed *ed = LINE_ED_FROM_LEX_SOURCE(src); + + long r = line_ed_readline(ed, out); + line_ed_set_scope_type(ed, NULL); + + if (r < 0) { + return BSHELL_ERR_EOF; + } + + return BSHELL_SUCCESS; +} + +struct line_ed *line_ed_create(void) +{ + struct line_ed *out = malloc(sizeof *out); + if (!out) { + return NULL; + } + + memset(out, 0x0, sizeof *out); + + out->l_buf = malloc(LINE_MAX); + if (!out->l_buf) { + free(out); + return NULL; + } + + out->l_history = fx_array_create(); + if (!out->l_history) { + free(out->l_buf); + free(out); + return NULL; + } + + out->l_tty = fx_stdtty; + out->l_buf_end = out->l_buf + LINE_MAX; + out->l_buf_ptr = out->l_buf; + out->l_line_end = out->l_buf; + + out->l_prompt[PROMPT_MAIN] = ">>> "; + out->l_prompt[PROMPT_CONT] = "> "; + + out->l_line_source.s_readline = readline; + + return out; +} + +void line_ed_destroy(struct line_ed *ed) +{ + fx_array_unref(ed->l_history); + free(ed->l_buf); + free(ed); +} + +void line_ed_set_flags(struct line_ed *ed, enum line_ed_flags flags) +{ + ed->l_flags |= flags; +} + +void line_ed_set_scope_type(struct line_ed *ed, const char *scope_type) +{ + ed->l_scope_type = scope_type; +} + +static void clear_buffer(struct line_ed *ed) +{ + memset(ed->l_buf, 0x0, ed->l_buf_end - ed->l_buf); + + ed->l_buf_ptr = ed->l_buf; + ed->l_line_end = ed->l_buf; + + ed->l_cursor_x = 0; + ed->l_cursor_y = 0; + + ed->l_continuations = 0; +} + +static void convert_continuation_feeds(char *s, size_t max) +{ + char *end = s + max; + size_t len = strlen(s); + + while (1) { + size_t r = strcspn(s, "\\"); + if (s + r > end) { + break; + } + + s += r; + len -= r; + + if (*s == '\0') { + break; + } + + if (*(s + 1) != '\n') { + s++; + continue; + } + + memmove(s, s + 1, len); + s += 1; + } +} + +static void remove_continuation_feeds(char *s, size_t max) +{ + char *end = s + max; + size_t len = strlen(s); + + while (1) { + size_t r = strcspn(s, "\\"); + if (s + r > end) { + break; + } + + s += r; + len -= r; + + if (*s == '\0') { + break; + } + + if (*(s + 1) != '\n') { + s++; + continue; + } + + memmove(s, s + 2, len); + s += 2; + } +} + +static bool input_is_empty(struct line_ed *ed) +{ + const char *p = ed->l_buf; + while (p < ed->l_line_end) { + if (!isspace(*p)) { + return false; + } + } + + return true; +} + +size_t line_ed_readline(struct line_ed *ed, fx_stringstream *out) +{ + clear_buffer(ed); + + bool append_history = false; + size_t append_to_index = (size_t)-1; + if (!ed->l_scope_type) { + alloc_empty_history_entry(ed); + } else { + append_history = true; + append_to_index = ed->l_history_pos; + } + + fx_tty *tty = ed->l_tty; + fx_tty_set_mode(tty, FX_TTY_RAW); + show_prompt(ed); + + for (int i = 0; ed->l_buf[i]; i++) { + if (ed->l_buf[i] == '\n') { + fputc('\r', stdout); + } + + fputc(ed->l_buf[i], stdout); + } + fflush(stdout); + + bool end = false; + bool eof = false; + + while (!end) { + fx_keycode key = fx_tty_read_key(tty); + hook_keypress(ed, key); + + if (key == FX_TTY_CTRL_KEY('d')) { + if (!input_is_empty(ed)) { + continue; + } + + eof = true; + break; + } + + if (key & FX_MOD_CTRL) { + continue; + } + + switch (key) { + case FX_KEY_RETURN: + fx_tty_reset_vmode(tty); + if (ed->l_line_end > ed->l_buf + && *(ed->l_line_end - 1) != '\\') { + end = true; + break; + } + + if (input_is_empty(ed)) { + fputc('\r', stdout); + fputc('\n', stdout); + clear_buffer(ed); + show_prompt(ed); + break; + } + + *ed->l_line_end = '\n'; + ed->l_line_end++; + ed->l_buf_ptr = ed->l_line_end; + + ed->l_cursor_x = 0; + ed->l_cursor_y++; + ed->l_continuations++; + fputc('\r', stdout); + fputc('\n', stdout); + // fputs("\033[G\n", stdout); + show_prompt(ed); + break; + case FX_KEY_BACKSPACE: + backspace(ed); + break; + case FX_KEY_ARROW_LEFT: + cursor_left(ed); + break; + case FX_KEY_ARROW_RIGHT: + cursor_right(ed); + break; + case FX_KEY_ARROW_UP: + arrow_up(ed); + break; + case FX_KEY_ARROW_DOWN: + arrow_down(ed); + break; + default: + if (iswgraph(key) || key == ' ') { + put_char(ed, key); + } + break; + } + } + + fx_tty_set_mode(tty, FX_TTY_CANONICAL); + fputc('\n', stdout); + + if (*ed->l_buf == '\0') { + return eof ? -1 : 0; + } + + if (append_history) { + ed->l_history_pos = append_to_index; + append_buf_to_history(ed); + } else { + ed->l_history_pos = fx_array_size(ed->l_history) - 1; + + const char *last = last_history_line(ed); + if (!last || strcmp(last, ed->l_buf) != 0) { + save_buf_to_history(ed); + } + } + + size_t sz = ed->l_line_end - ed->l_buf + 1; + + if ((ed->l_flags & LINE_ED_REMOVE_CONTINUATIONS) + == LINE_ED_REMOVE_CONTINUATIONS) { + remove_continuation_feeds(ed->l_buf, sz); + } else if ( + (ed->l_flags & LINE_ED_CONVERT_CONTINUATIONS) + == LINE_ED_CONVERT_CONTINUATIONS) { + convert_continuation_feeds(ed->l_buf, sz); + } + + fx_stream_write_cstr(out, ed->l_buf, NULL); + fx_stream_write_char(out, '\n'); + + return sz; +} diff --git a/bshell/line-ed/line-ed.h b/bshell/line-ed/line-ed.h new file mode 100644 index 0000000..f681a1b --- /dev/null +++ b/bshell/line-ed/line-ed.h @@ -0,0 +1,107 @@ +#ifndef LINE_ED_H_ +#define LINE_ED_H_ + +#define LINE_MAX 4096 + +#include "../line-source.h" + +#include +#include +#include +#include + +struct s_tty; +struct fx_tty_vmode; + +struct line_ed; + +struct line_ed_hook { + void (*hook_keypress)( + struct line_ed *, + struct line_ed_hook *, + fx_keycode); + void (*hook_buffer_modified)(struct line_ed *, struct line_ed_hook *); + fx_queue_entry hook_entry; +}; + +enum line_ed_flags { + /* always reprint an entire line when a character is added/deleted. + * without this flag, only the modified character any subsequent + * characters are reprinted. */ + LINE_ED_FULL_REPRINT = 0x01u, + + /* keep line continuation (backslash-newline) tokens in the output + * buffer (default behaviour) */ + LINE_ED_KEEP_CONTINUATIONS = 0x00u, + /* convert line continuation tokens in the output buffer to simple + * linefeeds. */ + LINE_ED_CONVERT_CONTINUATIONS = 0x02u, + /* remove line continuation tokens from the output buffer, so that all + * chars are on a single line */ + LINE_ED_REMOVE_CONTINUATIONS = 0x06u, +}; + +struct line_ed { + enum line_ed_flags l_flags; + + /* array of basic prompt strings */ + const char *l_prompt[2]; + /* input buffer, pointer to the buffer cell that corresponds to + * the current cursor position, and pointer to the byte AFTER the last + * usable byte in the buffer */ + char *l_buf, *l_buf_ptr, *l_buf_end; + /* pointer to the byte AFTER the last byte of the user's input */ + char *l_line_end; + /* 2-dimensional coordinates of the current cursor position. + * this does NOT include any prompts that are visible on the terminal */ + size_t l_cursor_x, l_cursor_y; + /* the number of line continuations that have been inputted */ + unsigned int l_continuations; + + struct line_source l_line_source; + + /* pointer to tty interface */ + fx_tty *l_tty; + + /* the lexical scope that we are currently in. + * this is provided by components further up the input pipeline, + * for example, when we are inside a string or if-statement. */ + const char *l_scope_type; + + /* array of previously entered commands */ + fx_array *l_history; + /* index of the currently selected history entry */ + size_t l_history_pos; + + /* list of defined highlight ranges */ + fx_queue l_hl_ranges; + /* list of installed hooks */ + fx_queue l_hooks; +}; + +extern struct line_ed *line_ed_create(void); +extern void line_ed_destroy(struct line_ed *ed); +extern void line_ed_set_flags(struct line_ed *ed, enum line_ed_flags flags); +extern void line_ed_set_scope_type(struct line_ed *ed, const char *scope_type); + +extern void line_ed_put_highlight( + struct line_ed *ed, + unsigned long start_x, + unsigned long start_y, + unsigned long end_x, + unsigned long end_y, + const struct fx_tty_vmode *vmode); +extern void line_ed_clear_highlights(struct line_ed *ed); +extern void line_ed_print_highlights(struct line_ed *ed); + +extern void line_ed_add_hook(struct line_ed *ed, struct line_ed_hook *hook); +extern void line_ed_remove_hook(struct line_ed *ed, struct line_ed_hook *hook); + +extern size_t line_ed_readline(struct line_ed *ed, fx_stringstream *out); + +static inline struct line_source *line_ed_to_line_source(struct line_ed *ed) +{ + return &ed->l_line_source; +} + +#endif diff --git a/bshell/line-ed/prompt.c b/bshell/line-ed/prompt.c new file mode 100644 index 0000000..963109c --- /dev/null +++ b/bshell/line-ed/prompt.c @@ -0,0 +1,27 @@ +#include +#include "line-ed.h" +#include "prompt.h" + +void show_prompt(struct line_ed *ed) +{ + int type = PROMPT_MAIN; + if (ed->l_scope_type) { + type = PROMPT_CONT; + + /* this is a temporary solution to show the current + * scope type, until prompts are implemented properly. */ + fputs(ed->l_scope_type, stdout); + } + + if (ed->l_continuations > 0) { + type = PROMPT_CONT; + } + + fputs(ed->l_prompt[type], stdout); + fflush(stdout); +} + +size_t prompt_length(struct line_ed *ed, int prompt_id) +{ + return strlen(ed->l_prompt[prompt_id]); +} diff --git a/bshell/line-ed/prompt.h b/bshell/line-ed/prompt.h new file mode 100644 index 0000000..eb135e3 --- /dev/null +++ b/bshell/line-ed/prompt.h @@ -0,0 +1,12 @@ +#ifndef LINE_ED_PROMPT_H_ +#define LINE_ED_PROMPT_H_ + +#define PROMPT_MAIN 0 +#define PROMPT_CONT 1 + +struct line_ed; + +extern void show_prompt(struct line_ed *ed); +extern size_t prompt_length(struct line_ed *ed, int prompt_id); + +#endif diff --git a/bshell/line-ed/refresh.c b/bshell/line-ed/refresh.c new file mode 100644 index 0000000..7167e28 --- /dev/null +++ b/bshell/line-ed/refresh.c @@ -0,0 +1,265 @@ +#include "refresh.h" + +#include "buffer.h" +#include "cursor.h" +#include "line-ed.h" + +#include +#include +#include + +/* prints the provided string to the terminal, applying any relevant highlight + * ranges. this function prints all characters in `s` until it encounters a null + * char (\0) or linefeed (\n). + * + * the (x, y) coordinates provided should be the data coordinates of the + * first character in `s`. + */ +void print_text(struct line_ed *ed, size_t x, size_t y, const char *s) +{ + fx_tty *tty = ed->l_tty; + + for (size_t i = 0; s[i] != '\n' && s[i] != '\0'; i++) { + fputc(s[i], stdout); + } +} + +void print_buffer(struct line_ed *ed) +{ + const char *line_buf = ed->l_buf; + size_t line_len = strcspn(line_buf, "\n"); + + unsigned int y = 0; + + while (1) { + print_text(ed, 0, y, line_buf); + + line_buf += line_len; + if (*line_buf == '\n') { + line_buf++; + } + + if (*line_buf == '\0') { + break; + } + + y++; + line_len = strcspn(line_buf, "\n"); + + fputc('\r', stdout); + fputc('\n', stdout); + } +} + +/* this function is called after a character is inserted into the line_ed + *buffer. the function performs the following steps: + * 1. get a pointer to the start of the line that was modified. + * 2. determine the first character in the line that needs to be redrawn. + * this may result in an append, a partial reprint, or a full reprint. + * 3. re-print the relevant portion of the buffer: + * for an append (a character added to the end of the line): + * * write the inserted char to the terminal. + * * done. + * for a partial reprint: + * * clear all printed chars from the logical cursor position to + *the end of the line. + * * print the portion of the line corresponding to the cleared + *section. + * * move the physical (terminal) cursor backwards until its + *position matches the logical (line_ed) cursor. for a full reprint: + * * same as a partial reprint except that, rather than reprinting + * from the logical cursor position, the entire line is + *reprinted. + */ +void put_refresh(struct line_ed *ed, struct refresh_state *state) +{ + /* get the data for the line that is being refreshed */ + const char *line_buf = line_start(ed, ed->l_cursor_y); + size_t line_len = strcspn(line_buf, "\n"); + + /* the index of the first char in line_buf that needs to be reprinted */ + size_t start_x = state->r_prev_cursor_x; + + /* the distance between the first char to be reprinted and the end + * of the line. + * the physical cursor will be moved back by this amount after the + * line is reprinted. */ + long cursor_rdelta = (long)(line_len - start_x); + + if (ed->l_flags & LINE_ED_FULL_REPRINT) { + if (start_x) { + // fprintf(stdout, "\033[%uD", start_x); + fx_tty_move_cursor_x( + ed->l_tty, + FX_TTY_POS_CURSOR, + -(long long)start_x); + } + + start_x = 0; + } + + print_text(ed, start_x, ed->l_cursor_y, line_buf + start_x); + + /* decrement the rdelta (move the cursor back one fewer cells), + * so that the physical cursor will be placed AFTER the character that + * was just inserted. */ + cursor_rdelta--; + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_CURSOR, -cursor_rdelta); + +#if 0 + for (unsigned int i = 0; i < cursor_rdelta; i++) { + fputs("\010", stdout); + } +#endif + + fflush(stdout); +} + +/* this function is called after a character is removed from the line_ed buffer. + * IF the character was a linefeed. + * + * this is separate from backspace_simple_refresh because, in this situation, + * the cursor position depends on the length of the previous line before + * the linefeed was deleted, and we have to reprint every line following the + * two that were combined. + */ +void backspace_nl_refresh(struct line_ed *ed, struct refresh_state *state) +{ + /* get the data for the line that is being refreshed. + * relative to where the cursor was before the linefeed was deleted, + * this is the PREVIOUS line. */ + const char *line_buf = line_start(ed, ed->l_cursor_y); + size_t line_len = strcspn(line_buf, "\n"); + + /* the index of the first char in line_buf that needs to be reprinted. + * subtract one to account for the linefeed that has since been deleted. + */ + size_t start_x = state->r_prev_line_len - 1; + + /* the column to move the physical cursor to after it has been moved + * to the previous line. + * NOTE that this number includes the length of the prompt! + * we add 1 to start_x to ensure that the cursor is moved to the cell + * AFTER the last char of the line. */ + size_t new_x; + line_ed_coords_to_physical_coords( + ed, + start_x + 1, + ed->l_cursor_y, + &new_x, + NULL); + + /* the physical cursor is currently at the beginning of the line that + * has just been moved up. from here, clear this line and the rest + * from the screen. */ + // fputs("\033[J", stdout); + fx_tty_clear(ed->l_tty, FX_TTY_CLEAR_SCREEN | FX_TTY_CLEAR_FROM_CURSOR); + + if (ed->l_flags & LINE_ED_FULL_REPRINT) { + /* next, move the physical cursor up and to the beginning of the + * previous line */ + size_t tmp_x; + line_ed_coords_to_physical_coords( + ed, + 0, + ed->l_cursor_y, + &tmp_x, + NULL); + fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, -1); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, tmp_x); + // fprintf(stdout, "\033[A\033[%uG", tmp_x + 1); + start_x = 0; + } else { + /* next, move the physical cursor up and to the end of the + * previous line */ + // fprintf(stdout, "\033[A\033[%uG", new_x); + fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, -1); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, new_x); + } + + /* now reprint all of the buffer lines, starting with the first of the + * two lines that were concatenated. */ + size_t ydiff = 0; + while (1) { + print_text( + ed, + start_x, + ed->l_cursor_y + ydiff, + line_buf + start_x); + + line_buf += line_len + 1; + line_len = strcspn(line_buf, "\n"); + start_x = 0; + + if (*line_buf == '\0') { + break; + } + + fputc('\r', stdout); + fputc('\n', stdout); + ydiff++; + } + + /* finally, move the cursor BACK to the point where the two lines + * were concatenated. */ + if (ydiff) { + // fprintf(stdout, "\033[%uA", ydiff); + fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, ydiff); + } + + // fprintf(stdout, "\033[%uG", new_x); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, new_x); + fflush(stdout); +} + +/* this function is called after a character is removed from the line_ed buffer. + * IF the character was not a linefeed. + */ +void backspace_simple_refresh(struct line_ed *ed, struct refresh_state *state) +{ + /* get the data for the line that is being refreshed */ + const char *line_buf = line_start(ed, ed->l_cursor_y); + size_t line_len = strcspn(line_buf, "\n"); + + /* the index of the first char in line_buf that needs to be reprinted */ + size_t start_x = ed->l_cursor_x; + // get_data_cursor_position(ed, &start_x, NULL); + + /* the distance between the first char to be reprinted and the end + * of the line. + * the physical cursor will be moved back by this amount after the + * line is reprinted. */ + long long cursor_rdelta = (long long)(line_len - start_x); + + if (ed->l_flags & LINE_ED_FULL_REPRINT) { + if (start_x) { + // fprintf(stdout, "\033[%uD", start_x); + fx_tty_move_cursor_x( + ed->l_tty, + FX_TTY_POS_CURSOR, + -(long long)start_x); + } + + start_x = 0; + } + + // fputc('\010', stdout); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_CURSOR, -1); + print_text(ed, start_x, ed->l_cursor_y, line_buf + start_x); + fputc(' ', stdout); + + /* increment the rdelta (move the cursor back one more cell), so + * that the cursor will appear to move back one cell (to accord with + * the fact that backspace was just pressed) */ + cursor_rdelta++; + + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_CURSOR, -cursor_rdelta); + +#if 0 + for (unsigned int i = 0; i < cursor_rdelta; i++) { + fputs("\010", stdout); + } +#endif + + fflush(stdout); +} diff --git a/bshell/line-ed/refresh.h b/bshell/line-ed/refresh.h new file mode 100644 index 0000000..94ee3e5 --- /dev/null +++ b/bshell/line-ed/refresh.h @@ -0,0 +1,28 @@ +#ifndef LINE_ED_REFRESH_H_ +#define LINE_ED_REFRESH_H_ + +#include + +struct line_ed; + +struct refresh_state { + /* cursor position before the update was performed (excluding the + * prompt) */ + size_t r_prev_cursor_x, r_prev_cursor_y; + /* when a backspace results in two separate lines being combined, + * this property contains the length of the first of the two combined + * lines BEFORE the concotenation was performed */ + size_t r_prev_line_len; +}; + +extern void print_text(struct line_ed *ed, size_t x, size_t y, const char *s); +extern void print_buffer(struct line_ed *ed); +extern void put_refresh(struct line_ed *ed, struct refresh_state *state); +extern void backspace_nl_refresh( + struct line_ed *ed, + struct refresh_state *state); +extern void backspace_simple_refresh( + struct line_ed *ed, + struct refresh_state *state); + +#endif diff --git a/bshell/line-source.c b/bshell/line-source.c new file mode 100644 index 0000000..0a9c6e6 --- /dev/null +++ b/bshell/line-source.c @@ -0,0 +1,39 @@ +#include "line-source.h" + +enum bshell_status line_source_get_name( + struct line_source *src, + char *buf, + size_t count, + size_t *nr_read) +{ + if (src->s_get_name) { + return src->s_get_name(src, buf, count, nr_read); + } + + return BSHELL_ERR_NOT_SUPPORTED; +} + +enum bshell_status line_source_readline( + struct line_source *src, + fx_stringstream *out) +{ + if (src->s_readline) { + return src->s_readline(src, out); + } + + return BSHELL_ERR_NOT_SUPPORTED; +} + +enum bshell_status line_source_get_row( + struct line_source *src, + size_t row, + char *buf, + size_t count, + size_t *nr_read) +{ + if (src->s_get_row) { + return src->s_get_row(src, row, buf, count, nr_read); + } + + return BSHELL_ERR_NOT_SUPPORTED; +} diff --git a/bshell/line-source.h b/bshell/line-source.h new file mode 100644 index 0000000..537dfb7 --- /dev/null +++ b/bshell/line-source.h @@ -0,0 +1,37 @@ +#ifndef LINE_SOURCE_H_ +#define LINE_SOURCE_H_ + +#include "status.h" + +#include +#include + +struct line_source { + enum bshell_status ( + *s_get_name)(struct line_source *, char *, size_t, size_t *); + enum bshell_status ( + *s_readline)(struct line_source *, fx_stringstream *); + enum bshell_status (*s_get_row)( + struct line_source *, + size_t, + char *, + size_t, + size_t *); +}; + +extern enum bshell_status line_source_get_name( + struct line_source *src, + char *buf, + size_t count, + size_t *nr_read); +extern enum bshell_status line_source_readline( + struct line_source *src, + fx_stringstream *out); +extern enum bshell_status line_source_get_row( + struct line_source *src, + size_t row, + char *buf, + size_t count, + size_t *nr_read); + +#endif diff --git a/bshell/main.c b/bshell/main.c index e69de29..acc6541 100644 --- a/bshell/main.c +++ b/bshell/main.c @@ -0,0 +1,38 @@ +#include "file.h" +#include "line-ed/line-ed.h" + +#include + +int main(int argc, const char **argv) +{ + printf("B Shell " BSHELL_VERSION "\n"); + + struct line_source *linesrc = NULL; + + enum bshell_status status = BSHELL_SUCCESS; + + if (argc > 1) { + struct file *file = NULL; + status = file_open(argv[1], &file); + linesrc = &file->f_base; + } else { + struct line_ed *ed = line_ed_create(); + linesrc = line_ed_to_line_source(ed); + } + + fx_stringstream *linebuf = fx_stringstream_create(); + + while (1) { + enum bshell_status status + = line_source_readline(linesrc, linebuf); + + if (status != BSHELL_SUCCESS) { + break; + } + + printf("%s", fx_stringstream_ptr(linebuf)); + fx_stringstream_reset(linebuf); + } + + return 0; +} diff --git a/bshell/misc.h b/bshell/misc.h new file mode 100644 index 0000000..f7ff770 --- /dev/null +++ b/bshell/misc.h @@ -0,0 +1,7 @@ +#ifndef MISC_H_ +#define MISC_H_ + +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#define MAX(x, y) ((x) > (y) ? (x) : (y)) + +#endif diff --git a/bshell/status.c b/bshell/status.c new file mode 100644 index 0000000..662c383 --- /dev/null +++ b/bshell/status.c @@ -0,0 +1,20 @@ +#include "status.h" + +#include + +enum bshell_status bshell_status_from_errno(int err) +{ + switch (err) { + case 0: + return BSHELL_SUCCESS; + case EIO: + return BSHELL_ERR_IO_FAILURE; + case ENOENT: + return BSHELL_ERR_NO_ENTRY; + case EPERM: + case EACCES: + return BSHELL_ERR_ACCESS_DENIED; + default: + return BSHELL_ERR_INTERNAL_FAILURE; + } +} diff --git a/bshell/status.h b/bshell/status.h new file mode 100644 index 0000000..f0b1da6 --- /dev/null +++ b/bshell/status.h @@ -0,0 +1,24 @@ +#ifndef STATUS_H_ +#define STATUS_H_ + +enum bshell_status { + BSHELL_SUCCESS = 0, + BSHELL_ERR_EOF, + BSHELL_ERR_BAD_SYNTAX, + BSHELL_ERR_BAD_FORMAT, + BSHELL_ERR_BAD_STATE, + BSHELL_ERR_INVALID_VALUE, + BSHELL_ERR_INVALID_ARGUMENT, + BSHELL_ERR_NO_MEMORY, + BSHELL_ERR_NO_ENTRY, + BSHELL_ERR_NO_DATA, + BSHELL_ERR_NAME_EXISTS, + BSHELL_ERR_NOT_SUPPORTED, + BSHELL_ERR_IO_FAILURE, + BSHELL_ERR_ACCESS_DENIED, + BSHELL_ERR_INTERNAL_FAILURE, +}; + +extern enum bshell_status bshell_status_from_errno(int err); + +#endif diff --git a/cmake/FindFX.cmake b/cmake/FindFX.cmake new file mode 100644 index 0000000..a42535e --- /dev/null +++ b/cmake/FindFX.cmake @@ -0,0 +1,124 @@ +#[=======================================================================[.rst: +FindFX +------------ + +Find the FX library and header directories + +Imported Targets +^^^^^^^^^^^^^^^^ + +This module defines the following :prop_tgt:`IMPORTED` target: + +``FX::FX`` +The FX library, if found + +Result Variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables in your project: + +``FX_FOUND`` +true if the FX C headers and libraries were found +``FX_INCLUDE_DIR`` +directories containing the FX C headers. + +``FX_LIBRARY`` +the C library to link against + +Hints +^^^^^ + +The user may set the environment variable ``FX_PREFIX`` to the root +directory of a FX library installation. +#]=======================================================================] + +set (FX_SEARCH_PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr/local/share + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt + ${FX_PREFIX} + $ENV{FX_PREFIX}) + +if (FX_STATIC) + set(_lib_suffix "-s") +endif () + +set(assemblies ${FX_FIND_COMPONENTS}) +set(required_vars) + +if (NOT FX_INCLUDE_DIR) + find_path(FX_INCLUDE_DIR + NAMES fx/misc.h ${FX_FIND_ARGS} + PATH_SUFFIXES include + PATHS ${FX_SEARCH_PATHS}) +endif () + +set(required_vars FX_INCLUDE_DIR) + +foreach (assembly ${assemblies}) + string(TOLOWER ${assembly} header_name) + string(REPLACE "." "_" macro_name ${assembly}) + string(TOUPPER ${macro_name} macro_name) + + set(lib_name ${assembly}${_lib_suffix}) + + if (NOT ${macro_name}_LIBRARY) + find_library(${macro_name}_LIBRARY + NAMES ${lib_name} ${FX_FIND_ARGS} + PATH_SUFFIXES lib + PATHS ${FX_SEARCH_PATHS}) + else () + # on Windows, ensure paths are in canonical format (forward slahes): + file(TO_CMAKE_PATH "${${macro_name}_LIBRARY}" ${macro_name}_LIBRARY) + endif() + + list(APPEND required_vars ${macro_name}_LIBRARY) +endforeach (assembly) + +unset(FX_FIND_ARGS) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(FX + REQUIRED_VARS ${required_vars}) + +if (FX_FOUND) + set(created_targets) + foreach (assembly ${assemblies}) + set(target_name ${assembly}) + string(REPLACE "fx." "" target_name ${target_name}) + string(SUBSTRING ${target_name} 0 1 target_name_prefix) + string(TOUPPER ${target_name_prefix} target_name_prefix) + string(SUBSTRING ${target_name} 1 -1 target_name_suffix) + set(target_name ${target_name_prefix}${target_name_suffix}) + + string(TOLOWER ${assembly} header_name) + string(REPLACE "." "_" macro_name ${assembly}) + string(REPLACE "." "_" macro_name ${assembly}) + string(TOUPPER ${macro_name} macro_name) + + set(lib_name ${assembly}${_lib_suffix}) + + if (NOT TARGET FX::${target_name}) + add_library(FX::${target_name} UNKNOWN IMPORTED) + set_target_properties(FX::${target_name} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${FX_INCLUDE_DIR}") + target_compile_definitions(FX::${target_name} INTERFACE _CRT_SECURE_NO_WARNINGS=1) + + if (FX_STATIC) + target_compile_definitions(FX::${target_name} INTERFACE FX_STATIC=1) + endif () + + set_target_properties(FX::${target_name} PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${${macro_name}_LIBRARY}") + set(created_targets ${created_targets} ${assembly}) + endif () + endforeach (assembly) +endif()