From 4291a61a92ebad484d14479253f2de62c2e9b325 Mon Sep 17 00:00:00 2001 From: "K. Lange" Date: Tue, 30 May 2023 08:05:56 +0900 Subject: [PATCH] kuroko: integrated language runtime support --- .gitignore | 1 + bootstrap | 1 + common/GNUmakefile | 5 +- common/console.c | 4 + common/entry.s3.c | 3 + common/kuroko-limine.h | 8 + common/kuroko/builtins.c | 1 + common/kuroko/chunk.c | 1 + common/kuroko/compiler.c | 1 + common/kuroko/debug.c | 1 + common/kuroko/exceptions.c | 1 + common/kuroko/limine-os.c | 198 +++ common/kuroko/malloc.c | 211 +++ common/kuroko/memory.c | 1 + common/kuroko/methods.h | 1 + common/kuroko/obj_base.c | 1 + common/kuroko/obj_bytes.c | 1 + common/kuroko/obj_dict.c | 1 + common/kuroko/obj_function.c | 1 + common/kuroko/obj_gen.c | 1 + common/kuroko/obj_list.c | 1 + common/kuroko/obj_long.c | 1 + common/kuroko/obj_numeric.c | 1 + common/kuroko/obj_range.c | 1 + common/kuroko/obj_set.c | 1 + common/kuroko/obj_slice.c | 1 + common/kuroko/obj_str.c | 1 + common/kuroko/obj_tuple.c | 1 + common/kuroko/obj_typing.c | 1 + common/kuroko/object.c | 1 + common/kuroko/opcode_enum.h | 1 + common/kuroko/opcodes.h | 1 + common/kuroko/parseargs.c | 1 + common/kuroko/private.h | 1 + common/kuroko/repl.c | 464 +++++++ common/kuroko/rline.c | 2348 ++++++++++++++++++++++++++++++++++ common/kuroko/scanner.c | 1 + common/kuroko/stubs.c | 568 ++++++++ common/kuroko/sys.c | 1 + common/kuroko/table.c | 1 + common/kuroko/threads.c | 1 + common/kuroko/value.c | 1 + common/kuroko/vm.c | 1 + common/kuroko/wcwidth._h | 1 + common/lib/readline.c | 4 +- common/menu.c | 11 + kuroko-inc/assert.h | 3 + kuroko-inc/dirent.h | 13 + kuroko-inc/errno.h | 0 kuroko-inc/inttypes.h | 28 + kuroko-inc/kuroko | 1 + kuroko-inc/rline.h | 63 + kuroko-inc/stdio.h | 36 + kuroko-inc/stdlib.h | 19 + kuroko-inc/string.h | 34 + kuroko-inc/sys/stat.h | 0 kuroko-inc/sys/time.h | 0 kuroko-inc/sys/types.h | 0 kuroko-inc/time.h | 9 + kuroko-inc/unistd.h | 0 60 files changed, 4063 insertions(+), 2 deletions(-) create mode 100644 common/kuroko-limine.h create mode 120000 common/kuroko/builtins.c create mode 120000 common/kuroko/chunk.c create mode 120000 common/kuroko/compiler.c create mode 120000 common/kuroko/debug.c create mode 120000 common/kuroko/exceptions.c create mode 100644 common/kuroko/limine-os.c create mode 100644 common/kuroko/malloc.c create mode 120000 common/kuroko/memory.c create mode 120000 common/kuroko/methods.h create mode 120000 common/kuroko/obj_base.c create mode 120000 common/kuroko/obj_bytes.c create mode 120000 common/kuroko/obj_dict.c create mode 120000 common/kuroko/obj_function.c create mode 120000 common/kuroko/obj_gen.c create mode 120000 common/kuroko/obj_list.c create mode 120000 common/kuroko/obj_long.c create mode 120000 common/kuroko/obj_numeric.c create mode 120000 common/kuroko/obj_range.c create mode 120000 common/kuroko/obj_set.c create mode 120000 common/kuroko/obj_slice.c create mode 120000 common/kuroko/obj_str.c create mode 120000 common/kuroko/obj_tuple.c create mode 120000 common/kuroko/obj_typing.c create mode 120000 common/kuroko/object.c create mode 120000 common/kuroko/opcode_enum.h create mode 120000 common/kuroko/opcodes.h create mode 120000 common/kuroko/parseargs.c create mode 120000 common/kuroko/private.h create mode 100644 common/kuroko/repl.c create mode 100644 common/kuroko/rline.c create mode 120000 common/kuroko/scanner.c create mode 100644 common/kuroko/stubs.c create mode 120000 common/kuroko/sys.c create mode 120000 common/kuroko/table.c create mode 120000 common/kuroko/threads.c create mode 120000 common/kuroko/value.c create mode 120000 common/kuroko/vm.c create mode 120000 common/kuroko/wcwidth._h create mode 100644 kuroko-inc/assert.h create mode 100644 kuroko-inc/dirent.h create mode 100644 kuroko-inc/errno.h create mode 100644 kuroko-inc/inttypes.h create mode 120000 kuroko-inc/kuroko create mode 100644 kuroko-inc/rline.h create mode 100644 kuroko-inc/stdio.h create mode 100644 kuroko-inc/stdlib.h create mode 100644 kuroko-inc/string.h create mode 100644 kuroko-inc/sys/stat.h create mode 100644 kuroko-inc/sys/time.h create mode 100644 kuroko-inc/sys/types.h create mode 100644 kuroko-inc/time.h create mode 100644 kuroko-inc/unistd.h diff --git a/.gitignore b/.gitignore index cc678579..a2c6a9ae 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ /common-uefi-riscv64 /decompressor-build /stage1.stamp +/kuroko diff --git a/bootstrap b/bootstrap index 87251592..b2f82155 100755 --- a/bootstrap +++ b/bootstrap @@ -25,6 +25,7 @@ fi [ -d freestanding-headers ] || git clone https://github.com/mintsuki/freestanding-headers.git $SHALLOW_CLONE_FLAG [ -d limine-efi ] || git clone https://github.com/limine-bootloader/limine-efi.git $SHALLOW_CLONE_FLAG [ -d libgcc-binaries ] || git clone https://github.com/mintsuki/libgcc-binaries.git $SHALLOW_CLONE_FLAG +[ -d kuroko ] || git clone https://github.com/kuroko-lang/kuroko.git $SHALLOW_CLONE_FLAG mkdir -p build-aux cp "$(automake --print-libdir)/install-sh" build-aux diff --git a/common/GNUmakefile b/common/GNUmakefile index f27f7ad4..f92181d2 100644 --- a/common/GNUmakefile +++ b/common/GNUmakefile @@ -70,6 +70,9 @@ override CPPFLAGS_FOR_TARGET := \ -MMD \ -MP +$(call MKESCAPE,$(BUILDDIR))/./kuroko/%.o: override CFLAGS_FOR_TARGET += -DKRK_STATIC_ONLY -DKRK_DISABLE_THREADS -DKRK_NO_FILESYSTEM -DKRK_NO_CALLGRIND -DKRK_NO_SOURCE_IN_TRACEBACK -DKRK_NO_FLOAT -DKRK_NO_GC_TRACING -Wno-shadow -Wno-unused-parameter -Wno-misleading-indentation +$(call MKESCAPE,$(BUILDDIR))/./kuroko/%.o: override CPPFLAGS_FOR_TARGET += -I'$(call SHESCAPE,$(BUILDDIR))/../kuroko-inc' + ifeq ($(TARGET),bios) override CFLAGS_FOR_TARGET += \ -fno-PIE \ @@ -200,7 +203,7 @@ ifeq ($(TARGET),uefi-riscv64) -z text endif -override C_FILES := $(shell find . -type f -name '*.c') +override C_FILES := $(shell find . -xtype f -name '*.c') ifeq ($(TARGET),bios) override ASMX86_FILES := $(shell find . -type f -name '*.asm_x86') override ASM32_FILES := $(shell find . -type f -name '*.asm_ia32') diff --git a/common/console.c b/common/console.c index a0b099fd..00049b66 100644 --- a/common/console.c +++ b/common/console.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,7 @@ static void console_help(void) { "exit -- Exit Limine console.\n" "clear -- Clear the console.\n" "%s" + "kuroko -- Start a Kuroko repl.\n" "lsvol -- List volumes.\n" "version -- Print version.\n" "copyright -- Print copyright.\n" @@ -55,6 +57,8 @@ void console(void) { print(LIMINE_COPYRIGHT "\n"); print("Limine is distributed under the terms of the BSD-2-Clause license.\n"); print("There is ABSOLUTELY NO WARRANTY, to the extent permitted by law.\n"); + } else if (strcmp(prompt, "kuroko") == 0) { + limine_krk_enter_repl(); } else if (*prompt != 0) { print("Invalid command: `%s`.\n", prompt); } diff --git a/common/entry.s3.c b/common/entry.s3.c index a4a0bb27..4071215c 100644 --- a/common/entry.s3.c +++ b/common/entry.s3.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -130,6 +131,8 @@ noreturn void stage3_common(void) { init_io_apics(); #endif + limine_krk_init(); + term_notready(); menu(true); diff --git a/common/kuroko-limine.h b/common/kuroko-limine.h new file mode 100644 index 00000000..ebea6ca8 --- /dev/null +++ b/common/kuroko-limine.h @@ -0,0 +1,8 @@ +#ifndef __KUROKO_LIMINE_H__ +#define __KUROKO_LIMINE_H__ + +void limine_krk_init(void); +void limine_krk_enter_repl(void); +void limine_krk_cleanup(void); + +#endif diff --git a/common/kuroko/builtins.c b/common/kuroko/builtins.c new file mode 120000 index 00000000..02a69880 --- /dev/null +++ b/common/kuroko/builtins.c @@ -0,0 +1 @@ +../../kuroko/src/builtins.c \ No newline at end of file diff --git a/common/kuroko/chunk.c b/common/kuroko/chunk.c new file mode 120000 index 00000000..61f60dbb --- /dev/null +++ b/common/kuroko/chunk.c @@ -0,0 +1 @@ +../../kuroko/src/chunk.c \ No newline at end of file diff --git a/common/kuroko/compiler.c b/common/kuroko/compiler.c new file mode 120000 index 00000000..9313b220 --- /dev/null +++ b/common/kuroko/compiler.c @@ -0,0 +1 @@ +../../kuroko/src/compiler.c \ No newline at end of file diff --git a/common/kuroko/debug.c b/common/kuroko/debug.c new file mode 120000 index 00000000..e1b8bc5e --- /dev/null +++ b/common/kuroko/debug.c @@ -0,0 +1 @@ +../../kuroko/src/debug.c \ No newline at end of file diff --git a/common/kuroko/exceptions.c b/common/kuroko/exceptions.c new file mode 120000 index 00000000..5469e620 --- /dev/null +++ b/common/kuroko/exceptions.c @@ -0,0 +1 @@ +../../kuroko/src/exceptions.c \ No newline at end of file diff --git a/common/kuroko/limine-os.c b/common/kuroko/limine-os.c new file mode 100644 index 00000000..ed89a344 --- /dev/null +++ b/common/kuroko/limine-os.c @@ -0,0 +1,198 @@ +/** + * @file limine-os.c + * @brief Limine bindings for os, time, fileio modules. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern int krk_exitRepl; +extern void krk_repl(void); + +KRK_Function(uname) { + KrkValue result = krk_dict_of(0, NULL, 0); + krk_push(result); + krk_attachNamedObject(AS_DICT(result), "sysname", (KrkObj*)S("Limine")); + krk_attachNamedObject(AS_DICT(result), "release", (KrkObj*)S(LIMINE_VERSION)); + krk_attachNamedObject(AS_DICT(result), "nodename", (KrkObj*)S("")); + + krk_attachNamedObject(AS_DICT(result), "version", (KrkObj*) +#if defined(BIOS) + S("BIOS") +#elif defined(UEFI) + S("UEFI") +#else + S("") +#endif + ); + + krk_attachNamedObject(AS_DICT(result), "machine", (KrkObj*) +#if defined(__x86_64__) + S("x86-64") +#elif defined(__aarch64__) + S("aarch64") +#elif defined(__i386__) + S("i386") +#elif defined(__riscv64) + S("riscv64") +#else + S("unknown") +#endif + ); + + return krk_pop(); +} + +/** + * def config_get_value(key: str, index: int = 0, config: str = None) -> str + */ +KRK_Function(config_get_value) { + char * config = NULL; + char * key = NULL; + size_t index = 0; + if (!krk_parseArgs("s|Nz", (const char*[]){"key", "index", "config"}, &key, &index, &config)) return NONE_VAL(); + char * value = config_get_value(config, index, key); + + if (!value) { + return NONE_VAL(); + } + + return OBJECT_VAL(krk_copyString(value, strlen(value))); +} + +/** + * def boot(config: str) -> does not return + */ +KRK_Function(boot) { + char * config = NULL; + if (!krk_parseArgs("s", (const char*[]){"config"}, &config)) return NONE_VAL(); + + boot(config); +} + +/** + * def getchar() -> int + */ +KRK_Function(getchar) { + return INTEGER_VAL(getchar()); +} + +/** + * def readline(prefix="",bufsize=1024) -> str + */ +KRK_Function(readline) { + int size = 1024; + char * prefix = ""; + if (!krk_parseArgs("|si", (const char*[]){"prefix","bufsize"}, &prefix, &size)) return NONE_VAL(); + + char * buf = malloc(size); + readline(prefix,buf,size); + krk_push(OBJECT_VAL(krk_copyString(buf,strlen(buf)))); + free(buf); + + return krk_pop(); +} + +/** + * Builtins + */ +KRK_Function(exit) { + krk_exitRepl = 1; + return NONE_VAL(); +} + +KRK_Function(clear) { + print("\e[2J\e[H"); + return NONE_VAL(); +} + +void krk_module_init_os(void) { + KrkInstance * module = krk_newInstance(vm.baseClasses->moduleClass); + krk_attachNamedObject(&vm.modules, "os", (KrkObj*)module); + krk_attachNamedObject(&module->fields, "__name__", (KrkObj*)S("os")); + krk_attachNamedValue(&module->fields, "__file__", NONE_VAL()); + BIND_FUNC(module,uname); + + /* Let's also make a Limine-specific module */ + KrkInstance * limine = krk_newInstance(vm.baseClasses->moduleClass); + krk_attachNamedObject(&vm.modules, "limine", (KrkObj*)limine); + krk_attachNamedObject(&limine->fields, "__name__", (KrkObj*)S("limine")); + krk_attachNamedValue(&limine->fields, "__file__", NONE_VAL()); + BIND_FUNC(limine,config_get_value); + BIND_FUNC(limine,boot); + BIND_FUNC(limine,getchar); + BIND_FUNC(limine,readline); + + /* And some builtins */ + + BIND_FUNC(vm.builtins,exit); + BIND_FUNC(vm.builtins,clear); +} + +KRK_Function(time) { + /* We don't have floats, so just return an int; 48 bits should be enough */ + return INTEGER_VAL(time()); +} + +void krk_module_init_time(void) { + KrkInstance * module = krk_newInstance(vm.baseClasses->moduleClass); + krk_attachNamedObject(&vm.modules, "time", (KrkObj*)module); + krk_attachNamedObject(&module->fields, "__name__", (KrkObj*)S("time")); + krk_attachNamedValue(&module->fields, "__file__", NONE_VAL()); + BIND_FUNC(module,time); +} + +void krk_module_init_fileio(void) { + /* Nope. Maybe later. */ +} + +/** + * @brief Initialize the Kuroko VM state and prep the @c limine module. + */ +void limine_krk_init(void) { + krk_initVM(0); + krk_startModule("__main__"); + krk_interpret( + "if True:\n" + " import limine\n" + " def config(modules=[],**kwargs):\n" + " let args = [f'{k.upper()}={v}' for k,v in kwargs.items()]\n" + " for module in modules:\n" + " args.append(f'MODULE_PATH={module}')\n" + " return '\\n'.join(args)\n" + " limine.config = config\n", + ""); +} + +/** + * @brief Start a repl session. + */ +void limine_krk_enter_repl(void) { + /* Print version number. */ + krk_interpret( + "if True:\n" + " import kuroko\n" + " print(f'Kuroko {kuroko.version} ({kuroko.builddate}) with {kuroko.buildenv}')\n" + " print('Type `help` for guidance, `exit()` to quit.')\n", + ""); + + /* Run the repl */ + krk_repl(); +} + +/** + * @brief Cleanup VM resources. + */ +void limine_krk_cleanup(void) { + /* Free the VM resources; we never actually release the heap, so this doesn't really do anything, + * but the heap is shared through multiple Kuroko sessions, so freeing it is still useful. */ + krk_freeVM(); +} + diff --git a/common/kuroko/malloc.c b/common/kuroko/malloc.c new file mode 100644 index 00000000..a0483f7a --- /dev/null +++ b/common/kuroko/malloc.c @@ -0,0 +1,211 @@ +#include +#include +#include +#include + +static void * sbrk(size_t s) { + return ext_mem_alloc_type_aligned(s, MEMMAP_BOOTLOADER_RECLAIMABLE, 4096); +} + +#if defined(__x86_64__) || defined(__aarch64__) || defined(__riscv64) +#define NUM_BINS 10U /* Number of bins, total, under 64-bit. */ +#define SMALLEST_BIN_LOG 3U /* Logarithm base two of the smallest bin: log_2(sizeof(int32)). */ +#else +#define NUM_BINS 11U /* Number of bins, total, under 32-bit. */ +#define SMALLEST_BIN_LOG 2U /* Logarithm base two of the smallest bin: log_2(sizeof(int32)). */ +#endif +#define BIG_BIN (NUM_BINS - 1) /* Index for the big bin, (NUM_BINS - 1) */ +#define SMALLEST_BIN (1UL << SMALLEST_BIN_LOG) /* Size of the smallest bin. */ + +#define PAGE_SIZE 0x1000 /* Size of a page (in bytes), should be 4KB */ +#define PAGE_MASK (PAGE_SIZE - 1) /* Block mask, size of a page * number of pages - 1. */ + +#define BIN_MAGIC 0xDEFAD00D + +static void * __attribute__ ((malloc)) klmalloc(uintptr_t size); +static void * __attribute__ ((malloc)) klrealloc(void * ptr, uintptr_t size); +static void * __attribute__ ((malloc)) klcalloc(uintptr_t nmemb, uintptr_t size); +static void klfree(void * ptr); + +void * __attribute__ ((malloc)) malloc(uintptr_t size) { + return klmalloc(size); +} + +void * __attribute__ ((malloc)) realloc(void * ptr, uintptr_t size) { + return klrealloc(ptr, size); +} + +void * __attribute__ ((malloc)) calloc(uintptr_t nmemb, uintptr_t size) { + return klcalloc(nmemb, size); +} + +void free(void * ptr) { + klfree(ptr); +} + +static inline uintptr_t __attribute__ ((always_inline, pure)) klmalloc_adjust_bin(uintptr_t bin) +{ + if (bin <= (uintptr_t)SMALLEST_BIN_LOG) + { + return 0; + } + bin -= SMALLEST_BIN_LOG + 1; + if (bin > (uintptr_t)BIG_BIN) { + return BIG_BIN; + } + return bin; +} + +static inline uintptr_t __attribute__ ((always_inline, pure)) klmalloc_bin_size(uintptr_t size) { + uintptr_t bin = sizeof(size) * 8 - __builtin_clzl(size); + bin += !!(size & (size - 1)); + return klmalloc_adjust_bin(bin); +} + +typedef struct _klmalloc_bin_header { + struct _klmalloc_bin_header * next; /* Pointer to the next node. */ + void * head; /* Head of this bin. */ + uintptr_t size; /* Size of this bin, if big; otherwise bin index. */ + uint32_t bin_magic; +} klmalloc_bin_header; + +typedef struct _klmalloc_bin_header_head { + klmalloc_bin_header * first; +} klmalloc_bin_header_head; + +static klmalloc_bin_header_head klmalloc_bin_head[NUM_BINS - 1]; /* Small bins */ + +static inline void __attribute__ ((always_inline)) klmalloc_list_decouple(klmalloc_bin_header_head *head, klmalloc_bin_header *node) { + klmalloc_bin_header *next = node->next; + head->first = next; + node->next = NULL; +} + +static inline void __attribute__ ((always_inline)) klmalloc_list_insert(klmalloc_bin_header_head *head, klmalloc_bin_header *node) { + node->next = head->first; + head->first = node; +} + +static inline klmalloc_bin_header * __attribute__ ((always_inline)) klmalloc_list_head(klmalloc_bin_header_head *head) { + return head->first; +} + +static void * klmalloc_stack_pop(klmalloc_bin_header *header) { + void *item = header->head; + uintptr_t **head = header->head; + uintptr_t *next = *head; + header->head = next; + return item; +} + +static void klmalloc_stack_push(klmalloc_bin_header *header, void *ptr) { + uintptr_t **item = (uintptr_t **)ptr; + *item = (uintptr_t *)header->head; + header->head = item; +} + +static inline int __attribute__ ((always_inline)) klmalloc_stack_empty(klmalloc_bin_header *header) { + return header->head == NULL; +} + +static void * __attribute__ ((malloc)) klmalloc(uintptr_t size) { + if (__builtin_expect(size == 0, 0)) + return NULL; + unsigned int bucket_id = klmalloc_bin_size(size); + + if (bucket_id < BIG_BIN) { + klmalloc_bin_header * bin_header = klmalloc_list_head(&klmalloc_bin_head[bucket_id]); + if (!bin_header) { + bin_header = (klmalloc_bin_header*)sbrk(PAGE_SIZE); + bin_header->bin_magic = BIN_MAGIC; + bin_header->head = (void*)((uintptr_t)bin_header + sizeof(klmalloc_bin_header)); + klmalloc_list_insert(&klmalloc_bin_head[bucket_id], bin_header); + uintptr_t adj = SMALLEST_BIN_LOG + bucket_id; + uintptr_t i, available = ((PAGE_SIZE - sizeof(klmalloc_bin_header)) >> adj) - 1; + + uintptr_t **base = bin_header->head; + for (i = 0; i < available; ++i) { + base[i << bucket_id] = (uintptr_t *)&base[(i + 1) << bucket_id]; + } + base[available << bucket_id] = NULL; + bin_header->size = bucket_id; + } + uintptr_t ** item = klmalloc_stack_pop(bin_header); + if (klmalloc_stack_empty(bin_header)) { + klmalloc_list_decouple(&(klmalloc_bin_head[bucket_id]),bin_header); + } + return item; + } else { + uintptr_t pages = (size + sizeof(klmalloc_bin_header)) / PAGE_SIZE + 1; + klmalloc_bin_header * bin_header = (klmalloc_bin_header*)sbrk(PAGE_SIZE * pages); + bin_header->bin_magic = BIN_MAGIC; + bin_header->size = pages * PAGE_SIZE - sizeof(klmalloc_bin_header); + bin_header->head = NULL; + return (void*)((uintptr_t)bin_header + sizeof(klmalloc_bin_header)); + } +} + +static void klfree(void *ptr) { + if (__builtin_expect(ptr == NULL, 0)) { + return; + } + + if ((uintptr_t)ptr % PAGE_SIZE == 0) { + ptr = (void *)((uintptr_t)ptr - 1); + } + + klmalloc_bin_header * header = (klmalloc_bin_header *)((uintptr_t)ptr & (uintptr_t)~PAGE_MASK); + + if (header->bin_magic != BIN_MAGIC) + return; + + uintptr_t bucket_id = header->size; + if (bucket_id > (uintptr_t)NUM_BINS) { + bucket_id = BIG_BIN; + klmalloc_bin_header *bheader = (klmalloc_bin_header*)header; + pmm_free(header, bheader->size + sizeof(klmalloc_bin_header)); + } else { + if (klmalloc_stack_empty(header)) { + klmalloc_list_insert(&klmalloc_bin_head[bucket_id], header); + } + klmalloc_stack_push(header, ptr); + } +} + +static void * __attribute__ ((malloc)) klrealloc(void *ptr, uintptr_t size) { + if (__builtin_expect(ptr == NULL, 0)) + return klmalloc(size); + + if (__builtin_expect(size == 0, 0)) + { + free(ptr); + return NULL; + } + + klmalloc_bin_header * header_old = (void *)((uintptr_t)ptr & (uintptr_t)~PAGE_MASK); + if (header_old->bin_magic != BIN_MAGIC) { + return NULL; + } + + uintptr_t old_size = header_old->size; + if (old_size < (uintptr_t)BIG_BIN) { + old_size = (1UL << (SMALLEST_BIN_LOG + old_size)); + } + + if (old_size == size) return ptr; + + void * newptr = klmalloc(size); + if (__builtin_expect(newptr != NULL, 1)) { + memcpy(newptr, ptr, (old_size < size) ? old_size : size); + klfree(ptr); + return newptr; + } + + return NULL; +} + +static void * __attribute__ ((malloc)) klcalloc(uintptr_t nmemb, uintptr_t size) { + void *ptr = klmalloc(nmemb * size); + if (ptr) memset(ptr,0x00,nmemb * size); + return ptr; +} diff --git a/common/kuroko/memory.c b/common/kuroko/memory.c new file mode 120000 index 00000000..bf08e3b2 --- /dev/null +++ b/common/kuroko/memory.c @@ -0,0 +1 @@ +../../kuroko/src/memory.c \ No newline at end of file diff --git a/common/kuroko/methods.h b/common/kuroko/methods.h new file mode 120000 index 00000000..c5303584 --- /dev/null +++ b/common/kuroko/methods.h @@ -0,0 +1 @@ +../../kuroko/src/methods.h \ No newline at end of file diff --git a/common/kuroko/obj_base.c b/common/kuroko/obj_base.c new file mode 120000 index 00000000..305c9ba1 --- /dev/null +++ b/common/kuroko/obj_base.c @@ -0,0 +1 @@ +../../kuroko/src/obj_base.c \ No newline at end of file diff --git a/common/kuroko/obj_bytes.c b/common/kuroko/obj_bytes.c new file mode 120000 index 00000000..f70699c6 --- /dev/null +++ b/common/kuroko/obj_bytes.c @@ -0,0 +1 @@ +../../kuroko/src/obj_bytes.c \ No newline at end of file diff --git a/common/kuroko/obj_dict.c b/common/kuroko/obj_dict.c new file mode 120000 index 00000000..8ab96e68 --- /dev/null +++ b/common/kuroko/obj_dict.c @@ -0,0 +1 @@ +../../kuroko/src/obj_dict.c \ No newline at end of file diff --git a/common/kuroko/obj_function.c b/common/kuroko/obj_function.c new file mode 120000 index 00000000..7f79b12b --- /dev/null +++ b/common/kuroko/obj_function.c @@ -0,0 +1 @@ +../../kuroko/src/obj_function.c \ No newline at end of file diff --git a/common/kuroko/obj_gen.c b/common/kuroko/obj_gen.c new file mode 120000 index 00000000..3c75fa28 --- /dev/null +++ b/common/kuroko/obj_gen.c @@ -0,0 +1 @@ +../../kuroko/src/obj_gen.c \ No newline at end of file diff --git a/common/kuroko/obj_list.c b/common/kuroko/obj_list.c new file mode 120000 index 00000000..781ad197 --- /dev/null +++ b/common/kuroko/obj_list.c @@ -0,0 +1 @@ +../../kuroko/src/obj_list.c \ No newline at end of file diff --git a/common/kuroko/obj_long.c b/common/kuroko/obj_long.c new file mode 120000 index 00000000..bf14673e --- /dev/null +++ b/common/kuroko/obj_long.c @@ -0,0 +1 @@ +../../kuroko/src/obj_long.c \ No newline at end of file diff --git a/common/kuroko/obj_numeric.c b/common/kuroko/obj_numeric.c new file mode 120000 index 00000000..8cff8753 --- /dev/null +++ b/common/kuroko/obj_numeric.c @@ -0,0 +1 @@ +../../kuroko/src/obj_numeric.c \ No newline at end of file diff --git a/common/kuroko/obj_range.c b/common/kuroko/obj_range.c new file mode 120000 index 00000000..08a15670 --- /dev/null +++ b/common/kuroko/obj_range.c @@ -0,0 +1 @@ +../../kuroko/src/obj_range.c \ No newline at end of file diff --git a/common/kuroko/obj_set.c b/common/kuroko/obj_set.c new file mode 120000 index 00000000..601134fc --- /dev/null +++ b/common/kuroko/obj_set.c @@ -0,0 +1 @@ +../../kuroko/src/obj_set.c \ No newline at end of file diff --git a/common/kuroko/obj_slice.c b/common/kuroko/obj_slice.c new file mode 120000 index 00000000..099a09e0 --- /dev/null +++ b/common/kuroko/obj_slice.c @@ -0,0 +1 @@ +../../kuroko/src/obj_slice.c \ No newline at end of file diff --git a/common/kuroko/obj_str.c b/common/kuroko/obj_str.c new file mode 120000 index 00000000..7072ff9a --- /dev/null +++ b/common/kuroko/obj_str.c @@ -0,0 +1 @@ +../../kuroko/src/obj_str.c \ No newline at end of file diff --git a/common/kuroko/obj_tuple.c b/common/kuroko/obj_tuple.c new file mode 120000 index 00000000..dedb3e58 --- /dev/null +++ b/common/kuroko/obj_tuple.c @@ -0,0 +1 @@ +../../kuroko/src/obj_tuple.c \ No newline at end of file diff --git a/common/kuroko/obj_typing.c b/common/kuroko/obj_typing.c new file mode 120000 index 00000000..79322218 --- /dev/null +++ b/common/kuroko/obj_typing.c @@ -0,0 +1 @@ +../../kuroko/src/obj_typing.c \ No newline at end of file diff --git a/common/kuroko/object.c b/common/kuroko/object.c new file mode 120000 index 00000000..71172c29 --- /dev/null +++ b/common/kuroko/object.c @@ -0,0 +1 @@ +../../kuroko/src/object.c \ No newline at end of file diff --git a/common/kuroko/opcode_enum.h b/common/kuroko/opcode_enum.h new file mode 120000 index 00000000..34bb1f42 --- /dev/null +++ b/common/kuroko/opcode_enum.h @@ -0,0 +1 @@ +../../kuroko/src/opcode_enum.h \ No newline at end of file diff --git a/common/kuroko/opcodes.h b/common/kuroko/opcodes.h new file mode 120000 index 00000000..515dab43 --- /dev/null +++ b/common/kuroko/opcodes.h @@ -0,0 +1 @@ +../../kuroko/src/opcodes.h \ No newline at end of file diff --git a/common/kuroko/parseargs.c b/common/kuroko/parseargs.c new file mode 120000 index 00000000..64cec299 --- /dev/null +++ b/common/kuroko/parseargs.c @@ -0,0 +1 @@ +../../kuroko/src/parseargs.c \ No newline at end of file diff --git a/common/kuroko/private.h b/common/kuroko/private.h new file mode 120000 index 00000000..69051070 --- /dev/null +++ b/common/kuroko/private.h @@ -0,0 +1 @@ +../../kuroko/src/private.h \ No newline at end of file diff --git a/common/kuroko/repl.c b/common/kuroko/repl.c new file mode 100644 index 00000000..f0f4489e --- /dev/null +++ b/common/kuroko/repl.c @@ -0,0 +1,464 @@ +/** + * @file repl.c + * @brief General-purpose Kuroko repl using rline, with tab completion. + * + * This is a generic repl. Initialize the @c __main__ module and then + * call @c krk_repl() to run a repl session. You can bind a builtin + * @c exit() function to set @c krk_exitRepl to 1 to exit the repl; + * the repl will also terminate if rline detects an EOF condition. + */ +#include +#include +#include +#include + +#define PROMPT_MAIN ">>> " +#define PROMPT_BLOCK " > " + +int krk_exitRepl = 0; + +void krk_printResult(KrkValue result) { + if (IS_NONE(result)) return; + krk_attachNamedValue(&vm.builtins->fields, "_", result); + KrkClass * type = krk_getType(result); + const char * formatStr = " \033[1;90m=> %s\033[0m\n"; + if (type->_reprer) { + krk_push(result); + result = krk_callDirect(type->_reprer, 1); + } else if (type->_tostr) { + krk_push(result); + result = krk_callDirect(type->_tostr, 1); + } + if (!IS_STRING(result)) { + printf(" \033[1;91m=> Unable to produce representation for value.\033[0m\n"); + } else { + printf(formatStr, AS_CSTRING(result)); + } +} + +#if 1 +static KrkValue findFromProperty(KrkValue current, KrkToken next) { + KrkValue member = OBJECT_VAL(krk_copyString(next.start, next.literalWidth)); + krk_push(member); + KrkValue value = krk_valueGetAttribute_default(current, AS_CSTRING(member), NONE_VAL()); + krk_pop(); + return value; +} + +static char * syn_krk_keywords[] = { + "and","class","def","else","for","if","in","import","del", + "let","not","or","return","while","try","except","raise", + "continue","break","as","from","elif","lambda","with","is", + "pass","assert","yield","finally","async","await", + "True","False","None", + NULL +}; + +static void tab_complete_func(rline_context_t * c) { + /* Figure out where the cursor is and if we should be completing anything. */ + if (c->offset) { + size_t stackIn = krk_currentThread.stackTop - krk_currentThread.stack; + /* Copy up to the cursor... */ + char * tmp = malloc(c->offset + 1); + memcpy(tmp, c->buffer, c->offset); + tmp[c->offset] = '\0'; + /* and pass it to the scanner... */ + KrkScanner scanner = krk_initScanner(tmp); + /* Logically, there can be at most (offset) tokens, plus some wiggle room. */ + KrkToken * space = malloc(sizeof(KrkToken) * (c->offset + 2)); + int count = 0; + do { + space[count++] = krk_scanToken(&scanner); + } while (space[count-1].type != TOKEN_EOF && space[count-1].type != TOKEN_ERROR); + + /* If count == 1, it was EOF or an error and we have nothing to complete. */ + if (count == 1) { + goto _cleanup; + } + + /* Otherwise we want to see if we're on an identifier or a dot. */ + int base = 2; + int n = base; + if (space[count-base].type == TOKEN_DOT) { + /* Dots we need to look back at the previous tokens for */ + n--; + base--; + } else if (space[count-base].type >= TOKEN_IDENTIFIER && space[count-base].type <= TOKEN_WITH) { + /* Something alphanumeric; only for the last element */ + } else { + /* Some other symbol */ + goto _cleanup; + } + + /* Work backwards to find the start of this chain of identifiers */ + while (n < count) { + if (space[count-n-1].type != TOKEN_DOT) break; + n++; + if (n == count) break; + if (space[count-n-1].type != TOKEN_IDENTIFIER) break; + n++; + } + + if (n <= count) { + /* Now work forwards, starting from the current globals. */ + KrkValue root = OBJECT_VAL(krk_currentThread.module); + int isGlobal = 1; + while (n > base) { + /* And look at the potential fields for instances/classes */ + KrkValue next = findFromProperty(root, space[count-n]); + if (IS_NONE(next)) { + /* If we hit None, we found something invalid (or literally hit a None + * object, but really the difference is minimal in this case: Nothing + * useful to tab complete from here. */ + if (!isGlobal) goto _cleanup; + /* Does this match a builtin? */ + if (!krk_tableGet_fast(&vm.builtins->fields, + krk_copyString(space[count-n].start,space[count-n].literalWidth), &next) || IS_NONE(next)) { + goto _cleanup; + } + } + isGlobal = 0; + root = next; + n -= 2; /* To skip every other dot. */ + } + + if (isGlobal && n < count && (space[count-n-1].type == TOKEN_IMPORT || space[count-n-1].type == TOKEN_FROM)) { + KrkInstance * modules = krk_newInstance(vm.baseClasses->objectClass); + root = OBJECT_VAL(modules); + krk_push(root); + for (size_t i = 0; i < vm.modules.capacity; ++i) { + KrkTableEntry * entry = &vm.modules.entries[i]; + if (IS_KWARGS(entry->key)) continue; + krk_attachNamedValue(&modules->fields, AS_CSTRING(entry->key), NONE_VAL()); + } + } + + /* Now figure out what we're completing - did we already have a partial symbol name? */ + int length = (space[count-base].type == TOKEN_DOT) ? 0 : (space[count-base].length); + isGlobal = isGlobal && (length != 0); + + /* Collect up to 256 of those that match */ + char * matches[256]; + int matchCount = 0; + + /* Take the last symbol name from the chain and get its member list from dir() */ + + for (;;) { + KrkValue dirList = krk_dirObject(1,(KrkValue[]){root},0); + krk_push(dirList); + if (!IS_INSTANCE(dirList)) { + fprintf(stderr,"\nInternal error while tab completting.\n"); + goto _cleanup; + } + + for (size_t i = 0; i < AS_LIST(dirList)->count; ++i) { + KrkString * s = AS_STRING(AS_LIST(dirList)->values[i]); + krk_push(OBJECT_VAL(s)); + KrkToken asToken = {.start = s->chars, .literalWidth = s->length}; + KrkValue thisValue = findFromProperty(root, asToken); + krk_push(thisValue); + if (IS_CLOSURE(thisValue) || IS_BOUND_METHOD(thisValue) || IS_NATIVE(thisValue)) { + size_t allocSize = s->length + 2; + char * tmp = malloc(allocSize); + size_t len = snprintf(tmp, allocSize, "%s(", s->chars); + s = krk_takeString(tmp, len); + krk_pop(); + krk_push(OBJECT_VAL(s)); + } else { + krk_pop(); + } + + /* If this symbol is shorter than the current submatch, skip it. */ + if (length && (int)s->length < length) continue; + + /* See if it's already in the matches */ + int found = 0; + for (int i = 0; i < matchCount; ++i) { + if (!strcmp(matches[i], s->chars)) { + found = 1; + break; + } + } + if (found) continue; + + if (!memcmp(s->chars, space[count-base].start, length)) { + matches[matchCount] = s->chars; + matchCount++; + if (matchCount == 255) goto _toomany; + } + } + + /* + * If the object we were scanning was the current module, + * then we should also throw the builtins into the ring. + */ + if (isGlobal && AS_OBJECT(root) == (KrkObj*)krk_currentThread.module) { + root = OBJECT_VAL(vm.builtins); + continue; + } else if (isGlobal && AS_OBJECT(root) == (KrkObj*)vm.builtins) { + KrkInstance * fakeKeywordsObject = krk_newInstance(vm.baseClasses->objectClass); + root = OBJECT_VAL(fakeKeywordsObject); + krk_push(root); + for (char ** keyword = syn_krk_keywords; *keyword; keyword++) { + krk_attachNamedValue(&fakeKeywordsObject->fields, *keyword, NONE_VAL()); + } + continue; + } else { + break; + } + } +_toomany: + + /* Now we can do things with the matches. */ + if (matchCount == 1) { + /* If there was only one, just fill it. */ + rline_insert(c, matches[0] + length); + rline_place_cursor(); + } else if (matchCount) { + /* Otherwise, try to find a common substring among them... */ + int j = length; + while (1) { + char m = matches[0][j]; + if (!m) break; + int diff = 0; + for (int i = 1; i < matchCount; ++i) { + if (matches[i][j] != m) { + diff = 1; + break; + } + } + if (diff) break; + j++; + } + /* If no common sub string could be filled in, we print the list. */ + if (j == length) { + /* First find the maximum width of an entry */ + int maxWidth = 0; + for (int i = 0; i < matchCount; ++i) { + if ((int)strlen(matches[i]) > maxWidth) maxWidth = strlen(matches[i]); + } + /* Now how many can we fit in a screen */ + int colsPerLine = rline_terminal_width / (maxWidth + 2); /* +2 for the spaces */ + fprintf(stderr, "\n"); + int column = 0; + for (int i = 0; i < matchCount; ++i) { + fprintf(stderr, "%-*s ", maxWidth, matches[i]); + column += 1; + if (column >= colsPerLine) { + fprintf(stderr, "\n"); + column = 0; + } + } + if (column != 0) fprintf(stderr, "\n"); + } else { + /* If we do have a common sub string, fill in those characters. */ + for (int i = length; i < j; ++i) { + char tmp[2] = {matches[0][i], '\0'}; + rline_insert(c, tmp); + } + } + } + } +_cleanup: + free(tmp); + free(space); + krk_currentThread.stackTop = &krk_currentThread.stack[stackIn]; + return; + } +} + +void krk_repl(void) { + krk_exitRepl = 0; + while (!krk_exitRepl) { + size_t lineCapacity = 8; + size_t lineCount = 0; + char ** lines = ALLOCATE(char *, lineCapacity); + size_t totalData = 0; + int valid = 1; + char * allData = NULL; + int inBlock = 0; + int blockWidth = 0; + rline_exp_set_prompts(PROMPT_MAIN, "", 4, 0); + rline_exit_string="exit"; + rline_exp_set_syntax("krk"); + rline_exp_set_tab_complete_func(tab_complete_func); + + while (1) { + char buf[4096] = {0}; + if (inBlock) { + rline_exp_set_prompts(PROMPT_BLOCK, "", 4, 0); + rline_preload = malloc(blockWidth + 1); + for (int i = 0; i < blockWidth; ++i) { + rline_preload[i] = ' '; + } + rline_preload[blockWidth] = '\0'; + } + rline_scroll = 0; + if (rline(buf, 4096) == 0) { + valid = 0; + krk_exitRepl = 1; + break; + } + if (krk_currentThread.flags & KRK_THREAD_SIGNALLED) { + /* We should actually be properly raising the interrupt and printing it with an empty traceback, but whatever. */ + krk_currentThread.flags &= ~(KRK_THREAD_SIGNALLED); /* Clear signal flag */ + printf("KeyboardInterrupt\n"); + valid = 0; + break; + } + if (buf[strlen(buf)-1] != '\n') { + valid = 0; + break; + } + if (lineCapacity < lineCount + 1) { + size_t old = lineCapacity; + lineCapacity = GROW_CAPACITY(old); + lines = GROW_ARRAY(char *,lines,old,lineCapacity); + } + int i = lineCount++; + lines[i] = strdup(buf); + size_t lineLength = strlen(lines[i]); + totalData += lineLength; + int isSpaces = 1; + int countSpaces = 0; + for (size_t j = 0; j < lineLength; ++j) { + if (lines[i][j] != ' ' && lines[i][j] != '\n') { + isSpaces = 0; + break; + } + countSpaces += 1; + } + if (lineLength > 1 && lines[i][lineLength-2] == ':') { + inBlock = 1; + blockWidth = countSpaces + 4; + continue; + } else if (lineLength > 1 && lines[i][lineLength-2] == '\\') { + inBlock = 1; + continue; + } else if (inBlock && lineLength != 1) { + if (isSpaces) { + free(lines[i]); + totalData -= lineLength; + lineCount--; + break; + } + blockWidth = countSpaces; + continue; + } else if (lineLength > 1 && lines[i][countSpaces] == '@') { + inBlock = 1; + blockWidth = countSpaces; + continue; + } + if (isSpaces && !i) valid = 0; + break; + } + if (valid) { + allData = malloc(totalData + 1); + allData[0] = '\0'; + } + for (size_t i = 0; i < lineCount; ++i) { + if (valid) strcat(allData, lines[i]); + rline_history_insert(strdup(lines[i])); + rline_scroll = 0; + free(lines[i]); + } + FREE_ARRAY(char *, lines, lineCapacity); + if (valid) { + KrkValue result = krk_interpret(allData, ""); + krk_printResult(result); + krk_resetStack(); + } + (void)blockWidth; + } +} +#else + +/* repl without rline */ +void krk_repl(void) { + krk_exitRepl = 0; + while (!krk_exitRepl) { + size_t lineCapacity = 8; + size_t lineCount = 0; + char ** lines = ALLOCATE(char *, lineCapacity); + size_t totalData = 0; + int valid = 1; + char * allData = NULL; + int inBlock = 0; + int blockWidth = 0; + + while (1) { + char buf[4096] = {0}; + print(">>> "); + readline("", buf, 4096); + if (krk_currentThread.flags & KRK_THREAD_SIGNALLED) { + /* We should actually be properly raising the interrupt and printing it with an empty traceback, but whatever. */ + krk_currentThread.flags &= ~(KRK_THREAD_SIGNALLED); /* Clear signal flag */ + printf("KeyboardInterrupt\n"); + valid = 0; + break; + } + if (buf[strlen(buf)-1] != '\n') { + valid = 0; + break; + } + if (lineCapacity < lineCount + 1) { + size_t old = lineCapacity; + lineCapacity = GROW_CAPACITY(old); + lines = GROW_ARRAY(char *,lines,old,lineCapacity); + } + int i = lineCount++; + lines[i] = strdup(buf); + size_t lineLength = strlen(lines[i]); + totalData += lineLength; + int isSpaces = 1; + int countSpaces = 0; + for (size_t j = 0; j < lineLength; ++j) { + if (lines[i][j] != ' ' && lines[i][j] != '\n') { + isSpaces = 0; + break; + } + countSpaces += 1; + } + if (lineLength > 1 && lines[i][lineLength-2] == ':') { + inBlock = 1; + blockWidth = countSpaces + 4; + continue; + } else if (lineLength > 1 && lines[i][lineLength-2] == '\\') { + inBlock = 1; + continue; + } else if (inBlock && lineLength != 1) { + if (isSpaces) { + free(lines[i]); + totalData -= lineLength; + lineCount--; + break; + } + blockWidth = countSpaces; + continue; + } else if (lineLength > 1 && lines[i][countSpaces] == '@') { + inBlock = 1; + blockWidth = countSpaces; + continue; + } + if (isSpaces && !i) valid = 0; + break; + } + if (valid) { + allData = malloc(totalData + 1); + allData[0] = '\0'; + } + for (size_t i = 0; i < lineCount; ++i) { + if (valid) strcat(allData, lines[i]); + free(lines[i]); + } + FREE_ARRAY(char *, lines, lineCapacity); + if (valid) { + KrkValue result = krk_interpret(allData, ""); + krk_printResult(result); + krk_resetStack(); + } + (void)blockWidth; + } +} + +#endif diff --git a/common/kuroko/rline.c b/common/kuroko/rline.c new file mode 100644 index 00000000..408e75cb --- /dev/null +++ b/common/kuroko/rline.c @@ -0,0 +1,2348 @@ +/** + * @brief Line editor + * + * Interactive line input editor with syntax highlighting for + * a handful of languages. Based on an old version of Bim. + * Used by the shell and Kuroko. + * + * This library is generally usable on Linux and even Windows. + * + * @copyright + * This file is part of ToaruOS and is released under the terms + * of the NCSA / University of Illinois License - see LICENSE.md + * Copyright (C) 2018-2022 K. Lange + */ +#define _XOPEN_SOURCE +#define _DEFAULT_SOURCE +#include +#include +#include + +#include +#include + +#include "wcwidth._h" +#include "rline.h" + +static int isdigit(int c) { + return (c >= '0') && (c <= '9'); +} + +static int isxdigit(int c) { + return (c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'); +} + +static int isalnum(int c) { + return (c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z'); +} + +static __attribute__((used)) int _isdigit(int c) { if (c > 128) return 0; return isdigit(c); } +static __attribute__((used)) int _isxdigit(int c) { if (c > 128) return 0; return isxdigit(c); } + +#undef isdigit +#undef isxdigit +#define isdigit(c) _isdigit(c) +#define isxdigit(c) _isxdigit(c) + +char * rline_history[RLINE_HISTORY_ENTRIES]; +int rline_history_count = 0; +int rline_history_offset = 0; +int rline_scroll = 0; +char * rline_exit_string = "exit\n"; +int rline_terminal_width = 0; +char * rline_preload = NULL; + +void rline_history_insert(char * str) { + if (str[strlen(str)-1] == '\n') { + str[strlen(str)-1] = '\0'; + } + if (rline_history_count) { + if (!strcmp(str, rline_history_prev(1))) { + free(str); + return; + } + } + if (rline_history_count == RLINE_HISTORY_ENTRIES) { + free(rline_history[rline_history_offset]); + rline_history[rline_history_offset] = str; + rline_history_offset = (rline_history_offset + 1) % RLINE_HISTORY_ENTRIES; + } else { + rline_history[rline_history_count] = str; + rline_history_count++; + } +} + +void rline_history_append_line(char * str) { + if (rline_history_count) { + char ** s = &rline_history[(rline_history_count - 1 + rline_history_offset) % RLINE_HISTORY_ENTRIES]; + size_t len = strlen(*s) + strlen(str) + 2; + char * c = malloc(len); + snprintf(c, len, "%s\n%s", *s, str); + if (c[strlen(c)-1] == '\n') { + c[strlen(c)-1] = '\0'; + } + free(*s); + *s = c; + } else { + /* wat */ + } +} + +char * rline_history_get(int item) { + return rline_history[(item + rline_history_offset) % RLINE_HISTORY_ENTRIES]; +} + +char * rline_history_prev(int item) { + return rline_history_get(rline_history_count - item); +} + +#define UTF8_ACCEPT 0 +#define UTF8_REJECT 1 + +/** + * Conceptually similar to its predecessor, this implementation is much + * less cool, as it uses three separate state tables and more shifts. + */ +static inline uint32_t decode(uint32_t* state, uint32_t* codep, uint32_t byte) { + static int state_table[32] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xxxxxxx */ + 1,1,1,1,1,1,1,1, /* 10xxxxxx */ + 2,2,2,2, /* 110xxxxx */ + 3,3, /* 1110xxxx */ + 4, /* 11110xxx */ + 1 /* 11111xxx */ + }; + + static int mask_bytes[32] = { + 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F, + 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x1F,0x1F,0x1F,0x1F, + 0x0F,0x0F, + 0x07, + 0x00 + }; + + static int next[5] = { + 0, + 1, + 0, + 2, + 3 + }; + + if (*state == UTF8_ACCEPT) { + *codep = byte & mask_bytes[byte >> 3]; + *state = state_table[byte >> 3]; + } else if (*state > 0) { + *codep = (byte & 0x3F) | (*codep << 6); + *state = next[*state]; + } + return *state; +} + +#define ENTER_KEY '\n' +#define BACKSPACE_KEY 0x08 +#define DELETE_KEY 0x7F +#define MINIMUM_SIZE 10 + +/** + * Same structures as in bim. + * A single character has: + * - A codepoint (Unicode) of up to 21 bits. + * - Flags for syntax highlighting. + * - A display width for rendering. + */ +typedef struct { + uint32_t display_width:4; + uint32_t flags:7; + uint32_t codepoint:21; +} __attribute__((packed)) char_t; + +/** + * We generally only have the one line, + * but this matches bim for compatibility reasons. + */ +typedef struct { + int available; + int actual; + int istate; + char_t text[]; +} line_t; + +/** + * We operate on a single line of text. + * Maybe we can expand this in the future + * for continuations of edits such as when + * a quote is unclosed? + */ +static line_t * the_line = NULL; + +/** + * Line editor state + */ +static int loading = 0; +static int column = 0; +static int offset = 0; +static int width = 0; +static int show_right_side = 0; +static int show_left_side = 0; +static int prompt_width_calc = 0; +static int buf_size_max = 0; + +/** + * Prompt strings. + * Defaults to just a "> " prompt with no right side. + * Support for right side prompts is important + * for the ToaruOS shell. + */ +static int prompt_width = 2; +static char * prompt = "> "; +static int prompt_right_width = 0; +static char * prompt_right = ""; + +int rline_exp_set_prompts(char * left, char * right, int left_width, int right_width) { + prompt = left; + prompt_right = right; + prompt_width = left_width; + prompt_right_width = right_width; + return 0; +} + +/** + * Extra shell commands to highlight as keywords. + * These are basically just copied from the + * shell's tab completion database on startup. + */ +static char ** shell_commands = {0}; +static int shell_commands_len = 0; + +int rline_exp_set_shell_commands(char ** cmds, int len) { + shell_commands = cmds; + shell_commands_len = len; + return 0; +} + +/** + * Tab completion callback. + * Compatible with the original rline version. + */ +static rline_callback_t tab_complete_func = NULL; + +int rline_exp_set_tab_complete_func(rline_callback_t func) { + tab_complete_func = func; + return 0; +} + +static int have_unget = -1; +static int getch(int timeout) { + if (have_unget >= 0) { + int out = have_unget; + have_unget = -1; + return out; + } + return getchar(); +} + +/** + * Convert from Unicode string to utf-8. + */ +static int to_eight(uint32_t codepoint, char * out) { + memset(out, 0x00, 7); + + if (codepoint < 0x0080) { + out[0] = (char)codepoint; + } else if (codepoint < 0x0800) { + out[0] = 0xC0 | (codepoint >> 6); + out[1] = 0x80 | (codepoint & 0x3F); + } else if (codepoint < 0x10000) { + out[0] = 0xE0 | (codepoint >> 12); + out[1] = 0x80 | ((codepoint >> 6) & 0x3F); + out[2] = 0x80 | (codepoint & 0x3F); + } else if (codepoint < 0x200000) { + out[0] = 0xF0 | (codepoint >> 18); + out[1] = 0x80 | ((codepoint >> 12) & 0x3F); + out[2] = 0x80 | ((codepoint >> 6) & 0x3F); + out[3] = 0x80 | ((codepoint) & 0x3F); + } else if (codepoint < 0x4000000) { + out[0] = 0xF8 | (codepoint >> 24); + out[1] = 0x80 | (codepoint >> 18); + out[2] = 0x80 | ((codepoint >> 12) & 0x3F); + out[3] = 0x80 | ((codepoint >> 6) & 0x3F); + out[4] = 0x80 | ((codepoint) & 0x3F); + } else { + out[0] = 0xF8 | (codepoint >> 30); + out[1] = 0x80 | ((codepoint >> 24) & 0x3F); + out[2] = 0x80 | ((codepoint >> 18) & 0x3F); + out[3] = 0x80 | ((codepoint >> 12) & 0x3F); + out[4] = 0x80 | ((codepoint >> 6) & 0x3F); + out[5] = 0x80 | ((codepoint) & 0x3F); + } + + return strlen(out); +} + +/** + * Obtain codepoint display width. + * + * This is copied from bim. Supports a few useful + * things like rendering escapes as codepoints. + */ +static int codepoint_width(int codepoint) { + if (codepoint == '\t') { + return 1; /* Recalculate later */ + } + if (codepoint < 32) { + /* We render these as ^@ */ + return 2; + } + if (codepoint == 0x7F) { + /* Renders as ^? */ + return 2; + } + if (codepoint > 0x7f && codepoint < 0xa0) { + /* Upper control bytes */ + return 4; + } + if (codepoint == 0xa0) { + /* Non-breaking space _ */ + return 1; + } + /* Skip wcwidth for anything under 256 */ + if (codepoint > 256) { + /* Higher codepoints may be wider (eg. Japanese) */ + int out = wcwidth(codepoint); + if (out >= 1) return out; + /* Invalid character, render as [U+ABCD] or [U+ABCDEF] */ + return (codepoint < 0x10000) ? 8 : 10; + } + return 1; +} + +static void recalculate_tabs(line_t * line) { + int j = 0; + for (int i = 0; i < line->actual; ++i) { + if (line->text[i].codepoint == '\t') { + line->text[i].display_width = 4 - (j % 4); + } + j += line->text[i].display_width; + } +} + + +/** + * Color themes have also been copied from bim. + * + * Slimmed down to only the ones we use for syntax + * highlighting; the UI colors have been removed. + */ +static const char * COLOR_FG = "@9"; +static const char * COLOR_BG = "@9"; +static const char * COLOR_ALT_FG = "@5"; +static const char * COLOR_ALT_BG = "@9"; +static const char * COLOR_KEYWORD = "@4"; +static const char * COLOR_STRING = "@2"; +static const char * COLOR_COMMENT = "@5"; +static const char * COLOR_TYPE = "@3"; +static const char * COLOR_PRAGMA = "@1"; +static const char * COLOR_NUMERAL = "@1"; +static const char * COLOR_RED = "@1"; +static const char * COLOR_GREEN = "@2"; +static const char * COLOR_ESCAPE = "@2"; +static const char * COLOR_SEARCH_FG = "@0"; +static const char * COLOR_SEARCH_BG = "@3"; +static const char * COLOR_ERROR_FG = "@9"; +static const char * COLOR_ERROR_BG = "@9"; +static const char * COLOR_BOLD = "@9"; +static const char * COLOR_LINK = "@9"; + +/** + * Themes are selected from the $RLINE_THEME + * environment variable. + */ +static void rline_exp_load_colorscheme_default(void) { + COLOR_FG = "@9"; + COLOR_BG = "@9"; + COLOR_ALT_FG = "@10"; + COLOR_ALT_BG = "@9"; + COLOR_KEYWORD = "@14"; + COLOR_STRING = "@2"; + COLOR_COMMENT = "@10"; + COLOR_TYPE = "@3"; + COLOR_PRAGMA = "@1"; + COLOR_NUMERAL = "@1"; + COLOR_RED = "@1"; + COLOR_GREEN = "@2"; + COLOR_ESCAPE = "@12"; + COLOR_SEARCH_FG = "@0"; + COLOR_SEARCH_BG = "@13"; + COLOR_ERROR_FG = "@17"; + COLOR_ERROR_BG = "@1"; + COLOR_BOLD = "@9"; + COLOR_LINK = "@14"; +} + +/** + * Syntax highlighting flags. + */ +#define FLAG_NONE 0 +#define FLAG_KEYWORD 1 +#define FLAG_STRING 2 +#define FLAG_COMMENT 3 +#define FLAG_TYPE 4 +#define FLAG_PRAGMA 5 +#define FLAG_NUMERAL 6 +#define FLAG_ERROR 7 +#define FLAG_DIFFPLUS 8 +#define FLAG_DIFFMINUS 9 +#define FLAG_NOTICE 10 +#define FLAG_BOLD 11 +#define FLAG_LINK 12 +#define FLAG_ESCAPE 13 + +#define FLAG_SELECT (1 << 5) + +struct syntax_state { + line_t * line; + int line_no; + int state; + int i; +}; + +#define paint(length, flag) do { for (int i = 0; i < (length) && state->i < state->line->actual; i++, state->i++) { state->line->text[state->i].flags = (flag); } } while (0) +#define charat() (state->i < state->line->actual ? state->line->text[(state->i)].codepoint : -1) +#define nextchar() (state->i + 1 < state->line->actual ? state->line->text[(state->i+1)].codepoint : -1) +#define lastchar() (state->i - 1 >= 0 ? state->line->text[(state->i-1)].codepoint : -1) +#define skip() (state->i++) +#define charrel(x) (state->i + (x) < state->line->actual ? state->line->text[(state->i+(x))].codepoint : -1) + +/** + * Match and paint a single keyword. Returns 1 if the keyword was matched and 0 otherwise, + * so it can be used for prefix checking for things that need further special handling. + */ +static int match_and_paint(struct syntax_state * state, const char * keyword, int flag, int (*keyword_qualifier)(int c)) { + if (keyword_qualifier(lastchar())) return 0; + if (!keyword_qualifier(charat())) return 0; + int i = state->i; + int slen = 0; + while (i < state->line->actual || *keyword == '\0') { + if (*keyword == '\0' && (i >= state->line->actual || !keyword_qualifier(state->line->text[i].codepoint))) { + for (int j = 0; j < slen; ++j) { + paint(1, flag); + } + return 1; + } + if (*keyword != state->line->text[i].codepoint) return 0; + + i++; + keyword++; + slen++; + } + return 0; +} + +/** + * Find keywords from a list and paint them, assuming they aren't in the middle of other words. + * Returns 1 if a keyword from the last was found, otherwise 0. + */ +static int find_keywords(struct syntax_state * state, char ** keywords, int flag, int (*keyword_qualifier)(int c)) { + if (keyword_qualifier(lastchar())) return 0; + if (!keyword_qualifier(charat())) return 0; + for (char ** keyword = keywords; *keyword; ++keyword) { + int d = 0; + while (state->i + d < state->line->actual && state->line->text[state->i+d].codepoint == (*keyword)[d]) d++; + if ((*keyword)[d] == '\0' && (state->i + d >= state->line->actual || !keyword_qualifier(state->line->text[state->i+d].codepoint))) { + paint((int)strlen(*keyword), flag); + return 1; + } + } + + return 0; +} + +/** + * This is a basic character matcher for "keyword" characters. + */ +static int simple_keyword_qualifier(int c) { + return isalnum(c) || (c == '_'); +} + + +static int common_comment_buzzwords(struct syntax_state * state) { + if (match_and_paint(state, "TODO", FLAG_NOTICE, simple_keyword_qualifier)) { return 1; } + else if (match_and_paint(state, "XXX", FLAG_NOTICE, simple_keyword_qualifier)) { return 1; } + else if (match_and_paint(state, "FIXME", FLAG_ERROR, simple_keyword_qualifier)) { return 1; } + return 0; +} + +/** + * Paint a comment until end of line, assumes this comment can not continue. + * (Some languages have comments that can continue with a \ - don't use this!) + * Assumes you've already painted your comment start characters. + */ +static int paint_comment(struct syntax_state * state) { + while (charat() != -1) { + if (common_comment_buzzwords(state)) continue; + else { paint(1, FLAG_COMMENT); } + } + return -1; +} + +static int c_keyword_qualifier(int c) { + return isalnum(c) || (c == '_'); +} + +static void paintNHex(struct syntax_state * state, int n) { + paint(2, FLAG_ESCAPE); + /* Why is my FLAG_ERROR not valid in rline? */ + for (int i = 0; i < n; ++i) { + paint(1, isxdigit(charat()) ? FLAG_ESCAPE : FLAG_DIFFMINUS); + } +} + +static char * syn_krk_keywords[] = { + "and","class","def","else","for","if","in","import","del", + "let","not","or","return","while","try","except","raise", + "continue","break","as","from","elif","lambda","with","is", + "pass","assert","yield","finally","async","await", + NULL +}; + +static char * syn_krk_types[] = { + /* built-in functions */ + "self", "super", /* implicit in a class method */ + "len", "str", "int", "float", "dir", "repr", /* global functions from __builtins__ */ + "list","dict","range", /* builtin classes */ + "object","exception","isinstance","type","tuple","reversed", + "print","set","any","all","bool","ord","chr","hex","oct","filter", + "sorted","bytes","getattr","sum","min","max","id","hash","map","bin", + "enumerate","zip","setattr","property","staticmethod","classmethod", + "issubclass","hasattr","delattr","NotImplemented","abs","slice","long", + NULL +}; + +static char * syn_krk_special[] = { + "True","False","None", + /* Exception names */ + NULL +}; + +static char * syn_krk_exception[] = { + "Exception", "TypeError", "ArgumentError", "IndexError", "KeyError", + "AttributeError", "NameError", "ImportError", "IOError", "ValueError", + "KeyboardInterrupt", "ZeroDivisionError", "NotImplementedError", "SyntaxError", + "AssertionError", "BaseException", "OSError", "SystemError", + NULL +}; + +static void paint_krk_string_shared(struct syntax_state * state, int type, int isFormat, int isTriple) { + if (charat() == '\\') { + if (nextchar() == 'x') { + paintNHex(state, 2); + } else if (nextchar() == 'u') { + paintNHex(state, 4); + } else if (nextchar() == 'U') { + paintNHex(state, 8); + } else if (nextchar() >= '0' && nextchar() <= '7') { + paint(2, FLAG_ESCAPE); + if (charat() >= '0' && charat() <= '7') { + paint(1, FLAG_ESCAPE); + if (charat() >= '0' && charat() <= '7') { + paint(1, FLAG_ESCAPE); + } + } + } else { + paint(2, FLAG_ESCAPE); + } + } else if (isFormat && charat() == '{') { + if (nextchar() == '{') { + paint(2, FLAG_STRING); + return; + } + paint(1, FLAG_ESCAPE); + if (charat() == '}') { + state->i--; + paint(2, FLAG_ERROR); /* Can't do that. */ + } else { + int x = 0; + while (charat() != -1) { + if (charat() == '{') { + x++; + } else if (charat() == '}') { + if (x == 0) { + paint(1, FLAG_ESCAPE); + break; + } + x--; + } else if (charat() == type && !isTriple) { + while (charat() != -1) { + paint(1, FLAG_ERROR); + } + return; + } else if (find_keywords(state, syn_krk_keywords, FLAG_ESCAPE, c_keyword_qualifier)) { + continue; + } else if (lastchar() != '.' && find_keywords(state, syn_krk_types, FLAG_TYPE, c_keyword_qualifier)) { + continue; + } else if (find_keywords(state, syn_krk_exception, FLAG_PRAGMA, c_keyword_qualifier)) { + continue; + } + paint(1, FLAG_NUMERAL); + } + } + } else { + paint(1, FLAG_STRING); + } +} + +static void paint_krk_string(struct syntax_state * state, int type, int isFormat) { + /* Assumes you came in from a check of charat() == '"' */ + paint(1, FLAG_STRING); + while (charat() != -1) { + if (charat() == '\\' && nextchar() == type) { + paint(2, FLAG_ESCAPE); + } else if (charat() == type) { + paint(1, FLAG_STRING); + return; + } else { + paint_krk_string_shared(state,type,isFormat,0); + } + } +} + +static int paint_krk_numeral(struct syntax_state * state) { + if (charat() == '0' && (nextchar() == 'x' || nextchar() == 'X')) { + paint(2, FLAG_NUMERAL); + while (isxdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); + } else if (charat() == '0' && (nextchar() == 'o' || nextchar() == 'O')) { + paint(2, FLAG_NUMERAL); + while ((charat() >= '0' && charat() <= '7') || charat() == '_') paint(1, FLAG_NUMERAL); + } else if (charat() == '0' && (nextchar() == 'b' || nextchar() == 'B')) { + paint(2, FLAG_NUMERAL); + while (charat() == '0' || charat() == '1' || charat() == '_') paint(1, FLAG_NUMERAL); + } else { + while (isdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); + if (charat() == '.' && isdigit(nextchar())) { + paint(1, FLAG_NUMERAL); + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + } + } + return 0; +} + +static int paint_krk_triple_string(struct syntax_state * state, int type, int isFormat) { + while (charat() != -1) { + if (charat() == '\\' && nextchar() == type) { + paint(2, FLAG_ESCAPE); + } else if (charat() == type) { + paint(1, FLAG_STRING); + if (charat() == type && nextchar() == type) { + paint(2, FLAG_STRING); + return 0; + } + } else { + paint_krk_string_shared(state,type,isFormat,1); + } + } + return (type == '"') ? 1 : 2; /* continues */ +} + +static int syn_krk_calculate(struct syntax_state * state) { + switch (state->state) { + case -1: + case 0: + if (charat() == '#') { + paint_comment(state); + } else if (charat() == '@') { + paint(1, FLAG_TYPE); + while (c_keyword_qualifier(charat())) paint(1, FLAG_TYPE); + return 0; + } else if (charat() == '"' || charat() == '\'') { + int isFormat = (lastchar() == 'f'); + if (nextchar() == charat() && charrel(2) == charat()) { + int type = charat(); + paint(3, FLAG_STRING); + return paint_krk_triple_string(state, type, isFormat); + } else { + paint_krk_string(state, charat(), isFormat); + } + return 0; + } else if (find_keywords(state, syn_krk_keywords, FLAG_KEYWORD, c_keyword_qualifier)) { + return 0; + } else if (lastchar() != '.' && find_keywords(state, syn_krk_types, FLAG_TYPE, c_keyword_qualifier)) { + return 0; + } else if (find_keywords(state, syn_krk_special, FLAG_NUMERAL, c_keyword_qualifier)) { + return 0; + } else if (find_keywords(state, syn_krk_exception, FLAG_PRAGMA, c_keyword_qualifier)) { + return 0; + } else if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) { + paint_krk_numeral(state); + return 0; + } else if (charat() != -1) { + skip(); + return 0; + } + break; + /* rline doesn't support multiline editing anyway */ + case 1: + return paint_krk_triple_string(state, '"', 0); + case 2: + return paint_krk_triple_string(state, '\'', 0); + } + return -1; +} + +static char * syn_krk_dbg_commands[] = { + "s", "skip", + "c", "continue", + "q", "quit", + "e", "enable", + "d", "disable", + "r", "remove", + "bt", "backtrace", + "break", + "abort", + "help", + NULL, +}; + +static char * syn_krk_dbg_info_types[] = { + "breakpoints", + NULL, +}; + +static int syn_krk_dbg_calculate(struct syntax_state * state) { + if (state->state < 1) { + if (state->i == 0) { + if (match_and_paint(state, "p", FLAG_KEYWORD, c_keyword_qualifier) || + match_and_paint(state, "print", FLAG_KEYWORD, c_keyword_qualifier)) { + while (1) { + int result = syn_krk_calculate(state); + if (result == 0) continue; + if (result == -1) return -1; + return result + 1; + } + } else if (match_and_paint(state,"info", FLAG_KEYWORD, c_keyword_qualifier) || + match_and_paint(state,"i", FLAG_KEYWORD, c_keyword_qualifier)) { + skip(); + find_keywords(state,syn_krk_dbg_info_types, FLAG_TYPE, c_keyword_qualifier); + return -1; + } else if (find_keywords(state, syn_krk_dbg_commands, FLAG_KEYWORD, c_keyword_qualifier)) { + return 0; + } + } + return -1; + } else { + state->state -= 1; + return syn_krk_calculate(state) + 1; + } +} + +#ifdef __toaru__ +static int esh_variable_qualifier(int c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '_'); +} + +static int paint_esh_variable(struct syntax_state * state) { + if (charat() == '{') { + paint(1, FLAG_TYPE); + while (charat() != '}' && charat() != -1) paint(1, FLAG_TYPE); + if (charat() == '}') paint(1, FLAG_TYPE); + } else { + if (charat() == '?' || charat() == '$' || charat() == '#') { + paint(1, FLAG_TYPE); + } else { + while (esh_variable_qualifier(charat())) paint(1, FLAG_TYPE); + } + } + return 0; +} + +static int paint_esh_string(struct syntax_state * state) { + int last = -1; + while (charat() != -1) { + if (last != '\\' && charat() == '"') { + paint(1, FLAG_STRING); + return 0; + } else if (charat() == '$') { + paint(1, FLAG_TYPE); + paint_esh_variable(state); + last = -1; + } else if (charat() != -1) { + last = charat(); + paint(1, FLAG_STRING); + } + } + return 2; +} + +static int paint_esh_single_string(struct syntax_state * state) { + int last = -1; + while (charat() != -1) { + if (last != '\\' && charat() == '\'') { + paint(1, FLAG_STRING); + return 0; + } else if (charat() != -1) { + last = charat(); + paint(1, FLAG_STRING); + } + } + return 1; +} + +static int esh_keyword_qualifier(int c) { + return (isalnum(c) || c == '?' || c == '_' || c == '-'); /* technically anything that isn't a space should qualify... */ +} + +static char * esh_keywords[] = { + "cd","exit","export","help","history","if","empty?", + "equals?","return","export-cmd","source","exec","not","while", + "then","else","echo", + NULL +}; + +static int syn_esh_calculate(struct syntax_state * state) { + if (state->state == 1) { + return paint_esh_single_string(state); + } else if (state->state == 2) { + return paint_esh_string(state); + } + if (charat() == '#') { + while (charat() != -1) { + if (common_comment_buzzwords(state)) continue; + else paint(1, FLAG_COMMENT); + } + return -1; + } else if (charat() == '$') { + paint(1, FLAG_TYPE); + paint_esh_variable(state); + return 0; + } else if (charat() == '\'') { + paint(1, FLAG_STRING); + return paint_esh_single_string(state); + } else if (charat() == '"') { + paint(1, FLAG_STRING); + return paint_esh_string(state); + } else if (match_and_paint(state, "export", FLAG_KEYWORD, esh_keyword_qualifier)) { + while (charat() == ' ') skip(); + while (esh_keyword_qualifier(charat())) paint(1, FLAG_TYPE); + return 0; + } else if (match_and_paint(state, "export-cmd", FLAG_KEYWORD, esh_keyword_qualifier)) { + while (charat() == ' ') skip(); + while (esh_keyword_qualifier(charat())) paint(1, FLAG_TYPE); + return 0; + } else if (find_keywords(state, esh_keywords, FLAG_KEYWORD, esh_keyword_qualifier)) { + return 0; + } else if (find_keywords(state, shell_commands, FLAG_KEYWORD, esh_keyword_qualifier)) { + return 0; + } else if (isdigit(charat())) { + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + return 0; + } else if (charat() != -1) { + skip(); + return 0; + } + return -1; +} + +static char * syn_py_keywords[] = { + "class","def","return","del","if","else","elif","for","while","continue", + "break","assert","as","and","or","except","finally","from","global", + "import","in","is","lambda","with","nonlocal","not","pass","raise","try","yield", + NULL +}; + +static char * syn_py_types[] = { + /* built-in functions */ + "abs","all","any","ascii","bin","bool","breakpoint","bytes", + "bytearray","callable","compile","complex","delattr","chr", + "dict","dir","divmod","enumerate","eval","exec","filter","float", + "format","frozenset","getattr","globals","hasattr","hash","help", + "hex","id","input","int","isinstance","issubclass","iter","len", + "list","locals","map","max","memoryview","min","next","object", + "oct","open","ord","pow","print","property","range","repr","reverse", + "round","set","setattr","slice","sorted","staticmethod","str","sum", + "super","tuple","type","vars","zip", + NULL +}; + +static char * syn_py_special[] = { + "True","False","None", + NULL +}; + +static int paint_py_triple_double(struct syntax_state * state) { + while (charat() != -1) { + if (charat() == '"') { + paint(1, FLAG_STRING); + if (charat() == '"' && nextchar() == '"') { + paint(2, FLAG_STRING); + return 0; + } + } else { + paint(1, FLAG_STRING); + } + } + return 1; /* continues */ +} + +static int paint_py_triple_single(struct syntax_state * state) { + while (charat() != -1) { + if (charat() == '\'') { + paint(1, FLAG_STRING); + if (charat() == '\'' && nextchar() == '\'') { + paint(2, FLAG_STRING); + return 0; + } + } else { + paint(1, FLAG_STRING); + } + } + return 2; /* continues */ +} + +static int paint_py_single_string(struct syntax_state * state) { + paint(1, FLAG_STRING); + while (charat() != -1) { + if (charat() == '\\' && nextchar() == '\'') { + paint(2, FLAG_ESCAPE); + } else if (charat() == '\'') { + paint(1, FLAG_STRING); + return 0; + } else if (charat() == '\\') { + paint(2, FLAG_ESCAPE); + } else { + paint(1, FLAG_STRING); + } + } + return 0; +} + +static int paint_py_numeral(struct syntax_state * state) { + if (charat() == '0' && (nextchar() == 'x' || nextchar() == 'X')) { + paint(2, FLAG_NUMERAL); + while (isxdigit(charat())) paint(1, FLAG_NUMERAL); + } else if (charat() == '0' && nextchar() == '.') { + paint(2, FLAG_NUMERAL); + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + if ((charat() == '+' || charat() == '-') && (nextchar() == 'e' || nextchar() == 'E')) { + paint(2, FLAG_NUMERAL); + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + } else if (charat() == 'e' || charat() == 'E') { + paint(1, FLAG_NUMERAL); + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + } + if (charat() == 'j') paint(1, FLAG_NUMERAL); + return 0; + } else { + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + if (charat() == '.') { + paint(1, FLAG_NUMERAL); + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + if ((charat() == '+' || charat() == '-') && (nextchar() == 'e' || nextchar() == 'E')) { + paint(2, FLAG_NUMERAL); + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + } else if (charat() == 'e' || charat() == 'E') { + paint(1, FLAG_NUMERAL); + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + } + if (charat() == 'j') paint(1, FLAG_NUMERAL); + return 0; + } + if (charat() == 'j') paint(1, FLAG_NUMERAL); + } + while (charat() == 'l' || charat() == 'L') paint(1, FLAG_NUMERAL); + return 0; +} + +static void paint_py_format_string(struct syntax_state * state, char type) { + paint(1, FLAG_STRING); + while (charat() != -1) { + if (charat() == '\\' && nextchar() == type) { + paint(2, FLAG_ESCAPE); + } else if (charat() == type) { + paint(1, FLAG_STRING); + return; + } else if (charat() == '\\') { + paint(2, FLAG_ESCAPE); + } else if (charat() == '{') { + paint(1, FLAG_NUMERAL); + if (charat() == '}') { + state->i--; + paint(2, FLAG_ERROR); /* Can't do that. */ + } else { + while (charat() != -1 && charat() != '}') { + paint(1, FLAG_NUMERAL); + } + paint(1, FLAG_NUMERAL); + } + } else { + paint(1, FLAG_STRING); + } + } +} + +static void paint_simple_string(struct syntax_state * state) { + /* Assumes you came in from a check of charat() == '"' */ + paint(1, FLAG_STRING); + while (charat() != -1) { + if (charat() == '\\' && nextchar() == '"') { + paint(2, FLAG_ESCAPE); + } else if (charat() == '"') { + paint(1, FLAG_STRING); + return; + } else if (charat() == '\\') { + paint(2, FLAG_ESCAPE); + } else { + paint(1, FLAG_STRING); + } + } +} + +static int syn_py_calculate(struct syntax_state * state) { + switch (state->state) { + case -1: + case 0: + if (charat() == '#') { + paint_comment(state); + } else if (state->i == 0 && match_and_paint(state, "import", FLAG_PRAGMA, c_keyword_qualifier)) { + return 0; + } else if (charat() == '@') { + paint(1, FLAG_PRAGMA); + while (c_keyword_qualifier(charat())) paint(1, FLAG_PRAGMA); + return 0; + } else if (charat() == '"') { + if (nextchar() == '"' && charrel(2) == '"') { + paint(3, FLAG_STRING); + return paint_py_triple_double(state); + } else if (lastchar() == 'f') { + /* I don't like backtracking like this, but it makes this parse easier */ + state->i--; + paint(1,FLAG_TYPE); + paint_py_format_string(state,'"'); + return 0; + } else { + paint_simple_string(state); + return 0; + } + } else if (find_keywords(state, syn_py_keywords, FLAG_KEYWORD, c_keyword_qualifier)) { + return 0; + } else if (lastchar() != '.' && find_keywords(state, syn_py_types, FLAG_TYPE, c_keyword_qualifier)) { + return 0; + } else if (find_keywords(state, syn_py_special, FLAG_NUMERAL, c_keyword_qualifier)) { + return 0; + } else if (charat() == '\'') { + if (nextchar() == '\'' && charrel(2) == '\'') { + paint(3, FLAG_STRING); + return paint_py_triple_single(state); + } else if (lastchar() == 'f') { + /* I don't like backtracking like this, but it makes this parse easier */ + state->i--; + paint(1,FLAG_TYPE); + paint_py_format_string(state,'\''); + return 0; + } else { + return paint_py_single_string(state); + } + } else if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) { + paint_py_numeral(state); + return 0; + } else if (charat() != -1) { + skip(); + return 0; + } + break; + case 1: /* multiline """ string */ + return paint_py_triple_double(state); + case 2: /* multiline ''' string */ + return paint_py_triple_single(state); + } + return -1; +} + +void * rline_exp_for_python(void * _stdin, void * _stdout, char * prompt) { + + rline_exp_set_prompts(prompt, "", strlen(prompt), 0); + + char * buf = malloc(1024); + memset(buf, 0, 1024); + + rline_exp_set_syntax("python"); + rline_exit_string = ""; + rline(buf, 1024); + rline_history_insert(strdup(buf)); + rline_scroll = 0; + + return buf; +} +#endif + +void rline_redraw(rline_context_t * context) { + if (context->quiet) return; + printf("\033[u%s\033[K", context->buffer); + for (int i = context->offset; i < context->collected; ++i) { + printf("\033[D"); + } + fflush(stdout); +} + + +/** + * Convert syntax hilighting flag to color code + */ +static const char * flag_to_color(int _flag) { + int flag = _flag & 0xF; + switch (flag) { + case FLAG_KEYWORD: + return COLOR_KEYWORD; + case FLAG_STRING: + return COLOR_STRING; + case FLAG_COMMENT: + return COLOR_COMMENT; + case FLAG_TYPE: + return COLOR_TYPE; + case FLAG_NUMERAL: + return COLOR_NUMERAL; + case FLAG_PRAGMA: + return COLOR_PRAGMA; + case FLAG_DIFFPLUS: + return COLOR_GREEN; + case FLAG_DIFFMINUS: + return COLOR_RED; + case FLAG_BOLD: + return COLOR_BOLD; + case FLAG_LINK: + return COLOR_LINK; + case FLAG_ESCAPE: + return COLOR_ESCAPE; + default: + return COLOR_FG; + } +} + +static struct syntax_definition { + char * name; + int (*calculate)(struct syntax_state *); + int tabIndents; +} syntaxes[] = { + {"krk",syn_krk_calculate, 1}, + {"krk-dbg",syn_krk_dbg_calculate, 1}, +#ifdef __toaru__ + {"python",syn_py_calculate, 1}, + {"esh",syn_esh_calculate, 0}, +#endif + {NULL, NULL, 0}, +}; + +static struct syntax_definition * syntax; + +int rline_exp_set_syntax(char * name) { + if (!name) { + syntax = NULL; + return 0; + } + for (struct syntax_definition * s = syntaxes; s->name; ++s) { + if (!strcmp(name,s->name)) { + syntax = s; + return 0; + } + } + return 1; +} + +/** + * Syntax highlighting + * Slimmed down from the bim implementation a bit, + * but generally compatible with the same definitions. + * + * Type highlighting has been removed as the sh highlighter + * didn't use it. This should be made pluggable again, and + * the bim syntax highlighters should probably be broken + * out into dynamically-loaded libraries? + */ +static void recalculate_syntax(line_t * line) { + /* Clear syntax for this line first */ + int line_no = 0; + //int is_original = 1; + while (1) { + for (int i = 0; i < line->actual; ++i) { + line->text[i].flags = 0; + } + + if (!syntax) { + return; + } + + /* Start from the line's stored in initial state */ + struct syntax_state state; + state.line = line; + state.line_no = line_no; + state.state = line->istate; + state.i = 0; + + while (1) { + state.state = syntax->calculate(&state); + + if (state.state != 0) { + /* TODO: Figure out a way to make this work for multiline input */ +#if 0 + if (line_no == -1) return; + if (!is_original) { + redraw_line(line_no); + } + if (line_no + 1 < env->line_count && env->lines[line_no+1]->istate != state.state) { + line_no++; + line = env->lines[line_no]; + line->istate = state.state; + if (env->loading) return; + is_original = 0; + goto _next; + } +#endif + return; + } + } +//_next: +// (void)0; + } +} + +/** + * Set colors + */ +static void set_colors(const char * fg, const char * bg) { + printf("\033[22;23;"); + if (*bg == '@') { + int _bg = atoi(bg+1); + if (_bg < 10) { + printf("4%d;", _bg); + } else { + printf("10%d;", _bg-10); + } + } else { + printf("48;%s;", bg); + } + if (*fg == '@') { + int _fg = atoi(fg+1); + if (_fg < 10) { + printf("3%dm", _fg); + } else { + printf("9%dm", _fg-10); + } + } else { + printf("38;%sm", fg); + } + fflush(stdout); +} + +/** + * Set just the foreground color + * + * (See set_colors above) + */ +static void set_fg_color(const char * fg) { + printf("\033[22;23;"); + if (*fg == '@') { + int _fg = atoi(fg+1); + if (_fg < 10) { + printf("3%dm", _fg); + } else { + printf("9%dm", _fg-10); + } + } else { + printf("38;%sm", fg); + } + fflush(stdout); +} + +void rline_set_colors(rline_style_t style) { + switch (style) { + case RLINE_STYLE_MAIN: + set_colors(COLOR_FG, COLOR_BG); + break; + case RLINE_STYLE_ALT: + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + break; + case RLINE_STYLE_KEYWORD: + set_fg_color(COLOR_KEYWORD); + break; + case RLINE_STYLE_STRING: + set_fg_color(COLOR_STRING); + break; + case RLINE_STYLE_COMMENT: + set_fg_color(COLOR_COMMENT); + break; + case RLINE_STYLE_TYPE: + set_fg_color(COLOR_TYPE); + break; + case RLINE_STYLE_PRAGMA: + set_fg_color(COLOR_PRAGMA); + break; + case RLINE_STYLE_NUMERAL: + set_fg_color(COLOR_NUMERAL); + break; + } +} + +/** + * Mostly copied from bim, but with some minor + * alterations and removal of selection support. + */ +static void render_line(void) { + printf("\033[?25l"); + if (show_left_side) { + printf("\033[0m\r%s", prompt); + } else { + printf("\033[0m\r$"); + } + + if (offset && prompt_width_calc) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("\b<"); + } + + int i = 0; /* Offset in char_t line data entries */ + int j = 0; /* Offset in terminal cells */ + + const char * last_color = NULL; + int was_searching = 0; + + /* Set default text colors */ + set_colors(COLOR_FG, COLOR_BG); + + /* + * When we are rendering in the middle of a wide character, + * we render -'s to fill the remaining amount of the + * charater's width + */ + int remainder = 0; + + int is_spaces = 1; + + line_t * line = the_line; + + /* For each character in the line ... */ + while (i < line->actual) { + + /* If there is remaining text... */ + if (remainder) { + + /* If we should be drawing by now... */ + if (j >= offset) { + /* Fill remainder with -'s */ + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("-"); + set_colors(COLOR_FG, COLOR_BG); + } + + /* One less remaining width cell to fill */ + remainder--; + + /* Terminal offset moves forward */ + j++; + + /* + * If this was the last remaining character, move to + * the next codepoint in the line + */ + if (remainder == 0) { + i++; + } + + continue; + } + + /* Get the next character to draw */ + char_t c = line->text[i]; + if (c.codepoint != ' ') is_spaces = 0; + + /* If we should be drawing by now... */ + if (j >= offset) { + + /* If this character is going to fall off the edge of the screen... */ + if (j - offset + c.display_width >= width - prompt_width_calc) { + /* We draw this with special colors so it isn't ambiguous */ + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + + /* If it's wide, draw ---> as needed */ + while (j - offset < width - prompt_width_calc - 1) { + printf("-"); + j++; + } + + /* End the line with a > to show it overflows */ + printf(">"); + set_colors(COLOR_FG, COLOR_BG); + j++; + break; + } + + /* Syntax hilighting */ + const char * color = flag_to_color(c.flags); + if (c.flags & FLAG_SELECT) { + set_colors(color, COLOR_BG); + fprintf(stdout,"\033[7m"); + was_searching = 1; + } else if (c.flags == FLAG_NOTICE) { + set_colors(COLOR_SEARCH_FG, COLOR_SEARCH_BG); + was_searching = 1; + } else if (c.flags == FLAG_ERROR) { + set_colors(COLOR_ERROR_FG, COLOR_ERROR_BG); + was_searching = 1; /* co-opting this should work... */ + } else if (was_searching) { + fprintf(stdout,"\033[0m"); + set_colors(color, COLOR_BG); + last_color = color; + } else if (!last_color || strcmp(color, last_color)) { + set_fg_color(color); + last_color = color; + } + + /* Render special characters */ + if (c.codepoint == '\t') { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("»"); + for (int i = 1; i < c.display_width; ++i) { + printf("·"); + } + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); + } else if (c.codepoint < 32) { + /* Codepoints under 32 to get converted to ^@ escapes */ + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("^%c", '@' + c.codepoint); + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); + } else if (c.codepoint == 0x7f) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("^?"); + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); + } else if (c.codepoint > 0x7f && c.codepoint < 0xa0) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("<%2x>", c.codepoint); + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); + } else if (c.codepoint == 0xa0) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("_"); + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); + } else if (c.display_width == 8) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("[U+%04x]", c.codepoint); + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); + } else if (c.display_width == 10) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("[U+%06x]", c.codepoint); + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); +#if 0 + } else if (c.codepoint == ' ' && i == line->actual - 1) { + /* Special case: space at end of line */ + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("·"); + set_colors(COLOR_FG, COLOR_BG); +#endif + } else if (i > 0 && is_spaces && c.codepoint == ' ' && !(i % 4)) { + set_colors(COLOR_ALT_FG, COLOR_BG); /* Normal background so this is more subtle */ + printf("▏"); + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); + } else { + /* Normal characters get output */ + char tmp[7]; /* Max six bytes, use 7 to ensure last is always nil */ + to_eight(c.codepoint, tmp); + printf("%s", tmp); + } + + /* Advance the terminal cell offset by the render width of this character */ + j += c.display_width; + + /* Advance to the next character */ + i++; + } else if (c.display_width > 1) { + /* + * If this is a wide character but we aren't ready to render yet, + * we may need to draw some filler text for the remainder of its + * width to ensure we don't jump around when horizontally scrolling + * past wide characters. + */ + remainder = c.display_width - 1; + j++; + } else { + /* Regular character, not ready to draw, advance without doing anything */ + j++; + i++; + } + } + + printf("\033[0m"); + set_colors(COLOR_FG, COLOR_BG); + + if (show_right_side && prompt_right_width) { + /* Fill to end right hand side */ + for (; j < width + offset - prompt_width_calc; ++j) { + printf(" "); + } + + /* Print right hand side */ + printf("\033[0m%s", prompt_right); + } else { + printf("\033[0K"); + } + fflush(stdout); +} + +/** + * Create a line_t + */ +static line_t * line_create(void) { + line_t * line = malloc(sizeof(line_t) + sizeof(char_t) * 32); + line->available = 32; + line->actual = 0; + line->istate = 0; + return line; +} + +/** + * Insert a character into a line + */ +static line_t * line_insert(line_t * line, char_t c, int offset) { + + /* If there is not enough space... */ + if (line->actual == line->available) { + /* Expand the line buffer */ + line->available *= 2; + line = realloc(line, sizeof(line_t) + sizeof(char_t) * line->available); + } + + /* If this was not the last character, then shift remaining characters forward. */ + if (offset < line->actual) { + memmove(&line->text[offset+1], &line->text[offset], sizeof(char_t) * (line->actual - offset)); + } + + /* Insert the new character */ + line->text[offset] = c; + + /* There is one new character in the line */ + line->actual += 1; + + if (!loading) { + recalculate_tabs(line); + recalculate_syntax(line); + } + + return line; +} + +/** + * Update terminal size + * + * We don't listen for sigwinch for various reasons... + */ +static void get_size(void) { + rline_terminal_width = terms[0]->cols; + if (rline_terminal_width - prompt_right_width - prompt_width > MINIMUM_SIZE) { + show_right_side = 1; + show_left_side = 1; + prompt_width_calc = prompt_width; + width = rline_terminal_width - prompt_right_width; + } else { + show_right_side = 0; + if (rline_terminal_width - prompt_width > MINIMUM_SIZE) { + show_left_side = 1; + prompt_width_calc = prompt_width; + } else { + show_left_side = 0; + prompt_width_calc = 1; + } + width = rline_terminal_width; + } +} + +/** + * Place the cursor within the line + */ +void rline_place_cursor(void) { + int x = prompt_width_calc + 1 - offset; + for (int i = 0; i < column; ++i) { + char_t * c = &the_line->text[i]; + x += c->display_width; + } + + if (x > width - 1) { + /* Adjust the offset appropriately to scroll horizontally */ + int diff = x - (width - 1); + offset += diff; + x -= diff; + render_line(); + } + + /* Same for scrolling horizontally to the left */ + if (x < prompt_width_calc + 1) { + int diff = (prompt_width_calc + 1) - x; + offset -= diff; + x += diff; + render_line(); + } + + printf("\033[?25h\033[%dG", x); + fflush(stdout); +} + +/** + * Delete a character + */ +static void line_delete(line_t * line, int offset) { + + /* Can't delete character before start of line. */ + if (offset == 0) return; + + /* If this isn't the last character, we need to move all subsequent characters backwards */ + if (offset < line->actual) { + memmove(&line->text[offset-1], &line->text[offset], sizeof(char_t) * (line->actual - offset)); + } + + /* The line is one character shorter */ + line->actual -= 1; + + if (!loading) { + recalculate_tabs(line); + recalculate_syntax(line); + } +} + +/** + * Backspace from the cursor position + */ +static void delete_at_cursor(void) { + if (column > 0) { + line_delete(the_line, column); + column--; + if (offset > 0) offset--; + } +} + +static void smart_backspace(void) { + if (column > 0) { + int i; + for (i = 0; i < column; ++i) { + if (the_line->text[i].codepoint != ' ') break; + } + if (i == column) { + delete_at_cursor(); + while (column > 0 && (column % 4)) delete_at_cursor(); + return; + } + } + delete_at_cursor(); +} + +/** + * Delete whole word + */ +static void delete_word(void) { + if (!the_line->actual) return; + if (!column) return; + + while (column > 0 && the_line->text[column-1].codepoint == ' ') { + delete_at_cursor(); + } + + do { + if (column > 0) { + delete_at_cursor(); + } + } while (column > 0 && the_line->text[column-1].codepoint != ' '); +} + +/** + * Insert at cursor position + */ +static void insert_char(uint32_t c) { + char_t _c; + _c.codepoint = c; + _c.flags = 0; + _c.display_width = codepoint_width(c); + + the_line = line_insert(the_line, _c, column); + + column++; +} + +static char * paren_pairs = "()[]{}<>"; + +static int is_paren(int c) { + char * p = paren_pairs; + while (*p) { + if (c == *p) return 1; + p++; + } + return 0; +} + +static void find_matching_paren(int * out_col, int in_col) { + if (column - in_col > the_line->actual) { + return; /* Invalid cursor position */ + } + + int paren_match = 0; + int direction = 0; + int start = the_line->text[column-in_col].codepoint; + int flags = the_line->text[column-in_col].flags & 0x1F; + int count = 0; + + /* TODO what about unicode parens? */ + for (int i = 0; paren_pairs[i]; ++i) { + if (start == paren_pairs[i]) { + direction = (i % 2 == 0) ? 1 : -1; + paren_match = paren_pairs[(i % 2 == 0) ? (i+1) : (i-1)]; + break; + } + } + + if (!paren_match) return; + + /* Scan for match */ + int col = column - in_col; + + while (col > -1 && col < the_line->actual) { + /* Only match on same syntax */ + if ((the_line->text[col].flags & 0x1F) == flags) { + /* Count up on same direction */ + if (the_line->text[col].codepoint == start) count++; + /* Count down on opposite direction */ + if (the_line->text[col].codepoint == paren_match) { + count--; + /* When count == 0 we have a match */ + if (count == 0) goto _match_found; + } + } + col += direction; + } + +_match_found: + *out_col = col; +} + +static void redraw_matching_paren(int col) { + for (int j = 0; j < the_line->actual; ++j) { + if (j == col) { + the_line->text[j].flags |= FLAG_SELECT; + } else { + the_line->text[j].flags &= ~(FLAG_SELECT); + } + } +} + +static void highlight_matching_paren(void) { + int col = -1; + if (column < the_line->actual && is_paren(the_line->text[column].codepoint)) { + find_matching_paren(&col, 0); + } else if (column > 0 && is_paren(the_line->text[column-1].codepoint)) { + find_matching_paren(&col, 1); + } + redraw_matching_paren(col); +} + + +/** + * Move cursor left + */ +static void cursor_left(void) { + if (column > 0) column--; + rline_place_cursor(); +} + +/** + * Move cursor right + */ +static void cursor_right(void) { + if (column < the_line->actual) column++; + rline_place_cursor(); +} + +#if 0 +/** + * Move cursor one whole word left + */ +static void word_left(void) { + if (column == 0) return; + column--; + while (column && the_line->text[column].codepoint == ' ') { + column--; + } + while (column > 0) { + if (the_line->text[column-1].codepoint == ' ') break; + column--; + } + rline_place_cursor(); +} + +/** + * Move cursor one whole word right + */ +static void word_right(void) { + while (column < the_line->actual && the_line->text[column].codepoint == ' ') { + column++; + } + while (column < the_line->actual) { + column++; + if (column < the_line->actual && the_line->text[column].codepoint == ' ') break; + } + rline_place_cursor(); +} +#endif + +/** + * Move cursor to start of line + */ +static void cursor_home(void) { + column = 0; + rline_place_cursor(); +} + +/* + * Move cursor to end of line + */ +static void cursor_end(void) { + column = the_line->actual; + rline_place_cursor(); +} + +/** + * Temporary buffer for holding utf-8 data + */ +static char temp_buffer[1024]; + +/** + * Cycle to previous history entry + */ +static void history_previous(void) { + if (rline_scroll == 0) { + /* Convert to temporaary buffer */ + unsigned int off = 0; + memset(temp_buffer, 0, sizeof(temp_buffer)); + for (int j = 0; j < the_line->actual; j++) { + char_t c = the_line->text[j]; + off += to_eight(c.codepoint, &temp_buffer[off]); + } + } + + if (rline_scroll < rline_history_count) { + rline_scroll++; + + /* Copy in from history */ + the_line->actual = 0; + column = 0; + loading = 1; + unsigned char * buf = (unsigned char *)rline_history_prev(rline_scroll); + uint32_t istate = 0, c = 0; + for (unsigned int i = 0; i < strlen((char *)buf); ++i) { + if (!decode(&istate, &c, buf[i])) { + insert_char(c); + } + } + loading = 0; + } + /* Set cursor at end */ + column = the_line->actual; + offset = 0; + recalculate_tabs(the_line); + recalculate_syntax(the_line); + render_line(); + rline_place_cursor(); +} + +/** + * Cycle to next history entry + */ +static void history_next(void) { + if (rline_scroll >= 1) { + unsigned char * buf; + if (rline_scroll > 1) buf = (unsigned char *)rline_history_prev(rline_scroll-1); + else buf = (unsigned char *)temp_buffer; + rline_scroll--; + + /* Copy in from history */ + the_line->actual = 0; + column = 0; + loading = 1; + uint32_t istate = 0, c = 0; + for (unsigned int i = 0; i < strlen((char *)buf); ++i) { + if (!decode(&istate, &c, buf[i])) { + insert_char(c); + } + } + loading = 0; + } + /* Set cursor at end */ + column = the_line->actual; + offset = 0; + recalculate_tabs(the_line); + recalculate_syntax(the_line); + render_line(); + rline_place_cursor(); +} + +/** + * Handle escape sequences (arrow keys, etc.) + */ +static int handle_escape(int c) { + switch (c) { + case GETCHAR_CURSOR_UP: history_previous(); break; + case GETCHAR_CURSOR_DOWN: history_next(); break; + case GETCHAR_CURSOR_RIGHT: cursor_right(); break; + case GETCHAR_CURSOR_LEFT: cursor_left(); break; + case GETCHAR_HOME: cursor_home(); break; + case GETCHAR_END: cursor_end(); break; + case GETCHAR_DELETE: + if (column < the_line->actual) { + line_delete(the_line, column+1); + if (offset > 0) offset--; + } + break; + default: + return 0; + } + + return 1; +} + +static void set_buffered(void) { +} + +static void set_unbuffered(void) { +} + +static void get_initial_termios(void) { +} + +static int tabbed; + +static void dummy_redraw(rline_context_t * context) { + /* Do nothing */ +} + +/** + * Juggle our buffer with an rline context so we can + * call original rline functions such as a tab-completion callback + * or reverse search. + */ +static void call_rline_func(rline_callback_t func, rline_context_t * context) { + /* Unicode parser state */ + uint32_t istate = 0; + uint32_t c; + + /* Don't let rline draw things */ + context->quiet = 1; + + /* Allocate a temporary buffer */ + context->buffer = malloc(buf_size_max); + memset(context->buffer,0,buf_size_max); + + /* Convert current data to utf-8 */ + unsigned int off = 0; + for (int j = 0; j < the_line->actual; j++) { + if (j == column) { + /* Track cursor position */ + context->offset = off; + } + char_t c = the_line->text[j]; + off += to_eight(c.codepoint, &context->buffer[off]); + } + + /* If the cursor was at the end, the loop above didn't catch it */ + if (column == the_line->actual) context->offset = off; + + /* + * Did we just press tab before this? This is actually managed + * by the tab-completion function. + */ + context->tabbed = tabbed; + + /* Empty callbacks */ + rline_callbacks_t tmp = {0}; + /* + * Because some clients expect this to be set... + * (we don't need it, we'll redraw ourselves later) + */ + tmp.redraw_prompt = dummy_redraw; + + /* Setup context */ + context->callbacks = &tmp; + context->collected = off; + context->buffer[off] = '\0'; + context->requested = 1024; + + /* Reset colors (for tab completion candidates, etc. */ + printf("\033[0m"); + + /* Call the function */ + func(context); + + /* Now convert back */ + loading = 1; + int final_column = 0; + the_line->actual = 0; + column = 0; + istate = 0; + for (int i = 0; i < context->collected; ++i) { + if (i == context->offset) { + final_column = column; + } + if (!decode(&istate, &c, ((unsigned char *)context->buffer)[i])) { + insert_char(c); + } + } + + free(context->buffer); + + /* Position cursor */ + if (context->offset == context->collected) { + column = the_line->actual; + } else { + column = final_column; + } + tabbed = context->tabbed; + loading = 0; + + /* Recalculate + redraw */ + recalculate_tabs(the_line); + recalculate_syntax(the_line); + render_line(); + rline_place_cursor(); +} + +static int reverse_search(void) { + /* Store state */ + char * old_prompt = prompt; + int old_prompt_width = prompt_width; + int old_prompt_width_calc = prompt_width_calc; + line_t * old_line = the_line; + + char buffer[1024] = {0}; + unsigned int off = 0; + + the_line = NULL; + + prompt = "(r-search) "; + prompt_width = strlen(prompt); + prompt_width_calc = prompt_width; + + int cin, timeout = 0; + uint32_t c = 0, istate = 0; + + int start_at = 0; + int retval = 0; + + while (1) { + _next: (void)0; + + off = 0; + buffer[0] = '\0'; + for (int j = 0; j < old_line->actual; j++) { + buffer[off] = '\0'; + char_t c = old_line->text[j]; + off += to_eight(c.codepoint, &buffer[off]); + } + + if (the_line) free(the_line); + the_line = line_create(); + + int match_offset = 0; + + if (off) { + for (int i = start_at; i < rline_history_count; ++i) { + char * buf= rline_history_prev(i+1); + char * match = strstr(buf, buffer); + if (match) { + match_offset = i; + column = 0; + loading = 1; + uint32_t istate = 0, c = 0; + int invert_start = 0; + for (unsigned int i = 0; i < strlen((char *)buf); ++i) { + if (match == &buf[i]) invert_start = the_line->actual; + if (!decode(&istate, &c, buf[i])) { + insert_char(c); + } + } + loading = 0; + offset = 0; + recalculate_tabs(the_line); + recalculate_syntax(the_line); + for (int i = 0; i < old_line->actual; ++i) { + the_line->text[invert_start+i].flags |= FLAG_SELECT; + } + column = invert_start; + break; + } + } + } + + render_line(); + + if (the_line->actual == 0) { + offset = 0; + column = 0; + rline_place_cursor(); + set_fg_color(COLOR_ALT_FG); + printf("%s", buffer); + fflush(stdout); + } + + while ((cin = getch(timeout))) { + if (cin == -1) continue; + if (!decode(&istate, &c, cin)) { + switch (c) { + case '\033': + have_unget = '\033'; + goto _done; + case DELETE_KEY: + case BACKSPACE_KEY: + line_delete(old_line, old_line->actual); + goto _next; + case 13: + case ENTER_KEY: + retval = 1; + goto _done; + case 18: + start_at = match_offset + 1; + goto _next; + default: { + char_t _c; + _c.codepoint = c; + _c.flags = 0; + _c.display_width = codepoint_width(c); + old_line = line_insert(old_line, _c, old_line->actual); + goto _next; + } + } + } + } + } + +_done: + free(old_line); + prompt = old_prompt; + prompt_width = old_prompt_width; + prompt_width_calc = old_prompt_width_calc; + offset = 0; + render_line(); + rline_place_cursor(); + return retval; +} + +/** + * Perform actual interactive line editing. + * + * This is mostly a reimplementation of bim's + * INSERT mode, but with some cleanups and fixes + * to work on a single line and to add some new + * key bindings we don't have in bim. + */ +static int read_line(void) { + int cin; + uint32_t c = 0; + int timeout = 0; + //int this_buf[20]; + uint32_t istate = 0; + + /* Let's disable this under Windows... */ + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + fprintf(stdout, "◄\033[0m"); /* TODO: This could be retrieved from an envvar */ + for (int i = 0; i < rline_terminal_width - 1; ++i) { + fprintf(stdout, " "); + } + + if (rline_preload) { + char * c = rline_preload; + while (*c) { + insert_char(*c); + c++; + } + free(rline_preload); + rline_preload = NULL; + } + + render_line(); + rline_place_cursor(); + + while ((cin = getch(timeout))) { + if (cin == -1) continue; + get_size(); + if (cin < -1) { + handle_escape(cin); + continue; + } + if (!decode(&istate, &c, cin)) { + if (timeout == 0) { + if (c != '\t') tabbed = 0; + #if 0 + if (_INTR && c == _INTR) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("^%c", (int)('@' + c)); + printf("\033[0m"); + loading = 1; + the_line->actual = 0; + column = 0; + insert_char('\n'); + raise(SIGINT); + return 1; + } + if (_EOF && c == _EOF) { + if (column == 0 && the_line->actual == 0) { + for (char *_c = rline_exit_string; *_c; ++_c) { + insert_char(*_c); + } + redraw_matching_paren(-1); + render_line(); + rline_place_cursor(); + if (!*rline_exit_string) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("^D\033[0m"); + } + return 1; + } else { /* Otherwise act like delete */ + if (column < the_line->actual) { + line_delete(the_line, column+1); + if (offset > 0) offset--; + } + continue; + } + } + #endif + switch (c) { + case '\033': + if (timeout == 0) { + //this_buf[timeout] = c; + timeout++; + } + break; + case DELETE_KEY: + case BACKSPACE_KEY: + smart_backspace(); + break; + case 13: + case ENTER_KEY: + /* Finished */ + loading = 1; + column = the_line->actual; + redraw_matching_paren(-1); + render_line(); + insert_char('\n'); + return 1; + #if 0 + case 22: /* ^V */ + /* Don't bother with unicode, just take the next byte */ + rline_place_cursor(); + printf("^\b"); + insert_char(getc(stdin)); + break; + #endif + case 23: /* ^W */ + delete_word(); + break; + case 18: /* ^R - Begin reverse search */ + if (reverse_search()) { + loading = 1; + column = the_line->actual; + recalculate_syntax(the_line); + render_line(); + insert_char('\n'); + return 1; + } + break; + case 12: /* ^L - Repaint the whole screen */ + printf("\033[2J\033[H"); + render_line(); + rline_place_cursor(); + break; + case 11: /* ^K - Clear to end */ + the_line->actual = column; + break; + case 21: /* ^U - Kill to beginning */ + while (column) { + delete_at_cursor(); + } + break; + case '\t': + if ((syntax && syntax->tabIndents) && (column == 0 || the_line->text[column-1].codepoint == ' ')) { + /* Insert tab character */ + insert_char(' '); + insert_char(' '); + insert_char(' '); + insert_char(' '); + } else if (tab_complete_func) { + /* Tab complete */ + rline_context_t context = {0}; + call_rline_func(tab_complete_func, &context); + continue; + } + break; + default: + insert_char(c); + break; + } + } else { + #if 0 + if (handle_escape(this_buf,&timeout,c)) { + render_line(); + rline_place_cursor(); + continue; + } + #endif + } + highlight_matching_paren(); + render_line(); + rline_place_cursor(); + } else if (istate == UTF8_REJECT) { + istate = 0; + } + } + return 0; +} + +/** + * Read a line of text with interactive editing. + */ +int rline(char * buffer, int buf_size) { + get_initial_termios(); + set_unbuffered(); + get_size(); + + column = 0; + offset = 0; + buf_size_max = buf_size; + + rline_exp_load_colorscheme_default(); + + the_line = line_create(); + loading = 0; + read_line(); + printf("\r\033[?25h\033[0m\n"); + + unsigned int off = 0; + for (int j = 0; j < the_line->actual; j++) { + char_t c = the_line->text[j]; + off += to_eight(c.codepoint, &buffer[off]); + } + + free(the_line); + + set_buffered(); + + return strlen(buffer); +} + +void rline_insert(rline_context_t * context, const char * what) { + size_t insertion_length = strlen(what); + + if (context->collected + (int)insertion_length > context->requested) { + insertion_length = context->requested - context->collected; + } + + /* Move */ + memmove(&context->buffer[context->offset + insertion_length], &context->buffer[context->offset], context->collected - context->offset); + memcpy(&context->buffer[context->offset], what, insertion_length); + context->collected += insertion_length; + context->offset += insertion_length; +} diff --git a/common/kuroko/scanner.c b/common/kuroko/scanner.c new file mode 120000 index 00000000..a09aa4d3 --- /dev/null +++ b/common/kuroko/scanner.c @@ -0,0 +1 @@ +../../kuroko/src/scanner.c \ No newline at end of file diff --git a/common/kuroko/stubs.c b/common/kuroko/stubs.c new file mode 100644 index 00000000..8410e853 --- /dev/null +++ b/common/kuroko/stubs.c @@ -0,0 +1,568 @@ +/** + * @file stubs.c + * @brief Implementations of libc functions needed under EFI and BIOS. + */ +#include +#include +#include +#include +#include + +#include +#include +void print_(char *str) { + FOR_TERM(flanterm_write(TERM, str, strlen(str))); +} + +void abort(void) {print_("ABORT\n"); while(1); } +void exit(int status) {print_("EXIT\n"); while(1); } + +char * strchr(const char * s, int c) { + while (*s && *s != c) s++; + return *s == c ? (char*)s : NULL; +} + +char * strrchr(const char * s, int c) { + size_t l = strlen(s); + for (size_t i = 0; i < l; ++i) { + if (s[l-i-1] == c) { + return (char*)&s[l-i-1]; + } + } + return NULL; +} + +void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) { + if (!nmemb) return; + if (!size) return; + char * tmp = malloc(size); + for (size_t i = 0; i < nmemb-1; ++i) { + for (size_t j = 0; j < nmemb-1; ++j) { + void * left = (char *)base + size * j; + void * right = (char *)base + size * (j + 1); + if (compar(left,right) > 0) { + memcpy(tmp, right, size); + memcpy(right, left, size); + memcpy(left, tmp, size); + } + } + } + free(tmp); +} + +static int isspace(int c) { + return c == ' '; +} + +static int is_valid(int base, char c) { + if (c < '0') return 0; + if (base <= 10) { + return c < ('0' + base); + } + + if (c >= 'a' && c < 'a' + (base - 10)) return 1; + if (c >= 'A' && c < 'A' + (base - 10)) return 1; + if (c >= '0' && c <= '9') return 1; + return 0; +} + +static int convert_digit(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'a' && c <= 'z') { + return c - 'a' + 0xa; + } + if (c >= 'A' && c <= 'Z') { + return c - 'A' + 0xa; + } + return 0; +} + +#define LONG_MAX 2147483647 +#define LONG_LONG_MAX 0x7FFFffffFFFFffffULL + +#define strtox(max, type) \ + if (base < 0 || base == 1 || base > 36) { \ + return max; \ + } \ + while (*nptr && isspace(*nptr)) nptr++; \ + int sign = 1; \ + if (*nptr == '-') { \ + sign = -1; \ + nptr++; \ + } else if (*nptr == '+') { \ + nptr++; \ + } \ + if (base == 16) { \ + if (*nptr == '0') { \ + nptr++; \ + if (*nptr == 'x') { \ + nptr++; \ + } \ + } \ + } \ + if (base == 0) { \ + if (*nptr == '0') { \ + base = 8; \ + nptr++; \ + if (*nptr == 'x') { \ + base = 16; \ + nptr++; \ + } \ + } else { \ + base = 10; \ + } \ + } \ + type val = 0; \ + while (is_valid(base, *nptr)) { \ + val *= base; \ + val += convert_digit(*nptr); \ + nptr++; \ + } \ + if (endptr) { \ + *endptr = (char *)nptr; \ + } \ + if (sign == -1) { \ + return -val; \ + } else { \ + return val; \ + } + +long int strtol(const char *nptr, char **endptr, int base) { + strtox(LONG_MAX, unsigned long int); +} + +long long int strtoll(const char *nptr, char **endptr, int base) { + strtox(LONG_LONG_MAX, unsigned long long int); +} + +#define ULONG_MAX ((unsigned long)(-1)) +unsigned long int strtoul(const char *nptr, char **endptr, int base) { + strtox(ULONG_MAX, unsigned long int); +} + +int atoi(const char * c) { + return strtol(c,NULL,10); +} + +FILE * stdout = NULL; +FILE * stderr = NULL; +FILE * stdin = NULL; + +int fputc(int c, FILE * stream) { + if (stream == stdout) { + char tmp[2] = {c,0}; + print_(tmp); + } + return c; +} + +int fputs(const char * s, FILE * stream) { + while (*s) { + fputc(*s,stream); + s++; + } + return 0; +} + +int puts(const char * s) { + fputs(s,stdout); + fputc('\n',stdout); + return 0; +} + +#define OUT(c) do { callback(userData, (c)); written++; } while (0) +static size_t print_dec(unsigned long long value, unsigned int width, int (*callback)(void*,char), void * userData, int fill_zero, int align_right, int precision) { + size_t written = 0; + unsigned long long n_width = 1; + unsigned long long i = 9; + if (precision == -1) precision = 1; + + if (value == 0) { + n_width = 0; + } else { + unsigned long long val = value; + while (val >= 10UL) { + val /= 10UL; + n_width++; + } + } + + if (n_width < (unsigned long long)precision) n_width = precision; + + int printed = 0; + if (align_right) { + while (n_width + printed < width) { + OUT(fill_zero ? '0' : ' '); + printed += 1; + } + + i = n_width; + char tmp[100]; + while (i > 0) { + unsigned long long n = value / 10; + long long r = value % 10; + tmp[i - 1] = r + '0'; + i--; + value = n; + } + while (i < n_width) { + OUT(tmp[i]); + i++; + } + } else { + i = n_width; + char tmp[100]; + while (i > 0) { + unsigned long long n = value / 10; + long long r = value % 10; + tmp[i - 1] = r + '0'; + i--; + value = n; + printed++; + } + while (i < n_width) { + OUT(tmp[i]); + i++; + } + while (printed < (long long)width) { + OUT(fill_zero ? '0' : ' '); + printed += 1; + } + } + + return written; +} + +/* + * Hexadecimal to string + */ +static size_t print_hex(unsigned long long value, unsigned int width, int (*callback)(void*,char), void* userData, int fill_zero, int alt, int caps, int align) { + size_t written = 0; + int i = width; + + unsigned long long n_width = 1; + unsigned long long j = 0x0F; + while (value > j && j < UINT64_MAX) { + n_width += 1; + j *= 0x10; + j += 0x0F; + } + + if (!fill_zero && align == 1) { + while (i > (long long)n_width + 2*!!alt) { + OUT(' '); + i--; + } + } + + if (alt) { + OUT('0'); + OUT(caps ? 'X' : 'x'); + } + + if (fill_zero && align == 1) { + while (i > (long long)n_width + 2*!!alt) { + OUT('0'); + i--; + } + } + + i = (long long)n_width; + while (i-- > 0) { + char c = (caps ? "0123456789ABCDEF" : "0123456789abcdef")[(value>>(i*4))&0xF]; + OUT(c); + } + + if (align == 0) { + i = width; + while (i > (long long)n_width + 2*!!alt) { + OUT(' '); + i--; + } + } + + return written; +} + +/* + * vasprintf() + */ +size_t xvasprintf(int (*callback)(void *, char), void * userData, const char * fmt, va_list args) { + char * s; + int precision = -1; + size_t written = 0; + for (const char *f = fmt; *f; f++) { + if (*f != '%') { + OUT(*f); + continue; + } + ++f; + unsigned int arg_width = 0; + int align = 1; /* right */ + int fill_zero = 0; + int big = 0; + int alt = 0; + int always_sign = 0; + while (1) { + if (*f == '-') { + align = 0; + ++f; + } else if (*f == '#') { + alt = 1; + ++f; + } else if (*f == '*') { + arg_width = (char)va_arg(args, int); + ++f; + } else if (*f == '0') { + fill_zero = 1; + ++f; + } else if (*f == '+') { + always_sign = 1; + ++f; + } else if (*f == ' ') { + always_sign = 2; + ++f; + } else { + break; + } + } + while (*f >= '0' && *f <= '9') { + arg_width *= 10; + arg_width += *f - '0'; + ++f; + } + if (*f == '.') { + ++f; + precision = 0; + if (*f == '*') { + precision = (int)va_arg(args, int); + ++f; + } else { + while (*f >= '0' && *f <= '9') { + precision *= 10; + precision += *f - '0'; + ++f; + } + } + } + if (*f == 'l') { + big = 1; + ++f; + if (*f == 'l') { + big = 2; + ++f; + } + } + if (*f == 'j') { + big = (sizeof(uintmax_t) == sizeof(unsigned long long) ? 2 : + sizeof(uintmax_t) == sizeof(unsigned long) ? 1 : 0); + ++f; + } + if (*f == 'z') { + big = (sizeof(size_t) == sizeof(unsigned long long) ? 2 : + sizeof(size_t) == sizeof(unsigned long) ? 1 : 0); + ++f; + } + if (*f == 't') { + big = (sizeof(ptrdiff_t) == sizeof(unsigned long long) ? 2 : + sizeof(ptrdiff_t) == sizeof(unsigned long) ? 1 : 0); + ++f; + } + /* fmt[i] == '%' */ + switch (*f) { + case 's': /* String pointer -> String */ + { + size_t count = 0; + if (big) { + return written; + } else { + s = (char *)va_arg(args, char *); + if (s == NULL) { + s = "(null)"; + } + if (precision >= 0) { + while (*s && precision > 0) { + OUT(*s++); + count++; + precision--; + if (arg_width && count == arg_width) break; + } + } else { + while (*s) { + OUT(*s++); + count++; + if (arg_width && count == arg_width) break; + } + } + } + while (count < arg_width) { + OUT(' '); + count++; + } + } + break; + case 'c': /* Single character */ + OUT((char)va_arg(args,int)); + break; + case 'p': + alt = 1; + if (sizeof(void*) == sizeof(long long)) big = 2; + /* fallthrough */ + case 'X': + case 'x': /* Hexadecimal number */ + { + unsigned long long val; + if (big == 2) { + val = (unsigned long long)va_arg(args, unsigned long long); + } else if (big == 1) { + val = (unsigned long)va_arg(args, unsigned long); + } else { + val = (unsigned int)va_arg(args, unsigned int); + } + written += print_hex(val, arg_width, callback, userData, fill_zero, alt, !(*f & 32), align); + } + break; + case 'i': + case 'd': /* Decimal number */ + { + long long val; + if (big == 2) { + val = (long long)va_arg(args, long long); + } else if (big == 1) { + val = (long)va_arg(args, long); + } else { + val = (int)va_arg(args, int); + } + if (val < 0) { + OUT('-'); + val = -val; + } else if (always_sign) { + OUT(always_sign == 2 ? ' ' : '+'); + } + written += print_dec(val, arg_width, callback, userData, fill_zero, align, precision); + } + break; + case 'u': /* Unsigned ecimal number */ + { + unsigned long long val; + if (big == 2) { + val = (unsigned long long)va_arg(args, unsigned long long); + } else if (big == 1) { + val = (unsigned long)va_arg(args, unsigned long); + } else { + val = (unsigned int)va_arg(args, unsigned int); + } + written += print_dec(val, arg_width, callback, userData, fill_zero, align, precision); + } + break; + case '%': /* Escape */ + OUT('%'); + break; + default: /* Nothing at all, just dump it */ + OUT(*f); + break; + } + } + return written; +} + +struct CBData { + char * str; + size_t size; + size_t written; +}; + +static int cb_sprintf(void * user, char c) { + struct CBData * data = user; + if (data->size > data->written + 1) { + data->str[data->written] = c; + data->written++; + if (data->written < data->size) { + data->str[data->written] = '\0'; + } + } + return 0; +} + +int vsnprintf(char *str, size_t size, const char *format, va_list ap) { + struct CBData data = {str,size,0}; + int out = xvasprintf(cb_sprintf, &data, format, ap); + cb_sprintf(&data, '\0'); + return out; +} + +int snprintf(char * str, size_t size, const char * format, ...) { + struct CBData data = {str,size,0}; + va_list args; + va_start(args, format); + int out = xvasprintf(cb_sprintf, &data, format, args); + va_end(args); + cb_sprintf(&data, '\0'); + return out; +} + +static int cb_fprintf(void * user, char c) { + fputc(c,(FILE*)user); + return 0; +} + +int fprintf(FILE *stream, const char * fmt, ...) { + va_list args; + va_start(args, fmt); + int out = xvasprintf(cb_fprintf, stream, fmt, args); + va_end(args); + return out; +} + +char * strdup(const char * src) { + char * out = malloc(strlen(src)+1); + char * c = out; + while (*src) { + *c = *src; + c++; src++; + } + *c = 0; + return out; +} + +char *strstr(const char *haystack, const char *needle) { + size_t s = strlen(needle); + const char * end = haystack + strlen(haystack); + + while (haystack + s <= end) { + if (!memcmp(haystack,needle,s)) return (char*)haystack; + haystack++; + } + + return NULL; +} + +char * strcat(char *dest, const char *src) { + char * end = dest; + while (*end != '\0') { + ++end; + } + while (*src) { + *end = *src; + end++; + src++; + } + *end = '\0'; + return dest; +} + +void ___chkstk_ms(void) { } + +size_t fwrite(void *ptr, size_t size, size_t nmemb, FILE * stream) { + for (size_t i = 0; i < size * nmemb; ++i) { + fputc(((char*)ptr)[i], stream); + } + return size * nmemb; +} + +void fflush(FILE * f) { + /* do nothing */ +} + diff --git a/common/kuroko/sys.c b/common/kuroko/sys.c new file mode 120000 index 00000000..c4e25633 --- /dev/null +++ b/common/kuroko/sys.c @@ -0,0 +1 @@ +../../kuroko/src/sys.c \ No newline at end of file diff --git a/common/kuroko/table.c b/common/kuroko/table.c new file mode 120000 index 00000000..10b9da92 --- /dev/null +++ b/common/kuroko/table.c @@ -0,0 +1 @@ +../../kuroko/src/table.c \ No newline at end of file diff --git a/common/kuroko/threads.c b/common/kuroko/threads.c new file mode 120000 index 00000000..f31d1cd7 --- /dev/null +++ b/common/kuroko/threads.c @@ -0,0 +1 @@ +../../kuroko/src/threads.c \ No newline at end of file diff --git a/common/kuroko/value.c b/common/kuroko/value.c new file mode 120000 index 00000000..af481482 --- /dev/null +++ b/common/kuroko/value.c @@ -0,0 +1 @@ +../../kuroko/src/value.c \ No newline at end of file diff --git a/common/kuroko/vm.c b/common/kuroko/vm.c new file mode 120000 index 00000000..81994194 --- /dev/null +++ b/common/kuroko/vm.c @@ -0,0 +1 @@ +../../kuroko/src/vm.c \ No newline at end of file diff --git a/common/kuroko/wcwidth._h b/common/kuroko/wcwidth._h new file mode 120000 index 00000000..927845c3 --- /dev/null +++ b/common/kuroko/wcwidth._h @@ -0,0 +1 @@ +../../kuroko/src/wcwidth._h \ No newline at end of file diff --git a/common/lib/readline.c b/common/lib/readline.c index 1a03d9dc..f3ebc5d2 100644 --- a/common/lib/readline.c +++ b/common/lib/readline.c @@ -91,6 +91,8 @@ int getchar_internal(uint8_t scancode, uint8_t ascii, uint32_t shift_state) { } } + if (ascii == '\t') return ascii; + // Guard against non-printable values if (ascii < 0x20 || ascii > 0x7e) { return -1; @@ -310,7 +312,7 @@ again: } if (kd.Key.ScanCode == 0x08) { - return '\b'; + return GETCHAR_DELETE; } if (kd.Key.ScanCode == SCAN_ESC) { diff --git a/common/menu.c b/common/menu.c index 8792ec8a..eaba5ab2 100644 --- a/common/menu.c +++ b/common/menu.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -815,6 +816,10 @@ refresh: } set_cursor_pos_helper(terms[0]->cols - 13, 3); print("\e[32mC\e[0m Console"); + + set_cursor_pos_helper(terms[0]->cols - 24, 3); + print("\e[32mK\e[0m Kuroko"); + set_cursor_pos_helper(x, y); } @@ -928,6 +933,12 @@ timeout_aborted: console(); goto refresh; } + case 'k': + case 'K': { + reset_term(); + limine_krk_enter_repl(); + goto refresh; + } } } } diff --git a/kuroko-inc/assert.h b/kuroko-inc/assert.h new file mode 100644 index 00000000..f1547de1 --- /dev/null +++ b/kuroko-inc/assert.h @@ -0,0 +1,3 @@ +#pragma once + +#define assert(...) diff --git a/kuroko-inc/dirent.h b/kuroko-inc/dirent.h new file mode 100644 index 00000000..870cb7cd --- /dev/null +++ b/kuroko-inc/dirent.h @@ -0,0 +1,13 @@ +#pragma once +#include + +typedef struct dirent { + int d_ino; + char d_name[256]; +} dirent; + +typedef void * DIR; + +extern DIR * opendir(const char * name); +extern int closedir(DIR * dirp); +extern struct dirent * readdir(DIR *dirp); diff --git a/kuroko-inc/errno.h b/kuroko-inc/errno.h new file mode 100644 index 00000000..e69de29b diff --git a/kuroko-inc/inttypes.h b/kuroko-inc/inttypes.h new file mode 100644 index 00000000..4c940110 --- /dev/null +++ b/kuroko-inc/inttypes.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +/** + * EFI is 32-bit long, 64-bit long long + */ + +#define PRIi8 "i" +#define PRIi16 "i" +#define PRIi32 "i" +#define PRIi64 "lli" + +#define PRIx8 "x" +#define PRIx16 "x" +#define PRIx32 "x" +#define PRIx64 "llx" + +#define PRIu8 "u" +#define PRIu16 "u" +#define PRIu32 "u" +#define PRIu64 "llu" + +#define PRId8 "d" +#define PRId16 "d" +#define PRId32 "d" +#define PRId64 "lld" diff --git a/kuroko-inc/kuroko b/kuroko-inc/kuroko new file mode 120000 index 00000000..27ee5564 --- /dev/null +++ b/kuroko-inc/kuroko @@ -0,0 +1 @@ +../kuroko/src/kuroko \ No newline at end of file diff --git a/kuroko-inc/rline.h b/kuroko-inc/rline.h new file mode 100644 index 00000000..3330a374 --- /dev/null +++ b/kuroko-inc/rline.h @@ -0,0 +1,63 @@ +#pragma once + +struct rline_callback; + +typedef struct { + char * buffer; + struct rline_callback * callbacks; + int collected; + int requested; + int newline; + int cancel; + int offset; + int tabbed; + int quiet; +} rline_context_t; + +typedef void (*rline_callback_t)(rline_context_t * context); + +typedef struct rline_callback { + rline_callback_t tab_complete; + rline_callback_t redraw_prompt; + rline_callback_t special_key; + rline_callback_t key_up; + rline_callback_t key_down; + rline_callback_t key_left; + rline_callback_t key_right; + rline_callback_t rev_search; +} rline_callbacks_t; + +typedef enum { + /* Base colors */ + RLINE_STYLE_MAIN, + RLINE_STYLE_ALT, + /* Syntax flags */ + RLINE_STYLE_KEYWORD, + RLINE_STYLE_STRING, + RLINE_STYLE_COMMENT, + RLINE_STYLE_TYPE, + RLINE_STYLE_PRAGMA, + RLINE_STYLE_NUMERAL, +} rline_style_t; + +extern int rline(char * buffer, int buf_size); +extern int rline_exp_set_prompts(char * left, char * right, int left_width, int right_width); +extern int rline_exp_set_shell_commands(char ** cmds, int len); +extern int rline_exp_set_tab_complete_func(rline_callback_t func); +extern int rline_exp_set_syntax(char * name); +extern void rline_history_insert(char * str); +extern void rline_history_append_line(char * str); +extern char * rline_history_get(int item); +extern char * rline_history_prev(int item); +extern void rline_place_cursor(void); +extern void rline_set_colors(rline_style_t style); +extern void rline_insert(rline_context_t * context, const char * what); +extern int rline_terminal_width; + +#define RLINE_HISTORY_ENTRIES 128 +extern char * rline_history[RLINE_HISTORY_ENTRIES]; +extern int rline_history_count; +extern int rline_history_offset; +extern int rline_scroll; +extern char * rline_exit_string; +extern char * rline_preload; diff --git a/kuroko-inc/stdio.h b/kuroko-inc/stdio.h new file mode 100644 index 00000000..fd84700b --- /dev/null +++ b/kuroko-inc/stdio.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +typedef void * FILE; + +extern FILE * stdout; +extern FILE * stderr; +extern FILE * stdin; + +extern int fprintf(FILE *stream, const char * fmt, ...); +extern int snprintf(char * str, size_t size, const char * format, ...); +extern int fputc(int c, FILE * stream); +extern int vsnprintf(char *str, size_t size, const char *format, va_list ap); +extern int puts(const char * s); + +#define printf(...) fprintf(stdout, __VA_ARGS__) + +extern int fgetc(FILE * stream); +extern FILE * fopen(const char * pathname, const char * mode); +extern int fclose(FILE * stream); +extern size_t fread(void * ptr, size_t size, size_t nmemb, FILE * stream); +extern size_t fwrite(void * ptr, size_t size, size_t nmemb, FILE * stream); +extern int fseek(FILE * stream, long offset, int whence); +extern long ftell(FILE * stream); +extern void fflush(FILE * stream); +#define SEEK_SET 1 +#define SEEK_END 2 +struct stat { + int pad; +}; +extern int stat(const char*,struct stat*); +extern int errno; +extern char * strerror(int errnum); +extern int feof(FILE * stream); +#define ferror(o) (0) diff --git a/kuroko-inc/stdlib.h b/kuroko-inc/stdlib.h new file mode 100644 index 00000000..3ec2582f --- /dev/null +++ b/kuroko-inc/stdlib.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include + +extern void * realloc(void * ptr, size_t size); +extern void free(void *ptr); +extern void * malloc(size_t size); +extern void * calloc(size_t nmemb, size_t size); +extern double strtod(const char *nptr, char **endptr); +extern long int strtol(const char *nptr, char **endptr, int base); +extern long long int strtoll(const char *nptr, char **endptr, int base); +extern unsigned long int strtoul(const char *nptr, char **endptr, int base); + +extern void qsort(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *)); + +extern void abort(void); +extern void exit(int status); +extern int atoi(const char * c); diff --git a/kuroko-inc/string.h b/kuroko-inc/string.h new file mode 100644 index 00000000..63a19ef2 --- /dev/null +++ b/kuroko-inc/string.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#ifndef __SIZE_TYPE__ +# if defined(__x86_64__) || defined(__aarch64__) +typedef unsigned long long size_t; +# else +typedef unsigned long size_t; +# endif +#else +typedef __SIZE_TYPE__ size_t; +#endif + +#ifndef ssize_t +# if defined(__x86_64__) || defined(__aarch64__) +typedef long long ssize_t; +# else +typedef long ssize_t; +# endif +#endif + +extern void * memcpy(void * restrict dest, const void * restrict src, size_t n); +extern void * memset(void * dest, int c, size_t n); +extern int strcmp(const char * l, const char * r); +extern size_t strlen(const char *s); +extern int memcmp(const void * vl, const void * vr, size_t n); +extern char * strdup(const char * src); +extern void * memmove(void * dest, const void * src, size_t n); +extern char * strcat(char *dest, const char *src); +extern char *strstr(const char *haystack, const char *needle); +extern char * strchr(const char * s, int c); +extern char * strrchr(const char * s, int c); +extern char * strcpy(char * restrict dest, const char * restrict src); diff --git a/kuroko-inc/sys/stat.h b/kuroko-inc/sys/stat.h new file mode 100644 index 00000000..e69de29b diff --git a/kuroko-inc/sys/time.h b/kuroko-inc/sys/time.h new file mode 100644 index 00000000..e69de29b diff --git a/kuroko-inc/sys/types.h b/kuroko-inc/sys/types.h new file mode 100644 index 00000000..e69de29b diff --git a/kuroko-inc/time.h b/kuroko-inc/time.h new file mode 100644 index 00000000..619a0850 --- /dev/null +++ b/kuroko-inc/time.h @@ -0,0 +1,9 @@ +#pragma once + +struct timespec { + int tv_sec; + int tv_nsec; +}; + +#define CLOCK_MONOTONIC 0 +#define clock_gettime(a,b) diff --git a/kuroko-inc/unistd.h b/kuroko-inc/unistd.h new file mode 100644 index 00000000..e69de29b