From 90fb68940b7b2c56b21905a81e57ebed5226666d Mon Sep 17 00:00:00 2001 From: Edgar Su Date: Sun, 10 Oct 2021 14:11:45 +0800 Subject: [PATCH] Initial commit --- .clangd | 2 + .gitignore | 22 + Linker.ld | 199 +++ Linker.ld.old | 62 + Makefile | 33 + Makefile.flags | 28 + Makefile.subdir | 19 + execformat/pe/format.h | 256 ++++ execformat/pe/read_headers.c | 106 ++ execformat/pe/reloc.c | 108 ++ execformat/pe/reloc.h | 30 + execformat/pe/struct.c | 50 + execformat/pe/struct.h | 29 + execformat/pe/test_headers.c | 118 ++ extlib/Makefile.old | 20 + extlib/liballoc/HOWTO | 26 + extlib/liballoc/LICENSE | 10 + extlib/liballoc/README.md | 68 + extlib/liballoc/liballoc_1_1.c | 832 +++++++++++ extlib/liballoc/liballoc_1_1.h | 87 ++ extlib/libvterm/LICENSE | 23 + extlib/libvterm/encoding.c | 223 +++ extlib/libvterm/encoding/DECdrawing.inc | 36 + extlib/libvterm/encoding/uk.inc | 6 + extlib/libvterm/fullwidth.inc | 104 ++ extlib/libvterm/keyboard.c | 228 +++ extlib/libvterm/mouse.c | 89 ++ extlib/libvterm/parser.c | 329 +++++ extlib/libvterm/pen.c | 586 ++++++++ extlib/libvterm/rect.h | 56 + extlib/libvterm/screen.c | 877 +++++++++++ extlib/libvterm/state.c | 1801 +++++++++++++++++++++++ extlib/libvterm/unicode.c | 229 +++ extlib/libvterm/utf8.h | 39 + extlib/libvterm/vterm.c | 360 +++++ extlib/libvterm/vterm.h | 533 +++++++ extlib/libvterm/vterm_internal.h | 252 ++++ extlib/libvterm/vterm_keycodes.h | 61 + graphics/graphics.c | 284 ++++ graphics/graphics.h | 86 ++ graphics/test_unifont.c | 18 + graphics/unifont.c | 43 + graphics/unifont.h | 29 + graphics/xcursor/xcursor.h | 71 + interrupt/handler.asm.S | 194 +++ interrupt/handler.c | 52 + interrupt/handlers.h | 38 + interrupt/init.c | 118 ++ interrupt/interrupt.h | 74 + interrupt/interrupt_testcode.S | 57 + interrupt/load_gdt.S | 45 + interrupt/map_handler.S | 25 + interrupt/syscall.S | 18 + interrupt/syscall.h | 8 + interrupt/testcode.h | 6 + kernel/kmain.c | 51 + kernel/kmain.h | 28 + kernel/kmain.init.S | 25 + libc/README | 11 + libc/abs.c | 9 + libc/include/assert.h | 50 + libc/include/ctype.h | 110 ++ libc/include/errno.h | 202 +++ libc/include/float.h | 153 ++ libc/include/inttypes.h | 368 +++++ libc/include/iso646.h | 31 + libc/include/limits.h | 50 + libc/include/locale.h | 114 ++ libc/include/pdclib/_PDCLIB_config.h | 904 ++++++++++++ libc/include/pdclib/_PDCLIB_defguard.h | 29 + libc/include/pdclib/_PDCLIB_glue.h | 93 ++ libc/include/pdclib/_PDCLIB_internal.h | 776 ++++++++++ libc/include/pdclib/_PDCLIB_lib_ext1.h | 32 + libc/include/pdclib/_PDCLIB_print.h | 71 + libc/include/pdclib/_PDCLIB_tzcode.h | 153 ++ libc/include/signal.h | 91 ++ libc/include/stdalign.h | 26 + libc/include/stdarg.h | 34 + libc/include/stdbool.h | 24 + libc/include/stddef.h | 55 + libc/include/stdint.h | 236 +++ libc/include/stdio.h | 935 ++++++++++++ libc/include/stdlib.h | 379 +++++ libc/include/stdnoreturn.h | 26 + libc/include/string.h | 394 +++++ libc/include/threads.h | 260 ++++ libc/include/time.h | 191 +++ libc/include/wctype.h | 152 ++ libc/memcmp.c | 46 + libc/strncat.c | 64 + libc/strncmp.c | 55 + libc/strncpy.c | 59 + main.c | 67 + main.h | 48 + memory/Makefile | 19 + memory/liballoc_impl.c | 41 + memory/memory.c | 40 + memory/memory.h | 100 ++ memory/memory.hpp | 60 + memory/memory_cpp.cpp | 13 + memory/paging.c | 22 + memory/paging_init.c | 212 +++ memory/paging_internal.h | 138 ++ memory/paging_map.c | 189 +++ memory/paging_modeswitch.S | 86 ++ memory/paging_physical.c | 136 ++ memory/test_fillbits.c | 38 + memory/test_take_bitfield.c | 16 + run.cmd | 4 + runtime/calling_convention.S | 53 + runtime/calling_convention.h | 12 + runtime/memcpy.c | 342 +++++ runtime/memcpy.h | 64 + runtime/memset_memmove.S | 60 + runtime/panic_assert.asm.S | 13 + runtime/panic_assert.h | 49 + runtime/printf.c | 901 ++++++++++++ runtime/printf.h | 117 ++ runtime/stdio.c | 98 ++ runtime/stdio.h | 40 + runtime/string.c | 10 + runtime/test_memmove.c | 38 + runtime/test_utf8.c | 30 + runtime/unicode.c | 84 ++ runtime/unicode.h | 51 + util/queue.c | 55 + util/queue.h | 42 + vterm/vterm.c | 2 + vterm/vterm.cpp | 13 + vterm/vterm.hpp | 28 + 130 files changed, 18721 insertions(+) create mode 100644 .clangd create mode 100644 .gitignore create mode 100644 Linker.ld create mode 100644 Linker.ld.old create mode 100644 Makefile create mode 100644 Makefile.flags create mode 100644 Makefile.subdir create mode 100644 execformat/pe/format.h create mode 100644 execformat/pe/read_headers.c create mode 100644 execformat/pe/reloc.c create mode 100644 execformat/pe/reloc.h create mode 100644 execformat/pe/struct.c create mode 100644 execformat/pe/struct.h create mode 100644 execformat/pe/test_headers.c create mode 100644 extlib/Makefile.old create mode 100644 extlib/liballoc/HOWTO create mode 100644 extlib/liballoc/LICENSE create mode 100644 extlib/liballoc/README.md create mode 100644 extlib/liballoc/liballoc_1_1.c create mode 100644 extlib/liballoc/liballoc_1_1.h create mode 100644 extlib/libvterm/LICENSE create mode 100644 extlib/libvterm/encoding.c create mode 100644 extlib/libvterm/encoding/DECdrawing.inc create mode 100644 extlib/libvterm/encoding/uk.inc create mode 100644 extlib/libvterm/fullwidth.inc create mode 100644 extlib/libvterm/keyboard.c create mode 100644 extlib/libvterm/mouse.c create mode 100644 extlib/libvterm/parser.c create mode 100644 extlib/libvterm/pen.c create mode 100644 extlib/libvterm/rect.h create mode 100644 extlib/libvterm/screen.c create mode 100644 extlib/libvterm/state.c create mode 100644 extlib/libvterm/unicode.c create mode 100644 extlib/libvterm/utf8.h create mode 100644 extlib/libvterm/vterm.c create mode 100644 extlib/libvterm/vterm.h create mode 100644 extlib/libvterm/vterm_internal.h create mode 100644 extlib/libvterm/vterm_keycodes.h create mode 100644 graphics/graphics.c create mode 100644 graphics/graphics.h create mode 100644 graphics/test_unifont.c create mode 100644 graphics/unifont.c create mode 100644 graphics/unifont.h create mode 100644 graphics/xcursor/xcursor.h create mode 100644 interrupt/handler.asm.S create mode 100644 interrupt/handler.c create mode 100644 interrupt/handlers.h create mode 100644 interrupt/init.c create mode 100644 interrupt/interrupt.h create mode 100644 interrupt/interrupt_testcode.S create mode 100644 interrupt/load_gdt.S create mode 100644 interrupt/map_handler.S create mode 100644 interrupt/syscall.S create mode 100644 interrupt/syscall.h create mode 100644 interrupt/testcode.h create mode 100644 kernel/kmain.c create mode 100644 kernel/kmain.h create mode 100644 kernel/kmain.init.S create mode 100644 libc/README create mode 100644 libc/abs.c create mode 100644 libc/include/assert.h create mode 100644 libc/include/ctype.h create mode 100644 libc/include/errno.h create mode 100644 libc/include/float.h create mode 100644 libc/include/inttypes.h create mode 100644 libc/include/iso646.h create mode 100644 libc/include/limits.h create mode 100644 libc/include/locale.h create mode 100644 libc/include/pdclib/_PDCLIB_config.h create mode 100644 libc/include/pdclib/_PDCLIB_defguard.h create mode 100644 libc/include/pdclib/_PDCLIB_glue.h create mode 100644 libc/include/pdclib/_PDCLIB_internal.h create mode 100644 libc/include/pdclib/_PDCLIB_lib_ext1.h create mode 100644 libc/include/pdclib/_PDCLIB_print.h create mode 100644 libc/include/pdclib/_PDCLIB_tzcode.h create mode 100644 libc/include/signal.h create mode 100644 libc/include/stdalign.h create mode 100644 libc/include/stdarg.h create mode 100644 libc/include/stdbool.h create mode 100644 libc/include/stddef.h create mode 100644 libc/include/stdint.h create mode 100644 libc/include/stdio.h create mode 100644 libc/include/stdlib.h create mode 100644 libc/include/stdnoreturn.h create mode 100644 libc/include/string.h create mode 100644 libc/include/threads.h create mode 100644 libc/include/time.h create mode 100644 libc/include/wctype.h create mode 100644 libc/memcmp.c create mode 100644 libc/strncat.c create mode 100644 libc/strncmp.c create mode 100644 libc/strncpy.c create mode 100644 main.c create mode 100644 main.h create mode 100644 memory/Makefile create mode 100644 memory/liballoc_impl.c create mode 100644 memory/memory.c create mode 100644 memory/memory.h create mode 100644 memory/memory.hpp create mode 100644 memory/memory_cpp.cpp create mode 100644 memory/paging.c create mode 100644 memory/paging_init.c create mode 100644 memory/paging_internal.h create mode 100644 memory/paging_map.c create mode 100644 memory/paging_modeswitch.S create mode 100644 memory/paging_physical.c create mode 100644 memory/test_fillbits.c create mode 100644 memory/test_take_bitfield.c create mode 100644 run.cmd create mode 100644 runtime/calling_convention.S create mode 100644 runtime/calling_convention.h create mode 100644 runtime/memcpy.c create mode 100644 runtime/memcpy.h create mode 100644 runtime/memset_memmove.S create mode 100644 runtime/panic_assert.asm.S create mode 100644 runtime/panic_assert.h create mode 100644 runtime/printf.c create mode 100644 runtime/printf.h create mode 100644 runtime/stdio.c create mode 100644 runtime/stdio.h create mode 100644 runtime/string.c create mode 100644 runtime/test_memmove.c create mode 100644 runtime/test_utf8.c create mode 100644 runtime/unicode.c create mode 100644 runtime/unicode.h create mode 100644 util/queue.c create mode 100644 util/queue.h create mode 100644 vterm/vterm.c create mode 100644 vterm/vterm.cpp create mode 100644 vterm/vterm.hpp diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..ceb652a --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: ["-I/usr/include/efi", "-I/usr/include/efi/x86_64", "-DHELOS"] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..73a70be --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Compiled result +Main.efi +*.o + +# Test files compiled result +a.out + +# macOS trash +.DS_Store +# vscode trash +.vscode +# Dolphin trash +.directory + + diff --git a/Linker.ld b/Linker.ld new file mode 100644 index 0000000..b9d9428 --- /dev/null +++ b/Linker.ld @@ -0,0 +1,199 @@ +/* Default linker script, for normal executables */ +/* Copyright (C) 2014-2020 Free Software Foundation, Inc. + Copying and distribution of this script, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. */ +OUTPUT_FORMAT(pei-x86-64) +SEARCH_DIR("/usr/x86_64-w64-mingw32/lib"); + +__section_alignment__ = 4096; + +SECTIONS +{ + /* Make the virtual address and file offset synced if the alignment is + lower than the target page size. */ + . = SIZEOF_HEADERS; + . = ALIGN(__section_alignment__); + .text __image_base__ + ( __section_alignment__ < 0x1000 ? . : __section_alignment__ ) : + { + PROVIDE(link_TextStart = .); + KEEP (*(SORT_NONE(.init))) + *(.text) + *(SORT(.text$*)) + *(.text.*) + *(.gnu.linkonce.t.*) + *(.glue_7t) + *(.glue_7) + . = ALIGN(8); + /* Note: we always define __CTOR_LIST__ and ___CTOR_LIST__ here, + we do not PROVIDE them. This is because the ctors.o startup + code in libgcc defines them as common symbols, with the + expectation that they will be overridden by the definitions + here. If we PROVIDE the symbols then they will not be + overridden and global constructors will not be run. + See PR 22762 for more details. + + This does mean that it is not possible for a user to define + their own __CTOR_LIST__ and __DTOR_LIST__ symbols; if they do, + the content from those variables are included but the symbols + defined here silently take precedence. If they truly need to + be redefined, a custom linker script will have to be used. + (The custom script can just be a copy of this script with the + PROVIDE() qualifiers added). + In particular this means that ld -Ur does not work, because + the proper __CTOR_LIST__ set by ld -Ur is overridden by a + bogus __CTOR_LIST__ set by the final link. See PR 46. */ + ___CTOR_LIST__ = .; + __CTOR_LIST__ = .; + LONG (-1); LONG (-1); + KEEP (*(.ctors)); + KEEP (*(.ctor)); + KEEP (*(SORT_BY_NAME(.ctors.*))); + LONG (0); LONG (0); + /* See comment about __CTOR_LIST__ above. The same reasoning + applies here too. */ + ___DTOR_LIST__ = .; + __DTOR_LIST__ = .; + LONG (-1); LONG (-1); + KEEP (*(.dtors)); + KEEP (*(.dtor)); + KEEP (*(SORT_BY_NAME(.dtors.*))); + LONG (0); LONG (0); + KEEP (*(SORT_NONE(.fini))) + /* ??? Why is .gcc_exc here? */ + *(.gcc_exc) + PROVIDE (etext = .); + PROVIDE(link_TextEnd = .); + KEEP (*(.gcc_except_table)) + } + /* The Cygwin32 library uses a section to avoid copying certain data + on fork. This used to be named ".data". The linker used + to include this between __data_start__ and __data_end__, but that + breaks building the cygwin32 dll. Instead, we name the section + ".data_cygwin_nocopy" and explicitly include it after __data_end__. */ + .data BLOCK(__section_alignment__) : + { + PROVIDE(link_DataStart = .); + __data_start__ = . ; + *(.data) + *(.data2) + *(SORT(.data$*)) + KEEP(*(.jcr)) + __data_end__ = . ; + *(.data_cygwin_nocopy) + } + .idata BLOCK(__section_alignment__) : + { + /* This cannot currently be handled with grouped sections. + See pep.em:sort_sections. */ + KEEP (SORT(*)(.idata$2)) + KEEP (SORT(*)(.idata$3)) + /* These zeroes mark the end of the import list. */ + LONG (0); LONG (0); LONG (0); LONG (0); LONG (0); + KEEP (SORT(*)(.idata$4)) + __IAT_start__ = .; + SORT(*)(.idata$5) + __IAT_end__ = .; + KEEP (SORT(*)(.idata$6)) + KEEP (SORT(*)(.idata$7)) + PROVIDE(link_DataEnd = .); + } + .rdata BLOCK(__section_alignment__) : + { + PROVIDE(link_RodataStart = .); + *(.rdata) + *(SORT(.rdata$*)) + *(.rodata) + . = ALIGN(4); + __rt_psrelocs_start = .; + KEEP(*(.rdata_runtime_pseudo_reloc)) + __rt_psrelocs_end = .; + PROVIDE(link_RodataEnd = .); + } + __rt_psrelocs_size = __rt_psrelocs_end - __rt_psrelocs_start; + ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .; + __RUNTIME_PSEUDO_RELOC_LIST_END__ = .; + ___RUNTIME_PSEUDO_RELOC_LIST__ = . - __rt_psrelocs_size; + __RUNTIME_PSEUDO_RELOC_LIST__ = . - __rt_psrelocs_size; + .eh_frame BLOCK(__section_alignment__) : + { + KEEP (*(.eh_frame*)) + } + .pdata BLOCK(__section_alignment__) : + { + KEEP(*(.pdata*)) + } + .xdata BLOCK(__section_alignment__) : + { + KEEP(*(.xdata*)) + } + .edata BLOCK(__section_alignment__) : + { + *(.edata) + } + .bss BLOCK(__section_alignment__) : + { + PROVIDE(link_BssStart = .); + __bss_start__ = . ; + *(.bss) + *(COMMON) + __bss_end__ = . ; + PROVIDE(link_BssEnd = .); + } + /DISCARD/ : + { + *(.debug$S) + *(.debug$T) + *(.debug$F) + *(.drectve) + *(.note.GNU-stack) + *(.gnu.lto_*) + } + .CRT BLOCK(__section_alignment__) : + { + ___crt_xc_start__ = . ; + KEEP (*(SORT(.CRT$XC*))) /* C initialization */ + ___crt_xc_end__ = . ; + ___crt_xi_start__ = . ; + KEEP (*(SORT(.CRT$XI*))) /* C++ initialization */ + ___crt_xi_end__ = . ; + ___crt_xl_start__ = . ; + KEEP (*(SORT(.CRT$XL*))) /* TLS callbacks */ + /* ___crt_xl_end__ is defined in the TLS Directory support code */ + ___crt_xp_start__ = . ; + KEEP (*(SORT(.CRT$XP*))) /* Pre-termination */ + ___crt_xp_end__ = . ; + ___crt_xt_start__ = . ; + KEEP (*(SORT(.CRT$XT*))) /* Termination */ + ___crt_xt_end__ = . ; + } + /* Windows TLS expects .tls$AAA to be at the start and .tls$ZZZ to be + at the end of the .tls section. This is important because _tls_start MUST + be at the beginning of the section to enable SECREL32 relocations with TLS + data. */ + .tls BLOCK(__section_alignment__) : + { + ___tls_start__ = . ; + KEEP (*(.tls$AAA)) + KEEP (*(.tls)) + KEEP (*(.tls$)) + KEEP (*(SORT(.tls$*))) + KEEP (*(.tls$ZZZ)) + ___tls_end__ = . ; + } + .endjunk BLOCK(__section_alignment__) : + { + /* end is deprecated, don't use it */ + PROVIDE (end = .); + PROVIDE ( _end = .); + __end__ = .; + } + .reloc BLOCK(__section_alignment__) : + { + PROVIDE(link_RelocStart = .); + *(.reloc) + PROVIDE(link_RelocEnd = .); + } +} + + diff --git a/Linker.ld.old b/Linker.ld.old new file mode 100644 index 0000000..056c7eb --- /dev/null +++ b/Linker.ld.old @@ -0,0 +1,62 @@ + +OUTPUT_FORMAT(pei-x86-64) +SEARCH_DIR("/usr/x86_64-w64-mingw32/lib"); + +/* Tell where the various sections of the object files will be put in the final + kernel image. */ +SECTIONS +{ + /* Make the virtual address and file offset synced if the alignment is + lower than the target page size. */ + . = SIZEOF_HEADERS; + . = ALIGN(__section_alignment__); + + /* First put the multiboot header, as it is required to be put very early + early in the image or the bootloader won't recognize the file format. + Next we'll put the .text section. */ + .text BLOCK(4K) : ALIGN(4K) + { + PROVIDE(link_TextStart = .); + /* *(.multiboot) */ + *(.text) + PROVIDE(link_TextEnd = .); + } + + /* Read-only data. */ + .rdata BLOCK(4K) : ALIGN(4K) + { + PROVIDE(link_RodataStart = .); + /* *(.rodata) */ /* as in ELF/SysV style */ + *(.rdata) + *(SORT(.rdata$*)) + . = ALIGN(4); + __rt_psrelocs_start = .; + KEEP(*(.rdata_runtime_pseudo_reloc)) + __rt_psrelocs_end = .; + PROVIDE(link_RodataEnd = .); + } + + /* Read-write data (initialized) */ + .data BLOCK(4K) : ALIGN(4K) + { + PROVIDE(link_DataStart = .); + *(.data) + *(.data) + *(.data2) + *(SORT(.data$*)) + KEEP(*(.jcr)) + PROVIDE(link_DataEnd = .); + } + + /* Read-write data (uninitialized) and stack */ + .bss BLOCK(4K) : ALIGN(4K) + { + PROVIDE(link_BssStart = .); + *(COMMON) + *(.bss) + PROVIDE(link_BssEnd = .); + } + + /* The compiler may produce other sections, by default it will put them in + a segment with the same name. Simply add stuff here as needed. */ +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..006f744 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ + +.SILENT: +include Makefile.flags + + +objects = $(patsubst %.c,%.o,$(shell find . -name "*.c")) $(patsubst %.cpp,%.o,$(shell find . -name "*.cpp")) +objects_fasm = $(patsubst %.S,%.o,$(shell find . -name "*.S")) + +objects_test = $(patsubst %.c,%.o,$(shell find . -name "test_*.c")) $(patsubst %.cpp,%.o,$(shell find . -name "test_*.cpp")) +objects := $(filter-out $(objects_test),$(objects)) + + +all: Main.efi + + +Main.efi: $(objects) $(objects_fasm) + $(LD) $(LDFLAGS) $(objects) $(objects_fasm) $(LDLIBS) + + +clean: + echo " -RM Main.efi $(objects)" + -$(RM) -f Main.efi $(objects) $(objects_fasm) + +install: Main.efi + echo " CP Main.efi ../FAT/EFI/Boot/bootx64.efi" + $(CP) Main.efi ../FAT/EFI/Boot/bootx64.efi + +upload: Main.efi + echo " SCP Main.efi router.edgaru089.ml:/opt/tftp/" + scp -P 29657 Main.efi root@router.edgaru089.ml:/opt/tftp/ + +.PHONY: all clean install upload + diff --git a/Makefile.flags b/Makefile.flags new file mode 100644 index 0000000..b445bad --- /dev/null +++ b/Makefile.flags @@ -0,0 +1,28 @@ + +.SILENT: + + +FLAGS_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +SELF_DIR = $(dir $@) + +export RM = rm +export CP = cp + +export AR = echo " AR $<" && x86_64-w64-mingw32-ar +export AS = echo " AS $^" && x86_64-w64-mingw32-as +export CC = echo " CC $^" && x86_64-w64-mingw32-gcc +export CXX = echo " CXX $^" && x86_64-w64-mingw32-g++ +export LD = echo " LD $@" && x86_64-w64-mingw32-gcc +export FASM = echo " FASM $^" && fasm >/dev/null + +export INCLUDEFLAGS = -I/usr/include/efi -I/usr/include/efi/x86_64 + +export CPPFLAGS = +export CFLAGS = $(INCLUDEFLAGS) -DHELOS -O2 -Wno-attributes -fPIE -ffreestanding -nostdlib -mcmodel=large -mno-red-zone +export CXXFLAGS = $(INCLUDEFLAGS) -DHELOS -O2 -Wno-unused-result -std=c++17 -fPIE -ffreestanding -nostdlib -mcmodel=large -mno-red-zone -fno-exceptions -fno-rtti +export LDFLAGS = -T Linker.ld -O2 -eefiMain -nostdlib -shared -fPIE -ffreestanding -Wl,--dynamicbase,--subsystem,10 -o Main.efi -s +export LDLIBS = ../Unifont/unifont.o -lgcc + +# Pattern rule for FASM assembly +%.o: %.S + $(FASM) $^ $@ diff --git a/Makefile.subdir b/Makefile.subdir new file mode 100644 index 0000000..a829280 --- /dev/null +++ b/Makefile.subdir @@ -0,0 +1,19 @@ + +SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST))) + +objects = $(patsubst %.c,%.o,$(wildcard *.c)) $(patsubst %.cpp,%.o,$(wildcard *.cpp)) +objects_fasm = $(patsubst %.S,%.o,$(wildcard *.S)) + +objects_test = $(patsubst %.c,%.o,$(wildcard test_*.c)) $(patsubst %.cpp,%.o,$(wildcard test_*.cpp)) +objects := $(filter-out $(objects_test),$(objects)) + + +all: $(objects) $(objects_fasm) + +$(objects_fasm): + $(FASM) $(patsubst %.o,%.S,$@) $@ + +clean: + -$(RM) *.o + + diff --git a/execformat/pe/format.h b/execformat/pe/format.h new file mode 100644 index 0000000..522427e --- /dev/null +++ b/execformat/pe/format.h @@ -0,0 +1,256 @@ +#pragma once + +#include "../../main.h" +#include "../../runtime/panic_assert.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// PE Header Magic, comes after the MS-DOS stub and right before PE Header +#define EXECFORMAT_PE_HEADER_MAGIC "PE\0" // comes with a \0 itself + +// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format +typedef struct { + uint16_t machineType; + uint16_t numSections; + uint32_t unixTimestamp; + uint32_t offsetSymbolTable; + uint32_t numSymbols; + uint16_t sizeofOptionalHeader; + uint16_t flags; // "Characteristics" +} PACKED execformat_pe_Header; + +// Machine types +#define EXECFORMAT_PE_MACHINE_UNKNOWN 0x0u // The content of this field is assumed to be applicable to any machine type +#define EXECFORMAT_PE_MACHINE_AMD64 0x8664u // x64 +#define EXECFORMAT_PE_MACHINE_I386 0x14cu // Intel 386 or later processors and compatible processors +#define EXECFORMAT_PE_MACHINE_EBC 0xebcu // EFI byte code + +// Characteristics +#define EXECFORMAT_PE_FLAG_RELOCS_STRIPPED 0x0001u // base relocs removed and must be loaded at its preferred base address. +#define EXECFORMAT_PE_FLAG_EXECUTABLE_IMAGE 0x0002u // the image file is valid and can be run. +#define EXECFORMAT_PE_FLAG_LINE_NUMS_STRIPPED 0x0004u // COFF line numbers removed. deprecated and should be zero. +#define EXECFORMAT_PE_FLAG_LOCAL_SYMS_STRIPPED 0x0008u // COFF symbol table entries for local symbols removed. deprecated and should be zero. +#define EXECFORMAT_PE_FLAG_AGGRESSIVE_WS_TRIM 0x0010u // Aggressively trim working set. deprecated and later and must be zero. +#define EXECFORMAT_PE_FLAG_LARGE_ADDRESS_AWARE 0x0020u // Application can handle >2GB addresses. +#define EXECFORMAT_PE_FLAG_RESERVED_0x0040 0x0040u +#define EXECFORMAT_PE_FLAG_BYTES_REVERSED_LO 0x0080u // Little endian. This flag is deprecated and should be zero. +#define EXECFORMAT_PE_FLAG_32BIT_MACHINE 0x0100u // Machine is based on a 32-bit-word architecture. +#define EXECFORMAT_PE_FLAG_DEBUG_STRIPPED 0x0200u // Debugging information is removed from the image file. +#define EXECFORMAT_PE_FLAG_REMOVABLE_RUN_FROM_SWAP 0x0400u // If the image is on removable media, fully load it and copy it to the swap file. +#define EXECFORMAT_PE_FLAG_NET_RUN_FROM_SWAP 0x0800u // If the image is on network media, fully load it and copy it to the swap file. +#define EXECFORMAT_PE_FLAG_SYSTEM 0x1000u // The image file is a system file, not a user program. +#define EXECFORMAT_PE_FLAG_DLL 0x2000u // The image file is a dynamic-link library (DLL). +#define EXECFORMAT_PE_FLAG_UP_SYSTEM_ONLY 0x4000u // The file should be run only on a uniprocessor machine. +#define EXECFORMAT_PE_FLAG_BYTES_REVERSED_HI 0x8000u // Big endian. This flag is deprecated and should be zero. + +// Optional Header Magic +#define EXECFORMAT_PE_OPTIONAL_HEADER_MAGIC_PE32 0x10bu // 2-byte magic for PE32 Optional Header, right following the PE Header. +#define EXECFORMAT_PE_OPTIONAL_HEADER_MAGIC_PE32P 0x20bu // 2-byte magic for PE32+ Optional Header, right following the PE Header. + +typedef struct { + uint16_t magic; // either 0x10b for PE32, or 0x20b for PE32+ + uint8_t majorLinkerVersion, minorLinkerVersion; + uint32_t sizeofText; // size of the .text section, or the sum of all text sections + uint32_t sizeofData; // size of initialized data sections + uint32_t sizeofBss; // size of uninitialized data + uint32_t offsetofEntryPoint; // offset of the entry point (starting address) relative to the image base + uint32_t offsetofText; // offset of the start of .text section relative to the image base +} PACKED execformat_pe_OptionalHeader_StandardFields_PE32P; + +typedef struct { + uint16_t magic; // either 0x10b for PE32, or 0x20b for PE32+ + uint8_t majorLinkerVersion, minorLinkerVersion; + uint32_t sizeofText; // size of the .text section, or the sum of all text sections + uint32_t sizeofData; // size of initialized data sections + uint32_t sizeofBss; // size of uninitialized data + uint32_t offsetofEntryPoint; // offset of the entry point (starting address) relative to the image base + uint32_t offsetofText; // offset of the start of .text section relative to the image base + uint32_t offsetofData; // only present in PE32 images +} PACKED execformat_pe_OptionalHeader_StandardFields_PE32; + +// PE32+ struct with a 64-bit ImageBase +typedef struct { + uint64_t imageBase; // The preferred address of the first byte of image when loaded into memory; must be aligned to 64K. + uint32_t sectionAlignment; // The alignment (in bytes) of sections when they are loaded into memory. + uint32_t fileAlignment; // The alignment factor (in bytes) that is used to align the raw data of sections in the image file. + uint16_t majorOSVersion, minorOSVersion; // The version number of the required operating system. + uint16_t majorImageVersion, minorImageVersion; + uint16_t majorSubsystemVersion, minorSubsystemVersion; + uint32_t win32VersionValue; // Reserved, must be 0 + uint32_t sizeofImage; // Size in bytes of the image, including all headers as the image is loaded into memory. + uint32_t sizeofHeaders; // Combined size of the MS-DOS stub, PE header, and section headers rounded up to FileAlignment. + uint32_t checksum; // Checksum of an unknown(???) algorithm + uint16_t subsystem; // Subsystem + uint16_t dllFlags; // DLL Characteristics + uint64_t sizeofStackReserve, sizeofStackCommit; + uint64_t sizeofHeapReserve, sizeofHeapCommit; + uint32_t loaderFlags; // Reserved, must be 0 + uint32_t numRVAandSizes; // The number of data-directory entries in the remainder of the optional header. Each describes a location and size. +} PACKED execformat_pe_OptionalHeader_WindowsFields_PE32P; + +// PE32 struct with a 32-bit ImageBase +typedef struct { + uint32_t imageBase; // The preferred address of the first byte of image when loaded into memory; must be aligned to 64K. + uint32_t sectionAlignment; // The alignment (in bytes) of sections when they are loaded into memory. + uint32_t fileAlignment; // The alignment factor (in bytes) that is used to align the raw data of sections in the image file. + uint16_t majorOSVersion, minorOSVersion; // The version number of the required operating system. + uint16_t majorImageVersion, minorImageVersion; + uint16_t majorSubsystemVersion, minorSubsystemVersion; + uint32_t win32VersionValue; // Reserved, must be 0 + uint32_t sizeofImage; // Size in bytes of the image, including all headers as the image is loaded into memory. + uint32_t sizeofHeaders; // Combined size of the MS-DOS stub, PE header, and section headers rounded up to FileAlignment. + uint32_t checksum; // Checksum of an unknown(???) algorithm + uint16_t subsystem; // Subsystem + uint16_t dllFlags; // DLL Characteristics + uint32_t sizeofStackReserve, sizeofStackCommit; + uint32_t sizeofHeapReserve, sizeofHeapCommit; + uint32_t loaderFlags; // Reserved, must be 0 + uint32_t numRVAandSizes; // The number of data-directory entries in the remainder of the optional header. Each describes a location and size. +} PACKED execformat_pe_OptionalHeader_WindowsFields_PE32; + +// Subsystems +#define EXECFORMAT_PE_SUBSYSTEM_UNKNOWN 0 +#define EXECFORMAT_PE_SUBSYSTEM_NATIVE 1 // Device drivers and native Windows processes +#define EXECFORMAT_PE_SUBSYSTEM_WINDOWS_GUI 2 // Windows graphical user interface (GUI) subsystem +#define EXECFORMAT_PE_SUBSYSTEM_WINDOWS_CUI 3 // Windows character subsystem +#define EXECFORMAT_PE_SUBSYSTEM_OS2_CUI 5 // OS/2 character subsystem +#define EXECFORMAT_PE_SUBSYSTEM_POSIX_CUI 7 // POSIX character subsystem +#define EXECFORMAT_PE_SUBSYSTEM_NATIVE_WINDOWS 8 // Native Win9x driver +#define EXECFORMAT_PE_SUBSYSTEM_WINDOWS_CE_GUI 9 // Windows CE +#define EXECFORMAT_PE_SUBSYSTEM_EFI_APPLICATION 10 // Extensible Firmware Interface (EFI) application +#define EXECFORMAT_PE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER 11 // EFI Boot Services driver +#define EXECFORMAT_PE_SUBSYSTEM_EFI_RUNTIME_DRIVER 12 // EFI Runtime driver +#define EXECFORMAT_PE_SUBSYSTEM_EFI_ROM 13 // EFI ROM image +#define EXECFORMAT_PE_SUBSYSTEM_XBOX 14 // XBOX +#define EXECFORMAT_PE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION 16 // Windows boot application + +// DLL Characteristics +#define EXECFORMAT_PE_DLLFLAG_RESERVED_0x0001 0x0001u // Reserved, must be 0 +#define EXECFORMAT_PE_DLLFLAG_RESERVED_0x0002 0x0002u // Reserved, must be 0 +#define EXECFORMAT_PE_DLLFLAG_RESERVED_0x0004 0x0004u // Reserved, must be 0 +#define EXECFORMAT_PE_DLLFLAG_RESERVED_0x0008 0x0008u // Reserved, must be 0 +#define EXECFORMAT_PE_DLLFLAG_HIGH_ENTROPY_VA 0x0020u // Image can handle a high entropy 64-bit virtual address space. +#define EXECFORMAT_PE_DLLFLAG_DYNAMIC_BASE 0x0040u // DLL can be relocated at load time. +#define EXECFORMAT_PE_DLLFLAG_FORCE_INTEGRITY 0x0080u // Code Integrity checks are enforced. +#define EXECFORMAT_PE_DLLFLAG_NX_COMPAT 0x0100u // Image is NX compatible. +#define EXECFORMAT_PE_DLLFLAG_NO_ISOLATION 0x0200u // Isolation aware, but do not isolate the image. +#define EXECFORMAT_PE_DLLFLAG_NO_SEH 0x0400u // Does not use structured exception (SE) handling. +#define EXECFORMAT_PE_DLLFLAG_NO_BIND 0x0800u // Do not bind the image. +#define EXECFORMAT_PE_DLLFLAG_APPCONTAINER 0x1000u // Image must execute in an AppContainer. +#define EXECFORMAT_PE_DLLFLAG_WDM_DRIVER 0x2000u // A WDM driver. +#define EXECFORMAT_PE_DLLFLAG_GUARD_CF 0x4000u // Image supports Control Flow Guard. +#define EXECFORMAT_PE_DLLFLAG_TERMINAL_SERVER_AWARE 0x8000u // Terminal Server aware. + +typedef struct { + uint32_t Offset; + uint32_t Size; +} PACKED execformat_pe_OptionalHeader_DataDirectory; + +// Data Directory entry indexes +#define EXECFORMAT_PE_DATADIR_INDEX_EXPORT 0 // .edata +#define EXECFORMAT_PE_DATADIR_INDEX_IMPORT 1 // .idata +#define EXECFORMAT_PE_DATADIR_INDEX_RESOURCE 2 // .rsrc +#define EXECFORMAT_PE_DATADIR_INDEX_EXCEPTION 3 // .pdata +#define EXECFORMAT_PE_DATADIR_INDEX_CERTIFICATE 4 +#define EXECFORMAT_PE_DATADIR_INDEX_BASE_RELOCATION 5 // .reloc +#define EXECFORMAT_PE_DATADIR_INDEX_DEBUG 6 // .debug +#define EXECFORMAT_PE_DATADIR_INDEX_ARCHITECTURE 7 // Reserved, must be 0 +#define EXECFORMAT_PE_DATADIR_INDEX_GLOBAL_PTR 8 // The RVA of the value to be stored in the global pointer register. The size must be 0. +#define EXECFORMAT_PE_DATADIR_INDEX_TLS 9 // .tls +#define EXECFORMAT_PE_DATADIR_INDEX_LOAD_CONFIG 10 +#define EXECFORMAT_PE_DATADIR_INDEX_BOUND_IMPORT 11 +#define EXECFORMAT_PE_DATADIR_INDEX_IAT 12 // Import Address Table +#define EXECFORMAT_PE_DATADIR_INDEX_DELAY_IMPORT_DESC 13 // Delay Import Descriptor +#define EXECFORMAT_PE_DATADIR_INDEX_CLR_RUNTIME_HEADER 14 // Common Language Runtime header +#define EXECFORMAT_PE_DATADIR_INDEX_RESERVED_15 15 // Reserved, must be 0 + +typedef struct { + execformat_pe_OptionalHeader_StandardFields_PE32P std; + execformat_pe_OptionalHeader_WindowsFields_PE32P win; + execformat_pe_OptionalHeader_DataDirectory data[1]; +} PACKED execformat_pe_OptionalHeader_PE32P; + +typedef struct { + execformat_pe_OptionalHeader_StandardFields_PE32 std; + execformat_pe_OptionalHeader_WindowsFields_PE32 win; + execformat_pe_OptionalHeader_DataDirectory data[1]; +} PACKED execformat_pe_OptionalHeader_PE32; + +static inline void execformat_pe_OptionalHeader_CheckPacking() { + assert(sizeof(execformat_pe_OptionalHeader_StandardFields_PE32) == 28); + assert(sizeof(execformat_pe_OptionalHeader_StandardFields_PE32P) == 24); + assert(sizeof(execformat_pe_OptionalHeader_WindowsFields_PE32) == 68); + assert(sizeof(execformat_pe_OptionalHeader_WindowsFields_PE32P) == 88); + + assert(offsetof(execformat_pe_OptionalHeader_PE32, std) == 0); + assert(offsetof(execformat_pe_OptionalHeader_PE32, win) == 28); + assert(offsetof(execformat_pe_OptionalHeader_PE32, data[0]) == 96); + assert(offsetof(execformat_pe_OptionalHeader_PE32P, std) == 0); + assert(offsetof(execformat_pe_OptionalHeader_PE32P, win) == 24); + assert(offsetof(execformat_pe_OptionalHeader_PE32P, data[0]) == 112); +} + + +typedef struct { + char name[8]; + uint32_t virtualSize; + uint32_t virtualAddr; // Offset(RVA) instead of a real Virtual Address + uint32_t sizeofRawData; + uint32_t pointerToRawData; // The file pointer to the first page of the section within the file. + uint32_t pointerToRelocations; + uint32_t pointerToLineNumbers; + uint16_t numRelocations; + uint16_t numLineNumbers; // deprecated, should be 0 + uint32_t flags; // The flags that describe the characteristics of the section. +} PACKED execformat_pe_SectionHeader; + +// Section Characteristics +#define EXECFORMAT_PE_SECTIONFLAG_NO_PAD 0x00000008u // section should not be padded to the next boundary, obsolete. +#define EXECFORMAT_PE_SECTIONFLAG_CNT_CODE 0x00000020u // section contains executable code. +#define EXECFORMAT_PE_SECTIONFLAG_CNT_INITIALIZED_DATA 0x00000040u // section contains initialized data. +#define EXECFORMAT_PE_SECTIONFLAG_CNT_UNINITIALIZED_DATA 0x00000080u // section contains uninitialized data. +#define EXECFORMAT_PE_SECTIONFLAG_LNK_OTHER 0x00000100u // Reserved. +#define EXECFORMAT_PE_SECTIONFLAG_LNK_INFO 0x00000200u // section contains comments or other info. .drectve section has this type. +#define EXECFORMAT_PE_SECTIONFLAG_LNK_REMOVE 0x00000800u // section will not become part of the image. valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_LNK_COMDAT 0x00001000u // The section contains COMDAT data. valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_GPREL 0x00008000u // section contains data referenced through the global pointer (GP). +#define EXECFORMAT_PE_SECTIONFLAG_MEM_PURGEABLE 0x00020000u // Reserved. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_16BIT 0x00020000u // Reserved. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_LOCKED 0x00040000u // Reserved. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_PRELOAD 0x00080000u // Reserved. + +// Section Characteristics - Alignment (only for object files) +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_1BYTES 0x00100000u // Align data on a 1-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_2BYTES 0x00200000u // Align data on a 2-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_4BYTES 0x00300000u // Align data on a 4-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_8BYTES 0x00400000u // Align data on a 8-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_16BYTES 0x00500000u // Align data on a 16-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_32BYTES 0x00600000u // Align data on a 32-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_64BYTES 0x00700000u // Align data on a 64-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_128BYTES 0x00800000u // Align data on a 128-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_256BYTES 0x00900000u // Align data on a 256-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_512BYTES 0x00a00000u // Align data on a 512-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_1024BYTES 0x00b00000u // Align data on a 1024-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_2048BYTES 0x00c00000u // Align data on a 2048-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_4096BYTES 0x00d00000u // Align data on a 4096-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_8192BYTES 0x00e00000u // Align data on a 8192-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_MASK 0x00f00000u // Mask for section data alignment type. + +// Section Characteristics - Memory +#define EXECFORMAT_PE_SECTIONFLAG_LNK_NRELOC_OVFL 0x01000000u // section contains extended relocations. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_DISCARDABLE 0x02000000u // section can be discarded as needed. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_NOT_CACHED 0x04000000u // section cannot be cached. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_NOT_PAGED 0x08000000u // section cannot be paged out. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_SHARED 0x10000000u // section can be shared in memory. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_EXECUTE 0x20000000u // section can be executed as code. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_READ 0x40000000u // section can be read. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_WRITE 0x80000000u // section can be written to. + + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/execformat/pe/read_headers.c b/execformat/pe/read_headers.c new file mode 100644 index 0000000..868f7e0 --- /dev/null +++ b/execformat/pe/read_headers.c @@ -0,0 +1,106 @@ + +#include "format.h" +#include "struct.h" +#include "../../memory/memory.h" + +#include + +static const char *caseMachine(uint16_t type) { +#define CASE(expr) \ + case EXECFORMAT_PE_##expr: \ + return #expr; + + switch (type) { + CASE(MACHINE_UNKNOWN) + CASE(MACHINE_AMD64) + CASE(MACHINE_I386) + CASE(MACHINE_EBC) + default: + return "(Unknown)"; + } + +#undef CASE +} + +static void caseFlags(uint32_t flags) { +#define CASE(expr) \ + if (flags & EXECFORMAT_PE_FLAG_##expr) { \ + printf("|" #expr); \ + } + CASE(RELOCS_STRIPPED) + CASE(EXECUTABLE_IMAGE) + CASE(LINE_NUMS_STRIPPED) + CASE(LOCAL_SYMS_STRIPPED) + CASE(AGGRESSIVE_WS_TRIM) + CASE(LARGE_ADDRESS_AWARE) + CASE(RESERVED_0x0040) + CASE(BYTES_REVERSED_LO) + CASE(32BIT_MACHINE) + CASE(DEBUG_STRIPPED) + CASE(REMOVABLE_RUN_FROM_SWAP) + CASE(NET_RUN_FROM_SWAP) + CASE(SYSTEM) + CASE(DLL) + CASE(UP_SYSTEM_ONLY) + CASE(BYTES_REVERSED_HI) + printf("|\n"); +#undef CASE +} +static void caseSectionFlags(uint32_t flags) { +#define CASE(expr) \ + if (flags & EXECFORMAT_PE_SECTIONFLAG_##expr) { \ + printf("|" #expr); \ + } + CASE(NO_PAD) + CASE(CNT_CODE) + CASE(CNT_INITIALIZED_DATA) + CASE(CNT_UNINITIALIZED_DATA) + CASE(LNK_INFO) + CASE(LNK_REMOVE) + CASE(LNK_COMDAT) + CASE(GPREL) + CASE(LNK_NRELOC_OVFL) + CASE(MEM_DISCARDABLE) + CASE(MEM_NOT_CACHED) + CASE(MEM_NOT_PAGED) + CASE(MEM_SHARED) + CASE(MEM_EXECUTE) + CASE(MEM_READ) + CASE(MEM_WRITE) + printf("|\n"); +#undef CASE +} + + +void execformat_pe_ReadSystemHeader(execformat_pe_PortableExecutable *pe) { + execformat_pe_OptionalHeader_CheckPacking(); + + uint64_t a; + asm volatile("leaq (%%rip), %0" + : "=r"(a)); + io_Printf("Stack position: %llx, RIP=%llx\n", &a, a); + + void *addr = (void *)paging_LoaderCodeAddress; + + execformat_pe_PortableExecutable rpe; + if (pe == 0) + pe = &rpe; + execformat_pe_LoadMemory(pe, addr, paging_LoaderCodeSize); + + printf("%s Executable, Machine Type=0x%x (%s), Flags=", pe->isPE32P ? "PE32+" : "PE32", pe->header->machineType, caseMachine(pe->header->machineType)); + caseFlags(pe->header->flags); + printf("\nImageBase=%d (0x%x)\n", ((execformat_pe_OptionalHeader_PE32P *)pe->optional)->win.imageBase, ((execformat_pe_OptionalHeader_PE32P *)pe->optional)->win.imageBase); + + printf("numRVAandSizes=%d (want %d)\n\n", pe->numDataDir, EXECFORMAT_PE_DATADIR_INDEX_CLR_RUNTIME_HEADER + 2); + + printf("numSections=%d\n", pe->numSections); + char name[9]; + for (int i = 0; i < pe->numSections; i++) { + name[8] = 0; + strncpy(name, pe->sections[i].name, 8); + + printf(" %8s RVA0x%08x File0x%08x Len0x%08x ", name, pe->sections[i].virtualAddr, pe->sections[i].pointerToRawData, pe->sections[i].virtualSize); + + caseSectionFlags(pe->sections[i].flags); + } +} diff --git a/execformat/pe/reloc.c b/execformat/pe/reloc.c new file mode 100644 index 0000000..32d0297 --- /dev/null +++ b/execformat/pe/reloc.c @@ -0,0 +1,108 @@ + +#include "reloc.h" +#include "../../runtime/panic_assert.h" +#include + + +static inline int __local_strncmp(const char *s1, const char *s2, size_t n) { + while (n && *s1 && (*s1 == *s2)) { + ++s1; + ++s2; + --n; + } + if (n == 0) { + return 0; + } else { + return (*(unsigned char *)s1 - *(unsigned char *)s2); + } +} + +static const char *caseFlags(uint16_t type) { +#define CASE(expr) \ + case EXECFORMAT_PE_BASERELOC_##expr: \ + return #expr; + + switch (type) { + CASE(ABSOLUTE) + CASE(HIGH) + CASE(LOW) + CASE(HIGHLOW) + CASE(HIGHADJ) + CASE(DIR64) + default: + return "(Unknown)"; + } + +#undef CASE +} + +void execformat_pe_BaseRelocate(execformat_pe_PortableExecutable *pe, void *relocBase, void *relocEnd, uint64_t currentBase, uint64_t targetBase) { + if (pe->begin == 0) + return; + + if (currentBase == 0) { + if (pe->isPE32P) + currentBase = ((execformat_pe_OptionalHeader_PE32P *)pe->optional)->win.imageBase; + else + currentBase = ((execformat_pe_OptionalHeader_PE32 *)pe->optional)->win.imageBase; + } + + void *reloc = 0, *reloc_end; + if (relocBase != 0 && relocEnd != 0) { + reloc = relocBase; + reloc_end = relocEnd; + } else { + // find the .reloc section + for (int i = 0; i < pe->numSections; i++) + if (__local_strncmp(pe->sections[i].name, ".reloc", 6) == 0) { + reloc = pe->begin + pe->sections[i].virtualAddr; + reloc_end = reloc + pe->sections[i].virtualSize; + break; + } + if (reloc == 0) + return; // no .reloc section + } + + uint64_t diff = targetBase - currentBase; + + while (reloc < reloc_end) { + execformat_pe_BaseRelocBlock *block = reloc; + int entries = (block->blockSize - 8) / 2; + assert((block->blockSize - 8) % 2 == 0); + + printf("BaseReloc Block RVA=0x%08x, entries=%d\n", block->pageOffset, entries); + + for (int i = 0; i < entries; i++) { + void *target = pe->begin + block->pageOffset + (block->entries[i] & EXECFORMAT_PE_BASERELOC_OFFSET_MASK); + //uint64_t cur = 0, new = 0; + switch (block->entries[i] & EXECFORMAT_PE_BASERELOC_FLAG_MASK) { + case EXECFORMAT_PE_BASERELOC_HIGH: + //cur = *((uint16_t *)target); + *((uint16_t *)target) += (uint16_t)(diff >> 16); + //new = *((uint16_t *)target); + break; + case EXECFORMAT_PE_BASERELOC_LOW: + //cur = *((uint16_t *)target); + *((uint16_t *)target) += (uint16_t)(diff); + //new = *((uint16_t *)target); + break; + case EXECFORMAT_PE_BASERELOC_HIGHLOW: + //cur = *((uint32_t *)target); + *((uint32_t *)target) += (uint32_t)(diff); + //new = *((uint32_t *)target); + break; + case EXECFORMAT_PE_BASERELOC_DIR64: + //cur = *((uint64_t *)target); + *((uint64_t *)target) += (diff); + //new = *((uint64_t *)target); + break; + case EXECFORMAT_PE_BASERELOC_HIGHADJ: + i++; + break; + } + //printf(" Reloc Off0x%08llx CUR=0x%08llx(Off0x%08llx), NEW=0x%llx, Type=%s\n", target - pe->begin, cur, cur - currentBase, new, caseFlags(block->entries[i] & EXECFORMAT_PE_BASERELOC_FLAG_MASK)); + } + + reloc += block->blockSize; + } +} diff --git a/execformat/pe/reloc.h b/execformat/pe/reloc.h new file mode 100644 index 0000000..ed251e1 --- /dev/null +++ b/execformat/pe/reloc.h @@ -0,0 +1,30 @@ + +#include "format.h" +#include "struct.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct { + uint32_t pageOffset; + uint32_t blockSize; // the size in bytes of the Relocation Block, including the 2 header fields and the entries that follow + uint16_t entries[1]; +} PACKED execformat_pe_BaseRelocBlock; + +#define EXECFORMAT_PE_BASERELOC_ABSOLUTE (0x0000u) // The base relocation is skipped. This type can be used to pad a block. +#define EXECFORMAT_PE_BASERELOC_HIGH (0x1000u) // The base relocation adds the high 16 bits of the difference to the 16-bit field at offset. +#define EXECFORMAT_PE_BASERELOC_LOW (0x2000u) // The base relocation adds the low 16 bits of the difference to the 16-bit field at offset. +#define EXECFORMAT_PE_BASERELOC_HIGHLOW (0x3000u) // The base relocation applies all 32 bits of the difference to the 32-bit field at offset. +#define EXECFORMAT_PE_BASERELOC_HIGHADJ (0x4000u) // Weird, ignored (together with the next entry). +#define EXECFORMAT_PE_BASERELOC_DIR64 (0xa000u) // The base relocation applies the difference to the 64-bit field at offset. +#define EXECFORMAT_PE_BASERELOC_FLAG_MASK (0xf000u) +#define EXECFORMAT_PE_BASERELOC_OFFSET_MASK (0x0fffu) + +void execformat_pe_BaseRelocate(execformat_pe_PortableExecutable *pe, void *relocBase, void *relocEnd, uint64_t currentBase, uint64_t targetBase); + + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/execformat/pe/struct.c b/execformat/pe/struct.c new file mode 100644 index 0000000..0067407 --- /dev/null +++ b/execformat/pe/struct.c @@ -0,0 +1,50 @@ + +#include "struct.h" + +#include "../../runtime/panic_assert.h" +#include + + +void execformat_pe_LoadMemory(execformat_pe_PortableExecutable *pe, void *image, uint32_t size) { + pe->begin = pe->pemagic = 0; + if (size < 2 || ((char *)image)[0] != 'M' || ((char *)image)[1] != 'Z') + return; + + // look for the "PE\0\0" magic on a 8-byte boundary + for (int i = 8; i < size; i++) { + if (memcmp(image + i, EXECFORMAT_PE_HEADER_MAGIC, 4) == 0) { + pe->pemagic = image + i; + break; + } + } + if (pe->pemagic == 0) + return; // not found + + pe->header = (execformat_pe_Header *)(pe->pemagic + 4); + pe->numSections = pe->header->numSections; + pe->optional = (void *)pe->header + sizeof(execformat_pe_Header); + pe->isPE32P = (*((uint16_t *)pe->optional) == EXECFORMAT_PE_OPTIONAL_HEADER_MAGIC_PE32P); + + if (pe->isPE32P) { + pe->numDataDir = ((execformat_pe_OptionalHeader_PE32P *)pe->optional)->win.numRVAandSizes; + pe->sections = + pe->optional + + sizeof(execformat_pe_OptionalHeader_StandardFields_PE32P) + + sizeof(execformat_pe_OptionalHeader_WindowsFields_PE32P) + + sizeof(execformat_pe_OptionalHeader_DataDirectory) * pe->numDataDir; + + assert((void *)pe->sections - pe->optional == pe->header->sizeofOptionalHeader && "PE32P OptionalHeader size mismatch"); + } else { + pe->numDataDir = ((execformat_pe_OptionalHeader_PE32 *)pe->optional)->win.numRVAandSizes; + pe->sections = + pe->optional + + sizeof(execformat_pe_OptionalHeader_StandardFields_PE32) + + sizeof(execformat_pe_OptionalHeader_WindowsFields_PE32) + + sizeof(execformat_pe_OptionalHeader_DataDirectory) * pe->numDataDir; + + assert((void *)pe->sections - pe->optional == pe->header->sizeofOptionalHeader && "PE32 OptionalHeader size mismatch"); + } + + pe->begin = image; + pe->size = size; +} diff --git a/execformat/pe/struct.h b/execformat/pe/struct.h new file mode 100644 index 0000000..3993a93 --- /dev/null +++ b/execformat/pe/struct.h @@ -0,0 +1,29 @@ +#pragma once + +#include "../../main.h" +#include "format.h" +#include "stdbool.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct { + void * begin; // beginning of the image + char * pemagic; // PE magic "PE\0\0", after the MS-DOS stub + execformat_pe_Header * header; // PE headers, 4 bytes right after the magic + void * optional; // optional headers, converted to OptionalHeader_PE32/PE32P on access + execformat_pe_SectionHeader *sections; // start of the section header tables + uint32_t size; // size of the file in bytes + int numDataDir; // shorthand for ((execformat_pe_OptionalHeader_PE32P*)pe->optional)->win.numRVAandSizes + int numSections; + bool isPE32P; +} execformat_pe_PortableExecutable; + +void execformat_pe_LoadMemory(execformat_pe_PortableExecutable *pe, void *image, uint32_t size); + + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/execformat/pe/test_headers.c b/execformat/pe/test_headers.c new file mode 100644 index 0000000..2a43c79 --- /dev/null +++ b/execformat/pe/test_headers.c @@ -0,0 +1,118 @@ + +#include "format.h" +#include "struct.h" +#include "reloc.h" + +#include "stdio.h" +#include "sys/types.h" +#include "sys/stat.h" +#include "fcntl.h" +#include "sys/mman.h" + +#include +#include +#include + +const char *caseMachine(uint16_t type) { +#define CASE(expr) \ + case EXECFORMAT_PE_##expr: \ + return #expr; + + switch (type) { + CASE(MACHINE_UNKNOWN) + CASE(MACHINE_AMD64) + CASE(MACHINE_I386) + CASE(MACHINE_EBC) + default: + return "(Unknown)"; + } + +#undef CASE +} + +void caseFlags(uint32_t flags) { +#define CASE(expr) \ + if (flags & EXECFORMAT_PE_FLAG_##expr) { \ + printf("|" #expr); \ + } + CASE(RELOCS_STRIPPED) + CASE(EXECUTABLE_IMAGE) + CASE(LINE_NUMS_STRIPPED) + CASE(LOCAL_SYMS_STRIPPED) + CASE(AGGRESSIVE_WS_TRIM) + CASE(LARGE_ADDRESS_AWARE) + CASE(RESERVED_0x0040) + CASE(BYTES_REVERSED_LO) + CASE(32BIT_MACHINE) + CASE(DEBUG_STRIPPED) + CASE(REMOVABLE_RUN_FROM_SWAP) + CASE(NET_RUN_FROM_SWAP) + CASE(SYSTEM) + CASE(DLL) + CASE(UP_SYSTEM_ONLY) + CASE(BYTES_REVERSED_HI) + printf("|\n"); +#undef CASE +} +void caseSectionFlags(uint32_t flags) { +#define CASE(expr) \ + if (flags & EXECFORMAT_PE_SECTIONFLAG_##expr) { \ + printf("|" #expr); \ + } + CASE(NO_PAD) + CASE(CNT_CODE) + CASE(CNT_INITIALIZED_DATA) + CASE(CNT_UNINITIALIZED_DATA) + CASE(LNK_INFO) + CASE(LNK_REMOVE) + CASE(LNK_COMDAT) + CASE(GPREL) + CASE(LNK_NRELOC_OVFL) + CASE(MEM_DISCARDABLE) + CASE(MEM_NOT_CACHED) + CASE(MEM_NOT_PAGED) + CASE(MEM_SHARED) + CASE(MEM_EXECUTE) + CASE(MEM_READ) + CASE(MEM_WRITE) + printf("|\n"); +#undef CASE +} + + +int main(int argc, char *argv[]) { + execformat_pe_OptionalHeader_CheckPacking(); + + int f = open("../../Main.efi", O_RDONLY); + assert(f != -1 && "File open failed"); + + struct stat st; + fstat(f, &st); + void *maddr = mmap(0, st.st_size, PROT_READ, MAP_SHARED, f, 0); + void *addr = malloc(st.st_size); + memcpy(addr, maddr, st.st_size); + + execformat_pe_PortableExecutable pe; + execformat_pe_LoadMemory(&pe, addr, st.st_size); + + printf("%s Executable, Machine Type=0x%x (%s), Flags=", pe.isPE32P ? "PE32+" : "PE32", pe.header->machineType, caseMachine(pe.header->machineType)); + caseFlags(pe.header->flags); + printf("\nImageBase=%d (0x%x)\n", ((execformat_pe_OptionalHeader_PE32P *)pe.optional)->win.imageBase, ((execformat_pe_OptionalHeader_PE32P *)pe.optional)->win.imageBase); + + printf("numRVAandSizes=%d (want %d)\n\n", pe.numDataDir, EXECFORMAT_PE_DATADIR_INDEX_CLR_RUNTIME_HEADER + 2); + + printf("numSections=%d\n", pe.numSections); + char name[9]; + for (int i = 0; i < pe.numSections; i++) { + name[8] = 0; + strncpy(name, pe.sections[i].name, 8); + + printf(" %8s Rva0x%08x File0x%08x Len0x%08x ", name, pe.sections[i].virtualAddr, pe.sections[i].pointerToRawData, pe.sections[i].virtualSize); + caseSectionFlags(pe.sections[i].flags); + } + + + execformat_pe_BaseRelocate(&pe, 0, 0xFFFFFFFFC0000000ull); + + return 0; +} diff --git a/extlib/Makefile.old b/extlib/Makefile.old new file mode 100644 index 0000000..45078df --- /dev/null +++ b/extlib/Makefile.old @@ -0,0 +1,20 @@ + +SELF_DIR = $(dir $@) + +export AR = echo " AR extlib/$(if $<,$<,$(SELF_DIR)$$<)" && x86_64-w64-mingw32-ar +export AS = echo " AS extlib/$(if $<,$<,$(SELF_DIR)$$<)" && x86_64-w64-mingw32-as +export CC = echo " CC extlib/$(if $<,$<,$(SELF_DIR)$$<)" && x86_64-w64-mingw32-gcc +export CXX = echo " CXX extlib/$(if $<,$<,$(SELF_DIR)$$<)" && x86_64-w64-mingw32-g++ +export LD = echo " LD extlib/$@" && x86_64-w64-mingw32-gcc +export FASM = echo " FASM extlib/$(SELF_DIR)$$(patsubst %.o,%.S,$$@)" && fasm >/dev/null + +SUBDIRS = $(wildcard */.) +$(SUBDIRS): + echo $@ + echo $(SUBDIRS) + $(MAKE) -sC $@ + +all: $(SUBDIRS) + +.PHONY: all $(SUBDIRS) + diff --git a/extlib/liballoc/HOWTO b/extlib/liballoc/HOWTO new file mode 100644 index 0000000..fb35cd3 --- /dev/null +++ b/extlib/liballoc/HOWTO @@ -0,0 +1,26 @@ + +This is a step-by-step instruction guide detailing how to +implement the library on your own system. + + +1. Copy the "liballoc.c" and "liballoc.h" files into + your working directory. (Whatever project you want + to use it in). + +2. Create the hooks that the library needs: + + Make a new file called "liballoc_hooks.c" (or + whatever) and implement the functions detailed + in the README and explained in "liballoc.h" + + Look at "linux.c" for an example. It implements + the hooks for a Linux system. + +3. Be sure to include the "liballoc.h" header + into your C/C++ files that are using the + malloc/free/memory operations. (So that + malloc/free/etc are declared.. obviously.) + +4. Compile as per your normal method and test. + + diff --git a/extlib/liballoc/LICENSE b/extlib/liballoc/LICENSE new file mode 100644 index 0000000..63d14cd --- /dev/null +++ b/extlib/liballoc/LICENSE @@ -0,0 +1,10 @@ + +This code is released into the public domain. Use this code at your own +risk. Feel free to use it for whatever purpose you want. I take no responsibilty or +whatever if anything goes wrong. Use it at your own risk. + +If you have any fixes or patches, please email me. + +Durand Miller + + diff --git a/extlib/liballoc/README.md b/extlib/liballoc/README.md new file mode 100644 index 0000000..d17dfc1 --- /dev/null +++ b/extlib/liballoc/README.md @@ -0,0 +1,68 @@ +https://github.com/blanham/liballoc + +liballoc - a small memory allocator +=================================== + +This is liballoc, a memory allocator for hobby operating systems, originally +written by Durand. According to the original page for liballoc it was released +into the public domain, but the copy I have contains the 3 clause BSD license. + +liballoc.c/h are the original release of liballoc taken from the spoon tarball +while liballoc_1_1.c/h are later versions found by detective work using Google. + +Using liballoc +============== + +There are 4 functions which you need to implement on your system: + + int liballoc_lock(); + int liballoc_unlock(); + void* liballoc_alloc(int); + int liballoc_free(void*,int); + +1) Have a look at liballoc.h for information about what each function is +supposed to do. + + +2) Have a look at linux.c for an example of how to implement the library +on linux. + + +NOTE: There are two ways to build the library: + + 1) Compile the library with a new system file. For example, I've + left linux.c with the default distribution. It gets compiled + directly into the liballoc_linux.so file. + + 2) Implement the functions in your application and then just + link against the default liballoc.so library when you compile + your app. + + +Quick Start +=========== + +You can simply type: "make linux" to build the linux shared +library. Thereafter, you can link it directly into your applications +during build or afterwards by export the LD_PRELOAD environment +variable. + + +To run bash with the library, for example: + + LD_PRELOAD=/full/path/to/liballoc.so bash + + +The above command will pre-link the library into the application, +essentially replacing the default malloc/free calls at runtime. It's +quite cool. + + +Originally by: +Durand Miller + + + + + + diff --git a/extlib/liballoc/liballoc_1_1.c b/extlib/liballoc/liballoc_1_1.c new file mode 100644 index 0000000..1c82d8f --- /dev/null +++ b/extlib/liballoc/liballoc_1_1.c @@ -0,0 +1,832 @@ +#include "liballoc_1_1.h" + +/** Durand's Amazing Super Duper Memory functions. */ + +#define VERSION "1.1" +#define ALIGNMENT 16ul//4ul ///< This is the byte alignment that memory must be allocated on. IMPORTANT for GTK and other stuff. + +#define ALIGN_TYPE char ///unsigned char[16] /// unsigned short +#define ALIGN_INFO sizeof(ALIGN_TYPE)*16 ///< Alignment information is stored right before the pointer. This is the number of bytes of information stored there. + + +#define USE_CASE1 +#define USE_CASE2 +#define USE_CASE3 +#define USE_CASE4 +#define USE_CASE5 + + +/** This macro will conveniently align our pointer upwards */ +#define ALIGN( ptr ) \ + if ( ALIGNMENT > 1 ) \ + { \ + uintptr_t diff; \ + ptr = (void*)((uintptr_t)ptr + ALIGN_INFO); \ + diff = (uintptr_t)ptr & (ALIGNMENT-1); \ + if ( diff != 0 ) \ + { \ + diff = ALIGNMENT - diff; \ + ptr = (void*)((uintptr_t)ptr + diff); \ + } \ + *((ALIGN_TYPE*)((uintptr_t)ptr - ALIGN_INFO)) = \ + diff + ALIGN_INFO; \ + } + + +#define UNALIGN( ptr ) \ + if ( ALIGNMENT > 1 ) \ + { \ + uintptr_t diff = *((ALIGN_TYPE*)((uintptr_t)ptr - ALIGN_INFO)); \ + if ( diff < (ALIGNMENT + ALIGN_INFO) ) \ + { \ + ptr = (void*)((uintptr_t)ptr - diff); \ + } \ + } + + + +#define LIBALLOC_MAGIC 0xc001c0de +#define LIBALLOC_DEAD 0xdeaddead + +#if defined DEBUG || defined INFO +#include +#include + +#define FLUSH() fflush( stdout ) + +#endif + +/** A structure found at the top of all system allocated + * memory blocks. It details the usage of the memory block. + */ +struct liballoc_major +{ + struct liballoc_major *prev; ///< Linked list information. + struct liballoc_major *next; ///< Linked list information. + unsigned int pages; ///< The number of pages in the block. + unsigned int size; ///< The number of pages in the block. + unsigned int usage; ///< The number of bytes used in the block. + struct liballoc_minor *first; ///< A pointer to the first allocated memory in the block. +}; + + +/** This is a structure found at the beginning of all + * sections in a major block which were allocated by a + * malloc, calloc, realloc call. + */ +struct liballoc_minor +{ + struct liballoc_minor *prev; ///< Linked list information. + struct liballoc_minor *next; ///< Linked list information. + struct liballoc_major *block; ///< The owning block. A pointer to the major structure. + unsigned int magic; ///< A magic number to idenfity correctness. + unsigned int size; ///< The size of the memory allocated. Could be 1 byte or more. + unsigned int req_size; ///< The size of memory requested. +}; + + +static struct liballoc_major *l_memRoot = NULL; ///< The root memory block acquired from the system. +static struct liballoc_major *l_bestBet = NULL; ///< The major with the most free memory. + +static unsigned int l_pageSize = 4096; ///< The size of an individual page. Set up in liballoc_init. +static unsigned int l_pageCount = 16; ///< The number of pages to request per chunk. Set up in liballoc_init. +static unsigned long long l_allocated = 0; ///< Running total of allocated memory. +static unsigned long long l_inuse = 0; ///< Running total of used memory. + + +static long long l_warningCount = 0; ///< Number of warnings encountered +static long long l_errorCount = 0; ///< Number of actual errors +static long long l_possibleOverruns = 0; ///< Number of possible overruns + + + + + +// *********** HELPER FUNCTIONS ******************************* + +static void *liballoc_memset(void* s, int c, size_t n) +{ + unsigned int i; + for ( i = 0; i < n ; i++) + ((char*)s)[i] = c; + + return s; +} +static void* liballoc_memcpy(void* s1, const void* s2, size_t n) +{ + char *cdest; + char *csrc; + unsigned int *ldest = (unsigned int*)s1; + unsigned int *lsrc = (unsigned int*)s2; + + while ( n >= sizeof(unsigned int) ) + { + *ldest++ = *lsrc++; + n -= sizeof(unsigned int); + } + + cdest = (char*)ldest; + csrc = (char*)lsrc; + + while ( n > 0 ) + { + *cdest++ = *csrc++; + n -= 1; + } + + return s1; +} + + +#if defined DEBUG || defined INFO +static void liballoc_dump() +{ +#ifdef DEBUG + struct liballoc_major *maj = l_memRoot; + struct liballoc_minor *min = NULL; +#endif + + printf( "liballoc: ------ Memory data ---------------\n"); + printf( "liballoc: System memory allocated: %i bytes\n", l_allocated ); + printf( "liballoc: Memory in used (malloc'ed): %i bytes\n", l_inuse ); + printf( "liballoc: Warning count: %i\n", l_warningCount ); + printf( "liballoc: Error count: %i\n", l_errorCount ); + printf( "liballoc: Possible overruns: %i\n", l_possibleOverruns ); + +#ifdef DEBUG + while ( maj != NULL ) + { + printf( "liballoc: %x: total = %i, used = %i\n", + maj, + maj->size, + maj->usage ); + + min = maj->first; + while ( min != NULL ) + { + printf( "liballoc: %x: %i bytes\n", + min, + min->size ); + min = min->next; + } + + maj = maj->next; + } +#endif + + FLUSH(); +} +#endif + + + +// *************************************************************** + +static struct liballoc_major *allocate_new_page( unsigned int size ) +{ + unsigned int st; + struct liballoc_major *maj; + + // This is how much space is required. + st = size + sizeof(struct liballoc_major); + st += sizeof(struct liballoc_minor); + + // Perfect amount of space? + if ( (st % l_pageSize) == 0 ) + st = st / (l_pageSize); + else + st = st / (l_pageSize) + 1; + // No, add the buffer. + + + // Make sure it's >= the minimum size. + if ( st < l_pageCount ) st = l_pageCount; + + maj = (struct liballoc_major*)liballoc_alloc( st ); + + if ( maj == NULL ) + { + l_warningCount += 1; + #if defined DEBUG || defined INFO + printf( "liballoc: WARNING: liballoc_alloc( %i ) return NULL\n", st ); + FLUSH(); + #endif + return NULL; // uh oh, we ran out of memory. + } + + maj->prev = NULL; + maj->next = NULL; + maj->pages = st; + maj->size = st * l_pageSize; + maj->usage = sizeof(struct liballoc_major); + maj->first = NULL; + + l_allocated += maj->size; + + #ifdef DEBUG + printf( "liballoc: Resource allocated %x of %i pages (%i bytes) for %i size.\n", maj, st, maj->size, size ); + + printf( "liballoc: Total memory usage = %i KB\n", (int)((l_allocated / (1024))) ); + FLUSH(); + #endif + + + return maj; +} + + + + + +void *PREFIX(malloc)(size_t req_size) +{ + int startedBet = 0; + unsigned long long bestSize = 0; + void *p = NULL; + uintptr_t diff; + struct liballoc_major *maj; + struct liballoc_minor *min; + struct liballoc_minor *new_min; + unsigned long size = req_size; + + // For alignment, we adjust size so there's enough space to align. + if ( ALIGNMENT > 1 ) + { + size += ALIGNMENT + ALIGN_INFO; + } + // So, ideally, we really want an alignment of 0 or 1 in order + // to save space. + + liballoc_lock(); + + if ( size == 0 ) + { + l_warningCount += 1; + #if defined DEBUG || defined INFO + printf( "liballoc: WARNING: alloc( 0 ) called from %x\n", + __builtin_return_address(0) ); + FLUSH(); + #endif + liballoc_unlock(); + return PREFIX(malloc)(1); + } + + + if ( l_memRoot == NULL ) + { + #if defined DEBUG || defined INFO + #ifdef DEBUG + printf( "liballoc: initialization of liballoc " VERSION "\n" ); + #endif + atexit( liballoc_dump ); + FLUSH(); + #endif + + // This is the first time we are being used. + l_memRoot = allocate_new_page( size ); + if ( l_memRoot == NULL ) + { + liballoc_unlock(); + #ifdef DEBUG + printf( "liballoc: initial l_memRoot initialization failed\n", p); + FLUSH(); + #endif + return NULL; + } + + #ifdef DEBUG + printf( "liballoc: set up first memory major %x\n", l_memRoot ); + FLUSH(); + #endif + } + + + #ifdef DEBUG + printf( "liballoc: %x PREFIX(malloc)( %i ): ", + __builtin_return_address(0), + size ); + FLUSH(); + #endif + + // Now we need to bounce through every major and find enough space.... + + maj = l_memRoot; + startedBet = 0; + + // Start at the best bet.... + if ( l_bestBet != NULL ) + { + bestSize = l_bestBet->size - l_bestBet->usage; + + if ( bestSize > (size + sizeof(struct liballoc_minor))) + { + maj = l_bestBet; + startedBet = 1; + } + } + + while ( maj != NULL ) + { + diff = maj->size - maj->usage; + // free memory in the block + + if ( bestSize < diff ) + { + // Hmm.. this one has more memory then our bestBet. Remember! + l_bestBet = maj; + bestSize = diff; + } + + +#ifdef USE_CASE1 + + // CASE 1: There is not enough space in this major block. + if ( diff < (size + sizeof( struct liballoc_minor )) ) + { + #ifdef DEBUG + printf( "CASE 1: Insufficient space in block %x\n", maj); + FLUSH(); + #endif + + // Another major block next to this one? + if ( maj->next != NULL ) + { + maj = maj->next; // Hop to that one. + continue; + } + + if ( startedBet == 1 ) // If we started at the best bet, + { // let's start all over again. + maj = l_memRoot; + startedBet = 0; + continue; + } + + // Create a new major block next to this one and... + maj->next = allocate_new_page( size ); // next one will be okay. + if ( maj->next == NULL ) break; // no more memory. + maj->next->prev = maj; + maj = maj->next; + + // .. fall through to CASE 2 .. + } + +#endif + +#ifdef USE_CASE2 + + // CASE 2: It's a brand new block. + if ( maj->first == NULL ) + { + maj->first = (struct liballoc_minor*)((uintptr_t)maj + sizeof(struct liballoc_major) ); + + + maj->first->magic = LIBALLOC_MAGIC; + maj->first->prev = NULL; + maj->first->next = NULL; + maj->first->block = maj; + maj->first->size = size; + maj->first->req_size = req_size; + maj->usage += size + sizeof( struct liballoc_minor ); + + + l_inuse += size; + + + p = (void*)((uintptr_t)(maj->first) + sizeof( struct liballoc_minor )); + + ALIGN( p ); + + #ifdef DEBUG + printf( "CASE 2: returning %x\n", p); + FLUSH(); + #endif + liballoc_unlock(); // release the lock + return p; + } + +#endif + +#ifdef USE_CASE3 + + // CASE 3: Block in use and enough space at the start of the block. + diff = (uintptr_t)(maj->first); + diff -= (uintptr_t)maj; + diff -= sizeof(struct liballoc_major); + + if ( diff >= (size + sizeof(struct liballoc_minor)) ) + { + // Yes, space in front. Squeeze in. + maj->first->prev = (struct liballoc_minor*)((uintptr_t)maj + sizeof(struct liballoc_major) ); + maj->first->prev->next = maj->first; + maj->first = maj->first->prev; + + maj->first->magic = LIBALLOC_MAGIC; + maj->first->prev = NULL; + maj->first->block = maj; + maj->first->size = size; + maj->first->req_size = req_size; + maj->usage += size + sizeof( struct liballoc_minor ); + + l_inuse += size; + + p = (void*)((uintptr_t)(maj->first) + sizeof( struct liballoc_minor )); + ALIGN( p ); + + #ifdef DEBUG + printf( "CASE 3: returning %x\n", p); + FLUSH(); + #endif + liballoc_unlock(); // release the lock + return p; + } + +#endif + + +#ifdef USE_CASE4 + + // CASE 4: There is enough space in this block. But is it contiguous? + min = maj->first; + + // Looping within the block now... + while ( min != NULL ) + { + // CASE 4.1: End of minors in a block. Space from last and end? + if ( min->next == NULL ) + { + // the rest of this block is free... is it big enough? + diff = (uintptr_t)(maj) + maj->size; + diff -= (uintptr_t)min; + diff -= sizeof( struct liballoc_minor ); + diff -= min->size; + // minus already existing usage.. + + if ( diff >= (size + sizeof( struct liballoc_minor )) ) + { + // yay.... + min->next = (struct liballoc_minor*)((uintptr_t)min + sizeof( struct liballoc_minor ) + min->size); + min->next->prev = min; + min = min->next; + min->next = NULL; + min->magic = LIBALLOC_MAGIC; + min->block = maj; + min->size = size; + min->req_size = req_size; + maj->usage += size + sizeof( struct liballoc_minor ); + + l_inuse += size; + + p = (void*)((uintptr_t)min + sizeof( struct liballoc_minor )); + ALIGN( p ); + + #ifdef DEBUG + printf( "CASE 4.1: returning %x\n", p); + FLUSH(); + #endif + liballoc_unlock(); // release the lock + return p; + } + } + + + + // CASE 4.2: Is there space between two minors? + if ( min->next != NULL ) + { + // is the difference between here and next big enough? + diff = (uintptr_t)(min->next); + diff -= (uintptr_t)min; + diff -= sizeof( struct liballoc_minor ); + diff -= min->size; + // minus our existing usage. + + if ( diff >= (size + sizeof( struct liballoc_minor )) ) + { + // yay...... + new_min = (struct liballoc_minor*)((uintptr_t)min + sizeof( struct liballoc_minor ) + min->size); + + new_min->magic = LIBALLOC_MAGIC; + new_min->next = min->next; + new_min->prev = min; + new_min->size = size; + new_min->req_size = req_size; + new_min->block = maj; + min->next->prev = new_min; + min->next = new_min; + maj->usage += size + sizeof( struct liballoc_minor ); + + l_inuse += size; + + p = (void*)((uintptr_t)new_min + sizeof( struct liballoc_minor )); + ALIGN( p ); + + + #ifdef DEBUG + printf( "CASE 4.2: returning %x\n", p); + FLUSH(); + #endif + + liballoc_unlock(); // release the lock + return p; + } + } // min->next != NULL + + min = min->next; + } // while min != NULL ... + + +#endif + +#ifdef USE_CASE5 + + // CASE 5: Block full! Ensure next block and loop. + if ( maj->next == NULL ) + { + #ifdef DEBUG + printf( "CASE 5: block full\n"); + FLUSH(); + #endif + + if ( startedBet == 1 ) + { + maj = l_memRoot; + startedBet = 0; + continue; + } + + // we've run out. we need more... + maj->next = allocate_new_page( size ); // next one guaranteed to be okay + if ( maj->next == NULL ) break; // uh oh, no more memory..... + maj->next->prev = maj; + + } + +#endif + + maj = maj->next; + } // while (maj != NULL) + + + + liballoc_unlock(); // release the lock + + #ifdef DEBUG + printf( "All cases exhausted. No memory available.\n"); + FLUSH(); + #endif + #if defined DEBUG || defined INFO + printf( "liballoc: WARNING: PREFIX(malloc)( %i ) returning NULL.\n", size); + liballoc_dump(); + FLUSH(); + #endif + return NULL; +} + + + + + + + + + +void PREFIX(free)(void *ptr) +{ + struct liballoc_minor *min; + struct liballoc_major *maj; + + if ( ptr == NULL ) + { + l_warningCount += 1; + #if defined DEBUG || defined INFO + printf( "liballoc: WARNING: PREFIX(free)( NULL ) called from %x\n", + __builtin_return_address(0) ); + FLUSH(); + #endif + return; + } + + UNALIGN( ptr ); + + liballoc_lock(); // lockit + + + min = (struct liballoc_minor*)((uintptr_t)ptr - sizeof( struct liballoc_minor )); + + + if ( min->magic != LIBALLOC_MAGIC ) + { + l_errorCount += 1; + + // Check for overrun errors. For all bytes of LIBALLOC_MAGIC + if ( + ((min->magic & 0xFFFFFF) == (LIBALLOC_MAGIC & 0xFFFFFF)) || + ((min->magic & 0xFFFF) == (LIBALLOC_MAGIC & 0xFFFF)) || + ((min->magic & 0xFF) == (LIBALLOC_MAGIC & 0xFF)) + ) + { + l_possibleOverruns += 1; + #if defined DEBUG || defined INFO + printf( "liballoc: ERROR: Possible 1-3 byte overrun for magic %x != %x\n", + min->magic, + LIBALLOC_MAGIC ); + FLUSH(); + #endif + } + + + if ( min->magic == LIBALLOC_DEAD ) + { + #if defined DEBUG || defined INFO + printf( "liballoc: ERROR: multiple PREFIX(free)() attempt on %x from %x.\n", + ptr, + __builtin_return_address(0) ); + FLUSH(); + #endif + } + else + { + #if defined DEBUG || defined INFO + printf( "liballoc: ERROR: Bad PREFIX(free)( %x ) called from %x\n", + ptr, + __builtin_return_address(0) ); + FLUSH(); + #endif + } + + // being lied to... + liballoc_unlock(); // release the lock + return; + } + + #ifdef DEBUG + printf( "liballoc: %x PREFIX(free)( %x ): ", + __builtin_return_address( 0 ), + ptr ); + FLUSH(); + #endif + + + maj = min->block; + + l_inuse -= min->size; + + maj->usage -= (min->size + sizeof( struct liballoc_minor )); + min->magic = LIBALLOC_DEAD; // No mojo. + + if ( min->next != NULL ) min->next->prev = min->prev; + if ( min->prev != NULL ) min->prev->next = min->next; + + if ( min->prev == NULL ) maj->first = min->next; + // Might empty the block. This was the first + // minor. + + + // We need to clean up after the majors now.... + + if ( maj->first == NULL ) // Block completely unused. + { + if ( l_memRoot == maj ) l_memRoot = maj->next; + if ( l_bestBet == maj ) l_bestBet = NULL; + if ( maj->prev != NULL ) maj->prev->next = maj->next; + if ( maj->next != NULL ) maj->next->prev = maj->prev; + l_allocated -= maj->size; + + liballoc_free( maj, maj->pages ); + } + else + { + if ( l_bestBet != NULL ) + { + int bestSize = l_bestBet->size - l_bestBet->usage; + int majSize = maj->size - maj->usage; + + if ( majSize > bestSize ) l_bestBet = maj; + } + + } + + + #ifdef DEBUG + printf( "OK\n"); + FLUSH(); + #endif + + liballoc_unlock(); // release the lock +} + + + + + +void* PREFIX(calloc)(size_t nobj, size_t size) +{ + int real_size; + void *p; + + real_size = nobj * size; + + p = PREFIX(malloc)( real_size ); + + liballoc_memset( p, 0, real_size ); + + return p; +} + + + +void* PREFIX(realloc)(void *p, size_t size) +{ + void *ptr; + struct liballoc_minor *min; + unsigned int real_size; + + // Honour the case of size == 0 => free old and return NULL + if ( size == 0 ) + { + PREFIX(free)( p ); + return NULL; + } + + // In the case of a NULL pointer, return a simple malloc. + if ( p == NULL ) return PREFIX(malloc)( size ); + + // Unalign the pointer if required. + ptr = p; + UNALIGN(ptr); + + liballoc_lock(); // lockit + + min = (struct liballoc_minor*)((uintptr_t)ptr - sizeof( struct liballoc_minor )); + + // Ensure it is a valid structure. + if ( min->magic != LIBALLOC_MAGIC ) + { + l_errorCount += 1; + + // Check for overrun errors. For all bytes of LIBALLOC_MAGIC + if ( + ((min->magic & 0xFFFFFF) == (LIBALLOC_MAGIC & 0xFFFFFF)) || + ((min->magic & 0xFFFF) == (LIBALLOC_MAGIC & 0xFFFF)) || + ((min->magic & 0xFF) == (LIBALLOC_MAGIC & 0xFF)) + ) + { + l_possibleOverruns += 1; + #if defined DEBUG || defined INFO + printf( "liballoc: ERROR: Possible 1-3 byte overrun for magic %x != %x\n", + min->magic, + LIBALLOC_MAGIC ); + FLUSH(); + #endif + } + + + if ( min->magic == LIBALLOC_DEAD ) + { + #if defined DEBUG || defined INFO + printf( "liballoc: ERROR: multiple PREFIX(free)() attempt on %x from %x.\n", + ptr, + __builtin_return_address(0) ); + FLUSH(); + #endif + } + else + { + #if defined DEBUG || defined INFO + printf( "liballoc: ERROR: Bad PREFIX(free)( %x ) called from %x\n", + ptr, + __builtin_return_address(0) ); + FLUSH(); + #endif + } + + // being lied to... + liballoc_unlock(); // release the lock + return NULL; + } + + // Definitely a memory block. + + real_size = min->req_size; + + if ( real_size >= size ) + { + min->req_size = size; + liballoc_unlock(); + return p; + } + + liballoc_unlock(); + + // If we got here then we're reallocating to a block bigger than us. + ptr = PREFIX(malloc)( size ); // We need to allocate new memory + liballoc_memcpy( ptr, p, real_size ); + PREFIX(free)( p ); + + return ptr; +} + + + + diff --git a/extlib/liballoc/liballoc_1_1.h b/extlib/liballoc/liballoc_1_1.h new file mode 100644 index 0000000..e05c796 --- /dev/null +++ b/extlib/liballoc/liballoc_1_1.h @@ -0,0 +1,87 @@ +#ifndef _LIBALLOC_H +#define _LIBALLOC_H + +/** \defgroup ALLOCHOOKS liballoc hooks + * + * These are the OS specific functions which need to + * be implemented on any platform that the library + * is expected to work on. + */ + +/** @{ */ + +#include +#include + + +// If we are told to not define our own size_t, then we skip the define. +//#define _HAVE_UINTPTR_T +//typedef unsigned long uintptr_t; +typedef uint64_t uintptr_t; + +//This lets you prefix malloc and friends +#ifndef PREFIX +#define PREFIX(func) liballoc_k ## func +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + + +/** This function is supposed to lock the memory data structures. It + * could be as simple as disabling interrupts or acquiring a spinlock. + * It's up to you to decide. + * + * \return 0 if the lock was acquired successfully. Anything else is + * failure. + */ +extern int liballoc_lock(); + +/** This function unlocks what was previously locked by the liballoc_lock + * function. If it disabled interrupts, it enables interrupts. If it + * had acquiried a spinlock, it releases the spinlock. etc. + * + * \return 0 if the lock was successfully released. + */ +extern int liballoc_unlock(); + +/** This is the hook into the local system which allocates pages. It + * accepts an integer parameter which is the number of pages + * required. The page size was set up in the liballoc_init function. + * + * \return NULL if the pages were not allocated. + * \return A pointer to the allocated memory. + */ +extern void* liballoc_alloc(size_t); + +/** This frees previously allocated memory. The void* parameter passed + * to the function is the exact same value returned from a previous + * liballoc_alloc call. + * + * The integer value is the number of pages to free. + * + * \return 0 if the memory was successfully freed. + */ +extern int liballoc_free(void*,size_t); + + + + +extern void *PREFIX(malloc)(size_t); ///< The standard function. +extern void *PREFIX(realloc)(void *, size_t); ///< The standard function. +extern void *PREFIX(calloc)(size_t, size_t); ///< The standard function. +extern void PREFIX(free)(void *); ///< The standard function. + + +#ifdef __cplusplus +} +#endif + + +/** @} */ + +#endif + + diff --git a/extlib/libvterm/LICENSE b/extlib/libvterm/LICENSE new file mode 100644 index 0000000..0d05163 --- /dev/null +++ b/extlib/libvterm/LICENSE @@ -0,0 +1,23 @@ + + +The MIT License + +Copyright (c) 2008 Paul Evans + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/extlib/libvterm/encoding.c b/extlib/libvterm/encoding.c new file mode 100644 index 0000000..b329634 --- /dev/null +++ b/extlib/libvterm/encoding.c @@ -0,0 +1,223 @@ +#include "vterm_internal.h" + +#define UNICODE_INVALID 0xFFFD + +#if defined(DEBUG) && DEBUG > 1 +#define DEBUG_PRINT_UTF8 +#endif + +struct UTF8DecoderData { + // number of bytes remaining in this codepoint + int bytes_remaining; + + // number of bytes total in this codepoint once it's finished + // (for detecting overlongs) + int bytes_total; + + int this_cp; +}; + +static void init_utf8(VTermEncoding *enc, void *data_) { + struct UTF8DecoderData *data = data_; + + data->bytes_remaining = 0; + data->bytes_total = 0; +} + +static void decode_utf8(VTermEncoding *enc, void *data_, uint32_t cp[], int *cpi, int cplen, const char bytes[], size_t *pos, size_t bytelen) { + struct UTF8DecoderData *data = data_; + +#ifdef DEBUG_PRINT_UTF8 + printf("BEGIN UTF-8\n"); +#endif + + for (; *pos < bytelen && *cpi < cplen; (*pos)++) { + unsigned char c = bytes[*pos]; + +#ifdef DEBUG_PRINT_UTF8 + printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining); +#endif + + if (c < 0x20) // C0 + return; + + else if (c >= 0x20 && c < 0x7f) { + if (data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + cp[(*cpi)++] = c; +#ifdef DEBUG_PRINT_UTF8 + printf(" UTF-8 char: U+%04x\n", c); +#endif + data->bytes_remaining = 0; + } + + else if (c == 0x7f) // DEL + return; + + else if (c >= 0x80 && c < 0xc0) { + if (!data->bytes_remaining) { + cp[(*cpi)++] = UNICODE_INVALID; + continue; + } + + data->this_cp <<= 6; + data->this_cp |= c & 0x3f; + data->bytes_remaining--; + + if (!data->bytes_remaining) { +#ifdef DEBUG_PRINT_UTF8 + printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total); +#endif + // Check for overlong sequences + switch (data->bytes_total) { + case 2: + if (data->this_cp < 0x0080) + data->this_cp = UNICODE_INVALID; + break; + case 3: + if (data->this_cp < 0x0800) + data->this_cp = UNICODE_INVALID; + break; + case 4: + if (data->this_cp < 0x10000) + data->this_cp = UNICODE_INVALID; + break; + case 5: + if (data->this_cp < 0x200000) + data->this_cp = UNICODE_INVALID; + break; + case 6: + if (data->this_cp < 0x4000000) + data->this_cp = UNICODE_INVALID; + break; + } + // Now look for plain invalid ones + if ((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) || + data->this_cp == 0xFFFE || + data->this_cp == 0xFFFF) + data->this_cp = UNICODE_INVALID; +#ifdef DEBUG_PRINT_UTF8 + printf(" char: U+%04x\n", data->this_cp); +#endif + cp[(*cpi)++] = data->this_cp; + } + } + + else if (c >= 0xc0 && c < 0xe0) { + if (data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x1f; + data->bytes_total = 2; + data->bytes_remaining = 1; + } + + else if (c >= 0xe0 && c < 0xf0) { + if (data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x0f; + data->bytes_total = 3; + data->bytes_remaining = 2; + } + + else if (c >= 0xf0 && c < 0xf8) { + if (data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x07; + data->bytes_total = 4; + data->bytes_remaining = 3; + } + + else if (c >= 0xf8 && c < 0xfc) { + if (data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x03; + data->bytes_total = 5; + data->bytes_remaining = 4; + } + + else if (c >= 0xfc && c < 0xfe) { + if (data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x01; + data->bytes_total = 6; + data->bytes_remaining = 5; + } + + else { + cp[(*cpi)++] = UNICODE_INVALID; + } + } +} + +static VTermEncoding encoding_utf8 = { + .init = &init_utf8, + .decode = &decode_utf8, +}; + +static void decode_usascii(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen, const char bytes[], size_t *pos, size_t bytelen) { + int is_gr = bytes[*pos] & 0x80; + + for (; *pos < bytelen && *cpi < cplen; (*pos)++) { + unsigned char c = bytes[*pos] ^ is_gr; + + if (c < 0x20 || c == 0x7f || c >= 0x80) + return; + + cp[(*cpi)++] = c; + } +} + +static VTermEncoding encoding_usascii = { + .decode = &decode_usascii, +}; + +struct StaticTableEncoding { + const VTermEncoding enc; + const uint32_t chars[128]; +}; + +static void decode_table(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen, const char bytes[], size_t *pos, size_t bytelen) { + struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc; + int is_gr = bytes[*pos] & 0x80; + + for (; *pos < bytelen && *cpi < cplen; (*pos)++) { + unsigned char c = bytes[*pos] ^ is_gr; + + if (c < 0x20 || c == 0x7f || c >= 0x80) + return; + + if (table->chars[c]) + cp[(*cpi)++] = table->chars[c]; + else + cp[(*cpi)++] = c; + } +} + +#include "encoding/DECdrawing.inc" +#include "encoding/uk.inc" + +static struct { + VTermEncodingType type; + char designation; + VTermEncoding * enc; +} encodings[] = { + {ENC_UTF8, 'u', &encoding_utf8}, + {ENC_SINGLE_94, '0', (VTermEncoding *)&encoding_DECdrawing}, + {ENC_SINGLE_94, 'A', (VTermEncoding *)&encoding_uk}, + {ENC_SINGLE_94, 'B', &encoding_usascii}, + {0}, +}; + +/* This ought to be INTERNAL but isn't because it's used by unit testing */ +VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation) { + for (int i = 0; encodings[i].designation; i++) + if (encodings[i].type == type && encodings[i].designation == designation) + return encodings[i].enc; + return NULL; +} diff --git a/extlib/libvterm/encoding/DECdrawing.inc b/extlib/libvterm/encoding/DECdrawing.inc new file mode 100644 index 0000000..47093ed --- /dev/null +++ b/extlib/libvterm/encoding/DECdrawing.inc @@ -0,0 +1,36 @@ +static const struct StaticTableEncoding encoding_DECdrawing = { + { .decode = &decode_table }, + { + [0x60] = 0x25C6, + [0x61] = 0x2592, + [0x62] = 0x2409, + [0x63] = 0x240C, + [0x64] = 0x240D, + [0x65] = 0x240A, + [0x66] = 0x00B0, + [0x67] = 0x00B1, + [0x68] = 0x2424, + [0x69] = 0x240B, + [0x6a] = 0x2518, + [0x6b] = 0x2510, + [0x6c] = 0x250C, + [0x6d] = 0x2514, + [0x6e] = 0x253C, + [0x6f] = 0x23BA, + [0x70] = 0x23BB, + [0x71] = 0x2500, + [0x72] = 0x23BC, + [0x73] = 0x23BD, + [0x74] = 0x251C, + [0x75] = 0x2524, + [0x76] = 0x2534, + [0x77] = 0x252C, + [0x78] = 0x2502, + [0x79] = 0x2A7D, + [0x7a] = 0x2A7E, + [0x7b] = 0x03C0, + [0x7c] = 0x2260, + [0x7d] = 0x00A3, + [0x7e] = 0x00B7, + } +}; diff --git a/extlib/libvterm/encoding/uk.inc b/extlib/libvterm/encoding/uk.inc new file mode 100644 index 0000000..da1445d --- /dev/null +++ b/extlib/libvterm/encoding/uk.inc @@ -0,0 +1,6 @@ +static const struct StaticTableEncoding encoding_uk = { + { .decode = &decode_table }, + { + [0x23] = 0x00a3, + } +}; diff --git a/extlib/libvterm/fullwidth.inc b/extlib/libvterm/fullwidth.inc new file mode 100644 index 0000000..7ff142f --- /dev/null +++ b/extlib/libvterm/fullwidth.inc @@ -0,0 +1,104 @@ + { 0x1100, 0x115f }, + { 0x231a, 0x231b }, + { 0x2329, 0x232a }, + { 0x23e9, 0x23ec }, + { 0x23f0, 0x23f0 }, + { 0x23f3, 0x23f3 }, + { 0x25fd, 0x25fe }, + { 0x2614, 0x2615 }, + { 0x2648, 0x2653 }, + { 0x267f, 0x267f }, + { 0x2693, 0x2693 }, + { 0x26a1, 0x26a1 }, + { 0x26aa, 0x26ab }, + { 0x26bd, 0x26be }, + { 0x26c4, 0x26c5 }, + { 0x26ce, 0x26ce }, + { 0x26d4, 0x26d4 }, + { 0x26ea, 0x26ea }, + { 0x26f2, 0x26f3 }, + { 0x26f5, 0x26f5 }, + { 0x26fa, 0x26fa }, + { 0x26fd, 0x26fd }, + { 0x2705, 0x2705 }, + { 0x270a, 0x270b }, + { 0x2728, 0x2728 }, + { 0x274c, 0x274c }, + { 0x274e, 0x274e }, + { 0x2753, 0x2755 }, + { 0x2757, 0x2757 }, + { 0x2795, 0x2797 }, + { 0x27b0, 0x27b0 }, + { 0x27bf, 0x27bf }, + { 0x2b1b, 0x2b1c }, + { 0x2b50, 0x2b50 }, + { 0x2b55, 0x2b55 }, + { 0x2e80, 0x2e99 }, + { 0x2e9b, 0x2ef3 }, + { 0x2f00, 0x2fd5 }, + { 0x2ff0, 0x2ffb }, + { 0x3000, 0x303e }, + { 0x3041, 0x3096 }, + { 0x3099, 0x30ff }, + { 0x3105, 0x312d }, + { 0x3131, 0x318e }, + { 0x3190, 0x31ba }, + { 0x31c0, 0x31e3 }, + { 0x31f0, 0x321e }, + { 0x3220, 0x3247 }, + { 0x3250, 0x32fe }, + { 0x3300, 0x4dbf }, + { 0x4e00, 0xa48c }, + { 0xa490, 0xa4c6 }, + { 0xa960, 0xa97c }, + { 0xac00, 0xd7a3 }, + { 0xf900, 0xfaff }, + { 0xfe10, 0xfe19 }, + { 0xfe30, 0xfe52 }, + { 0xfe54, 0xfe66 }, + { 0xfe68, 0xfe6b }, + { 0xff01, 0xff60 }, + { 0xffe0, 0xffe6 }, + { 0x16fe0, 0x16fe0 }, + { 0x17000, 0x187ec }, + { 0x18800, 0x18af2 }, + { 0x1b000, 0x1b001 }, + { 0x1f004, 0x1f004 }, + { 0x1f0cf, 0x1f0cf }, + { 0x1f18e, 0x1f18e }, + { 0x1f191, 0x1f19a }, + { 0x1f200, 0x1f202 }, + { 0x1f210, 0x1f23b }, + { 0x1f240, 0x1f248 }, + { 0x1f250, 0x1f251 }, + { 0x1f300, 0x1f320 }, + { 0x1f32d, 0x1f335 }, + { 0x1f337, 0x1f37c }, + { 0x1f37e, 0x1f393 }, + { 0x1f3a0, 0x1f3ca }, + { 0x1f3cf, 0x1f3d3 }, + { 0x1f3e0, 0x1f3f0 }, + { 0x1f3f4, 0x1f3f4 }, + { 0x1f3f8, 0x1f43e }, + { 0x1f440, 0x1f440 }, + { 0x1f442, 0x1f4fc }, + { 0x1f4ff, 0x1f53d }, + { 0x1f54b, 0x1f54e }, + { 0x1f550, 0x1f567 }, + { 0x1f57a, 0x1f57a }, + { 0x1f595, 0x1f596 }, + { 0x1f5a4, 0x1f5a4 }, + { 0x1f5fb, 0x1f64f }, + { 0x1f680, 0x1f6c5 }, + { 0x1f6cc, 0x1f6cc }, + { 0x1f6d0, 0x1f6d2 }, + { 0x1f6eb, 0x1f6ec }, + { 0x1f6f4, 0x1f6f6 }, + { 0x1f910, 0x1f91e }, + { 0x1f920, 0x1f927 }, + { 0x1f930, 0x1f930 }, + { 0x1f933, 0x1f93e }, + { 0x1f940, 0x1f94b }, + { 0x1f950, 0x1f95e }, + { 0x1f980, 0x1f991 }, + { 0x1f9c0, 0x1f9c0 }, diff --git a/extlib/libvterm/keyboard.c b/extlib/libvterm/keyboard.c new file mode 100644 index 0000000..3deac16 --- /dev/null +++ b/extlib/libvterm/keyboard.c @@ -0,0 +1,228 @@ +#include "vterm_internal.h" + +#include + +#include "utf8.h" + +void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod) { + /* The shift modifier is never important for Unicode characters + * apart from Space + */ + if (c != ' ') + mod &= ~VTERM_MOD_SHIFT; + + if (mod == 0) { + // Normal text - ignore just shift + char str[6]; + int seqlen = fill_utf8(c, str); + vterm_push_output_bytes(vt, str, seqlen); + return; + } + + int needs_CSIu; + switch (c) { + /* Special Ctrl- letters that can't be represented elsewise */ + case 'i': + case 'j': + case 'm': + case '[': + needs_CSIu = 1; + break; + /* Ctrl-\ ] ^ _ don't need CSUu */ + case '\\': + case ']': + case '^': + case '_': + needs_CSIu = 0; + break; + /* Shift-space needs CSIu */ + case ' ': + needs_CSIu = !!(mod & VTERM_MOD_SHIFT); + break; + /* All other characters needs CSIu except for letters a-z */ + default: + needs_CSIu = (c < 'a' || c > 'z'); + } + + /* ALT we can just prefix with ESC; anything else requires CSI u */ + if (needs_CSIu && (mod & ~VTERM_MOD_ALT)) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod + 1); + return; + } + + if (mod & VTERM_MOD_CTRL) + c &= 0x1f; + + vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c); +} + +typedef struct { + enum { + KEYCODE_NONE, + KEYCODE_LITERAL, + KEYCODE_TAB, + KEYCODE_ENTER, + KEYCODE_SS3, + KEYCODE_CSI, + KEYCODE_CSI_CURSOR, + KEYCODE_CSINUM, + KEYCODE_KEYPAD, + } type; + char literal; + int csinum; +} keycodes_s; + +static keycodes_s keycodes[] = { + {KEYCODE_NONE}, // NONE + + {KEYCODE_ENTER, '\r'}, // ENTER + {KEYCODE_TAB, '\t'}, // TAB + {KEYCODE_LITERAL, '\x7f'}, // BACKSPACE == ASCII DEL + {KEYCODE_LITERAL, '\x1b'}, // ESCAPE + + {KEYCODE_CSI_CURSOR, 'A'}, // UP + {KEYCODE_CSI_CURSOR, 'B'}, // DOWN + {KEYCODE_CSI_CURSOR, 'D'}, // LEFT + {KEYCODE_CSI_CURSOR, 'C'}, // RIGHT + + {KEYCODE_CSINUM, '~', 2}, // INS + {KEYCODE_CSINUM, '~', 3}, // DEL + {KEYCODE_CSI_CURSOR, 'H'}, // HOME + {KEYCODE_CSI_CURSOR, 'F'}, // END + {KEYCODE_CSINUM, '~', 5}, // PAGEUP + {KEYCODE_CSINUM, '~', 6}, // PAGEDOWN +}; + +static keycodes_s keycodes_fn[] = { + {KEYCODE_NONE}, // F0 - shouldn't happen + {KEYCODE_SS3, 'P'}, // F1 + {KEYCODE_SS3, 'Q'}, // F2 + {KEYCODE_SS3, 'R'}, // F3 + {KEYCODE_SS3, 'S'}, // F4 + {KEYCODE_CSINUM, '~', 15}, // F5 + {KEYCODE_CSINUM, '~', 17}, // F6 + {KEYCODE_CSINUM, '~', 18}, // F7 + {KEYCODE_CSINUM, '~', 19}, // F8 + {KEYCODE_CSINUM, '~', 20}, // F9 + {KEYCODE_CSINUM, '~', 21}, // F10 + {KEYCODE_CSINUM, '~', 23}, // F11 + {KEYCODE_CSINUM, '~', 24}, // F12 +}; + +static keycodes_s keycodes_kp[] = { + {KEYCODE_KEYPAD, '0', 'p'}, // KP_0 + {KEYCODE_KEYPAD, '1', 'q'}, // KP_1 + {KEYCODE_KEYPAD, '2', 'r'}, // KP_2 + {KEYCODE_KEYPAD, '3', 's'}, // KP_3 + {KEYCODE_KEYPAD, '4', 't'}, // KP_4 + {KEYCODE_KEYPAD, '5', 'u'}, // KP_5 + {KEYCODE_KEYPAD, '6', 'v'}, // KP_6 + {KEYCODE_KEYPAD, '7', 'w'}, // KP_7 + {KEYCODE_KEYPAD, '8', 'x'}, // KP_8 + {KEYCODE_KEYPAD, '9', 'y'}, // KP_9 + {KEYCODE_KEYPAD, '*', 'j'}, // KP_MULT + {KEYCODE_KEYPAD, '+', 'k'}, // KP_PLUS + {KEYCODE_KEYPAD, ',', 'l'}, // KP_COMMA + {KEYCODE_KEYPAD, '-', 'm'}, // KP_MINUS + {KEYCODE_KEYPAD, '.', 'n'}, // KP_PERIOD + {KEYCODE_KEYPAD, '/', 'o'}, // KP_DIVIDE + {KEYCODE_KEYPAD, '\n', 'M'}, // KP_ENTER + {KEYCODE_KEYPAD, '=', 'X'}, // KP_EQUAL +}; + +void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) { + if (key == VTERM_KEY_NONE) + return; + + keycodes_s k; + if (key < VTERM_KEY_FUNCTION_0) { + if (key >= sizeof(keycodes) / sizeof(keycodes[0])) + return; + k = keycodes[key]; + } else if (key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) { + if ((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn) / sizeof(keycodes_fn[0])) + return; + k = keycodes_fn[key - VTERM_KEY_FUNCTION_0]; + } else if (key >= VTERM_KEY_KP_0) { + if ((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp) / sizeof(keycodes_kp[0])) + return; + k = keycodes_kp[key - VTERM_KEY_KP_0]; + } + + switch (k.type) { + case KEYCODE_NONE: + break; + + case KEYCODE_TAB: + /* Shift-Tab is CSI Z but plain Tab is 0x09 */ + if (mod == VTERM_MOD_SHIFT) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z"); + else if (mod & VTERM_MOD_SHIFT) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod + 1); + else + goto case_LITERAL; + break; + + case KEYCODE_ENTER: + /* Enter is CRLF in newline mode, but just LF in linefeed */ + if (vt->state->mode.newline) + vterm_push_output_sprintf(vt, "\r\n"); + else + goto case_LITERAL; + break; + + case KEYCODE_LITERAL: + case_LITERAL: + if (mod & (VTERM_MOD_SHIFT | VTERM_MOD_CTRL)) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod + 1); + else + vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal); + break; + + case KEYCODE_SS3: + case_SS3: + if (mod == 0) + vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal); + else + goto case_CSI; + break; + + case KEYCODE_CSI: + case_CSI: + if (mod == 0) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal); + else + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal); + break; + + case KEYCODE_CSINUM: + if (mod == 0) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal); + else + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal); + break; + + case KEYCODE_CSI_CURSOR: + if (vt->state->mode.cursor) + goto case_SS3; + else + goto case_CSI; + + case KEYCODE_KEYPAD: + if (vt->state->mode.keypad) { + k.literal = k.csinum; + goto case_SS3; + } else + goto case_LITERAL; + } +} + +void vterm_keyboard_start_paste(VTerm *vt) { + if (vt->state->mode.bracketpaste) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~"); +} + +void vterm_keyboard_end_paste(VTerm *vt) { + if (vt->state->mode.bracketpaste) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~"); +} diff --git a/extlib/libvterm/mouse.c b/extlib/libvterm/mouse.c new file mode 100644 index 0000000..e1e9ad8 --- /dev/null +++ b/extlib/libvterm/mouse.c @@ -0,0 +1,89 @@ +#include "vterm_internal.h" + +#include "utf8.h" + +static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row) { + modifiers <<= 2; + + switch (state->mouse_protocol) { + case MOUSE_X10: + if (col + 0x21 > 0xff) + col = 0xff - 0x21; + if (row + 0x21 > 0xff) + row = 0xff - 0x21; + + if (!pressed) + code = 3; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c", (code | modifiers) + 0x20, col + 0x21, row + 0x21); + break; + + case MOUSE_UTF8: { + char utf8[18]; + size_t len = 0; + + if (!pressed) + code = 3; + + len += fill_utf8((code | modifiers) + 0x20, utf8 + len); + len += fill_utf8(col + 0x21, utf8 + len); + len += fill_utf8(row + 0x21, utf8 + len); + utf8[len] = 0; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8); + } break; + + case MOUSE_SGR: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c", code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm'); + break; + + case MOUSE_RXVT: + if (!pressed) + code = 3; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM", code | modifiers, col + 1, row + 1); + break; + } +} + +void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod) { + VTermState *state = vt->state; + + if (col == state->mouse_col && row == state->mouse_row) + return; + + state->mouse_col = col; + state->mouse_row = row; + + if ((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) || + (state->mouse_flags & MOUSE_WANT_MOVE)) { + int button = state->mouse_buttons & 0x01 ? 1 : + state->mouse_buttons & 0x02 ? 2 : + state->mouse_buttons & 0x04 ? 3 : + 4; + output_mouse(state, button - 1 + 0x20, 1, mod, col, row); + } +} + +void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod) { + VTermState *state = vt->state; + + int old_buttons = state->mouse_buttons; + + if (button > 0 && button <= 3) { + if (pressed) + state->mouse_buttons |= (1 << (button - 1)); + else + state->mouse_buttons &= ~(1 << (button - 1)); + } + + /* Most of the time we don't get button releases from 4/5 */ + if (state->mouse_buttons == old_buttons && button < 4) + return; + + if (button < 4) { + output_mouse(state, button - 1, pressed, mod, state->mouse_col, state->mouse_row); + } else if (button < 6) { + output_mouse(state, button - 4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row); + } +} diff --git a/extlib/libvterm/parser.c b/extlib/libvterm/parser.c new file mode 100644 index 0000000..f91d917 --- /dev/null +++ b/extlib/libvterm/parser.c @@ -0,0 +1,329 @@ +#include "vterm_internal.h" + +#include +#include + +#include "../../runtime/printf.h" + +#undef DEBUG_PARSER + +static bool is_intermed(unsigned char c) { + return c >= 0x20 && c <= 0x2f; +} + +static void do_control(VTerm *vt, unsigned char control) { + if (vt->parser.callbacks && vt->parser.callbacks->control) + if ((*vt->parser.callbacks->control)(control, vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled control 0x%02x\n", control); +} + +static void do_csi(VTerm *vt, char command) { +#ifdef DEBUG_PARSER + printf("Parsed CSI args as:\n", arglen, args); + printf(" leader: %s\n", vt->parser.csi_leader); + for (int argi = 0; argi < vt->parser.csi_argi; argi++) { + printf(" %lu", CSI_ARG(vt->parser.csi_args[argi])); + if (!CSI_ARG_HAS_MORE(vt->parser.csi_args[argi])) + printf("\n"); + printf(" intermed: %s\n", vt->parser.intermed); + } +#endif + + if (vt->parser.callbacks && vt->parser.callbacks->csi) + if ((*vt->parser.callbacks->csi)( + vt->parser.csi_leaderlen ? vt->parser.csi_leader : NULL, + vt->parser.csi_args, + vt->parser.csi_argi, + vt->parser.intermedlen ? vt->parser.intermed : NULL, + command, + vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled CSI %c\n", command); +} + +static void do_escape(VTerm *vt, char command) { + char seq[INTERMED_MAX + 1]; + + size_t len = vt->parser.intermedlen; + strncpy(seq, vt->parser.intermed, len); + seq[len++] = command; + seq[len] = 0; + + if (vt->parser.callbacks && vt->parser.callbacks->escape) + if ((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", command); +} + +static void append_strbuffer(VTerm *vt, const char *str, size_t len) { + if (len > vt->parser.strbuffer_len - vt->parser.strbuffer_cur) { + len = vt->parser.strbuffer_len - vt->parser.strbuffer_cur; + DEBUG_LOG("Truncating strbuffer preserve to %zd bytes\n", len); + } + + if (len > 0) { + strncpy(vt->parser.strbuffer + vt->parser.strbuffer_cur, str, len); + vt->parser.strbuffer_cur += len; + } +} + +static void start_string(VTerm *vt, VTermParserStringType type) { + vt->parser.stringtype = type; + + vt->parser.strbuffer_cur = 0; +} + +static void more_string(VTerm *vt, const char *str, size_t len) { + append_strbuffer(vt, str, len); +} + +static void done_string(VTerm *vt, const char *str, size_t len) { + if (vt->parser.strbuffer_cur) { + if (str) + append_strbuffer(vt, str, len); + + str = vt->parser.strbuffer; + len = vt->parser.strbuffer_cur; + } else if (!str) { + DEBUG_LOG("parser.c: TODO: No strbuffer _and_ no final fragment???\n"); + len = 0; + } + + switch (vt->parser.stringtype) { + case VTERM_PARSER_OSC: + if (vt->parser.callbacks && vt->parser.callbacks->osc) + if ((*vt->parser.callbacks->osc)(str, len, vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled OSC %.*s\n", (int)len, str); + return; + + case VTERM_PARSER_DCS: + if (vt->parser.callbacks && vt->parser.callbacks->dcs) + if ((*vt->parser.callbacks->dcs)(str, len, vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)len, str); + return; + + case VTERM_N_PARSER_TYPES: + return; + } +} + +size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len) { + size_t pos = 0; + const char *string_start; + + switch (vt->parser.state) { + case NORMAL: + case CSI_LEADER: + case CSI_ARGS: + case CSI_INTERMED: + case ESC: + string_start = NULL; + break; + case STRING: + case ESC_IN_STRING: + string_start = bytes; + break; + } + +#define ENTER_STRING_STATE(st) \ + do { \ + vt->parser.state = STRING; \ + string_start = bytes + pos + 1; \ + } while (0) +#define ENTER_STATE(st) \ + do { \ + vt->parser.state = st; \ + string_start = NULL; \ + } while (0) +#define ENTER_NORMAL_STATE() ENTER_STATE(NORMAL) + + for (; pos < len; pos++) { + unsigned char c = bytes[pos]; + + if (c == 0x00 || c == 0x7f) { // NUL, DEL + if (vt->parser.state >= STRING) { + more_string(vt, string_start, bytes + pos - string_start); + string_start = bytes + pos + 1; + } + continue; + } + if (c == 0x18 || c == 0x1a) { // CAN, SUB + ENTER_NORMAL_STATE(); + continue; + } else if (c == 0x1b) { // ESC + vt->parser.intermedlen = 0; + if (vt->parser.state == STRING) + vt->parser.state = ESC_IN_STRING; + else + ENTER_STATE(ESC); + continue; + } else if (c == 0x07 && // BEL, can stand for ST in OSC or DCS state + vt->parser.state == STRING) { + // fallthrough + } else if (c < 0x20) { // other C0 + if (vt->parser.state >= STRING) + more_string(vt, string_start, bytes + pos - string_start); + do_control(vt, c); + if (vt->parser.state >= STRING) + string_start = bytes + pos + 1; + continue; + } + // else fallthrough + + switch (vt->parser.state) { + case ESC_IN_STRING: + if (c == 0x5c) { // ST + vt->parser.state = STRING; + done_string(vt, string_start, bytes + pos - string_start - 1); + ENTER_NORMAL_STATE(); + break; + } + vt->parser.state = ESC; + // else fallthrough + + case ESC: + switch (c) { + case 0x50: // DCS + start_string(vt, VTERM_PARSER_DCS); + ENTER_STRING_STATE(); + break; + case 0x5b: // CSI + vt->parser.csi_leaderlen = 0; + ENTER_STATE(CSI_LEADER); + break; + case 0x5d: // OSC + start_string(vt, VTERM_PARSER_OSC); + ENTER_STRING_STATE(); + break; + default: + if (is_intermed(c)) { + if (vt->parser.intermedlen < INTERMED_MAX - 1) + vt->parser.intermed[vt->parser.intermedlen++] = c; + } else if (!vt->parser.intermedlen && c >= 0x40 && c < 0x60) { + do_control(vt, c + 0x40); + ENTER_NORMAL_STATE(); + } else if (c >= 0x30 && c < 0x7f) { + do_escape(vt, c); + ENTER_NORMAL_STATE(); + } else { + DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c); + } + } + break; + + case CSI_LEADER: + /* Extract leader bytes 0x3c to 0x3f */ + if (c >= 0x3c && c <= 0x3f) { + if (vt->parser.csi_leaderlen < CSI_LEADER_MAX - 1) + vt->parser.csi_leader[vt->parser.csi_leaderlen++] = c; + break; + } + + /* else fallthrough */ + vt->parser.csi_leader[vt->parser.csi_leaderlen] = 0; + + vt->parser.csi_argi = 0; + vt->parser.csi_args[0] = CSI_ARG_MISSING; + vt->parser.state = CSI_ARGS; + + /* fallthrough */ + case CSI_ARGS: + /* Numerical value of argument */ + if (c >= '0' && c <= '9') { + if (vt->parser.csi_args[vt->parser.csi_argi] == CSI_ARG_MISSING) + vt->parser.csi_args[vt->parser.csi_argi] = 0; + vt->parser.csi_args[vt->parser.csi_argi] *= 10; + vt->parser.csi_args[vt->parser.csi_argi] += c - '0'; + break; + } + if (c == ':') { + vt->parser.csi_args[vt->parser.csi_argi] |= CSI_ARG_FLAG_MORE; + c = ';'; + } + if (c == ';') { + vt->parser.csi_argi++; + vt->parser.csi_args[vt->parser.csi_argi] = CSI_ARG_MISSING; + break; + } + + /* else fallthrough */ + vt->parser.csi_argi++; + vt->parser.intermedlen = 0; + vt->parser.state = CSI_INTERMED; + case CSI_INTERMED: + if (is_intermed(c)) { + if (vt->parser.intermedlen < INTERMED_MAX - 1) + vt->parser.intermed[vt->parser.intermedlen++] = c; + break; + } else if (c == 0x1b) { + /* ESC in CSI cancels */ + } else if (c >= 0x40 && c <= 0x7e) { + vt->parser.intermed[vt->parser.intermedlen] = 0; + do_csi(vt, c); + } + /* else was invalid CSI */ + + ENTER_NORMAL_STATE(); + break; + + case STRING: + if (c == 0x07 || (c == 0x9c && !vt->mode.utf8)) { + done_string(vt, string_start, bytes + pos - string_start); + ENTER_NORMAL_STATE(); + } + break; + + case NORMAL: + if (c >= 0x80 && c < 0xa0 && !vt->mode.utf8) { + switch (c) { + case 0x90: // DCS + start_string(vt, VTERM_PARSER_DCS); + ENTER_STRING_STATE(); + break; + case 0x9b: // CSI + ENTER_STATE(CSI_LEADER); + break; + case 0x9d: // OSC + start_string(vt, VTERM_PARSER_OSC); + ENTER_STRING_STATE(); + break; + default: + do_control(vt, c); + break; + } + } else { + size_t eaten = 0; + if (vt->parser.callbacks && vt->parser.callbacks->text) + eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata); + + if (!eaten) { + DEBUG_LOG("libvterm: Text callback did not consume any input\n"); + /* force it to make progress */ + eaten = 1; + } + + pos += (eaten - 1); // we'll ++ it again in a moment + } + break; + } + } + + return len; +} + +void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user) { + vt->parser.callbacks = callbacks; + vt->parser.cbdata = user; +} + +void *vterm_parser_get_cbdata(VTerm *vt) { + return vt->parser.cbdata; +} diff --git a/extlib/libvterm/pen.c b/extlib/libvterm/pen.c new file mode 100644 index 0000000..426a4fa --- /dev/null +++ b/extlib/libvterm/pen.c @@ -0,0 +1,586 @@ +#include "vterm_internal.h" + +#include + +/** + * Structure used to store RGB triples without the additional metadata stored in + * VTermColor. + */ +typedef struct { + uint8_t red, green, blue; +} VTermRGB; + +static const VTermRGB ansi_colors[] = { + /* R G B */ + {0, 0, 0}, // black + {224, 0, 0}, // red + {0, 224, 0}, // green + {224, 224, 0}, // yellow + {0, 0, 224}, // blue + {224, 0, 224}, // magenta + {0, 224, 224}, // cyan + {224, 224, 224}, // white == light grey + + // high intensity + {128, 128, 128}, // black + {255, 64, 64}, // red + {64, 255, 64}, // green + {255, 255, 64}, // yellow + {64, 64, 255}, // blue + {255, 64, 255}, // magenta + {64, 255, 255}, // cyan + {255, 255, 255}, // white for real +}; + +static int ramp6[] = { + 0x00, + 0x33, + 0x66, + 0x99, + 0xCC, + 0xFF, +}; + +static int ramp24[] = { + 0x00, + 0x0B, + 0x16, + 0x21, + 0x2C, + 0x37, + 0x42, + 0x4D, + 0x58, + 0x63, + 0x6E, + 0x79, + 0x85, + 0x90, + 0x9B, + 0xA6, + 0xB1, + 0xBC, + 0xC7, + 0xD2, + 0xDD, + 0xE8, + 0xF3, + 0xFF, +}; + +static void lookup_default_colour_ansi(long idx, VTermColor *col) { + if (idx >= 0 && idx < 16) { + vterm_color_rgb( + col, + ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue); + } +} + +static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col) { + if (index >= 0 && index < 16) { + *col = state->colors[index]; + return true; + } + + return false; +} + +static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col) { + if (index >= 0 && index < 16) { + // Normal 8 colours or high intensity - parse as palette 0 + return lookup_colour_ansi(state, index, col); + } else if (index >= 16 && index < 232) { + // 216-colour cube + index -= 16; + + vterm_color_rgb(col, ramp6[index / 6 / 6 % 6], ramp6[index / 6 % 6], ramp6[index % 6]); + + return true; + } else if (index >= 232 && index < 256) { + // 24 greyscales + index -= 232; + + vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]); + + return true; + } + + return false; +} + +static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col) { + switch (palette) { + case 2: // RGB mode - 3 args contain colour values directly + if (argcount < 3) + return argcount; + + vterm_color_rgb(col, CSI_ARG(args[0]), CSI_ARG(args[1]), CSI_ARG(args[2])); + + return 3; + + case 5: // XTerm 256-colour mode + if (!argcount || CSI_ARG_IS_MISSING(args[0])) { + return argcount ? 1 : 0; + } + + vterm_color_indexed(col, args[0]); + + return argcount ? 1 : 0; + + default: + DEBUG_LOG("Unrecognised colour palette %d\n", palette); + return 0; + } +} + +// Some conveniences + +static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) { +#ifdef DEBUG + if (type != vterm_get_attr_type(attr)) { + DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", attr, vterm_get_attr_type(attr), type); + return; + } +#endif + if (state->callbacks && state->callbacks->setpenattr) + (*state->callbacks->setpenattr)(attr, val, state->cbdata); +} + +static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean) { + VTermValue val = {.boolean = boolean}; + setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val); +} + +static void setpenattr_int(VTermState *state, VTermAttr attr, int number) { + VTermValue val = {.number = number}; + setpenattr(state, attr, VTERM_VALUETYPE_INT, &val); +} + +static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color) { + VTermValue val = {.color = color}; + setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val); +} + +static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col) { + VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg; + + vterm_color_indexed(colp, col); + + setpenattr_col(state, attr, *colp); +} + +INTERNAL void vterm_state_newpen(VTermState *state) { + // 90% grey so that pure white is brighter + vterm_color_rgb(&state->default_fg, 240, 240, 240); + vterm_color_rgb(&state->default_bg, 0, 0, 0); + vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg); + + for (int col = 0; col < 16; col++) + lookup_default_colour_ansi(col, &state->colors[col]); +} + +INTERNAL void vterm_state_resetpen(VTermState *state) { + state->pen.bold = 0; + setpenattr_bool(state, VTERM_ATTR_BOLD, 0); + state->pen.underline = 0; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); + state->pen.italic = 0; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); + state->pen.blink = 0; + setpenattr_bool(state, VTERM_ATTR_BLINK, 0); + state->pen.reverse = 0; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); + state->pen.strike = 0; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); + state->pen.font = 0; + setpenattr_int(state, VTERM_ATTR_FONT, 0); + + state->pen.fg = state->default_fg; + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg); + state->pen.bg = state->default_bg; + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg); +} + +INTERNAL void vterm_state_savepen(VTermState *state, int save) { + if (save) { + state->saved.pen = state->pen; + } else { + state->pen = state->saved.pen; + + setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold); + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic); + setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink); + setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse); + setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike); + setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + } +} + +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b) { + /* First make sure that the two colours are of the same type (RGB/Indexed) */ + if (a->type != b->type) { + return false; + } + + /* Depending on the type inspect the corresponding members */ + if (VTERM_COLOR_IS_INDEXED(a)) { + return a->indexed.idx == b->indexed.idx; + } else if (VTERM_COLOR_IS_RGB(a)) { + return (a->rgb.red == b->rgb.red) && (a->rgb.green == b->rgb.green) && (a->rgb.blue == b->rgb.blue); + } + + return 0; +} + +void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg) { + *default_fg = state->default_fg; + *default_bg = state->default_bg; +} + +void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col) { + lookup_colour_palette(state, index, col); +} + +void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg) { + /* Copy the given colors */ + state->default_fg = *default_fg; + state->default_bg = *default_bg; + + /* Make sure the correct type flags are set */ + state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK) | VTERM_COLOR_DEFAULT_FG; + state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK) | VTERM_COLOR_DEFAULT_BG; +} + +void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col) { + if (index >= 0 && index < 16) + state->colors[index] = *col; +} + +void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col) { + if (VTERM_COLOR_IS_INDEXED(col)) { /* Convert indexed colors to RGB */ + lookup_colour_palette(state, col->indexed.idx, col); + } + col->type &= VTERM_COLOR_TYPE_MASK; /* Reset any metadata but the type */ +} + +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright) { + state->bold_is_highbright = bold_is_highbright; +} + +INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount) { + // SGR - ECMA-48 8.3.117 + + int argi = 0; + int value; + + while (argi < argcount) { + // This logic is easier to do 'done' backwards; set it true, and make it + // false again in the 'default' case + int done = 1; + + long arg; + switch (arg = CSI_ARG(args[argi])) { + case CSI_ARG_MISSING: + case 0: // Reset + vterm_state_resetpen(state); + break; + + case 1: { // Bold on + const VTermColor *fg = &state->pen.fg; + state->pen.bold = 1; + setpenattr_bool(state, VTERM_ATTR_BOLD, 1); + if (!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 && state->bold_is_highbright) + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0)); + break; + } + + case 3: // Italic on + state->pen.italic = 1; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 1); + break; + + case 4: // Underline + state->pen.underline = VTERM_UNDERLINE_SINGLE; + if (CSI_ARG_HAS_MORE(args[argi])) { + argi++; + switch (CSI_ARG(args[argi])) { + case 0: + state->pen.underline = 0; + break; + case 1: + state->pen.underline = VTERM_UNDERLINE_SINGLE; + break; + case 2: + state->pen.underline = VTERM_UNDERLINE_DOUBLE; + break; + case 3: + state->pen.underline = VTERM_UNDERLINE_CURLY; + break; + } + } + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + break; + + case 5: // Blink + state->pen.blink = 1; + setpenattr_bool(state, VTERM_ATTR_BLINK, 1); + break; + + case 7: // Reverse on + state->pen.reverse = 1; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 1); + break; + + case 9: // Strikethrough on + state->pen.strike = 1; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 1); + break; + + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: // Select font + state->pen.font = CSI_ARG(args[argi]) - 10; + setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); + break; + + case 21: // Underline double + state->pen.underline = VTERM_UNDERLINE_DOUBLE; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + break; + + case 22: // Bold off + state->pen.bold = 0; + setpenattr_bool(state, VTERM_ATTR_BOLD, 0); + break; + + case 23: // Italic and Gothic (currently unsupported) off + state->pen.italic = 0; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); + break; + + case 24: // Underline off + state->pen.underline = 0; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); + break; + + case 25: // Blink off + state->pen.blink = 0; + setpenattr_bool(state, VTERM_ATTR_BLINK, 0); + break; + + case 27: // Reverse off + state->pen.reverse = 0; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); + break; + + case 29: // Strikethrough off + state->pen.strike = 0; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); + break; + + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: // Foreground colour palette + value = CSI_ARG(args[argi]) - 30; + if (state->pen.bold && state->bold_is_highbright) + value += 8; + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); + break; + + case 38: // Foreground colour alternative palette + if (argcount - argi < 1) + return; + argi += 1 + lookup_colour(state, CSI_ARG(args[argi + 1]), args + argi + 2, argcount - argi - 2, &state->pen.fg); + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + break; + + case 39: // Foreground colour default + state->pen.fg = state->default_fg; + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + break; + + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: // Background colour palette + value = CSI_ARG(args[argi]) - 40; + set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); + break; + + case 48: // Background colour alternative palette + if (argcount - argi < 1) + return; + argi += 1 + lookup_colour(state, CSI_ARG(args[argi + 1]), args + argi + 2, argcount - argi - 2, &state->pen.bg); + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + break; + + case 49: // Default background + state->pen.bg = state->default_bg; + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + break; + + case 90: + case 91: + case 92: + case 93: + case 94: + case 95: + case 96: + case 97: // Foreground colour high-intensity palette + value = CSI_ARG(args[argi]) - 90 + 8; + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); + break; + + case 100: + case 101: + case 102: + case 103: + case 104: + case 105: + case 106: + case 107: // Background colour high-intensity palette + value = CSI_ARG(args[argi]) - 100 + 8; + set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); + break; + + default: + done = 0; + break; + } + + if (!done) + DEBUG_LOG("libvterm: Unhandled CSI SGR %lu\n", arg); + + while (CSI_ARG_HAS_MORE(args[argi++])) + ; + } +} + +static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg) { + /* Do nothing if the given color is the default color */ + if ((fg && VTERM_COLOR_IS_DEFAULT_FG(col)) || + (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) { + return argi; + } + + /* Decide whether to send an indexed color or an RGB color */ + if (VTERM_COLOR_IS_INDEXED(col)) { + const uint8_t idx = col->indexed.idx; + if (idx < 8) { + args[argi++] = (idx + (fg ? 30 : 40)); + } else if (idx < 16) { + args[argi++] = (idx - 8 + (fg ? 90 : 100)); + } else { + args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); + args[argi++] = CSI_ARG_FLAG_MORE | 5; + args[argi++] = idx; + } + } else if (VTERM_COLOR_IS_RGB(col)) { + args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); + args[argi++] = CSI_ARG_FLAG_MORE | 2; + args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red; + args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green; + args[argi++] = col->rgb.blue; + } + return argi; +} + +INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount) { + int argi = 0; + + if (state->pen.bold) + args[argi++] = 1; + + if (state->pen.italic) + args[argi++] = 3; + + if (state->pen.underline == VTERM_UNDERLINE_SINGLE) + args[argi++] = 4; + if (state->pen.underline == VTERM_UNDERLINE_CURLY) + args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3; + + if (state->pen.blink) + args[argi++] = 5; + + if (state->pen.reverse) + args[argi++] = 7; + + if (state->pen.strike) + args[argi++] = 9; + + if (state->pen.font) + args[argi++] = 10 + state->pen.font; + + if (state->pen.underline == VTERM_UNDERLINE_DOUBLE) + args[argi++] = 21; + + argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true); + + argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false); + + return argi; +} + +int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val) { + switch (attr) { + case VTERM_ATTR_BOLD: + val->boolean = state->pen.bold; + return 1; + + case VTERM_ATTR_UNDERLINE: + val->number = state->pen.underline; + return 1; + + case VTERM_ATTR_ITALIC: + val->boolean = state->pen.italic; + return 1; + + case VTERM_ATTR_BLINK: + val->boolean = state->pen.blink; + return 1; + + case VTERM_ATTR_REVERSE: + val->boolean = state->pen.reverse; + return 1; + + case VTERM_ATTR_STRIKE: + val->boolean = state->pen.strike; + return 1; + + case VTERM_ATTR_FONT: + val->number = state->pen.font; + return 1; + + case VTERM_ATTR_FOREGROUND: + val->color = state->pen.fg; + return 1; + + case VTERM_ATTR_BACKGROUND: + val->color = state->pen.bg; + return 1; + + case VTERM_N_ATTRS: + return 0; + } + + return 0; +} diff --git a/extlib/libvterm/rect.h b/extlib/libvterm/rect.h new file mode 100644 index 0000000..2114f24 --- /dev/null +++ b/extlib/libvterm/rect.h @@ -0,0 +1,56 @@ +/* + * Some utility functions on VTermRect structures + */ + +#define STRFrect "(%d,%d-%d,%d)" +#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col + +/* Expand dst to contain src as well */ +static void rect_expand(VTermRect *dst, VTermRect *src) +{ + if(dst->start_row > src->start_row) dst->start_row = src->start_row; + if(dst->start_col > src->start_col) dst->start_col = src->start_col; + if(dst->end_row < src->end_row) dst->end_row = src->end_row; + if(dst->end_col < src->end_col) dst->end_col = src->end_col; +} + +/* Clip the dst to ensure it does not step outside of bounds */ +static void rect_clip(VTermRect *dst, VTermRect *bounds) +{ + if(dst->start_row < bounds->start_row) dst->start_row = bounds->start_row; + if(dst->start_col < bounds->start_col) dst->start_col = bounds->start_col; + if(dst->end_row > bounds->end_row) dst->end_row = bounds->end_row; + if(dst->end_col > bounds->end_col) dst->end_col = bounds->end_col; + /* Ensure it doesn't end up negatively-sized */ + if(dst->end_row < dst->start_row) dst->end_row = dst->start_row; + if(dst->end_col < dst->start_col) dst->end_col = dst->start_col; +} + +/* True if the two rectangles are equal */ +static int rect_equal(VTermRect *a, VTermRect *b) +{ + return (a->start_row == b->start_row) && + (a->start_col == b->start_col) && + (a->end_row == b->end_row) && + (a->end_col == b->end_col); +} + +/* True if small is contained entirely within big */ +static int rect_contains(VTermRect *big, VTermRect *small) +{ + if(small->start_row < big->start_row) return 0; + if(small->start_col < big->start_col) return 0; + if(small->end_row > big->end_row) return 0; + if(small->end_col > big->end_col) return 0; + return 1; +} + +/* True if the rectangles overlap at all */ +static int rect_intersects(VTermRect *a, VTermRect *b) +{ + if(a->start_row > b->end_row || b->start_row > a->end_row) + return 0; + if(a->start_col > b->end_col || b->start_col > a->end_col) + return 0; + return 1; +} diff --git a/extlib/libvterm/screen.c b/extlib/libvterm/screen.c new file mode 100644 index 0000000..9e94b39 --- /dev/null +++ b/extlib/libvterm/screen.c @@ -0,0 +1,877 @@ +#include "vterm_internal.h" + +#include +#include + +#include "rect.h" +#include "utf8.h" + +#define UNICODE_SPACE 0x20 +#define UNICODE_LINEFEED 0x0a + +/* State of the pen at some moment in time, also used in a cell */ +typedef struct +{ + /* After the bitfield */ + VTermColor fg, bg; + + unsigned int bold : 1; + unsigned int underline : 2; + unsigned int italic : 1; + unsigned int blink : 1; + unsigned int reverse : 1; + unsigned int strike : 1; + unsigned int font : 4; /* 0 to 9 */ + + /* Extra state storage that isn't strictly pen-related */ + unsigned int protected_cell : 1; + unsigned int dwl : 1; /* on a DECDWL or DECDHL line */ + unsigned int dhl : 2; /* on a DECDHL line (1=top 2=bottom) */ +} ScreenPen; + +/* Internal representation of a screen cell */ +typedef struct +{ + uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; + ScreenPen pen; +} ScreenCell; + +static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell); + +struct VTermScreen { + VTerm * vt; + VTermState *state; + + const VTermScreenCallbacks *callbacks; + void * cbdata; + + VTermDamageSize damage_merge; + /* start_row == -1 => no damage */ + VTermRect damaged; + VTermRect pending_scrollrect; + int pending_scroll_downward, pending_scroll_rightward; + + int rows; + int cols; + int global_reverse; + + /* Primary and Altscreen. buffers[1] is lazily allocated as needed */ + ScreenCell *buffers[2]; + + /* buffer will == buffers[0] or buffers[1], depending on altscreen */ + ScreenCell *buffer; + + /* buffer for a single screen row used in scrollback storage callbacks */ + VTermScreenCell *sb_buffer; + + ScreenPen pen; +}; + +static inline ScreenCell *getcell(const VTermScreen *screen, int row, int col) { + if (row < 0 || row >= screen->rows) + return NULL; + if (col < 0 || col >= screen->cols) + return NULL; + return screen->buffer + (screen->cols * row) + col; +} + +static ScreenCell *realloc_buffer(VTermScreen *screen, ScreenCell *buffer, int new_rows, int new_cols) { + ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols); + + for (int row = 0; row < new_rows; row++) { + for (int col = 0; col < new_cols; col++) { + ScreenCell *new_cell = new_buffer + row * new_cols + col; + + if (buffer && row < screen->rows && col < screen->cols) + *new_cell = buffer[row * screen->cols + col]; + else { + new_cell->chars[0] = 0; + new_cell->pen = screen->pen; + } + } + } + + if (buffer) + vterm_allocator_free(screen->vt, buffer); + + return new_buffer; +} + +static void damagerect(VTermScreen *screen, VTermRect rect) { + VTermRect emit; + + switch (screen->damage_merge) { + case VTERM_DAMAGE_CELL: + /* Always emit damage event */ + emit = rect; + break; + + case VTERM_DAMAGE_ROW: + /* Emit damage longer than one row. Try to merge with existing damage in + * the same row */ + if (rect.end_row > rect.start_row + 1) { + // Bigger than 1 line - flush existing, emit this + vterm_screen_flush_damage(screen); + emit = rect; + } else if (screen->damaged.start_row == -1) { + // None stored yet + screen->damaged = rect; + return; + } else if (rect.start_row == screen->damaged.start_row) { + // Merge with the stored line + if (screen->damaged.start_col > rect.start_col) + screen->damaged.start_col = rect.start_col; + if (screen->damaged.end_col < rect.end_col) + screen->damaged.end_col = rect.end_col; + return; + } else { + // Emit the currently stored line, store a new one + emit = screen->damaged; + screen->damaged = rect; + } + break; + + case VTERM_DAMAGE_SCREEN: + case VTERM_DAMAGE_SCROLL: + /* Never emit damage event */ + if (screen->damaged.start_row == -1) + screen->damaged = rect; + else { + rect_expand(&screen->damaged, &rect); + } + return; + + default: + DEBUG_LOG("TODO: Maybe merge damage for level %d\n", screen->damage_merge); + return; + } + + if (screen->callbacks && screen->callbacks->damage) + (*screen->callbacks->damage)(emit, screen->cbdata); +} + +static void damagescreen(VTermScreen *screen) { + VTermRect rect = { + .start_row = 0, + .end_row = screen->rows, + .start_col = 0, + .end_col = screen->cols, + }; + + damagerect(screen, rect); +} + +static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) { + VTermScreen *screen = user; + ScreenCell * cell = getcell(screen, pos.row, pos.col); + + if (!cell) + return 0; + + int i; + for (i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) { + cell->chars[i] = info->chars[i]; + cell->pen = screen->pen; + } + if (i < VTERM_MAX_CHARS_PER_CELL) + cell->chars[i] = 0; + + for (int col = 1; col < info->width; col++) + getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1; + + VTermRect rect = { + .start_row = pos.row, + .end_row = pos.row + 1, + .start_col = pos.col, + .end_col = pos.col + info->width, + }; + + cell->pen.protected_cell = info->protected_cell; + cell->pen.dwl = info->dwl; + cell->pen.dhl = info->dhl; + + damagerect(screen, rect); + + return 1; +} + +static int moverect_internal(VTermRect dest, VTermRect src, void *user) { + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->sb_pushline && + dest.start_row == 0 && dest.start_col == 0 && // starts top-left corner + dest.end_col == screen->cols && // full width + screen->buffer == screen->buffers[0]) { // not altscreen + VTermPos pos; + for (pos.row = 0; pos.row < src.start_row; pos.row++) { + for (pos.col = 0; pos.col < screen->cols; pos.col++) + vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col); + + (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata); + } + } + + int cols = src.end_col - src.start_col; + int downward = src.start_row - dest.start_row; + + int init_row, test_row, inc_row; + if (downward < 0) { + init_row = dest.end_row - 1; + test_row = dest.start_row - 1; + inc_row = -1; + } else { + init_row = dest.start_row; + test_row = dest.end_row; + inc_row = +1; + } + + for (int row = init_row; row != test_row; row += inc_row) + memmove(getcell(screen, row, dest.start_col), getcell(screen, row + downward, src.start_col), cols * sizeof(ScreenCell)); + + return 1; +} + +static int moverect_user(VTermRect dest, VTermRect src, void *user) { + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->moverect) { + if (screen->damage_merge != VTERM_DAMAGE_SCROLL) + // Avoid an infinite loop + vterm_screen_flush_damage(screen); + + if ((*screen->callbacks->moverect)(dest, src, screen->cbdata)) + return 1; + } + + damagerect(screen, dest); + + return 1; +} + +static int erase_internal(VTermRect rect, int selective, void *user) { + VTermScreen *screen = user; + + for (int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) { + const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row); + + for (int col = rect.start_col; col < rect.end_col; col++) { + ScreenCell *cell = getcell(screen, row, col); + + if (selective && cell->pen.protected_cell) + continue; + + cell->chars[0] = 0; + cell->pen = screen->pen; + cell->pen.dwl = info->doublewidth; + cell->pen.dhl = info->doubleheight; + } + } + + return 1; +} + +static int erase_user(VTermRect rect, int selective, void *user) { + VTermScreen *screen = user; + + damagerect(screen, rect); + + return 1; +} + +static int erase(VTermRect rect, int selective, void *user) { + erase_internal(rect, selective, user); + return erase_user(rect, 0, user); +} + +static int scrollrect(VTermRect rect, int downward, int rightward, void *user) { + VTermScreen *screen = user; + + if (screen->damage_merge != VTERM_DAMAGE_SCROLL) { + vterm_scroll_rect(rect, downward, rightward, moverect_internal, erase_internal, screen); + + vterm_screen_flush_damage(screen); + + vterm_scroll_rect(rect, downward, rightward, moverect_user, erase_user, screen); + + return 1; + } + + if (screen->damaged.start_row != -1 && + !rect_intersects(&rect, &screen->damaged)) { + vterm_screen_flush_damage(screen); + } + + if (screen->pending_scrollrect.start_row == -1) { + screen->pending_scrollrect = rect; + screen->pending_scroll_downward = downward; + screen->pending_scroll_rightward = rightward; + } else if (rect_equal(&screen->pending_scrollrect, &rect) && ((screen->pending_scroll_downward == 0 && downward == 0) || (screen->pending_scroll_rightward == 0 && rightward == 0))) { + screen->pending_scroll_downward += downward; + screen->pending_scroll_rightward += rightward; + } else { + vterm_screen_flush_damage(screen); + + screen->pending_scrollrect = rect; + screen->pending_scroll_downward = downward; + screen->pending_scroll_rightward = rightward; + } + + vterm_scroll_rect(rect, downward, rightward, moverect_internal, erase_internal, screen); + + if (screen->damaged.start_row == -1) + return 1; + + if (rect_contains(&rect, &screen->damaged)) { + /* Scroll region entirely contains the damage; just move it */ + vterm_rect_move(&screen->damaged, -downward, -rightward); + rect_clip(&screen->damaged, &rect); + } + /* There are a number of possible cases here, but lets restrict this to only + * the common case where we might actually gain some performance by + * optimising it. Namely, a vertical scroll that neatly cuts the damage + * region in half. + */ + else if (rect.start_col <= screen->damaged.start_col && rect.end_col >= screen->damaged.end_col && rightward == 0) { + if (screen->damaged.start_row >= rect.start_row && + screen->damaged.start_row < rect.end_row) { + screen->damaged.start_row -= downward; + if (screen->damaged.start_row < rect.start_row) + screen->damaged.start_row = rect.start_row; + if (screen->damaged.start_row > rect.end_row) + screen->damaged.start_row = rect.end_row; + } + if (screen->damaged.end_row >= rect.start_row && + screen->damaged.end_row < rect.end_row) { + screen->damaged.end_row -= downward; + if (screen->damaged.end_row < rect.start_row) + screen->damaged.end_row = rect.start_row; + if (screen->damaged.end_row > rect.end_row) + screen->damaged.end_row = rect.end_row; + } + } else { + DEBUG_LOG("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n", ARGSrect(screen->damaged), ARGSrect(rect)); + } + + return 1; +} + +static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) { + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->movecursor) + return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata); + + return 0; +} + +static int setpenattr(VTermAttr attr, VTermValue *val, void *user) { + VTermScreen *screen = user; + + switch (attr) { + case VTERM_ATTR_BOLD: + screen->pen.bold = val->boolean; + return 1; + case VTERM_ATTR_UNDERLINE: + screen->pen.underline = val->number; + return 1; + case VTERM_ATTR_ITALIC: + screen->pen.italic = val->boolean; + return 1; + case VTERM_ATTR_BLINK: + screen->pen.blink = val->boolean; + return 1; + case VTERM_ATTR_REVERSE: + screen->pen.reverse = val->boolean; + return 1; + case VTERM_ATTR_STRIKE: + screen->pen.strike = val->boolean; + return 1; + case VTERM_ATTR_FONT: + screen->pen.font = val->number; + return 1; + case VTERM_ATTR_FOREGROUND: + screen->pen.fg = val->color; + return 1; + case VTERM_ATTR_BACKGROUND: + screen->pen.bg = val->color; + return 1; + + case VTERM_N_ATTRS: + return 0; + } + + return 0; +} + +static int settermprop(VTermProp prop, VTermValue *val, void *user) { + VTermScreen *screen = user; + + switch (prop) { + case VTERM_PROP_ALTSCREEN: + if (val->boolean && !screen->buffers[1]) + return 0; + + screen->buffer = val->boolean ? screen->buffers[1] : screen->buffers[0]; + /* only send a damage event on disable; because during enable there's an + * erase that sends a damage anyway + */ + if (!val->boolean) + damagescreen(screen); + break; + case VTERM_PROP_REVERSE: + screen->global_reverse = val->boolean; + damagescreen(screen); + break; + default:; /* ignore */ + } + + if (screen->callbacks && screen->callbacks->settermprop) + return (*screen->callbacks->settermprop)(prop, val, screen->cbdata); + + return 1; +} + +static int bell(void *user) { + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->bell) + return (*screen->callbacks->bell)(screen->cbdata); + + return 0; +} + +static int resize(int new_rows, int new_cols, VTermPos *delta, void *user) { + VTermScreen *screen = user; + + int is_altscreen = (screen->buffers[1] && screen->buffer == screen->buffers[1]); + + int old_rows = screen->rows; + int old_cols = screen->cols; + + if (!is_altscreen && new_rows < old_rows) { + // Fewer rows - determine if we're going to scroll at all, and if so, push + // those lines to scrollback + VTermPos pos = {0, 0}; + VTermPos cursor = screen->state->pos; + // Find the first blank row after the cursor. + for (pos.row = old_rows - 1; pos.row >= new_rows; pos.row--) + if (!vterm_screen_is_eol(screen, pos) || cursor.row == pos.row) + break; + + int first_blank_row = pos.row + 1; + if (first_blank_row > new_rows) { + VTermRect rect = { + .start_row = 0, + .end_row = old_rows, + .start_col = 0, + .end_col = old_cols, + }; + scrollrect(rect, first_blank_row - new_rows, 0, user); + vterm_screen_flush_damage(screen); + + delta->row -= first_blank_row - new_rows; + } + } + + screen->buffers[0] = realloc_buffer(screen, screen->buffers[0], new_rows, new_cols); + if (screen->buffers[1]) + screen->buffers[1] = realloc_buffer(screen, screen->buffers[1], new_rows, new_cols); + + screen->buffer = is_altscreen ? screen->buffers[1] : screen->buffers[0]; + + screen->rows = new_rows; + screen->cols = new_cols; + + if (screen->sb_buffer) + vterm_allocator_free(screen->vt, screen->sb_buffer); + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols); + + if (new_cols > old_cols) { + VTermRect rect = { + .start_row = 0, + .end_row = old_rows, + .start_col = old_cols, + .end_col = new_cols, + }; + damagerect(screen, rect); + } + + if (new_rows > old_rows) { + if (!is_altscreen && screen->callbacks && screen->callbacks->sb_popline) { + int rows = new_rows - old_rows; + while (rows) { + if (!(screen->callbacks->sb_popline(screen->cols, screen->sb_buffer, screen->cbdata))) + break; + + VTermRect rect = { + .start_row = 0, + .end_row = screen->rows, + .start_col = 0, + .end_col = screen->cols, + }; + scrollrect(rect, -1, 0, user); + + VTermPos pos = {0, 0}; + for (pos.col = 0; pos.col < screen->cols; pos.col += screen->sb_buffer[pos.col].width) + vterm_screen_set_cell(screen, pos, screen->sb_buffer + pos.col); + + rect.end_row = 1; + damagerect(screen, rect); + + vterm_screen_flush_damage(screen); + + rows--; + delta->row++; + } + } + + VTermRect rect = { + .start_row = old_rows, + .end_row = new_rows, + .start_col = 0, + .end_col = new_cols, + }; + damagerect(screen, rect); + } + + if (screen->callbacks && screen->callbacks->resize) + return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata); + + return 1; +} + +static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user) { + VTermScreen *screen = user; + + if (newinfo->doublewidth != oldinfo->doublewidth || + newinfo->doubleheight != oldinfo->doubleheight) { + for (int col = 0; col < screen->cols; col++) { + ScreenCell *cell = getcell(screen, row, col); + cell->pen.dwl = newinfo->doublewidth; + cell->pen.dhl = newinfo->doubleheight; + } + + VTermRect rect = { + .start_row = row, + .end_row = row + 1, + .start_col = 0, + .end_col = newinfo->doublewidth ? screen->cols / 2 : screen->cols, + }; + damagerect(screen, rect); + + if (newinfo->doublewidth) { + rect.start_col = screen->cols / 2; + rect.end_col = screen->cols; + + erase_internal(rect, 0, user); + } + } + + return 1; +} + +static VTermStateCallbacks state_cbs = { + .putglyph = &putglyph, + .movecursor = &movecursor, + .scrollrect = &scrollrect, + .erase = &erase, + .setpenattr = &setpenattr, + .settermprop = &settermprop, + .bell = &bell, + .resize = &resize, + .setlineinfo = &setlineinfo, +}; + +static VTermScreen *screen_new(VTerm *vt) { + VTermState *state = vterm_obtain_state(vt); + if (!state) + return NULL; + + VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen)); + int rows, cols; + + vterm_get_size(vt, &rows, &cols); + + screen->vt = vt; + screen->state = state; + + screen->damage_merge = VTERM_DAMAGE_CELL; + screen->damaged.start_row = -1; + screen->pending_scrollrect.start_row = -1; + + screen->rows = rows; + screen->cols = cols; + + screen->callbacks = NULL; + screen->cbdata = NULL; + + screen->buffers[0] = realloc_buffer(screen, NULL, rows, cols); + + screen->buffer = screen->buffers[0]; + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols); + + vterm_state_set_callbacks(screen->state, &state_cbs, screen); + + return screen; +} + +INTERNAL void vterm_screen_free(VTermScreen *screen) { + vterm_allocator_free(screen->vt, screen->buffers[0]); + if (screen->buffers[1]) + vterm_allocator_free(screen->vt, screen->buffers[1]); + + vterm_allocator_free(screen->vt, screen->sb_buffer); + + vterm_allocator_free(screen->vt, screen); +} + +void vterm_screen_reset(VTermScreen *screen, int hard) { + screen->damaged.start_row = -1; + screen->pending_scrollrect.start_row = -1; + vterm_state_reset(screen->state, hard); + vterm_screen_flush_damage(screen); +} + +static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect) { + size_t outpos = 0; + int padding = 0; + +#define PUT(c) \ + if (utf8) { \ + size_t thislen = utf8_seqlen(c); \ + if (buffer && outpos + thislen <= len) \ + outpos += fill_utf8((c), (char *)buffer + outpos); \ + else \ + outpos += thislen; \ + } else { \ + if (buffer && outpos + 1 <= len) \ + ((uint32_t *)buffer)[outpos++] = (c); \ + else \ + outpos++; \ + } + + for (int row = rect.start_row; row < rect.end_row; row++) { + for (int col = rect.start_col; col < rect.end_col; col++) { + ScreenCell *cell = getcell(screen, row, col); + + if (cell->chars[0] == 0) + // Erased cell, might need a space + padding++; + else if (cell->chars[0] == (uint32_t)-1) + // Gap behind a double-width char, do nothing + ; + else { + while (padding) { + PUT(UNICODE_SPACE); + padding--; + } + for (int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) { + PUT(cell->chars[i]); + } + } + } + + if (row < rect.end_row - 1) { + PUT(UNICODE_LINEFEED); + padding = 0; + } + } + + return outpos; +} + +size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect) { + return _get_chars(screen, 0, chars, len, rect); +} + +size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect) { + return _get_chars(screen, 1, str, len, rect); +} + +/* Copy internal to external representation of a screen cell */ +int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell) { + ScreenCell *intcell = getcell(screen, pos.row, pos.col); + if (!intcell) + return 0; + + for (int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) { + cell->chars[i] = intcell->chars[i]; + if (!intcell->chars[i]) + break; + } + + cell->attrs.bold = intcell->pen.bold; + cell->attrs.underline = intcell->pen.underline; + cell->attrs.italic = intcell->pen.italic; + cell->attrs.blink = intcell->pen.blink; + cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse; + cell->attrs.strike = intcell->pen.strike; + cell->attrs.font = intcell->pen.font; + + cell->attrs.dwl = intcell->pen.dwl; + cell->attrs.dhl = intcell->pen.dhl; + + cell->fg = intcell->pen.fg; + cell->bg = intcell->pen.bg; + + if (pos.col < (screen->cols - 1) && + getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1) + cell->width = 2; + else + cell->width = 1; + + return 1; +} + +/* Copy external to internal representation of a screen cell */ +/* static because it's only used internally for sb_popline during resize */ +static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell) { + ScreenCell *intcell = getcell(screen, pos.row, pos.col); + if (!intcell) + return 0; + + for (int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) { + intcell->chars[i] = cell->chars[i]; + if (!cell->chars[i]) + break; + } + + intcell->pen.bold = cell->attrs.bold; + intcell->pen.underline = cell->attrs.underline; + intcell->pen.italic = cell->attrs.italic; + intcell->pen.blink = cell->attrs.blink; + intcell->pen.reverse = cell->attrs.reverse ^ screen->global_reverse; + intcell->pen.strike = cell->attrs.strike; + intcell->pen.font = cell->attrs.font; + + intcell->pen.fg = cell->fg; + intcell->pen.bg = cell->bg; + + if (cell->width == 2) + getcell(screen, pos.row, pos.col + 1)->chars[0] = (uint32_t)-1; + + return 1; +} + +int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos) { + /* This cell is EOL if this and every cell to the right is black */ + for (; pos.col < screen->cols; pos.col++) { + ScreenCell *cell = getcell(screen, pos.row, pos.col); + if (cell->chars[0] != 0) + return 0; + } + + return 1; +} + +VTermScreen *vterm_obtain_screen(VTerm *vt) { + if (vt->screen) + return vt->screen; + + VTermScreen *screen = screen_new(vt); + vt->screen = screen; + + return screen; +} + +void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen) { + if (!screen->buffers[1] && altscreen) { + int rows, cols; + vterm_get_size(screen->vt, &rows, &cols); + + screen->buffers[1] = realloc_buffer(screen, NULL, rows, cols); + } +} + +void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user) { + screen->callbacks = callbacks; + screen->cbdata = user; +} + +void *vterm_screen_get_cbdata(VTermScreen *screen) { + return screen->cbdata; +} + +void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user) { + vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user); +} + +void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen) { + return vterm_state_get_unrecognised_fbdata(screen->state); +} + +void vterm_screen_flush_damage(VTermScreen *screen) { + if (screen->pending_scrollrect.start_row != -1) { + vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward, moverect_user, erase_user, screen); + + screen->pending_scrollrect.start_row = -1; + } + + if (screen->damaged.start_row != -1) { + if (screen->callbacks && screen->callbacks->damage) + (*screen->callbacks->damage)(screen->damaged, screen->cbdata); + + screen->damaged.start_row = -1; + } +} + +void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size) { + vterm_screen_flush_damage(screen); + screen->damage_merge = size; +} + +static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b) { + if ((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold)) + return 1; + if ((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline)) + return 1; + if ((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic)) + return 1; + if ((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink)) + return 1; + if ((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse)) + return 1; + if ((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike)) + return 1; + if ((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font)) + return 1; + if ((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg)) + return 1; + if ((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg)) + return 1; + + return 0; +} + +int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs) { + ScreenCell *target = getcell(screen, pos.row, pos.col); + + // TODO: bounds check + extent->start_row = pos.row; + extent->end_row = pos.row + 1; + + if (extent->start_col < 0) + extent->start_col = 0; + if (extent->end_col < 0) + extent->end_col = screen->cols; + + int col; + + for (col = pos.col - 1; col >= extent->start_col; col--) + if (attrs_differ(attrs, target, getcell(screen, pos.row, col))) + break; + extent->start_col = col + 1; + + for (col = pos.col + 1; col < extent->end_col; col++) + if (attrs_differ(attrs, target, getcell(screen, pos.row, col))) + break; + extent->end_col = col - 1; + + return 1; +} + +void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col) { + vterm_state_convert_color_to_rgb(screen->state, col); +} diff --git a/extlib/libvterm/state.c b/extlib/libvterm/state.c new file mode 100644 index 0000000..a1288da --- /dev/null +++ b/extlib/libvterm/state.c @@ -0,0 +1,1801 @@ +#include "vterm_internal.h" + +#include +#include + +#include "../../runtime/printf.h" + +#define strneq(a, b, n) (strncmp(a, b, n) == 0) + +#if defined(DEBUG) && DEBUG > 1 +#define DEBUG_GLYPH_COMBINE +#endif + +/* Some convenient wrappers to make callback functions easier */ + +static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos) { + VTermGlyphInfo info = { + .chars = chars, + .width = width, + .protected_cell = state->protected_cell, + .dwl = state->lineinfo[pos.row].doublewidth, + .dhl = state->lineinfo[pos.row].doubleheight, + }; + + if (state->callbacks && state->callbacks->putglyph) + if ((*state->callbacks->putglyph)(&info, pos, state->cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); +} + +static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom) { + if (state->pos.col == oldpos->col && state->pos.row == oldpos->row) + return; + + if (cancel_phantom) + state->at_phantom = 0; + + if (state->callbacks && state->callbacks->movecursor) + if ((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata)) + return; +} + +static void erase(VTermState *state, VTermRect rect, int selective) { + if (state->callbacks && state->callbacks->erase) + if ((*state->callbacks->erase)(rect, selective, state->cbdata)) + return; +} + +static VTermState *vterm_state_new(VTerm *vt) { + VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState)); + + state->vt = vt; + + state->rows = vt->rows; + state->cols = vt->cols; + + state->mouse_col = 0; + state->mouse_row = 0; + state->mouse_buttons = 0; + + state->mouse_protocol = MOUSE_X10; + + state->callbacks = NULL; + state->cbdata = NULL; + + vterm_state_newpen(state); + + state->bold_is_highbright = 0; + + return state; +} + +INTERNAL void vterm_state_free(VTermState *state) { + vterm_allocator_free(state->vt, state->tabstops); + vterm_allocator_free(state->vt, state->lineinfo); + vterm_allocator_free(state->vt, state->combine_chars); + vterm_allocator_free(state->vt, state); +} + +static void scroll(VTermState *state, VTermRect rect, int downward, int rightward) { + if (!downward && !rightward) + return; + + int rows = rect.end_row - rect.start_row; + if (downward > rows) + downward = rows; + else if (downward < -rows) + downward = -rows; + + int cols = rect.end_col - rect.start_col; + if (rightward > cols) + rightward = cols; + else if (rightward < -cols) + rightward = -cols; + + // Update lineinfo if full line + if (rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) { + int height = rect.end_row - rect.start_row - abs(downward); + + if (downward > 0) + memmove(state->lineinfo + rect.start_row, state->lineinfo + rect.start_row + downward, height * sizeof(state->lineinfo[0])); + else + memmove(state->lineinfo + rect.start_row - downward, state->lineinfo + rect.start_row, height * sizeof(state->lineinfo[0])); + } + + if (state->callbacks && state->callbacks->scrollrect) + if ((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) + return; + + if (state->callbacks) + vterm_scroll_rect(rect, downward, rightward, state->callbacks->moverect, state->callbacks->erase, state->cbdata); +} + +static void linefeed(VTermState *state) { + if (state->pos.row == SCROLLREGION_BOTTOM(state) - 1) { + VTermRect rect = { + .start_row = state->scrollregion_top, + .end_row = SCROLLREGION_BOTTOM(state), + .start_col = SCROLLREGION_LEFT(state), + .end_col = SCROLLREGION_RIGHT(state), + }; + + scroll(state, rect, 1, 0); + } else if (state->pos.row < state->rows - 1) + state->pos.row++; +} + +static void grow_combine_buffer(VTermState *state) { + size_t new_size = state->combine_chars_size * 2; + uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0])); + + memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0])); + + vterm_allocator_free(state->vt, state->combine_chars); + + state->combine_chars = new_chars; + state->combine_chars_size = new_size; +} + +static void set_col_tabstop(VTermState *state, int col) { + unsigned char mask = 1 << (col & 7); + state->tabstops[col >> 3] |= mask; +} + +static void clear_col_tabstop(VTermState *state, int col) { + unsigned char mask = 1 << (col & 7); + state->tabstops[col >> 3] &= ~mask; +} + +static int is_col_tabstop(VTermState *state, int col) { + unsigned char mask = 1 << (col & 7); + return state->tabstops[col >> 3] & mask; +} + +static int is_cursor_in_scrollregion(const VTermState *state) { + if (state->pos.row < state->scrollregion_top || + state->pos.row >= SCROLLREGION_BOTTOM(state)) + return 0; + if (state->pos.col < SCROLLREGION_LEFT(state) || + state->pos.col >= SCROLLREGION_RIGHT(state)) + return 0; + + return 1; +} + +static void tab(VTermState *state, int count, int direction) { + while (count > 0) { + if (direction > 0) { + if (state->pos.col >= THISROWWIDTH(state) - 1) + return; + + state->pos.col++; + } else if (direction < 0) { + if (state->pos.col < 1) + return; + + state->pos.col--; + } + + if (is_col_tabstop(state, state->pos.col)) + count--; + } +} + +#define NO_FORCE 0 +#define FORCE 1 + +#define DWL_OFF 0 +#define DWL_ON 1 + +#define DHL_OFF 0 +#define DHL_TOP 1 +#define DHL_BOTTOM 2 + +static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl) { + VTermLineInfo info = state->lineinfo[row]; + + if (dwl == DWL_OFF) + info.doublewidth = DWL_OFF; + else if (dwl == DWL_ON) + info.doublewidth = DWL_ON; + // else -1 to ignore + + if (dhl == DHL_OFF) + info.doubleheight = DHL_OFF; + else if (dhl == DHL_TOP) + info.doubleheight = DHL_TOP; + else if (dhl == DHL_BOTTOM) + info.doubleheight = DHL_BOTTOM; + + if ((state->callbacks && + state->callbacks->setlineinfo && + (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata)) || + force) + state->lineinfo[row] = info; +} + +static int on_text(const char bytes[], size_t len, void *user) { + VTermState *state = user; + + VTermPos oldpos = state->pos; + + // We'll have at most len codepoints + uint32_t codepoints[len]; + int npoints = 0; + size_t eaten = 0; + + VTermEncodingInstance *encoding = + state->gsingle_set ? &state->encoding[state->gsingle_set] : + !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] : + state->vt->mode.utf8 ? &state->encoding_utf8 : + &state->encoding[state->gr_set]; + + (*encoding->enc->decode)(encoding->enc, encoding->data, codepoints, &npoints, state->gsingle_set ? 1 : len, bytes, &eaten, len); + + /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet + * for even a single codepoint + */ + if (!npoints) + return eaten; + + if (state->gsingle_set && npoints) + state->gsingle_set = 0; + + int i = 0; + + /* This is a combining char. that needs to be merged with the previous + * glyph output */ + if (vterm_unicode_is_combining(codepoints[i])) { + /* See if the cursor has moved since */ + if (state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) { + /* Find where we need to append these combining chars */ + int saved_i = 0; + while (state->combine_chars[saved_i]) + saved_i++; + + /* Add extra ones */ + while (i < npoints && vterm_unicode_is_combining(codepoints[i])) { + if (saved_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[saved_i++] = codepoints[i++]; + } + if (saved_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[saved_i] = 0; + + /* Now render it */ + putglyph(state, state->combine_chars, state->combine_width, state->combine_pos); + } else { + DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n"); + } + } + + for (; i < npoints; i++) { + // Try to find combining characters following this + int glyph_starts = i; + int glyph_ends; + for (glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++) + if (!vterm_unicode_is_combining(codepoints[glyph_ends])) + break; + + int width = 0; + + uint32_t chars[glyph_ends - glyph_starts + 1]; + + for (; i < glyph_ends; i++) { + chars[i - glyph_starts] = codepoints[i]; + int this_width = vterm_unicode_width(codepoints[i]); + width += this_width; + } + + chars[glyph_ends - glyph_starts] = 0; + i--; + + if (state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) { + linefeed(state); + state->pos.col = 0; + state->at_phantom = 0; + } + + if (state->mode.insert) { + /* TODO: This will be a little inefficient for large bodies of text, as + * it'll have to 'ICH' effectively before every glyph. We should scan + * ahead and ICH as many times as required + */ + VTermRect rect = { + .start_row = state->pos.row, + .end_row = state->pos.row + 1, + .start_col = state->pos.col, + .end_col = THISROWWIDTH(state), + }; + scroll(state, rect, 0, -1); + } + + putglyph(state, chars, width, state->pos); + + if (i == npoints - 1) { + /* End of the buffer. Save the chars in case we have to combine with + * more on the next call */ + int save_i; + for (save_i = 0; chars[save_i]; save_i++) { + if (save_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[save_i] = chars[save_i]; + } + if (save_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[save_i] = 0; + state->combine_width = width; + state->combine_pos = state->pos; + } + + if (state->pos.col + width >= THISROWWIDTH(state)) { + if (state->mode.autowrap) + state->at_phantom = 1; + } else { + state->pos.col += width; + } + } + + updatecursor(state, &oldpos, 0); + + return eaten; +} + +static int on_control(unsigned char control, void *user) { + VTermState *state = user; + + VTermPos oldpos = state->pos; + + switch (control) { + case 0x07: // BEL - ECMA-48 8.3.3 + if (state->callbacks && state->callbacks->bell) + (*state->callbacks->bell)(state->cbdata); + break; + + case 0x08: // BS - ECMA-48 8.3.5 + if (state->pos.col > 0) + state->pos.col--; + break; + + case 0x09: // HT - ECMA-48 8.3.60 + tab(state, 1, +1); + break; + + case 0x0a: // LF - ECMA-48 8.3.74 + case 0x0b: // VT + case 0x0c: // FF + linefeed(state); + if (state->mode.newline) + state->pos.col = 0; + break; + + case 0x0d: // CR - ECMA-48 8.3.15 + state->pos.col = 0; + break; + + case 0x0e: // LS1 - ECMA-48 8.3.76 + state->gl_set = 1; + break; + + case 0x0f: // LS0 - ECMA-48 8.3.75 + state->gl_set = 0; + break; + + case 0x84: // IND - DEPRECATED but implemented for completeness + linefeed(state); + break; + + case 0x85: // NEL - ECMA-48 8.3.86 + linefeed(state); + state->pos.col = 0; + break; + + case 0x88: // HTS - ECMA-48 8.3.62 + set_col_tabstop(state, state->pos.col); + break; + + case 0x8d: // RI - ECMA-48 8.3.104 + if (state->pos.row == state->scrollregion_top) { + VTermRect rect = { + .start_row = state->scrollregion_top, + .end_row = SCROLLREGION_BOTTOM(state), + .start_col = SCROLLREGION_LEFT(state), + .end_col = SCROLLREGION_RIGHT(state), + }; + + scroll(state, rect, -1, 0); + } else if (state->pos.row > 0) + state->pos.row--; + break; + + case 0x8e: // SS2 - ECMA-48 8.3.141 + state->gsingle_set = 2; + break; + + case 0x8f: // SS3 - ECMA-48 8.3.142 + state->gsingle_set = 3; + break; + + default: + if (state->fallbacks && state->fallbacks->control) + if ((*state->fallbacks->control)(control, state->fbdata)) + return 1; + + return 0; + } + + updatecursor(state, &oldpos, 1); + + return 1; +} + +static int settermprop_bool(VTermState *state, VTermProp prop, int v) { + VTermValue val = {.boolean = v}; + return vterm_state_set_termprop(state, prop, &val); +} + +static int settermprop_int(VTermState *state, VTermProp prop, int v) { + VTermValue val = {.number = v}; + return vterm_state_set_termprop(state, prop, &val); +} + +static int settermprop_string(VTermState *state, VTermProp prop, const char *str, size_t len) { + char strvalue[len + 1]; + strncpy(strvalue, str, len); + strvalue[len] = 0; + + VTermValue val = {.string = strvalue}; + return vterm_state_set_termprop(state, prop, &val); +} + +static void savecursor(VTermState *state, int save) { + if (save) { + state->saved.pos = state->pos; + state->saved.mode.cursor_visible = state->mode.cursor_visible; + state->saved.mode.cursor_blink = state->mode.cursor_blink; + state->saved.mode.cursor_shape = state->mode.cursor_shape; + + vterm_state_savepen(state, 1); + } else { + VTermPos oldpos = state->pos; + + state->pos = state->saved.pos; + + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible); + settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape); + + vterm_state_savepen(state, 0); + + updatecursor(state, &oldpos, 1); + } +} + +static int on_escape(const char *bytes, size_t len, void *user) { + VTermState *state = user; + + /* Easier to decode this from the first byte, even though the final + * byte terminates it + */ + switch (bytes[0]) { + case ' ': + if (len != 2) + return 0; + + switch (bytes[1]) { + case 'F': // S7C1T + state->vt->mode.ctrl8bit = 0; + break; + + case 'G': // S8C1T + state->vt->mode.ctrl8bit = 1; + break; + + default: + return 0; + } + return 2; + + case '#': + if (len != 2) + return 0; + + switch (bytes[1]) { + case '3': // DECDHL top + if (state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP); + break; + + case '4': // DECDHL bottom + if (state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM); + break; + + case '5': // DECSWL + if (state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF); + break; + + case '6': // DECDWL + if (state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF); + break; + + case '8': // DECALN + { + VTermPos pos; + uint32_t E[] = {'E', 0}; + for (pos.row = 0; pos.row < state->rows; pos.row++) + for (pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++) + putglyph(state, E, 1, pos); + break; + } + + default: + return 0; + } + return 2; + + case '(': + case ')': + case '*': + case '+': // SCS + if (len != 2) + return 0; + + { + int setnum = bytes[0] - 0x28; + VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]); + + if (newenc) { + state->encoding[setnum].enc = newenc; + + if (newenc->init) + (*newenc->init)(newenc, state->encoding[setnum].data); + } + } + + return 2; + + case '7': // DECSC + savecursor(state, 1); + return 1; + + case '8': // DECRC + savecursor(state, 0); + return 1; + + case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100 + return 1; + + case '=': // DECKPAM + state->mode.keypad = 1; + return 1; + + case '>': // DECKPNM + state->mode.keypad = 0; + return 1; + + case 'c': // RIS - ECMA-48 8.3.105 + { + VTermPos oldpos = state->pos; + vterm_state_reset(state, 1); + if (state->callbacks && state->callbacks->movecursor) + (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata); + return 1; + } + + case 'n': // LS2 - ECMA-48 8.3.78 + state->gl_set = 2; + return 1; + + case 'o': // LS3 - ECMA-48 8.3.80 + state->gl_set = 3; + return 1; + + case '~': // LS1R - ECMA-48 8.3.77 + state->gr_set = 1; + return 1; + + case '}': // LS2R - ECMA-48 8.3.79 + state->gr_set = 2; + return 1; + + case '|': // LS3R - ECMA-48 8.3.81 + state->gr_set = 3; + return 1; + + default: + return 0; + } +} + +static void set_mode(VTermState *state, int num, int val) { + switch (num) { + case 4: // IRM - ECMA-48 7.2.10 + state->mode.insert = val; + break; + + case 20: // LNM - ANSI X3.4-1977 + state->mode.newline = val; + break; + + default: + DEBUG_LOG("libvterm: Unknown mode %d\n", num); + return; + } +} + +static void set_dec_mode(VTermState *state, int num, int val) { + switch (num) { + case 1: + state->mode.cursor = val; + break; + + case 5: // DECSCNM - screen mode + settermprop_bool(state, VTERM_PROP_REVERSE, val); + break; + + case 6: // DECOM - origin mode + { + VTermPos oldpos = state->pos; + state->mode.origin = val; + state->pos.row = state->mode.origin ? state->scrollregion_top : 0; + state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0; + updatecursor(state, &oldpos, 1); + } break; + + case 7: + state->mode.autowrap = val; + break; + + case 12: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, val); + break; + + case 25: + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val); + break; + + case 69: // DECVSSM - vertical split screen mode + // DECLRMM - left/right margin mode + state->mode.leftrightmargin = val; + if (val) { + // Setting DECVSSM must clear doublewidth/doubleheight state of every line + for (int row = 0; row < state->rows; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + } + + break; + + case 1000: + case 1002: + case 1003: + settermprop_int(state, VTERM_PROP_MOUSE, !val ? VTERM_PROP_MOUSE_NONE : (num == 1000) ? VTERM_PROP_MOUSE_CLICK : + (num == 1002) ? VTERM_PROP_MOUSE_DRAG : + VTERM_PROP_MOUSE_MOVE); + break; + + case 1004: + state->mode.report_focus = val; + break; + + case 1005: + state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10; + break; + + case 1006: + state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10; + break; + + case 1015: + state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10; + break; + + case 1047: + settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); + break; + + case 1048: + savecursor(state, val); + break; + + case 1049: + settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); + savecursor(state, val); + break; + + case 2004: + state->mode.bracketpaste = val; + break; + + default: + DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num); + return; + } +} + +static void request_dec_mode(VTermState *state, int num) { + int reply; + + switch (num) { + case 1: + reply = state->mode.cursor; + break; + + case 5: + reply = state->mode.screen; + break; + + case 6: + reply = state->mode.origin; + break; + + case 7: + reply = state->mode.autowrap; + break; + + case 12: + reply = state->mode.cursor_blink; + break; + + case 25: + reply = state->mode.cursor_visible; + break; + + case 69: + reply = state->mode.leftrightmargin; + break; + + case 1000: + reply = state->mouse_flags == MOUSE_WANT_CLICK; + break; + + case 1002: + reply = state->mouse_flags == (MOUSE_WANT_CLICK | MOUSE_WANT_DRAG); + break; + + case 1003: + reply = state->mouse_flags == (MOUSE_WANT_CLICK | MOUSE_WANT_MOVE); + break; + + case 1004: + reply = state->mode.report_focus; + break; + + case 1005: + reply = state->mouse_protocol == MOUSE_UTF8; + break; + + case 1006: + reply = state->mouse_protocol == MOUSE_SGR; + break; + + case 1015: + reply = state->mouse_protocol == MOUSE_RXVT; + break; + + case 1047: + reply = state->mode.alt_screen; + break; + + case 2004: + reply = state->mode.bracketpaste; + break; + + default: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0); + return; + } + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2); +} + +static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) { + VTermState *state = user; + int leader_byte = 0; + int intermed_byte = 0; + int cancel_phantom = 1; + + if (leader && leader[0]) { + if (leader[1]) // longer than 1 char + return 0; + + switch (leader[0]) { + case '?': + case '>': + leader_byte = leader[0]; + break; + default: + return 0; + } + } + + if (intermed && intermed[0]) { + if (intermed[1]) // longer than 1 char + return 0; + + switch (intermed[0]) { + case ' ': + case '"': + case '$': + case '\'': + intermed_byte = intermed[0]; + break; + default: + return 0; + } + } + + VTermPos oldpos = state->pos; + + // Some temporaries for later code + int count, val; + int row, col; + VTermRect rect; + int selective; + +#define LBOUND(v, min) \ + if ((v) < (min)) \ + (v) = (min) +#define UBOUND(v, max) \ + if ((v) > (max)) \ + (v) = (max) + +#define LEADER(l, b) ((l << 8) | b) +#define INTERMED(i, b) ((i << 16) | b) + + switch (intermed_byte << 16 | leader_byte << 8 | command) { + case 0x40: // ICH - ECMA-48 8.3.64 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + if (state->mode.leftrightmargin) + rect.end_col = SCROLLREGION_RIGHT(state); + else + rect.end_col = THISROWWIDTH(state); + + scroll(state, rect, 0, -count); + + break; + + case 0x41: // CUU - ECMA-48 8.3.22 + count = CSI_ARG_COUNT(args[0]); + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x42: // CUD - ECMA-48 8.3.19 + count = CSI_ARG_COUNT(args[0]); + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x43: // CUF - ECMA-48 8.3.20 + count = CSI_ARG_COUNT(args[0]); + state->pos.col += count; + state->at_phantom = 0; + break; + + case 0x44: // CUB - ECMA-48 8.3.18 + count = CSI_ARG_COUNT(args[0]); + state->pos.col -= count; + state->at_phantom = 0; + break; + + case 0x45: // CNL - ECMA-48 8.3.12 + count = CSI_ARG_COUNT(args[0]); + state->pos.col = 0; + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x46: // CPL - ECMA-48 8.3.13 + count = CSI_ARG_COUNT(args[0]); + state->pos.col = 0; + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x47: // CHA - ECMA-48 8.3.9 + val = CSI_ARG_OR(args[0], 1); + state->pos.col = val - 1; + state->at_phantom = 0; + break; + + case 0x48: // CUP - ECMA-48 8.3.21 + row = CSI_ARG_OR(args[0], 1); + col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); + // zero-based + state->pos.row = row - 1; + state->pos.col = col - 1; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + state->at_phantom = 0; + break; + + case 0x49: // CHT - ECMA-48 8.3.10 + count = CSI_ARG_COUNT(args[0]); + tab(state, count, +1); + break; + + case 0x4a: // ED - ECMA-48 8.3.39 + case LEADER('?', 0x4a): // DECSED - Selective Erase in Display + selective = (leader_byte == '?'); + switch (CSI_ARG(args[0])) { + case CSI_ARG_MISSING: + case 0: + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + rect.end_col = state->cols; + if (rect.end_col > rect.start_col) + erase(state, rect, selective); + + rect.start_row = state->pos.row + 1; + rect.end_row = state->rows; + rect.start_col = 0; + for (int row = rect.start_row; row < rect.end_row; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + if (rect.end_row > rect.start_row) + erase(state, rect, selective); + break; + + case 1: + rect.start_row = 0; + rect.end_row = state->pos.row; + rect.start_col = 0; + rect.end_col = state->cols; + for (int row = rect.start_row; row < rect.end_row; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + if (rect.end_col > rect.start_col) + erase(state, rect, selective); + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.end_col = state->pos.col + 1; + if (rect.end_row > rect.start_row) + erase(state, rect, selective); + break; + + case 2: + rect.start_row = 0; + rect.end_row = state->rows; + rect.start_col = 0; + rect.end_col = state->cols; + for (int row = rect.start_row; row < rect.end_row; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + erase(state, rect, selective); + break; + } + break; + + case 0x4b: // EL - ECMA-48 8.3.41 + case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line + selective = (leader_byte == '?'); + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + + switch (CSI_ARG(args[0])) { + case CSI_ARG_MISSING: + case 0: + rect.start_col = state->pos.col; + rect.end_col = THISROWWIDTH(state); + break; + case 1: + rect.start_col = 0; + rect.end_col = state->pos.col + 1; + break; + case 2: + rect.start_col = 0; + rect.end_col = THISROWWIDTH(state); + break; + default: + return 0; + } + + if (rect.end_col > rect.start_col) + erase(state, rect, selective); + + break; + + case 0x4c: // IL - ECMA-48 8.3.67 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, -count, 0); + + break; + + case 0x4d: // DL - ECMA-48 8.3.32 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, count, 0); + + break; + + case 0x50: // DCH - ECMA-48 8.3.26 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + if (state->mode.leftrightmargin) + rect.end_col = SCROLLREGION_RIGHT(state); + else + rect.end_col = THISROWWIDTH(state); + + scroll(state, rect, 0, count); + + break; + + case 0x53: // SU - ECMA-48 8.3.147 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, count, 0); + + break; + + case 0x54: // SD - ECMA-48 8.3.113 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, -count, 0); + + break; + + case 0x58: // ECH - ECMA-48 8.3.38 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + rect.end_col = state->pos.col + count; + UBOUND(rect.end_col, THISROWWIDTH(state)); + + erase(state, rect, 0); + break; + + case 0x5a: // CBT - ECMA-48 8.3.7 + count = CSI_ARG_COUNT(args[0]); + tab(state, count, -1); + break; + + case 0x60: // HPA - ECMA-48 8.3.57 + col = CSI_ARG_OR(args[0], 1); + state->pos.col = col - 1; + state->at_phantom = 0; + break; + + case 0x61: // HPR - ECMA-48 8.3.59 + count = CSI_ARG_COUNT(args[0]); + state->pos.col += count; + state->at_phantom = 0; + break; + + case 0x62: { // REP - ECMA-48 8.3.103 + const int row_width = THISROWWIDTH(state); + count = CSI_ARG_COUNT(args[0]); + col = state->pos.col + count; + UBOUND(col, row_width); + while (state->pos.col < col) { + putglyph(state, state->combine_chars, state->combine_width, state->pos); + state->pos.col += state->combine_width; + } + if (state->pos.col + state->combine_width >= row_width) { + if (state->mode.autowrap) { + state->at_phantom = 1; + cancel_phantom = 0; + } + } + break; + } + + case 0x63: // DA - ECMA-48 8.3.24 + val = CSI_ARG_OR(args[0], 0); + if (val == 0) + // DEC VT100 response + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c"); + break; + + case LEADER('>', 0x63): // DEC secondary Device Attributes + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0); + break; + + case 0x64: // VPA - ECMA-48 8.3.158 + row = CSI_ARG_OR(args[0], 1); + state->pos.row = row - 1; + if (state->mode.origin) + state->pos.row += state->scrollregion_top; + state->at_phantom = 0; + break; + + case 0x65: // VPR - ECMA-48 8.3.160 + count = CSI_ARG_COUNT(args[0]); + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x66: // HVP - ECMA-48 8.3.63 + row = CSI_ARG_OR(args[0], 1); + col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); + // zero-based + state->pos.row = row - 1; + state->pos.col = col - 1; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + state->at_phantom = 0; + break; + + case 0x67: // TBC - ECMA-48 8.3.154 + val = CSI_ARG_OR(args[0], 0); + + switch (val) { + case 0: + clear_col_tabstop(state, state->pos.col); + break; + case 3: + case 5: + for (col = 0; col < state->cols; col++) + clear_col_tabstop(state, col); + break; + case 1: + case 2: + case 4: + break; + /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */ + default: + return 0; + } + break; + + case 0x68: // SM - ECMA-48 8.3.125 + if (!CSI_ARG_IS_MISSING(args[0])) + set_mode(state, CSI_ARG(args[0]), 1); + break; + + case LEADER('?', 0x68): // DEC private mode set + if (!CSI_ARG_IS_MISSING(args[0])) + set_dec_mode(state, CSI_ARG(args[0]), 1); + break; + + case 0x6a: // HPB - ECMA-48 8.3.58 + count = CSI_ARG_COUNT(args[0]); + state->pos.col -= count; + state->at_phantom = 0; + break; + + case 0x6b: // VPB - ECMA-48 8.3.159 + count = CSI_ARG_COUNT(args[0]); + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x6c: // RM - ECMA-48 8.3.106 + if (!CSI_ARG_IS_MISSING(args[0])) + set_mode(state, CSI_ARG(args[0]), 0); + break; + + case LEADER('?', 0x6c): // DEC private mode reset + if (!CSI_ARG_IS_MISSING(args[0])) + set_dec_mode(state, CSI_ARG(args[0]), 0); + break; + + case 0x6d: // SGR - ECMA-48 8.3.117 + vterm_state_setpen(state, args, argcount); + break; + + case 0x6e: // DSR - ECMA-48 8.3.35 + case LEADER('?', 0x6e): // DECDSR + val = CSI_ARG_OR(args[0], 0); + + { + char *qmark = (leader_byte == '?') ? "?" : ""; + + switch (val) { + case 0: + case 1: + case 2: + case 3: + case 4: + // ignore - these are replies + break; + case 5: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark); + break; + case 6: // CPR - cursor position report + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1); + break; + } + } + break; + + + case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset + vterm_state_reset(state, 0); + break; + + case LEADER('?', INTERMED('$', 0x70)): + request_dec_mode(state, CSI_ARG(args[0])); + break; + + case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape + val = CSI_ARG_OR(args[0], 1); + + switch (val) { + case 0: + case 1: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + break; + case 2: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + break; + case 3: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); + break; + case 4: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); + break; + case 5: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); + break; + case 6: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); + break; + } + + break; + + case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute + val = CSI_ARG_OR(args[0], 0); + + switch (val) { + case 0: + case 2: + state->protected_cell = 0; + break; + case 1: + state->protected_cell = 1; + break; + } + + break; + + case 0x72: // DECSTBM - DEC custom + state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1; + state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); + LBOUND(state->scrollregion_top, 0); + UBOUND(state->scrollregion_top, state->rows); + LBOUND(state->scrollregion_bottom, -1); + if (state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows) + state->scrollregion_bottom = -1; + else + UBOUND(state->scrollregion_bottom, state->rows); + + if (SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { + // Invalid + state->scrollregion_top = 0; + state->scrollregion_bottom = -1; + } + + // Setting the scrolling region restores the cursor to the home position + state->pos.row = 0; + state->pos.col = 0; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + + break; + + case 0x73: // DECSLRM - DEC custom + // Always allow setting these margins, just they won't take effect without DECVSSM + state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1; + state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); + LBOUND(state->scrollregion_left, 0); + UBOUND(state->scrollregion_left, state->cols); + LBOUND(state->scrollregion_right, -1); + if (state->scrollregion_left == 0 && state->scrollregion_right == state->cols) + state->scrollregion_right = -1; + else + UBOUND(state->scrollregion_right, state->cols); + + if (state->scrollregion_right > -1 && + state->scrollregion_right <= state->scrollregion_left) { + // Invalid + state->scrollregion_left = 0; + state->scrollregion_right = -1; + } + + // Setting the scrolling region restores the cursor to the home position + state->pos.row = 0; + state->pos.col = 0; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + + break; + + case INTERMED('\'', 0x7D): // DECIC + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = state->pos.col; + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, 0, -count); + + break; + + case INTERMED('\'', 0x7E): // DECDC + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = state->pos.col; + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, 0, count); + + break; + + default: + if (state->fallbacks && state->fallbacks->csi) + if ((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) + return 1; + + return 0; + } + + if (state->mode.origin) { + LBOUND(state->pos.row, state->scrollregion_top); + UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state) - 1); + LBOUND(state->pos.col, SCROLLREGION_LEFT(state)); + UBOUND(state->pos.col, SCROLLREGION_RIGHT(state) - 1); + } else { + LBOUND(state->pos.row, 0); + UBOUND(state->pos.row, state->rows - 1); + LBOUND(state->pos.col, 0); + UBOUND(state->pos.col, THISROWWIDTH(state) - 1); + } + + updatecursor(state, &oldpos, cancel_phantom); + + return 1; +} + +static int on_osc(const char *command, size_t cmdlen, void *user) { + VTermState *state = user; + + if (cmdlen < 2) + return 0; + + if (strneq(command, "0;", 2)) { + settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2); + settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2); + return 1; + } else if (strneq(command, "1;", 2)) { + settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2); + return 1; + } else if (strneq(command, "2;", 2)) { + settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2); + return 1; + } else if (state->fallbacks && state->fallbacks->osc) + if ((*state->fallbacks->osc)(command, cmdlen, state->fbdata)) + return 1; + + return 0; +} + +static void request_status_string(VTermState *state, const char *command, size_t cmdlen) { + VTerm *vt = state->vt; + + if (cmdlen == 1) + switch (command[0]) { + case 'm': // Query SGR + { + long args[20]; + int argc = vterm_state_getpen(state, args, sizeof(args) / sizeof(args[0])); + size_t cur = 0; + + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "\x90" + "1$r" : + ESC_S "P" + "1$r"); // DCS 1$r ... + if (cur >= vt->tmpbuffer_len) + return; + + for (int argi = 0; argi < argc; argi++) { + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, argi == argc - 1 ? "%ld" : CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" : + "%ld;", + CSI_ARG(args[argi])); + if (cur >= vt->tmpbuffer_len) + return; + } + + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "m" + "\x9C" : + "m" ESC_S "\\"); // ... m ST + if (cur >= vt->tmpbuffer_len) + return; + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); + } + return; + case 'r': // Query DECSTBM + vterm_push_output_sprintf_dcs(vt, "1$r%d;%dr", state->scrollregion_top + 1, SCROLLREGION_BOTTOM(state)); + return; + case 's': // Query DECSLRM + vterm_push_output_sprintf_dcs(vt, "1$r%d;%ds", SCROLLREGION_LEFT(state) + 1, SCROLLREGION_RIGHT(state)); + return; + } + + if (cmdlen == 2) { + if (strneq(command, " q", 2)) { + int reply; + switch (state->mode.cursor_shape) { + case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break; + case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break; + case VTERM_PROP_CURSORSHAPE_BAR_LEFT: reply = 6; break; + } + if (state->mode.cursor_blink) + reply--; + vterm_push_output_sprintf_dcs(vt, "1$r%d q", reply); + return; + } else if (strneq(command, "\"q", 2)) { + vterm_push_output_sprintf_dcs(vt, "1$r%d\"q", state->protected_cell ? 1 : 2); + return; + } + } + + vterm_push_output_sprintf_dcs(state->vt, "0$r%.s", (int)cmdlen, command); +} + +static int on_dcs(const char *command, size_t cmdlen, void *user) { + VTermState *state = user; + + if (cmdlen >= 2 && strneq(command, "$q", 2)) { + request_status_string(state, command + 2, cmdlen - 2); + return 1; + } else if (state->fallbacks && state->fallbacks->dcs) + if ((*state->fallbacks->dcs)(command, cmdlen, state->fbdata)) + return 1; + + return 0; +} + +static int on_resize(int rows, int cols, void *user) { + VTermState *state = user; + VTermPos oldpos = state->pos; + + if (cols != state->cols) { + unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8); + + /* TODO: This can all be done much more efficiently bytewise */ + int col; + for (col = 0; col < state->cols && col < cols; col++) { + unsigned char mask = 1 << (col & 7); + if (state->tabstops[col >> 3] & mask) + newtabstops[col >> 3] |= mask; + else + newtabstops[col >> 3] &= ~mask; + } + + for (; col < cols; col++) { + unsigned char mask = 1 << (col & 7); + if (col % 8 == 0) + newtabstops[col >> 3] |= mask; + else + newtabstops[col >> 3] &= ~mask; + } + + vterm_allocator_free(state->vt, state->tabstops); + state->tabstops = newtabstops; + } + + if (rows != state->rows) { + VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo)); + + int row; + for (row = 0; row < state->rows && row < rows; row++) { + newlineinfo[row] = state->lineinfo[row]; + } + + for (; row < rows; row++) { + newlineinfo[row] = (VTermLineInfo){ + .doublewidth = 0, + }; + } + + vterm_allocator_free(state->vt, state->lineinfo); + state->lineinfo = newlineinfo; + } + + state->rows = rows; + state->cols = cols; + + if (state->scrollregion_bottom > -1) + UBOUND(state->scrollregion_bottom, state->rows); + if (state->scrollregion_right > -1) + UBOUND(state->scrollregion_right, state->cols); + + VTermPos delta = {0, 0}; + + if (state->callbacks && state->callbacks->resize) + (*state->callbacks->resize)(rows, cols, &delta, state->cbdata); + + if (state->at_phantom && state->pos.col < cols - 1) { + state->at_phantom = 0; + state->pos.col++; + } + + state->pos.row += delta.row; + state->pos.col += delta.col; + + if (state->pos.row >= rows) + state->pos.row = rows - 1; + if (state->pos.col >= cols) + state->pos.col = cols - 1; + + updatecursor(state, &oldpos, 1); + + return 1; +} + +static const VTermParserCallbacks parser_callbacks = { + .text = on_text, + .control = on_control, + .escape = on_escape, + .csi = on_csi, + .osc = on_osc, + .dcs = on_dcs, + .resize = on_resize, +}; + +VTermState *vterm_obtain_state(VTerm *vt) { + if (vt->state) + return vt->state; + + VTermState *state = vterm_state_new(vt); + vt->state = state; + + state->combine_chars_size = 16; + state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0])); + + state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8); + + state->lineinfo = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo)); + + state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u'); + if (*state->encoding_utf8.enc->init) + (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); + + vterm_parser_set_callbacks(vt, &parser_callbacks, state); + + return state; +} + +void vterm_state_reset(VTermState *state, int hard) { + state->scrollregion_top = 0; + state->scrollregion_bottom = -1; + state->scrollregion_left = 0; + state->scrollregion_right = -1; + + state->mode.keypad = 0; + state->mode.cursor = 0; + state->mode.autowrap = 1; + state->mode.insert = 0; + state->mode.newline = 0; + state->mode.alt_screen = 0; + state->mode.origin = 0; + state->mode.leftrightmargin = 0; + state->mode.bracketpaste = 0; + state->mode.report_focus = 0; + + state->vt->mode.ctrl8bit = 0; + + for (int col = 0; col < state->cols; col++) + if (col % 8 == 0) + set_col_tabstop(state, col); + else + clear_col_tabstop(state, col); + + for (int row = 0; row < state->rows; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + + if (state->callbacks && state->callbacks->initpen) + (*state->callbacks->initpen)(state->cbdata); + + vterm_state_resetpen(state); + + VTermEncoding *default_enc = state->vt->mode.utf8 ? + vterm_lookup_encoding(ENC_UTF8, 'u') : + vterm_lookup_encoding(ENC_SINGLE_94, 'B'); + + for (int i = 0; i < 4; i++) { + state->encoding[i].enc = default_enc; + if (default_enc->init) + (*default_enc->init)(default_enc, state->encoding[i].data); + } + + state->gl_set = 0; + state->gr_set = 1; + state->gsingle_set = 0; + + state->protected_cell = 0; + + // Initialise the props + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1); + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + + if (hard) { + state->pos.row = 0; + state->pos.col = 0; + state->at_phantom = 0; + + VTermRect rect = {0, state->rows, 0, state->cols}; + erase(state, rect, 0); + } +} + +void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos) { + *cursorpos = state->pos; +} + +void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user) { + if (callbacks) { + state->callbacks = callbacks; + state->cbdata = user; + + if (state->callbacks && state->callbacks->initpen) + (*state->callbacks->initpen)(state->cbdata); + } else { + state->callbacks = NULL; + state->cbdata = NULL; + } +} + +void *vterm_state_get_cbdata(VTermState *state) { + return state->cbdata; +} + +void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user) { + if (fallbacks) { + state->fallbacks = fallbacks; + state->fbdata = user; + } else { + state->fallbacks = NULL; + state->fbdata = NULL; + } +} + +void *vterm_state_get_unrecognised_fbdata(VTermState *state) { + return state->fbdata; +} + +int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) { + /* Only store the new value of the property if usercode said it was happy. + * This is especially important for altscreen switching */ + if (state->callbacks && state->callbacks->settermprop) + if (!(*state->callbacks->settermprop)(prop, val, state->cbdata)) + return 0; + + switch (prop) { + case VTERM_PROP_TITLE: + case VTERM_PROP_ICONNAME: + // we don't store these, just transparently pass through + return 1; + case VTERM_PROP_CURSORVISIBLE: + state->mode.cursor_visible = val->boolean; + return 1; + case VTERM_PROP_CURSORBLINK: + state->mode.cursor_blink = val->boolean; + return 1; + case VTERM_PROP_CURSORSHAPE: + state->mode.cursor_shape = val->number; + return 1; + case VTERM_PROP_REVERSE: + state->mode.screen = val->boolean; + return 1; + case VTERM_PROP_ALTSCREEN: + state->mode.alt_screen = val->boolean; + if (state->mode.alt_screen) { + VTermRect rect = { + .start_row = 0, + .start_col = 0, + .end_row = state->rows, + .end_col = state->cols, + }; + erase(state, rect, 0); + } + return 1; + case VTERM_PROP_MOUSE: + state->mouse_flags = 0; + if (val->number) + state->mouse_flags |= MOUSE_WANT_CLICK; + if (val->number == VTERM_PROP_MOUSE_DRAG) + state->mouse_flags |= MOUSE_WANT_DRAG; + if (val->number == VTERM_PROP_MOUSE_MOVE) + state->mouse_flags |= MOUSE_WANT_MOVE; + return 1; + + case VTERM_N_PROPS: + return 0; + } + + return 0; +} + +void vterm_state_focus_in(VTermState *state) { + if (state->mode.report_focus) + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I"); +} + +void vterm_state_focus_out(VTermState *state) { + if (state->mode.report_focus) + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O"); +} + +const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row) { + return state->lineinfo + row; +} diff --git a/extlib/libvterm/unicode.c b/extlib/libvterm/unicode.c new file mode 100644 index 0000000..e0fcef0 --- /dev/null +++ b/extlib/libvterm/unicode.c @@ -0,0 +1,229 @@ +#include "vterm_internal.h" + +// ### The following from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c +// With modifications: +// made functions static +// moved 'combining' table to file scope, so other functions can see it +// ################################################################### + +/* + * This is an implementation of wcwidth() and wcswidth() (defined in + * IEEE Std 1002.1-2001) for Unicode. + * + * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html + * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html + * + * In fixed-width output devices, Latin characters all occupy a single + * "cell" position of equal width, whereas ideographic CJK characters + * occupy two such cells. Interoperability between terminal-line + * applications and (teletype-style) character terminals using the + * UTF-8 encoding requires agreement on which character should advance + * the cursor by how many cell positions. No established formal + * standards exist at present on which Unicode character shall occupy + * how many cell positions on character terminals. These routines are + * a first attempt of defining such behavior based on simple rules + * applied to data provided by the Unicode Consortium. + * + * For some graphical characters, the Unicode standard explicitly + * defines a character-cell width via the definition of the East Asian + * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. + * In all these cases, there is no ambiguity about which width a + * terminal shall use. For characters in the East Asian Ambiguous (A) + * class, the width choice depends purely on a preference of backward + * compatibility with either historic CJK or Western practice. + * Choosing single-width for these characters is easy to justify as + * the appropriate long-term solution, as the CJK practice of + * displaying these characters as double-width comes from historic + * implementation simplicity (8-bit encoded characters were displayed + * single-width and 16-bit ones double-width, even for Greek, + * Cyrillic, etc.) and not any typographic considerations. + * + * Much less clear is the choice of width for the Not East Asian + * (Neutral) class. Existing practice does not dictate a width for any + * of these characters. It would nevertheless make sense + * typographically to allocate two character cells to characters such + * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be + * represented adequately with a single-width glyph. The following + * routines at present merely assign a single-cell width to all + * neutral characters, in the interest of simplicity. This is not + * entirely satisfactory and should be reconsidered before + * establishing a formal standard in this area. At the moment, the + * decision which Not East Asian (Neutral) characters should be + * represented by double-width glyphs cannot yet be answered by + * applying a simple rule from the Unicode database content. Setting + * up a proper standard for the behavior of UTF-8 character terminals + * will require a careful analysis not only of each Unicode character, + * but also of each presentation form, something the author of these + * routines has avoided to do so far. + * + * http://www.unicode.org/unicode/reports/tr11/ + * + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + * + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +struct interval { + int first; + int last; +}; + +/* sorted list of non-overlapping intervals of non-spacing characters */ +/* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ +static const struct interval combining[] = { + {0x0300, 0x036F}, {0x0483, 0x0486}, {0x0488, 0x0489}, {0x0591, 0x05BD}, {0x05BF, 0x05BF}, {0x05C1, 0x05C2}, {0x05C4, 0x05C5}, {0x05C7, 0x05C7}, {0x0600, 0x0603}, {0x0610, 0x0615}, {0x064B, 0x065E}, {0x0670, 0x0670}, {0x06D6, 0x06E4}, {0x06E7, 0x06E8}, {0x06EA, 0x06ED}, {0x070F, 0x070F}, {0x0711, 0x0711}, {0x0730, 0x074A}, {0x07A6, 0x07B0}, {0x07EB, 0x07F3}, {0x0901, 0x0902}, {0x093C, 0x093C}, {0x0941, 0x0948}, {0x094D, 0x094D}, {0x0951, 0x0954}, {0x0962, 0x0963}, {0x0981, 0x0981}, {0x09BC, 0x09BC}, {0x09C1, 0x09C4}, {0x09CD, 0x09CD}, {0x09E2, 0x09E3}, {0x0A01, 0x0A02}, {0x0A3C, 0x0A3C}, {0x0A41, 0x0A42}, {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, {0x0A70, 0x0A71}, {0x0A81, 0x0A82}, {0x0ABC, 0x0ABC}, {0x0AC1, 0x0AC5}, {0x0AC7, 0x0AC8}, {0x0ACD, 0x0ACD}, {0x0AE2, 0x0AE3}, {0x0B01, 0x0B01}, {0x0B3C, 0x0B3C}, {0x0B3F, 0x0B3F}, {0x0B41, 0x0B43}, {0x0B4D, 0x0B4D}, {0x0B56, 0x0B56}, {0x0B82, 0x0B82}, {0x0BC0, 0x0BC0}, {0x0BCD, 0x0BCD}, {0x0C3E, 0x0C40}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, {0x0CBC, 0x0CBC}, {0x0CBF, 0x0CBF}, {0x0CC6, 0x0CC6}, {0x0CCC, 0x0CCD}, {0x0CE2, 0x0CE3}, {0x0D41, 0x0D43}, {0x0D4D, 0x0D4D}, {0x0DCA, 0x0DCA}, {0x0DD2, 0x0DD4}, {0x0DD6, 0x0DD6}, {0x0E31, 0x0E31}, {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E}, {0x0EB1, 0x0EB1}, {0x0EB4, 0x0EB9}, {0x0EBB, 0x0EBC}, {0x0EC8, 0x0ECD}, {0x0F18, 0x0F19}, {0x0F35, 0x0F35}, {0x0F37, 0x0F37}, {0x0F39, 0x0F39}, {0x0F71, 0x0F7E}, {0x0F80, 0x0F84}, {0x0F86, 0x0F87}, {0x0F90, 0x0F97}, {0x0F99, 0x0FBC}, {0x0FC6, 0x0FC6}, {0x102D, 0x1030}, {0x1032, 0x1032}, {0x1036, 0x1037}, {0x1039, 0x1039}, {0x1058, 0x1059}, {0x1160, 0x11FF}, {0x135F, 0x135F}, {0x1712, 0x1714}, {0x1732, 0x1734}, {0x1752, 0x1753}, {0x1772, 0x1773}, {0x17B4, 0x17B5}, {0x17B7, 0x17BD}, {0x17C6, 0x17C6}, {0x17C9, 0x17D3}, {0x17DD, 0x17DD}, {0x180B, 0x180D}, {0x18A9, 0x18A9}, {0x1920, 0x1922}, {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193B}, {0x1A17, 0x1A18}, {0x1B00, 0x1B03}, {0x1B34, 0x1B34}, {0x1B36, 0x1B3A}, {0x1B3C, 0x1B3C}, {0x1B42, 0x1B42}, {0x1B6B, 0x1B73}, {0x1DC0, 0x1DCA}, {0x1DFE, 0x1DFF}, {0x200B, 0x200F}, {0x202A, 0x202E}, {0x2060, 0x2063}, {0x206A, 0x206F}, {0x20D0, 0x20EF}, {0x302A, 0x302F}, {0x3099, 0x309A}, {0xA806, 0xA806}, {0xA80B, 0xA80B}, {0xA825, 0xA826}, {0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F}, {0xFE20, 0xFE23}, {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F}, {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x1D167, 0x1D169}, {0x1D173, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, {0x1D242, 0x1D244}, {0xE0001, 0xE0001}, {0xE0020, 0xE007F}, {0xE0100, 0xE01EF}}; + + +/* auxiliary function for binary search in interval table */ +static int bisearch(uint32_t ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + + +/* The following two functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that uint32_t characters are encoded + * in ISO 10646. + */ + + +static int mk_wcwidth(uint32_t ucs) { + /* test for 8-bit control characters */ + if (ucs == 0) + return 0; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return -1; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, combining, sizeof(combining) / sizeof(struct interval) - 1)) + return 0; + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + + return 1 + + (ucs >= 0x1100 && + (ucs <= 0x115f || /* Hangul Jamo init. consonants */ + ucs == 0x2329 || ucs == 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && + ucs != 0x303f) || /* CJK ... Yi */ + (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ + (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ + (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ + (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ + (ucs >= 0xffe0 && ucs <= 0xffe6) || + (ucs >= 0x20000 && ucs <= 0x2fffd) || + (ucs >= 0x30000 && ucs <= 0x3fffd))); +} + + +static int mk_wcswidth(const uint32_t *pwcs, size_t n) { + int w, width = 0; + + for (; *pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} + + +/* + * The following functions are the same as mk_wcwidth() and + * mk_wcswidth(), except that spacing characters in the East Asian + * Ambiguous (A) category as defined in Unicode Technical Report #11 + * have a column width of 2. This variant might be useful for users of + * CJK legacy encodings who want to migrate to UCS without changing + * the traditional terminal character-width behaviour. It is not + * otherwise recommended for general use. + */ +static int mk_wcwidth_cjk(uint32_t ucs) { + /* sorted list of non-overlapping intervals of East Asian Ambiguous + * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ + static const struct interval ambiguous[] = { + {0x00A1, 0x00A1}, {0x00A4, 0x00A4}, {0x00A7, 0x00A8}, {0x00AA, 0x00AA}, {0x00AE, 0x00AE}, {0x00B0, 0x00B4}, {0x00B6, 0x00BA}, {0x00BC, 0x00BF}, {0x00C6, 0x00C6}, {0x00D0, 0x00D0}, {0x00D7, 0x00D8}, {0x00DE, 0x00E1}, {0x00E6, 0x00E6}, {0x00E8, 0x00EA}, {0x00EC, 0x00ED}, {0x00F0, 0x00F0}, {0x00F2, 0x00F3}, {0x00F7, 0x00FA}, {0x00FC, 0x00FC}, {0x00FE, 0x00FE}, {0x0101, 0x0101}, {0x0111, 0x0111}, {0x0113, 0x0113}, {0x011B, 0x011B}, {0x0126, 0x0127}, {0x012B, 0x012B}, {0x0131, 0x0133}, {0x0138, 0x0138}, {0x013F, 0x0142}, {0x0144, 0x0144}, {0x0148, 0x014B}, {0x014D, 0x014D}, {0x0152, 0x0153}, {0x0166, 0x0167}, {0x016B, 0x016B}, {0x01CE, 0x01CE}, {0x01D0, 0x01D0}, {0x01D2, 0x01D2}, {0x01D4, 0x01D4}, {0x01D6, 0x01D6}, {0x01D8, 0x01D8}, {0x01DA, 0x01DA}, {0x01DC, 0x01DC}, {0x0251, 0x0251}, {0x0261, 0x0261}, {0x02C4, 0x02C4}, {0x02C7, 0x02C7}, {0x02C9, 0x02CB}, {0x02CD, 0x02CD}, {0x02D0, 0x02D0}, {0x02D8, 0x02DB}, {0x02DD, 0x02DD}, {0x02DF, 0x02DF}, {0x0391, 0x03A1}, {0x03A3, 0x03A9}, {0x03B1, 0x03C1}, {0x03C3, 0x03C9}, {0x0401, 0x0401}, {0x0410, 0x044F}, {0x0451, 0x0451}, {0x2010, 0x2010}, {0x2013, 0x2016}, {0x2018, 0x2019}, {0x201C, 0x201D}, {0x2020, 0x2022}, {0x2024, 0x2027}, {0x2030, 0x2030}, {0x2032, 0x2033}, {0x2035, 0x2035}, {0x203B, 0x203B}, {0x203E, 0x203E}, {0x2074, 0x2074}, {0x207F, 0x207F}, {0x2081, 0x2084}, {0x20AC, 0x20AC}, {0x2103, 0x2103}, {0x2105, 0x2105}, {0x2109, 0x2109}, {0x2113, 0x2113}, {0x2116, 0x2116}, {0x2121, 0x2122}, {0x2126, 0x2126}, {0x212B, 0x212B}, {0x2153, 0x2154}, {0x215B, 0x215E}, {0x2160, 0x216B}, {0x2170, 0x2179}, {0x2190, 0x2199}, {0x21B8, 0x21B9}, {0x21D2, 0x21D2}, {0x21D4, 0x21D4}, {0x21E7, 0x21E7}, {0x2200, 0x2200}, {0x2202, 0x2203}, {0x2207, 0x2208}, {0x220B, 0x220B}, {0x220F, 0x220F}, {0x2211, 0x2211}, {0x2215, 0x2215}, {0x221A, 0x221A}, {0x221D, 0x2220}, {0x2223, 0x2223}, {0x2225, 0x2225}, {0x2227, 0x222C}, {0x222E, 0x222E}, {0x2234, 0x2237}, {0x223C, 0x223D}, {0x2248, 0x2248}, {0x224C, 0x224C}, {0x2252, 0x2252}, {0x2260, 0x2261}, {0x2264, 0x2267}, {0x226A, 0x226B}, {0x226E, 0x226F}, {0x2282, 0x2283}, {0x2286, 0x2287}, {0x2295, 0x2295}, {0x2299, 0x2299}, {0x22A5, 0x22A5}, {0x22BF, 0x22BF}, {0x2312, 0x2312}, {0x2460, 0x24E9}, {0x24EB, 0x254B}, {0x2550, 0x2573}, {0x2580, 0x258F}, {0x2592, 0x2595}, {0x25A0, 0x25A1}, {0x25A3, 0x25A9}, {0x25B2, 0x25B3}, {0x25B6, 0x25B7}, {0x25BC, 0x25BD}, {0x25C0, 0x25C1}, {0x25C6, 0x25C8}, {0x25CB, 0x25CB}, {0x25CE, 0x25D1}, {0x25E2, 0x25E5}, {0x25EF, 0x25EF}, {0x2605, 0x2606}, {0x2609, 0x2609}, {0x260E, 0x260F}, {0x2614, 0x2615}, {0x261C, 0x261C}, {0x261E, 0x261E}, {0x2640, 0x2640}, {0x2642, 0x2642}, {0x2660, 0x2661}, {0x2663, 0x2665}, {0x2667, 0x266A}, {0x266C, 0x266D}, {0x266F, 0x266F}, {0x273D, 0x273D}, {0x2776, 0x277F}, {0xE000, 0xF8FF}, {0xFFFD, 0xFFFD}, {0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD}}; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, ambiguous, sizeof(ambiguous) / sizeof(struct interval) - 1)) + return 2; + + return mk_wcwidth(ucs); +} + + +static int mk_wcswidth_cjk(const uint32_t *pwcs, size_t n) { + int w, width = 0; + + for (; *pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth_cjk(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} + +// ################################ +// ### The rest added by Paul Evans + +static const struct interval fullwidth[] = { +#include "fullwidth.inc" +}; + +INTERNAL int vterm_unicode_width(uint32_t codepoint) { + if (bisearch(codepoint, fullwidth, sizeof(fullwidth) / sizeof(fullwidth[0]) - 1)) + return 2; + + return mk_wcwidth(codepoint); +} + +INTERNAL int vterm_unicode_is_combining(uint32_t codepoint) { + return bisearch(codepoint, combining, sizeof(combining) / sizeof(struct interval) - 1); +} diff --git a/extlib/libvterm/utf8.h b/extlib/libvterm/utf8.h new file mode 100644 index 0000000..9a336d3 --- /dev/null +++ b/extlib/libvterm/utf8.h @@ -0,0 +1,39 @@ +/* The following functions copied and adapted from libtermkey + * + * http://www.leonerd.org.uk/code/libtermkey/ + */ +static inline unsigned int utf8_seqlen(long codepoint) +{ + if(codepoint < 0x0000080) return 1; + if(codepoint < 0x0000800) return 2; + if(codepoint < 0x0010000) return 3; + if(codepoint < 0x0200000) return 4; + if(codepoint < 0x4000000) return 5; + return 6; +} + +/* Does NOT NUL-terminate the buffer */ +static int fill_utf8(long codepoint, char *str) +{ + int nbytes = utf8_seqlen(codepoint); + + // This is easier done backwards + int b = nbytes; + while(b > 1) { + b--; + str[b] = 0x80 | (codepoint & 0x3f); + codepoint >>= 6; + } + + switch(nbytes) { + case 1: str[0] = (codepoint & 0x7f); break; + case 2: str[0] = 0xc0 | (codepoint & 0x1f); break; + case 3: str[0] = 0xe0 | (codepoint & 0x0f); break; + case 4: str[0] = 0xf0 | (codepoint & 0x07); break; + case 5: str[0] = 0xf8 | (codepoint & 0x03); break; + case 6: str[0] = 0xfc | (codepoint & 0x01); break; + } + + return nbytes; +} +/* end copy */ diff --git a/extlib/libvterm/vterm.c b/extlib/libvterm/vterm.c new file mode 100644 index 0000000..6cbf077 --- /dev/null +++ b/extlib/libvterm/vterm.c @@ -0,0 +1,360 @@ +#include "vterm_internal.h" + +#include +#include +#include +#include + +#include "../../runtime/panic_assert.h" +#include "../../runtime/printf.h" + +/***************** + * API functions * + *****************/ + +/* +static void *default_malloc(size_t size, void *allocdata) { + void *ptr = malloc(size); + if (ptr) + memset(ptr, 0, size); + return ptr; +} + +static void default_free(void *ptr, void *allocdata) { + free(ptr); +} +*/ + +static VTermAllocatorFunctions default_allocator = { + //.malloc = &default_malloc, + //.free = &default_free, +}; + +VTerm *vterm_new(int rows, int cols) { + return vterm_new_with_allocator(rows, cols, &default_allocator, NULL); +} + +VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata) { + /* Need to bootstrap using the allocator function directly */ + VTerm *vt = (*funcs->malloc)(sizeof(VTerm), allocdata); + + vt->allocator = funcs; + vt->allocdata = allocdata; + + vt->rows = rows; + vt->cols = cols; + + vt->parser.state = NORMAL; + + vt->parser.callbacks = NULL; + vt->parser.cbdata = NULL; + + vt->parser.strbuffer_len = 64; + vt->parser.strbuffer_cur = 0; + vt->parser.strbuffer = vterm_allocator_malloc(vt, vt->parser.strbuffer_len); + + vt->outfunc = NULL; + vt->outdata = NULL; + + vt->outbuffer_len = 64; + vt->outbuffer_cur = 0; + vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len); + + vt->tmpbuffer_len = 64; + vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len); + + return vt; +} + +void vterm_free(VTerm *vt) { + if (vt->screen) + vterm_screen_free(vt->screen); + + if (vt->state) + vterm_state_free(vt->state); + + vterm_allocator_free(vt, vt->parser.strbuffer); + vterm_allocator_free(vt, vt->outbuffer); + vterm_allocator_free(vt, vt->tmpbuffer); + + vterm_allocator_free(vt, vt); +} + +INTERNAL void *vterm_allocator_malloc(VTerm *vt, size_t size) { + return (*vt->allocator->malloc)(size, vt->allocdata); +} + +INTERNAL void vterm_allocator_free(VTerm *vt, void *ptr) { + (*vt->allocator->free)(ptr, vt->allocdata); +} + +void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp) { + if (rowsp) + *rowsp = vt->rows; + if (colsp) + *colsp = vt->cols; +} + +void vterm_set_size(VTerm *vt, int rows, int cols) { + vt->rows = rows; + vt->cols = cols; + + if (vt->parser.callbacks && vt->parser.callbacks->resize) + (*vt->parser.callbacks->resize)(rows, cols, vt->parser.cbdata); +} + +int vterm_get_utf8(const VTerm *vt) { + return vt->mode.utf8; +} + +void vterm_set_utf8(VTerm *vt, int is_utf8) { + vt->mode.utf8 = is_utf8; +} + +void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user) { + vt->outfunc = func; + vt->outdata = user; +} + +INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len) { + if (vt->outfunc) { + (vt->outfunc)(bytes, len, vt->outdata); + return; + } + + if (len > vt->outbuffer_len - vt->outbuffer_cur) + return; + + memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len); + vt->outbuffer_cur += len; +} + +INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args) { + size_t len = vsnprintf(vt->tmpbuffer, vt->tmpbuffer_len, format, args); + + vterm_push_output_bytes(vt, vt->tmpbuffer, len); +} + +INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...) { + va_list args; + va_start(args, format); + vterm_push_output_vsprintf(vt, format, args); + va_end(args); +} + +INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...) { + size_t cur; + + if (ctrl >= 0x80 && !vt->mode.ctrl8bit) + cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, ESC_S "%c", ctrl - 0x40); + else + cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, "%c", ctrl); + + if (cur >= vt->tmpbuffer_len) + return; + + va_list args; + va_start(args, fmt); + cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, fmt, args); + va_end(args); + + if (cur >= vt->tmpbuffer_len) + return; + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); +} + +INTERNAL void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...) { + size_t cur = 0; + + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "\x90" : ESC_S "P"); // DCS + + if (cur >= vt->tmpbuffer_len) + return; + + va_list args; + va_start(args, fmt); + cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, fmt, args); + va_end(args); + + if (cur >= vt->tmpbuffer_len) + return; + + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "\x9C" : ESC_S "\\"); // ST + + if (cur >= vt->tmpbuffer_len) + return; + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); +} + +size_t vterm_output_get_buffer_size(const VTerm *vt) { + return vt->outbuffer_len; +} + +size_t vterm_output_get_buffer_current(const VTerm *vt) { + return vt->outbuffer_cur; +} + +size_t vterm_output_get_buffer_remaining(const VTerm *vt) { + return vt->outbuffer_len - vt->outbuffer_cur; +} + +size_t vterm_output_read(VTerm *vt, char *buffer, size_t len) { + if (len > vt->outbuffer_cur) + len = vt->outbuffer_cur; + + memcpy(buffer, vt->outbuffer, len); + + if (len < vt->outbuffer_cur) + memmove(vt->outbuffer, vt->outbuffer + len, vt->outbuffer_cur - len); + + vt->outbuffer_cur -= len; + + return len; +} + +VTermValueType vterm_get_attr_type(VTermAttr attr) { + switch (attr) { + case VTERM_ATTR_BOLD: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_UNDERLINE: return VTERM_VALUETYPE_INT; + case VTERM_ATTR_ITALIC: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_BLINK: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_REVERSE: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_STRIKE: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_FONT: return VTERM_VALUETYPE_INT; + case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR; + case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR; + + case VTERM_N_ATTRS: return 0; + } + return 0; /* UNREACHABLE */ +} + +VTermValueType vterm_get_prop_type(VTermProp prop) { + switch (prop) { + case VTERM_PROP_CURSORVISIBLE: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORBLINK: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_ALTSCREEN: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_TITLE: return VTERM_VALUETYPE_STRING; + case VTERM_PROP_ICONNAME: return VTERM_VALUETYPE_STRING; + case VTERM_PROP_REVERSE: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORSHAPE: return VTERM_VALUETYPE_INT; + case VTERM_PROP_MOUSE: return VTERM_VALUETYPE_INT; + + case VTERM_N_PROPS: return 0; + } + return 0; /* UNREACHABLE */ +} + +void vterm_scroll_rect(VTermRect rect, int downward, int rightward, int (*moverect)(VTermRect src, VTermRect dest, void *user), int (*eraserect)(VTermRect rect, int selective, void *user), void *user) { + VTermRect src; + VTermRect dest; + + if (abs(downward) >= rect.end_row - rect.start_row || + abs(rightward) >= rect.end_col - rect.start_col) { + /* Scroll more than area; just erase the lot */ + (*eraserect)(rect, 0, user); + return; + } + + if (rightward >= 0) { + /* rect: [XXX................] + * src: [----------------] + * dest: [----------------] + */ + dest.start_col = rect.start_col; + dest.end_col = rect.end_col - rightward; + src.start_col = rect.start_col + rightward; + src.end_col = rect.end_col; + } else { + /* rect: [................XXX] + * src: [----------------] + * dest: [----------------] + */ + int leftward = -rightward; + dest.start_col = rect.start_col + leftward; + dest.end_col = rect.end_col; + src.start_col = rect.start_col; + src.end_col = rect.end_col - leftward; + } + + if (downward >= 0) { + dest.start_row = rect.start_row; + dest.end_row = rect.end_row - downward; + src.start_row = rect.start_row + downward; + src.end_row = rect.end_row; + } else { + int upward = -downward; + dest.start_row = rect.start_row + upward; + dest.end_row = rect.end_row; + src.start_row = rect.start_row; + src.end_row = rect.end_row - upward; + } + + if (moverect) + (*moverect)(dest, src, user); + + if (downward > 0) + rect.start_row = rect.end_row - downward; + else if (downward < 0) + rect.end_row = rect.start_row - downward; + + if (rightward > 0) + rect.start_col = rect.end_col - rightward; + else if (rightward < 0) + rect.end_col = rect.start_col - rightward; + + (*eraserect)(rect, 0, user); +} + +void vterm_copy_cells(VTermRect dest, VTermRect src, void (*copycell)(VTermPos dest, VTermPos src, void *user), void *user) { + int downward = src.start_row - dest.start_row; + int rightward = src.start_col - dest.start_col; + + int init_row, test_row, init_col, test_col; + int inc_row, inc_col; + + if (downward < 0) { + init_row = dest.end_row - 1; + test_row = dest.start_row - 1; + inc_row = -1; + } else /* downward >= 0 */ { + init_row = dest.start_row; + test_row = dest.end_row; + inc_row = +1; + } + + if (rightward < 0) { + init_col = dest.end_col - 1; + test_col = dest.start_col - 1; + inc_col = -1; + } else /* rightward >= 0 */ { + init_col = dest.start_col; + test_col = dest.end_col; + inc_col = +1; + } + + VTermPos pos; + for (pos.row = init_row; pos.row != test_row; pos.row += inc_row) + for (pos.col = init_col; pos.col != test_col; pos.col += inc_col) { + VTermPos srcpos = {pos.row + downward, pos.col + rightward}; + (*copycell)(pos, srcpos, user); + } +} + +void vterm_check_version(int major, int minor) { + if (major != VTERM_VERSION_MAJOR) { + //fprintf(stderr, "libvterm major version mismatch; %d (wants) != %d (library)\n", major, VTERM_VERSION_MAJOR); + Panicf("libvterm major version mismatch; %d (wants) != %d (library)\n", major, VTERM_VERSION_MAJOR); + } + + if (minor > VTERM_VERSION_MINOR) { + //fprintf(stderr, "libvterm minor version mismatch; %d (wants) > %d (library)\n", minor, VTERM_VERSION_MINOR); + Panicf("libvterm minor version mismatch; %d (wants) > %d (library)\n", minor, VTERM_VERSION_MINOR); + } + + // Happy +} diff --git a/extlib/libvterm/vterm.h b/extlib/libvterm/vterm.h new file mode 100644 index 0000000..03c2f77 --- /dev/null +++ b/extlib/libvterm/vterm.h @@ -0,0 +1,533 @@ +#ifndef __VTERM_H__ +#define __VTERM_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include "vterm_keycodes.h" + +#define VTERM_VERSION_MAJOR 0 +#define VTERM_VERSION_MINOR 1 + +#define VTERM_CHECK_VERSION \ + vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR) + +typedef struct VTerm VTerm; +typedef struct VTermState VTermState; +typedef struct VTermScreen VTermScreen; + +typedef struct { + int row; + int col; +} VTermPos; + +/* some small utility functions; we can just keep these static here */ + +/* order points by on-screen flow order */ +static inline int vterm_pos_cmp(VTermPos a, VTermPos b) +{ + return (a.row == b.row) ? a.col - b.col : a.row - b.row; +} + +typedef struct { + int start_row; + int end_row; + int start_col; + int end_col; +} VTermRect; + +/* true if the rect contains the point */ +static inline int vterm_rect_contains(VTermRect r, VTermPos p) +{ + return p.row >= r.start_row && p.row < r.end_row && + p.col >= r.start_col && p.col < r.end_col; +} + +/* move a rect */ +static inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta) +{ + rect->start_row += row_delta; rect->end_row += row_delta; + rect->start_col += col_delta; rect->end_col += col_delta; +} + +/** + * Bit-field describing the content of the tagged union `VTermColor`. + */ +typedef enum { + /** + * If the lower bit of `type` is not set, the colour is 24-bit RGB. + */ + VTERM_COLOR_RGB = 0x00, + + /** + * The colour is an index into a palette of 256 colours. + */ + VTERM_COLOR_INDEXED = 0x01, + + /** + * Mask that can be used to extract the RGB/Indexed bit. + */ + VTERM_COLOR_TYPE_MASK = 0x01, + + /** + * If set, indicates that this colour should be the default foreground + * color, i.e. there was no SGR request for another colour. When + * rendering this colour it is possible to ignore "idx" and just use a + * colour that is not in the palette. + */ + VTERM_COLOR_DEFAULT_FG = 0x02, + + /** + * If set, indicates that this colour should be the default background + * color, i.e. there was no SGR request for another colour. A common + * option when rendering this colour is to not render a background at + * all, for example by rendering the window transparently at this spot. + */ + VTERM_COLOR_DEFAULT_BG = 0x04, + + /** + * Mask that can be used to extract the default foreground/background bit. + */ + VTERM_COLOR_DEFAULT_MASK = 0x06 +} VTermColorType; + +/** + * Returns true if the VTERM_COLOR_RGB `type` flag is set, indicating that the + * given VTermColor instance is an indexed colour. + */ +#define VTERM_COLOR_IS_INDEXED(col) \ + (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_INDEXED) + +/** + * Returns true if the VTERM_COLOR_INDEXED `type` flag is set, indicating that + * the given VTermColor instance is an rgb colour. + */ +#define VTERM_COLOR_IS_RGB(col) \ + (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_RGB) + +/** + * Returns true if the VTERM_COLOR_DEFAULT_FG `type` flag is set, indicating + * that the given VTermColor instance corresponds to the default foreground + * color. + */ +#define VTERM_COLOR_IS_DEFAULT_FG(col) \ + (!!((col)->type & VTERM_COLOR_DEFAULT_FG)) + +/** + * Returns true if the VTERM_COLOR_DEFAULT_BG `type` flag is set, indicating + * that the given VTermColor instance corresponds to the default background + * color. + */ +#define VTERM_COLOR_IS_DEFAULT_BG(col) \ + (!!((col)->type & VTERM_COLOR_DEFAULT_BG)) + +/** + * Tagged union storing either an RGB color or an index into a colour palette. + * In order to convert indexed colours to RGB, you may use the + * vterm_state_convert_color_to_rgb() or vterm_screen_convert_color_to_rgb() + * functions which lookup the RGB colour from the palette maintained by a + * VTermState or VTermScreen instance. + */ +typedef union { + /** + * Tag indicating which union member is actually valid. This variable + * coincides with the `type` member of the `rgb` and the `indexed` struct + * in memory. Please use the `VTERM_COLOR_IS_*` test macros to check whether + * a particular type flag is set. + */ + uint8_t type; + + /** + * Valid if `VTERM_COLOR_IS_RGB(type)` is true. Holds the RGB colour values. + */ + struct { + /** + * Same as the top-level `type` member stored in VTermColor. + */ + uint8_t type; + + /** + * The actual 8-bit red, green, blue colour values. + */ + uint8_t red, green, blue; + } rgb; + + /** + * If `VTERM_COLOR_IS_INDEXED(type)` is true, this member holds the index into + * the colour palette. + */ + struct { + /** + * Same as the top-level `type` member stored in VTermColor. + */ + uint8_t type; + + /** + * Index into the colour map. + */ + uint8_t idx; + } indexed; +} VTermColor; + +/** + * Constructs a new VTermColor instance representing the given RGB values. + */ +static inline void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green, + uint8_t blue) +{ + col->type = VTERM_COLOR_RGB; + col->rgb.red = red; + col->rgb.green = green; + col->rgb.blue = blue; +} + +/** + * Construct a new VTermColor instance representing an indexed color with the + * given index. + */ +static inline void vterm_color_indexed(VTermColor *col, uint8_t idx) +{ + col->type = VTERM_COLOR_INDEXED; + col->indexed.idx = idx; +} + +/** + * Compares two colours. Returns true if the colors are equal, false otherwise. + */ +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b); + +typedef enum { + /* VTERM_VALUETYPE_NONE = 0 */ + VTERM_VALUETYPE_BOOL = 1, + VTERM_VALUETYPE_INT, + VTERM_VALUETYPE_STRING, + VTERM_VALUETYPE_COLOR, + + VTERM_N_VALUETYPES +} VTermValueType; + +typedef union { + int boolean; + int number; + char *string; + VTermColor color; +} VTermValue; + +typedef enum { + /* VTERM_ATTR_NONE = 0 */ + VTERM_ATTR_BOLD = 1, // bool: 1, 22 + VTERM_ATTR_UNDERLINE, // number: 4, 21, 24 + VTERM_ATTR_ITALIC, // bool: 3, 23 + VTERM_ATTR_BLINK, // bool: 5, 25 + VTERM_ATTR_REVERSE, // bool: 7, 27 + VTERM_ATTR_STRIKE, // bool: 9, 29 + VTERM_ATTR_FONT, // number: 10-19 + VTERM_ATTR_FOREGROUND, // color: 30-39 90-97 + VTERM_ATTR_BACKGROUND, // color: 40-49 100-107 + + VTERM_N_ATTRS +} VTermAttr; + +typedef enum { + /* VTERM_PROP_NONE = 0 */ + VTERM_PROP_CURSORVISIBLE = 1, // bool + VTERM_PROP_CURSORBLINK, // bool + VTERM_PROP_ALTSCREEN, // bool + VTERM_PROP_TITLE, // string + VTERM_PROP_ICONNAME, // string + VTERM_PROP_REVERSE, // bool + VTERM_PROP_CURSORSHAPE, // number + VTERM_PROP_MOUSE, // number + + VTERM_N_PROPS +} VTermProp; + +enum { + VTERM_PROP_CURSORSHAPE_BLOCK = 1, + VTERM_PROP_CURSORSHAPE_UNDERLINE, + VTERM_PROP_CURSORSHAPE_BAR_LEFT, + + VTERM_N_PROP_CURSORSHAPES +}; + +enum { + VTERM_PROP_MOUSE_NONE = 0, + VTERM_PROP_MOUSE_CLICK, + VTERM_PROP_MOUSE_DRAG, + VTERM_PROP_MOUSE_MOVE, + + VTERM_N_PROP_MOUSES +}; + +typedef struct { + const uint32_t *chars; + int width; + unsigned int protected_cell:1; /* DECSCA-protected against DECSEL/DECSED */ + unsigned int dwl:1; /* DECDWL or DECDHL double-width line */ + unsigned int dhl:2; /* DECDHL double-height line (1=top 2=bottom) */ +} VTermGlyphInfo; + +typedef struct { + unsigned int doublewidth:1; /* DECDWL or DECDHL line */ + unsigned int doubleheight:2; /* DECDHL line (1=top 2=bottom) */ +} VTermLineInfo; + +typedef struct { + /* libvterm relies on this memory to be zeroed out before it is returned + * by the allocator. */ + void *(*malloc)(size_t size, void *allocdata); + void (*free)(void *ptr, void *allocdata); +} VTermAllocatorFunctions; + +void vterm_check_version(int major, int minor); + +VTerm *vterm_new(int rows, int cols); +VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata); +void vterm_free(VTerm* vt); + +void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp); +void vterm_set_size(VTerm *vt, int rows, int cols); + +int vterm_get_utf8(const VTerm *vt); +void vterm_set_utf8(VTerm *vt, int is_utf8); + +size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len); + +/* Setting output callback will override the buffer logic */ +typedef void VTermOutputCallback(const char *s, size_t len, void *user); +void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user); + +/* These buffer functions only work if output callback is NOT set + * These are deprecated and will be removed in a later version */ +size_t vterm_output_get_buffer_size(const VTerm *vt); +size_t vterm_output_get_buffer_current(const VTerm *vt); +size_t vterm_output_get_buffer_remaining(const VTerm *vt); + +/* This too */ +size_t vterm_output_read(VTerm *vt, char *buffer, size_t len); + +void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod); +void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod); + +void vterm_keyboard_start_paste(VTerm *vt); +void vterm_keyboard_end_paste(VTerm *vt); + +void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod); +void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod); + +// ------------ +// Parser layer +// ------------ + +/* Flag to indicate non-final subparameters in a single CSI parameter. + * Consider + * CSI 1;2:3:4;5a + * 1 4 and 5 are final. + * 2 and 3 are non-final and will have this bit set + * + * Don't confuse this with the final byte of the CSI escape; 'a' in this case. + */ +#define CSI_ARG_FLAG_MORE (1U<<31) +#define CSI_ARG_MASK (~(1U<<31)) + +#define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE) +#define CSI_ARG(a) ((a) & CSI_ARG_MASK) + +/* Can't use -1 to indicate a missing argument; use this instead */ +#define CSI_ARG_MISSING ((1UL<<31)-1) + +#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING) +#define CSI_ARG_OR(a,def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a)) +#define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a)) + +typedef struct { + int (*text)(const char *bytes, size_t len, void *user); + int (*control)(unsigned char control, void *user); + int (*escape)(const char *bytes, size_t len, void *user); + int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); + int (*osc)(const char *command, size_t cmdlen, void *user); + int (*dcs)(const char *command, size_t cmdlen, void *user); + int (*resize)(int rows, int cols, void *user); +} VTermParserCallbacks; + +void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user); +void *vterm_parser_get_cbdata(VTerm *vt); + +// ----------- +// State layer +// ----------- + +typedef struct { + int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user); + int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); + int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user); + int (*moverect)(VTermRect dest, VTermRect src, void *user); + int (*erase)(VTermRect rect, int selective, void *user); + int (*initpen)(void *user); + int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user); + int (*settermprop)(VTermProp prop, VTermValue *val, void *user); + int (*bell)(void *user); + int (*resize)(int rows, int cols, VTermPos *delta, void *user); + int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user); +} VTermStateCallbacks; + +VTermState *vterm_obtain_state(VTerm *vt); + +void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user); +void *vterm_state_get_cbdata(VTermState *state); + +// Only invokes control, csi, osc, dcs +void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user); +void *vterm_state_get_unrecognised_fbdata(VTermState *state); + +void vterm_state_reset(VTermState *state, int hard); +void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos); +void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg); +void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col); +void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg); +void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col); +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright); +int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val); +int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val); +void vterm_state_focus_in(VTermState *state); +void vterm_state_focus_out(VTermState *state); +const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row); + +/** + * Makes sure that the given color `col` is indeed an RGB colour. After this + * function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other + * flags stored in `col->type` will have been reset. + * + * @param state is the VTermState instance from which the colour palette should + * be extracted. + * @param col is a pointer at the VTermColor instance that should be converted + * to an RGB colour. + */ +void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col); + +// ------------ +// Screen layer +// ------------ + +typedef struct { + unsigned int bold : 1; + unsigned int underline : 2; + unsigned int italic : 1; + unsigned int blink : 1; + unsigned int reverse : 1; + unsigned int strike : 1; + unsigned int font : 4; /* 0 to 9 */ + unsigned int dwl : 1; /* On a DECDWL or DECDHL line */ + unsigned int dhl : 2; /* On a DECDHL line (1=top 2=bottom) */ +} VTermScreenCellAttrs; + +enum { + VTERM_UNDERLINE_OFF, + VTERM_UNDERLINE_SINGLE, + VTERM_UNDERLINE_DOUBLE, + VTERM_UNDERLINE_CURLY, +}; + +typedef struct { +#define VTERM_MAX_CHARS_PER_CELL 6 + uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; + char width; + VTermScreenCellAttrs attrs; + VTermColor fg, bg; +} VTermScreenCell; + +typedef struct { + int (*damage)(VTermRect rect, void *user); + int (*moverect)(VTermRect dest, VTermRect src, void *user); + int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); + int (*settermprop)(VTermProp prop, VTermValue *val, void *user); + int (*bell)(void *user); + int (*resize)(int rows, int cols, void *user); + int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); + int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); +} VTermScreenCallbacks; + +VTermScreen *vterm_obtain_screen(VTerm *vt); + +void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user); +void *vterm_screen_get_cbdata(VTermScreen *screen); + +// Only invokes control, csi, osc, dcs +void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user); +void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen); + +void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen); + +typedef enum { + VTERM_DAMAGE_CELL, /* every cell */ + VTERM_DAMAGE_ROW, /* entire rows */ + VTERM_DAMAGE_SCREEN, /* entire screen */ + VTERM_DAMAGE_SCROLL, /* entire screen + scrollrect */ + + VTERM_N_DAMAGES +} VTermDamageSize; + +void vterm_screen_flush_damage(VTermScreen *screen); +void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size); + +void vterm_screen_reset(VTermScreen *screen, int hard); + +/* Neither of these functions NUL-terminate the buffer */ +size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect); +size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect); + +typedef enum { + VTERM_ATTR_BOLD_MASK = 1 << 0, + VTERM_ATTR_UNDERLINE_MASK = 1 << 1, + VTERM_ATTR_ITALIC_MASK = 1 << 2, + VTERM_ATTR_BLINK_MASK = 1 << 3, + VTERM_ATTR_REVERSE_MASK = 1 << 4, + VTERM_ATTR_STRIKE_MASK = 1 << 5, + VTERM_ATTR_FONT_MASK = 1 << 6, + VTERM_ATTR_FOREGROUND_MASK = 1 << 7, + VTERM_ATTR_BACKGROUND_MASK = 1 << 8, + + VTERM_ALL_ATTRS_MASK = (1 << 9) - 1 +} VTermAttrMask; + +int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs); + +int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell); + +int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos); + +/** + * Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state` + * instance. + */ +void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col); + +// --------- +// Utilities +// --------- + +VTermValueType vterm_get_attr_type(VTermAttr attr); +VTermValueType vterm_get_prop_type(VTermProp prop); + +void vterm_scroll_rect(VTermRect rect, + int downward, + int rightward, + int (*moverect)(VTermRect src, VTermRect dest, void *user), + int (*eraserect)(VTermRect rect, int selective, void *user), + void *user); + +void vterm_copy_cells(VTermRect dest, + VTermRect src, + void (*copycell)(VTermPos dest, VTermPos src, void *user), + void *user); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/extlib/libvterm/vterm_internal.h b/extlib/libvterm/vterm_internal.h new file mode 100644 index 0000000..b1e6e86 --- /dev/null +++ b/extlib/libvterm/vterm_internal.h @@ -0,0 +1,252 @@ +#ifndef __VTERM_INTERNAL_H__ +#define __VTERM_INTERNAL_H__ + +#include "vterm.h" + +#include + +#if defined(__GNUC__) +# define INTERNAL __attribute__((visibility("internal"))) +#else +# define INTERNAL +#endif + +#ifdef DEBUG +# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__) +#else +# define DEBUG_LOG(...) +#endif + +#define ESC_S "\x1b" + +#define INTERMED_MAX 16 + +#define CSI_ARGS_MAX 16 +#define CSI_LEADER_MAX 16 + +typedef struct VTermEncoding VTermEncoding; + +typedef struct { + VTermEncoding *enc; + + // This size should be increased if required by other stateful encodings + char data[4*sizeof(uint32_t)]; +} VTermEncodingInstance; + +struct VTermPen +{ + VTermColor fg; + VTermColor bg; + unsigned int bold:1; + unsigned int underline:2; + unsigned int italic:1; + unsigned int blink:1; + unsigned int reverse:1; + unsigned int strike:1; + unsigned int font:4; /* To store 0-9 */ +}; + +struct VTermState +{ + VTerm *vt; + + const VTermStateCallbacks *callbacks; + void *cbdata; + + const VTermParserCallbacks *fallbacks; + void *fbdata; + + int rows; + int cols; + + /* Current cursor position */ + VTermPos pos; + + int at_phantom; /* True if we're on the "81st" phantom column to defer a wraparound */ + + int scrollregion_top; + int scrollregion_bottom; /* -1 means unbounded */ +#define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > -1 ? (state)->scrollregion_bottom : (state)->rows) + int scrollregion_left; +#define SCROLLREGION_LEFT(state) ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0) + int scrollregion_right; /* -1 means unbounded */ +#define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin && (state)->scrollregion_right > -1 ? (state)->scrollregion_right : (state)->cols) + + /* Bitvector of tab stops */ + unsigned char *tabstops; + + VTermLineInfo *lineinfo; +#define ROWWIDTH(state,row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols) +#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row) + + /* Mouse state */ + int mouse_col, mouse_row; + int mouse_buttons; + int mouse_flags; +#define MOUSE_WANT_CLICK 0x01 +#define MOUSE_WANT_DRAG 0x02 +#define MOUSE_WANT_MOVE 0x04 + + enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT } mouse_protocol; + + /* Last glyph output, for Unicode recombining purposes */ + uint32_t *combine_chars; + size_t combine_chars_size; // Number of ELEMENTS in the above + int combine_width; // The width of the glyph above + VTermPos combine_pos; // Position before movement + + struct { + unsigned int keypad:1; + unsigned int cursor:1; + unsigned int autowrap:1; + unsigned int insert:1; + unsigned int newline:1; + unsigned int cursor_visible:1; + unsigned int cursor_blink:1; + unsigned int cursor_shape:2; + unsigned int alt_screen:1; + unsigned int origin:1; + unsigned int screen:1; + unsigned int leftrightmargin:1; + unsigned int bracketpaste:1; + unsigned int report_focus:1; + } mode; + + VTermEncodingInstance encoding[4], encoding_utf8; + int gl_set, gr_set, gsingle_set; + + struct VTermPen pen; + + VTermColor default_fg; + VTermColor default_bg; + VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only + + int bold_is_highbright; + + unsigned int protected_cell : 1; + + /* Saved state under DEC mode 1048/1049 */ + struct { + VTermPos pos; + struct VTermPen pen; + + struct { + unsigned int cursor_visible:1; + unsigned int cursor_blink:1; + unsigned int cursor_shape:2; + } mode; + } saved; +}; + +typedef enum { + VTERM_PARSER_OSC, + VTERM_PARSER_DCS, + + VTERM_N_PARSER_TYPES +} VTermParserStringType; + +struct VTerm +{ + VTermAllocatorFunctions *allocator; + void *allocdata; + + int rows; + int cols; + + struct { + unsigned int utf8:1; + unsigned int ctrl8bit:1; + } mode; + + struct { + enum VTermParserState { + NORMAL, + CSI_LEADER, + CSI_ARGS, + CSI_INTERMED, + ESC, + /* below here are the "string states" */ + STRING, + ESC_IN_STRING, + } state; + + int intermedlen; + char intermed[INTERMED_MAX]; + + int csi_leaderlen; + char csi_leader[CSI_LEADER_MAX]; + + int csi_argi; + long csi_args[CSI_ARGS_MAX]; + + const VTermParserCallbacks *callbacks; + void *cbdata; + + VTermParserStringType stringtype; + char *strbuffer; + size_t strbuffer_len; + size_t strbuffer_cur; + } parser; + + /* len == malloc()ed size; cur == number of valid bytes */ + + VTermOutputCallback *outfunc; + void *outdata; + + char *outbuffer; + size_t outbuffer_len; + size_t outbuffer_cur; + + char *tmpbuffer; + size_t tmpbuffer_len; + + VTermState *state; + VTermScreen *screen; +}; + +struct VTermEncoding { + void (*init) (VTermEncoding *enc, void *data); + void (*decode)(VTermEncoding *enc, void *data, + uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t len); +}; + +typedef enum { + ENC_UTF8, + ENC_SINGLE_94 +} VTermEncodingType; + +void *vterm_allocator_malloc(VTerm *vt, size_t size); +void vterm_allocator_free(VTerm *vt, void *ptr); + +void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len); +void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args); +void vterm_push_output_sprintf(VTerm *vt, const char *format, ...); +void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...); +void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...); + +void vterm_state_free(VTermState *state); + +void vterm_state_newpen(VTermState *state); +void vterm_state_resetpen(VTermState *state); +void vterm_state_setpen(VTermState *state, const long args[], int argcount); +int vterm_state_getpen(VTermState *state, long args[], int argcount); +void vterm_state_savepen(VTermState *state, int save); + +enum { + C1_SS3 = 0x8f, + C1_DCS = 0x90, + C1_CSI = 0x9b, + C1_ST = 0x9c, +}; + +void vterm_state_push_output_sprintf_CSI(VTermState *vts, const char *format, ...); + +void vterm_screen_free(VTermScreen *screen); + +VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation); + +int vterm_unicode_width(uint32_t codepoint); +int vterm_unicode_is_combining(uint32_t codepoint); + +#endif diff --git a/extlib/libvterm/vterm_keycodes.h b/extlib/libvterm/vterm_keycodes.h new file mode 100644 index 0000000..661759f --- /dev/null +++ b/extlib/libvterm/vterm_keycodes.h @@ -0,0 +1,61 @@ +#ifndef __VTERM_INPUT_H__ +#define __VTERM_INPUT_H__ + +typedef enum { + VTERM_MOD_NONE = 0x00, + VTERM_MOD_SHIFT = 0x01, + VTERM_MOD_ALT = 0x02, + VTERM_MOD_CTRL = 0x04, + + VTERM_ALL_MODS_MASK = 0x07 +} VTermModifier; + +typedef enum { + VTERM_KEY_NONE, + + VTERM_KEY_ENTER, + VTERM_KEY_TAB, + VTERM_KEY_BACKSPACE, + VTERM_KEY_ESCAPE, + + VTERM_KEY_UP, + VTERM_KEY_DOWN, + VTERM_KEY_LEFT, + VTERM_KEY_RIGHT, + + VTERM_KEY_INS, + VTERM_KEY_DEL, + VTERM_KEY_HOME, + VTERM_KEY_END, + VTERM_KEY_PAGEUP, + VTERM_KEY_PAGEDOWN, + + VTERM_KEY_FUNCTION_0 = 256, + VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255, + + VTERM_KEY_KP_0, + VTERM_KEY_KP_1, + VTERM_KEY_KP_2, + VTERM_KEY_KP_3, + VTERM_KEY_KP_4, + VTERM_KEY_KP_5, + VTERM_KEY_KP_6, + VTERM_KEY_KP_7, + VTERM_KEY_KP_8, + VTERM_KEY_KP_9, + VTERM_KEY_KP_MULT, + VTERM_KEY_KP_PLUS, + VTERM_KEY_KP_COMMA, + VTERM_KEY_KP_MINUS, + VTERM_KEY_KP_PERIOD, + VTERM_KEY_KP_DIVIDE, + VTERM_KEY_KP_ENTER, + VTERM_KEY_KP_EQUAL, + + VTERM_KEY_MAX, // Must be last + VTERM_N_KEYS = VTERM_KEY_MAX +} VTermKey; + +#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n)) + +#endif diff --git a/graphics/graphics.c b/graphics/graphics.c new file mode 100644 index 0000000..19b3559 --- /dev/null +++ b/graphics/graphics.c @@ -0,0 +1,284 @@ + + +#include "graphics.h" +#include "unifont.h" +#include "../runtime/stdio.h" +#include + +#include +#include +#include "../runtime/memcpy.h" + +const HelosGraphics_Color + HelosGraphics_Color_Black = {0x00, 0x00, 0x00, 0xff}, + HelosGraphics_Color_White = {0xff, 0xff, 0xff, 0xff}, + HelosGraphics_Color_Red = {0x00, 0x00, 0xff, 0xff}, + HelosGraphics_Color_Green = {0x00, 0xff, 0x00, 0xff}, + HelosGraphics_Color_Blue = {0xff, 0x00, 0x00, 0xff}, + HelosGraphics_Color_Cyan = {0xff, 0xff, 0x00, 0xff}, + HelosGraphics_Color_Magenta = {0xff, 0x00, 0xff, 0xff}, + HelosGraphics_Color_Yellow = {0x00, 0xff, 0xff, 0xff}; + + +static EFI_GUID gopID = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; +static EFI_GRAPHICS_OUTPUT_PROTOCOL *gop; + +static EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info; +static UINTN sizeofInfo, nModes, nativeMode; +static HelosGraphics_Mode modes[128]; + +void *graphics_DeviceFramebuffer; + +void * graphics_Framebuffer; +uint64_t graphics_FramebufferSize; + +uint32_t graphics_Doublebuffer[2048 * 1024]; + + +void graphics_Init() { + assert(sizeof(HelosGraphics_Color) == sizeof(uint32_t) && "HelosGraphics_Color not packed to be 32-bit(4 bytes)"); + + EFI_STATUS status = efiBootServices->LocateProtocol(&gopID, NULL, (void **)&gop); + if (EFI_ERROR(status)) { + io_Printf("graphics_Init: Error locating GOP\r\n"); + return; + } + + + status = gop->QueryMode(gop, gop->Mode == NULL ? 0 : gop->Mode->Mode, &sizeofInfo, &info); + if (status == EFI_NOT_STARTED) { + status = gop->SetMode(gop, 0); + } + if (EFI_ERROR(status)) { + io_Printf("graphics_Init: Error getting native modes\r\n"); + return; + } else { + nativeMode = gop->Mode->Mode; + nModes = gop->Mode->MaxMode; + } + + for (int i = 0; i < nModes; i++) { + status = gop->QueryMode(gop, i, &sizeofInfo, &info); + modes[i].Width = info->HorizontalResolution; + modes[i].Height = info->VerticalResolution; + modes[i].PixelsPerLine = info->PixelsPerScanLine; + modes[i].PixelFormat = info->PixelFormat; + + if (modes[i].Width == HELOS_GRAPHICS_TARGET_MODE_WIDTH && modes[i].Height == HELOS_GRAPHICS_TARGET_MODE_HEIGHT) + nativeMode = i; + } + + // set the new mode + status = gop->SetMode(gop, nativeMode); + if (EFI_ERROR(status)) { + io_Printf("graphics_Init: Unable to set mode %d\r\n", nativeMode); + return; + } + + io_Printf( + "graphics_Init: Framebuffer:\r\n addr %08x len %d, size %dx%d, ppl %d\r\n", + graphics_DeviceFramebuffer = (void *)gop->Mode->FrameBufferBase, + graphics_FramebufferSize = gop->Mode->FrameBufferSize, + gop->Mode->Info->HorizontalResolution, + gop->Mode->Info->VerticalResolution, + gop->Mode->Info->PixelsPerScanLine); + + // warn the user if the framebuffer is not RGB + io_Printf("graphics_Init: Framebuffer format: "); +#define CASE(v) \ + break; \ + case v: io_Printf(#v "\r\n"); + switch (gop->Mode->Info->PixelFormat) { + CASE(PixelRedGreenBlueReserved8BitPerColor) + graphics_SetPixel = graphics_SetPixel_RGB; + CASE(PixelBlueGreenRedReserved8BitPerColor) + graphics_SetPixel = graphics_SetPixel_BGR; + CASE(PixelBitMask) + CASE(PixelBltOnly) + CASE(PixelFormatMax) + } +#undef CASE + + graphics_Framebuffer = (void *)graphics_Doublebuffer; + graphics_CursorX = graphics_CursorY = 0; +} + + +graphics_SetPixel_Type *graphics_SetPixel; + +void graphics_SetPixel_RGB(int posX, int posY, const HelosGraphics_Color *color) { + struct { + uint8_t R, G, B; + uint8_t A; + } colorRGB = {color->R, color->G, color->B, 0}; + + *((uint32_t *)(graphics_Framebuffer + modes[nativeMode].PixelsPerLine * 4 * posY + 4 * posX)) = (*((uint32_t *)&colorRGB)); +} + +void graphics_SetPixel_BGR(int posX, int posY, const HelosGraphics_Color *color) { + *((uint32_t *)(graphics_Framebuffer + modes[nativeMode].PixelsPerLine * 4 * posY + 4 * posX)) = (*((uint32_t *)color)) & 0x00ffffffu; +} + + +void graphics_GetSize(int *sizeX, int *sizeY, int *bitsPerPixel) { + *sizeX = modes[nativeMode].Width; + *sizeY = modes[nativeMode].Height; + *bitsPerPixel = 24; +} + +void graphics_ClearBuffer(const HelosGraphics_Color *color) { + uint32_t data; + + if (*((uint32_t *)color) == *((uint32_t *)&HelosGraphics_Color_Black)) { + memset(graphics_Framebuffer, 0, graphics_FramebufferSize); + return; + } + + if (modes[nativeMode].PixelFormat == PixelRedGreenBlueReserved8BitPerColor) { + struct { + uint8_t R, G, B; + uint8_t A; + } colorRGB = {color->R, color->G, color->B, 0}; + data = (*(uint32_t *)&colorRGB) & 0x00ffffffu; + } else if (modes[nativeMode].PixelFormat == PixelBlueGreenRedReserved8BitPerColor) { + data = (*(uint32_t *)color) & 0x00ffffffu; + } + + uint32_t *buffer = graphics_Framebuffer, *end = graphics_Framebuffer + graphics_FramebufferSize / sizeof(uint32_t); + while (buffer != end) { + *buffer = data; + buffer++; + } +} + +void graphics_SwapBuffer() { + memcpy(graphics_DeviceFramebuffer, graphics_Framebuffer, graphics_FramebufferSize); +} + +void graphics_FillPixel(int startX, int startY, int endX, int endY, const HelosGraphics_Color *color) { + // TODO Optimize this! This is too sloooow + if (gop->Mode->Info->PixelFormat == PixelBlueGreenRedReserved8BitPerColor) + for (int i = startX; i < endX; i++) + for (int j = startY; j < endY; j++) + *((uint32_t *)(graphics_Framebuffer + modes[nativeMode].PixelsPerLine * 4 * j + 4 * i)) = (*((uint32_t *)color)) & 0x00ffffffu; + else if (gop->Mode->Info->PixelFormat == PixelRedGreenBlueReserved8BitPerColor) + for (int i = startX; i < endX; i++) + for (int j = startY; j < endY; j++) { + struct { + uint8_t B, G, R; + uint8_t A; + } colorRGB = {color->B, color->G, color->R, 0}; + + *((uint32_t *)(graphics_Framebuffer + modes[nativeMode].PixelsPerLine * 4 * j + 4 * i)) = (*((uint32_t *)&colorRGB)); + } +} + +int graphics_CursorX, graphics_CursorY; + +void graphics_Scroll(int scrollY) { + memmove( + graphics_Doublebuffer, + graphics_Doublebuffer + modes[nativeMode].PixelsPerLine * scrollY, + sizeof(uint32_t) * (modes[nativeMode].PixelsPerLine * (modes[nativeMode].Height - scrollY))); + // TODO proper memset instead of this sloooow FillPixel + /*memset( + graphics_Doublebuffer + modes[nativeMode].PixelsPerLine * (modes[nativeMode].Height - scrollY), + 0, + sizeof(uint32_t) * modes[nativeMode].PixelsPerLine * (scrollY));*/ + graphics_FillPixel(0, modes[nativeMode].Height - scrollY, modes[nativeMode].Width, modes[nativeMode].Height, &HelosGraphics_Color_Black); +} + +void graphics_ElementSize(int sizeX, int sizeY) { + if (graphics_CursorX + sizeX >= modes[nativeMode].Width) { // line breaking required + graphics_CursorY += sizeY; + graphics_CursorX = 0; + } + if (graphics_CursorY + sizeY >= modes[nativeMode].Height) { // scrolling required + graphics_Scroll(sizeY + graphics_CursorY - modes[nativeMode].Height); + graphics_CursorY = modes[nativeMode].Height - sizeY; + } +} + +void graphics_Newline(int advanceY) { + graphics_CursorY += advanceY; + graphics_CursorX = 0; +} + + +void console_WriteChar(const HelosGraphics_Color *color, uint32_t c) { + int width; + switch (c) { + case '\n': + graphics_Newline(UNIFONT_CHAR_HEIGHT); + break; + case '\r': + graphics_CursorX = 0; + break; + default: + width = unifont_IsCharDoublewidth(c) ? UNIFONT_CHAR_WIDTH * 2 : UNIFONT_CHAR_WIDTH; + graphics_ElementSize(width, UNIFONT_CHAR_HEIGHT); + graphics_FillPixel(graphics_CursorX, graphics_CursorY, graphics_CursorX + width, graphics_CursorY + UNIFONT_CHAR_HEIGHT, &HelosGraphics_Color_Black); + unifont_DrawChar(graphics_CursorX, graphics_CursorY, color, c); + graphics_CursorX += width; + } +} + +void console_Write(const HelosGraphics_Color *color, const uint32_t *str, int len) { + bool wantSwap = false; + if (len == 0) { + while (*str != 0) { + console_WriteChar(color, *str); + if (*str == '\n') + wantSwap = true; + str++; + } + } else { + for (int i = 0; i < len; i++) { + console_WriteChar(color, str[i]); + if (str[i] == '\n') + wantSwap = true; + } + } + if (wantSwap) + graphics_SwapBuffer(); +} + +void console_WriteUTF16(const HelosGraphics_Color *color, const uint16_t *str, int len) { + bool wantSwap = false; + if (len == 0) { + while (*str != 0) { + console_WriteChar(color, *str); + if (*str == '\n') + wantSwap = true; + str++; + } + } else { + for (int i = 0; i < len; i++) { + console_WriteChar(color, str[i]); + if (str[i] == '\n') + wantSwap = true; + } + } + if (wantSwap) + graphics_SwapBuffer(); +} + +void console_WriteASCII(const HelosGraphics_Color *color, const char *str, int len) { + bool wantSwap = false; + if (len == 0) { + while (*str != 0) { + console_WriteChar(color, *str); + if (*str == '\n') + wantSwap = true; + str++; + } + } else { + for (int i = 0; i < len; i++) { + console_WriteChar(color, str[i]); + if (str[i] == '\n') + wantSwap = true; + } + } + if (wantSwap) + graphics_SwapBuffer(); +} diff --git a/graphics/graphics.h b/graphics/graphics.h new file mode 100644 index 0000000..b2cf83f --- /dev/null +++ b/graphics/graphics.h @@ -0,0 +1,86 @@ +#pragma once + +#include "../main.h" +#include "efiprot.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +// This defines a default target display mode for graphics_Init(). +#define HELOS_GRAPHICS_TARGET_MODE_WIDTH 1600 +#define HELOS_GRAPHICS_TARGET_MODE_HEIGHT 900 + +// HelosGraphics_Color is in ARGB little-endian packed format +// (B,G,R,A in byte order) +typedef struct { + uint8_t B, G, R; + uint8_t A; +} PACKED HelosGraphics_Color; + +extern const HelosGraphics_Color + HelosGraphics_Color_Black, + HelosGraphics_Color_White, + HelosGraphics_Color_Red, + HelosGraphics_Color_Green, + HelosGraphics_Color_Blue, + HelosGraphics_Color_Cyan, + HelosGraphics_Color_Magenta, + HelosGraphics_Color_Yellow; + +typedef struct { + int Width, Height; + int PixelsPerLine; + + EFI_GRAPHICS_PIXEL_FORMAT PixelFormat; +} HelosGraphics_Mode; + + +extern void * graphics_DeviceFramebuffer; // this is the framebuffer directly for the device via memory mapping. +extern void * graphics_Framebuffer; // this is the double-buffered framebuffer (back buffer) +extern uint64_t graphics_FramebufferSize; + + +// Init() must be called prior to ExitBootServices() +void graphics_Init(); + + +// bitsPerPixel does not count the alpha bits, i.e., is 24 on most modern monitors +void graphics_GetSize(int *sizeX, int *sizeY, int *bitsPerPixel); +void graphics_ClearBuffer(const HelosGraphics_Color *color); +void graphics_SwapBuffer(); + + +// graphics_SetPixel is set by Init() to match one of SetPixel_RGB/BGR according to the framebuffer format. +typedef void(graphics_SetPixel_Type)(int posX, int posY, const HelosGraphics_Color *color); +extern graphics_SetPixel_Type *graphics_SetPixel; + +// graphics_SetPixel_RGB/BGR writes the given pixel to the framebuffer in RGB/BGR format. +void graphics_SetPixel_RGB(int posX, int posY, const HelosGraphics_Color *color); +void graphics_SetPixel_BGR(int posX, int posY, const HelosGraphics_Color *color); + + +void graphics_FillPixel(int startX, int startY, int endX, int endY, const HelosGraphics_Color *color); + + +extern int graphics_CursorX, graphics_CursorY; + +// graphics_Scroll scrolls the display vertically by scrollY pixels. +void graphics_Scroll(int scrollY); +// graphics_ElementSize handles size of an graphics element: scrolling, line breaking, etc. +// +// It does not change CursorX/Y, however. +void graphics_ElementSize(int sizeX, int sizeY); +void graphics_Newline(int advanceY); + + +void console_WriteChar(const HelosGraphics_Color *color, uint32_t c); +void console_Write(const HelosGraphics_Color *color, const uint32_t *str, int len); +void console_WriteUTF16(const HelosGraphics_Color *color, const uint16_t *str, int len); +void console_WriteASCII(const HelosGraphics_Color *color, const char *str, int len); + + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/graphics/test_unifont.c b/graphics/test_unifont.c new file mode 100644 index 0000000..68322d9 --- /dev/null +++ b/graphics/test_unifont.c @@ -0,0 +1,18 @@ +#include + +#include "Unifont.h" + +int main() { + printf("Data at 0x%X\n\n", unifont_Data); + + for (int i = 0; i < 64; i++) { + printf("%02d(%02X):", i, i); + for (int j = 0; j < 32; j++) { + printf("%02X", (unsigned int)unifont_Data[i * 32 + j]); + } + printf("\n"); + } + + + return 0; +} diff --git a/graphics/unifont.c b/graphics/unifont.c new file mode 100644 index 0000000..7910a1e --- /dev/null +++ b/graphics/unifont.c @@ -0,0 +1,43 @@ + +#include "unifont.h" +#include "graphics.h" +#include + +size_t strlen(const char *); + +void unifont_DrawChar(int posX, int posY, const HelosGraphics_Color *color, uint32_t codepoint) { + const unsigned char *data = unifont_Data + codepoint * UNIFONT_CHAR_WIDTH * UNIFONT_CHAR_HEIGHT * 2 / 8; + bool wide = unifont_IsCharDoublewidth(codepoint); + + int charWidth = UNIFONT_CHAR_WIDTH * (wide ? 2 : 1); + + for (int x = 0; x < charWidth; x++) + for (int y = 0; y < UNIFONT_CHAR_HEIGHT; y++) { + int pos = y * charWidth + x; + if (data[pos / 8] & (1u << (7 - pos % 8))) + graphics_SetPixel(posX + x, posY + y, color); + } +} + +void unifont_DrawString(int posX, int posY, const HelosGraphics_Color *color, const uint32_t *codepoints, int count) { + for (const uint32_t *end = codepoints + count; codepoints != end; codepoints++) { + unifont_DrawChar(posX, posY, color, *codepoints); + posX += UNIFONT_CHAR_WIDTH * (unifont_IsCharDoublewidth(*codepoints) ? 2 : 1); + } +} +void unifont_DrawStringUTF16(int posX, int posY, const HelosGraphics_Color *color, const uint16_t *codepoints, int count) { + for (const uint16_t *end = codepoints + count; codepoints != end; codepoints++) { + unifont_DrawChar(posX, posY, color, *codepoints); + posX += UNIFONT_CHAR_WIDTH * (unifont_IsCharDoublewidth(*codepoints) ? 2 : 1); + } +} +void unifont_DrawStringASCII(int posX, int posY, const HelosGraphics_Color *color, const char *codepoints, int count) { + if (count == 0) { + count = strlen(codepoints); + } + for (const char *end = codepoints + count; codepoints != end; codepoints++) { + unifont_DrawChar(posX, posY, color, *codepoints); + //posX += UNIFONT_CHAR_WIDTH * (unifont_IsCharDoublewidth(*codepoints) ? 2 : 1); + posX += UNIFONT_CHAR_WIDTH; // visible ASCII chars are all single-width + } +} diff --git a/graphics/unifont.h b/graphics/unifont.h new file mode 100644 index 0000000..b164151 --- /dev/null +++ b/graphics/unifont.h @@ -0,0 +1,29 @@ +#pragma once + +#include "../main.h" +#include "graphics.h" + +#include + + +#define UNIFONT_MAX_CHAR (0xffff) +#define UNIFONT_CHAR_COUNT (0xffff + 1) + +#define UNIFONT_CHAR_WIDTH 8 +#define UNIFONT_CHAR_HEIGHT 16 + +extern const unsigned char unifont_Data[], unifont_Width[]; +extern const unsigned char unifont_Data_End[], unifont_Width_End[]; // Past-the-end pointers for the data files + +static inline bool unifont_IsCharDoublewidth(uint32_t codepoint) { + const unsigned char *ptr = unifont_Width + codepoint / 8; + if (ptr < unifont_Width_End) + return (*ptr) & (1u << (7 - codepoint % 8)); + else + return false; +} + +void unifont_DrawChar(int posX, int posY, const HelosGraphics_Color *color, uint32_t codepoint); +void unifont_DrawString(int posX, int posY, const HelosGraphics_Color *color, const uint32_t *codepoints, int count); +void unifont_DrawStringUTF16(int posX, int posY, const HelosGraphics_Color *color, const uint16_t *codepoints, int count); +void unifont_DrawStringASCII(int posX, int posY, const HelosGraphics_Color *color, const char *codepoints, int count); diff --git a/graphics/xcursor/xcursor.h b/graphics/xcursor/xcursor.h new file mode 100644 index 0000000..302a983 --- /dev/null +++ b/graphics/xcursor/xcursor.h @@ -0,0 +1,71 @@ +#pragma once + +#include "../../main.h" +#include "../graphics.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// XCursor file format - see man page xcursor(3) +// https://www.x.org/releases/X11R7.7/doc/man/man3/Xcursor.3.xhtml + +// Header +typedef struct { + char magic[4]; // Magic string "Xcur" + uint32_t headerSize; // Size of the header (16) + uint32_t version; // File version number + uint32_t numTOC; // Number of entries in the Table of Contents +} PACKED xcursor_Header; + +// Table of Content Entry +typedef struct { + uint32_t type; // Entry chunk type (0xfffe0001 = Comment, 0xfffd0002 = Image) + uint32_t subtype; // Type specific Subtype, size(width=height) for images + uint32_t offset; // Absolute byte position in the file +} PACKED xcursor_TOCEntry; + + +#define XCURSOR_CHUNKTYPE_COMMENT 0xfffe0001u +#define XCURSOR_CHUNKTYPE_IMAGE 0xfffd0002u + +// Common parts in different types of Chunk Headers +typedef struct { + uint32_t headerSize; // Size of the header + uint32_t type; // Type of the Chunk, matches the Entry Type in the TOC + uint32_t subtype; // Type specific subtype + uint32_t version; // Version number of the chunk type +} PACKED xcursor_ChunkHeader; + +#define XCURSOR_COMMENT_SUBTYPE_COPYRIGHT 0x00000001u +#define XCURSOR_COMMENT_SUBTYPE_LICENSE 0x00000002u +#define XCURSOR_COMMENT_SUBTYPE_OTHER 0x00000003u + +// Chunk Header for type Comment +typedef struct { + uint32_t headerSize; // Size of the header + uint32_t type; // Type of the Chunk, matches the Entry Type in the TOC + uint32_t subtype; // Type specific subtype, Copyright, License or Other + uint32_t version; // Version number of the chunk type, =1 + + uint32_t length; // Length in bytes of the UTF-8 string + char string[1]; // The UTF-8 string, spanning the rest of the chunk +} PACKED xcursor_ChunkHeader_Comment; + +typedef struct { + uint32_t headerSize; // Size of the header + uint32_t type; // Type of the Chunk, matches the Entry Type in the TOC + uint32_t subtype; // Type specific subtype, Copyright, License or Other + uint32_t version; // Version number of the chunk type, =1 + + uint32_t width, height; // Width/Height, <=0x7fff + uint32_t xhot, yhot; // X/Y hotpoint, <=Width/Height + uint32_t delay; // Delay between animation frames in milliseconds + HelosGraphics_Color pixels[1]; // Packed ARGB little-endian format pixels, with A at the highest byte (BGRA in byte order) +} PACKED xcursor_ChunkHeader_Image; + + +#ifdef __cplusplus +} +#endif diff --git a/interrupt/handler.asm.S b/interrupt/handler.asm.S new file mode 100644 index 0000000..b9312c5 --- /dev/null +++ b/interrupt/handler.asm.S @@ -0,0 +1,194 @@ +format elf64 + +; sysvx64call void interrupt_Handler(a, b, c, d, e, f) +extrn interrupt_Handler +; sysvx64call void interrupt_Handler128(a, b, c, d, e, f) +; Input: rax(syscall opcode) +extrn interrupt_Handler128 + +extrn io_WriteConsoleASCII + +public interrupt_Int0 +public interrupt_Int1 +public interrupt_Int2 +public interrupt_Int3 +public interrupt_Int4 +public interrupt_Int5 +public interrupt_Int6 +public interrupt_Int7 +public interrupt_Int8 +public interrupt_Int9 +public interrupt_Int10 +public interrupt_Int11 +public interrupt_Int12 +public interrupt_Int13 +public interrupt_Int14 +public interrupt_Int15 +public interrupt_Int16 +public interrupt_Int17 +public interrupt_Int18 +public interrupt_Int19 +public interrupt_Int20 +public interrupt_Int21 +public interrupt_Int22 +public interrupt_Int23 +public interrupt_Int24 +public interrupt_Int25 +public interrupt_Int26 +public interrupt_Int27 +public interrupt_Int28 +public interrupt_Int29 +public interrupt_Int30 +public interrupt_Int31 + +public interrupt_Int128 + + +section '.text' executable + + +macro inth op1 { + push rdi + mov rdi, op1 + push rsi + push rdx + mov rdx, [rsp+24] + push rcx + push r8 + push r9 + push r10 + push r11 + push rax + call interrupt_Handler + pop rax + pop r11 + pop r10 + pop r9 + pop r8 + pop rcx + pop rdx + pop rsi + pop rdi + iretq +} + +macro inth_err op1 { + push rsi + push rdi + mov rdi, op1 + mov esi, [rsp+16] + push rdx + mov rdx, [rsp+32] + push rcx + push r8 + push r9 + push r10 + push r11 + push rax + call interrupt_Handler + pop rax + pop r11 + pop r10 + pop r9 + pop r8 + pop rcx + pop rdx + pop rdi + pop rsi + add rsp, 8 ; pop the error code + iretq +} + +interrupt_Int0: ; does not return + inth 0 +interrupt_Int1: + inth 1 +interrupt_Int2: + inth 2 +interrupt_Int3: + inth 3 +interrupt_Int4: + inth 4 +interrupt_Int5: + inth 5 +interrupt_Int6: + inth 6 +interrupt_Int7: + inth 7 +interrupt_Int8: + inth 8 +interrupt_Int9: + inth 9 +interrupt_Int10: + inth_err 10 +interrupt_Int11: + inth_err 11 +interrupt_Int12: + inth_err 12 +interrupt_Int13: + inth_err 13 +interrupt_Int14: + inth_err 14 +interrupt_Int15: + inth 15 +interrupt_Int16: + inth 16 +interrupt_Int17: + inth_err 17 +interrupt_Int18: + inth 18 +interrupt_Int19: + inth 19 +interrupt_Int20: + inth 20 +interrupt_Int21: + inth_err 21 +interrupt_Int22: + inth 22 +interrupt_Int23: + inth 23 +interrupt_Int24: + inth 24 +interrupt_Int25: + inth 25 +interrupt_Int26: + inth 26 +interrupt_Int27: + inth 27 +interrupt_Int28: + inth 28 +interrupt_Int29: + inth 29 +interrupt_Int30: + inth 30 +interrupt_Int31: + inth 31 + +interrupt_Int128: + ;sub rsp, 32 + ;mov rcx, interrupt_string + ;call io_WriteConsoleASCII + ;add rsp, 32 + ;iretq + + ; no need to save the registers + ;push rax + ;push rdi + ;push rsi + ;push rdx + ;push rcx + ;push r8 + ;push r9 + ;push r10 + ;push r11 + call interrupt_Handler128 + ;pop r11 + ;pop r10 + ;pop r9 + ;pop r8 + ;pop rcx + ;pop rdx + ;pop rsi + ;pop rdi + ;pop rax + iretq diff --git a/interrupt/handler.c b/interrupt/handler.c new file mode 100644 index 0000000..7ed2340 --- /dev/null +++ b/interrupt/handler.c @@ -0,0 +1,52 @@ + +#include "interrupt.h" +#include "../runtime/panic_assert.h" + + +const char *interrupt_Descriptions[] = { + "Divide Error Execption", + "Debug Exception", + "NMI Interrupt", + "Breakpoint Exception", + "Overflow Exception", + "BOUND Range Exceeded Exception", + "Invalid Opcode Exception", + "Device Not Available Exception", + "Double Fault Exception", + "Coprocessor Segment Overrun", + "Invalid TSS Exception", + "Segment Not Present", + "Stack Fault Exception", + "General Protection Exception", + "Page-Fault Exception", + "Interrupt 15", + "x87 FPU Floating-Point Error", + "Alignment Check Exception", + "Machine-Check Exception", + "SIMD Floating-Point Exception", + "Interrupt 20", + "Control Protection Exception", + "Interrupt 22", + "Interrupt 23", + "Interrupt 24", + "Interrupt 25", + "Interrupt 26", + "Interrupt 27", + "Interrupt 28", + "Interrupt 29", + "Interrupt 30", + "Interrupt 31", +}; + +SYSV_ABI void interrupt_Handler(int vec, int errcode, uint64_t rip, int c, int d, int e) { + io_Printf("Panic: INT %02xh: %s, err=%d(0x%02x), rip=%llx\n", vec, interrupt_Descriptions[vec], errcode, errcode, rip); + __Panic_HaltSystem(); +} + +// handler for INT 80h +SYSV_ABI void interrupt_Handler128(int a, int b, int c, int d, int e, int f) { + int opcode; + asm volatile("mov %%eax, %0" + : "=rm"(opcode)); // read the opcode + io_Printf("INT 80h: EAX(opcode)=%d, abcdef=[%d,%d,%d,%d,%d,%d]\n", opcode, a, b, c, d, e, f); +} diff --git a/interrupt/handlers.h b/interrupt/handlers.h new file mode 100644 index 0000000..3434b85 --- /dev/null +++ b/interrupt/handlers.h @@ -0,0 +1,38 @@ +#pragma once + + +// the functions are not to be called, but to be taken address and put into IDT +void interrupt_Int0(); +void interrupt_Int1(); +void interrupt_Int2(); +void interrupt_Int3(); +void interrupt_Int4(); +void interrupt_Int5(); +void interrupt_Int6(); +void interrupt_Int7(); +void interrupt_Int8(); +void interrupt_Int9(); +void interrupt_Int10(); +void interrupt_Int11(); +void interrupt_Int12(); +void interrupt_Int13(); +void interrupt_Int14(); +void interrupt_Int15(); +void interrupt_Int16(); +void interrupt_Int17(); +void interrupt_Int18(); +void interrupt_Int19(); +void interrupt_Int20(); +void interrupt_Int21(); +void interrupt_Int22(); +void interrupt_Int23(); +void interrupt_Int24(); +void interrupt_Int25(); +void interrupt_Int26(); +void interrupt_Int27(); +void interrupt_Int28(); +void interrupt_Int29(); +void interrupt_Int30(); +void interrupt_Int31(); + +void interrupt_Int128(); diff --git a/interrupt/init.c b/interrupt/init.c new file mode 100644 index 0000000..0b4e0ea --- /dev/null +++ b/interrupt/init.c @@ -0,0 +1,118 @@ + +#include "interrupt.h" +#include "handlers.h" +#include "../memory/memory.h" +#include "../runtime/stdio.h" +#include "../runtime/panic_assert.h" +#include "testcode.h" + +interrupt_DescriptorTableReference *interrupt_IDTR, *interrupt_GDTR; +bool interrupt_Enabled; + + +/* +SYSV_ABI void interrupt_MapHandler(void *handler, int interrupt) { + //io_Printf("interrupt_MapHandler: handler %llx, int %d\n", handler, interrupt); + uint64_t *base = (uint64_t *)(KERNEL_IDT_MAPPING + interrupt * 16ull); + uint64_t b = 0; + + b |= (uint64_t)handler & 0xFFFFull; // Offset[15:0], 15:0 + b |= (uint64_t)GDT_EXEC_SELECTOR << 16; // Segment Selector, 31:16 + b |= IDT_TYPE_32_INTERRUPT_GATE; // Type = 32-bit Interrupt Gate, 44:40 + b |= IDT_RING0; // Ring 0, 46:45 + b |= IDT_PRESENT; // Present, 47 + b |= ((uint64_t)handler & 0xFFFF0000ull) << 32; // Offset[31:16], 63:48 + + *base = b; + *(base + 1) = (uint64_t)handler >> 32; +} +*/ + +// defined in assembly +SYSV_ABI void interrupt_MapHandler(void *handler, int interrupt); + + +void interrupt_Init() { + assert(sizeof(interrupt_DescriptorTableReference) == 10 && "GDTR/IDTR size must be 10 bytes"); + assert(offsetof(interrupt_DescriptorTableReference, base) == 2 && "GDTR/IDTR must be packed"); + + assert(KERNEL_IDTR_MAPPING % 4 == 0 && "IDTR not aligned to 4-byte"); + assert(KERNEL_GDTR_MAPPING % 4 == 0 && "GDTR not aligned to 4-byte"); + + + // allocate GDTR + io_WriteConsoleASCII("interrupt_Init() calling\n"); + interrupt_GDTR = (interrupt_DescriptorTableReference *)KERNEL_GDTR_MAPPING; + interrupt_GDTR->length = 4 * GDT_SIZE_BYTES - 1; + interrupt_GDTR->base = (void *)KERNEL_GDT_MAPPING; + io_WriteConsoleASCII("GDTR Written\n"); + + // set the 2 dummy gdts + uint64_t *gdt = (uint64_t *)KERNEL_GDT_MAPPING; + gdt[0] = 0; + gdt[1] = GDT_EXEC; + gdt[2] = GDT_DATA; + gdt[3] = GDT_EXEC_RING3; + gdt[4] = GDT_DATA_RING3; + io_WriteConsoleASCII("GDT Installed\n"); + + interrupt_LoadGDT(interrupt_GDTR); // set it! + io_WriteConsoleASCII("GDT OK\n"); + + //interrupt_Testcode(); + io_WriteConsoleASCII("Testcode OK\n"); + + + // allocate IDTR + //interrupt_IDTR = kMalloc(sizeof(interrupt_DescriptorTableReference)); + interrupt_IDTR = (interrupt_DescriptorTableReference *)KERNEL_IDTR_MAPPING; + interrupt_IDTR->length = KERNEL_IDT_SIZE - 1; + interrupt_IDTR->base = (void *)KERNEL_IDT_MAPPING; + io_WriteConsoleASCII("IDT Written\n"); + + interrupt_MapHandler(interrupt_Int0, 0); + interrupt_MapHandler(interrupt_Int1, 1); + interrupt_MapHandler(interrupt_Int2, 2); + interrupt_MapHandler(interrupt_Int3, 3); + interrupt_MapHandler(interrupt_Int4, 4); + interrupt_MapHandler(interrupt_Int5, 5); + interrupt_MapHandler(interrupt_Int6, 6); + interrupt_MapHandler(interrupt_Int7, 7); + interrupt_MapHandler(interrupt_Int8, 8); + interrupt_MapHandler(interrupt_Int9, 9); + interrupt_MapHandler(interrupt_Int10, 10); + interrupt_MapHandler(interrupt_Int11, 11); + interrupt_MapHandler(interrupt_Int12, 12); + interrupt_MapHandler(interrupt_Int13, 13); + interrupt_MapHandler(interrupt_Int14, 14); + interrupt_MapHandler(interrupt_Int15, 15); + interrupt_MapHandler(interrupt_Int16, 16); + interrupt_MapHandler(interrupt_Int17, 17); + interrupt_MapHandler(interrupt_Int18, 18); + interrupt_MapHandler(interrupt_Int19, 19); + interrupt_MapHandler(interrupt_Int20, 20); + interrupt_MapHandler(interrupt_Int21, 21); + interrupt_MapHandler(interrupt_Int22, 22); + interrupt_MapHandler(interrupt_Int23, 23); + interrupt_MapHandler(interrupt_Int24, 24); + interrupt_MapHandler(interrupt_Int25, 25); + interrupt_MapHandler(interrupt_Int26, 26); + interrupt_MapHandler(interrupt_Int27, 27); + interrupt_MapHandler(interrupt_Int28, 28); + interrupt_MapHandler(interrupt_Int29, 29); + interrupt_MapHandler(interrupt_Int30, 30); + interrupt_MapHandler(interrupt_Int31, 31); + interrupt_MapHandler(interrupt_Int128, 128); + io_WriteConsoleASCII("IDT Installed\n"); + + interrupt_LoadIDT(interrupt_IDTR); // set it! + + + io_WriteConsoleASCII("IDT OK\n"); + + interrupt_Enabled = true; + asm volatile("sti"); + + interrupt_ReloadSegments(); + io_WriteConsoleASCII("Segment Registers Reloaded\n"); +} diff --git a/interrupt/interrupt.h b/interrupt/interrupt.h new file mode 100644 index 0000000..9e8c944 --- /dev/null +++ b/interrupt/interrupt.h @@ -0,0 +1,74 @@ +#pragma once + +#include "../main.h" +#include "stdbool.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +#define GDT_SIZE_BYTES 4 +#define GDT_EXEC 0x00AF9A000000FFFFull // Base=0, Limit=max, Access=Present|Ring0|TypeUser|Exec|Readable, Flag=GranularityPage|Long +#define GDT_DATA 0x00EF92000000FFFFull // Base=0, Limit=max, Access=Present|Ring0|TypeUser|Writable, Flag=GranularityPage|Size +#define GDT_EXEC_RING3 0x00AFFA000000FFFFull // Base=0, Limit=max, Access=Present|Ring3|TypeUser|Exec|Readable, Flag=GranularityPage|Long +#define GDT_DATA_RING3 0x00EFF2000000FFFFull // Base=0, Limit=max, Access=Present|Ring3|TypeUser|Writable, Flag=GranularityPage|Size + +#define GDT_EXEC_SELECTOR 0x08 // SelectorIndex=1, TableIndicator=GDT(0), Privilege=Ring0 +#define GDT_DATA_SELECTOR 0x10 // SelectorIndex=2, TableIndicator=GDT(0), Privilege=Ring0 +#define GDT_EXEC_RING3_SELECTOR 0x1B // SelectorIndex=3, TableIndicator=GDT(0), Privilege=Ring3 +#define GDT_DATA_RING3_SELECTOR 0x23 // SelectorIndex=4, TableIndicator=GDT(0), Privilege=Ring3 + +#define IDT_PRESENT (1ull << 47) +#define IDT_RING0 0 +#define IDT_RING1 (1ull << 45) +#define IDT_RING2 (2ull << 45) +#define IDT_RING3 (3ull << 45) + +#define IDT_TYPE_32_CALL_GATE (0x0Cull << 40) +#define IDT_TYPE_32_INTERRUPT_GATE (0x0Eull << 40) +#define IDT_TYPE_32_TRAP_GATE (0x0Full << 40) + +typedef struct { + uint16_t length; + void * base; +} PACKED interrupt_DescriptorTableReference; +// address of IDTR and GDTR, allocated by kMalloc() and is never freed +extern interrupt_DescriptorTableReference *interrupt_IDTR, *interrupt_GDTR; + +// true if Init() has been called and interrupt handling is on +extern bool interrupt_Enabled; + +// initializes interrupt handling like IDT and a dummy GDT +void interrupt_Init(); + +SYSV_ABI void interrupt_MapHandler(void *handler, int interrupt); + + +// errorcode is 0 if nonexistent +// +// for IRQs, params are documented in assembly +SYSV_ABI void interrupt_Handler(int vec, int errcode, uint64_t rip, int c, int d, int e); + +// defined in assembly +SYSV_ABI void interrupt_LoadGDT(void *gdtr); +SYSV_ABI void interrupt_LoadIDT(void *idtr); +SYSV_ABI void interrupt_ReloadSegments(); + + +#define INTERRUPT_DISABLE \ + uintptr_t __interrupt_flags; \ + asm volatile("pushf\n\tcli\n\tpop %0" \ + : "=r"(__interrupt_flags) \ + : \ + : "memory") +#define INTERRUPT_RESTORE \ + asm volatile("push %0\n\tpopf" \ + : \ + : "rm"(__interrupt_flags) \ + : "memory", "cc") + + +#ifdef __cplusplus +} +#endif diff --git a/interrupt/interrupt_testcode.S b/interrupt/interrupt_testcode.S new file mode 100644 index 0000000..a88423c --- /dev/null +++ b/interrupt/interrupt_testcode.S @@ -0,0 +1,57 @@ +format elf64 + +extrn io_WriteConsoleASCII + + +section '.rodata' +interrupt_string: + db "Interrupt Testcode", 0x0A, 0x00 + + +section '.data' writable +align 4 +idt: + rb 50*16 + +idtr: + dw (50*16)-1 + dq idt + + +section '.text' executable +int_handler: + push rax + push rcx + push rdx + push r8 + push r9 + push r10 + push r11 + sub rsp, 32 + mov rcx, interrupt_string + call io_WriteConsoleASCII + add rsp, 32 + pop r11 + pop r10 + pop r9 + pop r8 + pop rdx + pop rcx + pop rax + iretq + + +public interrupt_Testcode +interrupt_Testcode: + lidt [idtr] + mov rax, int_handler + mov [idt+49*16], ax + mov word [idt+49*16+2], 0x08 + mov word [idt+49*16+4], 0x8e00 + shr rax, 16 + mov [idt+49*16+6], ax + shr rax, 16 + mov [idt+49*16+8], rax + + int 49 + ret diff --git a/interrupt/load_gdt.S b/interrupt/load_gdt.S new file mode 100644 index 0000000..79a9d70 --- /dev/null +++ b/interrupt/load_gdt.S @@ -0,0 +1,45 @@ +format elf64 + +public interrupt_ReloadSegments +public interrupt_LoadGDT +public interrupt_LoadIDT + + +section '.text' executable + +; sysvx64call void interrupt_LoadGDT(void* gdtr) +; +; Input: (void* rdi) +; Clobbers: none +interrupt_LoadGDT: + lgdt [rdi] + ret + +; sysvx64call void interrupt_ReloadSegments() +; +; Clobbers: rax +interrupt_ReloadSegments: + mov eax, 0x10 ; my data segment + xor ax, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + ;jmp 0x08:.flush + ; as in https://forum.osdev.org/viewtopic.php?f=1&t=30739 + ; farjump does not work in long mode, you need to do a far return: + pop rax + push qword 0x08 ; my code segment + push rax + retfq + +; sysvx64call void interrupt_LoadIDT(void* idtr) +; +; Input: (void* rdi) +; Clobbers: none +interrupt_LoadIDT: + lidt [rdi] + ret + diff --git a/interrupt/map_handler.S b/interrupt/map_handler.S new file mode 100644 index 0000000..b3b3905 --- /dev/null +++ b/interrupt/map_handler.S @@ -0,0 +1,25 @@ +format elf64 + +extrn io_WriteConsoleASCII + +public interrupt_MapHandler + + +section '.text' executable + +; sysvx64call void interrupt_MapHandler(uint64_t handler, int interrupt) +; +; Input: (uint64_t rdi, int rsi) +; Clobbers: rax, flags +interrupt_MapHandler: + mov rax, 0xFFFFFFFEC0000000 ; KERNEL_IDT_MAPPING + shl rsi, 4 ; rsi *= 16 + add rsi, rax ; rsi += KERNEL_IDT_MAPPING + mov [rsi], di + mov word [rsi+2], 0x08 ; GDT_EXEC_SELECTOR (index=1) + mov word [rsi+4], 0x8e00 + shr rdi, 16 + mov [rsi+6], di + shr rdi, 16 + mov [rsi+8], rdi + ret diff --git a/interrupt/syscall.S b/interrupt/syscall.S new file mode 100644 index 0000000..dbe3f69 --- /dev/null +++ b/interrupt/syscall.S @@ -0,0 +1,18 @@ +format elf64 + +public asm_Syscall as 'Syscall' ; syscall is a reserved token + +section ".text" executable + +; sysvx64call int Syscall(int syscall_id, int a,b,c,d,e,f) +asm_Syscall: + mov rax, rdi + mov rdi, rsi + mov rsi, rdx + mov rdx, rcx + mov rcx, r8 + mov r8, r9 + mov r9, [rsp+8] + int 0x80 + ret + diff --git a/interrupt/syscall.h b/interrupt/syscall.h new file mode 100644 index 0000000..406b398 --- /dev/null +++ b/interrupt/syscall.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../main.h" + +// this function is here, well, mostly just for fun. +// +// userspace in the far future should need this +SYSV_ABI long Syscall(int id, long a, long b, long c, long d, long e, long f); diff --git a/interrupt/testcode.h b/interrupt/testcode.h new file mode 100644 index 0000000..e63789a --- /dev/null +++ b/interrupt/testcode.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../main.h" + + +SYSV_ABI void interrupt_Testcode(); diff --git a/kernel/kmain.c b/kernel/kmain.c new file mode 100644 index 0000000..b035a53 --- /dev/null +++ b/kernel/kmain.c @@ -0,0 +1,51 @@ + +#include "../main.h" +#include "kmain.h" + +#include "../runtime/stdio.h" +#include "../runtime/panic_assert.h" +#include "../memory/memory.h" +#include "../memory/paging_internal.h" +#include "../interrupt/interrupt.h" +#include "../interrupt/handlers.h" +#include "../interrupt/syscall.h" +#include "../driver/irq/pic/pic.h" +#include "../driver/irq/pic/ps2/ps2.h" + +#include "../execformat/pe/reloc.h" +void execformat_pe_ReadSystemHeader(execformat_pe_PortableExecutable *pe); + +static void tellRIP() { + uint64_t a, b; + asm volatile("leaq (%%rip), %0\n\tleaq runtime_InitPaging(%%rip), %1" + : "=r"(a), "=r"(b)); + io_Printf("tellRIP(): Stack position: %llx, RIP=%llx, kMain_StackPosition:%llx(%llx), interrupt_Int128: %llx\n", &a, a, (uint64_t)&kMain_StackPosition, b, (uint64_t)interrupt_Int128); +} + + +SYSV_ABI void kMain() { + io_WriteConsoleASCII("Yes! kMain survived!\n"); + + uint64_t a; + asm volatile("leaq (%%rip), %0" + : "=r"(a)); + io_Printf("Stack position: %llx, RIP=%llx, runtime_InitPaging:%llx, interrupt_Int128: %llx\n", &a, a, (uint64_t)runtime_InitPaging, (uint64_t)interrupt_Int128); + + interrupt_Init(); + io_WriteConsoleASCII("Interrupts initialized\n"); + + Syscall(4, 1, 2, 3, 4, 5, 6); + io_WriteConsoleASCII("Returning from Syscall()\n"); + + tellRIP(); + + irq_pic_Init(); + io_WriteConsoleASCII("PIC IRQ OK\n"); + irq_pic_ps2_Init(); + io_WriteConsoleASCII("PIC PS/2 OK\n"); + + for (;;) { + asm volatile("hlt"); + io_WriteConsoleASCII("kMain: Interrupt hit\n"); + } +} diff --git a/kernel/kmain.h b/kernel/kmain.h new file mode 100644 index 0000000..ae4e2d9 --- /dev/null +++ b/kernel/kmain.h @@ -0,0 +1,28 @@ +#pragma once + +#include "../main.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// set the position of the top of stack before calling kMain_Init() +extern uint64_t kMain_StackPosition; +extern char kMain_StackData[], kMain_StackData_End[]; + + +typedef SYSV_ABI void (*kMainType)(); + +// written in Assembly, this function deals with stack, registers, etc, and then calls kMain. +// +// remember setting kMain_StackPosition before calling kMain_Init() +SYSV_ABI noreturn void kMain_Init(); + +// this is the real main function. +// it should only be called by kMain_Init() +SYSV_ABI void kMain(); + + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/kernel/kmain.init.S b/kernel/kmain.init.S new file mode 100644 index 0000000..0cd3097 --- /dev/null +++ b/kernel/kmain.init.S @@ -0,0 +1,25 @@ +format elf64 + +extrn kMain + +public kMain_StackPosition +public kMain_Init + + +section '.bss' writable +kMain_StackPosition: + rq 1 + + +section '.text' executable + +; sysvx64call void kMain_Init() +kMain_Init: + mov rsp, [kMain_StackPosition] + call kMain + +.hlt: + hlt + jmp .hlt + + diff --git a/libc/README b/libc/README new file mode 100644 index 0000000..8c29afa --- /dev/null +++ b/libc/README @@ -0,0 +1,11 @@ + +The C library code here mostly comes from The Public Domain C Library (pdclib). + +Get it here: https://rootdirectory.de/doku.php?id=pdclib:start +and here's the repo: https://github.com/DevSolar/pdclib + + +The original code is licensed under CC0 (Public Domain), and I'm really having a hard time +keeping track which file has been modified and how, etc. + + diff --git a/libc/abs.c b/libc/abs.c new file mode 100644 index 0000000..649c3fb --- /dev/null +++ b/libc/abs.c @@ -0,0 +1,9 @@ + +#include + +int abs(int val) { + if (val < 0) + return -val; + else + return val; +} diff --git a/libc/include/assert.h b/libc/include/assert.h new file mode 100644 index 0000000..34bea52 --- /dev/null +++ b/libc/include/assert.h @@ -0,0 +1,50 @@ +/* Diagnostics + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_internal.h" + +#ifndef _PDCLIB_ASSERT_H +#define _PDCLIB_ASSERT_H _PDCLIB_ASSERT_H +_PDCLIB_PUBLIC void _PDCLIB_assert99( const char * const, const char * const, const char * const ); +_PDCLIB_PUBLIC void _PDCLIB_assert89( const char * const ); +#endif + +/* If NDEBUG is set, assert() is a null operation. */ +#undef assert + +#ifdef NDEBUG +#define assert( ignore ) ( (void) 0 ) +#else +#if __STDC_VERSION__ >= 199901L +#define assert( expression ) ( ( expression ) ? (void) 0 \ + : _PDCLIB_assert99( "Assertion failed: " #expression \ + ", function ", __func__, \ + ", file " __FILE__ \ + ", line " _PDCLIB_value2string( __LINE__ ) \ + ".\n" ) ) +#else +#define assert( expression ) ( ( expression ) ? (void) 0 \ + : _PDCLIB_assert89( "Assertion failed: " #expression \ + ", file " __FILE__ \ + ", line " _PDCLIB_value2string( __LINE__ ) \ + ".\n" ) ) +#endif +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_ASSERT_H +#include _PDCLIB_EXTEND_ASSERT_H +#endif + +#ifdef __cplusplus +} +#endif diff --git a/libc/include/ctype.h b/libc/include/ctype.h new file mode 100644 index 0000000..551667a --- /dev/null +++ b/libc/include/ctype.h @@ -0,0 +1,110 @@ +/* Character handling + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_CTYPE_H +#define _PDCLIB_CTYPE_H _PDCLIB_CTYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_internal.h" + +/* Character classification functions */ + +/* Note that there is a difference between "whitespace" (any printing, non- + graph character, like horizontal and vertical tab), and "blank" (the literal + ' ' space character). + + There will be masking macros for each of these later on, but right now I + focus on the functions only. +*/ + +/* Returns isalpha( c ) || isdigit( c ) */ +_PDCLIB_PUBLIC int isalnum( int c ); + +/* Returns isupper( c ) || islower( c ) in the "C" locale. + In any other locale, also returns true for a locale-specific set of + alphabetic characters which are neither control characters, digits, + punctation, or whitespace. +*/ +_PDCLIB_PUBLIC int isalpha( int c ); + +/* Returns true if the character isspace() and used for separating words within + a line of text. In the "C" locale, only ' ' and '\t' are considered blanks. +*/ +_PDCLIB_PUBLIC int isblank( int c ); + +/* Returns true if the character is a control character. */ +_PDCLIB_PUBLIC int iscntrl( int c ); + +/* Returns true if the character is a decimal digit. Locale-independent. */ +_PDCLIB_PUBLIC int isdigit( int c ); + +/* Returns true for every printing character except space (' '). + NOTE: This definition differs from that of iswgraph() in , + which considers any iswspace() character, not only ' '. +*/ +_PDCLIB_PUBLIC int isgraph( int c ); + +/* Returns true for lowercase letters in the "C" locale. + In any other locale, also returns true for a locale-specific set of + characters which are neither control characters, digits, punctation, or + space (' '). In a locale other than the "C" locale, a character might test + true for both islower() and isupper(). +*/ +_PDCLIB_PUBLIC int islower( int c ); + +/* Returns true for every printing character including space (' '). */ +_PDCLIB_PUBLIC int isprint( int c ); + +/* Returns true for a locale-specific set of punctuation charcters; these + may not be whitespace or alphanumeric. In the "C" locale, returns true + for every printing character that is not whitespace or alphanumeric. +*/ +_PDCLIB_PUBLIC int ispunct( int c ); + +/* Returns true for every standard whitespace character (' ', '\f', '\n', '\r', + '\t', '\v') in the "C" locale. In any other locale, also returns true for a + locale-specific set of characters for which isalnum() is false. +*/ +_PDCLIB_PUBLIC int isspace( int c ); + +/* Returns true for uppercase letters in the "C" locale. + In any other locale, also returns true for a locale-specific set of + characters which are neither control characters, digits, punctation, or + space (' '). In a locale other than the "C" locale, a character might test + true for both islower() and isupper(). +*/ +_PDCLIB_PUBLIC int isupper( int c ); + +/* Returns true for any hexadecimal-digit character. Locale-independent. */ +_PDCLIB_PUBLIC int isxdigit( int c ); + +/* Character case mapping functions */ + +/* Converts an uppercase letter to a corresponding lowercase letter. Input that + is not an uppercase letter remains unchanged. +*/ +_PDCLIB_PUBLIC int tolower( int c ); + +/* Converts a lowercase letter to a corresponding uppercase letter. Input that + is not a lowercase letter remains unchanged. +*/ +_PDCLIB_PUBLIC int toupper( int c ); + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_CTYPE_H +#include _PDCLIB_EXTEND_CTYPE_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/errno.h b/libc/include/errno.h new file mode 100644 index 0000000..28eaf5b --- /dev/null +++ b/libc/include/errno.h @@ -0,0 +1,202 @@ +/* Errors + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_ERRNO_H +#define _PDCLIB_ERRNO_H _PDCLIB_ERRNO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_lib_ext1.h" +#include "pdclib/_PDCLIB_internal.h" + +/* FIXME: With , this needs to be in thread-specific storage. */ +#define errno (*_PDCLIB_errno_func()) + +/* C only requires the following three */ + +/* Result too large */ +#define ERANGE _PDCLIB_ERANGE +/* Mathematics argument out of domain of function */ +#define EDOM _PDCLIB_EDOM +/* Illegal byte sequence */ +#define EILSEQ _PDCLIB_EILSEQ + +/* C++ additionally requires the folloing */ + +/* Argument list too long */ +#define E2BIG _PDCLIB_E2BIG +/* Permission denied */ +#define EACCES _PDCLIB_EACCES +/* Address in use */ +#define EADDRINUSE _PDCLIB_EADDRINUSE +/* Address not available */ +#define EADDRNOTAVAIL _PDCLIB_EADDRNOTAVAIL +/* Address family not supported */ +#define EAFNOSUPPORT _PDCLIB_EAFNOSUPPORT +/* Resource unavailable, try again */ +#define EAGAIN _PDCLIB_EAGAIN +/* Connection already in progress */ +#define EALREADY _PDCLIB_EALREADY +/* Bad file descriptor */ +#define EBADF _PDCLIB_EBADF +/* Bad message */ +#define EBADMSG _PDCLIB_EBADMSG +/* Device or resource busy */ +#define EBUSY _PDCLIB_EBUSY +/* Operation canceled */ +#define ECANCELED _PDCLIB_ECANCELED +/* No child processes */ +#define ECHILD _PDCLIB_ECHILD +/* Connection aborted */ +#define ECONNABORTED _PDCLIB_ECONNABORTED +/* Connection refused */ +#define ECONNREFUSED _PDCLIB_ECONNREFUSED +/* Connection reset */ +#define ECONNRESET _PDCLIB_ECONNRESET +/* Resource deadlock would occur */ +#define EDEADLK _PDCLIB_EDEADLK +/* Destination address required */ +#define EDESTADDRREQ _PDCLIB_EDESTADDRREQ +/* File exists */ +#define EEXIST _PDCLIB_EEXIST +/* Bad address */ +#define EFAULT _PDCLIB_EFAULT +/* File too large */ +#define EFBIG _PDCLIB_EFBIG +/* Host is unreachable */ +#define EHOSTUNREACH _PDCLIB_EHOSTUNREACH +/* Identifier removed */ +#define EIDRM _PDCLIB_EIDRM +/* Operation in progress */ +#define EINPROGRESS _PDCLIB_EINPROGRESS +/* Interrupted function */ +#define EINTR _PDCLIB_EINTR +/* Invalid argument */ +#define EINVAL _PDCLIB_EINVAL +/* I/O error */ +#define EIO _PDCLIB_EIO +/* Socket is connected */ +#define EISCONN _PDCLIB_EISCONN +/* Is a directory */ +#define EISDIR _PDCLIB_EISDIR +/* Too many levels of symbolic links */ +#define ELOOP _PDCLIB_ELOOP +/* File descriptor value too large */ +#define EMFILE _PDCLIB_EMFILE +/* Too many links */ +#define EMLINK _PDCLIB_EMLINK +/* Message too large */ +#define EMSGSIZE _PDCLIB_EMSGSIZE +/* Filename too long */ +#define ENAMETOOLONG _PDCLIB_ENAMETOOLONG +/* Network is down */ +#define ENETDOWN _PDCLIB_ENETDOWN +/* Connection aborted by network */ +#define ENETRESET _PDCLIB_ENETRESET +/* Network unreachable */ +#define ENETUNREACH _PDCLIB_ENETUNREACH +/* Too many files open in system */ +#define ENFILE _PDCLIB_ENFILE +/* No buffer space available */ +#define ENOBUFS _PDCLIB_ENOBUFS +/* No message is available on the STREAM head read queue */ +#define ENODATA _PDCLIB_ENODATA +/* No such device */ +#define ENODEV _PDCLIB_ENODEV +/* No such file or directory */ +#define ENOENT _PDCLIB_ENOENT +/* Executable file format error */ +#define ENOEXEC _PDCLIB_ENOEXEC +/* No locks available */ +#define ENOLCK _PDCLIB_ENOLCK +/* Link has been severed */ +#define ENOLINK _PDCLIB_ENOLINK +/* Not enough space */ +#define ENOMEM _PDCLIB_ENOMEM +/* No message of the desired type */ +#define ENOMSG _PDCLIB_ENOMSG +/* Protocol not available */ +#define ENOPROTOOPT _PDCLIB_ENOPROTOOPT +/* No space left on device */ +#define ENOSPC _PDCLIB_ENOSPC +/* No STREAM resources */ +#define ENOSR _PDCLIB_ENOSR +/* Not a STREAM */ +#define ENOSTR _PDCLIB_ENOSTR +/* Function not supported */ +#define ENOSYS _PDCLIB_ENOSYS +/* The socket is not connected */ +#define ENOTCONN _PDCLIB_ENOTCONN +/* Not a directory */ +#define ENOTDIR _PDCLIB_ENOTDIR +/* Directory not empty */ +#define ENOTEMPTY _PDCLIB_ENOTEMPTY +/* State not recoverable */ +#define ENOTRECOVERABLE _PDCLIB_ENOTRECOVERABLE +/* Not a socket */ +#define ENOTSOCK _PDCLIB_ENOTSOCK +/* Not supported */ +#define ENOTSUP _PDCLIB_ENOTSUP +/* Inappropriate I/O control operation */ +#define ENOTTY _PDCLIB_ENOTTY +/* No such device or address */ +#define ENXIO _PDCLIB_ENXIO +/* Operation not supported on socket */ +#define EOPNOTSUPP _PDCLIB_EOPNOTSUPP +/* Value too large to be stored in data type */ +#define EOVERFLOW _PDCLIB_EOVERFLOW +/* Previous owner died */ +#define EOWNERDEAD _PDCLIB_EOWNERDEAD +/* Operation not permitted */ +#define EPERM _PDCLIB_EPERM +/* Broken pipe */ +#define EPIPE _PDCLIB_EPIPE +/* Protocol error */ +#define EPROTO _PDCLIB_EPROTO +/* Protocol not supported */ +#define EPROTONOSUPPORT _PDCLIB_EPROTONOSUPPORT +/* Protocol wrong type for socket */ +#define EPROTOTYPE _PDCLIB_EPROTOTYPE +/* Read-only file system */ +#define EROFS _PDCLIB_EROFS +/* Invalid seek */ +#define ESPIPE _PDCLIB_ESPIPE +/* No such process */ +#define ESRCH _PDCLIB_ESRCH +/* Stream ioctl() timeout */ +#define ETIME _PDCLIB_ETIME +/* Connection timed out */ +#define ETIMEDOUT _PDCLIB_ETIMEDOUT +/* Text file busy */ +#define ETXTBSY _PDCLIB_ETXTBSY +/* Operation would block */ +#define EWOULDBLOCK _PDCLIB_EWOULDBLOCK +/* Cross-device link */ +#define EXDEV _PDCLIB_EXDEV + +/* Annex K -- Bounds-checking interfaces */ + +#if ( __STDC_WANT_LIB_EXT1__ + 0 ) != 0 +#ifndef _PDCLIB_ERRNO_T_DEFINED +#define _PDCLIB_ERRNO_T_DEFINED _PDCLIB_ERRNO_T_DEFINED +typedef int errno_t; +#endif +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_ERRNO_H +#include _PDCLIB_EXTEND_ERRNO_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/float.h b/libc/include/float.h new file mode 100644 index 0000000..fc34278 --- /dev/null +++ b/libc/include/float.h @@ -0,0 +1,153 @@ +/* Characteristics of floating types + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_FLOAT_H +#define _PDCLIB_FLOAT_H _PDCLIB_FLOAT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_internal.h" + +/* The following parameters are used to define the model for each + floating-point type: + + s sign (±1) + b base or radix of exponent representation (an integer > 1) + e exponent (an integer between a minimum eₘₙ and a maximum eₘₓ) + p precision (the number of base-b digits in the significand) + ƒₖ nonnegative integers less than b (the significand digits) + + A floating-point number (x) is defined by the following model: + + x = sbᵉ ₖ₌₁∑ᵖ ƒₖb⁻ᵏ, eₘₙ ≤ e ≤ eₘₓ + + In addition to normalized floating-point numbers (ƒ₁ > 0 if x ≠ 0), + floating types may be able to contain other kinds of floating-point + numbers, such as subnormal floating-point numbers (x ≠ 0, e = eₘₙ, + ƒ₁ = 0) and unnormalized floating-point numbers (x ≠ 0, e > eₘₙ, + ƒ₁ = 0), and values that are not floating-point numbers, such as + infinities and NaNs. +*/ + +/* Whether rounding toward zero (0), to nearest (1), toward positive + infinity (2), toward negative infinity (3), or indeterminate (-1). + FLT_ROUNDS is not a compile-time constant, and may change due to + calls to fesetround() (in ). +*/ +#define FLT_ROUNDS _PDCLIB_FLT_ROUNDS + +/* Whether operations are done in the given type (0), float is + evaluated as double (1), float and double are evaluated as + long double (2), or evaluation method is indeterminate (-1). +*/ +#define FLT_EVAL_METHOD _PDCLIB_FLT_EVAL_METHOD + +/* Whether the type supports subnormal numbers (1), does not support + them (0), or support is indeterminate (-1). +*/ +#define FLT_HAS_SUBNORM _PDCLIB_FLT_HAS_SUBNORM +#define DBL_HAS_SUBNORM _PDCLIB_DBL_HAS_SUBNORM +#define LDBL_HAS_SUBNORM _PDCLIB_LDBL_HAS_SUBNORM + +/* Radix of exponent representation, b */ +#define FLT_RADIX _PDCLIB_FLT_RADIX + +/* Number of base-b digits in the floating point significand, p */ +#define FLT_MANT_DIG _PDCLIB_FLT_MANT_DIG +#define DBL_MANT_DIG _PDCLIB_DBL_MANT_DIG +#define LDBL_MANT_DIG _PDCLIB_LDBL_MANT_DIG + +/* Number of decimal digits, n, so that any floating point number with + p radix b digits can be rounded to a floating point number with n + decimal digits and back without changing the value + pₘₓlog₁₀b if b is a power of 10, + ⌈1 + pₘₓlog₁₀b⌉ otherwise. +*/ +#define FLT_DECIMAL_DIG _PDCLIB_FLT_DECIMAL_DIG +#define DBL_DECIMAL_DIG _PDCLIB_DBL_DECIMAL_DIG +#define LDBL_DECIMAL_DIG _PDCLIB_LDBL_DECIMAL_DIG + +/* As above, for the widest supported type. */ +#define DECIMAL_DIG _PDCLIB_DECIMAL_DIG + +/* Number of decimal digits, q, so that any floating point number with + q decimal digits can be rounded to a floating point number with p + radix b digits and back without changing the value of the q decimal + digits. + p log₁₀b if b is a power of 10, + ⌊(p - 1)log₁₀b⌋ otherwise. +*/ +#define FLT_DIG _PDCLIB_FLT_DIG +#define DBL_DIG _PDCLIB_DBL_DIG +#define LDBL_DIG _PDCLIB_LDBL_DIG + +/* Minimum negative integer such that FLT_RADIX raised to one less + than that power is a normalized floating point number, eₘₙ +*/ +#define FLT_MIN_EXP _PDCLIB_FLT_MIN_EXP +#define DBL_MIN_EXP _PDCLIB_DBL_MIN_EXP +#define LDBL_MIN_EXP _PDCLIB_LDBL_MIN_EXP + +/* Minimum negative integer such that 10 raised to one less than that + power is in the range of normalized floating point numbers, + ⌈log₁₀b^{eₘₙ⁻¹}⌉ +*/ +#define FLT_MIN_10_EXP _PDCLIB_FLT_MIN_10_EXP +#define DBL_MIN_10_EXP _PDCLIB_DBL_MIN_10_EXP +#define LDBL_MIN_10_EXP _PDCLIB_LDBL_MIN_10_EXP + +/* Maximum integer such that FLT_RADIX raised to one less than that + power is a representable finite floating point number, eₘₓ +*/ +#define FLT_MAX_EXP _PDCLIB_FLT_MAX_EXP +#define DBL_MAX_EXP _PDCLIB_DBL_MAX_EXP +#define LDBL_MAX_EXP _PDCLIB_LDBL_MAX_EXP + +/* Maximum integer such that 10 raised to that power is in the range + of representable finite floating-point numbers, + ⌊log₁₀((1-b⁻ᵖ)b^{eₘₓ})⌋ +*/ +#define FLT_MAX_10_EXP _PDCLIB_FLT_MAX_10_EXP +#define DBL_MAX_10_EXP _PDCLIB_DBL_MAX_10_EXP +#define LDBL_MAX_10_EXP _PDCLIB_LDBL_MAX_10_EXP + +/* Maximum representable finite floating-point number, (1-b⁻ᵖ)b^{eₘₓ} +*/ +#define FLT_MAX _PDCLIB_FLT_MAX +#define DBL_MAX _PDCLIB_DBL_MAX +#define LDBL_MAX _PDCLIB_LDBL_MAX + +/* Difference between 1 and the least value greater than 1 that is + representable in the type, b¹⁻ᵖ +*/ +#define FLT_EPSILON _PDCLIB_FLT_EPSILON +#define DBL_EPSILON _PDCLIB_DBL_EPSILON +#define LDBL_EPSILON _PDCLIB_LDBL_EPSILON + +/* Minimum normalized positive floating-point number, b^{eₘₙ⁻¹} */ +#define FLT_MIN _PDCLIB_FLT_MIN +#define DBL_MIN _PDCLIB_DBL_MIN +#define LDBL_MIN _PDCLIB_LDBL_MIN + +/* Minimum positive floating-point number */ +#define FLT_TRUE_MIN _PDCLIB_FLT_TRUE_MIN +#define DBL_TRUE_MIN _PDCLIB_DBL_TRUE_MIN +#define LDBL_TRUE_MIN _PDCLIB_LDBL_TRUE_MIN + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_FLOAT_H +#include _PDCLIB_EXTEND_FLOAT_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/inttypes.h b/libc/include/inttypes.h new file mode 100644 index 0000000..0be401d --- /dev/null +++ b/libc/include/inttypes.h @@ -0,0 +1,368 @@ +/* Format conversion of integer types + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_INTTYPES_H +#define _PDCLIB_INTTYPES_H _PDCLIB_INTTYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct _PDCLIB_imaxdiv_t imaxdiv_t; + +/* 7.8.1 Macros for format specifiers */ + +/* The various leastN_t, fastN_t, intmax_t, and intptr_t types are typedefs + to native types. But the user does not know which ones, which gives some + problems when trying to *printf() / *scanf() those types. The various + macros defined below allow to give the correct conversion specifiers + without knowing the actual native type they represent. +*/ + +#if _PDCLIB_INT_LEAST8_MAX > _PDCLIB_INT_MAX +#define PRIdLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, d ) ) +#define PRIiLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, i ) ) +#define PRIoLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, o ) ) +#define PRIuLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, u ) ) +#define PRIxLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, x ) ) +#define PRIXLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, X ) ) +#else +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#endif + +#if _PDCLIB_INT_FAST8_MAX > _PDCLIB_INT_MAX +#define PRIdFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, d ) ) +#define PRIiFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, i ) ) +#define PRIoFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, o ) ) +#define PRIuFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, u ) ) +#define PRIxFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, x ) ) +#define PRIXFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, X ) ) +#else +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" +#endif + +#if _PDCLIB_INT_LEAST16_MAX > _PDCLIB_INT_MAX +#define PRIdLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, d ) ) +#define PRIiLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, i ) ) +#define PRIoLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, o ) ) +#define PRIuLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, u ) ) +#define PRIxLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, x ) ) +#define PRIXLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, X ) ) +#else +#define PRIdLEAST16 "d" +#define PRIiLEAST16 "i" +#define PRIoLEAST16 "o" +#define PRIuLEAST16 "u" +#define PRIxLEAST16 "x" +#define PRIXLEAST16 "X" +#endif + +#if _PDCLIB_INT_FAST16_MAX > _PDCLIB_INT_MAX +#define PRIdFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, d ) ) +#define PRIiFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, i ) ) +#define PRIoFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, o ) ) +#define PRIuFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, u ) ) +#define PRIxFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, x ) ) +#define PRIXFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, X ) ) +#else +#define PRIdFAST16 "d" +#define PRIiFAST16 "i" +#define PRIoFAST16 "o" +#define PRIuFAST16 "u" +#define PRIxFAST16 "x" +#define PRIXFAST16 "X" +#endif + +#if _PDCLIB_INT_LEAST32_MAX > _PDCLIB_INT_MAX +#define PRIdLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, d ) ) +#define PRIiLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, i ) ) +#define PRIoLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, o ) ) +#define PRIuLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, u ) ) +#define PRIxLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, x ) ) +#define PRIXLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, X ) ) +#else +#define PRIdLEAST32 "d" +#define PRIiLEAST32 "i" +#define PRIoLEAST32 "o" +#define PRIuLEAST32 "u" +#define PRIxLEAST32 "x" +#define PRIXLEAST32 "X" +#endif + +#if _PDCLIB_INT_FAST32_MAX > _PDCLIB_INT_MAX +#define PRIdFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, d ) ) +#define PRIiFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, i ) ) +#define PRIoFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, o ) ) +#define PRIuFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, u ) ) +#define PRIxFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, x ) ) +#define PRIXFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, X ) ) +#else +#define PRIdFAST32 "d" +#define PRIiFAST32 "i" +#define PRIoFAST32 "o" +#define PRIuFAST32 "u" +#define PRIxFAST32 "x" +#define PRIXFAST32 "X" +#endif + +#if _PDCLIB_INT_LEAST64_MAX > _PDCLIB_INT_MAX +#define PRIdLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, d ) ) +#define PRIiLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, i ) ) +#define PRIoLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, o ) ) +#define PRIuLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, u ) ) +#define PRIxLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, x ) ) +#define PRIXLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, X ) ) +#else +#define PRIdLEAST64 "d" +#define PRIiLEAST64 "i" +#define PRIoLEAST64 "o" +#define PRIuLEAST64 "u" +#define PRIxLEAST64 "x" +#define PRIXLEAST64 "X" +#endif + +#if _PDCLIB_INT_FAST64_MAX > _PDCLIB_INT_MAX +#define PRIdFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, d ) ) +#define PRIiFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, i ) ) +#define PRIoFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, o ) ) +#define PRIuFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, u ) ) +#define PRIxFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, x ) ) +#define PRIXFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, X ) ) +#else +#define PRIdFAST64 "d" +#define PRIiFAST64 "i" +#define PRIoFAST64 "o" +#define PRIuFAST64 "u" +#define PRIxFAST64 "x" +#define PRIXFAST64 "X" +#endif + +#if _PDCLIB_INTMAX_MAX > _PDCLIB_INT_MAX +#define PRIdMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, d ) ) +#define PRIiMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, i ) ) +#define PRIoMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, o ) ) +#define PRIuMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, u ) ) +#define PRIxMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, x ) ) +#define PRIXMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, X ) ) +#else +#define PRIdMAX "d" +#define PRIiMAX "i" +#define PRIoMAX "o" +#define PRIuMAX "u" +#define PRIxMAX "x" +#define PRIXMAX "X" +#endif + +#if _PDCLIB_INTPTR_MAX > _PDCLIB_INT_MAX +#define PRIdPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, d ) ) +#define PRIiPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, i ) ) +#define PRIoPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, o ) ) +#define PRIuPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, u ) ) +#define PRIxPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, x ) ) +#define PRIXPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, X ) ) +#else +#define PRIdPTR "d" +#define PRIiPTR "i" +#define PRIoPTR "o" +#define PRIuPTR "u" +#define PRIxPTR "x" +#define PRIXPTR "X" +#endif + +#define SCNdLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, d ) ) +#define SCNiLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, i ) ) +#define SCNoLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, o ) ) +#define SCNuLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, u ) ) +#define SCNxLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, x ) ) + +#define SCNdFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, d ) ) +#define SCNiFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, i ) ) +#define SCNoFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, o ) ) +#define SCNuFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, u ) ) +#define SCNxFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, x ) ) + +#define SCNdLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, d ) ) +#define SCNiLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, i ) ) +#define SCNoLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, o ) ) +#define SCNuLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, u ) ) +#define SCNxLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, x ) ) + +#define SCNdFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, d ) ) +#define SCNiFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, i ) ) +#define SCNoFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, o ) ) +#define SCNuFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, u ) ) +#define SCNxFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, x ) ) + +#define SCNdLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, d ) ) +#define SCNiLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, i ) ) +#define SCNoLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, o ) ) +#define SCNuLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, u ) ) +#define SCNxLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, x ) ) + +#define SCNdFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, d ) ) +#define SCNiFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, i ) ) +#define SCNoFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, o ) ) +#define SCNuFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, u ) ) +#define SCNxFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, x ) ) + +#define SCNdLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, d ) ) +#define SCNiLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, i ) ) +#define SCNoLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, o ) ) +#define SCNuLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, u ) ) +#define SCNxLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, x ) ) + +#define SCNdFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, d ) ) +#define SCNiFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, i ) ) +#define SCNoFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, o ) ) +#define SCNuFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, u ) ) +#define SCNxFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, x ) ) + +#define SCNdMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, d ) ) +#define SCNiMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, i ) ) +#define SCNoMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, o ) ) +#define SCNuMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, u ) ) +#define SCNxMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, x ) ) + +#define SCNdPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, d ) ) +#define SCNiPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, i ) ) +#define SCNoPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, o ) ) +#define SCNuPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, u ) ) +#define SCNxPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, x ) ) + +/* The exact-width types (int8_t, int16_t, ...) are *optional*, as not all + architectures support the necessary 8-bits-per-byte two's complement + native types. +*/ + +#if _PDCLIB_TWOS_COMPLEMENT == 1 + +#if _PDCLIB_INT_LEAST8_MAX == 0x7f +#if _PDCLIB_INT_LEAST8_MAX > _PDCLIB_INT_MAX +#define PRId8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, d ) ) +#define PRIi8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, i ) ) +#define PRIo8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, o ) ) +#define PRIu8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, u ) ) +#define PRIx8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, x ) ) +#define PRIX8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, X ) ) +#endif +#define SCNd8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, d ) ) +#define SCNi8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, i ) ) +#define SCNo8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, o ) ) +#define SCNu8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, u ) ) +#define SCNx8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, x ) ) +#endif + +#if _PDCLIB_INT_LEAST16_MAX == 0x7fff +#if _PDCLIB_INT_LEAST16_MAX > _PDCLIB_INT_MAX +#define PRId16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, d ) ) +#define PRIi16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, i ) ) +#define PRIo16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, o ) ) +#define PRIu16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, u ) ) +#define PRIx16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, x ) ) +#define PRIX16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, X ) ) +#endif +#define SCNd16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, d ) ) +#define SCNi16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, i ) ) +#define SCNo16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, o ) ) +#define SCNu16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, u ) ) +#define SCNx16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, x ) ) +#endif + +#if _PDCLIB_INT_LEAST32_MAX == 0x7fffffffl +#if _PDCLIB_INT_LEAST32_MAX > _PDCLIB_INT_MAX +#define PRId32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, d ) ) +#define PRIi32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, i ) ) +#define PRIo32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, o ) ) +#define PRIu32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, u ) ) +#define PRIx32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, x ) ) +#define PRIX32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, X ) ) +#endif +#define SCNd32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, d ) ) +#define SCNi32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, i ) ) +#define SCNo32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, o ) ) +#define SCNu32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, u ) ) +#define SCNx32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, x ) ) +#endif + +#if _PDCLIB_INT_LEAST64_MAX == 0x7fffffffffffffffll +#if _PDCLIB_INT_LEAST64_MAX > _PDCLIB_INT_MAX +#define PRId64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, d ) ) +#define PRIi64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, i ) ) +#define PRIo64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, o ) ) +#define PRIu64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, u ) ) +#define PRIx64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, x ) ) +#define PRIX64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, X ) ) +#endif +#define SCNd64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, d ) ) +#define SCNi64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, i ) ) +#define SCNo64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, o ) ) +#define SCNu64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, u ) ) +#define SCNx64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, x ) ) +#endif + +#endif + +/* 7.8.2 Functions for greatest-width integer types */ + +/* Calculate the absolute value of j */ +_PDCLIB_PUBLIC intmax_t imaxabs( intmax_t j ); + +/* Return quotient (quot) and remainder (rem) of an integer division in the + imaxdiv_t struct. +*/ +_PDCLIB_PUBLIC imaxdiv_t imaxdiv( intmax_t numer, intmax_t denom ); + +/* Separate the character array nptr into three parts: A (possibly empty) + sequence of whitespace characters, a character representation of an integer + to the given base, and trailing invalid characters (including the terminating + null character). If base is 0, assume it to be 10, unless the integer + representation starts with 0x / 0X (setting base to 16) or 0 (setting base to + 8). If given, base can be anything from 0 to 36, using the 26 letters of the + base alphabet (both lowercase and uppercase) as digits 10 through 35. + The integer representation is then converted into the return type of the + function. It can start with a '+' or '-' sign. If the sign is '-', the result + of the conversion is negated. + If the conversion is successful, the converted value is returned. If endptr + is not a NULL pointer, a pointer to the first trailing invalid character is + returned in *endptr. + If no conversion could be performed, zero is returned (and nptr in *endptr, + if endptr is not a NULL pointer). If the converted value does not fit into + the return type, the functions return INTMAX_MIN, INTMAX_MAX, or UINTMAX_MAX, + respectively, depending on the sign of the integer representation and the + return type, and errno is set to ERANGE. +*/ +/* This function is equivalent to strtol() / strtoul() in , but on + the potentially larger type. +*/ +_PDCLIB_PUBLIC intmax_t strtoimax( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr, int base ); +_PDCLIB_PUBLIC uintmax_t strtoumax( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr, int base ); + +/* TODO: wcstoimax(), wcstoumax() */ + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_INTTYPES_H +#include _PDCLIB_EXTEND_INTTYPES_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/iso646.h b/libc/include/iso646.h new file mode 100644 index 0000000..88e521c --- /dev/null +++ b/libc/include/iso646.h @@ -0,0 +1,31 @@ +/* Alternative spellings + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_ISO646_H +#define _PDCLIB_ISO646_H _PDCLIB_ISO646_H + +#ifndef __cplusplus +#define and && +#define and_eq &= +#define bitand & +#define bitor | +#define compl ~ +#define not ! +#define not_eq != +#define or || +#define or_eq |= +#define xor ^ +#define xor_eq ^= +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_ISO646_H +#include _PDCLIB_EXTEND_ISO646_H +#endif + +#endif diff --git a/libc/include/limits.h b/libc/include/limits.h new file mode 100644 index 0000000..72268a4 --- /dev/null +++ b/libc/include/limits.h @@ -0,0 +1,50 @@ +/* Sizes of integer types + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_LIMITS_H +#define _PDCLIB_LIMITS_H _PDCLIB_LIMITS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_internal.h" + +/* TODO: Defined to 1 as multibyte characters are not supported yet. */ +#define MB_LEN_MAX 1 + +#define LLONG_MIN _PDCLIB_LLONG_MIN +#define LLONG_MAX _PDCLIB_LLONG_MAX +#define ULLONG_MAX _PDCLIB_ULLONG_MAX + +#define CHAR_BIT _PDCLIB_CHAR_BIT +#define CHAR_MAX _PDCLIB_CHAR_MAX +#define CHAR_MIN _PDCLIB_CHAR_MIN +#define SCHAR_MAX _PDCLIB_SCHAR_MAX +#define SCHAR_MIN _PDCLIB_SCHAR_MIN +#define UCHAR_MAX _PDCLIB_UCHAR_MAX +#define SHRT_MAX _PDCLIB_SHRT_MAX +#define SHRT_MIN _PDCLIB_SHRT_MIN +#define INT_MAX _PDCLIB_INT_MAX +#define INT_MIN _PDCLIB_INT_MIN +#define LONG_MAX _PDCLIB_LONG_MAX +#define LONG_MIN _PDCLIB_LONG_MIN +#define USHRT_MAX _PDCLIB_USHRT_MAX +#define UINT_MAX _PDCLIB_UINT_MAX +#define ULONG_MAX _PDCLIB_ULONG_MAX + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_LIMITS_H +#include _PDCLIB_EXTEND_LIMITS_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/locale.h b/libc/include/locale.h new file mode 100644 index 0000000..0c18446 --- /dev/null +++ b/libc/include/locale.h @@ -0,0 +1,114 @@ +/* Localization + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_LOCALE_H +#define _PDCLIB_LOCALE_H _PDCLIB_LOCALE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_internal.h" + +#ifndef _PDCLIB_NULL_DEFINED +#define _PDCLIB_NULL_DEFINED _PDCLIB_NULL_DEFINED +#define NULL _PDCLIB_NULL +#endif + +/* The structure returned by localeconv(). + + The values for *_sep_by_space: + 0 - no space + 1 - if symbol and sign are adjacent, a space separates them from the value; + otherwise a space separates the symbol from the value + 2 - if symbol and sign are adjacent, a space separates them; otherwise a + space separates the sign from the value + + The values for *_sign_posn: + 0 - Parentheses surround value and symbol + 1 - sign precedes value and symbol + 2 - sign succeeds value and symbol + 3 - sign immediately precedes symbol + 4 - sign immediately succeeds symbol +*/ +struct lconv +{ + char * decimal_point; /* decimal point character */ /* LC_NUMERIC */ + char * thousands_sep; /* character for separating groups of digits */ /* LC_NUMERIC */ + char * grouping; /* string indicating the size of digit groups */ /* LC_NUMERIC */ + char * mon_decimal_point; /* decimal point for monetary quantities */ /* LC_MONETARY */ + char * mon_thousands_sep; /* thousands_sep for monetary quantities */ /* LC_MONETARY */ + char * mon_grouping; /* grouping for monetary quantities */ /* LC_MONETARY */ + char * positive_sign; /* string indicating nonnegative mty. qty. */ /* LC_MONETARY */ + char * negative_sign; /* string indicating negative mty. qty. */ /* LC_MONETARY */ + char * currency_symbol; /* local currency symbol (e.g. '$') */ /* LC_MONETARY */ + char * int_curr_symbol; /* international currency symbol (e.g. "USD" */ /* LC_MONETARY */ + char frac_digits; /* fractional digits in local monetary qty. */ /* LC_MONETARY */ + char p_cs_precedes; /* if currency_symbol precedes positive qty. */ /* LC_MONETARY */ + char n_cs_precedes; /* if currency_symbol precedes negative qty. */ /* LC_MONETARY */ + char p_sep_by_space; /* if it is separated by space from pos. qty. */ /* LC_MONETARY */ + char n_sep_by_space; /* if it is separated by space from neg. qty. */ /* LC_MONETARY */ + char p_sign_posn; /* positioning of positive_sign for mon. qty. */ /* LC_MONETARY */ + char n_sign_posn; /* positioning of negative_sign for mon. qty. */ /* LC_MONETARY */ + char int_frac_digits; /* Same as above, for international format */ /* LC_MONETARY */ + char int_p_cs_precedes; /* Same as above, for international format */ /* LC_MONETARY */ + char int_n_cs_precedes; /* Same as above, for international format */ /* LC_MONETARY */ + char int_p_sep_by_space; /* Same as above, for international format */ /* LC_MONETARY */ + char int_n_sep_by_space; /* Same as above, for international format */ /* LC_MONETARY */ + char int_p_sign_posn; /* Same as above, for international format */ /* LC_MONETARY */ + char int_n_sign_posn; /* Same as above, for international format */ /* LC_MONETARY */ +}; + +/* First arguments to setlocale(). + NOTE: If you add to / modify these, look at functions/locale/setlocale.c + and keep things in sync. +*/ +/* Entire locale */ +#define LC_ALL _PDCLIB_LC_ALL +/* Collation (strcoll(), strxfrm()) */ +#define LC_COLLATE _PDCLIB_LC_COLLATE +/* Character types (, ) */ +#define LC_CTYPE _PDCLIB_LC_CTYPE +/* Monetary formatting (as returned by localeconv) */ +#define LC_MONETARY _PDCLIB_LC_MONETARY +/* Decimal-point character (for printf() / scanf() functions), string + conversions, nonmonetary formatting as returned by localeconv +*/ +#define LC_NUMERIC _PDCLIB_LC_NUMERIC +/* Time formats (strftime(), wcsftime()) */ +#define LC_TIME _PDCLIB_LC_TIME +/* Messages (not specified but allowed by C99, and specified by POSIX) + (used by perror() / strerror()) +*/ +#define LC_MESSAGES _PDCLIB_LC_MESSAGES + +/* The category parameter can be any of the LC_* macros to specify if the call + to setlocale() shall affect the entire locale or only a portion thereof. + The category locale specifies which locale should be switched to, with "C" + being the minimal default locale, and "" being the locale-specific native + environment. A NULL pointer makes setlocale() return the *current* setting. + Otherwise, returns a pointer to a string associated with the specified + category for the new locale. +*/ +_PDCLIB_PUBLIC char * setlocale( int category, const char * locale ); + +/* Returns a struct lconv initialized to the values appropriate for the current + locale setting. +*/ +_PDCLIB_PUBLIC struct lconv * localeconv( void ); + +#ifdef __cplusplus +} +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_LOCALE_H +#include _PDCLIB_EXTEND_LOCALE_H +#endif + +#endif diff --git a/libc/include/pdclib/_PDCLIB_config.h b/libc/include/pdclib/_PDCLIB_config.h new file mode 100644 index 0000000..0660098 --- /dev/null +++ b/libc/include/pdclib/_PDCLIB_config.h @@ -0,0 +1,904 @@ +/* Internal PDCLib configuration <_PDCLIB_config.h> + ("Example" platform target, for PDCLib development) + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_CONFIG_H +#define _PDCLIB_CONFIG_H _PDCLIB_CONFIG_H + +/* -------------------------------------------------------------------------- */ +/* Misc */ +/* -------------------------------------------------------------------------- */ + +/* Helper macros also documented in _PDCLIB_internal.h, but defined here as */ +/* they are needed in this file already. */ +/* _PDCLIB_cc( x, y ) concatenates two preprocessor tokens without extending. */ +/* _PDCLIB_concat( x, y ) concatenates two preprocessor tokens with extending */ +#define _PDCLIB_cc( x, y ) x ## y +#define _PDCLIB_concat( x, y ) _PDCLIB_cc( x, y ) + +/* exit() can signal success to the host environment by the value of zero or */ +/* the constant EXIT_SUCCESS. Failure is signaled by EXIT_FAILURE. Note that */ +/* any other return value is "implementation-defined", i.e. your environment */ +/* is not required to handle it gracefully. Set your definitions here. */ +#define _PDCLIB_SUCCESS 0 +#define _PDCLIB_FAILURE -1 + +/* qsort() in requires a function that swaps two memory areas. */ +/* Below is a naive implementation that can be improved significantly for */ +/* specific platforms, e.g. by swapping int instead of char. */ +#define _PDCLIB_memswp( i, j, size ) \ + char tmp; \ + do { \ + tmp = *i; \ + *i++ = *j; \ + *j++ = tmp; \ + } while ( --size ); + +/* Define this to some compiler directive that can be written after the */ +/* parameter list of a function declaration to indicate the function does */ +/* never return. If your compiler does not support such a directive, define */ +/* to nothing. (This is to avoid warnings with the exit functions under GCC */ +/* when compiling with C99/C++ settings, where C11 _Noreturn is unavailable.) */ +#define _PDCLIB_NORETURN __attribute__(( noreturn )) + +/* -------------------------------------------------------------------------- */ +/* Symbol Visibility */ +/* -------------------------------------------------------------------------- */ + +/* This defines _PDCLIB_PUBLIC to indicate external linkage, and _PDCLIB_LOCAL + to indicate local linkage. +*/ + +#ifdef _PDCLIB_STATIC_DEFINE + #define _PDCLIB_PUBLIC + #define _PDCLIB_LOCAL +#else + #if defined _WIN32 || defined __CYGWIN__ + #ifdef _PDCLIB_BUILD + #ifdef __GNUC__ + #define _PDCLIB_PUBLIC __attribute__ ((dllexport)) + #else + #define _PDCLIB_PUBLIC __declspec(dllexport) + #endif + #else + #ifdef __GNUC__ + #define _PDCLIB_PUBLIC __attribute__ ((dllimport)) + #else + #define _PDCLIB_PUBLIC __declspec(dllimport) + #endif + #endif + #define _PDCLIB_LOCAL + #else + #if __GNUC__ >= 4 + #define _PDCLIB_PUBLIC __attribute__ ((visibility ("default"))) + #define _PDCLIB_LOCAL __attribute__ ((visibility ("hidden"))) + #else + #define _PDCLIB_PUBLIC + #define _PDCLIB_LOCAL + #endif + #endif +#endif + +/* -------------------------------------------------------------------------- */ +/* Integers */ +/* -------------------------------------------------------------------------- */ +/* The defines below make use of predefines offered by GCC and clang. If you */ +/* adapt PDCLib for a different compiler family, you will have to use what */ +/* that compiler provides, or enter actual values. */ +/* -------------------------------------------------------------------------- */ + +/* At the point of writing, PDCLib makes no provisions for, nor has it been */ +/* tested, on a platform that uses signed magnitude or one's complement to */ +/* encode its integers. Most importantly, there are no guarantees that the */ +/* negative zero of those encodings is in any form handled gracefully. */ +#define _PDCLIB_TWOS_COMPLEMENT 1 + +/* 1234 for little endian, 4321 for big endian; other types not supported. */ +#define _PDCLIB_ENDIANESS __BYTE_ORDER__ + +/* Calculation of a minimum value from a given maximum for two's complement. */ +/* (For convenience only, used only in this header file below.) */ +#define _PDCLIB_MIN_CALC( max ) ( ( - max ) - 1 ) + +/* Now, introducting the various predefines to the _PDCLIB_* namespace, so */ +/* the rest of PDCLib can work with that and adapting to a different compiler */ +/* will require changes only in this one file. */ + +/* Bits in a char */ +#define _PDCLIB_CHAR_BIT __CHAR_BIT__ + +/* Maximum and minimum values of signed / unsigned char */ +#define _PDCLIB_SCHAR_MAX __SCHAR_MAX__ +#define _PDCLIB_SCHAR_MIN _PDCLIB_MIN_CALC( __SCHAR_MAX__ ) +#define _PDCLIB_UCHAR_MAX ( __SCHAR_MAX__ * 2 + 1 ) + +/* Whether the 'char' type is unsigned */ +#ifdef __CHAR_UNSIGNED__ +#define _PDCLIB_CHAR_MAX _PDCLIB_UCHAR_MAX +#define _PDCLIB_CHAR_MIN 0 +#else +#define _PDCLIB_CHAR_MAX _PDCLIB_SCHAR_MAX +#define _PDCLIB_CHAR_MIN _PDCLIB_SCHAR_MIN +#endif + +/* Maximum and minimum values of signed / unsigned short */ +#define _PDCLIB_SHRT_MAX __SHRT_MAX__ +#define _PDCLIB_SHRT_MIN _PDCLIB_MIN_CALC( __SHRT_MAX__ ) +#define _PDCLIB_USHRT_MAX ( __SHRT_MAX__ * 2u + 1 ) + +/* Maximum and minimum values of signed / unsigned int */ +#define _PDCLIB_INT_MAX __INT_MAX__ +#define _PDCLIB_INT_MIN _PDCLIB_MIN_CALC( __INT_MAX__ ) +#define _PDCLIB_UINT_MAX ( __INT_MAX__ * 2u + 1 ) + +/* Maximum and minimum values of signed / unsigned long */ +#define _PDCLIB_LONG_MAX __LONG_MAX__ +#define _PDCLIB_LONG_MIN _PDCLIB_MIN_CALC( __LONG_MAX__ ) +#define _PDCLIB_ULONG_MAX ( __LONG_MAX__ * 2ul + 1 ) + +/* Maximum and minimum values of signed / unsigned long long */ +#define _PDCLIB_LLONG_MAX __LONG_LONG_MAX__ +#define _PDCLIB_LLONG_MIN _PDCLIB_MIN_CALC( __LONG_LONG_MAX__ ) +#define _PDCLIB_ULLONG_MAX ( __LONG_LONG_MAX__ * 2ull + 1 ) + +/* -------------------------------------------------------------------------- */ +/* defines a set of integer types that are of a minimum width, and */ +/* "usually fastest" on the system. (If, for example, accessing a single char */ +/* requires the CPU to access a complete int and then mask out the char, the */ +/* "usually fastest" type of at least 8 bits would be int, not char.) */ +/* If you do not have information on the relative performance of the types, */ +/* the standard allows you to define any type that meets minimum width and */ +/* signedness requirements. */ +/* The first define is the appropriate basic type (e.g. "long int"), second */ +/* its max value, the third its min value (both expressed in the given type). */ +/* The same follows for the unsigned type (for which the minimum value is */ +/* obviously zero and need not be defined). */ +/* There *are* predefines provided for the printf()/scanf() length specifiers */ +/* but tunneling them through here would have added many lines of repetitive */ +/* and mostly redundant defines. They are determined in <_PDCLIB_internal.h>. */ +/* -------------------------------------------------------------------------- */ + +/* int_fast8_t / uint_fast8_t */ +#define _PDCLIB_int_fast8_t __INT_FAST8_TYPE__ +#define _PDCLIB_INT_FAST8_MAX __INT_FAST8_MAX__ +#define _PDCLIB_INT_FAST8_MIN _PDCLIB_MIN_CALC( __INT_FAST8_MAX__ ) +#define _PDCLIB_uint_fast8_t __UINT_FAST8_TYPE__ +#define _PDCLIB_UINT_FAST8_MAX __UINT_FAST8_MAX__ + +/* int_least8_t / uint_least8_t */ +#define _PDCLIB_int_least8_t __INT_LEAST8_TYPE__ +#define _PDCLIB_INT_LEAST8_MAX __INT_LEAST8_MAX__ +#define _PDCLIB_INT_LEAST8_MIN _PDCLIB_MIN_CALC( __INT_LEAST8_MAX__ ) +#define _PDCLIB_uint_least8_t __UINT_LEAST8_TYPE__ +#define _PDCLIB_UINT_LEAST8_MAX __UINT_LEAST8_MAX__ + +/* int_fast16_t / uint_fast16_t */ +#define _PDCLIB_int_fast16_t __INT_FAST16_TYPE__ +#define _PDCLIB_INT_FAST16_MAX __INT_FAST16_MAX__ +#define _PDCLIB_INT_FAST16_MIN _PDCLIB_MIN_CALC( __INT_FAST16_MAX__ ) +#define _PDCLIB_uint_fast16_t __UINT_FAST16_TYPE__ +#define _PDCLIB_UINT_FAST16_MAX __UINT_FAST16_MAX__ + +/* int_least16_t / uint_least16_t */ +#define _PDCLIB_int_least16_t __INT_LEAST16_TYPE__ +#define _PDCLIB_INT_LEAST16_MAX __INT_LEAST16_MAX__ +#define _PDCLIB_INT_LEAST16_MIN _PDCLIB_MIN_CALC( __INT_LEAST16_MAX__ ) +#define _PDCLIB_uint_least16_t __UINT_LEAST16_TYPE__ +#define _PDCLIB_UINT_LEAST16_MAX __UINT_LEAST16_MAX__ + +/* int_fast32_t / uint_fast32_t */ +#define _PDCLIB_int_fast32_t __INT_FAST32_TYPE__ +#define _PDCLIB_INT_FAST32_MAX __INT_FAST32_MAX__ +#define _PDCLIB_INT_FAST32_MIN _PDCLIB_MIN_CALC( __INT_FAST32_MAX__ ) +#define _PDCLIB_uint_fast32_t __UINT_FAST32_TYPE__ +#define _PDCLIB_UINT_FAST32_MAX __UINT_FAST32_MAX__ + +/* int_least32_t / uint_least32_t */ +#define _PDCLIB_int_least32_t __INT_LEAST32_TYPE__ +#define _PDCLIB_INT_LEAST32_MAX __INT_LEAST32_MAX__ +#define _PDCLIB_INT_LEAST32_MIN _PDCLIB_MIN_CALC( __INT_LEAST32_MAX__ ) +#define _PDCLIB_uint_least32_t __UINT_LEAST32_TYPE__ +#define _PDCLIB_UINT_LEAST32_MAX __UINT_LEAST32_MAX__ + +/* int_fast64_t / uint_fast64_t */ +#define _PDCLIB_int_fast64_t __INT_FAST64_TYPE__ +#define _PDCLIB_INT_FAST64_MAX __INT_FAST64_MAX__ +#define _PDCLIB_INT_FAST64_MIN _PDCLIB_MIN_CALC( __INT_FAST64_MAX__ ) +#define _PDCLIB_uint_fast64_t __UINT_FAST64_TYPE__ +#define _PDCLIB_UINT_FAST64_MAX __UINT_FAST64_MAX__ + +/* int_least64_t / uint_least64_t */ +#define _PDCLIB_int_least64_t __INT_LEAST64_TYPE__ +#define _PDCLIB_INT_LEAST64_MAX __INT_LEAST64_MAX__ +#define _PDCLIB_INT_LEAST64_MIN _PDCLIB_MIN_CALC( __INT_LEAST64_MAX__ ) +#define _PDCLIB_uint_least64_t __UINT_LEAST64_TYPE__ +#define _PDCLIB_UINT_LEAST64_MAX __UINT_LEAST64_MAX__ + +/* Exact-width integer types. These are *optional*. If your platform does not */ +/* support types of these exact widths in two's complement encoding, just */ +/* leave them undefined. */ +#define _PDCLIB_int8_t __INT8_TYPE__ +#define _PDCLIB_int16_t __INT16_TYPE__ +#define _PDCLIB_int32_t __INT32_TYPE__ +#define _PDCLIB_int64_t __INT64_TYPE__ +#define _PDCLIB_uint8_t __UINT8_TYPE__ +#define _PDCLIB_uint16_t __UINT16_TYPE__ +#define _PDCLIB_uint32_t __UINT32_TYPE__ +#define _PDCLIB_uint64_t __UINT64_TYPE__ + +/* INTn_C / UINTn_C macros to define int_leastN_t / uint_leastN_t literals. */ +#if defined( __INT8_C ) +/* GCC */ +#define _PDCLIB_INT_LEAST8_C __INT8_C +#define _PDCLIB_UINT_LEAST8_C __UINT8_C +#define _PDCLIB_INT_LEAST16_C __INT16_C +#define _PDCLIB_UINT_LEAST16_C __UINT16_C +#define _PDCLIB_INT_LEAST32_C __INT32_C +#define _PDCLIB_UINT_LEAST32_C __UINT32_C +#define _PDCLIB_INT_LEAST64_C __INT64_C +#define _PDCLIB_UINT_LEAST64_C __UINT64_C +#elif defined( __INT8_C_SUFFIX__ ) +/* Clang */ +#define _PDCLIB_INT_LEAST8_C(c) _PDCLIB_concat( c, __INT8_C_SUFFIX__ ) +#define _PDCLIB_UINT_LEAST8_C(c) _PDCLIB_concat( c, __UINT8_C_SUFFIX__ ) +#define _PDCLIB_INT_LEAST16_C(c) _PDCLIB_concat( c, __INT16_C_SUFFIX__ ) +#define _PDCLIB_UINT_LEAST16_C(c) _PDCLIB_concat( c, __UINT16_C_SUFFIX__ ) +#define _PDCLIB_INT_LEAST32_C(c) _PDCLIB_concat( c, __INT32_C_SUFFIX__ ) +#define _PDCLIB_UINT_LEAST32_C(c) _PDCLIB_concat( c, __UINT32_C_SUFFIX__ ) +#define _PDCLIB_INT_LEAST64_C(c) _PDCLIB_concat( c, __INT64_C_SUFFIX__ ) +#define _PDCLIB_UINT_LEAST64_C(c) _PDCLIB_concat( c, __UINT64_C_SUFFIX__ ) +#else +#error Please create your own _PDCLIB_config.h. Using the existing one as-is will not work. (Unsupported *INTn_C macros.) +#endif + +/* defines the div() function family that allows taking quotient */ +/* and remainder of an integer division in one operation. Many platforms */ +/* support this in hardware / opcode, and the standard permits ordering of */ +/* the return structure in any way to fit the hardware. That is why those */ +/* structs can be configured here. */ + +struct _PDCLIB_div_t +{ + int quot; + int rem; +}; + +struct _PDCLIB_ldiv_t +{ + long int quot; + long int rem; +}; + +struct _PDCLIB_lldiv_t +{ + long long int quot; + long long int rem; +}; + +/* -------------------------------------------------------------------------- */ +/* What follows are a couple of "special" typedefs and their limits. */ +/* -------------------------------------------------------------------------- */ + +/* The result type of substracting two pointers */ +#define _PDCLIB_ptrdiff_t __PTRDIFF_TYPE__ +#define _PDCLIB_PTRDIFF_MAX __PTRDIFF_MAX__ +#define _PDCLIB_PTRDIFF_MIN _PDCLIB_MIN_CALC( __PTRDIFF_MAX__ ) + +/* An integer type that can be accessed as atomic entity (think asynchronous */ +/* interrupts). In a freestanding environment, the type itself need not be */ +/* defined, but its limits must. (Don't ask.) GCC is so kind to predefine it, */ +/* but clang is only giving us its MAX value, so we use that to identify the */ +/* type in _PDCLIB_int.h if the type definition is unavailable. */ +#ifdef __SIG_ATOMIC_TYPE__ +#define _PDCLIB_sig_atomic_t __SIG_ATOMIC_TYPE__ +#endif +#define _PDCLIB_SIG_ATOMIC_MAX __SIG_ATOMIC_MAX__ +#define _PDCLIB_SIG_ATOMIC_MIN _PDCLIB_MIN_CALC( __SIG_ATOMIC_MAX__ ) + +/* Result type of the 'sizeof' operator (must be unsigned). */ +/* Note: In , this is taken as the base for RSIZE_MAX, the limit */ +/* for the bounds-checking interfaces of Annex K. The recommendation by the */ +/* standard is to use ( SIZE_MAX >> 1 ) when "targeting machines with large */ +/* addess spaces", whereas small address spaces should use SIZE_MAX directly. */ +#define _PDCLIB_size_t __SIZE_TYPE__ +#define _PDCLIB_SIZE_MAX __SIZE_MAX__ + +/* Large enough an integer to hold all character codes of the widest */ +/* supported locale. */ +#define _PDCLIB_wchar_t __WCHAR_TYPE__ +#define _PDCLIB_WCHAR_MAX __WCHAR_MAX__ +#define _PDCLIB_WCHAR_MIN __WCHAR_MIN__ + +/* Large enough an integer to hold all character codes of the widest */ +/* supported locale plus WEOF (which needs not to be equal to EOF, nor needs */ +/* to be of negative value). */ +#define _PDCLIB_wint_t __WINT_TYPE__ +#define _PDCLIB_WINT_MAX __WINT_MAX__ +#define _PDCLIB_WINT_MIN __WINT_MIN__ + +/* Integer types capable of taking the (cast) value of a void *, and having */ +/* the value cast back to void *, comparing equal to the original. */ +#define _PDCLIB_intptr_t __INTPTR_TYPE__ +#define _PDCLIB_INTPTR_MAX __INTPTR_MAX__ +#define _PDCLIB_INTPTR_MIN _PDCLIB_MIN_CALC( __INTPTR_MAX__ ) +#define _PDCLIB_uintptr_t __UINTPTR_TYPE__ +#define _PDCLIB_UINTPTR_MAX __UINTPTR_MAX__ + +/* Largest supported integer type. Implementation note: see _PDCLIB_atomax(). */ +#define _PDCLIB_intmax_t __INTMAX_TYPE__ +#define _PDCLIB_INTMAX_MAX __INTMAX_MAX__ +#define _PDCLIB_INTMAX_MIN _PDCLIB_MIN_CALC( __INTMAX_MAX__ ) +#define _PDCLIB_INTMAX_C __INTMAX_C +#define _PDCLIB_uintmax_t __UINTMAX_TYPE__ +#define _PDCLIB_UINTMAX_MAX __UINTMAX_MAX__ +#define _PDCLIB_UINTMAX_C __UINTMAX_C + +/* defines imaxdiv(), which is equivalent to the div() function */ +/* family (see further above) with intmax_t as basis. */ +struct _PDCLIB_imaxdiv_t +{ + _PDCLIB_intmax_t quot; + _PDCLIB_intmax_t rem; +}; + +/* -------------------------------------------------------------------------- */ +/* Time types, limits, constants, and paths */ +/* -------------------------------------------------------------------------- */ + +/* _PDCLIB_time is the type for type_t; _PDCLIB_clock for clock_t. Both types */ +/* are defined as "real types capable of representing times". The "range and */ +/* precision of times representable" is implementation-defined. */ + +/* For clock_t, the standard defines that dividing the result of clock() by */ +/* CLOCKS_PER_SEC gives the seconds elapsed. */ +#ifdef __CYGWIN__ +#define _PDCLIB_clock_t unsigned long +#else +#define _PDCLIB_clock_t long +#endif +#define _PDCLIB_CLOCKS_PER_SEC 1000000 + +/* For time_t, no such divider exists. Most implementations use a count of */ +/* seconds since a specified epoch. While PDCLib really should support other */ +/* encodings as well, for now "count of seconds" is the only supported one. */ +/* MIN / MAX values for time_t are not required by the standard (and they are */ +/* not "exported" from the _PDCLIB namespace), but they are useful in support */ +/* of the _tzcode implementation. */ +#ifdef __MINGW64__ +#define _PDCLIB_time_t long long +#define _PDCLIB_TIME_MAX __LONG_LONG_MAX__ +#define _PDCLIB_TIME_MIN _PDCLIB_MIN_CALC( __LONG_LONG_MAX__ ) +#else +#define _PDCLIB_time_t long +#define _PDCLIB_TIME_MAX __LONG_MAX__ +#define _PDCLIB_TIME_MIN _PDCLIB_MIN_CALC( __LONG_MAX__ ) +#endif + +/* "Unix time" uses 1970-01-01T00:00:00 as "epoch". If your system uses a */ +/* different "zero point" for its timestamps, set this to the offset between */ +/* your epoch and Unix epoch. (For example, NTP uses 1900-01-01T00:00:00 as */ +/* epoch, giving an offset of (70 * 365 + 17) * 86400 = 220898800 seconds.) */ +#define _PDCLIB_EPOCH_BIAS INT64_C( 0 ) + +/* Leave this alone for now. */ +#define _PDCLIB_TIME_UTC 1 + +/* Path to TZ data. */ +/* IMPORTANT: *Must* end with separator character! */ +/* It does make it much easier for the time data handling code if this detail */ +/* can be relied upon and need not be handled in code. */ +#define _PDCLIB_TZDIR "/usr/share/zoneinfo/" + +/* Path to default (local) timezone */ +#define _PDCLIB_TZDEFAULT "/etc/localtime" + +/* -------------------------------------------------------------------------- */ +/* Floating Point */ +/* -------------------------------------------------------------------------- */ + +/* Whether the implementation rounds toward zero (0), to nearest (1), toward */ +/* positive infinity (2), or toward negative infinity (3). (-1) signifies */ +/* indeterminable rounding, any other value implementation-specific rounding. */ +#define _PDCLIB_FLT_ROUNDS -1 + +/* Check for explanations on each of these values. */ +#define _PDCLIB_FLT_EVAL_METHOD __FLT_EVAL_METHOD__ + +#define _PDCLIB_FLT_HAS_SUBNORM __FLT_HAS_DENORM__ +#define _PDCLIB_DBL_HAS_SUBNORM __DBL_HAS_DENORM__ +#define _PDCLIB_LDBL_HAS_SUBNORM __LDBL_HAS_DENORM__ + +#define _PDCLIB_FLT_RADIX __FLT_RADIX__ + +#define _PDCLIB_FLT_MANT_DIG __FLT_MANT_DIG__ +#define _PDCLIB_DBL_MANT_DIG __DBL_MANT_DIG__ +#define _PDCLIB_LDBL_MANT_DIG __LDBL_MANT_DIG__ + +#define _PDCLIB_FLT_DECIMAL_DIG __FLT_DECIMAL_DIG__ +#define _PDCLIB_DBL_DECIMAL_DIG __DBL_DECIMAL_DIG__ +#define _PDCLIB_LDBL_DECIMAL_DIG __LDBL_DECIMAL_DIG__ + +#define _PDCLIB_DECIMAL_DIG __DECIMAL_DIG__ + +#define _PDCLIB_FLT_DIG __FLT_DIG__ +#define _PDCLIB_DBL_DIG __DBL_DIG__ +#define _PDCLIB_LDBL_DIG __LDBL_DIG__ + +#define _PDCLIB_FLT_MIN_EXP __FLT_MIN_EXP__ +#define _PDCLIB_DBL_MIN_EXP __DBL_MIN_EXP__ +#define _PDCLIB_LDBL_MIN_EXP __LDBL_MIN_EXP__ + +#define _PDCLIB_FLT_MIN_10_EXP __FLT_MIN_10_EXP__ +#define _PDCLIB_DBL_MIN_10_EXP __DBL_MIN_10_EXP__ +#define _PDCLIB_LDBL_MIN_10_EXP __LDBL_MIN_10_EXP__ + +#define _PDCLIB_FLT_MAX_EXP __FLT_MAX_EXP__ +#define _PDCLIB_DBL_MAX_EXP __DBL_MAX_EXP__ +#define _PDCLIB_LDBL_MAX_EXP __LDBL_MAX_EXP__ + +#define _PDCLIB_FLT_MAX_10_EXP __FLT_MAX_10_EXP__ +#define _PDCLIB_DBL_MAX_10_EXP __DBL_MAX_10_EXP__ +#define _PDCLIB_LDBL_MAX_10_EXP __LDBL_MAX_10_EXP__ + +#define _PDCLIB_FLT_MAX __FLT_MAX__ +#define _PDCLIB_DBL_MAX __DBL_MAX__ +#define _PDCLIB_LDBL_MAX __LDBL_MAX__ + +#define _PDCLIB_FLT_EPSILON __FLT_EPSILON__ +#define _PDCLIB_DBL_EPSILON __DBL_EPSILON__ +#define _PDCLIB_LDBL_EPSILON __LDBL_EPSILON__ + +#define _PDCLIB_FLT_MIN __FLT_MIN__ +#define _PDCLIB_DBL_MIN __DBL_MIN__ +#define _PDCLIB_LDBL_MIN __LDBL_MIN__ + +#define _PDCLIB_FLT_TRUE_MIN __FLT_DENORM_MIN__ +#define _PDCLIB_DBL_TRUE_MIN __DBL_DENORM_MIN__ +#define _PDCLIB_LDBL_TRUE_MIN __LDBL_DENORM_MIN__ + +/* Macros for deconstructing floating point values */ +#define _PDCLIB_DBL_SIGN( bytes ) ( ( (unsigned)bytes[7] & 0x80 ) >> 7 ) +#define _PDCLIB_DBL_DEC( bytes ) ( ( _PDCLIB_DBL_EXP( bytes ) > 0 ) ? 1 : 0 ) +#define _PDCLIB_DBL_EXP( bytes ) ( ( ( (unsigned)bytes[7] & 0x7f ) << 4 ) | ( ( (unsigned)bytes[6] & 0xf0 ) >> 4 ) ) +#define _PDCLIB_DBL_BIAS 1023 +#define _PDCLIB_DBL_MANT_START( bytes ) ( bytes + 6 ) + +/* Most platforms today use IEEE 754 single precision for 'float', and double */ +/* precision for 'double'. But type 'long double' varies. We use what the */ +/* compiler states about LDBL_MANT_DIG to determine the type. */ +#if _PDCLIB_LDBL_MANT_DIG == 64 + +/* Intel "Extended Precision" format, using 80 bits (64bit mantissa) */ +#define _PDCLIB_LDBL_SIGN( bytes ) ( ( (unsigned)bytes[9] & 0x80 ) >> 7 ) +#define _PDCLIB_LDBL_DEC( bytes ) ( ( (unsigned)bytes[7] & 0x80 ) >> 7 ) +#define _PDCLIB_LDBL_EXP( bytes ) ( ( ( (unsigned)bytes[9] & 0x7f ) << 8 ) | (unsigned)bytes[8] ) +#define _PDCLIB_LDBL_BIAS 16383 +#define _PDCLIB_LDBL_MANT_START( bytes ) ( bytes + 7 ) + +#elif _PDCLIB_LDBL_MANT_DIG == 113 + +/* IEEE "Quadruple Precision" format, using 128 bits (113bit mantissa) */ +#define _PDCLIB_LDBL_SIGN( bytes ) ( ( (unsigned)bytes[15] & 0x80 ) >> 7 ) +#define _PDCLIB_LDBL_DEC( bytes ) ( ( _PDCLIB_LDBL_EXP( bytes ) > 0 ) ? 1 : 0 ) +#define _PDCLIB_LDBL_EXP( bytes ) ( ( ( (unsigned)bytes[15] & 0x7f ) << 8 ) | (unsigned)bytes[14] ) +#define _PDCLIB_LDBL_BIAS 16383 +#define _PDCLIB_LDBL_MANT_START( bytes ) ( bytes + 13 ) + +#else + +/* IEEE "Double Precision" format, using 64 bits (53bit mantissa, + same as DBL above) */ +#define _PDCLIB_LDBL_SIGN( bytes ) ( ( (unsigned)bytes[7] & 0x80 ) >> 7 ) +#define _PDCLIB_LDBL_DEC( bytes ) ( ( _PDCLIB_LDBL_EXP( bytes ) > 0 ) ? 1 : 0 ) +#define _PDCLIB_LDBL_EXP( bytes ) ( ( ( (unsigned)bytes[7] & 0x7f ) << 4 ) | ( ( (unsigned)bytes[6] & 0xf0 ) >> 4 ) ) +#define _PDCLIB_LDBL_BIAS 1023 +#define _PDCLIB_LDBL_MANT_START( bytes ) ( bytes + 6 ) + +#endif + +/* -------------------------------------------------------------------------- */ +/* Big Integer Arithmetic */ +/* -------------------------------------------------------------------------- */ +/* In support of the floating point converstions required by printf() etc., */ +/* PDCLib provides rudimentary big integer arithmetics. The _PDCLIB_bigint_t */ +/* type stores values in a sequence of integer "digits", which may be of any */ +/* uint_leastN_t type with N being 32 or 16. Note that multiplication and */ +/* division require the help of the next larger type. So set the define to */ +/* 32 if efficient 64bit integer arithmetics are available on your platform, */ +/* and to 16 otherwise. */ +/* (The value range of _PDCLIB_bigint_t is not affected by this setting.) */ + +#define _PDCLIB_BIGINT_DIGIT_BITS 16 + +/* -------------------------------------------------------------------------- */ +/* Platform-dependent macros defined by the standard headers. */ +/* -------------------------------------------------------------------------- */ + +/* The offsetof macro */ +/* Contract: Expand to an integer constant expression of type size_t, which */ +/* represents the offset in bytes to the structure member from the beginning */ +/* of the structure. If the specified member is a bitfield, behaviour is */ +/* undefined. */ +/* There is no standard-compliant way to do this. */ +/* This implementation casts an integer zero to 'pointer to type', and then */ +/* takes the address of member. This is undefined behaviour but should work */ +/* on most compilers. */ +#define _PDCLIB_offsetof( type, member ) ( (size_t) &( ( (type *) 0 )->member ) ) + +/* Variable Length Parameter List Handling () */ +/* The macros defined by are highly dependent on the calling */ +/* conventions used, and you probably have to replace them with builtins of */ +/* your compiler. */ + +#if defined( __i386 ) + +/* The following generic implementation works only for pure stack-based */ +/* architectures, and only if arguments are aligned to pointer type. Credits */ +/* to Michael Moody, who contributed this to the Public Domain. */ + +/* Internal helper macro. va_round is not part of . */ +#define _PDCLIB_va_round( type ) ( (sizeof(type) + sizeof(void *) - 1) & ~(sizeof(void *) - 1) ) + +typedef char * _PDCLIB_va_list; +#define _PDCLIB_va_arg( ap, type ) ( (ap) += (_PDCLIB_va_round(type)), ( *(type*) ( (ap) - (_PDCLIB_va_round(type)) ) ) ) +#define _PDCLIB_va_copy( dest, src ) ( (dest) = (src), (void)0 ) +#define _PDCLIB_va_end( ap ) ( (ap) = (void *)0, (void)0 ) +#define _PDCLIB_va_start( ap, parmN ) ( (ap) = (char *) &parmN + ( _PDCLIB_va_round(parmN) ), (void)0 ) + +#elif defined( __x86_64 ) || defined( __arm__ ) || defined( __ARM_NEON ) + +/* No way to cover x86_64 or arm with a generic implementation, as it uses */ +/* register-based parameter passing. Using compiler builtins here. */ +typedef __builtin_va_list _PDCLIB_va_list; +#define _PDCLIB_va_arg( ap, type ) ( __builtin_va_arg( ap, type ) ) +#define _PDCLIB_va_copy( dest, src ) ( __builtin_va_copy( dest, src ) ) +#define _PDCLIB_va_end( ap ) ( __builtin_va_end( ap ) ) +#define _PDCLIB_va_start( ap, parmN ) ( __builtin_va_start( ap, parmN ) ) + +#else + +#error Please create your own _PDCLIB_config.h. Using the existing one as-is will not work. (Unsupported varargs.) + +#endif + +/* -------------------------------------------------------------------------- */ +/* OS "glue", part 1 */ +/* These are values and data type definitions that you would have to adapt to */ +/* the capabilities and requirements of your OS. */ +/* The actual *functions* of the OS interface are declared in _PDCLIB_glue.h. */ +/* -------------------------------------------------------------------------- */ + +/* I/O ---------------------------------------------------------------------- */ + +/* The type of the file descriptor returned by _PDCLIB_open(), i.e. whatever */ +/* the underlying kernel uses for stream identification. */ +typedef int _PDCLIB_fd_t; + +/* The value of type _PDCLIB_fd_t returned by _PDCLIB_open() if the operation */ +/* failed. */ +#define _PDCLIB_NOHANDLE ( (_PDCLIB_fd_t) -1 ) + +/* The default size for file buffers. Must be at least 256. */ +#define _PDCLIB_BUFSIZ 1024 + +/* The minimum number of files the implementation guarantees can opened */ +/* simultaneously. Must be at least 8. Depends largely on how the platform */ +/* does the bookkeeping in whatever is called by _PDCLIB_open(). PDCLib puts */ +/* no further limits on the number of open files other than available memory. */ +#define _PDCLIB_FOPEN_MAX 8 + +/* Length of the longest filename the implementation guarantees to support. */ +#define _PDCLIB_FILENAME_MAX 128 + +/* Maximum length of filenames generated by tmpnam(). (See tmpfile.c.) */ +#define _PDCLIB_L_tmpnam 46 + +/* Number of distinct file names that can be generated by tmpnam(). */ +#define _PDCLIB_TMP_MAX 50 + +/* The values of SEEK_SET, SEEK_CUR and SEEK_END, used by fseek(). */ +/* Since at least one platform (POSIX) uses the same symbols for its own */ +/* "seek" function, you should use whatever the host defines (if it does */ +/* define them). */ +#define _PDCLIB_SEEK_SET 0 +#define _PDCLIB_SEEK_CUR 1 +#define _PDCLIB_SEEK_END 2 + +/* The number of characters that can be buffered with ungetc(). The standard */ +/* guarantees only one (1); PDCLib supports larger values, but applications */ +/* relying on this would rely on implementation-defined behaviour (not good). */ +#define _PDCLIB_UNGETCBUFSIZE 1 + +/* The number of functions that can be registered with atexit(). Needs to be */ +/* at least 33 (32 guaranteed by the standard, plus _PDCLIB_closeall() which */ +/* is used internally by PDCLib to close all open streams). */ +/* TODO: Should expand dynamically. */ +#define _PDCLIB_ATEXIT_SLOTS 40 + +/* errno -------------------------------------------------------------------- */ + +/* These are the values that _PDCLIB_errno can be set to by the library. */ +/* */ +/* By keeping PDCLib's errno in the _PDCLIB_* namespace, the library is */ +/* capable of "translating" between errno values used by the hosting OS and */ +/* those used and passed out by the library. */ +/* */ +/* Example: In the example platform, the remove() function uses the unlink() */ +/* system call as backend. Linux sets its errno to EISDIR if you try to */ +/* unlink() a directory, but POSIX demands EPERM. Within the remove() */ +/* function, you can catch 'errno == EISDIR', and set '*_PDCLIB_errno_func() */ +/* = _PDCLIB_EPERM'. Anyone using PDCLib's will "see" EPERM instead */ +/* of EISDIR. */ +/* */ +/* If you do not want that kind of translation, you might want to "match" the */ +/* values used by PDCLib with those used by the host OS, to avoid confusion. */ +/* auxiliary/errno/errno_readout.c provides a convenience program to read */ +/* those errno values mandated by the standard from a platform's , */ +/* giving output that can readily be pasted here. */ +/* Either way, note that the list below, the list in PDCLib's , and */ +/* the list in _PDCLIB_stdinit.h, need to be kept in sync. */ +/* */ +/* The values below are read from a Linux system. */ + +/* Argument list too long */ +#define _PDCLIB_E2BIG 7 +/* Permission denied */ +#define _PDCLIB_EACCES 13 +/* Address in use */ +#define _PDCLIB_EADDRINUSE 98 +/* Address not available */ +#define _PDCLIB_EADDRNOTAVAIL 99 +/* Address family not supported */ +#define _PDCLIB_EAFNOSUPPORT 97 +/* Resource unavailable, try again */ +#define _PDCLIB_EAGAIN 11 +/* Connection already in progress */ +#define _PDCLIB_EALREADY 114 +/* Bad file descriptor */ +#define _PDCLIB_EBADF 9 +/* Bad message */ +#define _PDCLIB_EBADMSG 74 +/* Device or resource busy */ +#define _PDCLIB_EBUSY 16 +/* Operation canceled */ +#define _PDCLIB_ECANCELED 125 +/* No child processes */ +#define _PDCLIB_ECHILD 10 +/* Connection aborted */ +#define _PDCLIB_ECONNABORTED 103 +/* Connection refused */ +#define _PDCLIB_ECONNREFUSED 111 +/* Connection reset */ +#define _PDCLIB_ECONNRESET 104 +/* Resource deadlock would occur */ +#define _PDCLIB_EDEADLK 35 +/* Destination address required */ +#define _PDCLIB_EDESTADDRREQ 89 +/* Mathematics argument out of domain of function */ +#define _PDCLIB_EDOM 33 +/* File exists */ +#define _PDCLIB_EEXIST 17 +/* Bad address */ +#define _PDCLIB_EFAULT 14 +/* File too large */ +#define _PDCLIB_EFBIG 27 +/* Host is unreachable */ +#define _PDCLIB_EHOSTUNREACH 113 +/* Identifier removed */ +#define _PDCLIB_EIDRM 43 +/* Illegal byte sequence */ +#define _PDCLIB_EILSEQ 84 +/* Operation in progress */ +#define _PDCLIB_EINPROGRESS 115 +/* Interrupted function */ +#define _PDCLIB_EINTR 4 +/* Invalid argument */ +#define _PDCLIB_EINVAL 22 +/* I/O error */ +#define _PDCLIB_EIO 5 +/* Socket is connected */ +#define _PDCLIB_EISCONN 106 +/* Is a directory */ +#define _PDCLIB_EISDIR 21 +/* Too many levels of symbolic links */ +#define _PDCLIB_ELOOP 40 +/* File descriptor value too large */ +#define _PDCLIB_EMFILE 24 +/* Too many links */ +#define _PDCLIB_EMLINK 31 +/* Message too large */ +#define _PDCLIB_EMSGSIZE 90 +/* Filename too long */ +#define _PDCLIB_ENAMETOOLONG 36 +/* Network is down */ +#define _PDCLIB_ENETDOWN 100 +/* Connection aborted by network */ +#define _PDCLIB_ENETRESET 102 +/* Network unreachable */ +#define _PDCLIB_ENETUNREACH 101 +/* Too many files open in system */ +#define _PDCLIB_ENFILE 23 +/* No buffer space available */ +#define _PDCLIB_ENOBUFS 105 +/* No message is available on the STREAM head read queue */ +#define _PDCLIB_ENODATA 61 +/* No such device */ +#define _PDCLIB_ENODEV 19 +/* No such file or directory */ +#define _PDCLIB_ENOENT 2 +/* Executable file format error */ +#define _PDCLIB_ENOEXEC 8 +/* No locks available */ +#define _PDCLIB_ENOLCK 37 +/* Link has been severed */ +#define _PDCLIB_ENOLINK 67 +/* Not enough space */ +#define _PDCLIB_ENOMEM 12 +/* No message of the desired type */ +#define _PDCLIB_ENOMSG 42 +/* Protocol not available */ +#define _PDCLIB_ENOPROTOOPT 92 +/* No space left on device */ +#define _PDCLIB_ENOSPC 28 +/* No STREAM resources */ +#define _PDCLIB_ENOSR 63 +/* Not a STREAM */ +#define _PDCLIB_ENOSTR 60 +/* Function not supported */ +#define _PDCLIB_ENOSYS 38 +/* The socket is not connected */ +#define _PDCLIB_ENOTCONN 107 +/* Not a directory */ +#define _PDCLIB_ENOTDIR 20 +/* Directory not empty */ +#define _PDCLIB_ENOTEMPTY 39 +/* State not recoverable */ +#define _PDCLIB_ENOTRECOVERABLE 131 +/* Not a socket */ +#define _PDCLIB_ENOTSOCK 88 +/* Not supported */ +#define _PDCLIB_ENOTSUP 95 +/* Inappropriate I/O control operation */ +#define _PDCLIB_ENOTTY 25 +/* No such device or address */ +#define _PDCLIB_ENXIO 6 +/* Operation not supported on socket */ +#define _PDCLIB_EOPNOTSUPP 95 +/* Value too large to be stored in data type */ +#define _PDCLIB_EOVERFLOW 75 +/* Previous owner died */ +#define _PDCLIB_EOWNERDEAD 130 +/* Operation not permitted */ +#define _PDCLIB_EPERM 1 +/* Broken pipe */ +#define _PDCLIB_EPIPE 32 +/* Protocol error */ +#define _PDCLIB_EPROTO 71 +/* Protocol not supported */ +#define _PDCLIB_EPROTONOSUPPORT 93 +/* Protocol wrong type for socket */ +#define _PDCLIB_EPROTOTYPE 91 +/* Result too large */ +#define _PDCLIB_ERANGE 34 +/* Read-only file system */ +#define _PDCLIB_EROFS 30 +/* Invalid seek */ +#define _PDCLIB_ESPIPE 29 +/* No such process */ +#define _PDCLIB_ESRCH 3 +/* Stream ioctl() timeout */ +#define _PDCLIB_ETIME 62 +/* Connection timed out */ +#define _PDCLIB_ETIMEDOUT 110 +/* Text file busy */ +#define _PDCLIB_ETXTBSY 26 +/* Operation would block */ +#define _PDCLIB_EWOULDBLOCK 11 +/* Cross-device link */ +#define _PDCLIB_EXDEV 18 + +/* The highest defined errno value, plus one. This is used to set the size */ +/* of the array in struct _PDCLIB_lc_text_t holding error messages for the */ +/* strerror() and perror() functions. (If you change this value because you */ +/* are using additional errno values, you *HAVE* to provide appropriate error */ +/* messages for *ALL* locales.) */ +#define _PDCLIB_ERRNO_MAX 132 + +/* The error message used for unknown error codes (generated by errno_readout */ +/* for consistency between the 'holes' in the list of defined error messages */ +/* and the text generated by e.g. strerror() for out-of-range error values.) */ +#define _PDCLIB_EUNKNOWN_TEXT (char*)"unknown error" + +/* locale data -------------------------------------------------------------- */ + +/* The default path where PDCLib should look for its locale data. */ +/* Must end with the appropriate separator character. */ +#define _PDCLIB_LOCALE_PATH "/usr/share/pdclib/i18n/" + +/* The name of the environment variable that can be used to override that */ +/* path setting. */ +#define _PDCLIB_LOCALE_PATH_ENV PDCLIB_I18N + +#ifdef __CYGWIN__ +typedef unsigned int wint_t; +#endif + +/* threads ------------------------------------------------------------------ */ + +/* This is relying on underlying implementation to provide thread */ +/* support. */ +/* The problem here is we cannot just #include and access the */ +/* original definitions. The standard library must not drag identifiers into */ +/* the user's namespace, so we have to set our own definitions in the _PDCLIB */ +/* namespace. Which are, obviously, platform-specific. */ +/* If you do NOT want to provide threads support, define __STDC_NO_THREADS__ */ +/* to 1 and simply delete the threads.h header and the corresponding files in */ +/* functions/threads/. This makes PDCLib non-thread-safe (obviously), as the */ +/* safeguards against race conditions (e.g. in ) will be omitted. */ + +/* auxiliary/pthread/pthread_readout.c provides a convenience program to read */ +/* appropriate definitions from a platform's , giving output that */ +/* can be copy & pasted here. */ + +typedef unsigned long int _PDCLIB_thrd_t; +typedef union { unsigned char _PDCLIB_cnd_t_data[ 48 ]; long long int _PDCLIB_cnd_t_align; } _PDCLIB_cnd_t; +#if defined( __arm__ ) || defined( __ARM_NEON ) +typedef union { unsigned char _PDCLIB_mtx_t_data[ 24 ]; long int _PDCLIB_mtx_t_align; } _PDCLIB_mtx_t; +#else +typedef union { unsigned char _PDCLIB_mtx_t_data[ 40 ]; long int _PDCLIB_mtx_t_align; } _PDCLIB_mtx_t; +#endif +typedef unsigned int _PDCLIB_tss_t; +typedef int _PDCLIB_once_flag; +#define _PDCLIB_ONCE_FLAG_INIT 0 +#define _PDCLIB_RECURSIVE_MUTEX_INIT PTHREAD_MUTEX_INITIALIZER +/* This one is actually hidden in , and only if __USE_POSIX is */ +/* defined prior to #include (PTHREAD_DESTRUCTOR_ITERATIONS). */ +#define _PDCLIB_TSS_DTOR_ITERATIONS 4 +/* The following are not made public in any header, but used internally for */ +/* interfacing with the pthread API. */ +typedef union { unsigned char _PDCLIB_cnd_attr_t_data[ 4 ]; int _PDCLIB_cnd_attr_t_align; } _PDCLIB_cnd_attr_t; +typedef union { unsigned char _PDCLIB_mtx_attr_t_data[ 4 ]; int _PDCLIB_mtx_attr_t_align; } _PDCLIB_mtx_attr_t; +#if defined( __arm__ ) || defined( __ARM_NEON ) +typedef union { unsigned char _PDCLIB_thrd_attr_t_data[ 36 ]; long int _PDCLIB_thrd_attr_t_align; } _PDCLIB_thrd_attr_t; +#else +typedef union { unsigned char _PDCLIB_thrd_attr_t_data[ 56 ]; long int _PDCLIB_thrd_attr_t_align; } _PDCLIB_thrd_attr_t; +#endif +/* Static initialization of recursive mutex. */ +#if defined( __arm__ ) || defined( __ARM_NEON ) +#define _PDCLIB_MTX_RECURSIVE_INIT { {\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } +/* Static initialization of plain / timeout mutex (identical with pthread). */ +#define _PDCLIB_MTX_PLAIN_INIT { {\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } +#else +#define _PDCLIB_MTX_RECURSIVE_INIT { {\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } +/* Static initialization of plain / timeout mutex (identical with pthread). */ +#define _PDCLIB_MTX_PLAIN_INIT { {\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } +#endif + +/* Termux defines atexit in crtbegin_so.o leading to a multiple definition */ +/* error from the linker. This is a crude workaround, which does NOT fix */ +/* various run-time issues on Termux likely also related to crt linkage. But */ +/* at least things compile OK, and SOME tests can be run. */ +#if defined( __ARM_NEON ) +#define atexit _PDCLIB_atexit +#endif + +#endif diff --git a/libc/include/pdclib/_PDCLIB_defguard.h b/libc/include/pdclib/_PDCLIB_defguard.h new file mode 100644 index 0000000..cd51abe --- /dev/null +++ b/libc/include/pdclib/_PDCLIB_defguard.h @@ -0,0 +1,29 @@ +/* Definition guard <_PDCLIB_defguard.h> + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_DEFGUARD_H +#define _PDCLIB_DEFGUARD_H _PDCLIB_DEFGUARD_H + +#if defined( __ANDROID__ ) +/* typedef sigset_t */ +#include "bits/signal_types.h" +#endif + +/* Linux defines its own version of struct timespec (from ) in + some internal header (depending on clib implementation), which leads + to problems when accessing e.g. sys/time.h (type redefinition). + The solution is to set the Linux header's include guard (to avoid + Linux' definition), and to include PDCLib's to define the + type unambiguously. +*/ + +#define _TIMESPEC_DEFINED +#define _SYS__TIMESPEC_H_ +#define _STRUCT_TIMESPEC + +#include + +#endif diff --git a/libc/include/pdclib/_PDCLIB_glue.h b/libc/include/pdclib/_PDCLIB_glue.h new file mode 100644 index 0000000..3e59302 --- /dev/null +++ b/libc/include/pdclib/_PDCLIB_glue.h @@ -0,0 +1,93 @@ +/* OS glue functions declaration <_PDCLIB_glue.h> + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_GLUE_H +#define _PDCLIB_GLUE_H _PDCLIB_GLUE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_internal.h" + +/* -------------------------------------------------------------------------- */ +/* OS "glue", part 2 */ +/* These are the functions you will have to touch, as they are where PDCLib */ +/* interfaces with the operating system. */ +/* They operate on data types partially defined by _PDCLIB_config.h. */ +/* -------------------------------------------------------------------------- */ + +/* stdlib.h */ + +/* A system call that terminates the calling process, returning a given status + to the environment. +*/ +_PDCLIB_LOCAL _PDCLIB_Noreturn void _PDCLIB_Exit( int status ) _PDCLIB_NORETURN; + + +/* stdio.h */ + +/* A system call that opens a file identified by name in a given mode. Return + a file descriptor uniquely identifying that file. + (The mode is the return value of the _PDCLIB_filemode() function.) +*/ +_PDCLIB_LOCAL _PDCLIB_fd_t _PDCLIB_open( const char * const filename, unsigned int mode ); + +/* A system call that writes a stream's buffer. + Returns 0 on success, EOF on write error. + Sets stream error flags and errno appropriately on error. +*/ +_PDCLIB_LOCAL int _PDCLIB_flushbuffer( struct _PDCLIB_file_t * stream ); + +/* A system call that fills a stream's buffer. + Returns 0 on success, EOF on read error / EOF. + Sets stream EOF / error flags and errno appropriately on error. +*/ +_PDCLIB_LOCAL int _PDCLIB_fillbuffer( struct _PDCLIB_file_t * stream ); + +/* A system call that repositions within a file. Returns new offset on success, + -1 / errno on error. +*/ +_PDCLIB_LOCAL _PDCLIB_int_least64_t _PDCLIB_seek( struct _PDCLIB_file_t * stream, _PDCLIB_int_least64_t offset, int whence ); + +/* A system call that closes a file identified by given file descriptor. Return + zero on success, non-zero otherwise. +*/ +_PDCLIB_LOCAL int _PDCLIB_close( _PDCLIB_fd_t fd ); + +/* A system call that changes the mode of a given stream to that passed as + argument (the argument being the value returned by _PDCLIB_filemode()), + *without* closing the stream. See comments in example implementation + for details. Return zero if the requested mode change is not supported + for this stream and freopen() should try to close and reopen the stream; + return INT_MIN if the change is not supported and freopen() should close + and NOT try to close / reopen (i.e., fail). Return any other value on + success. +*/ +_PDCLIB_LOCAL int _PDCLIB_changemode( struct _PDCLIB_file_t * stream, unsigned int mode ); + +/* A system call that returns a canonicalized absolute filename in + dynamically allocated memory, or NULL if the file does not exist. +*/ +_PDCLIB_LOCAL char * _PDCLIB_realpath( const char * path ); + +/* A system call that removes a file. Return zero on success, non-zero + otherwise. +*/ +_PDCLIB_LOCAL int _PDCLIB_remove( const char * pathname ); + +/* A system call that renames a file from given old name to given new name. + Return zero on success, non-zero otherwise. In case of failure, the file + must still be accessible by old name. Any handling of open files etc. is + done by standard rename() already. +*/ +_PDCLIB_LOCAL int _PDCLIB_rename( const char * oldpath, const char * newpath ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/pdclib/_PDCLIB_internal.h b/libc/include/pdclib/_PDCLIB_internal.h new file mode 100644 index 0000000..20b1ae0 --- /dev/null +++ b/libc/include/pdclib/_PDCLIB_internal.h @@ -0,0 +1,776 @@ +/* PDCLib internal logic <_PDCLIB_internal.h> + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_INTERNAL_H +#define _PDCLIB_INTERNAL_H _PDCLIB_INTERNAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* -------------------------------------------------------------------------- */ +/* You should not have to edit anything in this file; if you DO have to, it */ +/* would be considered a bug / missing feature: notify the author(s). */ +/* -------------------------------------------------------------------------- */ + +#include "pdclib/_PDCLIB_config.h" + +/* -------------------------------------------------------------------------- */ +/* Standard Version */ +/* -------------------------------------------------------------------------- */ + +/* Many a compiler gets this wrong, so you might have to hardcode it instead. */ + +#if __STDC__ != 1 +#error Compiler does not define _ _STDC_ _ to 1 (not standard-compliant)! +#endif + +#ifndef __STDC_HOSTED__ +#error Compiler does not define _ _STDC_HOSTED_ _ (not standard-compliant)! +#elif __STDC_HOSTED__ != 0 && __STDC_HOSTED__ != 1 +#error Compiler does not define _ _STDC_HOSTED_ _ to 0 or 1 (not standard-compliant)! +#endif + +/* null pointer constant -- ((void *)0) in C, 0 in C++98, nullptr since C++11 */ +#ifdef __cplusplus +#if __cplusplus >= 201103L +#define _PDCLIB_NULL nullptr +#else +#define _PDCLIB_NULL 0 +#endif +#else +#define _PDCLIB_NULL ((void *)0) +#endif + +/* restrict / inline enabled for C99 onward only */ +#if defined( __cplusplus ) || ! defined( __STDC_VERSION ) || __STDC_VERSION__ < 199901L +#define _PDCLIB_restrict +#define _PDCLIB_inline +#else +#define _PDCLIB_restrict restrict +#define _PDCLIB_inline inline +#endif + +/* noreturn enabled for C11 onward only */ +#if defined( __cplusplus ) && __cplusplus >= 201103L +#define _PDCLIB_Noreturn [[noreturn]] +#else +#if defined( __STDC_VERSION__ ) >= 201112L +#define _PDCLIB_Noreturn _Noreturn +#else +#define _PDCLIB_Noreturn +#endif +#endif + +/* -------------------------------------------------------------------------- */ +/* Helper macros: */ +/* */ +/* (defined in _PDCLIB_config.h) */ +/* _PDCLIB_cc( x, y ) concatenates two preprocessor tokens without extending. */ +/* _PDCLIB_concat( x, y ) concatenates two preprocessor tokens with extending */ +/* */ +/* (defined below) */ +/* _PDCLIB_static_assert( e, m ) does a compile-time assertion of expression */ +/* e, with m as the failure message. */ +/* _PDCLIB_symbol2string( x ) turn symbol into string literal (by adding ""). */ +/* _PDCLIB_value2string( x ) expands a preprocessor token and turns it into a */ +/* string literal (by adding ""). */ +/* _PDCLIB_TYPE_SIGNED( type ) resolves to true if type is signed. */ +/* _PDCLIB_LOCK( mtx ) lock a mutex if library has threads support. */ +/* _PDCLIB_UNLOCK( mtx ) unlock a mutex if library has threads support. */ +/* _PDCLIB_CONSTRAINT_VIOLATION( e ) expand errno number e to parameter list */ +/* fit for Annex K constraint violation */ +/* handler. */ +/* -------------------------------------------------------------------------- */ + +#define _PDCLIB_static_assert( e, m ) enum { _PDCLIB_concat( _PDCLIB_assert_, __LINE__ ) = 1 / ( !!(e) ) } + +#define _PDCLIB_TYPE_SIGNED( type ) (((type) -1) < 0) + +#define _PDCLIB_symbol2string( x ) #x +#define _PDCLIB_value2string( x ) _PDCLIB_symbol2string( x ) + +#ifndef __STDC_NO_THREADS__ +#define _PDCLIB_LOCK( mtx ) mtx_lock( &mtx ) +#define _PDCLIB_UNLOCK( mtx ) mtx_unlock( &mtx ) +#else +#define _PDCLIB_LOCK( mtx ) +#define _PDCLIB_UNLOCK( mtx ) +#endif + +#define _PDCLIB_CONSTRAINT_VIOLATION( e ) _PDCLIB_lc_messages->errno_texts[e], NULL, e + +#define _PDCLIB_GETC( fh ) ( ( fh->ungetidx == 0 ) ? ( unsigned char )fh->buffer[ fh->bufidx++ ] : ( unsigned char )fh->ungetbuf[ --fh->ungetidx ] ) + +#define _PDCLIB_CHECKBUFFER( fh ) ( ( ( fh->bufidx == fh->bufend ) && ( fh->ungetidx == 0 ) ) ? _PDCLIB_fillbuffer( fh ) : 0 ) + +/* -------------------------------------------------------------------------- */ +/* Preparing the length modifiers used in . */ +/* -------------------------------------------------------------------------- */ + +/* We use the _MAX value as a proxy for the actual type here. That is crude + but the best we can do, cross-platform wise. + Identifying which type the leastN_t / fastN_t / intmax_t / intptr_t are + and providing the appropriate printf()/scanf() length modifier. +*/ + +#if _PDCLIB_INT_FAST8_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_FAST8_PREFIX hh +#elif _PDCLIB_INT_FAST8_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_FAST8_PREFIX h +#elif _PDCLIB_INT_FAST8_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_FAST8_PREFIX +#elif _PDCLIB_INT_FAST8_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_FAST8_PREFIX l +#elif _PDCLIB_INT_FAST8_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_FAST8_PREFIX ll +#else +#error No matching native type for int_fast8_t. Please check your setup. +#endif + +#if _PDCLIB_INT_FAST16_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_FAST16_PREFIX hh +#elif _PDCLIB_INT_FAST16_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_FAST16_PREFIX h +#elif _PDCLIB_INT_FAST16_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_FAST16_PREFIX +#elif _PDCLIB_INT_FAST16_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_FAST16_PREFIX l +#elif _PDCLIB_INT_FAST16_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_FAST16_PREFIX ll +#else +#error No matching native type for int_fast16_t. Please check your setup. +#endif + +#if _PDCLIB_INT_FAST32_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_FAST32_PREFIX hh +#elif _PDCLIB_INT_FAST32_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_FAST32_PREFIX h +#elif _PDCLIB_INT_FAST32_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_FAST32_PREFIX +#elif _PDCLIB_INT_FAST32_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_FAST32_PREFIX l +#elif _PDCLIB_INT_FAST32_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_FAST32_PREFIX ll +#else +#error No matching native type for int_fast32_t. Please check your setup. +#endif + +#if _PDCLIB_INT_FAST64_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_FAST64_PREFIX hh +#elif _PDCLIB_INT_FAST64_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_FAST64_PREFIX h +#elif _PDCLIB_INT_FAST64_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_FAST64_PREFIX +#elif _PDCLIB_INT_FAST64_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_FAST64_PREFIX l +#elif _PDCLIB_INT_FAST64_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_FAST64_PREFIX ll +#else +#error No matching native type for int_fast64_t. Please check your setup. +#endif + +/* Many of the combinations below can very likely be ruled out logically. + All combinations are still listed for simplicity's sake (and to not fall + into the trap of false assumptions). +*/ + +#if _PDCLIB_INT_LEAST8_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_LEAST8_PREFIX hh +#elif _PDCLIB_INT_LEAST8_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_LEAST8_PREFIX h +#elif _PDCLIB_INT_LEAST8_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_LEAST8_PREFIX +#elif _PDCLIB_INT_LEAST8_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_LEAST8_PREFIX l +#elif _PDCLIB_INT_LEAST8_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_LEAST8_PREFIX ll +#else +#error No matching native type for int_least8_t. Please check your setup. +#endif + +#if _PDCLIB_INT_LEAST16_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_LEAST16_PREFIX hh +#elif _PDCLIB_INT_LEAST16_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_LEAST16_PREFIX h +#elif _PDCLIB_INT_LEAST16_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_LEAST16_PREFIX +#elif _PDCLIB_INT_LEAST16_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_LEAST16_PREFIX l +#elif _PDCLIB_INT_LEAST16_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_LEAST16_PREFIX ll +#else +#error No matching native type for int_least16_t. Please check your setup. +#endif + +#if _PDCLIB_INT_LEAST32_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_LEAST32_PREFIX hh +#elif _PDCLIB_INT_LEAST32_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_LEAST32_PREFIX h +#elif _PDCLIB_INT_LEAST32_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_LEAST32_PREFIX +#elif _PDCLIB_INT_LEAST32_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_LEAST32_PREFIX l +#elif _PDCLIB_INT_LEAST32_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_LEAST32_PREFIX ll +#else +#error No matching native type for int_least32_t. Please check your setup. +#endif + +#if _PDCLIB_INT_LEAST64_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_LEAST64_PREFIX hh +#elif _PDCLIB_INT_LEAST64_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_LEAST64_PREFIX h +#elif _PDCLIB_INT_LEAST64_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_LEAST64_PREFIX +#elif _PDCLIB_INT_LEAST64_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_LEAST64_PREFIX l +#elif _PDCLIB_INT_LEAST64_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_LEAST64_PREFIX ll +#else +#error No matching native type for int_least64_t. Please check your setup. +#endif + +#if _PDCLIB_INTMAX_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INTMAX_PREFIX hh +#elif _PDCLIB_INTMAX_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INTMAX_PREFIX h +#elif _PDCLIB_INTMAX_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INTMAX_PREFIX +#elif _PDCLIB_INTMAX_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INTMAX_PREFIX l +#elif _PDCLIB_INTMAX_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INTMAX_PREFIX ll +#else +#error No matching native type for intmax_t. Please check your setup. +#endif + +#if _PDCLIB_INTPTR_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INTPTR_PREFIX hh +#elif _PDCLIB_INTPTR_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INTPTR_PREFIX h +#elif _PDCLIB_INTPTR_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INTPTR_PREFIX +#elif _PDCLIB_INTPTR_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INTPTR_PREFIX l +#elif _PDCLIB_INTPTR_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INTPTR_PREFIX ll +#else +#error No matching native type for intptr_t. Please check your setup. +#endif + +/* We might not have a type definition for sig_atomic_t at this point. The */ +/* clang compiler does not provide an appropriate predefine for it. So if we */ +/* do not have _PDCLIB_sig_atomic_t, identify the type trough its MAX value. */ + +#ifndef _PDCLIB_sig_atomic_t + +#if _PDCLIB_SIG_ATOMIC_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_sig_atomic_t char +#elif _PDCLIB_SIG_ATOMIC_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_sig_atomic_t short +#elif _PDCLIB_SIG_ATOMIC_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_sig_atomic_t int +#elif _PDCLIB_SIG_ATOMIC_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_sig_atomic_t long +#elif _PDCLIB_SIG_ATOMIC_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_sig_atomic_t long long +#else +#error No matching native type for sig_atomic_t. Please check your setup. +#endif + +#endif + +/* -------------------------------------------------------------------------- */ +/* Various internals */ +/* -------------------------------------------------------------------------- */ + +/* Flags for representing mode (see fopen()). Note these must fit the same + status field as the _IO?BF flags in and the internal flags below. +*/ +#define _PDCLIB_FREAD (1u<<3) +#define _PDCLIB_FWRITE (1u<<4) +#define _PDCLIB_FAPPEND (1u<<5) +#define _PDCLIB_FRW (1u<<6) +#define _PDCLIB_FBIN (1u<<7) + +/* Internal flags, made to fit the same status field as the flags above. */ +/* -------------------------------------------------------------------------- */ +/* free() the buffer memory on closing (setvbuf()) */ +#define _PDCLIB_FREEBUFFER (1u<<8) +/* stream has encountered error / EOF */ +#define _PDCLIB_ERRORFLAG (1u<<9) +#define _PDCLIB_EOFFLAG (1u<<10) +/* stream is wide-oriented */ +#define _PDCLIB_WIDESTREAM (1u<<11) +/* stream is byte-oriented */ +#define _PDCLIB_BYTESTREAM (1u<<12) +/* file associated with stream should be remove()d on closing (tmpfile()) */ +#define _PDCLIB_DELONCLOSE (1u<<13) + +/* Position / status structure for getpos() / fsetpos(). */ +struct _PDCLIB_fpos_t +{ + _PDCLIB_uint_least64_t offset; /* File position offset */ + int status; /* Multibyte parsing state (unused, reserved) */ +}; + +/* FILE structure */ +struct _PDCLIB_file_t +{ + _PDCLIB_fd_t handle; /* OS file handle */ + char * buffer; /* Pointer to buffer memory */ + _PDCLIB_size_t bufsize; /* Size of buffer */ + _PDCLIB_size_t bufidx; /* Index of current position in buffer */ + _PDCLIB_size_t bufend; /* Index of last pre-read character in buffer */ + struct _PDCLIB_fpos_t pos; /* Offset and multibyte parsing state */ + _PDCLIB_size_t ungetidx; /* Number of ungetc()'ed characters */ + unsigned char ungetbuf[_PDCLIB_UNGETCBUFSIZE]; /* ungetc() buffer */ + unsigned int status; /* Status flags; see above */ + /* multibyte parsing status to be added later */ +#ifndef __STDC_NO_THREADS__ + _PDCLIB_mtx_t mtx; /* Multithreading safety */ +#endif + char * filename; /* Name the current stream has been opened with */ + struct _PDCLIB_file_t * next; /* Pointer to next struct (internal) */ +}; + +/* -------------------------------------------------------------------------- */ +/* Internal data types */ +/* -------------------------------------------------------------------------- */ + +/* Structure required by both atexit() and exit() for handling atexit functions */ +struct _PDCLIB_exitfunc_t +{ + struct _PDCLIB_exitfunc_t * next; + void ( *func )( void ); +}; + +/* Status structure required by _PDCLIB_print(). */ +struct _PDCLIB_status_t +{ + int base; /* base to which the value shall be converted */ + _PDCLIB_int_fast32_t flags; /* flags and length modifiers */ + _PDCLIB_size_t n; /* print: maximum characters to be written */ + /* scan: number matched conversion specifiers */ + _PDCLIB_size_t i; /* number of characters read/written */ + _PDCLIB_size_t current;/* chars read/written in the CURRENT conversion */ + char * s; /* *sprintf(): target buffer */ + /* *sscanf(): source string */ + _PDCLIB_size_t width; /* specified field width */ + int prec; /* specified field precision */ + struct _PDCLIB_file_t * stream; /* *fprintf() / *fscanf() stream */ + _PDCLIB_va_list arg; /* argument stack */ +}; + +/* -------------------------------------------------------------------------- */ +/* Declaration of helper functions (implemented in functions/_PDCLIB). */ +/* -------------------------------------------------------------------------- */ + +/* This is the main function called by atoi(), atol() and atoll(). */ +_PDCLIB_LOCAL _PDCLIB_intmax_t _PDCLIB_atomax( const char * s ); + +/* Two helper functions used by strtol(), strtoul() and long long variants. */ +_PDCLIB_LOCAL const char * _PDCLIB_strtox_prelim( const char * p, char * sign, int * base ); +_PDCLIB_LOCAL _PDCLIB_uintmax_t _PDCLIB_strtox_main( const char ** p, unsigned int base, _PDCLIB_uintmax_t error, _PDCLIB_uintmax_t limval, int limdigit, char * sign ); + +/* A helper function used by strtof(), strtod(), and strtold(). */ +_PDCLIB_LOCAL void _PDCLIB_strtod_scan( const char * s, const char ** dec, const char ** frac, const char ** exp, int base ); + +/* Digits arrays used by various integer conversion functions */ +extern const char _PDCLIB_digits[]; +extern const char _PDCLIB_Xdigits[]; + +/* The worker for all printf() type of functions. The pointer spec should point + to the introducing '%' of a conversion specifier. The status structure is to + be that of the current printf() function, of which the members n, s, stream + and arg will be preserved; i will be updated; and all others will be trashed + by the function. + Returns a pointer to the first character not parsed as conversion specifier. +*/ +_PDCLIB_LOCAL const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status ); + +/* The worker for all scanf() type of functions. The pointer spec should point + to the introducing '%' of a conversion specifier. The status structure is to + be that of the current scanf() function, of which the member stream will be + preserved; n, i, and s will be updated; and all others will be trashed by + the function. + Returns a pointer to the first character not parsed as conversion specifier, + or NULL in case of error. + FIXME: Should distinguish between matching and input error +*/ +_PDCLIB_LOCAL const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status ); + +/* Parsing any fopen() style filemode string into a number of flags. */ +_PDCLIB_LOCAL unsigned int _PDCLIB_filemode( const char * mode ); + +/* Initialize a FILE structure. If the parameter is NULL, a new FILE structure + is malloc'ed. Returns a pointer to the stream if successful, NULL otherwise. +*/ +_PDCLIB_LOCAL struct _PDCLIB_file_t * _PDCLIB_init_file_t( struct _PDCLIB_file_t * stream ); + +/* Sanity checking and preparing of read buffer, should be called first thing + by any stdio read-data function. + Returns 0 on success, EOF on error. + On error, EOF / error flags and errno are set appropriately. +*/ +_PDCLIB_LOCAL int _PDCLIB_prepread( struct _PDCLIB_file_t * stream ); + +/* Sanity checking, should be called first thing by any stdio write-data + function. + Returns 0 on success, EOF on error. + On error, error flags and errno are set appropriately. +*/ +_PDCLIB_LOCAL int _PDCLIB_prepwrite( struct _PDCLIB_file_t * stream ); + +/* Closing all streams on program exit */ +_PDCLIB_LOCAL void _PDCLIB_closeall( void ); + +/* Check if a given year is a leap year. Parameter is offset to 1900. */ +_PDCLIB_LOCAL int _PDCLIB_is_leap( int year_offset ); + +/* Read a specified number of lines from a file stream; return a pointer to + allocated memory holding the lines (newlines replaced with zero terminators) + or NULL in case of error. +*/ +_PDCLIB_LOCAL char * _PDCLIB_load_lines( struct _PDCLIB_file_t * stream, _PDCLIB_size_t lines ); + +/* Returns the (locale dependent) error message associated with the argument + errno value. +*/ +_PDCLIB_LOCAL char * _PDCLIB_geterrtext( int errnum ); + +/* Returns non-zero if the given stream is on the internal list of open files, + zero otherwise. Sets the second paramenter (if not NULL) to the previous + stream on the list (or NULL if the given stream is the first on the list). + This function does not lock _PDCLIB_filelist_mtx, this needs to be done by + the calling function (_PDCLIB_getstream() or freopen()). +*/ +_PDCLIB_LOCAL int _PDCLIB_isstream( struct _PDCLIB_file_t * stream, struct _PDCLIB_file_t ** previous ); + +/* Removes the given stream from the internal list of open files. Returns zero + if successful, non-zero otherwise. In case of error, sets errno to EBADF. + This function does not lock _PDCLIB_filelist_mtx, this needs to be done by + the calling function (fclose()). +*/ +_PDCLIB_LOCAL int _PDCLIB_getstream( struct _PDCLIB_file_t * stream ); + +/* Backend for strtok and strtok_s (plus potential extensions like strtok_r). */ +_PDCLIB_LOCAL char * _PDCLIB_strtok( char * _PDCLIB_restrict s1, _PDCLIB_size_t * _PDCLIB_restrict s1max, const char * _PDCLIB_restrict s2, char ** _PDCLIB_restrict ptr ); + +/* -------------------------------------------------------------------------- */ +/* errno */ +/* -------------------------------------------------------------------------- */ + +/* A mechanism for delayed evaluation. + If PDCLib would call its error number "errno" directly, there would be no way + to catch its value from underlying system calls that also use it (i.e., POSIX + operating systems). That is why we use an internal name, providing a means to + access it through . +*/ +_PDCLIB_PUBLIC int * _PDCLIB_errno_func( void ); + +/* -------------------------------------------------------------------------- */ +/* support */ +/* -------------------------------------------------------------------------- */ + +#define _PDCLIB_LC_ALL 0 +#define _PDCLIB_LC_COLLATE 1 +#define _PDCLIB_LC_CTYPE 2 +#define _PDCLIB_LC_MONETARY 3 +#define _PDCLIB_LC_NUMERIC 4 +#define _PDCLIB_LC_TIME 5 +#define _PDCLIB_LC_MESSAGES 6 +#define _PDCLIB_LC_COUNT 7 + +#define _PDCLIB_CTYPE_ALPHA 1 +#define _PDCLIB_CTYPE_BLANK 2 +#define _PDCLIB_CTYPE_CNTRL 4 +#define _PDCLIB_CTYPE_GRAPH 8 +#define _PDCLIB_CTYPE_PUNCT 16 +#define _PDCLIB_CTYPE_SPACE 32 +#define _PDCLIB_CTYPE_LOWER 64 +#define _PDCLIB_CTYPE_UPPER 128 + +#define _PDCLIB_CHARSET_SIZE ( 1 << _PDCLIB_CHAR_BIT ) + +struct _PDCLIB_lc_lconv_numeric_t +{ + char * decimal_point; + char * thousands_sep; + char * grouping; +}; + +struct _PDCLIB_lc_lconv_monetary_t +{ + char * mon_decimal_point; + char * mon_thousands_sep; + char * mon_grouping; + char * positive_sign; + char * negative_sign; + char * currency_symbol; + char * int_curr_symbol; + char frac_digits; + char p_cs_precedes; + char n_cs_precedes; + char p_sep_by_space; + char n_sep_by_space; + char p_sign_posn; + char n_sign_posn; + char int_frac_digits; + char int_p_cs_precedes; + char int_n_cs_precedes; + char int_p_sep_by_space; + char int_n_sep_by_space; + char int_p_sign_posn; + char int_n_sign_posn; +}; + +struct _PDCLIB_lc_numeric_monetary_t +{ + struct lconv * lconv; + int numeric_alloced; + int monetary_alloced; +}; + +extern struct _PDCLIB_lc_numeric_monetary_t _PDCLIB_lc_numeric_monetary; + +struct _PDCLIB_lc_collate_t +{ + int alloced; + /* 1..3 code points */ + /* 1..8, 18 collation elements of 3 16-bit integers */ +}; + +extern struct _PDCLIB_lc_collate_t _PDCLIB_lc_collate_C; +extern struct _PDCLIB_lc_collate_t * _PDCLIB_lc_collate; + +/* One entry to the _PDCLIB_lc_ctype_t.entry data table */ +struct _PDCLIB_lc_ctype_entry_t +{ + _PDCLIB_uint_least16_t flags; /* Whether a character is of a given CTYPE */ + unsigned char upper; /* Result for toupper() */ + unsigned char lower; /* Result for tolower() */ +}; + +struct _PDCLIB_lc_ctype_t +{ + int alloced; /* .entry dynamically allocated? */ + int digits_low; /* Where decimal digits start */ + int digits_high; /* Where decimal digits end */ + int Xdigits_low; /* Where A..F start */ + int Xdigits_high; /* Where A..F end */ + int xdigits_low; /* Where a..f start */ + int xdigits_high; /* Where a..f end */ + struct _PDCLIB_lc_ctype_entry_t * entry; /* The data table */ +}; + +extern struct _PDCLIB_lc_ctype_t _PDCLIB_lc_ctype_C; +extern struct _PDCLIB_lc_ctype_t * _PDCLIB_lc_ctype; + +struct _PDCLIB_lc_messages_t +{ + int alloced; + char * errno_texts[_PDCLIB_ERRNO_MAX]; /* strerror() / perror() */ +}; + +extern struct _PDCLIB_lc_messages_t _PDCLIB_lc_messages_C; +extern struct _PDCLIB_lc_messages_t * _PDCLIB_lc_messages; + +struct _PDCLIB_lc_time_t +{ + int alloced; + char * month_name_abbr[12]; /* month names, abbreviated */ + char * month_name_full[12]; /* month names, full */ + char * day_name_abbr[7]; /* weekday names, abbreviated */ + char * day_name_full[7]; /* weekday names, full */ + char * date_time_format; /* date / time format for strftime( "%c" ) */ + char * time_format_12h; /* 12-hour time format for strftime( "%r" ) */ + char * date_format; /* date format for strftime( "%x" ) */ + char * time_format; /* time format for strftime( "%X" ) */ + char * am_pm[2]; /* AM / PM designation */ +}; + +extern struct _PDCLIB_lc_time_t _PDCLIB_lc_time_C; +extern struct _PDCLIB_lc_time_t * _PDCLIB_lc_time; + +_PDCLIB_LOCAL struct _PDCLIB_lc_lconv_numeric_t * _PDCLIB_load_lc_numeric( const char * path, const char * locale ); +_PDCLIB_LOCAL struct _PDCLIB_lc_lconv_monetary_t * _PDCLIB_load_lc_monetary( const char * path, const char * locale ); +_PDCLIB_LOCAL struct _PDCLIB_lc_collate_t * _PDCLIB_load_lc_collate( const char * path, const char * locale ); +_PDCLIB_LOCAL struct _PDCLIB_lc_ctype_t * _PDCLIB_load_lc_ctype( const char * path, const char * locale ); +_PDCLIB_LOCAL struct _PDCLIB_lc_time_t * _PDCLIB_load_lc_time( const char * path, const char * locale ); +_PDCLIB_LOCAL struct _PDCLIB_lc_messages_t * _PDCLIB_load_lc_messages( const char * path, const char * locale ); + +/* -------------------------------------------------------------------------- */ +/* _PDCLIB_bigint_t support (required for floating point conversions) */ +/* -------------------------------------------------------------------------- */ + +/* Must be divisible by 32. */ +#define _PDCLIB_BIGINT_BITS 1024 + +#if _PDCLIB_BIGINT_DIGIT_BITS == 32 +#define _PDCLIB_BIGINT_DIGIT_MAX UINT32_C( 0xFFFFFFFF ) +#define _PDCLIB_BIGINT_BASE ( UINT64_C(1) << _PDCLIB_BIGINT_DIGIT_BITS ) +typedef _PDCLIB_uint_least32_t _PDCLIB_bigint_digit_t; +typedef _PDCLIB_uint_least64_t _PDCLIB_bigint_arith_t; +typedef _PDCLIB_int_least64_t _PDCLIB_bigint_sarith_t; +#elif _PDCLIB_BIGINT_DIGIT_BITS == 16 +#define _PDCLIB_BIGINT_DIGIT_MAX UINT16_C( 0xFFFF ) +#define _PDCLIB_BIGINT_BASE ( UINT32_C(1) << _PDCLIB_BIGINT_DIGIT_BITS ) +typedef _PDCLIB_uint_least16_t _PDCLIB_bigint_digit_t; +typedef _PDCLIB_uint_least32_t _PDCLIB_bigint_arith_t; +typedef _PDCLIB_int_least32_t _PDCLIB_bigint_sarith_t; +#elif _PDCLIB_BIGINT_DIGIT_BITS == 8 +/* For testing purposes only. */ +#define _PDCLIB_BIGINT_DIGIT_MAX UINT8_C( 0xFF ) +#define _PDCLIB_BIGINT_BASE ( UINT16_C(1) << _PDCLIB_BIGINT_DIGIT_BITS ) +typedef _PDCLIB_uint_least8_t _PDCLIB_bigint_digit_t; +typedef _PDCLIB_uint_least16_t _PDCLIB_bigint_arith_t; +typedef _PDCLIB_int_least16_t _PDCLIB_bigint_sarith_t; +#else +#error Only 16 or 32 supported for _PDCLIB_BIGINT_DIGIT_BITS. +#endif + +/* How many "digits" a _PDCLIB_bigint_t holds. */ +#define _PDCLIB_BIGINT_DIGITS _PDCLIB_BIGINT_BITS / _PDCLIB_BIGINT_DIGIT_BITS + +/* Maximum number of characters needed for _PDCLIB_bigint_tostring() */ +#define _PDCLIB_BIGINT_CHARS ( _PDCLIB_BIGINT_BITS / 4 + _PDCLIB_BIGINT_DIGITS + 2 ) + +/* Type */ +/* ---- */ + +typedef struct +{ + /* Least significant digit first */ + _PDCLIB_bigint_digit_t data[ _PDCLIB_BIGINT_DIGITS ]; + /* Number of digits used; zero value == zero size */ + _PDCLIB_size_t size; +} _PDCLIB_bigint_t; + +/* Initializer */ +/* ----------- */ + +/* Sets a bigint to pow2( n ) */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint2( _PDCLIB_bigint_t * bigint, unsigned n ); + +/* Sets a bigint to pow10( n ) */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint10( _PDCLIB_bigint_t * bigint, unsigned n ); + +/* Sets a bigint from a 32bit input value. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint32( _PDCLIB_bigint_t * bigint, _PDCLIB_uint_least32_t value ); + +/* Sets a bigint from two 32bit input values. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint64( _PDCLIB_bigint_t * bigint, _PDCLIB_uint_least32_t high, _PDCLIB_uint_least32_t low ); + +/* Sets a bigint from another bigint. (Copies only value->size digits, so it is + faster than a POD copy of a _PDCLIB_bigint_t in most cases.) +*/ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint( _PDCLIB_bigint_t * _PDCLIB_restrict bigint, _PDCLIB_bigint_t const * _PDCLIB_restrict value ); + +/* Comparison, Output */ +/* ------------------ */ + +/* Compares two given bigint values. Returns 0 if lhs == rhs, a negative number + if lhs < rhs, and a positive number if lhs > rhs. +*/ +_PDCLIB_LOCAL int _PDCLIB_bigint_cmp( _PDCLIB_bigint_t const * _PDCLIB_restrict lhs, _PDCLIB_bigint_t const * _PDCLIB_restrict rhs ); + +/* Writes a hexadecimal representation of the given bigint into the given buffer. + Buffer should be at least _PDCLIB_BIGINT_CHARS in size. +*/ +_PDCLIB_LOCAL char * _PDCLIB_bigint_tostring( _PDCLIB_bigint_t const * _PDCLIB_restrict value, char * _PDCLIB_restrict buffer ); + +/* Operations (in-place) */ +/* --------------------- */ + +/* Adds to a given bigint another given bigint. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint_add( _PDCLIB_bigint_t * _PDCLIB_restrict lhs, _PDCLIB_bigint_t const * _PDCLIB_restrict rhs ); + +/* Substracts from a given bigint another given bigint. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint_sub( _PDCLIB_bigint_t * _PDCLIB_restrict lhs, _PDCLIB_bigint_t const * _PDCLIB_restrict rhs ); + +/* Multiplies a given bigint with a given 32bit value. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint_mul_dig( _PDCLIB_bigint_t * lhs, _PDCLIB_bigint_digit_t rhs ); + +/* Divides a given bigint by a given 32bit value. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint_div_dig( _PDCLIB_bigint_t * lhs, _PDCLIB_bigint_digit_t rhs ); + +/* Shifts a given bigint left by a given count of bits. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint_shl( _PDCLIB_bigint_t * lhs, unsigned rhs ); + +/* Operations (into new bigint) */ +/* ---------------------------- */ + +/* Multiplies a given bigint with another given bigint. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint_mul( _PDCLIB_bigint_t * _PDCLIB_restrict result, _PDCLIB_bigint_t const * _PDCLIB_restrict lhs, _PDCLIB_bigint_t const * _PDCLIB_restrict rhs ); + +/* Divides a given bigint by another given bigint. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint_div( _PDCLIB_bigint_t * _PDCLIB_restrict result, _PDCLIB_bigint_t const * _PDCLIB_restrict lhs, _PDCLIB_bigint_t const * _PDCLIB_restrict rhs ); + +/* Queries */ +/* ------- */ + +/* Returns the log2() of a given bigint */ +_PDCLIB_LOCAL unsigned _PDCLIB_bigint_log2( _PDCLIB_bigint_t const * bigint ); + +/* FP Conversions */ +/* -------------- */ + +/* Split a float into its integral components. + Returns 1 if value is negative, zero otherwise. +*/ +_PDCLIB_LOCAL int _PDCLIB_float_split( float value, unsigned * exponent, _PDCLIB_bigint_t * significand ); + +/* Split a double into its integral components. + Returns 1 if value is negative, zero otherwise. +*/ +_PDCLIB_LOCAL int _PDCLIB_double_split( double value, unsigned * exponent, _PDCLIB_bigint_t * significand ); + +/* Split a long double into its integral components. + Returns 1 if value is negative, zero otherwise. +*/ +_PDCLIB_LOCAL int _PDCLIB_long_double_split( long double value, unsigned * exponent, _PDCLIB_bigint_t * significand ); + +/* -------------------------------------------------------------------------- */ +/* Sanity checks */ +/* -------------------------------------------------------------------------- */ + +/* signed-ness of char */ +_PDCLIB_static_assert( _PDCLIB_CHAR_MIN == ((((char) -1) < 0) ? _PDCLIB_SCHAR_MIN : 0), "Compiler disagrees on signed-ness of 'char'." ); + +/* two's complement */ +#if _PDCLIB_TWOS_COMPLEMENT == 1 +#if _PDCLIB_CHAR_MIN < 0 +_PDCLIB_static_assert( ((char) ~ (char) 0 < 0), "Not two's complement on 'char'." ); +#endif +_PDCLIB_static_assert( ((short) ~ (short) 0 < 0), "Not two's complement on 'short'." ); +_PDCLIB_static_assert( ((int) ~ (int) 0 < 0), "Not two's complement on 'int'." ); +_PDCLIB_static_assert( ((long) ~ (long) 0 < 0), "Not two's complement on 'long'." ); +_PDCLIB_static_assert( ((long long) ~ (long long) 0 < 0), "Not two's complement on 'long long'." ); +#endif + +/* size_t as the result of sizeof */ +_PDCLIB_static_assert( sizeof( sizeof( int ) ) == sizeof( _PDCLIB_size_t ), "Compiler disagrees on size_t." ); + +/* wchar_t as the type of wide character literals */ +_PDCLIB_static_assert( sizeof( _PDCLIB_wchar_t ) == sizeof( L'x' ), "Compiler disagrees on wchar_t." ); +#ifdef __cplusplus +_PDCLIB_static_assert( sizeof( _PDCLIB_wchar_t ) == sizeof( wchar_t ), "Compiler disagrees on wchar_t (C++)." ); +#endif + +/* intptr_t/uintptr_t being wide enough to store the value of a pointer */ +_PDCLIB_static_assert( sizeof( void * ) == sizeof( _PDCLIB_intptr_t ), "Compiler disagrees on intptr_t." ); +_PDCLIB_static_assert( sizeof( void * ) == sizeof( _PDCLIB_uintptr_t ), "Compiler disagrees on uintptr_t." ); + +/* ptrdiff_t as the result of pointer arithmetic */ +_PDCLIB_static_assert( sizeof( &_PDCLIB_digits[1] - &_PDCLIB_digits[0] ) == sizeof( _PDCLIB_ptrdiff_t ), "Compiler disagrees on ptrdiff_t." ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/pdclib/_PDCLIB_lib_ext1.h b/libc/include/pdclib/_PDCLIB_lib_ext1.h new file mode 100644 index 0000000..fe96bca --- /dev/null +++ b/libc/include/pdclib/_PDCLIB_lib_ext1.h @@ -0,0 +1,32 @@ +/* __STDC_WANT_LIB_EXT1__ redefinition guard + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef __STDC_WANT_LIB_EXT1__ + #ifdef __STDC_WANT_LIB_EXT1_PREVIOUS__ + #if __STDC_WANT_LIB_EXT1_PREVIOUS__ != -1 + #error __STDC_WANT_LIB_EXT1__ undefined when it was defined earlier. + #endif + #else + #define __STDC_WANT_LIB_EXT1_PREVIOUS__ -1 + #endif +#else + #if ( __STDC_WANT_LIB_EXT1__ + 0 ) == 0 && ( 0 - __STDC_WANT_LIB_EXT1__ - 1 ) == 1 + #error __STDC_WANT_LIB_EXT1__ defined but empty. Should be an integer value. + #endif + #ifdef __STDC_WANT_LIB_EXT1_PREVIOUS__ + #if ( __STDC_WANT_LIB_EXT1__ + 0 ) != __STDC_WANT_LIB_EXT1_PREVIOUS__ + #error __STDC_WANT_LIB_EXT1__ redefined from previous value. + #endif + #else + #if ( __STDC_WANT_LIB_EXT1__ + 0 ) == 0 + #define __STDC_WANT_LIB_EXT1_PREVIOUS__ 0 + #elif ( __STDC_WANT_LIB_EXT1__ + 0 ) == 1 + #define __STDC_WANT_LIB_EXT1_PREVIOUS__ 1 + #else + #error __STDC_WANT_LIB_EXT1__ set to value other than 0,1 -- undefined behavior + #endif + #endif +#endif diff --git a/libc/include/pdclib/_PDCLIB_print.h b/libc/include/pdclib/_PDCLIB_print.h new file mode 100644 index 0000000..da98c1a --- /dev/null +++ b/libc/include/pdclib/_PDCLIB_print.h @@ -0,0 +1,71 @@ +/* PDCLib printf logic <_PDCLIB_print.h> + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_PRINT_H +#define _PDCLIB_PRINT_H _PDCLIB_PRINT_H + +#include "pdclib/_PDCLIB_internal.h" + +#include +#include +#include + +/* This macro delivers a given character to either a memory buffer or a stream, + depending on the contents of 'status' (struct _PDCLIB_status_t). + x - the character to be delivered + i - pointer to number of characters already delivered in this call + n - pointer to maximum number of characters to be delivered in this call + s - the buffer into which the character shall be delivered +*/ +#define PUT( x ) \ + do { \ + int character = x; \ + if ( status->i < status->n ) { \ + if ( status->stream != NULL ) \ + putc( character, status->stream ); \ + else \ + status->s[status->i] = character; \ + } \ + ++(status->i); \ + } while ( 0 ) + + +/* Using an integer's bits as flags for both the conversion flags and length + modifiers. +*/ +#define E_minus (INT32_C(1)<<0) +#define E_plus (INT32_C(1)<<1) +#define E_alt (INT32_C(1)<<2) +#define E_space (INT32_C(1)<<3) +#define E_zero (INT32_C(1)<<4) +#define E_done (INT32_C(1)<<5) + +#define E_char (INT32_C(1)<<6) +#define E_short (INT32_C(1)<<7) +#define E_long (INT32_C(1)<<8) +#define E_llong (INT32_C(1)<<9) +#define E_intmax (INT32_C(1)<<10) +#define E_size (INT32_C(1)<<11) +#define E_ptrdiff (INT32_C(1)<<12) +#define E_pointer (INT32_C(1)<<13) + +#define E_double (INT32_C(1)<<14) +#define E_ldouble (INT32_C(1)<<15) + +#define E_decimal (INT32_C(1)<<18) +#define E_exponent (INT32_C(1)<<19) +#define E_generic (INT32_C(1)<<20) +#define E_hexa (INT32_C(1)<<21) + +#define E_lower (INT32_C(1)<<16) +#define E_unsigned (INT32_C(1)<<17) + +void _PDCLIB_print_integer( struct _PDCLIB_imaxdiv_t div, struct _PDCLIB_status_t * status ); +void _PDCLIB_print_string( const char * s, struct _PDCLIB_status_t * status ); +void _PDCLIB_print_double( double value, struct _PDCLIB_status_t * status ); +void _PDCLIB_print_ldouble( long double value, struct _PDCLIB_status_t * status ); + +#endif diff --git a/libc/include/pdclib/_PDCLIB_tzcode.h b/libc/include/pdclib/_PDCLIB_tzcode.h new file mode 100644 index 0000000..d12c4a7 --- /dev/null +++ b/libc/include/pdclib/_PDCLIB_tzcode.h @@ -0,0 +1,153 @@ +/* TZ Code declarations and definitions <_PDCLIB_tzcode.h> + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_TZCODE_H +#define _PDCLIB_TZCODE_H _PDCLIB_TZCODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include +#include + +/* Handy macros that are independent of tzfile implementation. */ +#define YEARSPERREPEAT 400 /* years before a Gregorian repeat */ + +#define SECSPERMIN 60 +#define MINSPERHOUR 60 +#define HOURSPERDAY 24 +#define DAYSPERWEEK 7 +#define DAYSPERNYEAR 365 +#define DAYSPERLYEAR 366 +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) +#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY) +#define MONSPERYEAR 12 + +#define AVGSECSPERYEAR 31556952L +#define SECSPERREPEAT ((int_fast64_t) YEARSPERREPEAT * (int_fast64_t) AVGSECSPERYEAR) +#define SECSPERREPEAT_BITS 34 /* ceil(log2(SECSPERREPEAT)) */ + +#define TM_SUNDAY 0 +#define TM_MONDAY 1 +#define TM_TUESDAY 2 +#define TM_WEDNESDAY 3 +#define TM_THURSDAY 4 +#define TM_FRIDAY 5 +#define TM_SATURDAY 6 + +#define TM_YEAR_BASE 1900 + +#define EPOCH_YEAR 1970 +#define EPOCH_WDAY TM_THURSDAY + +extern struct tm _PDCLIB_tm; +extern int lcl_is_set; + +static const char gmt[] = "GMT"; + +static const int mon_lengths[ 2 ][ MONSPERYEAR ] = +{ + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +}; + +static const int year_lengths[2] = +{ + DAYSPERNYEAR, DAYSPERLYEAR +}; + +/* time type information */ +struct ttinfo +{ + int_fast32_t utoff; /* UT offset in seconds */ + bool isdst; /* used to set tm_isdst */ + int desigidx; /* abbreviation list index */ + bool ttisstd; /* transition is std time */ + bool ttisut; /* transition is UT */ +}; + +/* leap second information */ +struct lsinfo +{ + time_t trans; /* transition time */ + int_fast64_t corr; /* correction to apply */ +}; + +#define BIGGEST( a, b ) (((a) > (b)) ? (a) : (b)) + +#ifndef TZ_MAX_TIMES +#define TZ_MAX_TIMES 2000 +#endif + +#ifndef TZ_MAX_TYPES +/* This must be at least 17 for Europe/Vilnius. */ +/* Limited by what (unsigned char)s can hold */ +#define TZ_MAX_TYPES 256 +#endif + +#ifndef TZ_MAX_CHARS +/* Maximum number of abbreviation characters */ +/* Limited by what (unsigned char)s can hold */ +#define TZ_MAX_CHARS 50 +#endif + +#ifndef TZ_MAX_LEAPS +/* Maximum number of leap second corrections */ +#define TZ_MAX_LEAPS 50 +#endif + +#ifdef TZNAME_MAX +#define MY_TZNAME_MAX TZNAME_MAX +#else +#define MY_TZNAME_MAX 255 +#endif + +struct state +{ + int leapcnt; + int timecnt; + int typecnt; + int charcnt; + bool goback; + bool goahead; + time_t ats[ TZ_MAX_TIMES ]; + unsigned char types[ TZ_MAX_TIMES ]; + struct ttinfo ttis[ TZ_MAX_TYPES ]; + char chars[ BIGGEST( BIGGEST( TZ_MAX_CHARS + 1, sizeof gmt ), ( 2 * ( MY_TZNAME_MAX + 1 ) ) ) ]; + struct lsinfo lsis[ TZ_MAX_LEAPS ]; + + /* The time type to use for early times or if no transitions. + It is always zero for recent tzdb releases. + It might be nonzero for data from tzdb 2018e or earlier. + */ + int defaulttype; +}; + +extern struct state _PDCLIB_lclmem; +extern struct state _PDCLIB_gmtmem; + +void _PDCLIB_gmtcheck(void); +struct tm * _PDCLIB_gmtsub( struct state const * sp, time_t const * timep, int_fast32_t offset, struct tm * tmp ); +bool _PDCLIB_increment_overflow( int * ip, int j ); +void _PDCLIB_init_ttinfo( struct ttinfo * s, int_fast32_t utoff, bool isdst, int desigidx ); +struct tm * _PDCLIB_localsub( struct state const * sp, time_t const * timep, int_fast32_t setname, struct tm * const tmp ); +struct tm * _PDCLIB_localtime_tzset( time_t const * timep, struct tm * tmp, bool setname ); +time_t _PDCLIB_mktime_tzname( struct state * sp, struct tm * tmp, bool setname ); +struct tm * _PDCLIB_timesub( const time_t * timep, int_fast32_t offset, const struct state * sp, struct tm * tmp ); +int _PDCLIB_tzload( char const * name, struct state * sp, bool doextend ); +bool _PDCLIB_tzparse(char const *, struct state *, bool); +void _PDCLIB_tzset_unlocked( void ); +void _PDCLIB_update_tzname_etc( struct state const * sp, struct ttinfo const * ttisp ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/signal.h b/libc/include/signal.h new file mode 100644 index 0000000..1f679ec --- /dev/null +++ b/libc/include/signal.h @@ -0,0 +1,91 @@ +/* Signal handling + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_SIGNAL_H +#define _PDCLIB_SIGNAL_H _PDCLIB_SIGNAL_H + +#include "pdclib/_PDCLIB_internal.h" + +/* Signals ------------------------------------------------------------------ */ + +/* A word on signals, to the people using PDCLib in their OS projects. + + The definitions of the C standard leave about everything that *could* be + useful to be "implementation defined". Without additional, non-standard + arrangements, it is not possible to turn them into a useful tool. + + This example implementation chose to "not generate any of these signals, + except as a result of explicit calls to the raise function", which is + allowed by the standard but of course does nothing for the usefulness of + . + + A useful signal handling would: + 1) make signal() a system call that registers the signal handler with the OS + 2) make raise() a system call triggering an OS signal to the running process + 3) make provisions that further signals of the same type are blocked until + the signal handler returns (optional for SIGILL) +*/ + +/* These are the values used by Linux. */ + +/* Abnormal termination / abort() */ +#define SIGABRT 6 +/* Arithmetic exception / division by zero / overflow */ +#define SIGFPE 8 +/* Illegal instruction */ +#define SIGILL 4 +/* Interactive attention signal */ +#define SIGINT 2 +/* Invalid memory access */ +#define SIGSEGV 11 +/* Termination request */ +#define SIGTERM 15 + +/* The following should be defined to pointer values that could NEVER point to + a valid signal handler function. (They are used as special arguments to + signal().) Again, these are the values used by Linux. +*/ +#define SIG_DFL (void (*)( int ))0 +#define SIG_ERR (void (*)( int ))-1 +#define SIG_IGN (void (*)( int ))1 + +typedef _PDCLIB_sig_atomic_t sig_atomic_t; + +/* Installs a signal handler "func" for the given signal. + A signal handler is a function that takes an integer as argument (the signal + number) and returns void. + + Note that a signal handler can do very little else than: + 1) assign a value to a static object of type "volatile sig_atomic_t", + 2) call signal() with the value of sig equal to the signal received, + 3) call _Exit(), + 4) call abort(). + Virtually everything else is undefind. + + The signal() function returns the previous installed signal handler, which + at program start may be SIG_DFL or SIG_ILL. (This implementation uses + SIG_DFL for all handlers.) If the request cannot be honored, SIG_ERR is + returned and errno is set to an unspecified positive value. +*/ +_PDCLIB_PUBLIC void ( *signal( int sig, void ( *func )( int ) ) )( int ); + +/* Raises the given signal (executing the registered signal handler with the + given signal number as parameter). + This implementation does not prevent further signals of the same time from + occuring, but executes signal( sig, SIG_DFL ) before entering the signal + handler (i.e., a second signal before the signal handler re-registers itself + or SIG_IGN will end the program). + Returns zero if successful, nonzero otherwise. */ +_PDCLIB_PUBLIC int raise( int sig ); + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_SIGNAL_H +#include _PDCLIB_EXTEND_SIGNAL_H +#endif + +#endif diff --git a/libc/include/stdalign.h b/libc/include/stdalign.h new file mode 100644 index 0000000..685b052 --- /dev/null +++ b/libc/include/stdalign.h @@ -0,0 +1,26 @@ +/* Alignment + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDALIGN_H +#define _PDCLIB_ALIGN_H _PDCLIB_ALIGN_H + +#ifndef __cplusplus +#define alignas _Alignas +#define alignof _Alignof +#endif + +#define __alignas_is_defined 1 +#define __alignof_is_defined 1 + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDALIGN_H +#include _PDCLIB_EXTEND_STDALIGN_H +#endif + +#endif + diff --git a/libc/include/stdarg.h b/libc/include/stdarg.h new file mode 100644 index 0000000..7741b70 --- /dev/null +++ b/libc/include/stdarg.h @@ -0,0 +1,34 @@ +/* Variable arguments + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDARG_H +#define _PDCLIB_STDARG_H _PDCLIB_STDARG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_config.h" + +typedef _PDCLIB_va_list va_list; + +#define va_arg( ap, type ) _PDCLIB_va_arg( ap, type ) +#define va_copy( dest, src ) _PDCLIB_va_copy( dest, src ) +#define va_end( ap ) _PDCLIB_va_end( ap ) +#define va_start( ap, parmN ) _PDCLIB_va_start( ap, parmN ) + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDARG_H +#include _PDCLIB_EXTEND_STDARG_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/stdbool.h b/libc/include/stdbool.h new file mode 100644 index 0000000..e901f63 --- /dev/null +++ b/libc/include/stdbool.h @@ -0,0 +1,24 @@ +/* Boolean type and values + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDBOOL_H +#define _PDCLIB_STDBOOL_H _PDCLIB_STDBOOL_H + +#ifndef __cplusplus +#define bool _Bool +#define true 1 +#define false 0 +#endif +#define __bool_true_false_are_defined 1 + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDBOOL_H +#include _PDCLIB_EXTEND_STDBOOL_H +#endif + +#endif diff --git a/libc/include/stddef.h b/libc/include/stddef.h new file mode 100644 index 0000000..7d03d07 --- /dev/null +++ b/libc/include/stddef.h @@ -0,0 +1,55 @@ +/* Common definitions + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDDEF_H +#define _PDCLIB_STDDEF_H _PDCLIB_STDDEF_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_lib_ext1.h" +#include "pdclib/_PDCLIB_internal.h" + +typedef _PDCLIB_ptrdiff_t ptrdiff_t; + +#ifndef _PDCLIB_SIZE_T_DEFINED +#define _PDCLIB_SIZE_T_DEFINED _PDCLIB_SIZE_T_DEFINED +typedef _PDCLIB_size_t size_t; +#endif + +#ifndef __cplusplus +typedef _PDCLIB_wchar_t wchar_t; +#endif + +#ifndef _PDCLIB_NULL_DEFINED +#define _PDCLIB_NULL_DEFINED _PDCLIB_NULL_DEFINED +#define NULL _PDCLIB_NULL +#endif + +#define offsetof( type, member ) _PDCLIB_offsetof( type, member ) + +/* Annex K -- Bounds-checking interfaces */ + +#if ( __STDC_WANT_LIB_EXT1__ + 0 ) != 0 +#ifndef _PDCLIB_RSIZE_T_DEFINED +#define _PDCLIB_RSIZE_T_DEFINED _PDCLIB_RSIZE_T_DEFINED +typedef size_t rsize_t; +#endif +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDDEF_H +#include _PDCLIB_EXTEND_STDDEF_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/stdint.h b/libc/include/stdint.h new file mode 100644 index 0000000..04af6b0 --- /dev/null +++ b/libc/include/stdint.h @@ -0,0 +1,236 @@ +/* Integer types + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDINT_H +#define _PDCLIB_STDINT_H _PDCLIB_STDINT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_lib_ext1.h" +#include "pdclib/_PDCLIB_internal.h" + +/* 7.18.1.1 Exact-width integer types. */ + +#ifdef _PDCLIB_int8_t +typedef _PDCLIB_int8_t int8_t; +typedef _PDCLIB_uint8_t uint8_t; +#endif + +#ifdef _PDCLIB_int16_t +typedef _PDCLIB_int16_t int16_t; +typedef _PDCLIB_uint16_t uint16_t; +#endif + +#ifdef _PDCLIB_int32_t +typedef _PDCLIB_int32_t int32_t; +typedef _PDCLIB_uint32_t uint32_t; +#endif + +#ifdef _PDCLIB_int64_t +typedef _PDCLIB_int64_t int64_t; +typedef _PDCLIB_uint64_t uint64_t; +#endif + +/* 7.18.1.2 Minimum-width integer types */ + +/* You are allowed to add more types here, e.g. int_least24_t. */ + +typedef _PDCLIB_int_least8_t int_least8_t; +typedef _PDCLIB_int_least16_t int_least16_t; +typedef _PDCLIB_int_least32_t int_least32_t; +typedef _PDCLIB_int_least64_t int_least64_t; + +typedef _PDCLIB_uint_least8_t uint_least8_t; +typedef _PDCLIB_uint_least16_t uint_least16_t; +typedef _PDCLIB_uint_least32_t uint_least32_t; +typedef _PDCLIB_uint_least64_t uint_least64_t; + +/* 7.18.1.3 Fastest minimum-width integer types */ + +/* You are allowed to add more types here, e.g. int_fast24_t. */ + +typedef _PDCLIB_int_fast8_t int_fast8_t; +typedef _PDCLIB_int_fast16_t int_fast16_t; +typedef _PDCLIB_int_fast32_t int_fast32_t; +typedef _PDCLIB_int_fast64_t int_fast64_t; + +typedef _PDCLIB_uint_fast8_t uint_fast8_t; +typedef _PDCLIB_uint_fast16_t uint_fast16_t; +typedef _PDCLIB_uint_fast32_t uint_fast32_t; +typedef _PDCLIB_uint_fast64_t uint_fast64_t; + +/* 7.18.1.4 Integer types capable of holding object pointers */ + +typedef _PDCLIB_intptr_t intptr_t; +typedef _PDCLIB_uintptr_t uintptr_t; + +/* 7.18.1.5 Greatest-width integer types */ + +typedef _PDCLIB_intmax_t intmax_t; +typedef _PDCLIB_uintmax_t uintmax_t; + +/* 7.18.2 Limits of specified-width integer types */ + +#if defined( __cplusplus ) && __cplusplus < 201103L +#ifndef __STDC_LIMIT_MACROS +#define _PDCLIB_NO_LIMIT_MACROS +#endif +#endif + +#ifndef _PDCLIB_NO_LIMIT_MACROS + +/* 7.18.2.1 Limits of exact-width integer types */ + +#if _PDCLIB_TWOS_COMPLEMENT == 1 + +#if _PDCLIB_INT_LEAST8_MAX == 0x7f +#define INT8_MAX _PDCLIB_INT_LEAST8_MAX +#define INT8_MIN _PDCLIB_INT_LEAST8_MIN +#define UINT8_MAX _PDCLIB_UINT_LEAST8_MAX +#endif + +#if _PDCLIB_INT_LEAST16_MAX == 0x7fff +#define INT16_MAX _PDCLIB_INT_LEAST16_MAX +#define INT16_MIN _PDCLIB_INT_LEAST16_MIN +#define UINT16_MAX _PDCLIB_UINT_LEAST16_MAX +#endif + +#if _PDCLIB_INT_LEAST32_MAX == 0x7fffffffl +#define INT32_MAX _PDCLIB_INT_LEAST32_MAX +#define INT32_MIN _PDCLIB_INT_LEAST32_MIN +#define UINT32_MAX _PDCLIB_UINT_LEAST32_MAX +#endif + +#if _PDCLIB_INT_LEAST64_MAX == 0x7fffffffffffffffll +#define INT64_MAX _PDCLIB_INT_LEAST64_MAX +#define INT64_MIN _PDCLIB_INT_LEAST64_MIN +#define UINT64_MAX _PDCLIB_UINT_LEAST64_MAX +#endif + +#endif + +/* 7.18.2.2 Limits of minimum-width integer types */ + +#define INT_LEAST8_MIN _PDCLIB_INT_LEAST8_MIN +#define INT_LEAST8_MAX _PDCLIB_INT_LEAST8_MAX +#define UINT_LEAST8_MAX _PDCLIB_UINT_LEAST8_MAX + +#define INT_LEAST16_MIN _PDCLIB_INT_LEAST16_MIN +#define INT_LEAST16_MAX _PDCLIB_INT_LEAST16_MAX +#define UINT_LEAST16_MAX _PDCLIB_UINT_LEAST16_MAX + +#define INT_LEAST32_MIN _PDCLIB_INT_LEAST32_MIN +#define INT_LEAST32_MAX _PDCLIB_INT_LEAST32_MAX +#define UINT_LEAST32_MAX _PDCLIB_UINT_LEAST32_MAX + +#define INT_LEAST64_MIN _PDCLIB_INT_LEAST64_MIN +#define INT_LEAST64_MAX _PDCLIB_INT_LEAST64_MAX +#define UINT_LEAST64_MAX _PDCLIB_UINT_LEAST64_MAX + +/* 7.18.2.3 Limits of fastest minimum-width integer types */ + +#define INT_FAST8_MIN _PDCLIB_INT_FAST8_MIN +#define INT_FAST8_MAX _PDCLIB_INT_FAST8_MAX +#define UINT_FAST8_MAX _PDCLIB_UINT_FAST8_MAX + +#define INT_FAST16_MIN _PDCLIB_INT_FAST16_MIN +#define INT_FAST16_MAX _PDCLIB_INT_FAST16_MAX +#define UINT_FAST16_MAX _PDCLIB_UINT_FAST16_MAX + +#define INT_FAST32_MIN _PDCLIB_INT_FAST32_MIN +#define INT_FAST32_MAX _PDCLIB_INT_FAST32_MAX +#define UINT_FAST32_MAX _PDCLIB_UINT_FAST32_MAX + +#define INT_FAST64_MIN _PDCLIB_INT_FAST64_MIN +#define INT_FAST64_MAX _PDCLIB_INT_FAST64_MAX +#define UINT_FAST64_MAX _PDCLIB_UINT_FAST64_MAX + +/* 7.18.2.4 Limits of integer types capable of holding object pointers */ + +#define INTPTR_MIN _PDCLIB_INTPTR_MIN +#define INTPTR_MAX _PDCLIB_INTPTR_MAX +#define UINTPTR_MAX _PDCLIB_UINTPTR_MAX + +/* 7.18.2.5 Limits of greatest-width integer types */ + +#define INTMAX_MIN _PDCLIB_INTMAX_MIN +#define INTMAX_MAX _PDCLIB_INTMAX_MAX +#define UINTMAX_MAX _PDCLIB_UINTMAX_MAX + +/* 7.18.3 Limits of other integer types */ + +#define PTRDIFF_MIN _PDCLIB_PTRDIFF_MIN +#define PTRDIFF_MAX _PDCLIB_PTRDIFF_MAX + +#define SIG_ATOMIC_MIN _PDCLIB_SIG_ATOMIC_MIN +#define SIG_ATOMIC_MAX _PDCLIB_SIG_ATOMIC_MAX + +#define SIZE_MAX _PDCLIB_SIZE_MAX + +#define WCHAR_MIN _PDCLIB_WCHAR_MIN +#define WCHAR_MAX _PDCLIB_WCHAR_MAX + +#define WINT_MIN _PDCLIB_WINT_MIN +#define WINT_MAX _PDCLIB_WINT_MAX + +#endif + +/* 7.18.4 Macros for integer constants */ + +#if defined( __cplusplus ) && __cplusplus < 201103L +#ifndef __STDC_CONSTANT_MACROS +#define _PDCLIB_NO_CONSTANT_MACROS +#endif +#endif + +#ifndef _PDCLIB_NO_CONSTANT_MACROS + +/* 7.18.4.1 Macros for minimum-width integer constants */ + +/* Expand to an integer constant of specified value and type int_leastN_t */ + +#define INT8_C _PDCLIB_INT_LEAST8_C +#define INT16_C _PDCLIB_INT_LEAST16_C +#define INT32_C _PDCLIB_INT_LEAST32_C +#define INT64_C _PDCLIB_INT_LEAST64_C + +/* Expand to an integer constant of specified value and type uint_leastN_t */ + +#define UINT8_C _PDCLIB_UINT_LEAST8_C +#define UINT16_C _PDCLIB_UINT_LEAST16_C +#define UINT32_C _PDCLIB_UINT_LEAST32_C +#define UINT64_C _PDCLIB_UINT_LEAST64_C + +/* 7.18.4.2 Macros for greatest-width integer constants */ + +/* Expand to an integer constant of specified value and type intmax_t */ +#define INTMAX_C( value ) _PDCLIB_INTMAX_C( value ) + +/* Expand to an integer constant of specified value and type uintmax_t */ +#define UINTMAX_C( value ) _PDCLIB_UINTMAX_C( value ) + +#endif + +/* Annex K -- Bounds-checking interfaces */ + +#if ( __STDC_WANT_LIB_EXT1__ + 0 ) != 0 +#define RSIZE_MAX ( _PDCLIB_SIZE_MAX >> 1 ) +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDINT_H +#include _PDCLIB_EXTEND_STDINT_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/stdio.h b/libc/include/stdio.h new file mode 100644 index 0000000..4ee538f --- /dev/null +++ b/libc/include/stdio.h @@ -0,0 +1,935 @@ +/* Input/output + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDIO_H +#define _PDCLIB_STDIO_H _PDCLIB_STDIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_lib_ext1.h" +#include "pdclib/_PDCLIB_internal.h" + +#ifndef _PDCLIB_SIZE_T_DEFINED +#define _PDCLIB_SIZE_T_DEFINED _PDCLIB_SIZE_T_DEFINED +typedef _PDCLIB_size_t size_t; +#endif + +#ifndef _PDCLIB_NULL_DEFINED +#define _PDCLIB_NULL_DEFINED _PDCLIB_NULL_DEFINED +#define NULL _PDCLIB_NULL +#endif + +/* See setvbuf(), third argument */ +#define _IOFBF (1u<<0) +#define _IOLBF (1u<<1) +#define _IONBF (1u<<2) + +/* The following are platform-dependent, and defined in _PDCLIB_config.h. */ +typedef struct _PDCLIB_fpos_t fpos_t; +typedef struct _PDCLIB_file_t FILE; +#define EOF (-1) +#define BUFSIZ _PDCLIB_BUFSIZ +#define FOPEN_MAX _PDCLIB_FOPEN_MAX +#define FILENAME_MAX _PDCLIB_FILENAME_MAX +#define L_tmpnam _PDCLIB_L_tmpnam +#define TMP_MAX _PDCLIB_TMP_MAX + +/* See fseek(), third argument */ +#define SEEK_CUR _PDCLIB_SEEK_CUR +#define SEEK_END _PDCLIB_SEEK_END +#define SEEK_SET _PDCLIB_SEEK_SET + +extern FILE * stdin; +extern FILE * stdout; +extern FILE * stderr; + +/* Operations on files */ + +/* Remove the given file. + Returns zero if successful, non-zero otherwise. + This implementation does detect if a file of that name is currently open, + and fails the remove in this case. This does not detect two distinct names + that merely result in the same file (e.g. "/home/user/foo" vs. "~/foo"). +*/ +_PDCLIB_PUBLIC int remove( const char * filename ); + +/* Rename the given old file to the given new name. + Returns zero if successful, non-zero otherwise. + This implementation does detect if the old filename corresponds to an open + file, and fails the rename in this case. + If there already is a file with the new filename, behaviour is defined by + the glue code (see functions/_PDCLIB/rename.c). +*/ +_PDCLIB_PUBLIC int rename( const char * oldpath, const char * newpath ); + +/* Open a temporary file with mode "wb+", i.e. binary-update. Remove the file + automatically if it is closed or the program exits normally (by returning + from main() or calling exit()). + Returns a pointer to a FILE handle for this file. + This implementation does not remove temporary files if the process aborts + abnormally (e.g. abort()). +*/ +_PDCLIB_PUBLIC FILE * tmpfile( void ); + +/* Generate a file name that is not equal to any existing filename AT THE TIME + OF GENERATION. Generate a different name each time it is called. + Returns a pointer to an internal static buffer containing the filename if s + is a NULL pointer. (This is not thread-safe!) + Returns s if it is not a NULL pointer (s is then assumed to point to an array + of at least L_tmpnam characters). + Returns NULL if unable to generate a suitable name (because all possible + names already exist, or the function has been called TMP_MAX times already). + Note that this implementation cannot guarantee a file of the name generated + is not generated between the call to this function and a subsequent fopen(). +*/ +_PDCLIB_PUBLIC char * tmpnam( char * s ); + +/* File access functions */ + +/* Close the file associated with the given stream (after flushing its buffers). + Returns zero if successful, EOF if any errors occur. +*/ +_PDCLIB_PUBLIC int fclose( FILE * stream ); + +/* Flush the buffers of the given output stream. If the stream is an input + stream, or an update stream with the last operation being an input operation, + behaviour is undefined. + If stream is a NULL pointer, perform the buffer flushing for all applicable + streams. + Returns zero if successful, EOF if a write error occurs. + Sets the error indicator of the stream if a write error occurs. +*/ +_PDCLIB_PUBLIC int fflush( FILE * stream ); + +/* Open the file with the given filename in the given mode, and return a stream + handle for it in which error and end-of-file indicator are cleared. Defined + values for mode are: + + READ MODES + text files binary files + without update "r" "rb" + with update "r+" "rb+" or "r+b" + + Opening in read mode fails if no file with the given filename exists, or if + cannot be read. + + WRITE MODES + text files binary files + without update "w" "wb" + with update "w+" "wb+" or "w+b" + + With write modes, if a file with the given filename already exists, it is + truncated to zero length. + + APPEND MODES + text files binary files + without update "a" "ab" + with update "a+" "ab+" or "a+b" + + With update modes, if a file with the given filename already exists, it is + not truncated to zero length, but all writes are forced to end-of-file (this + regardless to fseek() calls). Note that binary files opened in append mode + might have their end-of-file padded with '\0' characters. + + Update modes mean that both input and output functions can be performed on + the stream, but output must be terminated with a call to either fflush(), + fseek(), fsetpos(), or rewind() before input is performed, and input must + be terminated with a call to either fseek(), fsetpos(), or rewind() before + output is performed, unless input encountered end-of-file. + + If a text file is opened with update mode, the implementation is at liberty + to open a binary stream instead. This implementation honors the exact mode + given. + + The stream is fully buffered if and only if it can be determined not to + refer to an interactive device. + + If the mode string begins with but is longer than one of the above sequences + the implementation is at liberty to ignore the additional characters, or do + implementation-defined things. This implementation only accepts the exact + modes above. + + Returns a pointer to the stream handle if successfull, NULL otherwise. +*/ +_PDCLIB_PUBLIC FILE * fopen( const char * _PDCLIB_restrict filename, const char * _PDCLIB_restrict mode ); + +/* Close any file currently associated with the given stream. Open the file + identified by the given filename with the given mode (equivalent to fopen()), + and associate it with the given stream. If filename is a NULL pointer, + attempt to change the mode of the given stream. + This implementation allows any mode changes on "real" files, and associating + of the standard streams with files. It does *not* support mode changes on + standard streams. + (Primary use of this function is to redirect stdin, stdout, and stderr.) + + Returns a pointer to the stream handle if successfull, NULL otherwise. +*/ +_PDCLIB_PUBLIC FILE * freopen( const char * _PDCLIB_restrict filename, const char * _PDCLIB_restrict mode, FILE * _PDCLIB_restrict stream ); + +/* If buf is a NULL pointer, call setvbuf( stream, NULL, _IONBF, BUFSIZ ). + If buf is not a NULL pointer, call setvbuf( stream, buf, _IOFBF, BUFSIZ ). +*/ +_PDCLIB_PUBLIC void setbuf( FILE * _PDCLIB_restrict stream, char * _PDCLIB_restrict buf ); + +/* Set the given stream to the given buffering mode. If buf is not a NULL + pointer, use buf as file buffer (of given size). If buf is a NULL pointer, + use a buffer of given size allocated internally. _IONBF causes unbuffered + behaviour, _IOLBF causes line-buffered behaviour, _IOFBF causes fully + buffered behaviour. Calling this function is only valid right after a file is + opened, and before any other operation (except for any unsuccessful calls to + setvbuf()) has been performed. + Returns zero if successful, nonzero otherwise. +*/ +_PDCLIB_PUBLIC int setvbuf( FILE * _PDCLIB_restrict stream, char * _PDCLIB_restrict buf, int mode, size_t size ); + +/* Formatted input/output functions */ + +/* + Write output to the given stream, as defined by the given format string and + 0..n subsequent arguments (the argument stack). + + The format string is written to the given stream verbatim, except for any + conversion specifiers included, which start with the letter '%' and are + documented below. If the given conversion specifiers require more arguments + from the argument stack than provided, behaviour is undefined. Additional + arguments not required by conversion specifiers are evaluated but otherwise + ignored. + + (The standard specifies the format string is allowed to contain multibyte + character sequences as long as it starts and ends in initial shift state, + but this is not yet supported by this implementation, which interprets the + format string as sequence of char.) + TODO: Add multibyte support to printf() functions. + + A conversion specifier consists of: + - Zero or more flags (one of the characters "-+ #0"). + - Optional minimum field width as decimal integer. Default is padding to the + left, using spaces. Note that 0 is taken as a flag, not the beginning of a + field width. Note also that a small field width will not result in the + truncation of a value. + - Optional precision (given as ".#" with # being a decimal integer), + specifying: + - the min. number of digits to appear (diouxX), + - the max. number of digits after the decimal point (aAeEfF), + - the max. number of significant digits (gG), + - the max. number of bytes to be written (s). + - behaviour with other conversion specifiers is undefined. + - Optional length modifier specifying the size of the argument (one of "hh", + "ll", or one of the characters "hljztL"). + - Conversion specifier character specifying the type of conversion to be + applied (and the type of the next argument from the argument stack). One + of the characters "diouxXfFeEgGaAcspn%". + + Minimum field width and/or precision may be given as asterisk ('*') instead + of a decimal integer. In this case, the next argument from the argument + stack is assumed to be an int value specifying the width / precision. A + negative field width is interpreted as flag '-' followed by a positive field + width. A negative precision is interpreted as if no precision was given. + + FLAGS + - Left-justify the conversion result within its field width. + + Prefix a '+' on positive signed conversion results. Prefix a '-' on + floating conversions resulting in negative zero, or negative values + rounding to zero. + space Prefix a space on positive signed conversion results, or if a signed + conversion results in no characters. If both '+' and ' ' are given, + ' ' is ignored. + # Use an "alternative form" for + - 'o' conversion, increasing precision until the first digit of the + result is a zero; + - 'x' or 'X' conversion, prefixing "0x" or "0X" to nonzero results; + - "aAeEfF" conversions, always printing a decimal point even if no + digits are following; + - 'g' or 'G' conversions, always printing a decimal point even if no + digits are following, and not removing trailing zeroes. + - behaviour for other conversions is unspecified. + 0 Use leading zeroes instead of spaces for field width padding. If both + '-' and '0' are given, '0' is ignored. If a precision is specified for + any of the "diouxX" conversions, '0' is ignored. Behaviour is only + defined for "diouxXaAeEfFgG". + + LENGTH MODIFIERS + hh For "diouxX" conversions, the argument from the argument stack is + assumed to be of char width. (It will have been subject to integer + promotion but will be converted back.) For 'n' conversions, the argument + is assumed to be a pointer to signed char. + h For "diouxX" conversions, the argument from the argument stack is + assumed to be of short int width. (It will have been subject to integer + promotion but will be converted back.) For 'n' conversions, the argument + is assumed to be a pointer to short int. + l For "diouxX" conversions, the argument from the argument stack is + assumed to be of long int width. For 'n' conversions, the argument is + assumed to be a pointer to short int. For 'c' conversions, the argument + is assumed to be a wint_t. For 's' conversions, the argument is assumed + to be a pointer to wchar_t. No effect on "aAeEfFgG" conversions. + ll For "diouxX" conversions, the argument from the argument stack is + assumed to be of long long int width. For 'n' conversions, the argument + is assumed to be a pointer to long long int. + j For "diouxX" conversions, the argument from the argument stack is + assumed to be of intmax_t width. For 'n' conversions, the argument is + assumed to be a pointer to intmax_t. + z For "diouxX" conversions, the argument from the argument stack is + assumed to be of size_t width. For 'n' conversions, the argument is + assumed to be a pointer to size_t. + t For "diouxX" conversions, the argument from the argument stack is + assumed to be of ptrdiff_t width. For 'n' conversions, the argument is + assumed to be a pointer to ptrdiff_t. + L For "aAeEfFgG" conversions, the argument from the argument stack is + assumed to be a long double. + Length modifiers appearing for any conversions not mentioned above will have + undefined behaviour. + If a length modifier appears with any conversion specifier other than as + specified above, the behavior is undefined. + + CONVERSION SPECIFIERS + d,i The argument from the argument stack is assumed to be of type int, and + is converted to a signed decimal value with a minimum number of digits + as specified by the precision (default 1), padded with leading zeroes. + A zero value converted with precision zero yields no output. + o The argument from the argument stack is assumed to be of type unsigned + int, and is converted to an unsigned octal value, other behaviour being + as above. + u The argument from the argument stack is assumed to be of type unsigned + int, and converted to an unsigned decimal value, other behaviour being + as above. + x,X The argument from the argument stack is assumed to be of type unsigned + int, and converted to an unsigned hexadecimal value, using lowercase + "abcdef" for 'x' and uppercase "ABCDEF" for 'X' conversion, other + behaviour being as above. + f,F The argument from the argument stack is assumed to be of type double, + and converted to a decimal floating point in decimal-point notation, + with the number of digits after the decimal point as specified by the + precision (default 6) and the value being rounded appropriately. If + precision is zero (and the '#' flag is not given), no decimal point is + printed. At least one digit is always printed before the decimal point. + For 'f' conversions, an infinity value is printed as either [-]inf or + [-]infinity (, depending on the configuration of this implementation. A + NaN value is printed as [-]nan. For 'F' conversions uppercase characters + are used for these special values. The flags '-', '+' and ' ' apply as + usual to these special values, '#' and '0' have no effect. + e,E The argument from the argument stack is assumed to be of type double, + and converted to a decimal floating point in normalized exponential + notation ([?]d.ddd edd). "Normalized" means one nonzero digit before + the decimal point, unless the value is zero. The number of digits after + the decimal point is specified by the precision (default 6), the value + being rounded appropriately. If precision is zero (and the '#' flag is + not given), no decimal point is printed. The exponent has at least two + digits, and not more than necessary to represent the exponent. If the + value is zero, the exponent is zero. The 'e' written to indicate the + exponend is uppercase for 'E' conversions. + Infinity or NaN values are represented as for 'f' and 'F' conversions, + respectively. + g,G The argument from the argument stack is assumed to be of type double, + and converted according to either 'f' or 'e' format for 'g' conversions, + or 'F' or 'E' format for 'G' conversions, respectively, with the actual + conversion chosen depending on the value. 'e' / 'E' conversion is chosen + if the resulting exponent is < -4 or >= the precision (default 1). + Trailing zeroes are removed (unless the '#' flag is given). A decimal + point appears only if followed by a digit. + Infinity or NaN values are represented as for 'f' and 'F' conversions, + respectively. + a,A The argument from the argument stack is assumed to be of type double, + and converted to a floating point hexadecimal notation ([?]0xh.hhhh pd) + with one hexadecimal digit (being nonzero if the value is normalized, + and otherwise unspecified) before the decimal point, and the number of + digits after the decimal point being specified by the precision. If no + precision is given, the default is to print as many digits as nevessary + to give an exact representation of the value (if FLT_RADIX is a power of + 2). If no precision is given and FLT_RADIX is not a power of 2, the + default is to print as many digits to distinguish values of type double + (possibly omitting trailing zeroes). (A precision p is sufficient to + distinguish values of the source type if 16^p-1 > b^n where b is + FLT_RADIX and n is the number of digits in the significand (to base b) + of the source type. A smaller p might suffice depending on the + implementation's scheme for determining the digit to the left of the + decimal point.) The error has the correct sign for the current rounding + direction. + Unless the '#' flag is given, no decimal-point is given for zero + precision. + The 'a' conversion uses lowercase "abcdef", "0x" and 'p', the 'A' + conversion uppercase "ABCDEF", "0X" and 'P'. + The exponent always has at least one digit, and not more than necessary + to represent the decimal exponent of 2. If the value is zero, the + exponent is zero. + Infinity or NaN values are represented as for 'f' and 'F' conversions, + respectively. + Binary implementations are at liberty to chose the hexadecimal digit to + the left of the decimal point so that subsequent digits align to nibble + boundaries. + c The argument from the argument stack is assumed to be of type int, and + converted to a character after the value has been cast to unsigned char. + If the 'l' length modifier is given, the argument is assumed to be of + type wint_t, and converted as by a "%ls" conversion with no precision + and a pointer to a two-element wchar_t array, with the first element + being the wint_t argument and the second a '\0' wide character. + s The argument from the argument stack is assumed to be a char array (i.e. + pointer to char). Characters from that array are printed until a zero + byte is encountered or as many bytes as specified by a given precision + have been written. + If the l length modifier is given, the argument from the argument stack + is assumed to be a wchar_t array (i.e. pointer to wchar_t). Wide + characters from that array are converted to multibyte characters as by + calls to wcrtomb() (using a mbstate_t object initialized to zero prior + to the first conversion), up to and including the terminating null wide + character. The resulting multibyte character sequence is then printed up + to but not including the terminating null character. If a precision is + given, it specifies the maximum number of bytes to be written (including + shift sequences). If the given precision would require access to a wide + character one past the end of the array, the array shall contain a '\0' + wide character. In no case is a partial multibyte character written. + Redundant shift sequences may result if the multibyte characters have a + state-dependent encoding. + TODO: Clarify these statements regarding %ls. + p The argument from the argument stack is assumed to be a void pointer, + and converted to a sequence of printing characters in an implementation- + defined manner. + This implementation casts the pointer to type intptr_t, and prints the + value as if a %#x conversion specifier was given. + n The argument from the argument stack is assumed to be a pointer to a + signed integer, into which the number of characters written so far by + this call to fprintf is stored. The behaviour, should any flags, field + widths, or precisions be given is undefined. + % A verbatim '%' character is written. No argument is taken from the + argument stack. + + Returns the number of characters written if successful, a negative value + otherwise. +*/ +_PDCLIB_PUBLIC int fprintf( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, ... ); + +/* TODO: fscanf() documentation */ +/* + Read input from a given stream, as defined by the given format string, and + store converted input in the objects pointed to by 0..n subsequent arguments + (the argument stack). + + The format string contains a sequence of directives that are expected to + match the input. If such a directive fails to match, the function returns + (matching error). It also returns if an input error occurs (input error). + + Directives can be: + - one or more whitespaces, matching any number of whitespaces in the input; + - printing characters, matching the input verbatim; + - conversion specifications, which convert an input sequence into a value as + defined by the individual specifier, and store that value in a memory + location pointed to by the next pointer on the argument stack. Details are + documented below. If there is an insufficient number of pointers on the + argument stack, behaviour is undefined. Additional arguments not required + by any conversion specifications are evaluated, but otherwise ignored. + + (The standard specifies the format string is allowed to contain multibyte + character sequences as long as it starts and ends in initial shift state, + but this is not yet supported by this implementation, which interprets the + format string as sequence of char.) + TODO: Add multibyte support to scanf() functions. + + A conversion specifier consists of: + - Optional assignment-suppressing character ('*') that makes the conversion + read input as usual, but does not assign the conversion result. + - Optional maximum field width as decimal integer. + - Optional length modifier specifying the size of the argument (one of "hh", + "ll", or one of the characters "hljztL"). + - Conversion specifier character specifying the type of conversion to be + applied (and the type of the next argument from the argument stack). One + of the characters "diouxXaAeEfFgGcs[pn%". + + LENGTH MODIFIERS + hh For "diouxXn" conversions, the next pointer from the argument stack is + assumed to point to a variable of of char width. + h For "diouxXn" conversions, the next pointer from the argument stack is + assumed to point to a variable of short int width. + l For "diouxXn" conversions, the next pointer from the argument stack is + assumed to point to a variable of long int width. + For "aAeEfFgG" conversions, it is assumed to point to a variable of type + double. + For "cs[" conversions, it is assumed to point to a variable of type + wchar_t. + ll For "diouxXn" conversions, the next pointer from the argument stack is + assumed to point to a variable of long long int width. + j For "diouxXn" conversions, the next pointer from the argument stack is + assumed to point to a variable of intmax_t width. + z For "diouxXn" conversions, the next pointer from the argument stack is + assumed to point to a variable of size_t width. + t For "diouxXn" conversions, the next pointer from the argument stack is + assumed to point to a variable of ptrdiff_t width. + L For "aAeEfFgG" conversions, the next pointer from the argument stack is + assumed to point to a variable of type long double. + Length modifiers appearing for any conversions not mentioned above will have + undefined behaviour. + If a length modifier appears with any conversion specifier other than as + specified above, the behavior is undefined. + + CONVERSION SPECIFIERS + d Matches an (optionally signed) decimal integer of the format expected + by strtol() with base 10. The next pointer from the argument stack is + assumed to point to a signed integer. + i Matches an (optionally signed) integer of the format expected by + strtol() with base 0. The next pointer from the argument stack is + assumed to point to a signed integer. + o Matches an (optionally signed) octal integer of the format expected by + strtoul() with base 8. The next pointer from the argument stack is + assumed to point to an unsigned integer. + u Matches an (optionally signed) decimal integer of the format expected + by strtoul() with base 10. The next pointer from the argument stack is + assumed to point to an unsigned integer. + x Matches an (optionally signed) hexadecimal integer of the format + expected by strtoul() with base 16. The next pointer from the argument + stack is assumed to point to an unsigned integer. + aefg Matches an (optionally signed) floating point number, infinity, or not- + a-number-value of the format expected by strtod(). The next pointer + from the argument stack is assumed to point to a float. + c Matches a number of characters as specified by the field width (default + 1). The next pointer from the argument stack is assumed to point to a + character array large enough to hold that many characters. + If the 'l' length modifier is given, the input is assumed to match a + sequence of multibyte characters (starting in the initial shift state), + which will be converted to a wide character sequence as by successive + calls to mbrtowc() with a mbstate_t object initialized to zero prior to + the first conversion. The next pointer from the argument stack is + assumed to point to a wchar_t array large enough to hold that many + characters. + In either case, note that no '\0' character is added to terminate the + sequence. + s Matches a sequence of non-white-space characters. The next pointer from + the argument stack is assumed to point to a character array large + enough to hold the sequence including terminating '\0' character. + If the 'l' length modifier is given, the input is assumed to match a + sequence of multibyte characters (starting in the initial shift state), + which will be converted to a wide character sequence as by a call to + mbrtowc() with a mbstate_t object initialized to zero prior to the + first conversion. The next pointer from the argument stack is assumed + to point to a wchar_t array large enough to hold the sequence including + terminating '\0' character. + [ Matches a nonempty sequence consisting of any of those characters + specified between itself and a corresponding closing bracket (']'). + If the first character in the list is a circumflex ('^'), this matches + a nonempty sequence consisting of any characters NOT specified. If the + closing bracket appears as the first character in the scanset ("[]" or + "[^]", it is assumed to belong to the scanset, which then ends with the + NEXT closing bracket. + If there is a '-' character in the scanset which is not the first after + the opening bracket (or the circumflex, see above) or the last in the + scanset, behaviour is implementation-defined. This implementation + handles this character like any other. + + The extend of the input field is determined byte-by-byte for the above + conversions ('c', 's', '['), with no special provisions being made for + multibyte characters. The resulting field is nevertheless a multibyte + sequence begining in intial shift state. + + p Matches a sequence of characters as produced by the printf() "%p" + conversion. The next pointer from the argument stack is assumed to + point to a void pointer, which will be filled with the same location + as the pointer used in the printf() statement. Note that behaviour is + undefined if the input value is not the result of an earlier printf() + call. + n Does not read input. The next pointer from the argument stack is + assumed to point to a signed integer, into which the number of + characters read from input so far by this call to fscanf() is stored. + This does not affect the return value of fscanf(). The behaviour, + should an assignment-supressing character of field width be given, + is undefined. + This can be used to test the success of literal matches and suppressed + assignments. + % Matches a single, verbatim '%' character. + + A, E, F, G and X are valid, and equivalent to their lowercase counterparts. + + All conversions except [, c, or n imply that whitespace characters from the + input stream are consumed until a non-whitespace character is encountered. + Such whitespaces do not count against a maximum field width. + + Conversions push at most one character back into the input stream. That + implies that some character sequences converted by the strtol() and strtod() + function families are not converted identically by the scnaf() function + family. + + Returns the number of input items successfully assigned. This can be zero if + an early mismatch occurs. Returns EOF if an input failure occurs before the + first conversion. +*/ +_PDCLIB_PUBLIC int fscanf( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, ... ); + +/* Equivalent to fprintf( stdout, format, ... ). */ +_PDCLIB_PUBLIC int printf( const char * _PDCLIB_restrict format, ... ); + +/* Equivalent to fscanf( stdin, format, ... ). */ +_PDCLIB_PUBLIC int scanf( const char * _PDCLIB_restrict format, ... ); + +/* Equivalent to fprintf( stdout, format, ... ), except that the result is + written into the buffer pointed to by s, instead of stdout, and that any + characters beyond the (n-1)th are discarded. The (n)th character is + replaced by a '\0' character in this case. + Returns the number of characters that would have been written (not counting + the terminating '\0' character) if n had been sufficiently large, if + successful, and a negative number if an encoding error ocurred. +*/ +_PDCLIB_PUBLIC int snprintf( char * _PDCLIB_restrict s, size_t n, const char * _PDCLIB_restrict format, ... ); + +/* Equivalent to fprintf( stdout, format, ... ), except that the result is + written into the buffer pointed to by s, instead of stdout. +*/ +_PDCLIB_PUBLIC int sprintf( char * _PDCLIB_restrict s, const char * _PDCLIB_restrict format, ... ); + +/* Equivalent to fscanf( stdin, format, ... ), except that the input is read + from the buffer pointed to by s, instead of stdin. +*/ +_PDCLIB_PUBLIC int sscanf( const char * _PDCLIB_restrict s, const char * _PDCLIB_restrict format, ... ); + +/* Equivalent to fprintf( stream, format, ... ), except that the argument stack + is passed as va_list parameter. Note that va_list is not declared by + . +*/ +_PDCLIB_PUBLIC int vfprintf( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); + +/* Equivalent to fscanf( stream, format, ... ), except that the argument stack + is passed as va_list parameter. Note that va_list is not declared by + . +*/ +_PDCLIB_PUBLIC int vfscanf( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); + +/* Equivalent to fprintf( stdout, format, ... ), except that the argument stack + is passed as va_list parameter. Note that va_list is not declared by + . +*/ +_PDCLIB_PUBLIC int vprintf( const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); + +/* Equivalent to fscanf( stdin, format, ... ), except that the argument stack + is passed as va_list parameter. Note that va_list is not declared by + . +*/ +_PDCLIB_PUBLIC int vscanf( const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); + +/* Equivalent to snprintf( s, n, format, ... ), except that the argument stack + is passed as va_list parameter. Note that va_list is not declared by + . +*/ +_PDCLIB_PUBLIC int vsnprintf( char * _PDCLIB_restrict s, size_t n, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); + +/* Equivalent to fprintf( stdout, format, ... ), except that the argument stack + is passed as va_list parameter, and the result is written to the buffer + pointed to by s, instead of stdout. Note that va_list is not declared by + . +*/ +_PDCLIB_PUBLIC int vsprintf( char * _PDCLIB_restrict s, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); + +/* Equivalent to fscanf( stdin, format, ... ), except that the argument stack + is passed as va_list parameter, and the input is read from the buffer + pointed to by s, instead of stdin. Note that va_list is not declared by + . +*/ +_PDCLIB_PUBLIC int vsscanf( const char * _PDCLIB_restrict s, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); + +/* Character input/output functions */ + +/* Retrieve the next character from given stream. + Returns the character, EOF otherwise. + If end-of-file is reached, the EOF indicator of the stream is set. + If a read error occurs, the error indicator of the stream is set. +*/ +_PDCLIB_PUBLIC int fgetc( FILE * stream ); + +/* Read at most n-1 characters from given stream into the array s, stopping at + \n or EOF. Terminate the read string with \n. If EOF is encountered before + any characters are read, leave the contents of s unchanged. + Returns s if successful, NULL otherwise. + If a read error occurs, the error indicator of the stream is set. In this + case, the contents of s are indeterminate. +*/ +_PDCLIB_PUBLIC char * fgets( char * _PDCLIB_restrict s, int n, FILE * _PDCLIB_restrict stream ); + +/* Write the value c (cast to unsigned char) to the given stream. + Returns c if successful, EOF otherwise. + If a write error occurs, sets the error indicator of the stream is set. +*/ +_PDCLIB_PUBLIC int fputc( int c, FILE * stream ); + +/* Write the string s (not including the terminating \0) to the given stream. + Returns a value >=0 if successful, EOF otherwise. + This implementation does set the error indicator of the stream if a write + error occurs. +*/ +_PDCLIB_PUBLIC int fputs( const char * _PDCLIB_restrict s, FILE * _PDCLIB_restrict stream ); + +/* Equivalent to fgetc( stream ), but may be overloaded by a macro that + evaluates its parameter more than once. +*/ +_PDCLIB_PUBLIC int getc( FILE * stream ); + +/* Equivalent to fgetc( stdin ). */ +_PDCLIB_PUBLIC int getchar( void ); + +/* Equivalent to fputc( c, stream ), but may be overloaded by a macro that + evaluates its parameter more than once. +*/ +_PDCLIB_PUBLIC int putc( int c, FILE * stream ); + +/* Equivalent to fputc( c, stdout ), but may be overloaded by a macro that + evaluates its parameter more than once. +*/ +_PDCLIB_PUBLIC int putchar( int c ); + +/* Write the string s (not including the terminating \0) to stdout, and append + a newline to the output. Returns a value >= 0 when successful, EOF if a + write error occurred. +*/ +_PDCLIB_PUBLIC int puts( const char * s ); + +/* Push the value c (cast to unsigned char) back onto the given (input) stream. + A character pushed back in this way will be delivered by subsequent read + operations (and skipped by subsequent file positioning operations) as if it + has not been read. The external representation of the stream is unaffected + by this pushback (it is a buffer operation). One character of pushback is + guaranteed, further pushbacks may fail. EOF as value for c does not change + the input stream and results in failure of the function. + For text files, the file position indicator is indeterminate until all + pushed-back characters are read. For binary files, the file position + indicator is decremented by each successful call of ungetc(). If the file + position indicator for a binary file was zero before the call of ungetc(), + behaviour is undefined. (Older versions of the library allowed such a call.) + Returns the pushed-back character if successful, EOF if it fails. +*/ +_PDCLIB_PUBLIC int ungetc( int c, FILE * stream ); + +/* Direct input/output functions */ + +/* Read up to nmemb elements of given size from given stream into the buffer + pointed to by ptr. Returns the number of elements successfully read, which + may be less than nmemb if a read error or EOF is encountered. If a read + error is encountered, the value of the file position indicator is + indeterminate. If a partial element is read, its value is indeterminate. + If size or nmemb are zero, the function does nothing and returns zero. +*/ +_PDCLIB_PUBLIC size_t fread( void * _PDCLIB_restrict ptr, size_t size, size_t nmemb, FILE * _PDCLIB_restrict stream ); + +/* Write up to nmemb elements of given size from buffer pointed to by ptr to + the given stream. Returns the number of elements successfully written, which + will be less than nmemb only if a write error is encountered. If a write + error is encountered, the value of the file position indicator is + indeterminate. If size or nmemb are zero, the function does nothing and + returns zero. +*/ +_PDCLIB_PUBLIC size_t fwrite( const void * _PDCLIB_restrict ptr, size_t size, size_t nmemb, FILE * _PDCLIB_restrict stream ); + +/* File positioning functions */ + +/* Store the current position indicator (and, where appropriate, the current + mbstate_t status object) for the given stream into the given pos object. The + actual contents of the object are unspecified, but it can be used as second + parameter to fsetpos() to reposition the stream to the exact position and + parse state at the time fgetpos() was called. + Returns zero if successful, nonzero otherwise. + TODO: Implementation-defined errno setting for fgetpos(). +*/ +_PDCLIB_PUBLIC int fgetpos( FILE * _PDCLIB_restrict stream, fpos_t * _PDCLIB_restrict pos ); + +/* Set the position indicator for the given stream to the given offset from: + - the beginning of the file if whence is SEEK_SET, + - the current value of the position indicator if whence is SEEK_CUR, + - end-of-file if whence is SEEK_END. + On text streams, non-zero offsets are only allowed with SEEK_SET, and must + have been returned by ftell() for the same file. + Any characters buffered by ungetc() are dropped, the end-of-file indicator + for the stream is cleared. If the given stream is an update stream, the next + operation after a successful fseek() may be either input or output. + Returns zero if successful, nonzero otherwise. If a read/write error occurs, + the error indicator for the given stream is set. +*/ +_PDCLIB_PUBLIC int fseek( FILE * stream, long int offset, int whence ); + +/* Set the position indicator (and, where appropriate the mbstate_t status + object) for the given stream to the given pos object (created by an earlier + call to fgetpos() on the same file). + Any characters buffered by ungetc() are dropped, the end-of-file indicator + for the stream is cleared. If the given stream is an update stream, the next + operation after a successful fsetpos() may be either input or output. + Returns zero if successful, nonzero otherwise. If a read/write error occurs, + the error indicator for the given stream is set. + TODO: Implementation-defined errno setting for fsetpos(). +*/ +_PDCLIB_PUBLIC int fsetpos( FILE * stream, const fpos_t * pos ); + +/* Return the current offset of the given stream from the beginning of the + associated file. For text streams, the exact value returned is unspecified + (and may not be equal to the number of characters), but may be used in + subsequent calls to fseek(). + Returns -1L if unsuccessful. + TODO: Implementation-defined errno setting for ftell(). +*/ +_PDCLIB_PUBLIC long int ftell( FILE * stream ); + +/* Equivalent to (void)fseek( stream, 0L, SEEK_SET ), except that the error + indicator for the stream is also cleared. +*/ +_PDCLIB_PUBLIC void rewind( FILE * stream ); + +/* Error-handling functions */ + +/* Clear the end-of-file and error indicators for the given stream. */ +_PDCLIB_PUBLIC void clearerr( FILE * stream ); + +/* Return zero if the end-of-file indicator for the given stream is not set, + nonzero otherwise. +*/ +_PDCLIB_PUBLIC int feof( FILE * stream ); + +/* Return zero if the error indicator for the given stream is not set, nonzero + otherwise. +*/ +_PDCLIB_PUBLIC int ferror( FILE * stream ); + +/* If s is neither a NULL pointer nor an empty string, print the string to + stderr (with appended colon (':') and a space) first. In any case, print an + error message depending on the current value of errno (being the same as if + strerror( errno ) had been called). +*/ +_PDCLIB_PUBLIC void perror( const char * s ); + +/* Annex K -- Bounds-checking interfaces */ + +#if ( __STDC_WANT_LIB_EXT1__ + 0 ) != 0 + +#define L_tmpnam_s _PDCLIB_L_tmpnam +#define TMP_MAX_S _PDCLIB_TMP_MAX + +#ifndef _PDCLIB_ERRNO_T_DEFINED +#define _PDCLIB_ERRNO_T_DEFINED _PDCLIB_ERRNO_T_DEFINED +typedef int errno_t; +#endif + +#ifndef _PDCLIB_RSIZE_T_DEFINED +#define _PDCLIB_RSIZE_T_DEFINED _PDCLIB_RSIZE_T_DEFINED +typedef _PDCLIB_size_t rsize_t; +#endif + +/* Open a temporary file with mode "wb+", i.e. binary-update. Remove the file + automatically if it is closed or the program exits normally (by returning + from main() or calling exit()). + If successful, the FILE * pointed to by streamptr will be set to point at + the opened file handle, and the function returns zero. If unsuccessful, + the FILE * pointed to by streamptr will be set to NULL and a non-zero + value is returned. + The following conditions will be considered runtime constraint violations: + - streamptr being NULL. + In case of a constraint violation, no file is being created. + This implementation does not remove temporary files if the process aborts + abnormally (e.g. abort()). +*/ +_PDCLIB_PUBLIC errno_t tmpfile_s( FILE * _PDCLIB_restrict * _PDCLIB_restrict streamptr ); + +/* Open the file with the given filename in the given mode, and sets the given + streamptr to point at the file handle for that file, in which error and + end-of-file indicator are cleared. Defined values for mode are: + + READ MODES + text files binary files + without update "r" "rb" + with update "r+" "rb+" or "r+b" + + Opening in read mode fails if no file with the given filename exists, or if + cannot be read. + + WRITE MODES + text files binary files + without update "w" "wb" + with update "w+" "wb+" or "w+b" + + With write modes, if a file with the given filename already exists, it is + truncated to zero length. + + APPEND MODES + text files binary files + without update "a" "ab" + with update "a+" "ab+" or "a+b" + + With update modes, if a file with the given filename already exists, it is + not truncated to zero length, but all writes are forced to end-of-file (this + regardless to fseek() calls). Note that binary files opened in append mode + might have their end-of-file padded with '\0' characters. + + Update modes mean that both input and output functions can be performed on + the stream, but output must be terminated with a call to either fflush(), + fseek(), fsetpos(), or rewind() before input is performed, and input must + be terminated with a call to either fseek(), fsetpos(), or rewind() before + output is performed, unless input encountered end-of-file. + + If a text file is opened with update mode, the implementation is at liberty + to open a binary stream instead. This implementation honors the exact mode + given. + + The stream is fully buffered if and only if it can be determined not to + refer to an interactive device. + + If the mode string begins with but is longer than one of the above sequences + the implementation is at liberty to ignore the additional characters, or do + implementation-defined things. This implementation only accepts the exact + modes above. + + The following conditions will be considered runtime constraint violations: + - streamptr being NULL. + - filename being NULL. + - mode being NULL. + In case of a constraint violation, no file is opened. If streamptr is not + NULL, *streamptr is set to NULL. + + Returns zero if successful, non-zero otherwise. +*/ +_PDCLIB_PUBLIC errno_t fopen_s( FILE * _PDCLIB_restrict * _PDCLIB_restrict streamptr, const char * _PDCLIB_restrict filename, const char * _PDCLIB_restrict mode ); + +/* Close any file currently associated with the given stream. Open the file + identified by the given filename with the given mode (equivalent to fopen()), + and associate it with the given stream. If filename is a NULL pointer, + attempt to change the mode of the given stream. + This implementation allows any mode changes on "real" files, and associating + of the standard streams with files. It does *not* support mode changes on + standard streams. + (Primary use of this function is to redirect stdin, stdout, and stderr.) + + The following conditions will be considered runtime constraint violations: + - newstreamptr being NULL. + - mode being NULL. + - stream being NULL. + In case of a constraint violation, no attempt is made to close or open any + file. If newstreamptr is not NULL, *newstreamptr is set to NULL. + + Returns zero if successfull, non-zero otherwise. +*/ +_PDCLIB_PUBLIC errno_t freopen_s( FILE * _PDCLIB_restrict * _PDCLIB_restrict newstreamptr, const char * _PDCLIB_restrict filename, const char * _PDCLIB_restrict mode, FILE * _PDCLIB_restrict stream ); + +/* None of these are implemented yet. Placeholder declarations. */ +_PDCLIB_PUBLIC errno_t tmpnam_s( char * s, rsize_t maxsize ); +_PDCLIB_PUBLIC int fprintf_s( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, ... ); +_PDCLIB_PUBLIC int fscanf_s( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, ... ); +_PDCLIB_PUBLIC int printf_s( const char * _PDCLIB_restrict format, ... ); +_PDCLIB_PUBLIC int scanf_s( const char * _PDCLIB_restrict format, ... ); +_PDCLIB_PUBLIC int snprintf_s( char * _PDCLIB_restrict s, rsize_t n, const char * _PDCLIB_restrict format, ... ); +_PDCLIB_PUBLIC int sprintf_s( char * _PDCLIB_restrict s, rsize_t n, const char * _PDCLIB_restrict format, ... ); +_PDCLIB_PUBLIC int sscanf_s( const char * _PDCLIB_restrict s, const char * _PDCLIB_restrict format, ... ); +_PDCLIB_PUBLIC int vfprintf_s( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); +_PDCLIB_PUBLIC int vfscanf_s( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); +_PDCLIB_PUBLIC int vprintf_s( const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); +_PDCLIB_PUBLIC int vscanf_s( const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); +_PDCLIB_PUBLIC int vsnprintf_s( char * _PDCLIB_restrict s, rsize_t n, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); +_PDCLIB_PUBLIC int vsprintf_s( char * _PDCLIB_restrict s, rsize_t n, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); +_PDCLIB_PUBLIC int vsscanf_s( const char * _PDCLIB_restrict s, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); +_PDCLIB_PUBLIC char * gets_s( char * s, rsize_t n ); + +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDIO_H +#include _PDCLIB_EXTEND_STDIO_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/stdlib.h b/libc/include/stdlib.h new file mode 100644 index 0000000..9dc8b33 --- /dev/null +++ b/libc/include/stdlib.h @@ -0,0 +1,379 @@ +/* General utilities + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDLIB_H +#define _PDCLIB_STDLIB_H _PDCLIB_STDLIB_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_lib_ext1.h" +#include "pdclib/_PDCLIB_internal.h" + +#ifndef _PDCLIB_SIZE_T_DEFINED +#define _PDCLIB_SIZE_T_DEFINED _PDCLIB_SIZE_T_DEFINED +typedef _PDCLIB_size_t size_t; +#endif + +#ifndef _PDCLIB_NULL_DEFINED +#define _PDCLIB_NULL_DEFINED _PDCLIB_NULL_DEFINED +#define NULL _PDCLIB_NULL +#endif + +/* Numeric conversion functions */ + +/* TODO: atof(), strtof(), strtod(), strtold() */ + +_PDCLIB_PUBLIC double atof( const char * nptr ); +_PDCLIB_PUBLIC double strtod( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr ); +_PDCLIB_PUBLIC float strtof( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr ); +_PDCLIB_PUBLIC long double strtold( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr ); + +/* Separate the character array nptr into three parts: A (possibly empty) + sequence of whitespace characters, a character representation of an integer + to the given base, and trailing invalid characters (including the terminating + null character). If base is 0, assume it to be 10, unless the integer + representation starts with 0x / 0X (setting base to 16) or 0 (setting base to + 8). If given, base can be anything from 0 to 36, using the 26 letters of the + base alphabet (both lowercase and uppercase) as digits 10 through 35. + The integer representation is then converted into the return type of the + function. It can start with a '+' or '-' sign. If the sign is '-', the result + of the conversion is negated. + If the conversion is successful, the converted value is returned. If endptr + is not a NULL pointer, a pointer to the first trailing invalid character is + returned in *endptr. + If no conversion could be performed, zero is returned (and nptr in *endptr, + if endptr is not a NULL pointer). If the converted value does not fit into + the return type, the functions return LONG_MIN, LONG_MAX, ULONG_MAX, + LLONG_MIN, LLONG_MAX, or ULLONG_MAX respectively, depending on the sign of + the integer representation and the return type, and errno is set to ERANGE. +*/ +/* There is strtoimax() and strtoumax() in operating on intmax_t / + uintmax_t, if the long long versions do not suit your needs. +*/ +_PDCLIB_PUBLIC long int strtol( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr, int base ); +_PDCLIB_PUBLIC long long int strtoll( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr, int base ); +_PDCLIB_PUBLIC unsigned long int strtoul( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr, int base ); +_PDCLIB_PUBLIC unsigned long long int strtoull( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr, int base ); + +/* These functions are the equivalent of (int)strtol( nptr, NULL, 10 ), + strtol( nptr, NULL, 10 ) and strtoll(nptr, NULL, 10 ) respectively, with the + exception that they do not have to handle overflow situations in any defined + way. + (PDCLib does not simply forward these to their strtox() equivalents, but + provides a simpler atox() function that saves a couple of tests and simply + continues with the conversion in case of overflow.) +*/ +_PDCLIB_PUBLIC int atoi( const char * nptr ); +_PDCLIB_PUBLIC long int atol( const char * nptr ); +_PDCLIB_PUBLIC long long int atoll( const char * nptr ); + +/* Pseudo-random sequence generation functions */ + +extern unsigned long int _PDCLIB_seed; + +#define RAND_MAX 32767 + +/* Returns the next number in a pseudo-random sequence, which is between 0 and + RAND_MAX. + (PDCLib uses the implementation suggested by the standard document, which is + next = next * 1103515245 + 12345; return (unsigned int)(next/65536) % 32768;) +*/ +_PDCLIB_PUBLIC int rand( void ); + +/* Initialize a new pseudo-random sequence with the starting seed. Same seeds + result in the same pseudo-random sequence. The default seed is 1. +*/ +_PDCLIB_PUBLIC void srand( unsigned int seed ); + +/* Memory management functions */ + +/* Allocate a chunk of heap memory of given size. If request could not be + satisfied, return NULL. Otherwise, return a pointer to the allocated + memory. Memory contents are undefined. +*/ +_PDCLIB_PUBLIC void * malloc( size_t size ); + +/* Allocate a chunk of heap memory that is large enough to hold nmemb elements + of the given size, and zero-initialize that memory. If request could not be + satisfied, return NULL. Otherwise, return a pointer to the allocated + memory. +*/ +_PDCLIB_PUBLIC void * calloc( size_t nmemb, size_t size ); + +/* De-allocate a chunk of heap memory previously allocated using malloc(), + calloc(), or realloc(), and pointed to by ptr. If ptr does not match a + pointer previously returned by the mentioned allocation functions, or + free() has already been called for this ptr, behaviour is undefined. +*/ +_PDCLIB_PUBLIC void free( void * ptr ); + +/* Resize a chunk of memory previously allocated with malloc() and pointed to + by ptr to the given size (which might be larger or smaller than the original + size). Returns a pointer to the reallocated memory, or NULL if the request + could not be satisfied. Note that the resizing might include a memcpy() + from the original location to a different one, so the return value might or + might not equal ptr. If size is larger than the original size, the value of + memory beyond the original size is undefined. If ptr is NULL, realloc() + behaves like malloc(). +*/ +_PDCLIB_PUBLIC void * realloc( void * ptr, size_t size ); + +/* Communication with the environment */ + +/* These two can be passed to exit() or _Exit() as status values, to signal + successful and unsuccessful program termination, respectively. EXIT_SUCCESS + can be replaced by 0. How successful or unsuccessful program termination are + signaled to the environment, and what happens if exit() or _Exit() are being + called with a value that is neither of the three, is defined by the hosting + OS and its glue function. +*/ +#define EXIT_SUCCESS _PDCLIB_SUCCESS +#define EXIT_FAILURE _PDCLIB_FAILURE + +/* Initiate abnormal process termination, unless programm catches SIGABRT and + does not return from the signal handler. + This implementantion flushes all streams, closes all files, and removes any + temporary files before exiting with EXIT_FAILURE. + abort() does not return. +*/ +_PDCLIB_PUBLIC _PDCLIB_Noreturn void abort( void ) _PDCLIB_NORETURN; + +/* Register a function that will be called on quick_exit(). + At least 32 functions can be registered this way, and will be called in + reverse order of registration (last-in, first-out). + Returns zero if registration is successfull, nonzero if it failed. +*/ +_PDCLIB_PUBLIC int at_quick_exit( void ( *func )( void ) ); + +/* Register a function that will be called on exit(), or when main() returns. + At least 32 functions can be registered this way, and will be called in + reverse order of registration (last-in, first-out). + Returns zero if registration is successfull, nonzero if it failed. +*/ +_PDCLIB_PUBLIC int atexit( void ( *func )( void ) ); + +/* Normal process termination. Functions registered by atexit() (see above) are + called, streams flushed, files closed and temporary files removed before the + program is terminated with the given status. (See comment for EXIT_SUCCESS + and EXIT_FAILURE above.) + exit() does not return. +*/ +_PDCLIB_PUBLIC _PDCLIB_Noreturn void exit( int status ) _PDCLIB_NORETURN; + +/* Normal process termination. Functions registered by at_quick_exit() (see + above) are called, streams flushed, files closed and temporary files removed + before the program is terminated with the given status. (See comment for + EXIT_SUCCESS and EXIT_FAILURE above.) + quick_exit() does not return. +*/ +_PDCLIB_PUBLIC _PDCLIB_Noreturn void quick_exit( int status ) _PDCLIB_NORETURN; + +/* Normal process termination. Functions registered by atexit()/at_quick_exit() + (see above) are NOT CALLED. This implementation DOES flush streams, close + files and removes temporary files before the program is teminated with the + given status. (See comment for EXIT_SUCCESS and EXIT_FAILURE above.) + _Exit() does not return. +*/ +_PDCLIB_PUBLIC _PDCLIB_Noreturn void _Exit( int status ) _PDCLIB_NORETURN; + +/* Search an environment-provided key-value map for the given key name, and + return a pointer to the associated value string (or NULL if key name cannot + be found). The value string pointed to might be overwritten by a subsequent + call to getenv(). The library never calls getenv() itself. + Details on the provided keys and how to set / change them are determined by + the hosting OS and its glue function. +*/ +_PDCLIB_PUBLIC char * getenv( const char * name ); + +/* If string is a NULL pointer, system() returns nonzero if a command processor + is available, and zero otherwise. If string is not a NULL pointer, it is + passed to the command processor. If system() returns, it does so with a + value that is determined by the hosting OS and its glue function. +*/ +_PDCLIB_PUBLIC int system( const char * string ); + +/* Searching and sorting */ + +/* Do a binary search for a given key in the array with a given base pointer, + which consists of nmemb elements that are of the given size each. To compare + the given key with an element from the array, the given function compar is + called (with key as first parameter and a pointer to the array member as + second parameter); the function should return a value less than, equal to, + or greater than 0 if the key is considered to be less than, equal to, or + greater than the array element, respectively. + The function returns a pointer to a matching element found, or NULL if no + match is found. +*/ +_PDCLIB_PUBLIC void * bsearch( const void * key, const void * base, size_t nmemb, size_t size, int ( *compar )( const void *, const void * ) ); + +/* Do a quicksort on an array with a given base pointer, which consists of + nmemb elements that are of the given size each. To compare two elements from + the array, the given function compar is called, which should return a value + less than, equal to, or greater than 0 if the first argument is considered + to be less than, equal to, or greater than the second argument, respectively. + If two elements are compared equal, their order in the sorted array is not + specified. +*/ +_PDCLIB_PUBLIC void qsort( void * base, size_t nmemb, size_t size, int ( *compar )( const void *, const void * ) ); + +/* Integer arithmetic functions */ + +/* Return the absolute value of the argument. Note that on machines using two- + complement's notation (most modern CPUs), the largest negative value cannot + be represented as positive value. In this case, behaviour is unspecified. +*/ +_PDCLIB_PUBLIC int abs( int j ); +_PDCLIB_PUBLIC long int labs( long int j ); +_PDCLIB_PUBLIC long long int llabs( long long int j ); + +/* These structures each have a member quot and a member rem, of type int (for + div_t), long int (for ldiv_t) and long long it (for lldiv_t) respectively. + The order of the members is platform-defined to allow the div() functions + below to be implemented efficiently. +*/ +typedef struct _PDCLIB_div_t div_t; +typedef struct _PDCLIB_ldiv_t ldiv_t; +typedef struct _PDCLIB_lldiv_t lldiv_t; + +/* Return quotient (quot) and remainder (rem) of an integer division in one of + the structs above. +*/ +_PDCLIB_PUBLIC div_t div( int numer, int denom ); +_PDCLIB_PUBLIC ldiv_t ldiv( long int numer, long int denom ); +_PDCLIB_PUBLIC lldiv_t lldiv( long long int numer, long long int denom ); + +/* TODO: Multibyte / wide character conversion functions */ + +/* TODO: Macro MB_CUR_MAX */ + +/* +_PDCLIB_PUBLIC int mblen( const char * s, size_t n ); +_PDCLIB_PUBLIC int mbtowc( wchar_t * _PDCLIB_restrict pwc, const char * _PDCLIB_restrict s, size_t n ); +_PDCLIB_PUBLIC int wctomb( char * s, wchar_t wc ); +_PDCLIB_PUBLIC size_t mbstowcs( wchar_t * _PDCLIB_restrict pwcs, const char * _PDCLIB_restrict s, size_t n ); +_PDCLIB_PUBLIC size_t wcstombs( char * _PDCLIB_restrict s, const wchar_t * _PDCLIB_restrict pwcs, size_t n ); +*/ + +/* Annex K -- Bounds-checking interfaces */ + +#if ( __STDC_WANT_LIB_EXT1__ + 0 ) != 0 + +#ifndef _PDCLIB_ERRNO_T_DEFINED +#define _PDCLIB_ERRNO_T_DEFINED _PDCLIB_ERRNO_T_DEFINED +typedef int errno_t; +#endif + +#ifndef _PDCLIB_RSIZE_T_DEFINED +#define _PDCLIB_RSIZE_T_DEFINED _PDCLIB_RSIZE_T_DEFINED +typedef size_t rsize_t; +#endif + +/* A function type that can serve as a constraint handler (see below). The + first parameter is an error message on the constraint violation, the + second parameter a pointer to an implementation-defined object, the + third an error code related to the constraint violation. + If the function calling the constraint handler is defined to return + errno_t, the third parameter will be identical to the return value of + that function. + This implementation sets the second parameter of the constraint handler + call to NULL. +*/ +typedef void ( *constraint_handler_t )( const char * _PDCLIB_restrict msg, void * _PDCLIB_restrict ptr, errno_t error ); + +/* The currently active constraint violation handler. This implementation + sets abort_handler_s as the default constraint violation handler. +*/ +extern constraint_handler_t _PDCLIB_constraint_handler; + +/* Set the given function as the new constraint violation handler. */ +_PDCLIB_PUBLIC constraint_handler_t set_constraint_handler_s( constraint_handler_t handler ); + +/* One of two predefined constraint violation handlers. When called, it will + print an error message (including the message passed as the first + parameter to the handler function) and call abort(). +*/ +_PDCLIB_PUBLIC void abort_handler_s( const char * _PDCLIB_restrict msg, void * _PDCLIB_restrict ptr, errno_t error ); + +/* One of two predefined constraint violation handlers. Simply returns, + ignoring the constraint violation. +*/ +_PDCLIB_PUBLIC void ignore_handler_s( const char * _PDCLIB_restrict msg, void * _PDCLIB_restrict ptr, errno_t error ); + +/* Search an environment-provided key-value map for the given key name. + If the name is found, + - if len is not NULL, the length of the associated value string is stored + in that location. + - if len < maxsize, the value string is copied to the indicated location. + If the name is not found, + - if len is not NULL, a zero is stored in that location. + - if maxsize > 0, value[0] is set to the null character. + Details on the provided keys and how to set / change them are determined by + the hosting OS and its glue function. + The following conditions will be considered runtime constraint violations: + - value being a NULL pointer. + - maxsize == 0 or maxsize > RSIZE_MAX. + In case of a constraint violation, if len is not NULL a zero will be + stored at that location, and the environment key-value map not searched. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t getenv_s( size_t * _PDCLIB_restrict len, char * _PDCLIB_restrict value, rsize_t maxsize, const char * _PDCLIB_restrict name ); + +/* Do a binary search for a given key in the array with a given base pointer, + which consists of nmemb elements that are of the given size each. To compare + the given key with an element from the array, the given function compar is + called (with key as first parameter, a pointer to the array member as + second parameter, and the context parameter passed to bsearch_s() as third + parameter); the function should return a value less than, equal to, + or greater than 0 if the key is considered to be less than, equal to, or + greater than the array element, respectively. + The function returns a pointer to a matching element found, or NULL if no + match is found. + The following conditions will be considered runtime constraint violations: + - nmemb or size > RSIZE_MAX. + - nmemb > 0 and either of key, base, or compar being a null pointer. + In case of a constraint violation, the array will not be searched. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC void * bsearch_s( const void * key, const void * base, rsize_t nmemb, rsize_t size, int ( *compar )( const void * k, const void * y, void * context ), void * context ); + +/* Do a quicksort on an array with a given base pointer, which consists of + nmemb elements that are of the given size each. To compare two elements from + the array, the given function compar is called, with the first two arguments + being pointers to the two objects to be compared, and the third argument + being the context parameter passed to qsort_s. The compar function should + return a value less than, equal to, or greater than 0 if the first argument + is considered to be less than, equal to, or greater than the second argument, + respectively. If two elements are compared equal, their order in the sorted + array is not specified. + The following conditions will be considered runtime constraint violations: + - nmemb or size > RSIZE_MAX. + - nmemb > 0 and either of base or compar being a null pointer. + In case of a constraint violation, the array will not be sorted. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t qsort_s( void * base, rsize_t nmemb, rsize_t size, int ( *compar )( const void * x, const void * y, void * context ), void * context ); + +/* TODO: Multibyte / wide character functions */ + +#endif + +#ifdef __cplusplus +} +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDLIB_H +#include _PDCLIB_EXTEND_STDLIB_H +#endif + +#endif diff --git a/libc/include/stdnoreturn.h b/libc/include/stdnoreturn.h new file mode 100644 index 0000000..ab4ddb4 --- /dev/null +++ b/libc/include/stdnoreturn.h @@ -0,0 +1,26 @@ +/* _Noreturn + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDNORETURN_H +#define _PDCLIB_STDNORETURN_H _PDCLIB_STDNORETURN_H + +#include "pdclib/_PDCLIB_internal.h" + +/* This basically breaks the letter of the standard (which states that + noreturn be defined to _Noreturn). This defines noreturn -> _Noreturn + on C11 compliant compilers only (as older compilers do not know about + _Noreturn). +*/ +#define noreturn _PDCLIB_Noreturn + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDNORETURN_H +#include _PDCLIB_EXTEND_STDNORETURN_H +#endif + +#endif diff --git a/libc/include/string.h b/libc/include/string.h new file mode 100644 index 0000000..05062fd --- /dev/null +++ b/libc/include/string.h @@ -0,0 +1,394 @@ +/* String handling + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STRING_H +#define _PDCLIB_STRING_H _PDCLIB_STRING_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_lib_ext1.h" +#include "pdclib/_PDCLIB_internal.h" + +#ifndef _PDCLIB_SIZE_T_DEFINED +#define _PDCLIB_SIZE_T_DEFINED _PDCLIB_SIZE_T_DEFINED +typedef _PDCLIB_size_t size_t; +#endif + +#ifndef _PDCLIB_NULL_DEFINED +#define _PDCLIB_NULL_DEFINED _PDCLIB_NULL_DEFINED +#define NULL _PDCLIB_NULL +#endif + +/* String function conventions */ + +/* + In any of the following functions taking a size_t n to specify the length of + an array or size of a memory region, n may be 0, but the pointer arguments to + the call shall still be valid unless otherwise stated. +*/ + +/* Copying functions */ + +/* Copy a number of n characters from the memory area pointed to by s2 to the + area pointed to by s1. If the two areas overlap, behaviour is undefined. + Returns the value of s1. +*/ +_PDCLIB_PUBLIC void * memcpy( void * _PDCLIB_restrict s1, const void * _PDCLIB_restrict s2, size_t n ); + +/* Copy a number of n characters from the memory area pointed to by s2 to the + area pointed to by s1. The two areas may overlap. + Returns the value of s1. +*/ +_PDCLIB_PUBLIC void * memmove( void * _PDCLIB_restrict s1, const void * _PDCLIB_restrict s2, size_t n ); + +/* Copy the character array s2 (including terminating '\0' byte) into the + character array s1. + Returns the value of s1. +*/ +_PDCLIB_PUBLIC char * strcpy( char * _PDCLIB_restrict s1, const char * _PDCLIB_restrict s2 ); + +/* Copy a maximum of n characters from the character array s2 into the character + array s1. If s2 is shorter than n characters, '\0' bytes will be appended to + the copy in s1 until n characters have been written. If s2 is longer than n + characters, NO terminating '\0' will be written to s1. If the arrays overlap, + behaviour is undefined. + Returns the value of s1. +*/ +_PDCLIB_PUBLIC char * strncpy( char * _PDCLIB_restrict s1, const char * _PDCLIB_restrict s2, size_t n ); + +/* Concatenation functions */ + +/* Append the contents of the character array s2 (including terminating '\0') to + the character array s1 (first character of s2 overwriting the '\0' of s1). If + the arrays overlap, behaviour is undefined. + Returns the value of s1. +*/ +_PDCLIB_PUBLIC char * strcat( char * _PDCLIB_restrict s1, const char * _PDCLIB_restrict s2 ); + +/* Append a maximum of n characters from the character array s2 to the character + array s1 (first character of s2 overwriting the '\0' of s1). A terminating + '\0' is ALWAYS appended, even if the full n characters have already been + written. If the arrays overlap, behaviour is undefined. + Returns the value of s1. +*/ +_PDCLIB_PUBLIC char * strncat( char * _PDCLIB_restrict s1, const char * _PDCLIB_restrict s2, size_t n ); + +/* Comparison functions */ + +/* Compare the first n characters of the memory areas pointed to by s1 and s2. + Returns 0 if s1 == s2, a negative number if s1 < s2, and a positive number if + s1 > s2. +*/ +_PDCLIB_PUBLIC int memcmp( const void * s1, const void * s2, size_t n ); + +/* Compare the character arrays s1 and s2. + Returns 0 if s1 == s2, a negative number if s1 < s2, and a positive number if + s1 > s2. +*/ +_PDCLIB_PUBLIC int strcmp( const char * s1, const char * s2 ); + +/* Compare the character arrays s1 and s2, interpreted as specified by the + LC_COLLATE category of the current locale. + Returns 0 if s1 == s2, a negative number if s1 < s2, and a positive number if + s1 > s2. + TODO: Currently a dummy wrapper for strcmp() as PDCLib does not yet support + locales. +*/ +_PDCLIB_PUBLIC int strcoll( const char * s1, const char * s2 ); + +/* Compare no more than the first n characters of the character arrays s1 and + s2. + Returns 0 if s1 == s2, a negative number if s1 < s2, and a positive number if + s1 > s2. +*/ +_PDCLIB_PUBLIC int strncmp( const char * s1, const char * s2, size_t n ); + +/* Transform the character array s2 as appropriate for the LC_COLLATE setting of + the current locale. If length of resulting string is less than n, store it in + the character array pointed to by s1. Return the length of the resulting + string. +*/ +_PDCLIB_PUBLIC size_t strxfrm( char * _PDCLIB_restrict s1, const char * _PDCLIB_restrict s2, size_t n ); + +/* Search functions */ + +/* Search the first n characters in the memory area pointed to by s for the + character c (interpreted as unsigned char). + Returns a pointer to the first instance found, or NULL. +*/ +_PDCLIB_PUBLIC void * memchr( const void * s, int c, size_t n ); + +/* Search the character array s (including terminating '\0') for the character c + (interpreted as char). + Returns a pointer to the first instance found, or NULL. +*/ +_PDCLIB_PUBLIC char * strchr( const char * s, int c ); + +/* Determine the length of the initial substring of character array s1 which + consists only of characters not from the character array s2. + Returns the length of that substring. +*/ +_PDCLIB_PUBLIC size_t strcspn( const char * s1, const char * s2 ); + +/* Search the character array s1 for any character from the character array s2. + Returns a pointer to the first occurrence, or NULL. +*/ +_PDCLIB_PUBLIC char * strpbrk( const char * s1, const char * s2 ); + +/* Search the character array s (including terminating '\0') for the character c + (interpreted as char). + Returns a pointer to the last instance found, or NULL. +*/ +_PDCLIB_PUBLIC char * strrchr( const char * s, int c ); + +/* Determine the length of the initial substring of character array s1 which + consists only of characters from the character array s2. + Returns the length of that substring. +*/ +_PDCLIB_PUBLIC size_t strspn( const char * s1, const char * s2 ); + +/* Search the character array s1 for the substring in character array s2. + Returns a pointer to that sbstring, or NULL. If s2 is of length zero, + returns s1. +*/ +_PDCLIB_PUBLIC char * strstr( const char * s1, const char * s2 ); + +/* In a series of subsequent calls, parse a C string into tokens. + On the first call to strtok(), the first argument is a pointer to the to-be- + parsed C string. On subsequent calls, the first argument is NULL unless you + want to start parsing a new string. s2 holds an array of separator characters + which can differ from call to call. Leading separators are skipped, the first + trailing separator overwritten with '\0'. + Returns a pointer to the next token. + WARNING: This function uses static storage, and as such is not reentrant. +*/ +_PDCLIB_PUBLIC char * strtok( char * _PDCLIB_restrict s1, const char * _PDCLIB_restrict s2 ); + +/* Miscellaneous functions */ + +/* Write the character c (interpreted as unsigned char) to the first n + characters of the memory area pointed to by s. + Returns s. +*/ +_PDCLIB_PUBLIC void * memset( void * s, int c, size_t n ); + +/* Map an error number to a (locale-specific) error message string. Error + numbers are typically errno values, but any number is mapped to a message. + TODO: PDCLib does not yet support locales. +*/ +_PDCLIB_PUBLIC char * strerror( int errnum ); + +/* Returns the length of the string s (excluding terminating '\0'). +*/ +_PDCLIB_PUBLIC size_t strlen( const char * s ); + +/* Annex K -- Bounds-checking interfaces */ + +#if ( __STDC_WANT_LIB_EXT1__ + 0 ) != 0 + +#ifndef _PDCLIB_ERRNO_T_DEFINED +#define _PDCLIB_ERRNO_T_DEFINED _PDCLIB_ERRNO_T_DEFINED +typedef int errno_t; +#endif + +#ifndef _PDCLIB_RSIZE_T_DEFINED +#define _PDCLIB_RSIZE_T_DEFINED _PDCLIB_RSIZE_T_DEFINED +typedef _PDCLIB_size_t rsize_t; +#endif + +/* Copy a number of n characters from the memory area pointed to by s2 to the + area pointed to by s1 of size s1max. + Returns zero if successful, non-zero otherwise. + The following conditions will be considered runtime constraint violations: + - s1 or s2 being NULL. + - s1max or n being > RSIZE_MAX. + - n > s1max (not enough space in s1). + - copying between overlapping objects. + In case of a constraint violation, if s1 is not NULL and s1max <= RSIZE_MAX + then the first s1max characters of s1 will be set to zero. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t memcpy_s( void * _PDCLIB_restrict s1, rsize_t s1max, const void * _PDCLIB_restrict s2, rsize_t n ); + +/* Copy a number of n characters from the memory area pointed to by s2 to the + area pointed to by s1 of size s1max. The two areas may overlap. + Returns zero if successful, non-zero otherwise. + The following conditions will be considered runtime constraint violations: + - s1 or s2 being NULL. + - s1max or n being > RSIZE_MAX. + - n > s1max (not enough space in s1). + In case of a constraint violation, if s1 is not NULL and s1max <= RSIZE_MAX + then the first s1max characters of s1 will be set to zero. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t memmove_s( void * _PDCLIB_restrict s1, rsize_t s1max, const void * _PDCLIB_restrict s2, rsize_t n ); + +/* Copy the character array s2 (including terminating '\0' byte) into the + character array s1. + Returns zero if successful, non-zero otherwise. + The following conditions will be considered runtime constraint violations: + - s1 or s2 being NULL. + - s1max being zero or > RSIZE_MAX. + - s1max not greater than strnlen_s( s2, s1max ) (not enough space in s1). + - copying between overlapping objects. + In case of a constraint violation, if s1 is not NULL and s1max <= RSIZE_MAX + then s1[0] will be set to '\0'. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t strcpy_s( char * _PDCLIB_restrict s1, rsize_t s1max, const char * _PDCLIB_restrict s2 ); + +/* Copy a maximum of n characters from the character array s2 into the character + array s1. If s2 is longer than n, s1[n] will be set to '\0'. + Returns zero if successful, non-zero otherwise. + + ATTENTION ATTENTION ATTENTION + + This function differs in two fundamental ways from strncpy(): + - remaining space in s1 will NOT be zeroed. Their value is unspecified. + - s1 WILL be zero-terminated even if there is not enough space to hold + all n characters from s2. + + THANK YOU FOR YOUR ATTENTION. + + The following conditions will be considered runtime constraint violations: + - s1 or s2 being NULL. + - s1max or n being > RSIZE_MAX. + - s1max being zero. + - n >= s1max and s1max <= strnlen_s( s2, s1max ) (not enough space in s1). + - copying between overlapping objects. + In case of a constraint violation, if s1 is not NULL and s1max is greater + zero and <= RSIZE_MAX, s1[0] will be set to '\0'. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t strncpy_s( char * _PDCLIB_restrict s1, rsize_t s1max, const char * _PDCLIB_restrict s2, rsize_t n ); + +/* Append the contents of the character array s2 (including terminating '\0') to + the character array s1 (first character of s2 overwriting the '\0' of s1). + Elements following the terminating null character (if any) take unspecified + values. + Returns zero if successful, non-zero otherwise. + The following conditions will be considered runtime constraint violations: + - s1 or s2 being NULL. + - s1max being > RSIZE_MAX. + - s1max being zero. + - not enough space in s1 for both s1 and the characters copied from s2. + - copying between overlapping objects. + In case of a constraint violation, if s1 is not NULL and s1max is greater + zero and <= RSIZE_MAX, s1[0] will be set to '\0'. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t strcat_s( char * _PDCLIB_restrict s1, rsize_t s1max, const char * _PDCLIB_restrict s2 ); + +/* Append a maximum of n characters from the character array s2 to the + character array s1 (first character of s2 overwriting the '\0' of s1). A + terminating '\0' is ALWAYS appended, even if the full n characters have + already been written. + Elements following the terminating null character (if any) take unspecified + values. + Returns zero if successful, non-zero otherwise. + The following conditions will be considered runtime constraint violations: + - s1 or s2 being NULL. + - s1max or n being > RSIZE_MAX. + - s1max being zero. + - not enough space in s1 for both s1 and the characters copied from s2. + - copying between overlapping objects. + In case of a constraint violation, if s1 is not NULL and s1max is greater + zero and <= RSIZE_MAX, s1[0] will be set to '\0'. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t strncat_s( char * _PDCLIB_restrict s1, rsize_t s1max, const char * _PDCLIB_restrict s2, rsize_t n ); + +/* In a series of subsequent calls, parse a C string into tokens. + On the first call to strtok(), the first argument is a pointer to the to-be- + parsed C string of size *s1max. On subsequent calls, the first argument is + NULL unless you want to start parsing a new string. s2 holds an array of + separator characters which can differ from call to call. Leading separators + are skipped, the first trailing separator overwritten with '\0'. + Returns a pointer to the next token. + The following conditions will be considered runtime constraint violations: + - s1max, s2, or ptr being NULL. + - s1max or n being > RSIZE_MAX. + - s1max being zero. + - not enough space in s1 for both s1 and the characters copied from s2. + - copying between overlapping objects. + In case of a constraint violation, if s1 is not NULL and s1max is greater + zero and <= RSIZE_MAX, s1[0] will be set to '\0'. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC char * strtok_s( char * _PDCLIB_restrict s1, rsize_t * _PDCLIB_restrict s1max, const char * _PDCLIB_restrict s2, char ** _PDCLIB_restrict ptr ); + +/* Write the character c (interpreted as unsigned char) to the first n + characters of the memory area pointed to by s of size smax. + Returns zero if successful, non-zero otherwise. + The following conditions will be considered runtime constraint violations: + - s being NULL. + - smax or n being > RSIZE_MAX. + - n being > smax. + In case of a constraint violation, if s is not NULL and smax is <= RSIZE_MAX + the value of c (interpreted as unsigned char) is written to the first smax + characters of s. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t memset_s( void * s, rsize_t smax, int c, rsize_t n ); + +/* Map an error number to a (locale-specific) error message string. Error + numbers are typically errno values, but any number is mapped to a message. + TODO: PDCLib does not yet support locales. + If the length of the mapped string is < maxsize, the string is copied to s. + Otherwise, if maxsize is greater than zero, as much of the string as does + fit is copied, and s[maxsize-1] set to '\0'. If maxsize is greater than 3, + the partial string is made to end in "...". + Returns zero if the string was copied successfully in full, non-zero + otherwise. + The following conditions will be considered runtime constraint violations: + - s being NULL. + - maxsize being zero or > RSIZE_MAX. + In case of a constraint violation, s is not modified. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t strerror_s( char * s, rsize_t maxsize, errno_t errnum ); + +/* Map an error number to a (locale-specific) error message string, the same + way as strerror_s() would do. Error numbers are typically errno values, + but any number is mapped to a message. + TODO: PDCLib does not yet support locales. + Returns the length of the mapped string. +*/ +_PDCLIB_PUBLIC size_t strerrorlen_s( errno_t errnum ); + +/* Returns the length of the string s (excluding terminating '\0'). + If there is no null character in the first maxsize characters of s, + rerturns maxsize. If s is NULL, returns zero. + At most the first maxsize characters of s shall be accessed by the + function. +*/ +_PDCLIB_PUBLIC size_t strnlen_s( const char * s, size_t maxsize ); + +#endif + +#ifdef __cplusplus +} +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STRING_H +#include _PDCLIB_EXTEND_STRING_H +#endif + +#endif diff --git a/libc/include/threads.h b/libc/include/threads.h new file mode 100644 index 0000000..7de037d --- /dev/null +++ b/libc/include/threads.h @@ -0,0 +1,260 @@ +/* Threads + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +/* This header does not include any platform-specific information, but as + a whole is optional (depending on whether threads are supported or not, + ref. __STDC_NO_THREADS__), which is why it is located in the example + platform instead of the general include directory. +*/ + +#ifndef _PDCLIB_THREADS_H +#define _PDCLIB_THREADS_H _PDCLIB_THREADS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#if __STDC_NO_THREADS__ == 1 +#error __STDC_NO_THREADS__ defined but included. Something is wrong about your setup. +#endif + +#if __STDC_VERSION__ >= 201112L +/* The rest of can work with a pre-C11 compiler just fine. */ +#define thread_local _Thread_local +#endif + +/* Initializing value for an object of type once_flag */ +#define ONCE_FLAG_INIT _PDCLIB_ONCE_FLAG_INIT + +/* Maximum number of times destructors are called on thread termination */ +#define TSS_DTOR_ITERATIONS _PDCLIB_TSS_DTOR_ITERATIONS + +/* Condition variable */ +typedef _PDCLIB_cnd_t cnd_t; + +/* Thread */ +typedef _PDCLIB_thrd_t thrd_t; + +/* Thread-specific storage */ +typedef _PDCLIB_tss_t tss_t; + +/* Mutex */ +typedef _PDCLIB_mtx_t mtx_t; + +/* TSS destructor */ +typedef void ( *tss_dtor_t )( void * ); + +/* Thread start function */ +typedef int ( *thrd_start_t )( void * ); + +/* Flag for use with call_once() */ +typedef _PDCLIB_once_flag once_flag; + +/* TODO: Documentation. */ +enum +{ + mtx_plain, + mtx_recursive, + mtx_timed +}; + +/* TODO: Documentation. */ +enum +{ + thrd_timedout, + thrd_success, + thrd_busy, + thrd_error, + thrd_nomem +}; + +/* Initialization functions */ + +/* Ensure that func is called only the first time call_once() is called + for a given flag. +*/ +_PDCLIB_PUBLIC void call_once( once_flag * flag, void ( *func )( void ) ); + +/* Condition variable functions */ + +/* Unblock threads waiting on given condition. + Returns thrd_success if successful, thrd_error if the request could not + be honored. +*/ +_PDCLIB_PUBLIC int cnd_broadcast( cnd_t * cond ); + +/* Destroy condition variable. + No threads may be waiting on a condition when it is destroyed. +*/ +_PDCLIB_PUBLIC void cnd_destroy( cnd_t * cond ); + +/* Initialize condition variable. + Returns thrd_success if successful, thrd_nomem if out of memory, and + thrd_error if the request could not be honored. + Initializes the variable in a way that a thread calling cnd_wait() on it + would block. +*/ +_PDCLIB_PUBLIC int cnd_init( cnd_t * cond ); + +/* Unblock one thread waiting on the condition variable. + Returns thrd_success if successful, thrd_error if the request could not + be honored. +*/ +_PDCLIB_PUBLIC int cnd_signal( cnd_t * cond ); + +/* TODO: Documentation. + Returns thrd_success if successful, thrd_timedout if the specified time + is reached without acquiring the resource, or thrd_error if the request + could not be honored. +*/ +_PDCLIB_PUBLIC int cnd_timedwait( cnd_t * _PDCLIB_restrict cond, mtx_t * _PDCLIB_restrict mtx, const struct timespec * _PDCLIB_restrict ts ); + +/* TODO: Documentation. + Returns thrd_success if successful, thrd_error if the request could not + be honored. +*/ +int cnd_wait( cnd_t * cond, mtx_t * mtx ); + +/* Mutex functions */ + +/* Destroy mutex variable. + No threads may be waiting on a mutex when it is destroyed. +*/ +_PDCLIB_PUBLIC void mtx_destroy( mtx_t * mtx ); + +/* Initialize mutex variable. + Returns thrd_success if successful, thrd_error if the request could not + be honored. + Type must have one of the following values: + mtx_plain -- non-recursive mutex + mtx_timed -- non-recursive mutex supporting timeout + mtx_plain | mtx_recursive -- recursive mutex + mtx_timed | mtx_recursive -- recursive mutex supporting timeout +*/ +_PDCLIB_PUBLIC int mtx_init( mtx_t * mtx, int type ); + +/* Try to lock the given mutex (blocking). + Returns thrd_success if successful, thrd_error if the request could not + be honored. + If the given mutex is non-recursive, it must not be already locked by + the calling thread. +*/ +_PDCLIB_PUBLIC int mtx_lock( mtx_t * mtx ); + +/* TODO: Documentation. + Returns thrd_success if successful, thrd_timedout if the specified time + is reached without acquiring the resource, or thrd_error if the request + could not be honored. +*/ +_PDCLIB_PUBLIC int mtx_timedlock( mtx_t * _PDCLIB_restrict mtx, const struct timespec * _PDCLIB_restrict ts ); + +/* Try to lock the given mutex (non-blocking). + Returns thrd_success if successful, thrd_busy if the resource is already + locked, or thrd_error if the request could not be honored. +*/ +_PDCLIB_PUBLIC int mtx_trylock( mtx_t * mtx ); + +/* Unlock the given mutex. + Returns thrd_success if successful, thrd_error if the request could not + be honored. + The given mutex must be locked by the calling thread. +*/ +_PDCLIB_PUBLIC int mtx_unlock( mtx_t * mtx ); + +/* Thread functions */ + +/* Create a new thread. + Returns thrd_success if successful, thrd_nomem if out of memory, and + thrd_error if the request could not be honored. + Create a new thread executing func( arg ), and sets thr to identify + the created thread. (Identifiers may be reused afer a thread exited + and was either detached or joined.) +*/ +_PDCLIB_PUBLIC int thrd_create( thrd_t * thr, thrd_start_t func, void * arg ); + +/* Identify the calling thread. + Returns the identifier of the calling thread. +*/ +_PDCLIB_PUBLIC thrd_t thrd_current( void ); + +/* Notify the OS to destroy all resources of a given thread once it + terminates. + Returns thrd_success if successful, thrd_error if the request could not + be honored. + The given thread must not been previously detached or joined. +*/ +_PDCLIB_PUBLIC int thrd_detach( thrd_t thr ); + +/* Compare two thread identifiers for equality. + Returns nonzero if both parameters identify the same thread, zero + otherwise. +*/ +_PDCLIB_PUBLIC int thrd_equal( thrd_t thr0, thrd_t thr1 ); + +/* Terminate calling thread, set result code to res. + When the last thread of a program terminates the program shall terminate + normally as if by exit( EXIT_SUCCESS ). + FIXME: The result code setting is NOT implemented correctly at this point. + The value is indeterminate. +*/ +_PDCLIB_PUBLIC _PDCLIB_Noreturn void thrd_exit( int res ) _PDCLIB_NORETURN; + +/* Join the given thread with the calling thread. + Returns thrd_success if successful, thrd_error if the request could not + be honored. + Function blocks until the given thread terminates. If res is not NULL, + the given thread's result code will be stored at that address. +*/ +_PDCLIB_PUBLIC int thrd_join( thrd_t thr, int * res ); + +/* Suspend the calling thread for the given duration or until a signal not + being ignored is received. + Returns zero if the requested time has elapsed, -1 if interrupted by a + signal, negative if the request failed. + If remaining is not NULL, and the sleeping thread received a signal that + is not being ignored, the remaining time (duration minus actually elapsed + time) shall be stored at that address. +*/ +_PDCLIB_PUBLIC int thrd_sleep( const struct timespec * duration, struct timespec * remaining ); + +/* Permit other threads to run. */ +_PDCLIB_PUBLIC void thrd_yield( void ); + +/* Thread-specific storage functions */ + +/* Initialize thread-specific storage, with optional destructor + Returns thrd_success if successful, thrd_error otherwise (in this case + key is set to an undefined value). +*/ +_PDCLIB_PUBLIC int tss_create( tss_t * key, tss_dtor_t dtor ); + +/* Release all resources of a given thread-specific storage. */ +_PDCLIB_PUBLIC void tss_delete( tss_t key ); + +/* Returns the value for the current thread associated with the given key. +*/ +_PDCLIB_PUBLIC void * tss_get( tss_t key ); + +/* Sets the value associated with the given key for the current thread. + Returns thrd_success if successful, thrd_error if the request could not + be honored. +*/ +_PDCLIB_PUBLIC int tss_set( tss_t key, void * val ); + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_THREADS_H +#include _PDCLIB_EXTEND_THREADS_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/time.h b/libc/include/time.h new file mode 100644 index 0000000..17ae34f --- /dev/null +++ b/libc/include/time.h @@ -0,0 +1,191 @@ +/* Date and time + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_TIME_H +#define _PDCLIB_TIME_H _PDCLIB_TIMEH + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_lib_ext1.h" +#include "pdclib/_PDCLIB_internal.h" + +#ifndef _PDCLIB_SIZE_T_DEFINED +#define _PDCLIB_SIZE_T_DEFINED _PDCLIB_SIZE_T_DEFINED +typedef _PDCLIB_size_t size_t; +#endif + +#ifndef _PDCLIB_NULL_DEFINED +#define _PDCLIB_NULL_DEFINED _PDCLIB_NULL_DEFINED +#define NULL _PDCLIB_NULL +#endif + +/* See comments in _PDCLIB_config.h on the semantics of time_t and clock_t. */ + +typedef _PDCLIB_time_t time_t; +typedef _PDCLIB_clock_t clock_t; + +#define CLOCKS_PER_SEC _PDCLIB_CLOCKS_PER_SEC +#define TIME_UTC _PDCLIB_TIME_UTC + +/* Implementor's note: If you change this structure, and are using Pthread + threading support, check auxiliary/pthread/pthread_readout.c for its + twin. It is imperative that Pthread and PDCLib use identical layouts for + struct timespec, as they are implicitly cast from one to the other. This + cannot be checked for in this header (as we may not include host system + headers here), so the assert()s are in pthread_readout.c (which, in turn, + cannot include *this* header, which is why this admonishment to keep the + definitions in sync exists...). +*/ +struct timespec +{ + time_t tv_sec; + long tv_nsec; +}; + +struct tm +{ + int tm_sec; /* 0-60 */ + int tm_min; /* 0-59 */ + int tm_hour; /* 0-23 */ + int tm_mday; /* 1-31 */ + int tm_mon; /* 0-11 */ + int tm_year; /* years since 1900 */ + int tm_wday; /* 0-6 */ + int tm_yday; /* 0-365 */ + int tm_isdst; /* >0 DST, 0 no DST, <0 information unavailable */ +}; + +/* Returns the number of "clocks" in processor time since the invocation + of the program. Divide by CLOCKS_PER_SEC to get the value in seconds. + Returns -1 if the value cannot be represented in the return type or is + not available. +*/ +_PDCLIB_PUBLIC clock_t clock( void ); + +/* Returns the difference between two calendar times in seconds. */ +_PDCLIB_PUBLIC double difftime( time_t time1, time_t time0 ); + +/* Normalizes the values in the broken-down time pointed to by timeptr. + Returns the calender time specified by the broken-down time. +*/ +_PDCLIB_PUBLIC time_t mktime( struct tm * timeptr ); + +/* Returns the current calender time. If timer is not a NULL pointer, stores + the current calender time at that address as well. +*/ +_PDCLIB_PUBLIC time_t time( time_t * timer ); + +/* Sets the interval pointed to by ts to the current calender time, based + on the specified base. + Returns base, if successful, otherwise zero. +*/ +_PDCLIB_PUBLIC int timespec_get( struct timespec * ts, int base ); + +/* Converts the broken-down time pointed to by timeptr into a string in the + form "Sun Sep 16 01:03:52 1973\n\0". +*/ +_PDCLIB_PUBLIC char * asctime( const struct tm * timeptr ); + +/* Equivalent to asctime( localtime( timer ) ). */ +_PDCLIB_PUBLIC char * ctime( const time_t * timer ); + +/* Converts the calender time pointed to by timer into a broken-down time + expressed as UTC. + Returns a pointer to the broken-down time, or a NULL pointer if it + cannot be represented. +*/ +_PDCLIB_PUBLIC struct tm * gmtime( const time_t * timer ); + +/* Converts the calender time pointed to by timer into a broken-down time + expressed as local time. + Returns a pointer to the broken-down time, or a NULL pointer if if + cannot be represented. +*/ +_PDCLIB_PUBLIC struct tm * localtime( const time_t * timer ); + +/* Writes the broken-down time pointed to by timeptr into the character + array pointed to by s. The string pointed to by format controls the + exact output. No more than maxsize charactrs will be written. + Returns the number of characters written (excluding the terminating + null character), or zero on failure. +*/ +_PDCLIB_PUBLIC size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB_restrict format, const struct tm * _PDCLIB_restrict timeptr ); + +/* Annex K -- Bounds-checking interfaces */ + +#if ( __STDC_WANT_LIB_EXT1__ + 0 ) != 0 + +#ifndef _PDCLIB_ERRNO_T_DEFINED +#define _PDCLIB_ERRNO_T_DEFINED _PDCLIB_ERRNO_T_DEFINED +typedef int errno_t; +#endif + +#ifndef _PDCLIB_RSIZE_T_DEFINED +#define _PDCLIB_RSIZE_T_DEFINED _PDCLIB_RSIZE_T_DEFINED +typedef _PDCLIB_size_t rsize_t; +#endif + +/* Converts the broken-down time pointed to by timeptr into a string in the + form "Sun Sep 16 01:03:52 1973\n\0", which is stored in buffer s of maxsize. + Returns zero if the time was successfully converted and stored, non-zero + otherwise. + The following conditions will be considered runtime constraint violations: + - s or timeptr being NULL. + - maxsize being < 26 or > RSIZE_MAX. + - the broken-down time pointed to by timeptr not being normalized. + - the year represented by the broken-down time pointed to by timeptr + being < 0 or > 9999. + In case of a constraint violation, the time will not be converted. If + s is not NULL and maxsize is neither zero nor > RSIZE_MAX, s[0] will be + set to '\0'. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t asctime_s( char * s, rsize_t maxsize, const struct tm * timeptr ); + +/* Equivalent to asctime_s( s, maxsize, localtime( timer ) ). */ +_PDCLIB_PUBLIC errno_t ctime_s( char * s, rsize_t maxsize, const time_t * timer ); + +/* Converts the calender time pointed to by timer into a broken-down time + expressed as UTC, which gets stored in the result struct. + Returns a pointer to the broken-down time, or a NULL pointer if if + cannot be represented or stored. + The following conditions will be considered runtime constraint violations: + - timer or result being NULL. + In case of a constraint violation, the time will not be converted. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC struct tm * gmtime_s( const time_t * _PDCLIB_restrict timer, struct tm * _PDCLIB_restrict result ); + +/* Converts the calender time pointed to by timer into a broken-down time + expressed as local time, which gets stored in the result struct. + Returns a pointer to the broken-down time, or a NULL pointer if if + cannot be represented or stored. + The following conditions will be considered runtime constraint violations: + - timer or result being NULL. + In case of a constraint violation, the time will not be converted. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC struct tm * localtime_s( const time_t * _PDCLIB_restrict timer, struct tm * _PDCLIB_restrict result ); + +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_TIME_H +#include _PDCLIB_EXTEND_TIME_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/wctype.h b/libc/include/wctype.h new file mode 100644 index 0000000..3776b7a --- /dev/null +++ b/libc/include/wctype.h @@ -0,0 +1,152 @@ +/* Wide character classification and mapping utilities + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_WCTYPE_H +#define _PDCLIB_WCTYPE_H _PDCLIB_WCTYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_internal.h" + +typedef _PDCLIB_wint_t wint_t; +typedef int wctrans_t; +typedef int wctype_t; + +#ifndef _PDCLIB_WEOF_DEFINED +#define _PDCLIB_WEOF_DEFINED _PDCLIB_WEOF_DEFINED +#define WEOF (wint_t)-1 +#endif + +/* Wide character classification functions */ + +/* Returns iswalpha( wc ) || iswdigit( wc ) */ +_PDCLIB_PUBLIC int iswalnum( wint_t wc ); + +/* Returns true for wide characters for which either isupper( wc ) or + islower( wc ) is true, as well as a set of locale-specific wide + characters which are neither control characters, digits, punctuation, + or whitespace. +*/ +_PDCLIB_PUBLIC int iswalpha( wint_t wc ); + +/* Returns true if the character iswspace() and used for separating words + within a line of text. In the "C" locale, only L' ' and L'\t' are + considered blanks. +*/ +_PDCLIB_PUBLIC int iswblank( wint_t wc ); + +/* Returns true if the wide character is a control character. */ +_PDCLIB_PUBLIC int iswcntrl( wint_t wc ); + +/* Returns true if the wide character is a decimal digit. Locale- + independent. */ +_PDCLIB_PUBLIC int iswdigit( wint_t wc ); + +/* Returns iswprint( wc ) && ! iswspace( wc ). + NOTE: This definition differs from that of isgraph() in , + which considers only ' ', not all isspace() characters. +*/ +_PDCLIB_PUBLIC int iswgraph( wint_t wc ); + +/* Returns true for lowerspace wide characters, as well as a set of + locale-specific wide characters which are neither control charcters, + digits, punctuation, or whitespace. +*/ +_PDCLIB_PUBLIC int iswlower( wint_t wc ); + +/* Returns true for every printing wide character. */ +_PDCLIB_PUBLIC int iswprint( wint_t wc ); + +/* Returns true for a locale-specific set of punctuation characters that + are neither whitespace nor alphanumeric. +*/ +_PDCLIB_PUBLIC int iswpunct( wint_t wc ); + +/* Returns true for a locale-specific set of whitespace characters that + are neither alphanumeric, graphic, or punctuation. +*/ +_PDCLIB_PUBLIC int iswspace( wint_t wc ); + +/* Returns true for upperspace wide characters, as well as a set of + locale-specific wide characters which are neither control charcters, + digits, punctuation, or whitespace. +*/ +_PDCLIB_PUBLIC int iswupper( wint_t wc ); + +/* Returns true if the wide character is a hexadecimal digit. Locale- + independent. */ +_PDCLIB_PUBLIC int iswxdigit( wint_t wc ); + +/* Extensible wide character classification functions */ + +/* Returns true if the wide character wc has the property described by + desc (which was retrieved by a previous call to wctype() without + changing the LC_CTYPE locale setting between the two calls). +*/ +_PDCLIB_PUBLIC int iswctype( wint_t wc, wctype_t desc ); + +/* Returns a description object for a named character property, to be + used as parameter to the iswctype() function. Supported property + names are: + "alnum" -- alphanumeric, as per iswalnum() + "alpha" -- alphabetic, as per iswalpha() + "blank" -- blank, as per iswblank() + "cntrl" -- control, as per iswcntrl() + "digit" -- decimal digit, as per iswdigit() + "graph" -- graphic, as per iswgraph() + "lower" -- lowercase, as per iswlower() + "print" -- printing, as per iswprint() + "punct" -- punctuation, as per iswprint() + "space" -- whitespace, as per iswspace() + "upper" -- uppercase, as per iswupper() + "xdigit" -- hexadecimal digit, as per iswxdigit() + For unsupported properties, the function returns zero. +*/ +_PDCLIB_PUBLIC wctype_t wctype( const char * property ); + +/* Wide character case mapping utilities */ + +/* Converts an uppercase letter to a corresponding lowercase letter. Input for + which no corresponding lowercase letter exists remains unchanged. +*/ +_PDCLIB_PUBLIC wint_t towlower( wint_t wc ); + +/* Converts a lowercase letter to a corresponding uppercase letter. Input for + which no corresponding uppercase letter exists remains unchanged. +*/ +_PDCLIB_PUBLIC wint_t towupper( wint_t wc ); + +/* Extensible wide character case mapping utilities */ + +/* Converts the wide character wc according to the transition described + by desc (which was retrieved by a previous call to wctrans() without + changing the LC_CTYPE locale setting between the two calls). +*/ +_PDCLIB_PUBLIC wint_t towctrans( wint_t wc, wctrans_t desc ); + +/* Returns a description object for a named character transformation, to + be used as parameter to the towctrans() function. Supported transformation + properties are: + "tolower" -- lowercase mapping, as per towlower() + "toupper" -- uppercase mapping, as per towupper() + For unsupported properties, the function returns zero. +*/ +_PDCLIB_PUBLIC wctrans_t wctrans( const char * property ); + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_WCTYPE_H +#include _PDCLIB_EXTEND_WCTYPE_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/memcmp.c b/libc/memcmp.c new file mode 100644 index 0000000..2c9fdfe --- /dev/null +++ b/libc/memcmp.c @@ -0,0 +1,46 @@ +/* memcmp( const void *, const void *, size_t ) + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#include + +#ifndef REGTEST + +int memcmp( const void * s1, const void * s2, size_t n ) +{ + const unsigned char * p1 = ( const unsigned char * ) s1; + const unsigned char * p2 = ( const unsigned char * ) s2; + + while ( n-- ) + { + if ( *p1 != *p2 ) + { + return *p1 - *p2; + } + + ++p1; + ++p2; + } + + return 0; +} + +#endif + +#ifdef TEST + +#include "_PDCLIB_test.h" + +int main( void ) +{ + const char xxxxx[] = "xxxxx"; + TESTCASE( memcmp( abcde, abcdx, 5 ) < 0 ); + TESTCASE( memcmp( abcde, abcdx, 4 ) == 0 ); + TESTCASE( memcmp( abcde, xxxxx, 0 ) == 0 ); + TESTCASE( memcmp( xxxxx, abcde, 1 ) > 0 ); + return 0; +} + +#endif diff --git a/libc/strncat.c b/libc/strncat.c new file mode 100644 index 0000000..b39145a --- /dev/null +++ b/libc/strncat.c @@ -0,0 +1,64 @@ +/* strncat( char *, const char *, size_t ) + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#include + +#ifndef REGTEST + +char * strncat( char * s1, const char * s2, size_t n ) +{ + char * rc = s1; + + while ( *s1 ) + { + ++s1; + } + + while ( n && ( *s1++ = *s2++ ) ) + { + --n; + } + + if ( n == 0 ) + { + *s1 = '\0'; + } + + return rc; +} + +#endif + +#ifdef TEST + +#include "_PDCLIB_test.h" + +int main( void ) +{ + char s[] = "xx\0xxxxxx"; + TESTCASE( strncat( s, abcde, 10 ) == s ); + TESTCASE( s[2] == 'a' ); + TESTCASE( s[6] == 'e' ); + TESTCASE( s[7] == '\0' ); + TESTCASE( s[8] == 'x' ); + s[0] = '\0'; + TESTCASE( strncat( s, abcdx, 10 ) == s ); + TESTCASE( s[4] == 'x' ); + TESTCASE( s[5] == '\0' ); + TESTCASE( strncat( s, "\0", 10 ) == s ); + TESTCASE( s[5] == '\0' ); + TESTCASE( s[6] == 'e' ); + TESTCASE( strncat( s, abcde, 0 ) == s ); + TESTCASE( s[5] == '\0' ); + TESTCASE( s[6] == 'e' ); + TESTCASE( strncat( s, abcde, 3 ) == s ); + TESTCASE( s[5] == 'a' ); + TESTCASE( s[7] == 'c' ); + TESTCASE( s[8] == '\0' ); + return TEST_RESULTS; +} + +#endif diff --git a/libc/strncmp.c b/libc/strncmp.c new file mode 100644 index 0000000..6b35763 --- /dev/null +++ b/libc/strncmp.c @@ -0,0 +1,55 @@ +/* strncmp( const char *, const char *, size_t ) + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#include + +#ifndef REGTEST + +int strncmp( const char * s1, const char * s2, size_t n ) +{ + while ( n && *s1 && ( *s1 == *s2 ) ) + { + ++s1; + ++s2; + --n; + } + + if ( n == 0 ) + { + return 0; + } + else + { + return ( *( unsigned char * )s1 - * ( unsigned char * )s2 ); + } +} + +#endif + +#ifdef TEST + +#include "_PDCLIB_test.h" + +int main( void ) +{ + char cmpabcde[] = "abcde\0f"; + char cmpabcd_[] = "abcde\xfc"; + char empty[] = ""; + char x[] = "x"; + TESTCASE( strncmp( abcde, cmpabcde, 5 ) == 0 ); + TESTCASE( strncmp( abcde, cmpabcde, 10 ) == 0 ); + TESTCASE( strncmp( abcde, abcdx, 5 ) < 0 ); + TESTCASE( strncmp( abcdx, abcde, 5 ) > 0 ); + TESTCASE( strncmp( empty, abcde, 5 ) < 0 ); + TESTCASE( strncmp( abcde, empty, 5 ) > 0 ); + TESTCASE( strncmp( abcde, abcdx, 4 ) == 0 ); + TESTCASE( strncmp( abcde, x, 0 ) == 0 ); + TESTCASE( strncmp( abcde, x, 1 ) < 0 ); + TESTCASE( strncmp( abcde, cmpabcd_, 10 ) < 0 ); + return TEST_RESULTS; +} + +#endif diff --git a/libc/strncpy.c b/libc/strncpy.c new file mode 100644 index 0000000..2a89fb2 --- /dev/null +++ b/libc/strncpy.c @@ -0,0 +1,59 @@ +/* strncpy( char *, const char *, size_t ) + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#include + +#ifndef REGTEST + +char * strncpy( char * s1, const char * s2, size_t n ) +{ + char * rc = s1; + + while ( n && ( *s1++ = *s2++ ) ) + { + /* Cannot do "n--" in the conditional as size_t is unsigned and we have + to check it again for >0 in the next loop below, so we must not risk + underflow. + */ + --n; + } + + /* Checking against 1 as we missed the last --n in the loop above. */ + while ( n-- > 1 ) + { + *s1++ = '\0'; + } + + return rc; +} + +#endif + +#ifdef TEST + +#include "_PDCLIB_test.h" + +int main( void ) +{ + char s[] = "xxxxxxx"; + TESTCASE( strncpy( s, "", 1 ) == s ); + TESTCASE( s[0] == '\0' ); + TESTCASE( s[1] == 'x' ); + TESTCASE( strncpy( s, abcde, 6 ) == s ); + TESTCASE( s[0] == 'a' ); + TESTCASE( s[4] == 'e' ); + TESTCASE( s[5] == '\0' ); + TESTCASE( s[6] == 'x' ); + TESTCASE( strncpy( s, abcde, 7 ) == s ); + TESTCASE( s[6] == '\0' ); + TESTCASE( strncpy( s, "xxxx", 3 ) == s ); + TESTCASE( s[0] == 'x' ); + TESTCASE( s[2] == 'x' ); + TESTCASE( s[3] == 'd' ); + return TEST_RESULTS; +} + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..0e16eac --- /dev/null +++ b/main.c @@ -0,0 +1,67 @@ + +#include "main.h" + +#include +#include "runtime/panic_assert.h" +#include "runtime/stdio.h" +#include "graphics/graphics.h" +#include "graphics/unifont.h" +#include "memory/memory.h" + + +FASTCALL_ABI EFI_STATUS efiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { + SystemTable->ConOut->EnableCursor(SystemTable->ConOut, TRUE); + + efiImageHandle = ImageHandle; + efiSystemTable = SystemTable; + efiBootServices = SystemTable->BootServices; + efiStdin = SystemTable->ConIn; + efiStdout = SystemTable->ConOut; + efiStderr = SystemTable->StdErr; + + // disable the watchdog timer so that the application does not reset after 5mins + efiBootServices->SetWatchdogTimer(0, 0, 0, NULL); + + io_WriteConsole(PROJECT_NAME "\r\n\r\nAWAITING FOR USER INPUT"); + + // wait for a user keypress + UINTN index; + SystemTable->BootServices->WaitForEvent(1, &SystemTable->ConIn->WaitForKey, &index); + EFI_INPUT_KEY key; + SystemTable->ConIn->ReadKeyStroke(SystemTable->ConIn, &key); + io_WriteConsole("...Key Pressed\r\n"); + io_Printf("Pressed key: %d, %d\r\n", (int)key.ScanCode, (int)key.UnicodeChar); + + + graphics_Init(); + io_PauseForKeystroke(); + + graphics_ClearBuffer(&HelosGraphics_Color_Black); + graphics_FillPixel(0, 0, 20, 20, &HelosGraphics_Color_Black); + graphics_FillPixel(20, 0, 40, 20, &HelosGraphics_Color_White); + graphics_FillPixel(40, 0, 60, 20, &HelosGraphics_Color_Red); + graphics_FillPixel(60, 0, 80, 20, &HelosGraphics_Color_Green); + graphics_FillPixel(80, 0, 100, 20, &HelosGraphics_Color_Blue); + graphics_FillPixel(100, 0, 120, 20, &HelosGraphics_Color_Cyan); + graphics_FillPixel(120, 0, 140, 20, &HelosGraphics_Color_Magenta); + graphics_FillPixel(140, 0, 160, 20, &HelosGraphics_Color_Yellow); + + uint32_t helloString[] = {0x89C6, 0x7A97, 0x7CFB, 0x7EDF, 0x4E09, ' ', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'a', 'n', 'd', ',', ' ', 'n', 'i', 'c', 'e', ' ', 't', 'o', ' ', 'm', 'e', 'e', 't', ' ', 'y', 'o', 'u', '!'}; + unifont_DrawString(0, 20, &HelosGraphics_Color_White, helloString, sizeof(helloString) / sizeof(uint32_t)); + graphics_CursorX = 0; + graphics_CursorY = 52; + + runtime_InitPaging(); + + + return EFI_SUCCESS; +} + +EFI_HANDLE efiImageHandle; +EFI_SYSTEM_TABLE * efiSystemTable; +EFI_BOOT_SERVICES *efiBootServices; + +SIMPLE_TEXT_OUTPUT_INTERFACE *efiStdout, *efiStderr; +SIMPLE_INPUT_INTERFACE * efiStdin; + +char Buffer[HELOS_BUFFER_SIZE]; diff --git a/main.h b/main.h new file mode 100644 index 0000000..a8a4f90 --- /dev/null +++ b/main.h @@ -0,0 +1,48 @@ + +#ifdef __cplusplus +extern "C" { +#include +} +#else +#include +#endif + +#define PROJECT_NAME "Helos1" +#define PROJECT_NAME_LONG L"Helos1" + +#define FASTCALL_ABI __attribute__((ms_abi)) // declares a function as Microsoft x64 ABI. Used in the EFI part of the kernel. +#define SYSV_ABI __attribute__((sysv_abi)) // declares a function as System V AMD64 ABI. Used in the Kernel space (other than EFI bootstrap code). + +#define ALIGNED(n) __attribute__((__aligned__(n))) +#define PACKED __attribute__((__packed__)) + +#if (defined __STDC_VERSION__) && __STDC_VERSION__ >= 201112L +#define NORETURN _Noreturn +#else +#define NORETURN __attribute__((__noreturn__)) +#endif + +#define noreturn NORETURN + +// Don't (uint64_t) a function symbol directly, use this +#define LOCATE_SYMBOL(var, symbol) \ + asm volatile("leaq " #symbol "(%%rip), %0" \ + : "=r"(var)) + + +extern EFI_HANDLE efiImageHandle; +extern EFI_SYSTEM_TABLE * efiSystemTable; +extern EFI_BOOT_SERVICES *efiBootServices; + +extern SIMPLE_TEXT_OUTPUT_INTERFACE *efiStdout, *efiStderr; +extern SIMPLE_INPUT_INTERFACE * efiStdin; + +#define HELOS_BUFFER_SIZE 16384 +extern char Buffer[HELOS_BUFFER_SIZE]; // general-purpose buffer, user saved (volatile), not used in interrupt handlers + + +extern const char link_TextStart[], link_TextEnd[]; +extern const char link_DataStart[], link_DataEnd[]; +extern const char link_RodataStart[], link_RodataEnd[]; +extern const char link_BssStart[], link_BssEnd[]; +extern const char link_RelocStart[], link_RelocEnd[]; diff --git a/memory/Makefile b/memory/Makefile new file mode 100644 index 0000000..a829280 --- /dev/null +++ b/memory/Makefile @@ -0,0 +1,19 @@ + +SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST))) + +objects = $(patsubst %.c,%.o,$(wildcard *.c)) $(patsubst %.cpp,%.o,$(wildcard *.cpp)) +objects_fasm = $(patsubst %.S,%.o,$(wildcard *.S)) + +objects_test = $(patsubst %.c,%.o,$(wildcard test_*.c)) $(patsubst %.cpp,%.o,$(wildcard test_*.cpp)) +objects := $(filter-out $(objects_test),$(objects)) + + +all: $(objects) $(objects_fasm) + +$(objects_fasm): + $(FASM) $(patsubst %.o,%.S,$@) $@ + +clean: + -$(RM) *.o + + diff --git a/memory/liballoc_impl.c b/memory/liballoc_impl.c new file mode 100644 index 0000000..4b0f26b --- /dev/null +++ b/memory/liballoc_impl.c @@ -0,0 +1,41 @@ + +#include "memory.h" +#include "paging_internal.h" +#include "../runtime/stdio.h" +#include "../interrupt/interrupt.h" +#include + +#include "../extlib/liballoc/liballoc_1_1.h" + + +int liballoc_lock() { + if (interrupt_Enabled) + asm volatile("cli"); + return 0; +} + +int liballoc_unlock() { + if (interrupt_Enabled) + asm volatile("sti"); + return 0; +} + + +static uint64_t heapBreak = KERNEL_HEAP_VIRTUAL; + +void *liballoc_alloc(size_t pages) { + void *ret = (void *)heapBreak; + heapBreak += SYSTEM_PAGE_SIZE * pages; + + paging_map_PageAllocated((uint64_t)ret, pages, MAP_PROT_READ | MAP_PROT_WRITE); + + io_Printf("liballoc_alloc: allocated %u pages at HEAP+%llx (%llx)\n", pages, ret - KERNEL_HEAP_VIRTUAL, ret); + + return ret; +} + +int liballoc_free(void *ptr, size_t pages) { + paging_map_FreeAllocated((uint64_t)ptr, (uint64_t)ptr + SYSTEM_PAGE_SIZE * pages); + io_Printf("liballoc_free: freed %u pages at HEAP+%llx (%llx)\n", pages, ptr - KERNEL_HEAP_VIRTUAL, ptr); + return 0; +} diff --git a/memory/memory.c b/memory/memory.c new file mode 100644 index 0000000..2e62c2d --- /dev/null +++ b/memory/memory.c @@ -0,0 +1,40 @@ + +#include "../main.h" +#include "memory.h" +#include "../runtime/stdio.h" +#include "../extlib/liballoc/liballoc_1_1.h" + +#include + + +void *efiMallocTyped(size_t size, EFI_MEMORY_TYPE type) { + void *data; + efiBootServices->AllocatePool(type, size, &data); + memset(data, 0, size); + return data; +} + +void *efiMalloc(size_t size) { + return efiMallocTyped(size, EfiLoaderData); +} + +void efiFree(void *data) { + efiBootServices->FreePool(data); +} + +void *kMalloc(size_t size) { + void *mem = liballoc_kmalloc(size); + io_Printf("kMalloc: size=%llu, pos=0x%llx\n", size, mem); + return mem; +} + +void kFree(void *data) { + io_Printf("kFree: 0x%llx\n", data); + liballoc_kfree(data); +} + +void *kMemoryMap(void *desiredVirtual, int pageCount, int protectionFlags, int flags, int fd) { +} + +void kMemoryUnmap(void *pageStart, int pageCount) { +} diff --git a/memory/memory.h b/memory/memory.h new file mode 100644 index 0000000..87f7d78 --- /dev/null +++ b/memory/memory.h @@ -0,0 +1,100 @@ +#pragma once + +#include "../main.h" +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +#define KERNEL_CODE_VIRTUAL 0xFFFFFFFFC0000000ull // 2^64 - 1GiB +#define KERNEL_STACK_END_VIRTUAL (KERNEL_CODE_VIRTUAL) // kernel stack sits right below kernel code +#define KERNEL_STACK_INITIAL_SIZE SYSTEM_PAGE_2M_SIZE // currently must be 2 MiB +#define KERNEL_HEAP_VIRTUAL 0xFFFFFFFF00000000ull // 2^64 - 4GiB +#define KERNEL_FRAMEBUFFER_MAPPING 0xFFFFFFFEE0000000ull // 2^64 - 4GiB - 512MiB +#define KERNEL_MISC_MAPPING 0xFFFFFFFEC0000000ull // 2^64 - 5GiB + +#define KERNEL_IDT_MAPPING KERNEL_MISC_MAPPING +#define KERNEL_IDT_SIZE (256ull * 16) // fill the 256 interrupt vectors + +#define KERNEL_GDT_MAPPING (KERNEL_MISC_MAPPING + KERNEL_IDT_SIZE) +#define KERNEL_GDT_SIZE (16ull * 8) + +#define KERNEL_IDTR_MAPPING (KERNEL_MISC_MAPPING + KERNEL_IDT_SIZE + KERNEL_GDT_SIZE) +#define KERNEL_GDTR_MAPPING (KERNEL_MISC_MAPPING + KERNEL_IDT_SIZE + KERNEL_GDT_SIZE + 12) + +#define KERNEL_MISC_NEXT (KERNEL_MISC_MAPPING + KERNEL_IDT_SIZE + KERNEL_GDT_SIZE + 24) + +#define KERNEL_MISC_SIZE (KERNEL_IDT_SIZE + KERNEL_GDT_SIZE + 24) // add all the misc sizes + + +extern uint64_t paging_LoaderCodeAddress, paging_LoaderCodeSize; // physical address for loader code section +static inline uint64_t paging_MapFunction(void *func) { + return ((uint64_t)func - paging_LoaderCodeAddress) + KERNEL_CODE_VIRTUAL; +} + + +// efiMallocTyped allocates from EFI_BOOT_SERVICES.AllocatePool. +void *efiMallocTyped(size_t size, EFI_MEMORY_TYPE type); + +// efiMallocTyped allocates from EFI_BOOT_SERVICES.AllocatePool +// with a memory type of EfiLoaderData. +void *efiMalloc(size_t size); + +// efiFree frees data allocated from efiMalloc. +void efiFree(void *data); + + +// kMalloc allocates from system memory directly after paging has been set up +void *kMalloc(size_t size); + +// kFree frees data allocated from kMalloc. +void kFree(void *data); + + +extern EFI_MEMORY_DESCRIPTOR *efiMemoryMap; +extern UINTN efiMemoryMapSize; +extern UINTN efiMemoryMapKey; +extern UINTN efiDescriptorSize; +extern UINT32 efiDescriptorVertion; + +// runtime_InitPaging initializes paging and kMalloc/kFree allocator. +// This function calls ExitBootServices()!!! which is great +// Furthermore, it sets up a new stack, calls kMain() and does not return. +// +// If it fails, Panic() is called. +noreturn void runtime_InitPaging(); + +#define SYSTEM_PAGE_SIZE 4096ull // UEFI uses 4KiB pages by default +#define SYSTEM_PAGE_2M_SIZE 2097152ull // 2 MiB page size +#define SYSTEM_PAGE_1G_SIZE 1073741824ull // 1 GiB page size +#define MAX_SYSTEM_MEMORY_PAGES 16777216ull // 64 GiB + + +#define MAP_PROT_NONE 0 +#define MAP_PROT_EXEC 1 +#define MAP_PROT_WRITE 2 +#define MAP_PROT_READ 4 + +#define MAP_DATA 1 // Data, not initialized (kept as-is) +#define MAP_INITIALIZED_DATA 2 // Data, zeroed +#define MAP_FILE 3 // Memory-mapped file IO + +// kMemoryPageSize returns the size, in bytes, of the physical memory page. +inline static size_t kMemoryPageSize() { + return SYSTEM_PAGE_SIZE; +} + +// kMemoryMap maps new physical pages into the kernel virtual memory space. +// +// The memory is not cleared. +void *kMemoryMap(void *desiredVirtual, int pageCount, int protectionFlags, int dataType, int fd); + +// kMemoryUnmap unmaps previously mapped physical memory of the kernel space. +void kMemoryUnmap(void *pageStart, int pageCount); + + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/memory/memory.hpp b/memory/memory.hpp new file mode 100644 index 0000000..f377e5a --- /dev/null +++ b/memory/memory.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include + +#include "memory.h" + + +namespace helos { + + +class bad_alloc: public std::exception { +public: + bad_alloc() noexcept {} + bad_alloc(const bad_alloc &other) noexcept {} + bad_alloc &operator=(const bad_alloc &other) noexcept { return *this; } + + virtual const char *what() const noexcept override { return "helos::bad_alloc"; } +}; + + +// kAllocator is a class wrapper for kMalloc/Free satisfying the named requirement Allocator. +template +class kAllocator { +public: + typedef Type value_type; + + kAllocator() = default; + template + constexpr kAllocator(const kAllocator &) {} + + Type *allocate(std::size_t n) { + return kMalloc(n * sizeof(Type)); + } + + void deallocate(Type *p, std::size_t n) { + kFree(p); + } +}; + +template +bool operator==(const kAllocator &, const kAllocator &) { + return true; +} +template +bool operator!=(const kAllocator &, const kAllocator &) { + return false; +} + +} // namespace helos + + +// globally overload the new and delete operators +// so keep this header at the top of every source file +// +// operators new and delete only call kMalloc/kFree, so C++ code +// must stay after paging setup +void *operator new(std::size_t size); +void operator delete(void *ptr) noexcept; diff --git a/memory/memory_cpp.cpp b/memory/memory_cpp.cpp new file mode 100644 index 0000000..b35f4fa --- /dev/null +++ b/memory/memory_cpp.cpp @@ -0,0 +1,13 @@ +#include "memory.hpp" + +void *operator new(std::size_t size) { + void *data = kMalloc(size); + /*if (!data) { + throw helos::bad_alloc(); + }*/ + return data; +} + +void operator delete(void *ptr) noexcept { + kFree(ptr); +} diff --git a/memory/paging.c b/memory/paging.c new file mode 100644 index 0000000..6a0acfe --- /dev/null +++ b/memory/paging.c @@ -0,0 +1,22 @@ +#pragma once + +#include "../main.h" +#include "memory.h" +#include "../runtime/panic_assert.h" +#include "../runtime/stdio.h" + +#include "paging_internal.h" + + +void *kMemoryMap(void *desiredVirtual, int pageCount, int protectionFlags, int flags, int fd) { +} + +void kMemoryUnmap(void *pageStart, int pageCount) { +} + +void *kMalloc(size_t size) { + return NULL; +} + +void kFree(void *data) { +} diff --git a/memory/paging_init.c b/memory/paging_init.c new file mode 100644 index 0000000..3564554 --- /dev/null +++ b/memory/paging_init.c @@ -0,0 +1,212 @@ + +#include "../main.h" +#include "memory.h" +#include "../runtime/panic_assert.h" +#include "../runtime/stdio.h" +#include "../graphics/graphics.h" +#include "../kernel/kmain.h" +#include "../interrupt/interrupt.h" +#include "../execformat/pe/reloc.h" +void execformat_pe_ReadSystemHeader(execformat_pe_PortableExecutable *pe); + +#include +#include + +#include "paging_internal.h" + + +EFI_MEMORY_DESCRIPTOR *efiMemoryMap; +UINTN efiMemoryMapSize; +UINTN efiMemoryMapKey; +UINTN efiDescriptorSize; +UINT32 efiDescriptorVertion; + +uint64_t paging_TotalBytes, paging_UsableBytes; +bool paging_SupportExecuteDisable; +uint64_t paging_EndPhysicalAddress; // past-the-end marker (and length) for physical memory +int paging_EndPhysicalPage; // past-the-end for physical pages (EndPhysicalAddress/SYSTEM_PAGE_SIZE) +uint64_t paging_PML4Table[512] ALIGNED(4096); // Kernel-mode virtual memory paging directory pointer table (Level 4 paging) +uint64_t paging_LoaderCodeAddress, paging_LoaderCodeSize; // physical address for loader code section +int paging_LoaderCodePageCount; // page count for loader code section + +void runtime_InitPaging() { + // TODO Obtain Execute Disable support status instead of assumpting its existence + paging_SupportExecuteDisable = true; + + // obtain the UEFI memory mapping + EFI_STATUS status; + + efiMemoryMapSize = 0; + efiMemoryMap = NULL; + status = efiBootServices->GetMemoryMap( + &efiMemoryMapSize, + efiMemoryMap, + &efiMemoryMapKey, + &efiDescriptorSize, + &efiDescriptorVertion); + assert(status == EFI_BUFFER_TOO_SMALL && "What? An empty buffer is not too small?"); + + efiMemoryMapSize += 2 * sizeof(EFI_MEMORY_DESCRIPTOR); + efiMemoryMap = (EFI_MEMORY_DESCRIPTOR *)efiMalloc(efiMemoryMapSize); + assert(efiMemoryMap && "efiMemoryMap allocate failed"); + status = efiBootServices->GetMemoryMap( + &efiMemoryMapSize, + efiMemoryMap, + &efiMemoryMapKey, + &efiDescriptorSize, + &efiDescriptorVertion); + assert(!EFI_ERROR(status) && "GetMemoryMap() with buffer allocated failed"); + + io_Printf(" .text: [%08x-%08x] len=%d (%d pages)\n", link_TextStart, link_TextEnd, link_TextEnd - link_TextStart, roundUpToPageCount(link_TextEnd - link_TextStart)); + io_Printf(" .data: [%08x-%08x] len=%d (%d pages)\n", link_DataStart, link_DataEnd, link_DataEnd - link_DataStart, roundUpToPageCount(link_DataEnd - link_DataStart)); + io_Printf(".rodata: [%08x-%08x] len=%d (%d pages)\n", link_RodataStart, link_RodataEnd, link_RodataEnd - link_RodataStart, roundUpToPageCount(link_RodataEnd - link_RodataStart)); + io_Printf(" .bss: [%08x-%08x] len=%d (%d pages)\n\n", link_BssStart, link_BssEnd, link_BssEnd - link_BssStart, roundUpToPageCount(link_BssEnd - link_BssStart)); + + // iterate the listing, accumlate counters and print info + paging_LoaderCodeAddress = paging_TotalBytes = paging_UsableBytes = 0; + memset(paging_physical_Bitmap, 0xff, sizeof(paging_physical_Bitmap)); + io_WriteConsoleASCII("EFI Memory mapping:\n"); + for (EFI_MEMORY_DESCRIPTOR *entry = efiMemoryMap; + (char *)entry < (char *)efiMemoryMap + efiMemoryMapSize; + entry = NEXT_MEMORY_DESCRITOR(entry, efiDescriptorSize) { + io_Printf( + " [%08x-%08x] -> [%08x] %s (%d)\n", + entry->PhysicalStart, + entry->PhysicalStart + entry->NumberOfPages * SYSTEM_PAGE_SIZE, + entry->VirtualStart, + memoryTypeName(entry->Type), + entry->Type); + paging_TotalBytes += SYSTEM_PAGE_SIZE * entry->NumberOfPages; + if (entry->Type == EfiConventionalMemory) { + // TODO include EfiBootServicesCode/Data as usable + paging_physical_BitmapWriteZero( + entry->PhysicalStart / SYSTEM_PAGE_SIZE, + entry->PhysicalStart / SYSTEM_PAGE_SIZE + entry->NumberOfPages); + paging_UsableBytes += SYSTEM_PAGE_SIZE * entry->NumberOfPages; + } else // page unusable + /*paging_physical_BitmapWriteOne( + entry->PhysicalStart / SYSTEM_PAGE_SIZE, + entry->PhysicalStart / SYSTEM_PAGE_SIZE + entry->NumberOfPages);*/ + ; + if (entry->Type == EfiLoaderCode) { + assert(!paging_LoaderCodeAddress && "Two EfiLoaderCode mappings at the same time"); + paging_LoaderCodeAddress = entry->PhysicalStart; + paging_LoaderCodeSize = entry->NumberOfPages * SYSTEM_PAGE_SIZE; + paging_LoaderCodePageCount = entry->NumberOfPages; + } + if (paging_EndPhysicalAddress < entry->PhysicalStart + entry->NumberOfPages * SYSTEM_PAGE_SIZE) + paging_EndPhysicalAddress = entry->PhysicalStart + entry->NumberOfPages * SYSTEM_PAGE_SIZE; + } + paging_EndPhysicalPage = paging_EndPhysicalAddress / SYSTEM_PAGE_SIZE; + + io_Printf( + " Total memory: %llu (%.2lf MB, %.2lf GB), EndPhyAddr %08llx\n", + paging_TotalBytes, + paging_TotalBytes / 1024.0 / 1024.0, + paging_TotalBytes / 1024.0 / 1024.0 / 1024.0, + paging_EndPhysicalAddress); + io_Printf( + "Usable memory: %llu (%.2lf MB, %.2lf GB)\n", + paging_UsableBytes, + paging_UsableBytes / 1024.0 / 1024.0, + paging_UsableBytes / 1024.0 / 1024.0 / 1024.0); + + io_PauseForKeystroke(); + + assert(paging_LoaderCodeAddress && "EfiLoaderCode mapping not found"); + + io_WriteConsoleASCII("Mapping kernel memory:\n"); + // map kernel code + io_Printf(" .Text... %d 4K pages\n",roundUpToPageCount(link_TextEnd-link_TextStart)); + paging_map_Page( // map .text + (uint64_t)link_TextStart, + KERNEL_CODE_VIRTUAL + ((uint64_t)link_TextStart - paging_LoaderCodeAddress), + roundUpToPageCount(link_TextEnd - link_TextStart), + MAP_PROT_READ | MAP_PROT_EXEC); + io_Printf(" .Data... %d 4K pages\n",roundUpToPageCount(link_DataEnd-link_DataStart)); + paging_map_Page( // map .data + (uint64_t)link_DataStart, + KERNEL_CODE_VIRTUAL + ((uint64_t)link_DataStart - paging_LoaderCodeAddress), + roundUpToPageCount(link_DataEnd - link_DataStart), + MAP_PROT_READ | MAP_PROT_WRITE); + io_Printf(" .Rodata... %d 4K pages\n",roundUpToPageCount(link_RodataEnd-link_RodataStart)); + paging_map_Page( // map .rodata + (uint64_t)link_RodataStart, + KERNEL_CODE_VIRTUAL + ((uint64_t)link_RodataStart - paging_LoaderCodeAddress), + roundUpToPageCount(link_RodataEnd - link_RodataStart), + MAP_PROT_READ); + io_Printf(" .Bss... %d 4K pages\n",roundUpToPageCount(link_BssEnd-link_BssStart)); + paging_map_Page( // map .bss + (uint64_t)link_BssStart, + KERNEL_CODE_VIRTUAL + ((uint64_t)link_BssStart - paging_LoaderCodeAddress), + roundUpToPageCount(link_BssEnd - link_BssStart), + MAP_PROT_READ | MAP_PROT_WRITE); + //paging_map_Page(paging_LoaderCodeAddress,KERNEL_CODE_VIRTUAL,paging_LoaderCodePageCount,MAP_PROT_READ|MAP_PROT_WRITE|MAP_PROT_EXEC); + + // map other VM data + io_Printf(" Framebuffer... %d 2M pages\n",roundUpToPageCount2M(graphics_FramebufferSize)); + paging_map_Page2M( // map the framebuffer output + (uint64_t)graphics_DeviceFramebuffer, + KERNEL_FRAMEBUFFER_MAPPING, + roundUpToPageCount2M(graphics_FramebufferSize), + MAP_PROT_READ | MAP_PROT_WRITE); + io_Printf(" Physical... %d 2M pages\n",roundUpToPageCount2M(paging_EndPhysicalAddress)); + paging_map_Page2M( // map the physical memory + 0, + 0, + roundUpToPageCount2M(paging_EndPhysicalAddress), + MAP_PROT_READ | MAP_PROT_WRITE | MAP_PROT_EXEC); + io_Printf(" Stack\n"); + paging_map_Page(paging_physical_AllocateOneFrame(),KERNEL_STACK_END_VIRTUAL-SYSTEM_PAGE_SIZE,1,MAP_PROT_READ|MAP_PROT_WRITE); + /*paging_map_Page2M( // stack, allocate a fresh new 2M + paging_physical_AllocateOneFrame2M(), + KERNEL_STACK_END_VIRTUAL - KERNEL_STACK_INITIAL_SIZE, + KERNEL_STACK_INITIAL_SIZE / SYSTEM_PAGE_2M_SIZE, + MAP_PROT_READ | MAP_PROT_WRITE);*/ + kMain_StackPosition = KERNEL_STACK_END_VIRTUAL; + /*paging_map_PageAllocated( + KERNEL_MISC_MAPPING, + roundUpToPageCount(KERNEL_MISC_SIZE), + MAP_PROT_READ|MAP_PROT_WRITE);*/ + io_Printf(" Misc... %d 2M pages\n",roundUpToPageCount2M(KERNEL_MISC_SIZE)); + paging_map_PageAllocated2M( // misc data + KERNEL_MISC_MAPPING, + roundUpToPageCount2M(KERNEL_MISC_SIZE), + MAP_PROT_READ|MAP_PROT_WRITE); + //paging_map_PageAllocated(KERNEL_HEAP_VIRTUAL,512,MAP_PROT_READ|MAP_PROT_WRITE); + + io_WriteConsoleASCII("Mapping completed\n"); + + + + // woohoo byebye! + efiBootServices->ExitBootServices(efiImageHandle, efiMemoryMapKey); + io_WriteConsoleASCII("Goodbye BootServices!\n"); + + // so now we're in unmanaged mode, we need to set up heap and stack, and jump to the new entry point kMain_Init. + // disable interrupts asap + asm volatile("cli":::"memory"); + interrupt_Enabled=false; + + // set the new virtual memory mapping + if (paging_SupportExecuteDisable) + paging_modeswitch_4LevelPagingNX(paging_PML4Table, 0); + else + paging_modeswitch_4LevelPaging(paging_PML4Table, 0); + graphics_DeviceFramebuffer = (void *)KERNEL_FRAMEBUFFER_MAPPING; + io_WriteConsoleASCII("Virtual Memory mapping switched\n"); + + // relocate the hardcoded symbols + execformat_pe_PortableExecutable pe; + execformat_pe_ReadSystemHeader(&pe); + execformat_pe_BaseRelocate(&pe, (void *)link_RelocStart, (void *)link_RelocEnd, paging_LoaderCodeAddress, KERNEL_CODE_VIRTUAL); + + io_WriteConsoleASCII("Relocation OK\n"); + + // find the symbol kMain_Init + //uint64_t target_kmain = KERNEL_CODE_VIRTUAL + ((uint64_t)kMain_Init - paging_LoaderCodeAddress); + // call it, once and for all + //((kMainType)target_kmain)(); + kMain_Init(); + __builtin_unreachable(); // execution cannot reach here +} diff --git a/memory/paging_internal.h b/memory/paging_internal.h new file mode 100644 index 0000000..a012bd1 --- /dev/null +++ b/memory/paging_internal.h @@ -0,0 +1,138 @@ +#pragma once + +#include "memory.h" +#include "stdbool.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +// defined in paging_init.c +extern EFI_MEMORY_DESCRIPTOR *efiMemoryMap; +extern UINTN efiMemoryMapSize; +extern UINTN efiMemoryMapKey; +extern UINTN efiDescriptorSize; +extern UINT32 efiDescriptorVertion; + +// defined in paging_init.c +extern uint64_t paging_TotalBytes, paging_UsableBytes; +extern bool paging_SupportExecuteDisable; +extern uint64_t paging_EndPhysicalAddress; // past-the-end marker (and length) for physical memory +extern int paging_EndPhysicalPage; // past-the-end for physical pages (EndPhysicalAddress/SYSTEM_PAGE_SIZE) +extern uint64_t paging_PML4Table[512]; // Kernel-mode virtual memory paging directory pointer table (PAE/Level 4 paging) +extern uint64_t paging_LoaderCodeAddress; // physical address for loader code section +extern int paging_LoaderCodePageCount; // page count for loader code section + +// defined in paging_physical.c +#define BITMAP_BITS 64 +extern uint64_t paging_physical_Bitmap[MAX_SYSTEM_MEMORY_PAGES / BITMAP_BITS]; // mapped with Bitmap[i/64] | 1<<(i%64), unlike convention +void paging_physical_BitmapWriteOne(int begin, int end); +void paging_physical_BitmapWriteZero(int begin, int end); +// these functions do not do any bookkeeping so use with care +uint64_t paging_physical_AllocateOneFrame(); // zeros the returned page +uint64_t paging_physical_AllocateOneFrame2M(); // zeros the returned page; this is 512 normal frames +int paging_physical_AllocateFrames(int pageCount, uint64_t frames[]); // allocate frames, not continuous, ret allloced cnt +int paging_physical_AllocateFrames2M(int pageCount, uint64_t frames[]); // allocate 2M frames, not continuous +void paging_physical_FreeFrame(uint64_t frame, int pageCount); // frees continuous frames in physical addr + +// defined in paging_map.c +#define PML_PRESENT (1ull << 0) +#define PML_WRITEABLE (1ull << 1) +#define PML_USER (1ull << 2) +#define PML_PAGE_WRITETHROUGH (1ull << 3) +#define PML_PAGE_CACHE_DISABLE (1ull << 4) +#define PML_ACCESSED (1ull << 5) +#define PML_DIRTY (1ull << 6) +#define PML_PAGE_SIZE (1ull << 7) +#define PML_GLOBAL (1ull << 8) +#define PML_EXECUTE_DISABLE (1ull << 63) +#define PML_ADDR_MASK (0xFFFFFFFFFF000ull) // 51:12 at max length +#define PML_ADDR_MASK_2M (0xFFFFFFFE00000ull) // 51:21 at max length +#define PML_ADDR_MASK_1G (0xFFFFFC0000000ull) // 51:30 at max length +void paging_map_Page(uint64_t physical, uint64_t virt, int pageCount, int protectionFlags); +void paging_map_Page2M(uint64_t physical, uint64_t virt, int pageCount, int protectionFlags); +void paging_map_Page1G(uint64_t physical, uint64_t virt, int pageCount, int protectionFlags); +// Allocates pageCount fresh new 4K pages with paging_physical and maps them continuously to virtual +void paging_map_PageAllocated(uint64_t virt, int pageCount, int protectionFlags); +// Allocates pageCount fresh new 2M pages with paging_physical and maps them continuously to virtual +void paging_map_PageAllocated2M(uint64_t virt, int pageCount, int protectionFlags); +// Unmaps the pages at virtual and free the underlying physical frames, with past-the-end of the memory addr +void paging_map_FreeAllocated(uint64_t virt, uint64_t virt_end); + + +// defined in paging_modeswitch.S +FASTCALL_ABI void paging_modeswitch_4LevelPaging(void *pml4, int pcid); +FASTCALL_ABI void paging_modeswitch_4LevelPagingNX(void *pml4, int pcid); // with setting the Execute-Disalbe bit +FASTCALL_ABI void paging_modeswitch_Table(void *pml, int pcid); + +static inline const char * + memoryTypeName(EFI_MEMORY_TYPE type) { +#define CASE(c) \ + case c: \ + return #c; + switch (type) { + CASE(EfiReservedMemoryType) + CASE(EfiLoaderCode) + CASE(EfiLoaderData) + CASE(EfiBootServicesCode) + CASE(EfiBootServicesData) + CASE(EfiRuntimeServicesCode) + CASE(EfiRuntimeServicesData) + CASE(EfiConventionalMemory) + CASE(EfiUnusableMemory) + CASE(EfiACPIReclaimMemory) + CASE(EfiACPIMemoryNVS) + CASE(EfiMemoryMappedIO) + CASE(EfiMemoryMappedIOPortSpace) + CASE(EfiPalCode) + case EfiMaxMemoryType: + return "EfiPersistentMemory"; + } + return "(unknown)"; +#undef CASE +} + +#ifndef NEXT_MEMORY_DESCRITOR +#define NEXT_MEMORY_DESCRITOR(desc, size) ((EFI_MEMORY_DESCRIPTOR *)((char *)desc + size))) +#endif + + +inline static uint64_t roundUpTo2Exponent(uint64_t v) { + uint64_t s = 1; + while (s < v) + s <<= 1; + return s; +} + +inline static uint64_t takeBitfield(uint64_t v, int high, int low) { + return (v >> low) & ((1 << (high - low + 1)) - 1); +} + +inline static void flush_tlb_single(uint64_t addr) { + asm volatile( + "invlpg (%0)" ::"r"(addr) + : "memory"); +} + +inline static int roundUpToPageCount(uint64_t size) { + if (size % SYSTEM_PAGE_SIZE == 0) + return size / SYSTEM_PAGE_SIZE; + return size / SYSTEM_PAGE_SIZE + 1; +} +inline static int roundUpToPageCount2M(uint64_t size) { + if (size % SYSTEM_PAGE_2M_SIZE == 0) + return size / SYSTEM_PAGE_2M_SIZE; + return size / SYSTEM_PAGE_2M_SIZE + 1; +} +inline static int roundUpToPageCount1G(uint64_t size) { + if (size % SYSTEM_PAGE_1G_SIZE == 0) + return size / SYSTEM_PAGE_1G_SIZE; + return size / SYSTEM_PAGE_1G_SIZE + 1; +} + + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/memory/paging_map.c b/memory/paging_map.c new file mode 100644 index 0000000..0bf363e --- /dev/null +++ b/memory/paging_map.c @@ -0,0 +1,189 @@ + +#include "memory.h" +#include "paging_internal.h" +#include "../runtime/panic_assert.h" +#include "string.h" + + +// some learning: +// https://forum.osdev.org/viewtopic.php?f=1&t=51392 (Confirmative question about 4-level and 5-level paging) + +void paging_map_Page(uint64_t physical, uint64_t virtual, int pageCount, int protectionFlags) { + assert(physical % SYSTEM_PAGE_SIZE == 0 && "Physical address not page-aligned"); + assert(virtual % SYSTEM_PAGE_SIZE == 0 && "Virtual address not page-aligned"); + + while (pageCount--) { + // PML4E index pointing to a PML3 table (Page-Directory-Pointer Table) + uint64_t *table = paging_PML4Table; + uint64_t i = takeBitfield(virtual, 47, 39); + if ((table[i] & PML_PRESENT) == 0) // allocate a new page as PML3 table + table[i] = paging_physical_AllocateOneFrame() | PML_PRESENT | PML_WRITEABLE; + + // PML3E(PDPTE) index pointing to a PML2 table (Page-Directory) + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 38, 30); + if ((table[i] & PML_PRESENT) == 0) // allocate page as page directory + table[i] = paging_physical_AllocateOneFrame() | PML_PRESENT | PML_WRITEABLE; + + // PML2E(PD) index pointing to a PML1 table (Page Table) + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 29, 21); + if ((table[i] & PML_PRESENT) == 0) // allocate page as page table + table[i] = paging_physical_AllocateOneFrame() | PML_PRESENT | PML_WRITEABLE; + + // Finally, the page table. + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 20, 12); + + table[i] = physical | PML_PRESENT | + ((protectionFlags & MAP_PROT_WRITE) ? PML_WRITEABLE : 0) | + ((paging_SupportExecuteDisable && !(protectionFlags & MAP_PROT_EXEC)) ? PML_EXECUTE_DISABLE : 0); + + + flush_tlb_single(virtual); + physical += SYSTEM_PAGE_SIZE; + virtual += SYSTEM_PAGE_SIZE; + } +} + +void paging_map_Page2M(uint64_t physical, uint64_t virtual, int pageCount, int protectionFlags) { + assert(physical % SYSTEM_PAGE_2M_SIZE == 0 && "Physical address not page-aligned"); + assert(virtual % SYSTEM_PAGE_2M_SIZE == 0 && "Virtual address not page-aligned"); + + while (pageCount--) { + // PML4E index pointing to a PML3 table (Page-Directory-Pointer Table) + uint64_t *table = paging_PML4Table; + uint64_t i = takeBitfield(virtual, 47, 39); + if ((table[i] & PML_PRESENT) == 0) // allocate a new page as PML3 table + table[i] = paging_physical_AllocateOneFrame() | PML_PRESENT | PML_WRITEABLE; + + // PML3E(PDPTE) index pointing to a PML2 table (Page-Directory) + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 38, 30); + if ((table[i] & PML_PRESENT) == 0) // allocate page as page directory + table[i] = paging_physical_AllocateOneFrame() | PML_PRESENT | PML_WRITEABLE; + + // PML2E(PD) index pointing to a PML1 table (Page Table) + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 29, 21); + if ((table[i] & PML_PRESENT) != 0 && (table[i] & PML_PAGE_SIZE) == 0) // deallocate the page if present + paging_physical_FreeFrame(table[i] & PML_ADDR_MASK, 1); + + // 2MB pages in Page Tables + table[i] = physical | PML_PRESENT | PML_PAGE_SIZE | + ((protectionFlags & MAP_PROT_WRITE) ? PML_WRITEABLE : 0) | + ((paging_SupportExecuteDisable && !(protectionFlags & MAP_PROT_EXEC)) ? PML_EXECUTE_DISABLE : 0); + + + flush_tlb_single(virtual); + physical += SYSTEM_PAGE_2M_SIZE; + virtual += SYSTEM_PAGE_2M_SIZE; + } +} + +void paging_map_Page1G(uint64_t physical, uint64_t virtual, int pageCount, int protectionFlags) { + assert(physical % SYSTEM_PAGE_1G_SIZE == 0 && "Physical address not page-aligned"); + assert(virtual % SYSTEM_PAGE_1G_SIZE == 0 && "Virtual address not page-aligned"); + + while (pageCount--) { + // PML4E index pointing to a PML3 table (Page-Directory-Pointer Table) + uint64_t *table = paging_PML4Table; + uint64_t i = takeBitfield(virtual, 47, 39); + if ((table[i] & PML_PRESENT) == 0) // allocate a new page as PML3 table + table[i] = paging_physical_AllocateOneFrame() | PML_PRESENT | PML_WRITEABLE; + + // PML3E(PDPTE) index pointing to a PML2 table (Page-Directory) + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 38, 30); + if ((table[i] & PML_PRESENT) != 0 && (table[i] & PML_PAGE_SIZE) == 0) // deallocate the page if present + paging_physical_FreeFrame(table[i] & PML_ADDR_MASK, 1); + + // 1GB pages in Page Directories + table[i] = physical | PML_PRESENT | PML_PAGE_SIZE | + ((protectionFlags & MAP_PROT_WRITE) ? PML_WRITEABLE : 0) | + ((paging_SupportExecuteDisable && !(protectionFlags & MAP_PROT_EXEC)) ? PML_EXECUTE_DISABLE : 0); + + + flush_tlb_single(virtual); + physical += SYSTEM_PAGE_1G_SIZE; + virtual += SYSTEM_PAGE_1G_SIZE; + } +} + +void paging_map_PageAllocated(uint64_t virtual, int pageCount, int protectionFlags) { + assert(virtual % SYSTEM_PAGE_SIZE == 0 && "Virtual address not page-aligned"); + + // skip the first 1M + int i = 1024 / 4 / BITMAP_BITS; + while (pageCount--) { + uint64_t freshPage = 0; + + // this code is in sync with paging_physical.c, paging_physical_AllocateOneFrame, so you have to modify both + for (; i < paging_EndPhysicalPage / BITMAP_BITS; i++) + if (paging_physical_Bitmap[i] != ~0ull) + for (int j = 0; j < BITMAP_BITS; j++) + if ((paging_physical_Bitmap[i] & (1ull << j)) == 0) { + paging_physical_Bitmap[i] |= (1ull << j); + freshPage = (((uint64_t)i) * BITMAP_BITS + j) * SYSTEM_PAGE_SIZE; + memset((void *)freshPage, 0, SYSTEM_PAGE_SIZE); + } + + paging_map_Page(freshPage, virtual, 1, protectionFlags); + virtual += SYSTEM_PAGE_SIZE; + } +} + +void paging_map_PageAllocated2M(uint64_t virtual, int pageCount, int protectionFlags) { + assert(pageCount > HELOS_BUFFER_SIZE / SYSTEM_PAGE_2M_SIZE * 8 && "helos_Buffer unable to hold all pointers"); + assert(virtual % SYSTEM_PAGE_2M_SIZE == 0 && "Virtual address not page-aligned"); + + uint64_t *buf = (uint64_t *)Buffer; + + int allocated = paging_physical_AllocateFrames2M(pageCount, buf); + for (int i = 0; i < allocated; i++) + paging_map_Page2M(buf[i], virtual + SYSTEM_PAGE_2M_SIZE * i, 1, protectionFlags); +} + +void paging_map_FreeAllocated(uint64_t virtual, uint64_t end) { + assert(virtual % SYSTEM_PAGE_SIZE == 0 && "Virtual address not page-aligned"); + + while (virtual < end) { + uint64_t *table = paging_PML4Table; + uint64_t i = takeBitfield(virtual, 47, 39); + if ((table[i] & PML_PRESENT) == 0) + goto loop_end; + + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 38, 30); + if ((table[i] & PML_PRESENT) == 0) + goto loop_end; + if (table[i] & PML_PAGE_SIZE) { // 1G mapping + paging_physical_FreeFrame(table[i] & PML_ADDR_MASK_1G, 512 * 512); + table[i] = 0; + virtual += SYSTEM_PAGE_1G_SIZE; + continue; + } + + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 29, 21); + if ((table[i] & PML_PRESENT) == 0) + goto loop_end; + if (table[i] & PML_PAGE_SIZE) { // 2M mapping + paging_physical_FreeFrame(table[i] & PML_ADDR_MASK_2M, 512); + table[i] = 0; + virtual += SYSTEM_PAGE_2M_SIZE; + continue; + } + + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 20, 12); + + if (table[i] & PML_PRESENT) { + paging_physical_FreeFrame(table[i] & PML_ADDR_MASK, 1); + table[i] = 0; + } + +loop_end: + virtual += SYSTEM_PAGE_SIZE; + } +} diff --git a/memory/paging_modeswitch.S b/memory/paging_modeswitch.S new file mode 100644 index 0000000..aa94fe3 --- /dev/null +++ b/memory/paging_modeswitch.S @@ -0,0 +1,86 @@ +format elf64 + + +section '.text' executable + +; Details on Control Registers and MSRs can be found here: +; https://wiki.osdev.org/CPU_Registers_x86-64 + +; x64fastcall void paging_modeswitch_4LevelPaging(void* pml4, int pcid) +; +; This function assumes that the program is now in 64-bit mode (long mode). +; Paging(CR0.PG) in general and long mode (MSR EFER.LME) must already be enabled in this state. +; +; Input: (void* rcx, int rdx) +; Clobbers: rax, flags +public paging_modeswitch_4LevelPaging +paging_modeswitch_4LevelPaging: + ; 4 Level paging: CR0.PG (bit 31) = 1 (Protected mode enable) + ; CR4.PAE (bit 05) = 1 (PAE enable) + ; MSR IA32_EFER.LME (bit 10) = 1 (IA32e 64-bit mode enable) + ; CR4.LA57 (bit 12) = 0 (4-level paging instead of 5) + ; We only need to set CR4.LA57 + ; Let's also set CR4.PCIDE(bit 17)=1, enabling process-context identifiers + mov rax, cr0 + and rax, 0xFFFFFFFFFFFFEFFF ; unset CR4.LA57 + or rax, 0x20000 ; set CR4.PCIDE + mov cr0, rax + + and rdx, 0xFFF ; take only the 11:0 bits of the PCID + or rcx, rdx ; construct the full CR3 + mov cr3, rcx ; set CR3, invalidate all TLB cache + ret + +; x64fastcall void paging_modeswitch_4LevelPagingNX(void* pml4, int pcid) +; +; This function assumes that the program is now in 64-bit mode (long mode). +; Paging(CR0.PG) in general and long mode (MSR EFER.LME) must already be enabled in this state. +; +; This function also sets the IA32_EFER.NXE bit, enabling No-Execute feature. +; +; Input: (void* rcx, int rdx) +; Clobbers: rax, r8, r9, flags +public paging_modeswitch_4LevelPagingNX +paging_modeswitch_4LevelPagingNX: + ; 4 Level paging: CR0.PG (bit 31) = 1 (Protected mode enable) + ; CR4.PAE (bit 05) = 1 (PAE enable) + ; MSR IA32_EFER.LME (bit 10) = 1 (IA32e 64-bit mode enable) + ; CR4.LA57 (bit 12) = 0 (4-level paging instead of 5) + ; We only need to set CR4.LA57 + ; Let's also set CR4.PCIDE(bit 17)=1, enabling process-context identifiers + mov rax, cr0 + and rax, 0xFFFFFFFFFFFFEFFF ; unset CR4.LA57 + or rax, 0x20000 ; set CR4.PCIDE (bit 17) + mov cr0, rax + + ; save rcx and rdx, RDMSR/WRMSR uses these + mov r8, rcx + mov r9, rdx + + mov ecx, 0xC0000080 ; operate on the IA32_EFER MSR + rdmsr ; read the MSR into edx:eax + or eax, (1 shl 11) ; set No-Execute Enable (bit 11) + wrmsr ; write the MSR back + + ; restore rcx and rdx + mov rcx, r8 + mov rdx, r9 + + and rdx, 0xFFF ; take only the 11:0 bits of the PCID + or rcx, rdx ; construct the full CR3 + mov cr3, rcx ; set CR3, invalidate all TLB cache + ret + +; x64fastcall void paging_modeswitch_Table(void* pml, int pcid) +; +; This function simply sets CR3 and run INVLPG, flushing the TLB cache. +; +; Input: (void* rcx, int rdx) +; Clobbers: none +public paging_modeswitch_Table +paging_modeswitch_Table: + and rdx, 0xFFF ; take only the 11:0 bits of the PCID + or rcx, rdx ; construct the full CR3 + mov cr3, rcx ; set CR3, invalidate all TLB cache + ret + diff --git a/memory/paging_physical.c b/memory/paging_physical.c new file mode 100644 index 0000000..4ccb22d --- /dev/null +++ b/memory/paging_physical.c @@ -0,0 +1,136 @@ + +#include "memory.h" +#include "paging_internal.h" +#include "../runtime/stdio.h" +#include + +uint64_t paging_physical_Bitmap[MAX_SYSTEM_MEMORY_PAGES / BITMAP_BITS]; + +static inline uint64_t fillBits(int begin, int last) { + if (last == BITMAP_BITS - 1) + return ~((1ull << begin) - 1ull); + if (begin == 0) + return (1ull << (last + 1ull)) - 1ull; + return (~((1ull << begin) - 1ull)) & ((1ull << (last + 1ull)) - 1ull); +} + +void paging_physical_BitmapWriteOne(int begin, int end) { + int whereBegin = begin / BITMAP_BITS, whereLast = (end - 1) / BITMAP_BITS; + if (whereBegin == whereLast) + paging_physical_Bitmap[whereBegin] |= fillBits(begin % BITMAP_BITS, (end - 1) % BITMAP_BITS); + else { + paging_physical_Bitmap[whereBegin] |= fillBits(begin % BITMAP_BITS, BITMAP_BITS - 1); + paging_physical_Bitmap[whereLast] |= fillBits(0, (end - 1) % BITMAP_BITS); + for (int i = whereBegin + 1; i < whereLast; i++) + paging_physical_Bitmap[i] = (~0ull); + } +} + +void paging_physical_BitmapWriteZero(int begin, int end) { + int whereBegin = begin / BITMAP_BITS, whereLast = (end - 1) / BITMAP_BITS; + if (whereBegin == whereLast) + paging_physical_Bitmap[whereBegin] &= ~fillBits(begin % BITMAP_BITS, (end - 1) % BITMAP_BITS); + else { + paging_physical_Bitmap[whereBegin] &= ~fillBits(begin % BITMAP_BITS, BITMAP_BITS - 1); + paging_physical_Bitmap[whereLast] &= ~fillBits(0, (end - 1) % BITMAP_BITS); + for (int i = whereBegin + 1; i < whereLast; i++) + paging_physical_Bitmap[i] = 0; + } +} + +static int phy_i = 1024 / 4 / BITMAP_BITS; + +uint64_t paging_physical_AllocateOneFrame() { + // skip the first 1M + /*for (; phy_i < paging_EndPhysicalPage / BITMAP_BITS; phy_i++) + if (paging_physical_Bitmap[phy_i] != ~0ull) + for (int j = 0; j < BITMAP_BITS; j++) + if ((paging_physical_Bitmap[phy_i] & (1ull << j)) == 0) { + paging_physical_Bitmap[phy_i] |= (1ull << j); + uint64_t addr = (((uint64_t)phy_i) * BITMAP_BITS + j) * SYSTEM_PAGE_SIZE; + memset((void *)addr, 0, SYSTEM_PAGE_SIZE); + return addr; + }*/ + uint64_t addr; + paging_physical_AllocateFrames(1, &addr); + return addr; +} + +// skip the first 2M +static int phy_i_2M = 2048 / 4 / BITMAP_BITS; + +uint64_t paging_physical_AllocateOneFrame2M() { + // skip the first 2M + /*for (; phy_i_2M < paging_EndPhysicalPage / BITMAP_BITS; phy_i_2M += 8) { + for (int j = 0; j < 8; j++) + if (paging_physical_Bitmap[phy_i_2M + j] != 0) + goto for_end; + + // now here we have a whole chunk at [i, i+8) + for (int j = 0; j < 8; j++) + paging_physical_Bitmap[phy_i_2M + j] = ~0ull; + uint64_t addr = (((uint64_t)phy_i_2M) * BITMAP_BITS) * SYSTEM_PAGE_SIZE; + memset((void *)addr, 0, SYSTEM_PAGE_2M_SIZE); + return addr; +for_end:; + }*/ + uint64_t addr; + paging_physical_AllocateFrames2M(1, &addr); + return addr; +} + +int paging_physical_AllocateFrames(int pageCount, uint64_t frames[]) { + // this code is in sync with paging_map.c, paging_map_PageAllocated, so you have to modify both + int page = 0; + for (; page < pageCount; page++) { + frames[page] = 0; + for (; phy_i < paging_EndPhysicalPage / BITMAP_BITS; phy_i++) + if (paging_physical_Bitmap[phy_i] != ~0ull) + for (int j = 0; j < BITMAP_BITS; j++) + if ((paging_physical_Bitmap[phy_i] & (1ull << j)) == 0) { + paging_physical_Bitmap[phy_i] |= (1ull << j); + uint64_t addr = (((uint64_t)phy_i) * BITMAP_BITS + j) * SYSTEM_PAGE_SIZE; + memset((void *)addr, 0, SYSTEM_PAGE_SIZE); + frames[page] = addr; + goto for_end; // break the phy_i loop + } + if (frames[page] == 0) + break; +for_end:; + } + + return page; +} + +int paging_physical_AllocateFrames2M(int pageCount, uint64_t frames[]) { + int page = 0; + for (; page < pageCount; page++) { + frames[page] = 0; + for (; phy_i_2M < paging_EndPhysicalPage / BITMAP_BITS; phy_i_2M += 8) { + for (int j = 0; j < 8; j++) + if (paging_physical_Bitmap[phy_i_2M + j] != 0) + goto for_end; // continue the outer for loop + + // now here we have a whole chunk at [i, i+8) + for (int j = 0; j < 8; j++) + paging_physical_Bitmap[phy_i_2M + j] = ~0ull; + uint64_t addr = (((uint64_t)phy_i_2M) * BITMAP_BITS) * SYSTEM_PAGE_SIZE; + memset((void *)addr, 0, SYSTEM_PAGE_2M_SIZE); + + frames[page] = addr; + break; +for_end:; + } + if (frames[page] == 0) + break; + } + return page; +} + +void paging_physical_FreeFrame(uint64_t frame, int pageCount) { + if (frame % SYSTEM_PAGE_SIZE != 0) { + io_Printf("paging_physical_FreeFrame: frame %08llx is not aligned\n", frame); + return; + } + paging_physical_BitmapWriteZero(frame / SYSTEM_PAGE_SIZE, frame / SYSTEM_PAGE_SIZE + pageCount); +} diff --git a/memory/test_fillbits.c b/memory/test_fillbits.c new file mode 100644 index 0000000..81523fb --- /dev/null +++ b/memory/test_fillbits.c @@ -0,0 +1,38 @@ +#include +#include + +static inline uint64_t fillBits(int begin, int last) { + if (last == 63) + return ~((1ull << begin) - 1ull); + if (begin == 0) + return (1ull << (last + 1ull)) - 1ull; + return (~((1ull << begin) - 1ull)) & ((1ull << (last + 1ull)) - 1ull); +} + +void printit(int a, int b) { + printf("[%d,%d] = %08lx\n", a, b, fillBits(a, b)); +} + +int main() { + printit(0, 63); + printit(1, 63); + printit(2, 63); + printit(3, 63); + printit(4, 63); + printit(5, 63); + printit(6, 63); + printit(7, 63); + printit(8, 63); + printit(9, 63); + printit(10, 63); + printit(7, 62); + printit(8, 61); + printit(9, 60); + printit(10, 59); + printit(10, 58); + printit(10, 57); + printit(10, 56); + printit(10, 55); + printit(10, 54); + printit(4, 4); +} diff --git a/memory/test_take_bitfield.c b/memory/test_take_bitfield.c new file mode 100644 index 0000000..7ccfefb --- /dev/null +++ b/memory/test_take_bitfield.c @@ -0,0 +1,16 @@ + +#include +#include + +inline static uint64_t takeBitfield(uint64_t v, int high, int low) { + return (v >> low) & ((1 << (high - low + 1)) - 1); +} + +int main() { + uint64_t val = 0x400CD696E37AE; + printf("val = %lX\n", val); + printf("val[21:5] = %lX\n", takeBitfield(val, 21, 5)); + printf("val[33:12] = %lX\n", takeBitfield(val, 33, 12)); + printf("val[21:5] = %lX\n", takeBitfield(val, 21, 5)); + printf("val[21:5] = %lX\n", takeBitfield(val, 21, 5)); +} diff --git a/run.cmd b/run.cmd new file mode 100644 index 0000000..424cfd8 --- /dev/null +++ b/run.cmd @@ -0,0 +1,4 @@ + +qemu-system-x86_64 -bios ../OVMF.fd -drive file=fat:rw:../FAT,format=raw,media=disk -m 2G + + diff --git a/runtime/calling_convention.S b/runtime/calling_convention.S new file mode 100644 index 0000000..c7afaba --- /dev/null +++ b/runtime/calling_convention.S @@ -0,0 +1,53 @@ +format elf64 + +section '.text' executable + +; x64sysvcall int sysv_x64fastcall(void* addr, int numArgs, long args1, args2, args3, args4) +; +; Calls Microsoft x64 ABI functions under System V AMD64 ABI. +; +; This function can handle ONLY up to FOUR arguments. +; numArgs is in fact unused. +; +; Input: (void* rdi, int rsi, long rdx, rcx, r8, r9) +; Output: int rax +; Clobbers: flags +public sysv_x64fastcall +sysv_x64fastcall: + sub rsp, 4*8 ; reserve the 4*8 bytes of shadow space + + ; So, before we start, let's make a chart! + ; + ; | SysVx64 | MSx64 | + ; ----+---------------+---------------+ + ; RAX | Return Value | Return Value | + ; RBX | Callee Saved | Callee Saved | + ; RCX | Argument 4 | Argument 1 | + ; RDX | Argument 3 | Argument 2 | + ; RSI | Argument 2 | Callee Saved | + ; RDI | Argument 1 | Callee Saved | + ; RBP | Callee Saved | Callee Saved | + ; RSP | Stack Pointer | Stack Pointer | + ; R8 | Argument 5 | Argument 3 | + ; R9 | Argument 6 | Argument 4 | + ; R10 | Caller Saved | Caller Saved | + ; R11 | Caller Saved | Caller Saved | + ; R12 | Callee Saved | Callee Saved | + ; R13 | Callee Saved | Callee Saved | + ; R14 | Callee Saved | Callee Saved | + ; R15 | Callee Saved | Callee Saved | + ; + ; To sum up, all we need to do is: + ; - RCX = RDX; RDX = RCX; and that's it! + ; Other stuff correspond quite well actually. + + mov rax, rcx + mov rcx, rdx + mov rdx, rax + + call rdi + + add rsp, 4*8 ; pop the shadow space + ret + + diff --git a/runtime/calling_convention.h b/runtime/calling_convention.h new file mode 100644 index 0000000..96538d0 --- /dev/null +++ b/runtime/calling_convention.h @@ -0,0 +1,12 @@ + +// x64sysvcall int sysv_x64fastcall(void* addr, int numArgs, long args1, args2, args3, args4) +// +// Calls Microsoft x64 ABI functions under System V AMD64 ABI. +// +// This function can handle ONLY up to FOUR arguments. +// numArgs is in fact unused. +// +// Input: (void* rdi, int rsi, long rdx, rcx, r8, r9) +// Output: int rax +// Clobbers: flags +__attribute__((sysv_abi)) long sysv_x64fastcall(void *addr, int numArgs, long args1, long args2, long args3, long args4); diff --git a/runtime/memcpy.c b/runtime/memcpy.c new file mode 100644 index 0000000..15b7fae --- /dev/null +++ b/runtime/memcpy.c @@ -0,0 +1,342 @@ +/******************************************************************** + ** File: memcpy.c + ** + ** Copyright (C) 1999-2010 Daniel Vik + ** + ** This software is provided 'as-is', without any express or implied + ** warranty. In no event will the authors be held liable for any + ** damages arising from the use of this software. + ** Permission is granted to anyone to use this software for any + ** purpose, including commercial applications, and to alter it and + ** redistribute it freely, subject to the following restrictions: + ** + ** 1. The origin of this software must not be misrepresented; you + ** must not claim that you wrote the original software. If you + ** use this software in a product, an acknowledgment in the + ** use this software in a product, an acknowledgment in the + ** product documentation would be appreciated but is not + ** required. + ** + ** 2. Altered source versions must be plainly marked as such, and + ** must not be misrepresented as being the original software. + ** + ** 3. This notice may not be removed or altered from any source + ** distribution. + ** + ** + ** Description: Implementation of the standard library function memcpy. + ** This implementation of memcpy() is ANSI-C89 compatible. + ** + ** The following configuration options can be set: + ** + ** LITTLE_ENDIAN - Uses processor with little endian + ** addressing. Default is big endian. + ** + ** PRE_INC_PTRS - Use pre increment of pointers. + ** Default is post increment of + ** pointers. + ** + ** INDEXED_COPY - Copying data using array indexing. + ** Using this option, disables the + ** PRE_INC_PTRS option. + ** + ** MEMCPY_64BIT - Compiles memcpy for 64 bit + ** architectures + ** + ** + ** Best Settings: + ** + ** Intel x86: LITTLE_ENDIAN and INDEXED_COPY + ** + *******************************************************************/ + + +/******************************************************************** + ** Configuration definitions. + *******************************************************************/ + +#define LITTLE_ENDIAN +#define INDEXED_COPY +#define MEMCPY_64BIT + + +/******************************************************************** + ** Includes for size_t definition + *******************************************************************/ + +#include + + +/******************************************************************** + ** Typedefs + *******************************************************************/ + +typedef unsigned char UInt8; +typedef unsigned short UInt16; +typedef unsigned int UInt32; +#ifdef _WIN32 +typedef unsigned __int64 UInt64; +#else +typedef unsigned long long UInt64; +#endif + +#ifdef MEMCPY_64BIT +typedef UInt64 UIntN; +#define TYPE_WIDTH 8L +#else +typedef UInt32 UIntN; +#define TYPE_WIDTH 4L +#endif + + +/******************************************************************** + ** Remove definitions when INDEXED_COPY is defined. + *******************************************************************/ + +#if defined(INDEXED_COPY) +#if defined(PRE_INC_PTRS) +#undef PRE_INC_PTRS +#endif /*PRE_INC_PTRS*/ +#endif /*INDEXED_COPY*/ + + +/******************************************************************** + ** Definitions for pre and post increment of pointers. + *******************************************************************/ + +#if defined(PRE_INC_PTRS) + +#define START_VAL(x) (x)-- +#define INC_VAL(x) *++(x) +#define CAST_TO_U8(p, o) ((UInt8 *)p + o + TYPE_WIDTH) +#define WHILE_DEST_BREAK (TYPE_WIDTH - 1) +#define PRE_LOOP_ADJUST -(TYPE_WIDTH - 1) +#define PRE_SWITCH_ADJUST +1 + +#else /*PRE_INC_PTRS*/ + +#define START_VAL(x) +#define INC_VAL(x) *(x)++ +#define CAST_TO_U8(p, o) ((UInt8 *)p + o) +#define WHILE_DEST_BREAK 0 +#define PRE_LOOP_ADJUST +#define PRE_SWITCH_ADJUST + +#endif /*PRE_INC_PTRS*/ + + +/******************************************************************** + ** Definitions for endians + *******************************************************************/ + +#if defined(LITTLE_ENDIAN) + +#define SHL >> +#define SHR << + +#else /* LITTLE_ENDIAN */ + +#define SHL << +#define SHR >> + +#endif /* LITTLE_ENDIAN */ + + +/******************************************************************** + ** Macros for copying words of different alignment. + ** Uses incremening pointers. + *******************************************************************/ + +#define CP_INCR() \ + { \ + INC_VAL(dstN) = INC_VAL(srcN); \ + } + +#define CP_INCR_SH(shl, shr) \ + { \ + dstWord = srcWord SHL shl; \ + srcWord = INC_VAL(srcN); \ + dstWord |= srcWord SHR shr; \ + INC_VAL(dstN) = dstWord; \ + } + + +/******************************************************************** + ** Macros for copying words of different alignment. + ** Uses array indexes. + *******************************************************************/ + +#define CP_INDEX(idx) \ + { \ + dstN[idx] = srcN[idx]; \ + } + +#define CP_INDEX_SH(x, shl, shr) \ + { \ + dstWord = srcWord SHL shl; \ + srcWord = srcN[x]; \ + dstWord |= srcWord SHR shr; \ + dstN[x] = dstWord; \ + } + + +/******************************************************************** + ** Macros for copying words of different alignment. + ** Uses incremening pointers or array indexes depending on + ** configuration. + *******************************************************************/ + +#if defined(INDEXED_COPY) + +#define CP(idx) CP_INDEX(idx) +#define CP_SH(idx, shl, shr) CP_INDEX_SH(idx, shl, shr) + +#define INC_INDEX(p, o) ((p) += (o)) + +#else /* INDEXED_COPY */ + +#define CP(idx) CP_INCR() +#define CP_SH(idx, shl, shr) CP_INCR_SH(shl, shr) + +#define INC_INDEX(p, o) + +#endif /* INDEXED_COPY */ + + +#define COPY_REMAINING(count) \ + { \ + START_VAL(dst8); \ + START_VAL(src8); \ + \ + switch (count) { \ + case 7: INC_VAL(dst8) = INC_VAL(src8); \ + case 6: INC_VAL(dst8) = INC_VAL(src8); \ + case 5: INC_VAL(dst8) = INC_VAL(src8); \ + case 4: INC_VAL(dst8) = INC_VAL(src8); \ + case 3: INC_VAL(dst8) = INC_VAL(src8); \ + case 2: INC_VAL(dst8) = INC_VAL(src8); \ + case 1: INC_VAL(dst8) = INC_VAL(src8); \ + case 0: \ + default: break; \ + } \ + } + +#define COPY_NO_SHIFT() \ + { \ + UIntN *dstN = (UIntN *)(dst8 PRE_LOOP_ADJUST); \ + UIntN *srcN = (UIntN *)(src8 PRE_LOOP_ADJUST); \ + size_t length = count / TYPE_WIDTH; \ + \ + while (length & 7) { \ + CP_INCR(); \ + length--; \ + } \ + \ + length /= 8; \ + \ + while (length--) { \ + CP(0); \ + CP(1); \ + CP(2); \ + CP(3); \ + CP(4); \ + CP(5); \ + CP(6); \ + CP(7); \ + \ + INC_INDEX(dstN, 8); \ + INC_INDEX(srcN, 8); \ + } \ + \ + src8 = CAST_TO_U8(srcN, 0); \ + dst8 = CAST_TO_U8(dstN, 0); \ + \ + COPY_REMAINING(count &(TYPE_WIDTH - 1)); \ + \ + return dest; \ + } + + +#define COPY_SHIFT(shift) \ + { \ + UIntN *dstN = (UIntN *)((((UIntN)dst8)PRE_LOOP_ADJUST) & ~(TYPE_WIDTH - 1)); \ + UIntN *srcN = (UIntN *)((((UIntN)src8)PRE_LOOP_ADJUST) & ~(TYPE_WIDTH - 1)); \ + size_t length = count / TYPE_WIDTH; \ + UIntN srcWord = INC_VAL(srcN); \ + UIntN dstWord; \ + \ + while (length & 7) { \ + CP_INCR_SH(8 * shift, 8 * (TYPE_WIDTH - shift)); \ + length--; \ + } \ + \ + length /= 8; \ + \ + while (length--) { \ + CP_SH(0, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + CP_SH(1, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + CP_SH(2, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + CP_SH(3, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + CP_SH(4, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + CP_SH(5, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + CP_SH(6, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + CP_SH(7, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + \ + INC_INDEX(dstN, 8); \ + INC_INDEX(srcN, 8); \ + } \ + \ + src8 = CAST_TO_U8(srcN, (shift - TYPE_WIDTH)); \ + dst8 = CAST_TO_U8(dstN, 0); \ + \ + COPY_REMAINING(count &(TYPE_WIDTH - 1)); \ + \ + return dest; \ + } + + +/******************************************************************** + ** + ** void *memcpy(void *dest, const void *src, size_t count) + ** + ** Args: dest - pointer to destination buffer + ** src - pointer to source buffer + ** count - number of bytes to copy + ** + ** Return: A pointer to destination buffer + ** + ** Purpose: Copies count bytes from src to dest. + ** No overlap check is performed. + ** + *******************************************************************/ + +void *memcpy(void *dest, const void *src, size_t count) { + UInt8 *dst8 = (UInt8 *)dest; + UInt8 *src8 = (UInt8 *)src; + + if (count < 8) { + COPY_REMAINING(count); + return dest; + } + + START_VAL(dst8); + START_VAL(src8); + + while (((UIntN)dst8 & (TYPE_WIDTH - 1)) != WHILE_DEST_BREAK) { + INC_VAL(dst8) = INC_VAL(src8); + count--; + } + + switch ((((UIntN)src8)PRE_SWITCH_ADJUST) & (TYPE_WIDTH - 1)) { + case 0: COPY_NO_SHIFT(); break; + case 1: COPY_SHIFT(1); break; + case 2: COPY_SHIFT(2); break; + case 3: COPY_SHIFT(3); break; +#if TYPE_WIDTH > 4 + case 4: COPY_SHIFT(4); break; + case 5: COPY_SHIFT(5); break; + case 6: COPY_SHIFT(6); break; + case 7: COPY_SHIFT(7); break; +#endif + } +} diff --git a/runtime/memcpy.h b/runtime/memcpy.h new file mode 100644 index 0000000..f3edcaa --- /dev/null +++ b/runtime/memcpy.h @@ -0,0 +1,64 @@ +/******************************************************************** + ** File: memcpy.h + ** + ** Copyright (C) 2005 Daniel Vik + ** + ** This software is provided 'as-is', without any express or implied + ** warranty. In no event will the authors be held liable for any + ** damages arising from the use of this software. + ** Permission is granted to anyone to use this software for any + ** purpose, including commercial applications, and to alter it and + ** redistribute it freely, subject to the following restrictions: + ** + ** 1. The origin of this software must not be misrepresented; you + ** must not claim that you wrote the original software. If you + ** use this software in a product, an acknowledgment in the + ** use this software in a product, an acknowledgment in the + ** product documentation would be appreciated but is not + ** required. + ** + ** 2. Altered source versions must be plainly marked as such, and + ** must not be misrepresented as being the original software. + ** + ** 3. This notice may not be removed or altered from any source + ** distribution. + ** + ** + ** Description: Implementation of the standard library function memcpy. + ** This implementation of memcpy() is ANSI-C89 compatible. + ** + *******************************************************************/ +#pragma once + + +/******************************************************************** + ** Includes for size_t definition + *******************************************************************/ + +#include + + +/******************************************************************** + ** + ** void *memcpy(void *dest, const void *src, size_t count) + ** + ** Args: dest - pointer to destination buffer + ** src - pointer to source buffer + ** count - number of bytes to copy + ** + ** Return: A pointer to destination buffer + ** + ** Purpose: Copies count bytes from src to dest. No overlap check + ** is performed. + ** + *******************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +void *memcpy(void *dest, const void *src, size_t count); + +#ifdef __cplusplus +} +#endif diff --git a/runtime/memset_memmove.S b/runtime/memset_memmove.S new file mode 100644 index 0000000..cb5c2ba --- /dev/null +++ b/runtime/memset_memmove.S @@ -0,0 +1,60 @@ +format elf64 + + +section '.text' executable + +; x64fastcall void* memset(void* dest, int data, size_t count) +; +; Input: (void* rcx, int rdx, size_t r8) +; Output: void* rax +; Clobbers: r10, flags +public memset +memset: + mov rax, rcx + lea r10, [rcx+r8] + +memset_loop: + mov byte[rcx], dl + inc rcx + cmp rcx, r10 + jne memset_loop + + ret + +; x64fastcall void* memmove(void* dest, const void* src, size_t count) +; +; Input: (void* rcx, void* rdx, size_t r8) +; Output: void* rax +; Clobbers: r9, r10, r11, flags +public memmove +memmove: + mov rax, rcx + + lea r10, [rcx+r8] ; past-the-end for *dest + lea r11, [rdx+r8] ; past-the-end for *src + + cmp rdx, rcx + je memmove_end ; return if move buffers are the same + jl memmove_back ; *src < *dest: overlaps, copy backward + +memmove_front: ; *src > *dest: overlaps, copy forward + mov r9b, byte[rdx] + mov byte[rcx], r9b + inc rcx + inc rdx + cmp rcx, r10 + jne memmove_front + + jmp memmove_end + +memmove_back: + dec r10 + dec r11 + mov r9b, byte[r11] + mov byte[r10], r9b + cmp rcx, r10 + jne memmove_back + +memmove_end: + ret + diff --git a/runtime/panic_assert.asm.S b/runtime/panic_assert.asm.S new file mode 100644 index 0000000..858ebbe --- /dev/null +++ b/runtime/panic_assert.asm.S @@ -0,0 +1,13 @@ +format elf64 + + +section '.text' executable + +; whatevercall void __Panic_HaltSystem(); +; +; Panic_HaltSystem halts the system by an infinite loop calling the HLT instruction. +public __Panic_HaltSystem +__Panic_HaltSystem: + hlt + jmp __Panic_HaltSystem + diff --git a/runtime/panic_assert.h b/runtime/panic_assert.h new file mode 100644 index 0000000..1c9f997 --- /dev/null +++ b/runtime/panic_assert.h @@ -0,0 +1,49 @@ +#pragma once + +#ifndef HELOS +#include +#else + +#include "../main.h" +#include "stdio.h" +#include "printf.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define assert(expr) \ + do { \ + if (!(expr)) \ + Panicf("Assertion failed: (" __FILE__ ":%d[%s]), Expression: %s", __LINE__, __func__, #expr); \ + } while (0) + + +// defined in assembly +noreturn void __Panic_HaltSystem(); + + +// Panic() aborts the system after printing the message and some other information. +noreturn inline static void Panic(const char *message) { + io_Printf("Panic: %s\n", message); + __Panic_HaltSystem(); +} + +// Panicf() aborts the system after printing the message using vsnprintf. +noreturn inline static void Panicf(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + int ret = vsnprintf(Buffer, HELOS_BUFFER_SIZE, fmt, args); + va_end(args); + io_Printf("Panic: %s\n", Buffer); + __Panic_HaltSystem(); +} + + +#ifdef __cplusplus +} +#endif + +#endif // HELOS diff --git a/runtime/printf.c b/runtime/printf.c new file mode 100644 index 0000000..7a7fda0 --- /dev/null +++ b/runtime/printf.c @@ -0,0 +1,901 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. These routines are thread +// safe and reentrant! +// Use this instead of the bloated standard/newlib printf cause these use +// malloc for printf (and may not be thread safe). +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "printf.h" + + +// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the +// printf_config.h header file +// default: undefined +#ifdef PRINTF_INCLUDE_CONFIG_H +#include "printf_config.h" +#endif + + +// 'ntoa' conversion buffer size, this must be big enough to hold one converted +// numeric number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_NTOA_BUFFER_SIZE +#define PRINTF_NTOA_BUFFER_SIZE 32U +#endif + +// 'ftoa' conversion buffer size, this must be big enough to hold one converted +// float number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_FTOA_BUFFER_SIZE +#define PRINTF_FTOA_BUFFER_SIZE 32U +#endif + +// support for the floating point type (%f) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_FLOAT +#define PRINTF_SUPPORT_FLOAT +#endif + +// support for exponential floating point notation (%e/%g) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL +#define PRINTF_SUPPORT_EXPONENTIAL +#endif + +// define the default floating point precision +// default: 6 digits +#ifndef PRINTF_DEFAULT_FLOAT_PRECISION +#define PRINTF_DEFAULT_FLOAT_PRECISION 6U +#endif + +// define the largest float suitable to print with %f +// default: 1e9 +#ifndef PRINTF_MAX_FLOAT +#define PRINTF_MAX_FLOAT 1e9 +#endif + +// support for the long long types (%llu or %p) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG +#define PRINTF_SUPPORT_LONG_LONG +#endif + +// support for the ptrdiff_t type (%t) +// ptrdiff_t is normally defined in as long or long long type +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T +#define PRINTF_SUPPORT_PTRDIFF_T +#endif + +/////////////////////////////////////////////////////////////////////////////// + +// internal flag definitions +#define FLAGS_ZEROPAD (1U << 0U) +#define FLAGS_LEFT (1U << 1U) +#define FLAGS_PLUS (1U << 2U) +#define FLAGS_SPACE (1U << 3U) +#define FLAGS_HASH (1U << 4U) +#define FLAGS_UPPERCASE (1U << 5U) +#define FLAGS_CHAR (1U << 6U) +#define FLAGS_SHORT (1U << 7U) +#define FLAGS_LONG (1U << 8U) +#define FLAGS_LONG_LONG (1U << 9U) +#define FLAGS_PRECISION (1U << 10U) +#define FLAGS_ADAPT_EXP (1U << 11U) + + +// import float.h for DBL_MAX +#if defined(PRINTF_SUPPORT_FLOAT) +#include +#endif + + +// output function type +typedef void (*out_fct_type)(char character, void *buffer, size_t idx, size_t maxlen); + + +// wrapper (used as buffer) for output function type +typedef struct { + void (*fct)(char character, void *arg); + void *arg; +} out_fct_wrap_type; + + +// internal buffer output +static inline void _out_buffer(char character, void *buffer, size_t idx, size_t maxlen) { + if (idx < maxlen) { + ((char *)buffer)[idx] = character; + } +} + + +// internal null output +static inline void _out_null(char character, void *buffer, size_t idx, size_t maxlen) { + (void)character; + (void)buffer; + (void)idx; + (void)maxlen; +} + + +// internal _putchar wrapper +static inline void _out_char(char character, void *buffer, size_t idx, size_t maxlen) { + (void)buffer; + (void)idx; + (void)maxlen; + if (character) { + _putchar(character); + } +} + + +// internal output function wrapper +static inline void _out_fct(char character, void *buffer, size_t idx, size_t maxlen) { + (void)idx; + (void)maxlen; + if (character) { + // buffer is the output fct pointer + ((out_fct_wrap_type *)buffer)->fct(character, ((out_fct_wrap_type *)buffer)->arg); + } +} + + +// internal secure strlen +// \return The length of the string (excluding the terminating 0) limited by 'maxsize' +static inline unsigned int _strnlen_s(const char *str, size_t maxsize) { + const char *s; + for (s = str; *s && maxsize--; ++s) + ; + return (unsigned int)(s - str); +} + + +// internal test if char is a digit (0-9) +// \return true if char is a digit +static inline bool _is_digit(char ch) { + return (ch >= '0') && (ch <= '9'); +} + + +// internal ASCII string to unsigned int conversion +static unsigned int _atoi(const char **str) { + unsigned int i = 0U; + while (_is_digit(**str)) { + i = i * 10U + (unsigned int)(*((*str)++) - '0'); + } + return i; +} + + +// output the specified string in reverse, taking care of any zero-padding +static size_t _out_rev(out_fct_type out, char *buffer, size_t idx, size_t maxlen, const char *buf, size_t len, unsigned int width, unsigned int flags) { + const size_t start_idx = idx; + + // pad spaces up to given width + if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { + for (size_t i = len; i < width; i++) { + out(' ', buffer, idx++, maxlen); + } + } + + // reverse string + while (len) { + out(buf[--len], buffer, idx++, maxlen); + } + + // append pad spaces up to given width + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) { + out(' ', buffer, idx++, maxlen); + } + } + + return idx; +} + + +// internal itoa format +static size_t _ntoa_format(out_fct_type out, char *buffer, size_t idx, size_t maxlen, char *buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags) { + // pad leading zeros + if (!(flags & FLAGS_LEFT)) { + if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + // handle hash + if (flags & FLAGS_HASH) { + if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) { + len--; + if (len && (base == 16U)) { + len--; + } + } + if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'x'; + } else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'X'; + } else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'b'; + } + if (len < PRINTF_NTOA_BUFFER_SIZE) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_NTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + + +// internal itoa for 'long' type +static size_t _ntoa_long(out_fct_type out, char *buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags) { + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} + + +// internal itoa for 'long long' type +#if defined(PRINTF_SUPPORT_LONG_LONG) +static size_t _ntoa_long_long(out_fct_type out, char *buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags) { + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} +#endif // PRINTF_SUPPORT_LONG_LONG + + +#if defined(PRINTF_SUPPORT_FLOAT) + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT +static size_t _etoa(out_fct_type out, char *buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags); +#endif + + +// internal ftoa for fixed decimal floating point +static size_t _ftoa(out_fct_type out, char *buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) { + char buf[PRINTF_FTOA_BUFFER_SIZE]; + size_t len = 0U; + double diff = 0.0; + + // powers of 10 + static const double pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000}; + + // test for special values + if (value != value) + return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags); + if (value < -DBL_MAX) + return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags); + if (value > DBL_MAX) + return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); + + // test for very large values + // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad + if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) { +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + return _etoa(out, buffer, idx, maxlen, value, prec, width, flags); +#else + return 0U; +#endif + } + + // test for negative + bool negative = false; + if (value < 0) { + negative = true; + value = 0 - value; + } + + // set default precision, if not set explicitly + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + // limit precision to 9, cause a prec >= 10 can lead to overflow errors + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) { + buf[len++] = '0'; + prec--; + } + + int whole = (int)value; + double tmp = (value - whole) * pow10[prec]; + unsigned long frac = (unsigned long)tmp; + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + // handle rollover, e.g. case 0.99 with prec 1 is 1.0 + if (frac >= pow10[prec]) { + frac = 0; + ++whole; + } + } else if (diff < 0.5) { + } else if ((frac == 0U) || (frac & 1U)) { + // if halfway, round up if odd OR if last digit is 0 + ++frac; + } + + if (prec == 0U) { + diff = value - (double)whole; + if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) { + // exactly 0.5 and ODD, then round up + // 1.5 -> 2, but 2.5 -> 2 + ++whole; + } + } else { + unsigned int count = prec; + // now do fractional part, as an unsigned number + while (len < PRINTF_FTOA_BUFFER_SIZE) { + --count; + buf[len++] = (char)(48U + (frac % 10U)); + if (!(frac /= 10U)) { + break; + } + } + // add extra 0s + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { + buf[len++] = '0'; + } + if (len < PRINTF_FTOA_BUFFER_SIZE) { + // add decimal + buf[len++] = '.'; + } + } + + // do whole part, number is reversed + while (len < PRINTF_FTOA_BUFFER_SIZE) { + buf[len++] = (char)(48 + (whole % 10)); + if (!(whole /= 10)) { + break; + } + } + + // pad leading zeros + if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { + if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_FTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse +static size_t _etoa(out_fct_type out, char *buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) { + // check for NaN and special values + if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) { + return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags); + } + + // determine the sign + const bool negative = value < 0; + if (negative) { + value = -value; + } + + // default precision + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + + // determine the decimal exponent + // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) + union { + uint64_t U; + double F; + } conv; + + conv.F = value; + int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2 + conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2) + // now approximate log10 from the log2 integer part and an expansion of ln around 1.5 + int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168); + // now we want to compute 10^expval but we want to be sure it won't overflow + exp2 = (int)(expval * 3.321928094887362 + 0.5); + const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453; + const double z2 = z * z; + conv.U = (uint64_t)(exp2 + 1023) << 52U; + // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex + conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); + // correct for rounding errors + if (value < conv.F) { + expval--; + conv.F /= 10; + } + + // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters + unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U; + + // in "%g" mode, "prec" is the number of *significant figures* not decimals + if (flags & FLAGS_ADAPT_EXP) { + // do we want to fall-back to "%f" mode? + if ((value >= 1e-4) && (value < 1e6)) { + if ((int)prec > expval) { + prec = (unsigned)((int)prec - expval - 1); + } else { + prec = 0; + } + flags |= FLAGS_PRECISION; // make sure _ftoa respects precision + // no characters in exponent + minwidth = 0U; + expval = 0; + } else { + // we use one sigfig for the whole part + if ((prec > 0) && (flags & FLAGS_PRECISION)) { + --prec; + } + } + } + + // will everything fit? + unsigned int fwidth = width; + if (width > minwidth) { + // we didn't fall-back so subtract the characters required for the exponent + fwidth -= minwidth; + } else { + // not enough characters, so go back to default sizing + fwidth = 0U; + } + if ((flags & FLAGS_LEFT) && minwidth) { + // if we're padding on the right, DON'T pad the floating part + fwidth = 0U; + } + + // rescale the float value + if (expval) { + value /= conv.F; + } + + // output the floating part + const size_t start_idx = idx; + idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP); + + // output the exponent part + if (minwidth) { + // output the exponential symbol + out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen); + // output the exponent value + idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth - 1, FLAGS_ZEROPAD | FLAGS_PLUS); + // might need to right-pad spaces + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) out(' ', buffer, idx++, maxlen); + } + } + return idx; +} +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + + +// internal vsnprintf +static int _vsnprintf(out_fct_type out, char *buffer, const size_t maxlen, const char *format, va_list va) { + unsigned int flags, width, precision, n; + size_t idx = 0U; + + if (!buffer) { + // use null output function + out = _out_null; + } + + while (*format) { + // format specifier? %[flags][width][.precision][length] + if (*format != '%') { + // no + out(*format, buffer, idx++, maxlen); + format++; + continue; + } else { + // yes, evaluate it + format++; + } + + // evaluate flags + flags = 0U; + do { + switch (*format) { + case '0': + flags |= FLAGS_ZEROPAD; + format++; + n = 1U; + break; + case '-': + flags |= FLAGS_LEFT; + format++; + n = 1U; + break; + case '+': + flags |= FLAGS_PLUS; + format++; + n = 1U; + break; + case ' ': + flags |= FLAGS_SPACE; + format++; + n = 1U; + break; + case '#': + flags |= FLAGS_HASH; + format++; + n = 1U; + break; + default: n = 0U; break; + } + } while (n); + + // evaluate width field + width = 0U; + if (_is_digit(*format)) { + width = _atoi(&format); + } else if (*format == '*') { + const int w = va_arg(va, int); + if (w < 0) { + flags |= FLAGS_LEFT; // reverse padding + width = (unsigned int)-w; + } else { + width = (unsigned int)w; + } + format++; + } + + // evaluate precision field + precision = 0U; + if (*format == '.') { + flags |= FLAGS_PRECISION; + format++; + if (_is_digit(*format)) { + precision = _atoi(&format); + } else if (*format == '*') { + const int prec = (int)va_arg(va, int); + precision = prec > 0 ? (unsigned int)prec : 0U; + format++; + } + } + + // evaluate length field + switch (*format) { + case 'l': + flags |= FLAGS_LONG; + format++; + if (*format == 'l') { + flags |= FLAGS_LONG_LONG; + format++; + } + break; + case 'h': + flags |= FLAGS_SHORT; + format++; + if (*format == 'h') { + flags |= FLAGS_CHAR; + format++; + } + break; +#if defined(PRINTF_SUPPORT_PTRDIFF_T) + case 't': + flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; +#endif + case 'j': + flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + case 'z': + flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + default: + break; + } + + // evaluate specifier + switch (*format) { + case 'd': + case 'i': + case 'u': + case 'x': + case 'X': + case 'o': + case 'b': { + // set the base + unsigned int base; + if (*format == 'x' || *format == 'X') { + base = 16U; + } else if (*format == 'o') { + base = 8U; + } else if (*format == 'b') { + base = 2U; + } else { + base = 10U; + flags &= ~FLAGS_HASH; // no hash for dec format + } + // uppercase + if (*format == 'X') { + flags |= FLAGS_UPPERCASE; + } + + // no plus or space flag for u, x, X, o, b + if ((*format != 'i') && (*format != 'd')) { + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + } + + // ignore '0' flag when precision is given + if (flags & FLAGS_PRECISION) { + flags &= ~FLAGS_ZEROPAD; + } + + // convert the integer + if ((*format == 'i') || (*format == 'd')) { + // signed + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + const long long value = va_arg(va, long long); + idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); +#endif + } else if (flags & FLAGS_LONG) { + const long value = va_arg(va, long); + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); + } else { + const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : + va_arg(va, int); + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); + } + } else { + // unsigned + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags); +#endif + } else if (flags & FLAGS_LONG) { + idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags); + } else { + const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : + va_arg(va, unsigned int); + idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags); + } + } + format++; + break; + } +#if defined(PRINTF_SUPPORT_FLOAT) + case 'f': + case 'F': + if (*format == 'F') + flags |= FLAGS_UPPERCASE; + idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + case 'e': + case 'E': + case 'g': + case 'G': + if ((*format == 'g') || (*format == 'G')) + flags |= FLAGS_ADAPT_EXP; + if ((*format == 'E') || (*format == 'G')) + flags |= FLAGS_UPPERCASE; + idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + case 'c': { + unsigned int l = 1U; + // pre padding + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // char output + out((char)va_arg(va, int), buffer, idx++, maxlen); + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 's': { + const char * p = va_arg(va, char *); + unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1); + // pre padding + if (flags & FLAGS_PRECISION) { + l = (l < precision ? l : precision); + } + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // string output + while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) { + out(*(p++), buffer, idx++, maxlen); + } + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 'p': { + width = sizeof(void *) * 2U; + flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; +#if defined(PRINTF_SUPPORT_LONG_LONG) + const bool is_ll = sizeof(uintptr_t) == sizeof(long long); + if (is_ll) { + idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void *), false, 16U, precision, width, flags); + } else { +#endif + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void *)), false, 16U, precision, width, flags); +#if defined(PRINTF_SUPPORT_LONG_LONG) + } +#endif + format++; + break; + } + + case '%': + out('%', buffer, idx++, maxlen); + format++; + break; + + default: + out(*format, buffer, idx++, maxlen); + format++; + break; + } + } + + // termination + out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); + + // return written chars without terminating \0 + return (int)idx; +} + + +/////////////////////////////////////////////////////////////////////////////// + +int printf_(const char *format, ...) { + va_list va; + va_start(va, format); + char buffer[1]; + const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va); + va_end(va); + return ret; +} + + +int sprintf_(char *buffer, const char *format, ...) { + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va); + va_end(va); + return ret; +} + + +int snprintf_(char *buffer, size_t count, const char *format, ...) { + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, count, format, va); + va_end(va); + return ret; +} + + +int vprintf_(const char *format, va_list va) { + char buffer[1]; + return _vsnprintf(_out_char, buffer, (size_t)-1, format, va); +} + + +int vsnprintf_(char *buffer, size_t count, const char *format, va_list va) { + return _vsnprintf(_out_buffer, buffer, count, format, va); +} + + +int fctprintf(void (*out)(char character, void *arg), void *arg, const char *format, ...) { + va_list va; + va_start(va, format); + const out_fct_wrap_type out_fct_wrap = {out, arg}; + const int ret = _vsnprintf(_out_fct, (char *)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va); + va_end(va); + return ret; +} diff --git a/runtime/printf.h b/runtime/printf.h new file mode 100644 index 0000000..b0b6e38 --- /dev/null +++ b/runtime/printf.h @@ -0,0 +1,117 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. +// Use this instead of bloated standard/newlib printf. +// These routines are thread safe and reentrant. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _PRINTF_H_ +#define _PRINTF_H_ + +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * Output a character to a custom device like UART, used by the printf() function + * This function is declared here only. You have to write your custom implementation somewhere + * \param character Character to output + */ +void _putchar(char character); + + +/** + * Tiny printf implementation + * You have to implement _putchar if you use printf() + * To avoid conflicts with the regular printf() API it is overridden by macro defines + * and internal underscore-appended functions like printf_() are used + * \param format A string that specifies the format of the output + * \return The number of characters that are written into the array, not counting the terminating null character + */ +#define printf printf_ +int printf_(const char *format, ...); + + +/** + * Tiny sprintf implementation + * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD! + * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output! + * \param format A string that specifies the format of the output + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +#define sprintf sprintf_ +int sprintf_(char *buffer, const char *format, ...); + + +/** + * Tiny snprintf/vsnprintf implementation + * \param buffer A pointer to the buffer where to store the formatted string + * \param count The maximum number of characters to store in the buffer, including a terminating null character + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that COULD have been written into the buffer, not counting the terminating + * null character. A value equal or larger than count indicates truncation. Only when the returned value + * is non-negative and less than count, the string has been completely written. + */ +#define snprintf snprintf_ +#define vsnprintf vsnprintf_ +int snprintf_(char *buffer, size_t count, const char *format, ...); +int vsnprintf_(char *buffer, size_t count, const char *format, va_list va); + + +/** + * Tiny vprintf implementation + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +#define vprintf vprintf_ +int vprintf_(const char *format, va_list va); + + +/** + * printf with output function + * You may use this as dynamic alternative to printf() with its fixed _putchar() output + * \param out An output function which takes one character and an argument pointer + * \param arg An argument pointer for user data passed to output function + * \param format A string that specifies the format of the output + * \return The number of characters that are sent to the output function, not counting the terminating null character + */ +int fctprintf(void (*out)(char character, void *arg), void *arg, const char *format, ...); + + +#ifdef __cplusplus +} +#endif + + +#endif // _PRINTF_H_ diff --git a/runtime/stdio.c b/runtime/stdio.c new file mode 100644 index 0000000..51cb6bb --- /dev/null +++ b/runtime/stdio.c @@ -0,0 +1,98 @@ + +#include "stdio.h" +#include "unicode.h" +#include "printf.h" +#include "../memory/memory.h" +#include "../graphics/graphics.h" + +#include +#include + + +// printf wants this +void _putchar(char c) { + if (!graphics_Framebuffer) { + UINT16 buf[2] = {c, 0}; + efiStdout->OutputString(efiStdout, buf); + } else { + console_WriteChar(&HelosGraphics_Color_White, c); + if (c == '\n') { // swap buffer on newline + graphics_SwapBuffer(); + } + } +} + +int __io_WriteConsole_bufSize = 256; +UINT16 __io_WriteConsole_bufferReal[256]; +UINT16 *__io_WriteConsole_buffer = __io_WriteConsole_bufferReal; + +void __io_WriteConsole_ResizeBuffer(int size) { + if (__io_WriteConsole_bufSize < size) { + while (__io_WriteConsole_bufSize < size) + __io_WriteConsole_bufSize *= 2; + + DEBUG("allocate -> %d", __io_WriteConsole_bufSize); + + if (__io_WriteConsole_buffer != __io_WriteConsole_bufferReal) + kFree(__io_WriteConsole_buffer); + __io_WriteConsole_buffer = kMalloc(size * sizeof(UINT16)); + } +} + +void io_WriteConsole(const char *str) { + int size = 0; // don't include the \0 at the end here + int len = strlen(str); // left the \0 out here too + + for (int i = 0; + i < len; + i += utf8_Decode(str + i, len - i, NULL), size++) {} + __io_WriteConsole_ResizeBuffer(size + 1); + + uint32_t codepoint; + for (int i = 0, j = 0; + i < len;) { + i += utf8_Decode(str + i, len - i, &codepoint); + __io_WriteConsole_buffer[j++] = codepoint; + } + __io_WriteConsole_buffer[size] = 0; + + if (!graphics_Framebuffer) { + efiStdout->OutputString(efiStdout, __io_WriteConsole_buffer); + } else { + console_WriteUTF16(&HelosGraphics_Color_White, __io_WriteConsole_buffer, 0); + } +} + +void io_WriteConsoleASCII(const char *str) { + if (!graphics_Framebuffer) { + int len = strlen(str); + __io_WriteConsole_ResizeBuffer(len + 1); + for (int i = 0; i <= len; i++) + __io_WriteConsole_buffer[i] = str[i]; + efiStdout->OutputString(efiStdout, __io_WriteConsole_buffer); + } else { + console_WriteASCII(&HelosGraphics_Color_White, str, 0); + } +} + +char __io_Printf_buffer[4096]; + +int io_Printf(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + int ret = vsnprintf(__io_Printf_buffer, sizeof(__io_Printf_buffer), fmt, args); + va_end(args); + + io_WriteConsole(__io_Printf_buffer); + + return ret; +} + +EFI_INPUT_KEY io_PauseForKeystroke() { + UINTN index; + EFI_INPUT_KEY key; + efiBootServices->WaitForEvent(1, &efiStdin->WaitForKey, &index); + efiSystemTable->ConIn->ReadKeyStroke(efiSystemTable->ConIn, &key); + + return key; +} diff --git a/runtime/stdio.h b/runtime/stdio.h new file mode 100644 index 0000000..3bbca1b --- /dev/null +++ b/runtime/stdio.h @@ -0,0 +1,40 @@ +#pragma once + +#include "../main.h" + +#include "unicode.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +// io_WriteConsole converts UTF-8 string given to Unicode(BMP only), +// then writing it to the Stdout SIMPLE_TEXT_OUTPUT_INTERFACE or the graphical console. +// +// All \n not preceeded by \r is substituted by \r\n. (WIP) +void io_WriteConsole(const char *str); + +void io_WriteConsoleASCII(const char *str); + +// io_Printf is a printf() replacement, printing to WriteConsole function. +int io_Printf(const char *format, ...); + +EFI_INPUT_KEY io_PauseForKeystroke(); + + +// Debugging printing marcos +#ifndef NDEBUG +#define DEBUG(...) \ + do { \ + printf(__FILE__ ":%d(%s) ", __LINE__, __func__); \ + printf(__VA_ARGS__); \ + } while (0) +#else +#define DEBUG(...) +#endif + + +#ifdef __cplusplus +} +#endif diff --git a/runtime/string.c b/runtime/string.c new file mode 100644 index 0000000..71d1619 --- /dev/null +++ b/runtime/string.c @@ -0,0 +1,10 @@ + +#include + + +size_t strlen(const char *s) { + size_t len = 0; + while (*(s++) != 0) + len++; + return len; +} diff --git a/runtime/test_memmove.c b/runtime/test_memmove.c new file mode 100644 index 0000000..c081f32 --- /dev/null +++ b/runtime/test_memmove.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include + +__attribute__((ms_abi)) void *memmove(void *, const void *, size_t); +__attribute__((ms_abi)) void *memset(void *, int, size_t); + +void output_memmove(const char *str) { + puts(str); +} + +int main(void) { + char str[] = "1234567890"; + puts(str); + memmove(str + 4, str + 3, 3); // copy from [4,5,6] to [5,6,7] + puts(str); + memmove(str + 3, str + 4, 3); // copy from [4,5,6] to [5,6,7] + puts(str); + memset(str + 3, '0' + 7, 3); + puts(str); + + for (int i = 0; i <= 10; i++) + printf("str[%d]=%d (%c)\n", i, str[i], str[i]); + + // setting effective type of allocated memory to be int + int *p = malloc(3 * sizeof(int)); // allocated memory has no effective type + int arr[3] = {1, 2, 3}; + memmove(p, arr, 3 * sizeof(int)); // allocated memory now has an effective type + printf("%d %d %d\n", p[0], p[1], p[2]); + + // reinterpreting data + double d = 0.1; + // int64_t n = *(int64_t*)(&d); // strict aliasing violation + int64_t n; + memmove(&n, &d, sizeof d); // OK + printf("%a is %" PRIx64 " as an int64_t\n", d, n); +} diff --git a/runtime/test_utf8.c b/runtime/test_utf8.c new file mode 100644 index 0000000..d303fc4 --- /dev/null +++ b/runtime/test_utf8.c @@ -0,0 +1,30 @@ +#include +#include +#include + +#include "Unicode.h" + + +int main(int argc, char *argv[]) { + char str[] = {"天王盖地虎,宝塔镇河妖"}; + int len = strlen(str); + + printf("String: %s, Length: %d\n", str, len); + + uint32_t codepoint, buffer[128]; + int count = 0; + for (int i = 0; + i < len; + i += utf8_Decode(str + i, len - i, buffer + count), count++) {} + + + printf("Count=%d\n", count); + for (int i = 0; i < count; i++) { + printf("Buffer[%d] = %d, ", i, buffer[i]); + char bytes[5] = {}; + utf8_Encode(bytes, buffer[i]); + printf("%s\n", bytes); + } + + return 0; +} diff --git a/runtime/unicode.c b/runtime/unicode.c new file mode 100644 index 0000000..e44ab2f --- /dev/null +++ b/runtime/unicode.c @@ -0,0 +1,84 @@ + +#include "unicode.h" + + +size_t utf8_Decode(const char *utf8, size_t len, uint32_t *codepoint) { + // Some useful precomputed data + static const int trailing[256] = + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5}; + static const uint32_t offsets[6] = + {0x00000000, 0x00003080, 0x000E2080, 0x03C82080, 0xFA082080, 0x82082080}; + + // decode the character + uint32_t output; + int trailingBytes; + + // read the first byte + uint8_t first = *utf8; + trailingBytes = trailing[first]; + if ((first >= HELOS_UTF8_CONTINUATION_MIN && first <= HELOS_UTF8_OVERLONG_LEADER_2) || + (first >= HELOS_UTF8_INVALID_LEADER_MIN && first <= HELOS_UTF8_INVALID_LEADER_MAX) || + trailingBytes + 1 > len) { + // corrupted data or incomplete character + trailingBytes = 0; + if (codepoint != 0) + (*codepoint) = HELOS_UNICODE_ERROR; + } else if (codepoint != 0) { + output = 0; + + // so elegant! + switch (trailingBytes) { + case 5: output += (uint8_t)(*utf8++); output <<= 6; + case 4: output += (uint8_t)(*utf8++); output <<= 6; + case 3: output += (uint8_t)(*utf8++); output <<= 6; + case 2: output += (uint8_t)(*utf8++); output <<= 6; + case 1: output += (uint8_t)(*utf8++); output <<= 6; + case 0: output += (uint8_t)(*utf8++); + } + + (*codepoint) = output - offsets[trailingBytes]; + } + + return trailingBytes + 1; +} + +size_t utf8_EncodeLength(uint32_t codepoint) { + if (codepoint <= 0x007f) // 0000 ~ 007F + return 1; + else if (codepoint <= 0x07ff) // 0080 ~ 07FF + return 2; + else if (codepoint <= 0xffff) // 0800 ~ FFFF + return 3; + else if (codepoint <= 0x10ffff) // 10000 ~ 10FFFF + return 4; + + return 0; // invalid +} + +size_t utf8_Encode(char *utf8, uint32_t codepoint) { + // Some useful precomputed data + static const uint8_t firstBytes[7] = {0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC}; + + if (codepoint > HELOS_UNICODE_MAX) + codepoint = HELOS_UNICODE_ERROR; // substitute invalid codepoint + + // get the number of bytes to write + size_t len = utf8_EncodeLength(codepoint); + // write the bytes + // so elegant also! + switch (len) { + case 4: utf8[3] = (char)((codepoint | 0x80) & 0xBF); codepoint >>= 6; + case 3: utf8[2] = (char)((codepoint | 0x80) & 0xBF); codepoint >>= 6; + case 2: utf8[1] = (char)((codepoint | 0x80) & 0xBF); codepoint >>= 6; + case 1: utf8[0] = (char)(codepoint | firstBytes[len]); + } + + return len; +} diff --git a/runtime/unicode.h b/runtime/unicode.h new file mode 100644 index 0000000..1c9aa9b --- /dev/null +++ b/runtime/unicode.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + + +// Constants + +#define HELOS_UNICODE_TYPE (uint32_t) // We use uint32_t to hold a Unicode codepoint. + +#define HELOS_UNICODE_ERROR ((uint32_t)0xfffd) // The "error" char or "Unicode replacement character". +#define HELOS_UNICODE_MAX ((uint32_t)0x10ffff) // Maximum valid Unicode codepoint. +#define HELOS_UNICODE_SELF_REPRESENT (0x80) // Chars below SELF_REPRESENT represents themselves in a single byte. + +#define HELOS_UTF8_MAX_BYTES 4 // Maximum number of bytes encoding one unicode point in UTF-8. +#define HELOS_UTF8_CONTINUATION_MIN 0xc0 // Minimum of the continuation range in which bytes are not the first byte of a sequence. +#define HELOS_UTF8_CONTINUATION_MAX 0xbf // Maximum of the continuation range. +#define HELOS_UTF8_OVERLONG_LEADER_1 0xc0 // First leader byte forming an overlong sequence (encoding <=0xff in 2 bytes). +#define HELOS_UTF8_OVERLONG_LEADER_2 0xc1 // Second leader byte forming an overlong sequence. +#define HELOS_UTF8_INVALID_LEADER_MIN 0xf5 // Minimum of the tailing range in which leader bytes form sequences more than 4 bytes long. +#define HELOS_UTF8_INVALID_LEADER_MAX 0xff // Maximum of the tailing range in which leader bytes are invalid. + + +#ifdef __cplusplus +extern "C" { +#endif + + +// utf8_Decode advances the UTF-8 sequence by one character, +// returning the number of bytes advanced. +// +// The codepoint pointer, if not NULL, is set to the decoded value. +// If the Unicode sequence is invalid, the replacement char is returned. +size_t utf8_Decode(const char *utf8, size_t length, uint32_t *codepoint); + +// utf8_EncodeLength returns the number of bytes required to encode +// the given codepoint in UTF-8 (ranging from 1 to 4). +// +// returns 0 if the codepoint is invalid. +size_t utf8_EncodeLength(uint32_t codepoint); + +// utf8_Encode encodes a new character into the UTF-8 buffer, +// if utf8 is not NULL, returning the number of bytes written (max 4). +// +// The buffer must have enough space. +size_t utf8_Encode(char *utf8, uint32_t codepoint); + + +#ifdef __cplusplus +} +#endif diff --git a/util/queue.c b/util/queue.c new file mode 100644 index 0000000..f1f835b --- /dev/null +++ b/util/queue.c @@ -0,0 +1,55 @@ + +#include "queue.h" +#include "../runtime/stdio.h" + + +void queue_InitBuffered(queue *q, void *buffer, uintptr_t size) { + q->data = q->begin = q->end = buffer; + q->size = size; + q->count = 0; +} + +void queue_PushByte(queue *q, const uint8_t b) { + if (q->count == q->size) { // no more space + io_Printf("queue_PushByte: full[%llu bytes], discarding byte 0x%x\n", q->size, b); + return; + } + + q->count++; + *((uint8_t *)(q->end++)) = b; + if (q->end == q->data + q->size) + q->end = q->data; // out of the buffer: wrap around +} + +uint8_t queue_PopByte(queue *q) { + if (q->count == 0) { + io_WriteConsoleASCII("queue_PopByte: poping an empty queue\n"); + return 0; + } + + q->count--; + uint8_t data = *((uint8_t *)(q->begin++)); + if (q->begin == q->data + q->size) + q->begin = q->data; // wrap around + return data; +} + +uint8_t queue_TopByte(queue *q) { + if (q->count == 0) { + io_WriteConsoleASCII("queue_TopByte: accessing an empty queue\n"); + return 0; + } + return *((uint8_t *)q->begin); +} + +bool queue_Empty(queue *q) { + return q->count == 0; +} + +uintptr_t queue_Size(queue *q) { + return q->count; +} + +uintptr_t queue_Space(queue *q) { + return q->size - q->count; +} diff --git a/util/queue.h b/util/queue.h new file mode 100644 index 0000000..6c25b0e --- /dev/null +++ b/util/queue.h @@ -0,0 +1,42 @@ +#pragma once + +#include "../main.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct { + void * data; // the data buffer + uintptr_t size; // size of data buffer + void * begin, *end; // begin and past-the-end for in-queue data + uintptr_t count; // number of in-queue bytes +} queue; + +// initialize a queue with a existing buffer +void queue_InitBuffered(queue *q, void *buffer, uintptr_t size); + +// writes one byte to the queue, discarding if full +void queue_PushByte(queue *q, const uint8_t b); + +// pops one byte from the front of the queue, returning it +uint8_t queue_PopByte(queue *q); + +// return the byte at the front of the queue +uint8_t queue_FrontByte(queue *q); + +// tells if the queue is empty +bool queue_Empty(queue *q); + +// returns the number of bytes in the queue +uintptr_t queue_Size(queue *q); + +// returns the empty space left at the end of the queue +uintptr_t queue_Space(queue *q); + + +#ifdef __cplusplus +} +#endif diff --git a/vterm/vterm.c b/vterm/vterm.c new file mode 100644 index 0000000..ed13bd5 --- /dev/null +++ b/vterm/vterm.c @@ -0,0 +1,2 @@ + +#include "../extlib/libvterm/vterm.h" diff --git a/vterm/vterm.cpp b/vterm/vterm.cpp new file mode 100644 index 0000000..3212515 --- /dev/null +++ b/vterm/vterm.cpp @@ -0,0 +1,13 @@ + +#include "vterm.hpp" + +static void *defaultMalloc(size_t size, void *userdata) { + void *ptr = kMalloc(size); + return ptr; +} + +static void defaultFree(void *ptr, void *userdata) { + kFree(ptr); +} + +VTermAllocatorFunctions vterm_Allocator = {defaultMalloc, defaultFree}; diff --git a/vterm/vterm.hpp b/vterm/vterm.hpp new file mode 100644 index 0000000..e8d6f02 --- /dev/null +++ b/vterm/vterm.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "../main.h" + +#include "../memory/memory.h" +#include "../memory/memory.hpp" +#include "../extlib/libvterm/vterm.h" +#include + + +namespace helos { + + +class Terminal { +public: + Terminal() { + } +}; + + +extern VTermAllocatorFunctions vterm_Allocator; + + +} // namespace helos + +extern "C" { +FASTCALL_ABI void vterm_Init(); +}